chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,241 @@
|
||||
// Copyright 2026 Dolthub, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package node
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/expranalysis"
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
"github.com/dolthub/go-mysql-server/sql/plan"
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/core"
|
||||
)
|
||||
|
||||
// ValidateConstraint handles the ALTER TABLE ... VALIDATE CONSTRAINT statement.
|
||||
type ValidateConstraint struct {
|
||||
DbProvider sql.DatabaseProvider
|
||||
schemaName string
|
||||
tableName string
|
||||
constraintName string
|
||||
}
|
||||
|
||||
var _ sql.ExecSourceRel = (*ValidateConstraint)(nil)
|
||||
var _ sql.MultiDatabaser = (*ValidateConstraint)(nil)
|
||||
var _ vitess.Injectable = (*ValidateConstraint)(nil)
|
||||
|
||||
// NewValidateConstraint returns a new *ValidateConstraint.
|
||||
func NewValidateConstraint(schemaName, tableName, constraintName string) *ValidateConstraint {
|
||||
return &ValidateConstraint{
|
||||
schemaName: schemaName,
|
||||
tableName: tableName,
|
||||
constraintName: constraintName,
|
||||
}
|
||||
}
|
||||
|
||||
// Children implements sql.ExecSourceRel.
|
||||
func (v *ValidateConstraint) Children() []sql.Node { return nil }
|
||||
|
||||
// IsReadOnly implements sql.ExecSourceRel.
|
||||
func (v *ValidateConstraint) IsReadOnly() bool { return false }
|
||||
|
||||
// Resolved implements sql.ExecSourceRel.
|
||||
func (v *ValidateConstraint) Resolved() bool {
|
||||
return v.DbProvider != nil
|
||||
}
|
||||
|
||||
// Schema implements sql.ExecSourceRel.
|
||||
func (v *ValidateConstraint) Schema(ctx *sql.Context) sql.Schema { return nil }
|
||||
|
||||
// String implements sql.ExecSourceRel.
|
||||
func (v *ValidateConstraint) String() string { return "VALIDATE CONSTRAINT" }
|
||||
|
||||
// WithChildren implements sql.ExecSourceRel.
|
||||
func (v *ValidateConstraint) WithChildren(ctx *sql.Context, children ...sql.Node) (sql.Node, error) {
|
||||
return plan.NillaryWithChildren(v, children...)
|
||||
}
|
||||
|
||||
// WithResolvedChildren implements vitess.Injectable.
|
||||
func (v *ValidateConstraint) WithResolvedChildren(ctx context.Context, children []any) (any, error) {
|
||||
if len(children) != 0 {
|
||||
return nil, ErrVitessChildCount.New(0, len(children))
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// DatabaseProvider implements sql.MultiDatabaser.
|
||||
func (v *ValidateConstraint) DatabaseProvider() sql.DatabaseProvider { return v.DbProvider }
|
||||
|
||||
// WithDatabaseProvider implements sql.MultiDatabaser.
|
||||
func (v *ValidateConstraint) WithDatabaseProvider(provider sql.DatabaseProvider) (sql.Node, error) {
|
||||
nv := *v
|
||||
nv.DbProvider = provider
|
||||
return &nv, nil
|
||||
}
|
||||
|
||||
// RowIter implements sql.ExecSourceRel.
|
||||
func (v *ValidateConstraint) RowIter(ctx *sql.Context, r sql.Row) (sql.RowIter, error) {
|
||||
db, err := v.DbProvider.Database(ctx, ctx.GetCurrentDatabase())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
schemaName := v.schemaName
|
||||
if schemaName == "" {
|
||||
schemaName, err = core.GetCurrentSchema(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
schemaDb, ok := db.(sql.SchemaDatabase)
|
||||
if !ok {
|
||||
return nil, errors.Errorf("database does not support schemas")
|
||||
}
|
||||
dbSchema, ok, err := schemaDb.GetSchema(ctx, schemaName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !ok {
|
||||
return nil, errors.Errorf("schema %s does not exist", schemaName)
|
||||
}
|
||||
tblNode, _, err := dbSchema.GetTableInsensitive(ctx, v.tableName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if tblNode == nil {
|
||||
return nil, sql.ErrTableNotFound.New(v.tableName)
|
||||
}
|
||||
|
||||
// validate foreign key constraint
|
||||
if fkTbl, ok := tblNode.(sql.ForeignKeyTable); ok {
|
||||
fks, err := fkTbl.GetDeclaredForeignKeys(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, fk := range fks {
|
||||
if strings.EqualFold(fk.Name, v.constraintName) {
|
||||
return v.validateForeignKey(ctx, db, fkTbl, fk)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// validate check constraint
|
||||
if checkTbl, ok := tblNode.(sql.CheckTable); ok {
|
||||
checks, err := checkTbl.GetChecks(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, check := range checks {
|
||||
if strings.EqualFold(check.Name, v.constraintName) {
|
||||
return v.validateCheckConstraint(ctx, tblNode, check)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil, errors.Errorf(`constraint "%s" of relation "%s" does not exist`, v.constraintName, v.tableName)
|
||||
}
|
||||
|
||||
// validateCheckConstraint validates check constraints if on its |IsNotValid| field is TRUE.
|
||||
func (v *ValidateConstraint) validateCheckConstraint(ctx *sql.Context, tblNode sql.Table, check sql.CheckDefinition) (sql.RowIter, error) {
|
||||
if !check.IsNotValid {
|
||||
return sql.RowsToRowIter(), nil
|
||||
}
|
||||
|
||||
checkExpr, err := expranalysis.ResolveExpression(ctx, v.tableName, check.CheckExpression)
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("could not parse check expression for constraint %q: %v", check.Name, err)
|
||||
}
|
||||
|
||||
partitions, err := tblNode.Partitions(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer partitions.Close(ctx)
|
||||
|
||||
for {
|
||||
partition, err := partitions.Next(ctx)
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rows, err := tblNode.PartitionRows(ctx, partition)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close(ctx)
|
||||
for {
|
||||
row, err := rows.Next(ctx)
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, err := sql.EvaluateCondition(ctx, checkExpr, row)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if sql.IsFalse(res) {
|
||||
return nil, sql.ErrCheckConstraintViolated.New(check.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: clear the IsNotValid flag so pg_constraint reports convalidated = true
|
||||
return sql.RowsToRowIter(), nil
|
||||
}
|
||||
|
||||
// validateForeignKey validates foreign key constraints if on its |IsNotValid| field is TRUE.
|
||||
func (v *ValidateConstraint) validateForeignKey(ctx *sql.Context, db sql.Database, fkTbl sql.ForeignKeyTable, fkDef sql.ForeignKeyConstraint) (sql.RowIter, error) {
|
||||
if !fkDef.IsNotValid {
|
||||
return sql.RowsToRowIter(), nil
|
||||
}
|
||||
|
||||
refTblNode, _, err := db.GetTableInsensitive(ctx, fkDef.ParentTable)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if refTblNode == nil {
|
||||
return nil, sql.ErrTableNotFound.New(fkDef.ParentTable)
|
||||
}
|
||||
refFkTbl, ok := refTblNode.(sql.ForeignKeyTable)
|
||||
if !ok {
|
||||
return nil, errors.Errorf("table %s does not support foreign key constraints", fkDef.ParentTable)
|
||||
}
|
||||
|
||||
// this is to allow calling plan.ResolveForeignKey function to validate
|
||||
fkDef.IsResolved = false
|
||||
if err = plan.ResolveForeignKey(ctx, fkTbl, refFkTbl, fkDef, false, true, true); err != nil {
|
||||
// TODO: fix - currently error message includes "cannot add or update a child row"
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// undo - it's not used but safe to set it
|
||||
fkDef.IsResolved = true
|
||||
|
||||
// clear the IsNotValid flag so pg_constraint reports convalidated = true
|
||||
fkDef.IsNotValid = false
|
||||
if err = fkTbl.UpdateForeignKey(ctx, fkDef.Name, fkDef); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return sql.RowsToRowIter(), nil
|
||||
}
|
||||
Reference in New Issue
Block a user