func hasSubquery(node sqlparser.SQLNode) bool { has := false _ = sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) { if _, ok := node.(*sqlparser.Subquery); ok { has = true return false, errors.New("dummy") } return true, nil }, node) return has }
// findRoute identifies the right-most route for expr. In situations where // the expression addresses multiple routes, the expectation is that the // executor will use the results of the previous routes to feed the necessary // values for the external references. // If the expression contains a subquery, the right-most route identification // also follows the same rules of a normal expression. This is achieved by // looking at the Externs field of its symbol table that contains the list of // external references. // Once the target route is identified, we have to verify that the subquery's // route can be merged with it. If it cannot, we fail the query. This is because // we don't have the ability to wire up subqueries through expression evaluation // primitives. Consequently, if the plan for a subquery comes out as a Join, // we can immediately error out. // Since findRoute can itself be called from within a subquery, it has to assume // that some of the external references may actually be pointing to an outer // query. The isLocal response from the symtab is used to make sure that we // only analyze symbols that point to the current symtab. // If an expression has no references to the current query, then the left-most // route is chosen as the default. func findRoute(expr sqlparser.Expr, bldr builder) (rb *route, err error) { highestRoute := bldr.Leftmost() var subroutes []*route err = sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) { switch node := node.(type) { case *sqlparser.ColName: newRoute, isLocal, err := bldr.Symtab().Find(node, true) if err != nil { return false, err } if isLocal && newRoute.Order() > highestRoute.Order() { highestRoute = newRoute } case *sqlparser.Subquery: sel, ok := node.Select.(*sqlparser.Select) if !ok { return false, errors.New("unsupported: union operator in subqueries") } subplan, err := processSelect(sel, bldr.Symtab().VSchema, bldr) if err != nil { return false, err } subroute, ok := subplan.(*route) if !ok { return false, errors.New("unsupported: complex join in subqueries") } for _, extern := range subroute.Symtab().Externs { // No error expected. These are resolved externs. newRoute, isLocal, _ := bldr.Symtab().Find(extern, false) if isLocal && newRoute.Order() > highestRoute.Order() { highestRoute = newRoute } } subroutes = append(subroutes, subroute) return false, nil } return true, nil }, expr) if err != nil { return nil, err } for _, subroute := range subroutes { err = subqueryCanMerge(highestRoute, subroute) if err != nil { return nil, err } // This should be moved out if we become capable of processing // subqueries without push-down. subroute.Redirect = highestRoute } return highestRoute, nil }
// getBindvars returns a map of the bind vars referenced in the statement. func getBindvars(node sqlparser.SQLNode) map[string]struct{} { bindvars := make(map[string]struct{}) _ = sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) { switch node := node.(type) { case sqlparser.ValArg: bindvars[string(node[1:])] = struct{}{} case sqlparser.ListArg: bindvars[string(node[2:])] = struct{}{} } return true, nil }, node) return bindvars }
// pushGroupBy processes the group by clause. It resolves all symbols, // and ensures that there are no subqueries. It also verifies that the // references don't addres an outer query. We only support group by // for unsharded or single shard routes. func pushGroupBy(groupBy sqlparser.GroupBy, bldr builder) error { if groupBy == nil { return nil } rb, ok := bldr.(*route) if !ok { return errors.New("unsupported: complex join and group by") } err := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) { switch node := node.(type) { case *sqlparser.ColName: _, isLocal, err := bldr.Symtab().Find(node, true) if err != nil { return false, err } if !isLocal { return false, errors.New("unsupported: subquery references outer query in group by") } case *sqlparser.Subquery: // TODO(sougou): better error. return false, errors.New("unsupported: subqueries in group by expression") } return true, nil }, groupBy) if err != nil { return err } if rb.IsSingle() { rb.SetGroupBy(groupBy) return nil } // It's a scatter route. We can allow group by if it references a // column with a unique vindex. for _, expr := range groupBy { vindex := bldr.Symtab().Vindex(expr, rb, true) if vindex != nil && vindexes.IsUnique(vindex) { rb.SetGroupBy(groupBy) return nil } } return errors.New("unsupported: scatter and group by") }
// checkAggregates returns an error if the select statement // has aggregates that cannot be pushed down due to a complex // plan. func checkAggregates(sel *sqlparser.Select, bldr builder) error { hasAggregates := false if sel.Distinct != "" { hasAggregates = true } else { _ = sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) { switch node := node.(type) { case *sqlparser.FuncExpr: if node.IsAggregate() { hasAggregates = true return false, errors.New("dummy") } } return true, nil }, sel.SelectExprs) } if !hasAggregates { return nil } // Check if we can allow aggregates. rb, ok := bldr.(*route) if !ok { return errors.New("unsupported: complex join with aggregates") } if rb.IsSingle() { return nil } // It's a scatter rb. We can allow aggregates if there is a unique // vindex in the select list. for _, selectExpr := range sel.SelectExprs { switch selectExpr := selectExpr.(type) { case *sqlparser.NonStarExpr: vindex := bldr.Symtab().Vindex(selectExpr.Expr, rb, true) if vindex != nil && vindexes.IsUnique(vindex) { return nil } } } return errors.New("unsupported: scatter with aggregates") }
// Wireup performs the wire-up tasks. func (rb *route) Wireup(bldr builder, jt *jointab) error { // Resolve values stored in the builder. var err error switch vals := rb.ERoute.Values.(type) { case *sqlparser.ComparisonExpr: // A comparison expression is stored only if it was an IN clause. // We have to convert it to use a list argutment and resolve values. rb.ERoute.Values, err = rb.procureValues(bldr, jt, vals.Right) if err != nil { return err } vals.Right = sqlparser.ListArg("::" + engine.ListVarName) default: rb.ERoute.Values, err = rb.procureValues(bldr, jt, vals) if err != nil { return err } } // Fix up the AST. _ = sqlparser.Walk(func(node sqlparser.SQLNode) (bool, error) { switch node := node.(type) { case *sqlparser.Select: if len(node.SelectExprs) == 0 { node.SelectExprs = sqlparser.SelectExprs([]sqlparser.SelectExpr{ &sqlparser.NonStarExpr{ Expr: sqlparser.NumVal([]byte{'1'}), }, }) } case *sqlparser.ComparisonExpr: if node.Operator == sqlparser.EqualStr { if exprIsValue(node.Left, rb) && !exprIsValue(node.Right, rb) { node.Left, node.Right = node.Right, node.Left } } } return true, nil }, &rb.Select) // Generate query while simultaneously resolving values. varFormatter := func(buf *sqlparser.TrackedBuffer, node sqlparser.SQLNode) { switch node := node.(type) { case *sqlparser.ColName: if !rb.isLocal(node) { joinVar := jt.Procure(bldr, node, rb.Order()) rb.ERoute.JoinVars[joinVar] = struct{}{} buf.Myprintf("%a", ":"+joinVar) return } case *sqlparser.TableName: node.Name.Format(buf) return } node.Format(buf) } buf := sqlparser.NewTrackedBuffer(varFormatter) varFormatter(buf, &rb.Select) rb.ERoute.Query = buf.ParsedQuery().Query rb.ERoute.FieldQuery = rb.generateFieldQuery(&rb.Select, jt) return nil }