Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions internal/compiler/find_params.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,27 @@ func (l *limitOffset) Pos() int {
return 0
}

// likeEscape is the parent of a parameter in the ESCAPE clause of a pattern
// match. It is a string whatever the matched column is, so it must not be
// typed after the operands as the rest of the A_Expr is.
type likeEscape struct {
}

func (l *likeEscape) Pos() int {
return 0
}

func (p paramSearch) Visit(node ast.Node) astutils.Visitor {
switch n := node.(type) {

case *ast.A_Expr:
p.parent = node
if ref, ok := n.Escape.(*ast.ParamRef); ok {
if _, found := p.seen[ref.Location]; !found {
*p.refs = append(*p.refs, paramRef{parent: &likeEscape{}, ref: ref, rv: p.rangeVar})
p.seen[ref.Location] = struct{}{}
}
}

case *ast.BetweenExpr:
p.parent = node
Expand Down
13 changes: 13 additions & 0 deletions internal/compiler/resolve.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,19 @@ func (comp *Compiler) resolveCatalogRefs(qc *QueryCatalog, rvs []*ast.RangeVar,
},
})

case *likeEscape:
defaultP := named.NewInferredParam("escape", true)
p, isNamed := params.FetchMerge(ref.ref.Number, defaultP)
a = append(a, Parameter{
Number: ref.ref.Number,
Column: &Column{
Name: p.Name(),
DataType: "text",
NotNull: p.NotNull(),
IsNamedParam: isNamed,
},
})

case *ast.A_Expr:
// TODO: While this works for a wide range of simple expressions,
// more complicated expressions will cause this logic to fail.
Expand Down
23 changes: 22 additions & 1 deletion internal/core/analyzer/expr.go
Original file line number Diff line number Diff line change
Expand Up @@ -331,15 +331,36 @@ func (a *analyzer) typeAExpr(e *ast.A_Expr) (exprType, error) {
if err != nil {
return exprType{}, err
}
escapeT, err := a.typeEscape(e.Escape)
if err != nil {
return exprType{}, err
}
// An operator's result is NULL when an operand is, except for IS and
// IS NOT, which test for NULL rather than propagate it: x IS NULL and
// x IS y are never NULL, whatever x and y are.
return exprType{
typeOID: overload.ResultTypeOID,
nullable: (leftT.nullable || rightT.nullable) && !isNullTest(opName),
nullable: (leftT.nullable || rightT.nullable || escapeT.nullable) && !isNullTest(opName),
}, nil
}

// typeEscape types the ESCAPE operand of a pattern match. The escape is a
// one-character string whatever is being matched, so a placeholder there is
// text, named after the clause rather than after the matched column.
func (a *analyzer) typeEscape(n ast.Node) (exprType, error) {
if n == nil {
return exprType{}, nil
}
if pr, ok := n.(*ast.ParamRef); ok {
a.inferParam(pr, a.namedType("text"))
if cur := a.params[pr.Number]; cur.Name == "" && cur.Source == nil {
cur.Name = "escape"
a.params[pr.Number] = cur
}
}
return a.typeExpr(n)
}

// castParamRef is the placeholder a cast wraps, as ClickHouse's {p:UInt64}
// is written, or nil. The cast has typed it already; what it is compared
// with still names it and says which column it stands in for.
Expand Down
31 changes: 31 additions & 0 deletions internal/endtoend/testdata/like_escape/sqlite/go/db.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions internal/endtoend/testdata/like_escape/sqlite/go/models.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

193 changes: 193 additions & 0 deletions internal/endtoend/testdata/like_escape/sqlite/go/query.sql.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 17 additions & 0 deletions internal/endtoend/testdata/like_escape/sqlite/query.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
-- name: SearchName :many
SELECT id FROM t WHERE name LIKE ? ESCAPE ?;

-- name: SearchNameNamed :many
SELECT id FROM t WHERE name LIKE @pattern ESCAPE @esc;

-- name: SearchNotLike :many
SELECT id FROM t WHERE name NOT LIKE ? ESCAPE ?;

-- name: SearchEscapeOnly :many
SELECT id FROM t WHERE name LIKE 'a!%%' ESCAPE ?;

-- name: SearchLiteralEscape :many
SELECT id FROM t WHERE name LIKE ? ESCAPE '!';

-- name: SearchThenWhere :many
SELECT id FROM t WHERE name LIKE ? ESCAPE ? AND score > ?;
1 change: 1 addition & 0 deletions internal/endtoend/testdata/like_escape/sqlite/schema.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT NOT NULL, score INTEGER NOT NULL);
12 changes: 12 additions & 0 deletions internal/endtoend/testdata/like_escape/sqlite/sqlc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"version": "1",
"packages": [
{
"path": "go",
"engine": "sqlite",
"name": "querytest",
"schema": "schema.sql",
"queries": "query.sql"
}
]
}
6 changes: 6 additions & 0 deletions internal/endtoend/testdata/like_escape_core/sqlite/exec.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"command": "generate",
"env": {
"SQLCEXPERIMENT": "coreanalyzer"
}
}
31 changes: 31 additions & 0 deletions internal/endtoend/testdata/like_escape_core/sqlite/go/db.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading