Repository navigation
Conversation
The library exposes a typed model of the JCR Query Object Model, the factory that builds it, and the checks that report what Jahia can push down to Lucene. Constructs that Jackrabbit evaluates in memory carry a Slow suffix, so the cost of a query is visible in the code that writes it. The fluent facade and the execution sink follow in the next change.
🦜 Chachalog
|
commit: |
The type generator skipped the Query Object Model, so a module could not call the factory that Jahia hands to its Lucene translation. The blocked entries are now generated, together with the query row types that a count query reads. A subtype only inherits a member that its parent type emitted, so the five execution methods are whitelisted on the parent interface as well. The patch script now fails when one of its search patterns matches no file, which is how five dead patterns went unnoticed.
The fluent facade builds a query, and the sink hands it to Jahia as a Query Object Model. A query carries its own limit, which the execution entry applies before the query runs, so a page costs a page. The type state makes the limit mandatory: a builder reaches execution only after limit() or the explicit unboundedSlow(). The two library entry points accept a built query, and the hook form without a limit is deprecated in favour of one that requires it. Verified against Jahia 8.2.3.2 with the Jackrabbit fork. Three claims the design rested on were wrong and are corrected here: a join returns one row per match rather than deduplicated nodes, LENGTH over an internationalised property matches nothing, and bind variables do fail on this path, which is why the sink inlines them.
📝 Documentation GuidelinesThank you for contributing to our documentation! To ensure your contributions meet our standards, please review these resources:
This comment is posted automatically when changes are detected in the |
A comparison against the query libraries front-end developers use found that the mandatory limit was a compile-time rule only. A JavaScript caller, a cast, or the deprecated hook form all reached execution with no limit. Execution now refuses a query that carries neither a limit nor the explicit unbounded marker, so the rule holds for every caller. The error a developer meets when they forget names the fix, rather than reporting an unassignable string. Diagnostics no longer report an unconditional finding, so an empty list means a clean query. The two costly calls that reported nothing, the unbounded marker and an inner join, now each report one. Full-text search is called fullText, because contains means a substring match in the libraries developers arrive from. Everyday predicates that had to be written by hand are now methods: in, between, startsWith, isNull and notExists. The prefix search escapes the pattern wildcards.
Three spellings reached a JCR session across the query guide, the test module and the hook. The hook used the main resource, which is the page root rather than the node being rendered. All three now read currentNode.getSession(), which is the call the engine already uses to build the server context session.
The prefix search escaped wildcards on the developer's behalf, and no one had run that escaping against a repository. A matrix of probes on Jahia 8.2.3.2 settled seventeen questions about it. The escape holds for the two wildcards and for the escape character itself, so a literal prefix is a promise the engine keeps. It does not hold for a letter or a digit, where Jackrabbit passes the backslash through, so exactly three characters are escaped and no more. The measured capability is a pattern match over the whole value, which is wider than a prefix. The surface now says so: startsWith, endsWith and contains build the pattern for you, and like takes one when you want the wildcards yourself. Full-text search keeps its own name, because it is a different language with stemming. There is no regular expression anywhere in the JCR query surface, and the guide now says that rather than leaving it to be discovered. isNull is removed. The JCR tests whether a property exists, so notExists is the honest name and the only one.
A single-selector query had to name a selector it never refers to by
name, and the callback then destructured that name. The alias now falls
away and the callback receives the reference itself.
from("jnt:news").where((n) => n.isDescendantOf(site)).limit(10)
The query it builds is unchanged, because the model already defaults a
missing selector name to the node type.
A join still names both sides, since a join condition needs two
references to tell apart. Calling it without an alias is a compile
error that says to add one, in the same way the missing limit does.
Every single-selector sample and test moves to the shorter form. The
aliased form keeps its coverage, because it stays supported.
Jahia has two ways to search content and they read different stores. Full text reads the Lucene index, which is lowercased, accent folded and stemmed. A pattern reads the raw stored value. A query written for one and run against the other returns less and reports nothing. The GraphQL nodesByCriteria API names these two the other way round to this builder, so a developer arriving from it writes contains and loses accent folding, stemming and case insensitivity at once. Both methods now name the collision, and the guide carries a translation table. diagnose() now reports a full text expression that cannot parse, which otherwise fails at execution and takes the clauses beside it down. The rules come from measured pairs on 8.2.3.2, so an expression that runs is not reported: a trailing minus is fatal while a minus followed by a space is not. Twenty four tests pin the distinction from both directions: what each method builds, and what each one finds on a live repository.
What
A typed query builder for Jahia JavaScript modules, modeled on
javax.jcr.query.qom.QueryObjectModel.Why
Modules concatenate JCR-SQL2 strings today, with no type safety, no escaping guarantee, and no signal about what a query costs. Two library entry points also run unbounded by default.
The goal is performance by default, so the design rests on two rules. A query binds to the Query Object Model, which is the level Jahia hands to its Lucene translation, and a limit reaches
setLimitbefore the query runs, so a page costs a page. Constructs the query engine evaluates in memory are reachable only through a name that ends inSlow, which puts the cost in the code that writes it.What changed
javascript-modules-library/src/query: the model types, the factory mirroringQueryObjectModelFactory, the fluent facade, the interop sink and the execution entry. A builder reaches execution only afterlimit()or the explicitunboundedSlow(), enforced by the type state and by a runtime check, so the rule holds whatever language the caller wrote.getNodesByJCRQueryanduseJCRQueryaccept a built query, which carries its own limit. String callers keep today's behaviour. TheuseJCRQueryform without a limit is deprecated in favour of one that requires it.256 unit tests and a Cypress suite that runs against a live Jahia.
Verified on a real instance
Run against Jahia 8.2.3.2 with the Jackrabbit fork the design assumes. Measurement corrected the design four times, and each correction is in the code and the documentation:
LENGTHover an internationalised property matches nothing, because the rewrite moves the property to a translation subnode.The pattern surface is named after the measured capability rather than an assumed one:
startsWith,endsWithandcontainsbuild and escape a pattern,liketakes a raw one, andfullTextis the JCR full-text search, which is a different language with stemming. There is no regular expression anywhere in the JCR query surface, and the guide says so.Question for the reviewers
This change removes three entries from the type generator blacklist:
javax.jcr.query.Query,javax.jcr.query.qom.QueryObjectModelandjavax.jcr.query.qom.QueryObjectModelFactory. I could not find what put them there. If a product decision is behind them, say so and the approach changes.Known gap, left open deliberately
Both seams return
JCRNodeWrapper[]for every query, soselect()shapes the statement and a caller still reads properties with string keys. A typed rows seam that carries column aliases into the result type is the natural follow-up. It is out of scope here.Notes
JCR-QUERY-BUILDER-PLAN.mdat the repository root is the specification this was built from, kept for review. It should be removed before merge.