github.com/matrixorigin/matrixone@v1.2.0/pkg/sql/plan/apply_indices_vector.go (about)

     1  // Copyright 2024 Matrix Origin
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package plan
    16  
    17  import (
    18  	"github.com/matrixorigin/matrixone/pkg/catalog"
    19  	"github.com/matrixorigin/matrixone/pkg/container/types"
    20  	"github.com/matrixorigin/matrixone/pkg/pb/plan"
    21  )
    22  
    23  var (
    24  	/*
    25  		   ### Common Mistakes and Troubleshooting Tips:
    26  			1. If you use 2 Project's : one col[i] and other l2_distance(col[i]), make sure that l2_distance gets the Deep copy
    27  					   of the col[i].
    28  			2. If a plan doesn't work, try using idxColMap and early return to see if the plan works on each stage.
    29  			3. Feel free to check out the builder.Query.Nodes to see if the plan is being built correctly.
    30  
    31  
    32  			### NOTES:
    33  			1. INDEX JOIN Limit Rules:
    34  			2. Nodes that require BindingTags: TableScan, Project
    35  	*/
    36  	distFuncOpTypes = map[string]string{
    37  		"l2_distance":     "vector_l2_ops",
    38  		"inner_product":   "vector_ip_ops",
    39  		"cosine_distance": "vector_cosine_ops",
    40  	}
    41  	textType = types.T_text.ToType() // return type of @probe_limit
    42  )
    43  
    44  // You replace Sort Node with a new Project Node
    45  func (builder *QueryBuilder) applyIndicesForSortUsingVectorIndex(nodeID int32, projNode, sortNode, scanNode *plan.Node,
    46  	colRefCnt map[[2]int32]int, idxColMap map[[2]int32]*plan.Expr, multiTableIndexWithSortDistFn *MultiTableIndex,
    47  	colPosOrderBy int32) int32 {
    48  
    49  	var pkPos = scanNode.TableDef.Name2ColIndex[scanNode.TableDef.Pkey.PkeyColName] //TODO: watch out.
    50  
    51  	distFnExpr := sortNode.OrderBy[0].Expr.GetF()
    52  	sortDirection := sortNode.OrderBy[0].Flag // For the most part, it is ASC
    53  
    54  	// 1.a if any of the other columns in the table are referenced, skip
    55  	//for i := range scanNode.TableDef.Cols {
    56  	//	if i != int(colPosOrderBy) && colRefCnt[[2]int32{scanNode.BindingTags[0], int32(i)}] > 0 {
    57  	//		goto END0 //TODO: need to understand this part for Aungr
    58  	//	}
    59  	//}
    60  	//TODO: selectivity rule.
    61  
    62  	// 1.b Check the order by column has refCount > len(sortNode.OrderBy)
    63  	//colCntOrderBy := colRefCnt[[2]int32{scanNode.BindingTags[0], colPosOrderBy}] - len(sortNode.OrderBy)
    64  	//if colCntOrderBy > 0 {
    65  	//	//goto END0 //TODO: need to understand this part for Aungr
    66  	//}
    67  
    68  	// 2.a  idxTags, idxObjRefs and idxTableDefs
    69  	var idxTags = make(map[string]int32)
    70  	var idxObjRefs = make([]*ObjectRef, 3)
    71  	var idxTableDefs = make([]*TableDef, 3)
    72  	idxTags["meta.scan"] = builder.genNewTag()
    73  	idxTags["centroids.scan"] = builder.genNewTag()
    74  	idxTags["entries.scan"] = builder.genNewTag()
    75  	// TODO: plan node should hold snapshot info and account info
    76  	//idxObjRefs[0], idxTableDefs[0] = builder.compCtx.Resolve(scanNode.ObjRef.SchemaName, multiTableIndexWithSortDistFn.IndexDefs[catalog.SystemSI_IVFFLAT_TblType_Metadata].IndexTableName, *scanNode.ScanTS)
    77  	//idxObjRefs[1], idxTableDefs[1] = builder.compCtx.Resolve(scanNode.ObjRef.SchemaName, multiTableIndexWithSortDistFn.IndexDefs[catalog.SystemSI_IVFFLAT_TblType_Centroids].IndexTableName, *scanNode.ScanTS)
    78  	//idxObjRefs[2], idxTableDefs[2] = builder.compCtx.Resolve(scanNode.ObjRef.SchemaName, multiTableIndexWithSortDistFn.IndexDefs[catalog.SystemSI_IVFFLAT_TblType_Entries].IndexTableName, *scanNode.ScanTS)
    79  
    80  	scanSnapshot := scanNode.ScanSnapshot
    81  	if scanSnapshot == nil {
    82  		scanSnapshot = &Snapshot{}
    83  	}
    84  
    85  	idxObjRefs[0], idxTableDefs[0] = builder.compCtx.Resolve(scanNode.ObjRef.SchemaName, multiTableIndexWithSortDistFn.IndexDefs[catalog.SystemSI_IVFFLAT_TblType_Metadata].IndexTableName, *scanSnapshot)
    86  	idxObjRefs[1], idxTableDefs[1] = builder.compCtx.Resolve(scanNode.ObjRef.SchemaName, multiTableIndexWithSortDistFn.IndexDefs[catalog.SystemSI_IVFFLAT_TblType_Centroids].IndexTableName, *scanSnapshot)
    87  	idxObjRefs[2], idxTableDefs[2] = builder.compCtx.Resolve(scanNode.ObjRef.SchemaName, multiTableIndexWithSortDistFn.IndexDefs[catalog.SystemSI_IVFFLAT_TblType_Entries].IndexTableName, *scanSnapshot)
    88  
    89  	builder.addNameByColRef(idxTags["meta.scan"], idxTableDefs[0])
    90  	builder.addNameByColRef(idxTags["centroids.scan"], idxTableDefs[1])
    91  	builder.addNameByColRef(idxTags["entries.scan"], idxTableDefs[2])
    92  
    93  	// 2.b Create Centroids.Version == cast(MetaTable.Version)
    94  	//     Order By L2 Distance(centroids,	input_literal) ASC limit @probe_limit
    95  	metaForCurrVersion1, castMetaValueColToBigInt, _ := makeMetaTblScanWhereKeyEqVersionAndCastVersion(builder, builder.ctxByNode[nodeID],
    96  		idxTableDefs, idxObjRefs, idxTags, "meta")
    97  	centroidsForCurrVersionAndProbeLimit, _ := makeCentroidsSingleJoinMetaOnCurrVersionOrderByL2DistNormalizeL2(builder,
    98  		builder.ctxByNode[nodeID], idxTableDefs, idxObjRefs, idxTags, metaForCurrVersion1, distFnExpr, sortDirection, castMetaValueColToBigInt)
    99  
   100  	// 2.c Create Entries Node
   101  	entriesTblScan, _ := makeEntriesTblScan(builder, builder.ctxByNode[nodeID], idxTableDefs, idxObjRefs, idxTags)
   102  
   103  	// 2.d Create JOIN entries and centroids on
   104  	// entries.centroid_id_fk == centroids.centroid_id AND entries.version == centroids.version
   105  	entriesJoinCentroids := makeEntriesCrossJoinCentroidsOnCentroidId(builder, builder.ctxByNode[nodeID],
   106  		idxTableDefs, idxTags,
   107  		entriesTblScan, centroidsForCurrVersionAndProbeLimit)
   108  
   109  	// If scan node has no filter condition, then 2 fast path's can be taken.
   110  	// Path 1: Only use Index Table if Projection Columns are present in Index Table or Constants.
   111  	// Path 2: May be use INDEX JOIN (not working yet)
   112  	if scanNode.FilterList == nil {
   113  		// 3.a Sort By entries by l2_distance(vector_col, literal) ASC limit original_limit
   114  		sortTblByL2Distance := makeEntriesOrderByL2Distance(builder, builder.ctxByNode[nodeID], distFnExpr, entriesJoinCentroids, sortDirection, idxTableDefs, idxTags,
   115  			sortNode)
   116  
   117  		// Plan 1: Index-Table only Plan
   118  		{
   119  
   120  			// 3.a.1 Check if all the columns in the projection are present in Index Table or Constants.
   121  			useIndexTablesOnly := true
   122  			for _, projExp := range projNode.ProjectList {
   123  				if isRuntimeConstExpr(projExp) {
   124  					continue
   125  				}
   126  
   127  				if projExp.GetCol() != nil {
   128  					if projExp.GetCol().ColPos == pkPos {
   129  						continue
   130  					}
   131  					if projExp.GetCol().ColPos == colPosOrderBy {
   132  						continue
   133  					}
   134  				}
   135  				useIndexTablesOnly = false
   136  				break
   137  			}
   138  
   139  			// 3.a.2 If all the columns in the projection are present in Index Table or Constants, then use Index Tables only.
   140  			if useIndexTablesOnly {
   141  				idxColMap[[2]int32{scanNode.BindingTags[0], pkPos}] = &plan.Expr{
   142  					Typ: idxTableDefs[2].Cols[2].Typ,
   143  					Expr: &plan.Expr_Col{
   144  						Col: &plan.ColRef{
   145  							RelPos: idxTags["entries.scan"],
   146  							ColPos: 2, // entries.pk
   147  						},
   148  					},
   149  				}
   150  				idxColMap[[2]int32{scanNode.BindingTags[0], colPosOrderBy}] = &plan.Expr{
   151  					Typ: idxTableDefs[2].Cols[3].Typ,
   152  					Expr: &plan.Expr_Col{
   153  						Col: &plan.ColRef{
   154  							RelPos: idxTags["entries.scan"],
   155  							ColPos: 3, // entries.entry
   156  						},
   157  					},
   158  				}
   159  
   160  				return sortTblByL2Distance
   161  			}
   162  		}
   163  
   164  		//// Plan 2: Create tbl "INDEX JOIN" entries on entries.original_pk == tbl.pk
   165  		//{
   166  		//	// 3.b.1 Create Table "INDEX JOIN" entries on entries.original_pk == tbl.pk. This should only work
   167  		//	// when we don't have any filter condition on the scan node.
   168  		//	projectTbl := makeTblIndexJoinEntriesCentroidOnPK(builder, builder.ctxByNode[nodeID],
   169  		//		idxTableDefs, idxTags,
   170  		//		scanNode, sortTblByL2Distance, pkPos, sortNode)
   171  		//
   172  		//	return projectTbl
   173  		//}
   174  
   175  	}
   176  
   177  	// Path 3: Generic Plan which works for all cases.
   178  	{
   179  
   180  		// 1. Do Entries INNER JOIN Centroids
   181  		tlbJoinEntries := makeTblInnerJoinEntriesCentroidOnPK(builder, builder.ctxByNode[nodeID],
   182  			idxTableDefs, idxTags,
   183  			scanNode, entriesJoinCentroids, pkPos)
   184  
   185  		// 2. Do Sort by L2 Distance
   186  		sortTblByL2Distance := makeInnerJoinOrderByL2Distance(builder, builder.ctxByNode[nodeID],
   187  			distFnExpr, tlbJoinEntries, sortDirection, idxTableDefs, idxTags, sortNode)
   188  
   189  		return sortTblByL2Distance
   190  	}
   191  }
   192  
   193  func makeMetaTblScanWhereKeyEqVersionAndCastVersion(builder *QueryBuilder, bindCtx *BindContext,
   194  	indexTableDefs []*TableDef, idxRefs []*ObjectRef, idxTags map[string]int32, prefix string) (int32, *Expr, error) {
   195  
   196  	// 1. Scan <key, value> from meta table
   197  	metaTableScanId, scanCols, _ := makeHiddenTblScanWithBindingTag(builder, bindCtx, indexTableDefs[0], idxRefs[0], idxTags[prefix+".scan"])
   198  
   199  	// 2. WHERE key = 'version'
   200  	whereKeyEqVersion, err := BindFuncExprImplByPlanExpr(builder.GetContext(), "=", []*Expr{
   201  		scanCols[0], // key
   202  		MakePlan2StringConstExprWithType("version"), // "version"
   203  	})
   204  	if err != nil {
   205  		return -1, nil, err
   206  	}
   207  	metaScanNode := builder.qry.Nodes[metaTableScanId]
   208  	metaScanNode.FilterList = []*Expr{whereKeyEqVersion}
   209  
   210  	// 3. Project "value column" as BigInt
   211  	castMetaValueColToBigInt, err := makePlan2CastExpr(builder.GetContext(), scanCols[1], makePlan2Type(&bigIntType))
   212  	if err != nil {
   213  		return -1, nil, err
   214  	}
   215  
   216  	return metaTableScanId, castMetaValueColToBigInt, nil
   217  }
   218  
   219  func makeCentroidsSingleJoinMetaOnCurrVersionOrderByL2DistNormalizeL2(builder *QueryBuilder, bindCtx *BindContext,
   220  	indexTableDefs []*TableDef, idxRefs []*ObjectRef, idxTags map[string]int32,
   221  	metaTableScanId int32, distFnExpr *plan.Function, sortDirection plan.OrderBySpec_OrderByFlag, castMetaValueColToBigInt *Expr) (int32, error) {
   222  
   223  	// 1. Scan <version, centroid_id, centroid> from centroids table
   224  	centroidsScanId, scanCols, _ := makeHiddenTblScanWithBindingTag(builder, bindCtx, indexTableDefs[1], idxRefs[1],
   225  		idxTags["centroids.scan"])
   226  
   227  	//2. JOIN centroids and meta on version
   228  	joinCond, err := BindFuncExprImplByPlanExpr(builder.GetContext(), "=", []*Expr{
   229  		scanCols[0],              // centroids.version
   230  		castMetaValueColToBigInt, // cast(meta.value as BIGINT)
   231  	})
   232  	if err != nil {
   233  		return -1, err
   234  	}
   235  	joinMetaAndCentroidsId := builder.appendNode(&plan.Node{
   236  		NodeType: plan.Node_JOIN,
   237  		JoinType: plan.Node_INNER,
   238  		Children: []int32{centroidsScanId, metaTableScanId},
   239  		OnList:   []*Expr{joinCond},
   240  	}, bindCtx)
   241  
   242  	// 3. Build Projection for l2_distance(centroid, normalize_l2(literal))
   243  	centroidsCol := &plan.Expr{
   244  		Typ: indexTableDefs[1].Cols[2].Typ,
   245  		Expr: &plan.Expr_Col{
   246  			Col: &plan.ColRef{
   247  				RelPos: idxTags["centroids.scan"],
   248  				ColPos: 2,
   249  			},
   250  		},
   251  	}
   252  	normalizeL2Lit, _ := BindFuncExprImplByPlanExpr(builder.GetContext(), "normalize_l2", []*plan.Expr{
   253  		distFnExpr.Args[1],
   254  	})
   255  	distFnName := distFnExpr.Func.ObjName
   256  	l2DistanceLitNormalizeL2Col, _ := BindFuncExprImplByPlanExpr(builder.GetContext(), distFnName, []*plan.Expr{
   257  		centroidsCol,   // centroid
   258  		normalizeL2Lit, // normalize_l2(literal)
   259  	})
   260  
   261  	// 4. Sort by l2_distance(centroid, normalize_l2(literal)) limit @probe_limit
   262  	// 4.1 @probe_limit is a system variable
   263  	probeLimitValueExpr := &plan.Expr{
   264  		Typ: makePlan2Type(&textType), // T_text
   265  		Expr: &plan.Expr_V{
   266  			V: &plan.VarRef{
   267  				Name:   "probe_limit",
   268  				Global: false,
   269  				System: false,
   270  			},
   271  		},
   272  	}
   273  
   274  	//4.2 ISNULL(@var)
   275  	arg0, err := BindFuncExprImplByPlanExpr(builder.GetContext(), "isnull", []*plan.Expr{
   276  		probeLimitValueExpr,
   277  	})
   278  	if err != nil {
   279  		return -1, err
   280  	}
   281  
   282  	// 4.3 CAST( 1 AS BIGINT)
   283  	arg1 := makePlan2Int64ConstExprWithType(1)
   284  
   285  	// 4.4 CAST(@var AS BIGINT)
   286  	targetType := types.T_int64.ToType()
   287  	planTargetType := makePlan2Type(&targetType)
   288  	arg2, err := appendCastBeforeExpr(builder.GetContext(), probeLimitValueExpr, planTargetType)
   289  	if err != nil {
   290  		return -1, err
   291  	}
   292  
   293  	ifNullLimitExpr, err := BindFuncExprImplByPlanExpr(builder.GetContext(), "case", []*plan.Expr{
   294  		arg0,
   295  		arg1,
   296  		arg2,
   297  	})
   298  	if err != nil {
   299  		return -1, err
   300  	}
   301  
   302  	sortCentroidsByL2DistanceId := builder.appendNode(&plan.Node{
   303  		NodeType: plan.Node_SORT,
   304  		Children: []int32{joinMetaAndCentroidsId},
   305  		Limit:    ifNullLimitExpr,
   306  		OrderBy: []*OrderBySpec{
   307  			{
   308  				Expr: l2DistanceLitNormalizeL2Col,
   309  				Flag: sortDirection,
   310  			},
   311  		},
   312  	}, bindCtx)
   313  
   314  	return sortCentroidsByL2DistanceId, nil
   315  }
   316  
   317  func makeEntriesTblScan(builder *QueryBuilder, bindCtx *BindContext, indexTableDefs []*TableDef, idxRefs []*ObjectRef, idxTags map[string]int32) (int32, error) {
   318  
   319  	// 1. Scan <version, centroid_id_fk, origin_pk, embedding> from entries table
   320  	entriesScanId, _, _ := makeHiddenTblScanWithBindingTag(builder, bindCtx, indexTableDefs[2], idxRefs[2],
   321  		idxTags["entries.scan"])
   322  
   323  	return entriesScanId, nil
   324  }
   325  
   326  func makeEntriesCrossJoinCentroidsOnCentroidId(builder *QueryBuilder, bindCtx *BindContext, idxTableDefs []*TableDef,
   327  	idxTags map[string]int32, entries int32, centroidsForCurrVersion int32) int32 {
   328  
   329  	centroidVersionEqEntriesVersion, _ := BindFuncExprImplByPlanExpr(builder.GetContext(), "=", []*Expr{
   330  		{
   331  			Typ: idxTableDefs[2].Cols[0].Typ,
   332  			Expr: &plan.Expr_Col{
   333  				Col: &plan.ColRef{
   334  					RelPos: idxTags["entries.scan"],
   335  					ColPos: 0, // entries.__mo_version
   336  				},
   337  			},
   338  		},
   339  		{
   340  			Typ: idxTableDefs[1].Cols[0].Typ,
   341  			Expr: &plan.Expr_Col{
   342  				Col: &plan.ColRef{
   343  					RelPos: idxTags["centroids.scan"],
   344  					ColPos: 0, // centroids.__mo_version
   345  				},
   346  			},
   347  		},
   348  	})
   349  
   350  	entriesCentroidIdEqCentroidId, _ := BindFuncExprImplByPlanExpr(builder.GetContext(), "=", []*Expr{
   351  		{
   352  			Typ: idxTableDefs[2].Cols[1].Typ,
   353  			Expr: &plan.Expr_Col{
   354  				Col: &plan.ColRef{
   355  					RelPos: idxTags["entries.scan"],
   356  					ColPos: 1, // entries.__mo_index_centroid_fk_id
   357  				},
   358  			},
   359  		},
   360  		{
   361  			Typ: idxTableDefs[1].Cols[1].Typ,
   362  			Expr: &plan.Expr_Col{
   363  				Col: &plan.ColRef{
   364  					RelPos: idxTags["centroids.scan"],
   365  					ColPos: 1, // centroids.__mo_index_centroid_id
   366  				},
   367  			},
   368  		},
   369  	})
   370  
   371  	var onList = []*Expr{entriesCentroidIdEqCentroidId, centroidVersionEqEntriesVersion}
   372  	// Create JOIN entries and centroids
   373  	// ON
   374  	// - centroids.centroid_id == entries.centroid_id_fk AND
   375  	// - centroids.version == entries.version
   376  	joinEntriesAndCentroids := builder.appendNode(&plan.Node{
   377  		NodeType: plan.Node_JOIN,
   378  		JoinType: plan.Node_SEMI,
   379  		Children: []int32{entries, centroidsForCurrVersion},
   380  		OnList:   onList,
   381  	}, bindCtx)
   382  
   383  	return joinEntriesAndCentroids
   384  }
   385  
   386  //TODO: fix it later
   387  //func makeTblIndexJoinEntriesCentroidOnPK(builder *QueryBuilder, bindCtx *BindContext,
   388  //	idxTableDefs []*TableDef, idxTags map[string]int32,
   389  //	scanNode *plan.Node, entriesJoinCentroids int32, pkPos int32,
   390  //	sortNode *plan.Node) int32 {
   391  //
   392  //	entriesOriginPkEqTblPk, _ := BindFuncExprImplByPlanExpr(builder.GetContext(), "=", []*Expr{
   393  //
   394  //		{
   395  //			Typ: idxTableDefs[2].Cols[2].Typ,
   396  //			Expr: &plan.Expr_Col{
   397  //				Col: &plan.ColRef{
   398  //					RelPos: scanNode.BindingTags[0],
   399  //					ColPos: pkPos, // tbl.pk
   400  //				},
   401  //			},
   402  //		},
   403  //		{
   404  //			Typ: idxTableDefs[2].Cols[2].Typ,
   405  //			Expr: &plan.Expr_Col{
   406  //				Col: &plan.ColRef{
   407  //					RelPos: idxTags["entries.scan"],
   408  //					ColPos: 2, // entries.origin_pk
   409  //				},
   410  //			},
   411  //		},
   412  //	})
   413  //	entriesJoinTbl := builder.appendNode(&plan.Node{
   414  //		NodeType: plan.Node_JOIN,
   415  //		JoinType: plan.Node_INDEX,
   416  //		Children: []int32{scanNode.NodeId, entriesJoinCentroids},
   417  //		OnList:   []*Expr{entriesOriginPkEqTblPk},
   418  //	}, bindCtx)
   419  //
   420  //	return entriesJoinTbl
   421  //}
   422  
   423  func makeTblInnerJoinEntriesCentroidOnPK(builder *QueryBuilder, bindCtx *BindContext,
   424  	idxTableDefs []*TableDef, idxTags map[string]int32,
   425  	scanNode *plan.Node, entriesJoinCentroids int32, pkPos int32) int32 {
   426  
   427  	entriesOriginPkEqTblPk, _ := BindFuncExprImplByPlanExpr(builder.GetContext(), "=", []*Expr{
   428  
   429  		{
   430  			Typ: idxTableDefs[2].Cols[2].Typ,
   431  			Expr: &plan.Expr_Col{
   432  				Col: &plan.ColRef{
   433  					RelPos: scanNode.BindingTags[0],
   434  					ColPos: pkPos, // tbl.pk
   435  				},
   436  			},
   437  		},
   438  		{
   439  			Typ: idxTableDefs[2].Cols[2].Typ,
   440  			Expr: &plan.Expr_Col{
   441  				Col: &plan.ColRef{
   442  					RelPos: idxTags["entries.scan"],
   443  					ColPos: 2, // entries.origin_pk
   444  				},
   445  			},
   446  		},
   447  	})
   448  	entriesJoinTbl := builder.appendNode(&plan.Node{
   449  		NodeType: plan.Node_JOIN,
   450  		JoinType: plan.Node_INNER,
   451  		Children: []int32{scanNode.NodeId, entriesJoinCentroids},
   452  		OnList:   []*Expr{entriesOriginPkEqTblPk},
   453  	}, bindCtx)
   454  
   455  	return entriesJoinTbl
   456  }
   457  
   458  func makeEntriesOrderByL2Distance(builder *QueryBuilder, bindCtx *BindContext,
   459  	fn *plan.Function, entriesJoinCentroids int32,
   460  	sortDirection plan.OrderBySpec_OrderByFlag,
   461  	idxTableDefs []*TableDef, idxTags map[string]int32,
   462  	sortNode *plan.Node) int32 {
   463  
   464  	distFnName := fn.Func.ObjName
   465  	l2DistanceColLit, _ := BindFuncExprImplByPlanExpr(builder.GetContext(), distFnName, []*plan.Expr{
   466  		{
   467  			Typ: idxTableDefs[2].Cols[3].Typ,
   468  			Expr: &plan.Expr_Col{
   469  				Col: &plan.ColRef{
   470  					RelPos: idxTags["entries.scan"],
   471  					ColPos: 3, // entries.entry
   472  				},
   473  			},
   474  		},
   475  		fn.Args[1], // lit
   476  	})
   477  	sortTblByL2Distance := builder.appendNode(&plan.Node{
   478  		NodeType: plan.Node_SORT,
   479  		Children: []int32{entriesJoinCentroids},
   480  		Limit:    DeepCopyExpr(sortNode.Limit),
   481  		Offset:   DeepCopyExpr(sortNode.Offset),
   482  		OrderBy: []*OrderBySpec{
   483  			{
   484  				Expr: l2DistanceColLit,
   485  				Flag: sortDirection,
   486  			},
   487  		},
   488  	}, bindCtx)
   489  	return sortTblByL2Distance
   490  }
   491  
   492  func makeInnerJoinOrderByL2Distance(builder *QueryBuilder, bindCtx *BindContext,
   493  	fn *plan.Function, tlbJoinEntries int32,
   494  	sortDirection plan.OrderBySpec_OrderByFlag,
   495  	idxTableDefs []*TableDef, idxTags map[string]int32,
   496  	sortNode *plan.Node) int32 {
   497  
   498  	distFnName := fn.Func.ObjName
   499  	l2DistanceColLit, _ := BindFuncExprImplByPlanExpr(builder.GetContext(), distFnName, []*plan.Expr{
   500  		{
   501  			Typ: idxTableDefs[2].Cols[3].Typ,
   502  			Expr: &plan.Expr_Col{
   503  				Col: &plan.ColRef{
   504  					RelPos: idxTags["entries.scan"],
   505  					ColPos: 3, // entries.entry
   506  				},
   507  			},
   508  		},
   509  		fn.Args[1], // lit
   510  	})
   511  	sortTblByL2Distance := builder.appendNode(&plan.Node{
   512  		NodeType: plan.Node_SORT,
   513  		Children: []int32{tlbJoinEntries},
   514  		Limit:    DeepCopyExpr(sortNode.Limit),
   515  		Offset:   DeepCopyExpr(sortNode.Offset),
   516  		OrderBy: []*OrderBySpec{
   517  			{
   518  				Expr: l2DistanceColLit,
   519  				Flag: sortDirection,
   520  			},
   521  		},
   522  	}, bindCtx)
   523  	return sortTblByL2Distance
   524  }
   525  
   526  func makeHiddenTblScanWithBindingTag(builder *QueryBuilder, bindCtx *BindContext,
   527  	indexTableDef *TableDef, idxObjRef *ObjectRef, idxTag int32) (int32, []*Expr, *Node) {
   528  
   529  	// 1. Create Scan
   530  	scanId := builder.appendNode(&Node{
   531  		NodeType:    plan.Node_TABLE_SCAN,
   532  		TableDef:    indexTableDef,
   533  		ObjRef:      idxObjRef,
   534  		BindingTags: []int32{idxTag},
   535  	}, bindCtx)
   536  
   537  	// 2. Create Scan Cols
   538  	scanCols := make([]*Expr, len(indexTableDef.Cols))
   539  	for colIdx, column := range indexTableDef.Cols {
   540  		scanCols[colIdx] = &plan.Expr{
   541  			Typ: column.Typ,
   542  			Expr: &plan.Expr_Col{
   543  				Col: &plan.ColRef{
   544  					RelPos: idxTag,
   545  					ColPos: int32(colIdx),
   546  					Name:   column.Name,
   547  				},
   548  			},
   549  		}
   550  	}
   551  	return scanId, scanCols, nil
   552  }
   553  
   554  func (builder *QueryBuilder) resolveScanNodeWithIndex(node *plan.Node, depth int32) *plan.Node {
   555  	if depth == 0 {
   556  		if node.NodeType == plan.Node_TABLE_SCAN && node.TableDef.Indexes != nil {
   557  			return node
   558  		}
   559  		return nil
   560  	}
   561  
   562  	if node.NodeType == plan.Node_SORT && len(node.Children) == 1 {
   563  		return builder.resolveScanNodeWithIndex(builder.qry.Nodes[node.Children[0]], depth-1)
   564  	}
   565  
   566  	return nil
   567  }
   568  
   569  func (builder *QueryBuilder) resolveSortNode(node *plan.Node, depth int32) *plan.Node {
   570  	if depth == 0 {
   571  		if node.NodeType == plan.Node_SORT {
   572  			return node
   573  		}
   574  		return nil
   575  	}
   576  
   577  	if node.NodeType == plan.Node_PROJECT && len(node.Children) == 1 {
   578  		return builder.resolveSortNode(builder.qry.Nodes[node.Children[0]], depth-1)
   579  	}
   580  
   581  	return nil
   582  }