Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

resolve aliases in subqueries in function arguments #1838

Merged
merged 11 commits into from
Jun 21, 2023
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions enginetest/queries/queries.go
Original file line number Diff line number Diff line change
Expand Up @@ -5142,6 +5142,42 @@ Select * from (
{"b"},
},
},
{
Query: `SELECT if(1, 123, 456)`,
Expected: []sql.Row{
{123},
},
},
{
Query: `SELECT if(0, 123, 456)`,
Expected: []sql.Row{
{456},
},
},
{
Query: `SELECT if(0, "abc", 456)`,
Expected: []sql.Row{
{456},
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be a string, but the TestEvaluation doesn't go through the schema[0].Convert code path.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we move to brokenQueries?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or is it just bad test setup

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's just bad test setup, when the results are written to the shell we use the writerIters, but not for evaluation.
I can add it in a separate PR, and see how many failures we get

},
},
{
Query: `SELECT if(1, "abc", 456)`,
Expected: []sql.Row{
{"abc"},
},
},
{
Query: `SELECT 1 as foo, if((select foo), "a", "b")`,
Expected: []sql.Row{
{1, "a"},
},
},
{
Query: `SELECT 0 as foo, if((select foo), "a", "b")`,
Expected: []sql.Row{
{0, "b"},
},
},
{
Query: `SELECT if(NULL, "a", "b")`,
Expected: []sql.Row{
Expand Down
22 changes: 22 additions & 0 deletions server/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,28 @@ func TestHandlerOutput(t *testing.T) {
require.Equal(t, sqltypes.Float64, result.Rows[0][0].Type())
require.Equal(t, []byte("1"), result.Rows[0][0].ToBytes())
})

t.Run("if() type is correct", func(t *testing.T) {
handler.ComInitDB(dummyConn, "test")
var result *sqltypes.Result
err := handler.ComQuery(dummyConn, "select if(1, 123, 'def')", func(res *sqltypes.Result, more bool) error {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be do (0, "def", 123) -> "123?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The old code had return n.ifTrue.Type(), which was already Text, so it didn't actually test the new code I added.

result = res
return nil
})
require.NoError(t, err)
require.Equal(t, 1, len(result.Rows))
require.Equal(t, sqltypes.Text, result.Rows[0][0].Type())
require.Equal(t, []byte("123"), result.Rows[0][0].ToBytes())

err = handler.ComQuery(dummyConn, "select if(0, 123, 456)", func(res *sqltypes.Result, more bool) error {
result = res
return nil
})
require.NoError(t, err)
require.Equal(t, 1, len(result.Rows))
require.Equal(t, sqltypes.Int64, result.Rows[0][0].Type())
require.Equal(t, []byte("456"), result.Rows[0][0].ToBytes())
})
}

func TestHandlerComPrepare(t *testing.T) {
Expand Down
38 changes: 25 additions & 13 deletions sql/analyzer/reorder_projections.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,30 @@ func reorderProjection(ctx *sql.Context, a *Analyzer, n sql.Node, scope *plan.Sc
})
}

// findDeferredColumns is a function that searches through a projection's expressions and checks if there are any
// subqueries that reference the deferred column. Additionally, we have to check for subqueries in functions arguments
func findDeferredColumns(exprs ...sql.Expression) []column {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there any reason not to just transform.Expr for each of these?

var deferredColumns []column
for _, e := range exprs {
if a, ok := e.(*expression.Alias); ok {
e = a.Child
}

switch e := e.(type) {
case *plan.Subquery:
deferredColumns = append(deferredColumns, findDeferredColumnsAndAliasReferences(e.Query)...)
case sql.FunctionExpression:
for _, arg := range e.Children() {
innerCols := findDeferredColumns(arg)
if len(innerCols) > 0 {
deferredColumns = append(deferredColumns, innerCols...)
}
}
}
}
return deferredColumns
}

func addIntermediateProjections(
project *plan.Project,
projectedAliases map[string]sql.Expression,
Expand Down Expand Up @@ -194,19 +218,7 @@ func addIntermediateProjections(
// like a child node in this respect -- it draws its outer scope schema from the child of the node in which it's
// embedded. We identify any missing subquery columns by their being deferred or marked as an AliasReference
// from a previous analyzer step.
var deferredColumns []column
for _, e := range project.Projections {
if a, ok := e.(*expression.Alias); ok {
e = a.Child
}
s, ok := e.(*plan.Subquery)
if !ok {
continue
}

deferredColumns = append(deferredColumns, findDeferredColumnsAndAliasReferences(s.Query)...)
}

deferredColumns := findDeferredColumns(project.Projections...)
if len(deferredColumns) > 0 {
schema := child.Schema()
var projections = make([]sql.Expression, 0, len(schema)+len(deferredColumns))
Expand Down
8 changes: 7 additions & 1 deletion sql/expression/function/if.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,13 @@ func (f *If) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) {

// Type implements the Expression interface.
func (f *If) Type() sql.Type {
return f.ifTrue.Type()
// if either type is string type, this should be a string type, regardless need to promote
typ1 := f.ifTrue.Type()
typ2 := f.ifFalse.Type()
if types.IsText(typ1) || types.IsText(typ2) {
return types.Text
}
return typ1.Promote()
}

// CollationCoercibility implements the interface sql.CollationCoercible.
Expand Down
22 changes: 22 additions & 0 deletions sql/planbuilder/parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
type planTest struct {
Query string
ExpectedPlan string
Skip bool
}

func TestPlanBuilder(t *testing.T) {
Expand Down Expand Up @@ -839,6 +840,24 @@ Project
└─ Table
├─ name: xy
└─ columns: [x y z]
`,
},
{
Skip: true, // TODO: we don't correctly put aliases in the scope
Query: "select 1 as foo, concat((select foo), 'abc');",
ExpectedPlan: `
Project
├─ columns: [1 (tinyint) as foo, concat(Subquery
│ ├─ cacheable: false
│ └─ Project
│ ├─ columns: [foo:1!null]
│ └─ Table
│ ├─ name:
│ └─ columns: []
│ ,abc (longtext)) as concat((select foo), 'abc')]
└─ Table
├─ name:
└─ columns: []
`,
},
}
Expand Down Expand Up @@ -879,6 +898,9 @@ Project

for _, tt := range tests {
t.Run(tt.Query, func(t *testing.T) {
if tt.Skip {
t.Skip()
}
stmt, err := sqlparser.Parse(tt.Query)
require.NoError(t, err)

Expand Down
4 changes: 4 additions & 0 deletions sql/planbuilder/scope.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ func (s *scope) getExpr(name string) (columnId, bool) {
}
}
}
// TODO: possibly want to look in parent scopes
//if !ok && s.parent != nil {
// return s.parent.getExpr(name)
//}
return id, ok
}

Expand Down