Flattens predicates for boolean evaluation. Drops solitary context values, flattens nested predicates. @param root root expression @param ebv EBV check @param cc compilation context @return optimized or original expression @throws QueryException query exception
(final Expr root, final boolean ebv, final CompileContext cc)
| 238 | * @throws QueryException query exception |
| 239 | */ |
| 240 | public final Expr flattenEbv(final Expr root, final boolean ebv, final CompileContext cc) |
| 241 | throws QueryException { |
| 242 | |
| 243 | // only single predicate can be rewritten; root must yield nodes; no positional predicates |
| 244 | final SeqType rst = root.seqType(); |
| 245 | final int el = exprs.length; |
| 246 | if(el == 0 || mayBePositional()) return this; |
| 247 | |
| 248 | final Expr last = exprs[el - 1]; |
| 249 | final QuerySupplier<Expr> createRoot = () -> |
| 250 | root instanceof final Path path ? path.removePredicate(cc) : |
| 251 | el > 1 ? Filter.get(cc, info, root, Arrays.copyOfRange(exprs, 0, el - 1)) : root; |
| 252 | final QueryFunction<Expr, Expr> createSimpleMap = rhs -> |
| 253 | SimpleMap.get(cc, info, createRoot.get(), rhs); |
| 254 | final QueryBiFunction<Expr, Boolean, Expr> createExpr = (rhs, compare) -> |
| 255 | rhs instanceof ContextValue ? createRoot.get() : |
| 256 | rhs instanceof Path ? Path.get(cc, info, createRoot.get(), rhs) : |
| 257 | compare ? createSimpleMap.apply(rhs) : this; |
| 258 | |
| 259 | if(rst.type instanceof NodeType) { |
| 260 | // rewrite to path: root[path] → root/path |
| 261 | final Expr expr = createExpr.apply(last, false); |
| 262 | if(expr != this) return expr; |
| 263 | } else if(ebv) { |
| 264 | return this; |
| 265 | } |
| 266 | |
| 267 | // rewrite to general comparison |
| 268 | // a[. = 'x'] → a = 'x' |
| 269 | // a[@id eq 'id1'] → a/@id = 'id1' |
| 270 | // a[text() = data(.)] → skip: right operand must not depend on context |
| 271 | // a[b eq ('a', 'b')] → skip: an error must be raised as right operand yields a sequence |
| 272 | if(last instanceof final Cmp cmp) { |
| 273 | final Expr op1 = cmp.exprs[0], op2 = cmp.exprs[1]; |
| 274 | final SeqType st1 = op1.seqType(), st2 = op2.seqType(); |
| 275 | final Type type1 = st1.type, type2 = st2.type; |
| 276 | if((cmp instanceof CmpG || cmp instanceof CmpV && st1.zeroOrOne() && st2.zeroOrOne() && ( |
| 277 | type1 == type2 || type1.isStringOrUntyped() && type2.isStringOrUntyped() |
| 278 | )) && !op2.has(Flag.CTX)) { |
| 279 | final Expr expr = createExpr.apply(op1, true); |
| 280 | if(expr != this) return new CmpG(cmp.info, expr, op2, cmp.cmpOp()).optimize(cc); |
| 281 | } |
| 282 | } |
| 283 | |
| 284 | // rewrite to contains text expression (right operand must not depend on context): |
| 285 | // a[. contains text 'x'] → a contains text 'x' |
| 286 | // a[text() contains text 'x'] → a/text() contains text 'x' |
| 287 | if(last instanceof final FTContains cmp) { |
| 288 | final FTExpr ftexpr = cmp.ftexpr; |
| 289 | if(!ftexpr.has(Flag.CTX)) { |
| 290 | final Expr expr = createExpr.apply(cmp.expr, true); |
| 291 | if(expr != this) return new FTContains(expr, ftexpr, cmp.info).optimize(cc); |
| 292 | } |
| 293 | } |
| 294 | |
| 295 | // rewrite to simple map: $node[string()] → $node ! string() |
| 296 | if(rst.zeroOrOne()) return createSimpleMap.apply(last); |
| 297 |