diff --git a/core/src/main/java/org/apache/calcite/sql/fun/SqlQuantifyOperator.java b/core/src/main/java/org/apache/calcite/sql/fun/SqlQuantifyOperator.java index aeb770acbaac..041d7abf09bb 100644 --- a/core/src/main/java/org/apache/calcite/sql/fun/SqlQuantifyOperator.java +++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlQuantifyOperator.java @@ -90,7 +90,14 @@ public class SqlQuantifyOperator extends SqlInOperator { if (typeForCollectionArgument != null) { return typeForCollectionArgument; } - return super.deriveType(validator, scope, call); + // Right-hand side is a subquery (some,any, all) + final RelDataType returnType = super.deriveType(validator, scope, call); + if (validator.config().typeCoercionEnabled()) { + validator.getTypeCoercion() + .quantifyOperationCoercion( + new SqlCallBinding(validator, scope, call)); + } + return returnType; } /** diff --git a/core/src/main/java/org/apache/calcite/sql/validate/implicit/TypeCoercionImpl.java b/core/src/main/java/org/apache/calcite/sql/validate/implicit/TypeCoercionImpl.java index f3f5c53469a4..232ee6dcbe2b 100644 --- a/core/src/main/java/org/apache/calcite/sql/validate/implicit/TypeCoercionImpl.java +++ b/core/src/main/java/org/apache/calcite/sql/validate/implicit/TypeCoercionImpl.java @@ -651,41 +651,113 @@ private boolean coalesceCoercion(SqlCallBinding callBinding) { */ @Override public boolean quantifyOperationCoercion(SqlCallBinding binding) { final RelDataType type1 = binding.getOperandType(0); - final RelDataType collectionType = binding.getOperandType(1); - final RelDataType type2 = collectionType.getComponentType(); - requireNonNull(type2, "type2"); - final SqlCall sqlCall = binding.getCall(); - final SqlValidatorScope scope = binding.getScope(); + final RelDataType type2 = binding.getOperandType(1); final SqlNode node1 = binding.operand(0); final SqlNode node2 = binding.operand(1); - RelDataType widenType = commonTypeForBinaryComparison(type1, type2); - if (widenType == null) { - widenType = getTightestCommonType(type1, type2); - } - if (widenType == null) { + final SqlValidatorScope scope = binding.getScope(); + + // Check column counts match for struct types, consistent with inOperationCoercion. + if (type1.isStruct() + && type2.isStruct() + && type1.getFieldCount() != type2.getFieldCount()) { return false; } - final RelDataType leftWidenType = - binding.getTypeFactory().enforceTypeWithNullability(widenType, type1.isNullable()); - boolean coercedLeft = - coerceOperandType(scope, sqlCall, 0, leftWidenType); - if (coercedLeft) { - updateInferredType(node1, leftWidenType); + + int colCount = type1.isStruct() ? type1.getFieldCount() : 1; + RelDataType[] argTypes = new RelDataType[2]; + argTypes[0] = type1; + final boolean isSubQuery = node2 instanceof SqlSelect; + // For subquery, use the row type directly. + // For collection, use the component type for comparison, not the collection. + if (isSubQuery) { + argTypes[1] = type2; + } else { + RelDataType componentType = type2.getComponentType(); + if (componentType == null) { + return false; + } + argTypes[1] = componentType; + } + boolean coerced = false; + + // Find the common types for RHS and LHS columns, + // following the same rules as inOperationCoercion. + List widenTypes = new ArrayList<>(); + for (int i = 0; i < colCount; i++) { + final int i2 = i; + List columnIthTypes = new AbstractList() { + @Override public RelDataType get(int index) { + return argTypes[index].isStruct() + ? argTypes[index].getFieldList().get(i2).getType() + : argTypes[index]; + } + + @Override public int size() { + return argTypes.length; + } + }; + + RelDataType widenType = + commonTypeForBinaryComparison(columnIthTypes.get(0), columnIthTypes.get(1)); + if (widenType == null) { + widenType = getTightestCommonType(columnIthTypes.get(0), columnIthTypes.get(1)); + } + if (widenType == null) { + // Cannot find any common type, return early. + return false; + } + widenTypes.add(widenType); + } + assert widenTypes.size() == colCount; + + // Coerce LHS operand. + if (!type1.isStruct()) { + coerced = coerceOperandType(scope, binding.getCall(), 0, widenTypes.get(0)) || coerced; } - final RelDataType rightWidenType = - binding.getTypeFactory().enforceTypeWithNullability(widenType, type2.isNullable()); - RelDataType collectionWidenType = - binding.getTypeFactory().createArrayType(rightWidenType, -1); - collectionWidenType = - binding - .getTypeFactory() - .enforceTypeWithNullability(collectionWidenType, collectionType.isNullable()); - boolean coercedRight = - coerceOperandType(scope, sqlCall, 1, collectionWidenType); - if (coercedRight) { - updateInferredType(node2, collectionWidenType); + + for (int i = 0; i < widenTypes.size(); i++) { + RelDataType desired = widenTypes.get(i); + if (node1.getKind() == SqlKind.ROW) { + assert node1 instanceof SqlCall; + if (coerceOperandType(scope, (SqlCall) node1, i, desired)) { + updateInferredColumnType( + requireNonNull(scope, "scope"), + node1, i, widenTypes.get(i)); + coerced = true; + } + } + + // RHS: subquery uses rowTypeCoercion (consistent with inOperationCoercion), + // collection reconstructs the array type. + if (isSubQuery) { + // Use rowTypeCoercion on the subquery output column, + // consistent with how inOperationCoercion handles the subquery case. + SqlValidatorScope scope1 = validator.getSelectScope((SqlSelect) node2); + RelDataType source = validator.getValidatedNodeType(node2); + RelDataType target = binding.getTypeFactory() + .createTypeWithNullability(desired, source.isNullable() || desired.isNullable()); + coerced = rowTypeCoercion(scope1, node2, i, target) || coerced; + } else { + // Collection path (e.g. ARRAY, MULTISET): coerce the whole collection + // operand once, reconstructing the collection type with the widened + // component type + RelDataType componentType = argTypes[1]; + final RelDataType rightWidenType = + binding.getTypeFactory() + .enforceTypeWithNullability(desired, componentType.isNullable()); + RelDataType collectionWidenType = + binding.getTypeFactory().createArrayType(rightWidenType, -1); + collectionWidenType = + binding.getTypeFactory() + .enforceTypeWithNullability(collectionWidenType, type2.isNullable()); + if (coerceOperandType(scope, binding.getCall(), 1, collectionWidenType)) { + updateInferredType(node2, collectionWidenType); + coerced = true; + } + } } - return coercedLeft || coercedRight; + + return coerced; } @Override public boolean builtinFunctionCoercion( diff --git a/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml b/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml index ece2ad5258aa..099ff741aaab 100644 --- a/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml +++ b/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml @@ -1520,7 +1520,7 @@ from dept]]> ($2, 0)), AND(<($3, $2), null, <>($2, 0), IS NULL($5)))]) - LogicalJoin(condition=[=($1, $4)], joinType=[left]) + LogicalJoin(condition=[=(CAST($1):INTEGER NOT NULL, $4)], joinType=[left]) LogicalJoin(condition=[true], joinType=[inner]) LogicalTableScan(table=[[CATALOG, SALES, DEPT]]) LogicalAggregate(group=[{}], c=[COUNT()], ck=[COUNT($0)]) @@ -1544,7 +1544,7 @@ LogicalProject(DEPTNO=[$0], EXPR$1=[OR(AND(IS NOT NULL($5), <>($2, 0)), AND(<($3 ($2, 0)), AND(<($3, $2), null, <>($2, 0), IS NULL($5)))]) - LogicalJoin(condition=[=($1, $4)], joinType=[left]) + LogicalJoin(condition=[=(CAST($1):INTEGER NOT NULL, $4)], joinType=[left]) LogicalJoin(condition=[true], joinType=[inner]) LogicalTableScan(table=[[CATALOG, SALES, DEPT]]) LogicalAggregate(group=[{}], c=[COUNT()], ck=[COUNT($0)]) @@ -18265,7 +18265,7 @@ LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], SAL=[$ @@ -18289,7 +18289,7 @@ LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], SAL=[$ LogicalJoin(condition=[=($1, $9)], joinType=[inner]) LogicalTableScan(table=[[CATALOG, SALES, EMP]]) LogicalAggregate(group=[{0}]) - LogicalProject(NAME=[$1]) + LogicalProject(NAME=[CAST($1):VARCHAR(20) NOT NULL]) LogicalTableScan(table=[[CATALOG, SALES, DEPT]]) ]]> diff --git a/core/src/test/resources/sql/sub-query.iq b/core/src/test/resources/sql/sub-query.iq index 1a79635e640e..b301dae413de 100644 --- a/core/src/test/resources/sql/sub-query.iq +++ b/core/src/test/resources/sql/sub-query.iq @@ -2945,7 +2945,7 @@ where e.empno > ANY( select 2 from "scott".dept e2 where e2.deptno = e.deptno) ; !if (use_old_decorr) { EnumerableCalc(expr#0..6=[{inputs}], EMPNO=[$t5]) - EnumerableHashJoin(condition=[AND(IS NOT DISTINCT FROM($4, $6), OR(AND(>($5, $0), IS NOT TRUE(OR(IS NULL($3), =($1, 0)))), AND(>($5, $0), IS NOT TRUE(OR(IS NULL($3), =($1, 0))), IS NOT TRUE(>($5, $0)), <=($1, $2))))], joinType=[inner]) + EnumerableHashJoin(condition=[AND(IS NOT DISTINCT FROM($4, $6), OR(AND(>(CAST($5):INTEGER NOT NULL, $0), IS NOT TRUE(OR(IS NULL($3), =($1, 0)))), AND(>(CAST($5):INTEGER NOT NULL, $0), IS NOT TRUE(OR(IS NULL($3), =($1, 0))), IS NOT TRUE(>(CAST($5):INTEGER NOT NULL, $0)), <=($1, $2))))], joinType=[inner]) EnumerableCalc(expr#0..4=[{inputs}], expr#5=[IS NOT NULL($t3)], expr#6=[0], expr#7=[CASE($t5, $t3, $t6)], m=[$t2], c=[$t7], d=[$t7], trueLiteral=[$t4], DEPTNO=[$t0]) EnumerableHashJoin(condition=[IS NOT DISTINCT FROM($0, $1)], joinType=[left]) EnumerableAggregate(group=[{7}]) @@ -2983,7 +2983,7 @@ select empno, e.deptno > ANY( select 2 from "scott".dept e2 where e2.deptno = e.empno) from "scott".emp as e; !if (use_old_decorr) { -EnumerableCalc(expr#0..6=[{inputs}], expr#7=[>($t1, $t2)], expr#8=[IS TRUE($t7)], expr#9=[IS NULL($t5)], expr#10=[0], expr#11=[=($t3, $t10)], expr#12=[OR($t9, $t11)], expr#13=[IS NOT TRUE($t12)], expr#14=[AND($t8, $t13)], expr#15=[>($t3, $t4)], expr#16=[IS TRUE($t15)], expr#17=[null:BOOLEAN], expr#18=[IS NOT TRUE($t7)], expr#19=[AND($t16, $t17, $t13, $t18)], expr#20=[IS NOT TRUE($t15)], expr#21=[AND($t7, $t13, $t18, $t20)], expr#22=[OR($t14, $t19, $t21)], EMPNO=[$t0], EXPR$1=[$t22]) +EnumerableCalc(expr#0..6=[{inputs}], expr#7=[CAST($t1):INTEGER], expr#8=[>($t7, $t2)], expr#9=[IS TRUE($t8)], expr#10=[IS NULL($t5)], expr#11=[0], expr#12=[=($t3, $t11)], expr#13=[OR($t10, $t12)], expr#14=[IS NOT TRUE($t13)], expr#15=[AND($t9, $t14)], expr#16=[>($t3, $t4)], expr#17=[IS TRUE($t16)], expr#18=[null:BOOLEAN], expr#19=[IS NOT TRUE($t8)], expr#20=[AND($t17, $t18, $t14, $t19)], expr#21=[IS NOT TRUE($t16)], expr#22=[AND($t8, $t14, $t19, $t21)], expr#23=[OR($t15, $t20, $t22)], EMPNO=[$t0], EXPR$1=[$t23]) EnumerableHashJoin(condition=[=($0, $6)], joinType=[left]) EnumerableCalc(expr#0..7=[{inputs}], EMPNO=[$t0], DEPTNO=[$t7]) EnumerableTableScan(table=[[scott, EMP]]) @@ -3115,7 +3115,7 @@ select * from "scott".emp emp1 where empno <> some (select comm from "scott".emp where deptno = emp1.deptno); !if (use_old_decorr) { -EnumerableCalc(expr#0..13=[{inputs}], expr#14=[<>($t10, $t9)], expr#15=[1], expr#16=[<=($t11, $t15)], expr#17=[AND($t14, $t16)], expr#18=[=($t11, $t15)], expr#19=[OR($t17, $t18)], expr#20=[<>($t0, $t12)], expr#21=[IS NULL($t13)], expr#22=[0], expr#23=[=($t9, $t22)], expr#24=[OR($t21, $t23)], expr#25=[IS NOT TRUE($t24)], expr#26=[AND($t19, $t20, $t25)], expr#27=[IS NOT TRUE($t19)], expr#28=[AND($t25, $t27)], expr#29=[OR($t26, $t28)], proj#0..7=[{exprs}], $condition=[$t29]) +EnumerableCalc(expr#0..13=[{inputs}], expr#14=[<>($t10, $t9)], expr#15=[1], expr#16=[<=($t11, $t15)], expr#17=[AND($t14, $t16)], expr#18=[=($t11, $t15)], expr#19=[OR($t17, $t18)], expr#20=[CAST($t0):DECIMAL(7, 2) NOT NULL], expr#21=[<>($t20, $t12)], expr#22=[IS NULL($t13)], expr#23=[0], expr#24=[=($t9, $t23)], expr#25=[OR($t22, $t24)], expr#26=[IS NOT TRUE($t25)], expr#27=[AND($t19, $t21, $t26)], expr#28=[IS NOT TRUE($t19)], expr#29=[AND($t26, $t28)], expr#30=[OR($t27, $t29)], proj#0..7=[{exprs}], $condition=[$t30]) EnumerableHashJoin(condition=[IS NOT DISTINCT FROM($7, $8)], joinType=[left]) EnumerableTableScan(table=[[scott, EMP]]) EnumerableCalc(expr#0..6=[{inputs}], expr#7=[IS NOT NULL($t2)], expr#8=[0], expr#9=[CASE($t7, $t2, $t8)], expr#10=[IS NOT NULL($t3)], expr#11=[CASE($t10, $t3, $t8)], expr#12=[IS NOT NULL($t4)], expr#13=[CASE($t12, $t4, $t8)], DEPTNO=[$t0], c=[$t9], d=[$t11], dd=[$t13], m=[$t5], trueLiteral=[$t6]) @@ -3174,7 +3174,7 @@ select * from "scott".emp as emp1 where empno <> some (select 2 from "scott".dept dept1 where dept1.deptno = emp1.empno); !if (use_old_decorr) { -EnumerableCalc(expr#0..13=[{inputs}], expr#14=[<>($t9, $t8)], expr#15=[1], expr#16=[<=($t10, $t15)], expr#17=[<>($t0, $t11)], expr#18=[IS NULL($t12)], expr#19=[0], expr#20=[=($t8, $t19)], expr#21=[OR($t18, $t20)], expr#22=[IS NOT TRUE($t21)], expr#23=[AND($t14, $t16, $t17, $t22)], expr#24=[=($t10, $t15)], expr#25=[IS NOT NULL($t10)], expr#26=[AND($t14, $t25)], expr#27=[IS NOT TRUE($t26)], expr#28=[AND($t24, $t17, $t22, $t27)], expr#29=[AND($t14, $t16)], expr#30=[IS NOT TRUE($t29)], expr#31=[IS NOT TRUE($t24)], expr#32=[AND($t22, $t30, $t31)], expr#33=[OR($t23, $t28, $t32)], proj#0..7=[{exprs}], $condition=[$t33]) +EnumerableCalc(expr#0..13=[{inputs}], expr#14=[<>($t9, $t8)], expr#15=[1], expr#16=[<=($t10, $t15)], expr#17=[CAST($t0):INTEGER NOT NULL], expr#18=[<>($t17, $t11)], expr#19=[IS NULL($t12)], expr#20=[0], expr#21=[=($t8, $t20)], expr#22=[OR($t19, $t21)], expr#23=[IS NOT TRUE($t22)], expr#24=[AND($t14, $t16, $t18, $t23)], expr#25=[=($t10, $t15)], expr#26=[IS NOT NULL($t10)], expr#27=[AND($t14, $t26)], expr#28=[IS NOT TRUE($t27)], expr#29=[AND($t25, $t18, $t23, $t28)], expr#30=[AND($t14, $t16)], expr#31=[IS NOT TRUE($t30)], expr#32=[IS NOT TRUE($t25)], expr#33=[AND($t23, $t31, $t32)], expr#34=[OR($t24, $t29, $t33)], proj#0..7=[{exprs}], $condition=[$t34]) EnumerableHashJoin(condition=[=($0, $13)], joinType=[left]) EnumerableTableScan(table=[[scott, EMP]]) EnumerableCalc(expr#0..5=[{inputs}], expr#6=[IS NOT NULL($t2)], expr#7=[0], expr#8=[CASE($t6, $t2, $t7)], expr#9=[IS NOT NULL($t3)], expr#10=[CASE($t9, $t3, $t7)], c=[$t8], d=[$t8], dd=[$t10], m=[$t4], trueLiteral=[$t5], DEPTNO0=[$t0]) @@ -3227,18 +3227,18 @@ select * from "scott".emp as emp1 where comm <> some (select 2 from "scott".dept dept1 where dept1.deptno = emp1.empno); !if (use_old_decorr) { -EnumerableCalc(expr#0..13=[{inputs}], expr#14=[<>($t9, $t8)], expr#15=[1], expr#16=[<=($t10, $t15)], expr#17=[AND($t14, $t16)], expr#18=[=($t10, $t15)], expr#19=[OR($t17, $t18)], expr#20=[<>($t6, $t11)], expr#21=[IS NULL($t12)], expr#22=[IS NULL($t6)], expr#23=[0], expr#24=[=($t8, $t23)], expr#25=[OR($t21, $t22, $t24)], expr#26=[IS NOT TRUE($t25)], expr#27=[AND($t19, $t20, $t26)], expr#28=[IS NOT TRUE($t19)], expr#29=[AND($t26, $t28)], expr#30=[OR($t27, $t29)], proj#0..7=[{exprs}], $condition=[$t30]) +EnumerableCalc(expr#0..13=[{inputs}], expr#14=[<>($t9, $t8)], expr#15=[1], expr#16=[<=($t10, $t15)], expr#17=[AND($t14, $t16)], expr#18=[=($t10, $t15)], expr#19=[OR($t17, $t18)], expr#20=[CAST($t6):DECIMAL(12, 2)], expr#21=[<>($t20, $t11)], expr#22=[IS NULL($t12)], expr#23=[IS NULL($t6)], expr#24=[0], expr#25=[=($t8, $t24)], expr#26=[OR($t22, $t23, $t25)], expr#27=[IS NOT TRUE($t26)], expr#28=[AND($t19, $t21, $t27)], expr#29=[IS NOT TRUE($t19)], expr#30=[AND($t27, $t29)], expr#31=[OR($t28, $t30)], proj#0..7=[{exprs}], $condition=[$t31]) EnumerableHashJoin(condition=[=($0, $13)], joinType=[left]) EnumerableTableScan(table=[[scott, EMP]]) EnumerableCalc(expr#0..5=[{inputs}], expr#6=[IS NOT NULL($t2)], expr#7=[0], expr#8=[CASE($t6, $t2, $t7)], expr#9=[IS NOT NULL($t3)], expr#10=[CASE($t9, $t3, $t7)], c=[$t8], d=[$t8], dd=[$t10], m=[$t4], trueLiteral=[$t5], DEPTNO0=[$t0]) EnumerableHashJoin(condition=[IS NOT DISTINCT FROM($0, $1)], joinType=[left]) EnumerableCalc(expr#0..7=[{inputs}], EMPNO=[$t0]) EnumerableTableScan(table=[[scott, EMP]]) - EnumerableCalc(expr#0..4=[{inputs}], expr#5=[CAST($t1):BIGINT NOT NULL], expr#6=[CAST($t3):INTEGER NOT NULL], expr#7=[CAST($t4):BOOLEAN NOT NULL], DEPTNO0=[$t0], c=[$t5], dd=[$t2], m=[$t6], trueLiteral=[$t7]) + EnumerableCalc(expr#0..4=[{inputs}], expr#5=[CAST($t1):BIGINT NOT NULL], expr#6=[CAST($t3):DECIMAL(12, 2) NOT NULL], expr#7=[CAST($t4):BOOLEAN NOT NULL], DEPTNO0=[$t0], c=[$t5], dd=[$t2], m=[$t6], trueLiteral=[$t7]) EnumerableAggregate(group=[{0}], c_g0=[MIN($2) FILTER $6], dd_g0=[COUNT($1) FILTER $5], m_g0=[MIN($3) FILTER $6], trueLiteral_g0=[MIN(true, $4) FILTER $6]) EnumerableCalc(expr#0..5=[{inputs}], expr#6=[0], expr#7=[=($t5, $t6)], expr#8=[1], expr#9=[=($t5, $t8)], proj#0..4=[{exprs}], $g_0=[$t7], $g_1=[$t9]) EnumerableAggregate(group=[{0, 1}], groups=[[{0, 1}, {0}]], c=[COUNT()], m=[MAX($1)], trueLiteral=[LITERAL_AGG(true)], $g=[GROUPING($0, $1)]) - EnumerableCalc(expr#0..2=[{inputs}], expr#3=[CAST($t0):SMALLINT NOT NULL], expr#4=[2], DEPTNO0=[$t3], EXPR$0=[$t4]) + EnumerableCalc(expr#0..2=[{inputs}], expr#3=[CAST($t0):SMALLINT NOT NULL], expr#4=[2.00:DECIMAL(12, 2)], DEPTNO0=[$t3], EXPR$0=[$t4]) EnumerableTableScan(table=[[scott, DEPT]]) !plan !} @@ -9205,4 +9205,36 @@ lateral( (2 rows) !ok + +# Test case for [CALCITE-7437] SOME/ANY subquery throws RuntimeException +SELECT deptno, deptno > SOME(SELECT sal FROM emp) AS b FROM dept; ++--------+-------+ +| DEPTNO | B | ++--------+-------+ +| 10 | false | +| 20 | false | +| 30 | false | +| 40 | false | ++--------+-------+ +(4 rows) + +!ok +!if (use_old_decorr) { +EnumerableCalc(expr#0..3=[{inputs}], expr#4=[CAST($t3):DECIMAL(7, 2) NOT NULL], expr#5=[>($t4, $t0)], expr#6=[IS TRUE($t5)], expr#7=[0], expr#8=[<>($t1, $t7)], expr#9=[AND($t6, $t8)], expr#10=[>($t1, $t2)], expr#11=[null:BOOLEAN], expr#12=[IS NOT TRUE($t5)], expr#13=[AND($t10, $t11, $t8, $t12)], expr#14=[<=($t1, $t2)], expr#15=[AND($t5, $t8, $t12, $t14)], expr#16=[OR($t9, $t13, $t15)], DEPTNO=[$t3], B=[$t16]) + EnumerableNestedLoopJoin(condition=[true], joinType=[inner]) + EnumerableAggregate(group=[{}], m=[MIN($5)], c=[COUNT()], d=[COUNT($5)]) + EnumerableTableScan(table=[[scott, EMP]]) + EnumerableCalc(expr#0..2=[{inputs}], DEPTNO=[$t0]) + EnumerableTableScan(table=[[scott, DEPT]]) +!plan +!} + +# Case 2: incompatible types (VARCHAR vs SMALLINT). +# Before fix: java.lang.RuntimeException: while resolving method +# 'gt[class java.lang.String, short]' in class SqlFunctions +# After fix: incompatibleValueType validation error java.lang.NumberFormatException: For input string: "ACCOUNTING" +SELECT deptno, dname > SOME(SELECT empno FROM emp) AS b FROM dept; +For input string: "ACCOUNTING" +!error + # End sub-query.iq