[ASTERIXDB-2529][COMP] Incorrect result with MISSING field value
- user model changes: no
- storage format changes: no
- interface changes: no
Details:
- Fixed StaticTypeCastUtil.staticRecordTypeCast() to
correctly handle MISSING fields in record constructors
Change-Id: I5d3435274ebf0007fe7e63b86264337072fd8305
Reviewed-on: https://asterix-gerrit.ics.uci.edu/3269
Sonar-Qube: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Integration-Tests: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Tested-by: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Contrib: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Reviewed-by: Ali Alsuliman <ali.al.solaiman@gmail.com>
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/typecast/StaticTypeCastUtil.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/typecast/StaticTypeCastUtil.java
index 55b174b..eee53e2 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/typecast/StaticTypeCastUtil.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/typecast/StaticTypeCastUtil.java
@@ -21,6 +21,7 @@
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.BitSet;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -41,6 +42,7 @@
import org.apache.asterix.om.types.AbstractCollectionType;
import org.apache.asterix.om.types.BuiltinType;
import org.apache.asterix.om.types.IAType;
+import org.apache.asterix.om.utils.ConstantExpressionUtil;
import org.apache.asterix.om.utils.NonTaggedFormatUtil;
import org.apache.commons.lang3.mutable.Mutable;
import org.apache.commons.lang3.mutable.MutableObject;
@@ -234,11 +236,11 @@
switch (arg.getExpressionTag()) {
case FUNCTION_CALL:
ScalarFunctionCallExpression argFunc = (ScalarFunctionCallExpression) arg;
- changed = rewriteFuncExpr(argFunc, requiredItemType, currentItemType, env) || changed;
+ changed |= rewriteFuncExpr(argFunc, requiredItemType, currentItemType, env);
changed |= castItem(requiredItemType, currentItemType, argFunc, args.get(j));
break;
case VARIABLE:
- changed = injectCastToRelaxType(args.get(j), currentItemType, env) || changed;
+ changed |= injectCastToRelaxType(args.get(j), currentItemType, env);
break;
}
}
@@ -278,41 +280,44 @@
|| func.getFunctionIdentifier() == BuiltinFunctions.CLOSED_RECORD_CONSTRUCTOR)) {
return false;
}
+
+ List<Mutable<ILogicalExpression>> arguments = func.getArguments();
+ int fieldArgumentCount = arguments.size() / 2;
+
IAType[] reqFieldTypes = reqType.getFieldTypes();
String[] reqFieldNames = reqType.getFieldNames();
IAType[] inputFieldTypes = inputType.getFieldTypes();
String[] inputFieldNames = inputType.getFieldNames();
int[] fieldPermutation = new int[reqFieldTypes.length];
- boolean[] nullFields = new boolean[reqFieldTypes.length];
- boolean[] openFields = new boolean[inputFieldTypes.length];
+ BitSet nullFields = new BitSet(reqFieldTypes.length);
+ BitSet openFields = new BitSet(fieldArgumentCount);
- Arrays.fill(nullFields, false);
- Arrays.fill(openFields, true);
Arrays.fill(fieldPermutation, -1);
+ openFields.set(0, fieldArgumentCount);
// forward match: match from actual to required
- boolean matched = false;
for (int i = 0; i < inputFieldNames.length; i++) {
String fieldName = inputFieldNames[i];
IAType fieldType = inputFieldTypes[i];
- if (2 * i + 1 > func.getArguments().size()) {
- // it is not a record constructor function
+ int fieldNameArgumentIdx = findFieldNameArgumentIdx(arguments, fieldName);
+ if (fieldNameArgumentIdx < 0) {
return false;
}
+ int fieldValueArgumentIdx = fieldNameArgumentIdx + 1;
+ int fieldArgumentIdx = fieldNameArgumentIdx / 2;
- // 2*i+1 is the index of field value expression
- ILogicalExpression arg = func.getArguments().get(2 * i + 1).getValue();
- matched = false;
+ ILogicalExpression arg = arguments.get(fieldValueArgumentIdx).getValue();
+ boolean matched = false;
for (int j = 0; j < reqFieldNames.length; j++) {
String reqFieldName = reqFieldNames[j];
IAType reqFieldType = reqFieldTypes[j];
if (fieldName.equals(reqFieldName)) {
//type matched
if (fieldType.equals(reqFieldType)) {
- fieldPermutation[j] = i;
- openFields[i] = false;
+ fieldPermutation[j] = fieldNameArgumentIdx;
+ openFields.clear(fieldArgumentIdx);
matched = true;
if (arg.getExpressionTag() == LogicalExpressionTag.FUNCTION_CALL) {
@@ -327,8 +332,8 @@
IAType itemType = ((AUnionType) reqFieldType).getActualType();
reqFieldType = itemType;
if (fieldType.equals(BuiltinType.AMISSING) || fieldType.equals(itemType)) {
- fieldPermutation[j] = i;
- openFields[i] = false;
+ fieldPermutation[j] = fieldNameArgumentIdx;
+ openFields.clear(fieldArgumentIdx);
matched = true;
// rewrite record expr
@@ -345,15 +350,15 @@
if (NonTaggedFormatUtil.isOptional(fieldType)) {
IAType itemType = ((AUnionType) fieldType).getActualType();
if (reqFieldType.equals(itemType)) {
- fieldPermutation[j] = i;
- openFields[i] = false;
+ fieldPermutation[j] = fieldNameArgumentIdx;
+ openFields.clear(fieldArgumentIdx);
matched = true;
ScalarFunctionCallExpression notNullFunc = new ScalarFunctionCallExpression(
FunctionUtil.getFunctionInfo(BuiltinFunctions.CHECK_UNKNOWN));
- notNullFunc.getArguments().add(new MutableObject<ILogicalExpression>(arg));
+ notNullFunc.getArguments().add(new MutableObject<>(arg));
//wrap the not null function to the original function
- func.getArguments().get(2 * i + 1).setValue(notNullFunc);
+ arguments.get(fieldValueArgumentIdx).setValue(notNullFunc);
break;
}
}
@@ -362,8 +367,8 @@
if (arg.getExpressionTag() == LogicalExpressionTag.FUNCTION_CALL) {
ScalarFunctionCallExpression scalarFunc = (ScalarFunctionCallExpression) arg;
rewriteFuncExpr(scalarFunc, reqFieldType, fieldType, env);
- fieldPermutation[j] = i;
- openFields[i] = false;
+ fieldPermutation[j] = fieldNameArgumentIdx;
+ openFields.clear(fieldArgumentIdx);
matched = true;
break;
}
@@ -380,7 +385,7 @@
for (int i = 0; i < reqFieldNames.length; i++) {
String reqFieldName = reqFieldNames[i];
IAType reqFieldType = reqFieldTypes[i];
- matched = false;
+ boolean matched = false;
for (int j = 0; j < inputFieldNames.length; j++) {
String fieldName = inputFieldNames[j];
IAType fieldType = inputFieldTypes[j];
@@ -388,11 +393,16 @@
continue;
}
// should check open field here
- // because number of entries in fieldPermuations is the
+ // because number of entries in fieldPermutations is the
// number of required schema fields
// here we want to check if an input field is matched
- // the entry index of fieldPermuatons is req field index
- if (!openFields[j]) {
+ // the entry index of fieldPermutations is req field index
+ int fieldNameArgumentIdx = findFieldNameArgumentIdx(arguments, fieldName);
+ if (fieldNameArgumentIdx < 0) {
+ return false;
+ }
+ int fieldArgumentIdx = fieldNameArgumentIdx / 2;
+ if (!openFields.get(fieldArgumentIdx)) {
matched = true;
break;
}
@@ -413,7 +423,7 @@
if (NonTaggedFormatUtil.isOptional(reqFieldType)) {
// add a null field
- nullFields[i] = true;
+ nullFields.set(i);
} else {
// no matched field in the input for a required closed field
if (inputType.isOpen()) {
@@ -427,36 +437,47 @@
}
}
- List<Mutable<ILogicalExpression>> arguments = func.getArguments();
- List<Mutable<ILogicalExpression>> originalArguments = new ArrayList<Mutable<ILogicalExpression>>();
- originalArguments.addAll(arguments);
- arguments.clear();
+ List<Mutable<ILogicalExpression>> newArguments = new ArrayList<>(arguments.size());
+
// re-order the closed part and fill in null fields
- for (int i = 0; i < fieldPermutation.length; i++) {
+ for (int i = 0; i < reqFieldTypes.length; i++) {
int pos = fieldPermutation[i];
if (pos >= 0) {
- arguments.add(originalArguments.get(2 * pos));
- arguments.add(originalArguments.get(2 * pos + 1));
+ newArguments.add(arguments.get(pos));
+ newArguments.add(arguments.get(pos + 1));
}
- if (nullFields[i]) {
+ if (nullFields.get(i)) {
// add a null field
- arguments.add(new MutableObject<ILogicalExpression>(
+ newArguments.add(new MutableObject<>(
new ConstantExpression(new AsterixConstantValue(new AString(reqFieldNames[i])))));
- arguments.add(new MutableObject<ILogicalExpression>(
- new ConstantExpression(new AsterixConstantValue(ANull.NULL))));
+ newArguments.add(new MutableObject<>(new ConstantExpression(new AsterixConstantValue(ANull.NULL))));
}
}
// add the open part
- for (int i = 0; i < openFields.length; i++) {
- if (openFields[i]) {
- arguments.add(originalArguments.get(2 * i));
- Mutable<ILogicalExpression> expRef = originalArguments.get(2 * i + 1);
- injectCastToRelaxType(expRef, inputFieldTypes[i], env);
- arguments.add(expRef);
+ for (int i = openFields.nextSetBit(0); i >= 0; i = openFields.nextSetBit(i + 1)) {
+ newArguments.add(arguments.get(i * 2));
+ Mutable<ILogicalExpression> expRef = arguments.get(i * 2 + 1);
+ injectCastToRelaxType(expRef, inputFieldTypes[i], env);
+ newArguments.add(expRef);
+ }
+
+ arguments.clear();
+ arguments.addAll(newArguments);
+
+ return true;
+ }
+
+ private static int findFieldNameArgumentIdx(List<Mutable<ILogicalExpression>> arguments, String fieldName) {
+ for (int i = 0, ln = arguments.size(); i < ln; i += 2) {
+ String n = ConstantExpressionUtil.getStringConstant(arguments.get(i).getValue());
+ if (n == null) {
+ break;
+ } else if (fieldName.equals(n)) {
+ return i;
}
}
- return true;
+ return -1;
}
private static boolean injectCastToRelaxType(Mutable<ILogicalExpression> expRef, IAType inputFieldType,
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/objects/ObjectsQueries.xml b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/objects/ObjectsQueries.xml
index cfd8aeb..b91a5ef 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/objects/ObjectsQueries.xml
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/objects/ObjectsQueries.xml
@@ -201,4 +201,9 @@
<output-dir compare="Text">pairs</output-dir>
</compilation-unit>
</test-case>
+ <test-case FilePath="objects">
+ <compilation-unit name="query-ASTERIXDB-2529">
+ <output-dir compare="Text">query-ASTERIXDB-2529</output-dir>
+ </compilation-unit>
+ </test-case>
</test-group>
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/objects/query-ASTERIXDB-2529/query-ASTERIXDB-2529.1.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/objects/query-ASTERIXDB-2529/query-ASTERIXDB-2529.1.query.sqlpp
new file mode 100644
index 0000000..f8de25b
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/objects/query-ASTERIXDB-2529/query-ASTERIXDB-2529.1.query.sqlpp
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Description : Test record constructor with MISSING field value
+ * Expected Res : Success
+ */
+
+FROM [{'a': 9, 'b': missing, 'c': 33}] v
+SELECT v;
+
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/objects/query-ASTERIXDB-2529/query-ASTERIXDB-2529.1.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/objects/query-ASTERIXDB-2529/query-ASTERIXDB-2529.1.adm
new file mode 100644
index 0000000..0e58316
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/objects/query-ASTERIXDB-2529/query-ASTERIXDB-2529.1.adm
@@ -0,0 +1 @@
+{ "v": { "a": 9, "c": 33 } }
\ No newline at end of file