diff --git a/go/vt/vtgate/engine/fake_primitive_test.go b/go/vt/vtgate/engine/fake_primitive_test.go index 419d3c269b..e81286ddf6 100644 --- a/go/vt/vtgate/engine/fake_primitive_test.go +++ b/go/vt/vtgate/engine/fake_primitive_test.go @@ -139,5 +139,5 @@ func wrapStreamExecute(prim Primitive, vcursor VCursor, bindVars map[string]*que } func (f *fakePrimitive) description() PlanDescription { - return PlanDescription{OperatorType: "fake - not implemented"} + return PlanDescription{OperatorType: "fake"} } diff --git a/go/vt/vtgate/engine/memory_sort.go b/go/vt/vtgate/engine/memory_sort.go index d1768ba93c..b9941eba0a 100644 --- a/go/vt/vtgate/engine/memory_sort.go +++ b/go/vt/vtgate/engine/memory_sort.go @@ -23,6 +23,7 @@ import ( "math" "reflect" "sort" + "strings" "vitess.io/vitess/go/sqltypes" "vitess.io/vitess/go/vt/key" @@ -201,20 +202,27 @@ func (ms *MemorySort) description() PlanDescription { } func orderByParamsToString(i interface{}) string { - ob := i.(OrderbyParams) - return ob.String() + return i.(OrderbyParams).String() } +//GenericJoin will iterate over arrays, slices or maps, and executes the f function to get a +//string representation of each element, and then uses strings.Join() join all the strings into a single one func GenericJoin(input interface{}, f func(interface{}) string) string { sl := reflect.ValueOf(input) - str := "" - for i := 0; i < sl.Len(); i++ { - if len(str) != 0 { - str += "," + var keys []string + switch sl.Kind() { + case reflect.Slice: + for i := 0; i < sl.Len(); i++ { + keys = append(keys, f(sl.Index(i).Interface())) } - str += f(sl.Index(i).Interface()) + case reflect.Map: + for _, k := range sl.MapKeys() { + keys = append(keys, f(k.Interface())) + } + default: + panic("GenericJoin doesn't know how to deal with " + sl.Kind().String()) } - return str + return strings.Join(keys, ", ") } // sortHeap is sorted based on the orderBy params. diff --git a/go/vt/vtgate/engine/ordered_aggregate.go b/go/vt/vtgate/engine/ordered_aggregate.go index 08ffd372f1..07f433e746 100644 --- a/go/vt/vtgate/engine/ordered_aggregate.go +++ b/go/vt/vtgate/engine/ordered_aggregate.go @@ -18,6 +18,9 @@ package engine import ( "fmt" + "strconv" + + "vitess.io/vitess/go/vt/key" "vitess.io/vitess/go/vt/proto/vtrpc" "vitess.io/vitess/go/vt/vterrors" @@ -67,6 +70,14 @@ func (ap AggregateParams) isDistinct() bool { return ap.Opcode == AggregateCountDistinct || ap.Opcode == AggregateSumDistinct } +func (ap AggregateParams) String() string { + if ap.Alias != "" { + return fmt.Sprintf("%s(%d) AS %s", ap.Opcode.String(), ap.Col, ap.Alias) + } + + return fmt.Sprintf("%s(%d)", ap.Opcode.String(), ap.Col) +} + // AggregateOpcode is the aggregation Opcode. type AggregateOpcode int @@ -394,9 +405,26 @@ func createEmptyValueFor(opcode AggregateOpcode) (sqltypes.Value, error) { return sqltypes.NULL, vterrors.Errorf(vtrpc.Code_INVALID_ARGUMENT, "unknown aggregation %v", opcode) } +func aggregateParamsToString(in interface{}) string { + return in.(AggregateParams).String() +} + +func intToString(i interface{}) string { + return strconv.Itoa(i.(int)) +} + func (oa *OrderedAggregate) description() PlanDescription { + orderByIndexes := GenericJoin(oa.Aggregates, aggregateParamsToString) + groupBy := GenericJoin(oa.Keys, intToString) + other := map[string]string{ + "OrderBy": orderByIndexes, + "GroupBy": groupBy, + "Distinct": strconv.FormatBool(oa.HasDistinct), + } return PlanDescription{ - OperatorType: "OrderedAggregation not implemented", - Other: nil, + OperatorType: "Aggregate", + Variant: "Ordered", + TargetDestination: key.DestinationVtGate{}, + Other: other, } } diff --git a/go/vt/vtgate/engine/plan_description.go b/go/vt/vtgate/engine/plan_description.go index a783630064..b179aa5829 100644 --- a/go/vt/vtgate/engine/plan_description.go +++ b/go/vt/vtgate/engine/plan_description.go @@ -17,6 +17,8 @@ limitations under the License. package engine import ( + "encoding/json" + "vitess.io/vitess/go/vt/key" topodatapb "vitess.io/vitess/go/vt/proto/topodata" "vitess.io/vitess/go/vt/vtgate/vindexes" @@ -25,16 +27,42 @@ import ( // PlanDescription is used to create a serializable representation of the Primitive tree type PlanDescription struct { OperatorType string - Variant string + Variant string `json:",omitempty"` // Keyspace specifies the keyspace to send the query to. - Keyspace *vindexes.Keyspace + Keyspace *vindexes.Keyspace `json:",omitempty"` // TargetDestination specifies an explicit target destination to send the query to. - TargetDestination key.Destination + TargetDestination key.Destination `json:",omitempty"` // TargetTabletType specifies an explicit target destination tablet type // this is only used in conjunction with TargetDestination - TargetTabletType topodatapb.TabletType - Other map[string]string - Inputs []PlanDescription + TargetTabletType topodatapb.TabletType `json:",omitempty"` + Other map[string]string `json:",omitempty"` + Inputs []PlanDescription `json:",omitempty"` +} + +// MarshalJSON serializes the PlanDescription into a JSON representation. +func (pd *PlanDescription) MarshalJSON() ([]byte, error) { + var dest string + if pd.TargetDestination != nil { + dest = pd.TargetDestination.String() + } + out := struct { + OperatorType string + Variant string `json:",omitempty"` + Keyspace *vindexes.Keyspace `json:",omitempty"` + TargetDestination string `json:",omitempty"` + TargetTabletType string `json:",omitempty"` + Other map[string]string `json:",omitempty"` + Inputs []PlanDescription `json:",omitempty"` + }{ + OperatorType: pd.OperatorType, + Variant: pd.Variant, + Keyspace: pd.Keyspace, + TargetDestination: dest, + TargetTabletType: pd.TargetTabletType.String(), + Other: pd.Other, + Inputs: pd.Inputs, + } + return json.Marshal(out) } //PrimitiveToPlanDescription transforms a primitive tree into a corresponding PlanDescription tree diff --git a/go/vt/vtgate/engine/pullout_subquery.go b/go/vt/vtgate/engine/pullout_subquery.go index 7d3b00d915..313a318a46 100644 --- a/go/vt/vtgate/engine/pullout_subquery.go +++ b/go/vt/vtgate/engine/pullout_subquery.go @@ -19,6 +19,8 @@ package engine import ( "fmt" + "vitess.io/vitess/go/vt/key" + "vitess.io/vitess/go/sqltypes" "vitess.io/vitess/go/vt/vterrors" @@ -31,11 +33,14 @@ var _ Primitive = (*PulloutSubquery)(nil) // PulloutSubquery executes a "pulled out" subquery and stores // the results in a bind variable. type PulloutSubquery struct { - Opcode PulloutOpcode + Opcode PulloutOpcode + + // SubqueryResult and HasValues are used to send in the bindvar used in the query to the underlying primitive SubqueryResult string HasValues string - Subquery Primitive - Underlying Primitive + + Subquery Primitive + Underlying Primitive } // Inputs returns the input primitives for this join @@ -154,7 +159,11 @@ func (ps *PulloutSubquery) execSubquery(vcursor VCursor, bindVars map[string]*qu } func (ps *PulloutSubquery) description() PlanDescription { - return PlanDescription{OperatorType: "pullout subquery not implemented"} + return PlanDescription{ + OperatorType: "Subquery", + Variant: ps.Opcode.String(), + TargetDestination: key.DestinationVtGate{}, + } } // PulloutOpcode is a number representing the opcode diff --git a/go/vt/vtgate/engine/subquery.go b/go/vt/vtgate/engine/subquery.go index 9ef181fa45..1afa3ec728 100644 --- a/go/vt/vtgate/engine/subquery.go +++ b/go/vt/vtgate/engine/subquery.go @@ -18,6 +18,7 @@ package engine import ( "vitess.io/vitess/go/sqltypes" + "vitess.io/vitess/go/vt/key" querypb "vitess.io/vitess/go/vt/proto/query" ) @@ -104,5 +105,12 @@ func (sq *Subquery) buildFields(inner *sqltypes.Result) []*querypb.Field { } func (sq *Subquery) description() PlanDescription { - return PlanDescription{OperatorType: "subquery - not implemented"} + other := map[string]string{ + "Columns": GenericJoin(sq.Cols, intToString), + } + return PlanDescription{ + OperatorType: "Subquery", + TargetDestination: key.DestinationVtGate{}, + Other: other, + } } diff --git a/go/vt/vtgate/planbuilder/plan_test.go b/go/vt/vtgate/planbuilder/plan_test.go index aa5da5fd81..ba1e375630 100644 --- a/go/vt/vtgate/planbuilder/plan_test.go +++ b/go/vt/vtgate/planbuilder/plan_test.go @@ -301,13 +301,9 @@ func getPlanOrErrorOutput(err error, plan *engine.Plan) string { } descr := engine.PrimitiveToPlanDescription(plan.Instructions) - sss, _ := json.MarshalIndent(descr, "", " ") - fmt.Println(string(sss)) - bout, _ := json.MarshalIndent(testPlan{ - Original: plan.Original, - Instructions: plan.Instructions, - }, "", " ") + bout, _ := json.MarshalIndent(descr, " ", " ") + fmt.Println(string(bout)) return string(bout) }