EKOS — Enterprise Knowledge Operating System

Two real recovery gaps. Filed, fixed, re-verified.

The Pentaho + SQL recovery deck documented an honest gap — AdventureWorks' SQL files all failed to parse — as a limit, not hidden. It became GitHub issue #3. A second gap, `StreamLookup` steps falling through to Unmapped, became issue #2. Both are closed, and this deck reruns the exact repro shapes from each issue against the fixed pipeline, live.

issue #2 — StreamLookup → Unmapped· issue #3 — # comments / no ; between statements fixed re-verified against the original repro
§ 01 / the gaps
What was actually broken

Two real ETL scripts, two silent recovery failures.

Both bugs were found the same way the recovery deck found them originally: run EKOS cold against real files, don't fabricate anything to paper over a gap, and report exactly what didn't map.

  • Issue #2StreamLookup — one of the most common real-world Kettle step types — had no match arm in map_step, so it fell through to Unmapped.
  • Issue #3MySQL # line comments broke the tokenizer outright; hand-written scripts with no ; between statements failed the whole file, not just one statement.
the exact failure signatures
# issue #3, root cause 1
sqlparser error: Expected: an SQL statement, found: #

# issue #3, root cause 2
sqlparser error: Expected: end of statement, found: update

# issue #2
node_type: "Unmapped"
reason: "unrecognized step type: StreamLookup"
§ 02 / fix — dialects
Issue #3, root cause 1

Select the right dialect instead of guessing at ANSI SQL.

RFC 0031 made SQL dialect selection pluggable and config-driven — a per-file [[recover.sql.dialect-rules]] glob resolves which SqlDialectParser to use, instead of every recovery pass hardcoding GenericDialect. MySqlDialect already tokenizes #-style comments correctly — the fix is picking it, not writing a new tokenizer.

  • Registrygeneric, mysql, postgres — a new dialect crate plus one registry line, same pattern as every Observer plugin.
  • Configekos.toml: path-glob = "*MySQL*/*.sql"dialect = "mysql".
§ 03 / fix — statement_repair
Issue #3, root cause 2

A fallback that only ever runs after parsing has already failed.

crates/recovery/src/statement_repair.rs
let stmts = match Parser::parse_sql(dialect, sql) {
    Ok(s) => s,
    Err(first_err) => {
        // only after the unmodified text
        // has already failed to parse
        let repaired =
            ensure_statement_separators(sql);
        Parser::parse_sql(dialect, &repaired)
            .unwrap_or_else(|_| { /* ... */ vec![] })
    }
};

Hand-written scripts often separate statements with a blank line instead of ;sqlparser requires the explicit separator. Inserting one unconditionally would risk corrupting a legitimate UNION ALL SELECT ... chain, which also starts a line with SELECT. Restricting the repair to a retry-after-failure means it can never touch input that already parses correctly.

  • Safety netTracks paren depth and set-operation keywords (UNION/INTERSECT/EXCEPT) so multi-line constructs are never split.
  • SharedWired into both SqlAnalyzerPass (DDL) and SqlTransformAnalyzerPass (DML) — one fix, two consumers.
§ 04 / fix — StreamLookup
Issue #2

StreamLookup is a left join. Now it's modeled as one.

Kettle's StreamLookup XML has no join_type field, unlike DatabaseJoin/MergeJoin — because a stream lookup is semantically always a left join against the lookup stream on the configured key(s). The fix reuses the existing join-extraction shape and forces JoinKind::Left, rather than adding a new IR variant.

  • BeforeTransformNode::Unmapped { reason: "unrecognized step type: StreamLookup" }
  • AfterTransformNode::Join { kind: Left, keys: [...] } — same evidence-backed shape as every other join.
§ 05 / re-verified, live
Not a unit test claim — the same repro shape, run for real

Both issues' exact scenarios, through the real pipeline, today.

$ ekos ekl "FIND Object WHERE kind = 'Table'"
id                                    name                        kind
45590d6b-...                          eae_data_management_mmjja  Table
b4206f56-...                          testing_scenarios          Table

2 row(s).
ekos_transformation_explain — fact_sales.ktr
{ "node_type": "Join",
  "summary": "Left joins on
    [[\"SalesTerritoryKey\",\"SalesTerritoryKey\"]]",
  "evidence": [{ "fragment":
    "Left JOIN ON [(\"SalesTerritoryKey\",
    \"SalesTerritoryKey\")]" }] }

The first: a scratch DB Scripts/Destination MySQL/ + Source MSSQL/ fixture reproducing both of issue #3's root causes — # comments in one file, zero semicolons across three statements in the other — recovers both tables. The second: a real 3-step .ktr (TableInput → StreamLookup → TableOutput) explained via a live MCP JSON-RPC call, with the join key cited as evidence straight from the file.

§ 06 / coverage, before vs. after
The numbers that moved

Zero fabricated data either way — the difference is what recovers.

FixtureBeforeAfter
Dialect-mixed DB Scripts (issue #3 repro)0/2 tables recovered2/2 tables recovered
fact_sales.ktr, 3 steps (issue #2 repro)66% mapped (StreamLookup → Unmapped)100% mapped
2
GitHub issues closed
5
new regression tests
0
already-working inputs touched by either fix
Try it

File the gap you find. It becomes the next fix, verified the same way.

terminal
# recover a dialect-mixed estate — config-driven, per file
$ cat ekos.toml
[[recover.sql.dialect-rules]]
path-glob = "*MySQL*/*.sql"
dialect = "mysql"

# then the usual pipeline
$ ekos build && ekos recover && ekos resolve && ekos compile && ekos commit
DocWhere
Demo Acts 11 & 12demo/DEMO.md — full repro + verification steps
Issue #2 / #3github.com/alexeyban/EKOS/issues (closed)
EKOS · RFC 0031 (pluggable SQL dialects) · statement_repair · Pentaho StreamLookup → Join · github.com/alexeyban/EKOS