Optimizes this expression as predicate. @param cc compilation context @return resulting expression @throws QueryException query exception
(final CompileContext cc)
| 326 | * @throws QueryException query exception |
| 327 | */ |
| 328 | private Expr optPred(final CompileContext cc) throws QueryException { |
| 329 | final Value val = cc.qc.focus.value; |
| 330 | if(val == null) return this; |
| 331 | |
| 332 | final Expr expr1 = exprs[0], expr2 = exprs[1]; |
| 333 | if(val.seqType().type instanceof final NodeType nt && nt.kind() != Kind.NODE && |
| 334 | expr1 instanceof final ContextFn fn && |
| 335 | expr2 instanceof final Value value && op == CmpOp.EQ) { |
| 336 | // skip functions that do not refer to the current context value |
| 337 | if(fn.exprs.length > 0 && !(fn.exprs[0] instanceof ContextValue)) return this; |
| 338 | |
| 339 | final ArrayList<QNm> qnames = new ArrayList<>(); |
| 340 | Scope scope = null; |
| 341 | if(expr2.seqType().type.isStringOrUntyped()) { |
| 342 | // local-name() eq 'a' → self::*:a |
| 343 | if(LOCAL_NAME.is(fn)) { |
| 344 | scope = Scope.LOCAL; |
| 345 | for(final Item item : value) { |
| 346 | final byte[] name = item.string(info); |
| 347 | if(XMLToken.isNCName(name)) qnames.add(new QNm(name)); |
| 348 | } |
| 349 | } else if(NAMESPACE_URI.is(fn)) { |
| 350 | // namespace-uri() = ('URI1', 'URI2') → self::Q{URI1}* | self::Q{URI2}* |
| 351 | for(final Item item : value) { |
| 352 | final byte[] uri = item.string(info); |
| 353 | if(Token.eq(Token.normalize(uri), uri)) qnames.add(new QNm(Token.cpToken(':'), uri)); |
| 354 | } |
| 355 | if(qnames.size() == value.size()) scope = Scope.URI; |
| 356 | } else if(NAME.is(fn)) { |
| 357 | // (db-without-ns)[name() = 'city'] → (db-without-ns)[self::city] |
| 358 | final Data data = cc.qc.focus.value.data(); |
| 359 | final byte[] dataNs = data != null ? data.defaultNs() : null; |
| 360 | if(dataNs != null && dataNs.length == 0) { |
| 361 | scope = Scope.LOCAL; |
| 362 | for(final Item item : value) { |
| 363 | final byte[] name = item.string(info); |
| 364 | if(XMLToken.isNCName(name)) qnames.add(new QNm(name)); |
| 365 | } |
| 366 | } |
| 367 | } |
| 368 | } else if(NODE_NAME.is(fn) && expr2.seqType().type == BasicType.QNAME) { |
| 369 | // node-name() = #prefix:local → self::prefix:local |
| 370 | scope = NameTest.Scope.FULL; |
| 371 | for(final Item item : value) { |
| 372 | qnames.add((QNm) item); |
| 373 | } |
| 374 | } |
| 375 | |
| 376 | if(scope != null) { |
| 377 | final ExprList paths = new ExprList(2); |
| 378 | for(final QNm qname : qnames) { |
| 379 | final Test test = Test.get(nt.kind(), qname, scope, sc().elemNS); |
| 380 | final Expr step = Step.self(cc, null, info, test); |
| 381 | if(step != Empty.VALUE) paths.add(Path.get(cc, info, null, step)); |
| 382 | } |
| 383 | return paths.isEmpty() ? Bln.FALSE : paths.size() == 1 ? paths.get(0) : |
| 384 | new Union(info, paths.finish()).optimize(cc); |
| 385 | } |
no test coverage detected