chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,203 @@
|
||||
// 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
|
||||
}
|
||||
Reference in New Issue
Block a user