Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

release-23.1: tree: apply functions to TEXT expressions compared with the @@ operator #99748

Closed
Closed
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
74 changes: 74 additions & 0 deletions pkg/sql/logictest/testdata/logic_test/tsvector
Original file line number Diff line number Diff line change
Expand Up @@ -360,3 +360,77 @@ LIMIT
2
----
0

subtest 98804_regression_test

statement ok
RESET default_text_search_config

statement ok
CREATE TABLE ab (a TEXT, b TEXT)

statement ok
INSERT INTO ab VALUES('fat rats', 'fat cats chased fat, out of shape rats');

query B
SELECT a @@ b FROM ab
----
false

query B
SELECT b @@ a FROM ab
----
true

query B
SELECT 'fat rats' @@ b FROM ab
----
false

query B
SELECT b @@ 'fat rats' FROM ab
----
true

query B
SELECT a @@ 'fat cats ate fat bats' FROM ab
----
false

query B
SELECT 'fat cats ate fat bats' @@ a FROM ab
----
false

statement error pq: unsupported comparison operator: <string> @@ <tsvector>
SELECT b @@ a::tsvector FROM ab

statement error pq: unsupported comparison operator: <tsvector> @@ <string>
SELECT a::tsvector @@ b FROM ab

query B
SELECT 'fat bat cat' @@ 'bats fats'
----
true

query B
SELECT 'bats fats' @@ 'fat bat cat'
----
false

statement error pq: unsupported comparison operator: <string> @@ <tsvector>
SELECT 'fat cats chased fat, out of shape rats' @@ 'fat rats'::tsvector

statement error pq: unsupported comparison operator: <tsvector> @@ <string>
SELECT 'fat rats'::tsvector @@ 'fat cats chased fat, out of shape rats'

query B
SELECT 'fat' @@ 'fat rats'::tsvector
----
true

query B
SELECT 'fat rats'::tsvector @@ 'fat'
----
true

2 changes: 1 addition & 1 deletion pkg/sql/sem/tree/overload.go
Original file line number Diff line number Diff line change
Expand Up @@ -997,7 +997,7 @@ func (s *overloadTypeChecker) typeCheckOverloadedExprs(
return err
}

// The fourth heuristic is to prefer candidates that accepts the "best"
// The fourth heuristic is to prefer candidates that accept the "best"
// mutual type in the resolvable type set of all constants.
if bestConstType, ok := commonConstantType(s.exprs, s.constIdxs); ok {
// In case all overloads are filtered out at this step,
Expand Down
79 changes: 70 additions & 9 deletions pkg/sql/sem/tree/type_check.go
Original file line number Diff line number Diff line change
Expand Up @@ -2204,6 +2204,38 @@ func typeCheckComparisonOp(
_, leftIsTuple := foldedLeft.(*Tuple)
_, rightIsTuple := foldedRight.(*Tuple)
_, rightIsSubquery := foldedRight.(SubqueryExpr)
var tsMatchesWithText bool
var typedLeft, typedRight TypedExpr
var leftFamily, rightFamily types.Family
var err error

// Do an initial check for TEXT @@ XXX special cases which might need to
// inject a to_tsvector or plainto_tsquery function call.
if op.Symbol == treecmp.TSMatches {
if switched {
// The order of operators matters as to which function call to apply.
foldedLeft, foldedRight = foldedRight, foldedLeft
switched = false
}
disallowSwitch = true
typedLeft, err = foldedLeft.TypeCheck(ctx, semaCtx, types.Any)
if err != nil {
sigWithErr := fmt.Sprintf(compExprsFmt, left, op, right, err)
return nil, nil, nil, false,
pgerror.Newf(pgcode.InvalidParameterValue, unsupportedCompErrFmt, sigWithErr)
}
typedRight, err = foldedRight.TypeCheck(ctx, semaCtx, types.Any)
if err != nil {
sigWithErr := fmt.Sprintf(compExprsFmt, left, op, right, err)
return nil, nil, nil, false,
pgerror.Newf(pgcode.InvalidParameterValue, unsupportedCompErrFmt, sigWithErr)
}
leftFamily = typedLeft.ResolvedType().Family()
rightFamily = typedRight.ResolvedType().Family()
if leftFamily == types.StringFamily || rightFamily == types.StringFamily {
tsMatchesWithText = true
}
}

handleTupleTypeMismatch := false
switch {
Expand All @@ -2227,7 +2259,7 @@ func typeCheckComparisonOp(
pgerror.Newf(pgcode.InvalidParameterValue, unsupportedCompErrFmt, sig)
}

typedLeft := typedSubExprs[0]
typedLeft = typedSubExprs[0]
typedSubExprs = typedSubExprs[1:]

rightTuple.typ = types.MakeTuple(make([]*types.T, len(typedSubExprs)))
Expand All @@ -2241,7 +2273,7 @@ func typeCheckComparisonOp(
return typedLeft, rightTuple, fn, false, nil

case foldedOp.Symbol == treecmp.In && rightIsSubquery:
typedLeft, err := foldedLeft.TypeCheck(ctx, semaCtx, types.Any)
typedLeft, err = foldedLeft.TypeCheck(ctx, semaCtx, types.Any)
if err != nil {
sigWithErr := fmt.Sprintf(compExprsFmt, left, op, right, err)
return nil, nil, nil, false,
Expand All @@ -2257,14 +2289,14 @@ func typeCheckComparisonOp(
}

desired := types.MakeTuple([]*types.T{typ})
typedRight, err := foldedRight.TypeCheck(ctx, semaCtx, desired)
typedRight, err = foldedRight.TypeCheck(ctx, semaCtx, desired)
if err != nil {
sigWithErr := fmt.Sprintf(compExprsFmt, left, op, right, err)
return nil, nil, nil, false,
pgerror.Newf(pgcode.InvalidParameterValue, unsupportedCompErrFmt, sigWithErr)
}

if err := typeCheckSubqueryWithIn(
if err = typeCheckSubqueryWithIn(
typedLeft.ResolvedType(), typedRight.ResolvedType(),
); err != nil {
return nil, nil, nil, false, err
Expand All @@ -2279,23 +2311,52 @@ func typeCheckComparisonOp(
pgerror.Newf(pgcode.InvalidParameterValue, unsupportedCompErrFmt, sig)
}
// Using non-folded left and right to avoid having to swap later.
typedLeft, typedRight, err := typeCheckTupleComparison(ctx, semaCtx, op, left.(*Tuple), right.(*Tuple))
typedLeft, typedRight, err = typeCheckTupleComparison(ctx, semaCtx, op, left.(*Tuple), right.(*Tuple))
if err != nil {
return nil, nil, nil, false, err
}
return typedLeft, typedRight, fn, false, nil

case leftIsTuple || rightIsTuple:
var errLeft, errRight error
// Tuple must compare with a tuple type, as handled above.
typedLeft, errLeft := foldedLeft.TypeCheck(ctx, semaCtx, types.Any)
typedRight, errRight := foldedRight.TypeCheck(ctx, semaCtx, types.Any)
typedLeft, errLeft = foldedLeft.TypeCheck(ctx, semaCtx, types.Any)
typedRight, errRight = foldedRight.TypeCheck(ctx, semaCtx, types.Any)
if errLeft == nil && errRight == nil &&
((typedLeft.ResolvedType().Family() == types.TupleFamily &&
typedRight.ResolvedType().Family() != types.TupleFamily) ||
(typedRight.ResolvedType().Family() == types.TupleFamily &&
typedLeft.ResolvedType().Family() != types.TupleFamily)) {
handleTupleTypeMismatch = true
}
case tsMatchesWithText:
// Apply rules from:
// https://www.postgresql.org/docs/current/textsearch-intro.html#TEXTSEARCH-MATCHING
// Perform the following type conversions:
// initial | result
// -------------------------------------------------------------------------
// a::TEXT @@ b::TEXT | to_tsvector(a) @@ plainto_tsquery(b)
// a::TEXT @@ b::TSQUERY | to_tsvector(a) @@ b
// a::TSQUERY @@ b::TEXT | a @@ to_tsvector(b)
if leftFamily == types.StringFamily {
if rightFamily == types.StringFamily || rightFamily == types.TSQueryFamily {
leftExprs := make(Exprs, 1)
leftExprs[0] = typedLeft
foldedLeft = &FuncExpr{Func: WrapFunction("to_tsvector"), Exprs: leftExprs, AggType: GeneralAgg}
}
}

funcName := "plainto_tsquery"
if rightFamily == types.StringFamily {
if leftFamily == types.StringFamily || leftFamily == types.TSQueryFamily {
if leftFamily == types.TSQueryFamily {
funcName = "to_tsvector"
}
rightExprs := make(Exprs, 1)
rightExprs[0] = typedRight
foldedRight = &FuncExpr{Func: WrapFunction(funcName), Exprs: rightExprs, AggType: GeneralAgg}
}
}
}

// For comparisons, we do not stimulate the typing of untyped NULL with the
Expand Down Expand Up @@ -2356,8 +2417,8 @@ func typeCheckComparisonOp(
}
leftReturn := leftExpr.ResolvedType()
rightReturn := rightExpr.ResolvedType()
leftFamily := leftReturn.Family()
rightFamily := rightReturn.Family()
leftFamily = leftReturn.Family()
rightFamily = rightReturn.Family()

// Return early if at least one overload is possible, NULL is an argument,
// and none of the overloads accept NULL.
Expand Down
5 changes: 5 additions & 0 deletions pkg/sql/sem/tree/type_check_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,11 @@ func TestTypeCheck(t *testing.T) {

// String preference.
{`st_geomfromgeojson($1)`, `st_geomfromgeojson($1:::STRING):::GEOMETRY`},

// TSQuery and TSVector
{`'a' @@ 'b'`, `to_tsvector('a':::STRING) @@ plainto_tsquery('b':::STRING)`},
{`'a' @@ 'b':::TSQUERY`, `to_tsvector('a':::STRING) @@ '''b''':::TSQUERY`},
{`'a':::TSQUERY @@ 'b'`, `'''a''':::TSQUERY @@ to_tsvector('b':::STRING)`},
}
ctx := context.Background()
for _, d := range testData {
Expand Down