diff --git a/planner.go b/planner.go index 4b080d279..9fd141fc8 100644 --- a/planner.go +++ b/planner.go @@ -42,6 +42,10 @@ func (p *Planner) PlanStatement(ctx context.Context, stmt sql2.Statement) (*Stmt } func (p *Planner) planStatement(ctx context.Context, stmt sql2.Statement) (StmtNode, error) { + if err := p.checkStatement(stmt); err != nil { + return nil, err + } + switch stmt := stmt.(type) { case *sql2.SelectStatement: return p.planSelectStatement(ctx, stmt) @@ -58,21 +62,10 @@ func (p *Planner) planSelectStatement(ctx context.Context, stmt *sql2.SelectStat } func (p *Planner) planAggregateSelectStatement(ctx context.Context, stmt *sql2.SelectStatement) (_ StmtNode, err error) { - // Extract table name from source. - var source *sql2.QualifiedTableName - switch src := stmt.Source.(type) { - case *sql2.JoinClause: - return nil, fmt.Errorf("cannot use JOIN in aggregate query") - case *sql2.ParenSource: - return nil, fmt.Errorf("cannot use parenthesized source in aggregate query") - case *sql2.QualifiedTableName: - source = src - case *sql2.SelectStatement: - return nil, fmt.Errorf("cannot use sub-select in aggregate query") - default: - return nil, fmt.Errorf("unexpected source type in aggregate query: %T", source) + indexName, err := statementTableName(stmt) + if err != nil { + return nil, err } - indexName := sql2.IdentName(source.Name) // Convert WHERE clause. cond, err := p.planExprPQL(ctx, stmt, stmt.WhereExpr) @@ -83,16 +76,18 @@ func (p *Planner) planAggregateSelectStatement(ctx context.Context, stmt *sql2.S // Extract calls and grouped expressions from column list. // TODO: Recursively traverse all expression trees. var calls []*sql2.Call - var aliases []string - // var groupByCols []*sql2.Ident // TODO: Convert to QualifiedRef + var columns []*StmtColumn for _, c := range stmt.Columns { - aliases = append(aliases, c.Name()) + columns = append(columns, &StmtColumn{ + Name: c.Name(), + Type: sql2.ExprDataType(c.Expr), + }) switch c := c.Expr.(type) { case *sql2.Call: calls = append(calls, c) - case *sql2.Ident: - // groupByCols = append(groupByCols, c) + case *sql2.QualifiedRef: + // allowed default: return nil, fmt.Errorf("unsupported expression type in aggregate query: %T", c) } @@ -107,8 +102,8 @@ func (p *Planner) planAggregateSelectStatement(ctx context.Context, stmt *sql2.S var groupByColNames []string for _, expr := range stmt.GroupByExprs { switch expr := expr.(type) { - case *sql2.Ident: - groupByColNames = append(groupByColNames, expr.Name) + case *sql2.QualifiedRef: + groupByColNames = append(groupByColNames, expr.Column.Name) default: return nil, fmt.Errorf("unsupported expression type in GROUP BY clause: %T", expr) } @@ -119,7 +114,7 @@ func (p *Planner) planAggregateSelectStatement(ctx context.Context, stmt *sql2.S switch callName { case "COUNT": if len(groupByColNames) == 0 { - return NewCountNode(p.executor, indexName, aliases[0], cond), nil + return NewCountNode(p.executor, indexName, columns[0], cond), nil } var aggregate *pql.Call @@ -127,7 +122,7 @@ func (p *Planner) planAggregateSelectStatement(ctx context.Context, stmt *sql2.S if len(calls[0].Args) != 1 { return nil, fmt.Errorf("distinct count must have exactly one field specified") } - field, ok := calls[0].Args[0].(*sql2.Ident) + ref, ok := calls[0].Args[0].(*sql2.QualifiedRef) if !ok { return nil, fmt.Errorf("distinct count argument must be a field name") } @@ -136,28 +131,28 @@ func (p *Planner) planAggregateSelectStatement(ctx context.Context, stmt *sql2.S Name: "Count", Children: []*pql.Call{{ Name: "Distinct", - Args: map[string]interface{}{"field": field.Name}, + Args: map[string]interface{}{"field": ref.Column.Name}, }}, } } - return NewGroupByNode(p.executor, indexName, groupByColNames, aliases, aggregate, cond), nil + return NewGroupByNode(p.executor, indexName, groupByColNames, columns, aggregate, cond), nil case "SUM": if len(calls[0].Args) != 1 { return nil, fmt.Errorf("sum must have exactly one field specified") } - field, ok := calls[0].Args[0].(*sql2.Ident) + ref, ok := calls[0].Args[0].(*sql2.QualifiedRef) if !ok { return nil, fmt.Errorf("sum argument must be a field name") } aggregate := &pql.Call{ Name: "Sum", - Args: map[string]interface{}{"field": field.Name}, + Args: map[string]interface{}{"field": ref.Column.Name}, } - return NewGroupByNode(p.executor, indexName, groupByColNames, aliases, aggregate, cond), nil + return NewGroupByNode(p.executor, indexName, groupByColNames, columns, aggregate, cond), nil default: return nil, fmt.Errorf("unsupported call in aggregate query: %s", callName) @@ -167,21 +162,10 @@ func (p *Planner) planAggregateSelectStatement(ctx context.Context, stmt *sql2.S } func (p *Planner) planNonAggregateSelectStatement(ctx context.Context, stmt *sql2.SelectStatement) (_ StmtNode, err error) { - // Extract table name from source. - var source *sql2.QualifiedTableName - switch src := stmt.Source.(type) { - case *sql2.JoinClause: - return nil, fmt.Errorf("cannot use JOIN in non-aggregate query") - case *sql2.ParenSource: - return nil, fmt.Errorf("cannot use parenthesized source in non-aggregate query") - case *sql2.QualifiedTableName: - source = src - case *sql2.SelectStatement: - return nil, fmt.Errorf("cannot use sub-select in non-aggregate query") - default: - return nil, fmt.Errorf("unexpected source type in non-aggregate query: %T", source) + indexName, err := statementTableName(stmt) + if err != nil { + return nil, err } - indexName := sql2.IdentName(source.Name) // Lookup index. idx := p.executor.Holder.Index(indexName) @@ -196,57 +180,24 @@ func (p *Planner) planNonAggregateSelectStatement(ctx context.Context, stmt *sql } // Build column list. - var columnNames, columnAliases []string + var srcs []string + var columns []*StmtColumn for _, col := range stmt.Columns { - // Unqualified wildcard. - if col.Star.IsValid() { - columnNames = append(columnNames, "_id") - columnAliases = append(columnAliases, "_id") - - for _, field := range idx.Fields() { - if field.Name() == "_exists" { - continue - } - columnNames = append(columnNames, field.Name()) - columnAliases = append(columnAliases, field.Name()) - } - continue - } - // Handle expressions and qualified references. switch expr := col.Expr.(type) { - case *sql2.Ident: - columnNames = append(columnNames, expr.Name) - columnAliases = append(columnAliases, col.Name()) - case *sql2.QualifiedRef: - if tbl := sql2.IdentName(expr.Table); tbl != "" && tbl != source.TableName() { - return nil, fmt.Errorf("no such table: %q", tbl) - } - - if expr.Star.IsValid() { - columnNames = append(columnNames, "_id") - columnAliases = append(columnAliases, "_id") - - for _, field := range idx.Fields() { - if field.Name() == "_exists" { - continue - } - columnNames = append(columnNames, field.Name()) - columnAliases = append(columnAliases, field.Name()) - } - - } else { - columnNames = append(columnNames, sql2.IdentName(expr.Column)) - columnAliases = append(columnAliases, sql2.IdentName(expr.Column)) - } + srcs = append(srcs, sql2.IdentName(expr.Column)) + columns = append(columns, &StmtColumn{ + Name: sql2.IdentName(expr.Column), + Type: sql2.ExprDataType(col.Expr), + }) default: return nil, fmt.Errorf("unsupported column expression: %T", expr) } } - return NewExtractNode(p.executor, indexName, columnNames, columnAliases, cond), nil + return NewExtractNode(p.executor, indexName, srcs, columns, cond), nil } // planExprPQL returns a PQL call tree for a given expression. @@ -322,8 +273,8 @@ func (p *Planner) planBinaryExprPQL(ctx context.Context, stmt *sql2.SelectStatem case sql2.EQ, sql2.NE, sql2.LT, sql2.LE, sql2.GT, sql2.GE: // Ensure field reference exists in binary expression. x, y := expr.X, expr.Y - xIdent, xOk := x.(*sql2.Ident) - yIdent, yOk := y.(*sql2.Ident) + xRef, xOk := x.(*sql2.QualifiedRef) + yRef, yOk := y.(*sql2.QualifiedRef) if xOk && yOk { return nil, fmt.Errorf("cannot compare fields in a WHERE clause") } else if !xOk && !yOk { @@ -332,7 +283,7 @@ func (p *Planner) planBinaryExprPQL(ctx context.Context, stmt *sql2.SelectStatem // Rewrite expression so field ref is LHS. if !xOk && yOk { - xIdent, y = yIdent, x + xRef, y = yRef, x switch op { case sql2.LT: op = sql2.GT @@ -355,7 +306,7 @@ func (p *Planner) planBinaryExprPQL(ctx context.Context, stmt *sql2.SelectStatem return &pql.Call{ Name: "Row", Args: map[string]interface{}{ - sql2.IdentName(xIdent): pqlValue, + sql2.IdentName(xRef.Column): pqlValue, }, }, nil } @@ -367,7 +318,7 @@ func (p *Planner) planBinaryExprPQL(ctx context.Context, stmt *sql2.SelectStatem return &pql.Call{ Name: "Row", Args: map[string]interface{}{ - sql2.IdentName(xIdent): &pql.Condition{ + sql2.IdentName(xRef.Column): &pql.Condition{ Op: pqlOp, Value: pqlValue, }, @@ -426,6 +377,221 @@ func sqlToPQLValue(expr sql2.Expr) (interface{}, error) { } } +func (p *Planner) checkStatement(stmt sql2.Statement) error { + switch stmt := stmt.(type) { + case *sql2.SelectStatement: + return p.checkSelectStatement(stmt) + default: + return nil + } +} + +func (p *Planner) checkSelectStatement(stmt *sql2.SelectStatement) error { + indexName, err := statementTableName(stmt) + if err != nil { + return err + } + + // Look up index. + idx := p.executor.Holder.Index(indexName) + if idx == nil { + return newNotFoundError(ErrIndexNotFound, indexName) + } + + // Replace wildcards with column references. + columns := make([]*sql2.ResultColumn, 0, len(stmt.Columns)) + for _, col := range stmt.Columns { + // Unqualified wildcard. + isWildcard := col.Star.IsValid() + if ref, ok := col.Expr.(*sql2.QualifiedRef); ok && ref.Star.IsValid() { + if ref.Table.Name != indexName { + return fmt.Errorf("no such table: %q", ref.Table.Name) + } + isWildcard = true + } + + // Simply add column as-is if it is not a wildcard. + if !isWildcard { + columns = append(columns, col) + continue + } + + // Add identifier field first. + columns = append(columns, &sql2.ResultColumn{ + Expr: &sql2.QualifiedRef{ + Table: &sql2.Ident{Name: idx.Name()}, + Column: &sql2.Ident{Name: "_id"}, + }, + }) + + // Then add all fields besides the existence bit. + for _, field := range idx.Fields() { + if field.Name() == "_exists" { + continue + } + columns = append(columns, &sql2.ResultColumn{ + Expr: &sql2.QualifiedRef{ + Table: &sql2.Ident{Name: idx.Name()}, + Column: &sql2.Ident{Name: field.Name()}, + }, + }) + } + } + stmt.Columns = columns + + // Type check expressions in statement. + for _, col := range stmt.Columns { + if err := p.checkExpr(&col.Expr, stmt); err != nil { + return err + } + } + + if err := p.checkExpr(&stmt.WhereExpr, stmt); err != nil { + return err + } + + for i := range stmt.GroupByExprs { + if err := p.checkExpr(&stmt.GroupByExprs[i], stmt); err != nil { + return err + } + } + + if err := p.checkExpr(&stmt.HavingExpr, stmt); err != nil { + return err + } + + for _, term := range stmt.OrderingTerms { + if err := p.checkExpr(&term.X, stmt); err != nil { + return err + } + } + + if err := p.checkExpr(&stmt.LimitExpr, stmt); err != nil { + return err + } + + if err := p.checkExpr(&stmt.OffsetExpr, stmt); err != nil { + return err + } + + return nil +} + +func (p *Planner) checkExpr(expr *sql2.Expr, stmt sql2.Statement) error { + if e, err := sql2.Walk(&sqlExprTypeChecker{ + holder: p.executor.Holder, + stmt: stmt, + }, *expr); err != nil { + return err + } else if e != nil { + *expr = e.(sql2.Expr) + } else { + *expr = nil + } + return nil +} + +// sqlExprTypeChecker recursively performs type checking within an expression. +// Called by sqlTypeChecker. Implements sql2.Visitor. +type sqlExprTypeChecker struct { + holder *Holder + stmt sql2.Statement // scope +} + +var _ sql2.Visitor = (*sqlExprTypeChecker)(nil) + +func (v *sqlExprTypeChecker) Visit(node sql2.Node) (_ sql2.Visitor, _ sql2.Node, err error) { + switch n := node.(type) { + case *sql2.Call: + for i := range n.Args { + if err := v.checkExpr(&n.Args[i]); err != nil { + return nil, nil, err + } + } + return nil, node, nil // skip + case *sql2.Ident: + if node, err = v.visitIdent(n); err != nil { + return nil, nil, err + } + return nil, node, nil + case *sql2.QualifiedRef: + if node, err = v.visitQualifiedRef(n); err != nil { + return nil, nil, err + } + return nil, node, nil + default: + return v, node, nil + } +} + +func (v *sqlExprTypeChecker) visitIdent(ident *sql2.Ident) (sql2.Node, error) { + indexName, err := statementTableName(v.stmt) + if err != nil { + return nil, err + } + + // Convert to a table qualified reference and validate through ref visit function. + return v.visitQualifiedRef(&sql2.QualifiedRef{ + Table: &sql2.Ident{Name: indexName}, + Column: &sql2.Ident{Name: ident.Name}, + }) +} + +func (v *sqlExprTypeChecker) visitQualifiedRef(ref *sql2.QualifiedRef) (sql2.Node, error) { + idx := v.holder.Index(ref.Table.Name) + if idx == nil { + return nil, newNotFoundError(ErrIndexNotFound, ref.Table.Name) + } + + switch name := ref.Column.Name; name { + case "_id": + ref.DataType = sql2.DataTypeInt + default: + field := idx.Field(ref.Column.Name) + if field == nil { + return nil, newNotFoundError(ErrFieldNotFound, ref.Column.Name) + } + ref.DataType = fieldSQLDataType(field) + } + + return ref, nil +} + +func (v *sqlExprTypeChecker) checkExpr(node *sql2.Expr) error { + if expr, err := sql2.Walk(&sqlExprTypeChecker{ + holder: v.holder, + stmt: v.stmt, + }, *node); err != nil { + return err + } else if expr != nil { + *node = expr.(sql2.Expr) + } else { + *node = nil + } + return nil +} + +func (v *sqlExprTypeChecker) VisitEnd(node sql2.Node) (sql2.Node, error) { return node, nil } + +func fieldSQLDataType(f *Field) string { + if f.Keys() { + return sql2.DataTypeText + } + + switch f.Type() { + case FieldTypeInt, FieldTypeMutex, FieldTypeSet: + return sql2.DataTypeInt + case FieldTypeBool: + return sql2.DataTypeBool + case FieldTypeDecimal: + return sql2.DataTypeDecimal + case FieldTypeTime, FieldTypeTimestamp: + return sql2.DataTypeTimestamp + default: + return "" + } +} + type Stmt struct { node StmtNode } @@ -473,7 +639,7 @@ func (rs *StmtRows) Err() error { return nil } -func (rs *StmtRows) Columns() []string { +func (rs *StmtRows) Columns() []*StmtColumn { return rs.node.Columns() } @@ -574,6 +740,11 @@ func (r *StmtRow) Err() error { return r.err } +type StmtColumn struct { + Name string + Type string +} + type StmtNode interface { // Initializes the node to its start. First(ctx context.Context) error @@ -585,7 +756,7 @@ type StmtNode interface { Row() []interface{} // Returns column definitions for the node. - Columns() []string + Columns() []*StmtColumn // Returns a reference to the value register for a named column. // Lookup(table, column string) (interface{}, error) @@ -597,39 +768,38 @@ var _ StmtNode = (*ExtractNode)(nil) type ExtractNode struct { executor *executor indexName string - columns []string - aliases []string + srcs []string + columns []*StmtColumn cond *pql.Call result []ExtractedTableColumn row []interface{} } -func NewExtractNode(executor *executor, indexName string, columns, aliases []string, cond *pql.Call) *ExtractNode { +func NewExtractNode(executor *executor, indexName string, srcs []string, columns []*StmtColumn, cond *pql.Call) *ExtractNode { if cond == nil { cond = &pql.Call{Name: "All"} } // Ensure ID column is always the first column. - if len(columns) > 0 && columns[0] != "_id" { - columns = append([]string{"_id"}, columns...) - aliases = append([]string{"_id"}, aliases...) + // TODO(benbjohnson): Don't require id first. + if len(srcs) > 0 && srcs[0] != "_id" { + srcs = append([]string{"_id"}, srcs...) + columns = append([]*StmtColumn{{Name: "_id", Type: sql2.DataTypeInt}}, columns...) } - // TODO: Move "_id" column to the first position if it is specified later on in column list. - return &ExtractNode{ executor: executor, indexName: indexName, - columns: columns, // source column names - aliases: aliases, // external column alias + srcs: srcs, // source column names + columns: columns, // external column alias cond: cond, - row: make([]interface{}, len(columns)), + row: make([]interface{}, len(srcs)), } } -func (n *ExtractNode) Columns() []string { - return n.aliases +func (n *ExtractNode) Columns() []*StmtColumn { + return n.columns } func (n *ExtractNode) First(ctx context.Context) error { @@ -674,11 +844,11 @@ func (n *ExtractNode) init(ctx context.Context) error { // Generate PQL query with all specified rows. // Skip first column as it is the ID column. call := &pql.Call{Name: "Extract", Children: []*pql.Call{n.cond}} - for _, column := range n.columns[1:] { + for _, src := range n.srcs[1:] { call.Children = append(call.Children, &pql.Call{ Name: "Rows", - Args: map[string]interface{}{"field": column}, + Args: map[string]interface{}{"field": src}, }, ) } @@ -709,28 +879,28 @@ var _ StmtNode = (*CountNode)(nil) // CountNode executes a COUNT(*) against a FeatureBase index and returns a single row. type CountNode struct { - executor *executor - indexName string - columnName string - cond *pql.Call // conditional + executor *executor + indexName string + column *StmtColumn + cond *pql.Call // conditional row []interface{} } -func NewCountNode(executor *executor, indexName string, columnName string, cond *pql.Call) *CountNode { +func NewCountNode(executor *executor, indexName string, column *StmtColumn, cond *pql.Call) *CountNode { if cond == nil { cond = &pql.Call{Name: "All"} } return &CountNode{ - executor: executor, - indexName: indexName, - columnName: columnName, - cond: cond, + executor: executor, + indexName: indexName, + column: column, + cond: cond, } } -func (n *CountNode) Columns() []string { - return []string{n.columnName} +func (n *CountNode) Columns() []*StmtColumn { + return []*StmtColumn{n.column} } func (n *CountNode) First(ctx context.Context) error { @@ -764,8 +934,8 @@ func (n *CountNode) Row() []interface{} { return n.row } type GroupByNode struct { executor *executor indexName string - columns []string - aliases []string + srcs []string + columns []*StmtColumn aggregate *pql.Call cond *pql.Call @@ -775,20 +945,20 @@ type GroupByNode struct { row []interface{} } -func NewGroupByNode(executor *executor, indexName string, columns, aliases []string, aggregate, cond *pql.Call) *GroupByNode { +func NewGroupByNode(executor *executor, indexName string, srcs []string, columns []*StmtColumn, aggregate, cond *pql.Call) *GroupByNode { return &GroupByNode{ executor: executor, indexName: indexName, + srcs: srcs, columns: columns, - aliases: aliases, aggregate: aggregate, cond: cond, - row: make([]interface{}, len(columns)+1), + row: make([]interface{}, len(srcs)+1), } } -func (n *GroupByNode) Columns() []string { - return append([]string{"_aggregate"}, n.columns...) +func (n *GroupByNode) Columns() []*StmtColumn { + return n.columns } func (n *GroupByNode) First(ctx context.Context) error { @@ -840,9 +1010,9 @@ func (n *GroupByNode) fetch(ctx context.Context) (*GroupCounts, error) { } // Choose fields to group by. - for _, col := range n.columns { + for _, src := range n.srcs { call.Children = append(call.Children, &pql.Call{ - Name: "Rows", Args: map[string]interface{}{"_field": col}, + Name: "Rows", Args: map[string]interface{}{"_field": src}, }) } @@ -862,3 +1032,30 @@ func (n *GroupByNode) fetch(ctx context.Context) (*GroupCounts, error) { } func (n *GroupByNode) Row() []interface{} { return n.row } + +// statementTableName returns the table name for a single table SELECT statement. +// +// NOTE: This function is only temporary until we support more source types. +func statementTableName(stmt sql2.Statement) (string, error) { + switch stmt := stmt.(type) { + case *sql2.SelectStatement: + return sourceTableName(stmt.Source) + default: + return "", fmt.Errorf("statement not currently supported") + } +} + +func sourceTableName(source sql2.Source) (string, error) { + switch source := source.(type) { + case *sql2.JoinClause: + return "", fmt.Errorf("joins are not currently supported") + case *sql2.ParenSource: + return "", fmt.Errorf("parenthesized source is not currently supported") + case *sql2.QualifiedTableName: + return sql2.IdentName(source.Name), nil + case *sql2.SelectStatement: + return "", fmt.Errorf("sub-selects are not currently supported") + default: + return "", fmt.Errorf("unexpected source type: %T", source) + } +} diff --git a/planner_test.go b/planner_test.go index 4748046b6..e92bd77a6 100644 --- a/planner_test.go +++ b/planner_test.go @@ -210,54 +210,80 @@ func TestPlanner_Select(t *testing.T) { } t.Run("UnqualifiedColumns", func(t *testing.T) { - results := mustQueryRows(t, c.GetNode(0).Server, `SELECT _id, a, b FROM i0`) - if diff := cmp.Diff(results, [][]interface{}{ + results, columns := mustQueryRows(t, c.GetNode(0).Server, `SELECT _id, a, b FROM i0`) + if diff := cmp.Diff([][]interface{}{ {int64(1), int64(10), int64(100)}, {int64(2), int64(20), int64(200)}, - }); diff != "" { + }, results); diff != "" { + t.Fatal(diff) + } + + if diff := cmp.Diff([]*pilosa.StmtColumn{ + {Name: "_id", Type: "INT"}, + {Name: "a", Type: "INT"}, + {Name: "b", Type: "INT"}, + }, columns); diff != "" { t.Fatal(diff) } }) t.Run("QualifiedColumns", func(t *testing.T) { - results := mustQueryRows(t, c.GetNode(0).Server, `SELECT i0._id, i0.a, i0.b FROM i0`) - if diff := cmp.Diff(results, [][]interface{}{ + results, columns := mustQueryRows(t, c.GetNode(0).Server, `SELECT i0._id, i0.a, i0.b FROM i0`) + if diff := cmp.Diff([][]interface{}{ {int64(1), int64(10), int64(100)}, {int64(2), int64(20), int64(200)}, - }); diff != "" { + }, results); diff != "" { + t.Fatal(diff) + } + + if diff := cmp.Diff([]*pilosa.StmtColumn{ + {Name: "_id", Type: "INT"}, + {Name: "a", Type: "INT"}, + {Name: "b", Type: "INT"}, + }, columns); diff != "" { t.Fatal(diff) } }) t.Run("UnqualifiedStar", func(t *testing.T) { - results := mustQueryRows(t, c.GetNode(0).Server, `SELECT * FROM i0`) - if diff := cmp.Diff(results, [][]interface{}{ + results, columns := mustQueryRows(t, c.GetNode(0).Server, `SELECT * FROM i0`) + if diff := cmp.Diff([][]interface{}{ {int64(1), int64(10), int64(100)}, {int64(2), int64(20), int64(200)}, - }); diff != "" { + }, results); diff != "" { + t.Fatal(diff) + } + + if diff := cmp.Diff([]*pilosa.StmtColumn{ + {Name: "_id", Type: "INT"}, + {Name: "a", Type: "INT"}, + {Name: "b", Type: "INT"}, + }, columns); diff != "" { t.Fatal(diff) } }) t.Run("QualifiedStar", func(t *testing.T) { - results := mustQueryRows(t, c.GetNode(0).Server, `SELECT i0.* FROM i0`) - if diff := cmp.Diff(results, [][]interface{}{ + results, columns := mustQueryRows(t, c.GetNode(0).Server, `SELECT i0.* FROM i0`) + if diff := cmp.Diff([][]interface{}{ {int64(1), int64(10), int64(100)}, {int64(2), int64(20), int64(200)}, - }); diff != "" { + }, results); diff != "" { + t.Fatal(diff) + } + + if diff := cmp.Diff([]*pilosa.StmtColumn{ + {Name: "_id", Type: "INT"}, + {Name: "a", Type: "INT"}, + {Name: "b", Type: "INT"}, + }, columns); diff != "" { t.Fatal(diff) } }) t.Run("ErrFieldNotFound", func(t *testing.T) { - stmt, err := c.GetNode(0).Server.PlanSQL(context.Background(), `SELECT xyz FROM i0`) - if err != nil { - t.Fatal(err) - } - defer stmt.Close() - - var xyz interface{} - if err := stmt.QueryRowContext(context.Background()).Scan(&xyz); err == nil || !strings.Contains(err.Error(), `xyz: field not found`) { + _, err := c.GetNode(0).Server.PlanSQL(context.Background(), `SELECT xyz FROM i0`) + if err == nil || !strings.Contains(err.Error(), `xyz: field not found`) { t.Fatalf("unexpected error: %v", err) } }) @@ -301,37 +327,58 @@ func TestPlanner_GroupBy(t *testing.T) { } t.Run("Count", func(t *testing.T) { - results := mustQueryRows(t, c.GetNode(0).Server, `SELECT COUNT(*), x FROM i0 GROUP BY x`) + results, columns := mustQueryRows(t, c.GetNode(0).Server, `SELECT COUNT(*), x FROM i0 GROUP BY x`) if diff := cmp.Diff([][]interface{}{ {int64(2), int64(10)}, {int64(2), int64(20)}, }, results); diff != "" { t.Fatal(diff) } + + if diff := cmp.Diff([]*pilosa.StmtColumn{ + {Name: "count", Type: "INT"}, + {Name: "x", Type: "INT"}, + }, columns); diff != "" { + t.Fatal(diff) + } }) t.Run("DistinctCount", func(t *testing.T) { - results := mustQueryRows(t, c.GetNode(0).Server, `SELECT COUNT(DISTINCT z), x FROM i0 GROUP BY x`) + results, columns := mustQueryRows(t, c.GetNode(0).Server, `SELECT COUNT(DISTINCT z), x FROM i0 GROUP BY x`) if diff := cmp.Diff([][]interface{}{ {int64(1), int64(10)}, {int64(2), int64(20)}, }, results); diff != "" { t.Fatal(diff) } + + if diff := cmp.Diff([]*pilosa.StmtColumn{ + {Name: "count", Type: "INT"}, + {Name: "x", Type: "INT"}, + }, columns); diff != "" { + t.Fatal(diff) + } }) t.Run("Sum", func(t *testing.T) { - results := mustQueryRows(t, c.GetNode(0).Server, `SELECT sum(y), x FROM i0 GROUP BY x`) + results, columns := mustQueryRows(t, c.GetNode(0).Server, `SELECT sum(y), x FROM i0 GROUP BY x`) if diff := cmp.Diff([][]interface{}{ {int64(300), int64(10)}, {int64(100), int64(20)}, }, results); diff != "" { t.Fatal(diff) } + + if diff := cmp.Diff([]*pilosa.StmtColumn{ + {Name: "sum", Type: "INT"}, + {Name: "x", Type: "INT"}, + }, columns); diff != "" { + t.Fatal(diff) + } }) } -func mustQueryRows(tb testing.TB, svr *pilosa.Server, q string) [][]interface{} { +func mustQueryRows(tb testing.TB, svr *pilosa.Server, q string) (results [][]interface{}, columns []*pilosa.StmtColumn) { tb.Helper() stmt, err := svr.PlanSQL(context.Background(), q) @@ -345,7 +392,7 @@ func mustQueryRows(tb testing.TB, svr *pilosa.Server, q string) [][]interface{} tb.Fatal(err) } - results := make([][]interface{}, 0) + results = make([][]interface{}, 0) for rows.Next() { result := make([]interface{}, len(rows.Columns())) @@ -365,5 +412,5 @@ func mustQueryRows(tb testing.TB, svr *pilosa.Server, q string) [][]interface{} tb.Fatal(err) } - return results + return results, rows.Columns() } diff --git a/sql2/ast.go b/sql2/ast.go index ce231781f..929d1f945 100644 --- a/sql2/ast.go +++ b/sql2/ast.go @@ -194,6 +194,26 @@ func StatementSource(stmt Statement) Source { } } +// Data types +const ( + DataTypeBool = "BOOL" + DataTypeDecimal = "DECIMAL" + DataTypeInt = "INT" + DataTypeSet = "SET" + DataTypeText = "TEXT" + DataTypeTimestamp = "TIMESTAMP" +) + +// IsDataTypeValid returns true if typ is a valid data type. +func IsDataTypeValid(typ string) bool { + switch typ { + case DataTypeBool, DataTypeInt, DataTypeDecimal, DataTypeText: + return true + default: + return false + } +} + type Expr interface { Node expr() @@ -279,6 +299,49 @@ func cloneExprs(a []Expr) []Expr { return other } +// ExprDataType returns the data type for an expression. +func ExprDataType(expr Expr) string { + if expr == nil { + return "" + } + + switch expr := expr.(type) { + // Simple type assertions + case *BindExpr, *ExprList, *Ident, *NullLit, *Raise: + return "" + case *BlobLit, *StringLit: + return DataTypeText + case *BoolLit, *Exists, *Range: + return DataTypeBool + case *NumberLit: + return DataTypeInt + + // Complex type assertions + case *BinaryExpr: + return ExprDataType(expr.X) + case *Call: + return DataTypeInt // TODO: May be different for some aggregations + case *CaseExpr: + if len(expr.Blocks) > 0 { + return ExprDataType(expr.Blocks[0].Body) + } else if expr.ElseExpr != nil { + return ExprDataType(expr.ElseExpr) + } + return "" + case *CastExpr: + return "" // TODO: Inspect expr.Type.Name + case *ParenExpr: + return ExprDataType(expr.X) + case *QualifiedRef: + return expr.DataType + case *UnaryExpr: + return ExprDataType(expr.X) + + default: + panic(fmt.Sprintf("invalid expr type: %T", expr)) + } +} + // ExprString returns the string representation of expr. // Returns a blank string if expr is nil. func ExprString(expr Expr) string { @@ -1811,6 +1874,9 @@ type QualifiedRef struct { Dot Pos // position of dot Star Pos // position of * (result column only) Column *Ident // column name + + // Set by the planner; not at parse-time + DataType string } // IsAggregate returns false. @@ -3061,10 +3127,12 @@ func (c *ResultColumn) Name() string { } switch expr := c.Expr.(type) { + case *Call: + return strings.ToLower(IdentName(expr.Name)) case *Ident: return IdentName(expr) case *QualifiedRef: - return expr.String() + return IdentName(expr.Column) default: return "" } diff --git a/sql2/ast_test.go b/sql2/ast_test.go index 8d3c1fed5..25cf95ac9 100644 --- a/sql2/ast_test.go +++ b/sql2/ast_test.go @@ -1143,14 +1143,14 @@ func AssertNodeStringerPanic(tb testing.TB, node sql.Node, msg string) { func StripPos(root sql.Node) sql.Node { zero := reflect.ValueOf(sql.Pos{}) - _ = sql.Walk(sql.VisitFunc(func(node sql.Node) error { + _, _ = sql.Walk(sql.VisitFunc(func(node sql.Node) (sql.Node, error) { value := reflect.Indirect(reflect.ValueOf(node)) for i := 0; i < value.NumField(); i++ { if field := value.Field(i); field.Type() == zero.Type() { field.Set(zero) } } - return nil + return node, nil }), root) return root } diff --git a/sql2/walk.go b/sql2/walk.go index e8592a7b6..c04ca76ba 100644 --- a/sql2/walk.go +++ b/sql2/walk.go @@ -18,8 +18,8 @@ package sql2 // If the result visitor w is not nil, Walk visits each of the children // of node with the visitor w, followed by a call of w.Visit(nil). type Visitor interface { - Visit(node Node) (w Visitor, err error) - VisitEnd(node Node) error + Visit(node Node) (w Visitor, n Node, err error) + VisitEnd(node Node) (Node, error) } // Walk traverses an AST in depth-first order: It starts by calling @@ -27,606 +27,774 @@ type Visitor interface { // v.Visit(node) is not nil, Walk is invoked recursively with visitor // w for each of the non-nil children of node, followed by a call of // w.Visit(nil). -func Walk(v Visitor, node Node) error { +func Walk(v Visitor, node Node) (Node, error) { return walk(v, node) } -func walk(v Visitor, node Node) (err error) { +func walk(v Visitor, node Node) (_ Node, err error) { // Visit the node itself - if v, err = v.Visit(node); err != nil { - return err + if v, node, err = v.Visit(node); err != nil { + return node, err } else if v == nil { - return nil + return node, nil } // Visit node's children. switch n := node.(type) { case *Assignment: if err := walkIdentList(v, n.Columns); err != nil { - return err + return node, err } - if err := walkExpr(v, n.Expr); err != nil { - return err + if err := walkExpr(v, &n.Expr); err != nil { + return node, err } case *ExplainStatement: if n.Stmt != nil { - if err := walk(v, n.Stmt); err != nil { - return err + if stmt, err := walk(v, n.Stmt); err != nil { + return node, err + } else if stmt == nil { + n.Stmt = nil + } else { + n.Stmt = stmt.(Statement) } } case *RollbackStatement: - if err := walkIdent(v, n.SavepointName); err != nil { - return err + if err := walkIdent(v, &n.SavepointName); err != nil { + return node, err } case *SavepointStatement: - if err := walkIdent(v, n.Name); err != nil { - return err + if err := walkIdent(v, &n.Name); err != nil { + return node, err } case *ReleaseStatement: - if err := walkIdent(v, n.Name); err != nil { - return err + if err := walkIdent(v, &n.Name); err != nil { + return node, err } case *CreateTableStatement: - if err := walkIdent(v, n.Name); err != nil { - return err + if err := walkIdent(v, &n.Name); err != nil { + return node, err } if err := walkColumnDefinitionList(v, n.Columns); err != nil { - return err + return node, err } if err := walkConstraintList(v, n.Constraints); err != nil { - return err + return node, err } if n.Select != nil { - if err := walk(v, n.Select); err != nil { - return err + if sel, err := walk(v, n.Select); err != nil { + return node, err + } else if sel != nil { + n.Select = sel.(*SelectStatement) + } else { + n.Select = nil } } case *AlterTableStatement: - if err := walkIdent(v, n.Name); err != nil { - return err + if err := walkIdent(v, &n.Name); err != nil { + return node, err } - if err := walkIdent(v, n.NewName); err != nil { - return err + if err := walkIdent(v, &n.NewName); err != nil { + return node, err } - if err := walkIdent(v, n.ColumnName); err != nil { - return err + if err := walkIdent(v, &n.ColumnName); err != nil { + return node, err } - if err := walkIdent(v, n.NewColumnName); err != nil { - return err + if err := walkIdent(v, &n.NewColumnName); err != nil { + return node, err } if n.ColumnDef != nil { - if err := walk(v, n.ColumnDef); err != nil { - return err + if def, err := walk(v, n.ColumnDef); err != nil { + return node, err + } else if def != nil { + n.ColumnDef = def.(*ColumnDefinition) + } else { + n.ColumnDef = nil } } case *AnalyzeStatement: - if err := walkIdent(v, n.Name); err != nil { - return err + if err := walkIdent(v, &n.Name); err != nil { + return node, err } case *CreateViewStatement: - if err := walkIdent(v, n.Name); err != nil { - return err + if err := walkIdent(v, &n.Name); err != nil { + return node, err } if err := walkIdentList(v, n.Columns); err != nil { - return err + return node, err } if n.Select != nil { - if err := walk(v, n.Select); err != nil { - return err + if sel, err := walk(v, n.Select); err != nil { + return node, err + } else if sel != nil { + n.Select = sel.(*SelectStatement) + } else { + n.Select = nil } } case *DropTableStatement: - if err := walkIdent(v, n.Name); err != nil { - return err + if err := walkIdent(v, &n.Name); err != nil { + return node, err } case *DropViewStatement: - if err := walkIdent(v, n.Name); err != nil { - return err + if err := walkIdent(v, &n.Name); err != nil { + return node, err } case *DropIndexStatement: - if err := walkIdent(v, n.Name); err != nil { - return err + if err := walkIdent(v, &n.Name); err != nil { + return node, err } case *DropTriggerStatement: - if err := walkIdent(v, n.Name); err != nil { - return err + if err := walkIdent(v, &n.Name); err != nil { + return node, err } case *CreateIndexStatement: - if err := walkIdent(v, n.Name); err != nil { - return err + if err := walkIdent(v, &n.Name); err != nil { + return node, err } - if err := walkIdent(v, n.Table); err != nil { - return err + if err := walkIdent(v, &n.Table); err != nil { + return node, err } if err := walkIndexedColumnList(v, n.Columns); err != nil { - return err + return node, err } - if err := walkExpr(v, n.WhereExpr); err != nil { - return err + if err := walkExpr(v, &n.WhereExpr); err != nil { + return node, err } case *CreateTriggerStatement: - if err := walkIdent(v, n.Name); err != nil { - return err + if err := walkIdent(v, &n.Name); err != nil { + return node, err } if err := walkIdentList(v, n.UpdateOfColumns); err != nil { - return err + return node, err } - if err := walkIdent(v, n.Table); err != nil { - return err + if err := walkIdent(v, &n.Table); err != nil { + return node, err } - if err := walkExpr(v, n.WhenExpr); err != nil { - return err + if err := walkExpr(v, &n.WhenExpr); err != nil { + return node, err } - for _, x := range n.Body { - if err := walk(v, x); err != nil { - return err + for i := range n.Body { + if body, err := walk(v, n.Body[i]); err != nil { + return node, err + } else if body != nil { + n.Body[i] = body.(Statement) + } else { + n.Body[i] = nil } } case *SelectStatement: if n.WithClause != nil { - if err := walk(v, n.WithClause); err != nil { - return err + if clause, err := walk(v, n.WithClause); err != nil { + return node, err + } else if clause != nil { + n.WithClause = clause.(*WithClause) + } else { + n.WithClause = nil } } - for _, x := range n.ValueLists { - if err := walk(v, x); err != nil { - return err + for i := range n.ValueLists { + if list, err := walk(v, n.ValueLists[i]); err != nil { + return node, err + } else if list != nil { + n.ValueLists[i] = list.(*ExprList) + } else { + n.ValueLists[i] = nil } } - for _, x := range n.Columns { - if err := walk(v, x); err != nil { - return err + for i := range n.Columns { + if col, err := walk(v, n.Columns[i]); err != nil { + return node, err + } else if col != nil { + n.Columns[i] = col.(*ResultColumn) + } else { + n.Columns[i] = nil } } if n.Source != nil { - if err := walk(v, n.Source); err != nil { - return err + if src, err := walk(v, n.Source); err != nil { + return node, err + } else if src != nil { + n.Source = n.Source.(Source) + } else { + n.Source = nil } } - if err := walkExpr(v, n.WhereExpr); err != nil { - return err + if err := walkExpr(v, &n.WhereExpr); err != nil { + return node, err } if err := walkExprList(v, n.GroupByExprs); err != nil { - return err + return node, err } - if err := walkExpr(v, n.HavingExpr); err != nil { - return err + if err := walkExpr(v, &n.HavingExpr); err != nil { + return node, err } - for _, x := range n.Windows { - if err := walk(v, x); err != nil { - return err + for i := range n.Windows { + if w, err := walk(v, n.Windows[i]); err != nil { + return node, err + } else if w != nil { + n.Windows[i] = w.(*Window) + } else { + n.Windows[i] = nil } } if n.Compound != nil { - if err := walk(v, n.Compound); err != nil { - return err + if stmt, err := walk(v, n.Compound); err != nil { + return node, err + } else if stmt != nil { + n.Compound = stmt.(*SelectStatement) + } else { + n.Compound = nil } } - for _, x := range n.OrderingTerms { - if err := walk(v, x); err != nil { - return err + for i := range n.OrderingTerms { + if term, err := walk(v, n.OrderingTerms[i]); err != nil { + return node, err + } else if term != nil { + n.OrderingTerms[i] = term.(*OrderingTerm) + } else { + n.OrderingTerms[i] = nil } } - if err := walkExpr(v, n.LimitExpr); err != nil { - return err + if err := walkExpr(v, &n.LimitExpr); err != nil { + return node, err } - if err := walkExpr(v, n.OffsetExpr); err != nil { - return err + if err := walkExpr(v, &n.OffsetExpr); err != nil { + return node, err } case *InsertStatement: if n.WithClause != nil { - if err := walk(v, n.WithClause); err != nil { - return err + if clause, err := walk(v, n.WithClause); err != nil { + return node, err + } else if clause != nil { + n.WithClause = clause.(*WithClause) + } else { + n.WithClause = nil } } - if err := walkIdent(v, n.Table); err != nil { - return err + if err := walkIdent(v, &n.Table); err != nil { + return node, err } - if err := walkIdent(v, n.Alias); err != nil { - return err + if err := walkIdent(v, &n.Alias); err != nil { + return node, err } if err := walkIdentList(v, n.Columns); err != nil { - return err + return node, err } - for _, x := range n.ValueLists { - if err := walk(v, x); err != nil { - return err + for i := range n.ValueLists { + if list, err := walk(v, n.ValueLists[i]); err != nil { + return node, err + } else if list != nil { + n.ValueLists[i] = list.(*ExprList) + } else { + n.ValueLists[i] = nil } } if n.Select != nil { - if err := walk(v, n.Select); err != nil { - return err + if sel, err := walk(v, n.Select); err != nil { + return node, err + } else if sel != nil { + n.Select = sel.(*SelectStatement) + } else { + n.Select = nil } } if n.UpsertClause != nil { - if err := walk(v, n.UpsertClause); err != nil { - return err + if clause, err := walk(v, n.UpsertClause); err != nil { + return node, err + } else if clause != nil { + n.UpsertClause = clause.(*UpsertClause) + } else { + n.UpsertClause = nil } } case *UpdateStatement: if n.WithClause != nil { - if err := walk(v, n.WithClause); err != nil { - return err + if clause, err := walk(v, n.WithClause); err != nil { + return node, err + } else if clause != nil { + n.WithClause = clause.(*WithClause) + } else { + n.WithClause = nil } } if n.Table != nil { - if err := walk(v, n.Table); err != nil { - return err + if tbl, err := walk(v, n.Table); err != nil { + return node, err + } else if tbl != nil { + n.Table = tbl.(*QualifiedTableName) + } else { + n.Table = nil } } - for _, x := range n.Assignments { - if err := walk(v, x); err != nil { - return err + for i := range n.Assignments { + if assign, err := walk(v, n.Assignments[i]); err != nil { + return node, err + } else if assign != nil { + n.Assignments[i] = assign.(*Assignment) + } else { + n.Assignments[i] = nil } } - if err := walkExpr(v, n.WhereExpr); err != nil { - return err + if err := walkExpr(v, &n.WhereExpr); err != nil { + return node, err } case *UpsertClause: if err := walkIndexedColumnList(v, n.Columns); err != nil { - return err + return node, err } - if err := walkExpr(v, n.WhereExpr); err != nil { - return err + if err := walkExpr(v, &n.WhereExpr); err != nil { + return node, err } - for _, x := range n.Assignments { - if err := walk(v, x); err != nil { - return err + for i := range n.Assignments { + if assign, err := walk(v, n.Assignments[i]); err != nil { + return node, err + } else if assign != nil { + n.Assignments[i] = assign.(*Assignment) + } else { + n.Assignments[i] = nil } } - if err := walkExpr(v, n.UpdateWhereExpr); err != nil { - return err + if err := walkExpr(v, &n.UpdateWhereExpr); err != nil { + return node, err } case *DeleteStatement: if n.WithClause != nil { - if err := walk(v, n.WithClause); err != nil { - return err + if clause, err := walk(v, n.WithClause); err != nil { + return node, err + } else if clause != nil { + n.WithClause = clause.(*WithClause) + } else { + n.WithClause = nil } } if n.Table != nil { - if err := walk(v, n.Table); err != nil { - return err + if tbl, err := walk(v, n.Table); err != nil { + return node, err + } else if tbl != nil { + n.Table = tbl.(*QualifiedTableName) + } else { + n.Table = nil } } - if err := walkExpr(v, n.WhereExpr); err != nil { - return err + if err := walkExpr(v, &n.WhereExpr); err != nil { + return node, err } - for _, x := range n.OrderingTerms { - if err := walk(v, x); err != nil { - return err + for i := range n.OrderingTerms { + if term, err := walk(v, n.OrderingTerms[i]); err != nil { + return node, err + } else if term != nil { + n.OrderingTerms[i] = term.(*OrderingTerm) + } else { + n.OrderingTerms[i] = nil } } - if err := walkExpr(v, n.LimitExpr); err != nil { - return err + if err := walkExpr(v, &n.LimitExpr); err != nil { + return node, err } - if err := walkExpr(v, n.OffsetExpr); err != nil { - return err + if err := walkExpr(v, &n.OffsetExpr); err != nil { + return node, err } case *PrimaryKeyConstraint: - if err := walkIdent(v, n.Name); err != nil { - return err + if err := walkIdent(v, &n.Name); err != nil { + return node, err } if err := walkIdentList(v, n.Columns); err != nil { - return err + return node, err } case *NotNullConstraint: - if err := walkIdent(v, n.Name); err != nil { - return err + if err := walkIdent(v, &n.Name); err != nil { + return node, err } case *UniqueConstraint: - if err := walkIdent(v, n.Name); err != nil { - return err + if err := walkIdent(v, &n.Name); err != nil { + return node, err } if err := walkIdentList(v, n.Columns); err != nil { - return err + return node, err } case *CheckConstraint: - if err := walkIdent(v, n.Name); err != nil { - return err + if err := walkIdent(v, &n.Name); err != nil { + return node, err } - if err := walkExpr(v, n.Expr); err != nil { - return err + if err := walkExpr(v, &n.Expr); err != nil { + return node, err } case *DefaultConstraint: - if err := walkIdent(v, n.Name); err != nil { - return err + if err := walkIdent(v, &n.Name); err != nil { + return node, err } - if err := walkExpr(v, n.Expr); err != nil { - return err + if err := walkExpr(v, &n.Expr); err != nil { + return node, err } case *ForeignKeyConstraint: - if err := walkIdent(v, n.Name); err != nil { - return err + if err := walkIdent(v, &n.Name); err != nil { + return node, err } if err := walkIdentList(v, n.Columns); err != nil { - return err + return node, err } - if err := walkIdent(v, n.ForeignTable); err != nil { - return err + if err := walkIdent(v, &n.ForeignTable); err != nil { + return node, err } if err := walkIdentList(v, n.ForeignColumns); err != nil { - return err + return node, err } - for _, x := range n.Args { - if err := walk(v, x); err != nil { - return err + for i := range n.Args { + if arg, err := walk(v, n.Args[i]); err != nil { + return node, err + } else if arg != nil { + n.Args[i] = arg.(*ForeignKeyArg) + } else { + n.Args[i] = nil } } case *ParenExpr: - if err := walkExpr(v, n.X); err != nil { - return err + if err := walkExpr(v, &n.X); err != nil { + return node, err } case *UnaryExpr: - if err := walkExpr(v, n.X); err != nil { - return err + if err := walkExpr(v, &n.X); err != nil { + return node, err } case *BinaryExpr: - if err := walkExpr(v, n.X); err != nil { - return err + if err := walkExpr(v, &n.X); err != nil { + return node, err } - if err := walkExpr(v, n.Y); err != nil { - return err + if err := walkExpr(v, &n.Y); err != nil { + return node, err } case *CastExpr: - if err := walkExpr(v, n.X); err != nil { - return err + if err := walkExpr(v, &n.X); err != nil { + return node, err } if n.Type != nil { - if err := walk(v, n.Type); err != nil { - return err + if typ, err := walk(v, n.Type); err != nil { + return node, err + } else if typ != nil { + n.Type = typ.(*Type) + } else { + n.Type = nil } } case *CaseBlock: - if err := walkExpr(v, n.Condition); err != nil { - return err + if err := walkExpr(v, &n.Condition); err != nil { + return node, err } - if err := walkExpr(v, n.Body); err != nil { - return err + if err := walkExpr(v, &n.Body); err != nil { + return node, err } case *CaseExpr: - if err := walkExpr(v, n.Operand); err != nil { - return err + if err := walkExpr(v, &n.Operand); err != nil { + return node, err } - for _, x := range n.Blocks { - if err := walk(v, x); err != nil { - return err + for i := range n.Blocks { + if blk, err := walk(v, n.Blocks[i]); err != nil { + return node, err + } else if blk != nil { + n.Blocks[i] = blk.(*CaseBlock) + } else { + n.Blocks[i] = nil } } - if err := walkExpr(v, n.ElseExpr); err != nil { - return err + if err := walkExpr(v, &n.ElseExpr); err != nil { + return node, err } case *ExprList: if err := walkExprList(v, n.Exprs); err != nil { - return err + return node, err } case *QualifiedRef: - if err := walkIdent(v, n.Table); err != nil { - return err + if err := walkIdent(v, &n.Table); err != nil { + return node, err } - if err := walkIdent(v, n.Column); err != nil { - return err + if err := walkIdent(v, &n.Column); err != nil { + return node, err } case *Call: - if err := walkIdent(v, n.Name); err != nil { - return err + if err := walkIdent(v, &n.Name); err != nil { + return node, err } if err := walkExprList(v, n.Args); err != nil { - return err + return node, err } if n.Filter != nil { - if err := walk(v, n.Filter); err != nil { - return err + if filter, err := walk(v, n.Filter); err != nil { + return node, err + } else if filter != nil { + n.Filter = filter.(*FilterClause) + } else { + n.Filter = nil } } if n.Over != nil { - if err := walk(v, n.Over); err != nil { - return err + if over, err := walk(v, n.Over); err != nil { + return node, err + } else if over != nil { + n.Over = over.(*OverClause) + } else { + n.Over = nil } } case *FilterClause: - if err := walkExpr(v, n.X); err != nil { - return err + if err := walkExpr(v, &n.X); err != nil { + return node, err } case *OverClause: - if err := walkIdent(v, n.Name); err != nil { - return err + if err := walkIdent(v, &n.Name); err != nil { + return node, err } if n.Definition != nil { - if err := walk(v, n.Definition); err != nil { - return err + if def, err := walk(v, n.Definition); err != nil { + return node, err + } else if def != nil { + n.Definition = def.(*WindowDefinition) + } else { + n.Definition = nil } } case *OrderingTerm: - if err := walkExpr(v, n.X); err != nil { - return err + if err := walkExpr(v, &n.X); err != nil { + return node, err } case *FrameSpec: - if err := walkExpr(v, n.X); err != nil { - return err + if err := walkExpr(v, &n.X); err != nil { + return node, err } - if err := walkExpr(v, n.Y); err != nil { - return err + if err := walkExpr(v, &n.Y); err != nil { + return node, err } case *Range: - if err := walkExpr(v, n.X); err != nil { - return err + if err := walkExpr(v, &n.X); err != nil { + return node, err } - if err := walkExpr(v, n.Y); err != nil { - return err + if err := walkExpr(v, &n.Y); err != nil { + return node, err } case *Raise: if n.Error != nil { - if err := walk(v, n.Error); err != nil { - return err + if e, err := walk(v, n.Error); err != nil { + return node, err + } else if e != nil { + n.Error = e.(*StringLit) + } else { + n.Error = nil } } case *Exists: if n.Select != nil { - if err := walk(v, n.Select); err != nil { - return err + if sel, err := walk(v, n.Select); err != nil { + return node, err + } else if sel != nil { + n.Select = sel.(*SelectStatement) + } else { + n.Select = nil } } case *ParenSource: if n.X != nil { - if err := walk(v, n.X); err != nil { - return err + if x, err := walk(v, n.X); err != nil { + return node, err + } else if x != nil { + n.X = x.(Source) + } else { + n.X = nil } } - if err := walkIdent(v, n.Alias); err != nil { - return err + if err := walkIdent(v, &n.Alias); err != nil { + return node, err } case *QualifiedTableName: - if err := walkIdent(v, n.Name); err != nil { - return err + if err := walkIdent(v, &n.Name); err != nil { + return node, err } - if err := walkIdent(v, n.Alias); err != nil { - return err + if err := walkIdent(v, &n.Alias); err != nil { + return node, err } - if err := walkIdent(v, n.Index); err != nil { - return err + if err := walkIdent(v, &n.Index); err != nil { + return node, err } case *JoinClause: if n.X != nil { - if err := walk(v, n.X); err != nil { - return err + if x, err := walk(v, n.X); err != nil { + return node, err + } else if x != nil { + n.X = x.(Source) + } else { + n.X = nil } } if n.Operator != nil { - if err := walk(v, n.Operator); err != nil { - return err + if op, err := walk(v, n.Operator); err != nil { + return node, err + } else if op != nil { + n.Operator = op.(*JoinOperator) + } else { + n.Operator = nil } } if n.Y != nil { - if err := walk(v, n.Y); err != nil { - return err + if y, err := walk(v, n.Y); err != nil { + return node, err + } else if y != nil { + n.Y = y.(Source) + } else { + n.Y = nil } } if n.Constraint != nil { - if err := walk(v, n.Constraint); err != nil { - return err + if cons, err := walk(v, n.Constraint); err != nil { + return node, err + } else if cons != nil { + n.Constraint = cons.(JoinConstraint) + } else { + n.Constraint = nil } } case *OnConstraint: - if err := walkExpr(v, n.X); err != nil { - return err + if err := walkExpr(v, &n.X); err != nil { + return node, err } case *UsingConstraint: if err := walkIdentList(v, n.Columns); err != nil { - return err + return node, err } case *ColumnDefinition: - if err := walkIdent(v, n.Name); err != nil { - return err + if err := walkIdent(v, &n.Name); err != nil { + return node, err } if n.Type != nil { - if err := walk(v, n.Type); err != nil { - return err + if typ, err := walk(v, n.Type); err != nil { + return node, err + } else if typ != nil { + n.Type = typ.(*Type) + } else { + n.Type = nil } } if err := walkConstraintList(v, n.Constraints); err != nil { - return err + return node, err } case *ResultColumn: - if err := walkExpr(v, n.Expr); err != nil { - return err + if err := walkExpr(v, &n.Expr); err != nil { + return node, err } - if err := walkIdent(v, n.Alias); err != nil { - return err + if err := walkIdent(v, &n.Alias); err != nil { + return node, err } case *IndexedColumn: - if err := walkExpr(v, n.X); err != nil { - return err + if err := walkExpr(v, &n.X); err != nil { + return node, err } case *Window: - if err := walkIdent(v, n.Name); err != nil { - return err + if err := walkIdent(v, &n.Name); err != nil { + return node, err } if n.Definition != nil { - if err := walk(v, n.Definition); err != nil { - return err + if def, err := walk(v, n.Definition); err != nil { + return node, err + } else if def != nil { + n.Definition = def.(*WindowDefinition) + } else { + n.Definition = nil } } case *WindowDefinition: - if err := walkIdent(v, n.Base); err != nil { - return err + if err := walkIdent(v, &n.Base); err != nil { + return node, err } if err := walkExprList(v, n.Partitions); err != nil { - return err + return node, err } - for _, x := range n.OrderingTerms { - if err := walk(v, x); err != nil { - return err + for i := range n.OrderingTerms { + if term, err := walk(v, n.OrderingTerms[i]); err != nil { + return node, err + } else if term != nil { + n.OrderingTerms[i] = term.(*OrderingTerm) + } else { + n.OrderingTerms[i] = nil } } if n.Frame != nil { - if err := walk(v, n.Frame); err != nil { - return err + if frame, err := walk(v, n.Frame); err != nil { + return node, err + } else if frame != nil { + n.Frame = frame.(*FrameSpec) + } else { + n.Frame = nil } } case *Type: - if err := walkIdent(v, n.Name); err != nil { - return err + if err := walkIdent(v, &n.Name); err != nil { + return node, err } if n.Precision != nil { - if err := walk(v, n.Precision); err != nil { - return err + if p, err := walk(v, n.Precision); err != nil { + return node, err + } else if p != nil { + n.Precision = p.(*NumberLit) + } else { + n.Precision = nil } } if n.Scale != nil { - if err := walk(v, n.Scale); err != nil { - return err + if scale, err := walk(v, n.Scale); err != nil { + return node, err + } else if scale != nil { + n.Scale = scale.(*NumberLit) + } else { + n.Scale = nil } } } @@ -637,59 +805,72 @@ func walk(v Visitor, node Node) (err error) { // VisitFunc represents a function type that implements Visitor. // Only executes on node entry. -type VisitFunc func(Node) error +type VisitFunc func(Node) (Node, error) // Visit executes fn. Walk visits node children if fn returns true. -func (fn VisitFunc) Visit(node Node) (Visitor, error) { - if err := fn(node); err != nil { - return nil, err +func (fn VisitFunc) Visit(node Node) (Visitor, Node, error) { + node, err := fn(node) + if err != nil { + return nil, nil, err } - return fn, nil + return fn, node, nil } // VisitEnd is a no-op. -func (fn VisitFunc) VisitEnd(node Node) error { return nil } +func (fn VisitFunc) VisitEnd(node Node) (Node, error) { return node, nil } // VisitEndFunc represents a function type that implements Visitor. // Only executes on node exit. -type VisitEndFunc func(Node) error +type VisitEndFunc func(Node) (Node, error) // Visit is a no-op. -func (fn VisitEndFunc) Visit(node Node) (Visitor, error) { return fn, nil } +func (fn VisitEndFunc) Visit(node Node) (Visitor, Node, error) { return fn, node, nil } // VisitEnd executes fn. -func (fn VisitEndFunc) VisitEnd(node Node) error { return fn(node) } +func (fn VisitEndFunc) VisitEnd(node Node) (Node, error) { return fn(node) } -func walkIdent(v Visitor, x *Ident) error { - if x != nil { - if err := walk(v, x); err != nil { - return err - } +func walkIdent(v Visitor, x **Ident) error { + if *x == nil { + return nil + } + + ident, err := walk(v, *x) + if err != nil { + return err + } else if ident != nil { + *x = ident.(*Ident) + } else { + *x = nil } return nil } func walkIdentList(v Visitor, a []*Ident) error { - for _, x := range a { - if err := walk(v, x); err != nil { + for i := range a { + if err := walkIdent(v, &a[i]); err != nil { return err } } return nil } -func walkExpr(v Visitor, x Expr) error { - if x != nil { - if err := walk(v, x); err != nil { - return err - } +func walkExpr(v Visitor, x *Expr) error { + if *x == nil { + return nil + } + if other, err := walk(v, *x); err != nil { + return err + } else if other != nil { + *x = other.(Expr) + } else { + *x = nil } return nil } func walkExprList(v Visitor, a []Expr) error { - for _, x := range a { - if err := walk(v, x); err != nil { + for i := range a { + if err := walkExpr(v, &a[i]); err != nil { return err } } @@ -697,27 +878,39 @@ func walkExprList(v Visitor, a []Expr) error { } func walkConstraintList(v Visitor, a []Constraint) error { - for _, x := range a { - if err := walk(v, x); err != nil { + for i := range a { + if cons, err := walk(v, a[i]); err != nil { return err + } else if cons != nil { + a[i] = cons.(Constraint) + } else { + a[i] = nil } } return nil } func walkIndexedColumnList(v Visitor, a []*IndexedColumn) error { - for _, x := range a { - if err := walk(v, x); err != nil { + for i := range a { + if col, err := walk(v, a[i]); err != nil { return err + } else if col != nil { + a[i] = col.(*IndexedColumn) + } else { + a[i] = nil } } return nil } func walkColumnDefinitionList(v Visitor, a []*ColumnDefinition) error { - for _, x := range a { - if err := walk(v, x); err != nil { + for i := range a { + if def, err := walk(v, a[i]); err != nil { return err + } else if def != nil { + a[i] = def.(*ColumnDefinition) + } else { + a[i] = nil } } return nil