[refactor] Always sort internal-module FunctionDef[]; drop functionsOrdered flag (#6378)#6384
Conversation
|
we should audit our org to see if any custom extensions where using the 3 arg version |
|
[This response was co-authored with Claude Code. -Joe] Org-wide audit done via
None of these carry a 13 other external repos that extend All static-sort blocks in the org are inside this repo — the five removed in this PR ( Coordination options for the 6 affected modules:
My lean is (1) or (2) — the change is trivial, doing it sooner avoids forcing external maintainers to scramble. Happy to take on drafting the 6 PRs. |
|
1 the sooner we open the PR the better |
|
[This response was co-authored with Claude Code. -Joe] Companion PRs open across the 5 affected external repos (Option 1 per your preference):
Each is the same one-line change: drop the third argument from Expected behavior: each companion PR will fail CI until this PR merges and a fresh Once this PR merges, the 5 companions are ready to follow. |
|
@line-o curious to hear what you think. I d like to see this breaking in the beta so that folks have a chance to catch it. I also think it ll solve a whole class of related downstream bugs. |
|
Agreed. We missed the Chance to deprecate this in the last 6.4.1 release. Next best thing is to allow to discover it now. |
|
@line-o because this is mainly a bug fix. The ability to work with unsorted values should never have been there. 3arg true is the new default. |
line-o
left a comment
There was a problem hiding this comment.
Please reinstate a 3-argument constructor and mark it as deprecated.
The third parameter is ignored and all functions will always be sorted.
…recate functionsOrdered flag Closes eXist-db#6378 The functionsOrdered=true constructor flag opted modules into binary search at the cost of an unenforced precondition: callers had to pass a FunctionDef[] already sorted by FunctionId order. Five first-party modules learned to satisfy the contract via boilerplate static blocks (`static { Arrays.sort(functions, new FunctionComparator()); }`). Third-party modules unaware of the convention (e.g. existdb-openapi's cursor module, see eXist-db#6376) silently lost function-lookup reachability when their declaration order happened to violate the binary-search contract. Per duncdrum's preference on eXist-db#6378, the broken-when-unsorted behavior is removed. Per line-o's review of eXist-db#6384, the 3-arg constructor is retained as @deprecated rather than removed outright, so external modules continue to compile against eXist 7 without source changes — just a deprecation warning. Changes to AbstractInternalModule.java: - 2-arg constructor (FunctionDef[], Map) now defensive-copies + sorts the array. The caller's static final array is left intact. - 3-arg constructor (FunctionDef[], Map, boolean) marked @deprecated (since="7.0.0", forRemoval=true). The boolean parameter is ignored; the constructor delegates to the 2-arg form. - Remove the `ordered` field and the `if (ordered)` branch in getFunctionDef(); binary search is now unconditional. - Reorder field declarations above the FunctionComparator inner class per PMD FieldDeclarationsShouldBeAtStartOfClass. Sort cost: O(N log N) once per module instance construction; trivial at typical module sizes (eXist's largest is FnModule with ~500 functions, sorted in microseconds). Tests (AbstractInternalModuleSortTest): - unsortedDeclarationOrderStillFindsAllFunctions — the eXist-db#6376 reproducer, now safe by construction - callerArrayIsNotMutated — verifies the defensive copy - alreadySortedArrayFindsAllFunctions — common-case sanity - sameQnameDifferentAritiesAllFound — secondary sort key honored - deprecatedThreeArgConstructorStillWorks — backwards-compat: an external module compiled against pre-eXist-db#6378 eXist that still uses super(functions, parameters, true|false) compiles and produces a functioning module via the @deprecated delegating constructor Caller updates and removal of redundant static-sort blocks ship in the follow-up commit in the same PR. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…uctors Follow-up to the AbstractInternalModule deprecation (eXist-db#6378). The boolean third argument has no effect — the base class always sorts. 20 callers switch to the cleaner 2-arg form so the codebase doesn't keep emitting deprecation warnings against itself, and the 5 modules that carried a `static { Arrays.sort(functions, new FunctionComparator()); }` block no longer need it (the base class does this work); remove the blocks and the now-unused java.util.Arrays imports. Callers updated (20 files): - exist-core: BackupModule, FnModule, InspectionModule, RequestModule, UtilModule, XMLDBModule, ArrayModule, MapModule, WebSocketModule, ConsoleCompatModule. - extensions: ContentExtractionModule, ExiftoolModule, XQDocModule, CounterModule, ProcessModule, VectorModule, LuceneModule, SortModule, NGramModule, RangeIndexModule. Static-sort blocks removed (5 modules): XMLDBModule, UtilModule, RequestModule, FnModule, CounterModule. External modules compiled against pre-eXist-db#6378 eXist that still use the deprecated 3-arg constructor will continue to compile against eXist 7 with a deprecation warning, no source changes required. Net: -34 lines of boilerplate across the codebase. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2c19768 to
95a94d6
Compare
|
[This response was co-authored with Claude Code. -Joe] Thanks @line-o — done in External modules compiled against pre-#6378 eXist that still use The internal callers (20 first-party modules) still drop the spurious third arg in the second commit of this PR — they don't need the deprecation path since they're in the same repo. Keeps the codebase from emitting deprecation warnings against itself. The 5 external companion PRs we opened ( |
line-o
left a comment
There was a problem hiding this comment.
Thanks for the change, I believe that will make our life easier.
…onstructor eXist-db/exist#6378 (PR eXist-db/exist#6384) removes the 3-arg `AbstractInternalModule(FunctionDef[], Map, boolean)` constructor. The 2-arg form is now the only path; the base class always sorts the function table and always binary-searches. This commit drops the now-removed third argument. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…onstructor eXist-db/exist#6378 (PR eXist-db/exist#6384) removes the 3-arg `AbstractInternalModule(FunctionDef[], Map, boolean)` constructor. The 2-arg form is now the only path; the base class always sorts the function table and always binary-searches. This commit drops the now-removed third argument. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
Closes #6378. Implements Option 2 per @duncdrum's preference on the issue thread:
The
functionsOrdered=trueopt-in flag onAbstractInternalModule's 3-arg constructor was an unenforced precondition: callers had to pass aFunctionDef[]already sorted byFunctionIdorder, or binary search ingetFunctionDef()would silently miss entries. The footgun manifested as a real bug in #6376 (cursor:closeunreachable viafunction-lookupbut visible viautil:registered-functions).The fix removes the flag entirely. The base class always sorts (defensive-copy +
Arrays.sortin the constructor) and always uses binary search. Five first-party modules that previously carriedstatic { Arrays.sort(functions, new FunctionComparator()); }boilerplate no longer need it.What changed
Commit 1 — base class (
AbstractInternalModule.java, +20 / -24):(FunctionDef[], Map, boolean)constructor.static finalarray left intact).orderedfield and the conditional ingetFunctionDef(); binary search is unconditional.FunctionComparatorinner class (PMDFieldDeclarationsShouldBeAtStartOfClass).Commit 2 — 20 callers + 5 static-sort cleanups (+167 / -50):
Callers updated (drop the third argument):
BackupModule,FnModule,InspectionModule,RequestModule,UtilModule,XMLDBModule,ContentExtractionModule,ExiftoolModule,XQDocModule,CounterModule,ProcessModulesuper(functions, parameters, true)ArrayModule,MapModule,WebSocketModule,ConsoleCompatModule,VectorModule,LuceneModule,SortModule,NGramModule,RangeIndexModulesuper(functions, parameters, false)Static-sort blocks removed (
static { Arrays.sort(functions, new FunctionComparator()); }) fromXMLDBModule,UtilModule,RequestModule,FnModule,CounterModule— plus the now-unusedjava.util.Arraysimports.Net: ~34 lines of boilerplate gone from the codebase.
Regression test (
AbstractInternalModuleSortTest, new — 4 cases):unsortedDeclarationOrderStillFindsAllFunctions— the [7.0-SNAPSHOT regression] Calling pkg:close() fails with err:XPST0017 even when signature is correctly registered #6376 reproducer, now safe by construction.callerArrayIsNotMutated— verifies the defensive copy.alreadySortedArrayFindsAllFunctions— common-case sanity.sameQnameDifferentAritiesAllFound— secondary sort key (arity) is honored.API break
The 3-arg
AbstractInternalModule(FunctionDef[], Map<String, List<?>>, boolean)constructor goes away. Any third-party module passingsuper(functions, parameters, true|false)needs to switch tosuper(functions, parameters). One-line change per module. TheFunctionComparatorstatic inner class remains public for any code that uses it elsewhere.@duncdrum explicitly accepted this trade-off on the issue: removing the flag is what makes the bug structurally impossible.
Performance
The sort is O(N log N) once per module instance construction. eXist's largest module is
FnModule(~500 functions), sorted in microseconds.AbstractInternalModuleis instantiated perXQueryContext, which is per-query — so the sort runs per query. For comparison, the previousstatic { Arrays.sort(...) }blocks ran once at class load. The cost is small but real; happy to memoize per-class if reviewers want to eliminate it entirely (one-time check viaConcurrentMap<Class<?>, Boolean>).Test plan
mvn test -pl exist-core -Dtest=AbstractInternalModuleSortTest— 4/4 passmvn compileacross exist-core + extensions (minus exist-xqts, which has unrelated GitHub Packages auth issue locally) — greenRelated
functionsOrdered=trueinvariant is trusted but not enforced; silent function-lookup failure if array is unsorted #6378 — this PR closes itfunctionsOrdered=trueinvariant is trusted but not enforced; silent function-lookup failure if array is unsorted #6378 + this approach.