chore: import upstream snapshot with attribution
This commit is contained in:
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
Executable
+73
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
Executable
+87
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user