diff --git a/data/test/tabletserver/exec_cases.txt b/data/test/tabletserver/exec_cases.txt index 85a06cb4e0f..a0df3ba6f45 100644 --- a/data/test/tabletserver/exec_cases.txt +++ b/data/test/tabletserver/exec_cases.txt @@ -21,7 +21,7 @@ { "PlanID": "PASS_SELECT", "TableName": "a", - "FieldQuery": "select * from a where 1 != 1", + "FieldQuery": "select * from a where 1 != 1 group by b", "FullQuery": "select * from a group by b limit :#maxLimit" } diff --git a/data/test/vtgate/postprocess_cases.txt b/data/test/vtgate/postprocess_cases.txt index 2a547705f36..8b11127485b 100644 --- a/data/test/vtgate/postprocess_cases.txt +++ b/data/test/vtgate/postprocess_cases.txt @@ -1,3 +1,17 @@ +# Group by with aggregation +"select col1, count(col2) as col2_count from unsharded group by col1 order by col2_count asc" +{ + "Original": "select col1, count(col2) as col2_count from unsharded group by col1 order by col2_count asc", + "Instructions": { + "Opcode": "SelectUnsharded", + "Keyspace": { + "Name": "main", + "Sharded": false + }, + "Query": "select col1, count(col2) as col2_count from unsharded group by col1 order by col2_count asc", + "FieldQuery": "select col1, count(col2) as col2_count from unsharded where 1 != 1 group by col1" + } +} # Group by unsharded "select col1, col2 from unsharded group by col2" { @@ -9,7 +23,7 @@ "Sharded": false }, "Query": "select col1, col2 from unsharded group by col2", - "FieldQuery": "select col1, col2 from unsharded where 1 != 1" + "FieldQuery": "select col1, col2 from unsharded where 1 != 1 group by col2" } } @@ -24,7 +38,7 @@ "Sharded": true }, "Query": "select col1, col2 from user where id = 1 group by col2", - "FieldQuery": "select col1, col2 from user where 1 != 1", + "FieldQuery": "select col1, col2 from user where 1 != 1 group by col2", "Vindex": "user_index", "Values": 1 } @@ -41,7 +55,7 @@ "Sharded": true }, "Query": "select col1, id from user group by id", - "FieldQuery": "select col1, id from user where 1 != 1" + "FieldQuery": "select col1, id from user where 1 != 1 group by id" } } @@ -56,7 +70,7 @@ "Sharded": true }, "Query": "select id from user group by id, col", - "FieldQuery": "select id from user where 1 != 1" + "FieldQuery": "select id from user where 1 != 1 group by id, col" } } diff --git a/go/vt/sqlparser/impossible_query.go b/go/vt/sqlparser/impossible_query.go new file mode 100644 index 00000000000..f91952a7e7a --- /dev/null +++ b/go/vt/sqlparser/impossible_query.go @@ -0,0 +1,28 @@ +package sqlparser + +// FormatImpossibleQuery creates an impossible query in a TrackedBuffer. +// An impossible query is a modified version of a query where all selects have where clauses that are +// impossible for mysql to resolve. This is used in the vtgate and vttablet: +// +// - In the vtgate it's used for joins: if the first query returns no result, then vtgate uses the impossible +// query just to fetch field info from vttablet +// - In the vttablet, it's just an optimization: the field info is fetched once form MySQL, cached and reused +// for subsequent queries +func FormatImpossibleQuery(buf *TrackedBuffer, node SQLNode) { + switch node := node.(type) { + case *Select: + buf.Myprintf("select %v from %v where 1 != 1", node.SelectExprs, node.From) + if node.GroupBy != nil { + node.GroupBy.Format(buf) + } + case *JoinTableExpr: + if node.Join == LeftJoinStr || node.Join == RightJoinStr { + // ON clause is requried + buf.Myprintf("%v %s %v on 1 != 1", node.LeftExpr, node.Join, node.RightExpr) + } else { + buf.Myprintf("%v %s %v", node.LeftExpr, node.Join, node.RightExpr) + } + default: + node.Format(buf) + } +} diff --git a/go/vt/sqlparser/tracked_buffer.go b/go/vt/sqlparser/tracked_buffer.go index dc5a63f6443..db8d494f6ff 100644 --- a/go/vt/sqlparser/tracked_buffer.go +++ b/go/vt/sqlparser/tracked_buffer.go @@ -30,6 +30,13 @@ func NewTrackedBuffer(nodeFormatter func(buf *TrackedBuffer, node SQLNode)) *Tra } } +// Convenience function, initiates the writing of a single SQLNode tree by passing +// through to Myprintf with a default format string +func (buf *TrackedBuffer) WriteNode(node SQLNode) *TrackedBuffer { + buf.Myprintf("%v", node) + return buf +} + // Myprintf mimics fmt.Fprintf(buf, ...), but limited to Node(%v), // Node.Value(%s) and string(%s). It also allows a %a for a value argument, in // which case it adds tracking info for future substitutions. diff --git a/go/vt/tabletserver/endtoend/queries_test.go b/go/vt/tabletserver/endtoend/queries_test.go index f1a7c3a2069..39f7b2a8c43 100644 --- a/go/vt/tabletserver/endtoend/queries_test.go +++ b/go/vt/tabletserver/endtoend/queries_test.go @@ -93,7 +93,7 @@ func TestNocacheCases(t *testing.T) { {"1", "3"}, }, Rewritten: []string{ - "select eid, sum(id) from vitess_a where 1 != 1", + "select eid, sum(id) from vitess_a where 1 != 1 group by eid", "select /* group by */ eid, sum(id) from vitess_a group by eid limit 10001", }, RowsAffected: 1, diff --git a/go/vt/tabletserver/planbuilder/query_gen.go b/go/vt/tabletserver/planbuilder/query_gen.go index 3f7b50a97db..a3a36cb05c7 100644 --- a/go/vt/tabletserver/planbuilder/query_gen.go +++ b/go/vt/tabletserver/planbuilder/query_gen.go @@ -19,32 +19,13 @@ func GenerateFullQuery(statement sqlparser.Statement) *sqlparser.ParsedQuery { // GenerateFieldQuery generates a query to just fetch the field info // by adding impossible where clauses as needed. func GenerateFieldQuery(statement sqlparser.Statement) *sqlparser.ParsedQuery { - buf := sqlparser.NewTrackedBuffer(FormatImpossible) - buf.Myprintf("%v", statement) + buf := sqlparser.NewTrackedBuffer(sqlparser.FormatImpossibleQuery).WriteNode(statement) + if buf.HasBindVars() { return nil } - return buf.ParsedQuery() -} -// FormatImpossible is a callback function used by TrackedBuffer -// to generate a modified version of the query where all selects -// have impossible where clauses. It overrides a few node types -// and passes the rest down to the default FormatNode. -func FormatImpossible(buf *sqlparser.TrackedBuffer, node sqlparser.SQLNode) { - switch node := node.(type) { - case *sqlparser.Select: - buf.Myprintf("select %v from %v where 1 != 1", node.SelectExprs, node.From) - case *sqlparser.JoinTableExpr: - if node.Join == sqlparser.LeftJoinStr || node.Join == sqlparser.RightJoinStr { - // ON clause is requried - buf.Myprintf("%v %s %v on 1 != 1", node.LeftExpr, node.Join, node.RightExpr) - } else { - buf.Myprintf("%v %s %v", node.LeftExpr, node.Join, node.RightExpr) - } - default: - node.Format(buf) - } + return buf.ParsedQuery() } // GenerateSelectLimitQuery generates a select query with a limit clause. diff --git a/go/vt/vtgate/planbuilder/route.go b/go/vt/vtgate/planbuilder/route.go index 21046efb356..ffe67927fa5 100644 --- a/go/vt/vtgate/planbuilder/route.go +++ b/go/vt/vtgate/planbuilder/route.go @@ -519,17 +519,6 @@ func (rb *route) isLocal(col *sqlparser.ColName) bool { func (rb *route) generateFieldQuery(sel *sqlparser.Select, jt *jointab) string { formatter := func(buf *sqlparser.TrackedBuffer, node sqlparser.SQLNode) { switch node := node.(type) { - case *sqlparser.Select: - buf.Myprintf("select %v from %v where 1 != 1", node.SelectExprs, node.From) - return - case *sqlparser.JoinTableExpr: - if node.Join == sqlparser.LeftJoinStr || node.Join == sqlparser.RightJoinStr { - // ON clause is requried - buf.Myprintf("%v %s %v on 1 != 1", node.LeftExpr, node.Join, node.RightExpr) - } else { - buf.Myprintf("%v %s %v", node.LeftExpr, node.Join, node.RightExpr) - } - return case *sqlparser.ColName: if !rb.isLocal(node) { _, joinVar := jt.Lookup(node) @@ -540,11 +529,10 @@ func (rb *route) generateFieldQuery(sel *sqlparser.Select, jt *jointab) string { node.Name.Format(buf) return } - node.Format(buf) + sqlparser.FormatImpossibleQuery(buf, node) } - buf := sqlparser.NewTrackedBuffer(formatter) - formatter(buf, sel) - return buf.ParsedQuery().Query + + return sqlparser.NewTrackedBuffer(formatter).WriteNode(sel).ParsedQuery().Query } // SupplyVar should be unreachable.