The conversion report¶
Every conversion produces a ConversionReport alongside the generated files.
It is the machine-readable record of what happened and why - the artifact
you triage after a migration and the one you gate CI on. This page documents
its structure, how to obtain it, and how to act on it.
Getting the report¶
From the CLI, write it to a file with --report:
sql2sqlx ./sql -o ./definitions --report report.json
From Python it is always attached to the result:
from sql2sqlx import convert_directory
result = convert_directory("sql", "definitions")
report = result.report
print(report.actions_by_type)
report_dict = report.to_dict() # JSON-serializable
Report fields¶
ConversionReport is a dataclass with the following fields (see
ConversionReport for the autodoc entry):
Field |
Meaning |
|---|---|
|
Number of input files parsed, including files that failed. |
|
Number of top-level statements found across all files, counted before any whole-file script collapsing. |
|
Mapping of Dataform action type to count, e.g.
|
|
Number of table reference sites rewritten to |
|
Distinct referenced-but-not-produced table paths that could be
dataset-resolved and were left as literals. These are your external
sources; |
|
The list of every non-fatal finding (see below). |
|
Mapping of |
|
Wall-clock duration of the run, rounded to milliseconds. |
|
Total size of the SQL read, in bytes. |
Each entry in warnings is a ReportWarning with four fields:
codeA stable, machine-readable identifier such as
FALLBACK_OPERATIONSorMERGE_INCREMENTAL. Gate and filter on this, not on the message text.messageA human-readable explanation, including the specific table or reason.
pathThe source file the finding relates to (empty for synthesized actions).
lineThe 1-based source line (
0when not applicable).
JSON shape¶
report.to_dict() (and the CLI’s --report file) produces this structure:
{
"files_read": 1,
"statements": 3,
"actions_by_type": {
"table": 1,
"operations": 2
},
"refs_rewritten": 1,
"refs_unresolved": [
"raw.src"
],
"warnings": [
{
"code": "INSERT_VALUES",
"message": "INSERT ... VALUES has no query body; kept as operations.",
"path": "input.sql",
"line": 2
},
{
"code": "ORPHAN_SELECT",
"message": "Standalone query converted to an operations action; review whether it should be a table, view or assertion instead.",
"path": "input.sql",
"line": 3
}
],
"failures": {},
"elapsed_seconds": 0.0,
"input_bytes": 124
}
Key ordering in the JSON is stable (it follows the dataclass field order, not alphabetical), so the file diffs cleanly between runs.
The CLI summary¶
Unless you pass --quiet, the CLI prints a compact summary to stderr
(generated SQLX for a single-file input goes to stdout, so piping stays
clean):
sql2sqlx v0.1.0
files read: 1
statements: 3
actions: operations=2 table=1
refs rewritten: 1 (1 external left as literals)
warnings: 2
failures: 0
elapsed: 0.0s (0.0 MB)
Add -v/--verbose to also list every warning ([CODE] path:line: message)
and every generated file. Any failures are always listed, regardless of
verbosity.
Triage workflow¶
The warnings fall into three broad severities. None of them stop a build - the tool never emits SQL that changes behavior on its own - but they tell you where human review pays off.
1. Review the choice you opted into. When you select a non-default strategy, the tool tells you exactly what changed:
INSERT_INCREMENTAL,MERGE_INCREMENTAL,TARGET_SCHEMA_REQUIRED- a typed incremental was produced; verify the existing target’s schema matches the query’s output before the first incremental run.CREATE_REPLACE_SEMANTICS- a non-OR REPLACEcreate became a typed action that rebuilds on later runs instead of erroring on an existing object.IF_NOT_EXISTS,DECLARATION_DROPPED_DDL- a guard was dropped or DDL was replaced by a declaration per your option.
2. Understand a fallback. These explain why a statement stayed verbatim, so you can decide whether to restructure the SQL or accept the operations action:
FALLBACK_OPERATIONS,FALLBACK_SELECT_ALIAS,MERGE_FALLBACK,COLUMN_DDL,INSERT_VALUES,TEMP_TABLE,EXTERNAL_TABLE,SNAPSHOT_TABLE,SCRIPT_FILE,PROCEDURE_PRESERVED.
3. Check an inferred edge or an ambiguous shape. These flag places where static analysis was conservative:
ORDER_ASSUMED,FUTURE_CREATOR,DEPENDENCY_CYCLE,SELF_REFERENCE,DUPLICATE_TARGET- ordering/reference decisions to sanity-check.ORPHAN_SELECT,DYNAMIC_SIDE_EFFECTS,RERUN_RISK,SCRIPT_WRITES- statements whose intent or hidden effects need a human eye.
The full meaning of every code lives in the warning code reference.
Gating CI¶
failures and specific codes make good CI gates. For example, fail a build
on any parse failure and on a code you have decided to disallow:
import sys
from sql2sqlx import convert_directory
result = convert_directory("sql", "definitions")
report = result.report
blocking = {"DEPENDENCY_CYCLE"}
hits = [w for w in report.warnings if w.code in blocking]
if report.failures or hits:
for path, err in report.failures.items():
print(f"FAILED {path}: {err}", file=sys.stderr)
for w in hits:
print(f"[{w.code}] {w.path}:{w.line}: {w.message}", file=sys.stderr)
sys.exit(1)
The CLI already exits 1 when any file failed to convert (and 2 on a usage
error), so sql2sqlx ... --report report.json is itself a usable gate; parse
report.json for finer-grained policy.
Tip
dataform compile is the final gate. sql2sqlx produces a project that is
intended to compile, but only Dataform can confirm the whole graph resolves
against your warehouse’s real schemas. Run it after every conversion.