chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
// 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 analyzer
|
||||
|
||||
import (
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
"github.com/dolthub/go-mysql-server/sql/analyzer"
|
||||
"github.com/dolthub/go-mysql-server/sql/plan"
|
||||
"github.com/dolthub/go-mysql-server/sql/transform"
|
||||
)
|
||||
|
||||
// applyTablesForAnalyzeAllTables finds plan.AnalyzeTable nodes that don't have any tables explicitly specified and fills in all
|
||||
// tables for the current database. This enables the ANALYZE; statement to analyze all tables.
|
||||
func applyTablesForAnalyzeAllTables(ctx *sql.Context, a *analyzer.Analyzer, node sql.Node, scope *plan.Scope, selector analyzer.RuleSelector, qFlags *sql.QueryFlags) (sql.Node, transform.TreeIdentity, error) {
|
||||
analyzeTable, ok := node.(*plan.AnalyzeTable)
|
||||
if !ok {
|
||||
return node, transform.SameTree, nil
|
||||
}
|
||||
|
||||
// If a set of tables is already populated, we don't need to do anything. We only fill in all tables when
|
||||
// the caller didn't explicitly specify any tables to be analyzed.
|
||||
if len(analyzeTable.Tables) > 0 {
|
||||
return node, transform.SameTree, nil
|
||||
}
|
||||
|
||||
db, err := a.Catalog.Database(ctx, ctx.GetCurrentDatabase())
|
||||
if err != nil {
|
||||
return node, transform.SameTree, err
|
||||
}
|
||||
tableNames, err := db.GetTableNames(ctx)
|
||||
if err != nil {
|
||||
return node, transform.SameTree, err
|
||||
}
|
||||
|
||||
var tables []sql.Table
|
||||
for _, tableName := range tableNames {
|
||||
table, ok, err := db.GetTableInsensitive(ctx, tableName)
|
||||
if err != nil {
|
||||
return node, transform.SameTree, err
|
||||
} else if !ok {
|
||||
return node, transform.SameTree, sql.ErrTableNotFound.New(tableName)
|
||||
}
|
||||
tables = append(tables, table)
|
||||
}
|
||||
|
||||
return analyzeTable.WithTables(tables), transform.NewTree, nil
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
// Copyright 2024 Dolthub, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
"github.com/dolthub/go-mysql-server/sql/analyzer"
|
||||
"github.com/dolthub/go-mysql-server/sql/expression"
|
||||
"github.com/dolthub/go-mysql-server/sql/plan"
|
||||
"github.com/dolthub/go-mysql-server/sql/transform"
|
||||
"github.com/dolthub/go-mysql-server/sql/types"
|
||||
|
||||
pgexprs "github.com/dolthub/doltgresql/server/expression"
|
||||
"github.com/dolthub/doltgresql/server/node"
|
||||
pgtypes "github.com/dolthub/doltgresql/server/types"
|
||||
)
|
||||
|
||||
// AssignInsertCasts adds the appropriate assign casts for insertions.
|
||||
func AssignInsertCasts(ctx *sql.Context, a *analyzer.Analyzer, node sql.Node, scope *plan.Scope, selector analyzer.RuleSelector, qFlags *sql.QueryFlags) (sql.Node, transform.TreeIdentity, error) {
|
||||
insertInto, ok := node.(*plan.InsertInto)
|
||||
if !ok {
|
||||
return node, transform.SameTree, nil
|
||||
}
|
||||
|
||||
// We have some sources that are already postgres native, so skip them
|
||||
if isDoltgresNativeSource(ctx, insertInto.Destination, insertInto.Source) {
|
||||
return insertInto, transform.SameTree, nil
|
||||
}
|
||||
|
||||
// First we'll make a map for each column, so that it's easier to match a name to a type. We also ensure that the
|
||||
// types use Doltgres types, as casts rely on them. At this point, we shouldn't have any GMS types floating around
|
||||
// anymore, so no need to include a lot of additional code to handle them.
|
||||
destinationNameToType := make(map[string]*pgtypes.DoltgresType)
|
||||
for _, col := range insertInto.Destination.Schema(ctx) {
|
||||
colType, ok := col.Type.(*pgtypes.DoltgresType)
|
||||
if !ok {
|
||||
// Only non-Doltgres destination tables will have GMS types (such as system tables), so we don't error here
|
||||
colType = pgtypes.FromGmsType(col.Type)
|
||||
}
|
||||
destinationNameToType[strings.ToLower(col.Name)] = colType
|
||||
}
|
||||
|
||||
// Create the destination type slice that will match each inserted column
|
||||
destinationTypes := make([]*pgtypes.DoltgresType, len(insertInto.ColumnNames))
|
||||
for i, colName := range insertInto.ColumnNames {
|
||||
destinationTypes[i], ok = destinationNameToType[strings.ToLower(colName)]
|
||||
if !ok {
|
||||
return nil, transform.NewTree, errors.Errorf("INSERT: cannot find destination column with name `%s`", colName)
|
||||
}
|
||||
}
|
||||
|
||||
// Replace expressions with casts as needed
|
||||
if values, ok := insertInto.Source.(*plan.Values); ok {
|
||||
// Values do not return the correct Schema since each row may contain different types, so we must handle it differently
|
||||
newValues := make([][]sql.Expression, len(values.ExpressionTuples))
|
||||
for rowIndex, rowExprs := range values.ExpressionTuples {
|
||||
newValues[rowIndex] = make([]sql.Expression, len(rowExprs))
|
||||
for columnIndex, colExpr := range rowExprs {
|
||||
// Null ColumnDefaultValues or empty DefaultValues are not properly typed in TypeSanitizer, so we must handle them here
|
||||
colExprType := colExpr.Type(ctx)
|
||||
if colExprType == nil || colExprType == types.Null {
|
||||
colExprType = pgtypes.Unknown
|
||||
}
|
||||
fromColType, ok := colExprType.(*pgtypes.DoltgresType)
|
||||
if !ok {
|
||||
return nil, transform.NewTree, errors.Errorf("INSERT: non-Doltgres type found in values source: %s", fromColType.String())
|
||||
}
|
||||
toColType := destinationTypes[columnIndex]
|
||||
// We only assign the existing expression if the types perfectly match (same parameters), otherwise we'll cast
|
||||
if fromColType.Equals(toColType) {
|
||||
newValues[rowIndex][columnIndex] = colExpr
|
||||
} else {
|
||||
newValues[rowIndex][columnIndex] = pgexprs.NewAssignmentCast(colExpr, fromColType, toColType)
|
||||
}
|
||||
}
|
||||
}
|
||||
insertInto = insertInto.WithSource(plan.NewValues(newValues))
|
||||
} else {
|
||||
sourceSchema := insertInto.Source.Schema(ctx)
|
||||
projections := make([]sql.Expression, len(sourceSchema))
|
||||
for i, col := range sourceSchema {
|
||||
fromColType, ok := col.Type.(*pgtypes.DoltgresType)
|
||||
if !ok {
|
||||
return nil, transform.NewTree, errors.Errorf("INSERT: non-Doltgres type found in source: %s", fromColType.String())
|
||||
}
|
||||
toColType := destinationTypes[i]
|
||||
getField := expression.NewGetField(i, fromColType, col.Name, true)
|
||||
// We only assign the GetField if the types perfectly match (same parameters), otherwise we'll cast
|
||||
if fromColType.Equals(toColType) {
|
||||
projections[i] = getField
|
||||
} else {
|
||||
projections[i] = pgexprs.NewAssignmentCast(getField, fromColType, toColType)
|
||||
}
|
||||
}
|
||||
insertInto = insertInto.WithSource(plan.NewProject(ctx, projections, insertInto.Source))
|
||||
}
|
||||
|
||||
// handle on conflict clause if present
|
||||
if insertInto.OnDupExprs.HasUpdates() {
|
||||
newDupExprs, err := assignUpdateFieldCasts(ctx, insertInto.OnDupExprs.AllExpressions())
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
// TODO: this relies on a particular implementation detail InsertInto.WithExpressions
|
||||
newInsertInto, err := insertInto.WithExpressions(ctx, append(newDupExprs, insertInto.Checks().ToExpressions()...)...)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
insertInto = newInsertInto.(*plan.InsertInto)
|
||||
}
|
||||
|
||||
return insertInto, transform.NewTree, nil
|
||||
}
|
||||
|
||||
func isDoltgresNativeSource(ctx *sql.Context, dest sql.Node, source sql.Node) bool {
|
||||
// we still need this transformation if our destination has generated columns
|
||||
for _, col := range dest.Schema(ctx) {
|
||||
if col.Generated != nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
switch source.(type) {
|
||||
case *node.CopyFrom:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
// 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 analyzer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
"github.com/dolthub/go-mysql-server/sql/analyzer"
|
||||
"github.com/dolthub/go-mysql-server/sql/plan"
|
||||
"github.com/dolthub/go-mysql-server/sql/transform"
|
||||
|
||||
"github.com/dolthub/doltgresql/core"
|
||||
"github.com/dolthub/doltgresql/core/id"
|
||||
"github.com/dolthub/doltgresql/core/triggers"
|
||||
pgexprs "github.com/dolthub/doltgresql/server/expression"
|
||||
pgnodes "github.com/dolthub/doltgresql/server/node"
|
||||
pgtransform "github.com/dolthub/doltgresql/server/transform"
|
||||
)
|
||||
|
||||
// AssignTriggers assigns triggers wherever they're needed.
|
||||
func AssignTriggers(ctx *sql.Context, a *analyzer.Analyzer, node sql.Node, scope *plan.Scope, selector analyzer.RuleSelector, qFlags *sql.QueryFlags) (sql.Node, transform.TreeIdentity, error) {
|
||||
return pgtransform.NodeWithOpaque(ctx, node, func(ctx *sql.Context, node sql.Node) (sql.Node, transform.TreeIdentity, error) {
|
||||
switch node := node.(type) {
|
||||
case *plan.DeleteFrom, *plan.InsertInto, *plan.Truncate, *plan.Update:
|
||||
sch, beforeTrigs, afterTrigs, err := getTriggerInformation(ctx, node)
|
||||
if err != nil {
|
||||
return nil, transform.NewTree, err
|
||||
}
|
||||
if len(beforeTrigs) == 0 && len(afterTrigs) == 0 {
|
||||
return node, transform.SameTree, nil
|
||||
}
|
||||
newNode := node
|
||||
if len(beforeTrigs) > 0 {
|
||||
handling := getTriggerRowHandling(node)
|
||||
newNode, err = nodeWithTriggers(ctx, newNode, &pgnodes.TriggerExecution{
|
||||
Timing: triggers.TriggerTiming_Before,
|
||||
Triggers: beforeTrigs,
|
||||
Split: handling,
|
||||
Return: handling,
|
||||
Sch: sch,
|
||||
Source: getTriggerSource(node),
|
||||
Runner: pgexprs.StatementRunner{Runner: a.Runner},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, transform.NewTree, err
|
||||
}
|
||||
}
|
||||
if len(afterTrigs) > 0 {
|
||||
newNode = &pgnodes.TriggerExecution{
|
||||
Timing: triggers.TriggerTiming_After,
|
||||
Triggers: afterTrigs,
|
||||
Split: getTriggerRowHandling(node),
|
||||
Return: pgnodes.TriggerExecutionRowHandling_None,
|
||||
Sch: sch,
|
||||
Source: newNode,
|
||||
Runner: pgexprs.StatementRunner{Runner: a.Runner},
|
||||
}
|
||||
}
|
||||
return newNode, transform.NewTree, nil
|
||||
default:
|
||||
return node, transform.SameTree, nil
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// getTriggerInformation loads information that is common for the different trigger types.
|
||||
func getTriggerInformation(ctx *sql.Context, node sql.Node) (sch sql.Schema, beforeTrigs []triggers.Trigger, afterTrigs []triggers.Trigger, err error) {
|
||||
var tbl sql.Table
|
||||
switch node := node.(type) {
|
||||
case *plan.DeleteFrom:
|
||||
tbl, err = plan.GetDeletable(node.Child)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
case *plan.InsertInto:
|
||||
tbl, err = plan.GetInsertable(node.Destination)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
case *plan.Truncate:
|
||||
tbl, err = plan.GetTruncatable(node.Child)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
case *plan.Update:
|
||||
// TODO: If there is a JoinNode in here, then don't bother calling GetUpdatable, because
|
||||
// it doesn't currently return a type that can be used to query trigger information.
|
||||
// We need to rework the plan.GetUpdatable() API to support returning multiple
|
||||
// update targets and to return types that are compatible with the interfaces
|
||||
// Doltgres needs in order to populate trigger information.
|
||||
if hasJoinNode(node) {
|
||||
return nil, nil, nil, nil
|
||||
}
|
||||
|
||||
tbl, err = plan.GetUpdatable(node.Child)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
default:
|
||||
return nil, nil, nil, nil
|
||||
}
|
||||
|
||||
dbName := ctx.GetCurrentDatabase()
|
||||
// TODO: some dolt tables don't implement this interface, so we use the current db for now
|
||||
// An alternative would be to get the resolved table and use the db there.
|
||||
schTbl, ok := tbl.(sql.DatabaseSchemaTable)
|
||||
if ok {
|
||||
dbName = schTbl.DatabaseSchema().Name()
|
||||
}
|
||||
|
||||
trigCollection, err := core.GetTriggersCollectionFromContext(ctx, dbName)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
tblID, ok, _ := id.GetFromTable(ctx, tbl)
|
||||
if !ok {
|
||||
return nil, nil, nil, nil
|
||||
}
|
||||
allTrigs := trigCollection.GetTriggersForTable(ctx, tblID)
|
||||
// Return early if there are no triggers for the table
|
||||
if len(allTrigs) == 0 {
|
||||
return tbl.Schema(ctx), nil, nil, nil
|
||||
}
|
||||
// Trigger order is determined by the name
|
||||
sort.Slice(allTrigs, func(i, j int) bool {
|
||||
return allTrigs[i].ID.TriggerName() < allTrigs[j].ID.TriggerName()
|
||||
})
|
||||
beforeTrigs = make([]triggers.Trigger, 0, len(allTrigs))
|
||||
afterTrigs = make([]triggers.Trigger, 0, len(allTrigs))
|
||||
for _, trig := range allTrigs {
|
||||
matchesEventType := false
|
||||
for _, event := range trig.Events {
|
||||
switch node.(type) {
|
||||
case *plan.DeleteFrom:
|
||||
if event.Type == triggers.TriggerEventType_Delete {
|
||||
matchesEventType = true
|
||||
}
|
||||
case *plan.InsertInto:
|
||||
if event.Type == triggers.TriggerEventType_Insert {
|
||||
matchesEventType = true
|
||||
}
|
||||
case *plan.Truncate:
|
||||
if event.Type == triggers.TriggerEventType_Truncate {
|
||||
matchesEventType = true
|
||||
}
|
||||
case *plan.Update:
|
||||
if event.Type == triggers.TriggerEventType_Update {
|
||||
matchesEventType = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !matchesEventType {
|
||||
continue
|
||||
}
|
||||
switch trig.Timing {
|
||||
case triggers.TriggerTiming_Before:
|
||||
beforeTrigs = append(beforeTrigs, trig)
|
||||
case triggers.TriggerTiming_After:
|
||||
afterTrigs = append(afterTrigs, trig)
|
||||
default:
|
||||
return nil, nil, nil, fmt.Errorf("trigger timing has not yet been implemented")
|
||||
}
|
||||
}
|
||||
return tbl.Schema(ctx), beforeTrigs, afterTrigs, nil
|
||||
}
|
||||
|
||||
// hasJoinNode returns true if |node| or any child is a JoinNode.
|
||||
func hasJoinNode(node sql.Node) bool {
|
||||
updateJoinFound := false
|
||||
transform.Inspect(node, func(n sql.Node) bool {
|
||||
if _, ok := n.(*plan.JoinNode); ok {
|
||||
updateJoinFound = true
|
||||
}
|
||||
return !updateJoinFound
|
||||
})
|
||||
return updateJoinFound
|
||||
}
|
||||
|
||||
// getTriggerSource returns the trigger's source node.
|
||||
func getTriggerSource(node sql.Node) sql.Node {
|
||||
switch node := node.(type) {
|
||||
case *plan.DeleteFrom:
|
||||
return node.Child
|
||||
case *plan.InsertInto:
|
||||
return node.Source
|
||||
case *plan.Truncate:
|
||||
return node.Child
|
||||
case *plan.Update:
|
||||
return node.Child
|
||||
default:
|
||||
return node
|
||||
}
|
||||
}
|
||||
|
||||
// getTriggerRowHandling returns the trigger's row handling type (based on how GMS passes rows in the intermediate
|
||||
// steps).
|
||||
func getTriggerRowHandling(node sql.Node) pgnodes.TriggerExecutionRowHandling {
|
||||
switch node.(type) {
|
||||
case *plan.DeleteFrom:
|
||||
return pgnodes.TriggerExecutionRowHandling_Old
|
||||
case *plan.InsertInto:
|
||||
return pgnodes.TriggerExecutionRowHandling_New
|
||||
case *plan.Truncate:
|
||||
return pgnodes.TriggerExecutionRowHandling_None
|
||||
case *plan.Update:
|
||||
return pgnodes.TriggerExecutionRowHandling_OldNew
|
||||
default:
|
||||
return pgnodes.TriggerExecutionRowHandling_None
|
||||
}
|
||||
}
|
||||
|
||||
// nodeWithTriggers calls the appropriate WithX function depending on the node type.
|
||||
func nodeWithTriggers(ctx *sql.Context, node sql.Node, executionNode *pgnodes.TriggerExecution) (sql.Node, error) {
|
||||
switch node := node.(type) {
|
||||
case *plan.DeleteFrom:
|
||||
return node.WithChildren(ctx, executionNode)
|
||||
case *plan.InsertInto:
|
||||
return node.WithSource(executionNode), nil
|
||||
case *plan.Truncate:
|
||||
return node.WithChildren(ctx, executionNode)
|
||||
case *plan.Update:
|
||||
return node.WithChildren(ctx, executionNode)
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown node for triggers")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
// 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 analyzer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
"github.com/dolthub/go-mysql-server/sql/analyzer"
|
||||
"github.com/dolthub/go-mysql-server/sql/expression"
|
||||
"github.com/dolthub/go-mysql-server/sql/plan"
|
||||
"github.com/dolthub/go-mysql-server/sql/transform"
|
||||
|
||||
pgexprs "github.com/dolthub/doltgresql/server/expression"
|
||||
pgtypes "github.com/dolthub/doltgresql/server/types"
|
||||
)
|
||||
|
||||
// AssignUpdateCasts adds the appropriate assign casts for updates.
|
||||
func AssignUpdateCasts(ctx *sql.Context, a *analyzer.Analyzer, node sql.Node, scope *plan.Scope, selector analyzer.RuleSelector, qFlags *sql.QueryFlags) (sql.Node, transform.TreeIdentity, error) {
|
||||
update, ok := node.(*plan.Update)
|
||||
if !ok {
|
||||
return node, transform.SameTree, nil
|
||||
}
|
||||
var newUpdate sql.Node
|
||||
switch child := update.Child.(type) {
|
||||
case *plan.UpdateSource:
|
||||
newUpdateSource, err := assignUpdateCastsHandleSource(ctx, child)
|
||||
if err != nil {
|
||||
return nil, transform.NewTree, err
|
||||
}
|
||||
newUpdate, err = update.WithChildren(ctx, newUpdateSource)
|
||||
if err != nil {
|
||||
return nil, transform.NewTree, err
|
||||
}
|
||||
case *plan.ForeignKeyHandler:
|
||||
updateSource, ok := child.OriginalNode.(*plan.UpdateSource)
|
||||
if !ok {
|
||||
return nil, transform.NewTree, errors.Errorf("UPDATE: assumption that Foreign Key child is always UpdateSource is incorrect: %T", child.OriginalNode)
|
||||
}
|
||||
newUpdateSource, err := assignUpdateCastsHandleSource(ctx, updateSource)
|
||||
if err != nil {
|
||||
return nil, transform.NewTree, err
|
||||
}
|
||||
newHandler, err := child.WithChildren(ctx, newUpdateSource)
|
||||
if err != nil {
|
||||
return nil, transform.NewTree, err
|
||||
}
|
||||
newUpdate, err = update.WithChildren(ctx, newHandler)
|
||||
if err != nil {
|
||||
return nil, transform.NewTree, err
|
||||
}
|
||||
case *plan.UpdateJoin:
|
||||
updateSource, ok := child.Child.(*plan.UpdateSource)
|
||||
if !ok {
|
||||
return nil, transform.NewTree, fmt.Errorf("UPDATE: unknown source type: %T", child.Child)
|
||||
}
|
||||
|
||||
newUpdateSource, err := assignUpdateCastsHandleSource(ctx, updateSource)
|
||||
if err != nil {
|
||||
return nil, transform.NewTree, err
|
||||
}
|
||||
newHandler, err := child.WithChildren(ctx, newUpdateSource)
|
||||
if err != nil {
|
||||
return nil, transform.NewTree, err
|
||||
}
|
||||
newUpdate, err = update.WithChildren(ctx, newHandler)
|
||||
if err != nil {
|
||||
return nil, transform.NewTree, err
|
||||
}
|
||||
default:
|
||||
return nil, transform.NewTree, errors.Errorf("UPDATE: unknown source type: %T", child)
|
||||
}
|
||||
return newUpdate, transform.NewTree, nil
|
||||
}
|
||||
|
||||
// assignUpdateCastsHandleSource handles the *plan.UpdateSource portion of AssignUpdateCasts.
|
||||
func assignUpdateCastsHandleSource(ctx *sql.Context, updateSource *plan.UpdateSource) (*plan.UpdateSource, error) {
|
||||
updateExprs := updateSource.UpdateExprs
|
||||
newUpdateExprs, err := assignUpdateFieldCasts(ctx, updateExprs.AllExpressions())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newUpdateSource, err := updateSource.WithExpressions(ctx, newUpdateExprs...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newUpdateSource.(*plan.UpdateSource), nil
|
||||
}
|
||||
|
||||
func assignUpdateFieldCasts(ctx *sql.Context, updateExprs []sql.Expression) ([]sql.Expression, error) {
|
||||
newUpdateExprs := make([]sql.Expression, len(updateExprs))
|
||||
for i, updateExpr := range updateExprs {
|
||||
setField, ok := updateExpr.(*expression.SetField)
|
||||
if !ok {
|
||||
return nil, errors.Errorf("UPDATE: assumption that expression is always SetField is incorrect: %T", updateExpr)
|
||||
}
|
||||
fromType, ok := setField.RightChild.Type(ctx).(*pgtypes.DoltgresType)
|
||||
if !ok {
|
||||
return nil, errors.Errorf("UPDATE: non-Doltgres type found in source: %s", setField.RightChild.String())
|
||||
}
|
||||
toType, ok := setField.LeftChild.Type(ctx).(*pgtypes.DoltgresType)
|
||||
if !ok {
|
||||
// Only non-Doltgres destination tables will have GMS types (such as system tables), so we don't error here
|
||||
toType = pgtypes.FromGmsType(setField.LeftChild.Type(ctx))
|
||||
}
|
||||
// We only assign the existing expression if the types perfectly match (same parameters), otherwise we'll cast
|
||||
if fromType.Equals(toType) {
|
||||
newUpdateExprs[i] = setField
|
||||
} else {
|
||||
newSetField, err := setField.WithChildren(ctx, setField.LeftChild, pgexprs.NewAssignmentCast(setField.RightChild, fromType, toType))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newUpdateExprs[i] = newSetField
|
||||
}
|
||||
}
|
||||
return newUpdateExprs, nil
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
// 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 analyzer
|
||||
|
||||
import (
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
"github.com/dolthub/go-mysql-server/sql/analyzer"
|
||||
"github.com/dolthub/go-mysql-server/sql/plan"
|
||||
"github.com/dolthub/go-mysql-server/sql/transform"
|
||||
)
|
||||
|
||||
// convertDropPrimaryKeyConstraint converts a DropConstraint node dropping a primary key constraint into
|
||||
// an AlterPK node that GMS can process to remove the primary key.
|
||||
func convertDropPrimaryKeyConstraint(ctx *sql.Context, _ *analyzer.Analyzer, n sql.Node, _ *plan.Scope, _ analyzer.RuleSelector, _ *sql.QueryFlags) (sql.Node, transform.TreeIdentity, error) {
|
||||
return transform.Node(ctx, n, func(ctx *sql.Context, n sql.Node) (sql.Node, transform.TreeIdentity, error) {
|
||||
dropConstraint, ok := n.(*plan.DropConstraint)
|
||||
if !ok {
|
||||
return n, transform.SameTree, nil
|
||||
}
|
||||
|
||||
rt, ok := dropConstraint.Child.(*plan.ResolvedTable)
|
||||
if !ok {
|
||||
return nil, transform.SameTree, analyzer.ErrInAnalysis.New(
|
||||
"Expected a TableNode for ALTER TABLE DROP CONSTRAINT statement")
|
||||
}
|
||||
|
||||
table := rt.Table
|
||||
if it, ok := table.(sql.IndexAddressableTable); ok {
|
||||
indexes, err := it.GetIndexes(ctx)
|
||||
if err != nil {
|
||||
return nil, transform.SameTree, err
|
||||
}
|
||||
for _, index := range indexes {
|
||||
if index.ID() == "PRIMARY" && dropConstraint.Name == rt.Name()+"_pkey" {
|
||||
alterDropPk := plan.NewAlterDropPk(rt.Database(), rt)
|
||||
newNode, err := alterDropPk.WithTargetSchema(rt.Schema(ctx))
|
||||
if err != nil {
|
||||
return n, transform.SameTree, err
|
||||
}
|
||||
return newNode, transform.NewTree, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return n, transform.SameTree, nil
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
// 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 analyzer
|
||||
|
||||
import (
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
"github.com/dolthub/go-mysql-server/sql/analyzer"
|
||||
"github.com/dolthub/go-mysql-server/sql/plan"
|
||||
"github.com/dolthub/go-mysql-server/sql/planbuilder"
|
||||
"github.com/dolthub/go-mysql-server/sql/transform"
|
||||
|
||||
"github.com/dolthub/doltgresql/server/node"
|
||||
)
|
||||
|
||||
// ValidateCreateFunction validates that a function can be created as specified. It validates functions defined
|
||||
// with SQL language.
|
||||
func ValidateCreateFunction(ctx *sql.Context, a *analyzer.Analyzer, n sql.Node, scope *plan.Scope, sel analyzer.RuleSelector, qFlags *sql.QueryFlags) (sql.Node, transform.TreeIdentity, error) {
|
||||
ct, ok := n.(*node.CreateFunction)
|
||||
if !ok {
|
||||
return n, transform.SameTree, nil
|
||||
}
|
||||
|
||||
if len(ct.SqlDef) == 0 {
|
||||
return n, transform.SameTree, nil
|
||||
}
|
||||
|
||||
checkFunctionBodiesVar, err := ctx.GetSessionVariable(ctx, "check_function_bodies")
|
||||
if err != nil {
|
||||
return n, transform.SameTree, err
|
||||
}
|
||||
if checkFunctionBodiesVar.(int8) == 0 {
|
||||
return n, transform.SameTree, nil
|
||||
}
|
||||
|
||||
builder := planbuilder.New(ctx, a.Catalog, nil)
|
||||
for _, parsed := range ct.SqlDefParsedStmts {
|
||||
_, _, err = builder.BindOnly(parsed, ct.SqlDef, nil)
|
||||
if err != nil {
|
||||
return nil, transform.SameTree, err
|
||||
}
|
||||
}
|
||||
|
||||
return n, transform.SameTree, nil
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
// 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 analyzer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
"github.com/dolthub/go-mysql-server/sql/analyzer"
|
||||
"github.com/dolthub/go-mysql-server/sql/plan"
|
||||
"github.com/dolthub/go-mysql-server/sql/planbuilder"
|
||||
"github.com/dolthub/go-mysql-server/sql/transform"
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/parser"
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
"github.com/dolthub/doltgresql/server/ast"
|
||||
"github.com/dolthub/doltgresql/server/expression"
|
||||
"github.com/dolthub/doltgresql/server/node"
|
||||
pgtransform "github.com/dolthub/doltgresql/server/transform"
|
||||
pgtypes "github.com/dolthub/doltgresql/server/types"
|
||||
)
|
||||
|
||||
// AddDomainConstraints adds domain type's default value and check constraints
|
||||
// to the destination table schema and InsertNode/Update node's checks.
|
||||
func AddDomainConstraints(ctx *sql.Context, a *analyzer.Analyzer, node sql.Node, scope *plan.Scope, selector analyzer.RuleSelector, qFlags *sql.QueryFlags) (sql.Node, transform.TreeIdentity, error) {
|
||||
switch n := node.(type) {
|
||||
case *plan.InsertInto:
|
||||
return loadDomainConstraints(ctx, a, n, n.Schema(ctx))
|
||||
case *plan.Update:
|
||||
return loadDomainConstraints(ctx, a, n, n.Schema(ctx))
|
||||
default:
|
||||
return node, transform.SameTree, nil
|
||||
}
|
||||
}
|
||||
|
||||
// loadDomainConstraints retrieves and assigns domain type's default value, nullable and check constraints
|
||||
// to the destination table schema and InsertNode/Update node's checks.
|
||||
func loadDomainConstraints(ctx *sql.Context, a *analyzer.Analyzer, c sql.CheckConstraintNode, schema sql.Schema) (sql.Node, transform.TreeIdentity, error) {
|
||||
// get current checks to append the domain checks to.
|
||||
checks := c.Checks()
|
||||
var same = transform.SameTree
|
||||
for _, col := range schema {
|
||||
if dt, ok := col.Type.(*pgtypes.DoltgresType); ok && dt.TypType == pgtypes.TypeType_Domain {
|
||||
// assign column nullable
|
||||
col.Nullable = !dt.NotNull
|
||||
// get domain default value and assign to the column default value
|
||||
defVal, err := getDomainDefault(ctx, a, dt.Default, col.Source, col.Type, col.Nullable)
|
||||
if err != nil {
|
||||
return nil, transform.SameTree, err
|
||||
}
|
||||
col.Default = defVal
|
||||
// get domain checks
|
||||
colChecks, err := getDomainCheckConstraintsForTable(ctx, a, col.Name, col.Source, dt.Checks)
|
||||
if err != nil {
|
||||
return nil, transform.SameTree, err
|
||||
}
|
||||
checks = append(checks, colChecks...)
|
||||
same = transform.NewTree
|
||||
}
|
||||
}
|
||||
if same == transform.SameTree {
|
||||
return c, same, nil
|
||||
}
|
||||
return c.WithChecks(checks), same, nil
|
||||
}
|
||||
|
||||
// getDomainDefault takes the default value definition, parses, builds and returns sql.ColumnDefaultValue.
|
||||
func getDomainDefault(ctx *sql.Context, a *analyzer.Analyzer, defExpr, tblName string, typ sql.Type, nullable bool) (*sql.ColumnDefaultValue, error) {
|
||||
if defExpr == "" {
|
||||
return nil, nil
|
||||
}
|
||||
parsed, err := a.Parser.ParseSimple(fmt.Sprintf("select %s from %s", defExpr, tblName))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
selectStmt, ok := parsed.(*vitess.Select)
|
||||
if !ok || len(selectStmt.SelectExprs) != 1 {
|
||||
return nil, sql.ErrInvalidColumnDefaultValue.New(defExpr)
|
||||
}
|
||||
expr := selectStmt.SelectExprs[0]
|
||||
ae, ok := expr.(*vitess.AliasedExpr)
|
||||
if !ok {
|
||||
return nil, sql.ErrInvalidColumnDefaultValue.New(defExpr)
|
||||
}
|
||||
builder := planbuilder.New(ctx, a.Catalog, nil)
|
||||
return builder.BuildColumnDefaultValueWithTable(ae.Expr, selectStmt.From[0], typ, nullable), nil
|
||||
}
|
||||
|
||||
// getDomainCheckConstraintsForTable takes the check constraint definitions, parses, builds and returns sql.CheckConstraints.
|
||||
func getDomainCheckConstraintsForTable(ctx *sql.Context, a *analyzer.Analyzer, colName string, tblName string, checkDefs []*sql.CheckDefinition) (sql.CheckConstraints, error) {
|
||||
checks := make(sql.CheckConstraints, len(checkDefs))
|
||||
for i, check := range checkDefs {
|
||||
q := fmt.Sprintf("select %s from %s", check.CheckExpression, tblName)
|
||||
checkExpr, err := parseAndReplaceDomainCheckConstraint(ctx, a, check.CheckExpression, q, &tree.ColumnItem{
|
||||
ColumnName: tree.Name(colName),
|
||||
TableName: &tree.UnresolvedObjectName{NumParts: 1, Parts: [3]string{tblName}},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
checks[i] = &sql.CheckConstraint{
|
||||
Name: check.Name,
|
||||
Expr: checkExpr,
|
||||
Enforced: true,
|
||||
}
|
||||
}
|
||||
|
||||
return checks, nil
|
||||
}
|
||||
|
||||
// AddDomainConstraintsToCasts adds domain type's constraints to cast expressions.
|
||||
func AddDomainConstraintsToCasts(ctx *sql.Context, a *analyzer.Analyzer, node sql.Node, scope *plan.Scope, selector analyzer.RuleSelector, qFlags *sql.QueryFlags) (sql.Node, transform.TreeIdentity, error) {
|
||||
return pgtransform.NodeExprsWithOpaque(ctx, node, func(ctx *sql.Context, expr sql.Expression) (sql.Expression, transform.TreeIdentity, error) {
|
||||
var same = transform.SameTree
|
||||
switch e := expr.(type) {
|
||||
case *expression.ExplicitCast:
|
||||
if rt, ok := e.Type(ctx).(*pgtypes.DoltgresType); ok && rt.TypType == pgtypes.TypeType_Domain {
|
||||
// the domain type should be resolved by this point
|
||||
colChecks, err := getDomainCheckConstraintsForCast(ctx, a, rt.Checks, e.Child())
|
||||
if err != nil {
|
||||
return nil, transform.NewTree, err
|
||||
}
|
||||
same = transform.NewTree
|
||||
expr = e.WithDomainConstraints(!rt.NotNull, colChecks)
|
||||
}
|
||||
return expr, same, nil
|
||||
default:
|
||||
// TODO: add ASSIGNMENT, IMPLICIT cast and other expressions that use domain types
|
||||
return e, transform.SameTree, nil
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// getDomainCheckConstraintsForCast takes the check constraint definitions, parses, builds and returns sql.CheckConstraints.
|
||||
func getDomainCheckConstraintsForCast(ctx *sql.Context, a *analyzer.Analyzer, checkDefs []*sql.CheckDefinition, value sql.Expression) (sql.CheckConstraints, error) {
|
||||
checks := make(sql.CheckConstraints, len(checkDefs))
|
||||
for i, check := range checkDefs {
|
||||
q := fmt.Sprintf("select %s", check.CheckExpression)
|
||||
checkExpr, err := parseAndReplaceDomainCheckConstraint(ctx, a, check.CheckExpression, q, tree.DomainColumn{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// replace DomainColumn with given sql.Expression
|
||||
checkExpr, _, _ = transform.Expr(ctx, checkExpr, func(ctx *sql.Context, expr sql.Expression) (sql.Expression, transform.TreeIdentity, error) {
|
||||
switch e := expr.(type) {
|
||||
case *node.DomainColumn:
|
||||
expr = value
|
||||
return expr, transform.NewTree, nil
|
||||
default:
|
||||
return e, transform.SameTree, nil
|
||||
}
|
||||
})
|
||||
checks[i] = &sql.CheckConstraint{
|
||||
Name: check.Name,
|
||||
Expr: checkExpr,
|
||||
Enforced: true,
|
||||
}
|
||||
}
|
||||
return checks, nil
|
||||
}
|
||||
|
||||
// parseAndReplaceDomainCheckConstraint parses check constraint and replaces the `VALUE` column
|
||||
// reference with given Expr including resolved column given table and column names or DomainColumn.
|
||||
// It returns built check expression.
|
||||
func parseAndReplaceDomainCheckConstraint(ctx *sql.Context, a *analyzer.Analyzer, checkExpr, query string, replacesValue tree.Expr) (sql.Expression, error) {
|
||||
stmt, err := parser.ParseOne(query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
selectStmt, ok := stmt.AST.(*tree.Select)
|
||||
if !ok {
|
||||
return nil, sql.ErrInvalidCheckConstraint.New(checkExpr)
|
||||
}
|
||||
selectClause, ok := selectStmt.Select.(*tree.SelectClause)
|
||||
if !ok {
|
||||
return nil, sql.ErrInvalidCheckConstraint.New(checkExpr)
|
||||
}
|
||||
exprs := selectClause.Exprs
|
||||
if len(exprs) != 1 {
|
||||
return nil, sql.ErrInvalidCheckConstraint.New(checkExpr)
|
||||
}
|
||||
|
||||
updatedCheckExpr, err := tree.SimpleVisit(exprs[0].Expr, func(visitingExpr tree.Expr) (recurse bool, newExpr tree.Expr, err error) {
|
||||
switch v := visitingExpr.(type) {
|
||||
case *tree.UnresolvedName:
|
||||
if strings.ToLower(v.String()) != "value" {
|
||||
return false, nil, errors.Errorf(`column "%s" does not exist`, v.String())
|
||||
}
|
||||
return false, replacesValue, nil
|
||||
}
|
||||
return true, visitingExpr, nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
exprs[0].Expr = updatedCheckExpr
|
||||
|
||||
parsed, err := ast.Convert(stmt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
convertedSelectStmt, ok := parsed.(*vitess.Select)
|
||||
if !ok || len(convertedSelectStmt.SelectExprs) != 1 {
|
||||
return nil, sql.ErrInvalidCheckConstraint.New(checkExpr)
|
||||
}
|
||||
expr := convertedSelectStmt.SelectExprs[0]
|
||||
ae, ok := expr.(*vitess.AliasedExpr)
|
||||
if !ok {
|
||||
return nil, sql.ErrInvalidCheckConstraint.New(checkExpr)
|
||||
}
|
||||
|
||||
builder := planbuilder.New(ctx, a.Catalog, nil)
|
||||
var tblExpr vitess.TableExpr
|
||||
if len(convertedSelectStmt.From) == 1 {
|
||||
tblExpr = convertedSelectStmt.From[0]
|
||||
}
|
||||
return builder.BuildScalarWithTable(ae.Expr, tblExpr), nil
|
||||
}
|
||||
Executable
+85
@@ -0,0 +1,85 @@
|
||||
// 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 analyzer
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
"github.com/dolthub/go-mysql-server/sql/expression"
|
||||
|
||||
"github.com/dolthub/doltgresql/core"
|
||||
"github.com/dolthub/doltgresql/core/casts"
|
||||
"github.com/dolthub/doltgresql/server/functions/framework"
|
||||
"github.com/dolthub/doltgresql/server/types"
|
||||
)
|
||||
|
||||
// validateForeignKeyDefinition validates that the given foreign key definition is valid for creation
|
||||
func validateForeignKeyDefinition(ctx *sql.Context, fkDef sql.ForeignKeyConstraint, cols map[string]*sql.Column, parentCols map[string]*sql.Column) error {
|
||||
var castsColl *casts.Collection
|
||||
if len(fkDef.Columns) > 0 {
|
||||
var err error
|
||||
// TODO: which database is this supposed to use?
|
||||
castsColl, err = core.GetCastsCollectionFromContext(ctx, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for i := range fkDef.Columns {
|
||||
col := cols[strings.ToLower(fkDef.Columns[i])]
|
||||
parentCol := parentCols[strings.ToLower(fkDef.ParentColumns[i])]
|
||||
if !foreignKeyComparableTypes(ctx, castsColl, col.Type, parentCol.Type) {
|
||||
return errors.Errorf("Key columns %q and %q are of incompatible types: %s and %s", col.Name, parentCol.Name, col.Type.String(), parentCol.Type.String())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// foreignKeyComparableTypes returns whether the two given types are able to be used as parent/child columns in a
|
||||
// foreign key.
|
||||
func foreignKeyComparableTypes(ctx *sql.Context, castColl *casts.Collection, from sql.Type, to sql.Type) bool {
|
||||
dtFrom, ok := from.(*types.DoltgresType)
|
||||
if !ok {
|
||||
return false // should never be possible
|
||||
}
|
||||
|
||||
dtTo, ok := to.(*types.DoltgresType)
|
||||
if !ok {
|
||||
return false // should never be possible
|
||||
}
|
||||
|
||||
if dtFrom.Equals(dtTo) {
|
||||
return true
|
||||
}
|
||||
|
||||
fromLiteral := expression.NewLiteral(dtFrom.Zero(), from)
|
||||
toLiteral := expression.NewLiteral(dtTo.Zero(), to)
|
||||
|
||||
// a foreign key between two different types is valid if there is an equality operator on the two types
|
||||
// TODO: there are some subtleties in postgres not captured by this logic, e.g. a foreign key from double -> int
|
||||
// is valid, but the reverse is not. This works fine, but is more permissive than postgres is.
|
||||
eq := framework.GetBinaryFunction(framework.Operator_BinaryEqual).Compile(ctx, "=", fromLiteral, toLiteral)
|
||||
if eq == nil || eq.StashedError() != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Additionally, we need to be able to convert freely between the two types in both directions, since we do this
|
||||
// during the process of enforcing the constraints
|
||||
forwardConversion, fErr := castColl.GetAssignmentCast(ctx, dtFrom, dtTo)
|
||||
reverseConversion, rErr := castColl.GetAssignmentCast(ctx, dtTo, dtFrom)
|
||||
|
||||
return fErr == nil && rErr == nil && forwardConversion.ID.IsValid() && reverseConversion.ID.IsValid()
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
// Copyright 2025 Dolthub, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
"github.com/dolthub/go-mysql-server/sql/analyzer"
|
||||
"github.com/dolthub/go-mysql-server/sql/plan"
|
||||
"github.com/dolthub/go-mysql-server/sql/transform"
|
||||
|
||||
"github.com/dolthub/doltgresql/server/functions"
|
||||
)
|
||||
|
||||
// generateForeignKeyName populates a generated foreign key name, in the Postgres default foreign key name format,
|
||||
// when a foreign key is created without an explicit name specified.
|
||||
func generateForeignKeyName(ctx *sql.Context, _ *analyzer.Analyzer, n sql.Node, _ *plan.Scope, _ analyzer.RuleSelector, _ *sql.QueryFlags) (sql.Node, transform.TreeIdentity, error) {
|
||||
return transform.Node(ctx, n, func(ctx *sql.Context, n sql.Node) (sql.Node, transform.TreeIdentity, error) {
|
||||
switch n := n.(type) {
|
||||
case *plan.CreateTable:
|
||||
copiedForeignKeys := make([]*sql.ForeignKeyConstraint, len(n.ForeignKeys()))
|
||||
for i := range n.ForeignKeys() {
|
||||
fk := *n.ForeignKeys()[i]
|
||||
copiedForeignKeys[i] = &fk
|
||||
}
|
||||
|
||||
changedForeignKey := false
|
||||
for _, fk := range copiedForeignKeys {
|
||||
if fk.Name == "" {
|
||||
generatedName, err := generateFkName(ctx, n.Name(), fk)
|
||||
if err != nil {
|
||||
return nil, transform.SameTree, err
|
||||
}
|
||||
changedForeignKey = true
|
||||
fk.Name = generatedName
|
||||
}
|
||||
}
|
||||
if changedForeignKey {
|
||||
newCreateTable := plan.NewCreateTable(n.Db, n.Name(), n.IfNotExists(), n.Temporary(), &plan.TableSpec{
|
||||
Schema: n.PkSchema(),
|
||||
FkDefs: copiedForeignKeys,
|
||||
ChDefs: n.Checks(),
|
||||
IdxDefs: n.Indexes(),
|
||||
Collation: n.Collation,
|
||||
TableOpts: n.TableOpts,
|
||||
})
|
||||
return newCreateTable, transform.NewTree, nil
|
||||
} else {
|
||||
return n, transform.SameTree, nil
|
||||
}
|
||||
|
||||
case *plan.CreateForeignKey:
|
||||
if n.FkDef.Name == "" {
|
||||
copiedFk := *n.FkDef
|
||||
generatedName, err := generateFkName(ctx, copiedFk.Table, &copiedFk)
|
||||
if err != nil {
|
||||
return nil, transform.SameTree, err
|
||||
}
|
||||
copiedFk.Name = generatedName
|
||||
return &plan.CreateForeignKey{
|
||||
DbProvider: n.DbProvider,
|
||||
FkDef: &copiedFk,
|
||||
}, transform.NewTree, nil
|
||||
} else {
|
||||
return n, transform.SameTree, nil
|
||||
}
|
||||
|
||||
default:
|
||||
return n, transform.SameTree, nil
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// generateFkName creates a default foreign key name, according to Postgres naming rules
|
||||
// (i.e. "<tablename>_<col1name>_<col2name>_fkey"). If an existing foreign key is found with the default, generated
|
||||
// name, the generated name will be suffixed with a number to ensure uniqueness.
|
||||
func generateFkName(ctx *sql.Context, tableName string, newFk *sql.ForeignKeyConstraint) (string, error) {
|
||||
columnNames := strings.Join(newFk.Columns, "_")
|
||||
generatedBaseName := fmt.Sprintf("%s_%s_fkey", tableName, columnNames)
|
||||
|
||||
for counter := 0; counter < 100; counter += 1 {
|
||||
generatedFkName := generatedBaseName
|
||||
if counter > 0 {
|
||||
generatedFkName = fmt.Sprintf("%s%d", generatedBaseName, counter)
|
||||
}
|
||||
|
||||
duplicate := false
|
||||
err := functions.IterateCurrentDatabase(ctx, functions.Callbacks{
|
||||
ForeignKey: func(ctx *sql.Context, schema functions.ItemSchema, table functions.ItemTable, foreignKey functions.ItemForeignKey) (cont bool, err error) {
|
||||
if foreignKey.Item.Name == generatedFkName {
|
||||
duplicate = true
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if !duplicate {
|
||||
return generatedFkName, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("unable to create unique foreign key %s: "+
|
||||
"a foreign key constraint already exists with this name", generatedBaseName)
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
// 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 analyzer
|
||||
|
||||
import (
|
||||
"github.com/dolthub/go-mysql-server/sql/analyzer"
|
||||
"github.com/dolthub/go-mysql-server/sql/expression"
|
||||
"github.com/dolthub/go-mysql-server/sql/plan"
|
||||
"github.com/dolthub/go-mysql-server/sql/planbuilder"
|
||||
|
||||
pgexpression "github.com/dolthub/doltgresql/server/expression"
|
||||
)
|
||||
|
||||
// IDs are basically arbitrary, we just need to ensure that they do not conflict with existing IDs
|
||||
// Comments are to match the Stringer formatting rules in the original rule definition file, but we can't generate
|
||||
// human-readable strings for these extended types because they are in another package.
|
||||
const (
|
||||
ruleId_TypeSanitizer analyzer.RuleId = iota + 1000 // typeSanitizer
|
||||
ruleId_AddDomainConstraints // addDomainConstraints
|
||||
ruleId_AddDomainConstraintsToCasts // addDomainConstraintsToCasts
|
||||
ruleId_ApplyTablesForAnalyzeAllTables // applyTablesForAnalyzeAllTables
|
||||
ruleId_AssignInsertCasts // assignInsertCasts
|
||||
ruleId_AssignTriggers // assignTriggers
|
||||
ruleId_AssignUpdateCasts // assignUpdateCasts
|
||||
ruleId_ConvertDropPrimaryKeyConstraint // convertDropPrimaryKeyConstraint
|
||||
ruleId_GenerateForeignKeyName // generateForeignKeyName
|
||||
ruleId_ReplaceIndexedTables // replaceIndexedTables
|
||||
ruleId_ReplaceNode // replaceNode
|
||||
ruleId_TransformRecordFilter // transformRecordFilter
|
||||
ruleId_ReplaceSerial // replaceSerial
|
||||
ruleId_InsertContextRootFinalizer // insertContextRootFinalizer
|
||||
ruleId_ResolveType // resolveType
|
||||
ruleId_ReplaceArithmeticExpressions // replaceArithmeticExpressions
|
||||
ruleId_OptimizeFunctions // optimizeFunctions
|
||||
ruleId_ValidateColumnDefaults // validateColumnDefaults
|
||||
ruleId_ValidateCreateTable // validateCreateTable
|
||||
ruleId_ValidateCreateSchema // validateCreateSchema
|
||||
ruleId_ResolveAlterColumn // resolveAlterColumn
|
||||
ruleId_ValidateCreateFunction // validateCreateFunction
|
||||
ruleId_ResolveValuesTypes // resolveValuesTypes
|
||||
ruleId_ResolveProcedureDefaults // resolveProcedureDefaults
|
||||
ruleId_SetRunner // setRunner
|
||||
)
|
||||
|
||||
// Init adds additional rules to the analyzer to handle Doltgres-specific functionality.
|
||||
func Init() {
|
||||
// OnceBeforeDefault runs before AlwaysBeforeDefault in GMS
|
||||
analyzer.OnceBeforeDefault = append([]analyzer.Rule{
|
||||
{Id: ruleId_ResolveType, Apply: ResolveType}, // ResolveType rule must run before simplifyFilters rule in GMS
|
||||
{Id: ruleId_ApplyTablesForAnalyzeAllTables, Apply: applyTablesForAnalyzeAllTables},
|
||||
{Id: ruleId_ConvertDropPrimaryKeyConstraint, Apply: convertDropPrimaryKeyConstraint}},
|
||||
analyzer.OnceBeforeDefault...)
|
||||
|
||||
analyzer.AlwaysBeforeDefault = append(analyzer.AlwaysBeforeDefault,
|
||||
// ResolveType rule must run in this batch in addition to OnceBeforeDefault batch
|
||||
// because of custom batch set optimization in GMS skipping OnceBeforeDefault batch for some nodes.
|
||||
analyzer.Rule{Id: ruleId_ResolveType, Apply: ResolveType},
|
||||
analyzer.Rule{Id: ruleId_SetRunner, Apply: SetRunner},
|
||||
analyzer.Rule{Id: ruleId_TypeSanitizer, Apply: TypeSanitizer},
|
||||
analyzer.Rule{Id: ruleId_ResolveValuesTypes, Apply: ResolveValuesTypes},
|
||||
analyzer.Rule{Id: ruleId_GenerateForeignKeyName, Apply: generateForeignKeyName},
|
||||
analyzer.Rule{Id: ruleId_AddDomainConstraints, Apply: AddDomainConstraints},
|
||||
analyzer.Rule{Id: ruleId_ValidateColumnDefaults, Apply: ValidateColumnDefaults},
|
||||
analyzer.Rule{Id: ruleId_AssignInsertCasts, Apply: AssignInsertCasts},
|
||||
analyzer.Rule{Id: ruleId_AssignUpdateCasts, Apply: AssignUpdateCasts},
|
||||
analyzer.Rule{Id: ruleId_AssignTriggers, Apply: AssignTriggers},
|
||||
analyzer.Rule{Id: ruleId_ValidateCreateFunction, Apply: ValidateCreateFunction},
|
||||
analyzer.Rule{Id: ruleId_ValidateCreateSchema, Apply: ValidateCreateSchema},
|
||||
analyzer.Rule{Id: ruleId_ResolveProcedureDefaults, Apply: ResolveProcedureDefaults},
|
||||
)
|
||||
|
||||
// We remove several validation rules and substitute our own
|
||||
analyzer.OnceBeforeDefault = insertAnalyzerRules(analyzer.OnceBeforeDefault, analyzer.ValidateCreateTableId, true,
|
||||
analyzer.Rule{Id: ruleId_ValidateCreateTable, Apply: validateCreateTable})
|
||||
analyzer.OnceBeforeDefault = insertAnalyzerRules(analyzer.OnceBeforeDefault, analyzer.ResolveAlterColumnId, true,
|
||||
analyzer.Rule{Id: ruleId_ResolveAlterColumn, Apply: resolveAlterColumn})
|
||||
|
||||
analyzer.OnceBeforeDefault = removeAnalyzerRules(
|
||||
analyzer.OnceBeforeDefault,
|
||||
analyzer.ValidateColumnDefaultsId,
|
||||
analyzer.ValidateCreateTableId,
|
||||
analyzer.ResolveAlterColumnId,
|
||||
)
|
||||
|
||||
// Remove all other validation rules that do not apply to Postgres
|
||||
analyzer.DefaultValidationRules = removeAnalyzerRules(analyzer.DefaultValidationRules, analyzer.ValidateOperandsId)
|
||||
|
||||
analyzer.DefaultRules = append(analyzer.DefaultRules,
|
||||
analyzer.Rule{Id: ruleId_TransformRecordFilter, Apply: TransformRecordFilter},
|
||||
)
|
||||
|
||||
analyzer.OnceAfterDefault = append(analyzer.OnceAfterDefault,
|
||||
analyzer.Rule{Id: ruleId_ReplaceSerial, Apply: ReplaceSerial},
|
||||
analyzer.Rule{Id: ruleId_ReplaceArithmeticExpressions, Apply: ReplaceArithmeticExpressions},
|
||||
)
|
||||
|
||||
// The auto-commit rule writes the contents of the context, so we need to insert our finalizer before that.
|
||||
// We also should optimize functions last, since other rules may change the underlying expressions, potentially changing their return types.
|
||||
analyzer.OnceAfterAll = insertAnalyzerRules(analyzer.OnceAfterAll, analyzer.QuoteDefaultColumnValueNamesId, false,
|
||||
analyzer.Rule{Id: ruleId_OptimizeFunctions, Apply: OptimizeFunctions},
|
||||
// AddDomainConstraintsToCasts needs to run after 'assignExecIndexes' rule in GMS.
|
||||
analyzer.Rule{Id: ruleId_AddDomainConstraintsToCasts, Apply: AddDomainConstraintsToCasts},
|
||||
analyzer.Rule{Id: ruleId_ReplaceNode, Apply: ReplaceNode},
|
||||
analyzer.Rule{Id: ruleId_InsertContextRootFinalizer, Apply: InsertContextRootFinalizer},
|
||||
)
|
||||
|
||||
initEngine()
|
||||
}
|
||||
|
||||
// TODO: introduce a real pluggable architecture for this instead of swapping function pointers
|
||||
func initEngine() {
|
||||
// This technically takes place at execution time rather than as part of analysis, but we don't have a better
|
||||
// place to put it. Our foreign key validation logic is different from MySQL's, and since it's not an analyzer rule
|
||||
// we can't swap out a rule like the rest of the logic in this package, we have to do a function swap.
|
||||
plan.ValidateForeignKeyDefinition = validateForeignKeyDefinition
|
||||
|
||||
planbuilder.IsAggregateFunc = IsAggregateFunc
|
||||
|
||||
expression.DefaultExpressionFactory = pgexpression.PostgresExpressionFactory{}
|
||||
|
||||
expression.SplitConjunction = splitConjunction
|
||||
}
|
||||
|
||||
// IsAggregateFunc checks if the given function name is an aggregate function. This is the entire set supported by
|
||||
// MySQL plus some postgres specific ones.
|
||||
func IsAggregateFunc(name string) bool {
|
||||
if planbuilder.IsMySQLAggregateFuncName(name) {
|
||||
return true
|
||||
}
|
||||
|
||||
switch name {
|
||||
case "array_agg", "bool_and", "bool_or":
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// insertAnalyzerRules inserts the given rule(s) before or after the given analyzer.RuleId, returning an updated slice.
|
||||
func insertAnalyzerRules(rules []analyzer.Rule, id analyzer.RuleId, before bool, additionalRules ...analyzer.Rule) []analyzer.Rule {
|
||||
inserted := false
|
||||
newRules := make([]analyzer.Rule, len(rules)+len(additionalRules))
|
||||
for i, rule := range rules {
|
||||
if rule.Id == id {
|
||||
inserted = true
|
||||
if before {
|
||||
copy(newRules, rules[:i])
|
||||
copy(newRules[i:], additionalRules)
|
||||
copy(newRules[i+len(additionalRules):], rules[i:])
|
||||
} else {
|
||||
copy(newRules, rules[:i+1])
|
||||
copy(newRules[i+1:], additionalRules)
|
||||
copy(newRules[i+1+len(additionalRules):], rules[i+1:])
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !inserted {
|
||||
panic("no rules were inserted")
|
||||
}
|
||||
|
||||
return newRules
|
||||
}
|
||||
|
||||
// removeAnalyzerRules removes the given analyzer.RuleId(s), returning an updated slice.
|
||||
func removeAnalyzerRules(rules []analyzer.Rule, remove ...analyzer.RuleId) []analyzer.Rule {
|
||||
ids := make(map[analyzer.RuleId]struct{})
|
||||
for _, removal := range remove {
|
||||
ids[removal] = struct{}{}
|
||||
}
|
||||
|
||||
removedIds := 0
|
||||
var newRules []analyzer.Rule
|
||||
for _, rule := range rules {
|
||||
if _, ok := ids[rule.Id]; !ok {
|
||||
newRules = append(newRules, rule)
|
||||
} else {
|
||||
removedIds++
|
||||
}
|
||||
}
|
||||
|
||||
if removedIds < len(remove) {
|
||||
panic("one or more rules were not removed, this is a bug")
|
||||
}
|
||||
|
||||
return newRules
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
// 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 analyzer
|
||||
|
||||
import (
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
"github.com/dolthub/go-mysql-server/sql/analyzer"
|
||||
"github.com/dolthub/go-mysql-server/sql/plan"
|
||||
"github.com/dolthub/go-mysql-server/sql/transform"
|
||||
|
||||
pgnodes "github.com/dolthub/doltgresql/server/node"
|
||||
)
|
||||
|
||||
// InsertContextRootFinalizer inserts a ContextRootFinalizer node right before the transaction commits, yet after all
|
||||
// other nodes have finished. This ensures that the ContextRootFinalizer does not overwrite any changes from its
|
||||
// children.
|
||||
func InsertContextRootFinalizer(ctx *sql.Context, a *analyzer.Analyzer, node sql.Node, scope *plan.Scope, selector analyzer.RuleSelector, qFlags *sql.QueryFlags) (sql.Node, transform.TreeIdentity, error) {
|
||||
if _, ok := node.(*pgnodes.ContextRootFinalizer); ok {
|
||||
return node, transform.SameTree, nil
|
||||
}
|
||||
// Analysis may occur separately on child nodes, so we have to ensure that only one finalizer exists in the tree
|
||||
newNode, _, err := transform.NodeWithOpaque(ctx, node, transformRemoveContextRootFinalizer)
|
||||
if err != nil {
|
||||
return nil, transform.NewTree, err
|
||||
}
|
||||
return pgnodes.NewContextRootFinalizer(newNode), transform.NewTree, nil
|
||||
}
|
||||
|
||||
// transformRemoveContextRootFinalizer is the function used by the transform from within InsertContextRootFinalizer.
|
||||
func transformRemoveContextRootFinalizer(ctx *sql.Context, node sql.Node) (sql.Node, transform.TreeIdentity, error) {
|
||||
if finalizer, ok := node.(*pgnodes.ContextRootFinalizer); ok {
|
||||
return finalizer.Child(), transform.NewTree, nil
|
||||
} else if disjointedNode, ok := node.(plan.DisjointedChildrenNode); ok {
|
||||
var err error
|
||||
same := transform.SameTree
|
||||
disjointedChildGroups := disjointedNode.DisjointedChildren()
|
||||
newDisjointedChildGroups := make([][]sql.Node, len(disjointedChildGroups))
|
||||
for groupIdx, disjointedChildGroup := range disjointedChildGroups {
|
||||
newDisjointedChildGroups[groupIdx] = make([]sql.Node, len(disjointedChildGroup))
|
||||
for childIdx, disjointedChild := range disjointedChildGroup {
|
||||
var childIdentity transform.TreeIdentity
|
||||
newDisjointedChildGroups[groupIdx][childIdx], childIdentity, err = transform.NodeWithOpaque(ctx, disjointedChild, transformRemoveContextRootFinalizer)
|
||||
if err != nil {
|
||||
return nil, transform.NewTree, err
|
||||
}
|
||||
same = same && childIdentity
|
||||
}
|
||||
}
|
||||
if same == transform.NewTree {
|
||||
if newChild, err := disjointedNode.WithDisjointedChildren(newDisjointedChildGroups); err != nil {
|
||||
return nil, transform.NewTree, err
|
||||
} else {
|
||||
return newChild, transform.NewTree, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return node, transform.SameTree, nil
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
// 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 analyzer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
"github.com/dolthub/go-mysql-server/sql/analyzer"
|
||||
"github.com/dolthub/go-mysql-server/sql/expression"
|
||||
"github.com/dolthub/go-mysql-server/sql/plan"
|
||||
"github.com/dolthub/go-mysql-server/sql/planbuilder"
|
||||
"github.com/dolthub/go-mysql-server/sql/transform"
|
||||
|
||||
"github.com/dolthub/doltgresql/server/functions/framework"
|
||||
pgtransform "github.com/dolthub/doltgresql/server/transform"
|
||||
)
|
||||
|
||||
// OptimizeFunctions replaces all functions that fit specific criteria with their optimized variants. Also handles
|
||||
// SRFs (set-returning functions) by setting the `IncludesNestedIters` flag on the Project node if any SRF is found
|
||||
// inside projection expressions.
|
||||
func OptimizeFunctions(ctx *sql.Context, a *analyzer.Analyzer, node sql.Node, scope *plan.Scope, selector analyzer.RuleSelector, qFlags *sql.QueryFlags) (sql.Node, transform.TreeIdentity, error) {
|
||||
// This is supposed to be one of the last rules to run. Subqueries break that assumption, so we skip this rule in such cases.
|
||||
if scope != nil && scope.CurrentNodeIsFromSubqueryExpression {
|
||||
return node, transform.SameTree, nil
|
||||
}
|
||||
|
||||
_, isInsertNode := node.(*plan.InsertInto)
|
||||
return pgtransform.NodeWithOpaque(ctx, node, func(ctx *sql.Context, n sql.Node) (sql.Node, transform.TreeIdentity, error) {
|
||||
projectNode, ok := n.(*plan.Project)
|
||||
if !ok {
|
||||
return n, transform.SameTree, nil
|
||||
}
|
||||
|
||||
hasMultipleExpressionTuples := false
|
||||
hasSRF := false
|
||||
// Check if there is set returning function in the source node (e.g. SELECT * FROM unnest())
|
||||
n, sameNode, err := transform.NodeExprsWithNode(ctx, projectNode.Child, func(ctx *sql.Context, in sql.Node, expr sql.Expression) (sql.Expression, transform.TreeIdentity, error) {
|
||||
if compiledFunction, ok := expr.(*framework.CompiledFunction); ok {
|
||||
// TODO: need better way to detect sequence usage
|
||||
switch compiledFunction.FunctionName() {
|
||||
case "nextval", "setval", "currval":
|
||||
err := authCheckSequenceFromExpr(ctx, a.Catalog.AuthHandler, compiledFunction.Arguments[0])
|
||||
if err != nil {
|
||||
return nil, transform.SameTree, err
|
||||
}
|
||||
}
|
||||
hasSRF = hasSRF || compiledFunction.IsSRF()
|
||||
if quickFunction := compiledFunction.GetQuickFunction(); quickFunction != nil {
|
||||
return quickFunction, transform.NewTree, nil
|
||||
}
|
||||
|
||||
// fill in default exprs if applicable
|
||||
if err := compiledFunction.ResolveDefaultValues(ctx, func(defExpr string) (sql.Expression, error) {
|
||||
return getDefaultExpr(ctx, a.Catalog, defExpr)
|
||||
}); err != nil {
|
||||
return nil, transform.SameTree, err
|
||||
}
|
||||
}
|
||||
if v, ok := in.(*plan.Values); ok {
|
||||
hasMultipleExpressionTuples = len(v.ExpressionTuples) > 1
|
||||
}
|
||||
return expr, transform.SameTree, nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, transform.SameTree, err
|
||||
}
|
||||
if !sameNode {
|
||||
projectNode.Child = n
|
||||
}
|
||||
|
||||
// insert node cannot have more than 1 row value if it has set returning function
|
||||
if isInsertNode && hasMultipleExpressionTuples && hasSRF {
|
||||
return nil, false, errors.Errorf("set-returning functions are not allowed in VALUES")
|
||||
}
|
||||
|
||||
// Check if there is set returning function in the projection expressions (e.g. SELECT unnest() [FROM table/srf])
|
||||
hasSRFInProjection := false
|
||||
exprs, sameExprs, err := transform.Exprs(ctx, projectNode.Projections, func(ctx *sql.Context, expr sql.Expression) (sql.Expression, transform.TreeIdentity, error) {
|
||||
if compiledFunction, ok := expr.(*framework.CompiledFunction); ok {
|
||||
hasSRFInProjection = hasSRFInProjection || compiledFunction.IsSRF()
|
||||
if quickFunction := compiledFunction.GetQuickFunction(); quickFunction != nil {
|
||||
return quickFunction, transform.NewTree, nil
|
||||
}
|
||||
// TODO: need better way to detect sequence usage
|
||||
switch compiledFunction.FunctionName() {
|
||||
case "nextval", "setval", "currval":
|
||||
err = authCheckSequenceFromExpr(ctx, a.Catalog.AuthHandler, compiledFunction.Arguments[0])
|
||||
if err != nil {
|
||||
return nil, transform.SameTree, err
|
||||
}
|
||||
}
|
||||
|
||||
// fill in default exprs if applicablea
|
||||
if err = compiledFunction.ResolveDefaultValues(ctx, func(defExpr string) (sql.Expression, error) {
|
||||
return getDefaultExpr(ctx, a.Catalog, defExpr)
|
||||
}); err != nil {
|
||||
return nil, transform.SameTree, err
|
||||
}
|
||||
}
|
||||
return expr, transform.SameTree, nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, transform.SameTree, err
|
||||
}
|
||||
if !sameExprs {
|
||||
projectNode.Projections = exprs
|
||||
}
|
||||
|
||||
// nested iter is used for set returning functions in the projections only
|
||||
if hasSRFInProjection {
|
||||
// Under some conditions, there will be no quick-function replacement, but changing the Project node to include
|
||||
// nested iterators is still a change we need to tell the transform functions about.
|
||||
sameExprs = transform.NewTree
|
||||
projectNode = projectNode.WithIncludesNestedIters(true)
|
||||
}
|
||||
|
||||
return projectNode, sameNode && sameExprs, err
|
||||
})
|
||||
}
|
||||
|
||||
// getDefaultExpr takes the default value definition, parses, builds and returns sql.ColumnDefaultValue.
|
||||
func getDefaultExpr(ctx *sql.Context, c sql.Catalog, defExpr string) (sql.Expression, error) {
|
||||
builder := planbuilder.New(ctx, c, nil)
|
||||
proj, _, _, _, err := builder.Parse(fmt.Sprintf("select %s", defExpr), nil, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
parsedExpr := proj.(*plan.Project).Projections[0]
|
||||
if a, ok := parsedExpr.(*expression.Alias); ok {
|
||||
parsedExpr = a.Child
|
||||
}
|
||||
return parsedExpr, nil
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
// 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 analyzer
|
||||
|
||||
import (
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
"github.com/dolthub/go-mysql-server/sql/analyzer"
|
||||
gms_expression "github.com/dolthub/go-mysql-server/sql/expression"
|
||||
"github.com/dolthub/go-mysql-server/sql/plan"
|
||||
"github.com/dolthub/go-mysql-server/sql/transform"
|
||||
|
||||
"github.com/dolthub/doltgresql/server/expression"
|
||||
"github.com/dolthub/doltgresql/server/functions/framework"
|
||||
)
|
||||
|
||||
// ReplaceArithmeticExpressions replaces arithmetic expressions in the tree with the equivalent binary operators.
|
||||
// This is a problem in certain nodes that do arithmetic, such as TopN, but all nodes are covered.
|
||||
func ReplaceArithmeticExpressions(ctx *sql.Context, a *analyzer.Analyzer, node sql.Node, scope *plan.Scope, selector analyzer.RuleSelector, qFlags *sql.QueryFlags) (sql.Node, transform.TreeIdentity, error) {
|
||||
return transform.NodeExprsWithOpaque(ctx, node, func(ctx *sql.Context, e sql.Expression) (sql.Expression, transform.TreeIdentity, error) {
|
||||
switch e := e.(type) {
|
||||
case *gms_expression.Arithmetic:
|
||||
switch e.Operator() {
|
||||
case "+":
|
||||
expr, err := expression.NewBinaryOperator(framework.Operator_BinaryPlus).WithChildren(ctx, e.Children()...)
|
||||
if err != nil {
|
||||
return nil, transform.NewTree, err
|
||||
}
|
||||
return expr.(*expression.BinaryOperator), transform.NewTree, nil
|
||||
case "-":
|
||||
expr, err := expression.NewBinaryOperator(framework.Operator_BinaryMinus).WithChildren(ctx, e.Children()...)
|
||||
if err != nil {
|
||||
return nil, transform.NewTree, err
|
||||
}
|
||||
return expr.(*expression.BinaryOperator), transform.NewTree, nil
|
||||
case "*":
|
||||
expr, err := expression.NewBinaryOperator(framework.Operator_BinaryMultiply).WithChildren(ctx, e.Children()...)
|
||||
if err != nil {
|
||||
return nil, transform.NewTree, err
|
||||
}
|
||||
return expr.(*expression.BinaryOperator), transform.NewTree, nil
|
||||
case "/":
|
||||
expr, err := expression.NewBinaryOperator(framework.Operator_BinaryDivide).WithChildren(ctx, e.Children()...)
|
||||
if err != nil {
|
||||
return nil, transform.NewTree, err
|
||||
}
|
||||
return expr.(*expression.BinaryOperator), transform.NewTree, nil
|
||||
}
|
||||
}
|
||||
return e, transform.SameTree, nil
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
// Copyright 2025 Dolthub, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
"github.com/dolthub/go-mysql-server/sql/analyzer"
|
||||
"github.com/dolthub/go-mysql-server/sql/plan"
|
||||
"github.com/dolthub/go-mysql-server/sql/transform"
|
||||
|
||||
pgnodes "github.com/dolthub/doltgresql/server/node"
|
||||
)
|
||||
|
||||
// ReplaceNode is used to replace generic top-level nodes with Doltgres versions that wrap them, without performing any
|
||||
// additional analysis. This is used to handle relatively straightforward tasks, like delete cascading, etc.
|
||||
func ReplaceNode(ctx *sql.Context, a *analyzer.Analyzer, node sql.Node, scope *plan.Scope, selector analyzer.RuleSelector, qFlags *sql.QueryFlags) (sql.Node, transform.TreeIdentity, error) {
|
||||
// TODO: need to add the majority of other DDL operations here
|
||||
switch node := node.(type) {
|
||||
case *plan.DropTable:
|
||||
return pgnodes.NewDropTable(node), transform.NewTree, nil
|
||||
default:
|
||||
return node, transform.SameTree, nil
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
// 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 analyzer
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
"github.com/dolthub/go-mysql-server/sql/analyzer"
|
||||
"github.com/dolthub/go-mysql-server/sql/plan"
|
||||
"github.com/dolthub/go-mysql-server/sql/transform"
|
||||
|
||||
"github.com/dolthub/doltgresql/core"
|
||||
"github.com/dolthub/doltgresql/core/extensions"
|
||||
"github.com/dolthub/doltgresql/core/id"
|
||||
"github.com/dolthub/doltgresql/server/functions"
|
||||
"github.com/dolthub/doltgresql/server/functions/framework"
|
||||
pgnodes "github.com/dolthub/doltgresql/server/node"
|
||||
pgtypes "github.com/dolthub/doltgresql/server/types"
|
||||
)
|
||||
|
||||
// ResolveProcedureDefaults resolves default expressions of routines that are in string format by parsing it into sql.Expression.
|
||||
// This function retrieves the procedure overloads and sets CompiledFunction in the Call node.
|
||||
func ResolveProcedureDefaults(ctx *sql.Context, a *analyzer.Analyzer, node sql.Node, scope *plan.Scope, selector analyzer.RuleSelector, qFlags *sql.QueryFlags) (sql.Node, transform.TreeIdentity, error) {
|
||||
switch n := node.(type) {
|
||||
case *pgnodes.Call:
|
||||
procCollection, err := core.GetProceduresCollectionFromContext(ctx, "")
|
||||
if err != nil {
|
||||
return nil, transform.SameTree, err
|
||||
}
|
||||
typesCollection, err := core.GetTypesCollectionFromContext(ctx, "")
|
||||
if err != nil {
|
||||
return nil, transform.SameTree, err
|
||||
}
|
||||
schemaName, err := core.GetSchemaName(ctx, nil, n.SchemaName)
|
||||
if err != nil {
|
||||
return nil, transform.SameTree, err
|
||||
}
|
||||
procName := id.NewProcedure(schemaName, n.ProcedureName)
|
||||
overloads, err := procCollection.GetProcedureOverloads(ctx, procName)
|
||||
if err != nil {
|
||||
return nil, transform.SameTree, err
|
||||
}
|
||||
if len(overloads) == 0 {
|
||||
if strings.HasPrefix(n.ProcedureName, "dolt_") {
|
||||
return nil, transform.SameTree, functions.ErrDoltProcedureSelectOnly
|
||||
}
|
||||
return nil, transform.SameTree, sql.ErrStoredProcedureDoesNotExist.New(n.ProcedureName)
|
||||
}
|
||||
|
||||
same := transform.SameTree
|
||||
overloadTree := framework.NewOverloads()
|
||||
for _, overload := range overloads {
|
||||
paramTypes := make([]*pgtypes.DoltgresType, len(overload.ParameterTypes))
|
||||
for i, paramType := range overload.ParameterTypes {
|
||||
paramTypes[i], err = typesCollection.GetType(ctx, paramType)
|
||||
if err != nil || paramTypes[i] == nil {
|
||||
return nil, transform.SameTree, err
|
||||
}
|
||||
}
|
||||
// TODO: we should probably have procedure equivalents instead of converting these to functions
|
||||
// probably fine for now since we don't implement/support the differing functionality between the two just yet
|
||||
if len(overload.ExtensionName) > 0 {
|
||||
if err = overloadTree.Add(framework.CFunction{
|
||||
ID: id.Function(overload.ID),
|
||||
ReturnType: pgtypes.Void,
|
||||
ParameterTypes: paramTypes,
|
||||
Variadic: false,
|
||||
IsNonDeterministic: true,
|
||||
Strict: false,
|
||||
ExtensionName: extensions.LibraryIdentifier(overload.ExtensionName),
|
||||
ExtensionSymbol: overload.ExtensionSymbol,
|
||||
}); err != nil {
|
||||
return nil, transform.SameTree, err
|
||||
}
|
||||
} else if len(overload.SQLDefinition) > 0 {
|
||||
if err = overloadTree.Add(framework.SQLFunction{
|
||||
ID: id.Function(overload.ID),
|
||||
ReturnType: pgtypes.Void,
|
||||
ParameterNames: overload.ParameterNames,
|
||||
ParameterTypes: paramTypes,
|
||||
ParameterDefaults: overload.ParameterDefaults,
|
||||
Variadic: false,
|
||||
IsNonDeterministic: true,
|
||||
Strict: false,
|
||||
SqlStatement: overload.SQLDefinition,
|
||||
SetOf: false,
|
||||
}); err != nil {
|
||||
return nil, transform.SameTree, err
|
||||
}
|
||||
} else {
|
||||
if err = overloadTree.Add(framework.InterpretedFunction{
|
||||
ID: id.Function(overload.ID),
|
||||
ReturnType: pgtypes.Void,
|
||||
ParameterNames: overload.ParameterNames,
|
||||
ParameterTypes: paramTypes,
|
||||
Variadic: false,
|
||||
IsNonDeterministic: true,
|
||||
Strict: false,
|
||||
SRF: false,
|
||||
Statements: overload.Operations,
|
||||
}); err != nil {
|
||||
return nil, transform.SameTree, err
|
||||
}
|
||||
}
|
||||
}
|
||||
compiledFunction := framework.NewCompiledFunction(ctx, n.ProcedureName, n.Exprs, overloadTree, false)
|
||||
// fill in default exprs if applicable
|
||||
if err := compiledFunction.ResolveDefaultValues(ctx, func(defExpr string) (sql.Expression, error) {
|
||||
return getDefaultExpr(ctx, a.Catalog, defExpr)
|
||||
}); err != nil {
|
||||
return nil, transform.SameTree, err
|
||||
}
|
||||
n.CompiledFunc = compiledFunction
|
||||
return node, same, nil
|
||||
default:
|
||||
return node, transform.SameTree, nil
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,304 @@
|
||||
// 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 analyzer
|
||||
|
||||
import (
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
"github.com/dolthub/go-mysql-server/sql/analyzer"
|
||||
"github.com/dolthub/go-mysql-server/sql/plan"
|
||||
"github.com/dolthub/go-mysql-server/sql/transform"
|
||||
|
||||
pgnodes "github.com/dolthub/doltgresql/server/node"
|
||||
|
||||
"github.com/dolthub/doltgresql/core"
|
||||
"github.com/dolthub/doltgresql/core/id"
|
||||
pgexprs "github.com/dolthub/doltgresql/server/expression"
|
||||
pgtransform "github.com/dolthub/doltgresql/server/transform"
|
||||
pgtypes "github.com/dolthub/doltgresql/server/types"
|
||||
)
|
||||
|
||||
// ResolveType replaces types.ResolvableType to appropriate pgtypes.DoltgresType.
|
||||
func ResolveType(ctx *sql.Context, a *analyzer.Analyzer, node sql.Node, scope *plan.Scope, selector analyzer.RuleSelector, qFlags *sql.QueryFlags) (sql.Node, transform.TreeIdentity, error) {
|
||||
n, sameExpr, err := ResolveTypeForExprs(ctx, a, node, scope, selector, qFlags)
|
||||
if err != nil {
|
||||
return nil, transform.NewTree, err
|
||||
}
|
||||
|
||||
n, sameNode, err := ResolveTypeForNodes(ctx, a, n, scope, selector, qFlags)
|
||||
if err != nil {
|
||||
return nil, transform.NewTree, err
|
||||
}
|
||||
|
||||
return n, sameExpr && sameNode, nil
|
||||
}
|
||||
|
||||
// ResolveTypeForNodes replaces types.ResolvableType to appropriate pgtypes.DoltgresType.
|
||||
func ResolveTypeForNodes(ctx *sql.Context, a *analyzer.Analyzer, node sql.Node, scope *plan.Scope, selector analyzer.RuleSelector, qFlags *sql.QueryFlags) (sql.Node, transform.TreeIdentity, error) {
|
||||
var db sql.Database
|
||||
if dbnode, ok := node.(sql.Databaser); ok {
|
||||
db = dbnode.Database()
|
||||
}
|
||||
return transform.Node(ctx, node, func(ctx *sql.Context, node sql.Node) (sql.Node, transform.TreeIdentity, error) {
|
||||
var same = transform.SameTree
|
||||
switch n := node.(type) {
|
||||
case *plan.AddColumn:
|
||||
col := n.Column()
|
||||
if rt, ok := col.Type.(*pgtypes.DoltgresType); ok && !rt.IsResolvedType() {
|
||||
dt, err := resolveType(ctx, db, rt)
|
||||
if err != nil {
|
||||
return nil, transform.NewTree, err
|
||||
}
|
||||
same = transform.NewTree
|
||||
col.Type = dt
|
||||
}
|
||||
return node, same, nil
|
||||
case *pgnodes.CreateCast:
|
||||
if !n.Source.IsResolvedType() {
|
||||
source, err := resolveType(ctx, db, n.Source)
|
||||
if err != nil {
|
||||
return nil, transform.NewTree, err
|
||||
}
|
||||
same = transform.NewTree
|
||||
n.Source = source
|
||||
}
|
||||
if !n.Target.IsResolvedType() {
|
||||
target, err := resolveType(ctx, db, n.Target)
|
||||
if err != nil {
|
||||
return nil, transform.NewTree, err
|
||||
}
|
||||
same = transform.NewTree
|
||||
n.Target = target
|
||||
}
|
||||
for i, param := range n.FuncParams {
|
||||
if !param.Type.IsResolvedType() {
|
||||
target, err := resolveType(ctx, db, param.Type)
|
||||
if err != nil {
|
||||
return nil, transform.NewTree, err
|
||||
}
|
||||
same = transform.NewTree
|
||||
n.FuncParams[i].Type = target
|
||||
}
|
||||
}
|
||||
return node, same, nil
|
||||
case *pgnodes.CreateFunction:
|
||||
if !n.ReturnType.IsResolvedType() {
|
||||
retType, err := resolveType(ctx, db, n.ReturnType)
|
||||
if err != nil {
|
||||
return nil, transform.NewTree, err
|
||||
}
|
||||
same = transform.NewTree
|
||||
n.ReturnType = retType
|
||||
}
|
||||
for i, param := range n.Parameters {
|
||||
if !param.Type.IsResolvedType() {
|
||||
paramType, err := resolveType(ctx, db, param.Type)
|
||||
if err != nil {
|
||||
return nil, transform.NewTree, err
|
||||
}
|
||||
same = transform.NewTree
|
||||
n.Parameters[i].Type = paramType
|
||||
}
|
||||
}
|
||||
return node, same, nil
|
||||
case *pgnodes.CreateProcedure:
|
||||
for i := range n.Parameters {
|
||||
var err error
|
||||
n.Parameters[i].Type, err = resolveType(ctx, db, n.Parameters[i].Type)
|
||||
if err != nil {
|
||||
return nil, transform.NewTree, err
|
||||
}
|
||||
}
|
||||
return node, transform.NewTree, nil
|
||||
case *plan.CreateTable:
|
||||
for _, col := range n.TargetSchema() {
|
||||
if rt, ok := col.Type.(*pgtypes.DoltgresType); ok && !rt.IsResolvedType() {
|
||||
dt, err := resolveType(ctx, db, rt)
|
||||
if err != nil {
|
||||
return nil, transform.NewTree, err
|
||||
}
|
||||
same = transform.NewTree
|
||||
col.Type = dt
|
||||
}
|
||||
resolvedDefault, err := resolveDefaultColumnType(ctx, db, col.Default)
|
||||
if err != nil {
|
||||
return nil, transform.NewTree, err
|
||||
}
|
||||
resolvedGenerated, err := resolveDefaultColumnType(ctx, db, col.Generated)
|
||||
if err != nil {
|
||||
return nil, transform.NewTree, err
|
||||
}
|
||||
resolvedOnUpdate, err := resolveDefaultColumnType(ctx, db, col.OnUpdate)
|
||||
if err != nil {
|
||||
return nil, transform.NewTree, err
|
||||
}
|
||||
if resolvedDefault || resolvedGenerated || resolvedOnUpdate {
|
||||
same = transform.NewTree
|
||||
}
|
||||
}
|
||||
return node, same, nil
|
||||
case *pgnodes.DropCast:
|
||||
if !n.Source.IsResolvedType() {
|
||||
source, err := resolveType(ctx, db, n.Source)
|
||||
if err != nil {
|
||||
return nil, transform.NewTree, err
|
||||
}
|
||||
same = transform.NewTree
|
||||
n.Source = source
|
||||
}
|
||||
if !n.Target.IsResolvedType() {
|
||||
target, err := resolveType(ctx, db, n.Target)
|
||||
if err != nil {
|
||||
return nil, transform.NewTree, err
|
||||
}
|
||||
same = transform.NewTree
|
||||
n.Target = target
|
||||
}
|
||||
return node, same, nil
|
||||
case *pgnodes.DropFunction:
|
||||
for _, r := range n.RoutinesWithArgs {
|
||||
for j, arg := range r.Args {
|
||||
if !arg.Type.IsResolvedType() {
|
||||
dt, err := resolveType(ctx, db, arg.Type)
|
||||
if err != nil {
|
||||
// the type can be non-existing type
|
||||
continue
|
||||
}
|
||||
same = transform.NewTree
|
||||
r.Args[j].Type = dt
|
||||
}
|
||||
}
|
||||
}
|
||||
return node, same, nil
|
||||
case *pgnodes.DropProcedure:
|
||||
for _, r := range n.RoutinesWithArgs {
|
||||
for j, arg := range r.Args {
|
||||
if !arg.Type.IsResolvedType() {
|
||||
dt, err := resolveType(ctx, db, arg.Type)
|
||||
if err != nil {
|
||||
// the type can be non-existing type
|
||||
continue
|
||||
}
|
||||
same = transform.NewTree
|
||||
r.Args[j].Type = dt
|
||||
}
|
||||
}
|
||||
}
|
||||
return node, same, nil
|
||||
case *plan.ModifyColumn:
|
||||
col := n.NewColumn()
|
||||
if rt, ok := col.Type.(*pgtypes.DoltgresType); ok && !rt.IsResolvedType() {
|
||||
dt, err := resolveType(ctx, db, rt)
|
||||
if err != nil {
|
||||
return nil, transform.NewTree, err
|
||||
}
|
||||
same = transform.NewTree
|
||||
col.Type = dt
|
||||
}
|
||||
return node, same, nil
|
||||
default:
|
||||
// TODO: add nodes that use unresolved types like domain
|
||||
return node, transform.SameTree, nil
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ResolveTypeForExprs replaces types.ResolvableType to appropriate pgtypes.DoltgresType.
|
||||
func ResolveTypeForExprs(ctx *sql.Context, a *analyzer.Analyzer, node sql.Node, scope *plan.Scope, selector analyzer.RuleSelector, qFlags *sql.QueryFlags) (sql.Node, transform.TreeIdentity, error) {
|
||||
var db sql.Database
|
||||
if dbnode, ok := node.(sql.Databaser); ok {
|
||||
db = dbnode.Database()
|
||||
}
|
||||
return pgtransform.NodeExprsWithOpaque(ctx, node, func(ctx *sql.Context, e sql.Expression) (sql.Expression, transform.TreeIdentity, error) {
|
||||
switch expr := e.(type) {
|
||||
case *pgexprs.ColumnAccess:
|
||||
exprType, _ := expr.Type(ctx).(*pgtypes.DoltgresType)
|
||||
if exprType == nil {
|
||||
return nil, transform.NewTree, errors.New("column access is missing its child expression")
|
||||
} else if exprType.IsResolvedType() {
|
||||
// The type has already been resolved
|
||||
return expr, transform.SameTree, nil
|
||||
}
|
||||
newType, err := resolveType(ctx, db, exprType)
|
||||
if err != nil {
|
||||
return nil, transform.NewTree, err
|
||||
}
|
||||
return expr.WithType(newType), transform.NewTree, nil
|
||||
case *pgnodes.FunctionColumn:
|
||||
if expr.Typ.IsResolvedType() {
|
||||
// The type has already been resolved
|
||||
return expr, transform.SameTree, nil
|
||||
}
|
||||
newType, err := resolveType(ctx, db, expr.Typ)
|
||||
if err != nil {
|
||||
return nil, transform.NewTree, err
|
||||
}
|
||||
expr.Typ = newType
|
||||
return expr, transform.NewTree, nil
|
||||
default:
|
||||
// TODO: add expressions that use unresolved types like domain
|
||||
return expr, transform.SameTree, nil
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// resolveType resolves any type that is unresolved yet. (e.g.: domain types, built-in types that schema specified, etc.)
|
||||
func resolveType(ctx *sql.Context, db sql.Database, typ *pgtypes.DoltgresType) (*pgtypes.DoltgresType, error) {
|
||||
if typ.IsResolvedType() {
|
||||
return typ, nil
|
||||
}
|
||||
var dbname string
|
||||
if db != nil {
|
||||
dbname = db.Name()
|
||||
}
|
||||
typs, err := core.GetTypesCollectionFromContext(ctx, dbname)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// schema name can be empty
|
||||
schema, _ := core.GetSchemaName(ctx, db, typ.ID.SchemaName())
|
||||
resolvedType, _ := typs.GetType(ctx, id.NewType(schema, typ.ID.TypeName()))
|
||||
if resolvedType == nil {
|
||||
// If a blank schema is provided, then we'll also try the pg_catalog, since a type is most likely to be there
|
||||
if typ.ID.SchemaName() == "" {
|
||||
resolvedType, err = typs.GetType(ctx, id.NewType("pg_catalog", typ.ID.TypeName()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resolvedType != nil && (typ.ID.TypeName() == "unknown" || resolvedType.ID != pgtypes.Unknown.ID) {
|
||||
return resolvedType, nil
|
||||
}
|
||||
}
|
||||
return nil, pgtypes.ErrTypeDoesNotExist.New(typ.Name())
|
||||
}
|
||||
return resolvedType, nil
|
||||
}
|
||||
|
||||
// resolveDefaultColumnType resolves the OutType of a *sql.ColumnDefaultValue if it's not nil (and not already resolved).
|
||||
func resolveDefaultColumnType(ctx *sql.Context, db sql.Database, defaultVal *sql.ColumnDefaultValue) (bool, error) {
|
||||
if defaultVal == nil {
|
||||
return false, nil
|
||||
}
|
||||
if rt, ok := defaultVal.OutType.(*pgtypes.DoltgresType); ok && !rt.IsResolvedType() {
|
||||
dt, err := resolveType(ctx, db, rt)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defaultVal.OutType = dt
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
// 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 analyzer
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
"github.com/dolthub/go-mysql-server/sql/analyzer"
|
||||
"github.com/dolthub/go-mysql-server/sql/expression"
|
||||
"github.com/dolthub/go-mysql-server/sql/plan"
|
||||
"github.com/dolthub/go-mysql-server/sql/transform"
|
||||
|
||||
pgexprs "github.com/dolthub/doltgresql/server/expression"
|
||||
"github.com/dolthub/doltgresql/server/functions/framework"
|
||||
pgtransform "github.com/dolthub/doltgresql/server/transform"
|
||||
pgtypes "github.com/dolthub/doltgresql/server/types"
|
||||
)
|
||||
|
||||
// ResolveValuesTypes determines the common type for each column in a VALUES clause
|
||||
// by examining all rows, following PostgreSQL's type resolution rules.
|
||||
// This ensures VALUES(1),(2.01),(3) correctly infers numeric type, not integer.
|
||||
func ResolveValuesTypes(ctx *sql.Context, a *analyzer.Analyzer, node sql.Node, scope *plan.Scope, selector analyzer.RuleSelector, qFlags *sql.QueryFlags) (sql.Node, transform.TreeIdentity, error) {
|
||||
// Walk the tree and wrap mixed-type VALUES columns with ImplicitCast.
|
||||
// We record which VDTs changed so we can fix up GetField types afterward.
|
||||
transformedVDTs := make(map[sql.TableId]sql.Schema)
|
||||
node, same, err := transform.NodeWithOpaque(ctx, node, func(ctx *sql.Context, n sql.Node) (sql.Node, transform.TreeIdentity, error) {
|
||||
newNode, same, err := transformValuesNode(ctx, n)
|
||||
if err != nil {
|
||||
return nil, same, err
|
||||
}
|
||||
if !same {
|
||||
if vdt, ok := newNode.(*plan.ValueDerivedTable); ok {
|
||||
transformedVDTs[vdt.Id()] = vdt.Schema(ctx)
|
||||
}
|
||||
}
|
||||
return newNode, same, err
|
||||
})
|
||||
if err != nil {
|
||||
return nil, transform.SameTree, err
|
||||
}
|
||||
|
||||
// Now, fix GetField types that reference a transformed VDT. For example,
|
||||
// after wrapping VALUES(1),(2.5) with ImplicitCast to numeric, any
|
||||
// GetField reading column "n" from that VDT still says int4 and needs
|
||||
// to be updated to numeric.
|
||||
if len(transformedVDTs) > 0 {
|
||||
node, _, err = pgtransform.NodeExprsWithOpaque(ctx, node, func(ctx *sql.Context, expr sql.Expression) (sql.Expression, transform.TreeIdentity, error) {
|
||||
gf, ok := expr.(*expression.GetField)
|
||||
if !ok {
|
||||
return expr, transform.SameTree, nil
|
||||
}
|
||||
newSch, ok := transformedVDTs[gf.TableId()]
|
||||
if !ok {
|
||||
return expr, transform.SameTree, nil
|
||||
}
|
||||
|
||||
// We match by column name because GetField indices are global
|
||||
// across all tables in a JOIN (e.g., a.n=0, b.id=1, b.label=2).
|
||||
// We can't convert a global index to a per-table position without
|
||||
// knowing the table's starting offset, which we don't have here.
|
||||
schemaIdx := -1
|
||||
for i, col := range newSch {
|
||||
if col.Name == gf.Name() {
|
||||
schemaIdx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if schemaIdx < 0 {
|
||||
return expr, transform.SameTree, nil
|
||||
}
|
||||
|
||||
newType := newSch[schemaIdx].Type
|
||||
if gf.Type(ctx) == newType {
|
||||
return expr, transform.SameTree, nil
|
||||
}
|
||||
|
||||
return expression.NewGetFieldWithTable(
|
||||
gf.Index(), int(gf.TableId()), newType,
|
||||
gf.Database(), gf.Table(), gf.Name(), gf.IsNullable(ctx),
|
||||
), transform.NewTree, nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, transform.SameTree, err
|
||||
}
|
||||
|
||||
// The pass above only fixed GetFields that read directly from a VDT
|
||||
// (matched by tableId). But changing a VDT column's type can have a
|
||||
// ripple effect: if that column feeds into an aggregate like MIN or
|
||||
// MAX, the aggregate's return type changes too. Parent nodes that
|
||||
// read the aggregate result still have the old type. For example:
|
||||
//
|
||||
// SELECT MIN(n) FROM (VALUES(1),(2.5)) v(n)
|
||||
//
|
||||
// Project [GetField("min(v.n)", tableId=GroupBy, type=int4)]
|
||||
// └── GroupBy [MIN(GetField("n", tableId=VDT, type=numeric))]
|
||||
// └── VDT [n: int4 → numeric]
|
||||
//
|
||||
// The pass above fixed "n" inside MIN because its tableId=VDT.
|
||||
// MIN now returns numeric, so GroupBy produces numeric. But the
|
||||
// Project's GetField still says int4 because its tableId=GroupBy,
|
||||
// which wasn't in transformedVDTs. At runtime this causes a panic
|
||||
// because the actual value is *apd.Decimal but the type says int32.
|
||||
//
|
||||
// This pass catches those: for each GetField, check if its type
|
||||
// disagrees with what the child node actually produces.
|
||||
node, _, err = pgtransform.NodeExprsWithNodeWithOpaque(ctx, node, func(ctx *sql.Context, n sql.Node, expr sql.Expression) (sql.Expression, transform.TreeIdentity, error) {
|
||||
gf, ok := expr.(*expression.GetField)
|
||||
if !ok {
|
||||
return expr, transform.SameTree, nil
|
||||
}
|
||||
// Skip VDT GetFields — the first pass already handled these
|
||||
if _, isVDT := transformedVDTs[gf.TableId()]; isVDT {
|
||||
return expr, transform.SameTree, nil
|
||||
}
|
||||
// Collect the schema that this node's children produce
|
||||
var childSchema sql.Schema
|
||||
for _, child := range n.Children() {
|
||||
childSchema = append(childSchema, child.Schema(ctx)...)
|
||||
}
|
||||
// TODO: GMS is case-insensitive for identifiers, so aggregate
|
||||
// GetField names and child schema names may differ in casing.
|
||||
// We use strings.ToLower to handle this, but Postgres requires
|
||||
// case-sensitivity for quoted identifiers, which this breaks.
|
||||
gfName := strings.ToLower(gf.Name())
|
||||
for _, col := range childSchema {
|
||||
if strings.ToLower(col.Name) == gfName && gf.Type(ctx) != col.Type {
|
||||
return expression.NewGetFieldWithTable(
|
||||
gf.Index(), int(gf.TableId()), col.Type,
|
||||
gf.Database(), gf.Table(), gf.Name(), gf.IsNullable(ctx),
|
||||
), transform.NewTree, nil
|
||||
}
|
||||
}
|
||||
return expr, transform.SameTree, nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, transform.SameTree, err
|
||||
}
|
||||
}
|
||||
|
||||
return node, same, nil
|
||||
}
|
||||
|
||||
// transformValuesNode transforms a plan.Values or plan.ValueDerivedTable node to use common types
|
||||
func transformValuesNode(ctx *sql.Context, n sql.Node) (sql.Node, transform.TreeIdentity, error) {
|
||||
var values *plan.Values
|
||||
var expressionerNode sql.Expressioner
|
||||
switch v := n.(type) {
|
||||
case *plan.ValueDerivedTable:
|
||||
values = v.Values
|
||||
expressionerNode = v
|
||||
case *plan.Values:
|
||||
values = v
|
||||
expressionerNode = v
|
||||
default:
|
||||
return n, transform.SameTree, nil
|
||||
}
|
||||
|
||||
// Skip if no rows or single row (nothing to unify)
|
||||
if len(values.ExpressionTuples) <= 1 {
|
||||
return n, transform.SameTree, nil
|
||||
}
|
||||
numCols := len(values.ExpressionTuples[0])
|
||||
for i := 1; i < len(values.ExpressionTuples); i++ {
|
||||
if len(values.ExpressionTuples[i]) != numCols {
|
||||
return nil, transform.NewTree, errors.Errorf("VALUES: row %d has %d columns, expected %d", i+1, len(values.ExpressionTuples[i]), numCols)
|
||||
}
|
||||
}
|
||||
if numCols == 0 {
|
||||
return n, transform.SameTree, nil
|
||||
}
|
||||
|
||||
// Collect types for each column across all rows
|
||||
columnTypes := make([][]*pgtypes.DoltgresType, numCols)
|
||||
for colIdx := 0; colIdx < numCols; colIdx++ {
|
||||
columnTypes[colIdx] = make([]*pgtypes.DoltgresType, len(values.ExpressionTuples))
|
||||
for rowIdx, row := range values.ExpressionTuples {
|
||||
exprType := row[colIdx].Type(ctx)
|
||||
if exprType == nil {
|
||||
columnTypes[colIdx][rowIdx] = pgtypes.Unknown
|
||||
} else if pgType, ok := exprType.(*pgtypes.DoltgresType); ok {
|
||||
columnTypes[colIdx][rowIdx] = pgType
|
||||
} else {
|
||||
return nil, transform.NewTree, errors.Errorf("VALUES: non-Doltgres type found in row %d, column %d: %s", rowIdx, colIdx, exprType.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find common type for each column
|
||||
var newTuples [][]sql.Expression
|
||||
for colIdx := 0; colIdx < numCols; colIdx++ {
|
||||
commonType, requiresCasts, err := framework.FindCommonType(ctx, columnTypes[colIdx])
|
||||
if err != nil {
|
||||
return nil, transform.NewTree, err
|
||||
}
|
||||
// If we require any casts, then we'll add casting to all expressions in the list
|
||||
if requiresCasts {
|
||||
if len(newTuples) == 0 {
|
||||
// Deep copy to avoid mutating the original expression tuples.
|
||||
newTuples = make([][]sql.Expression, len(values.ExpressionTuples))
|
||||
for i, row := range values.ExpressionTuples {
|
||||
newTuples[i] = make([]sql.Expression, len(row))
|
||||
copy(newTuples[i], row)
|
||||
}
|
||||
}
|
||||
for rowIdx := 0; rowIdx < len(newTuples); rowIdx++ {
|
||||
newTuples[rowIdx][colIdx] = pgexprs.NewImplicitCast(
|
||||
newTuples[rowIdx][colIdx], columnTypes[colIdx][rowIdx], commonType)
|
||||
}
|
||||
}
|
||||
}
|
||||
// If we didn't require any casts, then we can simply return our old node
|
||||
if len(newTuples) == 0 {
|
||||
return n, transform.SameTree, nil
|
||||
}
|
||||
|
||||
// Flatten the new tuples into a single expression slice for WithExpressions
|
||||
flatExprs := make([]sql.Expression, 0, len(newTuples)*len(newTuples[0]))
|
||||
for _, row := range newTuples {
|
||||
flatExprs = append(flatExprs, row...)
|
||||
}
|
||||
newNode, err := expressionerNode.WithExpressions(ctx, flatExprs...)
|
||||
if err != nil {
|
||||
return nil, transform.NewTree, err
|
||||
}
|
||||
return newNode, transform.NewTree, nil
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
// 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 analyzer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"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/analyzer"
|
||||
"github.com/dolthub/go-mysql-server/sql/expression"
|
||||
"github.com/dolthub/go-mysql-server/sql/plan"
|
||||
"github.com/dolthub/go-mysql-server/sql/transform"
|
||||
"github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/core"
|
||||
"github.com/dolthub/doltgresql/core/id"
|
||||
"github.com/dolthub/doltgresql/core/sequences"
|
||||
"github.com/dolthub/doltgresql/server/ast"
|
||||
"github.com/dolthub/doltgresql/server/auth"
|
||||
pgexprs "github.com/dolthub/doltgresql/server/expression"
|
||||
"github.com/dolthub/doltgresql/server/functions"
|
||||
"github.com/dolthub/doltgresql/server/functions/framework"
|
||||
pgnodes "github.com/dolthub/doltgresql/server/node"
|
||||
pgtypes "github.com/dolthub/doltgresql/server/types"
|
||||
)
|
||||
|
||||
// maxSequenceAutoNames is the maximum number of otherwise-identical sequence names that can be generated before
|
||||
// resulting in an error. Under normal operation, sequence names should be automatically cleaned up when their table
|
||||
// gets dropped, so except in extremely unusual circumstances this limit should never be reached. If it is, it's
|
||||
// probably an indicator of a bug in sequence cleanup (or an extremely large schema).
|
||||
const maxSequenceAutoNames = 10_000
|
||||
|
||||
// ReplaceSerial replaces a CreateTable node containing a SERIAL type with a node that can create sequences alongside
|
||||
// the table.
|
||||
func ReplaceSerial(ctx *sql.Context, a *analyzer.Analyzer, node sql.Node, scope *plan.Scope, selector analyzer.RuleSelector, qFlags *sql.QueryFlags) (sql.Node, transform.TreeIdentity, error) {
|
||||
createTable, ok := node.(*plan.CreateTable)
|
||||
if !ok {
|
||||
return node, transform.SameTree, nil
|
||||
}
|
||||
|
||||
var ctSequences []*pgnodes.CreateSequence
|
||||
for _, col := range createTable.PkSchema().Schema {
|
||||
doltgresType, isDoltgresType := col.Type.(*pgtypes.DoltgresType)
|
||||
if !isDoltgresType || !doltgresType.IsSerial {
|
||||
continue
|
||||
}
|
||||
|
||||
// For always-generated columns we insert a placeholder sequence to be replaced by the actual sequence name. We
|
||||
// detect that here and treat these generated columns differently than other generated columns on serial types.
|
||||
isGeneratedFromSequence := false
|
||||
if col.Generated != nil {
|
||||
seenNextVal := false
|
||||
transform.InspectExpr(ctx, col.Generated, func(ctx *sql.Context, expr sql.Expression) bool {
|
||||
switch e := expr.(type) {
|
||||
case *framework.CompiledFunction:
|
||||
if strings.ToLower(e.Name) == "nextval" {
|
||||
seenNextVal = true
|
||||
}
|
||||
case *expression.Literal:
|
||||
placeholderName := fmt.Sprintf("'%s'", ast.DoltCreateTablePlaceholderSequenceName)
|
||||
if e.String() == placeholderName {
|
||||
isGeneratedFromSequence = true
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
if !seenNextVal && !isGeneratedFromSequence {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
schemaName, err := core.GetSchemaName(ctx, createTable.Db, "")
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
sequenceName, err := generateSequenceName(ctx, createTable, col, schemaName)
|
||||
if err != nil {
|
||||
return nil, transform.NewTree, err
|
||||
}
|
||||
|
||||
// TODO: need better way to detect sequence usage
|
||||
err = authCheckSequence(ctx, a.Catalog.AuthHandler, schemaName, sequenceName)
|
||||
if err != nil {
|
||||
return nil, transform.SameTree, err
|
||||
}
|
||||
|
||||
seqName := doltdb.TableName{Name: sequenceName, Schema: schemaName}.String()
|
||||
nextVal, isDoltgresType, err := framework.GetFunction(ctx, "nextval", pgexprs.NewTextLiteral(seqName))
|
||||
if err != nil {
|
||||
return nil, transform.NewTree, err
|
||||
}
|
||||
if !isDoltgresType {
|
||||
return nil, transform.NewTree, errors.Errorf(`function "nextval" could not be found for SERIAL default`)
|
||||
}
|
||||
|
||||
nextValExpr := &sql.ColumnDefaultValue{
|
||||
Expr: nextVal,
|
||||
OutType: pgtypes.Int64,
|
||||
Literal: false,
|
||||
ReturnNil: false,
|
||||
Parenthesized: false,
|
||||
}
|
||||
|
||||
if isGeneratedFromSequence {
|
||||
col.Generated = nextValExpr
|
||||
} else {
|
||||
col.Default = nextValExpr
|
||||
}
|
||||
|
||||
var maxValue int64
|
||||
switch doltgresType.Name() {
|
||||
case "smallserial":
|
||||
col.Type = pgtypes.Int16
|
||||
maxValue = 32767
|
||||
case "serial":
|
||||
col.Type = pgtypes.Int32
|
||||
maxValue = 2147483647
|
||||
case "bigserial":
|
||||
col.Type = pgtypes.Int64
|
||||
maxValue = 9223372036854775807
|
||||
}
|
||||
|
||||
ctSequences = append(ctSequences, pgnodes.NewCreateSequence(false, "", false, &sequences.Sequence{
|
||||
Id: id.NewSequence("", sequenceName),
|
||||
DataTypeID: col.Type.(*pgtypes.DoltgresType).ID,
|
||||
Persistence: sequences.Persistence_Permanent,
|
||||
Start: 1,
|
||||
Current: 1,
|
||||
Increment: 1,
|
||||
Minimum: 1,
|
||||
Maximum: maxValue,
|
||||
Cache: 1,
|
||||
Cycle: false,
|
||||
IsAtEnd: false,
|
||||
OwnerTable: id.NewTable("", createTable.Name()),
|
||||
OwnerColumn: col.Name,
|
||||
}))
|
||||
}
|
||||
return pgnodes.NewCreateTable(createTable, ctSequences), transform.NewTree, nil
|
||||
}
|
||||
|
||||
// generateSequenceName generates a unique sequence name for a SERIAL column in the table given
|
||||
func generateSequenceName(ctx *sql.Context, createTable *plan.CreateTable, col *sql.Column, schemaName string) (string, error) {
|
||||
baseSequenceName := fmt.Sprintf("%s_%s_seq", createTable.Name(), col.Name)
|
||||
sequenceName := baseSequenceName
|
||||
relationType, err := core.GetRelationType(ctx, schemaName, baseSequenceName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if relationType != core.RelationType_DoesNotExist {
|
||||
seqIndex := 1
|
||||
for ; seqIndex <= maxSequenceAutoNames; seqIndex++ {
|
||||
sequenceName = fmt.Sprintf("%s%d", baseSequenceName, seqIndex)
|
||||
relationType, err = core.GetRelationType(ctx, schemaName, sequenceName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if relationType == core.RelationType_DoesNotExist {
|
||||
break
|
||||
}
|
||||
}
|
||||
if seqIndex > maxSequenceAutoNames {
|
||||
return "", errors.Errorf("SERIAL sequence name reached max iterations")
|
||||
}
|
||||
}
|
||||
return sequenceName, nil
|
||||
}
|
||||
|
||||
// authCheckSequenceFromExpr checks authorization of sequence being used.
|
||||
// It parses schema and sequence names out of given expression.
|
||||
// There can be only one argument expression of string type.
|
||||
func authCheckSequenceFromExpr(ctx *sql.Context, ah sql.AuthorizationHandler, arg sql.Expression) error {
|
||||
schemaName, seqName, err := functions.ParseRelationName(ctx, strings.Trim(arg.String(), "'"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return authCheckSequence(ctx, ah, schemaName, seqName)
|
||||
}
|
||||
|
||||
// authCheckSequence checks authorization of sequence being used. We cannot check it during parsing because we cannot
|
||||
// detect sequence currently, so we try to catch any sequence being used and check authorization here.
|
||||
func authCheckSequence(ctx *sql.Context, ah sql.AuthorizationHandler, schemaName, seqName string) error {
|
||||
if err := ah.HandleAuth(ctx, ah.NewQueryState(ctx), sqlparser.AuthInformation{
|
||||
AuthType: auth.AuthType_USAGE,
|
||||
TargetType: auth.AuthTargetType_SequenceIdentifiers,
|
||||
TargetNames: []string{schemaName, seqName},
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// 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 analyzer
|
||||
|
||||
import (
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
"github.com/dolthub/go-mysql-server/sql/analyzer"
|
||||
"github.com/dolthub/go-mysql-server/sql/plan"
|
||||
"github.com/dolthub/go-mysql-server/sql/transform"
|
||||
|
||||
"github.com/dolthub/doltgresql/core"
|
||||
)
|
||||
|
||||
// SetRunner sets the StatementRunner in the context.
|
||||
func SetRunner(ctx *sql.Context, a *analyzer.Analyzer, node sql.Node, scope *plan.Scope, selector analyzer.RuleSelector, qFlags *sql.QueryFlags) (sql.Node, transform.TreeIdentity, error) {
|
||||
return node, transform.SameTree, core.SetRunnerOnContext(ctx, a.Runner)
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
// 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 analyzer
|
||||
|
||||
import (
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
"github.com/dolthub/go-mysql-server/sql/expression"
|
||||
|
||||
pgexprs "github.com/dolthub/doltgresql/server/expression"
|
||||
pgtypes "github.com/dolthub/doltgresql/server/types"
|
||||
)
|
||||
|
||||
// splitConjunction breaks AND expressions into their left and right parts, recursively.
|
||||
func splitConjunction(ctx *sql.Context, expr sql.Expression) []sql.Expression {
|
||||
if expr == nil {
|
||||
return nil
|
||||
}
|
||||
switch expr := expr.(type) {
|
||||
case *expression.And:
|
||||
return append(
|
||||
splitConjunction(ctx, expr.LeftChild),
|
||||
splitConjunction(ctx, expr.RightChild)...,
|
||||
)
|
||||
case *pgexprs.GMSCast:
|
||||
// We should check to see if we need to preserve the cast on each child individually
|
||||
split := splitConjunction(ctx, expr.Child())
|
||||
for i := range split {
|
||||
if _, ok := split[i].Type(ctx).(*pgtypes.DoltgresType); !ok {
|
||||
split[i] = pgexprs.NewGMSCast(split[i])
|
||||
}
|
||||
}
|
||||
return split
|
||||
default:
|
||||
return []sql.Expression{expr}
|
||||
}
|
||||
}
|
||||
|
||||
// LogicTreeWalker is a walker that removes GMSCast and other Doltgres specific expression nodes from
|
||||
// logic expression trees. This allows the analyzer logic to correctly reason about expressions in filters
|
||||
// to apply indexes.
|
||||
type LogicTreeWalker struct{}
|
||||
|
||||
var _ sql.ExpressionTreeFilter = &LogicTreeWalker{}
|
||||
|
||||
// Next implements the sql.ExpressionTreeFilter interface.
|
||||
func (l *LogicTreeWalker) Next(e sql.Expression) sql.Expression {
|
||||
switch expr := e.(type) {
|
||||
case *pgexprs.GMSCast:
|
||||
return l.Next(expr.Child())
|
||||
default:
|
||||
return e
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,334 @@
|
||||
// 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 analyzer
|
||||
|
||||
import (
|
||||
"github.com/dolthub/doltgresql/server/expression"
|
||||
"github.com/dolthub/doltgresql/server/functions/framework"
|
||||
"github.com/dolthub/doltgresql/server/types"
|
||||
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
"github.com/dolthub/go-mysql-server/sql/analyzer"
|
||||
gmsexpr "github.com/dolthub/go-mysql-server/sql/expression"
|
||||
"github.com/dolthub/go-mysql-server/sql/plan"
|
||||
"github.com/dolthub/go-mysql-server/sql/transform"
|
||||
)
|
||||
|
||||
// TransformRecordFilter finds Filter nodes over Indexable tables with composite indexes over Record columns and
|
||||
// decomposes the Record comparison into an equivalent filter expression over each column joined by ANDs and ORs.
|
||||
func TransformRecordFilter(
|
||||
ctx *sql.Context,
|
||||
_ *analyzer.Analyzer,
|
||||
node sql.Node,
|
||||
_ *plan.Scope,
|
||||
_ analyzer.RuleSelector,
|
||||
_ *sql.QueryFlags,
|
||||
) (sql.Node, transform.TreeIdentity, error) {
|
||||
return transform.Node(ctx, node, func(ctx *sql.Context, n sql.Node) (sql.Node, transform.TreeIdentity, error) {
|
||||
filter, ok := n.(*plan.Filter)
|
||||
if !ok {
|
||||
return n, transform.SameTree, nil
|
||||
}
|
||||
|
||||
var tblNode sql.TableNode
|
||||
switch child := filter.Child.(type) {
|
||||
case *plan.ResolvedTable:
|
||||
tblNode = child
|
||||
case *plan.TableAlias:
|
||||
tblNode, _ = child.Child.(sql.TableNode)
|
||||
default:
|
||||
return n, transform.SameTree, nil
|
||||
}
|
||||
// TODO: should only convert expressions when there's applicable index
|
||||
if _, ok = tblNode.UnderlyingTable().(sql.IndexAddressableTable); !ok {
|
||||
return n, transform.SameTree, nil
|
||||
}
|
||||
|
||||
newExpr, same, err := decomposeRecordFilter(ctx, filter.Expression)
|
||||
if err != nil {
|
||||
return nil, transform.SameTree, err
|
||||
}
|
||||
if same {
|
||||
return n, transform.SameTree, nil
|
||||
}
|
||||
return plan.NewFilter(ctx, newExpr, filter.Child), transform.NewTree, nil
|
||||
})
|
||||
}
|
||||
|
||||
// decomposeRecordFilter is the helper function to TransformRecordFilter that decomposes the Record comparison.
|
||||
func decomposeRecordFilter(ctx *sql.Context, expr sql.Expression) (sql.Expression, transform.TreeIdentity, error) {
|
||||
return transform.Expr(ctx, expr,
|
||||
func(ctx *sql.Context, e sql.Expression) (sql.Expression, transform.TreeIdentity, error) {
|
||||
binExpr, ok := e.(*expression.BinaryOperator)
|
||||
if !ok {
|
||||
return e, transform.SameTree, nil
|
||||
}
|
||||
// TODO: possible for Literal to be on left
|
||||
recExpr, ok := binExpr.Left().(*expression.RecordExpr)
|
||||
if !ok {
|
||||
return e, transform.SameTree, nil
|
||||
}
|
||||
recExprs := recExpr.Expressions()
|
||||
|
||||
// TODO: possible for RecordExpr to be on right
|
||||
litExpr, ok := binExpr.Right().(*gmsexpr.Literal)
|
||||
if !ok {
|
||||
return e, transform.SameTree, nil
|
||||
}
|
||||
recVals, ok := litExpr.Val.([]types.RecordValue)
|
||||
if !ok {
|
||||
return e, transform.SameTree, nil
|
||||
}
|
||||
|
||||
var newExpr sql.Expression
|
||||
var err error
|
||||
switch binExpr.Operator() {
|
||||
case framework.Operator_BinaryEqual:
|
||||
newExpr, err = decomposeRecordFilterEquals(ctx, recExprs, recVals)
|
||||
case framework.Operator_BinaryLessThan:
|
||||
newExpr, err = decomposeRecordFilterLessThan(ctx, recExprs, recVals)
|
||||
case framework.Operator_BinaryGreaterThan:
|
||||
newExpr, err = decomposeRecordFilterGreaterThan(ctx, recExprs, recVals)
|
||||
case framework.Operator_BinaryLessOrEqual:
|
||||
newExpr, err = decomposeRecordFilterLessThanEquals(ctx, recExprs, recVals)
|
||||
case framework.Operator_BinaryGreaterOrEqual:
|
||||
newExpr, err = decomposeRecordFilterGreaterThanEquals(ctx, recExprs, recVals)
|
||||
default:
|
||||
return e, transform.SameTree, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, transform.SameTree, err
|
||||
}
|
||||
return newExpr, transform.NewTree, nil
|
||||
})
|
||||
}
|
||||
|
||||
// decomposeRecordFilterEquals splits up (i, j, ...) = (x, y, ...) expression into (i = x) AND (j = y) AND ...
|
||||
func decomposeRecordFilterEquals(
|
||||
ctx *sql.Context,
|
||||
recExprs []sql.Expression,
|
||||
recVals []types.RecordValue,
|
||||
) (sql.Expression, error) {
|
||||
n := len(recExprs)
|
||||
exprs := make([]sql.Expression, n)
|
||||
for i := 0; i < n; i++ {
|
||||
newLit := gmsexpr.NewLiteral(recVals[i].Value, recVals[i].Type)
|
||||
expr, err := expression.NewBinaryOperator(framework.Operator_BinaryEqual).WithChildren(ctx, recExprs[i], newLit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
exprs[i] = expr
|
||||
}
|
||||
return gmsexpr.JoinAnd(exprs...), nil
|
||||
}
|
||||
|
||||
// decomposeRecordFilterLessThan splits up (i, j, ..., k) < (x, y, ..., z) into ((i = x) OR (j = y) ... OR (k < z)) AND ...
|
||||
func decomposeRecordFilterLessThan(
|
||||
ctx *sql.Context,
|
||||
recExprs []sql.Expression,
|
||||
recVals []types.RecordValue,
|
||||
) (sql.Expression, error) {
|
||||
n := len(recExprs)
|
||||
orExprs := make([]sql.Expression, n)
|
||||
for i := 0; i < n; i++ {
|
||||
andExprs := make([]sql.Expression, n-i)
|
||||
for j := 0; j < n-i-1; j++ {
|
||||
newLit := gmsexpr.NewLiteral(recVals[j].Value, recVals[j].Type)
|
||||
expr, err := expression.NewBinaryOperator(framework.Operator_BinaryEqual).WithChildren(
|
||||
ctx,
|
||||
recExprs[j],
|
||||
newLit,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
andExprs[j] = expr
|
||||
}
|
||||
newLit := gmsexpr.NewLiteral(recVals[n-i-1].Value, recVals[n-i-1].Type)
|
||||
expr, err := expression.NewBinaryOperator(framework.Operator_BinaryLessThan).WithChildren(
|
||||
ctx,
|
||||
recExprs[i],
|
||||
newLit,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
andExprs[n-i-1] = expr
|
||||
orExprs[i] = gmsexpr.JoinAnd(andExprs...)
|
||||
}
|
||||
return gmsexpr.JoinOr(orExprs...), nil
|
||||
}
|
||||
|
||||
// decomposeRecordFilterLessThan splits up (i, j, ..., k) > (x, y, ..., z) into ((i = x) OR (j = y) ... OR (k > z)) AND ...
|
||||
func decomposeRecordFilterGreaterThan(
|
||||
ctx *sql.Context,
|
||||
recExprs []sql.Expression,
|
||||
recVals []types.RecordValue,
|
||||
) (sql.Expression, error) {
|
||||
n := len(recExprs)
|
||||
orExprs := make([]sql.Expression, n)
|
||||
for i := 0; i < n; i++ {
|
||||
andExprs := make([]sql.Expression, n-i)
|
||||
for j := 0; j < n-i-1; j++ {
|
||||
newLit := gmsexpr.NewLiteral(recVals[j].Value, recVals[j].Type)
|
||||
expr, err := expression.NewBinaryOperator(framework.Operator_BinaryEqual).WithChildren(
|
||||
ctx,
|
||||
recExprs[j],
|
||||
newLit,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
andExprs[j] = expr
|
||||
}
|
||||
newLit := gmsexpr.NewLiteral(recVals[n-i-1].Value, recVals[n-i-1].Type)
|
||||
expr, err := expression.NewBinaryOperator(framework.Operator_BinaryGreaterThan).WithChildren(
|
||||
ctx,
|
||||
recExprs[n-i-1],
|
||||
newLit,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
andExprs[n-i-1] = expr
|
||||
orExprs[i] = gmsexpr.JoinAnd(andExprs...)
|
||||
}
|
||||
return gmsexpr.JoinOr(orExprs...), nil
|
||||
}
|
||||
|
||||
// decomposeRecordFilterLessThanEquals splits up (i, j, ..., k) <= (x, y, ..., z) into ((i = x) OR (j = y) ... OR (k <= z)) AND ...
|
||||
func decomposeRecordFilterLessThanEquals(
|
||||
ctx *sql.Context,
|
||||
recExprs []sql.Expression,
|
||||
recVals []types.RecordValue,
|
||||
) (sql.Expression, error) {
|
||||
n := len(recExprs)
|
||||
orExprs := make([]sql.Expression, n)
|
||||
andExprs := make([]sql.Expression, n)
|
||||
for i := 0; i < n-1; i++ {
|
||||
newLit := gmsexpr.NewLiteral(recVals[i].Value, recVals[i].Type)
|
||||
expr, err := expression.NewBinaryOperator(framework.Operator_BinaryEqual).WithChildren(
|
||||
ctx,
|
||||
recExprs[i],
|
||||
newLit,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
andExprs[i] = expr
|
||||
}
|
||||
newLit := gmsexpr.NewLiteral(recVals[n-1].Value, recVals[n-1].Type)
|
||||
expr, err := expression.NewBinaryOperator(framework.Operator_BinaryLessOrEqual).WithChildren(
|
||||
ctx,
|
||||
recExprs[n-1],
|
||||
newLit,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
andExprs[n-1] = expr
|
||||
orExprs[0] = gmsexpr.JoinAnd(andExprs...)
|
||||
|
||||
for i := 1; i < n; i++ {
|
||||
andExprs = make([]sql.Expression, n-i)
|
||||
for j := 0; j < n-i-1; j++ {
|
||||
newLit = gmsexpr.NewLiteral(recVals[j].Value, recVals[j].Type)
|
||||
expr, err = expression.NewBinaryOperator(framework.Operator_BinaryEqual).WithChildren(
|
||||
ctx,
|
||||
recExprs[j],
|
||||
newLit,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
andExprs[j] = expr
|
||||
}
|
||||
newLit = gmsexpr.NewLiteral(recVals[n-i-1].Value, recVals[n-i-1].Type)
|
||||
expr, err = expression.NewBinaryOperator(framework.Operator_BinaryLessThan).WithChildren(
|
||||
ctx,
|
||||
recExprs[n-i-1],
|
||||
newLit,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
andExprs[n-i-1] = expr
|
||||
orExprs[i] = gmsexpr.JoinAnd(andExprs...)
|
||||
}
|
||||
return gmsexpr.JoinOr(orExprs...), nil
|
||||
}
|
||||
|
||||
// decomposeRecordFilterGreaterThanEquals splits up (i, j, ..., k) >= (x, y, ..., z) into ((i = x) OR (j = y) ... OR (k >= z)) AND ...
|
||||
func decomposeRecordFilterGreaterThanEquals(
|
||||
ctx *sql.Context,
|
||||
recExprs []sql.Expression,
|
||||
recVals []types.RecordValue,
|
||||
) (sql.Expression, error) {
|
||||
n := len(recExprs)
|
||||
orExprs := make([]sql.Expression, n)
|
||||
andExprs := make([]sql.Expression, n)
|
||||
for i := 0; i < n-1; i++ {
|
||||
newLit := gmsexpr.NewLiteral(recVals[i].Value, recVals[i].Type)
|
||||
expr, err := expression.NewBinaryOperator(framework.Operator_BinaryEqual).WithChildren(
|
||||
ctx,
|
||||
recExprs[i],
|
||||
newLit,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
andExprs[i] = expr
|
||||
}
|
||||
|
||||
newLit := gmsexpr.NewLiteral(recVals[n-1].Value, recVals[n-1].Type)
|
||||
expr, err := expression.NewBinaryOperator(framework.Operator_BinaryGreaterOrEqual).WithChildren(
|
||||
ctx,
|
||||
recExprs[n-1],
|
||||
newLit,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
andExprs[n-1] = expr
|
||||
orExprs[0] = gmsexpr.JoinAnd(andExprs...)
|
||||
|
||||
for i := 1; i < n; i++ {
|
||||
andExprs = make([]sql.Expression, n-i)
|
||||
for j := 0; j < n-i-1; j++ {
|
||||
newLit = gmsexpr.NewLiteral(recVals[j].Value, recVals[j].Type)
|
||||
expr, err = expression.NewBinaryOperator(framework.Operator_BinaryEqual).WithChildren(
|
||||
ctx,
|
||||
recExprs[j],
|
||||
newLit,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
andExprs[j] = expr
|
||||
}
|
||||
newLit = gmsexpr.NewLiteral(recVals[n-i-1].Value, recVals[n-i-1].Type)
|
||||
expr, err = expression.NewBinaryOperator(framework.Operator_BinaryGreaterThan).WithChildren(
|
||||
ctx,
|
||||
recExprs[n-i-1],
|
||||
newLit,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
andExprs[n-i-1] = expr
|
||||
orExprs[i] = gmsexpr.JoinAnd(andExprs...)
|
||||
}
|
||||
return gmsexpr.JoinOr(orExprs...), nil
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
// 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 analyzer
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cockroachdb/apd/v3"
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
"github.com/dolthub/go-mysql-server/sql/analyzer"
|
||||
"github.com/dolthub/go-mysql-server/sql/expression"
|
||||
"github.com/dolthub/go-mysql-server/sql/plan"
|
||||
"github.com/dolthub/go-mysql-server/sql/transform"
|
||||
"github.com/dolthub/go-mysql-server/sql/types"
|
||||
"github.com/dolthub/vitess/go/vt/proto/query"
|
||||
|
||||
pgexprs "github.com/dolthub/doltgresql/server/expression"
|
||||
"github.com/dolthub/doltgresql/server/functions/framework"
|
||||
pgtransform "github.com/dolthub/doltgresql/server/transform"
|
||||
pgtypes "github.com/dolthub/doltgresql/server/types"
|
||||
)
|
||||
|
||||
// TypeSanitizer converts all GMS types into Doltgres types. Some places, such as parameter binding, will always default
|
||||
// to GMS types, so by taking care of all conversions here, we can ensure that Doltgres only needs to worry about its
|
||||
// own types.
|
||||
func TypeSanitizer(ctx *sql.Context, a *analyzer.Analyzer, node sql.Node, scope *plan.Scope, selector analyzer.RuleSelector, qFlags *sql.QueryFlags) (sql.Node, transform.TreeIdentity, error) {
|
||||
// TODO: this probably should not be opaque, we should let the analyzer dig into subqueries and analyze them when
|
||||
// it chooses. Doing all type transformations upfront like this masks bugs where certain tyupe conversion errors
|
||||
// only manifest in a subquery
|
||||
return pgtransform.NodeExprsWithNodeWithOpaque(ctx, node, func(ctx *sql.Context, n sql.Node, expr sql.Expression) (sql.Expression, transform.TreeIdentity, error) {
|
||||
// This can be updated if we find more expressions that return GMS types.
|
||||
// These should eventually be replaced with Doltgres-equivalents over time, rendering this function unnecessary.
|
||||
switch expr := expr.(type) {
|
||||
case *expression.GetField:
|
||||
switch n := n.(type) {
|
||||
case *plan.Project, *plan.Filter, *plan.GroupBy:
|
||||
child := n.Children()[0]
|
||||
// Some dolt_ tables do not have doltgres types for their columns, so we convert them here
|
||||
if rt, ok := child.(*plan.ResolvedTable); ok && strings.HasPrefix(rt.Name(), "dolt_") {
|
||||
// This is a projection on a table, so we can safely convert the type
|
||||
if _, ok := expr.Type(ctx).(*pgtypes.DoltgresType); !ok {
|
||||
return pgexprs.NewGMSCast(expr), transform.NewTree, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return expr, transform.SameTree, nil
|
||||
case *expression.Literal:
|
||||
// We want to leave limit literals alone, as they are expected to be GMS types when they appear in certain
|
||||
// parts of the query (subqueries in particular)
|
||||
// TODO: fix the limit and offset validation analysis to handle doltgres types
|
||||
if _, isLimit := n.(*plan.Limit); isLimit {
|
||||
break
|
||||
}
|
||||
if _, isOffset := n.(*plan.Offset); isOffset {
|
||||
break
|
||||
}
|
||||
return typeSanitizerLiterals(ctx, expr)
|
||||
case *expression.Not, *expression.And, *expression.Or, *expression.Like:
|
||||
return pgexprs.NewGMSCast(expr), transform.NewTree, nil
|
||||
case sql.FunctionExpression:
|
||||
// Compiled functions are Doltgres functions. We're only concerned with GMS functions.
|
||||
if _, ok := expr.(framework.Function); !ok {
|
||||
// Some aggregation functions cannot be wrapped due to expectations in the analyzer, so we exclude them here.
|
||||
switch expr.FunctionName() {
|
||||
case "Count", "CountDistinct", "group_concat", "JSONObjectAgg", "Sum":
|
||||
case "coalesce":
|
||||
// Replace GMS Coalesce with a Doltgres-native implementation that uses
|
||||
// Postgres type-resolution rules (FindCommonType) to infer the result type.
|
||||
// GMS's Coalesce.Type() falls back to LongText when its arguments are
|
||||
// DoltgresTypes because they don't satisfy GMS's IsNumber/IsText checks.
|
||||
if _, isPgCoalesce := expr.(*pgexprs.PgCoalesce); !isPgCoalesce {
|
||||
children := expr.Children()
|
||||
allDoltgresTypes := true
|
||||
for _, child := range children {
|
||||
if _, ok := child.Type(ctx).(*pgtypes.DoltgresType); !ok {
|
||||
allDoltgresTypes = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if allDoltgresTypes {
|
||||
pgCoalesce, err := pgexprs.NewPgCoalesce(ctx, children...)
|
||||
if err != nil {
|
||||
return nil, transform.NewTree, err
|
||||
}
|
||||
return pgCoalesce, transform.NewTree, nil
|
||||
}
|
||||
}
|
||||
// Fall through to GMSCast if children aren't DoltgresTypes yet.
|
||||
if _, ok := expr.Type(ctx).(*pgtypes.DoltgresType); !ok {
|
||||
return pgexprs.NewGMSCast(expr), transform.NewTree, nil
|
||||
}
|
||||
default:
|
||||
// Some GMS functions wrap Doltgres parameters, so we'll only handle those that return GMS types
|
||||
if _, ok := expr.Type(ctx).(*pgtypes.DoltgresType); !ok {
|
||||
return pgexprs.NewGMSCast(expr), transform.NewTree, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
case *plan.ExistsSubquery:
|
||||
return pgexprs.NewExplicitCast(pgexprs.NewGMSCast(expr), pgtypes.Bool), transform.NewTree, nil
|
||||
case *sql.ColumnDefaultValue:
|
||||
// Due to how interfaces work, we sometimes pass (*ColumnDefaultValue)(nil), so we have to check for it
|
||||
if expr != nil && expr.Expr != nil {
|
||||
defaultExpr := expr.Expr
|
||||
if _, ok := defaultExpr.Type(ctx).(*pgtypes.DoltgresType); !ok {
|
||||
defaultExpr = pgexprs.NewGMSCast(defaultExpr)
|
||||
}
|
||||
defaultExprType := defaultExpr.Type(ctx).(*pgtypes.DoltgresType)
|
||||
outType, ok := expr.OutType.(*pgtypes.DoltgresType)
|
||||
if !ok {
|
||||
return nil, transform.NewTree, errors.Errorf("default values must have a non-GMS OutType: `%s`", expr.OutType.String())
|
||||
}
|
||||
if !outType.Equals(defaultExprType) {
|
||||
defaultExpr = pgexprs.NewAssignmentCast(defaultExpr, defaultExprType, outType)
|
||||
}
|
||||
newDefault, err := sql.NewColumnDefaultValue(defaultExpr, outType, expr.Literal, expr.Parenthesized, expr.ReturnNil)
|
||||
return newDefault, transform.NewTree, err
|
||||
}
|
||||
}
|
||||
return expr, transform.SameTree, nil
|
||||
})
|
||||
}
|
||||
|
||||
// typeSanitizerLiterals handles literal expressions for TypeSanitizer.
|
||||
func typeSanitizerLiterals(ctx *sql.Context, gmsLiteral *expression.Literal) (sql.Expression, transform.TreeIdentity, error) {
|
||||
// GMS may resolve Doltgres literals and then stick them in GMS literals, so we have to account for that here
|
||||
if doltgresType, ok := gmsLiteral.Type(ctx).(*pgtypes.DoltgresType); ok {
|
||||
return pgexprs.NewUnsafeLiteral(gmsLiteral.Value(), doltgresType), transform.NewTree, nil
|
||||
}
|
||||
switch gmsLiteral.Type(ctx).Type() {
|
||||
case query.Type_INT8, query.Type_INT16, query.Type_YEAR, query.Type_INT24, query.Type_INT32:
|
||||
newVal, _, err := types.Int32.Convert(ctx, gmsLiteral.Value())
|
||||
if err != nil {
|
||||
return nil, transform.NewTree, err
|
||||
}
|
||||
if newVal == nil {
|
||||
return pgexprs.NewNullLiteral(), transform.NewTree, nil
|
||||
}
|
||||
return pgexprs.NewRawLiteralInt32(newVal.(int32)), transform.NewTree, nil
|
||||
case query.Type_INT64, query.Type_ENUM:
|
||||
newVal, _, err := types.Int64.Convert(ctx, gmsLiteral.Value())
|
||||
if err != nil {
|
||||
return nil, transform.NewTree, err
|
||||
}
|
||||
if newVal == nil {
|
||||
return pgexprs.NewNullLiteral(), transform.NewTree, nil
|
||||
}
|
||||
return pgexprs.NewRawLiteralInt64(newVal.(int64)), transform.NewTree, nil
|
||||
case query.Type_UINT8, query.Type_UINT16, query.Type_UINT24, query.Type_UINT32:
|
||||
newVal, _, err := types.Uint32.Convert(ctx, gmsLiteral.Value())
|
||||
if err != nil {
|
||||
return nil, transform.NewTree, err
|
||||
}
|
||||
if newVal == nil {
|
||||
return pgexprs.NewNullLiteral(), transform.NewTree, nil
|
||||
}
|
||||
return pgexprs.NewRawLiteralInt64(int64(newVal.(uint32))), transform.NewTree, nil
|
||||
case query.Type_UINT64, query.Type_SET:
|
||||
newVal, _, err := types.Uint64.Convert(ctx, gmsLiteral.Value())
|
||||
if err != nil {
|
||||
return nil, transform.NewTree, err
|
||||
}
|
||||
if newVal == nil {
|
||||
return pgexprs.NewNullLiteral(), transform.NewTree, nil
|
||||
}
|
||||
newLiteral, err := pgexprs.NewNumericLiteral(strconv.FormatUint(newVal.(uint64), 10))
|
||||
return newLiteral, transform.NewTree, err
|
||||
case query.Type_FLOAT32:
|
||||
newVal, _, err := types.Float32.Convert(ctx, gmsLiteral.Value())
|
||||
if err != nil {
|
||||
return nil, transform.NewTree, err
|
||||
}
|
||||
if newVal == nil {
|
||||
return pgexprs.NewNullLiteral(), transform.NewTree, nil
|
||||
}
|
||||
return pgexprs.NewRawLiteralFloat32(newVal.(float32)), transform.NewTree, nil
|
||||
case query.Type_FLOAT64:
|
||||
newVal, _, err := types.Float64.Convert(ctx, gmsLiteral.Value())
|
||||
if err != nil {
|
||||
return nil, transform.NewTree, err
|
||||
}
|
||||
if newVal == nil {
|
||||
return pgexprs.NewNullLiteral(), transform.NewTree, nil
|
||||
}
|
||||
return pgexprs.NewRawLiteralFloat64(newVal.(float64)), transform.NewTree, nil
|
||||
case query.Type_DECIMAL:
|
||||
dec, ok := gmsLiteral.Value().(*apd.Decimal)
|
||||
if !ok {
|
||||
return nil, transform.NewTree, errors.Errorf("SANITIZER: expected decimal type: %T", gmsLiteral.Value())
|
||||
}
|
||||
return pgexprs.NewRawLiteralNumeric(dec), transform.NewTree, nil
|
||||
case query.Type_DATE, query.Type_DATETIME, query.Type_TIMESTAMP:
|
||||
newVal, _, err := types.Datetime.Convert(ctx, gmsLiteral.Value())
|
||||
if err != nil {
|
||||
return nil, transform.NewTree, err
|
||||
}
|
||||
if newVal == nil {
|
||||
return pgexprs.NewNullLiteral(), transform.NewTree, nil
|
||||
}
|
||||
return pgexprs.NewRawLiteralTimestamp(newVal.(time.Time)), transform.NewTree, nil
|
||||
case query.Type_CHAR, query.Type_VARCHAR, query.Type_TEXT:
|
||||
str, ok := gmsLiteral.Value().(string)
|
||||
if !ok {
|
||||
return nil, transform.NewTree, errors.Errorf("SANITIZER: expected string type: %T", gmsLiteral.Value())
|
||||
}
|
||||
return pgexprs.NewUnknownLiteral(str), transform.NewTree, nil
|
||||
case query.Type_BINARY, query.Type_VARBINARY, query.Type_BLOB:
|
||||
newVal := gmsLiteral.Value()
|
||||
if newVal == nil {
|
||||
return pgexprs.NewNullLiteral(), transform.NewTree, nil
|
||||
} else if str, ok := newVal.(string); ok {
|
||||
return pgexprs.NewUnknownLiteral(str), transform.NewTree, nil
|
||||
} else if b, ok := newVal.([]byte); ok {
|
||||
return pgexprs.NewUnknownLiteral(string(b)), transform.NewTree, nil
|
||||
}
|
||||
return nil, transform.NewTree, errors.Errorf("SANITIZER: invalid binary type: %T", gmsLiteral.Value())
|
||||
case query.Type_JSON:
|
||||
newVal := gmsLiteral.Value()
|
||||
if newVal == nil {
|
||||
return pgexprs.NewNullLiteral(), transform.NewTree, nil
|
||||
}
|
||||
str, ok := newVal.(string)
|
||||
if !ok {
|
||||
return nil, transform.NewTree, errors.Errorf("SANITIZER: expected string type: %T", gmsLiteral.Value())
|
||||
}
|
||||
return pgexprs.NewUnknownLiteral(str), transform.NewTree, nil
|
||||
case query.Type_NULL_TYPE:
|
||||
return pgexprs.NewNullLiteral(), transform.NewTree, nil
|
||||
default:
|
||||
return nil, transform.NewTree, errors.Errorf("SANITIZER: encountered a GMS type that cannot be handled: %s", gmsLiteral.Type(ctx).String())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
// Copyright 2020-2021 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 analyzer
|
||||
|
||||
import (
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
"github.com/dolthub/go-mysql-server/sql/analyzer"
|
||||
"github.com/dolthub/go-mysql-server/sql/expression"
|
||||
"github.com/dolthub/go-mysql-server/sql/plan"
|
||||
"github.com/dolthub/go-mysql-server/sql/transform"
|
||||
|
||||
pgnode "github.com/dolthub/doltgresql/server/node"
|
||||
)
|
||||
|
||||
// ValidateColumnDefaults ensures that newly created column defaults from a DDL statement are legal for the type of
|
||||
// column, various other business logic checks to match MySQL's logic.
|
||||
func ValidateColumnDefaults(ctx *sql.Context, _ *analyzer.Analyzer, n sql.Node, _ *plan.Scope, _ analyzer.RuleSelector, qFlags *sql.QueryFlags) (sql.Node, transform.TreeIdentity, error) {
|
||||
span, ctx := ctx.Span("validateColumnDefaults")
|
||||
defer span.End()
|
||||
|
||||
return transform.Node(ctx, n, func(ctx *sql.Context, n sql.Node) (sql.Node, transform.TreeIdentity, error) {
|
||||
switch node := n.(type) {
|
||||
case *plan.AlterDefaultSet:
|
||||
table := getResolvedTable(node)
|
||||
sch := table.Schema(ctx)
|
||||
index := sch.IndexOfColName(node.ColumnName)
|
||||
if index == -1 {
|
||||
return nil, transform.SameTree, sql.ErrColumnNotFound.New(node.ColumnName)
|
||||
}
|
||||
col := sch[index]
|
||||
err := validateColumnDefault(ctx, col, node.Default)
|
||||
if err != nil {
|
||||
return node, transform.SameTree, err
|
||||
}
|
||||
|
||||
return node, transform.SameTree, nil
|
||||
|
||||
case sql.SchemaTarget:
|
||||
switch node.(type) {
|
||||
case *plan.AlterPK, *plan.AddColumn, *plan.ModifyColumn, *plan.AlterDefaultDrop, *plan.CreateTable, *plan.DropColumn, *pgnode.CreateTable:
|
||||
// DDL nodes must validate any new column defaults, continue to logic below
|
||||
default:
|
||||
// other node types are not altering the schema and therefore don't need validation of column defaults
|
||||
return n, transform.SameTree, nil
|
||||
}
|
||||
|
||||
// There may be multiple DDL nodes in the plan (ALTER TABLE statements can have many clauses), and for each of them
|
||||
// we need to count the column indexes in the very hacky way outlined above.
|
||||
i := 0
|
||||
return transform.NodeExprs(ctx, n, func(ctx *sql.Context, e sql.Expression) (sql.Expression, transform.TreeIdentity, error) {
|
||||
eWrapper, ok := e.(*expression.Wrapper)
|
||||
if !ok {
|
||||
return e, transform.SameTree, nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
i++
|
||||
}()
|
||||
|
||||
eVal := eWrapper.Unwrap()
|
||||
if eVal == nil {
|
||||
return e, transform.SameTree, nil
|
||||
}
|
||||
colDefault, ok := eVal.(*sql.ColumnDefaultValue)
|
||||
if !ok {
|
||||
return e, transform.SameTree, nil
|
||||
}
|
||||
|
||||
col, err := lookupColumnForTargetSchema(ctx, node, i)
|
||||
if err != nil {
|
||||
return nil, transform.SameTree, err
|
||||
}
|
||||
|
||||
err = validateColumnDefault(ctx, col, colDefault)
|
||||
if err != nil {
|
||||
return nil, transform.SameTree, err
|
||||
}
|
||||
|
||||
return e, transform.SameTree, nil
|
||||
})
|
||||
default:
|
||||
return node, transform.SameTree, nil
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// lookupColumnForTargetSchema looks at the target schema for the specified SchemaTarget node and returns
|
||||
// the column based on the specified index. For most node types, this is simply indexing into the target
|
||||
// schema but a few types require special handling.
|
||||
func lookupColumnForTargetSchema(_ *sql.Context, node sql.SchemaTarget, colIndex int) (*sql.Column, error) {
|
||||
schema := node.TargetSchema()
|
||||
|
||||
switch n := node.(type) {
|
||||
case *plan.ModifyColumn:
|
||||
if colIndex < len(schema) {
|
||||
return schema[colIndex], nil
|
||||
} else {
|
||||
return n.NewColumn(), nil
|
||||
}
|
||||
case *plan.AddColumn:
|
||||
if colIndex < len(schema) {
|
||||
return schema[colIndex], nil
|
||||
} else {
|
||||
return n.Column(), nil
|
||||
}
|
||||
case *plan.AlterDefaultSet:
|
||||
index := schema.IndexOfColName(n.ColumnName)
|
||||
if index == -1 {
|
||||
return nil, sql.ErrTableColumnNotFound.New(n.Table, n.ColumnName)
|
||||
}
|
||||
return schema[index], nil
|
||||
default:
|
||||
if colIndex < len(schema) {
|
||||
return schema[colIndex], nil
|
||||
} else {
|
||||
// TODO: sql.ErrColumnNotFound would be a better error here, but we need to add all the different node types to
|
||||
// the switch to get it
|
||||
return nil, expression.ErrIndexOutOfBounds.New(colIndex, len(schema))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// validateColumnDefault validates that the column default expression is valid for the column type and returns an error
|
||||
// if not
|
||||
func validateColumnDefault(ctx *sql.Context, col *sql.Column, colDefault *sql.ColumnDefaultValue) error {
|
||||
if colDefault == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var err error
|
||||
sql.Inspect(ctx, colDefault.Expr, func(ctx *sql.Context, e sql.Expression) bool {
|
||||
switch e.(type) {
|
||||
case sql.FunctionExpression, *expression.UnresolvedFunction:
|
||||
// TODO: functions must be deterministic to be used in column defaults
|
||||
return true
|
||||
case *plan.Subquery:
|
||||
err = sql.ErrColumnDefaultSubquery.New(col.Name)
|
||||
return false
|
||||
case *expression.GetField:
|
||||
if !colDefault.IsParenthesized() {
|
||||
err = sql.ErrInvalidColumnDefaultValue.New(col.Name)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
default:
|
||||
return true
|
||||
}
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// validate type of default expression
|
||||
if err = colDefault.CheckType(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Finds first ResolvedTable node that is a descendant of the node given
|
||||
// This function will not look inside SubqueryAliases
|
||||
func getResolvedTable(node sql.Node) *plan.ResolvedTable {
|
||||
var table *plan.ResolvedTable
|
||||
transform.Inspect(node, func(n sql.Node) bool {
|
||||
// Inspect is called on all children of a node even if an earlier child's call returns false.
|
||||
// We only want the first TableNode match.
|
||||
if table != nil {
|
||||
return false
|
||||
}
|
||||
switch nn := n.(type) {
|
||||
case *plan.SubqueryAlias:
|
||||
// We should not be matching with ResolvedTables inside SubqueryAliases
|
||||
return false
|
||||
case *plan.ResolvedTable:
|
||||
if !plan.IsDualTable(nn) {
|
||||
table = nn
|
||||
return false
|
||||
}
|
||||
case *plan.IndexedTableAccess:
|
||||
if rt, ok := nn.TableNode.(*plan.ResolvedTable); ok {
|
||||
table = rt
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
return table
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
// 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 analyzer
|
||||
|
||||
import (
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
"github.com/dolthub/go-mysql-server/sql/analyzer"
|
||||
"github.com/dolthub/go-mysql-server/sql/plan"
|
||||
"github.com/dolthub/go-mysql-server/sql/transform"
|
||||
|
||||
"github.com/dolthub/doltgresql/core"
|
||||
)
|
||||
|
||||
// ValidateCreateSchema validates that CREATE SCHEMA is executed with a valid database context.
|
||||
// In PostgreSQL, schemas exist within databases, so CREATE SCHEMA requires an active database.
|
||||
// See: https://github.com/dolthub/doltgresql/issues/1863
|
||||
func ValidateCreateSchema(
|
||||
ctx *sql.Context,
|
||||
a *analyzer.Analyzer,
|
||||
n sql.Node,
|
||||
scope *plan.Scope,
|
||||
sel analyzer.RuleSelector,
|
||||
qFlags *sql.QueryFlags,
|
||||
) (sql.Node, transform.TreeIdentity, error) {
|
||||
cs, ok := n.(*plan.CreateSchema)
|
||||
if !ok {
|
||||
return n, transform.SameTree, nil
|
||||
}
|
||||
|
||||
// Check if the current database actually has a working root.
|
||||
// GetRootFromContext will return sql.ErrDatabaseNotFound if the database
|
||||
// is not properly initialized.
|
||||
_, _, err := core.GetRootFromContext(ctx)
|
||||
if err != nil {
|
||||
if sql.ErrDatabaseNotFound.Is(err) {
|
||||
return nil, transform.SameTree, sql.ErrNoDatabaseSelected.New()
|
||||
}
|
||||
return nil, transform.SameTree, err
|
||||
}
|
||||
|
||||
return cs, transform.SameTree, nil
|
||||
}
|
||||
Executable
+462
@@ -0,0 +1,462 @@
|
||||
// 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 analyzer
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
"github.com/dolthub/go-mysql-server/sql/analyzer"
|
||||
"github.com/dolthub/go-mysql-server/sql/plan"
|
||||
"github.com/dolthub/go-mysql-server/sql/transform"
|
||||
)
|
||||
|
||||
// validateCreateTable validates that a table can be created as specified
|
||||
func validateCreateTable(ctx *sql.Context, a *analyzer.Analyzer, n sql.Node, scope *plan.Scope, sel analyzer.RuleSelector, qFlags *sql.QueryFlags) (sql.Node, transform.TreeIdentity, error) {
|
||||
ct, ok := n.(*plan.CreateTable)
|
||||
if !ok {
|
||||
return n, transform.SameTree, nil
|
||||
}
|
||||
|
||||
err := validateIdentifiers(ct)
|
||||
if err != nil {
|
||||
return nil, transform.SameTree, err
|
||||
}
|
||||
|
||||
sch := ct.PkSchema().Schema
|
||||
idxs := ct.Indexes()
|
||||
err = validateIndexes(ctx, sch, idxs)
|
||||
if err != nil {
|
||||
return nil, transform.SameTree, err
|
||||
}
|
||||
|
||||
return n, transform.SameTree, nil
|
||||
}
|
||||
|
||||
// validateIdentifiers validates the names of all schema elements for validity
|
||||
// TODO: we use 64 character as the max length for an identifier, postgres uses 63
|
||||
func validateIdentifiers(ct *plan.CreateTable) error {
|
||||
err := analyzer.ValidateIdentifier(ct.Name())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
colNames := make(map[string]bool)
|
||||
for _, col := range ct.PkSchema().Schema {
|
||||
err = analyzer.ValidateIdentifier(col.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
lower := strings.ToLower(col.Name)
|
||||
if colNames[lower] {
|
||||
return sql.ErrDuplicateColumn.New(col.Name)
|
||||
}
|
||||
colNames[lower] = true
|
||||
}
|
||||
|
||||
for _, chDef := range ct.Checks() {
|
||||
err = analyzer.ValidateIdentifier(chDef.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, idxDef := range ct.Indexes() {
|
||||
err = analyzer.ValidateIdentifier(idxDef.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, fkDef := range ct.ForeignKeys() {
|
||||
err = analyzer.ValidateIdentifier(fkDef.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateIndexes validates that the index definitions being created are valid
|
||||
func validateIndexes(ctx *sql.Context, sch sql.Schema, idxDefs sql.IndexDefs) error {
|
||||
colMap := schToColMap(sch)
|
||||
for _, idxDef := range idxDefs {
|
||||
if err := validateIndex(ctx, colMap, idxDef); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// schToColMap returns a map of columns, keyed by their name, for the specified
|
||||
// schema |sch|.
|
||||
func schToColMap(sch sql.Schema) map[string]*sql.Column {
|
||||
colMap := make(map[string]*sql.Column, len(sch))
|
||||
for _, col := range sch {
|
||||
colMap[strings.ToLower(col.Name)] = col
|
||||
}
|
||||
return colMap
|
||||
}
|
||||
|
||||
// validateIndex ensures that the Index Definition is valid for the table schema.
|
||||
// This function will throw errors and warnings as needed.
|
||||
// All columns in the index must be:
|
||||
// - in the schema
|
||||
// - not duplicated
|
||||
// - a compatible type for an index
|
||||
//
|
||||
// TODO: there are other constraints on indexes that we could enforce and are not yet (e.g. JSON as an index)
|
||||
func validateIndex(ctx *sql.Context, colMap map[string]*sql.Column, idxDef *sql.IndexDef) error {
|
||||
seenCols := make(map[string]struct{})
|
||||
for _, idxCol := range idxDef.Columns {
|
||||
if idxCol.Expression != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
schCol, exists := colMap[strings.ToLower(idxCol.Name)]
|
||||
if !exists {
|
||||
return sql.ErrKeyColumnDoesNotExist.New(idxCol.Name)
|
||||
}
|
||||
if _, ok := seenCols[schCol.Name]; ok {
|
||||
return sql.ErrDuplicateColumn.New(schCol.Name)
|
||||
}
|
||||
seenCols[schCol.Name] = struct{}{}
|
||||
if idxDef.IsFullText() {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if idxDef.IsSpatial() {
|
||||
return errors.Errorf("spatial indexes are not supported")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// resolveAlterColumn is a validation rule that validates the schema changes in an ALTER TABLE statement and updates
|
||||
// the nodes with necessary intermediate / update schema information
|
||||
func resolveAlterColumn(ctx *sql.Context, a *analyzer.Analyzer, n sql.Node, scope *plan.Scope, sel analyzer.RuleSelector, qFlags *sql.QueryFlags) (sql.Node, transform.TreeIdentity, error) {
|
||||
if !analyzer.FlagIsSet(qFlags, sql.QFlagAlterTable) {
|
||||
return n, transform.SameTree, nil
|
||||
}
|
||||
|
||||
var sch sql.Schema
|
||||
var indexes []string
|
||||
var validator sql.SchemaValidator
|
||||
keyedColumns := make(map[string]bool)
|
||||
var err error
|
||||
transform.Inspect(n, func(n sql.Node) bool {
|
||||
if st, ok := n.(sql.SchemaTarget); ok {
|
||||
sch = st.TargetSchema()
|
||||
}
|
||||
switch n := n.(type) {
|
||||
case *plan.ModifyColumn:
|
||||
if rt, ok := n.Table.(*plan.ResolvedTable); ok {
|
||||
if sv, ok := rt.UnwrappedDatabase().(sql.SchemaValidator); ok {
|
||||
validator = sv
|
||||
}
|
||||
}
|
||||
keyedColumns, err = analyzer.GetTableIndexColumns(ctx, n.Table)
|
||||
return false
|
||||
case *plan.RenameColumn:
|
||||
if rt, ok := n.Table.(*plan.ResolvedTable); ok {
|
||||
if sv, ok := rt.UnwrappedDatabase().(sql.SchemaValidator); ok {
|
||||
validator = sv
|
||||
}
|
||||
}
|
||||
return false
|
||||
case *plan.AddColumn:
|
||||
if rt, ok := n.Table.(*plan.ResolvedTable); ok {
|
||||
if sv, ok := rt.UnwrappedDatabase().(sql.SchemaValidator); ok {
|
||||
validator = sv
|
||||
}
|
||||
}
|
||||
keyedColumns, err = analyzer.GetTableIndexColumns(ctx, n.Table)
|
||||
return false
|
||||
case *plan.DropColumn:
|
||||
if rt, ok := n.Table.(*plan.ResolvedTable); ok {
|
||||
if sv, ok := rt.UnwrappedDatabase().(sql.SchemaValidator); ok {
|
||||
validator = sv
|
||||
}
|
||||
}
|
||||
return false
|
||||
case *plan.AlterIndex:
|
||||
if rt, ok := n.Table.(*plan.ResolvedTable); ok {
|
||||
if sv, ok := rt.UnwrappedDatabase().(sql.SchemaValidator); ok {
|
||||
validator = sv
|
||||
}
|
||||
}
|
||||
indexes, err = analyzer.GetTableIndexNames(ctx, a, n.Table)
|
||||
default:
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, transform.SameTree, err
|
||||
}
|
||||
|
||||
// Skip this validation if we didn't find one or more of the above node types
|
||||
if len(sch) == 0 {
|
||||
return n, transform.SameTree, nil
|
||||
}
|
||||
|
||||
sch = sch.Copy() // Make a copy of the original schema to deal with any references to the original table.
|
||||
initialSch := sch
|
||||
|
||||
// Need a TransformUp here because multiple of these statement types can be nested under a Block node.
|
||||
// It doesn't look it, but this is actually an iterative loop over all the independent clauses in an ALTER statement
|
||||
n, same, err := transform.Node(ctx, n, func(ctx *sql.Context, n sql.Node) (sql.Node, transform.TreeIdentity, error) {
|
||||
switch nn := n.(type) {
|
||||
case *plan.ModifyColumn:
|
||||
n, err := nn.WithTargetSchema(sch.Copy())
|
||||
if err != nil {
|
||||
return nil, transform.SameTree, err
|
||||
}
|
||||
|
||||
sch, err = analyzer.ValidateModifyColumn(ctx, initialSch, sch, n.(*plan.ModifyColumn), keyedColumns)
|
||||
if err != nil {
|
||||
return nil, transform.SameTree, err
|
||||
}
|
||||
return n, transform.NewTree, nil
|
||||
case *plan.RenameColumn:
|
||||
n, err := nn.WithTargetSchema(sch.Copy())
|
||||
if err != nil {
|
||||
return nil, transform.SameTree, err
|
||||
}
|
||||
sch, err = analyzer.ValidateRenameColumn(ctx, initialSch, sch, n.(*plan.RenameColumn))
|
||||
if err != nil {
|
||||
return nil, transform.SameTree, err
|
||||
}
|
||||
return n, transform.NewTree, nil
|
||||
case *plan.AddColumn:
|
||||
n, err := nn.WithTargetSchema(sch.Copy())
|
||||
if err != nil {
|
||||
return nil, transform.SameTree, err
|
||||
}
|
||||
|
||||
sch, err = analyzer.ValidateAddColumn(ctx, sch, n.(*plan.AddColumn))
|
||||
if err != nil {
|
||||
return nil, transform.SameTree, err
|
||||
}
|
||||
|
||||
return n, transform.NewTree, nil
|
||||
case *plan.DropColumn:
|
||||
n, err := nn.WithTargetSchema(sch.Copy())
|
||||
if err != nil {
|
||||
return nil, transform.SameTree, err
|
||||
}
|
||||
sch, err = analyzer.ValidateDropColumn(ctx, initialSch, sch, n.(*plan.DropColumn))
|
||||
if err != nil {
|
||||
return nil, transform.SameTree, err
|
||||
}
|
||||
delete(keyedColumns, nn.Column)
|
||||
|
||||
return n, transform.NewTree, nil
|
||||
case *plan.AlterIndex:
|
||||
n, err := nn.WithTargetSchema(sch.Copy())
|
||||
if err != nil {
|
||||
return nil, transform.SameTree, err
|
||||
}
|
||||
indexes, err = validateAlterIndex(ctx, initialSch, sch, n.(*plan.AlterIndex), indexes)
|
||||
if err != nil {
|
||||
return nil, transform.SameTree, err
|
||||
}
|
||||
|
||||
keyedColumns = analyzer.UpdateKeyedColumns(keyedColumns, nn)
|
||||
return n, transform.NewTree, nil
|
||||
case *plan.AlterPK:
|
||||
n, err := nn.WithTargetSchema(sch.Copy())
|
||||
if err != nil {
|
||||
return nil, transform.SameTree, err
|
||||
}
|
||||
sch, err = validatePrimaryKey(ctx, initialSch, sch, n.(*plan.AlterPK))
|
||||
if err != nil {
|
||||
return nil, transform.SameTree, err
|
||||
}
|
||||
return n, transform.NewTree, nil
|
||||
case *plan.AlterDefaultSet:
|
||||
n, err := nn.WithTargetSchema(sch.Copy())
|
||||
if err != nil {
|
||||
return nil, transform.SameTree, err
|
||||
}
|
||||
sch, err = analyzer.ValidateAlterDefault(ctx, initialSch, sch, n.(*plan.AlterDefaultSet))
|
||||
if err != nil {
|
||||
return nil, transform.SameTree, err
|
||||
}
|
||||
return n, transform.NewTree, nil
|
||||
case *plan.AlterDefaultDrop:
|
||||
n, err := nn.WithTargetSchema(sch.Copy())
|
||||
if err != nil {
|
||||
return nil, transform.SameTree, err
|
||||
}
|
||||
sch, err = analyzer.ValidateDropDefault(ctx, initialSch, sch, n.(*plan.AlterDefaultDrop))
|
||||
if err != nil {
|
||||
return nil, transform.SameTree, err
|
||||
}
|
||||
return n, transform.NewTree, nil
|
||||
}
|
||||
return n, transform.SameTree, nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, transform.SameTree, err
|
||||
}
|
||||
|
||||
if validator != nil {
|
||||
if err := validator.ValidateSchema(sch); err != nil {
|
||||
return nil, transform.SameTree, err
|
||||
}
|
||||
}
|
||||
|
||||
return n, same, nil
|
||||
}
|
||||
|
||||
// Returns the underlying table name for the node given
|
||||
func getTableName(node sql.Node) string {
|
||||
var tableName string
|
||||
transform.Inspect(node, func(node sql.Node) bool {
|
||||
switch node := node.(type) {
|
||||
case *plan.TableAlias:
|
||||
tableName = node.Name()
|
||||
return false
|
||||
case *plan.ResolvedTable:
|
||||
tableName = node.Name()
|
||||
return false
|
||||
case *plan.UnresolvedTable:
|
||||
tableName = node.Name()
|
||||
return false
|
||||
case *plan.IndexedTableAccess:
|
||||
tableName = node.Name()
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
return tableName
|
||||
}
|
||||
|
||||
// validatePrimaryKey validates a primary key add or drop operation.
|
||||
func validatePrimaryKey(ctx *sql.Context, initialSch, sch sql.Schema, ai *plan.AlterPK) (sql.Schema, error) {
|
||||
tableName := getTableName(ai.Table)
|
||||
switch ai.Action {
|
||||
case plan.PrimaryKeyAction_Create:
|
||||
if analyzer.HasPrimaryKeys(sch) {
|
||||
return nil, sql.ErrMultiplePrimaryKeysDefined.New()
|
||||
}
|
||||
|
||||
colMap := schToColMap(sch)
|
||||
idxDef := &sql.IndexDef{
|
||||
Name: "PRIMARY",
|
||||
Columns: ai.Columns,
|
||||
Constraint: sql.IndexConstraint_Primary,
|
||||
}
|
||||
|
||||
err := validateIndex(ctx, colMap, idxDef)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, idxCol := range ai.Columns {
|
||||
schCol := colMap[strings.ToLower(idxCol.Name)]
|
||||
if schCol.Virtual {
|
||||
return nil, sql.ErrVirtualColumnPrimaryKey.New()
|
||||
}
|
||||
}
|
||||
|
||||
// Set the primary keys
|
||||
for _, col := range ai.Columns {
|
||||
sch[sch.IndexOf(col.Name, tableName)].PrimaryKey = true
|
||||
}
|
||||
|
||||
return sch, nil
|
||||
case plan.PrimaryKeyAction_Drop:
|
||||
if !analyzer.HasPrimaryKeys(sch) {
|
||||
return nil, sql.ErrCantDropFieldOrKey.New("PRIMARY")
|
||||
}
|
||||
|
||||
for _, col := range sch {
|
||||
if col.PrimaryKey {
|
||||
col.PrimaryKey = false
|
||||
}
|
||||
}
|
||||
|
||||
return sch, nil
|
||||
default:
|
||||
return sch, nil
|
||||
}
|
||||
}
|
||||
|
||||
// validateAlterIndex validates the specified column can have an index added, dropped, or renamed. Returns an updated
|
||||
// list of index name given the add, drop, or rename operations.
|
||||
func validateAlterIndex(ctx *sql.Context, initialSch, sch sql.Schema, ai *plan.AlterIndex, indexes []string) ([]string, error) {
|
||||
switch ai.Action {
|
||||
case plan.IndexAction_Create:
|
||||
err := analyzer.ValidateIdentifier(ai.IndexName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
colMap := schToColMap(sch)
|
||||
|
||||
// TODO: plan.AlterIndex should just have a sql.IndexDef
|
||||
indexDef := &sql.IndexDef{
|
||||
Name: ai.IndexName,
|
||||
Columns: ai.Columns,
|
||||
Constraint: ai.Constraint,
|
||||
Storage: ai.Using,
|
||||
Comment: ai.Comment,
|
||||
}
|
||||
|
||||
err = validateIndex(ctx, colMap, indexDef)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return append(indexes, ai.IndexName), nil
|
||||
case plan.IndexAction_Drop:
|
||||
savedIdx := -1
|
||||
for i, idx := range indexes {
|
||||
if strings.EqualFold(idx, ai.IndexName) {
|
||||
savedIdx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if savedIdx == -1 {
|
||||
return nil, sql.ErrCantDropFieldOrKey.New(ai.IndexName)
|
||||
}
|
||||
// Remove the index from the list
|
||||
return append(indexes[:savedIdx], indexes[savedIdx+1:]...), nil
|
||||
case plan.IndexAction_Rename:
|
||||
err := analyzer.ValidateIdentifier(ai.IndexName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
savedIdx := -1
|
||||
for i, idx := range indexes {
|
||||
if strings.EqualFold(idx, ai.PreviousIndexName) {
|
||||
savedIdx = i
|
||||
}
|
||||
}
|
||||
if savedIdx == -1 {
|
||||
return nil, sql.ErrCantDropFieldOrKey.New(ai.IndexName)
|
||||
}
|
||||
// Simulate the rename by deleting the old name and adding the new one.
|
||||
return append(append(indexes[:savedIdx], indexes[savedIdx+1:]...), ai.IndexName), nil
|
||||
}
|
||||
|
||||
return indexes, nil
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
// Copyright 2023 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 ast
|
||||
|
||||
import (
|
||||
"github.com/cockroachdb/errors"
|
||||
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
"github.com/dolthub/doltgresql/server/auth"
|
||||
)
|
||||
|
||||
// nodeAliasedTableExpr handles *tree.AliasedTableExpr nodes.
|
||||
func nodeAliasedTableExpr(ctx *Context, node *tree.AliasedTableExpr) (*vitess.AliasedTableExpr, error) {
|
||||
if node.Ordinality {
|
||||
return nil, errors.Errorf("ordinality is not yet supported")
|
||||
}
|
||||
if node.IndexFlags != nil {
|
||||
return nil, errors.Errorf("index flags are not yet supported")
|
||||
}
|
||||
var aliasExpr vitess.SimpleTableExpr
|
||||
var authInfo vitess.AuthInformation
|
||||
|
||||
switch expr := node.Expr.(type) {
|
||||
case *tree.TableName:
|
||||
tableName, err := nodeTableName(ctx, expr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
aliasExpr = tableName
|
||||
authInfo = vitess.AuthInformation{
|
||||
AuthType: ctx.Auth().PeekAuthType(),
|
||||
TargetType: auth.AuthTargetType_TableIdentifiers,
|
||||
TargetNames: []string{tableName.DbQualifier.String(), tableName.SchemaQualifier.String(), tableName.Name.String()},
|
||||
}
|
||||
case *tree.Subquery:
|
||||
tableExpr, err := nodeTableExpr(ctx, expr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ate, ok := tableExpr.(*vitess.AliasedTableExpr)
|
||||
if !ok {
|
||||
return nil, errors.Errorf("expected *vitess.AliasedTableExpr, found %T", tableExpr)
|
||||
}
|
||||
|
||||
var selectStmt vitess.SelectStatement
|
||||
switch ate.Expr.(type) {
|
||||
case *vitess.Subquery:
|
||||
selectStmt = ate.Expr.(*vitess.Subquery).Select
|
||||
default:
|
||||
return nil, errors.Errorf("unhandled subquery table expression: `%T`", tableExpr)
|
||||
}
|
||||
|
||||
// If the subquery is a VALUES statement, it should be represented more directly
|
||||
innerSelect := selectStmt
|
||||
if parentSelect, ok := innerSelect.(*vitess.ParenSelect); ok {
|
||||
innerSelect = parentSelect.Select
|
||||
}
|
||||
if inSelect, ok := innerSelect.(*vitess.Select); ok {
|
||||
if isTrivialSelectStar(inSelect) {
|
||||
if aliasedTblExpr, ok := inSelect.From[0].(*vitess.AliasedTableExpr); ok {
|
||||
if valuesStmt, ok := aliasedTblExpr.Expr.(*vitess.ValuesStatement); ok {
|
||||
if len(node.As.Cols) > 0 {
|
||||
columns := make([]vitess.ColIdent, len(node.As.Cols))
|
||||
for i := range node.As.Cols {
|
||||
columns[i] = vitess.NewColIdent(string(node.As.Cols[i]))
|
||||
}
|
||||
valuesStmt.Columns = columns
|
||||
}
|
||||
aliasExpr = valuesStmt
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
subquery := &vitess.Subquery{
|
||||
Select: selectStmt,
|
||||
}
|
||||
|
||||
if len(node.As.Cols) > 0 {
|
||||
columns := make([]vitess.ColIdent, len(node.As.Cols))
|
||||
for i := range node.As.Cols {
|
||||
columns[i] = vitess.NewColIdent(string(node.As.Cols[i]))
|
||||
}
|
||||
subquery.Columns = columns
|
||||
}
|
||||
aliasExpr = subquery
|
||||
case *tree.RowsFromExpr:
|
||||
tableExpr, err := nodeTableExpr(ctx, expr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO: this should be represented as a table function more directly
|
||||
subquery := &vitess.Subquery{
|
||||
Select: &vitess.Select{
|
||||
From: vitess.TableExprs{tableExpr},
|
||||
},
|
||||
}
|
||||
|
||||
if len(node.As.Cols) > 0 {
|
||||
columns := make([]vitess.ColIdent, len(node.As.Cols))
|
||||
for i := range node.As.Cols {
|
||||
columns[i] = vitess.NewColIdent(string(node.As.Cols[i]))
|
||||
}
|
||||
subquery.Columns = columns
|
||||
}
|
||||
aliasExpr = subquery
|
||||
default:
|
||||
return nil, errors.Errorf("unhandled table expression: `%T`", expr)
|
||||
}
|
||||
alias := string(node.As.Alias)
|
||||
|
||||
var asOf *vitess.AsOf
|
||||
if node.AsOf != nil {
|
||||
asOfExpr, err := nodeExpr(ctx, node.AsOf.Expr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// TODO: other forms of AS OF (not just point in time)
|
||||
asOf = &vitess.AsOf{
|
||||
Time: asOfExpr,
|
||||
}
|
||||
}
|
||||
|
||||
return &vitess.AliasedTableExpr{
|
||||
Expr: aliasExpr,
|
||||
As: vitess.NewTableIdent(alias),
|
||||
AsOf: asOf,
|
||||
Lateral: node.Lateral,
|
||||
Auth: authInfo,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// isTrivialSelectStar returns true when the Select is just "SELECT * FROM <single table>"
|
||||
// with no other clauses that would alter semantics (no WHERE, ORDER BY, LIMIT, GROUP BY,
|
||||
// HAVING, DISTINCT, or WITH).
|
||||
func isTrivialSelectStar(s *vitess.Select) bool {
|
||||
if len(s.From) != 1 ||
|
||||
s.QueryOpts.Distinct ||
|
||||
s.With != nil ||
|
||||
s.Limit != nil ||
|
||||
len(s.OrderBy) != 0 ||
|
||||
s.Where != nil ||
|
||||
len(s.GroupBy) != 0 ||
|
||||
s.Having != nil ||
|
||||
len(s.SelectExprs) != 1 {
|
||||
return false
|
||||
}
|
||||
starExpr, ok := s.SelectExprs[0].(*vitess.StarExpr)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return starExpr.TableName.IsEmpty()
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// 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 ast
|
||||
|
||||
import (
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
)
|
||||
|
||||
// nodeAlterAggregate handles *tree.AlterAggregate nodes.
|
||||
func nodeAlterAggregate(ctx *Context, node *tree.AlterAggregate) (vitess.Statement, error) {
|
||||
if node == nil {
|
||||
return nil, nil
|
||||
}
|
||||
if err := validateAggArgMode(ctx, node.AggSig.Args, node.AggSig.OrderByArgs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NotYetSupportedError("ALTER AGGREGATE is not yet supported")
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright 2023 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 ast
|
||||
|
||||
import (
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
)
|
||||
|
||||
// nodeAlterDatabase handles *tree.AlterDatabase nodes.
|
||||
func nodeAlterDatabase(ctx *Context, node *tree.AlterDatabase) (vitess.Statement, error) {
|
||||
if node == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// We intentionally don't support OWNER TO since we don't support owning objects
|
||||
if node.Owner != "" && len(node.Options) == 0 {
|
||||
return NewNoOp("OWNER TO is unsupported and ignored"), nil
|
||||
}
|
||||
|
||||
return NotYetSupportedError("ALTER DATABASE is not yet supported")
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// 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 ast
|
||||
|
||||
import (
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
)
|
||||
|
||||
// nodeAlterDefaultPrivileges handles *tree.AlterDefaultPrivileges nodes.
|
||||
func nodeAlterDefaultPrivileges(ctx *Context, node *tree.AlterDefaultPrivileges) (vitess.Statement, error) {
|
||||
return NotYetSupportedError("ALTER DEFAULT PRIVILEGES statement is not yet supported")
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// 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 ast
|
||||
|
||||
import (
|
||||
"github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
)
|
||||
|
||||
// nodeAlterDomain handles ALTER DOMAIN nodes.
|
||||
func nodeAlterDomain(ctx *Context, stmt *tree.AlterDomain) (sqlparser.Statement, error) {
|
||||
if stmt == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// We intentionally don't support OWNER TO since we don't support owning objects
|
||||
if _, ok := stmt.Cmd.(*tree.AlterDomainOwner); ok {
|
||||
return NewNoOp("OWNER TO is unsupported and ignored"), nil
|
||||
}
|
||||
|
||||
return NotYetSupportedError("ALTER DOMAIN is not yet supported")
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
// 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 ast
|
||||
|
||||
import (
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
)
|
||||
|
||||
// nodeAlterFunction handles *tree.AlterFunction nodes.
|
||||
func nodeAlterFunction(ctx *Context, node *tree.AlterFunction) (vitess.Statement, error) {
|
||||
_, err := validateRoutineOptions(ctx, node.Options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// We intentionally don't support OWNER TO since we don't support owning objects
|
||||
if node.Owner != "" && len(node.Options) == 0 {
|
||||
return NewNoOp("OWNER TO is unsupported and ignored"), nil
|
||||
}
|
||||
|
||||
return NotYetSupportedError("ALTER FUNCTION statement is not yet supported")
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// Copyright 2023 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 ast
|
||||
|
||||
import (
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
)
|
||||
|
||||
// nodeAlterIndex handles *tree.AlterIndex nodes.
|
||||
func nodeAlterIndex(ctx *Context, node *tree.AlterIndex) (vitess.Statement, error) {
|
||||
if node == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Only PARTITION alterations are supported by the parser, so there's nothing to convert to yet
|
||||
return NotYetSupportedError("ALTER INDEX is not yet supported")
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// 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 ast
|
||||
|
||||
import (
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
)
|
||||
|
||||
// nodeAlterMaterializedView handles *tree.AlterMaterializedView nodes.
|
||||
func nodeAlterMaterializedView(ctx *Context, node *tree.AlterMaterializedView) (vitess.Statement, error) {
|
||||
if node == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return NotYetSupportedError("ALTER MATERIALIZED VIEW is not yet supported")
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
// 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 ast
|
||||
|
||||
import (
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
)
|
||||
|
||||
// nodeAlterProcedure handles *tree.AlterProcedure nodes.
|
||||
func nodeAlterProcedure(ctx *Context, node *tree.AlterProcedure) (vitess.Statement, error) {
|
||||
_, err := validateRoutineOptions(ctx, node.Options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// We intentionally don't support OWNER TO since we don't support owning objects
|
||||
if node.Owner != "" && len(node.Options) == 0 {
|
||||
return NewNoOp("OWNER TO is unsupported and ignored"), nil
|
||||
}
|
||||
|
||||
return NotYetSupportedError("ALTER PROCEDURE statement is not yet supported")
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
// Copyright 2023 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 ast
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
pgnodes "github.com/dolthub/doltgresql/server/node"
|
||||
)
|
||||
|
||||
// nodeAlterRole handles *tree.AlterRole nodes.
|
||||
func nodeAlterRole(ctx *Context, node *tree.AlterRole) (vitess.Statement, error) {
|
||||
if node == nil {
|
||||
return nil, nil
|
||||
}
|
||||
if len(node.Name) == 0 {
|
||||
// The parser should make this impossible, but extra error checking is never bad
|
||||
return nil, errors.New(`role name cannot be empty`)
|
||||
}
|
||||
// Some of the keys are used as markers, and do not contain values.
|
||||
// Therefore, the values will be nil since they're ignored.
|
||||
options := make(map[string]any)
|
||||
for _, kvOption := range node.KVOptions {
|
||||
optionName := strings.ToUpper(string(kvOption.Key))
|
||||
switch optionName {
|
||||
case "BYPASSRLS":
|
||||
options["BYPASSRLS"] = nil
|
||||
case "CONNECTION_LIMIT":
|
||||
switch value := kvOption.Value.(type) {
|
||||
case *tree.DInt:
|
||||
if value == nil {
|
||||
options["CONNECTION_LIMIT"] = int32(-1)
|
||||
} else {
|
||||
// We enforce that only int32 values will fit here in the parser
|
||||
options["CONNECTION_LIMIT"] = int32(*value)
|
||||
}
|
||||
case tree.NullLiteral:
|
||||
options["CONNECTION_LIMIT"] = int32(-1)
|
||||
default:
|
||||
return nil, errors.Errorf(`unknown role option value (%T) for option "%s"`, kvOption.Value, kvOption.Key)
|
||||
}
|
||||
case "CREATEDB":
|
||||
options["CREATEDB"] = nil
|
||||
case "CREATEROLE":
|
||||
options["CREATEROLE"] = nil
|
||||
case "INHERIT":
|
||||
options["INHERIT"] = nil
|
||||
case "LOGIN":
|
||||
options["LOGIN"] = nil
|
||||
case "NOBYPASSRLS":
|
||||
options["NOBYPASSRLS"] = nil
|
||||
case "NOCREATEDB":
|
||||
options["NOCREATEDB"] = nil
|
||||
case "NOCREATEROLE":
|
||||
options["NOCREATEROLE"] = nil
|
||||
case "NOINHERIT":
|
||||
options["NOINHERIT"] = nil
|
||||
case "NOLOGIN":
|
||||
options["NOLOGIN"] = nil
|
||||
case "NOREPLICATION":
|
||||
options["NOREPLICATION"] = nil
|
||||
case "NOSUPERUSER":
|
||||
options["NOSUPERUSER"] = nil
|
||||
case "PASSWORD":
|
||||
switch value := kvOption.Value.(type) {
|
||||
case *tree.DString:
|
||||
if value == nil {
|
||||
options["PASSWORD"] = nil
|
||||
} else {
|
||||
options["PASSWORD"] = (*string)(value)
|
||||
}
|
||||
case tree.NullLiteral:
|
||||
options["PASSWORD"] = nil
|
||||
default:
|
||||
return nil, errors.Errorf(`unknown role option value (%T) for option "%s"`, kvOption.Value, kvOption.Key)
|
||||
}
|
||||
case "REPLICATION":
|
||||
options["REPLICATION"] = nil
|
||||
case "SUPERUSER":
|
||||
options["SUPERUSER"] = nil
|
||||
case "SYSID":
|
||||
// This is an option that is ignored by Postgres. Assuming it used to be relevant, but not any longer.
|
||||
case "VALID_UNTIL":
|
||||
strVal, ok := kvOption.Value.(*tree.DString)
|
||||
if !ok {
|
||||
return nil, errors.Errorf(`unknown role option value (%T) for option "%s"`, kvOption.Value, kvOption.Key)
|
||||
}
|
||||
if strVal == nil {
|
||||
options["VALID_UNTIL"] = nil
|
||||
} else {
|
||||
options["VALID_UNTIL"] = (*string)(strVal)
|
||||
}
|
||||
default:
|
||||
return nil, errors.Errorf(`unknown role option "%s"`, kvOption.Key)
|
||||
}
|
||||
}
|
||||
return vitess.InjectedStatement{
|
||||
Statement: &pgnodes.AlterRole{
|
||||
Name: node.Name,
|
||||
Options: options,
|
||||
},
|
||||
Children: nil,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright 2023 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 ast
|
||||
|
||||
import (
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
)
|
||||
|
||||
// nodeAlterSchema handles *tree.AlterSchema nodes.
|
||||
func nodeAlterSchema(ctx *Context, node *tree.AlterSchema) (vitess.Statement, error) {
|
||||
if node == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// We intentionally don't support OWNER TO since we don't support owning objects
|
||||
if _, ok := node.Cmd.(*tree.AlterSchemaOwner); ok {
|
||||
return NewNoOp("OWNER TO is unsupported and ignored"), nil
|
||||
}
|
||||
|
||||
return NotYetSupportedError("ALTER SCHEMA is not yet supported")
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
// Copyright 2023 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 ast
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
"github.com/dolthub/doltgresql/server/auth"
|
||||
pgnodes "github.com/dolthub/doltgresql/server/node"
|
||||
)
|
||||
|
||||
// nodeAlterSequence handles *tree.AlterSequence nodes.
|
||||
func nodeAlterSequence(ctx *Context, node *tree.AlterSequence) (vitess.Statement, error) {
|
||||
if node == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var warnings []string
|
||||
if len(node.Owner) > 0 {
|
||||
// We intentionally don't support OWNER TO since we don't support owning objects
|
||||
if len(node.Options) == 0 {
|
||||
return NewNoOp("OWNER TO is unsupported and ignored"), nil
|
||||
} else {
|
||||
warnings = append(warnings, "OWNER TO is unsupported and ignored")
|
||||
}
|
||||
}
|
||||
if node.SetLog {
|
||||
return NotYetSupportedError("LOGGED and UNLOGGED are not yet supported")
|
||||
}
|
||||
|
||||
nodeName := node.Name.ToTableName()
|
||||
name, err := nodeTableName(ctx, &nodeName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(name.DbQualifier.String()) > 0 {
|
||||
return NotYetSupportedError("ALTER SEQUENCE does not yet support specifying the database")
|
||||
}
|
||||
|
||||
ownedBy := pgnodes.AlterSequenceOwnedBy{}
|
||||
for _, option := range node.Options {
|
||||
switch option.Name {
|
||||
case tree.SeqOptOwnedBy:
|
||||
ownedBy.IsSet = true
|
||||
// OWNED BY NONE is valid, so we have to check if a column was provided
|
||||
if option.ColumnItemVal != nil {
|
||||
expr, err := nodeExpr(ctx, option.ColumnItemVal)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
colName, ok := expr.(*vitess.ColName)
|
||||
if !ok {
|
||||
return nil, errors.New("expected sequence owner to be a table and column name")
|
||||
}
|
||||
if colName.Qualifier.SchemaQualifier.String() != name.SchemaQualifier.String() {
|
||||
return nil, errors.New("ALTER SEQUENCE must use the same schema for the sequence and owned table")
|
||||
}
|
||||
if len(colName.Qualifier.DbQualifier.String()) > 0 {
|
||||
return nil, errors.New("database specification is not yet supported for sequences")
|
||||
}
|
||||
if len(colName.Name.String()) == 0 || len(colName.Qualifier.Name.String()) == 0 {
|
||||
return nil, errors.New("invalid OWNED BY option")
|
||||
}
|
||||
ownedBy.Table = colName.Qualifier.Name.String()
|
||||
ownedBy.Column = colName.Name.String()
|
||||
}
|
||||
default:
|
||||
return NotYetSupportedError(fmt.Sprintf("%s is not yet supported", option.Name))
|
||||
}
|
||||
}
|
||||
|
||||
return vitess.InjectedStatement{
|
||||
Statement: pgnodes.NewAlterSequence(
|
||||
node.IfExists,
|
||||
name.SchemaQualifier.String(),
|
||||
name.Name.String(),
|
||||
ownedBy,
|
||||
warnings...),
|
||||
Children: nil,
|
||||
Auth: vitess.AuthInformation{
|
||||
AuthType: auth.AuthType_UPDATE,
|
||||
TargetType: auth.AuthTargetType_SequenceIdentifiers,
|
||||
TargetNames: []string{name.SchemaQualifier.String(), name.Name.String()},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,494 @@
|
||||
// Copyright 2023 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 ast
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
pgnodes "github.com/dolthub/doltgresql/server/node"
|
||||
pgtypes "github.com/dolthub/doltgresql/server/types"
|
||||
)
|
||||
|
||||
// nodeAlterTable handles *tree.AlterTable nodes.
|
||||
func nodeAlterTable(ctx *Context, node *tree.AlterTable) (vitess.Statement, error) {
|
||||
if node == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
treeTableName := node.Table.ToTableName()
|
||||
tableName, err := nodeTableName(ctx, &treeTableName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// We don't implement sequence addition/modification as a DDL since it's not in GMS, so we only support one of these
|
||||
// at a time. If there are more than one, then we'll return an error in the command loop.
|
||||
if len(node.Cmds) == 1 {
|
||||
cmd, ok := node.Cmds[0].(*tree.AlterTableComputed)
|
||||
if ok {
|
||||
return nodeAlterTableComputed(ctx, treeTableName, cmd)
|
||||
}
|
||||
if vcmd, ok := node.Cmds[0].(*tree.AlterTableValidateConstraint); ok {
|
||||
return vitess.InjectedStatement{
|
||||
Statement: pgnodes.NewValidateConstraint(
|
||||
tableName.SchemaQualifier.String(),
|
||||
tableName.Name.String(),
|
||||
bareIdentifier(vcmd.Constraint),
|
||||
),
|
||||
Children: nil,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
statements, noOps, err := nodeAlterTableCmds(ctx, node.Cmds, tableName, node.IfExists)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// If there are no valid statements return a no-op statement
|
||||
if len(noOps) > 0 && len(statements) == 0 {
|
||||
return NewNoOp(noOps...), nil
|
||||
}
|
||||
|
||||
// Otherwise emit warnings now, then return an AlterTable statement
|
||||
// TODO: we don't have a way to send or store the warnings alongside a valid AlterTable statement. We could either
|
||||
// get a *sql.Context here and emit warnings, or we could store the warnings in the Context and make the caller
|
||||
// emit them before it sends |ReadyForQuery|
|
||||
// if len(noOps) > 0 {
|
||||
// emit warnings
|
||||
// }
|
||||
|
||||
return &vitess.AlterTable{
|
||||
Table: tableName,
|
||||
Statements: statements,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// nodeAlterTableSetSchema handles *tree.AlterTableSetSchema nodes.
|
||||
func nodeAlterTableSetSchema(ctx *Context, node *tree.AlterTableSetSchema) (vitess.Statement, error) {
|
||||
if node == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, errors.Errorf("ALTER TABLE SET SCHEMA is not yet supported")
|
||||
}
|
||||
|
||||
// nodeAlterTableCmds converts tree.AlterTableCmds into a slice of vitess.DDL instances that can be executed by GMS.
|
||||
// |tableName| identifies the table being altered, and |ifExists| indicates whether the IF EXISTS clause was specified.
|
||||
// A second slice of unsupported but safely ignored statements is return as well.
|
||||
func nodeAlterTableCmds(
|
||||
ctx *Context,
|
||||
node tree.AlterTableCmds,
|
||||
tableName vitess.TableName,
|
||||
ifExists bool) ([]*vitess.DDL, []string, error) {
|
||||
|
||||
if len(node) == 0 {
|
||||
return nil, nil, errors.Errorf("no commands specified for ALTER TABLE statement")
|
||||
}
|
||||
|
||||
vitessDdlCmds := make([]*vitess.DDL, 0, len(node))
|
||||
var unsupportedWarnings []string
|
||||
for _, cmd := range node {
|
||||
var err error
|
||||
var statement *vitess.DDL
|
||||
switch cmd := cmd.(type) {
|
||||
case *tree.AlterTableAddConstraint:
|
||||
statement, err = nodeAlterTableAddConstraint(ctx, cmd, tableName, ifExists)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
vitessDdlCmds = append(vitessDdlCmds, statement)
|
||||
case *tree.AlterTableAddColumn:
|
||||
statement, err = nodeAlterTableAddColumn(ctx, cmd, tableName, ifExists)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
vitessDdlCmds = append(vitessDdlCmds, statement)
|
||||
|
||||
// If inline constraints have been specified, set the ConstraintAction so that they get processed
|
||||
if len(statement.TableSpec.Constraints) > 0 {
|
||||
statement.ConstraintAction = vitess.AddStr
|
||||
}
|
||||
|
||||
case *tree.AlterTableDropColumn:
|
||||
statement, err = nodeAlterTableDropColumn(ctx, cmd, tableName, ifExists)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
vitessDdlCmds = append(vitessDdlCmds, statement)
|
||||
case *tree.AlterTableDropConstraint:
|
||||
statement, err = nodeAlterTableDropConstraint(ctx, cmd, tableName, ifExists)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
vitessDdlCmds = append(vitessDdlCmds, statement)
|
||||
case *tree.AlterTableRenameColumn:
|
||||
statement, err = nodeAlterTableRenameColumn(ctx, cmd, tableName, ifExists)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
vitessDdlCmds = append(vitessDdlCmds, statement)
|
||||
case *tree.AlterTableSetDefault:
|
||||
statement, err = nodeAlterTableSetDefault(ctx, cmd, tableName, ifExists)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
vitessDdlCmds = append(vitessDdlCmds, statement)
|
||||
case *tree.AlterTableDropNotNull:
|
||||
statement, err = nodeAlterTableDropNotNull(ctx, cmd, tableName, ifExists)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
vitessDdlCmds = append(vitessDdlCmds, statement)
|
||||
case *tree.AlterTableSetNotNull:
|
||||
statement, err = nodeAlterTableSetNotNull(ctx, cmd, tableName, ifExists)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
vitessDdlCmds = append(vitessDdlCmds, statement)
|
||||
case *tree.AlterTableAlterColumnType:
|
||||
statement, err = nodeAlterTableAlterColumnType(ctx, cmd, tableName, ifExists)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
vitessDdlCmds = append(vitessDdlCmds, statement)
|
||||
case *tree.AlterTableOwner:
|
||||
unsupportedWarnings = append(unsupportedWarnings, fmt.Sprintf("ALTER TABLE %s OWNER TO %s", tableName.String(), cmd.Owner))
|
||||
case *tree.AlterTableComputed:
|
||||
return nil, nil, errors.New("This command does not currently support multiple actions in one statement")
|
||||
case *tree.AlterTableSetStatistics:
|
||||
// is unsupported and ignored
|
||||
case *tree.AlterTableRowLevelSecurity:
|
||||
// is unsupported and ignored
|
||||
case *tree.AlterTableReplicaIdentity:
|
||||
// is unsupported and ignored
|
||||
default:
|
||||
return nil, nil, errors.Errorf("ALTER TABLE with unsupported command type %T", cmd)
|
||||
}
|
||||
}
|
||||
|
||||
return vitessDdlCmds, unsupportedWarnings, nil
|
||||
}
|
||||
|
||||
// nodeAlterTableAddConstraint converts a tree.AlterTableAddConstraint instance
|
||||
// into a vitess.DDL instance that can be executed by GMS. |tableName| identifies
|
||||
// the table being altered, and |ifExists| indicates whether the IF EXISTS clause
|
||||
// was specified.
|
||||
func nodeAlterTableAddConstraint(
|
||||
ctx *Context,
|
||||
node *tree.AlterTableAddConstraint,
|
||||
tableName vitess.TableName,
|
||||
ifExists bool) (*vitess.DDL, error) {
|
||||
|
||||
notValid := node.ValidationBehavior == tree.ValidationSkip
|
||||
if notValid {
|
||||
if _, ok := node.ConstraintDef.(*tree.UniqueConstraintTableDef); ok {
|
||||
return nil, errors.Errorf("NOT VALID is not applicable to UNIQUE or PRIMARY KEY constraints")
|
||||
}
|
||||
}
|
||||
|
||||
switch constraintDef := node.ConstraintDef.(type) {
|
||||
case *tree.CheckConstraintTableDef:
|
||||
return nodeCheckConstraintTableDef(ctx, constraintDef, tableName, ifExists, notValid)
|
||||
case *tree.UniqueConstraintTableDef:
|
||||
return nodeUniqueConstraintTableDef(ctx, constraintDef, tableName, ifExists)
|
||||
case *tree.ForeignKeyConstraintTableDef:
|
||||
foreignKeyDefinition, err := nodeForeignKeyConstraintTableDef(ctx, constraintDef, notValid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &vitess.DDL{
|
||||
Action: "alter",
|
||||
ConstraintAction: "add",
|
||||
Table: tableName,
|
||||
IfExists: ifExists,
|
||||
TableSpec: &vitess.TableSpec{
|
||||
Constraints: []*vitess.ConstraintDefinition{
|
||||
{
|
||||
Name: bareIdentifier(constraintDef.Name),
|
||||
Details: foreignKeyDefinition,
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
|
||||
default:
|
||||
return nil, errors.Errorf("ALTER TABLE with unsupported constraint "+
|
||||
"definition type %T", node)
|
||||
}
|
||||
}
|
||||
|
||||
// bareIdentifier returns the string representation of a name without any quoting
|
||||
// (quoted is the default Name.String() behavior)
|
||||
func bareIdentifier(id tree.Name) string {
|
||||
ctx := tree.NewFmtCtx(tree.FmtBareIdentifiers)
|
||||
id.Format(ctx)
|
||||
return ctx.CloseAndGetString()
|
||||
}
|
||||
|
||||
// nodeAlterTableAddColumn converts a tree.AlterTableAddColumn instance into an equivalent vitess.DDL instance.
|
||||
func nodeAlterTableAddColumn(ctx *Context, node *tree.AlterTableAddColumn, tableName vitess.TableName, ifExists bool) (*vitess.DDL, error) {
|
||||
if node.IfNotExists {
|
||||
return nil, errors.Errorf("IF NOT EXISTS on a column in an ADD COLUMN statement is not supported yet")
|
||||
}
|
||||
|
||||
vitessColumnDef, err := nodeColumnTableDef(ctx, node.ColumnDef)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tableSpec := &vitess.TableSpec{}
|
||||
tableSpec.AddColumn(vitessColumnDef)
|
||||
|
||||
if node.ColumnDef.References.Table != nil {
|
||||
constraintDef, err := nodeForeignKeyDefinitionFromColumnTableDef(ctx, node.ColumnDef.Name, node.ColumnDef)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tableSpec.AddConstraint(&vitess.ConstraintDefinition{
|
||||
Name: string(node.ColumnDef.References.ConstraintName),
|
||||
Details: constraintDef,
|
||||
})
|
||||
}
|
||||
|
||||
return &vitess.DDL{
|
||||
Action: "alter",
|
||||
ColumnAction: "add",
|
||||
Table: tableName,
|
||||
IfExists: ifExists,
|
||||
Column: vitessColumnDef.Name,
|
||||
TableSpec: tableSpec,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// nodeAlterTableDropColumn converts a tree.AlterTableDropColumn instance into an equivalent vitess.DDL instance.
|
||||
func nodeAlterTableDropColumn(ctx *Context, node *tree.AlterTableDropColumn, tableName vitess.TableName, ifExists bool) (*vitess.DDL, error) {
|
||||
if node.IfExists {
|
||||
return nil, errors.Errorf("IF EXISTS on a column in a DROP COLUMN statement is not supported yet")
|
||||
}
|
||||
|
||||
switch node.DropBehavior {
|
||||
case tree.DropDefault:
|
||||
case tree.DropRestrict:
|
||||
return nil, errors.Errorf("ALTER TABLE DROP COLUMN does not support RESTRICT option")
|
||||
case tree.DropCascade:
|
||||
logrus.Warnf("CASCADE option on DROP COLUMN is not yet supported, ignoring")
|
||||
default:
|
||||
return nil, errors.Errorf("ALTER TABLE with unsupported drop behavior %v", node.DropBehavior)
|
||||
}
|
||||
|
||||
return &vitess.DDL{
|
||||
Action: "alter",
|
||||
ColumnAction: "drop",
|
||||
Table: tableName,
|
||||
IfExists: ifExists,
|
||||
Column: vitess.NewColIdent(node.Column.String()),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// nodeAlterTableRenameColumn converts a tree.AlterTableRenameColumn instance into an equivalent vitess.DDL instance.
|
||||
func nodeAlterTableRenameColumn(ctx *Context, node *tree.AlterTableRenameColumn, tableName vitess.TableName, ifExists bool) (*vitess.DDL, error) {
|
||||
return &vitess.DDL{
|
||||
Action: "alter",
|
||||
ColumnAction: "rename",
|
||||
Table: tableName,
|
||||
IfExists: ifExists,
|
||||
Column: vitess.NewColIdent(node.Column.String()),
|
||||
ToColumn: vitess.NewColIdent(node.NewName.String()),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// nodeAlterTableSetDefault converts a tree.AlterTableSetDefault instance into an equivalent vitess.DDL instance.
|
||||
func nodeAlterTableSetDefault(ctx *Context, node *tree.AlterTableSetDefault, tableName vitess.TableName, ifExists bool) (*vitess.DDL, error) {
|
||||
expr, err := nodeExpr(ctx, node.Default)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// GMS requires the AST to wrap function expressions in parens
|
||||
if _, ok := expr.(*vitess.FuncExpr); ok {
|
||||
expr = &vitess.ParenExpr{Expr: expr}
|
||||
}
|
||||
|
||||
return &vitess.DDL{
|
||||
Action: "alter",
|
||||
Table: tableName,
|
||||
IfExists: ifExists,
|
||||
DefaultSpec: &vitess.DefaultSpec{
|
||||
Action: "set",
|
||||
Column: vitess.NewColIdent(bareIdentifier(node.Column)),
|
||||
Value: expr,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// nodeAlterTableAlterColumnType converts a tree.AlterTableAlterColumnType instance into an equivalent vitess.DDL instance.
|
||||
func nodeAlterTableAlterColumnType(ctx *Context, node *tree.AlterTableAlterColumnType, tableName vitess.TableName, ifExists bool) (*vitess.DDL, error) {
|
||||
if node.Collation != "" {
|
||||
return nil, errors.Errorf("ALTER TABLE with COLLATE is not supported yet")
|
||||
}
|
||||
|
||||
if node.Using != nil {
|
||||
return nil, errors.Errorf("ALTER TABLE with USING is not supported yet")
|
||||
}
|
||||
|
||||
convertType, resolvedType, err := nodeResolvableTypeReference(ctx, node.ToType, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resolvedType == pgtypes.Record {
|
||||
return nil, errors.Errorf(`column "%s" has pseudo-type record`, node.Column.String())
|
||||
}
|
||||
|
||||
return &vitess.DDL{
|
||||
Action: "alter",
|
||||
Table: tableName,
|
||||
IfExists: ifExists,
|
||||
ColumnTypeSpec: &vitess.ColumnTypeSpec{
|
||||
Column: vitess.NewColIdent(node.Column.String()),
|
||||
Type: vitess.ColumnType{
|
||||
Type: convertType.Type,
|
||||
ResolvedType: resolvedType,
|
||||
Length: convertType.Length,
|
||||
Scale: convertType.Scale,
|
||||
Charset: convertType.Charset,
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// nodeAlterTableDropNotNull converts a tree.AlterTableDropNotNull instance into an equivalent vitess.DDL instance.
|
||||
func nodeAlterTableDropNotNull(ctx *Context, node *tree.AlterTableDropNotNull, tableName vitess.TableName, ifExists bool) (*vitess.DDL, error) {
|
||||
return &vitess.DDL{
|
||||
Action: "alter",
|
||||
Table: tableName,
|
||||
IfExists: ifExists,
|
||||
NotNullSpec: &vitess.NotNullSpec{
|
||||
Action: "drop",
|
||||
Column: vitess.NewColIdent(node.Column.String()),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// nodeAlterTableSetNotNull converts a tree.AlterTableSetNotNull instance into an equivalent vitess.DDL instance.
|
||||
func nodeAlterTableSetNotNull(ctx *Context, node *tree.AlterTableSetNotNull, tableName vitess.TableName, ifExists bool) (*vitess.DDL, error) {
|
||||
return &vitess.DDL{
|
||||
Action: "alter",
|
||||
Table: tableName,
|
||||
IfExists: ifExists,
|
||||
NotNullSpec: &vitess.NotNullSpec{
|
||||
Action: "set",
|
||||
Column: vitess.NewColIdent(node.Column.String()),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// nodeAlterTableSetNotNull converts a tree.AlterTablePartition instance into an equivalent vitess.DDL instance.
|
||||
func nodeAlterTablePartition(ctx *Context, node *tree.AlterTablePartition) (*vitess.AlterTable, error) {
|
||||
if node == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// TODO: This is an incomplete translation because our GMS implementation doesn't support the MySQL
|
||||
// equivalent of these statements either. Regardless, these are all no-ops.
|
||||
treeTableName := node.Name.ToTableName()
|
||||
tableName, err := nodeTableName(ctx, &treeTableName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch node.Spec.Type {
|
||||
case tree.PartitionBoundIn:
|
||||
case tree.PartitionBoundFromTo:
|
||||
case tree.PartitionBoundWith:
|
||||
default:
|
||||
return nil, errors.Errorf("ALTER TABLE with unsupported partition type %v", node.Spec.Type)
|
||||
}
|
||||
|
||||
partitionDef := &vitess.PartitionDefinition{
|
||||
Name: vitess.NewColIdent(node.Partition.String()),
|
||||
}
|
||||
|
||||
actionStr := ""
|
||||
if node.IsDetach {
|
||||
actionStr = vitess.DropStr
|
||||
} else {
|
||||
actionStr = vitess.AddStr
|
||||
}
|
||||
|
||||
partitionSpec := &vitess.PartitionSpec{
|
||||
Action: actionStr,
|
||||
Definitions: []*vitess.PartitionDefinition{partitionDef},
|
||||
}
|
||||
|
||||
return &vitess.AlterTable{
|
||||
Table: tableName,
|
||||
PartitionSpecs: []*vitess.PartitionSpec{partitionSpec},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// nodeAlterTableComputed converts a tree.AlterTableComputed instance into an equivalent CREATE/ALTER SEQUENCE statement.
|
||||
func nodeAlterTableComputed(ctx *Context, tableName tree.TableName, node *tree.AlterTableComputed) (vitess.Statement, error) {
|
||||
if len(node.Defs) > 0 {
|
||||
return NotYetSupportedError("ALTER TABLE variant is not yet supported")
|
||||
}
|
||||
var persistence tree.Persistence
|
||||
var seqName tree.TableName
|
||||
var trimmedOptions []tree.SequenceOption
|
||||
switch def := node.AddDefs.(type) {
|
||||
case *tree.ColumnComputedDef:
|
||||
if def.Expr != nil {
|
||||
return NotYetSupportedError("ALTER TABLE variant is not yet supported")
|
||||
}
|
||||
trimmedOptions = make([]tree.SequenceOption, 0, len(def.Options)+2)
|
||||
for _, opt := range def.Options {
|
||||
switch opt.Name {
|
||||
case tree.SeqOptOwnedBy:
|
||||
return NotYetSupportedError("OWNED BY is invalid here")
|
||||
case tree.SeqOptRestart:
|
||||
return NotYetSupportedError("RESTART is not yet supported")
|
||||
case tree.SeqOptName:
|
||||
seqName = opt.SeqName
|
||||
case tree.SeqOptLogged:
|
||||
persistence = tree.PersistencePermanent
|
||||
case tree.SeqOptUnlogged:
|
||||
persistence = tree.PersistenceUnlogged
|
||||
default:
|
||||
trimmedOptions = append(trimmedOptions, opt)
|
||||
}
|
||||
}
|
||||
default:
|
||||
return NotYetSupportedError("ALTER TABLE variant is not yet supported")
|
||||
}
|
||||
trimmedOptions = append(trimmedOptions,
|
||||
tree.SequenceOption{
|
||||
Name: tree.SeqOptOwnedBy,
|
||||
ColumnItemVal: tree.NewColumnItem(&tableName, node.Column),
|
||||
},
|
||||
tree.SequenceOption{
|
||||
Name: tree.SeqOptViaAlterTable,
|
||||
},
|
||||
)
|
||||
return nodeCreateSequence(ctx, &tree.CreateSequence{
|
||||
IfNotExists: false,
|
||||
Name: seqName,
|
||||
Persistence: persistence,
|
||||
Options: trimmedOptions,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright 2023 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 ast
|
||||
|
||||
import (
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
)
|
||||
|
||||
// nodeAlterType handles *tree.AlterType nodes.
|
||||
func nodeAlterType(ctx *Context, node *tree.AlterType) (vitess.Statement, error) {
|
||||
if node == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// We intentionally don't support OWNER TO since we don't support owning objects
|
||||
if _, ok := node.Cmd.(*tree.AlterTypeOwner); ok {
|
||||
return NewNoOp("OWNER TO is unsupported and ignored"), nil
|
||||
}
|
||||
|
||||
return NotYetSupportedError("ALTER TYPE is not yet supported")
|
||||
}
|
||||
Executable
+35
@@ -0,0 +1,35 @@
|
||||
// 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 ast
|
||||
|
||||
import (
|
||||
"github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
)
|
||||
|
||||
// nodeAlterView handles ALTER VIEW nodes.
|
||||
func nodeAlterView(ctx *Context, stmt *tree.AlterView) (sqlparser.Statement, error) {
|
||||
if stmt == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// We intentionally don't support OWNER TO since we don't support owning objects
|
||||
if _, ok := stmt.Cmd.(*tree.AlterViewOwnerTo); ok {
|
||||
return NewNoOp("OWNER TO is unsupported and ignored"), nil
|
||||
}
|
||||
|
||||
return NotYetSupportedError("ALTER VIEW is not yet supported")
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
// Copyright 2023 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 ast
|
||||
|
||||
import (
|
||||
"github.com/cockroachdb/errors"
|
||||
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
)
|
||||
|
||||
// nodeAnalyze handles *tree.Analyze nodes.
|
||||
func nodeAnalyze(ctx *Context, node *tree.Analyze) (vitess.Statement, error) {
|
||||
if node == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// If no tables were specified, return an empty Analyze statement, and the analyzer
|
||||
// will populate all tables to be analyzed.
|
||||
if node.Table == nil {
|
||||
return &vitess.Analyze{}, nil
|
||||
}
|
||||
|
||||
objectName, ok := node.Table.(*tree.UnresolvedObjectName)
|
||||
if !ok {
|
||||
return nil, errors.Errorf("unsupported table type in Analyze node: %T", node.Table)
|
||||
}
|
||||
|
||||
return &vitess.Analyze{Tables: []vitess.TableName{
|
||||
{
|
||||
Name: vitess.NewTableIdent(objectName.Object()),
|
||||
SchemaQualifier: vitess.NewTableIdent(objectName.Schema()),
|
||||
DbQualifier: vitess.NewTableIdent(objectName.Catalog()),
|
||||
},
|
||||
}}, nil
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright 2023 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 ast
|
||||
|
||||
import (
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
)
|
||||
|
||||
// nodeBackup handles *tree.Backup nodes.
|
||||
func nodeBackup(ctx *Context, node *tree.Backup) (vitess.Statement, error) {
|
||||
if node == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return NotYetSupportedError("BACKUP is not yet supported")
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
// Copyright 2023 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 ast
|
||||
|
||||
import (
|
||||
"github.com/cockroachdb/errors"
|
||||
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
)
|
||||
|
||||
// nodeBeginTransaction handles *tree.BeginTransaction nodes.
|
||||
func nodeBeginTransaction(ctx *Context, node *tree.BeginTransaction) (*vitess.Begin, error) {
|
||||
if node == nil {
|
||||
return nil, nil
|
||||
}
|
||||
if node.Modes.Isolation != tree.UnspecifiedIsolation {
|
||||
return nil, errors.Errorf("isolation levels are not yet supported")
|
||||
}
|
||||
if node.Modes.UserPriority != tree.UnspecifiedUserPriority {
|
||||
return nil, errors.Errorf("user priority is not yet supported")
|
||||
}
|
||||
if node.Modes.AsOf.Expr != nil {
|
||||
return nil, errors.Errorf("AS OF is not yet supported")
|
||||
}
|
||||
if node.Modes.Deferrable != tree.UnspecifiedDeferrableMode {
|
||||
return nil, errors.Errorf("deferrability is not yet supported")
|
||||
}
|
||||
var characteristic string
|
||||
switch node.Modes.ReadWriteMode {
|
||||
case tree.UnspecifiedReadWriteMode:
|
||||
characteristic = vitess.TxReadWrite
|
||||
case tree.ReadOnly:
|
||||
characteristic = vitess.TxReadOnly
|
||||
case tree.ReadWrite:
|
||||
characteristic = vitess.TxReadWrite
|
||||
default:
|
||||
return nil, errors.Errorf("unknown READ/WRITE setting")
|
||||
}
|
||||
return &vitess.Begin{
|
||||
TransactionCharacteristic: characteristic,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
// Copyright 2023 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 ast
|
||||
|
||||
import (
|
||||
"github.com/cockroachdb/errors"
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/server/auth"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
pgnodes "github.com/dolthub/doltgresql/server/node"
|
||||
)
|
||||
|
||||
// nodeCall handles *tree.Call nodes.
|
||||
func nodeCall(ctx *Context, node *tree.Call) (vitess.Statement, error) {
|
||||
if node == nil {
|
||||
return nil, nil
|
||||
}
|
||||
if node.Procedure.Type != 0 {
|
||||
return nil, errors.Errorf("procedure distinction is not yet supported")
|
||||
}
|
||||
if node.Procedure.Filter != nil {
|
||||
return nil, errors.Errorf("procedure filters are not yet supported")
|
||||
}
|
||||
if node.Procedure.WindowDef != nil {
|
||||
return nil, errors.Errorf("procedure window definitions are not yet supported")
|
||||
}
|
||||
if node.Procedure.AggType == tree.OrderedSetAgg {
|
||||
return nil, errors.Errorf("procedure aggregation is not yet supported")
|
||||
}
|
||||
if len(node.Procedure.OrderBy) > 0 {
|
||||
return nil, errors.Errorf("procedure ORDER BY is not yet supported")
|
||||
}
|
||||
|
||||
ctx.Auth().PushAuthType(auth.AuthType_EXECUTE)
|
||||
defer ctx.Auth().PopAuthType()
|
||||
|
||||
var qualifier vitess.TableIdent
|
||||
var name vitess.ColIdent
|
||||
switch funcRef := node.Procedure.Func.FunctionReference.(type) {
|
||||
case *tree.FunctionDefinition:
|
||||
name = vitess.NewColIdent(funcRef.Name)
|
||||
case *tree.UnresolvedName:
|
||||
if funcRef.NumParts > 2 {
|
||||
return nil, errors.Errorf("referencing items outside the schema or database is not yet supported")
|
||||
}
|
||||
if funcRef.NumParts == 2 {
|
||||
qualifier = vitess.NewTableIdent(funcRef.Parts[1])
|
||||
}
|
||||
name = vitess.NewColIdent(funcRef.Parts[0])
|
||||
default:
|
||||
return nil, errors.Errorf("unknown function reference")
|
||||
}
|
||||
exprs, err := nodeExprs(ctx, node.Procedure.Exprs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return vitess.InjectedStatement{
|
||||
Statement: pgnodes.NewCall(qualifier.String(), name.String(), exprs),
|
||||
Children: exprs,
|
||||
Auth: vitess.AuthInformation{
|
||||
AuthType: auth.AuthType_EXECUTE,
|
||||
TargetType: auth.AuthTargetType_FunctionIdentifiers,
|
||||
TargetNames: []string{qualifier.String(), name.String()},
|
||||
// TODO: need to get argument types separated by comma ( check routineArgTypesKey function )
|
||||
Extra: "",
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright 2023 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 ast
|
||||
|
||||
import (
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
)
|
||||
|
||||
// nodeCancelQueries handles *tree.CancelQueries nodes.
|
||||
func nodeCancelQueries(ctx *Context, node *tree.CancelQueries) (vitess.Statement, error) {
|
||||
if node == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return NotYetSupportedError("CANCEL QUERIES is not yet supported")
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright 2023 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 ast
|
||||
|
||||
import (
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
)
|
||||
|
||||
// nodeCancelSessions handles *tree.CancelSessions nodes.
|
||||
func nodeCancelSessions(ctx *Context, node *tree.CancelSessions) (vitess.Statement, error) {
|
||||
if node == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return NotYetSupportedError("CANCEL SESSIONS is not yet supported")
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright 2023 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 ast
|
||||
|
||||
import (
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
)
|
||||
|
||||
// nodeCannedOptPlan handles *tree.CannedOptPlan nodes.
|
||||
func nodeCannedOptPlan(ctx *Context, node *tree.CannedOptPlan) (vitess.Statement, error) {
|
||||
if node == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return NotYetSupportedError("PREPARE AS OPT PLAN is not yet supported")
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
// Copyright 2023 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 ast
|
||||
|
||||
import (
|
||||
"github.com/cockroachdb/errors"
|
||||
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
pgtypes "github.com/dolthub/doltgresql/server/types"
|
||||
)
|
||||
|
||||
// DoltCreateTablePlaceholderSequenceName is a Placeholder name used in translating computed columns to generated
|
||||
// columns that involve a sequence, used later in analysis
|
||||
const DoltCreateTablePlaceholderSequenceName = "dolt_create_table_placeholder_sequence"
|
||||
|
||||
// nodeColumnTableDef handles *tree.ColumnTableDef nodes.
|
||||
func nodeColumnTableDef(ctx *Context, node *tree.ColumnTableDef) (*vitess.ColumnDefinition, error) {
|
||||
if node == nil {
|
||||
return nil, nil
|
||||
}
|
||||
if len(node.Nullable.ConstraintName) > 0 ||
|
||||
len(node.DefaultExpr.ConstraintName) > 0 ||
|
||||
len(node.UniqueConstraintName) > 0 {
|
||||
return nil, errors.Errorf("non-foreign key column constraint names are not yet supported")
|
||||
}
|
||||
convertType, resolvedType, err := nodeResolvableTypeReference(ctx, node.Type, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resolvedType == pgtypes.Record {
|
||||
return nil, errors.Errorf(`column "%s" has pseudo-type record`, node.Name.String())
|
||||
}
|
||||
|
||||
var isNull vitess.BoolVal
|
||||
var isNotNull vitess.BoolVal
|
||||
switch node.Nullable.Nullability {
|
||||
case tree.NotNull:
|
||||
isNull = false
|
||||
isNotNull = true
|
||||
case tree.Null:
|
||||
isNull = true
|
||||
isNotNull = false
|
||||
case tree.SilentNull:
|
||||
isNull = true
|
||||
isNotNull = false
|
||||
default:
|
||||
return nil, errors.Errorf("unknown NULL type encountered")
|
||||
}
|
||||
keyOpt := vitess.ColumnKeyOption(0) // colKeyNone, unexported for some reason
|
||||
if node.PrimaryKey.IsPrimaryKey {
|
||||
keyOpt = 1 // colKeyPrimary
|
||||
isNull = false
|
||||
isNotNull = true
|
||||
} else if node.Unique {
|
||||
keyOpt = 3 // colKeyUnique
|
||||
}
|
||||
defaultExpr, err := nodeExpr(ctx, node.DefaultExpr.Expr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Wrap any default expression using a function call in parens to match MySQL's column default requirements
|
||||
if _, ok := defaultExpr.(*vitess.FuncExpr); ok {
|
||||
defaultExpr = &vitess.ParenExpr{Expr: defaultExpr}
|
||||
}
|
||||
|
||||
if node.References.Table != nil {
|
||||
if len(node.References.Col) == 0 {
|
||||
return nil, errors.Errorf("implicit primary key matching on column foreign key is not yet supported")
|
||||
}
|
||||
// Callers register the foreign key via the table constraint list, so ForeignKeyDef
|
||||
// is left unset here to avoid creating the same constraint twice.
|
||||
}
|
||||
|
||||
if len(node.Computed.Options) > 0 {
|
||||
return nil, errors.Errorf("sequence options are not yet supported, create a sequence separately")
|
||||
}
|
||||
|
||||
var generated vitess.Expr
|
||||
hasGeneratedExpr := node.IsComputed() && node.Computed.Expr != nil
|
||||
computedByDefaultAsIdentity := node.IsComputed() && !hasGeneratedExpr && node.Computed.ByDefault
|
||||
computedAsIdentity := node.IsComputed() && !hasGeneratedExpr && !node.Computed.ByDefault
|
||||
|
||||
if hasGeneratedExpr {
|
||||
generated, err = nodeExpr(ctx, node.Computed.Expr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if computedAsIdentity {
|
||||
generated, err = nodeExpr(ctx, &tree.FuncExpr{
|
||||
Func: tree.WrapFunction("nextval"),
|
||||
Exprs: tree.Exprs{
|
||||
tree.NewStrVal(DoltCreateTablePlaceholderSequenceName),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if generated != nil {
|
||||
// GMS requires the AST to wrap function expressions in parens
|
||||
if _, ok := generated.(*vitess.FuncExpr); ok {
|
||||
generated = &vitess.ParenExpr{Expr: generated}
|
||||
}
|
||||
|
||||
// clean up the expressions generated here. our default expression handling generates aliases that aren't
|
||||
// appropriate in this context.
|
||||
generated = clearAliases(generated)
|
||||
}
|
||||
|
||||
if node.IsSerial || computedByDefaultAsIdentity || computedAsIdentity {
|
||||
if resolvedType.IsEmptyType() {
|
||||
return nil, errors.Errorf("serial type was not resolvable")
|
||||
}
|
||||
switch resolvedType.ID {
|
||||
case pgtypes.Int16.ID:
|
||||
resolvedType = pgtypes.Int16Serial
|
||||
case pgtypes.Int32.ID:
|
||||
resolvedType = pgtypes.Int32Serial
|
||||
case pgtypes.Int64.ID:
|
||||
resolvedType = pgtypes.Int64Serial
|
||||
default:
|
||||
return nil, errors.Errorf(`type "%s" cannot be serial`, resolvedType.String())
|
||||
}
|
||||
if defaultExpr != nil {
|
||||
return nil, errors.Errorf(`multiple default values specified for column "%s"`, node.Name)
|
||||
}
|
||||
}
|
||||
|
||||
colDef := &vitess.ColumnDefinition{
|
||||
Name: vitess.NewColIdent(string(node.Name)),
|
||||
Type: vitess.ColumnType{
|
||||
Type: convertType.Type,
|
||||
ResolvedType: resolvedType,
|
||||
Null: isNull,
|
||||
NotNull: isNotNull,
|
||||
Autoincrement: false,
|
||||
Default: defaultExpr,
|
||||
Length: convertType.Length,
|
||||
Scale: convertType.Scale,
|
||||
KeyOpt: keyOpt,
|
||||
GeneratedExpr: generated,
|
||||
Stored: generated != nil, // postgres generated columns are always stored, never virtual
|
||||
},
|
||||
}
|
||||
|
||||
if len(node.CheckExprs) > 0 {
|
||||
// TODO: vitess does not support multiple check constraint on a single column
|
||||
if len(node.CheckExprs) > 1 {
|
||||
return nil, errors.Errorf("column-declared multiple CHECK expressions are not yet supported")
|
||||
}
|
||||
var checkConstraints = make([]*vitess.ConstraintDefinition, len(node.CheckExprs))
|
||||
for i, checkExpr := range node.CheckExprs {
|
||||
expr, err := nodeExpr(ctx, checkExpr.Expr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
checkConstraints[i] = &vitess.ConstraintDefinition{
|
||||
Name: string(checkExpr.ConstraintName),
|
||||
Details: &vitess.CheckConstraintDefinition{
|
||||
Expr: expr,
|
||||
Enforced: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
// until we support multiple constraints in a column
|
||||
colDef.Type.Constraint = checkConstraints[0]
|
||||
}
|
||||
return colDef, nil
|
||||
}
|
||||
|
||||
// clearAliases removes As and InputExpression from any AliasedExpr in the expression tree given. This is required
|
||||
// in some contexts where we expect the expression to serialize to a string without any alias names.
|
||||
func clearAliases(e vitess.Expr) vitess.Expr {
|
||||
_ = vitess.Walk(func(node vitess.SQLNode) (kontinue bool, err error) {
|
||||
if expr, ok := node.(*vitess.AliasedExpr); ok {
|
||||
expr.As = vitess.ColIdent{}
|
||||
expr.InputExpression = ""
|
||||
}
|
||||
return true, nil
|
||||
}, e)
|
||||
return e
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// 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 ast
|
||||
|
||||
import (
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
)
|
||||
|
||||
// nodeComment handles *tree.Comment nodes.
|
||||
func nodeComment(ctx *Context, node *tree.Comment) (vitess.Statement, error) {
|
||||
if node == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return NewNoOp("COMMENT ON is not yet supported"), nil
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// Copyright 2023 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 ast
|
||||
|
||||
import (
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
)
|
||||
|
||||
// nodeCommitTransaction handles *tree.CommitTransaction nodes.
|
||||
func nodeCommitTransaction(ctx *Context, node *tree.CommitTransaction) (*vitess.Commit, error) {
|
||||
if node == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return &vitess.Commit{}, nil
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
// 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 ast
|
||||
|
||||
import (
|
||||
"github.com/cockroachdb/errors"
|
||||
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
)
|
||||
|
||||
// nodeCheckConstraintTableDef converts a tree.CheckConstraintTableDef instance
|
||||
// into a vitess.DDL instance that can be executed by GMS. |tableName| identifies
|
||||
// the table being altered, and |ifExists| indicates whether the IF EXISTS clause
|
||||
// was specified. |notValid| reflects whether the NOT VALID clause was present.
|
||||
func nodeCheckConstraintTableDef(
|
||||
ctx *Context,
|
||||
node *tree.CheckConstraintTableDef,
|
||||
tableName vitess.TableName,
|
||||
ifExists bool,
|
||||
notValid bool) (*vitess.DDL, error) {
|
||||
|
||||
if node.NoInherit {
|
||||
return nil, errors.Errorf("NO INHERIT is not yet supported for check constraints")
|
||||
}
|
||||
|
||||
expr, err := nodeExpr(ctx, node.Expr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &vitess.DDL{
|
||||
Action: "alter",
|
||||
Table: tableName,
|
||||
IfExists: ifExists,
|
||||
ConstraintAction: "add",
|
||||
TableSpec: &vitess.TableSpec{
|
||||
Constraints: []*vitess.ConstraintDefinition{
|
||||
{
|
||||
Name: node.Name.String(),
|
||||
Details: &vitess.CheckConstraintDefinition{
|
||||
Expr: expr,
|
||||
Enforced: true,
|
||||
NotValid: notValid,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// nodeAlterTableDropConstraint converts a tree.AlterTableDropConstraint instance
|
||||
// into a vitess.DDL instance that can be executed by GMS. |tableName| identifies
|
||||
// the table being altered, and |ifExists| indicates whether the IF EXISTS clause
|
||||
// was specified.
|
||||
func nodeAlterTableDropConstraint(
|
||||
ctx *Context,
|
||||
node *tree.AlterTableDropConstraint,
|
||||
tableName vitess.TableName,
|
||||
ifExists bool) (*vitess.DDL, error) {
|
||||
|
||||
if node.DropBehavior == tree.DropCascade {
|
||||
return nil, errors.Errorf("CASCADE is not yet supported for drop constraint")
|
||||
}
|
||||
|
||||
return &vitess.DDL{
|
||||
Action: "alter",
|
||||
Table: tableName,
|
||||
IfExists: ifExists,
|
||||
ConstraintAction: "drop",
|
||||
ConstraintIfExists: node.IfExists,
|
||||
TableSpec: &vitess.TableSpec{
|
||||
Constraints: []*vitess.ConstraintDefinition{
|
||||
{Name: node.Constraint.String()},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// nodeUniqueConstraintTableDef converts a tree.UniqueConstraintTableDef instance
|
||||
// into a vitess.DDL instance that can be executed by GMS. |tableName| identifies
|
||||
// the table being altered, and |ifExists| indicates whether the IF EXISTS clause
|
||||
// was specified.
|
||||
func nodeUniqueConstraintTableDef(
|
||||
ctx *Context,
|
||||
node *tree.UniqueConstraintTableDef,
|
||||
tableName vitess.TableName,
|
||||
ifExists bool) (*vitess.DDL, error) {
|
||||
|
||||
if len(node.IndexParams.StorageParams) > 0 {
|
||||
return nil, errors.Errorf("STORAGE parameters not yet supported for indexes")
|
||||
}
|
||||
|
||||
if node.IndexParams.Tablespace != "" {
|
||||
return nil, errors.Errorf("TABLESPACE is not yet supported")
|
||||
}
|
||||
|
||||
if node.NullsNotDistinct {
|
||||
return nil, errors.Errorf("NULLS NOT DISTINCT is not yet supported")
|
||||
}
|
||||
|
||||
indexFields, err := nodeIndexElemList(ctx, node.Columns)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
indexType := "unique"
|
||||
if node.PrimaryKey {
|
||||
indexType = "primary"
|
||||
}
|
||||
|
||||
return &vitess.DDL{
|
||||
Action: "alter",
|
||||
Table: tableName,
|
||||
IfExists: ifExists,
|
||||
IndexSpec: &vitess.IndexSpec{
|
||||
ToName: vitess.NewColIdent(bareIdentifier(node.Name)),
|
||||
Action: "create",
|
||||
Type: indexType,
|
||||
Fields: indexFields,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
// 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 ast
|
||||
|
||||
import (
|
||||
"github.com/dolthub/doltgresql/postgres/parser/parser"
|
||||
"github.com/dolthub/doltgresql/server/auth"
|
||||
)
|
||||
|
||||
// Context contains any relevant context for the AST conversion. For example, the auth system uses the context to
|
||||
// determine which larger statement an expression exists in, which may influence how the expression should handle
|
||||
// authorization.
|
||||
type Context struct {
|
||||
authContext *auth.AuthContext
|
||||
originalQuery string
|
||||
}
|
||||
|
||||
// NewContext returns a new *Context.
|
||||
func NewContext(postgresStmt parser.Statement) *Context {
|
||||
return &Context{
|
||||
authContext: auth.NewAuthContext(),
|
||||
originalQuery: postgresStmt.SQL,
|
||||
}
|
||||
}
|
||||
|
||||
// Auth returns the portion that handles authentication.
|
||||
func (ctx *Context) Auth() *auth.AuthContext {
|
||||
return ctx.authContext
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
// Copyright 2023 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 ast
|
||||
|
||||
import (
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
)
|
||||
|
||||
// nodeControlJobs handles *tree.ControlJobs nodes.
|
||||
func nodeControlJobs(ctx *Context, node *tree.ControlJobs) (vitess.Statement, error) {
|
||||
if node == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return NotYetSupportedError("PAUSE/RESUME/CANCEL are not yet supported")
|
||||
}
|
||||
|
||||
// nodeControlJobsForSchedules handles *tree.ControlJobsForSchedules nodes.
|
||||
func nodeControlJobsForSchedules(ctx *Context, node *tree.ControlJobsForSchedules) (vitess.Statement, error) {
|
||||
if node == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return NotYetSupportedError("PAUSE/RESUME/CANCEL are not yet supported")
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright 2023 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 ast
|
||||
|
||||
import (
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
)
|
||||
|
||||
// nodeControlSchedules handles *tree.ControlSchedules nodes.
|
||||
func nodeControlSchedules(ctx *Context, node *tree.ControlSchedules) (vitess.Statement, error) {
|
||||
if node == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return NotYetSupportedError("PAUSE/RESUME SCHEDULE is not yet supported")
|
||||
}
|
||||
@@ -0,0 +1,315 @@
|
||||
// Copyright 2023 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 ast
|
||||
|
||||
import (
|
||||
"github.com/cockroachdb/errors"
|
||||
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/parser"
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
)
|
||||
|
||||
// UnknownColSentinelPrefix is prepended to the index position of unaliased string literals in a
|
||||
// SELECT clause. This gives GMS a unique per-position alias so its alias scope map can distinguish
|
||||
// multiple unnamed columns in the same SELECT. schemaToFieldDescriptions in doltgres_handler.go
|
||||
// strips this prefix and returns Postgres's "?column?" placeholder on the wire.
|
||||
const UnknownColSentinelPrefix = "__?column?__"
|
||||
|
||||
// Convert converts a Postgres AST into a Vitess AST.
|
||||
func Convert(postgresStmt parser.Statement) (vitess.Statement, error) {
|
||||
ctx := NewContext(postgresStmt)
|
||||
switch stmt := postgresStmt.AST.(type) {
|
||||
case *tree.AlterAggregate:
|
||||
return nodeAlterAggregate(ctx, stmt)
|
||||
case *tree.AlterDatabase:
|
||||
return nodeAlterDatabase(ctx, stmt)
|
||||
case *tree.AlterDefaultPrivileges:
|
||||
return nodeAlterDefaultPrivileges(ctx, stmt)
|
||||
case *tree.AlterDomain:
|
||||
return nodeAlterDomain(ctx, stmt)
|
||||
case *tree.AlterFunction:
|
||||
return nodeAlterFunction(ctx, stmt)
|
||||
case *tree.AlterIndex:
|
||||
return nodeAlterIndex(ctx, stmt)
|
||||
case *tree.AlterMaterializedView:
|
||||
return nodeAlterMaterializedView(ctx, stmt)
|
||||
case *tree.AlterProcedure:
|
||||
return nodeAlterProcedure(ctx, stmt)
|
||||
case *tree.AlterRole:
|
||||
return nodeAlterRole(ctx, stmt)
|
||||
case *tree.AlterSchema:
|
||||
return nodeAlterSchema(ctx, stmt)
|
||||
case *tree.AlterSequence:
|
||||
return nodeAlterSequence(ctx, stmt)
|
||||
case *tree.AlterTable:
|
||||
return nodeAlterTable(ctx, stmt)
|
||||
case *tree.AlterTablePartition:
|
||||
return nodeAlterTablePartition(ctx, stmt)
|
||||
case *tree.AlterTableSetSchema:
|
||||
return nodeAlterTableSetSchema(ctx, stmt)
|
||||
case *tree.AlterType:
|
||||
return nodeAlterType(ctx, stmt)
|
||||
case *tree.AlterView:
|
||||
return nodeAlterView(ctx, stmt)
|
||||
case *tree.Analyze:
|
||||
return nodeAnalyze(ctx, stmt)
|
||||
case *tree.Backup:
|
||||
return nodeBackup(ctx, stmt)
|
||||
case *tree.BeginTransaction:
|
||||
return nodeBeginTransaction(ctx, stmt)
|
||||
case *tree.Call:
|
||||
return nodeCall(ctx, stmt)
|
||||
case *tree.CancelQueries:
|
||||
return nodeCancelQueries(ctx, stmt)
|
||||
case *tree.CancelSessions:
|
||||
return nodeCancelSessions(ctx, stmt)
|
||||
case *tree.CannedOptPlan:
|
||||
return nodeCannedOptPlan(ctx, stmt)
|
||||
case *tree.Comment:
|
||||
return nodeComment(ctx, stmt)
|
||||
case *tree.CommitTransaction:
|
||||
return nodeCommitTransaction(ctx, stmt)
|
||||
case *tree.ControlJobs:
|
||||
return nodeControlJobs(ctx, stmt)
|
||||
case *tree.ControlJobsForSchedules:
|
||||
return nodeControlJobsForSchedules(ctx, stmt)
|
||||
case *tree.ControlSchedules:
|
||||
return nodeControlSchedules(ctx, stmt)
|
||||
case *tree.CopyFrom:
|
||||
return nodeCopyFrom(ctx, stmt)
|
||||
case *tree.CreateAggregate:
|
||||
return nodeCreateAggregate(ctx, stmt)
|
||||
case *tree.CreateCast:
|
||||
return nodeCreateCast(ctx, stmt)
|
||||
case *tree.CreateChangefeed:
|
||||
return nodeCreateChangefeed(ctx, stmt)
|
||||
case *tree.CreateDatabase:
|
||||
return nodeCreateDatabase(ctx, stmt)
|
||||
case *tree.CreateDomain:
|
||||
return nodeCreateDomain(ctx, stmt)
|
||||
case *tree.CreateExtension:
|
||||
return nodeCreateExtension(ctx, stmt)
|
||||
case *tree.CreateFunction:
|
||||
return nodeCreateFunction(ctx, stmt)
|
||||
case *tree.CreateIndex:
|
||||
return nodeCreateIndex(ctx, stmt)
|
||||
case *tree.CreateMaterializedView:
|
||||
return nodeCreateMaterializedView(ctx, stmt)
|
||||
case *tree.CreateProcedure:
|
||||
return nodeCreateProcedure(ctx, stmt)
|
||||
case *tree.CreateRole:
|
||||
return nodeCreateRole(ctx, stmt)
|
||||
case *tree.CreateSchema:
|
||||
return nodeCreateSchema(ctx, stmt)
|
||||
case *tree.CreateSequence:
|
||||
return nodeCreateSequence(ctx, stmt)
|
||||
case *tree.CreateStats:
|
||||
return nodeCreateStats(ctx, stmt)
|
||||
case *tree.CreateTable:
|
||||
return nodeCreateTable(ctx, stmt)
|
||||
case *tree.CreateTrigger:
|
||||
return nodeCreateTrigger(ctx, stmt)
|
||||
case *tree.CreateType:
|
||||
return nodeCreateType(ctx, stmt)
|
||||
case *tree.CreateView:
|
||||
return nodeCreateView(ctx, stmt)
|
||||
case *tree.Deallocate:
|
||||
return nodeDeallocate(ctx, stmt)
|
||||
case *tree.Delete:
|
||||
return nodeDelete(ctx, stmt)
|
||||
case *tree.Discard:
|
||||
return nodeDiscard(ctx, stmt)
|
||||
case *tree.DropAggregate:
|
||||
return nodeDropAggregate(ctx, stmt)
|
||||
case *tree.DropCast:
|
||||
return nodeDropCast(ctx, stmt)
|
||||
case *tree.DropDatabase:
|
||||
return nodeDropDatabase(ctx, stmt)
|
||||
case *tree.DropDomain:
|
||||
return nodeDropDomain(ctx, stmt)
|
||||
case *tree.DropExtension:
|
||||
return nodeDropExtension(ctx, stmt)
|
||||
case *tree.DropFunction:
|
||||
return nodeDropFunction(ctx, stmt)
|
||||
case *tree.DropIndex:
|
||||
return nodeDropIndex(ctx, stmt)
|
||||
case *tree.DropProcedure:
|
||||
return nodeDropProcedure(ctx, stmt)
|
||||
case *tree.DropRole:
|
||||
return nodeDropRole(ctx, stmt)
|
||||
case *tree.DropSchema:
|
||||
return nodeDropSchema(ctx, stmt)
|
||||
case *tree.DropSequence:
|
||||
return nodeDropSequence(ctx, stmt)
|
||||
case *tree.DropTable:
|
||||
return nodeDropTable(ctx, stmt)
|
||||
case *tree.DropTrigger:
|
||||
return nodeDropTrigger(ctx, stmt)
|
||||
case *tree.DropType:
|
||||
return nodeDropType(ctx, stmt)
|
||||
case *tree.DropView:
|
||||
return nodeDropView(ctx, stmt)
|
||||
case *tree.Execute:
|
||||
return nodeExecute(ctx, stmt)
|
||||
case *tree.Explain:
|
||||
return nodeExplain(ctx, stmt)
|
||||
case *tree.ExplainAnalyzeDebug:
|
||||
return nodeExplainAnalyzeDebug(ctx, stmt)
|
||||
case *tree.Export:
|
||||
return nodeExport(ctx, stmt)
|
||||
case *tree.Grant:
|
||||
return nodeGrant(ctx, stmt)
|
||||
case *tree.GrantRole:
|
||||
return nodeGrantRole(ctx, stmt)
|
||||
case *tree.Import:
|
||||
return nodeImport(ctx, stmt)
|
||||
case *tree.Insert:
|
||||
return nodeInsert(ctx, stmt)
|
||||
case *tree.ParenSelect:
|
||||
return nodeParenSelect(ctx, stmt)
|
||||
case *tree.Prepare:
|
||||
return nodePrepare(ctx, stmt)
|
||||
case *tree.RefreshMaterializedView:
|
||||
return nodeRefreshMaterializedView(ctx, stmt)
|
||||
case *tree.ReleaseSavepoint:
|
||||
return nodeReleaseSavepoint(ctx, stmt)
|
||||
case *tree.Relocate:
|
||||
return nodeRelocate(ctx, stmt)
|
||||
case *tree.RenameColumn:
|
||||
return nodeRenameColumn(ctx, stmt)
|
||||
case *tree.RenameDatabase:
|
||||
return nodeRenameDatabase(ctx, stmt)
|
||||
case *tree.RenameIndex:
|
||||
return nodeRenameIndex(ctx, stmt)
|
||||
case *tree.RenameTable:
|
||||
return nodeRenameTable(ctx, stmt)
|
||||
case *tree.ReparentDatabase:
|
||||
return nodeReparentDatabase(ctx, stmt)
|
||||
case *tree.Restore:
|
||||
return nodeRestore(ctx, stmt)
|
||||
case *tree.Return:
|
||||
return nodeReturn(ctx, stmt)
|
||||
case *tree.Revoke:
|
||||
return nodeRevoke(ctx, stmt)
|
||||
case *tree.RevokeRole:
|
||||
return nodeRevokeRole(ctx, stmt)
|
||||
case *tree.RollbackToSavepoint:
|
||||
return nodeRollbackToSavepoint(ctx, stmt)
|
||||
case *tree.RollbackTransaction:
|
||||
return nodeRollbackTransaction(ctx, stmt)
|
||||
case *tree.Savepoint:
|
||||
return nodeSavepoint(ctx, stmt)
|
||||
case *tree.Scatter:
|
||||
return nodeScatter(ctx, stmt)
|
||||
case *tree.ScheduledBackup:
|
||||
return nodeScheduledBackup(ctx, stmt)
|
||||
case *tree.Scrub:
|
||||
return nodeScrub(ctx, stmt)
|
||||
case *tree.Select:
|
||||
return nodeSelect(ctx, stmt)
|
||||
case *tree.SelectClause:
|
||||
return nodeSelectClause(ctx, stmt)
|
||||
case *tree.SetSessionAuthorization:
|
||||
return nodeSetSessionAuthorization(ctx, stmt)
|
||||
case *tree.SetSessionCharacteristics:
|
||||
return nodeSetSessionCharacteristics(ctx, stmt)
|
||||
case *tree.SetTransaction:
|
||||
return nodeSetTransaction(ctx, stmt)
|
||||
case *tree.SetVar:
|
||||
return nodeSetVar(ctx, stmt)
|
||||
case *tree.ShowBackup:
|
||||
return nodeShowBackup(ctx, stmt)
|
||||
case *tree.ShowColumns:
|
||||
return nodeShowColumns(ctx, stmt)
|
||||
case *tree.ShowConstraints:
|
||||
return nodeShowConstraints(ctx, stmt)
|
||||
case *tree.ShowCreate:
|
||||
return nodeShowCreate(ctx, stmt)
|
||||
case *tree.ShowDatabaseIndexes:
|
||||
return nodeShowDatabaseIndexes(ctx, stmt)
|
||||
case *tree.ShowDatabases:
|
||||
return nodeShowDatabases(ctx, stmt)
|
||||
case *tree.ShowEnums:
|
||||
return nodeShowEnums(ctx, stmt)
|
||||
case *tree.ShowFingerprints:
|
||||
return nodeShowFingerprints(ctx, stmt)
|
||||
case *tree.ShowGrants:
|
||||
return nodeShowGrants(ctx, stmt)
|
||||
case *tree.ShowHistogram:
|
||||
return nodeShowHistogram(ctx, stmt)
|
||||
case *tree.ShowIndexes:
|
||||
return nodeShowIndexes(ctx, stmt)
|
||||
case *tree.ShowJobs:
|
||||
return nodeShowJobs(ctx, stmt)
|
||||
case *tree.ShowLastQueryStatistics:
|
||||
return nodeShowLastQueryStatistics(ctx, stmt)
|
||||
case *tree.ShowPartitions:
|
||||
return nodeShowPartitions(ctx, stmt)
|
||||
case *tree.ShowQueries:
|
||||
return nodeShowQueries(ctx, stmt)
|
||||
case *tree.ShowRoleGrants:
|
||||
return nodeShowRoleGrants(ctx, stmt)
|
||||
case *tree.ShowRoles:
|
||||
return nodeShowRoles(ctx, stmt)
|
||||
case *tree.ShowSavepointStatus:
|
||||
return nodeShowSavepointStatus(ctx, stmt)
|
||||
case *tree.ShowSchedules:
|
||||
return nodeShowSchedules(ctx, stmt)
|
||||
case *tree.ShowSchemas:
|
||||
return nodeShowSchemas(ctx, stmt)
|
||||
case *tree.ShowSequences:
|
||||
return nodeShowSequences(ctx, stmt)
|
||||
case *tree.ShowSessions:
|
||||
return nodeShowSessions(ctx, stmt)
|
||||
case *tree.ShowSyntax:
|
||||
return nodeShowSyntax(ctx, stmt)
|
||||
case *tree.ShowTableStats:
|
||||
return nodeShowTableStats(ctx, stmt)
|
||||
case *tree.ShowTables:
|
||||
return nodeShowTables(ctx, stmt)
|
||||
case *tree.ShowTraceForSession:
|
||||
return nodeShowTraceForSession(ctx, stmt)
|
||||
case *tree.ShowTransactionStatus:
|
||||
return nodeShowTransactionStatus(ctx, stmt)
|
||||
case *tree.ShowTransactions:
|
||||
return nodeShowTransactions(ctx, stmt)
|
||||
case *tree.ShowTypes:
|
||||
return nodeShowTypes(ctx, stmt)
|
||||
case *tree.ShowUsers:
|
||||
return nodeShowUsers(ctx, stmt)
|
||||
case *tree.ShowVar:
|
||||
return nodeShowVar(ctx, stmt)
|
||||
case *tree.Split:
|
||||
return nodeSplit(ctx, stmt)
|
||||
case *tree.Truncate:
|
||||
return nodeTruncate(ctx, stmt)
|
||||
case *tree.UnionClause:
|
||||
return nodeUnionClause(ctx, stmt)
|
||||
case *tree.Unsplit:
|
||||
return nodeUnsplit(ctx, stmt)
|
||||
case *tree.Update:
|
||||
return nodeUpdate(ctx, stmt)
|
||||
case *tree.Vacuum:
|
||||
return nodeVacuum(ctx, stmt)
|
||||
case *tree.ValuesClause:
|
||||
return nodeValuesClause(ctx, stmt)
|
||||
case nil:
|
||||
return nil, nil
|
||||
default:
|
||||
return nil, errors.Errorf("unknown statement type encountered: `%T`", stmt)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
// Copyright 2023 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 ast
|
||||
|
||||
import (
|
||||
"github.com/cockroachdb/errors"
|
||||
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
pgnodes "github.com/dolthub/doltgresql/server/node"
|
||||
)
|
||||
|
||||
// nodeCopyFrom handles *tree.CopyFrom nodes.
|
||||
func nodeCopyFrom(ctx *Context, node *tree.CopyFrom) (vitess.Statement, error) {
|
||||
if node == nil {
|
||||
return nil, nil
|
||||
}
|
||||
if node.Options.CopyFormat == tree.CopyFormatBinary {
|
||||
return nil, errors.Errorf("COPY FROM does not support format BINARY")
|
||||
}
|
||||
|
||||
// We start by creating a stub insert statement for the COPY FROM statement, which we will use to build a basic
|
||||
// INSERT plan for. At runtime we will swap out the bogus row values for our actual data read from STDIN.
|
||||
var columns []vitess.ColIdent
|
||||
if len(node.Columns) > 0 {
|
||||
columns = make([]vitess.ColIdent, len(node.Columns))
|
||||
for i := range node.Columns {
|
||||
columns[i] = vitess.NewColIdent(string(node.Columns[i]))
|
||||
}
|
||||
}
|
||||
|
||||
tableName, err := nodeTableName(ctx, &node.Table)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stubValues := make(vitess.Values, 1)
|
||||
stubValues[0] = make(vitess.ValTuple, len(columns))
|
||||
for i := range columns {
|
||||
stubValues[0][i] = &vitess.NullVal{}
|
||||
}
|
||||
|
||||
return vitess.InjectedStatement{
|
||||
Statement: pgnodes.NewCopyFrom(
|
||||
node.Table.Catalog(),
|
||||
doltdb.TableName{
|
||||
Name: node.Table.Object(),
|
||||
Schema: node.Table.Schema(),
|
||||
},
|
||||
node.Options,
|
||||
node.File,
|
||||
node.Stdin,
|
||||
node.Columns,
|
||||
&vitess.Insert{
|
||||
Action: vitess.InsertStr,
|
||||
Table: tableName,
|
||||
Columns: columns,
|
||||
Rows: &vitess.AliasedValues{
|
||||
Values: stubValues,
|
||||
},
|
||||
},
|
||||
),
|
||||
Children: nil,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
// 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 ast
|
||||
|
||||
import (
|
||||
"github.com/cockroachdb/errors"
|
||||
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
)
|
||||
|
||||
// nodeCreateAggregate handles *tree.CreateAggregate nodes.
|
||||
func nodeCreateAggregate(ctx *Context, node *tree.CreateAggregate) (vitess.Statement, error) {
|
||||
if node == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if !ignoreUnsupportedStatements {
|
||||
if err := validateAggArgMode(ctx, node.Args, node.OrderByArgs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return NotYetSupportedError("CREATE AGGREGATE is not yet supported")
|
||||
}
|
||||
|
||||
// validateAggArgMode checks routine arguments for `OUT` and `INOUT` modes,
|
||||
// which cannot be used for AGGREGATE arguments.
|
||||
func validateAggArgMode(ctx *Context, args, orderByArgs tree.RoutineArgs) error {
|
||||
for _, sig := range args {
|
||||
if sig.Mode == tree.RoutineArgModeOut || sig.Mode == tree.RoutineArgModeInout {
|
||||
return errors.Errorf("aggregate functions do not support OUT or INOUT arguments")
|
||||
}
|
||||
}
|
||||
for _, sig := range orderByArgs {
|
||||
if sig.Mode == tree.RoutineArgModeOut || sig.Mode == tree.RoutineArgModeInout {
|
||||
return errors.Errorf("aggregate functions do not support OUT or INOUT arguments")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
// 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 ast
|
||||
|
||||
import (
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/core/casts"
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
"github.com/dolthub/doltgresql/server/auth"
|
||||
pgnodes "github.com/dolthub/doltgresql/server/node"
|
||||
)
|
||||
|
||||
// nodeCreateCast handles *tree.CreateCast nodes.
|
||||
func nodeCreateCast(ctx *Context, node *tree.CreateCast) (vitess.Statement, error) {
|
||||
if node == nil {
|
||||
return nil, nil
|
||||
}
|
||||
_, sourceType, err := nodeResolvableTypeReference(ctx, node.Source, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, targetType, err := nodeResolvableTypeReference(ctx, node.Target, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var funcName tree.TableName
|
||||
var funcParams []pgnodes.RoutineParam
|
||||
switch node.Type {
|
||||
case tree.CreateCastType_WithFunction:
|
||||
funcName = node.FuncName.ToTableName()
|
||||
funcParams = make([]pgnodes.RoutineParam, len(node.FuncArgs))
|
||||
for i, arg := range node.FuncArgs {
|
||||
funcParams[i].Name = arg.Name.String()
|
||||
_, funcParams[i].Type, err = nodeResolvableTypeReference(ctx, arg.Type, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
case tree.CreateCastType_WithoutFunction:
|
||||
// TODO: restrict to superusers only with error: "must be superuser to create a cast WITHOUT FUNCTION"
|
||||
case tree.CreateCastType_Inout:
|
||||
// Nothing to do
|
||||
default:
|
||||
panic("unhandled case")
|
||||
}
|
||||
var scope casts.CastType
|
||||
switch node.Scope {
|
||||
case tree.CreateCastScope_Explicit:
|
||||
scope = casts.CastType_Explicit
|
||||
case tree.CreateCastScope_Assignment:
|
||||
scope = casts.CastType_Assignment
|
||||
case tree.CreateCastScope_Implicit:
|
||||
scope = casts.CastType_Implicit
|
||||
}
|
||||
return vitess.InjectedStatement{
|
||||
Statement: pgnodes.NewCreateCast(
|
||||
sourceType,
|
||||
targetType,
|
||||
scope,
|
||||
node.Type,
|
||||
funcName.Schema(),
|
||||
funcName.Table(),
|
||||
funcParams,
|
||||
),
|
||||
Auth: vitess.AuthInformation{
|
||||
AuthType: auth.AuthType_CREATE,
|
||||
TargetType: auth.AuthTargetType_TODO,
|
||||
TargetNames: []string{},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright 2023 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 ast
|
||||
|
||||
import (
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
)
|
||||
|
||||
// nodeCreateChangefeed handles *tree.CreateChangefeed nodes.
|
||||
func nodeCreateChangefeed(ctx *Context, node *tree.CreateChangefeed) (vitess.Statement, error) {
|
||||
if node == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return NotYetSupportedError("CREATE CHANGEFEED is not yet supported")
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
// Copyright 2023 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 ast
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
)
|
||||
|
||||
// nodeCreateDatabase handles *tree.CreateDatabase nodes.
|
||||
func nodeCreateDatabase(_ *Context, node *tree.CreateDatabase) (*vitess.DBDDL, error) {
|
||||
var charsets []*vitess.CharsetAndCollate
|
||||
|
||||
if len(node.Template) > 0 {
|
||||
// TODO: special casing "template0", as some tests make use of it and we need them to pass for now
|
||||
if node.Template != "template0" {
|
||||
return nil, errors.Errorf("TEMPLATE clause is not yet supported")
|
||||
}
|
||||
}
|
||||
if len(node.Encoding) > 0 {
|
||||
logrus.Warnf("unsupported clause ENCODING, ignoring")
|
||||
}
|
||||
if len(node.Strategy) > 0 {
|
||||
return nil, errors.Errorf("STRATEGY clause is not yet supported")
|
||||
}
|
||||
if len(node.Locale) > 0 {
|
||||
logrus.Warnf("unsupported clause LC_LOCALE, ignoring")
|
||||
}
|
||||
if len(node.Collate) > 0 {
|
||||
collation, charset, err := parseLocaleString(node.Collate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if collation == "" {
|
||||
logrus.Warnf("unsupported LC_COLLATE, ignoring")
|
||||
} else {
|
||||
charsets = append(charsets,
|
||||
&vitess.CharsetAndCollate{
|
||||
Type: "CHARACTER SET",
|
||||
Value: charset,
|
||||
},
|
||||
&vitess.CharsetAndCollate{
|
||||
Type: "COLLATE",
|
||||
Value: collation,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
if len(node.CType) > 0 {
|
||||
logrus.Warnf("CTYPE clause is not yet supported, ignoring")
|
||||
}
|
||||
if len(node.IcuLocale) > 0 {
|
||||
return nil, errors.Errorf("ICU_LOCALE clause is not yet supported")
|
||||
}
|
||||
if len(node.IcuRules) > 0 {
|
||||
return nil, errors.Errorf("TEMPLATE clause is not yet supported")
|
||||
}
|
||||
if len(node.LocaleProvider) > 0 {
|
||||
return nil, errors.Errorf("LOCALE_PROVIDER clause is not yet supported")
|
||||
}
|
||||
if len(node.CollationVersion) > 0 {
|
||||
return nil, errors.Errorf("COLLATION_VERSION clause is not yet supported")
|
||||
}
|
||||
if len(node.Tablespace) > 0 {
|
||||
return nil, errors.Errorf("TABLESPACE clause is not yet supported")
|
||||
}
|
||||
// TODO: some clauses have default values in case of not being defined.
|
||||
// ALLOW_CONNECTIONS defaults to TRUE
|
||||
if node.AllowConnections != nil {
|
||||
return nil, errors.Errorf("ALLOW_CONNECTIONS clause is not yet supported")
|
||||
}
|
||||
// CONNECTION LIMIT defaults to -1
|
||||
if node.ConnectionLimit != nil {
|
||||
return nil, errors.Errorf("CONNECTION LIMIT clause is not yet supported")
|
||||
}
|
||||
// IS_TEMPLATE defaults to FALSE
|
||||
if node.IsTemplate != nil {
|
||||
return nil, errors.Errorf("IS_TEMPLATE clause is not yet supported")
|
||||
}
|
||||
if node.Oid != nil {
|
||||
return nil, errors.Errorf("OID clause is not yet supported")
|
||||
}
|
||||
|
||||
return &vitess.DBDDL{
|
||||
Action: vitess.CreateStr,
|
||||
SchemaOrDatabase: "database",
|
||||
DBName: bareIdentifier(node.Name),
|
||||
IfNotExists: node.IfNotExists,
|
||||
CharsetCollate: charsets,
|
||||
}, nil
|
||||
}
|
||||
|
||||
var collationRegex = regexp.MustCompile(`^(?P<Language>[^_]+)_?(?P<Region>[^.]+)?\.?(?P<CodePage>\d+)?$`)
|
||||
|
||||
// parseLocaleString attempts to parse the locale string given to extract a mysql collation we can use
|
||||
func parseLocaleString(collation string) (string, string, error) {
|
||||
// FindStringSubmatchIndex returns the indices of the matched elements
|
||||
match := collationRegex.FindStringSubmatch(collation)
|
||||
|
||||
result := make(map[string]string)
|
||||
for i, name := range collationRegex.SubexpNames() {
|
||||
if i > 0 && i <= len(match) {
|
||||
result[name] = match[i]
|
||||
}
|
||||
}
|
||||
|
||||
if result["Language"] == "" {
|
||||
return "", "", errors.Errorf("malformed collation: %s", collation)
|
||||
}
|
||||
|
||||
switch strings.ToLower(result["Language"]) {
|
||||
case "english", "en":
|
||||
return "latin1_general_cs", "latin1", nil
|
||||
}
|
||||
|
||||
return "", "", nil
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
// 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 ast
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
pgnodes "github.com/dolthub/doltgresql/server/node"
|
||||
pgtypes "github.com/dolthub/doltgresql/server/types"
|
||||
)
|
||||
|
||||
// nodeCreateDomain handles *tree.CreateDomain nodes.
|
||||
func nodeCreateDomain(ctx *Context, node *tree.CreateDomain) (vitess.Statement, error) {
|
||||
if node == nil {
|
||||
return nil, nil
|
||||
}
|
||||
name, err := nodeUnresolvedObjectName(ctx, node.TypeName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, dataType, err := nodeResolvableTypeReference(ctx, node.DataType, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if dataType == pgtypes.Record {
|
||||
return nil, errors.Errorf(`"record" is not a valid base type for a domain`)
|
||||
}
|
||||
|
||||
if node.Collate != "" {
|
||||
return nil, errors.Errorf("domain collation is not yet supported")
|
||||
}
|
||||
var children []vitess.Expr
|
||||
if node.Default != nil {
|
||||
defExpr, err := nodeExpr(ctx, node.Default)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Wrap any default expression using a function call in parens to match MySQL's column default requirements
|
||||
if _, ok := defExpr.(*vitess.FuncExpr); ok {
|
||||
defExpr = &vitess.ParenExpr{Expr: defExpr}
|
||||
}
|
||||
children = append(children, defExpr)
|
||||
}
|
||||
|
||||
var definedNotNull, definedNull bool
|
||||
var checkConstraintNames []string
|
||||
var checkConstraintExprs []vitess.Expr
|
||||
for _, constraint := range node.Constraints {
|
||||
if constraint.Check != nil {
|
||||
check, err := verifyAndReplaceValue(node.DataType, constraint.Check)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
expr, err := nodeExpr(ctx, check)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
checkConstraintNames = append(checkConstraintNames, string(constraint.Constraint))
|
||||
checkConstraintExprs = append(checkConstraintExprs, expr)
|
||||
} else if constraint.NotNull {
|
||||
definedNotNull = true
|
||||
if definedNull {
|
||||
return nil, errors.Errorf("conflicting NULL/NOT NULL constraints")
|
||||
}
|
||||
} else {
|
||||
definedNull = true
|
||||
if definedNotNull {
|
||||
return nil, errors.Errorf("conflicting NULL/NOT NULL constraints")
|
||||
}
|
||||
}
|
||||
}
|
||||
children = append(children, checkConstraintExprs...)
|
||||
return vitess.InjectedStatement{
|
||||
Statement: &pgnodes.CreateDomain{
|
||||
SchemaName: name.SchemaQualifier.String(),
|
||||
Name: name.Name.String(),
|
||||
AsType: dataType,
|
||||
Collation: node.Collate,
|
||||
HasDefault: node.Default != nil,
|
||||
IsNotNull: definedNotNull,
|
||||
CheckConstraintNames: checkConstraintNames,
|
||||
},
|
||||
Children: children,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// verifyAndReplaceValue verifies that only VALUE is referenced and replaces it with DomainColumn.
|
||||
// This function should be used for DOMAIN statements only.
|
||||
func verifyAndReplaceValue(typ tree.ResolvableTypeReference, expr tree.Expr) (tree.Expr, error) {
|
||||
return tree.SimpleVisit(expr, func(visitingExpr tree.Expr) (recurse bool, newExpr tree.Expr, err error) {
|
||||
switch v := visitingExpr.(type) {
|
||||
case *tree.UnresolvedName:
|
||||
if strings.ToLower(v.String()) != "value" {
|
||||
return false, nil, errors.Errorf(`column "%s" does not exist`, v.String())
|
||||
}
|
||||
return false, tree.DomainColumn{Typ: typ}, nil
|
||||
}
|
||||
return true, visitingExpr, nil
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// 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 ast
|
||||
|
||||
import (
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
pgnodes "github.com/dolthub/doltgresql/server/node"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
)
|
||||
|
||||
// nodeCreateExtension handles *tree.CreateExtension nodes.
|
||||
func nodeCreateExtension(ctx *Context, node *tree.CreateExtension) (vitess.Statement, error) {
|
||||
if len(node.Schema) > 0 {
|
||||
if node.Schema == "pg_catalog" && node.Name == "plpgsql" {
|
||||
return nil, nil
|
||||
} else if node.Schema != "public" {
|
||||
return NotYetSupportedError("non public SCHEMA is not yet supported")
|
||||
}
|
||||
// TODO filter out extensions we support
|
||||
}
|
||||
if len(node.Version) > 0 {
|
||||
return NotYetSupportedError("VERSION is not yet supported")
|
||||
}
|
||||
if node.Cascade {
|
||||
return NotYetSupportedError("CASCADE is not yet supported")
|
||||
}
|
||||
return vitess.InjectedStatement{
|
||||
Statement: pgnodes.NewCreateExtension(
|
||||
string(node.Name),
|
||||
node.IfNotExists,
|
||||
node.Schema,
|
||||
node.Version,
|
||||
node.Cascade,
|
||||
),
|
||||
Children: nil,
|
||||
}, 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 ast
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/core/id"
|
||||
"github.com/dolthub/doltgresql/postgres/parser/parser"
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
"github.com/dolthub/doltgresql/server/auth"
|
||||
"github.com/dolthub/doltgresql/server/functions/framework"
|
||||
pgnodes "github.com/dolthub/doltgresql/server/node"
|
||||
"github.com/dolthub/doltgresql/server/plpgsql"
|
||||
pgtypes "github.com/dolthub/doltgresql/server/types"
|
||||
)
|
||||
|
||||
// nodeCreateFunction handles *tree.CreateFunction nodes.
|
||||
func nodeCreateFunction(ctx *Context, node *tree.CreateFunction) (vitess.Statement, error) {
|
||||
options, err := validateRoutineOptions(ctx, node.Options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Grab the general information that we'll need to create the function
|
||||
tableName := node.Name.ToTableName()
|
||||
var retType *pgtypes.DoltgresType
|
||||
if len(node.RetType) == 0 {
|
||||
retType = pgtypes.Void
|
||||
} else if !node.ReturnsTable {
|
||||
// Return types may specify "trigger", but this doesn't apply elsewhere
|
||||
_, retType, err = nodeResolvableTypeReference(ctx, node.RetType[0].Type, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
retType = createAnonymousCompositeType(node.RetType)
|
||||
}
|
||||
|
||||
params := make([]pgnodes.RoutineParam, len(node.Args))
|
||||
var defaults []vitess.Expr
|
||||
for i, arg := range node.Args {
|
||||
// parameter name
|
||||
params[i].Name = arg.Name.String()
|
||||
// parameter type
|
||||
_, params[i].Type, err = nodeResolvableTypeReference(ctx, arg.Type, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// parameter default
|
||||
if arg.Default != nil {
|
||||
params[i].HasDefault = true
|
||||
d, err := nodeExpr(ctx, arg.Default)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defaults = append(defaults, d)
|
||||
}
|
||||
}
|
||||
var strict bool
|
||||
if nullInputOption, ok := options[tree.OptionNullInput]; ok {
|
||||
if nullInputOption.NullInput == tree.ReturnsNullOnNullInput || nullInputOption.NullInput == tree.StrictNullInput {
|
||||
strict = true
|
||||
}
|
||||
}
|
||||
// We only support PL/pgSQL, SQL and C for now, so we verify that here
|
||||
var parsedBody []plpgsql.InterpreterOperation
|
||||
var sqlDef string
|
||||
var sqlDefParsedStmts []vitess.Statement
|
||||
var extensionName, extensionSymbol string
|
||||
if languageOption, ok := options[tree.OptionLanguage]; ok {
|
||||
switch strings.ToLower(languageOption.Language) {
|
||||
case "plpgsql":
|
||||
// PL/pgSQL is different from standard Postgres SQL, so we have to use a special parser to handle it.
|
||||
// This parser also requires the full `CREATE FUNCTION` string, so we'll pass that.
|
||||
parsedBody, err = plpgsql.Parse(ctx.originalQuery)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// parse types
|
||||
for i, op := range parsedBody {
|
||||
switch op.OpCode {
|
||||
case plpgsql.OpCode_Declare:
|
||||
// ParseType uses casting to parse the given type, but
|
||||
// some special types cannot be cast. Eg: `user_defined_table_type%ROWTYPE`
|
||||
if declareTyp, err := parser.ParseType(op.PrimaryData); err == nil {
|
||||
if _, dt, err := nodeResolvableTypeReference(ctx, declareTyp, false); err == nil && dt != nil {
|
||||
dtName := dt.Name()
|
||||
if dt.Schema() != "" {
|
||||
dtName = fmt.Sprintf("%s.%s", dt.Schema(), dtName)
|
||||
}
|
||||
parsedBody[i].PrimaryData = dtName
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
case "sql":
|
||||
as, ok := options[tree.OptionAs1]
|
||||
if ok {
|
||||
sqlDef, sqlDefParsedStmts, err = handleLanguageSQLAs(as.Definition, params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
break
|
||||
}
|
||||
sqlBody, ok := options[tree.OptionSqlBody]
|
||||
if ok {
|
||||
beginAtomic, ok := sqlBody.SqlBody.(*tree.BeginEndBlock)
|
||||
if !ok {
|
||||
return nil, errors.Errorf("Expected BEGIN in CREATE FUNCTION definition, got %T", sqlBody.SqlBody)
|
||||
}
|
||||
stmts := make([]parser.Statement, len(beginAtomic.Statements))
|
||||
for i, s := range beginAtomic.Statements {
|
||||
stmts[i] = parser.Statement{
|
||||
AST: s,
|
||||
SQL: s.String(),
|
||||
}
|
||||
}
|
||||
sqlDef, sqlDefParsedStmts, err = convertSQLStmts(stmts, params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
break
|
||||
}
|
||||
return nil, errors.Errorf("CREATE FUNCTION definition needed for LANGUAGE SQL")
|
||||
case "c":
|
||||
symbolOption, ok := options[tree.OptionAs2]
|
||||
if !ok {
|
||||
return nil, errors.Errorf("LANGUAGE C is only supported when providing both the module name and symbol")
|
||||
}
|
||||
extensionName = symbolOption.ObjFile
|
||||
extensionSymbol = symbolOption.LinkSymbol
|
||||
default:
|
||||
return nil, errors.Errorf("CREATE FUNCTION only supports PL/pgSQL, C and SQL for now; others are not yet supported")
|
||||
}
|
||||
} else {
|
||||
return nil, errors.Errorf("CREATE FUNCTION does not define an input language")
|
||||
}
|
||||
// Returns the stored procedure call with all options
|
||||
return vitess.InjectedStatement{
|
||||
Statement: pgnodes.NewCreateFunction(
|
||||
tableName.Table(),
|
||||
tableName.Schema(),
|
||||
node.Replace,
|
||||
retType,
|
||||
params,
|
||||
strict,
|
||||
ctx.originalQuery,
|
||||
extensionName,
|
||||
extensionSymbol,
|
||||
parsedBody,
|
||||
sqlDef,
|
||||
sqlDefParsedStmts,
|
||||
node.ReturnsSetOf || node.ReturnsTable,
|
||||
),
|
||||
Auth: vitess.AuthInformation{
|
||||
AuthType: auth.AuthType_CREATE,
|
||||
TargetType: auth.AuthTargetType_SchemaIdentifiers,
|
||||
TargetNames: []string{tableName.Catalog(), tableName.Schema()},
|
||||
},
|
||||
Children: defaults,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// createAnonymousCompositeType creates a new DoltgresType for the anonymous composite return
|
||||
// type for a function, as represented by the |fieldTypes| that were specified in the function
|
||||
// definition.
|
||||
func createAnonymousCompositeType(fieldTypes []tree.SimpleColumnDef) *pgtypes.DoltgresType {
|
||||
attrs := make([]pgtypes.CompositeAttribute, len(fieldTypes))
|
||||
for i, fieldType := range fieldTypes {
|
||||
attrs[i] = pgtypes.NewCompositeAttribute(nil, id.Null, fieldType.Name.String(),
|
||||
pgtypes.NewUnresolvedDoltgresTypeFromID(id.NewType("", fieldType.Type.SQLString())), int16(i), "")
|
||||
}
|
||||
|
||||
typeIdString := "table("
|
||||
for i, attr := range attrs {
|
||||
if i > 0 {
|
||||
typeIdString += ","
|
||||
}
|
||||
typeIdString += attr.Name
|
||||
typeIdString += ":"
|
||||
typeIdString += attr.Type.ID.TypeName()
|
||||
}
|
||||
typeIdString += ")"
|
||||
|
||||
// NOTE: there is no schema needed, since these types are anonymous and can't be directly referenced
|
||||
typeId := id.NewType("", typeIdString)
|
||||
|
||||
return pgtypes.NewCompositeType(context.Background(), id.Null, nil, typeId, attrs)
|
||||
}
|
||||
|
||||
// handleLanguageSQLAs handles parsing SQL definition strings in both CREATE FUNCTION and CREATE PROCEDURE
|
||||
// and returns converted the sql statements into vitess statements.
|
||||
func handleLanguageSQLAs(definition string, params []pgnodes.RoutineParam) (string, []vitess.Statement, error) {
|
||||
stmts, err := parser.Parse(definition)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
return convertSQLStmts(stmts, params)
|
||||
}
|
||||
|
||||
// convertSQLStmts takes parser.Statements and routine parameters and
|
||||
// returns converted to string representation and vitess statements.
|
||||
func convertSQLStmts(stmts parser.Statements, params []pgnodes.RoutineParam) (string, []vitess.Statement, error) {
|
||||
paramMap := make(map[string]*framework.ParamTypAndValue, len(params))
|
||||
for i, param := range params {
|
||||
tv := &framework.ParamTypAndValue{
|
||||
Typ: param.Type,
|
||||
Val: nil,
|
||||
FromCreate: true,
|
||||
}
|
||||
// placeholder name is empty
|
||||
if param.Name == "\"\"" {
|
||||
n := fmt.Sprintf("$%d", i+1)
|
||||
paramMap[n] = tv
|
||||
params[i].Name = n
|
||||
} else {
|
||||
paramMap[param.Name] = tv
|
||||
}
|
||||
}
|
||||
|
||||
var sqlDefs = make([]string, len(stmts))
|
||||
var vitessASTs = make([]vitess.Statement, len(stmts))
|
||||
for i, stmt := range stmts {
|
||||
sqlDefs[i] = stmt.AST.String()
|
||||
err := framework.ReplaceFunctionColumn(stmt.AST, paramMap)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
// stmt.AST is updated at this point with FunctionColumn
|
||||
vitessASTs[i], err = Convert(stmt)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
}
|
||||
return strings.Join(sqlDefs, ";"), vitessASTs, nil
|
||||
}
|
||||
|
||||
// validateRoutineOptions ensures that each option is defined only once. Returns a map containing all options, or an
|
||||
// error if an option is invalid or is defined multiple times.
|
||||
func validateRoutineOptions(ctx *Context, options []tree.RoutineOption) (map[tree.FunctionOption]tree.RoutineOption, error) {
|
||||
var optDefined = make(map[tree.FunctionOption]tree.RoutineOption)
|
||||
for _, opt := range options {
|
||||
if _, ok := optDefined[opt.OptionType]; ok {
|
||||
return nil, errors.Errorf("ERROR: conflicting or redundant options")
|
||||
} else {
|
||||
optDefined[opt.OptionType] = opt
|
||||
}
|
||||
}
|
||||
return optDefined, nil
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
// Copyright 2023 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 ast
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
)
|
||||
|
||||
// nodeCreateIndex handles *tree.CreateIndex nodes.
|
||||
func nodeCreateIndex(ctx *Context, node *tree.CreateIndex) (*vitess.AlterTable, error) {
|
||||
if node == nil {
|
||||
return nil, nil
|
||||
}
|
||||
if node.Concurrently {
|
||||
return nil, errors.Errorf("concurrent index creation is not yet supported")
|
||||
}
|
||||
if node.Using != "" && strings.ToLower(node.Using) != "btree" {
|
||||
return nil, errors.Errorf("index method %s is not yet supported", node.Using)
|
||||
}
|
||||
indexDef, err := nodeIndexTableDef(ctx, &tree.IndexTableDef{
|
||||
Name: node.Name,
|
||||
Columns: node.Columns,
|
||||
IndexParams: node.IndexParams,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tableName, err := nodeTableName(ctx, &node.Table)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var indexType string
|
||||
if node.Unique {
|
||||
indexType = vitess.UniqueStr
|
||||
}
|
||||
var predicate vitess.Expr
|
||||
if node.Predicate != nil {
|
||||
predicate, err = nodeExpr(ctx, node.Predicate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &vitess.AlterTable{
|
||||
Table: tableName,
|
||||
Statements: []*vitess.DDL{
|
||||
{
|
||||
Action: vitess.AlterStr,
|
||||
Table: tableName,
|
||||
IfNotExists: node.IfNotExists,
|
||||
IndexSpec: &vitess.IndexSpec{
|
||||
Action: vitess.CreateStr,
|
||||
FromName: indexDef.Info.Name,
|
||||
ToName: indexDef.Info.Name,
|
||||
Type: indexType,
|
||||
Fields: indexDef.Fields,
|
||||
Options: indexDef.Options,
|
||||
Predicate: predicate,
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// 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 ast
|
||||
|
||||
import (
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
)
|
||||
|
||||
// nodeCreateMaterializedView handles *tree.CreateMaterializedView nodes.
|
||||
func nodeCreateMaterializedView(ctx *Context, node *tree.CreateMaterializedView) (vitess.Statement, error) {
|
||||
if node == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return NotYetSupportedError("CREATE MATERIALIZED VIEW is not yet supported")
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
// 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 ast
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/core/procedures"
|
||||
"github.com/dolthub/doltgresql/postgres/parser/parser"
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
"github.com/dolthub/doltgresql/server/auth"
|
||||
pgnodes "github.com/dolthub/doltgresql/server/node"
|
||||
"github.com/dolthub/doltgresql/server/plpgsql"
|
||||
)
|
||||
|
||||
// nodeCreateProcedure handles *tree.CreateProcedure nodes.
|
||||
func nodeCreateProcedure(ctx *Context, node *tree.CreateProcedure) (vitess.Statement, error) {
|
||||
options, err := validateRoutineOptions(ctx, node.Options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Grab the general information that we'll need to create the procedure
|
||||
tableName := node.Name.ToTableName()
|
||||
params := make([]pgnodes.RoutineParam, len(node.Args))
|
||||
var defaults []vitess.Expr
|
||||
for i, arg := range node.Args {
|
||||
// parameter name
|
||||
params[i].Name = arg.Name.String()
|
||||
// parameter type
|
||||
_, params[i].Type, err = nodeResolvableTypeReference(ctx, arg.Type, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// parameter mode
|
||||
switch arg.Mode {
|
||||
case tree.RoutineArgModeIn:
|
||||
params[i].Mode = procedures.ParameterMode_IN
|
||||
case tree.RoutineArgModeVariadic:
|
||||
params[i].Mode = procedures.ParameterMode_VARIADIC
|
||||
case tree.RoutineArgModeOut:
|
||||
params[i].Mode = procedures.ParameterMode_OUT
|
||||
case tree.RoutineArgModeInout:
|
||||
params[i].Mode = procedures.ParameterMode_INOUT
|
||||
default:
|
||||
return nil, errors.Newf("unknown procedure argmode: `%v`", arg.Mode)
|
||||
}
|
||||
// parameter default
|
||||
if arg.Default != nil {
|
||||
params[i].HasDefault = true
|
||||
d, err := nodeExpr(ctx, arg.Default)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defaults = append(defaults, d)
|
||||
}
|
||||
}
|
||||
// We only support PL/pgSQL, SQL and C for now, so we verify that here
|
||||
var parsedBody []plpgsql.InterpreterOperation
|
||||
var sqlDef string
|
||||
var sqlDefParsedStmts []vitess.Statement
|
||||
var extensionName, extensionSymbol string
|
||||
if languageOption, ok := options[tree.OptionLanguage]; ok {
|
||||
switch strings.ToLower(languageOption.Language) {
|
||||
case "plpgsql":
|
||||
// PL/pgSQL is different from standard Postgres SQL, so we have to use a special parser to handle it.
|
||||
// This parser also requires the full `CREATE PROCEDURE` string, so we'll pass that.
|
||||
parsedBody, err = plpgsql.Parse(ctx.originalQuery)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// parse types
|
||||
for i, op := range parsedBody {
|
||||
switch op.OpCode {
|
||||
case plpgsql.OpCode_Declare:
|
||||
// ParseType uses casting to parse the given type, but
|
||||
// some special types cannot be cast. Eg: `user_defined_table_type%ROWTYPE`
|
||||
if declareTyp, err := parser.ParseType(op.PrimaryData); err == nil {
|
||||
if _, dt, err := nodeResolvableTypeReference(ctx, declareTyp, false); err == nil && dt != nil {
|
||||
dtName := dt.Name()
|
||||
if dt.Schema() != "" {
|
||||
dtName = fmt.Sprintf("%s.%s", dt.Schema(), dtName)
|
||||
}
|
||||
parsedBody[i].PrimaryData = dtName
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
case "sql":
|
||||
as, ok := options[tree.OptionAs1]
|
||||
if !ok {
|
||||
return nil, errors.Errorf("CREATE PROCEDURE definition needed for LANGUAGE SQL")
|
||||
}
|
||||
sqlDef, sqlDefParsedStmts, err = handleLanguageSQLAs(as.Definition, params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case "c":
|
||||
symbolOption, ok := options[tree.OptionAs2]
|
||||
if !ok {
|
||||
return nil, errors.Errorf("LANGUAGE C is only supported when providing both the module name and symbol")
|
||||
}
|
||||
extensionName = symbolOption.ObjFile
|
||||
extensionSymbol = symbolOption.LinkSymbol
|
||||
default:
|
||||
return nil, errors.Errorf("CREATE PROCEDURE only supports PL/pgSQL, C and SQL for now; others are not yet supported")
|
||||
}
|
||||
} else {
|
||||
return nil, errors.Errorf("CREATE PROCEDURE does not define an input language")
|
||||
}
|
||||
// Returns the stored procedure call with all options
|
||||
return vitess.InjectedStatement{
|
||||
Statement: pgnodes.NewCreateProcedure(
|
||||
tableName.Table(),
|
||||
tableName.Schema(),
|
||||
node.Replace,
|
||||
params,
|
||||
ctx.originalQuery,
|
||||
extensionName,
|
||||
extensionSymbol,
|
||||
parsedBody,
|
||||
sqlDef,
|
||||
sqlDefParsedStmts,
|
||||
),
|
||||
Auth: vitess.AuthInformation{
|
||||
AuthType: auth.AuthType_CREATE,
|
||||
TargetType: auth.AuthTargetType_SchemaIdentifiers,
|
||||
TargetNames: []string{tableName.Catalog(), tableName.Schema()},
|
||||
},
|
||||
Children: defaults,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
// Copyright 2023 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 ast
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
pgnodes "github.com/dolthub/doltgresql/server/node"
|
||||
)
|
||||
|
||||
// nodeCreateRole handles *tree.CreateRole nodes.
|
||||
func nodeCreateRole(ctx *Context, node *tree.CreateRole) (vitess.Statement, error) {
|
||||
if node == nil {
|
||||
return nil, nil
|
||||
}
|
||||
if len(node.Name) == 0 {
|
||||
// The parser should make this impossible, but extra error checking is never bad
|
||||
return nil, errors.New(`role name cannot be empty`)
|
||||
}
|
||||
switch node.Name {
|
||||
case `public`:
|
||||
return nil, errors.New(`role name "public" is reserved`)
|
||||
case `current_role`, `current_user`, `session_user`:
|
||||
return nil, errors.Errorf(`%s cannot be used as a role name here`, strings.ToUpper(node.Name))
|
||||
}
|
||||
createRole := &pgnodes.CreateRole{
|
||||
Name: node.Name,
|
||||
IfNotExists: node.IfNotExists,
|
||||
Password: "",
|
||||
IsPasswordNull: true,
|
||||
IsSuperUser: false,
|
||||
CanCreateDB: false,
|
||||
CanCreateRoles: false,
|
||||
InheritPrivileges: true,
|
||||
CanLogin: !node.IsRole,
|
||||
IsReplicationRole: false,
|
||||
CanBypassRowLevelSecurity: false,
|
||||
ConnectionLimit: -1,
|
||||
ValidUntil: "",
|
||||
IsValidUntilSet: false,
|
||||
AddToRoles: nil,
|
||||
AddAsMembers: nil,
|
||||
AddAsAdminMembers: nil,
|
||||
}
|
||||
for _, kvOption := range node.KVOptions {
|
||||
switch strings.ToUpper(string(kvOption.Key)) {
|
||||
case "BYPASSRLS":
|
||||
createRole.CanBypassRowLevelSecurity = true
|
||||
case "CONNECTION_LIMIT":
|
||||
switch value := kvOption.Value.(type) {
|
||||
case *tree.DInt:
|
||||
if value == nil {
|
||||
createRole.ConnectionLimit = -1
|
||||
} else {
|
||||
// We enforce that only int32 values will fit here in the parser
|
||||
createRole.ConnectionLimit = int32(*value)
|
||||
}
|
||||
case tree.NullLiteral:
|
||||
createRole.ConnectionLimit = -1
|
||||
default:
|
||||
return nil, errors.Errorf(`unknown role option value (%T) for option "%s"`, kvOption.Value, kvOption.Key)
|
||||
}
|
||||
case "CREATEDB":
|
||||
createRole.CanCreateDB = true
|
||||
case "CREATEROLE":
|
||||
createRole.CanCreateRoles = true
|
||||
case "INHERIT":
|
||||
createRole.InheritPrivileges = true
|
||||
case "LOGIN":
|
||||
createRole.CanLogin = true
|
||||
case "NOBYPASSRLS":
|
||||
createRole.CanBypassRowLevelSecurity = false
|
||||
case "NOCREATEDB":
|
||||
createRole.CanCreateDB = false
|
||||
case "NOCREATEROLE":
|
||||
createRole.CanCreateRoles = false
|
||||
case "NOINHERIT":
|
||||
createRole.InheritPrivileges = false
|
||||
case "NOLOGIN":
|
||||
createRole.CanLogin = false
|
||||
case "NOREPLICATION":
|
||||
createRole.IsReplicationRole = false
|
||||
case "NOSUPERUSER":
|
||||
createRole.IsSuperUser = false
|
||||
case "PASSWORD":
|
||||
switch value := kvOption.Value.(type) {
|
||||
case *tree.DString:
|
||||
if value == nil {
|
||||
createRole.Password = ""
|
||||
createRole.IsPasswordNull = true
|
||||
} else {
|
||||
createRole.Password = string(*value)
|
||||
createRole.IsPasswordNull = false
|
||||
}
|
||||
case tree.NullLiteral:
|
||||
createRole.Password = ""
|
||||
createRole.IsPasswordNull = true
|
||||
default:
|
||||
return nil, errors.Errorf(`unknown role option value (%T) for option "%s"`, kvOption.Value, kvOption.Key)
|
||||
}
|
||||
case "REPLICATION":
|
||||
createRole.IsReplicationRole = true
|
||||
case "SUPERUSER":
|
||||
createRole.IsSuperUser = true
|
||||
case "SYSID":
|
||||
// This is an option that is ignored by Postgres. Assuming it used to be relevant, but not any longer.
|
||||
case "VALID_UNTIL":
|
||||
strVal, ok := kvOption.Value.(*tree.DString)
|
||||
if !ok {
|
||||
return nil, errors.Errorf(`unknown role option value (%T) for option "%s"`, kvOption.Value, kvOption.Key)
|
||||
}
|
||||
if strVal == nil {
|
||||
createRole.ValidUntil = ""
|
||||
createRole.IsValidUntilSet = false
|
||||
} else {
|
||||
createRole.ValidUntil = string(*strVal)
|
||||
createRole.IsValidUntilSet = true
|
||||
}
|
||||
default:
|
||||
return nil, errors.Errorf(`unknown role option "%s"`, kvOption.Key)
|
||||
}
|
||||
}
|
||||
return vitess.InjectedStatement{
|
||||
Statement: createRole,
|
||||
Children: nil,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
// Copyright 2023 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 ast
|
||||
|
||||
import (
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/server/auth"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
)
|
||||
|
||||
// nodeCreateSchema handles *tree.CreateSchema nodes.
|
||||
func nodeCreateSchema(ctx *Context, node *tree.CreateSchema) (vitess.Statement, error) {
|
||||
if node == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// CREATE SCHEMA AUTHORIZATION s1 creates a schema of the same name
|
||||
schemaName := node.Schema
|
||||
if schemaName == "" {
|
||||
schemaName = node.AuthRole
|
||||
}
|
||||
|
||||
return &vitess.DBDDL{
|
||||
Action: "CREATE",
|
||||
SchemaOrDatabase: "schema",
|
||||
DBName: schemaName,
|
||||
IfNotExists: node.IfNotExists,
|
||||
CharsetCollate: nil, // TODO
|
||||
Auth: vitess.AuthInformation{
|
||||
AuthType: auth.AuthType_CREATE,
|
||||
TargetType: auth.AuthTargetType_DatabaseIdentifiers,
|
||||
TargetNames: []string{""},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
// Copyright 2023 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 ast
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/core/id"
|
||||
"github.com/dolthub/doltgresql/core/sequences"
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
"github.com/dolthub/doltgresql/server/auth"
|
||||
pgnodes "github.com/dolthub/doltgresql/server/node"
|
||||
pgtypes "github.com/dolthub/doltgresql/server/types"
|
||||
)
|
||||
|
||||
// nodeCreateSequence handles *tree.CreateSequence nodes.
|
||||
func nodeCreateSequence(ctx *Context, node *tree.CreateSequence) (vitess.Statement, error) {
|
||||
if node == nil {
|
||||
return nil, nil
|
||||
}
|
||||
if node.Persistence.IsTemporary() {
|
||||
return nil, errors.Errorf("temporary sequences are not yet supported")
|
||||
}
|
||||
if node.Persistence.IsUnlogged() {
|
||||
return nil, errors.Errorf("unlogged sequences are not yet supported")
|
||||
}
|
||||
name, err := nodeTableName(ctx, &node.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(name.DbQualifier.String()) > 0 {
|
||||
return nil, errors.Errorf("CREATE SEQUENCE is currently only supported for the current database")
|
||||
}
|
||||
// Read all options and check whether they've been set (if not, we'll use the defaults)
|
||||
minValueLimit := int64(math.MinInt64)
|
||||
maxValueLimit := int64(math.MaxInt64)
|
||||
increment := int64(1)
|
||||
var minValue int64
|
||||
var maxValue int64
|
||||
var start int64
|
||||
var dataType *pgtypes.DoltgresType
|
||||
var ownerTableName string
|
||||
var ownerColumnName string
|
||||
minValueSet := false
|
||||
maxValueSet := false
|
||||
incrementSet := false
|
||||
startSet := false
|
||||
cycle := false
|
||||
fromAlter := false
|
||||
for _, option := range node.Options {
|
||||
switch option.Name {
|
||||
case tree.SeqOptAs:
|
||||
if !dataType.IsEmptyType() {
|
||||
return nil, errors.Errorf("conflicting or redundant options")
|
||||
}
|
||||
_, dataType, err = nodeResolvableTypeReference(ctx, option.AsType, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch dataType.ID {
|
||||
case pgtypes.Int16.ID:
|
||||
minValueLimit = int64(math.MinInt16)
|
||||
maxValueLimit = int64(math.MaxInt16)
|
||||
case pgtypes.Int32.ID:
|
||||
minValueLimit = int64(math.MinInt32)
|
||||
maxValueLimit = int64(math.MaxInt32)
|
||||
case pgtypes.Int64.ID:
|
||||
minValueLimit = int64(math.MinInt64)
|
||||
maxValueLimit = int64(math.MaxInt64)
|
||||
default:
|
||||
return nil, errors.Errorf("sequence type must be smallint, integer, or bigint")
|
||||
}
|
||||
case tree.SeqOptCycle:
|
||||
cycle = true
|
||||
case tree.SeqOptNoCycle:
|
||||
cycle = false
|
||||
case tree.SeqOptOwnedBy:
|
||||
expr, err := nodeExpr(ctx, option.ColumnItemVal)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
colName, ok := expr.(*vitess.ColName)
|
||||
if !ok {
|
||||
return nil, errors.New("expected sequence owner to be a table and column name")
|
||||
}
|
||||
if colName.Qualifier.SchemaQualifier.String() != name.SchemaQualifier.String() {
|
||||
return nil, errors.New("CREATE SEQUENCE must use the same schema for the sequence and owned table")
|
||||
}
|
||||
if len(colName.Qualifier.DbQualifier.String()) > 0 {
|
||||
return nil, errors.New("database specification is not yet supported for sequences")
|
||||
}
|
||||
ownerTableName = colName.Qualifier.Name.String()
|
||||
ownerColumnName = colName.Name.String()
|
||||
case tree.SeqOptCache:
|
||||
// TODO: implement caching
|
||||
if *option.IntVal != 1 {
|
||||
return nil, errors.Errorf("sequence caching for values other than 1 are not yet supported")
|
||||
}
|
||||
case tree.SeqOptIncrement:
|
||||
increment = *option.IntVal
|
||||
if incrementSet {
|
||||
return nil, errors.Errorf("conflicting or redundant options")
|
||||
}
|
||||
if increment == 0 {
|
||||
return nil, errors.Errorf("INCREMENT must not be zero")
|
||||
}
|
||||
incrementSet = true
|
||||
case tree.SeqOptMinValue:
|
||||
if option.IntVal != nil {
|
||||
minValue = *option.IntVal
|
||||
if minValueSet {
|
||||
return nil, errors.Errorf("conflicting or redundant options")
|
||||
}
|
||||
minValueSet = true
|
||||
}
|
||||
case tree.SeqOptMaxValue:
|
||||
if option.IntVal != nil {
|
||||
maxValue = *option.IntVal
|
||||
if maxValueSet {
|
||||
return nil, errors.Errorf("conflicting or redundant options")
|
||||
}
|
||||
maxValueSet = true
|
||||
}
|
||||
case tree.SeqOptStart:
|
||||
start = *option.IntVal
|
||||
if startSet {
|
||||
return nil, errors.Errorf("conflicting or redundant options")
|
||||
}
|
||||
startSet = true
|
||||
case tree.SeqOptViaAlterTable:
|
||||
fromAlter = true
|
||||
default:
|
||||
return nil, errors.Errorf("unknown CREATE SEQUENCE option")
|
||||
}
|
||||
}
|
||||
// Determine what all values should be based on what was set and what is inferred, as well as perform
|
||||
// validation for options that make sense
|
||||
if minValueSet {
|
||||
if minValue < minValueLimit || minValue > maxValueLimit {
|
||||
return nil, errors.Errorf("MINVALUE (%d) is out of range for sequence data type %s", minValue, dataType.String())
|
||||
}
|
||||
} else if increment > 0 {
|
||||
minValue = 1
|
||||
} else {
|
||||
minValue = minValueLimit
|
||||
}
|
||||
if maxValueSet {
|
||||
if maxValue < minValueLimit || maxValue > maxValueLimit {
|
||||
return nil, errors.Errorf("MAXVALUE (%d) is out of range for sequence data type %s", maxValue, dataType.String())
|
||||
}
|
||||
} else if increment > 0 {
|
||||
maxValue = maxValueLimit
|
||||
} else {
|
||||
maxValue = -1
|
||||
}
|
||||
if startSet {
|
||||
if start < minValue {
|
||||
return nil, errors.Errorf("START value (%d) cannot be less than MINVALUE (%d))", start, minValue)
|
||||
}
|
||||
if start > maxValue {
|
||||
return nil, errors.Errorf("START value (%d) cannot be greater than MAXVALUE (%d)", start, maxValue)
|
||||
}
|
||||
} else if increment > 0 {
|
||||
start = minValue
|
||||
} else {
|
||||
start = maxValue
|
||||
}
|
||||
if dataType.IsEmptyType() {
|
||||
dataType = pgtypes.Int64
|
||||
}
|
||||
// Returns the stored procedure call with all options
|
||||
return vitess.InjectedStatement{
|
||||
Statement: pgnodes.NewCreateSequence(node.IfNotExists, name.SchemaQualifier.String(), fromAlter, &sequences.Sequence{
|
||||
Id: id.NewSequence("", name.Name.String()),
|
||||
DataTypeID: dataType.ID,
|
||||
Persistence: sequences.Persistence_Permanent,
|
||||
Start: start,
|
||||
Current: start,
|
||||
Increment: increment,
|
||||
Minimum: minValue,
|
||||
Maximum: maxValue,
|
||||
Cache: 1,
|
||||
Cycle: cycle,
|
||||
IsAtEnd: false,
|
||||
OwnerTable: id.NewTable("", ownerTableName),
|
||||
OwnerColumn: ownerColumnName,
|
||||
}),
|
||||
Children: nil,
|
||||
Auth: vitess.AuthInformation{
|
||||
AuthType: auth.AuthType_CREATE,
|
||||
TargetType: auth.AuthTargetType_SchemaIdentifiers,
|
||||
TargetNames: []string{name.DbQualifier.String(), name.SchemaQualifier.String()},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright 2023 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 ast
|
||||
|
||||
import (
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
)
|
||||
|
||||
// nodeCreateStats handles *tree.CreateStats nodes.
|
||||
func nodeCreateStats(ctx *Context, node *tree.CreateStats) (vitess.Statement, error) {
|
||||
if node == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return NotYetSupportedError("CREATE STATISTICS is not yet supported")
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
// Copyright 2023 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 ast
|
||||
|
||||
import (
|
||||
"github.com/cockroachdb/errors"
|
||||
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
"github.com/dolthub/doltgresql/server/auth"
|
||||
)
|
||||
|
||||
// nodeCreateTable handles *tree.CreateTable nodes.
|
||||
func nodeCreateTable(ctx *Context, node *tree.CreateTable) (*vitess.DDL, error) {
|
||||
if node == nil {
|
||||
return nil, nil
|
||||
}
|
||||
if len(node.StorageParams) > 0 {
|
||||
return nil, errors.Errorf("storage parameters are not yet supported")
|
||||
}
|
||||
// TODO: support tree.CreateTableOnCommitDrop and tree.CreateTableOnCommitDeleteRows
|
||||
switch node.OnCommit {
|
||||
case tree.CreateTableOnCommitDrop:
|
||||
// is unsupported and ignored
|
||||
case tree.CreateTableOnCommitDeleteRows:
|
||||
// is unsupported and ignored
|
||||
}
|
||||
tableName, err := nodeTableName(ctx, &node.Table)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var isTemporary bool
|
||||
switch node.Persistence {
|
||||
case tree.PersistencePermanent:
|
||||
isTemporary = false
|
||||
case tree.PersistenceTemporary:
|
||||
isTemporary = true
|
||||
case tree.PersistenceUnlogged:
|
||||
return nil, errors.Errorf("UNLOGGED is not yet supported")
|
||||
default:
|
||||
return nil, errors.Errorf("unknown persistence strategy encountered")
|
||||
}
|
||||
var optSelect *vitess.OptSelect
|
||||
if node.Using != "" {
|
||||
return nil, errors.Errorf("USING is not yet supported")
|
||||
}
|
||||
if node.Tablespace != "" {
|
||||
return nil, errors.Errorf("TABLESPACE is not yet supported")
|
||||
}
|
||||
if node.AsSource != nil {
|
||||
selectStmt, err := nodeSelect(ctx, node.AsSource)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
optSelect = &vitess.OptSelect{
|
||||
Select: selectStmt,
|
||||
}
|
||||
}
|
||||
var optLike *vitess.OptLike
|
||||
if len(node.Inherits) > 0 {
|
||||
optLike = &vitess.OptLike{
|
||||
LikeTables: []vitess.TableName{},
|
||||
}
|
||||
for _, table := range node.Inherits {
|
||||
likeTable, err := nodeTableName(ctx, &table)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
optLike.LikeTables = append(optLike.LikeTables, likeTable)
|
||||
}
|
||||
}
|
||||
if node.WithNoData {
|
||||
return nil, errors.Errorf("WITH NO DATA is not yet supported")
|
||||
}
|
||||
ddl := &vitess.DDL{
|
||||
Action: vitess.CreateStr,
|
||||
Table: tableName,
|
||||
IfNotExists: node.IfNotExists,
|
||||
Temporary: isTemporary,
|
||||
OptSelect: optSelect,
|
||||
OptLike: optLike,
|
||||
Auth: vitess.AuthInformation{
|
||||
AuthType: auth.AuthType_CREATE,
|
||||
TargetType: auth.AuthTargetType_SchemaIdentifiers,
|
||||
TargetNames: []string{tableName.DbQualifier.String(), tableName.SchemaQualifier.String()},
|
||||
},
|
||||
}
|
||||
if err = assignTableDefs(ctx, node.Defs, ddl); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if node.PartitionBy != nil {
|
||||
switch node.PartitionBy.Type {
|
||||
case tree.PartitionByList:
|
||||
if len(node.PartitionBy.Elems) != 1 {
|
||||
return nil, errors.Errorf("PARTITION BY LIST must have a single column or expression")
|
||||
}
|
||||
}
|
||||
|
||||
// GMS does not support PARTITION BY, so we parse it and ignore it
|
||||
if ddl.TableSpec != nil {
|
||||
ddl.TableSpec.PartitionOpt = &vitess.PartitionOption{
|
||||
PartitionType: string(node.PartitionBy.Type),
|
||||
Expr: vitess.NewColName(string(node.PartitionBy.Elems[0].Column)),
|
||||
}
|
||||
}
|
||||
}
|
||||
if node.PartitionOf.Table() != "" {
|
||||
return nil, errors.Errorf("PARTITION OF is not yet supported")
|
||||
}
|
||||
return ddl, nil
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
// 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 ast
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/core/id"
|
||||
"github.com/dolthub/doltgresql/core/triggers"
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
pgnodes "github.com/dolthub/doltgresql/server/node"
|
||||
"github.com/dolthub/doltgresql/server/plpgsql"
|
||||
)
|
||||
|
||||
// createTriggerWhenCapture is a regex that should only capture the contents of the WHEN expression. Although a bit
|
||||
// complex, this is done to ensure that the capture group contains only the WHEN expression and nothing else.
|
||||
var createTriggerWhenCapture = regexp.MustCompile(`(?is)create\s+(?:or\s+replace\s+)?(?:constraint\s+)?trigger\s+.*\s+for\s+(?:each\s+)?(?:row|statement)\s+when\s+\((.*)\)\s+execute\s+(?:function|procedure).*`)
|
||||
|
||||
// nodeCreateTrigger handles *tree.CreateTrigger nodes.
|
||||
func nodeCreateTrigger(ctx *Context, node *tree.CreateTrigger) (_ vitess.Statement, err error) {
|
||||
if node.Constraint {
|
||||
return NotYetSupportedError("CREATE CONSTRAINT TRIGGER is not yet supported")
|
||||
}
|
||||
if !node.RefTable.IsEmpty() {
|
||||
return NotYetSupportedError("FROM is not yet supported for CREATE TRIGGER")
|
||||
}
|
||||
if node.Deferrable != tree.TriggerNotDeferrable {
|
||||
return NotYetSupportedError("DEFERRABLE is not yet supported for CREATE TRIGGER")
|
||||
}
|
||||
if len(node.Relations) > 0 {
|
||||
return NotYetSupportedError("REFERENCING is not yet supported for CREATE TRIGGER")
|
||||
}
|
||||
if !node.ForEachRow {
|
||||
return NotYetSupportedError("FOR EACH STATEMENT is not yet supported for CREATE TRIGGER")
|
||||
}
|
||||
funcName := node.FuncName.ToTableName()
|
||||
var timing triggers.TriggerTiming
|
||||
switch node.Time {
|
||||
case tree.TriggerTimeBefore:
|
||||
timing = triggers.TriggerTiming_Before
|
||||
case tree.TriggerTimeAfter:
|
||||
timing = triggers.TriggerTiming_After
|
||||
case tree.TriggerTimeInsteadOf:
|
||||
return NotYetSupportedError("INSTEAD OF is not yet supported for CREATE TRIGGER")
|
||||
}
|
||||
var events []triggers.TriggerEvent
|
||||
for _, event := range node.Events {
|
||||
switch event.Type {
|
||||
case tree.TriggerEventInsert:
|
||||
events = append(events, triggers.TriggerEvent{
|
||||
Type: triggers.TriggerEventType_Insert,
|
||||
})
|
||||
case tree.TriggerEventUpdate:
|
||||
if len(event.Cols) > 0 {
|
||||
return NotYetSupportedError("UPDATE specific columns are not yet supported for CREATE TRIGGER")
|
||||
}
|
||||
events = append(events, triggers.TriggerEvent{
|
||||
Type: triggers.TriggerEventType_Update,
|
||||
ColumnNames: event.Cols.ToStrings(),
|
||||
})
|
||||
case tree.TriggerEventDelete:
|
||||
events = append(events, triggers.TriggerEvent{
|
||||
Type: triggers.TriggerEventType_Delete,
|
||||
})
|
||||
case tree.TriggerEventTruncate:
|
||||
return NotYetSupportedError("TRUNCATE is not yet supported for CREATE TRIGGER")
|
||||
default:
|
||||
return NotYetSupportedError("UNKNOWN EVENT TYPE is not yet supported for CREATE TRIGGER")
|
||||
}
|
||||
}
|
||||
// WHEN expressions seem to behave identically to interpreted functions, so we'll parse them as interpreted functions.
|
||||
// To do this, we need the raw string, and we wrap it as though it were a trigger function (which has special logic
|
||||
// for handling NEW and OLD rows). Using a regex for this rather than modifying the parser may seem suboptimal, but
|
||||
// we want to retain the parser validation of using an expression, however we cannot rely on the expression's
|
||||
// String() function to return the **exact** same string, so we capture it with a regex.
|
||||
var whenOps []plpgsql.InterpreterOperation
|
||||
if node.When != nil {
|
||||
matches := createTriggerWhenCapture.FindStringSubmatch(ctx.originalQuery)
|
||||
if len(matches) != 2 {
|
||||
return nil, errors.New("unable to parse WHEN expression from CREATE TRIGGER")
|
||||
}
|
||||
whenOps, err = plpgsql.Parse(fmt.Sprintf(`CREATE FUNCTION when_wrapper() RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
RETURN %s;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;`, matches[1]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return vitess.InjectedStatement{
|
||||
Statement: pgnodes.NewCreateTrigger(
|
||||
id.NewTrigger(node.OnTable.Schema(), node.OnTable.Table(), node.Name.String()),
|
||||
id.NewFunction(funcName.Schema(), funcName.Table()),
|
||||
node.Replace,
|
||||
timing,
|
||||
events,
|
||||
node.ForEachRow,
|
||||
whenOps,
|
||||
node.Args.ToStrings(),
|
||||
ctx.originalQuery,
|
||||
),
|
||||
Children: nil,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
// Copyright 2023 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 ast
|
||||
|
||||
import (
|
||||
"github.com/cockroachdb/errors"
|
||||
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
pgnodes "github.com/dolthub/doltgresql/server/node"
|
||||
pgtypes "github.com/dolthub/doltgresql/server/types"
|
||||
)
|
||||
|
||||
// nodeCreateType handles *tree.CreateType nodes.
|
||||
func nodeCreateType(ctx *Context, node *tree.CreateType) (vitess.Statement, error) {
|
||||
if node == nil {
|
||||
return nil, nil
|
||||
}
|
||||
name, err := nodeUnresolvedObjectName(ctx, node.TypeName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
schemaName := name.SchemaQualifier.String()
|
||||
typName := name.Name.String()
|
||||
var createTypeNode *pgnodes.CreateType
|
||||
switch node.Variety {
|
||||
case tree.Composite:
|
||||
typs := make([]pgnodes.CompositeAsType, len(node.Composite.Types))
|
||||
for i, t := range node.Composite.Types {
|
||||
_, dataType, err := nodeResolvableTypeReference(ctx, t.Type, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if dataType == pgtypes.Record {
|
||||
return nil, errors.Errorf(`column "%s" has pseudo-type record`, t.AttrName)
|
||||
}
|
||||
|
||||
typs[i] = pgnodes.CompositeAsType{
|
||||
AttrName: t.AttrName,
|
||||
Typ: dataType,
|
||||
Collation: t.Collate,
|
||||
}
|
||||
}
|
||||
createTypeNode = pgnodes.NewCreateCompositeType(schemaName, typName, typs)
|
||||
case tree.Enum:
|
||||
createTypeNode = pgnodes.NewCreateEnumType(schemaName, typName, node.Enum.Labels)
|
||||
case tree.Range:
|
||||
return nil, errors.Errorf("CREATE RANGE TYPE is not yet supported")
|
||||
case tree.Base:
|
||||
return nil, errors.Errorf("CREATE BASE TYPE is not yet supported")
|
||||
case tree.Shell:
|
||||
createTypeNode = pgnodes.NewCreateShellType(schemaName, typName)
|
||||
case tree.Domain:
|
||||
// NOT POSSIBLE
|
||||
return nil, errors.Errorf("use CREATE DOMAIN to create domain type")
|
||||
}
|
||||
|
||||
return vitess.InjectedStatement{
|
||||
Statement: createTypeNode,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
// Copyright 2023 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 ast
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
)
|
||||
|
||||
// nodeCreateView handles *tree.CreateView nodes.
|
||||
func nodeCreateView(ctx *Context, node *tree.CreateView) (*vitess.DDL, error) {
|
||||
if node == nil {
|
||||
return nil, nil
|
||||
}
|
||||
if node.Persistence.IsTemporary() {
|
||||
return nil, errors.Errorf("CREATE TEMPORARY VIEW is not yet supported")
|
||||
}
|
||||
if node.IsRecursive {
|
||||
return nil, errors.Errorf("CREATE RECURSIVE VIEW is not yet supported")
|
||||
}
|
||||
var checkOption = tree.ViewCheckOptionUnspecified
|
||||
var sqlSecurity string
|
||||
if node.Options != nil {
|
||||
for _, opt := range node.Options {
|
||||
switch strings.ToLower(opt.Name) {
|
||||
case "check_option":
|
||||
switch strings.ToLower(opt.CheckOpt) {
|
||||
case "local":
|
||||
checkOption = tree.ViewCheckOptionLocal
|
||||
case "cascaded":
|
||||
checkOption = tree.ViewCheckOptionCascaded
|
||||
default:
|
||||
return nil, errors.Errorf(`"ERROR: syntax error at or near "%s"`, opt.Name)
|
||||
}
|
||||
case "security_barrier":
|
||||
if opt.Security {
|
||||
return nil, errors.Errorf("CREATE VIEW '%s' = true option is not yet supported", opt.Name)
|
||||
}
|
||||
case "security_invoker":
|
||||
if opt.Security {
|
||||
sqlSecurity = "invoker"
|
||||
} else {
|
||||
sqlSecurity = "definer"
|
||||
}
|
||||
default:
|
||||
return nil, errors.Errorf(`"ERROR: syntax error at or near "%s"`, opt.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if checkOption != tree.ViewCheckOptionUnspecified && node.CheckOption != tree.ViewCheckOptionUnspecified {
|
||||
return nil, errors.Errorf(`ERROR: parameter "check_option" specified more than once`)
|
||||
} else {
|
||||
checkOption = node.CheckOption
|
||||
}
|
||||
|
||||
vCheckOpt := vitess.ViewCheckOptionUnspecified
|
||||
switch checkOption {
|
||||
case tree.ViewCheckOptionCascaded:
|
||||
vCheckOpt = vitess.ViewCheckOptionCascaded
|
||||
case tree.ViewCheckOptionLocal:
|
||||
vCheckOpt = vitess.ViewCheckOptionLocal
|
||||
default:
|
||||
}
|
||||
|
||||
tableName, err := nodeTableName(ctx, &node.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
selectStmt, err := nodeSelect(ctx, node.AsSource)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var cols = make(vitess.Columns, len(node.ColumnNames))
|
||||
for i, col := range node.ColumnNames {
|
||||
cols[i] = vitess.NewColIdent(col.String())
|
||||
}
|
||||
|
||||
stmt := &vitess.DDL{
|
||||
Action: vitess.CreateStr,
|
||||
OrReplace: node.Replace,
|
||||
ViewSpec: &vitess.ViewSpec{
|
||||
ViewName: tableName,
|
||||
ViewExpr: selectStmt,
|
||||
Columns: cols,
|
||||
Security: sqlSecurity,
|
||||
CheckOption: vCheckOpt,
|
||||
},
|
||||
SubStatementStr: node.AsSource.String(),
|
||||
}
|
||||
return stmt, nil
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// Copyright 2023 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 ast
|
||||
|
||||
import (
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
)
|
||||
|
||||
// nodeDeallocate handles *tree.Deallocate nodes.
|
||||
func nodeDeallocate(ctx *Context, node *tree.Deallocate) (*vitess.Deallocate, error) {
|
||||
if node == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return &vitess.Deallocate{
|
||||
Name: string(node.Name),
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
// Copyright 2023 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 ast
|
||||
|
||||
import (
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
"github.com/dolthub/doltgresql/server/auth"
|
||||
)
|
||||
|
||||
// nodeDelete handles *tree.Delete nodes.
|
||||
func nodeDelete(ctx *Context, node *tree.Delete) (*vitess.Delete, error) {
|
||||
if node == nil {
|
||||
return nil, nil
|
||||
}
|
||||
ctx.Auth().PushAuthType(auth.AuthType_DELETE)
|
||||
defer ctx.Auth().PopAuthType()
|
||||
|
||||
var returningExprs vitess.SelectExprs
|
||||
if returning, ok := node.Returning.(*tree.ReturningExprs); ok {
|
||||
var err error
|
||||
returningExprs, err = nodeSelectExprs(ctx, tree.SelectExprs(*returning))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
with, err := nodeWith(ctx, node.With)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
table, err := nodeTableExpr(ctx, node.Table)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
where, err := nodeWhere(ctx, node.Where)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
orderBy, err := nodeOrderBy(ctx, node.OrderBy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
limit, err := nodeLimit(ctx, node.Limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &vitess.Delete{
|
||||
TableExprs: vitess.TableExprs{table},
|
||||
With: with,
|
||||
Where: where,
|
||||
OrderBy: orderBy,
|
||||
Limit: limit,
|
||||
Returning: returningExprs,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
// Copyright 2023 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 ast
|
||||
|
||||
import (
|
||||
"github.com/cockroachdb/errors"
|
||||
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
"github.com/dolthub/doltgresql/server/node"
|
||||
)
|
||||
|
||||
// nodeDiscard handles *tree.Discard nodes.
|
||||
func nodeDiscard(ctx *Context, discard *tree.Discard) (vitess.Statement, error) {
|
||||
if discard == nil {
|
||||
return nil, nil
|
||||
}
|
||||
if discard.Mode != tree.DiscardModeAll {
|
||||
return nil, errors.Errorf("unhandled DISCARD mode: %v", discard.Mode)
|
||||
}
|
||||
|
||||
return vitess.InjectedStatement{
|
||||
Statement: node.DiscardStatement{},
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
// 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 ast
|
||||
|
||||
import (
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
)
|
||||
|
||||
// nodeDropAggregate handles *tree.DropAggregate nodes.
|
||||
func nodeDropAggregate(ctx *Context, node *tree.DropAggregate) (vitess.Statement, error) {
|
||||
if node == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if !ignoreUnsupportedStatements {
|
||||
for _, agg := range node.Aggregates {
|
||||
if err := validateAggArgMode(ctx, agg.AggSig.Args, agg.AggSig.OrderByArgs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NotYetSupportedError("DROP AGGREGATE is not yet supported")
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
// 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 ast
|
||||
|
||||
import (
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
"github.com/dolthub/doltgresql/server/auth"
|
||||
pgnodes "github.com/dolthub/doltgresql/server/node"
|
||||
)
|
||||
|
||||
// nodeDropCast handles *tree.DropCast nodes.
|
||||
func nodeDropCast(ctx *Context, node *tree.DropCast) (vitess.Statement, error) {
|
||||
if node == nil {
|
||||
return nil, nil
|
||||
}
|
||||
_, sourceType, err := nodeResolvableTypeReference(ctx, node.Source, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, targetType, err := nodeResolvableTypeReference(ctx, node.Target, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return vitess.InjectedStatement{
|
||||
Statement: pgnodes.NewDropCast(
|
||||
sourceType,
|
||||
targetType,
|
||||
node.IfExists,
|
||||
),
|
||||
Auth: vitess.AuthInformation{
|
||||
AuthType: auth.AuthType_DELETE,
|
||||
TargetType: auth.AuthTargetType_TODO,
|
||||
TargetNames: []string{},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
// Copyright 2023 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 ast
|
||||
|
||||
import (
|
||||
"github.com/cockroachdb/errors"
|
||||
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
)
|
||||
|
||||
// nodeDropDatabase handles *tree.DropDatabase nodes.
|
||||
func nodeDropDatabase(_ *Context, node *tree.DropDatabase) (*vitess.DBDDL, error) {
|
||||
if node == nil {
|
||||
return nil, nil
|
||||
}
|
||||
if node.Force {
|
||||
return nil, errors.Errorf("WITH ( FORCE ) is not yet supported")
|
||||
}
|
||||
return &vitess.DBDDL{
|
||||
Action: vitess.DropStr,
|
||||
SchemaOrDatabase: "database",
|
||||
DBName: bareIdentifier(node.Name),
|
||||
IfExists: node.IfExists,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
// 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 ast
|
||||
|
||||
import (
|
||||
"github.com/cockroachdb/errors"
|
||||
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
pgnodes "github.com/dolthub/doltgresql/server/node"
|
||||
)
|
||||
|
||||
// nodeDropDomain handles *tree.DropDomain nodes.
|
||||
func nodeDropDomain(ctx *Context, node *tree.DropDomain) (vitess.Statement, error) {
|
||||
if node == nil {
|
||||
return nil, nil
|
||||
}
|
||||
if len(node.Names) != 1 {
|
||||
return nil, errors.Errorf("dropping multiple domains in DROP DOMAIN is not yet supported")
|
||||
}
|
||||
name, err := nodeTableName(ctx, &node.Names[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return vitess.InjectedStatement{
|
||||
Statement: pgnodes.NewDropDomain(
|
||||
node.IfExists,
|
||||
name.DbQualifier.String(),
|
||||
name.SchemaQualifier.String(),
|
||||
name.Name.String(),
|
||||
node.DropBehavior == tree.DropCascade),
|
||||
Children: nil,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// 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 ast
|
||||
|
||||
import (
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
pgnodes "github.com/dolthub/doltgresql/server/node"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
)
|
||||
|
||||
// nodeDropExtension handles *tree.DropExtension nodes.
|
||||
func nodeDropExtension(ctx *Context, node *tree.DropExtension) (vitess.Statement, error) {
|
||||
return vitess.InjectedStatement{
|
||||
Statement: pgnodes.NewDropExtension(
|
||||
node.Names.ToStrings(),
|
||||
node.IfExists,
|
||||
node.DropBehavior == tree.DropCascade,
|
||||
),
|
||||
Children: nil,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
// 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 ast
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
pgnodes "github.com/dolthub/doltgresql/server/node"
|
||||
)
|
||||
|
||||
// nodeDropFunction handles *tree.DropFunction nodes.
|
||||
func nodeDropFunction(ctx *Context, node *tree.DropFunction) (vitess.Statement, error) {
|
||||
if node == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if node.DropBehavior == tree.DropCascade {
|
||||
return nil, fmt.Errorf("DROP FUNCTION with CASCADE is not supported yet")
|
||||
}
|
||||
|
||||
if len(node.Functions) == 0 {
|
||||
return nil, fmt.Errorf("no function name specified for DROP FUNCTION")
|
||||
}
|
||||
|
||||
functions := make([]*pgnodes.RoutineWithParams, len(node.Functions))
|
||||
for i, fn := range node.Functions {
|
||||
var args []pgnodes.RoutineParam
|
||||
for _, a := range fn.Args {
|
||||
if a.Mode != tree.RoutineArgModeOut {
|
||||
_, dt, err := nodeResolvableTypeReference(ctx, a.Type, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
args = append(args, pgnodes.RoutineParam{
|
||||
Name: a.Name.String(),
|
||||
Type: dt,
|
||||
})
|
||||
}
|
||||
}
|
||||
objName := fn.Name.ToTableName()
|
||||
functions[i] = &pgnodes.RoutineWithParams{
|
||||
Args: args,
|
||||
SchemaName: objName.Schema(),
|
||||
RoutineName: objName.Object(),
|
||||
}
|
||||
}
|
||||
|
||||
return vitess.InjectedStatement{
|
||||
Statement: pgnodes.NewDropFunction(
|
||||
node.IfExists,
|
||||
functions,
|
||||
node.DropBehavior == tree.DropCascade),
|
||||
Children: nil,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
// Copyright 2023 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 ast
|
||||
|
||||
import (
|
||||
"github.com/cockroachdb/errors"
|
||||
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
)
|
||||
|
||||
// nodeDropIndex handles *tree.DropIndex nodes.
|
||||
func nodeDropIndex(ctx *Context, node *tree.DropIndex) (*vitess.AlterTable, error) {
|
||||
if node == nil || len(node.IndexList) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
switch node.DropBehavior {
|
||||
case tree.DropDefault:
|
||||
// Default behavior, nothing to do
|
||||
case tree.DropRestrict:
|
||||
return nil, errors.Errorf("RESTRICT is not yet supported")
|
||||
case tree.DropCascade:
|
||||
return nil, errors.Errorf("CASCADE is not yet supported")
|
||||
}
|
||||
if len(node.IndexList) > 1 {
|
||||
return nil, errors.Errorf("multi-index dropping is not yet supported")
|
||||
}
|
||||
if node.Concurrently {
|
||||
return nil, errors.Errorf("concurrent indexes are not yet supported")
|
||||
}
|
||||
var tableName vitess.TableName
|
||||
ddls := make([]*vitess.DDL, len(node.IndexList))
|
||||
for i, index := range node.IndexList {
|
||||
newTableName, err := nodeTableName(ctx, &index.Table)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !tableName.Name.IsEmpty() && tableName.String() != newTableName.String() {
|
||||
return nil, errors.Errorf("only dropping indexes from the same table is currently supported")
|
||||
}
|
||||
tableName = newTableName
|
||||
ddls[i] = &vitess.DDL{
|
||||
Action: vitess.AlterStr,
|
||||
Table: tableName,
|
||||
IfExists: node.IfExists,
|
||||
IndexSpec: &vitess.IndexSpec{
|
||||
Action: vitess.DropStr,
|
||||
FromName: vitess.NewColIdent(string(index.Index)),
|
||||
ToName: vitess.NewColIdent(string(index.Index)),
|
||||
},
|
||||
}
|
||||
}
|
||||
return &vitess.AlterTable{
|
||||
Table: tableName,
|
||||
Statements: ddls,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
// 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 ast
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
pgnodes "github.com/dolthub/doltgresql/server/node"
|
||||
)
|
||||
|
||||
// nodeDropProcedure handles *tree.DropProcedure nodes.
|
||||
func nodeDropProcedure(ctx *Context, node *tree.DropProcedure) (vitess.Statement, error) {
|
||||
if node == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if node.DropBehavior == tree.DropCascade {
|
||||
return nil, fmt.Errorf("DROP PROCEDURE with CASCADE is not supported yet")
|
||||
}
|
||||
|
||||
if len(node.Procedures) == 0 {
|
||||
return nil, fmt.Errorf("no function name specified for DROP PROCEDURE")
|
||||
}
|
||||
|
||||
procedures := make([]*pgnodes.RoutineWithParams, len(node.Procedures))
|
||||
for i, fn := range node.Procedures {
|
||||
var args []pgnodes.RoutineParam
|
||||
for _, a := range fn.Args {
|
||||
if a.Mode != tree.RoutineArgModeOut {
|
||||
_, dt, err := nodeResolvableTypeReference(ctx, a.Type, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
args = append(args, pgnodes.RoutineParam{
|
||||
Name: a.Name.String(),
|
||||
Type: dt,
|
||||
})
|
||||
}
|
||||
}
|
||||
objName := fn.Name.ToTableName()
|
||||
procedures[i] = &pgnodes.RoutineWithParams{
|
||||
Args: args,
|
||||
SchemaName: objName.Schema(),
|
||||
RoutineName: objName.Object(),
|
||||
}
|
||||
}
|
||||
|
||||
return vitess.InjectedStatement{
|
||||
Statement: pgnodes.NewDropProcedure(
|
||||
node.IfExists,
|
||||
procedures,
|
||||
node.DropBehavior == tree.DropCascade),
|
||||
Children: nil,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
// Copyright 2023 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 ast
|
||||
|
||||
import (
|
||||
"github.com/cockroachdb/errors"
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
pgnodes "github.com/dolthub/doltgresql/server/node"
|
||||
)
|
||||
|
||||
// nodeDropRole handles *tree.DropRole nodes.
|
||||
func nodeDropRole(ctx *Context, node *tree.DropRole) (vitess.Statement, error) {
|
||||
if node == nil {
|
||||
return nil, nil
|
||||
}
|
||||
var names []string
|
||||
for _, name := range node.Names {
|
||||
switch name := name.(type) {
|
||||
case *tree.StrVal:
|
||||
names = append(names, name.RawString())
|
||||
default:
|
||||
return nil, errors.Errorf("unknown type `%T` for DROP ROLE name", name)
|
||||
}
|
||||
}
|
||||
// Rather than account for every string type for error checking, we can just do it in a second loop
|
||||
for _, name := range names {
|
||||
switch name {
|
||||
case `public`, `current_role`, `current_user`, `session_user`:
|
||||
return nil, errors.New("cannot use special role specifier in DROP ROLE")
|
||||
}
|
||||
}
|
||||
return vitess.InjectedStatement{
|
||||
Statement: &pgnodes.DropRole{
|
||||
Names: names,
|
||||
IfExists: node.IfExists,
|
||||
},
|
||||
Children: nil,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
// Copyright 2023 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 ast
|
||||
|
||||
import (
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/server/auth"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
)
|
||||
|
||||
// nodeDropSchema handles *tree.DropSchema nodes.
|
||||
func nodeDropSchema(ctx *Context, node *tree.DropSchema) (vitess.Statement, error) {
|
||||
// TODO: disallow dropping pg_catalog for now
|
||||
if node == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if len(node.Names) > 1 {
|
||||
return NotYetSupportedError("DROP SCHEMA with multiple schema names is not yet supported.")
|
||||
}
|
||||
|
||||
if node.DropBehavior == tree.DropCascade {
|
||||
return NotYetSupportedError("DROP SCHEMA with CASCADE behavior is not yet supported.")
|
||||
}
|
||||
|
||||
schemaName := node.Names[0]
|
||||
|
||||
return &vitess.DBDDL{
|
||||
Action: vitess.DropStr,
|
||||
SchemaOrDatabase: "schema",
|
||||
DBName: schemaName,
|
||||
CharsetCollate: nil,
|
||||
IfExists: node.IfExists,
|
||||
Auth: vitess.AuthInformation{
|
||||
AuthType: auth.AuthType_DELETE,
|
||||
TargetType: auth.AuthTargetType_SchemaIdentifiers,
|
||||
TargetNames: []string{"", schemaName},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
// Copyright 2023 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 ast
|
||||
|
||||
import (
|
||||
"github.com/cockroachdb/errors"
|
||||
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
pgnodes "github.com/dolthub/doltgresql/server/node"
|
||||
)
|
||||
|
||||
// nodeDropSequence handles *tree.DropSequence nodes.
|
||||
func nodeDropSequence(ctx *Context, node *tree.DropSequence) (vitess.Statement, error) {
|
||||
if node == nil {
|
||||
return nil, nil
|
||||
}
|
||||
if len(node.Names) != 1 {
|
||||
return nil, errors.Errorf("dropping multiple sequences in DROP SEQUENCE is not yet supported")
|
||||
}
|
||||
name, err := nodeTableName(ctx, &node.Names[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(name.DbQualifier.String()) > 0 {
|
||||
return nil, errors.Errorf("DROP SEQUENCE is currently only supported for the current database")
|
||||
}
|
||||
return vitess.InjectedStatement{
|
||||
Statement: pgnodes.NewDropSequence(node.IfExists, name.SchemaQualifier.String(), name.Name.String(),
|
||||
node.DropBehavior == tree.DropCascade),
|
||||
Children: nil,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
// Copyright 2023 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 ast
|
||||
|
||||
import (
|
||||
"github.com/cockroachdb/errors"
|
||||
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
"github.com/dolthub/doltgresql/server/auth"
|
||||
)
|
||||
|
||||
// nodeDropTable handles *tree.DropTable nodes.
|
||||
func nodeDropTable(ctx *Context, node *tree.DropTable) (*vitess.DDL, error) {
|
||||
if node == nil || len(node.Names) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
switch node.DropBehavior {
|
||||
case tree.DropDefault:
|
||||
// Default behavior, nothing to do
|
||||
case tree.DropRestrict:
|
||||
return nil, errors.Errorf("RESTRICT is not yet supported")
|
||||
case tree.DropCascade:
|
||||
return nil, errors.Errorf("CASCADE is not yet supported")
|
||||
}
|
||||
tableNames := make([]vitess.TableName, len(node.Names))
|
||||
authTableNames := make([]string, 0, len(node.Names)*3)
|
||||
for i := range node.Names {
|
||||
var err error
|
||||
tableNames[i], err = nodeTableName(ctx, &node.Names[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
authTableNames = append(authTableNames,
|
||||
tableNames[i].DbQualifier.String(), tableNames[i].SchemaQualifier.String(), tableNames[i].Name.String())
|
||||
}
|
||||
return &vitess.DDL{
|
||||
Action: vitess.DropStr,
|
||||
FromTables: tableNames,
|
||||
IfExists: node.IfExists,
|
||||
Auth: vitess.AuthInformation{
|
||||
AuthType: auth.AuthType_DROPTABLE,
|
||||
TargetType: auth.AuthTargetType_TableIdentifiers,
|
||||
TargetNames: authTableNames,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
// 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 ast
|
||||
|
||||
import (
|
||||
"github.com/cockroachdb/errors"
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
pgnodes "github.com/dolthub/doltgresql/server/node"
|
||||
)
|
||||
|
||||
// nodeDropTrigger handles *tree.DropTrigger nodes.
|
||||
func nodeDropTrigger(ctx *Context, node *tree.DropTrigger) (vitess.Statement, error) {
|
||||
switch node.DropBehavior {
|
||||
case tree.DropDefault:
|
||||
// Default behavior, nothing to do
|
||||
case tree.DropRestrict:
|
||||
return nil, errors.Errorf("RESTRICT is not yet supported")
|
||||
case tree.DropCascade:
|
||||
return nil, errors.Errorf("CASCADE is not yet supported")
|
||||
}
|
||||
return vitess.InjectedStatement{
|
||||
Statement: pgnodes.NewDropTrigger(
|
||||
node.IfExists,
|
||||
node.Name.String(),
|
||||
node.OnTable.Schema(),
|
||||
node.OnTable.Table(),
|
||||
node.DropBehavior == tree.DropCascade),
|
||||
Children: nil,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
// Copyright 2023 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 ast
|
||||
|
||||
import (
|
||||
"github.com/cockroachdb/errors"
|
||||
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
pgnodes "github.com/dolthub/doltgresql/server/node"
|
||||
)
|
||||
|
||||
// nodeDropType handles *tree.DropType nodes.
|
||||
func nodeDropType(ctx *Context, node *tree.DropType) (vitess.Statement, error) {
|
||||
if node == nil {
|
||||
return nil, nil
|
||||
}
|
||||
if len(node.Names) != 1 {
|
||||
return nil, errors.Errorf("dropping multiple types in DROP TYPE is not yet supported")
|
||||
}
|
||||
tn := node.Names[0].ToTableName()
|
||||
return vitess.InjectedStatement{
|
||||
Statement: pgnodes.NewDropType(
|
||||
node.IfExists,
|
||||
tn.Catalog(),
|
||||
tn.Schema(),
|
||||
tn.Object(),
|
||||
node.DropBehavior == tree.DropCascade),
|
||||
Children: nil,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
// Copyright 2023 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 ast
|
||||
|
||||
import (
|
||||
"github.com/cockroachdb/errors"
|
||||
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
)
|
||||
|
||||
// nodeDropView handles *tree.DropView nodes.
|
||||
func nodeDropView(ctx *Context, node *tree.DropView) (*vitess.DDL, error) {
|
||||
if node == nil || len(node.Names) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
switch node.DropBehavior {
|
||||
case tree.DropDefault:
|
||||
// Default behavior, nothing to do
|
||||
case tree.DropRestrict:
|
||||
return nil, errors.Errorf("RESTRICT is not yet supported")
|
||||
case tree.DropCascade:
|
||||
return nil, errors.Errorf("CASCADE is not yet supported")
|
||||
}
|
||||
tableNames := make([]vitess.TableName, len(node.Names))
|
||||
for i := range node.Names {
|
||||
var err error
|
||||
tableNames[i], err = nodeTableName(ctx, &node.Names[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
//TODO: handle IsMaterialized
|
||||
return &vitess.DDL{
|
||||
Action: vitess.DropStr,
|
||||
IfExists: node.IfExists,
|
||||
FromViews: tableNames,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright 2023 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 ast
|
||||
|
||||
import (
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
)
|
||||
|
||||
// nodeExecute handles *tree.Execute nodes.
|
||||
func nodeExecute(ctx *Context, node *tree.Execute) (vitess.Statement, error) {
|
||||
if node == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return NotYetSupportedError("EXECUTE is not yet supported")
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
// Copyright 2023 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 ast
|
||||
|
||||
import (
|
||||
"github.com/cockroachdb/errors"
|
||||
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
)
|
||||
|
||||
// nodeExplain handles *tree.Explain nodes.
|
||||
func nodeExplain(ctx *Context, node *tree.Explain) (vitess.Statement, error) {
|
||||
if node == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if node.TableName != nil {
|
||||
tableName, err := nodeUnresolvedObjectName(ctx, node.TableName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var showTableOpts *vitess.ShowTablesOpt
|
||||
if node.AsOf != nil {
|
||||
asOf, err := nodeExpr(ctx, node.AsOf.Expr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
showTableOpts = &vitess.ShowTablesOpt{
|
||||
AsOf: asOf,
|
||||
SchemaName: tableName.SchemaQualifier.String(),
|
||||
DbName: tableName.DbQualifier.String(),
|
||||
}
|
||||
}
|
||||
|
||||
show := &vitess.Show{
|
||||
Type: "columns",
|
||||
Table: tableName,
|
||||
ShowTablesOpt: showTableOpts,
|
||||
}
|
||||
|
||||
return show, nil
|
||||
}
|
||||
|
||||
if stmt, ok := node.Statement.(*tree.Select); ok {
|
||||
// TODO: read tree.ExplainOptions
|
||||
selectStmt, err := nodeSelect(ctx, stmt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
explain := &vitess.Explain{
|
||||
ExplainFormat: vitess.TreeStr,
|
||||
Statement: selectStmt,
|
||||
}
|
||||
return explain, nil
|
||||
}
|
||||
|
||||
return nil, errors.Errorf("This EXPLAIN syntax is not yet supported")
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright 2023 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 ast
|
||||
|
||||
import (
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
)
|
||||
|
||||
// nodeExplainAnalyzeDebug handles *tree.ExplainAnalyzeDebug nodes.
|
||||
func nodeExplainAnalyzeDebug(ctx *Context, node *tree.ExplainAnalyzeDebug) (vitess.Statement, error) {
|
||||
if node == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return NotYetSupportedError("EXPLAIN ANALYZE is not yet supported")
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright 2023 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 ast
|
||||
|
||||
import (
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
)
|
||||
|
||||
// nodeExport handles *tree.Export nodes.
|
||||
func nodeExport(ctx *Context, node *tree.Export) (vitess.Statement, error) {
|
||||
if node == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return NotYetSupportedError("EXPORT is not yet supported")
|
||||
}
|
||||
@@ -0,0 +1,937 @@
|
||||
// Copyright 2023 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 ast
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"go/constant"
|
||||
"strings"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/dolthub/go-mysql-server/sql/expression"
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/dolthub/doltgresql/core/id"
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
"github.com/dolthub/doltgresql/postgres/parser/timeofday"
|
||||
"github.com/dolthub/doltgresql/postgres/parser/types"
|
||||
pgexprs "github.com/dolthub/doltgresql/server/expression"
|
||||
"github.com/dolthub/doltgresql/server/functions/framework"
|
||||
pgnodes "github.com/dolthub/doltgresql/server/node"
|
||||
pgtypes "github.com/dolthub/doltgresql/server/types"
|
||||
)
|
||||
|
||||
// nodeExprs handles tree.Exprs nodes.
|
||||
func nodeExprs(ctx *Context, node tree.Exprs) (vitess.Exprs, error) {
|
||||
if len(node) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
exprs := make(vitess.Exprs, len(node))
|
||||
for i := range node {
|
||||
var err error
|
||||
if exprs[i], err = nodeExpr(ctx, node[i]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return exprs, nil
|
||||
}
|
||||
|
||||
// nodeCompositeDatum handles tree.CompositeDatum nodes.
|
||||
func nodeCompositeDatum(ctx *Context, node tree.CompositeDatum) (vitess.Expr, error) {
|
||||
return nodeExpr(ctx, node)
|
||||
}
|
||||
|
||||
// nodeConstant handles tree.Constant nodes.
|
||||
func nodeConstant(ctx *Context, node tree.Constant) (vitess.Expr, error) {
|
||||
return nodeExpr(ctx, node)
|
||||
}
|
||||
|
||||
// nodeDatum handles tree.Datum nodes.
|
||||
func nodeDatum(ctx *Context, node tree.Datum) (vitess.Expr, error) {
|
||||
return nodeExpr(ctx, node)
|
||||
}
|
||||
|
||||
// nodeSubqueryExpr handles tree.SubqueryExpr nodes.
|
||||
func nodeSubqueryExpr(ctx *Context, node tree.SubqueryExpr) (vitess.Expr, error) {
|
||||
return nodeExpr(ctx, node)
|
||||
}
|
||||
|
||||
// nodeTypedExpr handles tree.TypedExpr nodes.
|
||||
func nodeTypedExpr(ctx *Context, node tree.TypedExpr) (vitess.Expr, error) {
|
||||
return nodeExpr(ctx, node)
|
||||
}
|
||||
|
||||
// nodeVariableExpr handles tree.VariableExpr nodes.
|
||||
func nodeVariableExpr(ctx *Context, node tree.VariableExpr) (vitess.Expr, error) {
|
||||
return nodeExpr(ctx, node)
|
||||
}
|
||||
|
||||
// nodeVarName handles tree.VarName nodes.
|
||||
func nodeVarName(ctx *Context, node tree.VarName) (vitess.Expr, error) {
|
||||
return nodeExpr(ctx, node)
|
||||
}
|
||||
|
||||
// nodeExpr handles tree.Expr nodes.
|
||||
func nodeExpr(ctx *Context, node tree.Expr) (vitess.Expr, error) {
|
||||
switch node := node.(type) {
|
||||
case *tree.AllColumnsSelector:
|
||||
return nil, errors.Errorf("table.* syntax is not yet supported in this context")
|
||||
case *tree.AndExpr:
|
||||
left, err := nodeExpr(ctx, node.Left)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
right, err := nodeExpr(ctx, node.Right)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &vitess.AndExpr{
|
||||
Left: left,
|
||||
Right: right,
|
||||
}, nil
|
||||
case *tree.AnnotateTypeExpr:
|
||||
return nil, errors.Errorf("ANNOTATE_TYPE is not yet supported")
|
||||
case *tree.Array:
|
||||
unresolvedChildren := make([]vitess.Expr, len(node.Exprs))
|
||||
var coercedType *pgtypes.DoltgresType
|
||||
if node.HasResolvedType() {
|
||||
_, resolvedType, err := nodeResolvableTypeReference(ctx, node.ResolvedType(), false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resolvedType.IsArrayType() {
|
||||
coercedType = resolvedType
|
||||
} else {
|
||||
return nil, errors.Errorf("array has invalid resolved type")
|
||||
}
|
||||
}
|
||||
for i, arrayExpr := range node.Exprs {
|
||||
var err error
|
||||
unresolvedChildren[i], err = nodeExpr(ctx, arrayExpr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
arrayExpr, err := pgexprs.NewArray(coercedType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return vitess.InjectedExpr{
|
||||
Expression: arrayExpr,
|
||||
Children: unresolvedChildren,
|
||||
}, nil
|
||||
case *tree.ArrayFlatten:
|
||||
subquery, err := nodeExpr(ctx, node.Subquery)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return vitess.InjectedExpr{
|
||||
Expression: pgexprs.ArrayFlatten{},
|
||||
Children: vitess.Exprs{subquery},
|
||||
}, nil
|
||||
case *tree.BinaryExpr:
|
||||
// We will eventually support operators in other schemas, but for now we only can handle built-ins
|
||||
if len(node.Schema) > 0 && node.Schema != "pg_catalog" {
|
||||
return nil, errors.Errorf("schema %q not allowed in OPERATOR syntax", node.Schema)
|
||||
}
|
||||
|
||||
left, err := nodeExpr(ctx, node.Left)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
right, err := nodeExpr(ctx, node.Right)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var operator framework.Operator
|
||||
switch node.Operator {
|
||||
case tree.Bitand:
|
||||
operator = framework.Operator_BinaryBitAnd
|
||||
case tree.Bitor:
|
||||
operator = framework.Operator_BinaryBitOr
|
||||
case tree.Bitxor:
|
||||
operator = framework.Operator_BinaryBitXor
|
||||
case tree.Plus:
|
||||
operator = framework.Operator_BinaryPlus
|
||||
case tree.Minus:
|
||||
operator = framework.Operator_BinaryMinus
|
||||
case tree.Mult:
|
||||
operator = framework.Operator_BinaryMultiply
|
||||
case tree.Div:
|
||||
operator = framework.Operator_BinaryDivide
|
||||
case tree.FloorDiv:
|
||||
// TODO: replace with floor divide function
|
||||
return nil, errors.Errorf("the floor divide operator is not yet supported")
|
||||
case tree.Mod:
|
||||
operator = framework.Operator_BinaryMod
|
||||
case tree.Pow:
|
||||
// TODO: replace with power function
|
||||
return nil, errors.Errorf("the power operator is not yet supported")
|
||||
case tree.Concat:
|
||||
operator = framework.Operator_BinaryConcatenate
|
||||
case tree.LShift:
|
||||
operator = framework.Operator_BinaryShiftLeft
|
||||
case tree.RShift:
|
||||
operator = framework.Operator_BinaryShiftRight
|
||||
case tree.JSONFetchVal:
|
||||
operator = framework.Operator_BinaryJSONExtractJson
|
||||
case tree.JSONFetchText:
|
||||
operator = framework.Operator_BinaryJSONExtractText
|
||||
case tree.JSONFetchValPath:
|
||||
operator = framework.Operator_BinaryJSONExtractPathJson
|
||||
case tree.JSONFetchTextPath:
|
||||
operator = framework.Operator_BinaryJSONExtractPathText
|
||||
default:
|
||||
return nil, errors.Errorf("the binary operator used is not yet supported")
|
||||
}
|
||||
return vitess.InjectedExpr{
|
||||
Expression: pgexprs.NewBinaryOperator(operator),
|
||||
Children: vitess.Exprs{left, right},
|
||||
}, nil
|
||||
case *tree.CaseExpr:
|
||||
expr, err := nodeExpr(ctx, node.Expr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
whens := make([]*vitess.When, len(node.Whens))
|
||||
for i := range node.Whens {
|
||||
val, err := nodeExpr(ctx, node.Whens[i].Val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cond, err := nodeExpr(ctx, node.Whens[i].Cond)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
whens[i] = &vitess.When{
|
||||
Val: val,
|
||||
Cond: cond,
|
||||
}
|
||||
}
|
||||
else_, err := nodeExpr(ctx, node.Else)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &vitess.CaseExpr{
|
||||
Expr: expr,
|
||||
Whens: whens,
|
||||
Else: else_,
|
||||
}, nil
|
||||
case *tree.CastExpr:
|
||||
expr, err := nodeExpr(ctx, node.Expr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch node.SyntaxMode {
|
||||
case tree.CastExplicit, tree.CastShort:
|
||||
// Both of these are acceptable
|
||||
case tree.CastPrepend:
|
||||
// used for typed literals
|
||||
strVal, isStrVal := node.Expr.(*tree.StrVal)
|
||||
t, isT := node.Type.(*types.T)
|
||||
if isStrVal && isT {
|
||||
typedExpr, err := strVal.ResolveAsType(context.TODO(), nil, t)
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("cannot resolve '%s' as type %s", strVal.String(), t.Name())
|
||||
}
|
||||
expr, err = nodeExpr(ctx, typedExpr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
default:
|
||||
return nil, errors.Errorf("unknown cast syntax")
|
||||
}
|
||||
|
||||
convertType, resolvedType, err := nodeResolvableTypeReference(ctx, node.Type, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// If we have the resolved type, then we've got a Doltgres type instead of a GMS type
|
||||
if !resolvedType.IsEmptyType() {
|
||||
cast, err := pgexprs.NewExplicitCastInjectable(resolvedType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return vitess.InjectedExpr{
|
||||
Expression: cast,
|
||||
Children: vitess.Exprs{expr},
|
||||
}, nil
|
||||
} else {
|
||||
convertType, err = translateConvertType(convertType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &vitess.ConvertExpr{
|
||||
Name: "CAST",
|
||||
Expr: expr,
|
||||
Type: convertType,
|
||||
}, nil
|
||||
}
|
||||
|
||||
case *tree.CoalesceExpr:
|
||||
exprs, err := nodeExprsToSelectExprs(ctx, node.Exprs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &vitess.FuncExpr{
|
||||
Name: vitess.NewColIdent("COALESCE"),
|
||||
Exprs: exprs,
|
||||
}, nil
|
||||
case *tree.CollateExpr:
|
||||
logrus.Warnf("collate is not yet supported, ignoring")
|
||||
return nodeExpr(ctx, node.Expr)
|
||||
case *tree.ColumnAccessExpr:
|
||||
colAccess, err := pgexprs.NewColumnAccess(node.ColName, node.ColIndex)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
expr, err := nodeExpr(ctx, node.Expr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return vitess.InjectedExpr{
|
||||
Expression: colAccess,
|
||||
Children: vitess.Exprs{expr},
|
||||
}, nil
|
||||
case *tree.ColumnItem:
|
||||
var tableName vitess.TableName
|
||||
if node.TableName != nil {
|
||||
if node.TableName.NumParts > 2 {
|
||||
return nil, errors.Errorf("referencing items outside the database is not yet supported")
|
||||
}
|
||||
tableName.Name = vitess.NewTableIdent(node.TableName.Parts[0])
|
||||
tableName.SchemaQualifier = vitess.NewTableIdent(node.TableName.Parts[1])
|
||||
}
|
||||
return &vitess.ColName{
|
||||
Name: vitess.NewColIdent(string(node.ColumnName)),
|
||||
Qualifier: tableName,
|
||||
}, nil
|
||||
case *tree.CommentOnColumn:
|
||||
return nil, errors.Errorf("comment on column is not yet supported")
|
||||
case *tree.ComparisonExpr:
|
||||
// We will eventually support operators in other schemas, but for now we only can handle built-ins
|
||||
if len(node.Schema) > 0 && node.Schema != "pg_catalog" {
|
||||
return nil, errors.Errorf("schema %q not allowed in OPERATOR syntax", node.Schema)
|
||||
}
|
||||
|
||||
left, err := nodeExpr(ctx, node.Left)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
right, err := nodeExpr(ctx, node.Right)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var operator string
|
||||
switch node.Operator {
|
||||
case tree.EQ:
|
||||
return vitess.InjectedExpr{
|
||||
Expression: pgexprs.NewBinaryOperator(framework.Operator_BinaryEqual),
|
||||
Children: vitess.Exprs{left, right},
|
||||
}, nil
|
||||
case tree.LT:
|
||||
return vitess.InjectedExpr{
|
||||
Expression: pgexprs.NewBinaryOperator(framework.Operator_BinaryLessThan),
|
||||
Children: vitess.Exprs{left, right},
|
||||
}, nil
|
||||
case tree.GT:
|
||||
return vitess.InjectedExpr{
|
||||
Expression: pgexprs.NewBinaryOperator(framework.Operator_BinaryGreaterThan),
|
||||
Children: vitess.Exprs{left, right},
|
||||
}, nil
|
||||
case tree.LE:
|
||||
return vitess.InjectedExpr{
|
||||
Expression: pgexprs.NewBinaryOperator(framework.Operator_BinaryLessOrEqual),
|
||||
Children: vitess.Exprs{left, right},
|
||||
}, nil
|
||||
case tree.GE:
|
||||
return vitess.InjectedExpr{
|
||||
Expression: pgexprs.NewBinaryOperator(framework.Operator_BinaryGreaterOrEqual),
|
||||
Children: vitess.Exprs{left, right},
|
||||
}, nil
|
||||
case tree.NE:
|
||||
return vitess.InjectedExpr{
|
||||
Expression: pgexprs.NewBinaryOperator(framework.Operator_BinaryNotEqual),
|
||||
Children: vitess.Exprs{left, right},
|
||||
}, nil
|
||||
case tree.In, tree.NotIn:
|
||||
var innerExpression vitess.InjectedExpr
|
||||
switch right := right.(type) {
|
||||
case vitess.ValTuple:
|
||||
innerExpression = vitess.InjectedExpr{
|
||||
Expression: pgexprs.NewInTuple(),
|
||||
Children: vitess.Exprs{left, right},
|
||||
}
|
||||
case *vitess.Subquery:
|
||||
innerExpression = vitess.InjectedExpr{
|
||||
Expression: pgexprs.NewInSubquery(),
|
||||
Children: vitess.Exprs{left, right},
|
||||
}
|
||||
case vitess.InjectedExpr:
|
||||
if _, ok := right.Expression.(*pgexprs.RecordExpr); ok {
|
||||
innerExpression = vitess.InjectedExpr{
|
||||
Expression: pgexprs.NewInTuple(),
|
||||
Children: vitess.Exprs{left, vitess.ValTuple(right.Children)},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if innerExpression.Expression == nil {
|
||||
return nil, errors.Errorf("right side of IN expression is not a tuple or subquery, got %T", right)
|
||||
}
|
||||
|
||||
switch node.Operator {
|
||||
case tree.In:
|
||||
return innerExpression, nil
|
||||
case tree.NotIn:
|
||||
return vitess.InjectedExpr{
|
||||
Expression: pgexprs.NewNot(),
|
||||
Children: vitess.Exprs{innerExpression},
|
||||
}, nil
|
||||
default:
|
||||
return nil, errors.Errorf("unknown comparison operator used")
|
||||
}
|
||||
case tree.Like:
|
||||
operator = vitess.LikeStr
|
||||
case tree.NotLike:
|
||||
operator = vitess.NotLikeStr
|
||||
case tree.ILike:
|
||||
return nil, errors.Errorf("ILIKE is not yet supported")
|
||||
case tree.NotILike:
|
||||
return nil, errors.Errorf("ILIKE is not yet supported")
|
||||
case tree.SimilarTo:
|
||||
return nil, errors.Errorf("similar to is not yet supported")
|
||||
case tree.NotSimilarTo:
|
||||
return nil, errors.Errorf("not similar to is not yet supported")
|
||||
case tree.RegMatch:
|
||||
operator = vitess.RegexpStr
|
||||
case tree.NotRegMatch:
|
||||
operator = vitess.NotRegexpStr
|
||||
case tree.RegIMatch:
|
||||
return nil, errors.Errorf("~* is not yet supported")
|
||||
case tree.NotRegIMatch:
|
||||
return nil, errors.Errorf("!~* is not yet supported")
|
||||
case tree.TextSearchMatch:
|
||||
return nil, errors.Errorf("@@ is not yet supported")
|
||||
case tree.IsDistinctFrom:
|
||||
return vitess.InjectedExpr{
|
||||
Expression: pgexprs.NewIsDistinctFrom(),
|
||||
Children: vitess.Exprs{left, right},
|
||||
}, nil
|
||||
case tree.IsNotDistinctFrom:
|
||||
return vitess.InjectedExpr{
|
||||
Expression: pgexprs.NewIsNotDistinctFrom(),
|
||||
Children: vitess.Exprs{left, right},
|
||||
}, nil
|
||||
case tree.Contains:
|
||||
return vitess.InjectedExpr{
|
||||
Expression: pgexprs.NewBinaryOperator(framework.Operator_BinaryJSONContainsRight),
|
||||
Children: vitess.Exprs{left, right},
|
||||
}, nil
|
||||
case tree.ContainedBy:
|
||||
return vitess.InjectedExpr{
|
||||
Expression: pgexprs.NewBinaryOperator(framework.Operator_BinaryJSONContainsLeft),
|
||||
Children: vitess.Exprs{left, right},
|
||||
}, nil
|
||||
case tree.JSONExists:
|
||||
return vitess.InjectedExpr{
|
||||
Expression: pgexprs.NewBinaryOperator(framework.Operator_BinaryJSONTopLevel),
|
||||
Children: vitess.Exprs{left, right},
|
||||
}, nil
|
||||
case tree.JSONSomeExists:
|
||||
return vitess.InjectedExpr{
|
||||
Expression: pgexprs.NewBinaryOperator(framework.Operator_BinaryJSONTopLevelAny),
|
||||
Children: vitess.Exprs{left, right},
|
||||
}, nil
|
||||
case tree.JSONAllExists:
|
||||
return vitess.InjectedExpr{
|
||||
Expression: pgexprs.NewBinaryOperator(framework.Operator_BinaryJSONTopLevelAll),
|
||||
Children: vitess.Exprs{left, right},
|
||||
}, nil
|
||||
case tree.Overlaps:
|
||||
return nil, errors.Errorf("&& is not yet supported")
|
||||
case tree.Any:
|
||||
return vitess.InjectedExpr{
|
||||
Expression: pgexprs.NewAnyExpr(node.SubOperator.String()),
|
||||
Children: vitess.Exprs{left, right},
|
||||
}, nil
|
||||
case tree.Some:
|
||||
return vitess.InjectedExpr{
|
||||
Expression: pgexprs.NewSomeExpr(node.SubOperator.String()),
|
||||
Children: vitess.Exprs{left, right},
|
||||
}, nil
|
||||
case tree.All:
|
||||
return nil, errors.Errorf("ALL is not yet supported")
|
||||
default:
|
||||
return nil, errors.Errorf("unknown comparison operator used")
|
||||
}
|
||||
return &vitess.ComparisonExpr{
|
||||
Operator: operator,
|
||||
Left: left,
|
||||
Right: right,
|
||||
Escape: nil, // TODO: is '\' the default in Postgres as well?
|
||||
}, nil
|
||||
case *tree.DArray:
|
||||
return nil, errors.Errorf("the statement is not yet supported")
|
||||
case *tree.DBitArray:
|
||||
// We convert bitarray to string representation for engine representation purposes so that we don't have to
|
||||
// represent another fundamental golang type. This means our representation in memory is more verbose.
|
||||
bitStr := tree.AsStringWithFlags(node, tree.FmtPgwireText)
|
||||
return vitess.InjectedExpr{
|
||||
Expression: pgexprs.NewUnsafeLiteral(bitStr, pgtypes.Bit),
|
||||
}, nil
|
||||
case *tree.DBool:
|
||||
return vitess.InjectedExpr{
|
||||
Expression: pgexprs.NewRawLiteralBool(bool(*node)),
|
||||
}, nil
|
||||
case *tree.DBox2D:
|
||||
return nil, errors.Errorf("the statement is not yet supported")
|
||||
case *tree.DBytes:
|
||||
return nil, errors.Errorf("the statement is not yet supported")
|
||||
case *tree.DCollatedString:
|
||||
return nil, errors.Errorf("the statement is not yet supported")
|
||||
case *tree.DDate:
|
||||
t, err := node.Date.ToTime()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return vitess.InjectedExpr{
|
||||
Expression: pgexprs.NewRawLiteralDate(t),
|
||||
}, nil
|
||||
case *tree.DDecimal:
|
||||
return vitess.InjectedExpr{
|
||||
Expression: pgexprs.NewRawLiteralNumeric(&node.Decimal)}, nil
|
||||
case *tree.DEnum:
|
||||
return nil, errors.Errorf("the statement is not yet supported")
|
||||
case *tree.DFloat:
|
||||
return vitess.InjectedExpr{
|
||||
Expression: pgexprs.NewRawLiteralFloat64(float64(*node)),
|
||||
}, nil
|
||||
case *tree.DGeography:
|
||||
return nil, errors.Errorf("the statement is not yet supported")
|
||||
case *tree.DGeometry:
|
||||
return nil, errors.Errorf("the statement is not yet supported")
|
||||
case *tree.DIPAddr:
|
||||
return nil, errors.Errorf("the statement is not yet supported")
|
||||
case *tree.DInt:
|
||||
return vitess.InjectedExpr{
|
||||
Expression: pgexprs.NewRawLiteralInt64(int64(*node)),
|
||||
}, nil
|
||||
case *tree.DInterval:
|
||||
cast, err := pgexprs.NewExplicitCastInjectable(pgtypes.Interval)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
expr := pgexprs.NewIntervalLiteral(node.Duration)
|
||||
return vitess.InjectedExpr{
|
||||
Expression: cast,
|
||||
Children: vitess.Exprs{vitess.InjectedExpr{Expression: expr}},
|
||||
}, nil
|
||||
case *tree.DJSON:
|
||||
// JSON type is handled in string format
|
||||
return vitess.InjectedExpr{
|
||||
Expression: pgexprs.NewRawLiteralJSON(node.JSON.String()),
|
||||
}, nil
|
||||
case *tree.DOid:
|
||||
internalID := id.Cache().ToInternal(uint32(node.DInt))
|
||||
if !internalID.IsValid() {
|
||||
internalID = id.NewOID(uint32(node.DInt)).AsId()
|
||||
}
|
||||
return vitess.InjectedExpr{
|
||||
Expression: pgexprs.NewRawLiteralOid(internalID),
|
||||
}, nil
|
||||
case *tree.DOidWrapper:
|
||||
return nodeExpr(ctx, node.Wrapped)
|
||||
case *tree.DString:
|
||||
return vitess.InjectedExpr{
|
||||
Expression: pgexprs.NewUnknownLiteral(string(*node)),
|
||||
}, nil
|
||||
case *tree.DTime:
|
||||
return vitess.InjectedExpr{
|
||||
Expression: pgexprs.NewRawLiteralTime(timeofday.TimeOfDay(*node)),
|
||||
}, nil
|
||||
case *tree.DTimeTZ:
|
||||
return vitess.InjectedExpr{
|
||||
Expression: pgexprs.NewRawLiteralTimeTZ(node.TimeTZ),
|
||||
}, nil
|
||||
case *tree.DTimestamp:
|
||||
return vitess.InjectedExpr{
|
||||
Expression: pgexprs.NewRawLiteralTimestamp(node.Time),
|
||||
}, nil
|
||||
case *tree.DTimestampTZ:
|
||||
return vitess.InjectedExpr{
|
||||
Expression: pgexprs.NewRawLiteralTimestampTZ(node.Time),
|
||||
}, nil
|
||||
case *tree.DTuple:
|
||||
return nil, errors.Errorf("the statement is not yet supported")
|
||||
case *tree.DUuid:
|
||||
return vitess.InjectedExpr{
|
||||
Expression: pgexprs.NewRawLiteralUuid(node.UUID),
|
||||
}, nil
|
||||
case tree.DefaultVal:
|
||||
// TODO: can we use this?
|
||||
defVal := &vitess.Default{ColName: ""}
|
||||
return defVal, nil
|
||||
case tree.DomainColumn:
|
||||
_, dataType, err := nodeResolvableTypeReference(ctx, node.Typ, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return vitess.InjectedExpr{
|
||||
Expression: &pgnodes.DomainColumn{Typ: dataType},
|
||||
}, nil
|
||||
case tree.FunctionColumn:
|
||||
if !node.FromCreate {
|
||||
return vitess.InjectedExpr{
|
||||
Expression: pgexprs.NewUnsafeLiteral(node.Val, node.Typ),
|
||||
}, nil
|
||||
} else {
|
||||
return vitess.InjectedExpr{
|
||||
Expression: &pgnodes.FunctionColumn{Name: node.Name, Typ: node.Typ, Idx: node.Idx},
|
||||
}, nil
|
||||
}
|
||||
case *tree.FuncExpr:
|
||||
return nodeFuncExpr(ctx, node)
|
||||
case *tree.IfErrExpr:
|
||||
return nil, errors.Errorf("IFERROR is not yet supported")
|
||||
case *tree.IfExpr:
|
||||
cond, err := nodeExpr(ctx, node.Cond)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
trueVal, err := nodeExpr(ctx, node.True)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
falseVal, err := nodeExpr(ctx, node.Else)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO: this could be a postgres func, but postgres doesn't have an IF function, this is an extension from cockroach
|
||||
return &vitess.FuncExpr{
|
||||
Name: vitess.NewColIdent("IF"),
|
||||
Exprs: vitess.SelectExprs{
|
||||
&vitess.AliasedExpr{
|
||||
Expr: cond,
|
||||
},
|
||||
&vitess.AliasedExpr{
|
||||
Expr: trueVal,
|
||||
},
|
||||
&vitess.AliasedExpr{
|
||||
Expr: falseVal,
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
case *tree.IndexedVar:
|
||||
// TODO: figure out if I can delete this
|
||||
return nil, errors.Errorf("this should probably be deleted (internal error, IndexedVar)")
|
||||
case *tree.IndirectionExpr:
|
||||
childExpr, err := nodeExpr(ctx, node.Expr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(node.Indirection) > 1 {
|
||||
return nil, errors.Errorf("multi dimensional array subscripts are not yet supported")
|
||||
} else if node.Indirection[0].Slice {
|
||||
return nil, errors.Errorf("slice subscripts are not yet supported")
|
||||
}
|
||||
|
||||
indexExpr, err := nodeExpr(ctx, node.Indirection[0].Begin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return vitess.InjectedExpr{
|
||||
Expression: &pgexprs.Subscript{},
|
||||
Children: vitess.Exprs{childExpr, indexExpr},
|
||||
}, nil
|
||||
case *tree.IsNotNullExpr:
|
||||
expr, err := nodeExpr(ctx, node.Expr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &vitess.IsExpr{
|
||||
Operator: vitess.IsNotNullStr,
|
||||
Expr: expr,
|
||||
}, nil
|
||||
case *tree.IsNullExpr:
|
||||
expr, err := nodeExpr(ctx, node.Expr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &vitess.IsExpr{
|
||||
Operator: vitess.IsNullStr,
|
||||
Expr: expr,
|
||||
}, nil
|
||||
case *tree.IsOfTypeExpr:
|
||||
return nil, errors.Errorf("IS OF is not yet supported")
|
||||
case *tree.NotExpr:
|
||||
expr, err := nodeExpr(ctx, node.Expr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &vitess.NotExpr{
|
||||
Expr: expr,
|
||||
}, nil
|
||||
case *tree.NullIfExpr:
|
||||
expr1, err := nodeExprToSelectExpr(ctx, node.Expr1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
expr2, err := nodeExprToSelectExpr(ctx, node.Expr2)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &vitess.FuncExpr{
|
||||
Name: vitess.NewColIdent("NULLIF"),
|
||||
Exprs: vitess.SelectExprs{expr1, expr2},
|
||||
}, nil
|
||||
case tree.NullLiteral:
|
||||
return &vitess.NullVal{}, nil
|
||||
case *tree.NumVal:
|
||||
switch node.Kind() {
|
||||
case constant.Int:
|
||||
intLiteral, err := pgexprs.NewIntegerLiteral(node.FormattedString())
|
||||
return vitess.InjectedExpr{
|
||||
Expression: intLiteral,
|
||||
}, err
|
||||
case constant.Float:
|
||||
numericLiteral, err := pgexprs.NewNumericLiteral(node.FormattedString())
|
||||
return vitess.InjectedExpr{
|
||||
Expression: numericLiteral,
|
||||
}, err
|
||||
default:
|
||||
return nil, errors.Errorf("unknown number format")
|
||||
}
|
||||
case *tree.OrExpr:
|
||||
left, err := nodeExpr(ctx, node.Left)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
right, err := nodeExpr(ctx, node.Right)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &vitess.OrExpr{
|
||||
Left: left,
|
||||
Right: right,
|
||||
}, nil
|
||||
case *tree.ParenExpr:
|
||||
expr, err := nodeExpr(ctx, node.Expr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &vitess.ParenExpr{
|
||||
Expr: expr,
|
||||
}, nil
|
||||
case *tree.PartitionMaxVal:
|
||||
return nil, errors.Errorf("MAXVALUE is not yet supported")
|
||||
case *tree.PartitionMinVal:
|
||||
return nil, errors.Errorf("MINVALUE is not yet supported")
|
||||
case *tree.Placeholder:
|
||||
// TODO: deal with type annotation
|
||||
mysqlBindVarIdx := node.Idx + 1
|
||||
return vitess.NewValArg([]byte(fmt.Sprintf(":v%d", mysqlBindVarIdx))), nil
|
||||
case *tree.RangeCond:
|
||||
left, err := nodeExpr(ctx, node.Left)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
from, err := nodeExpr(ctx, node.From)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
to, err := nodeExpr(ctx, node.To)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
retExpr := vitess.Expr(&vitess.AndExpr{
|
||||
Left: vitess.InjectedExpr{
|
||||
Expression: pgexprs.NewBinaryOperator(framework.Operator_BinaryGreaterOrEqual),
|
||||
Children: vitess.Exprs{left, from},
|
||||
},
|
||||
Right: vitess.InjectedExpr{
|
||||
Expression: pgexprs.NewBinaryOperator(framework.Operator_BinaryLessOrEqual),
|
||||
Children: vitess.Exprs{left, to},
|
||||
},
|
||||
})
|
||||
if node.Symmetric {
|
||||
retExpr = &vitess.OrExpr{
|
||||
Left: retExpr,
|
||||
Right: &vitess.AndExpr{
|
||||
Left: vitess.InjectedExpr{
|
||||
Expression: pgexprs.NewBinaryOperator(framework.Operator_BinaryGreaterOrEqual),
|
||||
Children: vitess.Exprs{left, to},
|
||||
},
|
||||
Right: vitess.InjectedExpr{
|
||||
Expression: pgexprs.NewBinaryOperator(framework.Operator_BinaryLessOrEqual),
|
||||
Children: vitess.Exprs{left, from},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
if node.Not {
|
||||
retExpr = vitess.InjectedExpr{
|
||||
Expression: pgexprs.NewNot(),
|
||||
Children: vitess.Exprs{retExpr},
|
||||
}
|
||||
}
|
||||
return retExpr, nil
|
||||
case *tree.StrVal:
|
||||
// TODO: determine what to do when node.WasScannedAsBytes() is true
|
||||
// For string literals, we mark the type as unknown, because Postgres has
|
||||
// more permissive implicit casting rules for literals than it does for strongly
|
||||
// typed values from a schema for example.
|
||||
unknownLiteral := pgexprs.NewUnknownLiteral(node.RawString())
|
||||
return vitess.InjectedExpr{
|
||||
Expression: unknownLiteral,
|
||||
}, nil
|
||||
case *tree.Subquery:
|
||||
return nodeSubqueryOrExists(ctx, node)
|
||||
case *tree.Tuple:
|
||||
if len(node.Labels) > 0 {
|
||||
return nil, errors.Errorf("tuple labels are not yet supported")
|
||||
}
|
||||
|
||||
valTuple, err := nodeExprs(ctx, node.Exprs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return vitess.InjectedExpr{
|
||||
Expression: pgexprs.NewRecordExpr(),
|
||||
Children: valTuple,
|
||||
}, nil
|
||||
case *tree.TupleStar:
|
||||
return nil, errors.Errorf("(E).* is not yet supported")
|
||||
case *tree.UnaryExpr:
|
||||
expr, err := nodeExpr(ctx, node.Expr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var operator framework.Operator
|
||||
switch node.Operator {
|
||||
// TODO: need to add UnaryPlus, it's like a no-op but Postgres actually implements it and it affects coercion
|
||||
case tree.UnaryMinus:
|
||||
operator = framework.Operator_UnaryMinus
|
||||
case tree.UnaryComplement:
|
||||
return &vitess.UnaryExpr{
|
||||
Operator: vitess.TildaStr,
|
||||
Expr: expr,
|
||||
}, nil
|
||||
case tree.UnarySqrt:
|
||||
// TODO: replace with a function
|
||||
return nil, errors.Errorf("square root operator is not yet supported")
|
||||
case tree.UnaryCbrt:
|
||||
// TODO: replace with a function
|
||||
return nil, errors.Errorf("cube root operator is not yet supported")
|
||||
case tree.UnaryAbsolute:
|
||||
// TODO: replace with a function
|
||||
return nil, errors.Errorf("absolute operator is not yet supported")
|
||||
default:
|
||||
return nil, errors.Errorf("the unary operator used is not yet supported")
|
||||
}
|
||||
return vitess.InjectedExpr{
|
||||
Expression: pgexprs.NewUnaryOperator(operator),
|
||||
Children: vitess.Exprs{expr},
|
||||
}, nil
|
||||
case tree.UnqualifiedStar:
|
||||
return nil, errors.Errorf("* syntax is not yet supported in this context")
|
||||
case *tree.UnresolvedName:
|
||||
if node.Star {
|
||||
return nil, errors.Errorf("* syntax is not yet supported in this context")
|
||||
}
|
||||
return unresolvedNameToColName(node)
|
||||
case nil:
|
||||
return nil, nil
|
||||
default:
|
||||
return nil, errors.Errorf("unknown expression: `%T`", node)
|
||||
}
|
||||
}
|
||||
|
||||
// unresolvedNameToColName converts a tree.UnresolvedName to a vitess.ColName with the appropriate name qualifiers set.
|
||||
func unresolvedNameToColName(name *tree.UnresolvedName) (*vitess.ColName, error) {
|
||||
var tableName vitess.TableName
|
||||
switch name.NumParts {
|
||||
case 4:
|
||||
tableName = vitess.TableName{
|
||||
Name: vitess.NewTableIdent(name.Parts[1]),
|
||||
SchemaQualifier: vitess.NewTableIdent(name.Parts[2]),
|
||||
DbQualifier: vitess.NewTableIdent(name.Parts[3]),
|
||||
}
|
||||
case 3:
|
||||
tableName = vitess.TableName{
|
||||
Name: vitess.NewTableIdent(name.Parts[1]),
|
||||
SchemaQualifier: vitess.NewTableIdent(name.Parts[2]),
|
||||
}
|
||||
case 2:
|
||||
tableName = vitess.TableName{
|
||||
Name: vitess.NewTableIdent(name.Parts[1]),
|
||||
}
|
||||
case 1:
|
||||
// no table name
|
||||
default:
|
||||
return nil, errors.Errorf("invalid name: %s", name)
|
||||
}
|
||||
|
||||
return &vitess.ColName{
|
||||
Name: vitess.NewColIdent(name.Parts[0]),
|
||||
Qualifier: tableName,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// translateConvertType translates the *vitess.ConvertType expression given to a new one, substituting type names as
|
||||
// appropriate. An error is returned if the type named cannot be supported.
|
||||
func translateConvertType(convertType *vitess.ConvertType) (*vitess.ConvertType, error) {
|
||||
switch strings.ToLower(convertType.Type) {
|
||||
// passthrough types that need no conversion
|
||||
case expression.ConvertToBinary, expression.ConvertToChar, expression.ConvertToNChar, expression.ConvertToDate,
|
||||
expression.ConvertToDatetime, expression.ConvertToFloat, expression.ConvertToDouble, expression.ConvertToJSON,
|
||||
expression.ConvertToReal, expression.ConvertToSigned, expression.ConvertToTime, expression.ConvertToUnsigned:
|
||||
return convertType, nil
|
||||
case "text", "character varying", "varchar":
|
||||
return &vitess.ConvertType{
|
||||
Type: expression.ConvertToChar,
|
||||
}, nil
|
||||
case "integer", "bigint":
|
||||
return &vitess.ConvertType{
|
||||
Type: expression.ConvertToSigned,
|
||||
}, nil
|
||||
case "decimal", "numeric":
|
||||
return &vitess.ConvertType{
|
||||
Type: expression.ConvertToFloat,
|
||||
}, nil
|
||||
case "boolean":
|
||||
return &vitess.ConvertType{
|
||||
Type: expression.ConvertToSigned,
|
||||
}, nil
|
||||
case "timestamp", "timestamp with time zone", "timestamp without time zone":
|
||||
return &vitess.ConvertType{
|
||||
Type: expression.ConvertToDatetime,
|
||||
}, nil
|
||||
default:
|
||||
return nil, errors.Errorf("unknown convert type: `%T`", convertType.Type)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
// Copyright 2023 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 ast
|
||||
|
||||
import (
|
||||
"github.com/cockroachdb/errors"
|
||||
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
)
|
||||
|
||||
// nodeForeignKeyConstraintTableDef handles *tree.ForeignKeyConstraintTableDef nodes.
|
||||
func nodeForeignKeyConstraintTableDef(ctx *Context, node *tree.ForeignKeyConstraintTableDef, notValid bool) (*vitess.ForeignKeyDefinition, error) {
|
||||
if node == nil {
|
||||
return nil, nil
|
||||
}
|
||||
var matchType vitess.ForeignKeyMatchType
|
||||
switch node.Match {
|
||||
case tree.MatchSimple:
|
||||
matchType = vitess.MatchSimple
|
||||
case tree.MatchFull:
|
||||
matchType = vitess.MatchFull
|
||||
case tree.MatchPartial:
|
||||
return nil, errors.Errorf("MATCH PARTIAL is not yet supported")
|
||||
default:
|
||||
return nil, errors.Errorf("unknown foreign key MATCH strategy")
|
||||
}
|
||||
tableName, err := nodeTableName(ctx, &node.Table)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fromCols := make([]vitess.ColIdent, len(node.FromCols))
|
||||
for i := range node.FromCols {
|
||||
fromCols[i] = vitess.NewColIdent(string(node.FromCols[i]))
|
||||
}
|
||||
toCols := make([]vitess.ColIdent, len(node.ToCols))
|
||||
for i := range node.ToCols {
|
||||
toCols[i] = vitess.NewColIdent(string(node.ToCols[i]))
|
||||
}
|
||||
var refActions [2]vitess.ReferenceAction
|
||||
for i, refAction := range []tree.RefAction{node.Actions.Delete, node.Actions.Update} {
|
||||
switch refAction.Action {
|
||||
case tree.NoAction:
|
||||
refActions[i] = vitess.NoAction
|
||||
case tree.Restrict:
|
||||
refActions[i] = vitess.Restrict
|
||||
case tree.SetNull:
|
||||
refActions[i] = vitess.SetNull
|
||||
if refAction.Columns != nil {
|
||||
return nil, errors.Errorf("SET NULL <columns> is not yet supported")
|
||||
}
|
||||
case tree.SetDefault:
|
||||
refActions[i] = vitess.SetDefault
|
||||
if refAction.Columns != nil {
|
||||
return nil, errors.Errorf("SET DEFAULT <columns> is not yet supported")
|
||||
}
|
||||
case tree.Cascade:
|
||||
refActions[i] = vitess.Cascade
|
||||
default:
|
||||
return nil, errors.Errorf("unknown foreign key reference action encountered")
|
||||
}
|
||||
}
|
||||
return &vitess.ForeignKeyDefinition{
|
||||
Source: fromCols,
|
||||
ReferencedTable: tableName,
|
||||
ReferencedColumns: toCols,
|
||||
OnDelete: refActions[0],
|
||||
OnUpdate: refActions[1],
|
||||
NotValid: notValid,
|
||||
MatchType: matchType,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright 2023 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 ast
|
||||
|
||||
import (
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
)
|
||||
|
||||
// nodeFrom handles *tree.From nodes.
|
||||
func nodeFrom(ctx *Context, node tree.From) (vitess.TableExprs, error) {
|
||||
if len(node.Tables) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
tableExprs, err := nodeTableExprs(ctx, node.Tables)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tableExprs, err
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
// Copyright 2023 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 ast
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
||||
"github.com/dolthub/doltgresql/server/auth"
|
||||
pgexprs "github.com/dolthub/doltgresql/server/expression"
|
||||
)
|
||||
|
||||
// nodeFuncExpr handles *tree.FuncExpr nodes.
|
||||
func nodeFuncExpr(ctx *Context, node *tree.FuncExpr) (vitess.Expr, error) {
|
||||
if node == nil {
|
||||
return nil, nil
|
||||
}
|
||||
if node.Filter != nil {
|
||||
return nil, errors.Errorf("function filters are not yet supported")
|
||||
}
|
||||
if node.AggType == tree.OrderedSetAgg {
|
||||
return nil, errors.Errorf("WITHIN GROUP is not yet supported")
|
||||
}
|
||||
|
||||
var qualifier vitess.TableIdent
|
||||
var name vitess.ColIdent
|
||||
switch funcRef := node.Func.FunctionReference.(type) {
|
||||
case *tree.FunctionDefinition:
|
||||
name = vitess.NewColIdent(funcRef.Name)
|
||||
case *tree.UnresolvedName:
|
||||
colName, err := unresolvedNameToColName(funcRef)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
qualifier = colName.Qualifier.Name
|
||||
name = colName.Name
|
||||
default:
|
||||
return nil, errors.Errorf("unknown function reference")
|
||||
}
|
||||
var distinct bool
|
||||
switch node.Type {
|
||||
case 0, tree.AllFuncType:
|
||||
distinct = false
|
||||
case tree.DistinctFuncType:
|
||||
distinct = true
|
||||
default:
|
||||
return nil, errors.Errorf("unknown function spec type %d", node.Type)
|
||||
}
|
||||
windowDef, err := nodeWindowDef(ctx, node.WindowDef)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
exprs, err := nodeExprsToSelectExprs(ctx, node.Exprs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch strings.ToLower(name.String()) {
|
||||
// special case for string_agg, which maps to the mysql aggregate function group_concat
|
||||
case "string_agg":
|
||||
if len(node.Exprs) != 2 {
|
||||
return nil, errors.Errorf("string_agg requires two arguments")
|
||||
}
|
||||
|
||||
sepString := ""
|
||||
if sep, ok := node.Exprs[1].(*tree.StrVal); ok {
|
||||
sepString = strings.Trim(sep.String(), "'")
|
||||
} else {
|
||||
// TODO: need to support this function in doltgres
|
||||
c, is := node.Exprs[1].(*tree.CastExpr)
|
||||
if !is && c.Type.SQLString() != "TEXT" {
|
||||
return nil, errors.Errorf("string_agg requires a string separator")
|
||||
}
|
||||
sepString = strings.Trim(c.Expr.String(), "'")
|
||||
}
|
||||
|
||||
var orderBy vitess.OrderBy
|
||||
if len(node.OrderBy) > 0 {
|
||||
orderBy, err = nodeOrderBy(ctx, node.OrderBy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &vitess.GroupConcatExpr{
|
||||
Exprs: exprs[:1],
|
||||
Separator: vitess.Separator{
|
||||
SeparatorString: sepString,
|
||||
},
|
||||
OrderBy: orderBy,
|
||||
}, nil
|
||||
case "array_agg":
|
||||
var orderBy vitess.OrderBy
|
||||
if len(node.OrderBy) > 0 {
|
||||
orderBy, err = nodeOrderBy(ctx, node.OrderBy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &vitess.OrderedInjectedExpr{
|
||||
InjectedExpr: vitess.InjectedExpr{
|
||||
Expression: &pgexprs.ArrayAgg{Distinct: distinct},
|
||||
SelectExprChildren: exprs,
|
||||
Auth: vitess.AuthInformation{},
|
||||
},
|
||||
OrderBy: orderBy,
|
||||
}, nil
|
||||
}
|
||||
|
||||
if len(node.OrderBy) > 0 {
|
||||
return nil, errors.Errorf("function ORDER BY is not yet supported")
|
||||
}
|
||||
|
||||
return &vitess.FuncExpr{
|
||||
Qualifier: qualifier,
|
||||
Name: name,
|
||||
Distinct: distinct,
|
||||
Exprs: exprs,
|
||||
Over: (*vitess.Over)(windowDef),
|
||||
Auth: vitess.AuthInformation{
|
||||
AuthType: auth.AuthType_EXECUTE,
|
||||
TargetType: auth.AuthTargetType_FunctionIdentifiers,
|
||||
TargetNames: []string{qualifier.String(), name.String()},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user