Skip to content

feat(grammar): add ANTLR4 grammar for OSSIE_SQL_2026 expressions - #657

Merged
jbonofre merged 4 commits into
apache:mainfrom
j0hannesz:feat/antlr4-expression-grammar
Nov 12, 2026
Merged

jbonofre merged 4 commits into
apache:mainfrom
j0hannesz:feat/antlr4-expression-grammar

Conversation

@j0hannesz

Copy link
Copy Markdown
Contributor

Summary

Adds an ANTLR 4 reference grammar for the OSSIE_SQL_2026 expression language.

  • core-spec/grammar/OssieExpression.g4 is the grammar.
  • compliance/expressions/valid.txt and invalid.txt are language-neutral conformance fixtures (one expression per line), so any parser can run them.
  • core-spec/grammar/tests/ has a pytest runner. It generates the Python parser, runs both fixture files, parses every ANSI_SQL / OSSIE_SQL_2026 expression in examples/*.yaml, and pins operator precedence on the parse tree.
  • .github/workflows/grammar-ci.yml runs the tests (Python 3.11 and 3.14).
  • core-spec/expression_language.md gets a short pointer to the grammar at the end. The spec stays normative; where the two differ, the spec wins.

Layout and approach follow JB's review.

Open points for the spec (not decided here)

  • || precedence: the grammar parses it at the level of + and -, so a || b + c is (a || b) + c. sqlglot and PostgreSQL parse it as a || (b + c).
  • Identifiers: the grammar is ASCII-only and allows $.
  • LIKE ... ESCAPE is not supported, as the spec does not mention it.
  • Comparisons do not chain (a = b = c needs parentheses).

The grammar follows whatever the spec decides on these.

Dependencies

No new runtime dependency. The tests use the ANTLR 4.13.2 tool (downloaded from Maven Central into the git-ignored dev/lib/, verified against a pinned SHA-256) and antlr4-python3-runtime 4.13.2, both BSD-3-Clause (ASF Category A), plus pytest and pyyaml.

Related Issues

https://lists.apache.org/thread/s5x6lo5gvxhyowb4pcohtplnly5rmgtm

Supported by Jean-Baptiste Onofré.

Checklist

Specification

  • Spec changes are included in core-spec/ and follow the existing structure
  • Spec changes have been discussed on the mailing list or in a linked issue
  • Breaking changes to the spec are clearly called out in the summary

Ontology

  • Ontology changes in ontology/ are consistent with spec changes
  • New or modified terms are defined and documented

Converters

Validation

  • Validation rules in validation/ are updated if the spec changed
  • New validation cases are covered by tests

Documentation

  • docs/ is updated to reflect any user-facing changes
  • New features or behaviors are documented with examples where appropriate
  • CONTRIBUTING.md is updated if the contribution process changed

Examples

  • examples/ are added or updated for any new spec constructs

Tests

  • All existing tests pass (pytest / CI green)
  • New functionality is covered by tests

Compliance

  • ASF license headers are present on all new source files
  • No third-party dependencies are added without PMC/IPMC approval
  • If third-party source code is vendored/copied (not just declared as a dependency), NOTICE and/or LICENSE have been updated per ASF policy

@github-actions

Copy link
Copy Markdown

Thank you for your first contribution to Apache Ossie (incubating)!

A committer will review your PR. In the meantime, please ensure:

  • All CI checks pass
  • ASF license headers are present on new source files
  • Tests cover new functionality

No ICLA is required to contribute. If you are elected as a committer in the future, you will need an Individual Contributor License Agreement (ICLA) on file with the ASF.

Questions? Ask in the PR comments or join us on Slack.

We appreciate your contribution!

Johannes Zemlin and others added 3 commits November 2, 2026 17:47
Add grammar/OssieExpression.g4, an ANTLR 4 grammar for the expression
language in core-spec/expression_language.md, with Python tests that
parse valid/invalid fixtures, the expressions in examples/, and check
operator precedence. Add a grammar CI workflow, a README, a label rule,
an .editorconfig rule for .g4 files, and a pointer from the spec.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Move the ANTLR grammar next to the spec (core-spec/grammar/) and the
valid/invalid expression fixtures to compliance/expressions/ so any parser
can run them. Require at least one argument after DISTINCT in function
calls, and add SUM(DISTINCT) and COUNT(DISTINCT) to the invalid fixtures.
Drop the stale NOT precedence note from the grammar header, update CI
paths, labels and links, and ignore generated ANTLR output.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@jbonofre
jbonofre force-pushed the feat/antlr4-expression-grammar branch from 0be85e1 to 6698f97 Compare November 2, 2026 16:59
jbonofre
jbonofre previously approved these changes Nov 2, 2026

@jbonofre jbonofre left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this, the grammar and the shared compliance fixtures are a useful addition.
I read through the diff, and the operator precedence matches the spec's list.

FYI, I rebased the branch onto main to clean the conflicts in .editorconfig and README.md.
Question: why does .editorconfig get a tab-indent block for Makefile, go.mod and *.go here?

The [{Makefile,go.mod,*.go}] section was added by mistake and is
unrelated to the grammar. Keep only the [*.g4] block.
@j0hannesz

j0hannesz commented Nov 2, 2026 via email

Copy link
Copy Markdown
Contributor Author

@MrBlack1995 MrBlack1995 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this, a reference grammar plus language-neutral fixtures is something every converter can use. I come at it from the Microsoft converter, whose DAX translation emits OSSIE_SQL_2026, so I ran the grammar CI on my fork (9 passed on Python 3.11 and 3.14) and read the grammar against what that engine produces (CASE WHEN, NULLIF denominators, IS NOT DISTINCT FROM, COALESCE, CAST(... AS DOUBLE), LOG(b, x), FLOOR/TRUNC, COUNT(DISTINCT ...)). All of those shapes are covered by the rules as written. Two suggestions and a question, none blocking:

  1. || precedence: the validator and the grammar disagree today. You list this as open for the spec, and I agree the spec should decide. Until it does, it may help to note that validation/validate.py parses with sqlglot, which reads a || b + c as a || (b + c), while this grammar reads (a || b) + c. So the repo would hold two parsers that build different trees for the same valid expression. Two options until the spec decides: follow sqlglot/PostgreSQL (one tree across the repo), or reject an unparenthesized mix of || with +/- (nobody gets a silently different tree). The tpcds example (c_first_name || ' ' || c_last_name) parses the same either way.

  2. Also parse the compliance models. test_example_models_parse reads examples/*.yaml. The portability fixtures (compliance/portability/m0-m3 on main, M4-M6 in #631) and compliance/query/scott hold portable expressions too (FILTER (WHERE ...), CASE, DATEDIFF, DATE_TRUNC). Globbing compliance/**/*.ossie.yaml as well would keep the grammar in sync with the fixtures converters are graded on, not only with the examples. Happy to add new rungs to valid.txt from the #631 side if that helps.

  3. Question: EXISTS_IN(). The "Not Supported in Expressions" table points subquery users to EXISTS_IN(). It parses as a plain function call, so nothing is needed in the grammar, but a line in valid.txt would pin it if the spec gives it arguments later.

I read the grammar against the spec's precedence list (unary, multiplicative, additive, comparison, NOT, AND, OR) and agree with JB that it matches; NOT a = b parsing as NOT (a = b) and the BETWEEN ... AND operands being value expressions both look right to me.

@j0hannesz

j0hannesz commented Nov 9, 2026 via email

Copy link
Copy Markdown
Contributor Author

@christianeu-db christianeu-db left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @j0hannesz! The dev@ thread reached lazy consensus. Left three small notes; feel free to address them as follow-ups.

cc @kayemkim if you want to take a look too since this seems useful for all the validation work

nonReservedWord
: DATE | TIME | TIMESTAMP | TIMESTAMP_NTZ
| FIRST | LAST | NULLS | ROW | ROWS | RANGE | UNBOUNDED | PRECEDING | FOLLOWING | CURRENT
| FILTER | OVER | WITHIN | GROUP | PARTITION | ASC | DESC | POSITION

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The portable example in spec.md (Expression Object) uses a dataset named order, and the grammar rejects it because ORDER is reserved:

SUM(order.sales) / COUNT(DISTINCT order.customer_id)
1:4 no viable alternative at input 'SUM(order'

validate.py accepts the same expression. Could ORDER join this list, or should the spec example be updated to something like orders.name

Comment on lines +39 to +40
- `||` is accepted, at the level of `+` and `-`. The spec's portable form is `CONCAT(a, b)`;
`a || b` only appears as a dialect form.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The String Manipulation table in expression_language.md lists || as a REQUIRED operator, and the portable sql in examples/tpcds_semantic_model.yaml uses it, so it is more than a dialect form. Maybe:

  • || is accepted, at the level of + and -. The spec lists || as a REQUIRED
    concatenation operator but does not say where it sits.

assert kind(value) == "ValueOnlyExpr"
add = value.valueExpr()
assert kind(add) == "AdditiveValue"
assert kind(add.valueExpr(1)) == "MultiplicativeValue"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No test pins unary +/- (level 2 of the spec's list): with # unaryValue moved below # additiveValue, all nine tests still pass and -a + b parses as -(a + b). Could we add a case for it?

@kayemkim kayemkim left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

validation/validate.py passes 8 of the 15 lines in compliance/expressions/invalid.txt as a field's sql today, with no error or warning (sqlglot 30.31.0): a b, a IS, x IN (), SELECT a FROM t, SUM(DISTINCT), COUNT(DISTINCT), order_date + INTERVAL '7 days' and DATE_ADD(d, INTERVAL 7 DAY). #647 and #698 are closing the SELECT one; the other seven have no PR.

The other way round the two agree. All 66 lines of valid.txt parse in both, and across the 263 distinct portable expressions in the Ossie documents of this repo and ossie-converters there is none that one accepts and the other rejects.

Nothing to change in this PR. I will pick up the seven on the validation side once it is merged.

@jbonofre jbonofre left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @j0hannesz for the .editorconfig cleanup!

LGTM, great one!

@jbonofre
jbonofre merged commit c05a3d1 into apache:main Nov 12, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants