Skip to content

Commit e0244f0

Browse files
[FIX] XQuery, Arithmetic expression: special cases
1 parent 3fe96d1 commit e0244f0

File tree

6 files changed

+98
-127
lines changed

6 files changed

+98
-127
lines changed

basex-core/src/main/java/org/basex/query/QueryError.java

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1103,7 +1103,7 @@ public enum QueryError {
11031103
/** Error code. */
11041104
INVTYPE_X(XPTY, 4, "%."),
11051105
/** Error code. */
1106-
CALCTYPE_X_X_X(XPTY, 4, "% not defined for % and %."),
1106+
CALCTYPE_X_X_X_X_X(XPTY, 4, "Arithmetics not defined for % and %: % % %."),
11071107
/** Error code. */
11081108
INVFUNCITEM_X_X(XPTY, 4, "Function expected, % found: %."),
11091109
/** Error code. */
@@ -1758,17 +1758,7 @@ public static QueryException testError(final Value value, final boolean pos,
17581758
* @return query exception
17591759
*/
17601760
public static QueryException numberError(final ParseExpr expr, final Item item) {
1761-
return numberError(item, expr.info());
1762-
}
1763-
1764-
/**
1765-
* Returns a number exception.
1766-
* @param item found item
1767-
* @param info input info (can be {@code null})
1768-
* @return query exception
1769-
*/
1770-
public static QueryException numberError(final Item item, final InputInfo info) {
1771-
return NONUMBER_X_X.get(info, item.type, item);
1761+
return NONUMBER_X_X.get(expr.info(), item.type, item);
17721762
}
17731763

17741764
/**

basex-core/src/main/java/org/basex/query/expr/Arith.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,12 +56,12 @@ public Expr optimize(final CompileContext cc) throws QueryException {
5656

5757
final SeqType st1 = expr1.seqType(), st2 = expr2.seqType();
5858
final Type type1 = st1.type, type2 = st2.type;
59-
final boolean nums = type1.isNumberOrUntyped() && type2.isNumberOrUntyped();
59+
final boolean numbers = type1.isNumberOrUntyped() && type2.isNumberOrUntyped();
6060

6161
final Type type = calc.type(type1, type2);
62-
final boolean noarray = !st1.mayBeArray() && !st2.mayBeArray();
63-
final boolean one = noarray && st1.oneOrMore() && st2.oneOrMore();
64-
exprType.assign(type, one ? Occ.EXACTLY_ONE : Occ.ZERO_OR_ONE);
62+
final boolean noArray = !st1.mayBeArray() && !st2.mayBeArray();
63+
final boolean oneOrMore = noArray && st1.oneOrMore() && st2.oneOrMore();
64+
exprType.assign(type, oneOrMore ? Occ.EXACTLY_ONE : Occ.ZERO_OR_ONE);
6565

6666
Expr expr = emptyExpr();
6767
// 0 - $x → -$x
@@ -72,7 +72,7 @@ public Expr optimize(final CompileContext cc) throws QueryException {
7272
if(expr == this && Function.COUNT.is(expr1) && calc == Calc.ADD && Function.COUNT.is(expr2)) {
7373
expr = cc.function(Function.COUNT, info, List.get(cc, info, expr1.arg(0), expr2.arg(0)));
7474
}
75-
if(expr == this && nums && noarray && st1.one() && st2.one()) {
75+
if(expr == this && numbers && noArray && st1.one() && st2.one()) {
7676
// example: number($a) + 0 → number($a)
7777
final Expr ex = calc.optimize(expr1, expr2, info, cc);
7878
if(ex != null) {

basex-core/src/main/java/org/basex/query/expr/Calc.java

Lines changed: 80 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -26,33 +26,27 @@ public Item eval(final Item item1, final Item item2, final InputInfo info)
2626
throws QueryException {
2727
final Type type1 = item1.type, type2 = item2.type;
2828
final boolean num1 = type1.isNumberOrUntyped(), num2 = type2.isNumberOrUntyped();
29-
if(num1 ^ num2) throw numberError(num1 ? item2 : item1, info);
30-
31-
// numbers or untyped values
32-
if(num1) {
29+
if(num1 && num2) {
3330
return switch(numType(type1, type2)) {
3431
case INTEGER -> addInt(item1, item2, info);
3532
case DOUBLE -> addDbl(item1, item2, info);
3633
case FLOAT -> addFlt(item1, item2, info);
3734
default -> addDec(item1, item2, info);
3835
};
39-
}
40-
41-
// dates or durations
42-
if(type1 == type2) {
43-
if(!(item1 instanceof Dur)) throw numberError(item1, info);
36+
} else if(type1 == type2) {
4437
if(type1 == YEAR_MONTH_DURATION) return new YMDur((YMDur) item1, (YMDur) item2, true, info);
4538
if(type1 == DAY_TIME_DURATION) return new DTDur((DTDur) item1, (DTDur) item2, true, info);
39+
} else {
40+
if(type1.instanceOf(DATE_TIME)) return new Dtm((Dtm) item1, dur(info, item2), true, info);
41+
if(type2.instanceOf(DATE_TIME)) return new Dtm((Dtm) item2, dur(info, item1), true, info);
42+
if(type1 == DATE) return new Dat((Dat) item1, dur(info, item2), true, info);
43+
if(type2 == DATE) return new Dat((Dat) item2, dur(info, item1), true, info);
44+
if(type1 == TIME && type2 == DAY_TIME_DURATION)
45+
return new Tim((Tim) item1, (DTDur) item2, true);
46+
if(type2 == TIME && type1 == DAY_TIME_DURATION)
47+
return new Tim((Tim) item2, (DTDur) item1, true);
4648
}
47-
if(type1.instanceOf(DATE_TIME)) return new Dtm((Dtm) item1, dur(info, item2), true, info);
48-
if(type2.instanceOf(DATE_TIME)) return new Dtm((Dtm) item2, dur(info, item1), true, info);
49-
if(type1 == DATE) return new Dat((Dat) item1, dur(info, item2), true, info);
50-
if(type2 == DATE) return new Dat((Dat) item2, dur(info, item1), true, info);
51-
if(type1 == TIME && type2 == DAY_TIME_DURATION)
52-
return new Tim((Tim) item1, (DTDur) item2, true);
53-
if(type2 == TIME && type1 == DAY_TIME_DURATION)
54-
return new Tim((Tim) item2, (DTDur) item1, true);
55-
throw typeError(info, type1, type2);
49+
throw typeError(info, item1, item2);
5650
}
5751

5852
@Override
@@ -81,8 +75,10 @@ public Expr optimize(final Expr expr1, final Expr expr2, final InputInfo info,
8175

8276
@Override
8377
public Type type(final Type type1, final Type type2) {
84-
if(type1 == YEAR_MONTH_DURATION && type2 == YEAR_MONTH_DURATION) return YEAR_MONTH_DURATION;
85-
if(type1 == DAY_TIME_DURATION && type2 == DAY_TIME_DURATION) return DAY_TIME_DURATION;
78+
if(type1 == type2) {
79+
if(type1 == YEAR_MONTH_DURATION) return YEAR_MONTH_DURATION;
80+
if(type1 == DAY_TIME_DURATION) return DAY_TIME_DURATION;
81+
}
8682
if(type1.instanceOf(DATE_TIME)) return type1;
8783
if(type2.instanceOf(DATE_TIME)) return type2;
8884
if(type1 == DATE || type2 == DATE) return DATE;
@@ -104,33 +100,27 @@ public Item eval(final Item item1, final Item item2, final InputInfo info)
104100
throws QueryException {
105101
final Type type1 = item1.type, type2 = item2.type;
106102
final boolean num1 = type1.isNumberOrUntyped(), num2 = type2.isNumberOrUntyped();
107-
if(num1 ^ num2) throw numberError(num1 ? item2 : item1, info);
108-
109-
// numbers or untyped values
110-
if(num1) {
103+
if(num1 && num2) {
111104
return switch(numType(type1, type2)) {
112105
case INTEGER -> subtractInt(item1, item2, info);
113106
case DOUBLE -> subtractDbl(item1, item2, info);
114107
case FLOAT -> subtractFlt(item1, item2, info);
115108
default -> subtractDec(item1, item2, info);
116109
};
117-
}
118-
119-
// dates or durations
120-
if(type1 == type2) {
121-
if(type1.oneOf(DATE_TIME_STAMP, DATE_TIME, DATE, TIME))
122-
return new DTDur((ADate) item1, (ADate) item2, info);
110+
} else if(type1.instanceOf(DATE_TIME) && type2.instanceOf(DATE_TIME)) {
111+
return new DTDur((ADate) item1, (ADate) item2, info);
112+
} else if(type1 == type2) {
113+
if(type1.oneOf(DATE, TIME)) return new DTDur((ADate) item1, (ADate) item2, info);
114+
if(type1 == DAY_TIME_DURATION) return new DTDur((DTDur) item1, (DTDur) item2, false, info);
123115
if(type1 == YEAR_MONTH_DURATION)
124116
return new YMDur((YMDur) item1, (YMDur) item2, false, info);
125-
if(type1 == DAY_TIME_DURATION)
126-
return new DTDur((DTDur) item1, (DTDur) item2, false, info);
127-
throw numberError(item1, info);
117+
} else {
118+
if(type1.instanceOf(DATE_TIME)) return new Dtm((Dtm) item1, dur(info, item2), false, info);
119+
if(type1 == DATE) return new Dat((Dat) item1, dur(info, item2), false, info);
120+
if(type1 == TIME && type2 == DAY_TIME_DURATION)
121+
return new Tim((Tim) item1, (DTDur) item2, false);
128122
}
129-
if(type1.instanceOf(DATE_TIME)) return new Dtm((Dtm) item1, dur(info, item2), false, info);
130-
if(type1 == DATE) return new Dat((Dat) item1, dur(info, item2), false, info);
131-
if(type1 == TIME && type2 == DAY_TIME_DURATION)
132-
return new Tim((Tim) item1, (DTDur) item2, false);
133-
throw typeError(info, type1, type2);
123+
throw typeError(info, item1, item2);
134124
}
135125

136126
@Override
@@ -161,9 +151,11 @@ public Expr optimize(final Expr expr1, final Expr expr2, final InputInfo info,
161151

162152
@Override
163153
public Type type(final Type type1, final Type type2) {
164-
if(type1.instanceOf(DATE_TIME) && type2.instanceOf(DATE_TIME) ||
165-
type1 == DATE && type2 == DATE) return DAY_TIME_DURATION;
166-
if(type1 == TIME && type2 == TIME) return DAY_TIME_DURATION;
154+
if(type1.instanceOf(DATE_TIME) && type2.instanceOf(DATE_TIME)) return DAY_TIME_DURATION;
155+
if(type1 == type2) {
156+
if(type1.oneOf(DATE, TIME, DAY_TIME_DURATION)) return DAY_TIME_DURATION;
157+
if(type1 == YEAR_MONTH_DURATION) return YEAR_MONTH_DURATION;
158+
}
167159
if(type1.instanceOf(DATE_TIME)) return type1;
168160
if(type1 == DATE) return DATE;
169161
if(type1 == TIME && type2 == DAY_TIME_DURATION) return TIME;
@@ -182,35 +174,25 @@ public Calc invert() {
182174
public Item eval(final Item item1, final Item item2, final InputInfo info)
183175
throws QueryException {
184176
final Type type1 = item1.type, type2 = item2.type;
185-
if(type1 == YEAR_MONTH_DURATION) {
186-
if(item2 instanceof ANum) return new YMDur((Dur) item1, item2.dbl(info), true, info);
187-
throw numberError(item2, info);
188-
}
189-
if(type2 == YEAR_MONTH_DURATION) {
190-
if(item1 instanceof ANum) return new YMDur((Dur) item2, item1.dbl(info), true, info);
191-
throw numberError(item1, info);
192-
}
193-
if(type1 == DAY_TIME_DURATION) {
194-
if(item2 instanceof ANum) return new DTDur((Dur) item1, item2.dbl(info), true, info);
195-
throw numberError(item2, info);
196-
}
197-
if(type2 == DAY_TIME_DURATION) {
198-
if(item1 instanceof ANum) return new DTDur((Dur) item2, item1.dbl(info), true, info);
199-
throw numberError(item1, info);
200-
}
201-
202177
final boolean num1 = type1.isNumberOrUntyped(), num2 = type2.isNumberOrUntyped();
203-
if(num1 ^ num2) throw typeError(info, type1, type2);
204-
// numbers or untyped values
205-
if(num1) {
178+
if(type1 == YEAR_MONTH_DURATION) {
179+
if(num2) return new YMDur((Dur) item1, item2.dbl(info), true, info);
180+
} else if(type2 == YEAR_MONTH_DURATION) {
181+
if(num1) return new YMDur((Dur) item2, item1.dbl(info), true, info);
182+
} else if(type1 == DAY_TIME_DURATION) {
183+
if(num2) return new DTDur((Dur) item1, item2.dbl(info), true, info);
184+
} else if(type2 == DAY_TIME_DURATION) {
185+
if(num1) return new DTDur((Dur) item2, item1.dbl(info), true, info);
186+
} else if(num1 && num2) {
206187
return switch(numType(type1, type2)) {
207188
case INTEGER -> multiplyInt(item1, item2, info);
208189
case DOUBLE -> multiplyDbl(item1, item2, info);
209190
case FLOAT -> multiplyFlt(item1, item2, info);
210191
default -> multiplyDec(item1, item2, info);
211192
};
212193
}
213-
throw numberError(item1, info);
194+
195+
throw typeError(info, item1, item2);
214196
}
215197

216198
@Override
@@ -263,6 +245,7 @@ public Calc invert() {
263245
public Item eval(final Item item1, final Item item2, final InputInfo info)
264246
throws QueryException {
265247
final Type type1 = item1.type, type2 = item2.type;
248+
final boolean num1 = type1.isNumberOrUntyped(), num2 = type2.isNumberOrUntyped();
266249
if(type1 == type2) {
267250
if(type1 == YEAR_MONTH_DURATION) {
268251
final BigDecimal bd = BigDecimal.valueOf(((YMDur) item2).ymd());
@@ -277,22 +260,18 @@ public Item eval(final Item item1, final Item item2, final InputInfo info)
277260
}
278261
}
279262
if(type1 == YEAR_MONTH_DURATION) {
280-
if(item2 instanceof ANum) return new YMDur((Dur) item1, item2.dbl(info), false, info);
281-
throw numberError(item2, info);
282-
}
283-
if(type1 == DAY_TIME_DURATION) {
284-
if(item2 instanceof ANum) return new DTDur((Dur) item1, item2.dbl(info), false, info);
285-
throw numberError(item2, info);
263+
if(num2) return new YMDur((Dur) item1, item2.dbl(info), false, info);
264+
} else if(type1 == DAY_TIME_DURATION) {
265+
if(num2) return new DTDur((Dur) item1, item2.dbl(info), false, info);
266+
} else if(num1 && num2) {
267+
return switch(numType(type1, type2)) {
268+
case DOUBLE -> divideDbl(item1, item2, info);
269+
case FLOAT -> divideFlt(item1, item2, info);
270+
default -> divideDec(item1, item2, info);
271+
};
286272
}
287273

288-
// numbers or untyped values
289-
if(!type1.isNumberOrUntyped()) throw numberError(item1, info);
290-
if(!type2.isNumberOrUntyped()) throw numberError(item2, info);
291-
return switch(numType(type1, type2)) {
292-
case DOUBLE -> divideDbl(item1, item2, info);
293-
case FLOAT -> divideFlt(item1, item2, info);
294-
default -> divideDec(item1, item2, info);
295-
};
274+
throw typeError(info, item1, item2);
296275
}
297276

298277
@Override
@@ -318,8 +297,7 @@ public Expr optimize(final Expr expr1, final Expr expr2, final InputInfo info,
318297

319298
@Override
320299
public Type type(final Type type1, final Type type2) {
321-
if(type1 == YEAR_MONTH_DURATION && type2 == YEAR_MONTH_DURATION ||
322-
type1 == DAY_TIME_DURATION && type2 == DAY_TIME_DURATION) return DECIMAL;
300+
if(type1 == type2 && type1.oneOf(YEAR_MONTH_DURATION, DAY_TIME_DURATION)) return DECIMAL;
323301
if(type1 == YEAR_MONTH_DURATION) return YEAR_MONTH_DURATION;
324302
if(type1 == DAY_TIME_DURATION) return DAY_TIME_DURATION;
325303
final Type type = numType(type1, type2);
@@ -337,17 +315,17 @@ public Calc invert() {
337315
@Override
338316
public Itr eval(final Item item1, final Item item2, final InputInfo info)
339317
throws QueryException {
340-
341-
// numbers or untyped values
342318
final Type type1 = item1.type, type2 = item2.type;
343-
if(!type1.isNumberOrUntyped()) throw numberError(item1, info);
344-
if(!type2.isNumberOrUntyped()) throw numberError(item2, info);
345-
return switch(numType(type1, type2)) {
346-
case INTEGER -> divideIntInt(item1, item2, info);
347-
case DOUBLE -> divideIntDbl(item1, item2, info);
348-
case FLOAT -> divideIntFlt(item1, item2, info);
349-
default -> divideIntDec(item1, item2, info);
350-
};
319+
final boolean num1 = type1.isNumberOrUntyped(), num2 = type2.isNumberOrUntyped();
320+
if(num1 && num2) {
321+
return switch(numType(type1, type2)) {
322+
case INTEGER -> divideIntInt(item1, item2, info);
323+
case DOUBLE -> divideIntDbl(item1, item2, info);
324+
case FLOAT -> divideIntFlt(item1, item2, info);
325+
default -> divideIntDec(item1, item2, info);
326+
};
327+
}
328+
throw typeError(info, item1, item2);
351329
}
352330

353331
@Override
@@ -383,14 +361,16 @@ public Calc invert() {
383361
public Item eval(final Item item1, final Item item2, final InputInfo info)
384362
throws QueryException {
385363
final Type type1 = item1.type, type2 = item2.type;
386-
if(!type1.isNumberOrUntyped()) throw numberError(item1, info);
387-
if(!type2.isNumberOrUntyped()) throw numberError(item2, info);
388-
return switch(numType(type1, type2)) {
389-
case INTEGER -> moduloInt(item1, item2, info);
390-
case DOUBLE -> moduloDbl(item1, item2, info);
391-
case FLOAT -> moduloFlt(item1, item2, info);
392-
default -> moduloDec(item1, item2, info);
393-
};
364+
final boolean num1 = type1.isNumberOrUntyped(), num2 = type2.isNumberOrUntyped();
365+
if(num1 && num2) {
366+
return switch(numType(type1, type2)) {
367+
case INTEGER -> moduloInt(item1, item2, info);
368+
case DOUBLE -> moduloDbl(item1, item2, info);
369+
case FLOAT -> moduloFlt(item1, item2, info);
370+
default -> moduloDec(item1, item2, info);
371+
};
372+
}
373+
throw typeError(info, item1, item2);
394374
}
395375

396376
@Override
@@ -489,12 +469,12 @@ public static AtomType numType(final Type type1, final Type type2) {
489469
/**
490470
* Returns a type error.
491471
* @param info input info (can be {@code null})
492-
* @param type1 first type
493-
* @param type2 second type
472+
* @param item1 first item
473+
* @param item2 second item
494474
* @return query exception
495475
*/
496-
final QueryException typeError(final InputInfo info, final Type type1, final Type type2) {
497-
return CALCTYPE_X_X_X.get(info, info(), type1, type2);
476+
final QueryException typeError(final InputInfo info, final Item item1, final Item item2) {
477+
return CALCTYPE_X_X_X_X_X.get(info, item1.type, item2.type, item1, name, item2);
498478
}
499479

500480
/**
@@ -513,14 +493,6 @@ static Dur dur(final InputInfo info, final Item item) throws QueryException {
513493
throw NODUR_X_X.get(info, type, item);
514494
}
515495

516-
/**
517-
* Returns a string representation of the operator.
518-
* @return string
519-
*/
520-
final String info() {
521-
return '\'' + name + "' expression";
522-
}
523-
524496
@Override
525497
public String toString() {
526498
return name;

basex-core/src/test/java/org/basex/query/expr/SimpleMapTest.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,9 @@ public final class SimpleMapTest extends SandboxTest {
9090

9191
/** Errors. */
9292
@Test public void error() {
93-
error("(1 + 'a') ! 2", NONUMBER_X_X);
93+
error("(1 + 'a') ! 2", CALCTYPE_X_X_X_X_X);
94+
query("('a' || <?_ x?>)[. = 'x'] ! error()", "");
95+
query("('a' || <?_ x?>) ! () ! error()", "");
9496
}
9597

9698
/** Replicate results. */

basex-core/src/test/java/org/basex/query/func/JobModuleTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ public final class JobModuleTest extends SandboxTest {
198198
query(func.args("1 + 2"), 3);
199199
query(func.args("(1 to 1000)[. = 0]"), "");
200200
query(func.args("(1 to 1000)[. = 1]"), 1);
201-
error(func.args("1 + ''"), NONUMBER_X_X);
201+
error(func.args("1 + ''"), CALCTYPE_X_X_X_X_X);
202202
}
203203

204204
/** Test method. */

basex-core/src/test/java/org/basex/query/simple/SimpleTest.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,13 @@ public final class SimpleTest extends QueryTest {
312312
{ "Range 1", integers(100), "count((1 to 10) ! (. to . + 9))" },
313313
{ "Range 2", integers(100), "count((1 to 10) ! (. to . - -9))" },
314314
{ "Range 3", integers(1_000_000_000), "count((1 to 100_000) ! (. to . + 9_999))" },
315+
316+
{ "Arith 1", strings("P6M"), "string(.5 * xs:yearMonthDuration('P1Y'))" },
317+
{ "Arith 2", strings("P6M"), "string(<_>.5</_> * xs:yearMonthDuration('P1Y'))" },
318+
{ "Arith 3", strings("P6M"), "string(xs:yearMonthDuration('P1Y') * .5)" },
319+
{ "Arith 4", strings("P6M"), "string(xs:yearMonthDuration('P1Y') * <_>.5</_>)" },
320+
{ "Arith 5", strings("P2Y"), "string(xs:yearMonthDuration('P1Y') div .5)" },
321+
{ "Arith 6", strings("P2Y"), "string(xs:yearMonthDuration('P1Y') div <_>.5</_>)" },
315322
};
316323
}
317324
}

0 commit comments

Comments
 (0)