![JAR search and dependency download from the Maven repository](/logo.png)
oracle.kv.impl.query.compiler.ExprFieldStep Maven / Gradle / Ivy
/*-
* 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 oracle.kv.impl.api.table.ArrayDefImpl;
import oracle.kv.impl.api.table.FieldValueImpl;
import oracle.kv.impl.api.table.RecordDefImpl;
import oracle.kv.impl.query.QueryException;
import oracle.kv.impl.query.QueryStateException;
import oracle.kv.impl.query.types.ExprType;
import oracle.kv.impl.query.types.ExprType.Quantifier;
import oracle.kv.impl.query.types.ExprType.TypeCode;
import oracle.kv.impl.query.types.TypeManager;
import oracle.kv.table.FieldDef;
/**
* Represents a "dot" step in a path expr. In general, a field step will select
* the value of a field from one or more input records.
*
* Syntactically, an field step looks like this:
*
* input_expr.name_expr
*
* A field step has 2 operands: an input expr that is supposed to return a
* sequence of zero or more complex values, and a "name" expr, that is
* supposed to return at most one string. We refer to the string returned
* by the name expr as the "key name".
*
* For each value in the input sequence, the step computes zero or more result
* values. The overall result of the step is the concatenation of the results
* produced for each input value, in the order of their computation.
*
* Let V be the current value that the field step operates upon.
*
* 1. If V is not a complex value, an error is raised.
*
* 2. If V is a record, the name expr is computed. The name expr may reference
* V via the $$ variable. Note that if the name expr does not reference $$,
* it does not need to be computed for each V; it can be computed only once,
* before any of the input values are processed.
*
* If the name expr returns an empty result (no key name), V is skipped. If
* the name expr returns more than 1 result, or a non-string result, an error
* is raised. Otherwise, let K be the key name computed by the name expr.
* Then, if V contains a field whose name is equal to K, the value of that
* field is selected. Otherwise, an error is raised.
*
* 3. If V is a map. the name expr is computed. As in case 2, if there is no
* key name of the name expr returns more than 1 result, or a non-string
* result, an error is raised. Otherwise, let K be the key name computed
* by the name expr. Contrary to case 2, no error is raised if V does not
* contain an entry with key equal to K; instead V is skipped. Otherwise,
* the value of the entry whose key is equal to K is selected.
*
* 4 If V is an array, the field step is applied recursively to each element
* of the array.
*
* Should we allow theFieldNameExpr to return multiple strings ????
* Maybe have another kind of step (using the { expr } syntax, where expr
* produces any number of strings and the step selects matching pairs and
* constructs a new record out of the selected pairs.
*
* Should we define $$pos and allow its use in the name expr ???? It would be
* defined as the number of records or maps processed so far.
*/
class ExprFieldStep extends Expr {
private Expr theInput;
private Expr theFieldNameExpr;
private String theFieldName;
private int theFieldPos = -1;
private ExprVar theCtxItemVar;
ExprFieldStep(
QueryControlBlock qcb,
StaticContext sctx,
QueryException.Location location,
Expr input) {
super(qcb, sctx, ExprKind.FIELD_STEP, location);
theInput = input;
theInput.addParent(this);
}
ExprFieldStep(
QueryControlBlock qcb,
StaticContext sctx,
QueryException.Location location,
Expr input,
String fieldName) {
this(qcb, sctx, location, input);
theFieldName = fieldName;
getFieldPos();
}
ExprFieldStep(
QueryControlBlock qcb,
StaticContext sctx,
QueryException.Location location,
Expr input,
int fieldPos) {
this(qcb, sctx, location, input);
theFieldPos = fieldPos;
FieldDef inType = theInput.getType().getDef();
while (inType.isArray()) {
inType = ((ArrayDefImpl)inType).getElement();
}
if (!inType.isRecord()) {
throw new QueryStateException(
"Input to field step expression does not have a record type");
}
theFieldName = ((RecordDefImpl)inType).getFieldName(theFieldPos);
}
void addCtxVars(ExprVar ctxItemVar) {
theCtxItemVar = ctxItemVar;
}
void addFieldNameExpr(String fieldName, Expr fieldNameExpr) {
theFieldName = fieldName;
theFieldNameExpr = fieldNameExpr;
if (theFieldNameExpr != null) {
assert(theFieldName == null);
theFieldNameExpr.addParent(this);
}
checkConst();
if (!isConst()) {
theFieldNameExpr = ExprPromote.create(
this, theFieldNameExpr, TypeManager.STRING_QSTN());
} else {
getFieldPos();
}
}
@Override
int getNumChildren() {
return (theFieldNameExpr != null ? 2 : 1);
}
@Override
Expr getInput() {
return theInput;
}
void setInput(Expr newExpr, boolean destroy) {
theInput.removeParent(this, destroy);
theInput = newExpr;
newExpr.addParent(this);
getFieldPos();
}
Expr getFieldNameExpr() {
return theFieldNameExpr;
}
void setFieldNameExpr(Expr newExpr, boolean destroy) {
assert(theFieldName == null);
theFieldNameExpr.removeParent(this, destroy);
theFieldNameExpr = newExpr;
newExpr.addParent(this);
}
String getFieldName() {
return theFieldName;
}
ExprVar getCtxItemVar() {
return theCtxItemVar;
}
public boolean isConst() {
return theFieldName != null;
}
public void checkConst() {
if (isConst()) {
assert(theFieldNameExpr == null);
assert(theCtxItemVar == null);
return;
}
if (theFieldNameExpr.getKind() == ExprKind.CONST &&
theFieldNameExpr.getType().getCode() == TypeCode.STRING) {
FieldValueImpl value = ((ExprConst)theFieldNameExpr).getValue();
theFieldName = value.getString();
theFieldNameExpr.removeParent(this, true/*destroy*/);
theFieldNameExpr = null;
theCtxItemVar = null;
}
if (theCtxItemVar != null && !theCtxItemVar.hasParents()) {
theCtxItemVar = null;
}
}
int getFieldPos() {
if (theFieldPos >= 0) {
return theFieldPos;
}
if (theFieldName == null) {
return -1;
}
FieldDef inType = theInput.getType().getDef();
while (inType.isArray()) {
inType = ((ArrayDefImpl)inType).getElement();
}
if (!inType.isRecord()) {
return -1;
}
try {
theFieldPos = ((RecordDefImpl)inType).getFieldPos(theFieldName);
} catch (IllegalArgumentException e) {
throw new QueryException(
"There is no field named " + theFieldName +
" in type " + inType, getLocation());
}
return theFieldPos;
}
@Override
public ExprType computeType() {
ExprType inType = theInput.getType();
if (inType.isEmpty()) {
return TypeManager.EMPTY();
}
checkConst();
while (inType.isArray()) {
inType = inType.getArrayElementType(Quantifier.STAR);
}
if (theQCB.strictMode() && inType.isAtomic()) {
throw new QueryException(
"Wrong input type for path step " +
"(must be a record, array, or map type): " +
inType.getDef().getDDLString(), theLocation);
}
if (inType.isAtomic()) {
return TypeManager.EMPTY();
}
Quantifier inQuant = inType.getQuantifier();
Quantifier outQuant;
if (inType.isRecord() && isConst()) {
return inType.getFieldType(getFieldPos(), inQuant);
}
/*
* If the input type is json or any, the input value may be an array,
* in which case multiple items will be returned, in general.
*/
if (inType.isAnyJson() || inType.isAny()) {
inQuant = Quantifier.STAR;
}
outQuant = (inType.isRecord() ?
inQuant :
TypeManager.getUnionQuant(inQuant, Quantifier.QSTN));
return inType.getMapElementType(outQuant);
}
@Override
public boolean mayReturnNULL() {
if (theInput.mayReturnNULL() ||
getType().isAny() ||
getType().isAnyJson()) {
return true;
}
FieldDef inType = theInput.getType().getDef();
while (inType.isArray()) {
inType = inType.asArray().getElement();
}
if (inType.isMap()) {
return false;
}
if (inType.isRecord()) {
if (theFieldName == null) {
/* TODO: check if all fields are non-nullable */
return true;
}
return inType.asRecord().isNullable(theFieldName);
}
/* The inType is a wildcard */
return true;
}
@Override
void displayContent(StringBuilder sb, QueryFormatter formatter) {
formatter.indent(sb);
sb.append("type :\n");
formatter.indent(sb);
getType().display(sb, formatter);
sb.append("\n");
theInput.display(sb, formatter);
sb.append(".\n");
formatter.indent(sb);
if (isConst()) {
sb.append(theFieldName);
} else {
theFieldNameExpr.display(sb, formatter);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy