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
+197
View File
@@ -0,0 +1,197 @@
// 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 node
import (
"context"
"time"
"github.com/cockroachdb/errors"
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/go-mysql-server/sql/plan"
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
gmserrors "gopkg.in/src-d/go-errors.v1"
"github.com/dolthub/doltgresql/server/auth"
pgtypes "github.com/dolthub/doltgresql/server/types"
)
// AlterRole handles the ALTER ROLE and ALTER USER statements (ALTER USER is an alias).
type AlterRole struct {
Name string
Options map[string]any
}
// ErrVitessChildCount is returned by WithResolvedChildren to indicate that the expected child count is incorrect.
var ErrVitessChildCount = gmserrors.NewKind("invalid vitess child count, expected `%d` but got `%d`")
var _ sql.ExecSourceRel = (*AlterRole)(nil)
var _ vitess.Injectable = (*AlterRole)(nil)
// Children implements the interface sql.ExecSourceRel.
func (c *AlterRole) Children() []sql.Node {
return nil
}
// IsReadOnly implements the interface sql.ExecSourceRel.
func (c *AlterRole) IsReadOnly() bool {
return false
}
// Resolved implements the interface sql.ExecSourceRel.
func (c *AlterRole) Resolved() bool {
return true
}
// RowIter implements the interface sql.ExecSourceRel.
func (c *AlterRole) RowIter(ctx *sql.Context, r sql.Row) (sql.RowIter, error) {
var userRole auth.Role
var role auth.Role
auth.LockRead(func() {
userRole = auth.GetRole(ctx.Client().User)
role = auth.GetRole(c.Name)
})
if !userRole.IsValid() {
return nil, errors.Errorf(`role "%s" does not exist`, userRole.Name)
}
if !role.IsValid() {
return nil, errors.Errorf(`role "%s" does not exist`, c.Name)
}
if role.IsSuperUser && !userRole.IsSuperUser {
// Only superusers can modify other superusers
// TODO: grab the actual error message
return nil, errors.Errorf(`role "%s" does not have permission to alter role "%s"`, userRole.Name, role.Name)
} else if !userRole.IsSuperUser && !userRole.CanCreateRoles && role.ID() != userRole.ID() {
// A role may only modify itself if it doesn't have the ability to create roles
// TODO: allow non-role-creating roles to only modify their own password, and grab actual error message
return nil, errors.Errorf(`role "%s" does not have permission to alter role "%s"`, userRole.Name, role.Name)
}
for optionName, optionValue := range c.Options {
switch optionName {
case "BYPASSRLS":
if !userRole.IsSuperUser {
// TODO: grab the actual error message
return nil, errors.Errorf(`role "%s" does not have permission to alter role "%s"`, userRole.Name, role.Name)
}
role.CanBypassRowLevelSecurity = true
case "CONNECTION_LIMIT":
role.ConnectionLimit = optionValue.(int32)
case "CREATEDB":
role.CanCreateDB = true
case "CREATEROLE":
role.CanCreateRoles = true
case "INHERIT":
role.InheritPrivileges = true
case "LOGIN":
role.CanLogin = true
case "NOBYPASSRLS":
if !userRole.IsSuperUser {
// TODO: grab the actual error message
return nil, errors.Errorf(`role "%s" does not have permission to alter role "%s"`, userRole.Name, role.Name)
}
role.CanBypassRowLevelSecurity = false
case "NOCREATEDB":
role.CanCreateDB = false
case "NOCREATEROLE":
role.CanCreateRoles = false
case "NOINHERIT":
role.InheritPrivileges = false
case "NOLOGIN":
role.CanLogin = false
case "NOREPLICATION":
if !userRole.IsSuperUser {
// TODO: grab the actual error message
return nil, errors.Errorf(`role "%s" does not have permission to alter role "%s"`, userRole.Name, role.Name)
}
role.IsReplicationRole = false
case "NOSUPERUSER":
if !userRole.IsSuperUser {
// TODO: grab the actual error message
return nil, errors.Errorf(`role "%s" does not have permission to alter role "%s"`, userRole.Name, role.Name)
}
role.IsSuperUser = false
case "PASSWORD":
password, _ := optionValue.(*string)
if password == nil {
role.Password = nil
} else {
var err error
role.Password, err = auth.NewScramSha256Password(*password)
if err != nil {
return nil, err
}
}
case "REPLICATION":
if !userRole.IsSuperUser {
// TODO: grab the actual error message
return nil, errors.Errorf(`role "%s" does not have permission to alter role "%s"`, userRole.Name, role.Name)
}
role.IsReplicationRole = true
case "SUPERUSER":
if !userRole.IsSuperUser {
// TODO: grab the actual error message
return nil, errors.Errorf(`role "%s" does not have permission to alter role "%s"`, userRole.Name, role.Name)
}
role.IsSuperUser = true
case "VALID_UNTIL":
timeString, _ := optionValue.(*string)
if timeString == nil {
role.ValidUntil = nil
} else {
validUntilAny, err := pgtypes.TimestampTZ.IoInput(ctx, *timeString)
if err != nil {
return nil, err
}
validUntilTime := validUntilAny.(time.Time)
role.ValidUntil = &validUntilTime
}
default:
return nil, errors.Errorf(`unknown role option "%s"`, optionName)
}
}
var err error
auth.LockWrite(func() {
auth.SetRole(role)
err = auth.PersistChanges()
})
if err != nil {
return nil, err
}
return sql.RowsToRowIter(), nil
}
// Schema implements the interface sql.ExecSourceRel.
func (c *AlterRole) Schema(ctx *sql.Context) sql.Schema {
return nil
}
// String implements the interface sql.ExecSourceRel.
func (c *AlterRole) String() string {
return "ALTER ROLE"
}
// WithChildren implements the interface sql.ExecSourceRel.
func (c *AlterRole) WithChildren(ctx *sql.Context, children ...sql.Node) (sql.Node, error) {
return plan.NillaryWithChildren(c, children...)
}
// WithResolvedChildren implements the interface vitess.Injectable.
func (c *AlterRole) WithResolvedChildren(ctx context.Context, children []any) (any, error) {
if len(children) != 0 {
return nil, ErrVitessChildCount.New(0, len(children))
}
return c, nil
}
+171
View File
@@ -0,0 +1,171 @@
// 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 node
import (
"context"
"github.com/cockroachdb/errors"
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/dsess"
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/go-mysql-server/sql/plan"
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
"github.com/jackc/pgx/v5/pgproto3"
"github.com/dolthub/doltgresql/core"
"github.com/dolthub/doltgresql/core/id"
)
// AlterSequence handles the ALTER SEQUENCE statement.
type AlterSequence struct {
ifExists bool
targetSchema string
targetSequence string
ownedBy AlterSequenceOwnedBy
warnings []string
}
// AlterSequenceOwnedBy is an option in AlterSequence to represent OWNED BY.
type AlterSequenceOwnedBy struct {
IsSet bool
Table string
Column string
}
var _ sql.ExecSourceRel = (*AlterSequence)(nil)
var _ vitess.Injectable = (*AlterSequence)(nil)
// NewAlterSequence returns a new *AlterSequence.
func NewAlterSequence(ifExists bool, targetSchema string, targetSequence string, ownedBy AlterSequenceOwnedBy, warnings ...string) *AlterSequence {
return &AlterSequence{
ifExists: ifExists,
targetSchema: targetSchema,
targetSequence: targetSequence,
ownedBy: ownedBy,
warnings: warnings,
}
}
// Children implements the interface sql.ExecSourceRel.
func (c *AlterSequence) Children() []sql.Node {
return nil
}
// IsReadOnly implements the interface sql.ExecSourceRel.
func (c *AlterSequence) IsReadOnly() bool {
return false
}
// Resolved implements the interface sql.ExecSourceRel.
func (c *AlterSequence) Resolved() bool {
return true
}
// RowIter implements the interface sql.ExecSourceRel.
func (c *AlterSequence) RowIter(ctx *sql.Context, r sql.Row) (sql.RowIter, error) {
targetSchema, err := core.GetSchemaName(ctx, nil, c.targetSchema)
if err != nil {
return nil, err
}
target := id.NewSequence(targetSchema, c.targetSequence)
collection, err := core.GetSequencesCollectionFromContext(ctx, ctx.GetCurrentDatabase())
if err != nil {
return nil, err
}
if !collection.HasSequence(ctx, target) {
if c.ifExists {
return sql.RowsToRowIter(), nil
}
return nil, errors.Errorf(`relation "%s" does not exist`, c.targetSequence)
}
seq, err := collection.GetSequence(ctx, target)
if err != nil {
return nil, err
}
if c.ownedBy.IsSet {
if len(c.ownedBy.Table) > 0 {
relationType, err := core.GetRelationType(ctx, targetSchema, c.ownedBy.Table)
if err != nil {
return nil, err
}
if relationType == core.RelationType_DoesNotExist {
return nil, errors.Errorf(`relation "%s" does not exist`, c.ownedBy.Table)
} else if relationType != core.RelationType_Table {
return nil, errors.Errorf(`sequence cannot be owned by relation "%s"`, c.ownedBy.Table)
}
table, err := core.GetSqlTableFromContext(ctx, "", doltdb.TableName{Name: c.ownedBy.Table, Schema: targetSchema})
if err != nil {
return nil, err
}
if table == nil {
return nil, errors.Errorf(`table "%s" cannot be found but says it exists`, c.ownedBy.Table)
}
var tableColumn *sql.Column
for _, col := range table.Schema(ctx) {
if col.Name == c.ownedBy.Column {
tableColumn = col.Copy()
break
}
}
if tableColumn == nil {
return nil, errors.Errorf(`column "%s" of relation "%s" does not exist`,
c.ownedBy.Column, c.ownedBy.Table)
}
// We've verified the existence of the table's column, so we can assign it now
seq.OwnerTable = id.NewTable(targetSchema, c.ownedBy.Table)
seq.OwnerColumn = c.ownedBy.Column
} else {
seq.OwnerTable = ""
seq.OwnerColumn = ""
}
}
// Display any warnings that were encountered during parsing
for _, warning := range c.warnings {
noticeResponse := &pgproto3.NoticeResponse{
Severity: "WARNING",
Message: warning,
}
sess := dsess.DSessFromSess(ctx.Session)
sess.Notice(noticeResponse)
}
// Any changes made to the sequence will be persisted at the end of the transaction, so we can just return now
return sql.RowsToRowIter(), nil
}
// Schema implements the interface sql.ExecSourceRel.
func (c *AlterSequence) Schema(ctx *sql.Context) sql.Schema {
return nil
}
// String implements the interface sql.ExecSourceRel.
func (c *AlterSequence) String() string {
return "ALTER SEQUENCE"
}
// WithChildren implements the interface sql.ExecSourceRel.
func (c *AlterSequence) WithChildren(ctx *sql.Context, children ...sql.Node) (sql.Node, error) {
return plan.NillaryWithChildren(c, children...)
}
// WithResolvedChildren implements the interface vitess.Injectable.
func (c *AlterSequence) WithResolvedChildren(ctx context.Context, children []any) (any, error) {
if len(children) != 0 {
return nil, ErrVitessChildCount.New(0, len(children))
}
return c, nil
}
+136
View File
@@ -0,0 +1,136 @@
// 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 node
import (
"context"
"github.com/cockroachdb/errors"
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/go-mysql-server/sql/plan"
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
"github.com/dolthub/doltgresql/core"
pgexprs "github.com/dolthub/doltgresql/server/expression"
"github.com/dolthub/doltgresql/server/functions/framework"
)
// Call is used to call stored procedures.
type Call struct {
SchemaName string
ProcedureName string
Exprs []sql.Expression
Runner pgexprs.StatementRunner
cachedSch sql.Schema
originalExprs vitess.Exprs
CompiledFunc *framework.CompiledFunction
}
var _ sql.ExecSourceRel = (*Call)(nil)
var _ sql.Expressioner = (*Call)(nil)
var _ vitess.Injectable = (*Call)(nil)
// NewCall returns a new *Call.
func NewCall(schema string, name string, originalExprs vitess.Exprs) *Call {
return &Call{
SchemaName: schema,
ProcedureName: name,
Exprs: nil,
originalExprs: originalExprs,
}
}
// Children implements the interface sql.ExecSourceRel.
func (c *Call) Children() []sql.Node {
return nil
}
// Expressions implements the interface sql.Expressioner.
func (c *Call) Expressions() []sql.Expression {
exprs := make([]sql.Expression, len(c.Exprs)+1)
exprs[0] = c.Runner
copy(exprs[1:], c.Exprs)
return exprs
}
// IsReadOnly implements the interface sql.ExecSourceRel.
func (c *Call) IsReadOnly() bool {
// TODO: some procedures are read-only, some are not
return false
}
// Resolved implements the interface sql.ExecSourceRel.
func (c *Call) Resolved() bool {
return true
}
// RowIter implements the interface sql.ExecSourceRel.
func (c *Call) RowIter(ctx *sql.Context, r sql.Row) (sql.RowIter, error) {
if c.CompiledFunc == nil || !c.CompiledFunc.Resolved() {
return nil, errors.New("cannot call unresolved procedure")
}
if !core.IsContextValid(ctx) {
return nil, errors.New("invalid context while attempting to call a procedure")
}
cf := c.CompiledFunc.SetStatementRunner(ctx, c.Runner.Runner).(*framework.CompiledFunction)
_, err := cf.Eval(ctx, nil)
if err != nil {
return nil, err
}
return sql.RowsToRowIter(), nil
}
// Schema implements the interface sql.ExecSourceRel.
func (c *Call) Schema(ctx *sql.Context) sql.Schema {
// TODO: this should be the INOUT and OUT parameters of the target procedure assuming we're not using the cached schema
return c.cachedSch
}
// String implements the interface sql.ExecSourceRel.
func (c *Call) String() string {
return "CALL"
}
// WithChildren implements the interface sql.ExecSourceRel.
func (c *Call) WithChildren(ctx *sql.Context, children ...sql.Node) (sql.Node, error) {
return plan.NillaryWithChildren(c, children...)
}
// WithExpressions implements the interface sql.Expressioner.
func (c *Call) WithExpressions(ctx *sql.Context, exprs ...sql.Expression) (sql.Node, error) {
if len(c.Exprs)+1 != len(exprs) {
return nil, errors.Errorf("expected `%d` child expressions but received `%d`", len(c.Exprs), len(exprs))
}
nc := *c
nc.Runner = exprs[0].(pgexprs.StatementRunner)
nc.Exprs = exprs[1:]
return &nc, nil
}
// WithResolvedChildren implements the interface vitess.Injectable.
func (c *Call) WithResolvedChildren(ctx context.Context, children []any) (any, error) {
resolvedChildren := make([]sql.Expression, len(children))
for i, child := range children {
var ok bool
resolvedChildren[i], ok = child.(sql.Expression)
if !ok {
return nil, errors.Errorf("expected vitess child to be an expression but has type `%T`", child)
}
}
nc := *c
nc.Exprs = resolvedChildren
return &nc, nil
}
+125
View File
@@ -0,0 +1,125 @@
// 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 node
import (
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/doltgresql/core"
)
// ContextRootFinalizer is a node that finalizes any changes persisted within the context.
type ContextRootFinalizer struct {
child sql.Node
}
var _ sql.DebugStringer = (*ContextRootFinalizer)(nil)
var _ sql.ExecBuilderNode = (*ContextRootFinalizer)(nil)
// NewContextRootFinalizer returns a new *ContextRootFinalizer.
func NewContextRootFinalizer(child sql.Node) *ContextRootFinalizer {
return &ContextRootFinalizer{
child: child,
}
}
// Child returns the single child of this node
func (rf *ContextRootFinalizer) Child() sql.Node {
return rf.child
}
// Children implements the interface sql.ExecBuilderNode.
func (rf *ContextRootFinalizer) Children() []sql.Node {
return []sql.Node{rf.child}
}
// DebugString implements the interface sql.DebugStringer.
func (rf *ContextRootFinalizer) DebugString(ctx *sql.Context) string {
return sql.DebugString(ctx, rf.child)
}
// IsReadOnly implements the interface sql.ExecBuilderNode.
func (rf *ContextRootFinalizer) IsReadOnly() bool {
return rf.child.IsReadOnly()
}
// Resolved implements the interface sql.ExecBuilderNode.
func (rf *ContextRootFinalizer) Resolved() bool {
return rf.child.Resolved()
}
// BuildRowIter implements the interface sql.ExecBuilderNode.
func (rf *ContextRootFinalizer) BuildRowIter(ctx *sql.Context, b sql.NodeExecBuilder, r sql.Row) (sql.RowIter, error) {
childIter, err := b.Build(ctx, rf.child, r)
if err != nil {
return nil, err
}
if childIter == nil {
childIter = sql.RowsToRowIter()
}
return &rootFinalizerIter{childIter: childIter}, nil
}
// Schema implements the interface sql.ExecBuilderNode.
func (rf *ContextRootFinalizer) Schema(ctx *sql.Context) sql.Schema {
return rf.child.Schema(ctx)
}
// String implements the interface sql.ExecBuilderNode.
func (rf *ContextRootFinalizer) String() string {
return rf.child.String()
}
// WithChildren implements the interface sql.ExecBuilderNode.
func (rf *ContextRootFinalizer) WithChildren(ctx *sql.Context, children ...sql.Node) (sql.Node, error) {
if len(children) != 1 {
return nil, sql.ErrInvalidChildrenNumber.New(rf, len(children), 1)
}
return NewContextRootFinalizer(children[0]), nil
}
// rootFinalizerIter is the iterator for *ContextRootFinalizer that finalizes the context.
type rootFinalizerIter struct {
childIter sql.RowIter
}
var _ sql.MutableRowIter = (*rootFinalizerIter)(nil)
// Next implements the interface sql.RowIter.
func (r *rootFinalizerIter) Next(ctx *sql.Context) (sql.Row, error) {
return r.childIter.Next(ctx)
}
// Close implements the interface sql.RowIter.
func (r *rootFinalizerIter) Close(ctx *sql.Context) error {
err := r.childIter.Close(ctx)
if err != nil {
_ = core.CloseContextRootFinalizer(ctx)
return err
}
return core.CloseContextRootFinalizer(ctx)
}
// GetChildIter implements the interface sql.CustomRowIter.
func (r *rootFinalizerIter) GetChildIter() sql.RowIter {
return r.childIter
}
// WithChildIter implements the interface sql.CustomRowIter.
func (r *rootFinalizerIter) WithChildIter(childIter sql.RowIter) sql.RowIter {
nr := *r
nr.childIter = childIter
return &nr
}
+126
View File
@@ -0,0 +1,126 @@
// 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 node
import (
"context"
"fmt"
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
"github.com/dolthub/go-mysql-server/sql"
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
"github.com/dolthub/doltgresql/core/dataloader"
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
)
// CopyFrom handles the COPY ... FROM ... statement.
type CopyFrom struct {
DatabaseName string
TableName doltdb.TableName
File string
Stdin bool
Columns tree.NameList
CopyOptions tree.CopyOptions
InsertStub *vitess.Insert
DataLoader dataloader.DataLoader
}
var _ vitess.Injectable = (*CopyFrom)(nil)
var _ sql.ExecSourceRel = (*CopyFrom)(nil)
// NewCopyFrom returns a new *CopyFrom.
func NewCopyFrom(
databaseName string,
tableName doltdb.TableName,
options tree.CopyOptions,
fileName string,
stdin bool,
columns tree.NameList,
insertStub *vitess.Insert,
) *CopyFrom {
switch options.CopyFormat {
case tree.CopyFormatCsv, tree.CopyFormatText:
// no-op
case tree.CopyFormatBinary:
panic("BINARY format is not supported for COPY FROM")
default:
panic(fmt.Sprintf("unknown COPY FROM format: %d", options.CopyFormat))
}
return &CopyFrom{
DatabaseName: databaseName,
TableName: tableName,
File: fileName,
Stdin: stdin,
Columns: columns,
CopyOptions: options,
InsertStub: insertStub,
}
}
// Children implements the interface sql.ExecSourceRel.
func (cf *CopyFrom) Children() []sql.Node {
return nil
}
// IsReadOnly implements the interface sql.ExecSourceRel.
func (cf *CopyFrom) IsReadOnly() bool {
return false
}
// Resolved implements the interface sql.ExecSourceRel.
func (cf *CopyFrom) Resolved() bool {
return true
}
// RowIter implements the interface sql.ExecSourceRel.
func (cf *CopyFrom) RowIter(ctx *sql.Context, r sql.Row) (_ sql.RowIter, err error) {
return cf.DataLoader.RowIter(ctx, r)
}
// Schema implements the interface sql.ExecSourceRel.
func (cf *CopyFrom) Schema(ctx *sql.Context) sql.Schema {
// For Parse calls, we need access to the schema before we have a DataLoader created, so return a stub schema.
if cf.DataLoader == nil {
return nil
}
return cf.DataLoader.Schema(ctx)
}
// String implements the interface sql.ExecSourceRel.
func (cf *CopyFrom) String() string {
source := "STDIN"
if cf.File != "" {
source = fmt.Sprintf("'%s'", cf.File)
}
return fmt.Sprintf("COPY FROM %s", source)
}
// WithChildren implements the interface sql.ExecSourceRel.
func (cf *CopyFrom) WithChildren(ctx *sql.Context, children ...sql.Node) (sql.Node, error) {
if len(children) != 0 {
return nil, sql.ErrInvalidChildrenNumber.New(cf, len(children), 0)
}
return cf, nil
}
// WithResolvedChildren implements the interface vitess.Injectable.
func (cf *CopyFrom) WithResolvedChildren(ctx context.Context, children []any) (any, error) {
if len(children) != 0 {
return nil, ErrVitessChildCount.New(0, len(children))
}
return cf, nil
}
+174
View File
@@ -0,0 +1,174 @@
// 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 node
import (
"context"
"github.com/cockroachdb/errors"
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/go-mysql-server/sql/plan"
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
"github.com/dolthub/doltgresql/core"
"github.com/dolthub/doltgresql/core/casts"
"github.com/dolthub/doltgresql/core/id"
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
pgtypes "github.com/dolthub/doltgresql/server/types"
)
// CreateCast implements CREATE CAST.
type CreateCast struct {
Source *pgtypes.DoltgresType
Target *pgtypes.DoltgresType
Type casts.CastType
CreateType tree.CreateCastType
FuncSchema string
FuncName string
FuncParams []RoutineParam
}
var _ sql.ExecSourceRel = (*CreateCast)(nil)
var _ vitess.Injectable = (*CreateCast)(nil)
// NewCreateCast returns a new *CreateCast.
func NewCreateCast(
source, target *pgtypes.DoltgresType,
typ casts.CastType,
createType tree.CreateCastType,
funcSch, funcName string,
params []RoutineParam) *CreateCast {
return &CreateCast{
Source: source,
Target: target,
Type: typ,
CreateType: createType,
FuncSchema: funcSch,
FuncName: funcName,
FuncParams: params,
}
}
// Children implements the interface sql.ExecSourceRel.
func (c *CreateCast) Children() []sql.Node {
return nil
}
// IsReadOnly implements the interface sql.ExecSourceRel.
func (c *CreateCast) IsReadOnly() bool {
return false
}
// Resolved implements the interface sql.ExecSourceRel.
func (c *CreateCast) Resolved() bool {
return true
}
// RowIter implements the interface sql.ExecSourceRel.
func (c *CreateCast) RowIter(ctx *sql.Context, r sql.Row) (sql.RowIter, error) {
castCollection, err := core.GetCastsCollectionFromContext(ctx, "")
if err != nil {
return nil, err
}
newCast := casts.Cast{
ID: id.NewCast(c.Source.ID, c.Target.ID),
CastType: c.Type,
}
if c.Source.ID == c.Target.ID {
return nil, errors.New("source data type and target data type are the same")
}
switch c.CreateType {
case tree.CreateCastType_WithFunction:
if len(c.FuncParams) < 1 || len(c.FuncParams) > 3 {
return nil, errors.New("cast function must take one to three arguments")
}
if len(c.FuncParams) >= 2 && c.FuncParams[1].Type.ID != pgtypes.Int32.ID {
return nil, errors.New("second argument of cast function must be type integer")
}
if len(c.FuncParams) >= 3 && c.FuncParams[2].Type.ID != pgtypes.Bool.ID {
return nil, errors.New("third argument of cast function must be type boolean")
}
schemaName, err := core.GetSchemaName(ctx, nil, c.FuncSchema)
if err != nil {
return nil, err
}
paramTypes := make([]id.Type, len(c.FuncParams))
for i, param := range c.FuncParams {
paramTypes[i] = param.Type.ID
}
funcID := id.NewFunction(schemaName, c.FuncName, paramTypes...)
funcCollection, err := core.GetFunctionsCollectionFromContext(ctx, "")
if err != nil {
return nil, err
}
if !funcCollection.HasFunction(ctx, funcID) {
return nil, errors.Errorf("function %s does not exist", funcID.DisplayString())
}
f, err := funcCollection.GetFunction(ctx, funcID)
if err != nil {
return nil, err
}
if f.ParameterTypes[0] != c.Source.ID {
// Although the error mentions binary-coercible, we can't actually support that due to how types work in Go.
// Our bit representations of values will differ from Postgres.
return nil, errors.New("argument of cast function must match or be binary-coercible from source data type")
}
if f.ReturnType != c.Target.ID {
// Although the error mentions binary-coercible, we can't actually support that due to how types work in Go.
// Our bit representations of values will differ from Postgres.
return nil, errors.New("return data type of cast function must match or be binary-coercible to target data type")
}
newCast.Function = funcID
case tree.CreateCastType_WithoutFunction:
if c.Source.IsCompositeType() || c.Target.IsCompositeType() {
return nil, errors.New("composite data types are not binary-compatible")
}
// Due to the differences in how we handle values at the bit level compared to Postgres, we must mark all of
// these types of casts as physically incompatible
return nil, errors.New("source and target data types are not physically compatible")
case tree.CreateCastType_Inout:
newCast.UseInOut = true
}
if castCollection.HasCast(ctx, newCast.ID) {
return nil, errors.Errorf("cast from type %s to type %s already exists", c.Source.ID.TypeName(), c.Target.ID.TypeName())
}
if err = castCollection.AddCast(ctx, newCast); err != nil {
return nil, err
}
return sql.RowsToRowIter(), nil
}
// Schema implements the interface sql.ExecSourceRel.
func (c *CreateCast) Schema(ctx *sql.Context) sql.Schema {
return nil
}
// String implements the interface sql.ExecSourceRel.
func (c *CreateCast) String() string {
return "CREATE CAST"
}
// WithChildren implements the interface sql.ExecSourceRel.
func (c *CreateCast) WithChildren(ctx *sql.Context, children ...sql.Node) (sql.Node, error) {
return plan.NillaryWithChildren(c, children...)
}
// WithResolvedChildren implements the interface vitess.Injectable.
func (c *CreateCast) WithResolvedChildren(ctx context.Context, children []any) (any, error) {
if len(children) != 0 {
return nil, ErrVitessChildCount.New(0, len(children))
}
return c, nil
}
+268
View File
@@ -0,0 +1,268 @@
// 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 node
import (
"context"
"fmt"
"slices"
"github.com/cockroachdb/errors"
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/go-mysql-server/sql/plan"
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
"github.com/dolthub/doltgresql/core"
"github.com/dolthub/doltgresql/core/id"
pgtypes "github.com/dolthub/doltgresql/server/types"
)
// CreateDomain handles the CREATE DOMAIN statement.
type CreateDomain struct {
SchemaName string
Name string
AsType *pgtypes.DoltgresType
Collation string
HasDefault bool
DefaultExpr sql.Expression
IsNotNull bool
CheckConstraintNames []string
CheckConstraints sql.CheckConstraints
overrides sql.EngineOverrides
}
var _ sql.ExecSourceRel = (*CreateDomain)(nil)
var _ sql.NodeOverriding = (*CreateDomain)(nil)
var _ vitess.Injectable = (*CreateDomain)(nil)
// Children implements the interface sql.ExecSourceRel.
func (c *CreateDomain) Children() []sql.Node {
return nil
}
// IsReadOnly implements the interface sql.ExecSourceRel.
func (c *CreateDomain) IsReadOnly() bool {
return false
}
// Resolved implements the interface sql.ExecSourceRel.
func (c *CreateDomain) Resolved() bool {
return true
}
// RowIter implements the interface sql.ExecSourceRel.
func (c *CreateDomain) RowIter(ctx *sql.Context, r sql.Row) (sql.RowIter, error) {
schema, err := core.GetSchemaName(ctx, nil, c.SchemaName)
if err != nil {
return nil, err
}
collection, err := core.GetTypesCollectionFromContext(ctx, "")
if err != nil {
return nil, err
}
internalID := id.NewType(schema, c.Name)
arrayID := id.NewType(schema, "_"+c.Name)
if collection.HasType(ctx, internalID) {
return nil, pgtypes.ErrTypeAlreadyExists.New(c.Name)
}
var defExpr string
if c.DefaultExpr != nil {
defExpr = c.DefaultExpr.String()
}
checkDefs := make([]*sql.CheckDefinition, len(c.CheckConstraints))
for i, check := range c.CheckConstraints {
checkName := c.CheckConstraintNames[i]
if checkName == "" {
checkName = generateCheckNameForDomain(c.Name, c.CheckConstraintNames)
// add this to the list of names to avoid duplicates later in the loop
c.CheckConstraintNames[i] = checkName
check.Name = checkName
}
checkDefs[i], err = plan.NewCheckDefinition(ctx, check, sql.GetSchemaFormatter(c.overrides))
if err != nil {
return nil, err
}
}
newType := pgtypes.NewDomainType(ctx, c.AsType, defExpr, c.IsNotNull, checkDefs, arrayID, internalID)
err = collection.CreateType(ctx, newType)
if err != nil {
return nil, err
}
// create array type of this type
arrayType := pgtypes.CreateArrayTypeFromBaseType(newType)
err = collection.CreateType(ctx, arrayType)
if err != nil {
return nil, err
}
newType.Array = arrayType
return sql.RowsToRowIter(), nil
}
// generateCheckNameForDomain generates a unique check constraint name for a domain when one is not provided. The
// name must be unique for this type, but doesn't need to be unique across all constraint names in the database.
// Postgres generates names like `domainname_check`, and then uses `domainname_check1`, `domainname_check2`, etc.
// Our behavior varies slightly, in that if a user provides a name that collides with a generated name, we will use
// `domain_check` (no suffix) before moving on to `domain_check2` etc.
func generateCheckNameForDomain(domainName string, allNames []string) string {
name := domainName + "_check"
for i := 1; ; i++ {
if !slices.Contains(allNames, name) {
return name
}
name = fmt.Sprintf("%s_check%d", domainName, i)
}
}
// Schema implements the interface sql.ExecSourceRel.
func (c *CreateDomain) Schema(ctx *sql.Context) sql.Schema {
return nil
}
// String implements the interface sql.ExecSourceRel.
func (c *CreateDomain) String() string {
return "CREATE DOMAIN"
}
// WithChildren implements the interface sql.ExecSourceRel.
func (c *CreateDomain) WithChildren(ctx *sql.Context, children ...sql.Node) (sql.Node, error) {
return plan.NillaryWithChildren(c, children...)
}
// WithOverrides implements the interface sql.NodeOverriding.
func (c *CreateDomain) WithOverrides(overrides sql.EngineOverrides) sql.Node {
c.overrides = overrides
return c
}
// WithResolvedChildren implements the interface vitess.Injectable.
func (c *CreateDomain) WithResolvedChildren(ctx context.Context, children []any) (any, error) {
checksStartAt := 0
var defExpr sql.Expression
if c.HasDefault {
expr, ok := children[0].(sql.Expression)
if !ok {
return nil, errors.Errorf("invalid vitess child, expected sql.Expression for Default value but got %t", children[0])
}
defExpr = expr
checksStartAt = 1
}
var checks sql.CheckConstraints
for i, child := range children[checksStartAt:] {
expr, ok := child.(sql.Expression)
if !ok {
return nil, errors.Errorf("invalid vitess child, expected sql.Expression for Check constraint value but got %t", children[0])
}
checks = append(checks, &sql.CheckConstraint{
Name: c.CheckConstraintNames[i],
Expr: expr,
Enforced: true,
})
}
if !c.AsType.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.AsType.ID)
if err != nil {
return nil, err
}
c.AsType = resolvedType
}
if !c.AsType.IsDefined {
return nil, pgtypes.ErrTypeIsOnlyAShell.New(c.AsType.Name())
}
return &CreateDomain{
SchemaName: c.SchemaName,
Name: c.Name,
AsType: c.AsType,
Collation: c.Collation,
HasDefault: c.HasDefault,
DefaultExpr: defExpr,
IsNotNull: c.IsNotNull,
CheckConstraintNames: c.CheckConstraintNames,
CheckConstraints: checks,
}, nil
}
// DomainColumn represents the column name `VALUE.
// It is a placeholder column reference later
// used for column defined as the domain type.
type DomainColumn struct {
Typ *pgtypes.DoltgresType
}
var _ vitess.Injectable = (*DomainColumn)(nil)
var _ sql.Expression = (*DomainColumn)(nil)
// Resolved implements the interface sql.Expression.
func (d *DomainColumn) Resolved() bool {
return true
}
// String implements the interface sql.Expression.
func (d *DomainColumn) String() string {
return "VALUE"
}
// Type implements the interface sql.Expression.
func (d *DomainColumn) Type(ctx *sql.Context) sql.Type {
if d.Typ.IsEmptyType() {
return pgtypes.Unknown
}
return d.Typ
}
// IsNullable implements the interface sql.Expression.
func (d *DomainColumn) IsNullable(ctx *sql.Context) bool {
return false
}
// Eval implements the interface sql.Expression.
func (d *DomainColumn) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) {
panic("DomainColumn is a placeholder expression, but Eval() was called")
}
// Children implements the interface sql.Expression.
func (d *DomainColumn) Children() []sql.Expression {
return nil
}
// WithChildren implements the interface sql.Expression.
func (d *DomainColumn) WithChildren(ctx *sql.Context, children ...sql.Expression) (sql.Expression, error) {
if len(children) != 0 {
return nil, sql.ErrInvalidChildrenNumber.New(d, len(children), 0)
}
return d, nil
}
// WithResolvedChildren implements the interface vitess.Injectable.
func (d *DomainColumn) WithResolvedChildren(ctx context.Context, children []any) (any, error) {
if len(children) != 0 {
return nil, errors.Errorf("invalid vitess child count, expected `0` but got `%d`", len(children))
}
return d, nil
}
+204
View File
@@ -0,0 +1,204 @@
// 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 node
import (
"context"
"fmt"
"strings"
"github.com/cockroachdb/errors"
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/go-mysql-server/sql/plan"
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
"github.com/dolthub/doltgresql/core"
"github.com/dolthub/doltgresql/core/extensions"
"github.com/dolthub/doltgresql/core/id"
"github.com/dolthub/doltgresql/postgres/parser/parser"
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
pgexprs "github.com/dolthub/doltgresql/server/expression"
)
// CreateExtension implements CREATE EXTENSION.
type CreateExtension struct {
Name string
IfNotExists bool
SchemaName string
Version string
Cascade bool
Runner pgexprs.StatementRunner
}
var _ sql.ExecSourceRel = (*CreateExtension)(nil)
var _ sql.Expressioner = (*CreateExtension)(nil)
var _ vitess.Injectable = (*CreateExtension)(nil)
// NewCreateExtension returns a new *CreateExtension.
func NewCreateExtension(name string, ifNotExists bool, schemaName string, version string, cascade bool) *CreateExtension {
return &CreateExtension{
Name: name,
IfNotExists: ifNotExists,
SchemaName: schemaName,
Version: version,
Cascade: cascade,
}
}
// Children implements the interface sql.ExecSourceRel.
func (c *CreateExtension) Children() []sql.Node {
return nil
}
// Expressions implements the interface sql.Expressioner.
func (c *CreateExtension) Expressions() []sql.Expression {
return []sql.Expression{c.Runner}
}
// IsReadOnly implements the interface sql.ExecSourceRel.
func (c *CreateExtension) IsReadOnly() bool {
return false
}
// Resolved implements the interface sql.ExecSourceRel.
func (c *CreateExtension) Resolved() bool {
return true
}
// RowIter implements the interface sql.ExecSourceRel.
func (c *CreateExtension) RowIter(ctx *sql.Context, r sql.Row) (sql.RowIter, error) {
extCollection, err := core.GetExtensionsCollectionFromContext(ctx, "")
if err != nil {
return nil, err
}
if extCollection.HasLoadedExtension(ctx, id.NewExtension(c.Name)) {
if c.IfNotExists {
return sql.RowsToRowIter(), nil
}
return nil, errors.Errorf(`extension "%s" already exists`, c.Name)
}
ext, err := extensions.GetExtension(c.Name)
if err != nil {
return nil, err
}
// The returned files are in their proper order of execution, so we can iterate and execute
sqlFiles, err := ext.LoadSQLFiles()
if err != nil {
return nil, err
}
// save the current search_path
originalSchema, err := ctx.GetSessionVariable(ctx, "search_path")
if err != nil {
return nil, err
}
if c.SchemaName != "" {
defer func() {
_ = ctx.SetSessionVariable(ctx, "search_path", originalSchema)
}()
spErr := ctx.SetSessionVariable(ctx, "search_path", c.SchemaName)
if spErr != nil {
return nil, spErr
}
}
for _, sqlFile := range sqlFiles {
// Remove echo PSQL control statements
for {
echoStartIdx := strings.Index(sqlFile, `\echo`)
if echoStartIdx == -1 {
break
}
echoEndIdx := strings.Index(sqlFile[echoStartIdx:], "\n")
if echoEndIdx != -1 {
// Set the correct absolute position if there is a newline
echoEndIdx += echoStartIdx
} else {
// Set the position at the end of the file if there's no newline (comment appears before EOF)
echoEndIdx = len(sqlFile)
}
sqlFile = strings.Replace(sqlFile, sqlFile[echoStartIdx:echoEndIdx], "", 1)
}
statements, err := parser.Parse(sqlFile)
if err != nil {
return nil, err
}
for _, statement := range statements {
statementSQL := statement.SQL
if _, ok := statement.AST.(*tree.CreateFunction); ok {
statementSQL = strings.ReplaceAll(statementSQL, `'MODULE_PATHNAME'`, fmt.Sprintf(`'%s'`, c.Name))
}
_, err = sql.RunInterpreted(ctx, func(subCtx *sql.Context) ([]sql.Row, error) {
_, rowIter, _, err := c.Runner.Runner.QueryWithBindings(subCtx, statementSQL, nil, nil, nil)
if err != nil {
return nil, err
}
return sql.RowIterToRows(subCtx, rowIter)
})
if err != nil {
return nil, err
}
}
}
namespace := id.NullNamespace
if len(ext.Control.Schema) > 0 {
namespace = id.NewNamespace(ext.Control.Schema)
}
err = extCollection.AddLoadedExtension(ctx, extensions.Extension{
ExtName: id.NewExtension(c.Name),
Namespace: namespace,
Relocatable: ext.Control.Relocatable,
LibIdentifier: extensions.CreateLibraryIdentifier(c.Name, ext.Control.DefaultVersion),
})
if err != nil {
return nil, err
}
return sql.RowsToRowIter(), nil
}
// Schema implements the interface sql.ExecSourceRel.
func (c *CreateExtension) Schema(ctx *sql.Context) sql.Schema {
return nil
}
// String implements the interface sql.ExecSourceRel.
func (c *CreateExtension) String() string {
return "CREATE EXTENSION"
}
// WithChildren implements the interface sql.ExecSourceRel.
func (c *CreateExtension) WithChildren(ctx *sql.Context, children ...sql.Node) (sql.Node, error) {
return plan.NillaryWithChildren(c, children...)
}
// WithExpressions implements the interface sql.Expressioner.
func (c *CreateExtension) WithExpressions(ctx *sql.Context, expressions ...sql.Expression) (sql.Node, error) {
if len(expressions) != 1 {
return nil, sql.ErrInvalidChildrenNumber.New(c, len(expressions), 1)
}
newC := *c
newC.Runner = expressions[0].(pgexprs.StatementRunner)
return &newC, nil
}
// WithResolvedChildren implements the interface vitess.Injectable.
func (c *CreateExtension) WithResolvedChildren(ctx context.Context, children []any) (any, error) {
if len(children) != 0 {
return nil, ErrVitessChildCount.New(0, len(children))
}
return c, nil
}
+274
View File
@@ -0,0 +1,274 @@
// 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 node
import (
"context"
"fmt"
"github.com/cockroachdb/errors"
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/go-mysql-server/sql/plan"
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
"github.com/dolthub/doltgresql/core"
"github.com/dolthub/doltgresql/core/extensions"
"github.com/dolthub/doltgresql/core/functions"
"github.com/dolthub/doltgresql/core/id"
"github.com/dolthub/doltgresql/core/procedures"
"github.com/dolthub/doltgresql/server/plpgsql"
pgtypes "github.com/dolthub/doltgresql/server/types"
)
// RoutineParam represents a routine parameter with parameter name, type and default value if exists.
type RoutineParam struct {
Mode procedures.ParameterMode
Name string
Type *pgtypes.DoltgresType
HasDefault bool
Default sql.Expression
}
// CreateFunction implements CREATE FUNCTION.
type CreateFunction struct {
FunctionName string
SchemaName string
Replace bool
ReturnType *pgtypes.DoltgresType
Parameters []RoutineParam
Strict bool
Statements []plpgsql.InterpreterOperation
ExtensionName string
ExtensionSymbol string
Definition string
SqlDef string
SqlDefParsedStmts []vitess.Statement
SetOf bool
}
var _ sql.ExecSourceRel = (*CreateFunction)(nil)
var _ vitess.Injectable = (*CreateFunction)(nil)
// NewCreateFunction returns a new *CreateFunction.
func NewCreateFunction(
functionName string,
schemaName string,
replace bool,
retType *pgtypes.DoltgresType,
params []RoutineParam,
strict bool,
definition string,
extensionName string,
extensionSymbol string,
statements []plpgsql.InterpreterOperation,
sqlDef string,
sqlDefParsedStmts []vitess.Statement,
setOf bool) *CreateFunction {
return &CreateFunction{
FunctionName: functionName,
SchemaName: schemaName,
Replace: replace,
ReturnType: retType,
Parameters: params,
Strict: strict,
Statements: statements,
ExtensionName: extensionName,
ExtensionSymbol: extensionSymbol,
Definition: definition,
SqlDef: sqlDef,
SqlDefParsedStmts: sqlDefParsedStmts,
SetOf: setOf,
}
}
// Children implements the interface sql.ExecSourceRel.
func (c *CreateFunction) Children() []sql.Node {
return nil
}
// IsReadOnly implements the interface sql.ExecSourceRel.
func (c *CreateFunction) IsReadOnly() bool {
return false
}
// Resolved implements the interface sql.ExecSourceRel.
func (c *CreateFunction) Resolved() bool {
return true
}
// RowIter implements the interface sql.ExecSourceRel.
func (c *CreateFunction) RowIter(ctx *sql.Context, r sql.Row) (sql.RowIter, error) {
funcCollection, err := core.GetFunctionsCollectionFromContext(ctx, "")
if err != nil {
return nil, err
}
paramNames := make([]string, len(c.Parameters))
paramTypes := make([]id.Type, len(c.Parameters))
paramDefaults := make([]string, len(c.Parameters))
for i, param := range c.Parameters {
paramNames[i] = param.Name
paramTypes[i] = param.Type.ID
if param.Default != nil {
paramDefaults[i] = param.Default.String()
}
}
schemaName, err := core.GetSchemaName(ctx, nil, c.SchemaName)
if err != nil {
return nil, err
}
funcID := id.NewFunction(schemaName, c.FunctionName, paramTypes...)
if c.Replace && funcCollection.HasFunction(ctx, funcID) {
if err = funcCollection.DropFunction(ctx, funcID); err != nil {
return nil, err
}
}
var extName string
if len(c.ExtensionName) > 0 {
ext, err := extensions.GetExtension(c.ExtensionName)
if err != nil {
return nil, err
}
ident := extensions.CreateLibraryIdentifier(c.ExtensionName, ext.Control.DefaultVersion)
_, err = extensions.GetExtensionFunction(extensions.CreateLibraryIdentifier(c.ExtensionName, ext.Control.DefaultVersion), c.ExtensionSymbol)
if err != nil {
return nil, err
}
extName = string(ident)
}
err = funcCollection.AddFunction(ctx, functions.Function{
ID: funcID,
ReturnType: c.ReturnType.ID,
ParameterNames: paramNames,
ParameterTypes: paramTypes,
ParameterDefaults: paramDefaults,
Variadic: false, // TODO: implement this
IsNonDeterministic: true,
Strict: c.Strict,
Definition: c.Definition,
ExtensionName: extName,
ExtensionSymbol: c.ExtensionSymbol,
Operations: c.Statements,
SQLDefinition: c.SqlDef,
SetOf: c.SetOf,
})
if err != nil {
return nil, err
}
return sql.RowsToRowIter(), nil
}
// Schema implements the interface sql.ExecSourceRel.
func (c *CreateFunction) Schema(ctx *sql.Context) sql.Schema {
return nil
}
// String implements the interface sql.ExecSourceRel.
func (c *CreateFunction) String() string {
// TODO: fully implement this
return "CREATE FUNCTION"
}
// WithChildren implements the interface sql.ExecSourceRel.
func (c *CreateFunction) WithChildren(ctx *sql.Context, children ...sql.Node) (sql.Node, error) {
return plan.NillaryWithChildren(c, children...)
}
// WithResolvedChildren implements the interface vitess.Injectable.
func (c *CreateFunction) WithResolvedChildren(ctx context.Context, children []any) (any, error) {
if len(children) > len(c.Parameters) {
// the number of default values can be fewer but cannot be more.
return nil, ErrVitessChildCount.New(len(c.Parameters), len(children))
}
newParams := make([]RoutineParam, len(c.Parameters))
childIdx := 0
for i, param := range c.Parameters {
newParams[i] = param
if c.Parameters[i].HasDefault && childIdx < len(children) {
expr, ok := children[childIdx].(sql.Expression)
if !ok {
return nil, errors.Errorf("invalid vitess child, expected sql.Expression for Default value but got %t", children[i])
}
newParams[i].Default = expr
childIdx++
}
}
ncf := *c
ncf.Parameters = newParams
return &ncf, nil
}
// FunctionColumn represents the deferred column used in functions.
// It is a placeholder column reference later used for function calls.
type FunctionColumn struct {
Name string
Typ *pgtypes.DoltgresType
Idx uint16
}
var _ vitess.Injectable = (*FunctionColumn)(nil)
var _ sql.Expression = (*FunctionColumn)(nil)
// Resolved implements the interface sql.Expression.
func (f *FunctionColumn) Resolved() bool {
return !f.Typ.IsEmptyType()
}
// String implements the interface sql.Expression.
func (f *FunctionColumn) String() string {
if f.Name != "" {
return fmt.Sprintf(`$%v`, f.Idx+1)
}
return f.Name
}
// Type implements the interface sql.Expression.
func (f *FunctionColumn) Type(ctx *sql.Context) sql.Type {
if f.Typ.IsEmptyType() {
return pgtypes.Unknown
}
return f.Typ
}
// IsNullable implements the interface sql.Expression.
func (f *FunctionColumn) IsNullable(ctx *sql.Context) bool {
return false
}
// Eval implements the interface sql.Expression.
func (f *FunctionColumn) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) {
panic("FunctionColumn is a placeholder expression, but Eval() was called")
}
// Children implements the interface sql.Expression.
func (f *FunctionColumn) Children() []sql.Expression {
return nil
}
// WithChildren implements the interface sql.Expression.
func (f *FunctionColumn) WithChildren(ctx *sql.Context, children ...sql.Expression) (sql.Expression, error) {
if len(children) != 0 {
return nil, sql.ErrInvalidChildrenNumber.New(f, len(children), 0)
}
return f, nil
}
// WithResolvedChildren implements the interface vitess.Injectable.
func (f *FunctionColumn) WithResolvedChildren(ctx context.Context, children []any) (any, error) {
if len(children) != 0 {
return nil, errors.Errorf("invalid FunctionColumn child count, expected `0` but got `%d`", len(children))
}
return f, nil
}
+187
View File
@@ -0,0 +1,187 @@
// 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 node
import (
"context"
"github.com/cockroachdb/errors"
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/go-mysql-server/sql/plan"
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
"github.com/dolthub/doltgresql/core"
"github.com/dolthub/doltgresql/core/extensions"
"github.com/dolthub/doltgresql/core/id"
"github.com/dolthub/doltgresql/core/procedures"
"github.com/dolthub/doltgresql/server/plpgsql"
)
// CreateProcedure implements CREATE PROCEDURE.
type CreateProcedure struct {
ProcedureName string
SchemaName string
Replace bool
Parameters []RoutineParam
Statements []plpgsql.InterpreterOperation
ExtensionName string
ExtensionSymbol string
Definition string
SqlDef string
SqlDefParsedStmts []vitess.Statement
}
var _ sql.ExecSourceRel = (*CreateProcedure)(nil)
var _ vitess.Injectable = (*CreateProcedure)(nil)
// NewCreateProcedure returns a new *CreateProcedure.
func NewCreateProcedure(
procedureName string,
schemaName string,
replace bool,
params []RoutineParam,
definition string,
extensionName string,
extensionSymbol string,
statements []plpgsql.InterpreterOperation,
sqlDef string,
sqlDefParsedStmts []vitess.Statement) *CreateProcedure {
return &CreateProcedure{
ProcedureName: procedureName,
SchemaName: schemaName,
Replace: replace,
Parameters: params,
Statements: statements,
ExtensionName: extensionName,
ExtensionSymbol: extensionSymbol,
Definition: definition,
SqlDef: sqlDef,
SqlDefParsedStmts: sqlDefParsedStmts,
}
}
// Children implements the interface sql.ExecSourceRel.
func (c *CreateProcedure) Children() []sql.Node {
return nil
}
// IsReadOnly implements the interface sql.ExecSourceRel.
func (c *CreateProcedure) IsReadOnly() bool {
return false
}
// Resolved implements the interface sql.ExecSourceRel.
func (c *CreateProcedure) Resolved() bool {
return true
}
// RowIter implements the interface sql.ExecSourceRel.
func (c *CreateProcedure) RowIter(ctx *sql.Context, _ sql.Row) (sql.RowIter, error) {
procCollection, err := core.GetProceduresCollectionFromContext(ctx, "")
if err != nil {
return nil, err
}
paramTypes := make([]id.Type, len(c.Parameters))
paramNames := make([]string, len(c.Parameters))
paramModes := make([]procedures.ParameterMode, len(c.Parameters))
paramDefaults := make([]string, len(c.Parameters))
for i, param := range c.Parameters {
paramNames[i] = param.Name
paramTypes[i] = param.Type.ID
if param.Default != nil {
paramDefaults[i] = param.Default.String()
}
}
schemaName, err := core.GetSchemaName(ctx, nil, c.SchemaName)
if err != nil {
return nil, err
}
procID := id.NewProcedure(schemaName, c.ProcedureName, paramTypes...)
if c.Replace && procCollection.HasProcedure(ctx, procID) {
if err = procCollection.DropProcedure(ctx, procID); err != nil {
return nil, err
}
}
var extName string
if len(c.ExtensionName) > 0 {
ext, err := extensions.GetExtension(c.ExtensionName)
if err != nil {
return nil, err
}
ident := extensions.CreateLibraryIdentifier(c.ExtensionName, ext.Control.DefaultVersion)
_, err = extensions.GetExtensionFunction(extensions.CreateLibraryIdentifier(c.ExtensionName, ext.Control.DefaultVersion), c.ExtensionSymbol)
if err != nil {
return nil, err
}
extName = string(ident)
}
err = procCollection.AddProcedure(ctx, procedures.Procedure{
ID: procID,
ParameterNames: paramNames,
ParameterTypes: paramTypes,
ParameterModes: paramModes,
ParameterDefaults: paramDefaults,
Definition: c.Definition,
ExtensionName: extName,
ExtensionSymbol: c.ExtensionSymbol,
Operations: c.Statements,
SQLDefinition: c.SqlDef,
})
if err != nil {
return nil, err
}
return sql.RowsToRowIter(), nil
}
// Schema implements the interface sql.ExecSourceRel.
func (c *CreateProcedure) Schema(ctx *sql.Context) sql.Schema {
return nil
}
// String implements the interface sql.ExecSourceRel.
func (c *CreateProcedure) String() string {
return "CREATE PROCEDURE"
}
// WithChildren implements the interface sql.ExecSourceRel.
func (c *CreateProcedure) WithChildren(ctx *sql.Context, children ...sql.Node) (sql.Node, error) {
return plan.NillaryWithChildren(c, children...)
}
// WithResolvedChildren implements the interface vitess.Injectable.
func (c *CreateProcedure) WithResolvedChildren(ctx context.Context, children []any) (any, error) {
if len(children) > len(c.Parameters) {
// the number of default values can be fewer but cannot be more.
return nil, ErrVitessChildCount.New(len(c.Parameters), len(children))
}
newParams := make([]RoutineParam, len(c.Parameters))
childIdx := 0
for i, param := range c.Parameters {
newParams[i] = param
if c.Parameters[i].HasDefault && childIdx < len(children) {
expr, ok := children[childIdx].(sql.Expression)
if !ok {
return nil, errors.Errorf("invalid vitess child, expected sql.Expression for Default value but got %t", children[i])
}
newParams[i].Default = expr
childIdx++
}
}
ncp := *c
ncp.Parameters = newParams
return &ncp, nil
}
+164
View File
@@ -0,0 +1,164 @@
// 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 node
import (
"context"
"time"
"github.com/cockroachdb/errors"
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/go-mysql-server/sql/plan"
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
"github.com/dolthub/doltgresql/server/auth"
pgtypes "github.com/dolthub/doltgresql/server/types"
)
// CreateRole handles the CREATE ROLE and CREATE USER statements (CREATE USER is an alias).
type CreateRole struct {
Name string
IfNotExists bool
Password string // PASSWORD 'password'
IsPasswordNull bool // PASSWORD NULL
IsSuperUser bool // SUPERUSER | NOSUPERUSER
CanCreateDB bool // CREATEDB | NOCREATEDB
CanCreateRoles bool // CREATEROLE | NOCREATEROLE
InheritPrivileges bool // INHERIT | NOINHERIT
CanLogin bool // LOGIN | NOLOGIN
IsReplicationRole bool // REPLICATION | NOREPLICATION
CanBypassRowLevelSecurity bool // BYPASSRLS | NOBYPASSRLS
ConnectionLimit int32 // CONNECTION LIMIT connlimit
ValidUntil string // VALID UNTIL 'timestamp'
IsValidUntilSet bool
AddToRoles []string // IN ROLE role_name [, ...]
AddAsMembers []string // ROLE role_name [, ...]
AddAsAdminMembers []string // ADMIN role_name [, ...]
}
var _ sql.ExecSourceRel = (*CreateRole)(nil)
var _ vitess.Injectable = (*CreateRole)(nil)
// Children implements the interface sql.ExecSourceRel.
func (c *CreateRole) Children() []sql.Node {
return nil
}
// IsReadOnly implements the interface sql.ExecSourceRel.
func (c *CreateRole) IsReadOnly() bool {
return false
}
// Resolved implements the interface sql.ExecSourceRel.
func (c *CreateRole) Resolved() bool {
return true
}
// RowIter implements the interface sql.ExecSourceRel.
func (c *CreateRole) RowIter(ctx *sql.Context, r sql.Row) (sql.RowIter, error) {
var userRole auth.Role
var roleExists bool
auth.LockRead(func() {
roleExists = auth.RoleExists(c.Name)
userRole = auth.GetRole(ctx.Client().User)
})
if !userRole.IsValid() {
return nil, errors.Errorf(`role "%s" does not exist`, ctx.Client().User)
}
if roleExists {
if c.IfNotExists {
return sql.RowsToRowIter(), nil
}
return nil, errors.Errorf(`role "%s" already exists`, c.Name)
}
if !userRole.IsSuperUser && (!userRole.CanCreateRoles || c.IsSuperUser) {
// TODO: grab the actual error message
return nil, errors.Errorf(`role "%s" does not have permission to create the role`, userRole.Name)
}
var role auth.Role
auth.LockWrite(func() {
role = auth.CreateDefaultRole(c.Name)
})
if !c.IsPasswordNull {
password, err := auth.NewScramSha256Password(c.Password)
if err != nil {
return nil, err
}
role.Password = password
}
role.IsSuperUser = c.IsSuperUser
role.CanCreateDB = c.CanCreateDB
role.CanCreateRoles = c.CanCreateRoles
role.InheritPrivileges = c.InheritPrivileges
role.CanLogin = c.CanLogin
role.IsReplicationRole = c.IsReplicationRole
role.CanBypassRowLevelSecurity = c.CanBypassRowLevelSecurity
role.ConnectionLimit = c.ConnectionLimit
if c.IsValidUntilSet {
validUntilAny, err := pgtypes.TimestampTZ.IoInput(ctx, c.ValidUntil)
if err != nil {
return nil, err
}
validUntilTime := validUntilAny.(time.Time)
role.ValidUntil = &validUntilTime
}
if len(c.AddToRoles) > 0 {
return nil, errors.New("IN ROLE is not yet supported")
}
if len(c.AddAsMembers) > 0 {
return nil, errors.New("ROLE is not yet supported")
}
if len(c.AddAsAdminMembers) > 0 {
return nil, errors.New("ADMIN is not yet supported")
}
var err error
auth.LockWrite(func() {
auth.SetRole(role)
err = auth.PersistChanges()
})
if err != nil {
return nil, err
}
return sql.RowsToRowIter(), nil
}
// Schema implements the interface sql.ExecSourceRel.
func (c *CreateRole) Schema(ctx *sql.Context) sql.Schema {
return nil
}
// String implements the interface sql.ExecSourceRel.
func (c *CreateRole) String() string {
if c.CanLogin {
return "CREATE USER"
} else {
return "CREATE ROLE"
}
}
// WithChildren implements the interface sql.ExecSourceRel.
func (c *CreateRole) WithChildren(ctx *sql.Context, children ...sql.Node) (sql.Node, error) {
return plan.NillaryWithChildren(c, children...)
}
// WithResolvedChildren implements the interface vitess.Injectable.
func (c *CreateRole) WithResolvedChildren(ctx context.Context, children []any) (any, error) {
if len(children) != 0 {
return nil, ErrVitessChildCount.New(0, len(children))
}
return c, nil
}
+252
View File
@@ -0,0 +1,252 @@
// 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 node
import (
"context"
"fmt"
"math"
"strings"
"github.com/cockroachdb/errors"
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
"github.com/dolthub/dolt/go/libraries/doltcore/sqle"
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/go-mysql-server/sql/plan"
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
"github.com/dolthub/doltgresql/core"
"github.com/dolthub/doltgresql/core/id"
"github.com/dolthub/doltgresql/core/sequences"
pgexprs "github.com/dolthub/doltgresql/server/expression"
"github.com/dolthub/doltgresql/server/functions/framework"
"github.com/dolthub/doltgresql/server/tables"
pgtypes "github.com/dolthub/doltgresql/server/types"
)
// CreateSequence handles the CREATE SEQUENCE statement, along with SERIAL type definitions.
type CreateSequence struct {
schema string
ifNotExists bool
fromAlter bool
sequence *sequences.Sequence
}
var _ sql.ExecSourceRel = (*CreateSequence)(nil)
var _ vitess.Injectable = (*CreateSequence)(nil)
// NewCreateSequence returns a new *CreateSequence.
func NewCreateSequence(ifNotExists bool, schema string, fromAlter bool, sequence *sequences.Sequence) *CreateSequence {
return &CreateSequence{
schema: schema,
ifNotExists: ifNotExists,
fromAlter: fromAlter,
sequence: sequence,
}
}
// Children implements the interface sql.ExecSourceRel.
func (c *CreateSequence) Children() []sql.Node {
return nil
}
// IsReadOnly implements the interface sql.ExecSourceRel.
func (c *CreateSequence) IsReadOnly() bool {
return false
}
// Resolved implements the interface sql.ExecSourceRel.
func (c *CreateSequence) Resolved() bool {
return true
}
// RowIter implements the interface sql.ExecSourceRel.
func (c *CreateSequence) RowIter(ctx *sql.Context, r sql.Row) (sql.RowIter, error) {
if strings.HasPrefix(strings.ToLower(c.sequence.Id.SequenceName()), "dolt") {
return nil, errors.Errorf("sequences cannot be prefixed with 'dolt'")
}
schema, err := core.GetSchemaName(ctx, nil, c.schema)
if err != nil {
return nil, err
}
// The sequence won't have the schema filled in, so we have to do that now
c.sequence.Id = id.NewSequence(schema, c.sequence.Id.SequenceName())
// Check that the sequence name is free across all relation types (tables, sequences, views, indexes).
// GetSqlDatabaseFromContext returns a plain sqle.Database (not a PgDatabase), so we wrap it
// with tables.WrapSqleDatabase before calling GetSchema so that ValidateNewRelationName is available.
// TODO: Consider always wrapping the returned db from GetSqlDatabaseFromContext()
rawDb, err := core.GetSqlDatabaseFromContext(ctx, "")
if err != nil {
return nil, err
}
if rawDb != nil {
if sdb, ok := rawDb.(sqle.Database); ok {
pgDb := tables.WrapSqleDatabase(sdb)
if schemaDb, found, dbErr := pgDb.GetSchema(ctx, schema); dbErr != nil {
return nil, dbErr
} else if found {
if v, ok := schemaDb.(sql.SchemaObjectNameValidator); ok {
nameAlreadyUsed, err := v.ValidateNewSequenceName(ctx, c.sequence.Id.SequenceName(), c.ifNotExists)
if err != nil {
return nil, err
} else if nameAlreadyUsed && c.ifNotExists {
return sql.RowsToRowIter(), nil
}
}
} else if !found {
return nil, fmt.Errorf(`schema "%s" not found`, schema)
}
}
}
// Check that the OWNED BY is valid, if it exists
var table sql.Table
var tableSch sql.Schema
var tableColumn *sql.Column
if c.sequence.OwnerTable.IsValid() {
// The table will only have its name set, so we need to fill in the schema as well
c.sequence.OwnerTable = id.NewTable(schema, c.sequence.OwnerTable.TableName())
relationType, err := core.GetRelationType(ctx, schema, c.sequence.OwnerTable.TableName())
if err != nil {
return nil, err
}
if relationType == core.RelationType_DoesNotExist {
return nil, errors.Errorf(`relation "%s" does not exist`, c.sequence.OwnerTable.TableName())
} else if relationType != core.RelationType_Table {
return nil, errors.Errorf(`sequence cannot be owned by relation "%s"`, c.sequence.OwnerTable.TableName())
}
table, err = core.GetSqlTableFromContext(ctx, "", doltdb.TableName{Name: c.sequence.OwnerTable.TableName(), Schema: schema})
if err != nil {
return nil, err
}
if table == nil {
return nil, errors.Errorf(`table "%s" cannot be found but says it exists`, c.sequence.OwnerTable.TableName())
}
tableSch = table.Schema(ctx)
for _, col := range tableSch {
if col.Name == c.sequence.OwnerColumn {
tableColumn = col.Copy()
break
}
}
if tableColumn == nil {
return nil, errors.Errorf(`column "%s" of relation "%s" does not exist`,
c.sequence.OwnerColumn, c.sequence.OwnerTable.TableName())
}
// If this is from an ALTER TABLE statement, then we have to adjust the type since we didn't have that information earlier
if c.fromAlter {
dgType, ok := tableColumn.Type.(*pgtypes.DoltgresType)
if !ok {
return nil, errors.Errorf(`column "%s" of relation "%s" has unexpected type: "%s"`,
c.sequence.OwnerColumn, c.sequence.OwnerTable.TableName(), tableColumn.Type.String())
}
switch dgType.ID {
case pgtypes.Int16.ID:
c.sequence.DataTypeID = pgtypes.Int16.ID
if c.sequence.Minimum < int64(math.MinInt16) {
c.sequence.Minimum = int64(math.MinInt16)
}
if c.sequence.Maximum > int64(math.MaxInt16) {
c.sequence.Maximum = int64(math.MaxInt16)
}
case pgtypes.Int32.ID:
c.sequence.DataTypeID = pgtypes.Int32.ID
if c.sequence.Minimum < int64(math.MinInt32) {
c.sequence.Minimum = int64(math.MinInt32)
}
if c.sequence.Maximum > int64(math.MaxInt32) {
c.sequence.Maximum = int64(math.MaxInt32)
}
case pgtypes.Int64.ID:
c.sequence.DataTypeID = pgtypes.Int64.ID
// Minimum and Maximum are already set to the Int64 values
default:
// Not sure what we should do if we encounter a non-integer type, so we'll error for now
return nil, errors.Errorf(`column "%s" of relation "%s" has an unsupported type: "%s"`,
c.sequence.OwnerColumn, c.sequence.OwnerTable.TableName(), tableColumn.Type.String())
}
}
}
// Create the sequence since we know it's completely valid
// TODO: we always create the sequence in the current database, but there's no need to require this, and in fact we
// need to support it to create a sequence on another branch than the current one
collection, err := core.GetSequencesCollectionFromContext(ctx, ctx.GetCurrentDatabase())
if err != nil {
return nil, err
}
if err = collection.CreateSequence(ctx, c.sequence); err != nil {
return nil, err
}
if c.fromAlter {
if tableColumn == nil {
// This check is to satisfy the linter
return nil, errors.New(`somehow unable to find the matching table column which should have failed a previous step`)
}
// TODO: What happens when we add a sequence to a column that already has a default value? We'll error until we find out
if tableColumn.Default != nil || tableColumn.Generated != nil {
return nil, errors.New(`cannot add a sequence to a column that already has a default value`)
}
alterableTable, ok := table.(*sqle.AlterableDoltTable)
if !ok {
return nil, errors.Errorf(`expected a Dolt table but received "%T"`, table)
}
// TODO: Do we need to convert to a TableName and then call String? Are we reliant on the specific way it's formatted?
// This is how it's done in the analyzer for SERIAL types, so assuming it's for a good reason.
seqName := doltdb.TableName{Name: c.sequence.Id.SequenceName(), Schema: c.sequence.Id.SchemaName()}.String()
nextVal, foundFunc, err := framework.GetFunction(ctx, "nextval", pgexprs.NewTextLiteral(seqName))
if err != nil {
return nil, err
}
if !foundFunc {
return nil, errors.Errorf(`function "nextval" could not be found`)
}
tableColumn.Default = &sql.ColumnDefaultValue{
Expr: nextVal,
OutType: tableColumn.Type.(*pgtypes.DoltgresType),
Literal: false,
ReturnNil: false,
Parenthesized: false,
}
err = alterableTable.ModifyColumn(ctx, tableColumn.Name, tableColumn, nil)
if err != nil {
return nil, err
}
}
return sql.RowsToRowIter(), nil
}
// Schema implements the interface sql.ExecSourceRel.
func (c *CreateSequence) Schema(ctx *sql.Context) sql.Schema {
return nil
}
// String implements the interface sql.ExecSourceRel.
func (c *CreateSequence) String() string {
return "CREATE SEQUENCE"
}
// WithChildren implements the interface sql.ExecSourceRel.
func (c *CreateSequence) WithChildren(ctx *sql.Context, children ...sql.Node) (sql.Node, error) {
return plan.NillaryWithChildren(c, children...)
}
// WithResolvedChildren implements the interface vitess.Injectable.
func (c *CreateSequence) WithResolvedChildren(ctx context.Context, children []any) (any, error) {
if len(children) != 0 {
return nil, ErrVitessChildCount.New(0, len(children))
}
return c, nil
}
+148
View File
@@ -0,0 +1,148 @@
// 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 node
import (
"fmt"
"strings"
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/go-mysql-server/sql/plan"
"github.com/dolthub/doltgresql/core"
)
// CreateTable is a node that implements functionality specifically relevant to Doltgres' table creation needs.
type CreateTable struct {
gmsCreateTable *plan.CreateTable
sequences []*CreateSequence
}
var _ sql.ExecBuilderNode = (*CreateTable)(nil)
var _ sql.SchemaTarget = (*CreateTable)(nil)
var _ sql.Expressioner = (*CreateTable)(nil)
// NewCreateTable returns a new *CreateTable.
func NewCreateTable(createTable *plan.CreateTable, sequences []*CreateSequence) *CreateTable {
return &CreateTable{
gmsCreateTable: createTable,
sequences: sequences,
}
}
// Children implements the interface sql.ExecBuilderNode.
func (c *CreateTable) Children() []sql.Node {
return c.gmsCreateTable.Children()
}
// DebugString implements the sql.DebugStringer interface
func (c *CreateTable) DebugString(ctx *sql.Context) string {
return sql.DebugString(ctx, c.gmsCreateTable)
}
// Expressions implements the sql.Expressioner interface.
func (c *CreateTable) Expressions() []sql.Expression {
return c.gmsCreateTable.Expressions()
}
// IsReadOnly implements the interface sql.ExecBuilderNode.
func (c *CreateTable) IsReadOnly() bool {
return false
}
// Resolved implements the interface sql.ExecBuilderNode.
func (c *CreateTable) Resolved() bool {
return c.gmsCreateTable != nil && c.gmsCreateTable.Resolved()
}
// BuildRowIter implements the interface sql.ExecBuilderNode.
func (c *CreateTable) BuildRowIter(ctx *sql.Context, b sql.NodeExecBuilder, r sql.Row) (sql.RowIter, error) {
// Prevent tables from having names like `guid()`, which resembles a function
leftParen := strings.IndexByte(c.gmsCreateTable.Name(), '(')
rightParen := strings.IndexByte(c.gmsCreateTable.Name(), ')')
if leftParen != -1 && rightParen != -1 && rightParen > leftParen {
return nil, fmt.Errorf("table name `%s` cannot contain a parenthesized portion", c.gmsCreateTable.Name())
}
createTableIter, err := b.Build(ctx, c.gmsCreateTable, r)
if err != nil {
return nil, err
}
schemaName, err := core.GetSchemaName(ctx, c.gmsCreateTable.Db, "")
if err != nil {
return nil, err
}
for _, sequence := range c.sequences {
sequence.schema = schemaName
_, err = sequence.RowIter(ctx, r)
if err != nil {
_ = createTableIter.Close(ctx)
return nil, err
}
}
return createTableIter, err
}
// Schema implements the interface sql.ExecBuilderNode.
func (c *CreateTable) Schema(ctx *sql.Context) sql.Schema {
return c.gmsCreateTable.Schema(ctx)
}
// String implements the interface sql.ExecBuilderNode.
func (c *CreateTable) String() string {
return c.gmsCreateTable.String()
}
// TargetSchema implements the interface sql.SchemaTarget.
func (c *CreateTable) TargetSchema() sql.Schema {
return c.gmsCreateTable.TargetSchema()
}
// WithChildren implements the interface sql.ExecBuilderNode.
func (c *CreateTable) WithChildren(ctx *sql.Context, children ...sql.Node) (sql.Node, error) {
gmsCreateTable, err := c.gmsCreateTable.WithChildren(ctx, children...)
if err != nil {
return nil, err
}
return &CreateTable{
gmsCreateTable: gmsCreateTable.(*plan.CreateTable),
sequences: c.sequences,
}, nil
}
// WithExpressions implements the interface sql.Expressioner.
func (c *CreateTable) WithExpressions(ctx *sql.Context, expression ...sql.Expression) (sql.Node, error) {
nc := *c
n, err := nc.gmsCreateTable.WithExpressions(ctx, expression...)
if err != nil {
return nil, err
}
nc.gmsCreateTable = n.(*plan.CreateTable)
return &nc, nil
}
// WithTargetSchema implements the interface sql.SchemaTarget.
func (c CreateTable) WithTargetSchema(schema sql.Schema) (sql.Node, error) {
n, err := c.gmsCreateTable.WithTargetSchema(schema)
if err != nil {
return nil, err
}
c.gmsCreateTable = n.(*plan.CreateTable)
return &c, nil
}
+199
View File
@@ -0,0 +1,199 @@
// 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 node
import (
"context"
"github.com/cockroachdb/errors"
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/go-mysql-server/sql/plan"
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
"github.com/dolthub/doltgresql/core"
"github.com/dolthub/doltgresql/core/functions"
"github.com/dolthub/doltgresql/core/id"
"github.com/dolthub/doltgresql/core/triggers"
"github.com/dolthub/doltgresql/server/plpgsql"
pgtypes "github.com/dolthub/doltgresql/server/types"
)
// CreateTrigger implements CREATE TRIGGER.
type CreateTrigger struct {
Name id.Trigger
Function id.Function
Replace bool
Timing triggers.TriggerTiming
Events []triggers.TriggerEvent
ForEachRow bool
When []plpgsql.InterpreterOperation
Arguments []string
Definition string
}
var _ sql.ExecSourceRel = (*CreateTrigger)(nil)
var _ vitess.Injectable = (*CreateTrigger)(nil)
// NewCreateTrigger returns a new *CreateTrigger.
func NewCreateTrigger(
triggerName id.Trigger,
functionName id.Function,
replace bool,
timing triggers.TriggerTiming,
events []triggers.TriggerEvent,
forEachRow bool,
when []plpgsql.InterpreterOperation,
arguments []string,
definition string) *CreateTrigger {
return &CreateTrigger{
Name: triggerName,
Function: functionName,
Replace: replace,
Timing: timing,
Events: events,
ForEachRow: forEachRow,
When: when,
Arguments: arguments,
Definition: definition,
}
}
// Children implements the interface sql.ExecSourceRel.
func (c *CreateTrigger) Children() []sql.Node {
return nil
}
// IsReadOnly implements the interface sql.ExecSourceRel.
func (c *CreateTrigger) IsReadOnly() bool {
return false
}
// Resolved implements the interface sql.ExecSourceRel.
func (c *CreateTrigger) Resolved() bool {
return true
}
// RowIter implements the interface sql.ExecSourceRel.
func (c *CreateTrigger) RowIter(ctx *sql.Context, r sql.Row) (sql.RowIter, error) {
schema, err := core.GetSchemaName(ctx, nil, c.Name.SchemaName())
if err != nil {
return nil, err
}
triggerID := id.NewTrigger(schema, c.Name.TableName(), c.Name.TriggerName())
relationType, err := core.GetRelationType(ctx, schema, c.Name.TableName())
if err != nil {
return nil, err
}
if relationType == core.RelationType_DoesNotExist {
return nil, errors.Errorf(`relation "%s" does not exist`, c.Name.TableName())
} else if relationType != core.RelationType_Table {
return nil, errors.Errorf(`"%s" is not a table or view`, c.Name.TableName())
}
function, err := loadFunction(ctx, nil, c.Function)
if err != nil {
return nil, err
}
if !function.ID.IsValid() {
return nil, errors.Errorf("function %s() does not exist", c.Function.FunctionName())
}
if function.ReturnType != pgtypes.Trigger.ID {
return nil, errors.Errorf(`function %s must return type trigger`, function.ID.FunctionName())
}
trigCollection, err := core.GetTriggersCollectionFromContext(ctx, ctx.GetCurrentDatabase())
if err != nil {
return nil, err
}
if c.Replace && trigCollection.HasTrigger(ctx, triggerID) {
if err = trigCollection.DropTrigger(ctx, triggerID); err != nil {
return nil, err
}
}
err = trigCollection.AddTrigger(ctx, triggers.Trigger{
ID: triggerID,
Function: function.ID,
Timing: c.Timing,
Events: c.Events,
ForEachRow: c.ForEachRow,
When: c.When,
Deferrable: triggers.TriggerDeferrable_NotDeferrable,
ReferencedTableName: "",
Constraint: false,
OldTransitionName: "",
NewTransitionName: "",
Arguments: c.Arguments,
Definition: c.Definition,
})
if err != nil {
return nil, err
}
return sql.RowsToRowIter(), nil
}
// Schema implements the interface sql.ExecSourceRel.
func (c *CreateTrigger) Schema(ctx *sql.Context) sql.Schema {
return nil
}
// String implements the interface sql.ExecSourceRel.
func (c *CreateTrigger) String() string {
return "CREATE TRIGGER"
}
// WithChildren implements the interface sql.ExecSourceRel.
func (c *CreateTrigger) WithChildren(ctx *sql.Context, children ...sql.Node) (sql.Node, error) {
return plan.NillaryWithChildren(c, children...)
}
// WithResolvedChildren implements the interface vitess.Injectable.
func (c *CreateTrigger) WithResolvedChildren(ctx context.Context, children []any) (any, error) {
if len(children) != 0 {
return nil, ErrVitessChildCount.New(0, len(children))
}
return c, nil
}
// loadFunction loads the function with the given ID from the given collection. If the collection is nil, then this also
// loads the collection from the context.
func loadFunction(ctx *sql.Context, funcCollection *functions.Collection, funcID id.Function) (functions.Function, error) {
var err error
if funcCollection == nil {
funcCollection, err = core.GetFunctionsCollectionFromContext(ctx, "")
if err != nil {
return functions.Function{}, err
}
}
var function functions.Function
if len(funcID.SchemaName()) > 0 {
function, err = funcCollection.GetFunction(ctx, funcID)
if err != nil {
return functions.Function{}, err
}
} else {
searchPaths, err := core.SearchPath(ctx)
if err != nil {
return functions.Function{}, err
}
for _, searchPath := range searchPaths {
function, err = funcCollection.GetFunction(ctx, id.NewFunction(searchPath, funcID.FunctionName()))
if err != nil {
return functions.Function{}, err
}
if function.ID.IsValid() {
break
}
}
}
return function, nil
}
+184
View File
@@ -0,0 +1,184 @@
// 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 node
import (
"context"
"github.com/cockroachdb/errors"
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/go-mysql-server/sql/plan"
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
"github.com/dolthub/doltgresql/core"
"github.com/dolthub/doltgresql/core/id"
"github.com/dolthub/doltgresql/server/auth"
"github.com/dolthub/doltgresql/server/types"
)
// CreateType handles the CREATE TYPE statement.
type CreateType struct {
SchemaName string
Name string
// composite type
AsTypes []CompositeAsType
// enum type
Labels []string
typType types.TypeType
}
// CompositeAsType represents an attribute name
// and data type for a composite type.
type CompositeAsType struct {
AttrName string
Typ *types.DoltgresType
Collation string
}
var _ sql.ExecSourceRel = (*CreateType)(nil)
var _ vitess.Injectable = (*CreateType)(nil)
// NewCreateCompositeType creates CreateType node for creating COMPOSITE type.
func NewCreateCompositeType(schema, name string, typs []CompositeAsType) *CreateType {
return &CreateType{SchemaName: schema, Name: name, AsTypes: typs, typType: types.TypeType_Composite}
}
// NewCreateEnumType creates CreateType node for creating ENUM type.
func NewCreateEnumType(schema, name string, labels []string) *CreateType {
return &CreateType{SchemaName: schema, Name: name, Labels: labels, typType: types.TypeType_Enum}
}
// NewCreateShellType creates CreateType node for creating
// a placeholder for a type to be defined later.
func NewCreateShellType(schema, name string) *CreateType {
return &CreateType{SchemaName: schema, Name: name, typType: types.TypeType_Pseudo}
}
// Children implements the interface sql.ExecSourceRel.
func (c *CreateType) Children() []sql.Node {
return nil
}
// IsReadOnly implements the interface sql.ExecSourceRel.
func (c *CreateType) IsReadOnly() bool {
return false
}
// Resolved implements the interface sql.ExecSourceRel.
func (c *CreateType) Resolved() bool {
return true
}
// RowIter implements the interface sql.ExecSourceRel.
func (c *CreateType) RowIter(ctx *sql.Context, r sql.Row) (sql.RowIter, error) {
var userRole auth.Role
auth.LockRead(func() {
userRole = auth.GetRole(ctx.Client().User)
})
if !userRole.IsValid() {
return nil, errors.Errorf(`role "%s" does not exist`, ctx.Client().User)
}
schema, err := core.GetSchemaName(ctx, nil, c.SchemaName)
if err != nil {
return nil, err
}
collection, err := core.GetTypesCollectionFromContext(ctx, "")
if err != nil {
return nil, err
}
typeID := id.NewType(schema, c.Name)
arrayID := id.NewType(schema, "_"+c.Name)
if collection.HasType(ctx, typeID) {
// TODO: if the existing type is array type, it updates the array type name and creates the new type.
return nil, types.ErrTypeAlreadyExists.New(c.Name)
}
var newType *types.DoltgresType
switch c.typType {
case types.TypeType_Pseudo:
newType = types.NewShellType(ctx, typeID)
case types.TypeType_Enum:
enumLabelMap := make(map[string]types.EnumLabel)
for i, l := range c.Labels {
if _, ok := enumLabelMap[l]; ok {
// DETAIL: Key (enumtypid, enumlabel)=(16702, ok) already exists.
return nil, errors.Errorf(`duplicate key value violates unique constraint "pg_enum_typid_label_index"`)
}
labelID := id.NewEnumLabel(typeID, l)
el := types.NewEnumLabel(ctx, labelID, float32(i+1))
enumLabelMap[l] = el
}
newType = types.NewEnumType(ctx, arrayID, typeID, enumLabelMap)
// TODO: store labels somewhere
case types.TypeType_Composite:
// TODO: non-composite types have a zero oid for their relID, which for us would be a null ID.
// We need to find a way to distinguish a null ID from a composite type that does not reference a table
// (which is what relID points to if it represents a table row's composite type)
relID := id.Null
attrs := make([]types.CompositeAttribute, len(c.AsTypes))
for i, a := range c.AsTypes {
attrs[i] = types.NewCompositeAttribute(ctx, relID, a.AttrName, a.Typ, int16(i+1), a.Collation)
}
newType = types.NewCompositeType(ctx, relID, &types.DoltgresType{ID: arrayID, IsUnresolved: true}, typeID, attrs)
default:
return nil, errors.Errorf("create type as %s is not supported", c.typType)
}
err = collection.CreateType(ctx, newType)
if err != nil {
return nil, err
}
// create array type for defined types
if newType.IsDefined {
arrayType := types.CreateArrayTypeFromBaseType(newType)
err = collection.CreateType(ctx, arrayType)
if err != nil {
return nil, err
}
newType.Array = arrayType
}
return sql.RowsToRowIter(), nil
}
// Schema implements the interface sql.ExecSourceRel.
func (c *CreateType) Schema(ctx *sql.Context) sql.Schema {
return nil
}
// String implements the interface sql.ExecSourceRel.
func (c *CreateType) String() string {
return "CREATE TYPE"
}
// WithChildren implements the interface sql.ExecSourceRel.
func (c *CreateType) WithChildren(ctx *sql.Context, children ...sql.Node) (sql.Node, error) {
return plan.NillaryWithChildren(c, children...)
}
// WithResolvedChildren implements the interface vitess.Injectable.
func (c *CreateType) WithResolvedChildren(ctx context.Context, children []any) (any, error) {
if len(children) != 0 {
return nil, ErrVitessChildCount.New(0, len(children))
}
return c, nil
}
+73
View File
@@ -0,0 +1,73 @@
// 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 node
import (
"context"
"github.com/dolthub/go-mysql-server/sql"
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
)
// DiscardStatement is just a marker type, since all functionality is handled by the connection handler,
// rather than the engine. It has to conform to the sql.ExecSourceRel interface to be used in the handler, but this
// functionality is all unused.
type DiscardStatement struct{}
var _ vitess.Injectable = DiscardStatement{}
var _ sql.ExecSourceRel = DiscardStatement{}
// Children implements the interface sql.ExecSourceRel.
func (d DiscardStatement) Children() []sql.Node {
return nil
}
// IsReadOnly implements the interface sql.ExecSourceRel.
func (d DiscardStatement) IsReadOnly() bool {
return true
}
// Resolved implements the interface sql.ExecSourceRel.
func (d DiscardStatement) Resolved() bool {
return true
}
// RowIter implements the interface sql.ExecSourceRel.
func (d DiscardStatement) RowIter(ctx *sql.Context, r sql.Row) (sql.RowIter, error) {
panic("DISCARD ALL should be handled by the connection handler")
}
// Schema implements the interface sql.ExecSourceRel.
func (d DiscardStatement) Schema(ctx *sql.Context) sql.Schema {
return nil
}
// String implements the interface sql.ExecSourceRel.
func (d DiscardStatement) String() string {
return "DISCARD ALL"
}
// WithChildren implements the interface sql.ExecSourceRel.
func (d DiscardStatement) WithChildren(ctx *sql.Context, children ...sql.Node) (sql.Node, error) {
return d, nil
}
// WithResolvedChildren implements the interface vitess.Injectable.
func (d DiscardStatement) WithResolvedChildren(ctx context.Context, children []any) (any, error) {
if len(children) != 0 {
return nil, ErrVitessChildCount.New(0, len(children))
}
return d, nil
}
+105
View File
@@ -0,0 +1,105 @@
// 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 node
import (
"context"
"github.com/cockroachdb/errors"
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/go-mysql-server/sql/plan"
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
"github.com/dolthub/doltgresql/core"
"github.com/dolthub/doltgresql/core/id"
pgtypes "github.com/dolthub/doltgresql/server/types"
)
// DropCast implements DROP CAST.
type DropCast struct {
Source *pgtypes.DoltgresType
Target *pgtypes.DoltgresType
IfExists bool
}
var _ sql.ExecSourceRel = (*DropCast)(nil)
var _ vitess.Injectable = (*DropCast)(nil)
// NewDropCast returns a new *DropCast.
func NewDropCast(source *pgtypes.DoltgresType, target *pgtypes.DoltgresType, ifExists bool) *DropCast {
return &DropCast{
Source: source,
Target: target,
IfExists: ifExists,
}
}
// Children implements the interface sql.ExecSourceRel.
func (c *DropCast) Children() []sql.Node {
return nil
}
// IsReadOnly implements the interface sql.ExecSourceRel.
func (c *DropCast) IsReadOnly() bool {
return false
}
// Resolved implements the interface sql.ExecSourceRel.
func (c *DropCast) Resolved() bool {
return true
}
// RowIter implements the interface sql.ExecSourceRel.
func (c *DropCast) RowIter(ctx *sql.Context, r sql.Row) (sql.RowIter, error) {
castCollection, err := core.GetCastsCollectionFromContext(ctx, "")
if err != nil {
return nil, err
}
castID := id.NewCast(c.Source.ID, c.Target.ID)
if !castCollection.HasCast(ctx, castID) {
if c.IfExists {
// TODO: send notice "cast from type SOURCE_NAME to type TARGET_NAME does not exist, skipping"
return sql.RowsToRowIter(), nil
}
return nil, errors.Errorf("cast from type %s to type %s does not exist", c.Source.Name(), c.Target.Name())
}
if err = castCollection.DropCast(ctx, castID); err != nil {
return nil, err
}
return sql.RowsToRowIter(), nil
}
// Schema implements the interface sql.ExecSourceRel.
func (c *DropCast) Schema(ctx *sql.Context) sql.Schema {
return nil
}
// String implements the interface sql.ExecSourceRel.
func (c *DropCast) String() string {
return "DROP CAST"
}
// WithChildren implements the interface sql.ExecSourceRel.
func (c *DropCast) WithChildren(ctx *sql.Context, children ...sql.Node) (sql.Node, error) {
return plan.NillaryWithChildren(c, children...)
}
// WithResolvedChildren implements the interface vitess.Injectable.
func (c *DropCast) WithResolvedChildren(ctx context.Context, children []any) (any, error) {
if len(children) != 0 {
return nil, ErrVitessChildCount.New(0, len(children))
}
return c, nil
}
+163
View File
@@ -0,0 +1,163 @@
// 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 node
import (
"context"
"fmt"
"github.com/cockroachdb/errors"
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/go-mysql-server/sql/plan"
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
"github.com/dolthub/doltgresql/core"
"github.com/dolthub/doltgresql/core/id"
"github.com/dolthub/doltgresql/server/types"
)
// DropDomain handles the DROP DOMAIN statement.
type DropDomain struct {
database string
schema string
domain string
ifExists bool
cascade bool
}
var _ sql.ExecSourceRel = (*DropDomain)(nil)
var _ vitess.Injectable = (*DropDomain)(nil)
// NewDropDomain returns a new *DropDomain.
func NewDropDomain(ifExists bool, db string, schema string, domain string, cascade bool) *DropDomain {
return &DropDomain{
database: db,
schema: schema,
domain: domain,
ifExists: ifExists,
cascade: cascade,
}
}
// Children implements the interface sql.ExecSourceRel.
func (c *DropDomain) Children() []sql.Node {
return nil
}
// IsReadOnly implements the interface sql.ExecSourceRel.
func (c *DropDomain) IsReadOnly() bool {
return false
}
// Resolved implements the interface sql.ExecSourceRel.
func (c *DropDomain) Resolved() bool {
return true
}
// RowIter implements the interface sql.ExecSourceRel.
func (c *DropDomain) RowIter(ctx *sql.Context, r sql.Row) (sql.RowIter, error) {
currentDb := ctx.GetCurrentDatabase()
if len(c.database) > 0 && c.database != currentDb {
return nil, errors.Errorf("DROP DOMAIN is currently only supported for the current database")
}
schema, err := core.GetSchemaName(ctx, nil, c.schema)
if err != nil {
return nil, err
}
collection, err := core.GetTypesCollectionFromContext(ctx, "")
if err != nil {
return nil, err
}
typeID := id.NewType(schema, c.domain)
domain, err := collection.GetDomainType(ctx, typeID)
if err != nil {
return nil, err
}
if domain == nil {
if c.ifExists {
// TODO: issue a notice
return sql.RowsToRowIter(), nil
} else {
return nil, types.ErrTypeDoesNotExist.New(c.domain)
}
}
if c.cascade {
// TODO: handle cascade
return nil, errors.Errorf(`cascading domain drops are not yet supported`)
}
// iterate on all table columns to check if this domain is currently used.
db, err := core.GetSqlDatabaseFromContext(ctx, "")
if err != nil {
return nil, err
}
tableNames, err := db.GetTableNames(ctx)
if err != nil {
return nil, err
}
for _, tableName := range tableNames {
t, ok, err := db.GetTableInsensitive(ctx, tableName)
if err != nil {
return nil, err
}
if ok {
for _, col := range t.Schema(ctx) {
if dt, isDoltgresType := col.Type.(*types.DoltgresType); isDoltgresType && dt.TypType == types.TypeType_Domain {
if dt.Name() == domain.Name() {
// TODO: issue a detail (list of all columns and tables that uses this domain)
// and a hint (when we support CASCADE)
return nil, errors.Errorf(`cannot drop type %s because other objects depend on it - column %s of table %s depends on type %s'`, c.domain, col.Name, t.Name(), c.domain)
}
}
}
}
}
if err = collection.DropType(ctx, typeID); err != nil {
return nil, err
}
// drop array type of this type
arrayTypeName := fmt.Sprintf(`_%s`, c.domain)
arrayID := id.NewType(schema, arrayTypeName)
if err = collection.DropType(ctx, arrayID); err != nil {
return nil, err
}
return sql.RowsToRowIter(), nil
}
// Schema implements the interface sql.ExecSourceRel.
func (c *DropDomain) Schema(ctx *sql.Context) sql.Schema {
return nil
}
// String implements the interface sql.ExecSourceRel.
func (c *DropDomain) String() string {
return "DROP DOMAIN"
}
// WithChildren implements the interface sql.ExecSourceRel.
func (c *DropDomain) WithChildren(ctx *sql.Context, children ...sql.Node) (sql.Node, error) {
return plan.NillaryWithChildren(c, children...)
}
// WithResolvedChildren implements the interface vitess.Injectable.
func (c *DropDomain) WithResolvedChildren(ctx context.Context, children []any) (any, error) {
if len(children) != 0 {
return nil, ErrVitessChildCount.New(0, len(children))
}
return c, nil
}
+87
View File
@@ -0,0 +1,87 @@
// 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 node
import (
"context"
"github.com/cockroachdb/errors"
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/go-mysql-server/sql/plan"
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
)
// DropExtension implements DROP EXTENSION.
type DropExtension struct {
Names []string
IfExists bool
Cascade bool
}
var _ sql.ExecSourceRel = (*DropExtension)(nil)
var _ vitess.Injectable = (*DropExtension)(nil)
// NewDropExtension returns a new *DropExtension.
func NewDropExtension(names []string, ifExists bool, cascade bool) *DropExtension {
return &DropExtension{
Names: names,
IfExists: ifExists,
Cascade: cascade,
}
}
// Children implements the interface sql.ExecSourceRel.
func (c *DropExtension) Children() []sql.Node {
return nil
}
// IsReadOnly implements the interface sql.ExecSourceRel.
func (c *DropExtension) IsReadOnly() bool {
return false
}
// Resolved implements the interface sql.ExecSourceRel.
func (c *DropExtension) Resolved() bool {
return true
}
// RowIter implements the interface sql.ExecSourceRel.
func (c *DropExtension) RowIter(ctx *sql.Context, r sql.Row) (sql.RowIter, error) {
// TODO: implement this
return nil, errors.Errorf("DROP EXTENSION is not yet implemented")
}
// Schema implements the interface sql.ExecSourceRel.
func (c *DropExtension) Schema(ctx *sql.Context) sql.Schema {
return nil
}
// String implements the interface sql.ExecSourceRel.
func (c *DropExtension) String() string {
return "DROP EXTENSION"
}
// WithChildren implements the interface sql.ExecSourceRel.
func (c *DropExtension) WithChildren(ctx *sql.Context, children ...sql.Node) (sql.Node, error) {
return plan.NillaryWithChildren(c, children...)
}
// WithResolvedChildren implements the interface vitess.Injectable.
func (c *DropExtension) WithResolvedChildren(ctx context.Context, children []any) (any, error) {
if len(children) != 0 {
return nil, ErrVitessChildCount.New(0, len(children))
}
return c, nil
}
+143
View File
@@ -0,0 +1,143 @@
// 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 node
import (
"context"
"github.com/cockroachdb/errors"
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/go-mysql-server/sql/plan"
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
"github.com/dolthub/doltgresql/core"
"github.com/dolthub/doltgresql/core/functions"
"github.com/dolthub/doltgresql/core/id"
)
// RoutineWithParams represent a function or a procedure with schema name, routine name and its parameters.
type RoutineWithParams struct {
SchemaName string
RoutineName string
Args []RoutineParam
}
// DropFunction implements DROP FUNCTION.
type DropFunction struct {
RoutinesWithArgs []*RoutineWithParams
IfExists bool
Cascade bool
}
var _ sql.ExecSourceRel = (*DropFunction)(nil)
var _ vitess.Injectable = (*DropFunction)(nil)
// NewDropFunction returns a new *DropFunction.
func NewDropFunction(ifExists bool, routinesWithArgs []*RoutineWithParams, cascade bool) *DropFunction {
return &DropFunction{
IfExists: ifExists,
RoutinesWithArgs: routinesWithArgs,
Cascade: cascade,
}
}
// Resolved implements the interface sql.ExecSourceRel.
func (d *DropFunction) Resolved() bool {
return true
}
// String implements the interface sql.ExecSourceRel.
func (d *DropFunction) String() string {
return "DROP FUNCTION"
}
// Schema implements the interface sql.ExecSourceRel.
func (d *DropFunction) Schema(ctx *sql.Context) sql.Schema {
return nil
}
// Children implements the interface sql.ExecSourceRel.
func (d *DropFunction) Children() []sql.Node {
return nil
}
// WithChildren implements the interface sql.ExecSourceRel.
func (d *DropFunction) WithChildren(ctx *sql.Context, children ...sql.Node) (sql.Node, error) {
return plan.NillaryWithChildren(d, children...)
}
// IsReadOnly implements the interface sql.ExecSourceRel.
func (d *DropFunction) IsReadOnly() bool {
return false
}
// RowIter implements the interface sql.ExecSourceRel.
func (d *DropFunction) RowIter(ctx *sql.Context, r sql.Row) (iter sql.RowIter, err error) {
funcColl, err := core.GetFunctionsCollectionFromContext(ctx, "")
if err != nil {
return nil, err
}
for _, routineWithArgs := range d.RoutinesWithArgs {
err = dropFunction(ctx, funcColl, routineWithArgs, d.IfExists)
if err != nil {
return nil, err
}
}
return sql.RowsToRowIter(), nil
}
// WithResolvedChildren implements the interface vitess.Injectable.
func (d *DropFunction) WithResolvedChildren(ctx context.Context, children []any) (any, error) {
if len(children) != 0 {
return nil, ErrVitessChildCount.New(0, len(children))
}
return d, nil
}
func dropFunction(ctx *sql.Context, funcColl *functions.Collection, fn *RoutineWithParams, ifExists bool) error {
// TODO: provide db
schema, err := core.GetSchemaName(ctx, nil, fn.SchemaName)
if err != nil {
return err
}
var funcId = id.NewFunction(schema, fn.RoutineName)
if len(fn.Args) == 0 {
funcs, err := funcColl.GetFunctionOverloads(ctx, funcId)
if err != nil {
return err
}
if len(funcs) == 1 {
funcId = funcs[0].ID
} else if len(funcs) > 1 {
funcExists := funcColl.HasFunction(ctx, funcId)
if !funcExists {
return errors.Errorf(`function name "%s" is not unique`, fn.RoutineName)
}
}
} else {
var argTypes = make([]id.Type, len(fn.Args))
for i, arg := range fn.Args {
argTypes[i] = arg.Type.ID
}
funcId = id.NewFunction(schema, fn.RoutineName, argTypes...)
}
funcExists := funcColl.HasFunction(ctx, funcId)
if !funcExists && ifExists {
return nil
}
return funcColl.DropFunction(ctx, funcId)
}
+137
View File
@@ -0,0 +1,137 @@
// 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 node
import (
"context"
"github.com/cockroachdb/errors"
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/go-mysql-server/sql/plan"
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
"github.com/dolthub/doltgresql/core"
"github.com/dolthub/doltgresql/core/id"
"github.com/dolthub/doltgresql/core/procedures"
)
// DropProcedure implements DROP PROCEDURE.
type DropProcedure struct {
RoutinesWithArgs []*RoutineWithParams
IfExists bool
Cascade bool
}
var _ sql.ExecSourceRel = (*DropProcedure)(nil)
var _ vitess.Injectable = (*DropProcedure)(nil)
// NewDropProcedure returns a new *DropProcedure.
func NewDropProcedure(ifExists bool, routinesWithArgs []*RoutineWithParams, cascade bool) *DropProcedure {
return &DropProcedure{
IfExists: ifExists,
RoutinesWithArgs: routinesWithArgs,
Cascade: cascade,
}
}
// Resolved implements the interface sql.ExecSourceRel.
func (d *DropProcedure) Resolved() bool {
return true
}
// String implements the interface sql.ExecSourceRel.
func (d *DropProcedure) String() string {
return "DROP PROCEDURE"
}
// Schema implements the interface sql.ExecSourceRel.
func (d *DropProcedure) Schema(ctx *sql.Context) sql.Schema {
return nil
}
// Children implements the interface sql.ExecSourceRel.
func (d *DropProcedure) Children() []sql.Node {
return nil
}
// WithChildren implements the interface sql.ExecSourceRel.
func (d *DropProcedure) WithChildren(ctx *sql.Context, children ...sql.Node) (sql.Node, error) {
return plan.NillaryWithChildren(d, children...)
}
// IsReadOnly implements the interface sql.ExecSourceRel.
func (d *DropProcedure) IsReadOnly() bool {
return false
}
// RowIter implements the interface sql.ExecSourceRel.
func (d *DropProcedure) RowIter(ctx *sql.Context, r sql.Row) (iter sql.RowIter, err error) {
procColl, err := core.GetProceduresCollectionFromContext(ctx, "")
if err != nil {
return nil, err
}
for _, routineWithArgs := range d.RoutinesWithArgs {
err = dropProcedure(ctx, procColl, routineWithArgs, d.IfExists)
if err != nil {
return nil, err
}
}
return sql.RowsToRowIter(), nil
}
// WithResolvedChildren implements the interface vitess.Injectable.
func (d *DropProcedure) WithResolvedChildren(ctx context.Context, children []any) (any, error) {
if len(children) != 0 {
return nil, ErrVitessChildCount.New(0, len(children))
}
return d, nil
}
func dropProcedure(ctx *sql.Context, procColl *procedures.Collection, fn *RoutineWithParams, ifExists bool) error {
// TODO: provide db
schema, err := core.GetSchemaName(ctx, nil, fn.SchemaName)
if err != nil {
return err
}
var procId = id.NewProcedure(schema, fn.RoutineName)
if len(fn.Args) == 0 {
procs, err := procColl.GetProcedureOverloads(ctx, procId)
if err != nil {
return err
}
if len(procs) == 1 {
procId = procs[0].ID
} else if len(procs) > 1 {
procExists := procColl.HasProcedure(ctx, procId)
if !procExists {
return errors.Errorf(`procedure name "%s" is not unique`, fn.RoutineName)
}
}
} else {
var argTypes = make([]id.Type, len(fn.Args))
for i, arg := range fn.Args {
argTypes[i] = arg.Type.ID
}
procId = id.NewProcedure(schema, fn.RoutineName, argTypes...)
}
procExists := procColl.HasProcedure(ctx, procId)
if !procExists && ifExists {
return nil
}
return procColl.DropProcedure(ctx, procId)
}
+113
View File
@@ -0,0 +1,113 @@
// 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 node
import (
"context"
"github.com/cockroachdb/errors"
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/go-mysql-server/sql/plan"
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
"github.com/dolthub/doltgresql/server/auth"
)
// DropRole handles the DROP ROLE statement.
type DropRole struct {
Names []string
IfExists bool
}
var _ sql.ExecSourceRel = (*DropRole)(nil)
var _ vitess.Injectable = (*DropRole)(nil)
// Children implements the interface sql.ExecSourceRel.
func (c *DropRole) Children() []sql.Node {
return nil
}
// IsReadOnly implements the interface sql.ExecSourceRel.
func (c *DropRole) IsReadOnly() bool {
return false
}
// Resolved implements the interface sql.ExecSourceRel.
func (c *DropRole) Resolved() bool {
return true
}
// RowIter implements the interface sql.ExecSourceRel.
func (c *DropRole) RowIter(ctx *sql.Context, r sql.Row) (sql.RowIter, error) {
// TODO: disallow dropping the role if it owns anything
// First we'll loop over all of the names to check that they all exist
var userRole auth.Role
var roles []auth.Role
var err error
auth.LockRead(func() {
userRole = auth.GetRole(ctx.Client().User)
for _, roleName := range c.Names {
role := auth.GetRole(roleName)
if role.IsValid() {
roles = append(roles, role)
} else if !c.IfExists {
err = errors.Errorf(`role "%s" does not exist`, roleName)
break
}
if !userRole.IsSuperUser && (role.IsSuperUser || !userRole.CanCreateRoles) {
// TODO: grab the actual error message
err = errors.Errorf(`role "%s" does not have permission to drop role "%s"`, userRole.Name, role.Name)
break
}
}
})
if err != nil {
return nil, err
}
// Then we'll loop again, dropping all of the users
auth.LockWrite(func() {
for _, role := range roles {
auth.DropRole(role.Name)
}
err = auth.PersistChanges()
})
if err != nil {
return nil, err
}
return sql.RowsToRowIter(), nil
}
// Schema implements the interface sql.ExecSourceRel.
func (c *DropRole) Schema(ctx *sql.Context) sql.Schema {
return nil
}
// String implements the interface sql.ExecSourceRel.
func (c *DropRole) String() string {
return "DROP ROLE"
}
// WithChildren implements the interface sql.ExecSourceRel.
func (c *DropRole) WithChildren(ctx *sql.Context, children ...sql.Node) (sql.Node, error) {
return plan.NillaryWithChildren(c, children...)
}
// WithResolvedChildren implements the interface vitess.Injectable.
func (c *DropRole) WithResolvedChildren(ctx context.Context, children []any) (any, error) {
if len(children) != 0 {
return nil, ErrVitessChildCount.New(0, len(children))
}
return c, nil
}
+129
View File
@@ -0,0 +1,129 @@
// 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 node
import (
"context"
"github.com/cockroachdb/errors"
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/go-mysql-server/sql/plan"
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
"github.com/dolthub/doltgresql/core"
"github.com/dolthub/doltgresql/core/id"
)
// DropSequence handles the DROP SEQUENCE statement.
type DropSequence struct {
schema string
sequence string
ifExists bool
cascade bool
}
var _ sql.ExecSourceRel = (*DropSequence)(nil)
var _ vitess.Injectable = (*DropSequence)(nil)
// NewDropSequence returns a new *DropSequence.
func NewDropSequence(ifExists bool, schema string, sequence string, cascade bool) *DropSequence {
return &DropSequence{
schema: schema,
sequence: sequence,
ifExists: ifExists,
cascade: cascade,
}
}
// Children implements the interface sql.ExecSourceRel.
func (c *DropSequence) Children() []sql.Node {
return nil
}
// IsReadOnly implements the interface sql.ExecSourceRel.
func (c *DropSequence) IsReadOnly() bool {
return false
}
// Resolved implements the interface sql.ExecSourceRel.
func (c *DropSequence) Resolved() bool {
return true
}
// RowIter implements the interface sql.ExecSourceRel.
func (c *DropSequence) RowIter(ctx *sql.Context, r sql.Row) (sql.RowIter, error) {
schema, err := core.GetSchemaName(ctx, nil, c.schema)
if err != nil {
return nil, err
}
relationType, err := core.GetRelationType(ctx, schema, c.sequence)
if err != nil {
return nil, err
}
if relationType == core.RelationType_DoesNotExist {
if c.ifExists {
// TODO: issue a notice
return sql.RowsToRowIter(), nil
}
return nil, errors.Errorf(`sequence "%s" does not exist`, c.sequence)
}
// TODO: we always use the current database for this operation, but it should also be possible drop a sequence in
// a different DB (e.g. on a different branch)
collection, err := core.GetSequencesCollectionFromContext(ctx, ctx.GetCurrentDatabase())
if err != nil {
return nil, err
}
sequenceID := id.NewSequence(schema, c.sequence)
sequence, err := collection.GetSequence(ctx, sequenceID)
if err != nil {
return nil, err
}
if sequence.OwnerTable.IsValid() {
if c.cascade {
// TODO: if the sequence is referenced by the column's default value, then we also need to delete the default
return nil, errors.Errorf(`cascading sequence drops are not yet supported`)
} else {
// TODO: this error is only true if the sequence is referenced by the column's default value
return nil, errors.Errorf(`cannot drop sequence %s because other objects depend on it`, c.sequence)
}
}
if err = collection.DropSequence(ctx, sequenceID); err != nil {
return nil, err
}
return sql.RowsToRowIter(), nil
}
// Schema implements the interface sql.ExecSourceRel.
func (c *DropSequence) Schema(ctx *sql.Context) sql.Schema {
return nil
}
// String implements the interface sql.ExecSourceRel.
func (c *DropSequence) String() string {
return "DROP SEQUENCE"
}
// WithChildren implements the interface sql.ExecSourceRel.
func (c *DropSequence) WithChildren(ctx *sql.Context, children ...sql.Node) (sql.Node, error) {
return plan.NillaryWithChildren(c, children...)
}
// WithResolvedChildren implements the interface vitess.Injectable.
func (c *DropSequence) WithResolvedChildren(ctx context.Context, children []any) (any, error) {
if len(children) != 0 {
return nil, ErrVitessChildCount.New(0, len(children))
}
return c, nil
}
+109
View File
@@ -0,0 +1,109 @@
// 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 node
import (
"github.com/cockroachdb/errors"
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/go-mysql-server/sql/plan"
"github.com/dolthub/doltgresql/core"
"github.com/dolthub/doltgresql/core/id"
)
// DropTable is a node that implements functionality specifically relevant to Doltgres' table dropping needs.
type DropTable struct {
gmsDropTable *plan.DropTable
}
var _ sql.ExecBuilderNode = (*DropTable)(nil)
// NewDropTable returns a new *DropTable.
func NewDropTable(dropTable *plan.DropTable) *DropTable {
return &DropTable{
gmsDropTable: dropTable,
}
}
// Children implements the interface sql.ExecBuilderNode.
func (c *DropTable) Children() []sql.Node {
return c.gmsDropTable.Children()
}
// IsReadOnly implements the interface sql.ExecBuilderNode.
func (c *DropTable) IsReadOnly() bool {
return false
}
// Resolved implements the interface sql.ExecBuilderNode.
func (c *DropTable) Resolved() bool {
return c.gmsDropTable != nil && c.gmsDropTable.Resolved()
}
// BuildRowIter implements the interface sql.ExecBuilderNode.
func (c *DropTable) BuildRowIter(ctx *sql.Context, b sql.NodeExecBuilder, r sql.Row) (sql.RowIter, error) {
dropTableIter, err := b.Build(ctx, c.gmsDropTable, r)
if err != nil {
return nil, err
}
for _, table := range c.gmsDropTable.Tables {
var dbName string
var schemaName string
var tableName string
switch table := table.(type) {
case *plan.ResolvedTable:
schemaName, err = core.GetSchemaName(ctx, table.Database(), "")
if err != nil {
return nil, err
}
tableName = table.Name()
dbName = table.Database().Name()
default:
return nil, errors.Errorf("encountered unexpected table type `%T` during DROP TABLE", table)
}
tableID := id.NewTable(schemaName, tableName).AsId()
if err = id.ValidateOperation(ctx, id.Section_Table, id.Operation_Delete, dbName, tableID, id.Null); err != nil {
return nil, err
}
if err = id.PerformOperation(ctx, id.Section_Table, id.Operation_Delete, dbName, tableID, id.Null); err != nil {
return nil, err
}
}
return dropTableIter, err
}
// Schema implements the interface sql.ExecBuilderNode.
func (c *DropTable) Schema(ctx *sql.Context) sql.Schema {
return c.gmsDropTable.Schema(ctx)
}
// String implements the interface sql.ExecBuilderNode.
func (c *DropTable) String() string {
return c.gmsDropTable.String()
}
// WithChildren implements the interface sql.ExecBuilderNode.
func (c *DropTable) WithChildren(ctx *sql.Context, children ...sql.Node) (sql.Node, error) {
gmsDropTable, err := c.gmsDropTable.WithChildren(ctx, children...)
if err != nil {
return nil, err
}
return &DropTable{
gmsDropTable: gmsDropTable.(*plan.DropTable),
}, nil
}
+108
View File
@@ -0,0 +1,108 @@
// 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 node
import (
"context"
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/go-mysql-server/sql/plan"
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
"github.com/dolthub/doltgresql/core"
"github.com/dolthub/doltgresql/core/id"
)
// DropTrigger handles the DROP TRIGGER statement.
type DropTrigger struct {
ifExists bool
trigger string
onTblSchema string
onTable string
cascade bool
}
var _ sql.ExecSourceRel = (*DropTrigger)(nil)
var _ vitess.Injectable = (*DropTrigger)(nil)
// NewDropTrigger returns a new *DropTrigger.
func NewDropTrigger(ifExists bool, trigger, schema, table string, cascade bool) *DropTrigger {
return &DropTrigger{
ifExists: ifExists,
trigger: trigger,
onTblSchema: schema,
onTable: table,
cascade: cascade,
}
}
// Children implements the interface sql.ExecSourceRel.
func (c *DropTrigger) Children() []sql.Node {
return nil
}
// IsReadOnly implements the interface sql.ExecSourceRel.
func (c *DropTrigger) IsReadOnly() bool {
return false
}
// Resolved implements the interface sql.ExecSourceRel.
func (c *DropTrigger) Resolved() bool {
return true
}
// RowIter implements the interface sql.ExecSourceRel.
func (c *DropTrigger) RowIter(ctx *sql.Context, r sql.Row) (sql.RowIter, error) {
schema, err := core.GetSchemaName(ctx, nil, c.onTblSchema)
if err != nil {
return nil, err
}
collection, err := core.GetTriggersCollectionFromContext(ctx, ctx.GetCurrentDatabase())
if err != nil {
return nil, err
}
triggerID := id.NewTrigger(schema, c.onTable, c.trigger)
hasTrigger := collection.HasTrigger(ctx, triggerID)
if !hasTrigger && c.ifExists {
return sql.RowsToRowIter(), nil
}
if err = collection.DropTrigger(ctx, triggerID); err != nil {
return nil, err
}
return sql.RowsToRowIter(), nil
}
// Schema implements the interface sql.ExecSourceRel.
func (c *DropTrigger) Schema(ctx *sql.Context) sql.Schema {
return nil
}
// String implements the interface sql.ExecSourceRel.
func (c *DropTrigger) String() string {
return "DROP TRIGGER"
}
// WithChildren implements the interface sql.ExecSourceRel.
func (c *DropTrigger) WithChildren(ctx *sql.Context, children ...sql.Node) (sql.Node, error) {
return plan.NillaryWithChildren(c, children...)
}
// WithResolvedChildren implements the interface vitess.Injectable.
func (c *DropTrigger) WithResolvedChildren(ctx context.Context, children []any) (any, error) {
if len(children) != 0 {
return nil, ErrVitessChildCount.New(0, len(children))
}
return c, nil
}
+187
View File
@@ -0,0 +1,187 @@
// 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 node
import (
"context"
"fmt"
"strings"
"github.com/cockroachdb/errors"
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/go-mysql-server/sql/plan"
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
"github.com/dolthub/doltgresql/core"
"github.com/dolthub/doltgresql/core/id"
"github.com/dolthub/doltgresql/server/auth"
"github.com/dolthub/doltgresql/server/types"
)
// DropType handles the DROP TYPE statement.
type DropType struct {
database string
schName string
typName string
ifExists bool
cascade bool
}
var _ sql.ExecSourceRel = (*DropType)(nil)
var _ vitess.Injectable = (*DropType)(nil)
// NewDropType returns a new *DropType.
func NewDropType(ifExists bool, db, sch, typ string, cascade bool) *DropType {
return &DropType{
database: db,
schName: sch,
typName: typ,
ifExists: ifExists,
cascade: cascade,
}
}
// Children implements the interface sql.ExecSourceRel.
func (c *DropType) Children() []sql.Node {
return nil
}
// IsReadOnly implements the interface sql.ExecSourceRel.
func (c *DropType) IsReadOnly() bool {
return false
}
// Resolved implements the interface sql.ExecSourceRel.
func (c *DropType) Resolved() bool {
return true
}
// RowIter implements the interface sql.ExecSourceRel.
func (c *DropType) RowIter(ctx *sql.Context, r sql.Row) (sql.RowIter, error) {
var userRole auth.Role
auth.LockRead(func() {
userRole = auth.GetRole(ctx.Client().User)
})
if !userRole.IsValid() {
return nil, errors.Errorf(`role "%s" does not exist`, ctx.Client().User)
}
currentDb := ctx.GetCurrentDatabase()
if len(c.database) > 0 && c.database != currentDb {
return nil, errors.Errorf("DROP TYPE is currently only supported for the current database")
}
schema, err := core.GetSchemaName(ctx, nil, c.schName)
if err != nil {
return nil, err
}
collection, err := core.GetTypesCollectionFromContext(ctx, "")
if err != nil {
return nil, err
}
typeID := id.NewType(schema, c.typName)
typ, err := collection.GetType(ctx, typeID)
if err != nil {
return nil, err
}
if typ == nil {
if c.ifExists {
// TODO: issue a notice
return sql.RowsToRowIter(), nil
} else {
return nil, types.ErrTypeDoesNotExist.New(c.typName)
}
}
if c.cascade {
// TODO: handle cascade
return nil, errors.Errorf(`cascading type drops are not yet supported`)
}
if _, ok := types.IDToBuiltInDoltgresType[typ.ID]; ok {
return nil, types.ErrCannotDropSystemType.New(typ.String())
}
// TODO: use .IsArrayType() when we support OIDs, so Elem OID isn't 0
if typ.TypCategory == types.TypeCategory_ArrayTypes {
// TODO: get the base type name
// add HINT: You can drop type ___ instead. (base type)
arrTypeName := typ.String()
return nil, types.ErrCannotDropArrayType.New(arrTypeName, strings.TrimSuffix(arrTypeName, "[]"))
}
// iterate on all table columns to check if this type is currently used.
db, err := core.GetSqlDatabaseFromContext(ctx, "")
if err != nil {
return nil, err
}
tableNames, err := db.GetTableNames(ctx)
if err != nil {
return nil, err
}
for _, tableName := range tableNames {
t, ok, err := db.GetTableInsensitive(ctx, tableName)
if err != nil {
return nil, err
}
if ok {
for _, col := range t.Schema(ctx) {
if dt, isDoltgresType := col.Type.(*types.DoltgresType); isDoltgresType {
if dt.Name() == typ.Name() {
// TODO: issue a detail (list of all columns and tables that uses this type)
// and a hint (when we support CASCADE)
return nil, errors.Errorf(`cannot drop type %s because other objects depend on it - column %s of table %s depends on type %s'`, c.typName, col.Name, t.Name(), c.typName)
}
}
}
}
}
if err = collection.DropType(ctx, typeID); err != nil {
return nil, err
}
// undefined/shell type doesn't create array type.
if typ.IsDefined {
arrayTypeName := fmt.Sprintf(`_%s`, c.typName)
arrayID := id.NewType(schema, arrayTypeName)
if err = collection.DropType(ctx, arrayID); err != nil {
return nil, err
}
}
return sql.RowsToRowIter(), nil
}
// Schema implements the interface sql.ExecSourceRel.
func (c *DropType) Schema(ctx *sql.Context) sql.Schema {
return nil
}
// String implements the interface sql.ExecSourceRel.
func (c *DropType) String() string {
return "DROP TYPE"
}
// WithChildren implements the interface sql.ExecSourceRel.
func (c *DropType) WithChildren(ctx *sql.Context, children ...sql.Node) (sql.Node, error) {
return plan.NillaryWithChildren(c, children...)
}
// WithResolvedChildren implements the interface vitess.Injectable.
func (c *DropType) WithResolvedChildren(ctx context.Context, children []any) (any, error) {
if len(children) != 0 {
return nil, ErrVitessChildCount.New(0, len(children))
}
return c, nil
}
+391
View File
@@ -0,0 +1,391 @@
// 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 node
import (
"context"
"github.com/cockroachdb/errors"
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/go-mysql-server/sql/plan"
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
"github.com/dolthub/doltgresql/core"
"github.com/dolthub/doltgresql/server/auth"
)
// Grant handles all of the GRANT statements.
type Grant struct {
GrantTable *GrantTable
GrantSchema *GrantSchema
GrantDatabase *GrantDatabase
GrantSequence *GrantSequence
GrantRoutine *GrantRoutine
GrantRole *GrantRole
ToRoles []string
WithGrantOption bool // This is "WITH ADMIN OPTION" for GrantRole only
GrantedBy string
}
// GrantTable specifically handles the GRANT ... ON TABLE statement.
type GrantTable struct {
Privileges []auth.Privilege
Tables []doltdb.TableName
}
// GrantSchema specifically handles the GRANT ... ON SCHEMA statement.
type GrantSchema struct {
Privileges []auth.Privilege
Schemas []string
}
// GrantDatabase specifically handles the GRANT ... ON DATABASE statement.
type GrantDatabase struct {
Privileges []auth.Privilege
Databases []string
}
// GrantSequence specifically handles the GRANT ... ON SEQUENCE statement.
type GrantSequence struct {
Privileges []auth.Privilege
Sequences []auth.SequencePrivilegeKey
}
// GrantRoutine specifically handles the GRANT ... ON FUNCTION/PROCEDURE/ROUTINE statement.
type GrantRoutine struct {
Privileges []auth.Privilege
Routines []auth.RoutinePrivilegeKey
}
// GrantRole specifically handles the GRANT <roles> TO <roles> statement.
type GrantRole struct {
Groups []string
}
var _ sql.ExecSourceRel = (*Grant)(nil)
var _ vitess.Injectable = (*Grant)(nil)
// Children implements the interface sql.ExecSourceRel.
func (g *Grant) Children() []sql.Node {
return nil
}
// IsReadOnly implements the interface sql.ExecSourceRel.
func (g *Grant) IsReadOnly() bool {
return false
}
// Resolved implements the interface sql.ExecSourceRel.
func (g *Grant) Resolved() bool {
return true
}
// RowIter implements the interface sql.ExecSourceRel.
func (g *Grant) RowIter(ctx *sql.Context, r sql.Row) (sql.RowIter, error) {
var err error
auth.LockWrite(func() {
switch {
case g.GrantTable != nil:
if err = g.grantTable(ctx); err != nil {
return
}
case g.GrantSchema != nil:
if err = g.grantSchema(ctx); err != nil {
return
}
case g.GrantDatabase != nil:
if err = g.grantDatabase(ctx); err != nil {
return
}
case g.GrantSequence != nil:
if err = g.grantSequence(ctx); err != nil {
return
}
case g.GrantRoutine != nil:
if err = g.grantRoutine(ctx); err != nil {
return
}
case g.GrantRole != nil:
if err = g.grantRole(ctx); err != nil {
return
}
default:
err = errors.Errorf("GRANT statement is not yet supported")
return
}
err = auth.PersistChanges()
})
if err != nil {
return nil, err
}
return sql.RowsToRowIter(), nil
}
// Schema implements the interface sql.ExecSourceRel.
func (g *Grant) Schema(ctx *sql.Context) sql.Schema {
return nil
}
// String implements the interface sql.ExecSourceRel.
func (g *Grant) String() string {
switch {
case g.GrantTable != nil:
return "GRANT TABLE"
default:
return "GRANT"
}
}
// WithChildren implements the interface sql.ExecSourceRel.
func (g *Grant) WithChildren(ctx *sql.Context, children ...sql.Node) (sql.Node, error) {
return plan.NillaryWithChildren(g, children...)
}
// WithResolvedChildren implements the interface vitess.Injectable.
func (g *Grant) WithResolvedChildren(ctx context.Context, children []any) (any, error) {
if len(children) != 0 {
return nil, ErrVitessChildCount.New(0, len(children))
}
return g, nil
}
// common handles the initial logic for each GRANT statement. `roles` are the `ToRoles`. `userRole` is the role of the
// session's selected user.
func (g *Grant) common(ctx *sql.Context) (roles []auth.Role, userRole auth.Role, err error) {
roles = make([]auth.Role, len(g.ToRoles))
// First we'll verify that all of the roles exist
for i, roleName := range g.ToRoles {
roles[i] = auth.GetRole(roleName)
if !roles[i].IsValid() {
return nil, auth.Role{}, errors.Errorf(`role "%s" does not exist`, roleName)
}
}
// Then we'll check that the role that is granting the privileges exists
userRole = auth.GetRole(ctx.Client().User)
if !userRole.IsValid() {
return nil, auth.Role{}, errors.Errorf(`role "%s" does not exist`, ctx.Client().User)
}
if len(g.GrantedBy) != 0 {
grantedByRole := auth.GetRole(g.GrantedBy)
if !grantedByRole.IsValid() {
return nil, auth.Role{}, errors.Errorf(`role "%s" does not exist`, g.GrantedBy)
}
if userRole.ID() != grantedByRole.ID() {
// TODO: grab the actual error message
return nil, auth.Role{}, errors.New("GRANTED BY may only be set to the calling user")
}
}
return roles, userRole, nil
}
// grantTable handles *GrantTable from within RowIter.
func (g *Grant) grantTable(ctx *sql.Context) error {
roles, userRole, err := g.common(ctx)
if err != nil {
return err
}
for _, role := range roles {
for _, table := range g.GrantTable.Tables {
schemaName, err := core.GetSchemaName(ctx, nil, table.Schema)
if err != nil {
return err
}
key := auth.TablePrivilegeKey{
Role: userRole.ID(),
Table: doltdb.TableName{Name: table.Name, Schema: schemaName},
}
for _, privilege := range g.GrantTable.Privileges {
grantedBy := auth.HasTablePrivilegeGrantOption(key, privilege)
if !grantedBy.IsValid() {
// TODO: grab the actual error message
return errors.Errorf(`role "%s" does not have permission to grant this privilege`, userRole.Name)
}
auth.AddTablePrivilege(auth.TablePrivilegeKey{
Role: role.ID(),
Table: doltdb.TableName{Name: table.Name, Schema: schemaName},
}, auth.GrantedPrivilege{
Privilege: privilege,
GrantedBy: grantedBy,
}, g.WithGrantOption)
}
}
}
return nil
}
// grantSchema handles *GrantSchema from within RowIter.
func (g *Grant) grantSchema(ctx *sql.Context) error {
roles, userRole, err := g.common(ctx)
if err != nil {
return err
}
for _, role := range roles {
for _, schema := range g.GrantSchema.Schemas {
key := auth.SchemaPrivilegeKey{
Role: userRole.ID(),
Schema: schema,
}
for _, privilege := range g.GrantSchema.Privileges {
grantedBy := auth.HasSchemaPrivilegeGrantOption(key, privilege)
if !grantedBy.IsValid() {
// TODO: grab the actual error message
return errors.Errorf(`role "%s" does not have permission to grant this privilege`, userRole.Name)
}
auth.AddSchemaPrivilege(auth.SchemaPrivilegeKey{
Role: role.ID(),
Schema: schema,
}, auth.GrantedPrivilege{
Privilege: privilege,
GrantedBy: grantedBy,
}, g.WithGrantOption)
}
}
}
return nil
}
// grantDatabase handles *GrantDatabase from within RowIter.
func (g *Grant) grantDatabase(ctx *sql.Context) error {
roles, userRole, err := g.common(ctx)
if err != nil {
return err
}
for _, role := range roles {
for _, database := range g.GrantDatabase.Databases {
key := auth.DatabasePrivilegeKey{
Role: userRole.ID(),
Name: database,
}
for _, privilege := range g.GrantDatabase.Privileges {
grantedBy := auth.HasDatabasePrivilegeGrantOption(key, privilege)
if !grantedBy.IsValid() {
// TODO: grab the actual error message
return errors.Errorf(`role "%s" does not have permission to grant this privilege`, userRole.Name)
}
auth.AddDatabasePrivilege(auth.DatabasePrivilegeKey{
Role: role.ID(),
Name: database,
}, auth.GrantedPrivilege{
Privilege: privilege,
GrantedBy: grantedBy,
}, g.WithGrantOption)
}
}
}
return nil
}
// grantSequence handles *GrantSequence from within RowIter.
func (g *Grant) grantSequence(ctx *sql.Context) error {
roles, userRole, err := g.common(ctx)
if err != nil {
return err
}
for _, role := range roles {
for _, seq := range g.GrantSequence.Sequences {
schemaName, err := core.GetSchemaName(ctx, nil, seq.Schema)
if err != nil {
return err
}
key := auth.SequencePrivilegeKey{
Role: userRole.ID(),
Schema: schemaName,
Name: seq.Name,
}
for _, privilege := range g.GrantSequence.Privileges {
grantedBy := auth.HasSequencePrivilegeGrantOption(key, privilege)
if !grantedBy.IsValid() {
// TODO: grab the actual error message
return errors.Errorf(`role "%s" does not have permission to grant this privilege`, userRole.Name)
}
auth.AddSequencePrivilege(auth.SequencePrivilegeKey{
Role: role.ID(),
Schema: schemaName,
Name: seq.Name,
}, auth.GrantedPrivilege{
Privilege: privilege,
GrantedBy: grantedBy,
}, g.WithGrantOption)
}
}
}
return nil
}
// grantRoutine handles *GrantRoutine from within RowIter.
func (g *Grant) grantRoutine(ctx *sql.Context) error {
roles, userRole, err := g.common(ctx)
if err != nil {
return err
}
for _, role := range roles {
for _, routine := range g.GrantRoutine.Routines {
schemaName, err := core.GetSchemaName(ctx, nil, routine.Schema)
if err != nil {
return err
}
key := auth.RoutinePrivilegeKey{
Role: userRole.ID(),
Schema: schemaName,
Name: routine.Name,
ArgTypes: routine.ArgTypes,
}
for _, privilege := range g.GrantRoutine.Privileges {
grantedBy := auth.HasRoutinePrivilegeGrantOption(key, privilege)
if !grantedBy.IsValid() {
// TODO: grab the actual error message
return errors.Errorf(`role "%s" does not have permission to grant this privilege`, userRole.Name)
}
auth.AddRoutinePrivilege(auth.RoutinePrivilegeKey{
Role: role.ID(),
Schema: schemaName,
Name: routine.Name,
ArgTypes: routine.ArgTypes,
}, auth.GrantedPrivilege{
Privilege: privilege,
GrantedBy: grantedBy,
}, g.WithGrantOption)
}
}
}
return nil
}
// grantRole handles *GrantRole from within RowIter.
func (g *Grant) grantRole(ctx *sql.Context) error {
members, userRole, err := g.common(ctx)
if err != nil {
return err
}
groups := make([]auth.Role, len(g.GrantRole.Groups))
for i, groupName := range g.GrantRole.Groups {
groups[i] = auth.GetRole(groupName)
if !groups[i].IsValid() {
return errors.Errorf(`role "%s" does not exist`, groupName)
}
}
for _, member := range members {
for _, group := range groups {
memberGroupID, _, withAdminOption := auth.IsRoleAMember(userRole.ID(), group.ID())
if !memberGroupID.IsValid() || !withAdminOption {
// TODO: grab the actual error message
return errors.Errorf(`role "%s" does not have permission to grant role "%s"`, userRole.Name, group.Name)
}
auth.AddMemberToGroup(member.ID(), group.ID(), g.WithGrantOption, memberGroupID)
}
}
return nil
}
+87
View File
@@ -0,0 +1,87 @@
// 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 node
import (
"context"
"fmt"
"io"
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/dsess"
"github.com/dolthub/go-mysql-server/sql"
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
"github.com/jackc/pgx/v5/pgproto3"
)
var _ vitess.Injectable = (*NoOp)(nil)
var _ sql.ExecSourceRel = (*NoOp)(nil)
// NoOp is a node that does nothing and issues zero or more warnings when run.
// Used when a statement should parse but isn't expected to do anything, for compatibility with Postgres dumps / tools.
type NoOp struct {
Warnings []string
}
func (n NoOp) Resolved() bool {
return true
}
func (n NoOp) String() string {
return fmt.Sprintf("%v", n.Warnings)
}
func (n NoOp) Schema(ctx *sql.Context) sql.Schema {
return nil
}
func (n NoOp) Children() []sql.Node {
return nil
}
func (n NoOp) WithChildren(ctx *sql.Context, children ...sql.Node) (sql.Node, error) {
return n, nil
}
func (n NoOp) IsReadOnly() bool {
return true
}
func (n NoOp) WithResolvedChildren(ctx context.Context, children []any) (any, error) {
return n, nil
}
type noOpRowIter struct {
warnings []string
}
func (n noOpRowIter) Next(ctx *sql.Context) (sql.Row, error) {
return nil, io.EOF
}
func (n noOpRowIter) Close(ctx *sql.Context) error {
for _, warning := range n.warnings {
noticeResponse := &pgproto3.NoticeResponse{
Severity: "WARNING",
Message: warning,
}
sess := dsess.DSessFromSess(ctx.Session)
sess.Notice(noticeResponse)
}
return nil
}
func (n NoOp) RowIter(ctx *sql.Context, r sql.Row) (sql.RowIter, error) {
return noOpRowIter{warnings: n.Warnings}, nil
}
+115
View File
@@ -0,0 +1,115 @@
// 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 node
import (
"context"
"fmt"
"github.com/cockroachdb/errors"
"github.com/dolthub/go-mysql-server/sql"
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
)
// Return represents the statement RETURN statement.
type Return struct {
Expr sql.Expression
exprStmt string
}
var _ sql.ExecSourceRel = (*Return)(nil)
var _ sql.Expressioner = (*Return)(nil)
var _ vitess.Injectable = (*Return)(nil)
// NewReturn creates a new *Return node.
func NewReturn(exprStmt string) *Return {
return &Return{
Expr: nil,
exprStmt: exprStmt,
}
}
// Children implements the interface sql.ExecSourceRel.
func (r *Return) Children() []sql.Node {
return nil
}
// IsReadOnly implements the interface sql.ExecSourceRel.
func (r *Return) IsReadOnly() bool {
return true
}
// Resolved implements the interface sql.ExecSourceRel.
func (r *Return) Resolved() bool {
if r.Expr == nil {
return false
}
return !r.Expr.Resolved()
}
// RowIter implements the interface sql.ExecSourceRel.
func (r *Return) RowIter(ctx *sql.Context, row sql.Row) (sql.RowIter, error) {
return nil, errors.Errorf(`cannot call RowIter on Return node`)
}
// String implements the interface sql.ExecSourceRel.
func (r *Return) String() string {
if r.Expr == nil {
return fmt.Sprintf("RETURN %s", r.exprStmt)
}
return fmt.Sprintf("RETURN %s", r.Expr.String())
}
// Schema implements the interface sql.ExecSourceRel.
func (r *Return) Schema(ctx *sql.Context) sql.Schema {
return sql.Schema{
{Name: r.Expr.String(), Type: r.Expr.Type(ctx), Source: ""},
}
}
// WithChildren implements the interface sql.ExecSourceRel.
func (r *Return) WithChildren(ctx *sql.Context, children ...sql.Node) (sql.Node, error) {
if len(children) != 0 {
return nil, sql.ErrInvalidChildrenNumber.New(r, len(children), 0)
}
return r, nil
}
// WithResolvedChildren implements the interface sql.ExecSourceRel.
func (r *Return) WithResolvedChildren(ctx context.Context, children []any) (any, error) {
if len(children) != 1 {
return nil, sql.ErrInvalidChildrenNumber.New(r, len(children), 1)
}
nr := *r
nr.Expr = children[0].(sql.Expression)
return &nr, nil
}
// Expressions implements the interface sql.Expressioner.
func (r *Return) Expressions() []sql.Expression {
return []sql.Expression{r.Expr}
}
// WithExpressions implements the interface sql.Expressioner.
func (r *Return) WithExpressions(ctx *sql.Context, exprs ...sql.Expression) (sql.Node, error) {
if len(exprs) != 1 {
return nil, sql.ErrInvalidChildrenNumber.New(r, len(exprs), 1)
}
nr := *r
nr.Expr = exprs[0]
return &nr, nil
}
+393
View File
@@ -0,0 +1,393 @@
// 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 node
import (
"context"
"github.com/cockroachdb/errors"
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/go-mysql-server/sql/plan"
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
"github.com/dolthub/doltgresql/core"
"github.com/dolthub/doltgresql/server/auth"
)
// Revoke handles all of the REVOKE statements.
type Revoke struct {
RevokeTable *RevokeTable
RevokeSchema *RevokeSchema
RevokeDatabase *RevokeDatabase
RevokeSequence *RevokeSequence
RevokeRoutine *RevokeRoutine
RevokeRole *RevokeRole
FromRoles []string
GrantedBy string
GrantOptionFor bool // This is "ADMIN OPTION FOR" for RevokeRole only
Cascade bool // When false, represents RESTRICT
}
// RevokeTable specifically handles the REVOKE ... ON TABLE statement.
type RevokeTable struct {
Privileges []auth.Privilege
Tables []doltdb.TableName
}
// RevokeSchema specifically handles the REVOKE ... ON SCHEMA statement.
type RevokeSchema struct {
Privileges []auth.Privilege
Schemas []string
}
// RevokeDatabase specifically handles the REVOKE ... ON DATABASE statement.
type RevokeDatabase struct {
Privileges []auth.Privilege
Databases []string
}
// RevokeSequence specifically handles the REVOKE ... ON SEQUENCE statement.
type RevokeSequence struct {
Privileges []auth.Privilege
Sequences []auth.SequencePrivilegeKey
}
// RevokeRoutine specifically handles the REVOKE ... ON FUNCTION/PROCEDURE/ROUTINE statement.
type RevokeRoutine struct {
Privileges []auth.Privilege
Routines []auth.RoutinePrivilegeKey
}
// RevokeRole specifically handles the REVOKE <roles> FROM <roles> statement.
type RevokeRole struct {
Groups []string
}
var _ sql.ExecSourceRel = (*Revoke)(nil)
var _ vitess.Injectable = (*Revoke)(nil)
// Children implements the interface sql.ExecSourceRel.
func (r *Revoke) Children() []sql.Node {
return nil
}
// IsReadOnly implements the interface sql.ExecSourceRel.
func (r *Revoke) IsReadOnly() bool {
return false
}
// Resolved implements the interface sql.ExecSourceRel.
func (r *Revoke) Resolved() bool {
return true
}
// RowIter implements the interface sql.ExecSourceRel.
func (r *Revoke) RowIter(ctx *sql.Context, _ sql.Row) (sql.RowIter, error) {
if r.Cascade {
return nil, errors.New("REVOKE does not yet support CASCADE")
}
var err error
auth.LockWrite(func() {
switch {
case r.RevokeTable != nil:
if err = r.revokeTable(ctx); err != nil {
return
}
case r.RevokeSchema != nil:
if err = r.revokeSchema(ctx); err != nil {
return
}
case r.RevokeDatabase != nil:
if err = r.revokeDatabase(ctx); err != nil {
return
}
case r.RevokeSequence != nil:
if err = r.revokeSequence(ctx); err != nil {
return
}
case r.RevokeRoutine != nil:
if err = r.revokeRoutine(ctx); err != nil {
return
}
case r.RevokeRole != nil:
if err = r.revokeRole(ctx); err != nil {
return
}
default:
err = errors.Errorf("REVOKE statement is not yet supported")
return
}
err = auth.PersistChanges()
})
if err != nil {
return nil, err
}
return sql.RowsToRowIter(), nil
}
// Schema implements the interface sql.ExecSourceRel.
func (r *Revoke) Schema(ctx *sql.Context) sql.Schema {
return nil
}
// String implements the interface sql.ExecSourceRel.
func (r *Revoke) String() string {
switch {
case r.RevokeTable != nil:
return "REVOKE TABLE"
default:
return "REVOKE"
}
}
// WithChildren implements the interface sql.ExecSourceRel.
func (r *Revoke) WithChildren(ctx *sql.Context, children ...sql.Node) (sql.Node, error) {
return plan.NillaryWithChildren(r, children...)
}
// WithResolvedChildren implements the interface vitess.Injectable.
func (r *Revoke) WithResolvedChildren(ctx context.Context, children []any) (any, error) {
if len(children) != 0 {
return nil, ErrVitessChildCount.New(0, len(children))
}
return r, nil
}
// common handles the initial logic for each REVOKE statement. `roles` are the `FromRoles`. `userRole` is the role of
// the session's selected user. `grantedByID` is the `GrantedBy` user if specified (or `userRole` if not).
func (r *Revoke) common(ctx *sql.Context) (roles []auth.Role, userRole auth.Role, grantedByID auth.RoleID, err error) {
roles = make([]auth.Role, len(r.FromRoles))
// First we'll verify that all of the roles exist
for i, roleName := range r.FromRoles {
roles[i] = auth.GetRole(roleName)
if !roles[i].IsValid() {
return nil, auth.Role{}, 0, errors.Errorf(`role "%s" does not exist`, roleName)
}
}
// Then we'll check that the role that is revoking the privileges exists
userRole = auth.GetRole(ctx.Client().User)
if !userRole.IsValid() {
return nil, auth.Role{}, 0, errors.Errorf(`role "%s" does not exist`, ctx.Client().User)
}
if len(r.GrantedBy) != 0 {
grantedByRole := auth.GetRole(r.GrantedBy)
if !grantedByRole.IsValid() {
return nil, auth.Role{}, 0, errors.Errorf(`role "%s" does not exist`, r.GrantedBy)
}
if groupID, _, _ := auth.IsRoleAMember(userRole.ID(), grantedByRole.ID()); !groupID.IsValid() {
// TODO: grab the actual error message
return nil, auth.Role{}, 0, errors.Errorf(`role "%s" does not have permission to revoke this privilege`, userRole.Name)
}
} else {
grantedByID = userRole.ID()
}
return roles, userRole, grantedByID, nil
}
// revokeTable handles *RevokeTable from within RowIter.
func (r *Revoke) revokeTable(ctx *sql.Context) error {
roles, userRole, grantedByID, err := r.common(ctx)
if err != nil {
return err
}
for _, role := range roles {
for _, table := range r.RevokeTable.Tables {
schemaName, err := core.GetSchemaName(ctx, nil, table.Schema)
if err != nil {
return err
}
key := auth.TablePrivilegeKey{
Role: userRole.ID(),
Table: doltdb.TableName{Name: table.Name, Schema: schemaName},
}
for _, privilege := range r.RevokeTable.Privileges {
if id := auth.HasTablePrivilegeGrantOption(key, privilege); !id.IsValid() {
// TODO: grab the actual error message
return errors.Errorf(`role "%s" does not have permission to revoke this privilege`, userRole.Name)
}
auth.RemoveTablePrivilege(auth.TablePrivilegeKey{
Role: role.ID(),
Table: doltdb.TableName{Name: table.Name, Schema: schemaName},
}, auth.GrantedPrivilege{
Privilege: privilege,
GrantedBy: grantedByID,
}, r.GrantOptionFor)
}
}
}
return nil
}
// revokeSchema handles *RevokeSchema from within RowIter.
func (r *Revoke) revokeSchema(ctx *sql.Context) error {
roles, userRole, grantedByID, err := r.common(ctx)
if err != nil {
return err
}
for _, role := range roles {
for _, schema := range r.RevokeSchema.Schemas {
key := auth.SchemaPrivilegeKey{
Role: userRole.ID(),
Schema: schema,
}
for _, privilege := range r.RevokeSchema.Privileges {
if id := auth.HasSchemaPrivilegeGrantOption(key, privilege); !id.IsValid() {
// TODO: grab the actual error message
return errors.Errorf(`role "%s" does not have permission to revoke this privilege`, userRole.Name)
}
auth.RemoveSchemaPrivilege(auth.SchemaPrivilegeKey{
Role: role.ID(),
Schema: schema,
}, auth.GrantedPrivilege{
Privilege: privilege,
GrantedBy: grantedByID,
}, r.GrantOptionFor)
}
}
}
return nil
}
// revokeDatabase handles *RevokeDatabase from within RowIter.
func (r *Revoke) revokeDatabase(ctx *sql.Context) error {
roles, userRole, grantedByID, err := r.common(ctx)
if err != nil {
return err
}
for _, role := range roles {
for _, databases := range r.RevokeDatabase.Databases {
key := auth.DatabasePrivilegeKey{
Role: userRole.ID(),
Name: databases,
}
for _, privilege := range r.RevokeDatabase.Privileges {
if id := auth.HasDatabasePrivilegeGrantOption(key, privilege); !id.IsValid() {
// TODO: grab the actual error message
return errors.Errorf(`role "%s" does not have permission to revoke this privilege`, userRole.Name)
}
auth.RemoveDatabasePrivilege(auth.DatabasePrivilegeKey{
Role: role.ID(),
Name: databases,
}, auth.GrantedPrivilege{
Privilege: privilege,
GrantedBy: grantedByID,
}, r.GrantOptionFor)
}
}
}
return nil
}
// revokeSequence handles *RevokeSequence from within RowIter.
func (r *Revoke) revokeSequence(ctx *sql.Context) error {
roles, userRole, grantedByID, err := r.common(ctx)
if err != nil {
return err
}
for _, role := range roles {
for _, seq := range r.RevokeSequence.Sequences {
schemaName, err := core.GetSchemaName(ctx, nil, seq.Schema)
if err != nil {
return err
}
key := auth.SequencePrivilegeKey{
Role: userRole.ID(),
Schema: schemaName,
Name: seq.Name,
}
for _, privilege := range r.RevokeSequence.Privileges {
if id := auth.HasSequencePrivilegeGrantOption(key, privilege); !id.IsValid() {
// TODO: grab the actual error message
return errors.Errorf(`role "%s" does not have permission to revoke this privilege`, userRole.Name)
}
auth.RemoveSequencePrivilege(auth.SequencePrivilegeKey{
Role: role.ID(),
Schema: schemaName,
Name: seq.Name,
}, auth.GrantedPrivilege{
Privilege: privilege,
GrantedBy: grantedByID,
}, r.GrantOptionFor)
}
}
}
return nil
}
// revokeRoutine handles *RevokeRoutine from within RowIter.
func (r *Revoke) revokeRoutine(ctx *sql.Context) error {
roles, userRole, grantedByID, err := r.common(ctx)
if err != nil {
return err
}
for _, role := range roles {
for _, routine := range r.RevokeRoutine.Routines {
schemaName, err := core.GetSchemaName(ctx, nil, routine.Schema)
if err != nil {
return err
}
key := auth.RoutinePrivilegeKey{
Role: userRole.ID(),
Schema: schemaName,
Name: routine.Name,
ArgTypes: routine.ArgTypes,
}
for _, privilege := range r.RevokeRoutine.Privileges {
if id := auth.HasRoutinePrivilegeGrantOption(key, privilege); !id.IsValid() {
// TODO: grab the actual error message
return errors.Errorf(`role "%s" does not have permission to revoke this privilege`, userRole.Name)
}
auth.RemoveRoutinePrivilege(auth.RoutinePrivilegeKey{
Role: role.ID(),
Schema: schemaName,
Name: routine.Name,
ArgTypes: routine.ArgTypes,
}, auth.GrantedPrivilege{
Privilege: privilege,
GrantedBy: grantedByID,
}, r.GrantOptionFor)
}
}
}
return nil
}
// revokeRole handles *RevokeRole from within RowIter.
func (r *Revoke) revokeRole(ctx *sql.Context) error {
members, userRole, _, err := r.common(ctx)
if err != nil {
return err
}
groups := make([]auth.Role, len(r.RevokeRole.Groups))
for i, groupName := range r.RevokeRole.Groups {
groups[i] = auth.GetRole(groupName)
if !groups[i].IsValid() {
return errors.Errorf(`role "%s" does not exist`, groupName)
}
}
for _, member := range members {
for _, group := range groups {
memberGroupID, _, withAdminOption := auth.IsRoleAMember(userRole.ID(), group.ID())
if !memberGroupID.IsValid() || !withAdminOption {
// TODO: grab the actual error message
return errors.Errorf(`role "%s" does not have permission to revoke role "%s"`, userRole.Name, group.Name)
}
auth.RemoveMemberFromGroup(member.ID(), group.ID(), r.GrantOptionFor)
}
}
return nil
}
+125
View File
@@ -0,0 +1,125 @@
// 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 node
import (
"context"
"github.com/cockroachdb/errors"
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/doltgresql/core"
"github.com/dolthub/doltgresql/server/types"
)
// ShowSchemas is a node that implements the SHOW SCHEMAS statement.
type ShowSchemas struct {
// TODO: we need planbuilder integration to support SHOW SCHEMAS, rather than getting everything at runtime
database string
}
var _ sql.ExecSourceRel = (*ShowSchemas)(nil)
// NewShowSchemas returns a new *ShowSchemas.
func NewShowSchemas(database string) *ShowSchemas {
return &ShowSchemas{
database: database,
}
}
// Children implements the interface sql.ExecSourceRel.
func (s *ShowSchemas) Children() []sql.Node {
return []sql.Node{}
}
// IsReadOnly implements the interface sql.ExecSourceRel.
func (s *ShowSchemas) IsReadOnly() bool {
return true
}
// Resolved implements the interface sql.ExecSourceRel.
func (s *ShowSchemas) Resolved() bool {
return true
}
// RowIter implements the interface sql.ExecSourceRel.
func (s *ShowSchemas) RowIter(ctx *sql.Context, r sql.Row) (sql.RowIter, error) {
database := s.database
if database == "" {
database = ctx.GetCurrentDatabase()
if database == "" {
return nil, errors.New("no database selected (this is a bug)")
}
}
db, err := core.GetSqlDatabaseFromContext(ctx, database)
if err != nil {
return nil, err
}
if db == nil {
return nil, errors.New("database not found: " + database)
}
sdb, ok := db.(sql.SchemaDatabase)
if !ok {
// This handles any database that doesn't support schemas (such as some system databases)
// TODO: mirror the postgres behavior of returning, every database should have schemas
return sql.RowsToRowIter(), nil
}
schemas, err := sdb.AllSchemas(ctx)
if err != nil {
return nil, err
}
rows := make([]sql.Row, len(schemas))
for i, schema := range schemas {
rows[i] = sql.Row{schema.SchemaName()}
}
return sql.RowsToRowIter(rows...), nil
}
// Schema implements the interface sql.ExecSourceRel.
func (s *ShowSchemas) Schema(ctx *sql.Context) sql.Schema {
return sql.Schema{
{Name: "schema_name", Type: types.Text, Source: "show schemas"},
}
}
// String implements the interface sql.ExecSourceRel.
func (s *ShowSchemas) String() string {
if s.database == "" {
return "SHOW SCHEMAS FROM " + s.database
}
return "SHOW SCHEMAS"
}
// WithChildren implements the interface sql.ExecSourceRel.
func (s *ShowSchemas) WithChildren(ctx *sql.Context, children ...sql.Node) (sql.Node, error) {
if len(children) != 0 {
return nil, errors.New("SHOW SCHEMAS does not support children")
}
return s, nil
}
// WithResolvedChildren implements the interface vitess.InjectedStatement.
func (s *ShowSchemas) WithResolvedChildren(ctx context.Context, children []any) (any, error) {
if len(children) != 0 {
return nil, errors.New("SHOW SCHEMAS does not support children")
}
return s, nil
}
+116
View File
@@ -0,0 +1,116 @@
// 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 node
import (
"context"
"github.com/cockroachdb/errors"
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/doltgresql/core"
"github.com/dolthub/doltgresql/core/sequences"
"github.com/dolthub/doltgresql/server/types"
)
// ShowSequences is a node that implements the SHOW SCHEMAS statement.
type ShowSequences struct {
// TODO: we need planbuilder integration to support SHOW SCHEMAS, rather than getting everything at runtime
database string
}
var _ sql.ExecSourceRel = (*ShowSequences)(nil)
// NewShowSequences returns a new ShowSequences.
func NewShowSequences(database string) *ShowSequences {
return &ShowSequences{
database: database,
}
}
// Children implements the interface sql.ExecSourceRel.
func (s *ShowSequences) Children() []sql.Node {
return []sql.Node{}
}
// IsReadOnly implements the interface sql.ExecSourceRel.
func (s *ShowSequences) IsReadOnly() bool {
return true
}
// Resolved implements the interface sql.ExecSourceRel.
func (s *ShowSequences) Resolved() bool {
return true
}
// RowIter implements the interface sql.ExecSourceRel.
func (s *ShowSequences) RowIter(ctx *sql.Context, r sql.Row) (sql.RowIter, error) {
database := s.database
if database == "" {
database = ctx.GetCurrentDatabase()
if database == "" {
return nil, errors.New("no database selected (this is a bug)")
}
}
seqs, err := core.GetSequencesCollectionFromContext(ctx, database)
if err != nil {
return nil, err
}
var rows []sql.Row
err = seqs.IterateSequences(ctx, func(seq *sequences.Sequence) (stop bool, err error) {
name := seq.Name()
rows = append(rows, sql.Row{name.Schema, name.Name})
return false, nil
})
if err != nil {
return nil, err
}
return sql.RowsToRowIter(rows...), nil
}
// Schema implements the interface sql.ExecSourceRel.
func (s *ShowSequences) Schema(ctx *sql.Context) sql.Schema {
return sql.Schema{
{Name: "sequence_schema", Type: types.Text, Source: "show sequences"},
{Name: "sequence_name", Type: types.Text, Source: "show sequences"},
}
}
// String implements the interface sql.ExecSourceRel.
func (s *ShowSequences) String() string {
if s.database == "" {
return "SHOW SEQUENCES FROM " + s.database
}
return "SHOW SEQUENCES"
}
// WithChildren implements the interface sql.ExecSourceRel.
func (s *ShowSequences) WithChildren(ctx *sql.Context, children ...sql.Node) (sql.Node, error) {
if len(children) != 0 {
return nil, errors.New("SHOW SCHEMAS does not support children")
}
return s, nil
}
// WithResolvedChildren implements the interface vitess.InjectedStatement.
func (s *ShowSequences) WithResolvedChildren(ctx context.Context, children []any) (any, error) {
if len(children) != 0 {
return nil, errors.New("SHOW SCHEMAS does not support children")
}
return s, nil
}
+293
View File
@@ -0,0 +1,293 @@
// 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 node
import (
"fmt"
"strings"
"github.com/cockroachdb/errors"
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/go-mysql-server/sql/plan"
"github.com/dolthub/doltgresql/core/triggers"
pgexprs "github.com/dolthub/doltgresql/server/expression"
"github.com/dolthub/doltgresql/server/functions/framework"
"github.com/dolthub/doltgresql/server/plpgsql"
pgtypes "github.com/dolthub/doltgresql/server/types"
)
// TriggerExecutionRowHandling states how to interpret the source row, or how to return the resulting row.
type TriggerExecutionRowHandling uint8
const (
TriggerExecutionRowHandling_None TriggerExecutionRowHandling = iota
TriggerExecutionRowHandling_Old
TriggerExecutionRowHandling_OldNew
TriggerExecutionRowHandling_NewOld
TriggerExecutionRowHandling_New
)
// TriggerExecution handles the execution of a set of triggers on a table.
type TriggerExecution struct {
Timing triggers.TriggerTiming
Triggers []triggers.Trigger
Split TriggerExecutionRowHandling // How the source row should be split
Return TriggerExecutionRowHandling // How the returned rows should be combined
Sch sql.Schema
Source sql.Node
Runner pgexprs.StatementRunner
}
var _ sql.ExecBuilderNode = (*TriggerExecution)(nil)
var _ sql.Expressioner = (*TriggerExecution)(nil)
func (te *TriggerExecution) Children() []sql.Node {
return []sql.Node{te.Source}
}
// Expressions implements the interface sql.Expressioner.
func (te *TriggerExecution) Expressions() []sql.Expression {
return []sql.Expression{te.Runner}
}
// IsReadOnly implements the interface sql.ExecBuilderNode.
func (te *TriggerExecution) IsReadOnly() bool {
return te.Source.IsReadOnly()
}
// Resolved implements the interface sql.ExecBuilderNode.
func (te *TriggerExecution) Resolved() bool {
return te.Source.Resolved()
}
// BuildRowIter implements the interface sql.ExecBuilderNode.
func (te *TriggerExecution) BuildRowIter(ctx *sql.Context, b sql.NodeExecBuilder, r sql.Row) (sql.RowIter, error) {
sourceIter, err := b.Build(ctx, te.Source, r)
if err != nil {
return nil, err
}
// If there are no triggers, then we'll just return the source iter
if len(te.Triggers) == 0 {
return sourceIter, nil
}
trigFuncs := make([]framework.InterpretedFunction, len(te.Triggers))
whens := make([]framework.InterpretedFunction, len(te.Triggers))
for i, trig := range te.Triggers {
trigFuncs[i], err = te.loadTriggerFunction(ctx, trig)
if err != nil {
return nil, err
}
// If we have a WHEN expression, then we need to build a "function" to execute the expression
if len(trig.When) > 0 {
whens[i] = framework.InterpretedFunction{
ID: trigFuncs[i].ID, // Assign the same ID just so we have a valid one for later
ReturnType: pgtypes.Bool,
Statements: trig.When,
}
}
}
tgOp := ""
switch te.Source.(type) {
case *plan.InsertInto:
tgOp = "INSERT"
case *plan.Update:
tgOp = "UPDATE"
case *plan.DeleteFrom:
tgOp = "DELETE"
case *plan.Truncate:
tgOp = "TRUNCATE"
}
return &triggerExecutionIter{
functions: trigFuncs,
whens: whens,
split: te.Split,
treturn: te.Return,
runner: te.Runner.Runner,
sch: te.Sch,
source: sourceIter,
tgOp: tgOp,
timing: te.Timing,
}, nil
}
// Schema implements the interface sql.ExecBuilderNode.
func (te *TriggerExecution) Schema(ctx *sql.Context) sql.Schema {
return te.Source.Schema(ctx)
}
// String implements the interface sql.ExecBuilderNode.
func (te *TriggerExecution) String() string {
return "TRIGGER EXECUTION"
}
// WithChildren implements the interface sql.ExecBuilderNode.
func (te *TriggerExecution) WithChildren(ctx *sql.Context, children ...sql.Node) (sql.Node, error) {
if len(children) != 1 {
return nil, sql.ErrInvalidChildrenNumber.New(te, len(children), 1)
}
newTe := *te
newTe.Source = children[0]
return &newTe, nil
}
// WithExpressions implements the interface sql.Expressioner.
func (te *TriggerExecution) WithExpressions(ctx *sql.Context, expressions ...sql.Expression) (sql.Node, error) {
if len(expressions) != 1 {
return nil, sql.ErrInvalidChildrenNumber.New(te, len(expressions), 1)
}
newTe := *te
newTe.Runner = expressions[0].(pgexprs.StatementRunner)
return &newTe, nil
}
// loadTriggerFunction loads the given trigger's framework.InterpretedFunction.
func (te *TriggerExecution) loadTriggerFunction(ctx *sql.Context, trigger triggers.Trigger) (framework.InterpretedFunction, error) {
function, err := loadFunction(ctx, nil, trigger.Function)
if err != nil {
return framework.InterpretedFunction{}, err
}
if !function.ID.IsValid() {
return framework.InterpretedFunction{}, errors.Errorf("function %s() does not exist", trigger.Function.FunctionName())
}
if function.ReturnType != pgtypes.Trigger.ID {
return framework.InterpretedFunction{}, errors.Errorf(`function %s must return type trigger`, function.ID.FunctionName())
}
return framework.InterpretedFunction{
ID: function.ID,
ReturnType: pgtypes.Trigger,
ParameterNames: nil,
ParameterTypes: nil,
Variadic: function.Variadic,
IsNonDeterministic: function.IsNonDeterministic,
Strict: function.Strict,
SRF: false,
Statements: function.Operations,
}, nil
}
// triggerExecutionIter is the iterator for TriggerExecution.
type triggerExecutionIter struct {
functions []framework.InterpretedFunction
whens []framework.InterpretedFunction
split TriggerExecutionRowHandling
treturn TriggerExecutionRowHandling
runner sql.StatementRunner
sch sql.Schema
source sql.RowIter
tgOp string
timing triggers.TriggerTiming
}
var _ sql.RowIter = (*triggerExecutionIter)(nil)
// Next implements the interface sql.RowIter.
func (t *triggerExecutionIter) Next(ctx *sql.Context) (sql.Row, error) {
nextRow, err := t.source.Next(ctx)
if err != nil {
return nextRow, err
}
var oldRow sql.Row
var newRow sql.Row
switch t.split {
case TriggerExecutionRowHandling_Old:
oldRow = nextRow
case TriggerExecutionRowHandling_OldNew:
oldRow = nextRow[:len(t.sch)]
newRow = nextRow[len(t.sch):]
case TriggerExecutionRowHandling_NewOld:
newRow = nextRow[:len(t.sch)]
oldRow = nextRow[len(t.sch):]
case TriggerExecutionRowHandling_New:
newRow = nextRow
}
// TODO: handle other special variables
triggerVars := make(map[string]any)
if t.tgOp != "" {
triggerVars["TG_OP"] = t.tgOp
}
for funcIdx, function := range t.functions {
if t.whens[funcIdx].ID.IsValid() {
whenValue, err := plpgsql.TriggerCall(ctx, t.whens[funcIdx], t.runner, t.sch, oldRow, newRow, triggerVars)
if err != nil {
if strings.Contains(err.Error(), "no valid cast for return value") {
// TODO: this error should technically be caught during parsing, but interpreted functions don't
// have the ability to determine types during parsing yet (also applies to the same error below)
return nil, fmt.Errorf("argument of WHEN must be type boolean")
}
return nil, err
}
whenBool, ok := whenValue.(bool)
if !ok {
return nil, fmt.Errorf("argument of WHEN must be type boolean")
}
if !whenBool {
continue
}
}
returnedValue, err := plpgsql.TriggerCall(ctx, function, t.runner, t.sch, oldRow, newRow, triggerVars)
if err != nil {
return nil, err
}
if returnedValue == nil {
// a returned value of NULL on a BEFORE trigger means to not modify the row, so we return a signal error
if t.timing == triggers.TriggerTiming_Before {
return nil, sql.ErrRowEditCanceled.New()
} else {
return nextRow, nil
}
}
var ok bool
returnedRow, ok := returnedValue.(sql.Row)
if !ok {
return nil, fmt.Errorf("invalid trigger return value")
}
switch t.split {
case TriggerExecutionRowHandling_Old:
oldRow = returnedRow
case TriggerExecutionRowHandling_OldNew, TriggerExecutionRowHandling_NewOld, TriggerExecutionRowHandling_New:
newRow = returnedRow
}
}
switch t.treturn {
case TriggerExecutionRowHandling_Old:
return oldRow, nil
case TriggerExecutionRowHandling_OldNew:
retRow := make(sql.Row, len(nextRow))
copy(retRow, oldRow)
copy(retRow[len(oldRow):], newRow)
return retRow, nil
case TriggerExecutionRowHandling_NewOld:
retRow := make(sql.Row, len(nextRow))
copy(retRow, newRow)
copy(retRow[len(newRow):], oldRow)
return retRow, nil
case TriggerExecutionRowHandling_New:
return newRow, nil
default:
return nextRow, nil
}
}
// Close implements the interface sql.RowIter.
func (t *triggerExecutionIter) Close(ctx *sql.Context) error {
return t.source.Close(ctx)
}
+241
View File
@@ -0,0 +1,241 @@
// 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 node
import (
"context"
"io"
"strings"
"github.com/cockroachdb/errors"
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/expranalysis"
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/go-mysql-server/sql/plan"
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
"github.com/dolthub/doltgresql/core"
)
// ValidateConstraint handles the ALTER TABLE ... VALIDATE CONSTRAINT statement.
type ValidateConstraint struct {
DbProvider sql.DatabaseProvider
schemaName string
tableName string
constraintName string
}
var _ sql.ExecSourceRel = (*ValidateConstraint)(nil)
var _ sql.MultiDatabaser = (*ValidateConstraint)(nil)
var _ vitess.Injectable = (*ValidateConstraint)(nil)
// NewValidateConstraint returns a new *ValidateConstraint.
func NewValidateConstraint(schemaName, tableName, constraintName string) *ValidateConstraint {
return &ValidateConstraint{
schemaName: schemaName,
tableName: tableName,
constraintName: constraintName,
}
}
// Children implements sql.ExecSourceRel.
func (v *ValidateConstraint) Children() []sql.Node { return nil }
// IsReadOnly implements sql.ExecSourceRel.
func (v *ValidateConstraint) IsReadOnly() bool { return false }
// Resolved implements sql.ExecSourceRel.
func (v *ValidateConstraint) Resolved() bool {
return v.DbProvider != nil
}
// Schema implements sql.ExecSourceRel.
func (v *ValidateConstraint) Schema(ctx *sql.Context) sql.Schema { return nil }
// String implements sql.ExecSourceRel.
func (v *ValidateConstraint) String() string { return "VALIDATE CONSTRAINT" }
// WithChildren implements sql.ExecSourceRel.
func (v *ValidateConstraint) WithChildren(ctx *sql.Context, children ...sql.Node) (sql.Node, error) {
return plan.NillaryWithChildren(v, children...)
}
// WithResolvedChildren implements vitess.Injectable.
func (v *ValidateConstraint) WithResolvedChildren(ctx context.Context, children []any) (any, error) {
if len(children) != 0 {
return nil, ErrVitessChildCount.New(0, len(children))
}
return v, nil
}
// DatabaseProvider implements sql.MultiDatabaser.
func (v *ValidateConstraint) DatabaseProvider() sql.DatabaseProvider { return v.DbProvider }
// WithDatabaseProvider implements sql.MultiDatabaser.
func (v *ValidateConstraint) WithDatabaseProvider(provider sql.DatabaseProvider) (sql.Node, error) {
nv := *v
nv.DbProvider = provider
return &nv, nil
}
// RowIter implements sql.ExecSourceRel.
func (v *ValidateConstraint) RowIter(ctx *sql.Context, r sql.Row) (sql.RowIter, error) {
db, err := v.DbProvider.Database(ctx, ctx.GetCurrentDatabase())
if err != nil {
return nil, err
}
schemaName := v.schemaName
if schemaName == "" {
schemaName, err = core.GetCurrentSchema(ctx)
if err != nil {
return nil, err
}
}
schemaDb, ok := db.(sql.SchemaDatabase)
if !ok {
return nil, errors.Errorf("database does not support schemas")
}
dbSchema, ok, err := schemaDb.GetSchema(ctx, schemaName)
if err != nil {
return nil, err
}
if !ok {
return nil, errors.Errorf("schema %s does not exist", schemaName)
}
tblNode, _, err := dbSchema.GetTableInsensitive(ctx, v.tableName)
if err != nil {
return nil, err
}
if tblNode == nil {
return nil, sql.ErrTableNotFound.New(v.tableName)
}
// validate foreign key constraint
if fkTbl, ok := tblNode.(sql.ForeignKeyTable); ok {
fks, err := fkTbl.GetDeclaredForeignKeys(ctx)
if err != nil {
return nil, err
}
for _, fk := range fks {
if strings.EqualFold(fk.Name, v.constraintName) {
return v.validateForeignKey(ctx, db, fkTbl, fk)
}
}
}
// validate check constraint
if checkTbl, ok := tblNode.(sql.CheckTable); ok {
checks, err := checkTbl.GetChecks(ctx)
if err != nil {
return nil, err
}
for _, check := range checks {
if strings.EqualFold(check.Name, v.constraintName) {
return v.validateCheckConstraint(ctx, tblNode, check)
}
}
}
return nil, errors.Errorf(`constraint "%s" of relation "%s" does not exist`, v.constraintName, v.tableName)
}
// validateCheckConstraint validates check constraints if on its |IsNotValid| field is TRUE.
func (v *ValidateConstraint) validateCheckConstraint(ctx *sql.Context, tblNode sql.Table, check sql.CheckDefinition) (sql.RowIter, error) {
if !check.IsNotValid {
return sql.RowsToRowIter(), nil
}
checkExpr, err := expranalysis.ResolveExpression(ctx, v.tableName, check.CheckExpression)
if err != nil {
return nil, errors.Errorf("could not parse check expression for constraint %q: %v", check.Name, err)
}
partitions, err := tblNode.Partitions(ctx)
if err != nil {
return nil, err
}
defer partitions.Close(ctx)
for {
partition, err := partitions.Next(ctx)
if err == io.EOF {
break
}
if err != nil {
return nil, err
}
rows, err := tblNode.PartitionRows(ctx, partition)
if err != nil {
return nil, err
}
defer rows.Close(ctx)
for {
row, err := rows.Next(ctx)
if err == io.EOF {
break
}
if err != nil {
return nil, err
}
res, err := sql.EvaluateCondition(ctx, checkExpr, row)
if err != nil {
return nil, err
}
if sql.IsFalse(res) {
return nil, sql.ErrCheckConstraintViolated.New(check.Name)
}
}
}
// TODO: clear the IsNotValid flag so pg_constraint reports convalidated = true
return sql.RowsToRowIter(), nil
}
// validateForeignKey validates foreign key constraints if on its |IsNotValid| field is TRUE.
func (v *ValidateConstraint) validateForeignKey(ctx *sql.Context, db sql.Database, fkTbl sql.ForeignKeyTable, fkDef sql.ForeignKeyConstraint) (sql.RowIter, error) {
if !fkDef.IsNotValid {
return sql.RowsToRowIter(), nil
}
refTblNode, _, err := db.GetTableInsensitive(ctx, fkDef.ParentTable)
if err != nil {
return nil, err
}
if refTblNode == nil {
return nil, sql.ErrTableNotFound.New(fkDef.ParentTable)
}
refFkTbl, ok := refTblNode.(sql.ForeignKeyTable)
if !ok {
return nil, errors.Errorf("table %s does not support foreign key constraints", fkDef.ParentTable)
}
// this is to allow calling plan.ResolveForeignKey function to validate
fkDef.IsResolved = false
if err = plan.ResolveForeignKey(ctx, fkTbl, refFkTbl, fkDef, false, true, true); err != nil {
// TODO: fix - currently error message includes "cannot add or update a child row"
return nil, err
}
// undo - it's not used but safe to set it
fkDef.IsResolved = true
// clear the IsNotValid flag so pg_constraint reports convalidated = true
fkDef.IsNotValid = false
if err = fkTbl.UpdateForeignKey(ctx, fkDef.Name, fkDef); err != nil {
return nil, err
}
return sql.RowsToRowIter(), nil
}