Skip to content

Commit

Permalink
Merge pull request #100918 from msirek/backport23.1-99583-100704
Browse files Browse the repository at this point in the history
release-23.1: tree: apply functions to TEXT expressions compared with the @@ operator
  • Loading branch information
Mark Sirek authored Apr 7, 2023
2 parents 508ee2c + d2e3c38 commit 7a8b969
Show file tree
Hide file tree
Showing 4 changed files with 155 additions and 10 deletions.
75 changes: 75 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,78 @@ 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 pgcode 22023 unsupported comparison operator: <string> @@ <tsvector>
SELECT b @@ a::tsvector FROM ab

statement error pgcode 22023 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

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

# TODO(#75101): The error should be "syntax error in TSQuery".
statement error pgcode 22023 unsupported comparison operator: <string> @@ <tsvector>
SELECT 'fat cats chased fat, out of shape rats' @@ 'fat rats'::tsvector

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

# TODO(#75101): The error should be "syntax error in TSQuery".
statement error pgcode 22023 unsupported comparison operator: <tsvector> @@ <string>
SELECT 'fat rats'::tsvector @@ 'fat cats chased fat, out of shape rats'
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
81 changes: 72 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,54 @@ 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)
// a::TSVECTOR @@ b::TEXT | a @@ b::TSQUERY
// a::TEXT @@ b::TSVECTOR | a::TSQUERY @@ 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 +2419,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
7 changes: 7 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,13 @@ 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)`},
{`'a' @@ 'b':::TSVECTOR`, `'''a''':::TSQUERY @@ '''b''':::TSVECTOR`},
{`'a':::TSVECTOR @@ 'b'`, `'''a''':::TSVECTOR @@ '''b''':::TSQUERY`},
}
ctx := context.Background()
for _, d := range testData {
Expand Down

0 comments on commit 7a8b969

Please sign in to comment.