Repository navigation
fix(compliance): grade the portable expression.sql form as OSSIE_SQL_2026 - #642
Conversation
…2026
The spec now gives expressions a primary, portable sql form, with dialects
optional. The grader read only dialects, so a model returned unchanged in the
sql form scored SURVIVED_VENDOR_ONLY ("no expression returned").
_dialect_map() now reads sql under OSSIE_SQL_2026, winning over an
OSSIE_SQL_2026 entry in dialects. Which tags count as portable is unchanged.
Co-authored-by: Isaac <no-reply@databricks.com>
| for entry in expression_obj.get("dialects") or []: | ||
| if isinstance(entry, dict) and entry.get("dialect") is not None: | ||
| result[entry["dialect"]] = _normalize(entry.get("expression")) | ||
| if isinstance(expression_obj.get("sql"), str) and expression_obj["sql"].strip(): |
There was a problem hiding this comment.
_dialect_map adds the primary sql form after the dialects entries, and _authored_tag picks the first portable key in insertion order.
So when a source has both a primary sql and an ANSI_SQL entry in dialects, the authored tag resolves to ANSI_SQL instead of OSSIE_SQL_2026.
Example source:
expression:
sql: "SUM(a)"
dialects:
- dialect: ANSI_SQL
expression: "SUM(a)"The map is {ANSI_SQL, OSSIE_SQL_2026}, so _authored_tag returns ANSI_SQL. A converter that returns only the primary sql form then gets tag_preserved=False and is graded RELABELED or REWRITTEN instead of PORTABLE. That would misreport a correct conversion.
I would suggest either insert the sql entry first, or make _authored_tag prefer OSSIE_SQL_2026 when it is present.
Could you also add a test in test_grade.py with sql plus a non-Ossie dialects entry, so this ordering is pinned?
Review on apache#642: _authored_tag picks the first portable key, and sql was added after the dialects entries, so sql plus an ANSI_SQL copy resolved to ANSI_SQL and a correct sql-only return graded RELABELED. sql now goes in first; an OSSIE_SQL_2026 entry next to it is skipped (sql is primary). Models without sql build the same map as before. Co-authored-by: Isaac <no-reply@databricks.com>
|
Thanks, reproduced: your example graded RELABELED ("authored ANSI_SQL, returned OSSIE_SQL_2026"). Fixed in the latest commit with your first suggestion: |
|
@MikeNitsenko absolutely! Thanks! |
| return result | ||
| has_sql = isinstance(expression_obj.get("sql"), str) and bool(expression_obj["sql"].strip()) | ||
| if has_sql: | ||
| result["OSSIE_SQL_2026"] = _normalize(expression_obj["sql"]) |
There was a problem hiding this comment.
Reading sql first makes OSSIE_SQL_2026 the only authored tag whenever both forms are present.
That swaps which case is mis-graded instead of fixing it.
With the spec's own example shape:
expression:
sql: SUM(a)
dialects:
- dialect: ANSI_SQL
expression: SUM(a)a converter that only reads and writes dialects returns the ANSI_SQL entry unchanged. _authored_tag gives OSSIE_SQL_2026, which is not in the output, so the RELABELED loop matches ANSI_SQL and reports "authored OSSIE_SQL_2026, returned ANSI_SQL".
If the ANSI copy differs from sql, the unchanged return is graded REWRITTEN. Before this commit it was PORTABLE.
Both forms were authored, so returning either one unchanged should be PORTABLE.
Could we make that an explicit rule in _classify_present instead of relying on insertion order here? Something like:
# PORTABLE: any authored portable (tag, text) pair came back unchanged.
kept = [d for d in in_dialects
if d in PORTABLE_DIALECTS and out_dialects.get(d) == in_dialects[d]]
if kept:
preserved = "OSSIE_SQL_2026" in kept
return Finding(
input_element.kind, input_element.name, PORTABLE,
tag_preserved=preserved,
detail=f"returned as {'OSSIE_SQL_2026' if preserved else kept[0]}",
)There was a problem hiding this comment.
You're right, thanks: the ordering only moved the mis-grade. Fixed in 7f82a22 with your rule in _classify_present: any authored portable (tag, text) pair that comes back unchanged is PORTABLE, and tag_preserved is set when the OSSIE_SQL_2026 one is among them.
One small difference from the snippet: the pairs are kept next to the dialect map instead of read from it. The map holds one text per tag, so with sql: SUM(a) plus an OSSIE_SQL_2026 entry SUM(b), the entry was not in it, and a converter returning that entry unchanged would still have missed. That case is tested too.
New tests: your example (only the ANSI_SQL entry returned, equal to sql and different), both forms returned, the OSSIE_SQL_2026 entry next to a different sql, neither returned unchanged (REWRITTEN), and, since the rule is general, two portable entries without sql whose second one comes back unchanged (now PORTABLE; it graded by the first entry only). Four of them fail on the previous commit. 25 grader tests pass, also on top of current main. The posted fixtures each carry one dialect entry, so their scores do not change.
…TABLE Reading the sql form first made OSSIE_SQL_2026 the only authored tag when an expression carries both sql and dialect entries, which moved the mis-grade instead of fixing it: a converter that reads and writes only dialects returned the ANSI_SQL entry unchanged and got RELABELED, or REWRITTEN when the ANSI copy differs from sql. Both forms were authored, so PORTABLE is now an explicit rule in _classify_present: any authored portable (dialect, text) pair that comes back unchanged. The pairs are kept separately from the dialect map, so an OSSIE_SQL_2026 entry next to a different sql form counts too (the map holds one text per tag). TAG_PRESERVED is set when the OSSIE_SQL_2026 pair is among those kept. Co-authored-by: Isaac <no-reply@databricks.com>
Co-authored-by: Isaac <no-reply@databricks.com>
christianeu-db
left a comment
There was a problem hiding this comment.
Thanks @MrBlack1995! Left three comments; I think the first one, on the shadowed OSSIE_SQL_2026 entry, should be fixed before merge.
| pairs.add(("OSSIE_SQL_2026", _normalize(expression_obj["sql"]))) | ||
| for entry in expression_obj.get("dialects") or []: | ||
| if isinstance(entry, dict) and entry.get("dialect") is not None: | ||
| pairs.add((entry["dialect"], _normalize(entry.get("expression")))) |
There was a problem hiding this comment.
The spec says a top-level sql is authoritative and consumers MUST use it as the OSSIE_SQL_2026 form, but this keeps the shadowed entry, so a returned sql that changed still grades PORTABLE:
# input
expression: {sql: SUM(a)}
# returned: [PORTABLE (tag preserved)] metric total -- returned as OSSIE_SQL_2026
expression:
sql: SUM(x)
dialects: [{dialect: OSSIE_SQL_2026, expression: SUM(a)}]The input side is the same, and a new test pins it: sql: SUM(a) with an OSSIE_SQL_2026 entry SUM(b), returned as that entry, grades PORTABLE. Could _authored_pairs skip the entry when sql is present, as _dialect_map does?
There was a problem hiding this comment.
Agreed, the spec is clear on this: next to sql the OSSIE_SQL_2026 entry is shadowed. Fixed in 3b04057: the authored forms are now read the way a consumer reads them (_dialect_map, which already skipped the entry), on the input and the returned side, so the separate authored-pairs set was redundant and is gone. Your example is a test now (a changed sql next to a shadowed entry with the old text is REWRITTEN), and the test that pinned the old behaviour is inverted: returning only the shadowed entry's text is a rewrite of the authored form.
| kept = sorted( | ||
| dialect | ||
| for dialect, _ in input_element.authored & output_element.authored | ||
| if dialect in PORTABLE_DIALECTS |
There was a problem hiding this comment.
Not a blocker: when a converter rewrites sql but passes the ANSI_SQL copy through, this grades PORTABLE and the change to the primary form is not reported:
# input
expression:
sql: SUM(a)
dialects: [{dialect: ANSI_SQL, expression: SUM(a)}]
# returned: [PORTABLE] metric total -- returned as ANSI_SQL
expression:
sql: SUM(x)
dialects: [{dialect: ANSI_SQL, expression: SUM(a)}]Should a returned portable form whose text changed make the element REWRITTEN? With that rule, a converter that returns only one form unchanged still grades PORTABLE, as @jbonofre asked.
There was a problem hiding this comment.
Yes, done in the same commit: an element whose authored portable form comes back changed is REWRITTEN, and the changed form is the one reported, even when another authored form comes back unchanged. A converter that returns only one form unchanged (the other absent) still grades PORTABLE, so the earlier rule holds. Your example is a test.
| returned = _doc(metrics=[_sql_metric("total", "SUM(sales.amount)")]) | ||
| assert _only(grade.grade(src, returned)).outcome == grade.PORTABLE |
There was a problem hiding this comment.
Not a blocker: this test still passes with the continue in _dialect_map removed, because the unchanged sql pair already grades PORTABLE through kept. A second return of the sql text under ANSI_SQL would pin that sql wins, since it grades RELABELED only while that entry is skipped:
returned = _doc(metrics=[_sql_metric("total", "SUM(sales.amount)")])
assert _only(grade.grade(src, returned)).outcome == grade.PORTABLE
relabeled = _doc(metrics=[_metric("total", "ANSI_SQL", "SUM(sales.amount)")])
assert _only(grade.grade(src, relabeled)).outcome == grade.RELABELED
There was a problem hiding this comment.
Good catch, added your second return to that test in 3b04057: the sql text under ANSI_SQL grades RELABELED. With the continue in _dialect_map removed it now fails, together with the two new shadowing tests. 27 grader tests pass; the 3 from your examples fail on 8102ea9. Merged with current main the fixtures still validate (the only warnings are the bare model-scoped variants, which #631 moves to dataset-scoped metrics).
…ble form is a rewrite The spec makes a top-level sql authoritative: when both sql and an OSSIE_SQL_2026 entry in dialects are present, consumers MUST use sql as the OSSIE_SQL_2026 form. The grader still kept the shadowed entry as an authored form, so a returned sql that changed graded PORTABLE when the entry carried the old text, on the input side and the returned side. The authored forms are now read the way a consumer reads them (_dialect_map), which also makes the separate authored-pairs set redundant, so it is gone. And an element whose authored portable form comes back changed is REWRITTEN even when another authored form comes back unchanged; a converter that returns only one form unchanged still grades PORTABLE. Three tests from the review's examples fail on 8102ea9; the sql-wins test now also returns the sql text under ANSI_SQL (RELABELED), which fails if _dialect_map stops skipping the shadowed entry. Co-authored-by: Isaac <no-reply@databricks.com>
christianeu-db
left a comment
There was a problem hiding this comment.
LGTM, thanks @MrBlack1995! One note inline for a follow-up. @jbonofre, do you mind taking another look at your change request when you get a chance?
| ) | ||
| changed = [ | ||
| dialect | ||
| for dialect in in_dialects # authored order: the `sql` form first |
There was a problem hiding this comment.
With an ANSI_SQL-only input, an unchanged copy still hides a rewritten sql form, because changed only checks the authored tags:
# input
expression: {dialects: [{dialect: ANSI_SQL, expression: SUM(a)}]}
# returned: [PORTABLE] metric total -- returned as ANSI_SQL
expression:
sql: SUM(x)
dialects: [{dialect: ANSI_SQL, expression: SUM(a)}]Returned without the copy, sql: SUM(x) grades REWRITTEN. Could changed also count a returned sql that differs from the authored text?
There was a problem hiding this comment.
Thanks, agreed: a returned sql is the primary form, so it should count as changed whenever its text differs from the authored one, whichever tags the input used. I'll take this in the grader follow-up right after this merges (together with the empty-return case from #65: an empty but valid return as DROPPED rather than REJECTED), with your example as a test, so the approval here stays as is.
Returning only an ANSI_SQL entry that differs from the authored `sql` form drops the authoritative form, so it should not grade PORTABLE. Assert the intended outcome and mark it xfail(strict=True) until the grader compares effective forms, instead of locking in the current grade. Also drop two extra blank lines between top-level definitions.
jbonofre
left a comment
There was a problem hiding this comment.
Thanks @MrBlack1995! Reading sql as OSSIE_SQL_2026 closes the bigger gap: on main, an expression that only uses sql has nothing to grade. The remaining mis-grade also happen on main.
I pushed one commit: test_an_unchanged_ansi_entry_that_differs_from_sql_is_portable is now test_dropping_sql_for_a_differing_ansi_entry_is_not_portable. It asserts the intended outcome (returning only an ANSI_SQL entry that differs from the authored sql drops the authoritative form, so it is not PORTABLE) and is marked xfail(strict=True). It also removes two extra blank lines.
Summary
The spec now gives every expression a primary, portable
sqlform (OSSIE_SQL_2026), withdialectsoptional (core-spec/spec.md). The portability grader read onlydialects, so a converter that returns an expression in thesqlform was scored as if the expression were gone. Even a model returned completely unchanged:sqlform)total,sql: SUM(sales.amount)sales.amount,sql: amount_dialect_map()readssqlunderOSSIE_SQL_2026, so every outcome rule applies to it unchanged._classify_present: an authored portable form (thesqlform, or a portabledialectsentry) returned unchanged under its tag, and no authored portable form returned changed. When an expression carriessqlplus anANSI_SQLcopy, a converter that reads and writes only one of them is PORTABLE either way; one that passes a copy through but rewrites the other is REWRITTEN, and the changed form is the one reported. TAG_PRESERVED is set when theOSSIE_SQL_2026form is among those kept. (Thanks to @jbonofre's review: ordering alone only moved the mis-grade.)sqlis authoritative: next to it, anOSSIE_SQL_2026entry indialectsis shadowed and not read, on the input and the returned side (core-spec/spec.md: consumers MUST usesqlas the OSSIE_SQL_2026 form).sqlreturned as anOSSIE_SQL_2026dialect entry, or the other way round, grades PORTABLE: same tag, same text.ANSI_SQLshould still count as portable now that it is an opaque label belongs with moving the fixtures to thesqlform, which I would propose separately.Validation
sqlform,sqlvs anOSSIE_SQL_2026entry in both directions,sqlwinning over that entry, anANSI_SQLinput returned in thesqlform grading RELABELED, and forsqlplus a dialect entry: only thesqlform returned, only an unchangedANSI_SQLentry returned (equal tosql, and different), both returned, and neither returned unchanged, REWRITTEN; plus two portable entries withoutsql, the second returned unchanged). Since 3b04057, 27 passed: returning only a shadowedOSSIE_SQL_2026entry's text is REWRITTEN, a changedsqlnext to a shadowed entry with the old text is REWRITTEN, a rewrittensqlnext to an unchangedANSI_SQLcopy is REWRITTEN (these three fail on 8102ea9), and thesqltext returned underANSI_SQLgrades RELABELED (fails if the shadowed entry is read).ruff checkclean. Merges cleanly with main; conflicts mechanically with test(compliance): add portability rungs M4-M6 and the FILTER (WHERE) form of M3 #631 and test(compliance): add portability rungs M7 (window ORDER BY) and M8 (ratio-to-total) #700 ingrade.py(same functions), so whichever lands second rebases.Related Issues
Refs apache/ossie-converters#24 (portability measurement). Follows #596.