Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions server/ast/alter_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ func nodeAlterTableCmds(
}
statement.IndexSpec = &vitess.IndexSpec{
Action: "create",
ToName: vitess.NewColIdent(string(cmd.ColumnDef.UniqueConstraintName)),
Type: "unique",
Fields: indexFields,
}
Expand Down
30 changes: 3 additions & 27 deletions server/ast/column_table_def.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,6 @@ func nodeColumnTableDef(ctx *Context, node *tree.ColumnTableDef) (*vitess.Column
if node == nil {
return nil, nil
}
if len(node.Nullable.ConstraintName) > 0 ||
len(node.DefaultExpr.ConstraintName) > 0 ||
len(node.UniqueConstraintName) > 0 {
return nil, errors.Errorf("non-foreign key column constraint names are not yet supported")
}
convertType, resolvedType, err := nodeResolvableTypeReference(ctx, node.Type, false)
if err != nil {
return nil, err
Expand Down Expand Up @@ -110,15 +105,9 @@ func nodeColumnTableDef(ctx *Context, node *tree.ColumnTableDef) (*vitess.Column
}
}

if generated != nil {
// GMS requires the AST to wrap function expressions in parens
if _, ok := generated.(*vitess.FuncExpr); ok {
generated = &vitess.ParenExpr{Expr: generated}
}

// clean up the expressions generated here. our default expression handling generates aliases that aren't
// appropriate in this context.
generated = clearAliases(generated)
// GMS requires the AST to wrap function expressions in parens
if _, ok := generated.(*vitess.FuncExpr); ok {
generated = &vitess.ParenExpr{Expr: generated}
}

if node.IsSerial || computedByDefaultAsIdentity || computedAsIdentity {
Expand Down Expand Up @@ -181,16 +170,3 @@ func nodeColumnTableDef(ctx *Context, node *tree.ColumnTableDef) (*vitess.Column
}
return colDef, nil
}

// clearAliases removes As and InputExpression from any AliasedExpr in the expression tree given. This is required
// in some contexts where we expect the expression to serialize to a string without any alias names.
func clearAliases(e vitess.Expr) vitess.Expr {
_ = vitess.Walk(func(node vitess.SQLNode) (kontinue bool, err error) {
if expr, ok := node.(*vitess.AliasedExpr); ok {
expr.As = vitess.ColIdent{}
expr.InputExpression = ""
}
return true, nil
}, e)
return e
}
2 changes: 2 additions & 0 deletions server/ast/resolvable_type_reference.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,8 @@ func nodeResolvableTypeReference(ctx *Context, typ tree.ResolvableTypeReference,
doltgresType = pgtypes.Oidvector
case oid.T_regclass:
doltgresType = pgtypes.Regclass
case oid.T_regnamespace:
doltgresType = pgtypes.Regnamespace
case oid.T_regproc:
doltgresType = pgtypes.Regproc
case oid.T_regtype:
Expand Down
17 changes: 16 additions & 1 deletion server/ast/select.go
Original file line number Diff line number Diff line change
Expand Up @@ -237,9 +237,13 @@ func nodeExprToSelectExpr(ctx *Context, node tree.Expr) (vitess.SelectExpr, erro
if node == nil {
return nil, nil
}
return nodeSelectExpr(ctx, tree.SelectExpr{
selectExpr, err := nodeSelectExpr(ctx, tree.SelectExpr{
Expr: node,
})
if err != nil {
return nil, err
}
return clearArgumentAlias(selectExpr), nil
}

// nodeExprsToSelectExprs handles tree.Exprs nodes and returns the results as vitess.SelectExprs.
Expand All @@ -256,6 +260,17 @@ func nodeExprsToSelectExprs(ctx *Context, node tree.Exprs) (vitess.SelectExprs,
if err != nil {
return nil, err
}
selectExprs[i] = clearArgumentAlias(selectExprs[i])
}
return selectExprs, nil
}

// clearArgumentAlias removes the alias that `nodeSelectExpr` gives a function argument, which would otherwise be
// written back to text as "x as x".
func clearArgumentAlias(node vitess.SelectExpr) vitess.SelectExpr {
if aliasedExpr, ok := node.(*vitess.AliasedExpr); ok {
aliasedExpr.As = vitess.ColIdent{}
aliasedExpr.InputExpression = ""
}
return node
}
2 changes: 1 addition & 1 deletion server/ast/table_def.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ func assignTableDef(ctx *Context, node tree.TableDef, target *vitess.DDL) error
return err
}
target.TableSpec.Indexes = append(target.TableSpec.Indexes, &vitess.IndexDefinition{
Info: &vitess.IndexInfo{Unique: true},
Info: &vitess.IndexInfo{Name: vitess.NewColIdent(string(node.UniqueConstraintName)), Unique: true},
Fields: indexFields,
})
}
Expand Down
31 changes: 25 additions & 6 deletions server/auth/auth_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import (
vitess "github.com/dolthub/vitess/go/vt/sqlparser"

"github.com/dolthub/doltgresql/core"
"github.com/dolthub/doltgresql/server/functions/framework"
"github.com/dolthub/doltgresql/core/id"
)

// AuthorizationQueryState contains any cached state for a query.
Expand Down Expand Up @@ -373,15 +373,34 @@ func checkPrivilegeOnRoutine(ctx *sql.Context, state AuthorizationQueryState, sc
}
for _, privilege := range privileges {
if !HasRoutinePrivilege(roleRoutineKey, privilege) && !HasRoutinePrivilege(publicRoutineKey, privilege) {
// check if it's system function
_, ok := framework.Catalog[strings.ToLower(routineName)]
if ok && schemaName == "" {
// TODO: for now we don't check privilege for pg_catalog tables as it's granted for PUBLIC by default
// need to fix it when we support 'REVOKE privileges FROM PUBLIC'
userDefined, err := isUserDefinedRoutine(ctx, schName, routineName)
if err != nil {
return err
}
if !userDefined {
//TODO: built-in routines are granted to PUBLIC by default, so deny them once REVOKE ... FROM PUBLIC is supported
return nil
}
return errors.Errorf("permission denied for routine %s", routineName)
}
}
return nil
}

// isUserDefinedRoutine returns whether a function or procedure with the given name exists in the given schema.
func isUserDefinedRoutine(ctx *sql.Context, schemaName string, routineName string) (bool, error) {
funcCollection, err := core.GetFunctionsCollectionFromContext(ctx, "")
if err != nil {
return false, err
}
funcOverloads, err := funcCollection.GetFunctionOverloads(ctx, id.NewFunction(schemaName, routineName))
if err != nil || len(funcOverloads) > 0 {
return len(funcOverloads) > 0, err
}
procCollection, err := core.GetProceduresCollectionFromContext(ctx, "")
if err != nil {
return false, err
}
procOverloads, err := procCollection.GetProcedureOverloads(ctx, id.NewProcedure(schemaName, routineName))
return len(procOverloads) > 0, err
}
10 changes: 7 additions & 3 deletions server/cast/char.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,14 +92,18 @@ func charImplicit(builtInCasts map[id.Cast]casts.Cast) {
if err != nil {
return nil, err
}
return handleStringCast(str, targetType)
return handleStringCast(strings.TrimRight(str, " "), targetType)
},
})
framework.MustAddImplicitTypeCast(builtInCasts, framework.TypeCast{
FromType: pgtypes.BpChar,
ToType: pgtypes.Text,
Function: func(ctx *sql.Context, val any, _, targetType *pgtypes.DoltgresType) (any, error) {
return val, nil
str, err := framework.UnwrapString(ctx, val)
if err != nil {
return nil, err
}
return strings.TrimRight(str, " "), nil
},
})
framework.MustAddImplicitTypeCast(builtInCasts, framework.TypeCast{
Expand All @@ -110,7 +114,7 @@ func charImplicit(builtInCasts map[id.Cast]casts.Cast) {
if err != nil {
return nil, err
}
return handleStringCast(str, targetType)
return handleStringCast(strings.TrimRight(str, " "), targetType)
},
})
}
1 change: 1 addition & 0 deletions server/cast/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ func Init(builtInCasts map[id.Cast]casts.Cast) {
initNumeric(builtInCasts)
initOid(builtInCasts)
initRegclass(builtInCasts)
initRegnamespace(builtInCasts)
initRegproc(builtInCasts)
initRegtype(builtInCasts)
initText(builtInCasts)
Expand Down
10 changes: 10 additions & 0 deletions server/cast/int16.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,16 @@ func int16Implicit(builtInCasts map[id.Cast]casts.Cast) {
return id.NewOID(uint32(val.(int16))).AsId(), nil
},
})
framework.MustAddImplicitTypeCast(builtInCasts, framework.TypeCast{
FromType: pgtypes.Int16,
ToType: pgtypes.Regnamespace,
Function: func(ctx *sql.Context, val any, _, targetType *pgtypes.DoltgresType) (any, error) {
if internalID := id.Cache().ToInternal(uint32(val.(int16))); internalID.IsValid() {
return internalID, nil
}
return id.NewOID(uint32(val.(int16))).AsId(), nil
},
})
framework.MustAddImplicitTypeCast(builtInCasts, framework.TypeCast{
FromType: pgtypes.Int16,
ToType: pgtypes.Regproc,
Expand Down
10 changes: 10 additions & 0 deletions server/cast/int32.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,16 @@ func int32Implicit(builtInCasts map[id.Cast]casts.Cast) {
return id.NewOID(uint32(val.(int32))).AsId(), nil
},
})
framework.MustAddImplicitTypeCast(builtInCasts, framework.TypeCast{
FromType: pgtypes.Int32,
ToType: pgtypes.Regnamespace,
Function: func(ctx *sql.Context, val any, _, targetType *pgtypes.DoltgresType) (any, error) {
if internalID := id.Cache().ToInternal(uint32(val.(int32))); internalID.IsValid() {
return internalID, nil
}
return id.NewOID(uint32(val.(int32))).AsId(), nil
},
})
framework.MustAddImplicitTypeCast(builtInCasts, framework.TypeCast{
FromType: pgtypes.Int32,
ToType: pgtypes.Regproc,
Expand Down
13 changes: 13 additions & 0 deletions server/cast/int64.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,19 @@ func int64Implicit(builtInCasts map[id.Cast]casts.Cast) {
return id.NewOID(uint32(val.(int64))).AsId(), nil
},
})
framework.MustAddImplicitTypeCast(builtInCasts, framework.TypeCast{
FromType: pgtypes.Int64,
ToType: pgtypes.Regnamespace,
Function: func(ctx *sql.Context, val any, _, targetType *pgtypes.DoltgresType) (any, error) {
if val.(int64) > int64(math.MaxUint32) || val.(int64) < 0 {
return nil, errOutOfRange.New(targetType.String())
}
if internalID := id.Cache().ToInternal(uint32(val.(int64))); internalID.IsValid() {
return internalID, nil
}
return id.NewOID(uint32(val.(int64))).AsId(), nil
},
})
framework.MustAddImplicitTypeCast(builtInCasts, framework.TypeCast{
FromType: pgtypes.Int64,
ToType: pgtypes.Regproc,
Expand Down
7 changes: 7 additions & 0 deletions server/cast/oid.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,13 @@ func oidImplicit(builtInCasts map[id.Cast]casts.Cast) {
return val, nil
},
})
framework.MustAddImplicitTypeCast(builtInCasts, framework.TypeCast{
FromType: pgtypes.Oid,
ToType: pgtypes.Regnamespace,
Function: func(ctx *sql.Context, val any, _, targetType *pgtypes.DoltgresType) (any, error) {
return val, nil
},
})
framework.MustAddImplicitTypeCast(builtInCasts, framework.TypeCast{
FromType: pgtypes.Oid,
ToType: pgtypes.Regproc,
Expand Down
59 changes: 59 additions & 0 deletions server/cast/regnamespace.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// 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 cast

import (
"github.com/dolthub/go-mysql-server/sql"

"github.com/dolthub/doltgresql/core/casts"
"github.com/dolthub/doltgresql/core/id"
"github.com/dolthub/doltgresql/server/functions/framework"
pgtypes "github.com/dolthub/doltgresql/server/types"
)

// initRegnamespace handles all casts that are built-in. This comprises only the source types.
func initRegnamespace(builtInCasts map[id.Cast]casts.Cast) {
regnamespaceAssignment(builtInCasts)
regnamespaceImplicit(builtInCasts)
}

// regnamespaceAssignment registers all assignment casts. This comprises only the source types.
func regnamespaceAssignment(builtInCasts map[id.Cast]casts.Cast) {
framework.MustAddAssignmentTypeCast(builtInCasts, framework.TypeCast{
FromType: pgtypes.Regnamespace,
ToType: pgtypes.Int32,
Function: func(ctx *sql.Context, val any, _, targetType *pgtypes.DoltgresType) (any, error) {
return int32(id.Cache().ToOID(val.(id.Id))), nil
},
})
framework.MustAddAssignmentTypeCast(builtInCasts, framework.TypeCast{
FromType: pgtypes.Regnamespace,
ToType: pgtypes.Int64,
Function: func(ctx *sql.Context, val any, _, targetType *pgtypes.DoltgresType) (any, error) {
return int64(id.Cache().ToOID(val.(id.Id))), nil
},
})
}

// regnamespaceImplicit registers all implicit casts. This comprises only the source types.
func regnamespaceImplicit(builtInCasts map[id.Cast]casts.Cast) {
framework.MustAddImplicitTypeCast(builtInCasts, framework.TypeCast{
FromType: pgtypes.Regnamespace,
ToType: pgtypes.Oid,
Function: func(ctx *sql.Context, val any, _, targetType *pgtypes.DoltgresType) (any, error) {
return val, nil
},
})
}
36 changes: 36 additions & 0 deletions server/compare/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,3 +142,39 @@ func callComparisonFunction(ctx *sql.Context, op framework.Operator, leftLiteral
ctx, "_internal_record_comparison_function", leftLiteral, rightLiteral)
return compiledFunction.Eval(ctx, nil)
}

// RecordsAreDistinct returns whether two records differ in any field, with NULL fields only equal to each other.
func RecordsAreDistinct(ctx *sql.Context, v1 interface{}, v2 interface{}) (bool, error) {
leftRecord, rightRecord, err := checkRecordArgs(v1, v2)
if err != nil {
return false, err
}
var leftLiteral, rightLiteral expression.Literal
for i := 0; i < len(leftRecord); i++ {
if leftRecord[i].Value == nil || rightRecord[i].Value == nil {
if leftRecord[i].Value != nil || rightRecord[i].Value != nil {
return true, nil
}
continue
}
if _, ok := leftRecord[i].Value.([]pgtypes.RecordValue); ok {
distinct, err := RecordsAreDistinct(ctx, leftRecord[i].Value, rightRecord[i].Value)
if err != nil || distinct {
return distinct, err
}
continue
}
leftLiteral.Val = leftRecord[i].Value
leftLiteral.Typ = leftRecord[i].Type
rightLiteral.Val = rightRecord[i].Value
rightLiteral.Typ = rightRecord[i].Type
res, err := callComparisonFunction(ctx, framework.Operator_BinaryNotEqual, &leftLiteral, &rightLiteral)
if err != nil {
return false, err
}
if res == true {
return true, nil
}
}
return false, nil
}
4 changes: 2 additions & 2 deletions server/expression/binary_operator.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,10 @@ func (b *BinaryOperator) String() 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",
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",
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)
Expand Down
5 changes: 5 additions & 0 deletions server/expression/is_distinct_from.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"github.com/dolthub/go-mysql-server/sql/expression"
vitess "github.com/dolthub/vitess/go/vt/sqlparser"

"github.com/dolthub/doltgresql/server/compare"
"github.com/dolthub/doltgresql/server/functions/framework"
pgtypes "github.com/dolthub/doltgresql/server/types"
)
Expand Down Expand Up @@ -68,6 +69,10 @@ func (n *IsDistinctFrom) Eval(ctx *sql.Context, row sql.Row) (any, error) {
} else if left == nil || right == nil {
return true, nil
}
if _, ok := left.([]pgtypes.RecordValue); ok {
distinct, err := compare.RecordsAreDistinct(ctx, left, right)
return distinct, err
}

n.staticLeftLiteral.Val = left
n.staticRightLiteral.Val = right
Expand Down
Loading
Loading