Files
2026-07-13 12:32:25 +08:00

132 lines
4.8 KiB
Go

// 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
}