Files
wehub-resource-sync 5357c39144
Fuzzer / Run Fuzzer (push) Has been cancelled
Race tests / Go race tests (ubuntu-22.04) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:01:40 +08:00

174 lines
6.1 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 expranalysis
import (
"fmt"
"github.com/dolthub/go-mysql-server/memory"
"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/dolt/go/libraries/doltcore/schema"
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/overrides"
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/sqlfmt"
)
// ResolveDefaultExpression returns a sql.Expression for the column default or generated expression for the
// column provided
func ResolveDefaultExpression(ctx *sql.Context, tableName string, sch schema.Schema, col schema.Column) (sql.Expression, error) {
ct, err := parseCreateTable(ctx, tableName, sch)
if err != nil {
return nil, err
}
colIdx := ct.PkSchema().Schema.IndexOfColName(col.Name)
if colIdx < 0 {
return nil, fmt.Errorf("unable to find column %s in analyzed query", col.Name)
}
sqlCol := ct.PkSchema().Schema[colIdx]
expr := sqlCol.Default
if expr == nil || expr.Expr == nil {
expr = sqlCol.Generated
}
if expr == nil || expr.Expr == nil {
return nil, fmt.Errorf("unable to find default or generated expression")
}
return expr, nil
}
// ResolveCheckExpression returns a sql.Expression for the check provided
func ResolveCheckExpression(ctx *sql.Context, tableName string, sch schema.Schema, checkExpr string) (sql.Expression, error) {
ct, err := parseCreateTable(ctx, tableName, sch)
if err != nil {
return nil, err
}
formatter := overrides.SchemaFormatterFromContext(ctx)
for _, check := range ct.Checks() {
// Check definitions created before v1.55.3 may not have backquotes around identifiers
quotedExpr := stripTableNamesFromExpression(ctx, formatter, check.Expr, true).String()
unquotedExpr := stripTableNamesFromExpression(ctx, formatter, check.Expr, false).String()
if quotedExpr == checkExpr || unquotedExpr == checkExpr {
return check.Expr, nil
}
}
return nil, fmt.Errorf("unable to find check expression")
}
// ResolveExpression compiles a predicate or check constraint string into a sql.Expression resolved.
// Used to re-hydrate partial index predicates and check expressions from their string representation.
// It uses SELECT statement on the expression FROM given table.
func ResolveExpression(ctx *sql.Context, tableName string, predicateStr string) (sql.Expression, error) {
// TODO: added * in SELECT stmt to avoid pruning columns during analyzer.
parsed, err := parseQuery(ctx, fmt.Sprintf("SELECT *, %s FROM %s", predicateStr, tableName))
if err != nil {
return nil, err
}
sess, ok := ctx.Session.(SessionDbProvider)
if ok {
// field index in GetField is incorrect, it needs `fix_exec_indexes` analyzer rule
a := analyzer.NewDefault(sess.GenericProvider())
parsed, err = a.Analyze(ctx, parsed, nil, nil)
if err != nil {
return nil, err
}
// comes out in ContextRootFinalizer, so unwrap it
parsed = parsed.Children()[0]
}
proj, ok := parsed.(*plan.Project)
if !ok {
return nil, fmt.Errorf("expected *plan.Project parsing predicate, got %T", parsed)
}
expr := proj.Projections[len(proj.Projections)-1]
if alias, ok := expr.(*expression.Alias); ok {
expr = alias.Child
}
return expr, nil
}
func stripTableNamesFromExpression(ctx *sql.Context, formatter sql.SchemaFormatter, expr sql.Expression, quoted bool) sql.Expression {
e, _, _ := transform.Expr(ctx, expr, func(ctx *sql.Context, e sql.Expression) (sql.Expression, transform.TreeIdentity, error) {
if col, ok := e.(*expression.GetField); ok {
return col.WithTable("").WithQuotedNames(formatter, quoted), transform.NewTree, nil
}
return e, transform.SameTree, nil
})
return e
}
// SessionDbProvider is a way to access the Dolt database provider without import cycles.
type SessionDbProvider interface {
GenericProvider() sql.MutableDatabaseProvider
}
func parseCreateTable(ctx *sql.Context, tableName string, sch schema.Schema) (*plan.CreateTable, error) {
createTable, err := sqlfmt.GenerateCreateTableStatement(ctx, tableName, sch, nil, nil)
if err != nil {
return nil, err
}
pseudoAnalyzedQuery, err := parseQuery(ctx, createTable)
if err != nil {
return nil, err
}
ct, ok := pseudoAnalyzedQuery.(*plan.CreateTable)
if !ok {
return nil, fmt.Errorf("expected a *plan.CreateTable node, but got %T", pseudoAnalyzedQuery)
}
return ct, nil
}
func parseQuery(ctx *sql.Context, query string) (sql.Node, error) {
// Doltgres must use the existing provider due to custom functions, so we split the logic here depending on whether
// this is being called from a query context (which will have a valid Dolt session) or some other context (which
// will need to construct a new provider).
var b *planbuilder.Builder
engOverrides := overrides.EngineOverridesFromContext(ctx)
sess, ok := ctx.Session.(SessionDbProvider)
if ok {
catalog := analyzer.NewCatalog(sess.GenericProvider(), engOverrides)
catalog.AuthHandler = sql.NoopAuthorizationHandler{}
b = planbuilder.New(ctx, catalog, nil)
} else {
mockDatabase := memory.NewDatabase("mydb")
mockProvider := memory.NewDBProvider(mockDatabase)
catalog := analyzer.NewCatalog(mockProvider, engOverrides)
catalog.AuthHandler = sql.NoopAuthorizationHandler{}
// We need a new context for this operation
parseCtx := sql.NewEmptyContext()
parseCtx.SetCurrentDatabase("mydb")
b = planbuilder.New(parseCtx, catalog, nil)
}
pseudoAnalyzedQuery, _, _, _, err := b.Parse(query, nil, false)
if err != nil {
return nil, err
}
return pseudoAnalyzedQuery, nil
}