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

204 lines
7.5 KiB
Go

// 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/dolthub/go-mysql-server/sql/expression"
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
pgexprs "github.com/dolthub/doltgresql/server/expression"
)
// nodeSelectClause handles tree.SelectClause nodes.
func nodeSelectClause(ctx *Context, node *tree.SelectClause) (*vitess.Select, error) {
if node == nil {
return nil, nil
}
selectExprs, err := nodeSelectExprs(ctx, node.Exprs)
if err != nil {
return nil, err
}
// Multiple tables in the FROM column with an "equals" filter for some columns within each table should be treated
// as a join. The analyzer should catch this, however GMS processes this form of a join differently than a standard
// join, which is currently incompatible with Doltgres expressions. As a workaround, we rewrite the tree so that we
// pass along a join node.
// TODO: handle more than two tables, also make this more robust with handling more node types
if len(node.From.Tables) == 2 && node.Where != nil {
tableNames := make(map[tree.TableName]int)
tableAliases := make(map[tree.TableName]int)
// First we need to get the table names and aliases, since they'll be referenced by the filters
for i := range node.From.Tables {
switch table := node.From.Tables[i].(type) {
case *tree.AliasedTableExpr:
if tableName, ok := table.Expr.(*tree.TableName); ok {
tableNames[*tableName] = i
} else {
goto PostJoinRewrite
}
tableAliases[tree.MakeUnqualifiedTableName(table.As.Alias)] = i
case *tree.TableName:
tableNames[*table] = i
case *tree.UnresolvedObjectName:
tableNames[table.ToTableName()] = i
default:
goto PostJoinRewrite
}
}
// For now, we'll check if the entire filter should be moved into the join condition. Eventually, this should
// move only the needed expressions into the join condition.
var delveExprs func(expr tree.Expr) bool
delveExprs = func(expr tree.Expr) bool {
switch expr := expr.(type) {
case *tree.AndExpr:
return delveExprs(expr.Left) && delveExprs(expr.Right)
case *tree.OrExpr:
return delveExprs(expr.Left) && delveExprs(expr.Right)
case *tree.ComparisonExpr:
if expr.Operator != tree.EQ {
return false
}
var refTables [2]int
for argIndex, arg := range []tree.Expr{expr.Left, expr.Right} {
switch arg := arg.(type) {
case *tree.UnresolvedName:
refTable := arg.GetUnresolvedObjectName().ToTableName()
if aliasIndex, ok := tableAliases[refTable]; ok {
refTables[argIndex] = aliasIndex
} else if tableIndex, ok := tableNames[refTable]; ok {
refTables[argIndex] = tableIndex
} else {
return false
}
default:
return false
}
}
// In this case, the expression does not reference multiple tables, so it's not a join condition
if refTables[0] == refTables[1] {
return false
}
return true
default:
return false
}
}
if !delveExprs(node.Where.Expr) {
goto PostJoinRewrite
}
// The filter condition represents a join, so we need to rewrite our FROM node to be a join node
node.From.Tables = tree.TableExprs{&tree.JoinTableExpr{
JoinType: "",
Left: node.From.Tables[0],
Right: node.From.Tables[1],
Cond: &tree.OnJoinCond{Expr: node.Where.Expr},
}}
node.Where = nil
}
PostJoinRewrite:
from, err := nodeFrom(ctx, node.From)
if err != nil {
return nil, err
}
// We use TableFuncExprs to represent queries on functions that behave as though they were tables. This is something
// that we have to situationally support, as inner nodes do not have the proper context to output a TableFuncExpr,
// since TableFuncExprs pertain only to SELECT statements.
for i, fromExpr := range from {
// Nodes are very liberal in wrapping themselves within other nodes, which gives them a technically correct
// tree, however GMS makes assumptions about the makeup of the trees that it receives. We'll eventually
// generalize this on the GMS side, but for now we need to transform our tree in case we need to use a TableFuncExpr.
if aliasedTableExpr, ok := fromExpr.(*vitess.AliasedTableExpr); ok {
subquery, ok := aliasedTableExpr.Expr.(*vitess.Subquery)
// If all of these are true, then the AliasedTableExpr is probably a wrapper around a subquery, but we have
// to confirm that the subquery contains a *Select with a single child in its From expressions.
if !aliasedTableExpr.Lateral &&
aliasedTableExpr.Hints == nil &&
len(aliasedTableExpr.Partitions) == 0 &&
ok && len(subquery.Columns) == 0 {
// If this is true, then we can confirm that it's just a wrapper (and not an explicit AliasedTableExpr).
// This may seem like a lot of fragile checks, but AliasedTableExpr explicitly sets its state to this in
// this circumstance. We do not want to create a TableFuncExpr except under very specific circumstances.
if subquerySelect, ok := subquery.Select.(*vitess.Select); ok && len(subquerySelect.From) == 1 {
if valuesStatement, ok := subquerySelect.From[0].(*vitess.ValuesStatement); ok {
if len(valuesStatement.Columns) == 0 && len(valuesStatement.Rows) == 1 && len(valuesStatement.Rows[0]) == 1 {
if funcExpr, ok := valuesStatement.Rows[0][0].(*vitess.FuncExpr); ok {
// It appears that GMS hardcodes the expectation of vitess literals here, so we have to
// convert from Doltgres literals to GMS literals. Eventually we need to remove this
// hardcoded behavior.
for _, fExpr := range funcExpr.Exprs {
if aliasedExpr, ok := fExpr.(*vitess.AliasedExpr); ok {
if injectedExpr, ok := aliasedExpr.Expr.(vitess.InjectedExpr); ok {
if literal, ok := injectedExpr.Expression.(*expression.Literal); ok {
aliasedExpr.Expr = pgexprs.ToVitessLiteral(literal)
}
}
}
}
from[i] = &vitess.TableFuncExpr{
Name: funcExpr.Name.String(),
Exprs: funcExpr.Exprs,
Alias: aliasedTableExpr.As,
}
}
}
}
}
}
}
}
distinct := node.Distinct
var distinctOn vitess.Exprs
if len(node.DistinctOn) > 0 {
distinct = true
distinctOn = make(vitess.Exprs, len(node.DistinctOn))
for i, expr := range node.DistinctOn {
distinctOn[i], err = nodeExpr(ctx, expr)
if err != nil {
return nil, err
}
}
}
where, err := nodeWhere(ctx, node.Where)
if err != nil {
return nil, err
}
having, err := nodeWhere(ctx, node.Having)
if err != nil {
return nil, err
}
groupBy, err := nodeGroupBy(ctx, node.GroupBy)
if err != nil {
return nil, err
}
window, err := nodeWindow(ctx, node.Window)
if err != nil {
return nil, err
}
return &vitess.Select{
QueryOpts: vitess.QueryOpts{
Distinct: distinct,
DistinctOn: distinctOn,
},
SelectExprs: selectExprs,
From: from,
Where: where,
GroupBy: groupBy,
Having: having,
Window: window,
Comments: vitess.Comments{[]byte(node.BlockComment)},
}, nil
}