oracle.kv.impl.query.compiler.ExprUtils Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of oracle-nosql-server Show documentation
Show all versions of oracle-nosql-server Show documentation
NoSQL Database Server - supplies build and runtime support for the server (store) side of the Oracle NoSQL Database.
/*-
* Copyright (C) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
*
* This file was distributed by Oracle as part of a version of Oracle NoSQL
* Database made available at:
*
* http://www.oracle.com/technetwork/database/database-technologies/nosqldb/downloads/index.html
*
* Please see the LICENSE file included in the top-level directory of the
* appropriate version of Oracle NoSQL Database for a copy of the license and
* additional information.
*/
package oracle.kv.impl.query.compiler;
import java.util.ArrayList;
import java.util.List;
import oracle.kv.impl.api.KVStoreImpl;
import oracle.kv.impl.api.table.ArrayDefImpl;
import oracle.kv.impl.api.table.MapDefImpl;
import oracle.kv.impl.api.table.FieldDefImpl;
import oracle.kv.impl.api.table.FieldValueImpl;
import oracle.kv.impl.api.table.IndexImpl;
import oracle.kv.impl.api.table.RecordDefImpl;
import oracle.kv.impl.api.table.TableAPIImpl;
import oracle.kv.impl.api.table.TableImpl;
import oracle.kv.impl.api.table.TupleValue;
import oracle.kv.impl.query.QueryException;
import oracle.kv.impl.query.QueryStateException;
import oracle.kv.impl.query.compiler.Expr.ExprIter;
import oracle.kv.impl.query.compiler.Expr.ExprKind;
import oracle.kv.impl.query.compiler.ExprVar.VarKind;
import oracle.kv.impl.query.runtime.CastIter;
import oracle.kv.impl.query.runtime.PlanIter;
import oracle.kv.impl.query.runtime.RuntimeControlBlock;
import oracle.kv.impl.query.types.ExprType;
import oracle.kv.table.FieldDef;
import oracle.kv.query.ExecuteOptions;
/**
* Various utility methods used during optimizations
*/
class ExprUtils {
/*
* Method called just before codegen to adjust the expr type of constructor
* expressions, based on where the constructed arrays/maps are going to be
* used. Specifically, if a constructed array/map C is going to be inserted
* into another constructed array/map P, and the element type of P is JSON,
* the element type of C must also be JSON. We do this to guarantee that
* strongly type data does not get inserted into JSON data.
*
* The method traverses the expr graph looking for array/map constructors.
* When it finds such a constructor P, it calls constructJsonArrayMap() on
* its child exprs to propagate the JSON-ness down to any descendant
* constructors. Descendant constructors will have their element type
* changed to JSON, but only if the array/map they construct may indeed be
* consumed by the P constructor.
*/
static void adjustConstructorTypes(Expr expr) {
switch (expr.getKind()) {
case ARRAY_CONSTR: {
ExprArrayConstr arrExpr = (ExprArrayConstr)expr;
ArrayDefImpl arrDef = arrExpr.getArrayType();
if (arrDef.getElement().equals(FieldDefImpl.jsonDef)) {
int numArgs = arrExpr.getNumArgs();
for (int i = 0; i < numArgs; ++i) {
constructJsonArrayMap(arrExpr.getArg(i));
}
}
break;
}
case MAP_CONSTR: {
ExprMapConstr mapExpr = (ExprMapConstr)expr;
MapDefImpl mapDef = mapExpr.getMapType();
if (mapDef.getElement().equals(FieldDefImpl.jsonDef)) {
int numArgs = mapExpr.getNumArgs();
for (int i = 1; i < numArgs; i += 2) {
constructJsonArrayMap(mapExpr.getArg(i));
}
}
break;
}
case REC_CONSTR: {
ExprRecConstr recExpr = (ExprRecConstr)expr;
RecordDefImpl recDef = recExpr.getDef();
int numArgs = recExpr.getNumArgs();
for (int i = 0; i < numArgs; ++i) {
if (recDef.getFieldDef(i).equals(FieldDefImpl.jsonDef)) {
constructJsonArrayMap(recExpr.getArg(i));
}
}
break;
}
case UPDATE_FIELD: {
ExprUpdateField upd = (ExprUpdateField)expr;
Expr path = expr.getInput();
if (path.getType().getDef().equals(FieldDefImpl.jsonDef) &&
upd.getNewValueExpr() != null) {
constructJsonArrayMap(upd.getNewValueExpr());
}
break;
}
case SFW: {
/*
* SFW expr does implicit record construction, so it should also
* be handled here.
*/
ExprSFW sfw = (ExprSFW)expr;
int numFields = sfw.getNumFields();
for (int i = 0; i < numFields; ++i) {
Expr fieldExpr = sfw.getFieldExpr(i);
FieldDefImpl fieldDef = fieldExpr.getType().getDef();
if (fieldDef.equals(FieldDefImpl.jsonDef)) {
constructJsonArrayMap(fieldExpr);
}
}
break;
}
default:
break;
}
ExprIter children = expr.getChildren();
while (children.hasNext()) {
Expr child = children.next();
adjustConstructorTypes(child);
}
children.reset();
}
static private void constructJsonArrayMap(Expr expr) {
FieldDefImpl exprDef = expr.getType().getDef();
if (exprDef.isAtomic() ||
exprDef.isRecord() ||
exprDef.isAnyRecord()) {
return;
}
switch (expr.getKind()) {
case ARRAY_CONSTR:
ExprArrayConstr arrExpr = (ExprArrayConstr)expr;
ArrayDefImpl arrayDef = arrExpr.getArrayType();
if (arrayDef.getElement().isSubtype(FieldDefImpl.jsonDef)) {
arrExpr.setJsonArrayType();
if (arrExpr.computeType(false)) {
propagateTypeChange(arrExpr);
}
}
break;
case MAP_CONSTR:
ExprMapConstr mapExpr = (ExprMapConstr)expr;
MapDefImpl mapDef = mapExpr.getMapType();
if (mapDef.getElement().isSubtype(FieldDefImpl.jsonDef)) {
mapExpr.setJsonMapType();
if (mapExpr.computeType(false)) {
propagateTypeChange(mapExpr);
}
}
break;
case REC_CONSTR:
ExprRecConstr recExpr = (ExprRecConstr)expr;
int numArgs = recExpr.getNumArgs();
for (int i = 0; i < numArgs; ++i) {
constructJsonArrayMap(recExpr.getArg(i));
}
break;
case VAR:
ExprVar var = (ExprVar)expr;
if (var.isFor()) {
constructJsonArrayMap(var.getDomainExpr());
}
return;
case SFW:
ExprSFW sfw = (ExprSFW)expr;
int numFieldExprs = sfw.getNumFields();
for (int i = 0; i < numFieldExprs; ++i) {
constructJsonArrayMap(sfw.getFieldExpr(i));
}
return;
case ARRAY_SLICE:
case ARRAY_FILTER:
case MAP_FILTER:
case FIELD_STEP:
expr = expr.getInput();
break;
case FUNC_CALL:
ExprFuncCall funcExpr = (ExprFuncCall)expr;
switch (funcExpr.getFunction().getCode()) {
case FN_SEQ_CONCAT:
break;
default:
return;
}
break;
case SEQ_MAP:
ExprSeqMap seqMapExpr = (ExprSeqMap)expr;
expr = seqMapExpr.getMapExpr();
break;
case PROMOTE:
case RECEIVE:
case CASE:
break;
case CONST:
ExprConst constExpr = (ExprConst)expr;
FieldValueImpl val = constExpr.getValue();
FieldDefImpl valDef = val.getDefinition();
FieldValueImpl newVal = null;
QueryException.Location loc = expr.getLocation();
if (valDef.isArray() &&
!valDef.equals(FieldDefImpl.arrayJsonDef)) {
newVal = CastIter.castValue(val, FieldDefImpl.arrayJsonDef, loc);
} else if (valDef.isMap() &&
!valDef.equals(FieldDefImpl.mapJsonDef)) {
newVal = CastIter.castValue(val, FieldDefImpl.mapJsonDef, loc);
}
if (newVal != null) {
ExprConst newExpr = new ExprConst(expr.getQCB(),
expr.getSctx(),
loc,
newVal);
expr.replace(newExpr, true/*destroy*/);
}
return;
case CAST:
case IS_OF_TYPE:
case BASE_TABLE:
return;
default:
throw new QueryStateException(
"Unexpected expression kind: " + expr.getKind());
}
ExprIter children = expr.getChildren();
while (children.hasNext()) {
Expr child = children.next();
constructJsonArrayMap(child);
}
children.reset();
}
/*
* The type of the given expr was presumably just changed. This method
* propagates the type change to the ancestors of the given expr.
*/
static private void propagateTypeChange(Expr expr) {
int numParents = expr.getNumParents();
for (int i = 0; i < numParents; ++i) {
Expr parent = expr.getParent(i);
switch (parent.getKind()) {
case SFW: {
ExprSFW sfw = (ExprSFW)parent;
ArrayList vars = sfw.findVarsForExpr(expr);
if (vars != null) {
for (ExprVar var : vars) {
boolean modified = var.computeType(false);
if (modified) {
propagateTypeChange(var);
}
}
}
break;
}
case MAP_FILTER: {
ExprMapFilter mapFilter = (ExprMapFilter)parent;
ExprVar ctxVar = mapFilter.getCtxItemVar();
ExprVar elemVar = mapFilter.getCtxElemVar();
if (ctxVar != null && ctxVar.computeType(false)) {
propagateTypeChange(ctxVar);
}
if (elemVar != null && elemVar.computeType(false)) {
propagateTypeChange(elemVar);
}
break;
}
case ARRAY_FILTER: {
ExprArrayFilter arrFilter = (ExprArrayFilter)parent;
ExprVar ctxVar = arrFilter.getCtxItemVar();
ExprVar elemVar = arrFilter.getCtxElemVar();
if (ctxVar != null && ctxVar.computeType(false)) {
propagateTypeChange(ctxVar);
}
if (elemVar != null && elemVar.computeType(false)) {
propagateTypeChange(elemVar);
}
break;
}
case ARRAY_SLICE: {
ExprArraySlice arrSlice = (ExprArraySlice)parent;
ExprVar ctxVar = arrSlice.getCtxItemVar();
if (ctxVar != null && ctxVar.computeType(false)) {
propagateTypeChange(ctxVar);
}
break;
}
case FIELD_STEP: {
ExprFieldStep fieldStep = (ExprFieldStep)parent;
ExprVar ctxVar = fieldStep.getCtxItemVar();
if (ctxVar != null && ctxVar.computeType(false)) {
propagateTypeChange(ctxVar);
}
break;
}
case SEQ_MAP: {
ExprSeqMap seqmap = (ExprSeqMap)parent;
ExprVar ctxVar = seqmap.getCtxVar();
if (ctxVar != null && ctxVar.computeType(false)) {
propagateTypeChange(ctxVar);
}
break;
}
case CASE:
case FUNC_CALL:
case IS_OF_TYPE:
case PROMOTE:
case CAST:
case ARRAY_CONSTR:
case MAP_CONSTR:
case REC_CONSTR:
case INSERT_ROW:
case UPDATE_ROW:
case UPDATE_FIELD:
case RECEIVE:
break;
case BASE_TABLE:
case CONST:
case VAR:
throw new QueryStateException(
"A " + parent.getKind() + " expression cannot be the " +
"parent of any expression");
}
boolean modified = parent.computeType(false);
if (modified) {
propagateTypeChange(parent);
}
}
}
/**
* return true if two expressions are identical; otherwise return false
*/
static boolean matchExprs(Expr expr1, Expr expr2) {
return matchExprsInternal(expr1, expr2, ++Expr.theVisitCounter);
}
private static boolean matchExprsInternal(Expr expr1, Expr expr2, int vid) {
expr1.theVisitId = vid;
expr2.theVisitId = vid;
if (expr1.getKind() != expr2.getKind()) {
return false;
}
if (expr1.getNumChildren() != expr2.getNumChildren()) {
return false;
}
switch (expr1.getKind()) {
case CONST: {
ExprConst e1 = (ExprConst)expr1;
ExprConst e2 = (ExprConst)expr2;
return e1.getValue().equals(e2.getValue());
}
case BASE_TABLE: {
ExprBaseTable e1 = (ExprBaseTable)expr1;
ExprBaseTable e2 = (ExprBaseTable)expr2;
/*
* For now, there can be only one ExprBaseTable in the query, so
* just return true. Otherwise, uncomment and finish up the code
* below (TODO).
*/
assert(e1 == e2);
return true;
/*
if (e1.getTable() != e2.getTable()) {
return false;
}
if (e1.getPrimaryKey() != null) {
if (e2.getPrimaryKey() == null) {
return false;
}
if (!e1.getPrimaryKey().equals(e2.getPrimaryKey())) {
return false;
}
} else if (e2.getPrimaryKey() != null) {
return false;
}
if (e2.getSecondaryKey() != null) {
if (e2.getSecondaryKey() == null) {
return false;
}
if (!e1.getSecondaryKey().equals(e2.getSecondaryKey())) {
return false;
}
} else if (e2.getSecondaryKey() != null) {
return false;
}
compare range the filtering preds as well.....
break;
*/
}
case VAR: {
ExprVar e1 = (ExprVar)expr1;
ExprVar e2 = (ExprVar)expr2;
if (e1.getVarKind() != e2.getVarKind()) {
return false;
}
if (e1.getVarKind() == VarKind.EXTERNAL) {
return e1.getId() == e2.getId();
}
if (e1.isContext()) {
Expr ctxExpr1 = e1.getCtxExpr();
Expr ctxExpr2 = e2.getCtxExpr();
/*
* The context expr will typically reference the contex
* vars, so if the context exprs have been visited already
* during this traversal, don't try to match them again
* as this will lead to infinite recursive calls.
*/
if (ctxExpr1.theVisitId == vid && ctxExpr2.theVisitId == vid) {
return e1.getName().equals(e2.getName());
}
return matchExprsInternal(ctxExpr1, ctxExpr2, vid);
}
/*
* If they are both table vars, compare the associated tables.
* Notice that in the case of nested tables queries, both vars
* may have the same ExprBaseTable as their domain expr, even
* though they range over different tables, so matching their
* domain exprs would not work.
*/
if (e1.getTable() != null && e2.getTable() != null) {
return e1.getTable().getId() == e2.getTable().getId();
}
return matchExprsInternal(e1.getDomainExpr(), e2.getDomainExpr(),
vid);
}
case FUNC_CALL: {
ExprFuncCall e1 = (ExprFuncCall)expr1;
ExprFuncCall e2 = (ExprFuncCall)expr2;
if (e1.getFunction() != e2.getFunction()) {
return false;
}
return matchChildren(e1, e2, vid);
}
case PROMOTE: {
ExprPromote e1 = (ExprPromote)expr1;
ExprPromote e2 = (ExprPromote)expr2;
return (e1.getTargetType().equals(e2.getTargetType()) &&
matchExprsInternal(e1.getInput(), e2.getInput(), vid));
}
case IS_OF_TYPE: {
ExprIsOfType e1 = (ExprIsOfType)expr1;
ExprIsOfType e2 = (ExprIsOfType)expr2;
if (e1.isNot() != e2.isNot()) {
return false;
}
List types1 = e1.getTargetTypes();
List types2 = e2.getTargetTypes();
List quants1 = e1.getTargetQuantifiers();
List quants2 = e2.getTargetQuantifiers();
List onlyflags1 = e1.getOnlyTargetFlags();
List onlyflags2 = e2.getOnlyTargetFlags();
if (types1.size() != types2.size()) {
return false;
}
for (int i = 0; i < types1.size(); ++i) {
if (quants1.get(i) != quants2.get(i) ||
onlyflags1.get(i) != onlyflags2.get(i) ||
!types1.get(i).equals(types2.get(i))) {
return false;
}
}
return matchChildren(e1, e2, vid);
}
case CAST: {
ExprCast e1 = (ExprCast)expr1;
ExprCast e2 = (ExprCast)expr2;
if (e1.getTargetQuantifier() != e2.getTargetQuantifier() ||
!e1.getTargetType().equals(e2.getTargetType())) {
return false;
}
return matchChildren(e1, e2, vid);
}
case FIELD_STEP: {
ExprFieldStep e1 = (ExprFieldStep)expr1;
ExprFieldStep e2 = (ExprFieldStep)expr2;
if (e1.isConst() != e2.isConst()) {
return false;
}
if (e1.isConst()) {
if (e1.getFieldPos() >= 0 && e2.getFieldPos() >= 0) {
if (e1.getFieldPos() != e2.getFieldPos()) {
return false;
}
} else if (!e1.getFieldName().equals(e2.getFieldName())) {
return false;
}
}
return matchChildren(e1, e2, vid);
}
case MAP_FILTER: {
ExprMapFilter e1 = (ExprMapFilter)expr1;
ExprMapFilter e2 = (ExprMapFilter)expr2;
if (e1.isConst() != e2.isConst()) {
return false;
}
if (e1.isConst()) {
if (e1.getConstValue() != e2.getConstValue()) {
return false;
}
}
return matchChildren(e1, e2, vid);
}
case ARRAY_FILTER: {
ExprArrayFilter e1 = (ExprArrayFilter)expr1;
ExprArrayFilter e2 = (ExprArrayFilter)expr2;
if (e1.isConst() != e2.isConst()) {
return false;
}
if (e1.isConst()) {
if (!e1.getConstValue().equals(e2.getConstValue())) {
return false;
}
}
return matchChildren(e1, e2, vid);
}
case ARRAY_SLICE: {
ExprArraySlice e1 = (ExprArraySlice)expr1;
ExprArraySlice e2 = (ExprArraySlice)expr2;
if (e1.getLowValue() != null) {
if (!e1.getLowValue().equals(e2.getLowValue())) {
return false;
}
} else if (e2.getLowValue() != null) {
return false;
}
if (e1.getHighValue() != null) {
if (!e1.getHighValue().equals(e2.getHighValue())) {
return false;
}
} else if (e2.getHighValue() != null) {
return false;
}
return matchChildren(e1, e2, vid);
}
case SEQ_MAP: {
return matchChildren(expr1, expr2, vid);
}
case CASE: {
return matchChildren(expr1, expr2, vid);
}
case ARRAY_CONSTR: {
return matchChildren(expr1, expr2, vid);
}
case MAP_CONSTR: {
ExprMapConstr map1 = (ExprMapConstr)expr1;
ExprMapConstr map2 = (ExprMapConstr)expr2;
if (map1.theArgs.size() != map2.theArgs.size()) {
return false;
}
int numArgs = map1.theArgs.size();
boolean[] matched = new boolean[numArgs];
for (int i = 0; i < numArgs; ++i) {
int j = 0;
for (; j < numArgs; ++j) {
if ((i % 2 != j % 2) || matched[j]) {
continue;
}
if (matchExprsInternal(map1.theArgs.get(i),
map2.theArgs.get(j),
vid)) {
matched[j] = true;
break;
}
}
if (j == numArgs) {
return false;
}
}
return true;
}
case REC_CONSTR: {
ExprRecConstr rec1 = (ExprRecConstr)expr1;
ExprRecConstr rec2 = (ExprRecConstr)expr2;
if (!rec1.getDef().equals(rec2.getDef())) {
return false;
}
return matchChildren(expr1, expr2, vid);
}
case SFW: {
return matchChildren(expr1, expr2, vid);
}
case RECEIVE:
case INSERT_ROW:
case UPDATE_FIELD:
case UPDATE_ROW: {
throw new QueryStateException(
"Unexprected expression kind : " + expr1.getKind());
}
}
throw new QueryStateException("Should not be here!!");
}
private static boolean matchChildren(Expr expr1, Expr expr2, int vid) {
ExprIter children1 = expr1.getChildren();
ExprIter children2 = expr2.getChildren();
while (children1.hasNext()) {
assert(children2.hasNext());
Expr child1 = children1.next();
Expr child2 = children2.next();
if (!matchExprsInternal(child1, child2, vid)) {
return false;
}
}
children1.reset();
return true;
}
/**
* This method checks whether the given expr is a reference to a
* a given primary key column of a given table. The primary key column
* is specified by its ordinal number within the prim key (e.g. the 2nd
* column of the primary key). Notice that the reference to the prim-key
* column may be over the table row var or the index var (and its position
* within the table rows is not the same as its position within the index
* entries).
*/
static boolean isPrimKeyColumnRef(
TableImpl table,
int pkCol,
Expr expr) {
if (expr.getKind() != ExprKind.FIELD_STEP) {
return false;
}
ExprFieldStep stepExpr = (ExprFieldStep)expr;
int fieldPos = stepExpr.getFieldPos();
if (fieldPos < 0) {
return false;
}
if (stepExpr.getInput().getKind() != ExprKind.VAR) {
return false;
}
ExprVar var = (ExprVar)stepExpr.getInput();
TableImpl table2 = var.getTable();
IndexImpl index = var.getIndex();
if (table2 == null || table2.getId() != table.getId()) {
return false;
}
if (index == null) {
int[] pkPositions = table.getPrimKeyPositions();
return (fieldPos == pkPositions[pkCol]);
}
pkCol += index.numFields();
return (fieldPos == pkCol);
}
/*
* Compute an expr during compilation.
*/
static List computeConstExpr(Expr expr) {
QueryControlBlock qcb = expr.getQCB();
KVStoreImpl store = qcb.getStore();
TableAPIImpl tapi = (TableAPIImpl)store.getTableAPI();
ExecuteOptions options = qcb.getOptions();
CodeGenerator codegen = new CodeGenerator(qcb);
codegen.generatePlan(expr);
if (codegen.getException() != null) {
throw codegen.getException();
}
PlanIter plan = codegen.getRootIter();
RuntimeControlBlock rcb = new RuntimeControlBlock(
tapi.getStore(),
tapi.getStore().getLogger(),
tapi.getTableMetadataHelper(),
null, // partitions
null, // shards
options,
plan,
codegen.getNumIterators(),
codegen.getNumRegs(),
null // externalVars
);
int resReg = plan.getResultReg();
ArrayList results = new ArrayList();
plan.open(rcb);
while (plan.next(rcb)) {
FieldValueImpl res = rcb.getRegVal(resReg);
if (res.isTuple()) {
res = ((TupleValue)res).toRecord();
}
results.add(res);
}
plan.close(rcb);
return results;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy