Skip to content

Commit 5da9081

Browse files
authored
add is distinct and not distinct from exprs (#2494)
1 parent a42c3e4 commit 5da9081

19 files changed

Lines changed: 637 additions & 43 deletions

postgres/parser/parser/sql.y

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4523,7 +4523,7 @@ create_procedure_option_list:
45234523
}
45244524

45254525
create_procedure_option:
4526-
LANGUAGE name
4526+
LANGUAGE non_reserved_word_or_sconst
45274527
{
45284528
$$.val = tree.RoutineOption{OptionType: tree.OptionLanguage, Language: $2}
45294529
}
@@ -9031,9 +9031,21 @@ view_option:
90319031
| SECURITY_BARRIER { $$.val = tree.ViewOption{Name: $1, Security: false} }
90329032
| SECURITY_BARRIER '=' TRUE { $$.val = tree.ViewOption{Name: $1, Security: true} }
90339033
| SECURITY_BARRIER '=' FALSE { $$.val = tree.ViewOption{Name: $1, Security: false} }
9034+
| SECURITY_BARRIER '=' non_reserved_word_or_sconst
9035+
{
9036+
v, err := tree.GetBoolFromString("security_barrier", $3)
9037+
if err != nil { return setErr(sqllex, err) }
9038+
$$.val = tree.ViewOption{Name: $1, Security: v}
9039+
}
90349040
| SECURITY_INVOKER { $$.val = tree.ViewOption{Name: $1, Security: false} }
90359041
| SECURITY_INVOKER '=' TRUE { $$.val = tree.ViewOption{Name: $1, Security: true} }
90369042
| SECURITY_INVOKER '=' FALSE { $$.val = tree.ViewOption{Name: $1, Security: false} }
9043+
| SECURITY_INVOKER '=' non_reserved_word_or_sconst
9044+
{
9045+
v, err := tree.GetBoolFromString("security_invoker", $3)
9046+
if err != nil { return setErr(sqllex, err) }
9047+
$$.val = tree.ViewOption{Name: $1, Security: v}
9048+
}
90379049

90389050
create_materialized_view_stmt:
90399051
CREATE MATERIALIZED VIEW view_name opt_column_list opt_using_method opt_with_storage_parameter_list opt_tablespace AS select_stmt opt_create_as_with_data

postgres/parser/sem/tree/create_view.go

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,11 @@
3333

3434
package tree
3535

36-
import "strings"
36+
import (
37+
"strings"
38+
39+
"github.com/cockroachdb/errors"
40+
)
3741

3842
var _ Statement = &CreateView{}
3943

@@ -112,3 +116,16 @@ func (node ViewOptions) Format(ctx *FmtCtx) {
112116
}
113117
ctx.WriteString(" )")
114118
}
119+
120+
// GetBoolFromString returns bool value parsed from given string.
121+
// E.g. "fal" is accepted as false.
122+
func GetBoolFromString(option, s string) (bool, error) {
123+
switch strings.ToLower(s) {
124+
case "true", "tru", "tr", "t", "yes", "ye", "y", "on", "1":
125+
return true, nil
126+
case "false", "fals", "fal", "fa", "f", "no", "n", "off", "of", "0":
127+
return false, nil
128+
default:
129+
return false, errors.New(`invalid value for boolean option "` + option + `": ` + s)
130+
}
131+
}

server/analyzer/resolve_type.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,8 @@ func ResolveTypeForNodes(ctx *sql.Context, a *analyzer.Analyzer, node sql.Node,
124124
if !arg.Type.IsResolvedType() {
125125
dt, err := resolveType(ctx, db, arg.Type)
126126
if err != nil {
127-
return nil, transform.NewTree, err
127+
// the type can be non-existing type
128+
continue
128129
}
129130
same = transform.NewTree
130131
r.Args[j].Type = dt
@@ -138,7 +139,8 @@ func ResolveTypeForNodes(ctx *sql.Context, a *analyzer.Analyzer, node sql.Node,
138139
if !arg.Type.IsResolvedType() {
139140
dt, err := resolveType(ctx, db, arg.Type)
140141
if err != nil {
141-
return nil, transform.NewTree, err
142+
// the type can be non-existing type
143+
continue
142144
}
143145
same = transform.NewTree
144146
r.Args[j].Type = dt

server/ast/create_extension.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,11 @@ import (
2525
// nodeCreateExtension handles *tree.CreateExtension nodes.
2626
func nodeCreateExtension(ctx *Context, node *tree.CreateExtension) (vitess.Statement, error) {
2727
if len(node.Schema) > 0 {
28-
return NotYetSupportedError("SCHEMA is not yet supported")
28+
if node.Schema == "pg_catalog" && node.Name == "plpgsql" {
29+
return nil, nil
30+
} else {
31+
return NotYetSupportedError("SCHEMA is not yet supported")
32+
}
2933
}
3034
if len(node.Version) > 0 {
3135
return NotYetSupportedError("VERSION is not yet supported")

server/ast/expr.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -421,21 +421,27 @@ func nodeExpr(ctx *Context, node tree.Expr) (vitess.Expr, error) {
421421
case tree.SimilarTo:
422422
return nil, errors.Errorf("similar to is not yet supported")
423423
case tree.NotSimilarTo:
424-
return nil, errors.Errorf("similar to is not yet supported")
424+
return nil, errors.Errorf("not similar to is not yet supported")
425425
case tree.RegMatch:
426426
operator = vitess.RegexpStr
427427
case tree.NotRegMatch:
428428
operator = vitess.NotRegexpStr
429429
case tree.RegIMatch:
430430
return nil, errors.Errorf("~* is not yet supported")
431431
case tree.NotRegIMatch:
432-
return nil, errors.Errorf("~* is not yet supported")
432+
return nil, errors.Errorf("!~* is not yet supported")
433433
case tree.TextSearchMatch:
434434
return nil, errors.Errorf("@@ is not yet supported")
435435
case tree.IsDistinctFrom:
436-
return nil, errors.Errorf("IS DISTINCT FROM is not yet supported")
436+
return vitess.InjectedExpr{
437+
Expression: pgexprs.NewIsDistinctFrom(),
438+
Children: vitess.Exprs{left, right},
439+
}, nil
437440
case tree.IsNotDistinctFrom:
438-
return nil, errors.Errorf("IS NOT DISTINCT FROM is not yet supported")
441+
return vitess.InjectedExpr{
442+
Expression: pgexprs.NewIsNotDistinctFrom(),
443+
Children: vitess.Exprs{left, right},
444+
}, nil
439445
case tree.Contains:
440446
return vitess.InjectedExpr{
441447
Expression: pgexprs.NewBinaryOperator(framework.Operator_BinaryJSONContainsRight),
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
// Copyright 2026 Dolthub, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package expression
16+
17+
import (
18+
"github.com/cockroachdb/errors"
19+
"github.com/dolthub/go-mysql-server/sql"
20+
"github.com/dolthub/go-mysql-server/sql/expression"
21+
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
22+
23+
"github.com/dolthub/doltgresql/server/functions/framework"
24+
25+
pgtypes "github.com/dolthub/doltgresql/server/types"
26+
)
27+
28+
// IsDistinctFrom represents IS DISTINCT FROM expression.
29+
type IsDistinctFrom struct {
30+
leftExpr sql.Expression
31+
rightExpr sql.Expression
32+
staticLeftLiteral *expression.Literal
33+
staticRightLiteral *expression.Literal
34+
notEqualFunc *framework.CompiledFunction
35+
}
36+
37+
var _ vitess.Injectable = (*IsDistinctFrom)(nil)
38+
var _ sql.Expression = (*IsDistinctFrom)(nil)
39+
40+
// NewIsDistinctFrom returns a new *IsDistinctFrom.
41+
func NewIsDistinctFrom() *IsDistinctFrom {
42+
return &IsDistinctFrom{
43+
leftExpr: nil,
44+
rightExpr: nil,
45+
}
46+
}
47+
48+
// Children implements the sql.Expression interface.
49+
func (n *IsDistinctFrom) Children() []sql.Expression {
50+
return []sql.Expression{n.leftExpr, n.rightExpr}
51+
}
52+
53+
// Eval implements the sql.Expression interface.
54+
func (n *IsDistinctFrom) Eval(ctx *sql.Context, row sql.Row) (any, error) {
55+
left, err := n.leftExpr.Eval(ctx, row)
56+
if err != nil {
57+
return nil, err
58+
}
59+
right, err := n.rightExpr.Eval(ctx, row)
60+
if err != nil {
61+
return nil, err
62+
}
63+
64+
if left == nil && right == nil {
65+
return false, nil
66+
} else if left == nil || right == nil {
67+
return true, nil
68+
}
69+
70+
n.staticLeftLiteral.Val = left
71+
n.staticRightLiteral.Val = right
72+
73+
if n.notEqualFunc == nil {
74+
return nil, errors.Errorf("input types do not match: %s %s", n.leftExpr.Type().String(), n.rightExpr.Type().String())
75+
}
76+
return n.notEqualFunc.Eval(ctx, row)
77+
}
78+
79+
// IsNullable implements the sql.Expression interface.
80+
func (n *IsDistinctFrom) IsNullable() bool {
81+
return true
82+
}
83+
84+
// Resolved implements the sql.Expression interface.
85+
func (n *IsDistinctFrom) Resolved() bool {
86+
if n.leftExpr == nil || n.rightExpr == nil {
87+
return false
88+
}
89+
return n.leftExpr.Resolved() && n.rightExpr.Resolved()
90+
}
91+
92+
// String implements the sql.Expression interface.
93+
func (n *IsDistinctFrom) String() string {
94+
return n.leftExpr.String() + " IS DISTINCT FROM " + n.rightExpr.String()
95+
}
96+
97+
// Type implements the sql.Expression interface.
98+
func (n *IsDistinctFrom) Type() sql.Type {
99+
return pgtypes.Bool
100+
}
101+
102+
// WithChildren implements the sql.Expression interface.
103+
func (n *IsDistinctFrom) WithChildren(children ...sql.Expression) (sql.Expression, error) {
104+
if len(children) != 2 {
105+
return nil, sql.ErrInvalidChildrenNumber.New(n, len(children), 2)
106+
}
107+
108+
// This allows evaluating the arguments separate from function.Eval() in order to resolve NULL values.
109+
// This follows the same logic as InTuple expression.
110+
allValidChildren := true
111+
leftType, ok := children[0].Type().(*pgtypes.DoltgresType)
112+
if !ok {
113+
allValidChildren = false
114+
}
115+
rightType, ok := children[1].Type().(*pgtypes.DoltgresType)
116+
if !ok {
117+
allValidChildren = false
118+
}
119+
staticLeftLiteral := expression.NewLiteral(nil, leftType)
120+
staticRightLiteral := expression.NewLiteral(nil, rightType)
121+
122+
if allValidChildren {
123+
cf := framework.GetBinaryFunction(framework.Operator_BinaryNotEqual).Compile("internal_binary_operator_func_<>", staticLeftLiteral, staticRightLiteral)
124+
return &IsDistinctFrom{
125+
leftExpr: children[0],
126+
rightExpr: children[1],
127+
staticLeftLiteral: staticLeftLiteral,
128+
staticRightLiteral: staticRightLiteral,
129+
notEqualFunc: cf,
130+
}, nil
131+
}
132+
133+
return &IsDistinctFrom{
134+
leftExpr: children[0],
135+
rightExpr: children[1],
136+
}, nil
137+
}
138+
139+
// WithResolvedChildren implements the vitess.InjectableExpression interface.
140+
func (n *IsDistinctFrom) WithResolvedChildren(children []any) (any, error) {
141+
if len(children) != 2 {
142+
return nil, errors.Errorf("invalid vitess child count, expected `2` but got `%d`", len(children))
143+
}
144+
left, ok := children[0].(sql.Expression)
145+
if !ok {
146+
return nil, errors.Errorf("expected vitess child to be an expression but has type `%T`", children[0])
147+
}
148+
right, ok := children[1].(sql.Expression)
149+
if !ok {
150+
return nil, errors.Errorf("expected vitess child to be an expression but has type `%T`", children[1])
151+
}
152+
153+
return n.WithChildren(left, right)
154+
}

0 commit comments

Comments
 (0)