diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index aa63853..0cf572e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -27,6 +27,9 @@ to [Discussions](https://github.com/secondwindoss/sqlx/discussions). tests` when it cannot connect; CI fails on either line, so make sure both databases are reachable before you rely on a local run. 3. Run `make tooling` once, then `make lint fmt vuln-check` before pushing. + If the change alters what `Named`, `In` or `Rebind` emit, run + `go test -run TestGolden -update`, review the diff of + `testdata/golden.txt`, and mention the changed lines in the pull request. 4. Open a pull request against `master`. CI runs the lint job and the race tests on Go 1.23 to stable with all three databases and must be green before the merge. diff --git a/golden_corpus_test.go b/golden_corpus_test.go new file mode 100644 index 0000000..84579b7 --- /dev/null +++ b/golden_corpus_test.go @@ -0,0 +1,207 @@ +package sqlx_test + +// The golden corpus: realistic queries run through BindNamed, In and Rebind. +// goldenRender writes one line per case and bind type; testdata/golden.txt +// holds the reviewed output and TestGolden compares against it. The same +// file, with the import path changed, renders the corpus against any other +// sqlx version for a diff. + +import ( + "database/sql/driver" + "fmt" + "sort" + "strings" + + "github.com/secondwindoss/sqlx" +) + +type goldenPerson struct { + FirstName string `db:"first_name"` + LastName string `db:"last_name"` + Email string `db:"email"` +} + +type goldenPlace struct { + Country string `db:"country"` + City string `db:"city"` +} + +type goldenBooking struct { + Person goldenPerson `db:"person"` + Place goldenPlace `db:"place"` + Nights int `db:"nights"` +} + +type goldenValuer struct{ v string } + +func (g goldenValuer) Value() (driver.Value, error) { return g.v, nil } + +type goldenSliceValuer struct{} + +func (goldenSliceValuer) Value() (driver.Value, error) { return []int{7, 8}, nil } + +var goldenBindTypes = []struct { + name string + bt int +}{ + {"QUESTION", sqlx.QUESTION}, + {"DOLLAR", sqlx.DOLLAR}, + {"NAMED", sqlx.NAMED}, + {"AT", sqlx.AT}, +} + +type goldenNamed struct { + name string + query string + arg interface{} +} + +type goldenIn struct { + name string + query string + args []interface{} +} + +type goldenRebind struct { + name string + query string +} + +var person = goldenPerson{FirstName: "Jason", LastName: "Moiron", Email: "jmoiron@jmoiron.net"} +var people = []goldenPerson{person, {FirstName: "John", LastName: "Doe", Email: "johndoeDNE@gmail.net"}} +var personMap = map[string]interface{}{"first_name": "Jane", "last_name": "Citizen", "email": "jane.citzen@example.com"} +var personMaps = []map[string]interface{}{personMap, {"first_name": "Bin", "last_name": "Smuth", "email": "bensmith@allblacks.nz"}} +var idMap = map[string]interface{}{"id": 42} +var abMap = map[string]interface{}{"a": 1, "b": 2} +var abMaps = []map[string]interface{}{abMap, {"a": 3, "b": 4}} + +var goldenNamedCases = []goldenNamed{ + {"insert struct", `INSERT INTO person (first_name, last_name, email) VALUES (:first_name, :last_name, :email)`, person}, + {"select map", `SELECT * FROM person WHERE first_name=:first_name AND last_name=:last_name`, personMap}, + {"select struct trailing param", `SELECT * FROM person WHERE email=:email`, person}, + {"params without spaces", `INSERT INTO t (a,b) VALUES(:a,:b)`, abMap}, + {"batch insert structs", `INSERT INTO person (first_name, last_name, email) VALUES (:first_name, :last_name, :email)`, people}, + {"batch insert maps", `INSERT INTO person (first_name, last_name, email) VALUES (:first_name, :last_name, :email)`, personMaps}, + {"batch insert without column list", `insert into t values (:a, :b)`, abMaps}, + {"batch insert multiline", "INSERT INTO t (a, b)\nVALUES\n (:a, :b)", abMaps}, + {"batch values in from", `UPDATE t SET b = u.b FROM (VALUES (:a, :b)) AS u(a, b) WHERE t.a = u.a`, abMaps}, + {"batch on duplicate key", `INSERT INTO t (a, b) VALUES (:a, :b) ON DUPLICATE KEY UPDATE b = VALUES(b)`, abMaps}, + {"batch table named values", `INSERT INTO "values" (a, b) VALUES (:a, :b)`, abMaps}, + {"nested struct dotted names", `INSERT INTO booking (first, country, city, nights) VALUES (:person.first_name, :place.country, :place.city, :nights)`, goldenBooking{Person: person, Place: goldenPlace{Country: "NZ", City: "Wellington"}, Nights: 3}}, + {"cast attached to param", `SELECT * FROM t WHERE id = :id::uuid`, idMap}, + {"cast attached to param text", `SELECT :id::text AS id`, idMap}, + {"cast elsewhere", `SELECT (now() AT TIME ZONE 'utc')::date FROM t WHERE id = :id`, idMap}, + {"double cast", `SELECT :id::text::int FROM t`, idMap}, + {"old four-colon escape", `SELECT :id::::uuid`, idMap}, + {"colon in string literal time", `SELECT * FROM t WHERE at > '12:30' AND id = :id`, idMap}, + {"colon in string literal words", `SELECT 'note: see :id' AS note, id FROM t WHERE id = :id`, idMap}, + {"colon in line comment", "SELECT * FROM t -- filter: by id\nWHERE id = :id", idMap}, + {"colon in block comment", `SELECT /* owner: ops */ * FROM t WHERE id = :id`, idMap}, + {"nested block comment", `SELECT /* a /* b: */ c */ * FROM t WHERE id = :id`, idMap}, + {"mysql assignment", `SELECT @x := :id, @x + 1`, idMap}, + {"array slice", `SELECT arr[1:2] FROM t WHERE id = :id`, idMap}, + {"unicode name", `INSERT INTO t (name) VALUES (:名前)`, map[string]interface{}{"名前": "x"}}, + {"dollar quoted string", `SELECT $$it's :not a param$$ AS s, id FROM t WHERE id = :id`, idMap}, + {"dollar quoted with tag", `SELECT $q$:a::b$q$ AS s FROM t WHERE id = :id`, idMap}, + {"double quoted identifier", `SELECT "col:on" FROM t WHERE id = :id`, idMap}, + {"backtick identifier", "SELECT `col:on` FROM t WHERE id = :id", idMap}, + {"postgres escape string", `SELECT E'it\'s' AS s FROM t WHERE id = :id`, idMap}, + {"doubled quote in literal", `SELECT 'it''s :fine' AS s FROM t WHERE id = :id`, idMap}, + {"json path with colon", `SELECT data->>'k:v' FROM t WHERE id = :id`, idMap}, + {"question mark literal", `SELECT '?' AS q FROM t WHERE id = :id`, idMap}, + {"jsonb operator", `SELECT * FROM t WHERE data ? 'k' AND id = :id`, idMap}, + {"two params adjacent", `SELECT :a:b`, abMap}, + {"name starting with digit", `SELECT :1 AS one, :a`, abMap}, + {"param then colon then space", `SELECT :a, b: c`, abMap}, + {"missing argument", `SELECT :a, :missing`, abMap}, + {"no params at all", `SELECT 1`, abMap}, + {"empty map", `SELECT 1`, map[string]interface{}{}}, +} + +var goldenInCases = []goldenIn{ + {"single slice", `SELECT * FROM t WHERE id IN (?)`, []interface{}{[]int{1, 2, 3}}}, + {"one element", `SELECT * FROM t WHERE id IN (?)`, []interface{}{[]int{1}}}, + {"two slices", `SELECT * FROM t WHERE a IN (?) AND b IN (?)`, []interface{}{[]int{1, 2}, []string{"x", "y"}}}, + {"scalar and slice", `SELECT * FROM t WHERE x = ? AND id IN (?) AND y = ?`, []interface{}{"a", []int{1, 2}, "b"}}, + {"interface slice", `SELECT * FROM t WHERE id IN (?)`, []interface{}{[]interface{}{1, "two", 3.0}}}, + {"bytes are a value", `SELECT * FROM t WHERE blob = ?`, []interface{}{[]byte("x")}}, + {"valuer scalar", `SELECT * FROM t WHERE x = ? AND id IN (?)`, []interface{}{goldenValuer{"v"}, []int{1}}}, + {"valuer slice", `SELECT * FROM t WHERE id IN (?)`, []interface{}{goldenSliceValuer{}}}, + {"empty slice", `SELECT * FROM t WHERE id IN (?)`, []interface{}{[]int{}}}, + {"nil slice", `SELECT * FROM t WHERE id IN (?)`, []interface{}{[]int(nil)}}, + {"empty slice not in", `SELECT * FROM t WHERE id NOT IN (?)`, []interface{}{[]int{}}}, + {"empty and full", `SELECT * FROM t WHERE a IN (?) AND b IN (?)`, []interface{}{[]int{}, []int{1, 2}}}, + {"question in line comment", "SELECT * -- which ids?\nFROM t WHERE id IN (?)", []interface{}{[]int{1, 2}}}, + {"question in block comment", `SELECT /* why? */ * FROM t WHERE id IN (?)`, []interface{}{[]int{1, 2}}}, + {"question in literal", `SELECT '?' AS q FROM t WHERE id IN (?)`, []interface{}{[]int{1, 2}}}, + {"question in identifier", `SELECT "what?" FROM t WHERE id IN (?)`, []interface{}{[]int{1, 2}}}, + {"double question escape", `SELECT * FROM t WHERE data ?? 'k' AND id IN (?)`, []interface{}{[]int{1, 2}}}, + {"mysql escaped quote", `SELECT * FROM t WHERE note = 'don\'t' AND id IN (?)`, []interface{}{[]int{1, 2}}}, + {"dollar quoted question", `SELECT $$?$$ AS s FROM t WHERE id IN (?)`, []interface{}{[]int{1, 2}}}, + {"too few args", `SELECT * FROM t WHERE a IN (?) AND b = ?`, []interface{}{[]int{1}}}, + {"too many args", `SELECT * FROM t WHERE a IN (?)`, []interface{}{[]int{1}, 2}}, + {"no slices passthrough", `SELECT * FROM t WHERE a = ? AND b = ?`, []interface{}{1, 2}}, + {"colon param left alone", `SELECT * FROM t WHERE a = :a AND id IN (?)`, []interface{}{[]int{1, 2}}}, +} + +var goldenRebindCases = []goldenRebind{ + {"three placeholders", `INSERT INTO t (a, b, c) VALUES (?, ?, ?)`}, + {"no placeholders", `SELECT 1`}, + {"question in literal", `SELECT '?' AS q FROM t WHERE a = ?`}, + {"question in line comment", "SELECT * -- a?\nFROM t WHERE a = ?"}, + {"question in block comment", `SELECT /* a? */ * FROM t WHERE a = ?`}, + {"double question escape", `SELECT * FROM t WHERE data ?? 'k' AND a = ?`}, + {"jsonb operators escaped", `SELECT * FROM t WHERE data ??| array['a'] AND data ??& array['b'] AND a = ?`}, + {"mysql escaped quote", `SELECT * FROM t WHERE note = 'don\'t' AND a = ?`}, + {"like escape clause", `SELECT * FROM t WHERE a LIKE ? ESCAPE '\' AND b = ?`}, + {"bracket identifier", `SELECT [what?] FROM t WHERE a = ?`}, + {"dollar quoted", `SELECT $$?$$ FROM t WHERE a = ?`}, + {"backtick identifier", "SELECT `a?` FROM t WHERE a = ?"}, + {"postgres escape string", `SELECT E'\'?' FROM t WHERE a = ?`}, + {"already dollar", `SELECT * FROM t WHERE a = $1 AND b = ?`}, +} + +func goldenRender() string { + var b strings.Builder + line := func(format string, args ...interface{}) { + fmt.Fprintf(&b, format, args...) + b.WriteByte('\n') + } + for _, c := range goldenNamedCases { + for _, bt := range goldenBindTypes { + q, args, err := sqlx.BindNamed(bt.bt, c.query, c.arg) + line("named/%s/%s: %q args=%s err=%v", c.name, bt.name, q, goldenArgs(args), err) + } + } + for _, c := range goldenInCases { + q, args, err := sqlx.In(c.query, c.args...) + line("in/%s: %q args=%s err=%v", c.name, q, goldenArgs(args), err) + } + for _, c := range goldenRebindCases { + for _, bt := range goldenBindTypes { + line("rebind/%s/%s: %q", c.name, bt.name, sqlx.Rebind(bt.bt, c.query)) + } + } + return b.String() +} + +func goldenArgs(args []interface{}) string { + if args == nil { + return "nil" + } + parts := make([]string, len(args)) + for i, a := range args { + switch v := a.(type) { + case map[string]interface{}: + keys := make([]string, 0, len(v)) + for k := range v { + keys = append(keys, k) + } + sort.Strings(keys) + parts[i] = fmt.Sprintf("map%v", keys) + default: + parts[i] = fmt.Sprintf("%#v", a) + } + } + return "[" + strings.Join(parts, " ") + "]" +} diff --git a/golden_test.go b/golden_test.go new file mode 100644 index 0000000..906c818 --- /dev/null +++ b/golden_test.go @@ -0,0 +1,39 @@ +package sqlx_test + +import ( + "flag" + "os" + "os/exec" + "testing" +) + +var updateGolden = flag.Bool("update", false, "rewrite testdata/golden.txt from the current output") + +// TestGolden pins the exact output of BindNamed, In and Rebind for the corpus +// in golden_corpus_test.go. Run `go test -run TestGolden -update` after an +// intended change and review the diff of testdata/golden.txt. +// testdata/golden-v1.4.0.txt is the same corpus rendered by upstream v1.4.0; +// diffing the two files lists every output the fork changed on purpose. +func TestGolden(t *testing.T) { + const path = "testdata/golden.txt" + got := goldenRender() + if *updateGolden { + if err := os.WriteFile(path, []byte(got), 0o644); err != nil { + t.Fatal(err) + } + return + } + want, err := os.ReadFile(path) + if err != nil { + t.Fatal(err) + } + if string(want) == got { + return + } + tmp := t.TempDir() + "/golden.txt" + if err := os.WriteFile(tmp, []byte(got), 0o644); err != nil { + t.Fatal(err) + } + diff, _ := exec.Command("diff", "-u", path, tmp).CombinedOutput() + t.Errorf("golden output changed; run `go test -run TestGolden -update` if intended:\n%s", diff) +} diff --git a/testdata/golden-v1.4.0.txt b/testdata/golden-v1.4.0.txt new file mode 100644 index 0000000..cf3d180 --- /dev/null +++ b/testdata/golden-v1.4.0.txt @@ -0,0 +1,239 @@ +named/insert struct/QUESTION: "INSERT INTO person (first_name, last_name, email) VALUES (?, ?, ?)" args=["Jason" "Moiron" "jmoiron@jmoiron.net"] err= +named/insert struct/DOLLAR: "INSERT INTO person (first_name, last_name, email) VALUES ($1, $2, $3)" args=["Jason" "Moiron" "jmoiron@jmoiron.net"] err= +named/insert struct/NAMED: "INSERT INTO person (first_name, last_name, email) VALUES (:first_name, :last_name, :email)" args=["Jason" "Moiron" "jmoiron@jmoiron.net"] err= +named/insert struct/AT: "INSERT INTO person (first_name, last_name, email) VALUES (@p1, @p2, @p3)" args=["Jason" "Moiron" "jmoiron@jmoiron.net"] err= +named/select map/QUESTION: "SELECT * FROM person WHERE first_name=? AND last_name=?" args=["Jane" "Citizen"] err= +named/select map/DOLLAR: "SELECT * FROM person WHERE first_name=$1 AND last_name=$2" args=["Jane" "Citizen"] err= +named/select map/NAMED: "SELECT * FROM person WHERE first_name=:first_name AND last_name=:last_name" args=["Jane" "Citizen"] err= +named/select map/AT: "SELECT * FROM person WHERE first_name=@p1 AND last_name=@p2" args=["Jane" "Citizen"] err= +named/select struct trailing param/QUESTION: "SELECT * FROM person WHERE email=?" args=["jmoiron@jmoiron.net"] err= +named/select struct trailing param/DOLLAR: "SELECT * FROM person WHERE email=$1" args=["jmoiron@jmoiron.net"] err= +named/select struct trailing param/NAMED: "SELECT * FROM person WHERE email=:email" args=["jmoiron@jmoiron.net"] err= +named/select struct trailing param/AT: "SELECT * FROM person WHERE email=@p1" args=["jmoiron@jmoiron.net"] err= +named/params without spaces/QUESTION: "INSERT INTO t (a,b) VALUES(?,?)" args=[1 2] err= +named/params without spaces/DOLLAR: "INSERT INTO t (a,b) VALUES($1,$2)" args=[1 2] err= +named/params without spaces/NAMED: "INSERT INTO t (a,b) VALUES(:a,:b)" args=[1 2] err= +named/params without spaces/AT: "INSERT INTO t (a,b) VALUES(@p1,@p2)" args=[1 2] err= +named/batch insert structs/QUESTION: "INSERT INTO person (first_name, last_name, email) VALUES (?, ?, ?),(?, ?, ?)" args=["Jason" "Moiron" "jmoiron@jmoiron.net" "John" "Doe" "johndoeDNE@gmail.net"] err= +named/batch insert structs/DOLLAR: "INSERT INTO person (first_name, last_name, email) VALUES ($1, $2, $3),($4, $5, $6)" args=["Jason" "Moiron" "jmoiron@jmoiron.net" "John" "Doe" "johndoeDNE@gmail.net"] err= +named/batch insert structs/NAMED: "INSERT INTO person (first_name, last_name, email) VALUES (:arg1, :arg2, :arg3),(:arg4, :arg5, :arg6)" args=["Jason" "Moiron" "jmoiron@jmoiron.net" "John" "Doe" "johndoeDNE@gmail.net"] err= +named/batch insert structs/AT: "INSERT INTO person (first_name, last_name, email) VALUES (@p1, @p2, @p3),(@p4, @p5, @p6)" args=["Jason" "Moiron" "jmoiron@jmoiron.net" "John" "Doe" "johndoeDNE@gmail.net"] err= +named/batch insert maps/QUESTION: "INSERT INTO person (first_name, last_name, email) VALUES (?, ?, ?),(?, ?, ?)" args=["Jane" "Citizen" "jane.citzen@example.com" "Bin" "Smuth" "bensmith@allblacks.nz"] err= +named/batch insert maps/DOLLAR: "INSERT INTO person (first_name, last_name, email) VALUES ($1, $2, $3),($4, $5, $6)" args=["Jane" "Citizen" "jane.citzen@example.com" "Bin" "Smuth" "bensmith@allblacks.nz"] err= +named/batch insert maps/NAMED: "INSERT INTO person (first_name, last_name, email) VALUES (:arg1, :arg2, :arg3),(:arg4, :arg5, :arg6)" args=["Jane" "Citizen" "jane.citzen@example.com" "Bin" "Smuth" "bensmith@allblacks.nz"] err= +named/batch insert maps/AT: "INSERT INTO person (first_name, last_name, email) VALUES (@p1, @p2, @p3),(@p4, @p5, @p6)" args=["Jane" "Citizen" "jane.citzen@example.com" "Bin" "Smuth" "bensmith@allblacks.nz"] err= +named/batch insert without column list/QUESTION: "insert into t values (?, ?)" args=[1 2 3 4] err= +named/batch insert without column list/DOLLAR: "insert into t values ($1, $2)" args=[1 2 3 4] err= +named/batch insert without column list/NAMED: "insert into t values (:arg1, :arg2)" args=[1 2 3 4] err= +named/batch insert without column list/AT: "insert into t values (@p1, @p2)" args=[1 2 3 4] err= +named/batch insert multiline/QUESTION: "INSERT INTO t (a, b)\nVALUES\n (?, ?),(?, ?)" args=[1 2 3 4] err= +named/batch insert multiline/DOLLAR: "INSERT INTO t (a, b)\nVALUES\n ($1, $2),($3, $4)" args=[1 2 3 4] err= +named/batch insert multiline/NAMED: "INSERT INTO t (a, b)\nVALUES\n (:arg1, :arg2),(:arg3, :arg4)" args=[1 2 3 4] err= +named/batch insert multiline/AT: "INSERT INTO t (a, b)\nVALUES\n (@p1, @p2),(@p3, @p4)" args=[1 2 3 4] err= +named/batch values in from/QUESTION: "UPDATE t SET b = u.b FROM (VALUES (?, ?)) AS u(a, b) WHERE t.a = u.a" args=[1 2 3 4] err= +named/batch values in from/DOLLAR: "UPDATE t SET b = u.b FROM (VALUES ($1, $2)) AS u(a, b) WHERE t.a = u.a" args=[1 2 3 4] err= +named/batch values in from/NAMED: "UPDATE t SET b = u.b FROM (VALUES (:arg1, :arg2)) AS u(a, b) WHERE t.a = u.a" args=[1 2 3 4] err= +named/batch values in from/AT: "UPDATE t SET b = u.b FROM (VALUES (@p1, @p2)) AS u(a, b) WHERE t.a = u.a" args=[1 2 3 4] err= +named/batch on duplicate key/QUESTION: "INSERT INTO t (a, b) VALUES (?, ?),(?, ?) ON DUPLICATE KEY UPDATE b = VALUES(b)" args=[1 2 3 4] err= +named/batch on duplicate key/DOLLAR: "INSERT INTO t (a, b) VALUES ($1, $2),($3, $4) ON DUPLICATE KEY UPDATE b = VALUES(b)" args=[1 2 3 4] err= +named/batch on duplicate key/NAMED: "INSERT INTO t (a, b) VALUES (:arg1, :arg2),(:arg3, :arg4) ON DUPLICATE KEY UPDATE b = VALUES(b)" args=[1 2 3 4] err= +named/batch on duplicate key/AT: "INSERT INTO t (a, b) VALUES (@p1, @p2),(@p3, @p4) ON DUPLICATE KEY UPDATE b = VALUES(b)" args=[1 2 3 4] err= +named/batch table named values/QUESTION: "INSERT INTO \"values\" (a, b) VALUES (?, ?),(?, ?)" args=[1 2 3 4] err= +named/batch table named values/DOLLAR: "INSERT INTO \"values\" (a, b) VALUES ($1, $2),($3, $4)" args=[1 2 3 4] err= +named/batch table named values/NAMED: "INSERT INTO \"values\" (a, b) VALUES (:arg1, :arg2),(:arg3, :arg4)" args=[1 2 3 4] err= +named/batch table named values/AT: "INSERT INTO \"values\" (a, b) VALUES (@p1, @p2),(@p3, @p4)" args=[1 2 3 4] err= +named/nested struct dotted names/QUESTION: "INSERT INTO booking (first, country, city, nights) VALUES (?, ?, ?, ?)" args=["Jason" "NZ" "Wellington" 3] err= +named/nested struct dotted names/DOLLAR: "INSERT INTO booking (first, country, city, nights) VALUES ($1, $2, $3, $4)" args=["Jason" "NZ" "Wellington" 3] err= +named/nested struct dotted names/NAMED: "INSERT INTO booking (first, country, city, nights) VALUES (:person.first_name, :place.country, :place.city, :nights)" args=["Jason" "NZ" "Wellington" 3] err= +named/nested struct dotted names/AT: "INSERT INTO booking (first, country, city, nights) VALUES (@p1, @p2, @p3, @p4)" args=["Jason" "NZ" "Wellington" 3] err= +named/cast attached to param/QUESTION: "" args=[] err=unexpected `:` while reading named param at 30 +named/cast attached to param/DOLLAR: "" args=[] err=unexpected `:` while reading named param at 30 +named/cast attached to param/NAMED: "" args=[] err=unexpected `:` while reading named param at 30 +named/cast attached to param/AT: "" args=[] err=unexpected `:` while reading named param at 30 +named/cast attached to param text/QUESTION: "" args=[] err=unexpected `:` while reading named param at 10 +named/cast attached to param text/DOLLAR: "" args=[] err=unexpected `:` while reading named param at 10 +named/cast attached to param text/NAMED: "" args=[] err=unexpected `:` while reading named param at 10 +named/cast attached to param text/AT: "" args=[] err=unexpected `:` while reading named param at 10 +named/cast elsewhere/QUESTION: "SELECT (now() AT TIME ZONE 'utc'):date FROM t WHERE id = ?" args=[42] err= +named/cast elsewhere/DOLLAR: "SELECT (now() AT TIME ZONE 'utc'):date FROM t WHERE id = $1" args=[42] err= +named/cast elsewhere/NAMED: "SELECT (now() AT TIME ZONE 'utc'):date FROM t WHERE id = :id" args=[42] err= +named/cast elsewhere/AT: "SELECT (now() AT TIME ZONE 'utc'):date FROM t WHERE id = @p1" args=[42] err= +named/double cast/QUESTION: "" args=[] err=unexpected `:` while reading named param at 10 +named/double cast/DOLLAR: "" args=[] err=unexpected `:` while reading named param at 10 +named/double cast/NAMED: "" args=[] err=unexpected `:` while reading named param at 10 +named/double cast/AT: "" args=[] err=unexpected `:` while reading named param at 10 +named/old four-colon escape/QUESTION: "" args=[] err=unexpected `:` while reading named param at 10 +named/old four-colon escape/DOLLAR: "" args=[] err=unexpected `:` while reading named param at 10 +named/old four-colon escape/NAMED: "" args=[] err=unexpected `:` while reading named param at 10 +named/old four-colon escape/AT: "" args=[] err=unexpected `:` while reading named param at 10 +named/colon in string literal time/QUESTION: "SELECT * FROM t WHERE at > '12?' AND id = ?" args=[] err=could not find name 30 in map[string]interface {}{"id":42} +named/colon in string literal time/DOLLAR: "SELECT * FROM t WHERE at > '12$1' AND id = $2" args=[] err=could not find name 30 in map[string]interface {}{"id":42} +named/colon in string literal time/NAMED: "SELECT * FROM t WHERE at > '12:30' AND id = :id" args=[] err=could not find name 30 in map[string]interface {}{"id":42} +named/colon in string literal time/AT: "SELECT * FROM t WHERE at > '12@p1' AND id = @p2" args=[] err=could not find name 30 in map[string]interface {}{"id":42} +named/colon in string literal words/QUESTION: "SELECT 'note? see ?' AS note, id FROM t WHERE id = ?" args=[] err=could not find name in map[string]interface {}{"id":42} +named/colon in string literal words/DOLLAR: "SELECT 'note$1 see $2' AS note, id FROM t WHERE id = $3" args=[] err=could not find name in map[string]interface {}{"id":42} +named/colon in string literal words/NAMED: "SELECT 'note: see :id' AS note, id FROM t WHERE id = :id" args=[] err=could not find name in map[string]interface {}{"id":42} +named/colon in string literal words/AT: "SELECT 'note@p1 see @p2' AS note, id FROM t WHERE id = @p3" args=[] err=could not find name in map[string]interface {}{"id":42} +named/colon in line comment/QUESTION: "SELECT * FROM t -- filter? by id\nWHERE id = ?" args=[] err=could not find name in map[string]interface {}{"id":42} +named/colon in line comment/DOLLAR: "SELECT * FROM t -- filter$1 by id\nWHERE id = $2" args=[] err=could not find name in map[string]interface {}{"id":42} +named/colon in line comment/NAMED: "SELECT * FROM t -- filter: by id\nWHERE id = :id" args=[] err=could not find name in map[string]interface {}{"id":42} +named/colon in line comment/AT: "SELECT * FROM t -- filter@p1 by id\nWHERE id = @p2" args=[] err=could not find name in map[string]interface {}{"id":42} +named/colon in block comment/QUESTION: "SELECT /* owner? ops */ * FROM t WHERE id = ?" args=[] err=could not find name in map[string]interface {}{"id":42} +named/colon in block comment/DOLLAR: "SELECT /* owner$1 ops */ * FROM t WHERE id = $2" args=[] err=could not find name in map[string]interface {}{"id":42} +named/colon in block comment/NAMED: "SELECT /* owner: ops */ * FROM t WHERE id = :id" args=[] err=could not find name in map[string]interface {}{"id":42} +named/colon in block comment/AT: "SELECT /* owner@p1 ops */ * FROM t WHERE id = @p2" args=[] err=could not find name in map[string]interface {}{"id":42} +named/nested block comment/QUESTION: "SELECT /* a /* b? */ c */ * FROM t WHERE id = ?" args=[] err=could not find name in map[string]interface {}{"id":42} +named/nested block comment/DOLLAR: "SELECT /* a /* b$1 */ c */ * FROM t WHERE id = $2" args=[] err=could not find name in map[string]interface {}{"id":42} +named/nested block comment/NAMED: "SELECT /* a /* b: */ c */ * FROM t WHERE id = :id" args=[] err=could not find name in map[string]interface {}{"id":42} +named/nested block comment/AT: "SELECT /* a /* b@p1 */ c */ * FROM t WHERE id = @p2" args=[] err=could not find name in map[string]interface {}{"id":42} +named/mysql assignment/QUESTION: "SELECT @x := ?, @x + 1" args=[42] err= +named/mysql assignment/DOLLAR: "SELECT @x := $1, @x + 1" args=[42] err= +named/mysql assignment/NAMED: "SELECT @x := :id, @x + 1" args=[42] err= +named/mysql assignment/AT: "SELECT @x := @p1, @x + 1" args=[42] err= +named/array slice/QUESTION: "SELECT arr[1?] FROM t WHERE id = ?" args=[] err=could not find name 2 in map[string]interface {}{"id":42} +named/array slice/DOLLAR: "SELECT arr[1$1] FROM t WHERE id = $2" args=[] err=could not find name 2 in map[string]interface {}{"id":42} +named/array slice/NAMED: "SELECT arr[1:2] FROM t WHERE id = :id" args=[] err=could not find name 2 in map[string]interface {}{"id":42} +named/array slice/AT: "SELECT arr[1@p1] FROM t WHERE id = @p2" args=[] err=could not find name 2 in map[string]interface {}{"id":42} +named/unicode name/QUESTION: "INSERT INTO t (name) VALUES (?\x90\x8d前)" args=[] err=could not find name å in map[string]interface {}{"名前":"x"} +named/unicode name/DOLLAR: "INSERT INTO t (name) VALUES ($1\x90\x8d前)" args=[] err=could not find name å in map[string]interface {}{"名前":"x"} +named/unicode name/NAMED: "INSERT INTO t (name) VALUES (:名前)" args=[] err=could not find name å in map[string]interface {}{"名前":"x"} +named/unicode name/AT: "INSERT INTO t (name) VALUES (@p1\x90\x8d前)" args=[] err=could not find name å in map[string]interface {}{"名前":"x"} +named/dollar quoted string/QUESTION: "SELECT $$it's ? a param$$ AS s, id FROM t WHERE id = ?" args=[] err=could not find name not in map[string]interface {}{"id":42} +named/dollar quoted string/DOLLAR: "SELECT $$it's $1 a param$$ AS s, id FROM t WHERE id = $2" args=[] err=could not find name not in map[string]interface {}{"id":42} +named/dollar quoted string/NAMED: "SELECT $$it's :not a param$$ AS s, id FROM t WHERE id = :id" args=[] err=could not find name not in map[string]interface {}{"id":42} +named/dollar quoted string/AT: "SELECT $$it's @p1 a param$$ AS s, id FROM t WHERE id = @p2" args=[] err=could not find name not in map[string]interface {}{"id":42} +named/dollar quoted with tag/QUESTION: "" args=[] err=unexpected `:` while reading named param at 12 +named/dollar quoted with tag/DOLLAR: "" args=[] err=unexpected `:` while reading named param at 12 +named/dollar quoted with tag/NAMED: "" args=[] err=unexpected `:` while reading named param at 12 +named/dollar quoted with tag/AT: "" args=[] err=unexpected `:` while reading named param at 12 +named/double quoted identifier/QUESTION: "SELECT \"col?\" FROM t WHERE id = ?" args=[] err=could not find name on in map[string]interface {}{"id":42} +named/double quoted identifier/DOLLAR: "SELECT \"col$1\" FROM t WHERE id = $2" args=[] err=could not find name on in map[string]interface {}{"id":42} +named/double quoted identifier/NAMED: "SELECT \"col:on\" FROM t WHERE id = :id" args=[] err=could not find name on in map[string]interface {}{"id":42} +named/double quoted identifier/AT: "SELECT \"col@p1\" FROM t WHERE id = @p2" args=[] err=could not find name on in map[string]interface {}{"id":42} +named/backtick identifier/QUESTION: "SELECT `col?` FROM t WHERE id = ?" args=[] err=could not find name on in map[string]interface {}{"id":42} +named/backtick identifier/DOLLAR: "SELECT `col$1` FROM t WHERE id = $2" args=[] err=could not find name on in map[string]interface {}{"id":42} +named/backtick identifier/NAMED: "SELECT `col:on` FROM t WHERE id = :id" args=[] err=could not find name on in map[string]interface {}{"id":42} +named/backtick identifier/AT: "SELECT `col@p1` FROM t WHERE id = @p2" args=[] err=could not find name on in map[string]interface {}{"id":42} +named/postgres escape string/QUESTION: "SELECT E'it\\'s' AS s FROM t WHERE id = ?" args=[42] err= +named/postgres escape string/DOLLAR: "SELECT E'it\\'s' AS s FROM t WHERE id = $1" args=[42] err= +named/postgres escape string/NAMED: "SELECT E'it\\'s' AS s FROM t WHERE id = :id" args=[42] err= +named/postgres escape string/AT: "SELECT E'it\\'s' AS s FROM t WHERE id = @p1" args=[42] err= +named/doubled quote in literal/QUESTION: "SELECT 'it''s ?' AS s FROM t WHERE id = ?" args=[] err=could not find name fine in map[string]interface {}{"id":42} +named/doubled quote in literal/DOLLAR: "SELECT 'it''s $1' AS s FROM t WHERE id = $2" args=[] err=could not find name fine in map[string]interface {}{"id":42} +named/doubled quote in literal/NAMED: "SELECT 'it''s :fine' AS s FROM t WHERE id = :id" args=[] err=could not find name fine in map[string]interface {}{"id":42} +named/doubled quote in literal/AT: "SELECT 'it''s @p1' AS s FROM t WHERE id = @p2" args=[] err=could not find name fine in map[string]interface {}{"id":42} +named/json path with colon/QUESTION: "SELECT data->>'k?' FROM t WHERE id = ?" args=[] err=could not find name v in map[string]interface {}{"id":42} +named/json path with colon/DOLLAR: "SELECT data->>'k$1' FROM t WHERE id = $2" args=[] err=could not find name v in map[string]interface {}{"id":42} +named/json path with colon/NAMED: "SELECT data->>'k:v' FROM t WHERE id = :id" args=[] err=could not find name v in map[string]interface {}{"id":42} +named/json path with colon/AT: "SELECT data->>'k@p1' FROM t WHERE id = @p2" args=[] err=could not find name v in map[string]interface {}{"id":42} +named/question mark literal/QUESTION: "SELECT '?' AS q FROM t WHERE id = ?" args=[42] err= +named/question mark literal/DOLLAR: "SELECT '?' AS q FROM t WHERE id = $1" args=[42] err= +named/question mark literal/NAMED: "SELECT '?' AS q FROM t WHERE id = :id" args=[42] err= +named/question mark literal/AT: "SELECT '?' AS q FROM t WHERE id = @p1" args=[42] err= +named/jsonb operator/QUESTION: "SELECT * FROM t WHERE data ? 'k' AND id = ?" args=[42] err= +named/jsonb operator/DOLLAR: "SELECT * FROM t WHERE data ? 'k' AND id = $1" args=[42] err= +named/jsonb operator/NAMED: "SELECT * FROM t WHERE data ? 'k' AND id = :id" args=[42] err= +named/jsonb operator/AT: "SELECT * FROM t WHERE data ? 'k' AND id = @p1" args=[42] err= +named/two params adjacent/QUESTION: "" args=[] err=unexpected `:` while reading named param at 9 +named/two params adjacent/DOLLAR: "" args=[] err=unexpected `:` while reading named param at 9 +named/two params adjacent/NAMED: "" args=[] err=unexpected `:` while reading named param at 9 +named/two params adjacent/AT: "" args=[] err=unexpected `:` while reading named param at 9 +named/name starting with digit/QUESTION: "SELECT ? AS one, ?" args=[] err=could not find name 1 in map[string]interface {}{"a":1, "b":2} +named/name starting with digit/DOLLAR: "SELECT $1 AS one, $2" args=[] err=could not find name 1 in map[string]interface {}{"a":1, "b":2} +named/name starting with digit/NAMED: "SELECT :1 AS one, :a" args=[] err=could not find name 1 in map[string]interface {}{"a":1, "b":2} +named/name starting with digit/AT: "SELECT @p1 AS one, @p2" args=[] err=could not find name 1 in map[string]interface {}{"a":1, "b":2} +named/param then colon then space/QUESTION: "SELECT ?, b? c" args=[1] err=could not find name in map[string]interface {}{"a":1, "b":2} +named/param then colon then space/DOLLAR: "SELECT $1, b$2 c" args=[1] err=could not find name in map[string]interface {}{"a":1, "b":2} +named/param then colon then space/NAMED: "SELECT :a, b: c" args=[1] err=could not find name in map[string]interface {}{"a":1, "b":2} +named/param then colon then space/AT: "SELECT @p1, b@p2 c" args=[1] err=could not find name in map[string]interface {}{"a":1, "b":2} +named/missing argument/QUESTION: "SELECT ?, ?" args=[1] err=could not find name missing in map[string]interface {}{"a":1, "b":2} +named/missing argument/DOLLAR: "SELECT $1, $2" args=[1] err=could not find name missing in map[string]interface {}{"a":1, "b":2} +named/missing argument/NAMED: "SELECT :a, :missing" args=[1] err=could not find name missing in map[string]interface {}{"a":1, "b":2} +named/missing argument/AT: "SELECT @p1, @p2" args=[1] err=could not find name missing in map[string]interface {}{"a":1, "b":2} +named/no params at all/QUESTION: "SELECT 1" args=[] err= +named/no params at all/DOLLAR: "SELECT 1" args=[] err= +named/no params at all/NAMED: "SELECT 1" args=[] err= +named/no params at all/AT: "SELECT 1" args=[] err= +named/empty map/QUESTION: "SELECT 1" args=[] err= +named/empty map/DOLLAR: "SELECT 1" args=[] err= +named/empty map/NAMED: "SELECT 1" args=[] err= +named/empty map/AT: "SELECT 1" args=[] err= +in/single slice: "SELECT * FROM t WHERE id IN (?, ?, ?)" args=[1 2 3] err= +in/one element: "SELECT * FROM t WHERE id IN (?)" args=[1] err= +in/two slices: "SELECT * FROM t WHERE a IN (?, ?) AND b IN (?, ?)" args=[1 2 "x" "y"] err= +in/scalar and slice: "SELECT * FROM t WHERE x = ? AND id IN (?, ?) AND y = ?" args=["a" 1 2 "b"] err= +in/interface slice: "SELECT * FROM t WHERE id IN (?, ?, ?)" args=[1 "two" 3] err= +in/bytes are a value: "SELECT * FROM t WHERE blob = ?" args=[[]byte{0x78}] err= +in/valuer scalar: "SELECT * FROM t WHERE x = ? AND id IN (?)" args=["v" 1] err= +in/valuer slice: "SELECT * FROM t WHERE id IN (?, ?)" args=[7 8] err= +in/empty slice: "" args=nil err=empty slice passed to 'in' query +in/nil slice: "" args=nil err=empty slice passed to 'in' query +in/empty slice not in: "" args=nil err=empty slice passed to 'in' query +in/empty and full: "" args=nil err=empty slice passed to 'in' query +in/question in line comment: "" args=nil err=number of bindVars exceeds arguments +in/question in block comment: "" args=nil err=number of bindVars exceeds arguments +in/question in literal: "" args=nil err=number of bindVars exceeds arguments +in/question in identifier: "" args=nil err=number of bindVars exceeds arguments +in/double question escape: "" args=nil err=number of bindVars exceeds arguments +in/mysql escaped quote: "SELECT * FROM t WHERE note = 'don\\'t' AND id IN (?, ?)" args=[1 2] err= +in/dollar quoted question: "" args=nil err=number of bindVars exceeds arguments +in/too few args: "" args=nil err=number of bindVars exceeds arguments +in/too many args: "" args=nil err=number of bindVars less than number arguments +in/no slices passthrough: "SELECT * FROM t WHERE a = ? AND b = ?" args=[1 2] err= +in/colon param left alone: "SELECT * FROM t WHERE a = :a AND id IN (?, ?)" args=[1 2] err= +rebind/three placeholders/QUESTION: "INSERT INTO t (a, b, c) VALUES (?, ?, ?)" +rebind/three placeholders/DOLLAR: "INSERT INTO t (a, b, c) VALUES ($1, $2, $3)" +rebind/three placeholders/NAMED: "INSERT INTO t (a, b, c) VALUES (:arg1, :arg2, :arg3)" +rebind/three placeholders/AT: "INSERT INTO t (a, b, c) VALUES (@p1, @p2, @p3)" +rebind/no placeholders/QUESTION: "SELECT 1" +rebind/no placeholders/DOLLAR: "SELECT 1" +rebind/no placeholders/NAMED: "SELECT 1" +rebind/no placeholders/AT: "SELECT 1" +rebind/question in literal/QUESTION: "SELECT '?' AS q FROM t WHERE a = ?" +rebind/question in literal/DOLLAR: "SELECT '$1' AS q FROM t WHERE a = $2" +rebind/question in literal/NAMED: "SELECT ':arg1' AS q FROM t WHERE a = :arg2" +rebind/question in literal/AT: "SELECT '@p1' AS q FROM t WHERE a = @p2" +rebind/question in line comment/QUESTION: "SELECT * -- a?\nFROM t WHERE a = ?" +rebind/question in line comment/DOLLAR: "SELECT * -- a$1\nFROM t WHERE a = $2" +rebind/question in line comment/NAMED: "SELECT * -- a:arg1\nFROM t WHERE a = :arg2" +rebind/question in line comment/AT: "SELECT * -- a@p1\nFROM t WHERE a = @p2" +rebind/question in block comment/QUESTION: "SELECT /* a? */ * FROM t WHERE a = ?" +rebind/question in block comment/DOLLAR: "SELECT /* a$1 */ * FROM t WHERE a = $2" +rebind/question in block comment/NAMED: "SELECT /* a:arg1 */ * FROM t WHERE a = :arg2" +rebind/question in block comment/AT: "SELECT /* a@p1 */ * FROM t WHERE a = @p2" +rebind/double question escape/QUESTION: "SELECT * FROM t WHERE data ?? 'k' AND a = ?" +rebind/double question escape/DOLLAR: "SELECT * FROM t WHERE data $1$2 'k' AND a = $3" +rebind/double question escape/NAMED: "SELECT * FROM t WHERE data :arg1:arg2 'k' AND a = :arg3" +rebind/double question escape/AT: "SELECT * FROM t WHERE data @p1@p2 'k' AND a = @p3" +rebind/jsonb operators escaped/QUESTION: "SELECT * FROM t WHERE data ??| array['a'] AND data ??& array['b'] AND a = ?" +rebind/jsonb operators escaped/DOLLAR: "SELECT * FROM t WHERE data $1$2| array['a'] AND data $3$4& array['b'] AND a = $5" +rebind/jsonb operators escaped/NAMED: "SELECT * FROM t WHERE data :arg1:arg2| array['a'] AND data :arg3:arg4& array['b'] AND a = :arg5" +rebind/jsonb operators escaped/AT: "SELECT * FROM t WHERE data @p1@p2| array['a'] AND data @p3@p4& array['b'] AND a = @p5" +rebind/mysql escaped quote/QUESTION: "SELECT * FROM t WHERE note = 'don\\'t' AND a = ?" +rebind/mysql escaped quote/DOLLAR: "SELECT * FROM t WHERE note = 'don\\'t' AND a = $1" +rebind/mysql escaped quote/NAMED: "SELECT * FROM t WHERE note = 'don\\'t' AND a = :arg1" +rebind/mysql escaped quote/AT: "SELECT * FROM t WHERE note = 'don\\'t' AND a = @p1" +rebind/like escape clause/QUESTION: "SELECT * FROM t WHERE a LIKE ? ESCAPE '\\' AND b = ?" +rebind/like escape clause/DOLLAR: "SELECT * FROM t WHERE a LIKE $1 ESCAPE '\\' AND b = $2" +rebind/like escape clause/NAMED: "SELECT * FROM t WHERE a LIKE :arg1 ESCAPE '\\' AND b = :arg2" +rebind/like escape clause/AT: "SELECT * FROM t WHERE a LIKE @p1 ESCAPE '\\' AND b = @p2" +rebind/bracket identifier/QUESTION: "SELECT [what?] FROM t WHERE a = ?" +rebind/bracket identifier/DOLLAR: "SELECT [what$1] FROM t WHERE a = $2" +rebind/bracket identifier/NAMED: "SELECT [what:arg1] FROM t WHERE a = :arg2" +rebind/bracket identifier/AT: "SELECT [what@p1] FROM t WHERE a = @p2" +rebind/dollar quoted/QUESTION: "SELECT $$?$$ FROM t WHERE a = ?" +rebind/dollar quoted/DOLLAR: "SELECT $$$1$$ FROM t WHERE a = $2" +rebind/dollar quoted/NAMED: "SELECT $$:arg1$$ FROM t WHERE a = :arg2" +rebind/dollar quoted/AT: "SELECT $$@p1$$ FROM t WHERE a = @p2" +rebind/backtick identifier/QUESTION: "SELECT `a?` FROM t WHERE a = ?" +rebind/backtick identifier/DOLLAR: "SELECT `a$1` FROM t WHERE a = $2" +rebind/backtick identifier/NAMED: "SELECT `a:arg1` FROM t WHERE a = :arg2" +rebind/backtick identifier/AT: "SELECT `a@p1` FROM t WHERE a = @p2" +rebind/postgres escape string/QUESTION: "SELECT E'\\'?' FROM t WHERE a = ?" +rebind/postgres escape string/DOLLAR: "SELECT E'\\'$1' FROM t WHERE a = $2" +rebind/postgres escape string/NAMED: "SELECT E'\\':arg1' FROM t WHERE a = :arg2" +rebind/postgres escape string/AT: "SELECT E'\\'@p1' FROM t WHERE a = @p2" +rebind/already dollar/QUESTION: "SELECT * FROM t WHERE a = $1 AND b = ?" +rebind/already dollar/DOLLAR: "SELECT * FROM t WHERE a = $1 AND b = $1" +rebind/already dollar/NAMED: "SELECT * FROM t WHERE a = $1 AND b = :arg1" +rebind/already dollar/AT: "SELECT * FROM t WHERE a = $1 AND b = @p1" diff --git a/testdata/golden.txt b/testdata/golden.txt new file mode 100644 index 0000000..b5bfc0a --- /dev/null +++ b/testdata/golden.txt @@ -0,0 +1,239 @@ +named/insert struct/QUESTION: "INSERT INTO person (first_name, last_name, email) VALUES (?, ?, ?)" args=["Jason" "Moiron" "jmoiron@jmoiron.net"] err= +named/insert struct/DOLLAR: "INSERT INTO person (first_name, last_name, email) VALUES ($1, $2, $3)" args=["Jason" "Moiron" "jmoiron@jmoiron.net"] err= +named/insert struct/NAMED: "INSERT INTO person (first_name, last_name, email) VALUES (:first_name, :last_name, :email)" args=["Jason" "Moiron" "jmoiron@jmoiron.net"] err= +named/insert struct/AT: "INSERT INTO person (first_name, last_name, email) VALUES (@p1, @p2, @p3)" args=["Jason" "Moiron" "jmoiron@jmoiron.net"] err= +named/select map/QUESTION: "SELECT * FROM person WHERE first_name=? AND last_name=?" args=["Jane" "Citizen"] err= +named/select map/DOLLAR: "SELECT * FROM person WHERE first_name=$1 AND last_name=$2" args=["Jane" "Citizen"] err= +named/select map/NAMED: "SELECT * FROM person WHERE first_name=:first_name AND last_name=:last_name" args=["Jane" "Citizen"] err= +named/select map/AT: "SELECT * FROM person WHERE first_name=@p1 AND last_name=@p2" args=["Jane" "Citizen"] err= +named/select struct trailing param/QUESTION: "SELECT * FROM person WHERE email=?" args=["jmoiron@jmoiron.net"] err= +named/select struct trailing param/DOLLAR: "SELECT * FROM person WHERE email=$1" args=["jmoiron@jmoiron.net"] err= +named/select struct trailing param/NAMED: "SELECT * FROM person WHERE email=:email" args=["jmoiron@jmoiron.net"] err= +named/select struct trailing param/AT: "SELECT * FROM person WHERE email=@p1" args=["jmoiron@jmoiron.net"] err= +named/params without spaces/QUESTION: "INSERT INTO t (a,b) VALUES(?,?)" args=[1 2] err= +named/params without spaces/DOLLAR: "INSERT INTO t (a,b) VALUES($1,$2)" args=[1 2] err= +named/params without spaces/NAMED: "INSERT INTO t (a,b) VALUES(:a,:b)" args=[1 2] err= +named/params without spaces/AT: "INSERT INTO t (a,b) VALUES(@p1,@p2)" args=[1 2] err= +named/batch insert structs/QUESTION: "INSERT INTO person (first_name, last_name, email) VALUES (?, ?, ?),(?, ?, ?)" args=["Jason" "Moiron" "jmoiron@jmoiron.net" "John" "Doe" "johndoeDNE@gmail.net"] err= +named/batch insert structs/DOLLAR: "INSERT INTO person (first_name, last_name, email) VALUES ($1, $2, $3),($4, $5, $6)" args=["Jason" "Moiron" "jmoiron@jmoiron.net" "John" "Doe" "johndoeDNE@gmail.net"] err= +named/batch insert structs/NAMED: "INSERT INTO person (first_name, last_name, email) VALUES (:arg1, :arg2, :arg3),(:arg4, :arg5, :arg6)" args=["Jason" "Moiron" "jmoiron@jmoiron.net" "John" "Doe" "johndoeDNE@gmail.net"] err= +named/batch insert structs/AT: "INSERT INTO person (first_name, last_name, email) VALUES (@p1, @p2, @p3),(@p4, @p5, @p6)" args=["Jason" "Moiron" "jmoiron@jmoiron.net" "John" "Doe" "johndoeDNE@gmail.net"] err= +named/batch insert maps/QUESTION: "INSERT INTO person (first_name, last_name, email) VALUES (?, ?, ?),(?, ?, ?)" args=["Jane" "Citizen" "jane.citzen@example.com" "Bin" "Smuth" "bensmith@allblacks.nz"] err= +named/batch insert maps/DOLLAR: "INSERT INTO person (first_name, last_name, email) VALUES ($1, $2, $3),($4, $5, $6)" args=["Jane" "Citizen" "jane.citzen@example.com" "Bin" "Smuth" "bensmith@allblacks.nz"] err= +named/batch insert maps/NAMED: "INSERT INTO person (first_name, last_name, email) VALUES (:arg1, :arg2, :arg3),(:arg4, :arg5, :arg6)" args=["Jane" "Citizen" "jane.citzen@example.com" "Bin" "Smuth" "bensmith@allblacks.nz"] err= +named/batch insert maps/AT: "INSERT INTO person (first_name, last_name, email) VALUES (@p1, @p2, @p3),(@p4, @p5, @p6)" args=["Jane" "Citizen" "jane.citzen@example.com" "Bin" "Smuth" "bensmith@allblacks.nz"] err= +named/batch insert without column list/QUESTION: "insert into t values (?, ?),(?, ?)" args=[1 2 3 4] err= +named/batch insert without column list/DOLLAR: "insert into t values ($1, $2),($3, $4)" args=[1 2 3 4] err= +named/batch insert without column list/NAMED: "insert into t values (:arg1, :arg2),(:arg3, :arg4)" args=[1 2 3 4] err= +named/batch insert without column list/AT: "insert into t values (@p1, @p2),(@p3, @p4)" args=[1 2 3 4] err= +named/batch insert multiline/QUESTION: "INSERT INTO t (a, b)\nVALUES\n (?, ?),(?, ?)" args=[1 2 3 4] err= +named/batch insert multiline/DOLLAR: "INSERT INTO t (a, b)\nVALUES\n ($1, $2),($3, $4)" args=[1 2 3 4] err= +named/batch insert multiline/NAMED: "INSERT INTO t (a, b)\nVALUES\n (:arg1, :arg2),(:arg3, :arg4)" args=[1 2 3 4] err= +named/batch insert multiline/AT: "INSERT INTO t (a, b)\nVALUES\n (@p1, @p2),(@p3, @p4)" args=[1 2 3 4] err= +named/batch values in from/QUESTION: "UPDATE t SET b = u.b FROM (VALUES (?, ?),(?, ?)) AS u(a, b) WHERE t.a = u.a" args=[1 2 3 4] err= +named/batch values in from/DOLLAR: "UPDATE t SET b = u.b FROM (VALUES ($1, $2),($3, $4)) AS u(a, b) WHERE t.a = u.a" args=[1 2 3 4] err= +named/batch values in from/NAMED: "UPDATE t SET b = u.b FROM (VALUES (:arg1, :arg2),(:arg3, :arg4)) AS u(a, b) WHERE t.a = u.a" args=[1 2 3 4] err= +named/batch values in from/AT: "UPDATE t SET b = u.b FROM (VALUES (@p1, @p2),(@p3, @p4)) AS u(a, b) WHERE t.a = u.a" args=[1 2 3 4] err= +named/batch on duplicate key/QUESTION: "INSERT INTO t (a, b) VALUES (?, ?),(?, ?) ON DUPLICATE KEY UPDATE b = VALUES(b)" args=[1 2 3 4] err= +named/batch on duplicate key/DOLLAR: "INSERT INTO t (a, b) VALUES ($1, $2),($3, $4) ON DUPLICATE KEY UPDATE b = VALUES(b)" args=[1 2 3 4] err= +named/batch on duplicate key/NAMED: "INSERT INTO t (a, b) VALUES (:arg1, :arg2),(:arg3, :arg4) ON DUPLICATE KEY UPDATE b = VALUES(b)" args=[1 2 3 4] err= +named/batch on duplicate key/AT: "INSERT INTO t (a, b) VALUES (@p1, @p2),(@p3, @p4) ON DUPLICATE KEY UPDATE b = VALUES(b)" args=[1 2 3 4] err= +named/batch table named values/QUESTION: "INSERT INTO \"values\" (a, b) VALUES (?, ?),(?, ?)" args=[1 2 3 4] err= +named/batch table named values/DOLLAR: "INSERT INTO \"values\" (a, b) VALUES ($1, $2),($3, $4)" args=[1 2 3 4] err= +named/batch table named values/NAMED: "INSERT INTO \"values\" (a, b) VALUES (:arg1, :arg2),(:arg3, :arg4)" args=[1 2 3 4] err= +named/batch table named values/AT: "INSERT INTO \"values\" (a, b) VALUES (@p1, @p2),(@p3, @p4)" args=[1 2 3 4] err= +named/nested struct dotted names/QUESTION: "INSERT INTO booking (first, country, city, nights) VALUES (?, ?, ?, ?)" args=["Jason" "NZ" "Wellington" 3] err= +named/nested struct dotted names/DOLLAR: "INSERT INTO booking (first, country, city, nights) VALUES ($1, $2, $3, $4)" args=["Jason" "NZ" "Wellington" 3] err= +named/nested struct dotted names/NAMED: "INSERT INTO booking (first, country, city, nights) VALUES (:person.first_name, :place.country, :place.city, :nights)" args=["Jason" "NZ" "Wellington" 3] err= +named/nested struct dotted names/AT: "INSERT INTO booking (first, country, city, nights) VALUES (@p1, @p2, @p3, @p4)" args=["Jason" "NZ" "Wellington" 3] err= +named/cast attached to param/QUESTION: "SELECT * FROM t WHERE id = ?::uuid" args=[42] err= +named/cast attached to param/DOLLAR: "SELECT * FROM t WHERE id = $1::uuid" args=[42] err= +named/cast attached to param/NAMED: "SELECT * FROM t WHERE id = :id::uuid" args=[42] err= +named/cast attached to param/AT: "SELECT * FROM t WHERE id = @p1::uuid" args=[42] err= +named/cast attached to param text/QUESTION: "SELECT ?::text AS id" args=[42] err= +named/cast attached to param text/DOLLAR: "SELECT $1::text AS id" args=[42] err= +named/cast attached to param text/NAMED: "SELECT :id::text AS id" args=[42] err= +named/cast attached to param text/AT: "SELECT @p1::text AS id" args=[42] err= +named/cast elsewhere/QUESTION: "SELECT (now() AT TIME ZONE 'utc')::date FROM t WHERE id = ?" args=[42] err= +named/cast elsewhere/DOLLAR: "SELECT (now() AT TIME ZONE 'utc')::date FROM t WHERE id = $1" args=[42] err= +named/cast elsewhere/NAMED: "SELECT (now() AT TIME ZONE 'utc')::date FROM t WHERE id = :id" args=[42] err= +named/cast elsewhere/AT: "SELECT (now() AT TIME ZONE 'utc')::date FROM t WHERE id = @p1" args=[42] err= +named/double cast/QUESTION: "SELECT ?::text::int FROM t" args=[42] err= +named/double cast/DOLLAR: "SELECT $1::text::int FROM t" args=[42] err= +named/double cast/NAMED: "SELECT :id::text::int FROM t" args=[42] err= +named/double cast/AT: "SELECT @p1::text::int FROM t" args=[42] err= +named/old four-colon escape/QUESTION: "SELECT ?::::uuid" args=[42] err= +named/old four-colon escape/DOLLAR: "SELECT $1::::uuid" args=[42] err= +named/old four-colon escape/NAMED: "SELECT :id::::uuid" args=[42] err= +named/old four-colon escape/AT: "SELECT @p1::::uuid" args=[42] err= +named/colon in string literal time/QUESTION: "SELECT * FROM t WHERE at > '12:30' AND id = ?" args=[42] err= +named/colon in string literal time/DOLLAR: "SELECT * FROM t WHERE at > '12:30' AND id = $1" args=[42] err= +named/colon in string literal time/NAMED: "SELECT * FROM t WHERE at > '12:30' AND id = :id" args=[42] err= +named/colon in string literal time/AT: "SELECT * FROM t WHERE at > '12:30' AND id = @p1" args=[42] err= +named/colon in string literal words/QUESTION: "SELECT 'note: see :id' AS note, id FROM t WHERE id = ?" args=[42] err= +named/colon in string literal words/DOLLAR: "SELECT 'note: see :id' AS note, id FROM t WHERE id = $1" args=[42] err= +named/colon in string literal words/NAMED: "SELECT 'note: see :id' AS note, id FROM t WHERE id = :id" args=[42] err= +named/colon in string literal words/AT: "SELECT 'note: see :id' AS note, id FROM t WHERE id = @p1" args=[42] err= +named/colon in line comment/QUESTION: "SELECT * FROM t -- filter: by id\nWHERE id = ?" args=[42] err= +named/colon in line comment/DOLLAR: "SELECT * FROM t -- filter: by id\nWHERE id = $1" args=[42] err= +named/colon in line comment/NAMED: "SELECT * FROM t -- filter: by id\nWHERE id = :id" args=[42] err= +named/colon in line comment/AT: "SELECT * FROM t -- filter: by id\nWHERE id = @p1" args=[42] err= +named/colon in block comment/QUESTION: "SELECT /* owner: ops */ * FROM t WHERE id = ?" args=[42] err= +named/colon in block comment/DOLLAR: "SELECT /* owner: ops */ * FROM t WHERE id = $1" args=[42] err= +named/colon in block comment/NAMED: "SELECT /* owner: ops */ * FROM t WHERE id = :id" args=[42] err= +named/colon in block comment/AT: "SELECT /* owner: ops */ * FROM t WHERE id = @p1" args=[42] err= +named/nested block comment/QUESTION: "SELECT /* a /* b: */ c */ * FROM t WHERE id = ?" args=[42] err= +named/nested block comment/DOLLAR: "SELECT /* a /* b: */ c */ * FROM t WHERE id = $1" args=[42] err= +named/nested block comment/NAMED: "SELECT /* a /* b: */ c */ * FROM t WHERE id = :id" args=[42] err= +named/nested block comment/AT: "SELECT /* a /* b: */ c */ * FROM t WHERE id = @p1" args=[42] err= +named/mysql assignment/QUESTION: "SELECT @x := ?, @x + 1" args=[42] err= +named/mysql assignment/DOLLAR: "SELECT @x := $1, @x + 1" args=[42] err= +named/mysql assignment/NAMED: "SELECT @x := :id, @x + 1" args=[42] err= +named/mysql assignment/AT: "SELECT @x := @p1, @x + 1" args=[42] err= +named/array slice/QUESTION: "SELECT arr[1:2] FROM t WHERE id = ?" args=[42] err= +named/array slice/DOLLAR: "SELECT arr[1:2] FROM t WHERE id = $1" args=[42] err= +named/array slice/NAMED: "SELECT arr[1:2] FROM t WHERE id = :id" args=[42] err= +named/array slice/AT: "SELECT arr[1:2] FROM t WHERE id = @p1" args=[42] err= +named/unicode name/QUESTION: "INSERT INTO t (name) VALUES (?)" args=["x"] err= +named/unicode name/DOLLAR: "INSERT INTO t (name) VALUES ($1)" args=["x"] err= +named/unicode name/NAMED: "INSERT INTO t (name) VALUES (:名前)" args=["x"] err= +named/unicode name/AT: "INSERT INTO t (name) VALUES (@p1)" args=["x"] err= +named/dollar quoted string/QUESTION: "SELECT $$it's :not a param$$ AS s, id FROM t WHERE id = ?" args=[42] err= +named/dollar quoted string/DOLLAR: "SELECT $$it's :not a param$$ AS s, id FROM t WHERE id = $1" args=[42] err= +named/dollar quoted string/NAMED: "SELECT $$it's :not a param$$ AS s, id FROM t WHERE id = :id" args=[42] err= +named/dollar quoted string/AT: "SELECT $$it's :not a param$$ AS s, id FROM t WHERE id = @p1" args=[42] err= +named/dollar quoted with tag/QUESTION: "SELECT $q$:a::b$q$ AS s FROM t WHERE id = ?" args=[42] err= +named/dollar quoted with tag/DOLLAR: "SELECT $q$:a::b$q$ AS s FROM t WHERE id = $1" args=[42] err= +named/dollar quoted with tag/NAMED: "SELECT $q$:a::b$q$ AS s FROM t WHERE id = :id" args=[42] err= +named/dollar quoted with tag/AT: "SELECT $q$:a::b$q$ AS s FROM t WHERE id = @p1" args=[42] err= +named/double quoted identifier/QUESTION: "SELECT \"col:on\" FROM t WHERE id = ?" args=[42] err= +named/double quoted identifier/DOLLAR: "SELECT \"col:on\" FROM t WHERE id = $1" args=[42] err= +named/double quoted identifier/NAMED: "SELECT \"col:on\" FROM t WHERE id = :id" args=[42] err= +named/double quoted identifier/AT: "SELECT \"col:on\" FROM t WHERE id = @p1" args=[42] err= +named/backtick identifier/QUESTION: "SELECT `col:on` FROM t WHERE id = ?" args=[42] err= +named/backtick identifier/DOLLAR: "SELECT `col:on` FROM t WHERE id = $1" args=[42] err= +named/backtick identifier/NAMED: "SELECT `col:on` FROM t WHERE id = :id" args=[42] err= +named/backtick identifier/AT: "SELECT `col:on` FROM t WHERE id = @p1" args=[42] err= +named/postgres escape string/QUESTION: "SELECT E'it\\'s' AS s FROM t WHERE id = ?" args=[42] err= +named/postgres escape string/DOLLAR: "SELECT E'it\\'s' AS s FROM t WHERE id = $1" args=[42] err= +named/postgres escape string/NAMED: "SELECT E'it\\'s' AS s FROM t WHERE id = :id" args=[42] err= +named/postgres escape string/AT: "SELECT E'it\\'s' AS s FROM t WHERE id = @p1" args=[42] err= +named/doubled quote in literal/QUESTION: "SELECT 'it''s :fine' AS s FROM t WHERE id = ?" args=[42] err= +named/doubled quote in literal/DOLLAR: "SELECT 'it''s :fine' AS s FROM t WHERE id = $1" args=[42] err= +named/doubled quote in literal/NAMED: "SELECT 'it''s :fine' AS s FROM t WHERE id = :id" args=[42] err= +named/doubled quote in literal/AT: "SELECT 'it''s :fine' AS s FROM t WHERE id = @p1" args=[42] err= +named/json path with colon/QUESTION: "SELECT data->>'k:v' FROM t WHERE id = ?" args=[42] err= +named/json path with colon/DOLLAR: "SELECT data->>'k:v' FROM t WHERE id = $1" args=[42] err= +named/json path with colon/NAMED: "SELECT data->>'k:v' FROM t WHERE id = :id" args=[42] err= +named/json path with colon/AT: "SELECT data->>'k:v' FROM t WHERE id = @p1" args=[42] err= +named/question mark literal/QUESTION: "SELECT '?' AS q FROM t WHERE id = ?" args=[42] err= +named/question mark literal/DOLLAR: "SELECT '?' AS q FROM t WHERE id = $1" args=[42] err= +named/question mark literal/NAMED: "SELECT '?' AS q FROM t WHERE id = :id" args=[42] err= +named/question mark literal/AT: "SELECT '?' AS q FROM t WHERE id = @p1" args=[42] err= +named/jsonb operator/QUESTION: "SELECT * FROM t WHERE data ? 'k' AND id = ?" args=[42] err= +named/jsonb operator/DOLLAR: "SELECT * FROM t WHERE data ? 'k' AND id = $1" args=[42] err= +named/jsonb operator/NAMED: "SELECT * FROM t WHERE data ? 'k' AND id = :id" args=[42] err= +named/jsonb operator/AT: "SELECT * FROM t WHERE data ? 'k' AND id = @p1" args=[42] err= +named/two params adjacent/QUESTION: "" args=[] err=unexpected `:` while reading named param at 9 +named/two params adjacent/DOLLAR: "" args=[] err=unexpected `:` while reading named param at 9 +named/two params adjacent/NAMED: "" args=[] err=unexpected `:` while reading named param at 9 +named/two params adjacent/AT: "" args=[] err=unexpected `:` while reading named param at 9 +named/name starting with digit/QUESTION: "SELECT :1 AS one, ?" args=[1] err= +named/name starting with digit/DOLLAR: "SELECT :1 AS one, $1" args=[1] err= +named/name starting with digit/NAMED: "SELECT :1 AS one, :a" args=[1] err= +named/name starting with digit/AT: "SELECT :1 AS one, @p1" args=[1] err= +named/param then colon then space/QUESTION: "SELECT ?, b: c" args=[1] err= +named/param then colon then space/DOLLAR: "SELECT $1, b: c" args=[1] err= +named/param then colon then space/NAMED: "SELECT :a, b: c" args=[1] err= +named/param then colon then space/AT: "SELECT @p1, b: c" args=[1] err= +named/missing argument/QUESTION: "SELECT ?, ?" args=[1] err=could not find name missing in map[string]interface {}{"a":1, "b":2} +named/missing argument/DOLLAR: "SELECT $1, $2" args=[1] err=could not find name missing in map[string]interface {}{"a":1, "b":2} +named/missing argument/NAMED: "SELECT :a, :missing" args=[1] err=could not find name missing in map[string]interface {}{"a":1, "b":2} +named/missing argument/AT: "SELECT @p1, @p2" args=[1] err=could not find name missing in map[string]interface {}{"a":1, "b":2} +named/no params at all/QUESTION: "SELECT 1" args=[] err= +named/no params at all/DOLLAR: "SELECT 1" args=[] err= +named/no params at all/NAMED: "SELECT 1" args=[] err= +named/no params at all/AT: "SELECT 1" args=[] err= +named/empty map/QUESTION: "SELECT 1" args=[] err= +named/empty map/DOLLAR: "SELECT 1" args=[] err= +named/empty map/NAMED: "SELECT 1" args=[] err= +named/empty map/AT: "SELECT 1" args=[] err= +in/single slice: "SELECT * FROM t WHERE id IN (?, ?, ?)" args=[1 2 3] err= +in/one element: "SELECT * FROM t WHERE id IN (?)" args=[1] err= +in/two slices: "SELECT * FROM t WHERE a IN (?, ?) AND b IN (?, ?)" args=[1 2 "x" "y"] err= +in/scalar and slice: "SELECT * FROM t WHERE x = ? AND id IN (?, ?) AND y = ?" args=["a" 1 2 "b"] err= +in/interface slice: "SELECT * FROM t WHERE id IN (?, ?, ?)" args=[1 "two" 3] err= +in/bytes are a value: "SELECT * FROM t WHERE blob = ?" args=[[]byte{0x78}] err= +in/valuer scalar: "SELECT * FROM t WHERE x = ? AND id IN (?)" args=["v" 1] err= +in/valuer slice: "SELECT * FROM t WHERE id IN (?, ?)" args=[7 8] err= +in/empty slice: "SELECT * FROM t WHERE id IN (NULL)" args=[] err= +in/nil slice: "SELECT * FROM t WHERE id IN (NULL)" args=[] err= +in/empty slice not in: "SELECT * FROM t WHERE id NOT IN (NULL)" args=[] err= +in/empty and full: "SELECT * FROM t WHERE a IN (NULL) AND b IN (?, ?)" args=[1 2] err= +in/question in line comment: "SELECT * -- which ids?\nFROM t WHERE id IN (?, ?)" args=[1 2] err= +in/question in block comment: "SELECT /* why? */ * FROM t WHERE id IN (?, ?)" args=[1 2] err= +in/question in literal: "SELECT '?' AS q FROM t WHERE id IN (?, ?)" args=[1 2] err= +in/question in identifier: "SELECT \"what?\" FROM t WHERE id IN (?, ?)" args=[1 2] err= +in/double question escape: "SELECT * FROM t WHERE data ?? 'k' AND id IN (?, ?)" args=[1 2] err= +in/mysql escaped quote: "SELECT * FROM t WHERE note = 'don\\'t' AND id IN (?, ?)" args=[1 2] err= +in/dollar quoted question: "SELECT $$?$$ AS s FROM t WHERE id IN (?, ?)" args=[1 2] err= +in/too few args: "" args=nil err=number of bindVars exceeds arguments +in/too many args: "" args=nil err=number of bindVars less than number arguments +in/no slices passthrough: "SELECT * FROM t WHERE a = ? AND b = ?" args=[1 2] err= +in/colon param left alone: "SELECT * FROM t WHERE a = :a AND id IN (?, ?)" args=[1 2] err= +rebind/three placeholders/QUESTION: "INSERT INTO t (a, b, c) VALUES (?, ?, ?)" +rebind/three placeholders/DOLLAR: "INSERT INTO t (a, b, c) VALUES ($1, $2, $3)" +rebind/three placeholders/NAMED: "INSERT INTO t (a, b, c) VALUES (:arg1, :arg2, :arg3)" +rebind/three placeholders/AT: "INSERT INTO t (a, b, c) VALUES (@p1, @p2, @p3)" +rebind/no placeholders/QUESTION: "SELECT 1" +rebind/no placeholders/DOLLAR: "SELECT 1" +rebind/no placeholders/NAMED: "SELECT 1" +rebind/no placeholders/AT: "SELECT 1" +rebind/question in literal/QUESTION: "SELECT '?' AS q FROM t WHERE a = ?" +rebind/question in literal/DOLLAR: "SELECT '?' AS q FROM t WHERE a = $1" +rebind/question in literal/NAMED: "SELECT '?' AS q FROM t WHERE a = :arg1" +rebind/question in literal/AT: "SELECT '?' AS q FROM t WHERE a = @p1" +rebind/question in line comment/QUESTION: "SELECT * -- a?\nFROM t WHERE a = ?" +rebind/question in line comment/DOLLAR: "SELECT * -- a?\nFROM t WHERE a = $1" +rebind/question in line comment/NAMED: "SELECT * -- a?\nFROM t WHERE a = :arg1" +rebind/question in line comment/AT: "SELECT * -- a?\nFROM t WHERE a = @p1" +rebind/question in block comment/QUESTION: "SELECT /* a? */ * FROM t WHERE a = ?" +rebind/question in block comment/DOLLAR: "SELECT /* a? */ * FROM t WHERE a = $1" +rebind/question in block comment/NAMED: "SELECT /* a? */ * FROM t WHERE a = :arg1" +rebind/question in block comment/AT: "SELECT /* a? */ * FROM t WHERE a = @p1" +rebind/double question escape/QUESTION: "SELECT * FROM t WHERE data ? 'k' AND a = ?" +rebind/double question escape/DOLLAR: "SELECT * FROM t WHERE data ? 'k' AND a = $1" +rebind/double question escape/NAMED: "SELECT * FROM t WHERE data ? 'k' AND a = :arg1" +rebind/double question escape/AT: "SELECT * FROM t WHERE data ? 'k' AND a = @p1" +rebind/jsonb operators escaped/QUESTION: "SELECT * FROM t WHERE data ?| array['a'] AND data ?& array['b'] AND a = ?" +rebind/jsonb operators escaped/DOLLAR: "SELECT * FROM t WHERE data ?| array['a'] AND data ?& array['b'] AND a = $1" +rebind/jsonb operators escaped/NAMED: "SELECT * FROM t WHERE data ?| array['a'] AND data ?& array['b'] AND a = :arg1" +rebind/jsonb operators escaped/AT: "SELECT * FROM t WHERE data ?| array['a'] AND data ?& array['b'] AND a = @p1" +rebind/mysql escaped quote/QUESTION: "SELECT * FROM t WHERE note = 'don\\'t' AND a = ?" +rebind/mysql escaped quote/DOLLAR: "SELECT * FROM t WHERE note = 'don\\'t' AND a = $1" +rebind/mysql escaped quote/NAMED: "SELECT * FROM t WHERE note = 'don\\'t' AND a = :arg1" +rebind/mysql escaped quote/AT: "SELECT * FROM t WHERE note = 'don\\'t' AND a = @p1" +rebind/like escape clause/QUESTION: "SELECT * FROM t WHERE a LIKE ? ESCAPE '\\' AND b = ?" +rebind/like escape clause/DOLLAR: "SELECT * FROM t WHERE a LIKE $1 ESCAPE '\\' AND b = $2" +rebind/like escape clause/NAMED: "SELECT * FROM t WHERE a LIKE :arg1 ESCAPE '\\' AND b = :arg2" +rebind/like escape clause/AT: "SELECT * FROM t WHERE a LIKE @p1 ESCAPE '\\' AND b = @p2" +rebind/bracket identifier/QUESTION: "SELECT [what?] FROM t WHERE a = ?" +rebind/bracket identifier/DOLLAR: "SELECT [what$1] FROM t WHERE a = $2" +rebind/bracket identifier/NAMED: "SELECT [what:arg1] FROM t WHERE a = :arg2" +rebind/bracket identifier/AT: "SELECT [what?] FROM t WHERE a = @p1" +rebind/dollar quoted/QUESTION: "SELECT $$?$$ FROM t WHERE a = ?" +rebind/dollar quoted/DOLLAR: "SELECT $$?$$ FROM t WHERE a = $1" +rebind/dollar quoted/NAMED: "SELECT $$?$$ FROM t WHERE a = :arg1" +rebind/dollar quoted/AT: "SELECT $$?$$ FROM t WHERE a = @p1" +rebind/backtick identifier/QUESTION: "SELECT `a?` FROM t WHERE a = ?" +rebind/backtick identifier/DOLLAR: "SELECT `a?` FROM t WHERE a = $1" +rebind/backtick identifier/NAMED: "SELECT `a?` FROM t WHERE a = :arg1" +rebind/backtick identifier/AT: "SELECT `a?` FROM t WHERE a = @p1" +rebind/postgres escape string/QUESTION: "SELECT E'\\'?' FROM t WHERE a = ?" +rebind/postgres escape string/DOLLAR: "SELECT E'\\'?' FROM t WHERE a = $1" +rebind/postgres escape string/NAMED: "SELECT E'\\'?' FROM t WHERE a = :arg1" +rebind/postgres escape string/AT: "SELECT E'\\'?' FROM t WHERE a = @p1" +rebind/already dollar/QUESTION: "SELECT * FROM t WHERE a = $1 AND b = ?" +rebind/already dollar/DOLLAR: "SELECT * FROM t WHERE a = $1 AND b = $1" +rebind/already dollar/NAMED: "SELECT * FROM t WHERE a = $1 AND b = :arg1" +rebind/already dollar/AT: "SELECT * FROM t WHERE a = $1 AND b = @p1"