You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Two major feature areas:
• Object mapping — queryAs / queryAsList / executeAs / executeAsList /
JsonValue.as(Class) map query results directly to Java records,
POJOs, and scalars, for both single-source and multi-source JOIN
queries. New MappingSettings + MissingFieldPolicy (IGNORE / FAIL),
new SQL4JsonMappingException. Internal mapper/ package is
JPMS-hidden so the implementation can evolve without API churn.
• JDBC-style parameter binding — ? (positional) and :name (named)
placeholders in WHERE / SELECT / GROUP BY / HAVING / JOIN / function
args / LIMIT / OFFSET, bound via BoundParameters on PreparedQuery
and SQL4JsonEngine. IN-list expansion (IN (?) + collection → N
literals; empty collection → zero-row predicate), LIMIT/OFFSET
validation, new SQL4JsonBindException, LimitsSettings.maxParameters
(default 1024 placeholders per query). Parameterised queries bypass
the engine's result cache.
Notable fix: parameterised CASE WHEN (SearchedCaseWhen) now
substitutes bound values in the embedded CriteriaNode. Previously any
? / :name inside a CASE WHEN condition crashed at execute time with
"ParameterRef reached evaluator".
Internal: ISO date/datetime/instant parsing consolidated into
json.IsoTemporals; registry.DateCoercion delegates. Grammar additions:
POSITIONAL_PARAM, NAMED_PARAM, limitValue nonterminal.
Zero new runtime dependencies. 1753 tests passing.
See CHANGELOG.md for full details.
- Grammar support for `?` (positional) and `:name` (named) placeholders throughout WHERE / SELECT / GROUP BY / HAVING / JOIN / function arguments.
26
+
- Dynamic `LIMIT` / `OFFSET` binding (placeholders accepted in both positions).
27
+
- IN-list expansion: `IN (?)` + collection bound → expanded to N literals; empty collection → zero-row predicate.
28
+
-`LimitsSettings.maxParameters` (default `1024`) — DoS guard against placeholder flooding.
29
+
-`SQL4JsonBindException` (new sealed subclass of `SQL4JsonException`) — surfaced at substitute time for missing / extra / type-mismatch bindings, IN-list overflow, and LIMIT/OFFSET validation failures.
30
+
31
+
### Changed
32
+
-`Sql4jsonSettings` gains a `mapping` component (all existing construction paths preserved via builder).
33
+
- Grammar now recognizes `?` and `:name` as placeholders in value positions.
34
+
-`LimitsSettings` canonical-constructor signature extended (`maxParameters`) — public callers using `.builder()` unaffected.
35
+
-`SQL4JsonEngine` bypasses `QueryResultCache` for parameterized queries.
36
+
- Internal: ISO date/datetime/instant parsing consolidated into `json.IsoTemporals`; `registry.DateCoercion` delegates.
37
+
38
+
### Dependencies
39
+
- No new runtime dependencies — zero-dep philosophy preserved.
Tests use JUnit 5 covering unit tests, integration tests, edge cases, and performance. **Gotcha:** Default `./mvnw test` excludes tests tagged `@Tag("large")` — run `./mvnw test -Plarge-tests` to execute only those. CI runs `spotless:check` then `clean verify` via GitHub Actions (`ci.yml`).
29
36
37
+
**Never run two `./mvnw` invocations in parallel against this repo** — any goal that touches `target/` (including `clean`) races with any other goal reading it and fails with "Failed to delete target". Run maven commands sequentially.
38
+
39
+
**Reliable way to run an isolated Java snippet:** write a throwaway `@Test` under `src/test/java` and run via `-Dtest=`. Ad-hoc `javac -cp ...` against the Maven local repo is brittle (classpath assembly, Windows separator issues) — use the maven harness.
40
+
30
41
## Public API
31
42
32
-
Six usage patterns (all via static methods on `SQL4Json`):
43
+
Nine usage patterns (all via static methods on `SQL4Json`):
-**Sealed types:** Used extensively — sealed interfaces (`JsonValue`, `SqlValue`, `SQL4JsonException`, `Expression`) with record implementations for type-safe pattern matching.
116
-
-**Expression AST:** Sealed `Expression` interface (`ColumnRef`, `ScalarFnCall`, `AggregateFnCall`, `LiteralVal`, `WindowFnCall`, `SimpleCaseWhen`, `SearchedCaseWhen`, `NowRef`) represents all column expressions. `ExpressionEvaluator` is the tree-walking interpreter — used by all pipeline stages, condition handlers, and aggregation. `WindowFnCall` is evaluated by `WindowStage`, not by `ExpressionEvaluator`. Functions nest arbitrarily: `ROUND(AVG(NULLIF(col, 0)), 2)`.
154
+
-**Expression AST:** Sealed `Expression` interface (`ColumnRef`, `ScalarFnCall`, `AggregateFnCall`, `LiteralVal`, `WindowFnCall`, `SimpleCaseWhen`, `SearchedCaseWhen`, `NowRef`, `ParameterRef`) represents all column expressions. `ExpressionEvaluator` is the tree-walking interpreter — used by all pipeline stages, condition handlers, and aggregation. `WindowFnCall` is evaluated by `WindowStage`, not by `ExpressionEvaluator`. Functions nest arbitrarily: `ROUND(AVG(NULLIF(col, 0)), 2)`.
155
+
-**AST cross-hierarchy trap:**`Expression` and `CriteriaNode` are two separate sealed hierarchies that intersect inside `SearchedCaseWhen.SearchWhen` (it holds both a `CriteriaNode` condition and an `Expression` result). Any transformation or analysis that walks `Expression` must explicitly descend into the embedded `CriteriaNode` too — recursing only through the expression branches silently misses WHEN conditions.
156
+
-**Adding a new sealed variant is cross-cutting:** a new `Expression` / `CriteriaNode` / `SqlValue` / `JsonValue` subtype requires updating every exhaustive `switch` on the parent. Grep the sealed parent name before adding.
117
157
-**JSON flattening:** Core mechanism. Nested JSON is flattened to `Map<FieldKey, Object>` for processing, then unflattened for output. `FieldKey` tracks the "family" (base path) for nested field grouping.
118
158
-**ANTLR generated code:** Located in `target/generated-sources/antlr4/`. Never edit generated files — modify `SQL4Json.g4` in `src/main/antlr4/` instead. Grammar changes require a rebuild (`mvn clean compile`).
119
-
-**Settings subsections:**`Sql4jsonSettings` is composed of four immutable record subsections (`security`, `limits`, `cache`, `codec`). Customize via `Sql4jsonSettings.builder().limits(l -> l.maxRowsPerQuery(N)).build()`. `Sql4jsonSettings.defaults()` is a JVM-wide shared singleton — all no-settings API calls land on it so the `BoundedPatternCache` and derived `ConditionHandlerRegistry` are shared.
159
+
-**Settings subsections:**`Sql4jsonSettings` is composed of five immutable record subsections (`security`, `limits`, `cache`, `mapping`, `codec`). Customize via `Sql4jsonSettings.builder().limits(l -> l.maxRowsPerQuery(N)).build()`. `Sql4jsonSettings.defaults()` is a JVM-wide shared singleton — all no-settings API calls land on it so the `BoundedPatternCache` and derived `ConditionHandlerRegistry` are shared.
120
160
-**JSON codec limits:** Built-in JSON parser limits live in `DefaultJsonCodecSettings` (in the `settings` package, not `json`). Customize via `Sql4jsonSettings.builder().codec(new DefaultJsonCodec(DefaultJsonCodecSettings.builder()...)).build()`.
121
161
-**Registry lifetime:**`ConditionHandlerRegistry.forSettings(settings)` is the single entry point and internally caches one registry per distinct `cache.likePatternCacheSize` — in practice one per JVM, since nearly every caller uses defaults. It shares a `BoundedPatternCache` between `LikeConditionHandler` and `NotLikeConditionHandler` (both package-private inside the `registry` package).
122
162
-**Functional style:** Heavy use of Java streams, `BiPredicate`, `Function`, `Supplier`, `Optional`
123
163
-**Spotless:** Code formatter configured — removes unused imports, trims trailing whitespace, ensures final newline. Run `./mvnw spotless:apply` before committing.
124
164
-**Surefire config:** Tests run with `--add-modules=java.management` and `--add-reads=io.github.mnesimiyilmaz.sql4json=java.management` JVM flags, plus `-Xmx8g -Xms1g`.
125
165
166
+
## Code-Quality Guidelines (SonarQube-enforced)
167
+
168
+
The repo is scanned by SonarQube; write new code with these general principles in mind so findings don't accumulate.
169
+
170
+
-**Prefer record patterns over pattern variables.** When matching a record with `instanceof`, deconstruct its components directly rather than binding a variable and calling accessors on it. Java 21 record patterns are the default style here.
171
+
-**Empty method bodies need a nested comment.** Javadoc above the signature doesn't satisfy the rule — put a short `//` inside the body stating why it's empty. Applies to constructors too.
172
+
-**Keep cognitive complexity low.** Long `if/else` chains and deeply nested branches should be broken into small focused helpers. Reach for suppression only when extraction would genuinely hurt readability.
173
+
-**Suppress rules sparingly, and only with a reason.** Use the narrowest scope (declaration or method, not whole class unless the rule is class-level) and add a one-line comment above the annotation stating why the suppression is correct.
174
+
-**Use idiomatic Java 21 APIs.** Prefer modern sequenced-collection and convenience methods (`getFirst()`, `getLast()`, `reversed()`, `addFirst/Last`, etc.) over positional index arithmetic like `list.get(0)` or `list.get(list.size() - 1)`. Similarly, lean on switch expressions, pattern matching, `var`, and `List.of`/`Map.of` where they improve clarity.
175
+
-**No dangling Javadoc.** Don't place `/** ... */` blocks where the Javadoc tool won't attach them — most commonly on individual record components inline in the header. Document components via `@param` tags on the record's top-level Javadoc instead. The same applies to local variables, statements, and any other position that isn't a declaration.
176
+
177
+
## Development Workflow
178
+
179
+
-**Javadoc is mandatory:** Every new development (public types, methods, fields) must include a Java API doc comment. Do not skip this step — write Javadoc as part of the implementation, not as a follow-up.
180
+
-**Test placement:** Before creating a new test class, look for an existing test class where the new tests logically belong and add them there. Only create a new test file when no appropriate existing class exists — and state the reason explicitly in the response.
181
+
-**`@since` on public API:** Any new public API addition must carry an `@since` Javadoc tag with the version number the change ships in. If the user has not specified a version number, ask before adding the tag. If the user declines to specify one, omit the `@since` tag rather than guessing.
182
+
-**Tests must assert, not log:** a `try { ... } catch (Throwable t) { System.out.println(t); }` test passes silently even when the code throws. For "must not throw" / "must return X" behavior, assert on the success path so failure is a red CI; use `assertThrows` only when the throw is the contract.
-**Window functions:**`ROW_NUMBER()`, `RANK()`, `DENSE_RANK()`, `NTILE(n)`, `LAG(col [, offset])`, `LEAD(col [, offset])` with `OVER (PARTITION BY ... ORDER BY ...)`. Aggregate functions (`SUM`, `AVG`, `COUNT`, `MIN`, `MAX`) also work as window functions with `OVER`. Window functions are only valid in SELECT and do not collapse rows (unlike GROUP BY). Evaluated by `WindowStage` (materializing, runs after HAVING, before ORDER BY).
140
198
-`CASE` expressions: simple (`CASE expr WHEN val THEN result END`) and searched (`CASE WHEN condition THEN result END`), fully nestable in SELECT, WHERE, ORDER BY, GROUP BY, HAVING
-**Parameters:**`?` (positional) and `:name` (named) placeholders in WHERE / SELECT / GROUP BY / HAVING / function args / LIMIT / OFFSET. Bound via `BoundParameters` on `PreparedQuery.execute(...)` or `SQL4JsonEngine.query(...)`. Cannot mix `?` and `:name` in the same query (parse-time error). IN-list expansion: `IN (?)` + collection bind expands to N literals; empty collection → zero-row predicate. Null bind produces SQL-standard `col = NULL` (always false) — use literal `IS NULL` / `IS NOT NULL` for nullability tests (cannot be parameterized). `SQL4JsonEngine` bypasses result cache for parameterized queries. `LimitsSettings.maxParameters` (default 1024) caps total placeholder count per query.
0 commit comments