Skip to content

Commit e98ddcb

Browse files
committed
Add resource storage footprint analysis and update Janus-QL grammar audit
1 parent cc3b58d commit e98ddcb

10 files changed

Lines changed: 419 additions & 7 deletions

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,6 @@ tests/user_query_repro.rs
4444
/logs/
4545
experiments/benchmark-results/
4646
/results/storage_footprint*/
47+
*.pdf
48+
*.csv
49+
*.png
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# Janus-QL Grammar Audit
2+
3+
Date: 2026-07-09
4+
5+
This note compares the Janus-QL Core EBNF in `/Users/kushbisen/Code/janusql-spec/spec-src/sections/03a-core-grammar.bs` with the current implementation in `/Users/kushbisen/Code/janus/src/parsing/janusql_parser/`.
6+
7+
## Verdict
8+
9+
The EBNF is broadly faithful for the public Janus-QL Core window model:
10+
11+
- `PREFIX` declarations are parsed by `parse_prefix_declaration()`.
12+
- `REGISTER RStream ... AS` is optional and enforced by `parse_register_clause()`.
13+
- `FROM NAMED WINDOW` declarations are split into `ON STREAM [RANGE ... STEP ...]`, `ON LOG [START ... END]`, and `ON LOG [OFFSET ... RANGE ... STEP ...]` by `parse_window_clause()`.
14+
- `WINDOW <name> { ... }` references are extracted from `WHERE` and checked against declared windows by `validate_where_window_references()`.
15+
- Historical-only nested subqueries are identified in `extract_nested_subqueries()` and restricted during subquery planning in `plan_nested_subqueries()` and `validate_subquery_plan()`.
16+
- Top-level `GROUP BY` and `HAVING` are preserved in the AST and in generated historical SPARQL.
17+
18+
The main mismatch is that the implementation is looser than the EBNF around `SELECT` items and `WINDOW` bodies:
19+
20+
- `SELECT` clauses are stored as text. The parser does not fully parse projection items against a strict `Var | (Expression AS Var)` grammar.
21+
- `WINDOW` bodies are also stored as text. The parser does not fully parse triple patterns or `FILTER` expressions. It performs only shallow Janus-specific checks such as undeclared windows, `SERVICE` rejection, and property-path rejection.
22+
- As a result, the EBNF is a conservative public contract, but the implementation may accept some extra SPARQL-like shapes as opaque body text.
23+
24+
## Syntax vs Semantic Validation
25+
26+
The current implementation splits enforcement across phases.
27+
28+
Rejected during parsing:
29+
30+
- `ON STREAM [START ... END]`
31+
- `ON STREAM [OFFSET ... RANGE ... STEP ...]`
32+
- `ON LOG [RANGE ... STEP ...]`
33+
- unsupported `REGISTER` operators such as `IStream` and `DStream`
34+
35+
Rejected during later validation or planning:
36+
37+
- duplicate window names
38+
- `RANGE == 0` or `STEP == 0`
39+
- `START >= END`
40+
- historical sliding `RANGE > OFFSET`
41+
- undeclared `WINDOW` references
42+
- nested subqueries with no known `WINDOW`
43+
- nested subqueries that reference live windows only
44+
- nested subqueries that mix live and historical windows
45+
46+
This distinction matters for the specification text: some invalid combinations are not merely semantic constraints over a permissive grammar; they are already rejected by the parser based on the `ON STREAM` versus `ON LOG` token sequence.
47+
48+
## Construct Coverage
49+
50+
The implementation and tests currently cover the requested constructs as follows:
51+
52+
- `PREFIX` declarations: implemented and tested.
53+
- optional `REGISTER RStream`: implemented and tested, including historical-only queries without `REGISTER`.
54+
- `SELECT` with variables and aliased expressions: implemented as text-preserving parsing with output-variable extraction; tested with variables and aggregate aliases.
55+
- `FROM NAMED WINDOW`: implemented and tested.
56+
- `ON STREAM` with `RANGE/STEP`: implemented and tested.
57+
- `ON LOG` with fixed `START/END`: implemented and tested.
58+
- `ON LOG` with sliding `OFFSET/RANGE/STEP`: implemented and tested.
59+
- `WHERE` clauses with `WINDOW` blocks: implemented and tested.
60+
- triple patterns: passed through inside `WINDOW` bodies; not structurally parsed by Janus.
61+
- `FILTER` expressions: passed through inside `WINDOW` bodies; not structurally parsed beyond Janus-specific exclusions.
62+
- historical nested `SELECT` subqueries: implemented and tested.
63+
- `GROUP BY` and `HAVING`: implemented and tested for top-level historical queries and nested historical subqueries.
64+
65+
## SPARQL-Inherited Terminals
66+
67+
The specification says Janus-QL Core inherits SPARQL-style lexical forms for `IRIREF`, `PrefixedName`, `Literal`, `Name`, `PrefixName`, and `Var`.
68+
69+
That is directionally true, but the current implementation does not contain a full SPARQL lexer for those terminals. In practice:
70+
71+
- Janus parses the Janus-specific top-level clauses itself.
72+
- prefixed names and `<iri>` forms are handled by lightweight token splitting and prefix expansion.
73+
- triple-pattern terms and most expression text are forwarded as SPARQL-like text rather than fully validated by the Janus parser.
74+
75+
## Recommended Specification Changes
76+
77+
- Keep the current EBNF split between `ON STREAM` and `ON LOG`; it matches implementation behavior.
78+
- Add an explicit note near the EBNF that `SELECT` items, triple patterns, and `FILTER` expressions are specified conservatively, while the current implementation uses shallow parsing and may accept extension syntax as opaque SPARQL-like text.
79+
- In the invalid examples section, add explicit examples for:
80+
- `ON STREAM [START ... END]`
81+
- `ON STREAM [OFFSET ... RANGE ... STEP ...]`
82+
- `ON LOG [RANGE ... STEP ...]`
83+
- Keep the nested-subquery rule outside the pure EBNF as a validation/planning rule: the syntax shape is parseable, but live-only and mixed nested subqueries are rejected semantically after window-dependency analysis.
84+
85+
## Local Test Updates
86+
87+
This repo now has explicit public-spec regression tests for:
88+
89+
- invalid `ON STREAM [START ... END]`
90+
- invalid `ON STREAM [OFFSET ... RANGE ... STEP ...]`
91+
- invalid `ON LOG [RANGE ... STEP ...]`
92+
93+
Those were added to `/Users/kushbisen/Code/janus/tests/public_spec_behavior_test.rs`.
-118 Bytes
Binary file not shown.
2.54 KB
Loading
-971 Bytes
Binary file not shown.
1.25 KB
Loading

plot_historical_access_latency.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@
5151
QUERY_ORDER = ["Point", "Fixed 60s", "Range 10%", "Range 50%", "Range 100%"]
5252
SYSTEM_ORDER = ["Janus", "Oxigraph"]
5353
SIZE_ORDER = ["10k", "50k", "100k", "500k", "1M"]
54+
SYSTEM_DISPLAY_NAMES = {
55+
"Janus": "Janus",
56+
"Oxigraph": "Decomposed Baseline",
57+
}
5458

5559

5660
def clean_latex_label(text: str) -> str:
@@ -257,12 +261,12 @@ def plot_shared_yaxis_figure(
257261
linewidth=1.2,
258262
markersize=3.5,
259263
capsize=2,
260-
label=system,
264+
label=SYSTEM_DISPLAY_NAMES[system],
261265
)
262266

263267
if query == QUERY_ORDER[0]:
264268
handles.append(handle)
265-
labels.append(system)
269+
labels.append(SYSTEM_DISPLAY_NAMES[system])
266270

267271
ax.set_title(query, fontsize=9)
268272
ax.set_yscale("log")

plot_memory_cpu_1m_median_local.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212

1313
QUERY_ORDER = ["Point", "60s", "10%", "50%", "100%"]
1414
SYSTEM_STYLES = [
15-
("Janus", "o", "-"),
16-
("Oxigraph", "s", "--"),
15+
("Janus", "Janus", "o", "-"),
16+
("Oxigraph", "Decomposed Baseline", "s", "--"),
1717
]
1818

1919

@@ -63,8 +63,8 @@ def main() -> None:
6363
]
6464

6565
for ax, (metric, ylabel, title) in zip(axes, panels):
66-
for system, marker, linestyle in SYSTEM_STYLES:
67-
subset = ordered_subset(df, system)
66+
for system_key, system_label, marker, linestyle in SYSTEM_STYLES:
67+
subset = ordered_subset(df, system_key)
6868

6969
ax.plot(
7070
x,
@@ -73,7 +73,7 @@ def main() -> None:
7373
linestyle=linestyle,
7474
linewidth=1.2,
7575
markersize=3.5,
76-
label=system,
76+
label=system_label,
7777
)
7878

7979
ax.set_title(title, fontsize=8)

0 commit comments

Comments
 (0)