Skip to content

Commit 722a545

Browse files
committed
Adding the tuple type
1 parent 8d53477 commit 722a545

File tree

16 files changed

+892
-30
lines changed

16 files changed

+892
-30
lines changed

server/ast/expr.go

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -373,9 +373,15 @@ func nodeExpr(ctx *Context, node tree.Expr) (vitess.Expr, error) {
373373
Expression: pgexprs.NewInSubquery(),
374374
Children: vitess.Exprs{left, right},
375375
}, nil
376-
default:
377-
return nil, errors.Errorf("right side of IN expression is not a tuple or subquery, got %T", right)
376+
case vitess.InjectedExpr:
377+
if _, ok := right.Expression.(*pgexprs.TupleExpr); ok {
378+
return vitess.InjectedExpr{
379+
Expression: pgexprs.NewInTuple(),
380+
Children: vitess.Exprs{left, vitess.ValTuple(right.Children)},
381+
}, nil
382+
}
378383
}
384+
return nil, errors.Errorf("right side of IN expression is not a tuple or subquery, got %T", right)
379385
case tree.NotIn:
380386
innerExpr := vitess.InjectedExpr{
381387
Expression: pgexprs.NewInTuple(),
@@ -776,15 +782,16 @@ func nodeExpr(ctx *Context, node tree.Expr) (vitess.Expr, error) {
776782
if len(node.Labels) > 0 {
777783
return nil, errors.Errorf("tuple labels are not yet supported")
778784
}
779-
if node.Row {
780-
return nil, errors.Errorf("ROW keyword for tuples not yet supported")
781-
}
782785

783786
valTuple, err := nodeExprs(ctx, node.Exprs)
784787
if err != nil {
785788
return nil, err
786789
}
787-
return vitess.ValTuple(valTuple), nil
790+
791+
return vitess.InjectedExpr{
792+
Expression: pgexprs.NewTuple(),
793+
Children: valTuple,
794+
}, nil
788795
case *tree.TupleStar:
789796
return nil, errors.Errorf("(E).* is not yet supported")
790797
case *tree.UnaryExpr:

server/expression/in_tuple.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -242,11 +242,15 @@ func (it *InTuple) WithResolvedChildren(children []any) (any, error) {
242242
if !ok {
243243
return nil, errors.Errorf("expected vitess child to be an expression but has type `%T`", children[0])
244244
}
245-
right, ok := children[1].(expression.Tuple)
245+
right, ok := children[1].(*TupleExpr)
246246
if !ok {
247-
return nil, errors.Errorf("expected vitess child to be an expression tuple but has type `%T`", children[1])
247+
return nil, errors.Errorf("expected child to be a TupleExpr but has type `%T`", children[1])
248248
}
249-
return it.WithChildren(left, right)
249+
250+
// TODO: Fixing tests by converting the Doltgres TupleExpr back into a GMS expression.Tuple, but we should
251+
// really change InTuple to work with TupleExpr instead of expression.Tuple if we're going to have
252+
// a tuple implementation in the Doltgres layer.
253+
return it.WithChildren(left, expression.Tuple(right.exprs))
250254
}
251255

252256
// Left implements the expression.BinaryExpression interface.

server/expression/tuple.go

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
// Copyright 2025 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+
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
21+
22+
pgtypes "github.com/dolthub/doltgresql/server/types"
23+
)
24+
25+
// NewTuple creates a new tuple expression.
26+
func NewTuple() *TupleExpr {
27+
// Initialize the tuple expression with the generic Tuple type. When the analyzer
28+
// resolves the children of the InjectedExpr and sets them by calling
29+
// WithResolvedChildren(), the type will be updated with the exact field types.
30+
return &TupleExpr{
31+
typ: pgtypes.Tuple,
32+
}
33+
}
34+
35+
// TupleExpr is a set of sql.Expressions wrapped together in a single value.
36+
type TupleExpr struct {
37+
exprs []sql.Expression
38+
typ *pgtypes.DoltgresType
39+
}
40+
41+
var _ sql.Expression = (*TupleExpr)(nil)
42+
var _ vitess.Injectable = (*TupleExpr)(nil)
43+
44+
// Resolved implements the sql.Expression interface.
45+
func (t *TupleExpr) Resolved() bool {
46+
for _, expr := range t.exprs {
47+
if !expr.Resolved() {
48+
return false
49+
}
50+
}
51+
return true
52+
}
53+
54+
// String implements the sql.Expression interface.
55+
func (t *TupleExpr) String() string {
56+
return "TUPLE EXPR"
57+
}
58+
59+
// Type implements the sql.Expression interface.
60+
func (t *TupleExpr) Type() sql.Type {
61+
return t.typ
62+
}
63+
64+
// IsNullable implements the sql.Expression interface.
65+
func (t *TupleExpr) IsNullable() bool {
66+
return false
67+
}
68+
69+
// Eval implements the sql.Expression interface.
70+
func (t *TupleExpr) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) {
71+
vals := make([]interface{}, len(t.exprs))
72+
for i, expr := range t.exprs {
73+
val, err := expr.Eval(ctx, row)
74+
if err != nil {
75+
return nil, err
76+
}
77+
vals[i] = val
78+
}
79+
80+
return vals, nil
81+
}
82+
83+
// Children implements the sql.Expression interface.
84+
func (t *TupleExpr) Children() []sql.Expression {
85+
return t.exprs
86+
}
87+
88+
// WithChildren implements the sql.Expression interface.
89+
func (t *TupleExpr) WithChildren(children ...sql.Expression) (sql.Expression, error) {
90+
tCopy := *t
91+
tCopy.exprs = children
92+
return &tCopy, nil
93+
}
94+
95+
// WithResolvedChildren implements the vitess.Injectable interface
96+
func (t *TupleExpr) WithResolvedChildren(children []any) (any, error) {
97+
newExpressions := make([]sql.Expression, len(children))
98+
for i, resolvedChild := range children {
99+
resolvedExpression, ok := resolvedChild.(sql.Expression)
100+
if !ok {
101+
return nil, errors.Errorf("expected vitess child to be an expression but has type `%T`", resolvedChild)
102+
}
103+
newExpressions[i] = resolvedExpression
104+
}
105+
newTuple, err := t.WithChildren(newExpressions...)
106+
if err != nil {
107+
return nil, err
108+
}
109+
110+
fieldTypes := make([]sql.Type, len(newExpressions))
111+
for i, expr := range newExpressions {
112+
doltgresType, ok := expr.Type().(sql.Type)
113+
if !ok {
114+
return nil, errors.Errorf("expected expression type to be a sql.Type implementation but has type `%T`", expr.Type())
115+
}
116+
fieldTypes[i] = doltgresType
117+
}
118+
119+
newTuple.(*TupleExpr).typ = pgtypes.CreateTupleTypeFromFieldTypes(fieldTypes)
120+
return newTuple, err
121+
}

server/functions/binary/equal.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ func initBinaryEqual() {
6161
framework.RegisterBinaryFunction(framework.Operator_BinaryEqual, oideq)
6262
framework.RegisterBinaryFunction(framework.Operator_BinaryEqual, texteqname)
6363
framework.RegisterBinaryFunction(framework.Operator_BinaryEqual, text_eq)
64+
framework.RegisterBinaryFunction(framework.Operator_BinaryEqual, tuple_eq)
6465
framework.RegisterBinaryFunction(framework.Operator_BinaryEqual, time_eq)
6566
framework.RegisterBinaryFunction(framework.Operator_BinaryEqual, timestamp_eq_date)
6667
framework.RegisterBinaryFunction(framework.Operator_BinaryEqual, timestamp_eq)
@@ -423,6 +424,25 @@ var text_eq = framework.Function2{
423424
},
424425
}
425426

427+
// tuple_eq represents the PostgreSQL function of the same name, taking the same parameters.
428+
var tuple_eq = framework.Function2{
429+
Name: "tuple_eq",
430+
Return: pgtypes.Bool,
431+
Parameters: [2]*pgtypes.DoltgresType{pgtypes.Tuple, pgtypes.Tuple},
432+
Strict: true,
433+
Callable: func(ctx *sql.Context, _ [3]*pgtypes.DoltgresType, val1 any, val2 any) (any, error) {
434+
if err := pgtypes.ValidateEqualTupleFieldCount(val1, val2); err != nil {
435+
return nil, err
436+
}
437+
// tuples can only be compared for equality if there are no NULL values
438+
if pgtypes.TupleValueHasNull(val1) || pgtypes.TupleValueHasNull(val2) {
439+
return nil, nil
440+
}
441+
res, err := pgtypes.Tuple.Compare(ctx, val1, val2)
442+
return res == 0, err
443+
},
444+
}
445+
426446
// time_eq represents the PostgreSQL function of the same name, taking the same parameters.
427447
var time_eq = framework.Function2{
428448
Name: "time_eq",

server/functions/binary/greater.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ func initBinaryGreaterThan() {
7070
framework.RegisterBinaryFunction(framework.Operator_BinaryGreaterThan, timestamptz_gt_timestamp)
7171
framework.RegisterBinaryFunction(framework.Operator_BinaryGreaterThan, timestamptz_gt)
7272
framework.RegisterBinaryFunction(framework.Operator_BinaryGreaterThan, timetz_gt)
73+
framework.RegisterBinaryFunction(framework.Operator_BinaryGreaterThan, tuple_gt)
7374
framework.RegisterBinaryFunction(framework.Operator_BinaryGreaterThan, uuid_gt)
7475
}
7576

@@ -517,6 +518,24 @@ var timetz_gt = framework.Function2{
517518
},
518519
}
519520

521+
// tuple_gt represents the PostgreSQL function of the same name, taking the same parameters.
522+
var tuple_gt = framework.Function2{
523+
Name: "tuple_gt",
524+
Return: pgtypes.Bool,
525+
Parameters: [2]*pgtypes.DoltgresType{pgtypes.Tuple, pgtypes.Tuple},
526+
Strict: true,
527+
Callable: func(ctx *sql.Context, _ [3]*pgtypes.DoltgresType, val1 any, val2 any) (any, error) {
528+
if err := pgtypes.ValidateEqualTupleFieldCount(val1, val2); err != nil {
529+
return nil, err
530+
}
531+
if !pgtypes.CanCompareTupleValues(val1, val2) {
532+
return nil, nil
533+
}
534+
res, err := pgtypes.Tuple.Compare(ctx, val1, val2)
535+
return res == 1, err
536+
},
537+
}
538+
520539
// uuid_gt represents the PostgreSQL function of the same name, taking the same parameters.
521540
var uuid_gt = framework.Function2{
522541
Name: "uuid_gt",

server/functions/binary/greater_equal.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ func initBinaryGreaterOrEqual() {
7070
framework.RegisterBinaryFunction(framework.Operator_BinaryGreaterOrEqual, timestamptz_ge_timestamp)
7171
framework.RegisterBinaryFunction(framework.Operator_BinaryGreaterOrEqual, timestamptz_ge)
7272
framework.RegisterBinaryFunction(framework.Operator_BinaryGreaterOrEqual, timetz_ge)
73+
framework.RegisterBinaryFunction(framework.Operator_BinaryGreaterOrEqual, tuple_ge)
7374
framework.RegisterBinaryFunction(framework.Operator_BinaryGreaterOrEqual, uuid_ge)
7475
}
7576

@@ -517,6 +518,24 @@ var timetz_ge = framework.Function2{
517518
},
518519
}
519520

521+
// tuple_ge represents the PostgreSQL function of the same name, taking the same parameters.
522+
var tuple_ge = framework.Function2{
523+
Name: "tuple_ge",
524+
Return: pgtypes.Bool,
525+
Parameters: [2]*pgtypes.DoltgresType{pgtypes.Tuple, pgtypes.Tuple},
526+
Strict: true,
527+
Callable: func(ctx *sql.Context, _ [3]*pgtypes.DoltgresType, val1 any, val2 any) (any, error) {
528+
if err := pgtypes.ValidateEqualTupleFieldCount(val1, val2); err != nil {
529+
return nil, err
530+
}
531+
if !pgtypes.CanCompareTupleValues(val1, val2) {
532+
return nil, nil
533+
}
534+
res, err := pgtypes.Tuple.Compare(ctx, val1, val2)
535+
return res >= 0, err
536+
},
537+
}
538+
520539
// uuid_ge represents the PostgreSQL function of the same name, taking the same parameters.
521540
var uuid_ge = framework.Function2{
522541
Name: "uuid_ge",

server/functions/binary/less.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ func initBinaryLessThan() {
7070
framework.RegisterBinaryFunction(framework.Operator_BinaryLessThan, timestamptz_lt_timestamp)
7171
framework.RegisterBinaryFunction(framework.Operator_BinaryLessThan, timestamptz_lt)
7272
framework.RegisterBinaryFunction(framework.Operator_BinaryLessThan, timetz_lt)
73+
framework.RegisterBinaryFunction(framework.Operator_BinaryLessThan, tuple_lt)
7374
framework.RegisterBinaryFunction(framework.Operator_BinaryLessThan, uuid_lt)
7475
}
7576

@@ -517,6 +518,24 @@ var timetz_lt = framework.Function2{
517518
},
518519
}
519520

521+
// tuple_lt represents the PostgreSQL function of the same name, taking the same parameters.
522+
var tuple_lt = framework.Function2{
523+
Name: "tuple_lt",
524+
Return: pgtypes.Bool,
525+
Parameters: [2]*pgtypes.DoltgresType{pgtypes.Tuple, pgtypes.Tuple},
526+
Strict: true,
527+
Callable: func(ctx *sql.Context, _ [3]*pgtypes.DoltgresType, val1 any, val2 any) (any, error) {
528+
if err := pgtypes.ValidateEqualTupleFieldCount(val1, val2); err != nil {
529+
return nil, err
530+
}
531+
if !pgtypes.CanCompareTupleValues(val1, val2) {
532+
return nil, nil
533+
}
534+
res, err := pgtypes.Tuple.Compare(ctx, val1, val2)
535+
return res == -1, err
536+
},
537+
}
538+
520539
// uuid_lt represents the PostgreSQL function of the same name, taking the same parameters.
521540
var uuid_lt = framework.Function2{
522541
Name: "uuid_lt",

server/functions/binary/less_equal.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ func initBinaryLessOrEqual() {
7070
framework.RegisterBinaryFunction(framework.Operator_BinaryLessOrEqual, timestamptz_le_timestamp)
7171
framework.RegisterBinaryFunction(framework.Operator_BinaryLessOrEqual, timestamptz_le)
7272
framework.RegisterBinaryFunction(framework.Operator_BinaryLessOrEqual, timetz_le)
73+
framework.RegisterBinaryFunction(framework.Operator_BinaryLessOrEqual, tuple_le)
7374
framework.RegisterBinaryFunction(framework.Operator_BinaryLessOrEqual, uuid_le)
7475
}
7576

@@ -517,6 +518,24 @@ var timetz_le = framework.Function2{
517518
},
518519
}
519520

521+
// tuple_le represents the PostgreSQL function of the same name, taking the same parameters.
522+
var tuple_le = framework.Function2{
523+
Name: "tuple_le",
524+
Return: pgtypes.Bool,
525+
Parameters: [2]*pgtypes.DoltgresType{pgtypes.Tuple, pgtypes.Tuple},
526+
Strict: true,
527+
Callable: func(ctx *sql.Context, _ [3]*pgtypes.DoltgresType, val1 any, val2 any) (any, error) {
528+
if err := pgtypes.ValidateEqualTupleFieldCount(val1, val2); err != nil {
529+
return nil, err
530+
}
531+
if !pgtypes.CanCompareTupleValues(val1, val2) {
532+
return nil, nil
533+
}
534+
res, err := pgtypes.Tuple.Compare(ctx, val1, val2)
535+
return res <= 0, err
536+
},
537+
}
538+
520539
// uuid_le represents the PostgreSQL function of the same name, taking the same parameters.
521540
var uuid_le = framework.Function2{
522541
Name: "uuid_le",

server/functions/binary/not_equal.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ func initBinaryNotEqual() {
6969
framework.RegisterBinaryFunction(framework.Operator_BinaryNotEqual, timestamptz_ne_timestamp)
7070
framework.RegisterBinaryFunction(framework.Operator_BinaryNotEqual, timestamptz_ne)
7171
framework.RegisterBinaryFunction(framework.Operator_BinaryNotEqual, timetz_ne)
72+
framework.RegisterBinaryFunction(framework.Operator_BinaryNotEqual, tuple_ne)
7273
framework.RegisterBinaryFunction(framework.Operator_BinaryNotEqual, uuid_ne)
7374
framework.RegisterBinaryFunction(framework.Operator_BinaryNotEqual, xidneqint4)
7475
framework.RegisterBinaryFunction(framework.Operator_BinaryNotEqual, xidneq)
@@ -518,6 +519,24 @@ var timetz_ne = framework.Function2{
518519
},
519520
}
520521

522+
// tuple_ne represents the PostgreSQL function of the same name, taking the same parameters.
523+
var tuple_ne = framework.Function2{
524+
Name: "tuple_ne",
525+
Return: pgtypes.Bool,
526+
Parameters: [2]*pgtypes.DoltgresType{pgtypes.Tuple, pgtypes.Tuple},
527+
Strict: true,
528+
Callable: func(ctx *sql.Context, _ [3]*pgtypes.DoltgresType, val1 any, val2 any) (any, error) {
529+
if err := pgtypes.ValidateEqualTupleFieldCount(val1, val2); err != nil {
530+
return nil, err
531+
}
532+
if !pgtypes.CanCompareTupleValuesForNotEquals(val1, val2) {
533+
return nil, nil
534+
}
535+
res, err := pgtypes.Tuple.Compare(ctx, val1, val2)
536+
return res != 0, err
537+
},
538+
}
539+
521540
// uuid_ne represents the PostgreSQL function of the same name, taking the same parameters.
522541
var uuid_ne = framework.Function2{
523542
Name: "uuid_ne",

server/functions/init.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ func Init() {
178178
initTranslate()
179179
initTrimScale()
180180
initTrunc()
181+
initTuple()
181182
initTxidCurrent()
182183
initUnnest()
183184
initUpper()

0 commit comments

Comments
 (0)