This repository has been archived by the owner on Jan 28, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 110
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
sql: implement EXPLODE and generators (#720)
sql: implement EXPLODE and generators
- Loading branch information
Showing
15 changed files
with
1,010 additions
and
66 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
package analyzer | ||
|
||
import ( | ||
"gopkg.in/src-d/go-errors.v1" | ||
"github.com/src-d/go-mysql-server/sql" | ||
"github.com/src-d/go-mysql-server/sql/expression" | ||
"github.com/src-d/go-mysql-server/sql/expression/function" | ||
"github.com/src-d/go-mysql-server/sql/plan" | ||
) | ||
|
||
var ( | ||
errMultipleGenerators = errors.NewKind("there can't be more than 1 instance of EXPLODE in a SELECT") | ||
errExplodeNotArray = errors.NewKind("argument of type %q given to EXPLODE, expecting array") | ||
) | ||
|
||
func resolveGenerators(ctx *sql.Context, a *Analyzer, n sql.Node) (sql.Node, error) { | ||
return n.TransformUp(func(n sql.Node) (sql.Node, error) { | ||
p, ok := n.(*plan.Project) | ||
if !ok { | ||
return n, nil | ||
} | ||
|
||
projection := p.Projections | ||
|
||
g, err := findGenerator(projection) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// There might be no generator in the project, in that case we don't | ||
// have to do anything. | ||
if g == nil { | ||
return n, nil | ||
} | ||
|
||
projection[g.idx] = g.expr | ||
|
||
var name string | ||
if n, ok := g.expr.(sql.Nameable); ok { | ||
name = n.Name() | ||
} else { | ||
name = g.expr.String() | ||
} | ||
|
||
return plan.NewGenerate( | ||
plan.NewProject(projection, p.Child), | ||
expression.NewGetField(g.idx, g.expr.Type(), name, g.expr.IsNullable()), | ||
), nil | ||
}) | ||
} | ||
|
||
type generator struct { | ||
idx int | ||
expr sql.Expression | ||
} | ||
|
||
// findGenerator will find in the given projection a generator column. If there | ||
// is no generator, it will return nil. | ||
// If there are is than one generator or the argument to explode is not an | ||
// array it will fail. | ||
// All occurrences of Explode will be replaced with Generate. | ||
func findGenerator(exprs []sql.Expression) (*generator, error) { | ||
var g = &generator{idx: -1} | ||
for i, e := range exprs { | ||
var found bool | ||
switch e := e.(type) { | ||
case *function.Explode: | ||
found = true | ||
g.expr = function.NewGenerate(e.Child) | ||
case *expression.Alias: | ||
if exp, ok := e.Child.(*function.Explode); ok { | ||
found = true | ||
g.expr = expression.NewAlias( | ||
function.NewGenerate(exp.Child), | ||
e.Name(), | ||
) | ||
} | ||
} | ||
|
||
if found { | ||
if g.idx >= 0 { | ||
return nil, errMultipleGenerators.New() | ||
} | ||
g.idx = i | ||
|
||
if !sql.IsArray(g.expr.Type()) { | ||
return nil, errExplodeNotArray.New(g.expr.Type()) | ||
} | ||
} | ||
} | ||
|
||
if g.expr == nil { | ||
return nil, nil | ||
} | ||
|
||
return g, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
package analyzer | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/require" | ||
"gopkg.in/src-d/go-errors.v1" | ||
"github.com/src-d/go-mysql-server/sql" | ||
"github.com/src-d/go-mysql-server/sql/expression" | ||
"github.com/src-d/go-mysql-server/sql/expression/function" | ||
"github.com/src-d/go-mysql-server/sql/plan" | ||
) | ||
|
||
func TestResolveGenerators(t *testing.T) { | ||
testCases := []struct { | ||
name string | ||
node sql.Node | ||
expected sql.Node | ||
err *errors.Kind | ||
}{ | ||
{ | ||
name: "regular explode", | ||
node: plan.NewProject( | ||
[]sql.Expression{ | ||
expression.NewGetField(0, sql.Int64, "a", false), | ||
function.NewExplode(expression.NewGetField(1, sql.Array(sql.Int64), "b", false)), | ||
expression.NewGetField(2, sql.Int64, "c", false), | ||
}, | ||
plan.NewUnresolvedTable("foo", ""), | ||
), | ||
expected: plan.NewGenerate( | ||
plan.NewProject( | ||
[]sql.Expression{ | ||
expression.NewGetField(0, sql.Int64, "a", false), | ||
function.NewGenerate(expression.NewGetField(1, sql.Array(sql.Int64), "b", false)), | ||
expression.NewGetField(2, sql.Int64, "c", false), | ||
}, | ||
plan.NewUnresolvedTable("foo", ""), | ||
), | ||
expression.NewGetField(1, sql.Array(sql.Int64), "EXPLODE(b)", false), | ||
), | ||
err: nil, | ||
}, | ||
{ | ||
name: "explode with alias", | ||
node: plan.NewProject( | ||
[]sql.Expression{ | ||
expression.NewGetField(0, sql.Int64, "a", false), | ||
expression.NewAlias( | ||
function.NewExplode( | ||
expression.NewGetField(1, sql.Array(sql.Int64), "b", false), | ||
), | ||
"x", | ||
), | ||
expression.NewGetField(2, sql.Int64, "c", false), | ||
}, | ||
plan.NewUnresolvedTable("foo", ""), | ||
), | ||
expected: plan.NewGenerate( | ||
plan.NewProject( | ||
[]sql.Expression{ | ||
expression.NewGetField(0, sql.Int64, "a", false), | ||
expression.NewAlias( | ||
function.NewGenerate( | ||
expression.NewGetField(1, sql.Array(sql.Int64), "b", false), | ||
), | ||
"x", | ||
), | ||
expression.NewGetField(2, sql.Int64, "c", false), | ||
}, | ||
plan.NewUnresolvedTable("foo", ""), | ||
), | ||
expression.NewGetField(1, sql.Array(sql.Int64), "x", false), | ||
), | ||
err: nil, | ||
}, | ||
{ | ||
name: "non array type on explode", | ||
node: plan.NewProject( | ||
[]sql.Expression{ | ||
expression.NewGetField(0, sql.Int64, "a", false), | ||
function.NewExplode(expression.NewGetField(1, sql.Int64, "b", false)), | ||
}, | ||
plan.NewUnresolvedTable("foo", ""), | ||
), | ||
expected: nil, | ||
err: errExplodeNotArray, | ||
}, | ||
{ | ||
name: "more than one generator", | ||
node: plan.NewProject( | ||
[]sql.Expression{ | ||
expression.NewGetField(0, sql.Int64, "a", false), | ||
function.NewExplode(expression.NewGetField(1, sql.Array(sql.Int64), "b", false)), | ||
function.NewExplode(expression.NewGetField(2, sql.Array(sql.Int64), "c", false)), | ||
}, | ||
plan.NewUnresolvedTable("foo", ""), | ||
), | ||
expected: nil, | ||
err: errMultipleGenerators, | ||
}, | ||
} | ||
|
||
for _, tt := range testCases { | ||
t.Run(tt.name, func(t *testing.T) { | ||
require := require.New(t) | ||
result, err := resolveGenerators(sql.NewEmptyContext(), nil, tt.node) | ||
if tt.err != nil { | ||
require.Error(err) | ||
require.True(tt.err.Is(err)) | ||
} else { | ||
require.NoError(err) | ||
require.Equal(tt.expected, result) | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.