chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:32:25 +08:00
commit e014feafe1
2285 changed files with 1131979 additions and 0 deletions
+374
View File
@@ -0,0 +1,374 @@
// Copyright 2024 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package expression
import (
"context"
"fmt"
"github.com/cockroachdb/errors"
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/go-mysql-server/sql/expression"
"github.com/dolthub/go-mysql-server/sql/plan"
"github.com/dolthub/doltgresql/server/functions/framework"
pgtypes "github.com/dolthub/doltgresql/server/types"
)
// AnyExpr represents the ANY/SOME expression.
type AnyExpr struct {
leftExpr sql.Expression
rightExpr sql.Expression
subOperator string
name string // ANY or SOME
subqueryAnyExpr *subqueryAnyExpr
expressionAnyExpr *expressionAnyExpr
}
// subqueryAnyExpr represents the resolved comparison functions for a plan.Subquery.
type subqueryAnyExpr struct {
rightSub *plan.Subquery
staticLiteral *expression.Literal
arrayLiterals []*expression.Literal
compFuncs []framework.Function
}
// expressionAnyExpr represents the resolved comparison function for a sql.Expression.
type expressionAnyExpr struct {
rightExpr sql.Expression
staticLiteral *expression.Literal
arrayLiteral *expression.Literal
compFunc framework.Function
}
// NewAnyExpr creates a new AnyExpr expression.
func NewAnyExpr(subOperator string) *AnyExpr {
return &AnyExpr{
leftExpr: nil,
rightExpr: nil,
subOperator: subOperator,
name: "ANY",
}
}
// Children implements the Expression interface.
func (a *AnyExpr) Children() []sql.Expression {
return []sql.Expression{a.leftExpr, a.rightExpr}
}
// Resolved implements the Expression interface.
func (a *AnyExpr) Resolved() bool {
if a.leftExpr == nil || !a.leftExpr.Resolved() || a.rightExpr == nil || !a.rightExpr.Resolved() {
return false
}
if a.subqueryAnyExpr != nil {
return a.subqueryAnyExpr.resolved()
}
if a.expressionAnyExpr != nil {
return a.expressionAnyExpr.resolved()
}
return true
}
// IsNullable implements the Expression interface.
func (a *AnyExpr) IsNullable(ctx *sql.Context) bool {
return a.leftExpr.IsNullable(ctx) || a.rightExpr.IsNullable(ctx)
}
// Type implements the Expression interface.
func (a *AnyExpr) Type(ctx *sql.Context) sql.Type {
return pgtypes.Bool
}
// resolved checks if the comparison functions for subqueryAnyExpr is resolved.
func (a *subqueryAnyExpr) resolved() bool {
if len(a.compFuncs) == 0 {
return false
}
for _, compFunc := range a.compFuncs {
if !compFunc.Resolved() {
return false
}
}
return true
}
// eval evaluates the comparison functions for subqueryAnyExpr.
func (a *subqueryAnyExpr) eval(ctx *sql.Context, subOperator string, row sql.Row, left interface{}) (interface{}, error) {
if len(a.compFuncs) == 0 {
return nil, errors.Errorf("%T: cannot Eval as it has not been fully resolved", a)
}
// TODO: This sometimes panics in `evalMultiple` for subqueries that return
// more than one row, when len(row) > len(iter.Next())
rightValues, err := a.rightSub.EvalMultiple(ctx, row)
if err != nil {
return nil, err
}
if len(rightValues) == 0 {
return nil, nil
}
// TODO: This is a workaround some subqueries where the schema length does not
// match the row length
if len(a.arrayLiterals) == 1 && len(rightValues) != 1 {
op, err := framework.GetOperatorFromString(subOperator)
if err != nil {
return nil, err
}
for i := len(a.arrayLiterals); i < len(rightValues); i++ {
arrayLiteral := expression.NewLiteral(nil, a.arrayLiterals[0].Type(ctx))
a.arrayLiterals = append(a.arrayLiterals, arrayLiteral)
compFunc := framework.GetBinaryFunction(op).Compile(ctx, "internal_any_comparison", a.staticLiteral, a.arrayLiterals[i])
a.compFuncs = append(a.compFuncs, compFunc)
}
}
if len(a.arrayLiterals) != len(rightValues) {
return nil, errors.Errorf("%T: expected right child to return `%d` values but returned `%d`", a, len(a.arrayLiterals), len(rightValues))
}
// Next we'll assign our evaluated values to the expressions that the comparison functions reference
// Note that the compiled function has a reference to the staticLiteral and arrayLiterals, so we must alter them in place
a.staticLiteral.Val = left
for i, rightValue := range rightValues {
a.arrayLiterals[i].Val = rightValue
}
// Now we can loop over all comparison functions, as they'll reference their respective values
for _, compFunc := range a.compFuncs {
result, err := compFunc.Eval(ctx, row)
if err != nil {
return nil, err
}
if result.(bool) {
return true, nil
}
}
return false, nil
}
// resolved checks if the comparison function for expressionAnyExpr is resolved.
func (a *expressionAnyExpr) resolved() bool {
if a.compFunc == nil || !a.compFunc.Resolved() {
return false
}
return true
}
// eval evaluates the comparison function for expressionAnyExpr.
func (a *expressionAnyExpr) eval(ctx *sql.Context, row sql.Row, left interface{}) (interface{}, error) {
if a.compFunc == nil {
return nil, errors.Errorf("%T: cannot Eval as it has not been fully resolved", a)
}
rightInterface, err := a.rightExpr.Eval(ctx, row)
if err != nil {
return nil, err
}
if rightInterface == nil {
return nil, nil
}
rightValues, ok := rightInterface.([]any)
if !ok {
return nil, errors.Errorf("%T: expected right child to return `%T` but returned `%T`", a, []any{}, rightInterface)
}
if len(rightValues) == 0 {
return nil, nil
}
// Next we'll assign our evaluated values to the expressions that the comparison function reference
// Note that the compiled function has a reference to the staticLiteral and arrayLiteral, so we must alter them in place
a.staticLiteral.Val = left
for _, rightValue := range rightValues {
a.arrayLiteral.Val = rightValue
result, err := a.compFunc.Eval(ctx, row)
if err != nil {
return nil, err
}
if result == nil {
return nil, nil
}
if result.(bool) {
return true, nil
}
}
return false, nil
}
// Eval implements the Expression interface.
func (a *AnyExpr) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) {
left, err := a.leftExpr.Eval(ctx, row)
if err != nil {
return nil, err
}
if a.subqueryAnyExpr != nil {
return a.subqueryAnyExpr.eval(ctx, a.subOperator, row, left)
}
if a.expressionAnyExpr != nil {
return a.expressionAnyExpr.eval(ctx, row, left)
}
return nil, errors.Errorf("%T: cannot Eval as it has not been fully resolved", a)
}
// WithChildren implements the Expression interface.
func (a *AnyExpr) WithChildren(ctx *sql.Context, children ...sql.Expression) (sql.Expression, error) {
if len(children) != 2 {
return nil, sql.ErrInvalidChildrenNumber.New(a, len(children), 2)
}
leftExpr := children[0]
rightExpr := children[1]
// Unmodified BindVars use deferred type resolution, so we replace the deference with the left's type in array form
if bv, ok := rightExpr.(*expression.BindVar); ok {
if _, ok = bv.Typ.(*pgtypes.DoltgresType); !ok {
if leftType, ok := leftExpr.Type(ctx).(*pgtypes.DoltgresType); ok {
bv.Typ = leftType.ToArrayType()
}
}
}
anyExpr := &AnyExpr{
leftExpr: leftExpr,
rightExpr: rightExpr,
subOperator: a.subOperator,
name: a.name,
}
if sub, ok := children[1].(*plan.Subquery); ok {
return anySubqueryWithChildren(ctx, anyExpr, sub)
}
return anyExpressionWithChildren(ctx, anyExpr)
}
// WithResolvedChildren implements the Expression interface.
func (a *AnyExpr) WithResolvedChildren(ctx context.Context, children []any) (any, error) {
if len(children) != 2 {
return nil, errors.Errorf("invalid vitess child count, expected `2` but got `%d`", len(children))
}
left, ok := children[0].(sql.Expression)
if !ok {
return nil, errors.Errorf("expected vitess child to be an expression but has type `%T`", children[0])
}
right, ok := children[1].(sql.Expression)
if !ok {
return nil, errors.Errorf("expected vitess child to be an expression but has type `%T`", children[1])
}
return a.WithChildren(ctx.(*sql.Context), left, right)
}
// String implements the fmt.Stringer interface.
func (a *AnyExpr) String() string {
if a.leftExpr == nil || a.rightExpr == nil {
return fmt.Sprintf("? %s (?)", a.name)
}
return fmt.Sprintf("%s = %s (%s)", a.leftExpr, a.name, a.rightExpr)
}
// DebugString implements the Expression interface.
func (a *AnyExpr) DebugString(ctx *sql.Context) string {
return fmt.Sprintf("%s %s (%s)", sql.DebugString(ctx, a.leftExpr), a.name, sql.DebugString(ctx, a.rightExpr))
}
// anySubqueryWithChildren resolves the comparison functions for a plan.Subquery.
func anySubqueryWithChildren(ctx *sql.Context, anyExpr *AnyExpr, sub *plan.Subquery) (sql.Expression, error) {
schema := sub.Query.Schema(ctx)
subTypes := make([]*pgtypes.DoltgresType, len(schema))
for i, col := range schema {
dgType, ok := col.Type.(*pgtypes.DoltgresType)
if !ok {
return nil, errors.Errorf("expected right child to be a DoltgresType but got `%T`", sub)
}
subTypes[i] = dgType
}
op, err := framework.GetOperatorFromString(anyExpr.subOperator)
if err != nil {
return nil, err
}
if leftType, ok := anyExpr.leftExpr.Type(ctx).(*pgtypes.DoltgresType); ok {
// Resolve comparison functions once and reuse the functions in Eval.
staticLiteral := expression.NewLiteral(nil, leftType)
arrayLiterals := make([]*expression.Literal, len(subTypes))
// Each expression may be a different type (which is valid), so we need a comparison function for each expression.
compFuncs := make([]framework.Function, len(subTypes))
for i, rightType := range subTypes {
arrayLiterals[i] = expression.NewLiteral(nil, rightType)
compFuncs[i] = framework.GetBinaryFunction(op).Compile(ctx, "internal_any_comparison", staticLiteral, arrayLiterals[i])
if compFuncs[i] == nil {
return nil, errors.Errorf("operator does not exist: %s = %s", leftType.String(), rightType.String())
}
if compFuncs[i].Type(ctx).(*pgtypes.DoltgresType).ID != pgtypes.Bool.ID {
// This should never happen, but this is just to be safe
return nil, errors.Errorf("%T: found equality comparison that does not return a bool", anyExpr)
}
}
anyExpr.subqueryAnyExpr = &subqueryAnyExpr{
rightSub: sub,
staticLiteral: staticLiteral,
arrayLiterals: arrayLiterals,
compFuncs: compFuncs,
}
}
return anyExpr, nil
}
// anyExpressionWithChildren resolves the comparison functions for a sql.Expression.
func anyExpressionWithChildren(ctx *sql.Context, anyExpr *AnyExpr) (sql.Expression, error) {
arrType, ok := anyExpr.rightExpr.Type(ctx).(*pgtypes.DoltgresType)
if !ok {
return nil, errors.Errorf("expected right child to be a DoltgresType but got `%T`", anyExpr.rightExpr)
}
rightType := arrType.ArrayBaseType()
op, err := framework.GetOperatorFromString(anyExpr.subOperator)
if err != nil {
return nil, err
}
if leftType, ok := anyExpr.leftExpr.Type(ctx).(*pgtypes.DoltgresType); ok {
// Resolve comparison function once and reuse the function in Eval.
staticLiteral := expression.NewLiteral(nil, leftType)
arrayLiteral := expression.NewLiteral(nil, rightType)
compFunc := framework.GetBinaryFunction(op).Compile(ctx, "internal_any_comparison", staticLiteral, arrayLiteral)
if compFunc == nil || compFunc.StashedError() != nil {
return nil, errors.Errorf("operator does not exist: %s = %s", leftType.String(), rightType.String())
}
compFuncType := compFunc.Type(ctx)
if compFuncType.(*pgtypes.DoltgresType).ID != pgtypes.Bool.ID {
// This should never happen, but this is just to be safe
return nil, errors.Errorf("%T: found equality comparison that does not return a bool", anyExpr)
}
anyExpr.expressionAnyExpr = &expressionAnyExpr{
rightExpr: anyExpr.rightExpr,
staticLiteral: staticLiteral,
arrayLiteral: arrayLiteral,
compFunc: compFunc,
}
}
return anyExpr, nil
}
+202
View File
@@ -0,0 +1,202 @@
// Copyright 2024 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package expression
import (
"context"
"strings"
"github.com/cockroachdb/errors"
"github.com/dolthub/go-mysql-server/sql"
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
"github.com/dolthub/doltgresql/core"
"github.com/dolthub/doltgresql/core/id"
"github.com/dolthub/doltgresql/server/functions/framework"
pgtypes "github.com/dolthub/doltgresql/server/types"
)
// Array represents an ARRAY[...] expression.
type Array struct {
children []sql.Expression
coercedType *pgtypes.DoltgresType
}
var _ vitess.Injectable = (*Array)(nil)
var _ sql.Expression = (*Array)(nil)
// NewArray returns a new *Array.
func NewArray(coercedType sql.Type) (*Array, error) {
var arrayCoercedType *pgtypes.DoltgresType
if dt, ok := coercedType.(*pgtypes.DoltgresType); ok {
if dt.IsEmptyType() {
// DoltgresType pointer can be nil
} else if dt.IsArrayType() {
arrayCoercedType = dt
} else if !dt.IsEmptyType() {
return nil, errors.Errorf("cannot cast array to %s", coercedType.String())
}
} else if coercedType != nil {
return nil, errors.Errorf("cannot cast array to %s", coercedType.String())
}
return &Array{
children: nil,
coercedType: arrayCoercedType,
}, nil
}
// Children implements the sql.Expression interface.
func (array *Array) Children() []sql.Expression {
return array.children
}
// Eval implements the sql.Expression interface.
func (array *Array) Eval(ctx *sql.Context, row sql.Row) (any, error) {
resultTyp := array.coercedType.ArrayBaseType()
values := make([]any, len(array.children))
castsColl, err := core.GetCastsCollectionFromContext(ctx, "")
if err != nil {
return nil, err
}
for i, expr := range array.children {
val, err := expr.Eval(ctx, row)
if err != nil {
return nil, err
}
if val == nil {
values[i] = val
continue
}
doltgresType, ok := expr.Type(ctx).(*pgtypes.DoltgresType)
if !ok {
return nil, errors.Errorf("expected DoltgresType, but got %s", expr.Type(ctx).String())
}
// We always cast the element, as there may be parameter restrictions in place
cast, err := castsColl.GetImplicitCast(ctx, doltgresType, resultTyp)
if err != nil {
return nil, err
}
if !cast.ID.IsValid() {
return nil, errors.Errorf("cannot find cast function from %s to %s", doltgresType.String(), resultTyp.String())
}
values[i], err = cast.Eval(ctx, val, doltgresType, resultTyp)
if err != nil {
return nil, err
}
}
return values, nil
}
// IsNullable implements the sql.Expression interface.
func (array *Array) IsNullable(ctx *sql.Context) bool {
// TODO: verify if this is actually nullable
return false
}
// Resolved implements the sql.Expression interface.
func (array *Array) Resolved() bool {
for _, child := range array.children {
if child == nil || !child.Resolved() {
return false
}
}
return true
}
// String implements the sql.Expression interface.
func (array *Array) String() string {
sb := strings.Builder{}
sb.WriteString("ARRAY[")
for i, child := range array.children {
if i > 0 {
sb.WriteString(", ")
}
if child == nil {
sb.WriteString("...")
} else {
sb.WriteString(child.String())
}
}
sb.WriteRune(']')
return sb.String()
}
// Type implements the sql.Expression interface.
func (array *Array) Type(ctx *sql.Context) sql.Type {
return array.coercedType
}
// WithChildren implements the sql.Expression interface.
func (array *Array) WithChildren(ctx *sql.Context, children ...sql.Expression) (sql.Expression, error) {
resultType, err := array.getTargetType(ctx, children...)
if err != nil {
return nil, err
}
return &Array{
children: children,
coercedType: resultType,
}, nil
}
// WithResolvedChildren implements the vitess.InjectableExpression interface.
func (array *Array) WithResolvedChildren(ctx context.Context, children []any) (any, error) {
newExpressions := make([]sql.Expression, len(children))
for i, resolvedChild := range children {
resolvedExpression, ok := resolvedChild.(sql.Expression)
if !ok {
return nil, errors.Errorf("expected vitess child to be an expression but has type `%T`", resolvedChild)
}
newExpressions[i] = resolvedExpression
}
return array.WithChildren(ctx.(*sql.Context), newExpressions...)
}
// getTargetType returns the evaluated type for this expression.
// Returns the "anyarray" type if the type combination is invalid.
func (array *Array) getTargetType(ctx *sql.Context, children ...sql.Expression) (*pgtypes.DoltgresType, error) {
var childrenTypes []*pgtypes.DoltgresType
for _, child := range children {
if child != nil {
childType, ok := child.Type(ctx).(*pgtypes.DoltgresType)
if !ok {
// We use "anyarray" as the indeterminate/invalid type
return pgtypes.AnyArray, nil
}
childrenTypes = append(childrenTypes, childType)
}
}
targetType, _, err := framework.FindCommonType(ctx, childrenTypes)
if err != nil {
return nil, errors.Errorf("ARRAY %s", err.Error())
}
// If the common type is unresolved (e.g. a user-defined composite type seen before the analyzer runs),
// look it up from the type collection so that ToArrayType can find the array type ID.
if !targetType.IsResolvedType() {
schemaName := targetType.ID.SchemaName()
if schemaName == "" {
schemaName, _ = core.GetCurrentSchema(ctx)
}
if typeColl, tcErr := core.GetTypesCollectionFromContext(ctx, ""); tcErr == nil && typeColl != nil {
if resolved, rErr := typeColl.GetType(ctx, id.NewType(schemaName, targetType.ID.TypeName())); rErr == nil && resolved != nil {
targetType = resolved
}
}
}
return targetType.ToArrayType(), nil
}
+249
View File
@@ -0,0 +1,249 @@
// Copyright 2025 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package expression
import (
"context"
"sort"
"strings"
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/go-mysql-server/sql/expression"
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
"github.com/dolthub/doltgresql/server/types"
)
type ArrayAgg struct {
selectExprs []sql.Expression
orderBy sql.SortFields
id sql.ColumnId
Distinct bool
}
var _ sql.Aggregation = (*ArrayAgg)(nil)
var _ vitess.Injectable = (*ArrayAgg)(nil)
var _ sql.OrderedAggregation = (*ArrayAgg)(nil)
// WithResolvedChildren returns a new ArrayAgg with the provided children as its select expressions.
// The last child is expected to be the order by expressions.
func (a *ArrayAgg) WithResolvedChildren(ctx context.Context, children []any) (any, error) {
a.selectExprs = make([]sql.Expression, len(children)-1)
for i := 0; i < len(children)-1; i++ {
a.selectExprs[i] = children[i].(sql.Expression)
}
a.orderBy = children[len(children)-1].(sql.SortFields)
return a, nil
}
// Resolved implements sql.Expression
func (a *ArrayAgg) Resolved() bool {
return expression.ExpressionsResolved(a.selectExprs...) && expression.ExpressionsResolved(a.orderBy.ToExpressions()...)
}
// String implements sql.Expression
func (a *ArrayAgg) String() string {
sb := strings.Builder{}
sb.WriteString("array_agg(")
if a.selectExprs != nil {
var exprs = make([]string, len(a.selectExprs))
for i, expr := range a.selectExprs {
exprs[i] = expr.String()
}
sb.WriteString(strings.Join(exprs, ", "))
}
if len(a.orderBy) > 0 {
sb.WriteString(" order by ")
for i, ob := range a.orderBy {
if i > 0 {
sb.WriteString(", ")
}
sb.WriteString(ob.String())
}
}
sb.WriteString(")")
return sb.String()
}
// Type implements sql.Expression
func (a *ArrayAgg) Type(ctx *sql.Context) sql.Type {
dt := a.selectExprs[0].Type(ctx).(*types.DoltgresType)
return dt.ToArrayType()
}
// IsNullable implements sql.Expression
func (a *ArrayAgg) IsNullable(ctx *sql.Context) bool {
return true
}
// Eval implements sql.Expression
func (a *ArrayAgg) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) {
panic("eval should never be called on an aggregation function")
}
// Children implements sql.Expression
func (a *ArrayAgg) Children() []sql.Expression {
return append(a.selectExprs, a.orderBy.ToExpressions()...)
}
func (a *ArrayAgg) OutputExpressions() []sql.Expression {
return a.selectExprs
}
// WithChildren implements sql.Expression
func (a ArrayAgg) WithChildren(ctx *sql.Context, children ...sql.Expression) (sql.Expression, error) {
if len(children) != len(a.selectExprs)+len(a.orderBy) {
return nil, sql.ErrInvalidChildrenNumber.New(a, len(children), len(a.selectExprs)+len(a.orderBy))
}
a.selectExprs = children[:len(a.selectExprs)]
a.orderBy = a.orderBy.FromExpressions(ctx, children[len(a.selectExprs):]...)
return &a, nil
}
// Id implements sql.IdExpression
func (a *ArrayAgg) Id() sql.ColumnId {
return a.id
}
// WithId implements sql.IdExpression
func (a ArrayAgg) WithId(id sql.ColumnId) sql.IdExpression {
a.id = id
return &a
}
// NewWindowFunction implements sql.WindowAdaptableExpression
func (a *ArrayAgg) NewWindowFunction(ctx *sql.Context) (sql.WindowFunction, error) {
panic("window functions not yet supported for array_agg")
}
// WithWindow implements sql.WindowAdaptableExpression
func (a *ArrayAgg) WithWindow(ctx *sql.Context, window *sql.WindowDefinition) sql.WindowAdaptableExpression {
panic("window functions not yet supported for array_agg")
}
// Window implements sql.WindowAdaptableExpression
func (a *ArrayAgg) Window() *sql.WindowDefinition {
return nil
}
// NewBuffer implements sql.Aggregation
func (a *ArrayAgg) NewBuffer(ctx *sql.Context) (sql.AggregationBuffer, error) {
return &arrayAggBuffer{
elements: make([]sql.Row, 0),
a: a,
}, nil
}
// arrayAggBuffer is the buffer used to accumulate values for the array_agg aggregation function.
type arrayAggBuffer struct {
elements []sql.Row
seen []interface{} // sorted, non-NULL distinct values for binary search
seenNull bool
a *ArrayAgg
}
// Dispose implements sql.AggregationBuffer
func (a *arrayAggBuffer) Dispose(ctx *sql.Context) {}
// Eval implements sql.AggregationBuffer
func (a *arrayAggBuffer) Eval(ctx *sql.Context) (interface{}, error) {
if len(a.elements) == 0 {
return nil, nil
}
if a.a.orderBy != nil {
sorter := &expression.Sorter{
SortFields: a.a.orderBy,
Rows: a.elements,
Ctx: ctx,
}
sort.Stable(sorter)
if sorter.LastError != nil {
return nil, sorter.LastError
}
}
// convert to []interface for return. The last element in each row is the one we want to return, the rest are sort fields.
result := make([]interface{}, len(a.elements))
for i, row := range a.elements {
result[i] = row[(len(row) - 1)]
}
return result, nil
}
// Update implements sql.AggregationBuffer
func (a *arrayAggBuffer) Update(ctx *sql.Context, row sql.Row) error {
evalRow, err := evalExprs(ctx, a.a.selectExprs, row)
if err != nil {
return err
}
if a.a.Distinct {
val := evalRow[0]
if val == nil {
if a.seenNull {
return nil
}
a.seenNull = true
} else {
exprType := a.a.selectExprs[0].Type(ctx).(*types.DoltgresType)
lo, hi := 0, len(a.seen)
for lo < hi {
mid := (lo + hi) / 2
cmp, err := exprType.Compare(ctx, val, a.seen[mid])
if err != nil {
return err
}
if cmp == 0 {
return nil
} else if cmp < 0 {
hi = mid
} else {
lo = mid + 1
}
}
a.seen = append(a.seen, nil)
copy(a.seen[lo+1:], a.seen[lo:])
a.seen[lo] = val
}
}
// Append the current value to the end of the row. We want to preserve the row's original structure
// for sort ordering in the final step.
a.elements = append(a.elements, append(row, evalRow[0]))
return nil
}
// evalExprs evaluates the provided expressions against the given row and returns the results as a new row.
func evalExprs(ctx *sql.Context, exprs []sql.Expression, row sql.Row) (sql.Row, error) {
result := make(sql.Row, len(exprs))
for i, expr := range exprs {
var err error
result[i], err = expr.Eval(ctx, row)
if err != nil {
return nil, err
}
}
return result, nil
}
+113
View File
@@ -0,0 +1,113 @@
// Copyright 2025 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package expression
import (
"context"
"github.com/cockroachdb/errors"
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/go-mysql-server/sql/plan"
gmstypes "github.com/dolthub/go-mysql-server/sql/types"
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
"github.com/dolthub/doltgresql/server/types"
)
// ArrayFlatten is an expression that represents the results of a subquery expression as an array.
// Currently only subqueries that return a single column are supported.
type ArrayFlatten struct {
Subquery sql.Expression
}
var _ vitess.Injectable = (*ArrayFlatten)(nil)
var _ sql.Expression = (*ArrayFlatten)(nil)
// Resolved implements sql.Expression.
func (a ArrayFlatten) Resolved() bool {
return a.Subquery.Resolved()
}
// String implements sql.Expression.
func (a ArrayFlatten) String() string {
return "ARRAY(" + a.Subquery.String() + ")"
}
// Type implements sql.Expression.
func (a ArrayFlatten) Type(ctx *sql.Context) sql.Type {
sqType := a.Subquery.Type(ctx)
dt, ok := sqType.(*types.DoltgresType)
if !ok {
// If we don't have a doltgres type, we'll error out at execution time. A special case is the tuple type,
// where we need to choose a single one to avoid erroring out too early.
if tt, ok := sqType.(gmstypes.TupleType); ok {
return tt[0]
}
return sqType
}
return dt.ToArrayType()
}
// IsNullable implements sql.Expression.
func (a ArrayFlatten) IsNullable(ctx *sql.Context) bool {
return false
}
// Eval implements sql.Expression.
func (a ArrayFlatten) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) {
subquery, ok := a.Subquery.(*plan.Subquery)
if !ok {
return nil, errors.Errorf("expected subquery, got %T", a.Subquery)
}
sqType := subquery.Type(ctx)
_, ok = sqType.(*types.DoltgresType)
if !ok {
if tt, ok := sqType.(gmstypes.TupleType); ok {
return nil, errors.Errorf("only a single column subquery is supported in ARRAY(), got %d columns", len(tt))
}
return nil, errors.Errorf("expected doltgres type, got %T", sqType)
}
return subquery.EvalMultiple(ctx, row)
}
// Children implements sql.Expression.
func (a ArrayFlatten) Children() []sql.Expression {
return []sql.Expression{a.Subquery}
}
// WithChildren implements sql.Expression.
func (a ArrayFlatten) WithChildren(ctx *sql.Context, children ...sql.Expression) (sql.Expression, error) {
if len(children) != 1 {
return nil, sql.ErrInvalidChildrenNumber.New(a, len(children), 1)
}
return ArrayFlatten{Subquery: children[0]}, nil
}
// WithResolvedChildren implements vitess.Injectable.
func (a ArrayFlatten) WithResolvedChildren(ctx context.Context, children []any) (any, error) {
if len(children) != 1 {
return nil, sql.ErrInvalidChildrenNumber.New(a, len(children), 1)
}
subquery, ok := children[0].(sql.Expression)
if !ok {
return nil, sql.ErrInvalidChildType.New(a, 0, children[0])
}
return ArrayFlatten{Subquery: subquery}, nil
}
+106
View File
@@ -0,0 +1,106 @@
// Copyright 2024 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package expression
import (
"github.com/cockroachdb/errors"
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/doltgresql/core"
pgtypes "github.com/dolthub/doltgresql/server/types"
)
// AssignmentCast handles assignment casts.
type AssignmentCast struct {
expr sql.Expression
sourceType *pgtypes.DoltgresType
targetType *pgtypes.DoltgresType
}
var _ sql.Expression = (*AssignmentCast)(nil)
// NewAssignmentCast returns a new *AssignmentCast expression.
func NewAssignmentCast(expr sql.Expression, sourceType *pgtypes.DoltgresType, targetType *pgtypes.DoltgresType) *AssignmentCast {
targetType = checkForDomainType(targetType)
sourceType = checkForDomainType(sourceType)
return &AssignmentCast{
expr: expr,
sourceType: sourceType,
targetType: targetType,
}
}
// Children implements the sql.Expression interface.
func (ac *AssignmentCast) Children() []sql.Expression {
return []sql.Expression{ac.expr}
}
// Eval implements the sql.Expression interface.
func (ac *AssignmentCast) Eval(ctx *sql.Context, row sql.Row) (any, error) {
val, err := ac.expr.Eval(ctx, row)
if err != nil || val == nil {
return val, err
}
castsColl, err := core.GetCastsCollectionFromContext(ctx, "")
if err != nil {
return nil, err
}
cast, err := castsColl.GetAssignmentCast(ctx, ac.sourceType, ac.targetType)
if err != nil {
return nil, err
}
if !cast.ID.IsValid() {
return nil, errors.Errorf("ASSIGNMENT_CAST: target is of type %s but expression is of type %s: %s",
ac.targetType.String(), ac.sourceType.String(), ac.expr.String())
}
return cast.Eval(ctx, val, ac.sourceType, ac.targetType)
}
// IsNullable implements the sql.Expression interface.
func (ac *AssignmentCast) IsNullable(ctx *sql.Context) bool {
return true
}
// Resolved implements the sql.Expression interface.
func (ac *AssignmentCast) Resolved() bool {
return ac.expr.Resolved()
}
// String implements the sql.Expression interface.
func (ac *AssignmentCast) String() string {
return ac.expr.String()
}
// Type implements the sql.Expression interface.
func (ac *AssignmentCast) Type(ctx *sql.Context) sql.Type {
return ac.targetType
}
// WithChildren implements the sql.Expression interface.
func (ac *AssignmentCast) WithChildren(ctx *sql.Context, children ...sql.Expression) (sql.Expression, error) {
if len(children) != 1 {
return nil, sql.ErrInvalidChildrenNumber.New(ac, len(children), 1)
}
return NewAssignmentCast(children[0], ac.sourceType, ac.targetType), nil
}
// checkForDomainType returns the underlying type if the given type is a domain type. Casting always applies to the base
// type.
func checkForDomainType(t *pgtypes.DoltgresType) *pgtypes.DoltgresType {
if t.TypType == pgtypes.TypeType_Domain {
t = t.DomainUnderlyingBaseType()
}
return t
}
+206
View File
@@ -0,0 +1,206 @@
// Copyright 2024 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package expression
import (
"context"
"fmt"
"github.com/cockroachdb/errors"
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/go-mysql-server/sql/expression"
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
"github.com/dolthub/doltgresql/server/functions/framework"
)
// BinaryOperator represents a VALUE OPERATOR VALUE expression.
type BinaryOperator struct {
operator framework.Operator
compiledFunc framework.Function
}
var _ vitess.Injectable = (*BinaryOperator)(nil)
var _ sql.Expression = (*BinaryOperator)(nil)
var _ expression.BinaryExpression = (*BinaryOperator)(nil)
var _ expression.Equality = (*BinaryOperator)(nil)
var _ sql.IndexComparisonExpression = (*BinaryOperator)(nil)
// NewBinaryOperator returns a new *BinaryOperator.
func NewBinaryOperator(operator framework.Operator) *BinaryOperator {
return &BinaryOperator{operator: operator}
}
// Children implements the sql.Expression interface.
func (b *BinaryOperator) Children() []sql.Expression {
return b.compiledFunc.Children()
}
// Eval implements the sql.Expression interface.
func (b *BinaryOperator) Eval(ctx *sql.Context, row sql.Row) (any, error) {
return b.compiledFunc.Eval(ctx, row)
}
// IsNullable implements the sql.Expression interface.
func (b *BinaryOperator) IsNullable(ctx *sql.Context) bool {
return b.compiledFunc.IsNullable(ctx)
}
// RepresentsEquality implements the expression.Equality interface.
func (b *BinaryOperator) RepresentsEquality() bool {
return b.operator == framework.Operator_BinaryEqual
}
// Resolved implements the sql.Expression interface.
func (b *BinaryOperator) Resolved() bool {
return b.compiledFunc.Resolved()
}
// String implements the sql.Expression interface.
func (b *BinaryOperator) String() string {
if b.compiledFunc == nil {
return fmt.Sprintf("? %s ?", b.operator.String())
}
// We know that we'll always have two parameters here
switch f := b.compiledFunc.(type) {
case *framework.CompiledFunction:
return fmt.Sprintf("%s %s %s",
f.Arguments[0].String(), b.operator.String(), f.Arguments[1].String())
case *framework.QuickFunction2:
return fmt.Sprintf("%s %s %s",
f.Arguments[0].String(), b.operator.String(), f.Arguments[1].String())
default:
return fmt.Sprintf("unexpected binary operator function type: %T", b.compiledFunc)
}
}
// SwapParameters implements the expression.Equality interface.
func (b *BinaryOperator) SwapParameters(ctx *sql.Context) (expression.Equality, error) {
// TODO: for now we'll assume this is valid, but we should check for the `COMMUTATOR` property on the operator
f, err := b.WithResolvedChildren(ctx, []any{b.Right(), b.Left()})
if err != nil {
return nil, err
}
return f.(expression.Equality), nil
}
// ToComparer implements the expression.Equality interface.
func (b *BinaryOperator) ToComparer(ctx *sql.Context) (expression.Comparer, error) {
return NewJoinComparator(ctx, b)
}
// Type implements the sql.Expression interface.
func (b *BinaryOperator) Type(ctx *sql.Context) sql.Type {
return b.compiledFunc.Type(ctx)
}
// WithChildren implements the sql.Expression interface.
func (b *BinaryOperator) WithChildren(ctx *sql.Context, children ...sql.Expression) (sql.Expression, error) {
if len(children) != 2 {
return nil, sql.ErrInvalidChildrenNumber.New(b, len(children), 2)
}
if b.compiledFunc != nil {
compiledFunc, err := b.compiledFunc.WithChildren(ctx, children...)
if err != nil {
return nil, err
}
return &BinaryOperator{
operator: b.operator,
compiledFunc: compiledFunc.(framework.Function),
}, nil
} else {
binOp, err := b.WithResolvedChildren(ctx, []any{children[0], children[1]})
if err != nil {
return nil, err
}
return binOp.(sql.Expression), nil
}
}
// WithResolvedChildren implements the vitess.InjectableExpression interface.
func (b *BinaryOperator) WithResolvedChildren(ctx context.Context, children []any) (any, error) {
if len(children) != 2 {
return nil, errors.Errorf("invalid vitess child count, expected `2` but got `%d`", len(children))
}
sqlCtx := ctx.(*sql.Context)
left, ok := children[0].(sql.Expression)
if !ok {
return nil, errors.Errorf("expected vitess child to be an expression but has type `%T`", children[0])
}
right, ok := children[1].(sql.Expression)
if !ok {
return nil, errors.Errorf("expected vitess child to be an expression but has type `%T`", children[1])
}
funcName := "internal_binary_operator_func_" + b.operator.String()
compiledFunc := framework.GetBinaryFunction(b.operator).Compile(sqlCtx, funcName, left, right)
if compiledFunc == nil {
return nil, errors.Errorf("operator does not exist: %s %s %s",
left.Type(sqlCtx).String(), b.operator.String(), right.Type(sqlCtx).String())
}
return &BinaryOperator{
operator: b.operator,
compiledFunc: compiledFunc,
}, nil
}
// Operator returns the operator that is used.
func (b *BinaryOperator) Operator() framework.Operator {
return b.operator
}
// Left implements the expression.BinaryExpression interface.
func (b *BinaryOperator) Left() sql.Expression {
// We know that we'll always have two parameters here
switch f := b.compiledFunc.(type) {
case *framework.CompiledFunction:
return f.Arguments[0]
case *framework.QuickFunction2:
return f.Arguments[0]
default:
return nil
}
}
// Right implements the expression.BinaryExpression interface.
func (b *BinaryOperator) Right() sql.Expression {
// We know that we'll always have two parameters here
switch f := b.compiledFunc.(type) {
case *framework.CompiledFunction:
return f.Arguments[1]
case *framework.QuickFunction2:
return f.Arguments[1]
default:
return nil
}
}
// IndexScanOperation implements the sql.IndexComparisonExpression interface.
func (b *BinaryOperator) IndexScanOperation() (sql.IndexScanOp, sql.Expression, sql.Expression, bool) {
switch b.operator {
case framework.Operator_BinaryEqual:
return sql.IndexScanOpEq, b.Left(), b.Right(), true
case framework.Operator_BinaryLessThan:
return sql.IndexScanOpLt, b.Left(), b.Right(), true
case framework.Operator_BinaryLessOrEqual:
return sql.IndexScanOpLte, b.Left(), b.Right(), true
case framework.Operator_BinaryGreaterThan:
return sql.IndexScanOpGt, b.Left(), b.Right(), true
case framework.Operator_BinaryGreaterOrEqual:
return sql.IndexScanOpGte, b.Left(), b.Right(), true
case framework.Operator_BinaryNotEqual:
return sql.IndexScanOpNotEq, b.Left(), b.Right(), true
}
return 0, nil, nil, false
}
+158
View File
@@ -0,0 +1,158 @@
// Copyright 2026 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package expression
import (
"fmt"
"strings"
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/doltgresql/server/functions/framework"
pgtypes "github.com/dolthub/doltgresql/server/types"
)
// PgCoalesce is a Doltgres-native COALESCE implementation. It uses Postgres type-resolution rules
// (FindCommonType) to compute the correct result type.
type PgCoalesce struct {
args []sql.Expression
typ *pgtypes.DoltgresType
}
var _ sql.Expression = (*PgCoalesce)(nil)
var _ sql.FunctionExpression = (*PgCoalesce)(nil)
var _ sql.CollationCoercible = (*PgCoalesce)(nil)
// NewPgCoalesce creates a new PgCoalesce expression.
func NewPgCoalesce(ctx *sql.Context, args ...sql.Expression) (*PgCoalesce, error) {
if len(args) == 0 {
return nil, sql.ErrInvalidArgumentNumber.New("COALESCE", "1 or more", 0)
}
expr, err := (&PgCoalesce{typ: pgtypes.Unknown}).WithChildren(ctx, args...)
if err != nil {
return nil, err
}
return expr.(*PgCoalesce), nil
}
// FunctionName implements sql.FunctionExpression.
func (c *PgCoalesce) FunctionName() string { return "coalesce" }
// Description implements sql.FunctionExpression.
func (c *PgCoalesce) Description() string { return "returns the first non-null value in a list." }
// Type implements sql.Expression.
func (c *PgCoalesce) Type(_ *sql.Context) sql.Type {
return c.typ
}
// CollationCoercibility implements sql.CollationCoercible.
func (c *PgCoalesce) CollationCoercibility(ctx *sql.Context) (collation sql.CollationID, coercibility byte) {
if cc, ok := c.Type(ctx).(sql.CollationCoercible); ok {
return cc.CollationCoercibility(ctx)
}
return sql.Collation_binary, 6
}
// IsNullable implements sql.Expression.
func (c *PgCoalesce) IsNullable(_ *sql.Context) bool {
return true
}
// Resolved implements sql.Expression.
func (c *PgCoalesce) Resolved() bool {
for _, arg := range c.args {
if arg == nil || !arg.Resolved() {
return false
}
}
return true
}
// Children implements sql.Expression.
func (c *PgCoalesce) Children() []sql.Expression { return c.args }
// WithChildren implements sql.Expression.
func (c *PgCoalesce) WithChildren(ctx *sql.Context, children ...sql.Expression) (sql.Expression, error) {
if len(children) == 0 {
return nil, sql.ErrInvalidArgumentNumber.New("COALESCE", "1 or more", 0)
}
newC := &PgCoalesce{args: children, typ: pgtypes.Unknown}
childTypes := make([]*pgtypes.DoltgresType, 0, len(children))
for _, child := range children {
dt, ok := child.Type(ctx).(*pgtypes.DoltgresType)
if !ok {
return newC, nil
}
childTypes = append(childTypes, dt)
}
commonType, _, err := framework.FindCommonType(ctx, childTypes)
if err != nil {
return nil, err
}
if commonType != nil {
newC.typ = commonType
}
return newC, nil
}
// Eval implements sql.Expression. Returns the first non-null argument value, cast to the common type.
func (c *PgCoalesce) Eval(ctx *sql.Context, row sql.Row) (any, error) {
commonType := c.typ
for _, arg := range c.args {
if arg == nil {
continue
}
val, err := arg.Eval(ctx, row)
if err != nil {
return nil, err
}
if val == nil {
continue
}
if commonType == pgtypes.Unknown {
return val, nil
}
argType, ok := arg.Type(ctx).(*pgtypes.DoltgresType)
if ok && argType.Equals(commonType) {
return val, nil
}
// Cast the value to the common type (handles mixed-type args, e.g. int2 and int4).
converted, _, err := commonType.ConvertToType(ctx, argType, val)
if err != nil {
return nil, err
}
return converted, nil
}
return nil, nil
}
// String implements sql.Expression.
func (c *PgCoalesce) String() string {
args := make([]string, len(c.args))
for i, arg := range c.args {
args[i] = arg.String()
}
return fmt.Sprintf("coalesce(%s)", strings.Join(args, ","))
}
// DebugString implements the sql.Debuggable interface.
func (c *PgCoalesce) DebugString(ctx *sql.Context) string {
args := make([]string, len(c.args))
for i, arg := range c.args {
args[i] = sql.DebugString(ctx, arg)
}
return fmt.Sprintf("coalesce(%s)", strings.Join(args, ","))
}
+212
View File
@@ -0,0 +1,212 @@
// Copyright 2026 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package expression
import (
"context"
"fmt"
"github.com/cockroachdb/errors"
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/go-mysql-server/sql/expression"
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
"github.com/dolthub/doltgresql/core"
pgtypes "github.com/dolthub/doltgresql/server/types"
)
// ColumnAccess represents an ARRAY[...] expression.
type ColumnAccess struct {
colName string
colNameIdx int
colTyp *pgtypes.DoltgresType
child sql.Expression
}
var _ vitess.Injectable = (*ColumnAccess)(nil)
var _ sql.Expression = (*ColumnAccess)(nil)
// NewColumnAccess returns a new *ColumnAccess.
func NewColumnAccess(colName string, colIdx int) (*ColumnAccess, error) {
if len(colName) > 0 {
return &ColumnAccess{
colName: colName,
colNameIdx: -1,
colTyp: nil,
child: nil,
}, nil
} else {
return &ColumnAccess{
colName: "",
colNameIdx: colIdx,
colTyp: nil,
child: nil,
}, nil
}
}
// Children implements the sql.Expression interface.
func (expr *ColumnAccess) Children() []sql.Expression {
return []sql.Expression{expr.child}
}
// Eval implements the sql.Expression interface.
func (expr *ColumnAccess) Eval(ctx *sql.Context, row sql.Row) (any, error) {
field, err := expr.child.Eval(ctx, row)
if err != nil {
return nil, err
}
if field == nil {
return nil, nil
}
recordVals, ok := field.([]pgtypes.RecordValue)
if !ok {
if len(expr.colName) > 0 {
return nil, errors.Errorf("column notation .%s applied to type %s, which is not a composite type",
expr.colName, expr.child.Type(ctx).String())
} else {
return nil, errors.Errorf("column notation .@%d applied to type %s, which is not a composite type",
expr.colNameIdx+1, expr.child.Type(ctx).String())
}
}
return recordVals[expr.colNameIdx].Value, nil
}
// IsNullable implements the sql.Expression interface.
func (expr *ColumnAccess) IsNullable(ctx *sql.Context) bool {
return true
}
// Resolved implements the sql.Expression interface.
func (expr *ColumnAccess) Resolved() bool {
return expr.child != nil && expr.child.Resolved()
}
// String implements the sql.Expression interface.
func (expr *ColumnAccess) String() string {
if expr.child == nil {
return "COLUMN_ACCESS"
}
if len(expr.colName) > 0 {
return fmt.Sprintf("(%s).%s", expr.child.String(), expr.colName)
} else {
return fmt.Sprintf("(%s).@%d", expr.child.String(), expr.colNameIdx+1)
}
}
// Type implements the sql.Expression interface.
func (expr *ColumnAccess) Type(ctx *sql.Context) sql.Type {
if expr.colTyp != nil {
return expr.colTyp
}
if expr.child == nil {
return nil
}
typ, ok := expr.child.Type(ctx).(*pgtypes.DoltgresType)
if !ok {
return pgtypes.Unknown
}
return typ.CompositeAttrs[expr.colNameIdx].Type
}
// WithChildren implements the sql.Expression interface.
func (expr *ColumnAccess) WithChildren(ctx *sql.Context, children ...sql.Expression) (sql.Expression, error) {
if len(children) != 1 {
return nil, sql.ErrInvalidChildrenNumber.New(expr, len(children), 1)
}
child := children[0]
childType := child.Type(ctx)
doltgresType, ok := childType.(*pgtypes.DoltgresType)
if !ok {
if _, ok := child.(*expression.BindVar); ok {
return &ColumnAccess{
colName: expr.colName,
colNameIdx: expr.colNameIdx,
colTyp: expr.colTyp,
child: child,
}, nil
}
return nil, errors.New("column access is only valid for Doltgres types")
}
if !doltgresType.IsResolvedType() {
typeColl, err := core.GetTypesCollectionFromContext(ctx, "")
if err != nil {
return nil, err
}
resolvedType, err := typeColl.ResolveType(ctx, doltgresType.ID)
if err != nil {
return nil, err
}
doltgresType = resolvedType
}
if !doltgresType.IsDefined {
return nil, pgtypes.ErrTypeIsOnlyAShell.New(doltgresType.Name())
}
if !doltgresType.IsCompositeType() {
if len(expr.colName) > 0 {
return nil, errors.Errorf("column notation .%s applied to type %s, which is not a composite type",
expr.colName, child.Type(ctx).String())
} else {
return nil, errors.Errorf("column notation .@%d applied to type %s, which is not a composite type",
expr.colNameIdx+1, child.Type(ctx).String())
}
}
var idx int
if len(expr.colName) > 0 {
idx = -1
for _, attr := range doltgresType.CompositeAttrs {
if attr.Name == expr.colName {
idx = int(attr.Num - 1)
break
}
}
if idx == -1 {
return nil, errors.Errorf(`column "%s" not found in data type %s`,
expr.colName, doltgresType.String())
}
} else {
if expr.colNameIdx < 0 || expr.colNameIdx >= len(doltgresType.CompositeAttrs) {
return nil, errors.Errorf("column notation .@%d applied to type %s is out of bounds",
expr.colNameIdx+1, child.Type(ctx).String())
}
idx = expr.colNameIdx
}
return &ColumnAccess{
colName: expr.colName,
colNameIdx: idx,
colTyp: doltgresType.CompositeAttrs[idx].Type,
child: child,
}, nil
}
// WithResolvedChildren implements the vitess.InjectableExpression interface.
func (expr *ColumnAccess) WithResolvedChildren(ctx context.Context, children []any) (any, error) {
newExpressions := make([]sql.Expression, len(children))
for i, resolvedChild := range children {
resolvedExpression, ok := resolvedChild.(sql.Expression)
if !ok {
return nil, errors.Errorf("expected vitess child to be an expression but has type `%T`", resolvedChild)
}
newExpressions[i] = resolvedExpression
}
return expr.WithChildren(ctx.(*sql.Context), newExpressions...)
}
// WithType returns this expression with the given type set, as it must be set within the analyzer.
func (expr *ColumnAccess) WithType(typ *pgtypes.DoltgresType) sql.Expression {
ne := *expr
ne.colTyp = typ
return &ne
}
+240
View File
@@ -0,0 +1,240 @@
// Copyright 2024 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package expression
import (
"context"
"fmt"
"strings"
"github.com/cockroachdb/errors"
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/go-mysql-server/sql/expression"
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
"github.com/dolthub/doltgresql/core"
pgtypes "github.com/dolthub/doltgresql/server/types"
)
// ExplicitCast represents a VALUE::TYPE expression.
type ExplicitCast struct {
sqlChild sql.Expression
castToType *pgtypes.DoltgresType
domainNullable bool
domainChecks sql.CheckConstraints
}
var _ vitess.Injectable = (*ExplicitCast)(nil)
var _ sql.Expression = (*ExplicitCast)(nil)
// NewExplicitCastInjectable returns an incomplete *ExplicitCast that must be resolved through the vitess.Injectable interface.
func NewExplicitCastInjectable(castToType sql.Type) (*ExplicitCast, error) {
pgtype, ok := castToType.(*pgtypes.DoltgresType)
if !ok {
return nil, errors.Errorf("cast expects a Doltgres type as the target type")
}
return &ExplicitCast{
sqlChild: nil,
castToType: pgtype,
}, nil
}
// NewExplicitCast returns a new *ExplicitCast expression.
func NewExplicitCast(expr sql.Expression, toType *pgtypes.DoltgresType) *ExplicitCast {
toType = checkForDomainType(toType)
return &ExplicitCast{
sqlChild: expr,
castToType: toType,
}
}
// Children implements the sql.Expression interface.
func (c *ExplicitCast) Children() []sql.Expression {
return []sql.Expression{c.sqlChild}
}
// Child returns the child that is being cast.
func (c *ExplicitCast) Child() sql.Expression {
return c.sqlChild
}
// Eval implements the sql.Expression interface.
func (c *ExplicitCast) Eval(ctx *sql.Context, row sql.Row) (any, error) {
if !c.castToType.IsResolvedType() {
return nil, errors.Errorf("cannot call ExplicitCast.Eval with unresolved cast to type: %s", c.castToType.String())
}
val, err := c.sqlChild.Eval(ctx, row)
if err != nil {
return nil, err
}
sourceType, ok := c.sqlChild.Type(ctx).(*pgtypes.DoltgresType)
if !ok {
// We'll leverage GMSCast to handle the conversion from a GMS type to a Doltgres type.
// Rather than re-evaluating the expression, we put the result in a literal.
gmsCast := NewGMSCast(expression.NewLiteral(val, c.sqlChild.Type(ctx)))
val, err = gmsCast.Eval(ctx, row)
if err != nil {
return nil, err
}
sourceType = gmsCast.DoltgresType(ctx)
}
baseCastToType := checkForDomainType(c.castToType)
castsColl, err := core.GetCastsCollectionFromContext(ctx, "")
if err != nil {
return nil, err
}
cast, err := castsColl.GetExplicitCast(ctx, sourceType, baseCastToType)
if err != nil {
return nil, err
}
if !cast.ID.IsValid() {
return nil, errors.Errorf(
"EXPLICIT CAST: cast from `%s` to `%s` does not exist: %s",
sourceType.String(), c.castToType.String(), c.sqlChild.String(),
)
}
if val == nil {
if c.castToType.TypType == pgtypes.TypeType_Domain && !c.domainNullable {
return nil, pgtypes.ErrDomainDoesNotAllowNullValues.New(c.castToType.Name())
}
if !cast.Function.IsValid() {
return nil, nil
}
}
castResult, err := cast.Eval(ctx, val, sourceType, c.castToType)
if err != nil {
// For string types and string array types, we intentionally ignore the error as using a length-restricted cast
// is a way to intentionally truncate the data. All string types will always return the truncated result, even
// during an error, so it's safe to use.
castToType := c.castToType
if c.castToType.IsArrayType() {
castToType = c.castToType.ArrayBaseType()
}
// A nil result will be returned if there's a critical error, which we should never ignore.
if castToType.TypCategory != pgtypes.TypeCategory_StringTypes || castResult == nil {
return nil, err
}
}
if c.castToType.TypType == pgtypes.TypeType_Domain {
for _, check := range c.domainChecks {
res, err := sql.EvaluateCondition(ctx, check.Expr, sql.Row{castResult})
if err != nil {
return nil, err
}
if sql.IsFalse(res) {
return nil, pgtypes.ErrDomainValueViolatesCheckConstraint.New(c.castToType.Name(), check.Name)
}
}
}
return castResult, nil
}
// IsNullable implements the sql.Expression interface.
func (c *ExplicitCast) IsNullable(ctx *sql.Context) bool {
// TODO: verify if this is actually nullable
return true
}
// Resolved implements the sql.Expression interface.
func (c *ExplicitCast) Resolved() bool {
if c.sqlChild != nil && c.sqlChild.Resolved() {
return true
}
return false
}
// String implements the sql.Expression interface.
func (c *ExplicitCast) String() string {
var sqlChild string
if c.sqlChild == nil {
sqlChild = "unresolved"
} else {
sqlChild = c.sqlChild.String()
}
// type needs to be upper-case to match InputExpression in AliasExpr
return fmt.Sprintf("%s::%s", sqlChild, strings.ToUpper(c.castToType.String()))
}
// Type implements the sql.Expression interface.
func (c *ExplicitCast) Type(ctx *sql.Context) sql.Type {
return c.castToType
}
// WithChildren implements the sql.Expression interface.
func (c *ExplicitCast) WithChildren(ctx *sql.Context, children ...sql.Expression) (sql.Expression, error) {
if len(children) != 1 {
return nil, sql.ErrInvalidChildrenNumber.New(c, len(children), 1)
}
return &ExplicitCast{
sqlChild: children[0],
castToType: c.castToType,
domainNullable: c.domainNullable,
domainChecks: c.domainChecks,
}, nil
}
// WithResolvedChildren implements the vitess.InjectableExpression interface.
func (c *ExplicitCast) WithResolvedChildren(ctx context.Context, children []any) (any, error) {
if len(children) != 1 {
return nil, errors.Errorf("invalid vitess child count, expected `1` but got `%d`", len(children))
}
resolvedExpression, ok := children[0].(sql.Expression)
if !ok {
return nil, errors.Errorf("expected vitess child to be an expression but has type `%T`", children[0])
}
if !c.castToType.IsResolvedType() {
sqlCtx, ok := ctx.(*sql.Context)
if !ok {
return nil, errors.Errorf("%T requires a SQL context for type resolution", c)
}
typeColl, err := core.GetTypesCollectionFromContext(sqlCtx, "")
if err != nil {
return nil, err
}
resolvedType, err := typeColl.ResolveType(sqlCtx, c.castToType.ID)
if err != nil {
return nil, err
}
c.castToType = resolvedType
}
if !c.castToType.IsDefined {
return nil, pgtypes.ErrTypeIsOnlyAShell.New(c.castToType.Name())
}
return &ExplicitCast{
sqlChild: resolvedExpression,
castToType: c.castToType,
domainNullable: c.domainNullable,
domainChecks: c.domainChecks,
}, nil
}
// WithCastToType returns a copy of the expression with castToType replaced.
func (c *ExplicitCast) WithCastToType(t *pgtypes.DoltgresType) sql.Expression {
ec := *c
ec.castToType = t
return &ec
}
// WithDomainConstraints returns a copy of the expression with domain constraints defined.
func (c *ExplicitCast) WithDomainConstraints(nullable bool, checks sql.CheckConstraints) sql.Expression {
ec := *c
ec.domainNullable = nullable
ec.domainChecks = checks
return &ec
}
+36
View File
@@ -0,0 +1,36 @@
// Copyright 2025 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package expression
import (
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/go-mysql-server/sql/expression"
)
// PostgresExpressionFactory implements the expression.ExpressionFactory interface and
// allows callers to produce expressions that have custom behavior for Postgres.
type PostgresExpressionFactory struct{}
var _ expression.ExpressionFactory = (*PostgresExpressionFactory)(nil)
// NewIsNull implements the expression.ExpressionFactory interface.
func (m PostgresExpressionFactory) NewIsNull(e sql.Expression) sql.Expression {
return NewIsNull(e)
}
// NewIsNotNull implements the expression.ExpressionFactory interface.
func (m PostgresExpressionFactory) NewIsNotNull(e sql.Expression) sql.Expression {
return NewIsNotNull(e)
}
+233
View File
@@ -0,0 +1,233 @@
// Copyright 2024 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package expression
import (
"encoding/json"
"time"
"github.com/cockroachdb/apd/v3"
"github.com/cockroachdb/errors"
"github.com/dolthub/dolt/go/store/prolly/tree"
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/go-mysql-server/sql/expression"
"github.com/dolthub/go-mysql-server/sql/types"
"github.com/dolthub/vitess/go/vt/proto/query"
pgtypes "github.com/dolthub/doltgresql/server/types"
)
// GMSCast handles the conversion from a GMS expression's type to its Doltgres type that is most similar.
type GMSCast struct {
sqlChild sql.Expression
}
var _ sql.Expression = (*GMSCast)(nil)
// NewGMSCast returns a new *GMSCast.
func NewGMSCast(child sql.Expression) *GMSCast {
return &GMSCast{
sqlChild: child,
}
}
// Children implements the sql.Expression interface.
func (c *GMSCast) Children() []sql.Expression {
return []sql.Expression{c.sqlChild}
}
// Child returns the child that is being cast.
func (c *GMSCast) Child() sql.Expression {
return c.sqlChild
}
// DoltgresType returns the DoltgresType that the cast evaluates to. This is the same value that is returned by Type().
func (c *GMSCast) DoltgresType(ctx *sql.Context) *pgtypes.DoltgresType {
// GMSCast shouldn't receive a DoltgresType, but we shouldn't error if it happens
if t, ok := c.sqlChild.Type(ctx).(*pgtypes.DoltgresType); ok {
return t
}
return pgtypes.FromGmsType(c.sqlChild.Type(ctx))
}
// Eval implements the sql.Expression interface.
func (c *GMSCast) Eval(ctx *sql.Context, row sql.Row) (any, error) {
val, err := c.sqlChild.Eval(ctx, row)
if err != nil {
return nil, err
}
if val == nil {
return nil, nil
}
// GMSCast shouldn't receive a DoltgresType, but we shouldn't error if it happens
if _, ok := c.sqlChild.Type(ctx).(*pgtypes.DoltgresType); ok {
return val, nil
}
sqlTyp := c.sqlChild.Type(ctx)
switch sqlTyp.Type() {
// Boolean types are a special case because of how they are translated on the wire in Postgres. If we identify a
// boolean result, we want to convert it from an int back to a boolean.
case query.Type_INT8:
if sqlTyp == types.Boolean {
newVal, _, err := types.Int32.Convert(ctx, val)
if err != nil {
return nil, err
}
if _, ok := newVal.(int32); !ok {
return nil, errors.Errorf("GMSCast expected type `int32`, got `%T`", val)
}
if newVal.(int32) == 0 {
return false, nil
} else {
return true, nil
}
}
fallthrough
// In Postgres, Int32 is generally the smallest value returned. But we convert int8 and int16 to this type during
// schema conversion, which means we must do so here as well to avoid runtime panics.
case query.Type_INT16, query.Type_INT24, query.Type_INT32, query.Type_YEAR:
newVal, _, err := types.Int32.Convert(ctx, val)
if err != nil {
return nil, err
}
if _, ok := newVal.(int32); !ok {
return nil, errors.Errorf("GMSCast expected type `int32`, got `%T`", val)
}
return newVal, nil
case query.Type_INT64, query.Type_BIT, query.Type_UINT8, query.Type_UINT16, query.Type_UINT24, query.Type_UINT32:
newVal, _, err := types.Int64.Convert(ctx, val)
if err != nil {
return nil, err
}
if _, ok := newVal.(int64); !ok {
return nil, errors.Errorf("GMSCast expected type `int64`, got `%T`", val)
}
return newVal, nil
case query.Type_UINT64:
// Postgres doesn't have a "public" Uint64 type, so we return a Numeric value
newVal, _, err := types.InternalDecimalType.Convert(ctx, val)
if err != nil {
return nil, err
}
dec, ok := newVal.(*apd.Decimal)
if !ok {
return nil, errors.Errorf("GMSCast expected type `*apd.Decimal`, got `%T`", val)
}
return dec, nil
case query.Type_FLOAT32:
newVal, _, err := types.Float32.Convert(ctx, val)
if err != nil {
return nil, err
}
if _, ok := newVal.(float32); !ok {
return nil, errors.Errorf("GMSCast expected type `float32`, got `%T`", val)
}
return newVal, nil
case query.Type_FLOAT64:
newVal, _, err := types.Float64.Convert(ctx, val)
if err != nil {
return nil, err
}
if _, ok := newVal.(float64); !ok {
return nil, errors.Errorf("GMSCast expected type `float64`, got `%T`", val)
}
return newVal, nil
case query.Type_DECIMAL:
newVal, _, err := types.InternalDecimalType.Convert(ctx, val)
if err != nil {
return nil, err
}
dec, ok := newVal.(*apd.Decimal)
if !ok {
return nil, errors.Errorf("GMSCast expected type `*apd.Decimal`, got `%T`", val)
}
return dec, nil
case query.Type_DATE, query.Type_DATETIME, query.Type_TIMESTAMP:
if val, ok := val.(time.Time); ok {
return val, nil
}
return nil, errors.Errorf("GMSCast expected type `Time`, got `%T`", val)
case query.Type_TIME:
if val, ok := val.(types.Timespan); ok {
return val.String(), nil
}
return nil, errors.Errorf("GMSCast expected type `Timespan`, got `%T`", val)
case query.Type_CHAR, query.Type_VARCHAR, query.Type_TEXT, query.Type_BINARY, query.Type_VARBINARY, query.Type_BLOB, query.Type_SET, query.Type_ENUM:
newVal, _, err := types.LongText.Convert(ctx, val)
if err != nil {
return nil, err
}
switch newVal := newVal.(type) {
case string:
return newVal, nil
case sql.StringWrapper:
return newVal.Unwrap(ctx)
default:
return nil, errors.Errorf("GMSCast expected type `string`, got `%T`", val)
}
case query.Type_JSON:
switch val := val.(type) {
case types.JSONDocument:
return val.JSONString()
case tree.IndexedJsonDocument:
return val.String(), nil
default:
// TODO: there are particular dolt tables (dolt_constraint_violations) that return json-marshallable structs
// that we need to handle here like this
bytes, err := json.Marshal(val)
return string(bytes), err
}
case query.Type_NULL_TYPE:
return nil, nil
case query.Type_GEOMETRY:
return nil, errors.Errorf("GMS geometry types are not supported")
default:
return nil, errors.Errorf("GMS type `%s` is not supported", c.sqlChild.Type(ctx).String())
}
}
// IsNullable implements the sql.Expression interface.
func (c *GMSCast) IsNullable(ctx *sql.Context) bool {
return true
}
// Resolved implements the sql.Expression interface.
func (c *GMSCast) Resolved() bool {
return c.sqlChild.Resolved()
}
// String implements the sql.Expression interface.
func (c *GMSCast) String() string {
if gf, ok := c.sqlChild.(*expression.GetField); ok {
return gf.Name()
}
return c.sqlChild.String()
}
// Type implements the sql.Expression interface.
func (c *GMSCast) Type(ctx *sql.Context) sql.Type {
return c.DoltgresType(ctx)
}
// WithChildren implements the sql.Expression interface.
func (c *GMSCast) WithChildren(ctx *sql.Context, children ...sql.Expression) (sql.Expression, error) {
if len(children) != 1 {
return nil, sql.ErrInvalidChildrenNumber.New(c, len(children), 1)
}
return &GMSCast{
sqlChild: children[0],
}, nil
}
+96
View File
@@ -0,0 +1,96 @@
// Copyright 2024 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package expression
import (
"github.com/cockroachdb/errors"
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/doltgresql/core"
pgtypes "github.com/dolthub/doltgresql/server/types"
)
// ImplicitCast handles implicit casts.
type ImplicitCast struct {
expr sql.Expression
sourceType *pgtypes.DoltgresType
targetType *pgtypes.DoltgresType
}
var _ sql.Expression = (*ImplicitCast)(nil)
// NewImplicitCast returns a new *ImplicitCast expression.
func NewImplicitCast(expr sql.Expression, fromType *pgtypes.DoltgresType, toType *pgtypes.DoltgresType) *ImplicitCast {
toType = checkForDomainType(toType)
fromType = checkForDomainType(fromType)
return &ImplicitCast{
expr: expr,
sourceType: fromType,
targetType: toType,
}
}
// Children implements the sql.Expression interface.
func (ic *ImplicitCast) Children() []sql.Expression {
return []sql.Expression{ic.expr}
}
// Eval implements the sql.Expression interface.
func (ic *ImplicitCast) Eval(ctx *sql.Context, row sql.Row) (any, error) {
val, err := ic.expr.Eval(ctx, row)
if err != nil || val == nil {
return val, err
}
castsColl, err := core.GetCastsCollectionFromContext(ctx, "")
if err != nil {
return nil, err
}
cast, err := castsColl.GetImplicitCast(ctx, ic.sourceType, ic.targetType)
if err != nil {
return nil, err
}
if !cast.ID.IsValid() {
return nil, errors.Errorf("target is of type %s but expression is of type %s", ic.targetType.String(), ic.sourceType.String())
}
return cast.Eval(ctx, val, ic.sourceType, ic.targetType)
}
// IsNullable implements the sql.Expression interface.
func (ic *ImplicitCast) IsNullable(ctx *sql.Context) bool {
return true
}
// Resolved implements the sql.Expression interface.
func (ic *ImplicitCast) Resolved() bool {
return ic.expr.Resolved()
}
// String implements the sql.Expression interface.
func (ic *ImplicitCast) String() string {
return ic.expr.String()
}
// Type implements the sql.Expression interface.
func (ic *ImplicitCast) Type(ctx *sql.Context) sql.Type {
return ic.targetType
}
// WithChildren implements the sql.Expression interface.
func (ic *ImplicitCast) WithChildren(ctx *sql.Context, children ...sql.Expression) (sql.Expression, error) {
if len(children) != 1 {
return nil, sql.ErrInvalidChildrenNumber.New(ic, len(children), 1)
}
return NewImplicitCast(children[0], ic.sourceType, ic.targetType), nil
}
+264
View File
@@ -0,0 +1,264 @@
// Copyright 2024 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package expression
import (
"context"
"fmt"
"github.com/cockroachdb/errors"
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/go-mysql-server/sql/expression"
"github.com/dolthub/go-mysql-server/sql/hash"
"github.com/dolthub/go-mysql-server/sql/plan"
"github.com/dolthub/go-mysql-server/sql/types"
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
"github.com/dolthub/doltgresql/server/functions/framework"
pgtypes "github.com/dolthub/doltgresql/server/types"
)
// InSubquery represents a VALUE IN (SELECT ...) expression.
type InSubquery struct {
leftExpr sql.Expression
rightExpr *plan.Subquery
// These variables are used so that we can resolve the comparison functions once and reuse them as we iterate over rows.
// These are assigned in WithChildren, so refer there for more information.
leftLiteral *expression.Literal
rightLiterals []*expression.Literal
compFuncs []framework.Function
}
var _ vitess.Injectable = (*InSubquery)(nil)
var _ sql.Expression = (*InSubquery)(nil)
var _ expression.BinaryExpression = (*InSubquery)(nil)
// nilKey is the hash of a row with a single nil value.
var nilKey, _ = hash.HashOf(nil, nil, sql.NewRow(nil))
// NewInSubquery returns a new *InSubquery.
func NewInSubquery() *InSubquery {
return &InSubquery{}
}
// Children implements the sql.Expression interface.
func (in *InSubquery) Children() []sql.Expression {
return []sql.Expression{in.leftExpr, in.rightExpr}
}
// Eval implements the sql.Expression interface.
func (in *InSubquery) Eval(ctx *sql.Context, row sql.Row) (any, error) {
if len(in.compFuncs) == 0 {
return nil, errors.Errorf("%T: cannot Eval as it has not been fully resolved", in)
}
left, err := in.leftExpr.Eval(ctx, row)
if err != nil {
return nil, err
}
// The NULL handling for IN expressions is tricky. According to
// https://www.postgresql.org/docs/16/functions-comparisons.html#FUNCTIONS-COMPARISONS-IN-SCALAR:
// To comply with the SQL standard, IN() returns NULL not only if the expression on the left hand side is NULL, but
// also if no match is found in the list and one of the expressions in the list is NULL.
leftNull := left == nil
if types.NumColumns(in.Left().Type(ctx)) != types.NumColumns(in.Right().Type(ctx)) {
return nil, sql.ErrInvalidOperandColumns.New(types.NumColumns(in.Left().Type(ctx)), types.NumColumns(in.Right().Type(ctx)))
}
right := in.rightExpr
// TODO: does this work for all pg values?
values, err := right.HashMultiple(ctx, row)
if err != nil {
return nil, err
}
// NULL IN (list) returns NULL. NULL IN (empty list) returns 0
if leftNull {
if values.Size() == 0 {
return false, nil
}
return nil, nil
}
// TODO: it might be possible for the left value to hash to a different value than the right even though they pass
// an equality check. We need to perform a type conversion here to catch this case.
key, err := hash.HashOf(ctx, nil, sql.NewRow(left))
if err != nil {
return nil, err
}
// If the hashed values don't contain the left value hash, we know it's not there.
// If we do find the hash of the left value, we still need to check for equality,
// since non-equal values could have the same hash in some cases.
val, notFoundErr := values.Get(key)
if notFoundErr != nil {
if _, nilValNotFoundErr := values.Get(nilKey); nilValNotFoundErr == nil {
return nil, nil
}
return false, nil
}
var r sql.Row
rowVal, ok := val.([]any)
if !ok {
r = sql.Row{val}
} else {
r = sql.NewRow(rowVal...)
}
return in.valuesEqual(ctx, left, r)
}
// valuesEqual returns true if the left value is equal to the row provided using the equality functions previously
// assigned to |compFuncs| during analysis. If the left value is a single scalar, then |row| has a single value as
// well. Otherwise, (left is a tuple), |row| has a matching number of values.
func (in *InSubquery) valuesEqual(ctx *sql.Context, left interface{}, row sql.Row) (bool, error) {
// Note that we have to edit the literals in place, since the comparison functions reference them directly.
in.leftLiteral.Val = left
for i, v := range row {
in.rightLiterals[i].Val = v
}
for _, compFunc := range in.compFuncs {
result, err := compFunc.Eval(ctx, nil)
if err != nil {
return false, err
}
if !result.(bool) {
return false, nil
}
}
return true, nil
}
// IsNullable implements the sql.Expression interface.
func (in *InSubquery) IsNullable(ctx *sql.Context) bool {
return true
}
// Resolved implements the sql.Expression interface.
func (in *InSubquery) Resolved() bool {
if in.leftExpr == nil || !in.leftExpr.Resolved() || in.rightExpr == nil || !in.rightExpr.Resolved() || len(in.compFuncs) == 0 {
return false
}
for _, compFunc := range in.compFuncs {
if !compFunc.Resolved() {
return false
}
}
return true
}
// String implements the sql.Expression interface.
func (in *InSubquery) String() string {
if in.leftExpr == nil || in.rightExpr == nil {
return "? IN ?"
}
return fmt.Sprintf("%s IN %s", in.leftExpr.String(), in.rightExpr.String())
}
// Type implements the sql.Expression interface.
func (in *InSubquery) Type(ctx *sql.Context) sql.Type {
return pgtypes.Bool
}
// WithChildren implements the sql.Expression interface.
func (in *InSubquery) WithChildren(ctx *sql.Context, children ...sql.Expression) (sql.Expression, error) {
if len(children) != 2 {
return nil, sql.ErrInvalidChildrenNumber.New(in, len(children), 2)
}
sq, ok := children[1].(*plan.Subquery)
if !ok {
return nil, errors.Errorf("%T: expected right child to be `%T` but has type `%T`", in, &plan.Subquery{}, children[1])
}
// We'll only resolve the comparison functions once we have all Doltgres types.
// We may see GMS types during some analyzer steps, so we should wait until those are done.
if leftType, ok := children[0].Type(ctx).(*pgtypes.DoltgresType); ok {
// Rather than finding and resolving a comparison function every time we call Eval, we resolve them once and
// reuse the functions. We also want to avoid re-assigning the parameters of the comparison functions since that
// will also cause the functions to resolve again. To do this, we store expressions within our struct that the
// functions reference, so we can freely switch the values within the literals without changing anything
// regarding the comparison functions. This is usually unsafe, but since we're verifying the types returned by
// the parameters, and assigning the values to our own literals, we do not have to worry. This offers a
// significant speedup as function resolution is very expensive, so we want to do it as few times as possible
// (preferably once).
// We need a comparison function for each type in the query result
sch := sq.Query.Schema(ctx)
leftLiteral := expression.NewLiteral(nil, leftType)
rightLiterals := make([]*expression.Literal, len(sch))
compFuncs := make([]framework.Function, len(sch))
allValidChildren := true
for i, rightCol := range sch {
rightType, ok := rightCol.Type.(*pgtypes.DoltgresType)
if !ok {
allValidChildren = false
break
}
rightLiterals[i] = expression.NewLiteral(nil, rightType)
compFuncs[i] = framework.GetBinaryFunction(framework.Operator_BinaryEqual).Compile(ctx, "internal_in_comparison", leftLiteral, rightLiterals[i])
if compFuncs[i] == nil {
return nil, errors.Errorf("operator does not exist: %s = %s", leftType.String(), rightType.String())
}
if compFuncs[i].Type(ctx).(*pgtypes.DoltgresType).ID != pgtypes.Bool.ID {
// This should never happen, but this is just to be safe
return nil, errors.Errorf("%T: found equality comparison that does not return a bool", in)
}
}
if allValidChildren {
return &InSubquery{
leftExpr: children[0],
rightExpr: sq,
leftLiteral: leftLiteral,
rightLiterals: rightLiterals,
compFuncs: compFuncs,
}, nil
}
}
return &InSubquery{
leftExpr: children[0],
rightExpr: sq,
}, nil
}
// WithResolvedChildren implements the vitess.InjectableExpression interface.
func (in *InSubquery) WithResolvedChildren(ctx context.Context, children []any) (any, error) {
if len(children) != 2 {
return nil, errors.Errorf("invalid vitess child count, expected `2` but got `%d`", len(children))
}
left, ok := children[0].(sql.Expression)
if !ok {
return nil, errors.Errorf("expected vitess child to be an expression but has type `%T`", children[0])
}
right, ok := children[1].(*plan.Subquery)
if !ok {
return nil, errors.Errorf("expected vitess child to be a *plan.Subquery but has type `%T`", children[1])
}
return in.WithChildren(ctx.(*sql.Context), left, right)
}
// Left implements the expression.BinaryExpression interface.
func (in *InSubquery) Left() sql.Expression {
return in.leftExpr
}
// Right implements the expression.BinaryExpression interface.
func (in *InSubquery) Right() sql.Expression {
return in.rightExpr
}
+286
View File
@@ -0,0 +1,286 @@
// Copyright 2024 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package expression
import (
"context"
"fmt"
"github.com/cockroachdb/errors"
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/go-mysql-server/sql/expression"
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
"github.com/dolthub/doltgresql/server/functions/framework"
pgtypes "github.com/dolthub/doltgresql/server/types"
)
// InTuple represents a VALUE IN (<VALUES>) expression.
type InTuple struct {
leftExpr sql.Expression
rightExpr expression.Tuple
// These variables are used so that we can resolve the comparison functions once and reuse them as we iterate over rows.
// These are assigned in WithChildren, so refer there for more information.
staticLiteral *expression.Literal
arrayLiterals []*expression.Literal
compFuncs []framework.Function
}
var _ vitess.Injectable = (*InTuple)(nil)
var _ sql.Expression = (*InTuple)(nil)
var _ expression.BinaryExpression = (*InTuple)(nil)
var _ sql.IndexComparisonExpression = (*InTuple)(nil)
// NewInTuple returns a new *InTuple.
func NewInTuple() *InTuple {
return &InTuple{
leftExpr: nil,
rightExpr: nil,
}
}
// Children implements the sql.Expression interface.
func (it *InTuple) Children() []sql.Expression {
return []sql.Expression{it.leftExpr, it.rightExpr}
}
// Decay returns the expression as a series of OR expressions. The behavior is not the same, however it allows some
// paths to simplify their expression handling (such as filters).
func (it *InTuple) Decay() sql.Expression {
switch f := it.compFuncs[0].(type) {
case *framework.CompiledFunction:
f.Arguments = []sql.Expression{it.leftExpr, it.rightExpr[0]}
case *framework.QuickFunction2:
f.Arguments = [2]sql.Expression{it.leftExpr, it.rightExpr[0]}
}
var expr sql.Expression = &BinaryOperator{
operator: framework.Operator_BinaryEqual,
compiledFunc: it.compFuncs[0],
}
for i := 1; i < len(it.rightExpr); i++ {
switch f := it.compFuncs[i].(type) {
case *framework.CompiledFunction:
f.Arguments = []sql.Expression{it.leftExpr, it.rightExpr[i]}
case *framework.QuickFunction2:
f.Arguments = [2]sql.Expression{it.leftExpr, it.rightExpr[i]}
}
expr = expression.NewOr(expr, &BinaryOperator{
operator: framework.Operator_BinaryEqual,
compiledFunc: it.compFuncs[i],
})
}
return expr
}
// Eval implements the sql.Expression interface.
func (it *InTuple) Eval(ctx *sql.Context, row sql.Row) (any, error) {
if len(it.compFuncs) == 0 {
return nil, errors.Errorf("%T: cannot Eval as it has not been fully resolved", it)
}
// First we'll evaluate everything before we do the comparisons
left, err := it.leftExpr.Eval(ctx, row)
if err != nil {
return nil, err
}
if left == nil {
return nil, nil
}
rightInterface, err := it.rightExpr.Eval(ctx, row)
if err != nil {
return nil, err
}
rightValues, ok := rightInterface.([]any)
if !ok {
// Tuples will return the value directly if it has a length of one, so we'll check for that first
if len(it.rightExpr) == 1 {
rightValues = []any{rightInterface}
} else {
return nil, errors.Errorf("%T: expected right child to return `%T` but returned `%T`", it, []any{}, rightInterface)
}
}
// Next we'll assign our evaluated values to the expressions that the comparison functions reference
// Note that the compiled functions already have a reference to this literal, so we have to edit it in place
it.staticLiteral.Val = left
for i, rightValue := range rightValues {
it.arrayLiterals[i].Val = rightValue
}
// Now we can loop over all of the comparison functions, as they'll reference their respective values
// The rules for null comparisons are subtle: an IN expression that includes a NULL in the tuple will return null
// instead of false if a match is not found, but true otherwise.
sawNull := false
for _, compFunc := range it.compFuncs {
result, err := compFunc.Eval(ctx, row)
if err != nil {
return nil, err
}
if result == nil {
sawNull = true
} else if result.(bool) {
return true, nil
}
}
if sawNull {
return nil, nil
}
return false, nil
}
// IsNullable implements the sql.Expression interface.
func (it *InTuple) IsNullable(ctx *sql.Context) bool {
return true
}
// Resolved implements the sql.Expression interface.
func (it *InTuple) Resolved() bool {
if it.leftExpr == nil || !it.leftExpr.Resolved() || it.rightExpr == nil || !it.rightExpr.Resolved() || len(it.compFuncs) == 0 {
return false
}
for _, compFunc := range it.compFuncs {
if !compFunc.Resolved() {
return false
}
}
return true
}
// String implements the sql.Expression interface.
func (it *InTuple) String() string {
if it.leftExpr == nil || it.rightExpr == nil {
return "? IN ?"
}
return fmt.Sprintf("%s IN %s", it.leftExpr.String(), it.rightExpr.String())
}
// Type implements the sql.Expression interface.
func (it *InTuple) Type(ctx *sql.Context) sql.Type {
return pgtypes.Bool
}
// WithChildren implements the sql.Expression interface.
func (it *InTuple) WithChildren(ctx *sql.Context, children ...sql.Expression) (sql.Expression, error) {
if len(children) != 2 {
return nil, sql.ErrInvalidChildrenNumber.New(it, len(children), 2)
}
rightTuple, ok := children[1].(expression.Tuple)
if !ok {
return nil, errors.Errorf("%T: expected right child to be `%T` but has type `%T`", it, expression.Tuple{}, children[1])
}
if len(rightTuple) == 0 {
return nil, errors.Errorf("IN must contain at least 1 expression")
}
// We'll only resolve the comparison functions once we have all Doltgres types.
// We may see GMS types during some analyzer steps, so we should wait until those are done.
if leftType, ok := children[0].Type(ctx).(*pgtypes.DoltgresType); ok {
// Rather than finding and resolving a comparison function every time we call Eval, we resolve them once and
// reuse the functions. We also want to avoid re-assigning the parameters of the comparison functions since that
// will also cause the functions to resolve again. To do this, we store expressions within our struct that the
// functions reference, so we can freely switch the values within the literals without changing anything
// regarding the comparison functions. This is usually unsafe, but since we're verifying the types returned by
// the parameters, and assigning the values to our own literals, we do not have to worry. This offers a
// significant speedup as function resolution is very expensive, so we want to do it as few times as possible
// (preferably once).
staticLiteral := expression.NewLiteral(nil, leftType)
arrayLiterals := make([]*expression.Literal, len(rightTuple))
// Each expression may be a different type (which is valid), so we need a comparison function for each expression.
compFuncs := make([]framework.Function, len(rightTuple))
allValidChildren := true
for i, rightExpr := range rightTuple {
rightType, ok := rightExpr.Type(ctx).(*pgtypes.DoltgresType)
if !ok {
allValidChildren = false
break
}
arrayLiterals[i] = expression.NewLiteral(nil, rightType)
compFunc := framework.GetBinaryFunction(framework.Operator_BinaryEqual).Compile(ctx, "internal_in_comparison", staticLiteral, arrayLiterals[i])
if compFunc == nil {
return nil, errors.Errorf("operator does not exist: %s = %s", leftType.String(), rightType.String())
}
cid := compFunc.Type(ctx).(*pgtypes.DoltgresType).ID
if cid != pgtypes.Bool.ID {
// Prepared statement binding values will need explicit casting to appropriate type
ec := NewAssignmentCast(arrayLiterals[i], pgtypes.Unknown, staticLiteral.Type(ctx).(*pgtypes.DoltgresType))
compFunc = framework.GetBinaryFunction(framework.Operator_BinaryEqual).Compile(ctx, "internal_in_comparison", staticLiteral, ec)
if compFunc == nil || compFunc.StashedError() != nil {
return nil, errors.Errorf("operator does not exist: %s = %s", leftType.String(), rightType.String())
}
cid = compFunc.Type(ctx).(*pgtypes.DoltgresType).ID
if cid != pgtypes.Bool.ID {
// This should never happen, but this is just to be safe
return nil, errors.Errorf("%T: found equality comparison that does not return a bool", it)
}
}
compFuncs[i] = compFunc
}
if allValidChildren {
return &InTuple{
leftExpr: children[0],
rightExpr: rightTuple,
staticLiteral: staticLiteral,
arrayLiterals: arrayLiterals,
compFuncs: compFuncs,
}, nil
}
}
return &InTuple{
leftExpr: children[0],
rightExpr: rightTuple,
}, nil
}
// WithResolvedChildren implements the vitess.InjectableExpression interface.
func (it *InTuple) WithResolvedChildren(ctx context.Context, children []any) (any, error) {
if len(children) != 2 {
return nil, errors.Errorf("invalid vitess child count, expected `2` but got `%d`", len(children))
}
sqlCtx := ctx.(*sql.Context)
left, ok := children[0].(sql.Expression)
if !ok {
return nil, errors.Errorf("expected vitess child to be an expression but has type `%T`", children[0])
}
switch right := children[1].(type) {
case expression.Tuple:
return it.WithChildren(sqlCtx, left, right)
case *RecordExpr:
// TODO: For now, if we see a RecordExpr come in, we convert it to a vitess Tuple representation, so that
// the existing in_tuple code can work with it. Alternatively, we could change in_tuple to always
// work directly with a Record expression.
return it.WithChildren(sqlCtx, left, expression.Tuple(right.exprs))
default:
return nil, errors.Errorf("expected child to be a RecordExpr or vitess Tuple but has type `%T`", children[1])
}
}
// Left implements the expression.BinaryExpression interface.
func (it *InTuple) Left() sql.Expression {
return it.leftExpr
}
// Right implements the expression.BinaryExpression interface.
func (it *InTuple) Right() sql.Expression {
return it.rightExpr
}
// IndexScanOperation implements the sql.IndexComparisonExpression interface.
func (it *InTuple) IndexScanOperation() (sql.IndexScanOp, sql.Expression, sql.Expression, bool) {
return sql.IndexScanOpInSet, it.leftExpr, it.rightExpr, true
}
+155
View File
@@ -0,0 +1,155 @@
// Copyright 2026 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package expression
import (
"context"
"github.com/cockroachdb/errors"
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/go-mysql-server/sql/expression"
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
"github.com/dolthub/doltgresql/server/functions/framework"
pgtypes "github.com/dolthub/doltgresql/server/types"
)
// IsDistinctFrom represents IS DISTINCT FROM expression.
type IsDistinctFrom struct {
leftExpr sql.Expression
rightExpr sql.Expression
staticLeftLiteral *expression.Literal
staticRightLiteral *expression.Literal
notEqualFunc *framework.CompiledFunction
}
var _ vitess.Injectable = (*IsDistinctFrom)(nil)
var _ sql.Expression = (*IsDistinctFrom)(nil)
// NewIsDistinctFrom returns a new *IsDistinctFrom.
func NewIsDistinctFrom() *IsDistinctFrom {
return &IsDistinctFrom{
leftExpr: nil,
rightExpr: nil,
}
}
// Children implements the sql.Expression interface.
func (n *IsDistinctFrom) Children() []sql.Expression {
return []sql.Expression{n.leftExpr, n.rightExpr}
}
// Eval implements the sql.Expression interface.
func (n *IsDistinctFrom) Eval(ctx *sql.Context, row sql.Row) (any, error) {
left, err := n.leftExpr.Eval(ctx, row)
if err != nil {
return nil, err
}
right, err := n.rightExpr.Eval(ctx, row)
if err != nil {
return nil, err
}
if left == nil && right == nil {
return false, nil
} else if left == nil || right == nil {
return true, nil
}
n.staticLeftLiteral.Val = left
n.staticRightLiteral.Val = right
if n.notEqualFunc == nil {
return nil, errors.Errorf("input types do not match: %s %s", n.leftExpr.Type(ctx).String(), n.rightExpr.Type(ctx).String())
}
return n.notEqualFunc.Eval(ctx, row)
}
// IsNullable implements the sql.Expression interface.
func (n *IsDistinctFrom) IsNullable(ctx *sql.Context) bool {
return true
}
// Resolved implements the sql.Expression interface.
func (n *IsDistinctFrom) Resolved() bool {
if n.leftExpr == nil || n.rightExpr == nil {
return false
}
return n.leftExpr.Resolved() && n.rightExpr.Resolved()
}
// String implements the sql.Expression interface.
func (n *IsDistinctFrom) String() string {
return n.leftExpr.String() + " IS DISTINCT FROM " + n.rightExpr.String()
}
// Type implements the sql.Expression interface.
func (n *IsDistinctFrom) Type(ctx *sql.Context) sql.Type {
return pgtypes.Bool
}
// WithChildren implements the sql.Expression interface.
func (n *IsDistinctFrom) WithChildren(ctx *sql.Context, children ...sql.Expression) (sql.Expression, error) {
if len(children) != 2 {
return nil, sql.ErrInvalidChildrenNumber.New(n, len(children), 2)
}
// This allows evaluating the arguments separate from function.Eval() in order to resolve NULL values.
// This follows the same logic as InTuple expression.
allValidChildren := true
leftType, ok := children[0].Type(ctx).(*pgtypes.DoltgresType)
if !ok {
allValidChildren = false
}
rightType, ok := children[1].Type(ctx).(*pgtypes.DoltgresType)
if !ok {
allValidChildren = false
}
staticLeftLiteral := expression.NewLiteral(nil, leftType)
staticRightLiteral := expression.NewLiteral(nil, rightType)
if allValidChildren {
cf := framework.GetBinaryFunction(framework.Operator_BinaryNotEqual).Compile(ctx, "internal_binary_operator_func_<>", staticLeftLiteral, staticRightLiteral)
return &IsDistinctFrom{
leftExpr: children[0],
rightExpr: children[1],
staticLeftLiteral: staticLeftLiteral,
staticRightLiteral: staticRightLiteral,
notEqualFunc: cf,
}, nil
}
return &IsDistinctFrom{
leftExpr: children[0],
rightExpr: children[1],
}, nil
}
// WithResolvedChildren implements the vitess.InjectableExpression interface.
func (n *IsDistinctFrom) WithResolvedChildren(ctx context.Context, children []any) (any, error) {
if len(children) != 2 {
return nil, errors.Errorf("invalid vitess child count, expected `2` but got `%d`", len(children))
}
left, ok := children[0].(sql.Expression)
if !ok {
return nil, errors.Errorf("expected vitess child to be an expression but has type `%T`", children[0])
}
right, ok := children[1].(sql.Expression)
if !ok {
return nil, errors.Errorf("expected vitess child to be an expression but has type `%T`", children[1])
}
return n.WithChildren(ctx.(*sql.Context), left, right)
}
+155
View File
@@ -0,0 +1,155 @@
// Copyright 2026 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package expression
import (
"context"
"github.com/cockroachdb/errors"
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/go-mysql-server/sql/expression"
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
"github.com/dolthub/doltgresql/server/functions/framework"
pgtypes "github.com/dolthub/doltgresql/server/types"
)
// IsNotDistinctFrom represents IS NOT DISTINCT FROM expression.
type IsNotDistinctFrom struct {
leftExpr sql.Expression
rightExpr sql.Expression
staticLeftLiteral *expression.Literal
staticRightLiteral *expression.Literal
equalFunc *framework.CompiledFunction
}
var _ vitess.Injectable = (*IsNotDistinctFrom)(nil)
var _ sql.Expression = (*IsNotDistinctFrom)(nil)
// NewIsNotDistinctFrom returns a new *IsNotDistinctFrom.
func NewIsNotDistinctFrom() *IsNotDistinctFrom {
return &IsNotDistinctFrom{
leftExpr: nil,
rightExpr: nil,
}
}
// Children implements the sql.Expression interface.
func (n *IsNotDistinctFrom) Children() []sql.Expression {
return []sql.Expression{n.leftExpr, n.rightExpr}
}
// Eval implements the sql.Expression interface.
func (n *IsNotDistinctFrom) Eval(ctx *sql.Context, row sql.Row) (any, error) {
left, err := n.leftExpr.Eval(ctx, row)
if err != nil {
return nil, err
}
right, err := n.rightExpr.Eval(ctx, row)
if err != nil {
return nil, err
}
if left == nil && right == nil {
return true, nil
} else if left == nil || right == nil {
return false, nil
}
n.staticLeftLiteral.Val = left
n.staticRightLiteral.Val = right
if n.equalFunc == nil {
return nil, errors.Errorf("input types do not match: %s %s", n.leftExpr.Type(ctx).String(), n.rightExpr.Type(ctx).String())
}
return n.equalFunc.Eval(ctx, row)
}
// IsNullable implements the sql.Expression interface.
func (n *IsNotDistinctFrom) IsNullable(ctx *sql.Context) bool {
return true
}
// Resolved implements the sql.Expression interface.
func (n *IsNotDistinctFrom) Resolved() bool {
if n.leftExpr == nil || n.rightExpr == nil {
return false
}
return n.leftExpr.Resolved() && n.rightExpr.Resolved()
}
// String implements the sql.Expression interface.
func (n *IsNotDistinctFrom) String() string {
return n.leftExpr.String() + " IS NOT DISTINCT FROM " + n.rightExpr.String()
}
// Type implements the sql.Expression interface.
func (n *IsNotDistinctFrom) Type(ctx *sql.Context) sql.Type {
return pgtypes.Bool
}
// WithChildren implements the sql.Expression interface.
func (n *IsNotDistinctFrom) WithChildren(ctx *sql.Context, children ...sql.Expression) (sql.Expression, error) {
if len(children) != 2 {
return nil, sql.ErrInvalidChildrenNumber.New(n, len(children), 2)
}
// This allows evaluating the arguments separate from function.Eval() in order to resolve NULL values.
// This follows the same logic as InTuple expression.
allAreWell := true
leftType, ok := children[0].Type(ctx).(*pgtypes.DoltgresType)
if !ok {
allAreWell = false
}
rightType, ok := children[1].Type(ctx).(*pgtypes.DoltgresType)
if !ok {
allAreWell = false
}
staticLeftLiteral := expression.NewLiteral(nil, leftType)
staticRightLiteral := expression.NewLiteral(nil, rightType)
if allAreWell {
cf := framework.GetBinaryFunction(framework.Operator_BinaryEqual).Compile(ctx, "internal_binary_operator_func_=", staticLeftLiteral, staticRightLiteral)
return &IsNotDistinctFrom{
leftExpr: children[0],
rightExpr: children[1],
staticLeftLiteral: staticLeftLiteral,
staticRightLiteral: staticRightLiteral,
equalFunc: cf,
}, nil
}
return &IsNotDistinctFrom{
leftExpr: children[0],
rightExpr: children[1],
}, nil
}
// WithResolvedChildren implements the vitess.InjectableExpression interface.
func (n *IsNotDistinctFrom) WithResolvedChildren(ctx context.Context, children []any) (any, error) {
if len(children) != 2 {
return nil, errors.Errorf("invalid vitess child count, expected `2` but got `%d`", len(children))
}
left, ok := children[0].(sql.Expression)
if !ok {
return nil, errors.Errorf("expected vitess child to be an expression but has type `%T`", children[0])
}
right, ok := children[1].(sql.Expression)
if !ok {
return nil, errors.Errorf("expected vitess child to be an expression but has type `%T`", children[1])
}
return n.WithChildren(ctx.(*sql.Context), left, right)
}
+95
View File
@@ -0,0 +1,95 @@
// Copyright 2025 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package expression
import (
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/go-mysql-server/sql/expression"
pgtypes "github.com/dolthub/doltgresql/server/types"
)
// IsNotNull is an implementation of sql.Expression for the IS NOT NULL operator
// and includes Postgres-specific logic for handling records and composites.
type IsNotNull struct {
expression.UnaryExpressionStub
}
var _ sql.Expression = (*IsNotNull)(nil)
var _ sql.CollationCoercible = (*IsNotNull)(nil)
var _ sql.IsNotNullExpression = (*IsNotNull)(nil)
// NewIsNotNull creates a new IsNotNull expression.
func NewIsNotNull(child sql.Expression) *IsNotNull {
return &IsNotNull{expression.UnaryExpressionStub{Child: child}}
}
// IsNotNullExpression implements the sql.IsNotNullExpression interface. This function exists primarily
// to ensure the IsNotNullExpression interface has a unique signature.
func (e *IsNotNull) IsNotNullExpression() bool {
return true
}
// Type implements the Expression interface.
func (e *IsNotNull) Type(ctx *sql.Context) sql.Type {
return pgtypes.Bool
}
// CollationCoercibility implements the interface sql.CollationCoercible.
func (*IsNotNull) CollationCoercibility(ctx *sql.Context) (collation sql.CollationID, coercibility byte) {
return sql.Collation_binary, 5
}
// IsNullable implements the Expression interface.
func (e *IsNotNull) IsNullable(ctx *sql.Context) bool {
return false
}
// Eval implements the Expression interface.
func (e *IsNotNull) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) {
v, err := e.Child.Eval(ctx, row)
if err != nil {
return nil, err
}
// Slices of typed values (e.g. Record and Composite types in Postgres) evaluate
// true for IS NOT NULL only if ALL of their entries are not NULL.
if tupleValue, ok := v.([]pgtypes.RecordValue); ok {
for _, typedValue := range tupleValue {
if typedValue.Value == nil {
return false, nil
}
}
return true, nil
}
return v != nil, nil
}
func (e *IsNotNull) String() string {
return e.Child.String() + " IS NOT NULL"
}
func (e *IsNotNull) DebugString(ctx *sql.Context) string {
return sql.DebugString(ctx, e.Child) + " IS NOT NULL"
}
// WithChildren implements the Expression interface.
func (e *IsNotNull) WithChildren(ctx *sql.Context, children ...sql.Expression) (sql.Expression, error) {
if len(children) != 1 {
return nil, sql.ErrInvalidChildrenNumber.New(e, len(children), 1)
}
return NewIsNotNull(children[0]), nil
}
+95
View File
@@ -0,0 +1,95 @@
// Copyright 2025 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package expression
import (
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/go-mysql-server/sql/expression"
pgtypes "github.com/dolthub/doltgresql/server/types"
)
// IsNull is an implementation of sql.Expression for the IS NULL operator and
// includes Postgres-specific logic for handling records and composites.
type IsNull struct {
expression.UnaryExpressionStub
}
var _ sql.Expression = (*IsNull)(nil)
var _ sql.CollationCoercible = (*IsNull)(nil)
var _ sql.IsNullExpression = (*IsNull)(nil)
// NewIsNull creates a new IsNull expression.
func NewIsNull(child sql.Expression) *IsNull {
return &IsNull{expression.UnaryExpressionStub{Child: child}}
}
// IsNullExpression implements the sql.IsNullExpression interface. This function exists primarily
// to ensure the IsNullExpression interface has a unique signature.
func (e *IsNull) IsNullExpression() bool {
return true
}
// Type implements the Expression interface.
func (e *IsNull) Type(ctx *sql.Context) sql.Type {
return pgtypes.Bool
}
// CollationCoercibility implements the interface sql.CollationCoercible.
func (*IsNull) CollationCoercibility(ctx *sql.Context) (collation sql.CollationID, coercibility byte) {
return sql.Collation_binary, 5
}
// IsNullable implements the Expression interface.
func (e *IsNull) IsNullable(ctx *sql.Context) bool {
return false
}
// Eval implements the Expression interface.
func (e *IsNull) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) {
v, err := e.Child.Eval(ctx, row)
if err != nil {
return nil, err
}
// Slices of typed values (e.g. Record and Composite types in Postgres) evaluate
// to NULL if all of their entries are NULL.
if tupleValue, ok := v.([]pgtypes.RecordValue); ok {
for _, typedValue := range tupleValue {
if typedValue.Value != nil {
return false, nil
}
}
return true, nil
}
return v == nil, nil
}
func (e *IsNull) String() string {
return e.Child.String() + " IS NULL"
}
func (e *IsNull) DebugString(ctx *sql.Context) string {
return sql.DebugString(ctx, e.Child) + " IS NULL"
}
// WithChildren implements the Expression interface.
func (e *IsNull) WithChildren(ctx *sql.Context, children ...sql.Expression) (sql.Expression, error) {
if len(children) != 1 {
return nil, sql.ErrInvalidChildrenNumber.New(e, len(children), 1)
}
return NewIsNull(children[0]), nil
}
+145
View File
@@ -0,0 +1,145 @@
// Copyright 2024 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package expression
import (
"github.com/cockroachdb/errors"
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/go-mysql-server/sql/expression"
"github.com/dolthub/doltgresql/server/functions/framework"
)
// JoinComparator is specifically for handling how GMS implements joins by implementing expression.Comparer over binary
// operators.
type JoinComparator struct {
eq *BinaryOperator
less *BinaryOperator
}
var _ sql.Expression = (*JoinComparator)(nil)
var _ expression.BinaryExpression = (*JoinComparator)(nil)
var _ expression.Equality = (*JoinComparator)(nil)
var _ expression.Comparer = (*JoinComparator)(nil)
// NewJoinComparator returns a new *JoinComparator.
func NewJoinComparator(ctx *sql.Context, eq *BinaryOperator) (*JoinComparator, error) {
if eq.operator != framework.Operator_BinaryEqual {
return nil, errors.Errorf("join comparator may only be created from equality operators")
}
less, err := (&BinaryOperator{operator: framework.Operator_BinaryLessThan}).WithResolvedChildren(ctx, []any{eq.Left(), eq.Right()})
if err != nil {
return nil, err
}
return &JoinComparator{
eq: eq,
less: less.(*BinaryOperator),
}, nil
}
// Children implements the sql.Expression interface.
func (j *JoinComparator) Children() []sql.Expression {
return []sql.Expression{j.eq}
}
// Compare implements the expression.Comparer interface.
func (j *JoinComparator) Compare(ctx *sql.Context, row sql.Row) (int, error) {
// Check equals
res, err := j.eq.Eval(ctx, row)
if err != nil {
return 0, err
}
if res.(bool) {
return 0, nil
}
// Check less than
res, err = j.less.Eval(ctx, row)
if err != nil {
return 0, err
}
if res.(bool) {
return -1, nil
}
// We'll assume it's greater
return 1, nil
}
// Eval implements the sql.Expression interface.
func (j *JoinComparator) Eval(ctx *sql.Context, row sql.Row) (any, error) {
// We only care about less in Compare.
return j.eq.Eval(ctx, row)
}
// IsNullable implements the sql.Expression interface.
func (j *JoinComparator) IsNullable(ctx *sql.Context) bool {
return j.eq.IsNullable(ctx)
}
// RepresentsEquality implements the expression.Equality interface.
func (j *JoinComparator) RepresentsEquality() bool {
return j.eq.RepresentsEquality()
}
// Resolved implements the sql.Expression interface.
func (j *JoinComparator) Resolved() bool {
return j.eq.Resolved() && j.less.Resolved()
}
// String implements the sql.Expression interface.
func (j *JoinComparator) String() string {
return j.eq.String()
}
// SwapParameters implements the expression.Equality interface.
func (j *JoinComparator) SwapParameters(ctx *sql.Context) (expression.Equality, error) {
newOper, err := j.eq.SwapParameters(ctx)
if err != nil {
return nil, err
}
return NewJoinComparator(ctx, newOper.(*BinaryOperator))
}
// ToComparer implements the expression.Equality interface.
func (j *JoinComparator) ToComparer(ctx *sql.Context) (expression.Comparer, error) {
return j, nil
}
// Type implements the sql.Expression interface.
func (j *JoinComparator) Type(ctx *sql.Context) sql.Type {
return j.eq.Type(ctx)
}
// WithChildren implements the sql.Expression interface.
func (j *JoinComparator) WithChildren(ctx *sql.Context, children ...sql.Expression) (sql.Expression, error) {
if len(children) != 1 {
return nil, sql.ErrInvalidChildrenNumber.New(j, len(children), 1)
}
binaryOper, ok := children[0].(*BinaryOperator)
if !ok {
return nil, errors.Errorf("expected join comparator child to be a binary operator but has type `%T`", children[0])
}
return NewJoinComparator(ctx, binaryOper)
}
// Left implements the expression.BinaryExpression interface.
func (j *JoinComparator) Left() sql.Expression {
return j.eq.Left()
}
// Right implements the expression.BinaryExpression interface.
func (j *JoinComparator) Right() sql.Expression {
return j.eq.Right()
}
+193
View File
@@ -0,0 +1,193 @@
// Copyright 2024 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package expression
import (
"strconv"
"time"
"github.com/cockroachdb/apd/v3"
"github.com/dolthub/go-mysql-server/sql/expression"
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
"github.com/dolthub/doltgresql/core/id"
"github.com/dolthub/doltgresql/postgres/parser/duration"
"github.com/dolthub/doltgresql/postgres/parser/timeofday"
"github.com/dolthub/doltgresql/postgres/parser/timetz"
"github.com/dolthub/doltgresql/postgres/parser/uuid"
pgtypes "github.com/dolthub/doltgresql/server/types"
)
// NewNumericLiteral returns a new *expression.Literal containing a NUMERIC value.
func NewNumericLiteral(numericValue string) (*expression.Literal, error) {
d, err := pgtypes.GetNumericValueFromStringWithTypmod(numericValue, -1)
if err != nil {
return nil, err
}
return expression.NewLiteral(d, pgtypes.Numeric), err
}
// NewIntegerLiteral returns a new *expression.Literal containing an integer (INT2/4/8 or NUMERIC) value.
func NewIntegerLiteral(integerValue string) (*expression.Literal, error) {
i, err := strconv.ParseInt(integerValue, 10, 64)
// If we don't get an error, then we know the value is either an INT32 or INT64
if err == nil {
if i >= -2147483648 && i <= 2147483647 {
return expression.NewLiteral(int32(i), pgtypes.Int32), err
} else {
return expression.NewLiteral(i, pgtypes.Int64), err
}
} else {
// If we errored the first time, then we'll assume it's a NUMERIC value
return NewNumericLiteral(integerValue)
}
}
// NewNullLiteral returns a new *expression.Literal containing a null value.
func NewNullLiteral() *expression.Literal {
return expression.NewLiteral(nil, pgtypes.Unknown)
}
// NewUnknownLiteral returns a new *expression.Literal containing a UNKNOWN type value.
func NewUnknownLiteral(stringValue string) *expression.Literal {
return expression.NewLiteral(stringValue, pgtypes.Unknown)
}
// NewTextLiteral returns a new *expression.Literal containing a TEXT type value.
// This should be used for internal uses when the type of the value is certain.
func NewTextLiteral(stringValue string) *expression.Literal {
return expression.NewLiteral(stringValue, pgtypes.Text)
}
// NewIntervalLiteral returns a new *expression.Literal containing a INTERVAL value.
func NewIntervalLiteral(duration duration.Duration) *expression.Literal {
return expression.NewLiteral(duration, pgtypes.Interval)
}
// NewJSONLiteral returns a new *expression.Literal containing a JSON value. This is different from JSONB.
func NewJSONLiteral(jsonValue string) *expression.Literal {
return expression.NewLiteral(jsonValue, pgtypes.Json)
}
// NewRawLiteralBool returns a new *expression.Literal containing a boolean value.
func NewRawLiteralBool(val bool) *expression.Literal {
return expression.NewLiteral(val, pgtypes.Bool)
}
// NewRawLiteralInt32 returns a new *expression.Literal containing an int32 value.
func NewRawLiteralInt32(val int32) *expression.Literal {
return expression.NewLiteral(val, pgtypes.Int32)
}
// NewRawLiteralInt64 returns a new *expression.Literal containing an int64 value.
func NewRawLiteralInt64(val int64) *expression.Literal {
return expression.NewLiteral(val, pgtypes.Int64)
}
// NewRawLiteralFloat32 returns a new *expression.Literal containing a float32 value.
func NewRawLiteralFloat32(val float32) *expression.Literal {
return expression.NewLiteral(val, pgtypes.Float32)
}
// NewRawLiteralFloat64 returns a new *expression.Literal containing a float64 value.
func NewRawLiteralFloat64(val float64) *expression.Literal {
return expression.NewLiteral(val, pgtypes.Float64)
}
// NewRawLiteralNumeric returns a new *expression.Literal containing an *apd.Decimal value.
func NewRawLiteralNumeric(val *apd.Decimal) *expression.Literal {
return expression.NewLiteral(val, pgtypes.Numeric)
}
// NewRawLiteralDate returns a new *expression.Literal containing a DATE value.
func NewRawLiteralDate(date time.Time) *expression.Literal {
return expression.NewLiteral(date, pgtypes.Date)
}
// NewRawLiteralTime returns a new *expression.Literal containing a TIME value.
func NewRawLiteralTime(t timeofday.TimeOfDay) *expression.Literal {
return expression.NewLiteral(t, pgtypes.Time)
}
// NewRawLiteralTimeTZ returns a new *expression.Literal containing a TIMETZ value.
func NewRawLiteralTimeTZ(ttz timetz.TimeTZ) *expression.Literal {
return expression.NewLiteral(ttz, pgtypes.TimeTZ)
}
// NewRawLiteralTimestamp returns a new *expression.Literal containing a TIMESTAMP value. This is the variant without a time zone.
func NewRawLiteralTimestamp(val time.Time) *expression.Literal {
return expression.NewLiteral(val, pgtypes.Timestamp)
}
// NewRawLiteralTimestampTZ returns a new *expression.Literal containing a TIMESTAMPTZ value. This is the variant with a time zone.
func NewRawLiteralTimestampTZ(val time.Time) *expression.Literal {
return expression.NewLiteral(val, pgtypes.TimestampTZ)
}
// NewRawLiteralJSON returns a new *expression.Literal containing a JSON value.
func NewRawLiteralJSON(val string) *expression.Literal {
return expression.NewLiteral(val, pgtypes.Json)
}
// NewRawLiteralOid returns a new *expression.Literal containing a OID value.
func NewRawLiteralOid(val id.Id) *expression.Literal {
return expression.NewLiteral(val, pgtypes.Oid)
}
// NewRawLiteralUuid returns a new *expression.Literal containing a UUID value.
func NewRawLiteralUuid(val uuid.UUID) *expression.Literal {
return expression.NewLiteral(val, pgtypes.Uuid)
}
// NewUnsafeLiteral returns a new *expression.Literal containing the given value and type. This should almost never be used, as
// it does not perform any checking and circumvents type safety, which may lead to hard-to-debug errors. This is
// currently only used within the analyzer, and will likely be removed in the future.
func NewUnsafeLiteral(val any, t *pgtypes.DoltgresType) *expression.Literal {
return expression.NewLiteral(val, t)
}
// ToVitessLiteral returns the literal as a Vitess literal. This is strictly for situations where GMS is hardcoded to
// expect a Vitess literal. This should only be used as a temporary measure, as the GMS code needs to be updated, or the
// equivalent functionality should be built into Doltgres (recommend the second approach).
func ToVitessLiteral(l *expression.Literal) *vitess.SQLVal {
// It's fine to use a nil context here as we know that the Literal expression doesn't need it
switch l.Type(nil).(*pgtypes.DoltgresType).ID {
case pgtypes.Bool.ID:
if l.Value().(bool) {
return vitess.NewIntVal([]byte("1"))
} else {
return vitess.NewIntVal([]byte("0"))
}
case pgtypes.Int32.ID:
return vitess.NewIntVal([]byte(strconv.FormatInt(int64(l.Value().(int32)), 10)))
case pgtypes.Int64.ID:
return vitess.NewIntVal([]byte(strconv.FormatInt(l.Value().(int64), 10)))
case pgtypes.Numeric.ID:
d := l.Value().(*apd.Decimal)
return vitess.NewFloatVal([]byte(d.String()))
case pgtypes.Text.ID:
return vitess.NewStrVal([]byte(l.Value().(string)))
case pgtypes.Unknown.ID:
if l.Value() == nil {
return nil
} else if str, ok := l.Value().(string); ok {
return vitess.NewStrVal([]byte(str))
} else {
panic("unhandled value of 'unknown' type in temporary literal conversion: " + l.Type(nil).String())
}
default:
panic("unhandled type in temporary literal conversion: " + l.Type(nil).String())
}
}
+120
View File
@@ -0,0 +1,120 @@
// Copyright 2024 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package expression
import (
"context"
"github.com/cockroachdb/errors"
"github.com/dolthub/go-mysql-server/sql"
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
pgtypes "github.com/dolthub/doltgresql/server/types"
)
// Not represents a NOT expression.
type Not struct {
child sql.Expression
}
var _ vitess.Injectable = (*Not)(nil)
var _ sql.Expression = (*Not)(nil)
// NewNot returns a new *Not.
func NewNot() *Not {
return &Not{
child: nil,
}
}
var _ sql.IndexComparisonExpression = (*Not)(nil)
// Children implements the sql.Expression interface.
func (n *Not) Children() []sql.Expression {
return []sql.Expression{n.child}
}
// Eval implements the sql.Expression interface.
func (n *Not) Eval(ctx *sql.Context, row sql.Row) (any, error) {
val, err := n.child.Eval(ctx, row)
if err != nil {
return nil, err
}
if val == nil {
return nil, nil
}
boolVal, ok := val.(bool)
if !ok {
return nil, errors.Errorf("NOT only applies to boolean values")
}
return !boolVal, nil
}
// IsNullable implements the sql.Expression interface.
func (n *Not) IsNullable(ctx *sql.Context) bool {
return true
}
// Resolved implements the sql.Expression interface.
func (n *Not) Resolved() bool {
return n.child != nil && n.child.Resolved()
}
// String implements the sql.Expression interface.
func (n *Not) String() string {
if n.child == nil {
return "NOT ?"
}
return "NOT " + n.child.String()
}
// Type implements the sql.Expression interface.
func (n *Not) Type(ctx *sql.Context) sql.Type {
return pgtypes.Bool
}
// WithChildren implements the sql.Expression interface.
func (n *Not) WithChildren(ctx *sql.Context, children ...sql.Expression) (sql.Expression, error) {
if len(children) != 1 {
return nil, sql.ErrInvalidChildrenNumber.New(n, len(children), 1)
}
return &Not{
child: children[0],
}, nil
}
// WithResolvedChildren implements the vitess.InjectableExpression interface.
func (n *Not) WithResolvedChildren(ctx context.Context, children []any) (any, error) {
if len(children) != 1 {
return nil, errors.Errorf("invalid vitess child count, expected `1` but got `%d`", len(children))
}
child, ok := children[0].(sql.Expression)
if !ok {
return nil, errors.Errorf("expected vitess child to be an expression but has type `%T`", children[0])
}
return &Not{
child: child,
}, nil
}
// IndexScanOperation implements the sql.IndexComparisonExpression interface.
func (n *Not) IndexScanOperation() (sql.IndexScanOp, sql.Expression, sql.Expression, bool) {
it, ok := n.child.(*InTuple)
if !ok {
return 0, nil, nil, false
}
return sql.IndexScanOpNotInSet, it.Left(), it.Right(), true
}
+122
View File
@@ -0,0 +1,122 @@
// Copyright 2025 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package expression
import (
"context"
"fmt"
"github.com/cockroachdb/errors"
"github.com/dolthub/go-mysql-server/sql"
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
pgtypes "github.com/dolthub/doltgresql/server/types"
)
// NewRecordExpr creates a new record expression.
func NewRecordExpr() *RecordExpr {
return &RecordExpr{}
}
// RecordExpr is a set of sql.Expressions wrapped together in a single value.
type RecordExpr struct {
exprs []sql.Expression
}
var _ sql.Expression = (*RecordExpr)(nil)
var _ vitess.Injectable = (*RecordExpr)(nil)
// Resolved implements the sql.Expression interface.
func (t *RecordExpr) Resolved() bool {
for _, expr := range t.exprs {
if !expr.Resolved() {
return false
}
}
return true
}
// String implements the sql.Expression interface.
func (t *RecordExpr) String() string {
return "RECORD EXPR"
}
// Type implements the sql.Expression interface.
func (t *RecordExpr) Type(ctx *sql.Context) sql.Type {
return pgtypes.Record
}
// IsNullable implements the sql.Expression interface.
func (t *RecordExpr) IsNullable(ctx *sql.Context) bool {
return false
}
// Eval implements the sql.Expression interface.
func (t *RecordExpr) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) {
vals := make([]pgtypes.RecordValue, len(t.exprs))
for i, expr := range t.exprs {
val, err := expr.Eval(ctx, row)
if err != nil {
return nil, err
}
t := expr.Type(ctx)
typ, ok := t.(*pgtypes.DoltgresType)
if !ok {
// TODO: it would be better if we had a doltgres type for NULL literals in these records
if typ, ok := t.(sql.NullType); ok && typ.IsNullType() {
typ = pgtypes.Unknown
} else {
return nil, fmt.Errorf("expected a DoltgresType, but got %T", t)
}
}
vals[i] = pgtypes.RecordValue{
Value: val,
Type: typ,
}
}
return vals, nil
}
// Children implements the sql.Expression interface.
func (t *RecordExpr) Children() []sql.Expression {
return t.exprs
}
// WithChildren implements the sql.Expression interface.
func (t *RecordExpr) WithChildren(ctx *sql.Context, children ...sql.Expression) (sql.Expression, error) {
tCopy := *t
tCopy.exprs = children
return &tCopy, nil
}
// WithResolvedChildren implements the vitess.Injectable interface
func (t *RecordExpr) WithResolvedChildren(ctx context.Context, children []any) (any, error) {
newExpressions := make([]sql.Expression, len(children))
for i, resolvedChild := range children {
resolvedExpression, ok := resolvedChild.(sql.Expression)
if !ok {
return nil, errors.Errorf("expected vitess child to be an expression but has type `%T`", resolvedChild)
}
newExpressions[i] = resolvedExpression
}
return t.WithChildren(ctx.(*sql.Context), newExpressions...)
}
// Expressions implements the expression.TupleLike interface.
func (t *RecordExpr) Expressions() []sql.Expression {
return t.exprs
}
+25
View File
@@ -0,0 +1,25 @@
// Copyright 2024 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package expression
// NewSomeExpr creates a new AnyExpr expression for SOME. SOME is synonymous with ANY.
func NewSomeExpr(subOperator string) *AnyExpr {
return &AnyExpr{
leftExpr: nil,
rightExpr: nil,
subOperator: subOperator,
name: "SOME",
}
}
+74
View File
@@ -0,0 +1,74 @@
// Copyright 2025 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package expression
import (
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/go-mysql-server/sql/procedures"
pgtypes "github.com/dolthub/doltgresql/server/types"
)
// StatementRunner is an expression that can be added to a node to grab the statement runner.
type StatementRunner struct {
Runner sql.StatementRunner
}
var _ sql.Expression = StatementRunner{}
var _ procedures.InterpreterExpr = StatementRunner{}
// Children implements the sql.Expression interface.
func (StatementRunner) Children() []sql.Expression {
return nil
}
// Eval implements the sql.Expression interface.
func (StatementRunner) Eval(ctx *sql.Context, row sql.Row) (any, error) {
return nil, nil
}
// IsNullable implements the sql.Expression interface.
func (StatementRunner) IsNullable(ctx *sql.Context) bool {
return false
}
// Resolved implements the sql.Expression interface.
func (StatementRunner) Resolved() bool {
return true
}
// SetStatementRunner implements the sql.Expression interface.
func (sr StatementRunner) SetStatementRunner(ctx *sql.Context, runner sql.StatementRunner) sql.Expression {
sr.Runner = runner
return sr
}
// String implements the sql.Expression interface.
func (StatementRunner) String() string {
return "StatementRunner"
}
// Type implements the sql.Expression interface.
func (StatementRunner) Type(ctx *sql.Context) sql.Type {
return pgtypes.Unknown
}
// WithChildren implements the sql.Expression interface.
func (sr StatementRunner) WithChildren(ctx *sql.Context, children ...sql.Expression) (sql.Expression, error) {
if len(children) != 0 {
return nil, sql.ErrInvalidChildrenNumber.New(sr, len(children), 0)
}
return sr, nil
}
+135
View File
@@ -0,0 +1,135 @@
// Copyright 2025 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package expression
import (
"context"
"fmt"
"github.com/dolthub/go-mysql-server/sql"
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
"github.com/dolthub/doltgresql/server/types"
)
// Subscript represents a subscript expression, e.g. `a[1]`.
type Subscript struct {
Child sql.Expression
Index sql.Expression
}
var _ vitess.Injectable = (*Subscript)(nil)
var _ sql.Expression = (*Subscript)(nil)
// NewSubscript creates a new Subscript expression.
func NewSubscript(child, index sql.Expression) *Subscript {
return &Subscript{
Child: child,
Index: index,
}
}
// Resolved implements the sql.Expression interface.
func (s Subscript) Resolved() bool {
return s.Child.Resolved() && s.Index.Resolved()
}
// String implements the sql.Expression interface.
func (s Subscript) String() string {
return fmt.Sprintf("%s[%s]", s.Child, s.Index)
}
// Type implements the sql.Expression interface.
func (s Subscript) Type(ctx *sql.Context) sql.Type {
dt, ok := s.Child.Type(ctx).(*types.DoltgresType)
if !ok {
panic(fmt.Sprintf("unexpected type %T for subscript", s.Child.Type(ctx)))
}
return dt.ArrayBaseType()
}
// IsNullable implements the sql.Expression interface.
func (s Subscript) IsNullable(ctx *sql.Context) bool {
return true
}
// Eval implements the sql.Expression interface.
func (s Subscript) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) {
childVal, err := s.Child.Eval(ctx, row)
if err != nil {
return nil, err
}
if childVal == nil {
return nil, nil
}
indexVal, err := s.Index.Eval(ctx, row)
if err != nil {
return nil, err
}
if indexVal == nil {
return nil, nil
}
switch child := childVal.(type) {
case []interface{}:
index, ok := indexVal.(int32)
if !ok {
converted, _, err := types.Int32.Convert(ctx, indexVal)
if err != nil {
return nil, err
}
index = converted.(int32)
}
// subscripts are 1-based
if index < 1 || int(index) > len(child) {
return nil, nil
}
return child[index-1], nil
default:
return nil, fmt.Errorf("unsupported type %T for subscript", child)
}
}
// Children implements the sql.Expression interface.
func (s Subscript) Children() []sql.Expression {
return []sql.Expression{s.Child, s.Index}
}
// WithChildren implements the sql.Expression interface.
func (s Subscript) WithChildren(ctx *sql.Context, children ...sql.Expression) (sql.Expression, error) {
if len(children) != 2 {
return nil, fmt.Errorf("expected 2 children, got %d", len(children))
}
return NewSubscript(children[0], children[1]), nil
}
// WithResolvedChildren implements the vitess.Injectable interface.
func (s Subscript) WithResolvedChildren(ctx context.Context, children []any) (any, error) {
if len(children) != 2 {
return nil, fmt.Errorf("expected 2 children, got %d", len(children))
}
child, ok := children[0].(sql.Expression)
if !ok {
return nil, fmt.Errorf("expected child to be an expression but has type `%T`", children[0])
}
index, ok := children[1].(sql.Expression)
if !ok {
return nil, fmt.Errorf("expected index to be an expression but has type `%T`", children[1])
}
return NewSubscript(child, index), nil
}
+113
View File
@@ -0,0 +1,113 @@
// Copyright 2025 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package expression
import (
"fmt"
"github.com/cockroachdb/errors"
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/doltgresql/core"
"github.com/dolthub/doltgresql/core/id"
pgtypes "github.com/dolthub/doltgresql/server/types"
)
// TableToComposite is a set of sql.Expressions wrapped together in a single value.
type TableToComposite struct {
fields []sql.Expression
typ *pgtypes.DoltgresType
}
var _ sql.Expression = (*TableToComposite)(nil)
// NewTableToComposite creates a new composite table type.
func NewTableToComposite(ctx *sql.Context, tableName string, fields []sql.Expression) (sql.Expression, error) {
coll, err := core.GetTypesCollectionFromContext(ctx, "")
if err != nil {
return nil, err
}
// TODO: we need to get the schema, but the GMS builder doesn't have that information
typ, err := coll.GetType(ctx, id.NewType("", tableName))
if err != nil {
return nil, err
}
if typ == nil {
return nil, errors.New(fmt.Sprintf(`could not create a composite type for table "%s"`, tableName))
}
return &TableToComposite{
fields: fields,
typ: typ,
}, nil
}
// Resolved implements the sql.Expression interface.
func (t *TableToComposite) Resolved() bool {
for _, expr := range t.fields {
if !expr.Resolved() {
return false
}
}
return true
}
// String implements the sql.Expression interface.
func (t *TableToComposite) String() string {
return "TABLE TO COMPOSITE"
}
// Type implements the sql.Expression interface.
func (t *TableToComposite) Type(ctx *sql.Context) sql.Type {
return t.typ
}
// IsNullable implements the sql.Expression interface.
func (t *TableToComposite) IsNullable(ctx *sql.Context) bool {
return false
}
// Eval implements the sql.Expression interface.
func (t *TableToComposite) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) {
vals := make([]pgtypes.RecordValue, len(t.fields))
for i, expr := range t.fields {
val, err := expr.Eval(ctx, row)
if err != nil {
return nil, err
}
typ, ok := expr.Type(ctx).(*pgtypes.DoltgresType)
if !ok {
return nil, fmt.Errorf("expected a DoltgresType, but got %T", expr.Type(ctx))
}
vals[i] = pgtypes.RecordValue{
Value: val,
Type: typ,
}
}
return vals, nil
}
// Children implements the sql.Expression interface.
func (t *TableToComposite) Children() []sql.Expression {
return t.fields
}
// WithChildren implements the sql.Expression interface.
func (t *TableToComposite) WithChildren(ctx *sql.Context, children ...sql.Expression) (sql.Expression, error) {
tCopy := *t
tCopy.fields = children
return &tCopy, nil
}
+117
View File
@@ -0,0 +1,117 @@
// Copyright 2024 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package expression
import (
"context"
"fmt"
"github.com/cockroachdb/errors"
"github.com/dolthub/go-mysql-server/sql"
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
"github.com/dolthub/doltgresql/server/functions/framework"
)
// UnaryOperator represents a VALUE OPERATOR VALUE expression.
type UnaryOperator struct {
operator framework.Operator
compiledFunc framework.Function
}
var _ vitess.Injectable = (*UnaryOperator)(nil)
var _ sql.Expression = (*UnaryOperator)(nil)
// NewUnaryOperator returns a new *UnaryOperator.
func NewUnaryOperator(operator framework.Operator) *UnaryOperator {
return &UnaryOperator{operator: operator}
}
// Children implements the sql.Expression interface.
func (b *UnaryOperator) Children() []sql.Expression {
return b.compiledFunc.Children()
}
// Eval implements the sql.Expression interface.
func (b *UnaryOperator) Eval(ctx *sql.Context, row sql.Row) (any, error) {
return b.compiledFunc.Eval(ctx, row)
}
// IsNullable implements the sql.Expression interface.
func (b *UnaryOperator) IsNullable(ctx *sql.Context) bool {
return b.compiledFunc.IsNullable(ctx)
}
// Resolved implements the sql.Expression interface.
func (b *UnaryOperator) Resolved() bool {
return b.compiledFunc.Resolved()
}
// String implements the sql.Expression interface.
func (b *UnaryOperator) String() string {
if b.compiledFunc == nil {
return fmt.Sprintf("%s?", b.operator.String())
}
// We know that we'll always have one parameter here
switch f := b.compiledFunc.(type) {
case *framework.CompiledFunction:
return fmt.Sprintf("%s%s", b.operator.String(), f.Arguments[0].String())
case *framework.QuickFunction1:
return fmt.Sprintf("%s%s", b.operator.String(), f.Argument.String())
default:
return fmt.Sprintf("unexpected unary operator function type: %T", b.compiledFunc)
}
}
// Type implements the sql.Expression interface.
func (b *UnaryOperator) Type(ctx *sql.Context) sql.Type {
return b.compiledFunc.Type(ctx)
}
// WithChildren implements the sql.Expression interface.
func (b *UnaryOperator) WithChildren(ctx *sql.Context, children ...sql.Expression) (sql.Expression, error) {
if len(children) != 1 {
return nil, sql.ErrInvalidChildrenNumber.New(b, len(children), 1)
}
compiledFunc, err := b.compiledFunc.WithChildren(ctx, children...)
if err != nil {
return nil, err
}
return &UnaryOperator{
operator: b.operator,
compiledFunc: compiledFunc.(framework.Function),
}, nil
}
// WithResolvedChildren implements the vitess.InjectableExpression interface.
func (b *UnaryOperator) WithResolvedChildren(ctx context.Context, children []any) (any, error) {
if len(children) != 1 {
return nil, errors.Errorf("invalid vitess child count, expected `1` but got `%d`", len(children))
}
sqlCtx := ctx.(*sql.Context)
child, ok := children[0].(sql.Expression)
if !ok {
return nil, errors.Errorf("expected vitess child to be an expression but has type `%T`", children[0])
}
funcName := "internal_unary_operator_func_" + b.operator.String()
compiledFunc := framework.GetUnaryFunction(b.operator).Compile(sqlCtx, funcName, child)
if compiledFunc == nil {
return nil, errors.Errorf("operator does not exist: %s%s", b.operator.String(), child.Type(sqlCtx).String())
}
return &UnaryOperator{
operator: b.operator,
compiledFunc: compiledFunc,
}, nil
}