Skip to content

Commit ad5e0a2

Browse files
committed
MB-65809 Handle subquery plans in prepared statements
For a prepared statement with SAVE option, or if plan stability is turned on (PREPARED_ONLY, AD_HOC, AD_HOC_READ_ONLY), subquery plans is part of the prepared statement plan. To properly unmarshal such plans, we now put a couple of extra fields in planContext, which is part of each operator. The extra fields keep track of the saved subquery plans from the prepared statement (with the subquery text as key), as well as unmarshalled subquery plan (with the *algebra.Select as key). When we unmarshal plans for a prepared statement, we first look for the subquery plans, and if found, we generate a map of the saved subquery plan (subqMap), and also create a new subquery plans place holder (subqPlans). These two fields are put as part of the plan context. We then proceed to unmarshal the main query plan. As the main query plan is unmarshalled, when we need to parse an expression, and there is a possiblity of encountering a subquery in the expression, we use the new parsing function (introduced in the previous checkin) for parsing, which will parse and generate an expression, but then checks whether the subqMap is available, if so, gather subqueries from the expression just parsed, and look for saved subquery plan for each subquery. When found, it'll then unmarshal the subquery plan, and save the subquery plan with the new subquery pointer (*algebra.Select) into subqPlans. At the end of this process when the main query plan is unmarshalled, all the subquery expressions in the main plan should hopefully have a plan that has been unmarshalled and saved in subqPlans. Then subqPlans is put in the prepared plan, which then allows subquery execution to find the appropriate plans (logic for looking for subquery plans in a prepared statement already exists). Change-Id: I6ebd5b8dc6288d3d0607596123962fa5db70c1a7 Reviewed-on: https://review.couchbase.org/c/query/+/242212 Reviewed-by: Sitaram Vemulapalli <sitaram.vemulapalli@couchbase.com> Tested-by: Bingjie Miao <bingjie.miao@couchbase.com>
1 parent 0d4f07f commit ad5e0a2

5 files changed

Lines changed: 121 additions & 43 deletions

File tree

algebra/subquery.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,8 @@ func (this *SubqueryPlans) MarshalPlans(lock bool, prepName string,
369369
for key := range this.plans {
370370
r := make(map[string]interface{}, 4)
371371
str := marshal(r, key, this.plans[key], this.isks[key], prepName)
372+
// duplicates are removed, i.e. if the exact same subquery appears multiple times in
373+
// the query, the subquery plan is only saved once
372374
subqueries[str] = r
373375
}
374376
if lock {

execution/context.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import (
3434
"github.com/couchbase/query/plan"
3535
"github.com/couchbase/query/planner"
3636
"github.com/couchbase/query/sequences"
37+
"github.com/couchbase/query/settings"
3738
"github.com/couchbase/query/system"
3839
"github.com/couchbase/query/tenant"
3940
"github.com/couchbase/query/timestamp"
@@ -1466,6 +1467,10 @@ func (this *opContext) EvaluateSubquery(query *algebra.Select, parent value.Valu
14661467
}
14671468
}
14681469
}
1470+
if !planFound && (this.prepared.Persist() || settings.IsPlanStabilityEnabled()) {
1471+
logging.Debugf("Plan Stability (mode %d): subquery plan not found. Prepared name(%s) persist(%t) subquery (%s)",
1472+
settings.GetPlanStabilityMode(), this.prepared.Name(), this.prepared.Persist(), query.String())
1473+
}
14691474
}
14701475

14711476
if !planFound {

plan/context.go

Lines changed: 84 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
package plan
1010

1111
import (
12+
"encoding/json"
13+
14+
"github.com/couchbase/query/algebra"
1215
"github.com/couchbase/query/expression"
1316
"github.com/couchbase/query/value"
1417
)
@@ -19,20 +22,40 @@ type planContext struct {
1922
withs *value.ScopeValue
2023
vars *value.ScopeValue
2124
keyspaces *value.ScopeValue
25+
26+
// for subquery plans in prepared
27+
subqMap map[string]*subqPlanInfo
28+
subqPlans *algebra.SubqueryPlans
29+
}
30+
31+
func newPlanContext(subqMap map[string]*subqPlanInfo, subqPlans *algebra.SubqueryPlans) *planContext {
32+
rv := &planContext{
33+
withs: value.NewScopeValue(make(map[string]interface{}), nil),
34+
vars: value.NewScopeValue(make(map[string]interface{}), nil),
35+
keyspaces: value.NewScopeValue(make(map[string]interface{}), nil),
36+
subqMap: subqMap,
37+
subqPlans: subqPlans,
38+
}
39+
40+
rv.SetMapper(rv)
41+
return rv
2242
}
2343

24-
func newPlanContext(parent *planContext) *planContext {
25-
var wv, vv, kv value.Value
26-
if parent != nil {
27-
wv = parent.withs
28-
vv = parent.vars
29-
kv = parent.keyspaces
44+
func subqPlanContext(parent *planContext) *planContext {
45+
// parent should not be nil
46+
if parent == nil {
47+
panic("subqPlanContext: parent pointer is nil")
3048
}
3149

50+
// the subqMap and subqPlans should contain subqueries at all levels, and it's ok to share
51+
// the pointers since this is currently only used for unmarshal of subquery plans from a
52+
// prepared statement (no parallelism)
3253
rv := &planContext{
33-
withs: value.NewScopeValue(make(map[string]interface{}), wv),
34-
vars: value.NewScopeValue(make(map[string]interface{}), vv),
35-
keyspaces: value.NewScopeValue(make(map[string]interface{}), kv),
54+
withs: value.NewScopeValue(make(map[string]interface{}), parent.withs),
55+
vars: value.NewScopeValue(make(map[string]interface{}), parent.vars),
56+
keyspaces: value.NewScopeValue(make(map[string]interface{}), parent.keyspaces),
57+
subqMap: parent.subqMap,
58+
subqPlans: parent.subqPlans,
3659
}
3760

3861
rv.SetMapper(rv)
@@ -222,7 +245,7 @@ func (this *planContext) VisitIdentifier(expr *expression.Identifier) (interface
222245

223246
func (this *planContext) VisitSubquery(expr expression.Subquery) (interface{}, error) {
224247
// use a new planContext for an extra scope
225-
planContext := newPlanContext(this)
248+
planContext := subqPlanContext(this)
226249

227250
err := expr.MapChildren(planContext)
228251
if err != nil {
@@ -259,3 +282,54 @@ func (this *planContext) addSubqTermAlias(alias string) {
259282
this.keyspaces.SetField(alias, value.NewValue(ident_flags))
260283
}
261284
}
285+
286+
// functionality for handling subquery map
287+
288+
type subqPlanInfo struct {
289+
op []byte
290+
isks map[string]bool
291+
}
292+
293+
func (this *planContext) hasSubqueryMap() bool {
294+
return this.subqMap != nil
295+
}
296+
297+
// Look for subquery expressions in the expr just parsed, and for each subquery, match with the
298+
// subquery plans from prepared statement; when matched, unmarshal the subquery plan and put the
299+
// subquery plan into algebra.SubqueryPlans (for the prepared statement)
300+
func (this *planContext) checkSubqueryMap(expr expression.Expression) (expression.Expression, error) {
301+
if len(this.subqMap) == 0 {
302+
return expr, nil
303+
}
304+
305+
subqs, err := expression.ListSubqueries(expression.Expressions{expr}, false)
306+
if err != nil {
307+
return expr, err
308+
}
309+
310+
for _, subqExpr := range subqs {
311+
subq, _ := subqExpr.(*algebra.Subquery)
312+
str := subq.String()
313+
if planInfo, ok := this.subqMap[str]; ok {
314+
// unmarshal the subquery plan here (in case there are identical subqueries
315+
// present we need separate plan for each subquery)
316+
var op_type struct {
317+
Operator string `json:"#operator"`
318+
}
319+
320+
err = json.Unmarshal(planInfo.op, &op_type)
321+
if err != nil {
322+
return nil, err
323+
}
324+
325+
subPlanContext := subqPlanContext(this)
326+
subqPlan, err := MakeOperator(op_type.Operator, planInfo.op, subPlanContext)
327+
if err != nil {
328+
return nil, err
329+
}
330+
// no need for locking since this is only called under unmarshal (no parallelism)
331+
this.subqPlans.Set(subq.Select(), nil, NewQueryPlan(subqPlan), planInfo.isks, false)
332+
}
333+
}
334+
return expr, nil
335+
}

plan/parser.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,16 @@ import (
1313
"github.com/couchbase/query/expression/parser"
1414
)
1515

16+
// This function is used to parse expressions, and if there are subquery plans available when
17+
// unmarshalling prepared statements, it handles the matching of parsed subquery expression and
18+
// unmarshalled subquery plan
1619
func parseWithContext(s string, plContext *planContext) (expression.Expression, error) {
17-
return parser.Parse(s)
20+
expr, err := parser.Parse(s)
21+
if expr == nil || err != nil {
22+
return expr, err
23+
}
24+
if plContext != nil && plContext.hasSubqueryMap() {
25+
expr, err = plContext.checkSubqueryMap(expr)
26+
}
27+
return expr, err
1828
}

plan/prepared.go

Lines changed: 19 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ import (
2323
"github.com/couchbase/query/algebra"
2424
"github.com/couchbase/query/datastore"
2525
"github.com/couchbase/query/errors"
26-
"github.com/couchbase/query/expression/parser"
2726
"github.com/couchbase/query/util"
2827
"github.com/couchbase/query/value"
2928
)
@@ -307,45 +306,33 @@ func (this *Prepared) unmarshalInternal(body []byte) error {
307306
}
308307
}
309308

310-
planContext := newPlanContext(nil)
311-
this.Operator, err = MakeOperator(op_type.Operator, _unmarshalled.Operator, planContext)
312-
if err != nil {
313-
return err
314-
}
315-
309+
var subqMap map[string]*subqPlanInfo
310+
var subqPlans *algebra.SubqueryPlans
311+
hasSubq := false
316312
if len(_unmarshalled.SubqPlans) > 0 {
317-
this.subqueryPlans = algebra.NewSubqueryPlans()
313+
hasSubq = true
314+
subqPlans = algebra.NewSubqueryPlans()
315+
subqMap = make(map[string]*subqPlanInfo, len(_unmarshalled.SubqPlans))
318316
for _, planP := range _unmarshalled.SubqPlans {
319317
subqText := planP.SubqText
320318
if subqText == "" {
321319
return errors.NewPlanInternalError("prepared.unmarshalInternal: missing subquery text")
322320
}
323-
subqExpr, err := parser.Parse(subqText)
324-
if err != nil {
325-
return err
326-
}
327-
subquery, ok := subqExpr.(*algebra.Subquery)
328-
if !ok {
329-
return errors.NewPlanInternalError("prepared.unmarshalInternal: subquery expression is not a Subquery.")
330-
}
331-
332-
var op_type1 struct {
333-
Operator string `json:"#operator"`
334-
}
335-
336-
err = json.Unmarshal(planP.PlanOp, &op_type1)
337-
if err != nil {
338-
return err
321+
subqMap[subqText] = &subqPlanInfo{
322+
op: planP.PlanOp,
323+
isks: planP.IndexScanKeyspaces,
339324
}
325+
}
326+
}
340327

341-
subPlanContext := newPlanContext(planContext)
342-
subqPlan, err := MakeOperator(op_type1.Operator, planP.PlanOp, subPlanContext)
343-
if err != nil {
344-
return err
345-
}
328+
planContext := newPlanContext(subqMap, subqPlans)
329+
this.Operator, err = MakeOperator(op_type.Operator, _unmarshalled.Operator, planContext)
330+
if err != nil {
331+
return err
332+
}
346333

347-
this.subqueryPlans.Set(subquery.Select(), nil, NewQueryPlan(subqPlan), planP.IndexScanKeyspaces, false)
348-
}
334+
if hasSubq {
335+
this.subqueryPlans = subqPlans
349336
}
350337

351338
return nil
@@ -894,7 +881,7 @@ func marshalSubqueryPlan(r map[string]interface{}, subselect *algebra.Select, pl
894881
}
895882
text := subselect.String()
896883
if subselect.IsCorrelated() {
897-
// needed for parsing to work properly
884+
// this text needs to match what gets generated from Subquery.String()
898885
text = "correlated (" + text + ")"
899886
}
900887
r["subquery"] = text

0 commit comments

Comments
 (0)