sql2sqlx 0.1.0__tar.gz → 0.1.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. {sql2sqlx-0.1.0 → sql2sqlx-0.1.1}/MANIFEST.in +0 -1
  2. {sql2sqlx-0.1.0 → sql2sqlx-0.1.1}/PKG-INFO +2 -3
  3. {sql2sqlx-0.1.0 → sql2sqlx-0.1.1}/README.md +1 -1
  4. sql2sqlx-0.1.1/docs/api.md +129 -0
  5. sql2sqlx-0.1.1/docs/architecture.md +170 -0
  6. sql2sqlx-0.1.1/docs/cli.md +211 -0
  7. sql2sqlx-0.1.1/docs/concepts.md +255 -0
  8. {sql2sqlx-0.1.0 → sql2sqlx-0.1.1}/docs/conversion_rules.md +151 -20
  9. sql2sqlx-0.1.1/docs/dependencies.md +236 -0
  10. sql2sqlx-0.1.1/docs/examples.md +99 -0
  11. sql2sqlx-0.1.1/docs/index.md +81 -0
  12. sql2sqlx-0.1.1/docs/installation.md +93 -0
  13. sql2sqlx-0.1.1/docs/limitations.md +103 -0
  14. sql2sqlx-0.1.1/docs/migration_guide.md +173 -0
  15. sql2sqlx-0.1.1/docs/quickstart.md +145 -0
  16. sql2sqlx-0.1.1/docs/report.md +205 -0
  17. {sql2sqlx-0.1.0 → sql2sqlx-0.1.1}/pyproject.toml +0 -1
  18. {sql2sqlx-0.1.0 → sql2sqlx-0.1.1}/src/sql2sqlx/converter.py +16 -4
  19. {sql2sqlx-0.1.0 → sql2sqlx-0.1.1}/src/sql2sqlx/emitter.py +59 -21
  20. {sql2sqlx-0.1.0 → sql2sqlx-0.1.1}/src/sql2sqlx/version.py +1 -1
  21. {sql2sqlx-0.1.0 → sql2sqlx-0.1.1}/src/sql2sqlx.egg-info/PKG-INFO +2 -3
  22. {sql2sqlx-0.1.0 → sql2sqlx-0.1.1}/src/sql2sqlx.egg-info/SOURCES.txt +6 -3
  23. sql2sqlx-0.1.1/tests/fixtures/dataform_compile/13_sqlx_lexical_safety.sql +12 -0
  24. {sql2sqlx-0.1.0 → sql2sqlx-0.1.1}/tests/test_correctness_regressions.py +50 -1
  25. {sql2sqlx-0.1.0 → sql2sqlx-0.1.1}/tests/test_parser_refs.py +1 -1
  26. sql2sqlx-0.1.0/CHANGELOG.md +0 -12
  27. sql2sqlx-0.1.0/docs/api.md +0 -78
  28. sql2sqlx-0.1.0/docs/architecture.md +0 -105
  29. sql2sqlx-0.1.0/docs/changelog.md +0 -2
  30. sql2sqlx-0.1.0/docs/cli.md +0 -59
  31. sql2sqlx-0.1.0/docs/examples.md +0 -27
  32. sql2sqlx-0.1.0/docs/index.md +0 -43
  33. sql2sqlx-0.1.0/docs/installation.md +0 -30
  34. sql2sqlx-0.1.0/docs/limitations.md +0 -56
  35. sql2sqlx-0.1.0/docs/quickstart.md +0 -75
  36. {sql2sqlx-0.1.0 → sql2sqlx-0.1.1}/CITATION.cff +0 -0
  37. {sql2sqlx-0.1.0 → sql2sqlx-0.1.1}/CONTRIBUTING.md +0 -0
  38. {sql2sqlx-0.1.0 → sql2sqlx-0.1.1}/LICENSE +0 -0
  39. {sql2sqlx-0.1.0 → sql2sqlx-0.1.1}/docs/Makefile +0 -0
  40. {sql2sqlx-0.1.0 → sql2sqlx-0.1.1}/docs/conf.py +0 -0
  41. {sql2sqlx-0.1.0 → sql2sqlx-0.1.1}/docs/contributing.md +0 -0
  42. {sql2sqlx-0.1.0 → sql2sqlx-0.1.1}/docs/notebooks/01_in_memory_and_file_conversion.ipynb +0 -0
  43. {sql2sqlx-0.1.0 → sql2sqlx-0.1.1}/docs/notebooks/02_directory_options_and_dependencies.ipynb +0 -0
  44. {sql2sqlx-0.1.0 → sql2sqlx-0.1.1}/docs/notebooks/03_reports_benchmark_and_review_gates.ipynb +0 -0
  45. {sql2sqlx-0.1.0 → sql2sqlx-0.1.1}/docs/requirements.txt +0 -0
  46. {sql2sqlx-0.1.0 → sql2sqlx-0.1.1}/examples/01_convert_string.py +0 -0
  47. {sql2sqlx-0.1.0 → sql2sqlx-0.1.1}/examples/02_convert_file.py +0 -0
  48. {sql2sqlx-0.1.0 → sql2sqlx-0.1.1}/examples/03_convert_directory.py +0 -0
  49. {sql2sqlx-0.1.0 → sql2sqlx-0.1.1}/examples/04_custom_options.py +0 -0
  50. {sql2sqlx-0.1.0 → sql2sqlx-0.1.1}/examples/05_programmatic_report.py +0 -0
  51. {sql2sqlx-0.1.0 → sql2sqlx-0.1.1}/examples/06_benchmark.py +0 -0
  52. {sql2sqlx-0.1.0 → sql2sqlx-0.1.1}/examples/README.md +0 -0
  53. {sql2sqlx-0.1.0 → sql2sqlx-0.1.1}/examples/_notebook_benchmark_helper.py +0 -0
  54. {sql2sqlx-0.1.0 → sql2sqlx-0.1.1}/examples/notebooks/01_in_memory_and_file_conversion.ipynb +0 -0
  55. {sql2sqlx-0.1.0 → sql2sqlx-0.1.1}/examples/notebooks/02_directory_options_and_dependencies.ipynb +0 -0
  56. {sql2sqlx-0.1.0 → sql2sqlx-0.1.1}/examples/notebooks/03_reports_benchmark_and_review_gates.ipynb +0 -0
  57. {sql2sqlx-0.1.0 → sql2sqlx-0.1.1}/examples/sql/maintenance/backfill_script.sql +0 -0
  58. {sql2sqlx-0.1.0 → sql2sqlx-0.1.1}/examples/sql/maintenance/cleanup.sql +0 -0
  59. {sql2sqlx-0.1.0 → sql2sqlx-0.1.1}/examples/sql/maintenance/dedupe_merge.sql +0 -0
  60. {sql2sqlx-0.1.0 → sql2sqlx-0.1.1}/examples/sql/marts/customer_orders.sql +0 -0
  61. {sql2sqlx-0.1.0 → sql2sqlx-0.1.1}/examples/sql/marts/order_facts.sql +0 -0
  62. {sql2sqlx-0.1.0 → sql2sqlx-0.1.1}/examples/sql/sources/raw_events.sql +0 -0
  63. {sql2sqlx-0.1.0 → sql2sqlx-0.1.1}/examples/sql/staging/stg_customers.sql +0 -0
  64. {sql2sqlx-0.1.0 → sql2sqlx-0.1.1}/examples/sql/staging/stg_orders.sql +0 -0
  65. {sql2sqlx-0.1.0 → sql2sqlx-0.1.1}/setup.cfg +0 -0
  66. {sql2sqlx-0.1.0 → sql2sqlx-0.1.1}/src/sql2sqlx/__init__.py +0 -0
  67. {sql2sqlx-0.1.0 → sql2sqlx-0.1.1}/src/sql2sqlx/__main__.py +0 -0
  68. {sql2sqlx-0.1.0 → sql2sqlx-0.1.1}/src/sql2sqlx/cli.py +0 -0
  69. {sql2sqlx-0.1.0 → sql2sqlx-0.1.1}/src/sql2sqlx/errors.py +0 -0
  70. {sql2sqlx-0.1.0 → sql2sqlx-0.1.1}/src/sql2sqlx/keywords.py +0 -0
  71. {sql2sqlx-0.1.0 → sql2sqlx-0.1.1}/src/sql2sqlx/lexer.py +0 -0
  72. {sql2sqlx-0.1.0 → sql2sqlx-0.1.1}/src/sql2sqlx/model.py +0 -0
  73. {sql2sqlx-0.1.0 → sql2sqlx-0.1.1}/src/sql2sqlx/parser.py +0 -0
  74. {sql2sqlx-0.1.0 → sql2sqlx-0.1.1}/src/sql2sqlx/py.typed +0 -0
  75. {sql2sqlx-0.1.0 → sql2sqlx-0.1.1}/src/sql2sqlx/refs.py +0 -0
  76. {sql2sqlx-0.1.0 → sql2sqlx-0.1.1}/src/sql2sqlx/splitter.py +0 -0
  77. {sql2sqlx-0.1.0 → sql2sqlx-0.1.1}/src/sql2sqlx.egg-info/dependency_links.txt +0 -0
  78. {sql2sqlx-0.1.0 → sql2sqlx-0.1.1}/src/sql2sqlx.egg-info/entry_points.txt +0 -0
  79. {sql2sqlx-0.1.0 → sql2sqlx-0.1.1}/src/sql2sqlx.egg-info/requires.txt +0 -0
  80. {sql2sqlx-0.1.0 → sql2sqlx-0.1.1}/src/sql2sqlx.egg-info/top_level.txt +0 -0
  81. {sql2sqlx-0.1.0 → sql2sqlx-0.1.1}/tests/fixtures/dataform_compile/01_source.sql +0 -0
  82. {sql2sqlx-0.1.0 → sql2sqlx-0.1.1}/tests/fixtures/dataform_compile/02_insert.sql +0 -0
  83. {sql2sqlx-0.1.0 → sql2sqlx-0.1.1}/tests/fixtures/dataform_compile/03_merge.sql +0 -0
  84. {sql2sqlx-0.1.0 → sql2sqlx-0.1.1}/tests/fixtures/dataform_compile/04_transaction.sql +0 -0
  85. {sql2sqlx-0.1.0 → sql2sqlx-0.1.1}/tests/fixtures/dataform_compile/05_procedure.sql +0 -0
  86. {sql2sqlx-0.1.0 → sql2sqlx-0.1.1}/tests/fixtures/dataform_compile/06_cycle_a.sql +0 -0
  87. {sql2sqlx-0.1.0 → sql2sqlx-0.1.1}/tests/fixtures/dataform_compile/07_cycle_b.sql +0 -0
  88. {sql2sqlx-0.1.0 → sql2sqlx-0.1.1}/tests/fixtures/dataform_compile/08_plain_create.sql +0 -0
  89. {sql2sqlx-0.1.0 → sql2sqlx-0.1.1}/tests/fixtures/dataform_compile/09_guarded_view.sql +0 -0
  90. {sql2sqlx-0.1.0 → sql2sqlx-0.1.1}/tests/fixtures/dataform_compile/10_cross_project_dependency.sql +0 -0
  91. {sql2sqlx-0.1.0 → sql2sqlx-0.1.1}/tests/fixtures/dataform_compile/11_clone.sql +0 -0
  92. {sql2sqlx-0.1.0 → sql2sqlx-0.1.1}/tests/fixtures/dataform_compile/12_literal_sqlx.sql +0 -0
  93. {sql2sqlx-0.1.0 → sql2sqlx-0.1.1}/tests/test_end_to_end.py +0 -0
  94. {sql2sqlx-0.1.0 → sql2sqlx-0.1.1}/tests/test_lexer_splitter.py +0 -0
@@ -1,6 +1,5 @@
1
1
  include LICENSE
2
2
  include README.md
3
- include CHANGELOG.md
4
3
  include CONTRIBUTING.md
5
4
  include CITATION.cff
6
5
  recursive-include src/sql2sqlx py.typed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sql2sqlx
3
- Version: 0.1.0
3
+ Version: 0.1.1
4
4
  Summary: Convert BigQuery SQL into Dataform SQLX with conservative, dependency-aware migration tooling.
5
5
  Author-email: Soumyadip Sarkar <soumyadip@soumyadipsarkar.com>
6
6
  License-Expression: Apache-2.0
@@ -8,7 +8,6 @@ Project-URL: Homepage, https://github.com/neuralsorcerer/sql2sqlx
8
8
  Project-URL: Documentation, https://neuralsorcerer.github.io/sql2sqlx/
9
9
  Project-URL: Source, https://github.com/neuralsorcerer/sql2sqlx
10
10
  Project-URL: Issues, https://github.com/neuralsorcerer/sql2sqlx/issues
11
- Project-URL: Changelog, https://github.com/neuralsorcerer/sql2sqlx/blob/main/CHANGELOG.md
12
11
  Keywords: analytics-engineering,bigquery,bigquery-sql,code-generation,data-engineering,dataform,dataform-sqlx,ddl,dependency-graph,dml,elt,etl,gcp,google-cloud,googlesql,migration,sql,sql-converter,sql-migration,sql-parser,sqlx,warehouse-migration
13
12
  Classifier: Development Status :: 4 - Beta
14
13
  Classifier: Intended Audience :: Developers
@@ -56,13 +55,13 @@ BigQuery SQL -> Dataform SQLX migration tooling.
56
55
 
57
56
  <div align="center">
58
57
 
59
- [![Current Release](https://img.shields.io/github/release/neuralsorcerer/sql2sqlx.svg)](https://github.com/neuralsorcerer/sql2sqlx/releases)
60
58
  [![PyPI](https://img.shields.io/pypi/v/sql2sqlx.svg?logo=pypi&logoColor=white)](https://pypi.org/project/sql2sqlx/)
61
59
  [![Python 3.10+](https://img.shields.io/badge/Python-3.10+-fcbc2c.svg?logo=python&logoColor=white)](https://www.python.org/downloads/)
62
60
  [![Test Ubuntu](https://github.com/neuralsorcerer/sql2sqlx/actions/workflows/test_ubuntu.yml/badge.svg)](https://github.com/neuralsorcerer/sql2sqlx/actions/workflows/test_ubuntu.yml?query=branch%3Amain)
63
61
  [![Test Windows](https://github.com/neuralsorcerer/sql2sqlx/actions/workflows/test_windows.yml/badge.svg)](https://github.com/neuralsorcerer/sql2sqlx/actions/workflows/test_windows.yml?query=branch%3Amain)
64
62
  [![Test MacOS](https://github.com/neuralsorcerer/sql2sqlx/actions/workflows/test_macos.yml/badge.svg)](https://github.com/neuralsorcerer/sql2sqlx/actions/workflows/test_macos.yml?query=branch%3Amain)
65
63
  [![Lints](https://github.com/neuralsorcerer/sql2sqlx/actions/workflows/lints.yml/badge.svg)](https://github.com/neuralsorcerer/sql2sqlx/actions/workflows/lints.yml?query=branch%3Amain)
64
+ [![CodeQL](https://github.com/neuralsorcerer/sql2sqlx/actions/workflows/codeql.yml/badge.svg)](https://github.com/neuralsorcerer/sql2sqlx/actions/workflows/codeql.yml?query=branch%3Amain)
66
65
  [![Dataform Compile](https://github.com/neuralsorcerer/sql2sqlx/actions/workflows/dataform_compile.yml/badge.svg)](https://github.com/neuralsorcerer/sql2sqlx/actions/workflows/dataform_compile.yml?query=branch%3Amain)
67
66
  [![Build](https://github.com/neuralsorcerer/sql2sqlx/actions/workflows/build.yml/badge.svg)](https://github.com/neuralsorcerer/sql2sqlx/actions/workflows/build.yml?query=branch%3Amain)
68
67
  [![Documentation](https://github.com/neuralsorcerer/sql2sqlx/actions/workflows/docs.yml/badge.svg)](https://neuralsorcerer.github.io/sql2sqlx/)
@@ -9,13 +9,13 @@ BigQuery SQL -> Dataform SQLX migration tooling.
9
9
 
10
10
  <div align="center">
11
11
 
12
- [![Current Release](https://img.shields.io/github/release/neuralsorcerer/sql2sqlx.svg)](https://github.com/neuralsorcerer/sql2sqlx/releases)
13
12
  [![PyPI](https://img.shields.io/pypi/v/sql2sqlx.svg?logo=pypi&logoColor=white)](https://pypi.org/project/sql2sqlx/)
14
13
  [![Python 3.10+](https://img.shields.io/badge/Python-3.10+-fcbc2c.svg?logo=python&logoColor=white)](https://www.python.org/downloads/)
15
14
  [![Test Ubuntu](https://github.com/neuralsorcerer/sql2sqlx/actions/workflows/test_ubuntu.yml/badge.svg)](https://github.com/neuralsorcerer/sql2sqlx/actions/workflows/test_ubuntu.yml?query=branch%3Amain)
16
15
  [![Test Windows](https://github.com/neuralsorcerer/sql2sqlx/actions/workflows/test_windows.yml/badge.svg)](https://github.com/neuralsorcerer/sql2sqlx/actions/workflows/test_windows.yml?query=branch%3Amain)
17
16
  [![Test MacOS](https://github.com/neuralsorcerer/sql2sqlx/actions/workflows/test_macos.yml/badge.svg)](https://github.com/neuralsorcerer/sql2sqlx/actions/workflows/test_macos.yml?query=branch%3Amain)
18
17
  [![Lints](https://github.com/neuralsorcerer/sql2sqlx/actions/workflows/lints.yml/badge.svg)](https://github.com/neuralsorcerer/sql2sqlx/actions/workflows/lints.yml?query=branch%3Amain)
18
+ [![CodeQL](https://github.com/neuralsorcerer/sql2sqlx/actions/workflows/codeql.yml/badge.svg)](https://github.com/neuralsorcerer/sql2sqlx/actions/workflows/codeql.yml?query=branch%3Amain)
19
19
  [![Dataform Compile](https://github.com/neuralsorcerer/sql2sqlx/actions/workflows/dataform_compile.yml/badge.svg)](https://github.com/neuralsorcerer/sql2sqlx/actions/workflows/dataform_compile.yml?query=branch%3Amain)
20
20
  [![Build](https://github.com/neuralsorcerer/sql2sqlx/actions/workflows/build.yml/badge.svg)](https://github.com/neuralsorcerer/sql2sqlx/actions/workflows/build.yml?query=branch%3Amain)
21
21
  [![Documentation](https://github.com/neuralsorcerer/sql2sqlx/actions/workflows/docs.yml/badge.svg)](https://neuralsorcerer.github.io/sql2sqlx/)
@@ -0,0 +1,129 @@
1
+ # API reference
2
+
3
+ Everything in this section is generated from the package's docstrings. The
4
+ narrative below orients you before the autodoc entries.
5
+
6
+ The public surface is small and stable. Everything importable from the
7
+ top-level `sql2sqlx` package (listed in its `__all__`) is what semantic
8
+ versioning covers.
9
+
10
+ ## Choosing an entry point
11
+
12
+ Three functions convert; two helpers support them.
13
+
14
+ :::{list-table}
15
+ :header-rows: 1
16
+ :widths: 26 30 44
17
+
18
+ * - Function
19
+ - Input
20
+ - Writes to disk?
21
+ * - `convert_string(sql, options=None, name="input.sql")`
22
+ - a SQL string (one or many statements)
23
+ - No - returns a result you can inspect or `write_result`.
24
+ * - `convert_file(path, options=None)`
25
+ - one `.sql` file
26
+ - No - returns a result.
27
+ * - `convert_directory(input_dir, output_dir=None, options=None)`
28
+ - a directory tree (scanned by `include_glob`)
29
+ - **Only if** `output_dir` is given.
30
+ :::
31
+
32
+ `parse_source(...)` exposes just Phase 1 (lex + split + classify) for one
33
+ source, returning a `ParsedFile`; `write_result(result, output_dir)` writes a
34
+ `ConversionResult` to disk (and refuses paths that would escape the
35
+ destination). All three converters run the same three-phase pipeline described
36
+ in [architecture](architecture.md); `convert_directory` additionally
37
+ parallelizes parsing across a process pool.
38
+
39
+ ```{eval-rst}
40
+ .. autofunction:: sql2sqlx.convert_string
41
+ .. autofunction:: sql2sqlx.convert_file
42
+ .. autofunction:: sql2sqlx.convert_directory
43
+ .. autofunction:: sql2sqlx.write_result
44
+ .. autofunction:: sql2sqlx.parse_source
45
+ ```
46
+
47
+ ## Options and results
48
+
49
+ `ConversionOptions` holds every knob; its defaults preserve semantics (see the
50
+ [CLI reference](cli.md) for the flag-to-field mapping). Its `__post_init__`
51
+ **validates and normalizes** on construction: the string enum values
52
+ (`"incremental"`, `"mirror"`, ...) are accepted as well as the enum members, so
53
+ options are easy to build from JSON or config files, and invalid values raise
54
+ `ValueError` immediately rather than silently mis-selecting a strategy.
55
+
56
+ A conversion returns a `ConversionResult` with two attributes: `files` (the
57
+ generated `SqlxFile` objects, in deterministic sorted-path order) and `report`
58
+ (a [`ConversionReport`](report.md)). All of these are plain, picklable
59
+ dataclasses.
60
+
61
+ ```{eval-rst}
62
+ .. autoclass:: sql2sqlx.ConversionOptions
63
+ :members:
64
+
65
+ .. autoclass:: sql2sqlx.InsertStrategy
66
+ :members:
67
+ .. autoclass:: sql2sqlx.MergeStrategy
68
+ :members:
69
+ .. autoclass:: sql2sqlx.PlainCreateStrategy
70
+ :members:
71
+ .. autoclass:: sql2sqlx.IfNotExistsStrategy
72
+ :members:
73
+ .. autoclass:: sql2sqlx.Layout
74
+ :members:
75
+
76
+ .. autoclass:: sql2sqlx.ConversionResult
77
+ :members:
78
+ .. autoclass:: sql2sqlx.ConversionReport
79
+ :members:
80
+ .. autoclass:: sql2sqlx.ReportWarning
81
+ :members:
82
+ .. autoclass:: sql2sqlx.SqlxFile
83
+ :members:
84
+ .. autoclass:: sql2sqlx.ActionType
85
+ :members:
86
+ .. autoclass:: sql2sqlx.TableName
87
+ :members:
88
+ ```
89
+
90
+ ## Errors
91
+
92
+ Every exception raised deliberately by the library derives from
93
+ `Sql2SqlxError`, so a single `except Sql2SqlxError` catches them all.
94
+ Location-aware errors (`LexError`, `SplitError`) carry 1-based `line` and
95
+ `column` attributes. Note that *unsupported-but-valid* SQL never raises - it
96
+ falls back to an `operations` action and a warning; the exceptions are for
97
+ genuinely broken input (unreadable files, lexer failures). During directory
98
+ conversion, a per-file failure is captured in `report.failures` rather than
99
+ aborting the run.
100
+
101
+ ```{eval-rst}
102
+ .. automodule:: sql2sqlx.errors
103
+ :members:
104
+ ```
105
+
106
+ ## Internals
107
+
108
+ The internal modules are stable enough to build on, but the public surface
109
+ above is what semantic versioning covers.
110
+
111
+ ```{eval-rst}
112
+ .. automodule:: sql2sqlx.lexer
113
+ :members: tokenize, comment_spans, unquote_identifier, Token, LineIndex
114
+
115
+ .. automodule:: sql2sqlx.splitter
116
+ :members:
117
+
118
+ .. automodule:: sql2sqlx.parser
119
+ :members: classify_statement
120
+
121
+ .. automodule:: sql2sqlx.refs
122
+ :members: scan_ref_sites, parse_table_path, PathMatch
123
+
124
+ .. automodule:: sql2sqlx.emitter
125
+ :members:
126
+
127
+ .. automodule:: sql2sqlx.cli
128
+ :members: main, build_arg_parser
129
+ ```
@@ -0,0 +1,170 @@
1
+ # Architecture
2
+
3
+ sql2sqlx is a three-phase pipeline built on one invariant:
4
+
5
+ > **Untouched SQL is preserved character-for-character after decoding.** Output
6
+ > bodies are produced by applying *span edits* to slices of the original text -
7
+ > tokens are never re-serialized, so formatting, casing and inline comments
8
+ > survive exactly.
9
+
10
+ ```text
11
+ .sql files
12
+ |
13
+ v
14
+ Phase 1: parse each file, in parallel when possible
15
+ - lexer: tokens and comment spans
16
+ - splitter: top-level statements
17
+ - classifier: action drafts and metadata
18
+ - reference scanner: candidate table read sites
19
+ |
20
+ v
21
+ Phase 2: link corpus metadata
22
+ - creators and duplicate demotion
23
+ - writer chains and ordering dependencies
24
+ - hasOutput election
25
+ - ref resolution and declaration synthesis
26
+ - action names and output paths
27
+ |
28
+ v
29
+ Phase 3: emit SQLX
30
+ - config blocks
31
+ - source-span edits
32
+ - provenance and original comments
33
+ - .sqlx files
34
+ ```
35
+
36
+ The phases map onto the module layout: `lexer`, `splitter`, `parser` and
37
+ `refs` do Phase 1; `converter._Linker` does Phase 2; `emitter` does Phase 3.
38
+ The shared, picklable data model lives in `model` so parsed results can cross
39
+ process boundaries.
40
+
41
+ ## Phase 1 - lexing, splitting, classifying
42
+
43
+ This phase is **per file** and has no cross-file dependencies, which is what
44
+ makes it parallelizable.
45
+
46
+ ### Lexer (`sql2sqlx.lexer`)
47
+
48
+ A single compiled regex alternation, executed by CPython's C regex engine,
49
+ implements the GoogleSQL lexical specification. Every alternative is written to
50
+ match in strictly linear time (no ambiguous nested quantifiers, hence no
51
+ catastrophic backtracking), giving high throughput while remaining exactly
52
+ character-accurate. It recognizes:
53
+
54
+ - line comments (`--` and `#`) and non-nested block comments (`/* ... */`,
55
+ ending at the first `*/`, exactly as GoogleSQL defines them);
56
+ - all string/bytes/raw literal forms, including triple-quoted strings and
57
+ every valid `r`/`b` prefix combination and case, with GoogleSQL's exact
58
+ backslash-escape rules (a raw literal can never end in an odd number of
59
+ backslashes, and so on);
60
+ - backtick-quoted identifiers with escapes and embedded dots;
61
+ - numeric literals, named/positional query parameters and system variables,
62
+ and every GoogleSQL operator and punctuation token.
63
+
64
+ Tokens carry `(kind, text, start, end)` with character offsets into the
65
+ original text - the anchor for span-exact emission later. Unterminated strings,
66
+ backtick identifiers and block comments raise a `LexError` with an exact
67
+ 1-based line and column. Comment spans are captured in the **same pass** (via
68
+ an output list), so no file is ever lexed twice.
69
+
70
+ ### Splitter (`sql2sqlx.splitter`)
71
+
72
+ Splits the token stream into top-level statements on `;`, but only at
73
+ parenthesis depth 0 **and** with an empty *block-frame stack*. Frames model
74
+ BigQuery scripting constructs - `BEGIN ... END`, `IF ... END IF`, loops, and
75
+ `CASE` - and openers are recognized only in statement position. A bounded
76
+ lookahead distinguishes the scripting `IF cond THEN` from the `IF(a, b, c)`
77
+ function, and `BEGIN [TRANSACTION];` is treated as a statement, not a block
78
+ opener. The splitter is deliberately lenient: a mismatched `END` is tolerated
79
+ rather than fatal, so odd-but-valid scripts still split sensibly.
80
+
81
+ ### Classifier (`sql2sqlx.parser`)
82
+
83
+ Maps each statement to an `ActionDraft`: its action type, target table,
84
+ already-known `config` entries, the metadata extracted from
85
+ `PARTITION BY`/`CLUSTER BY`/`OPTIONS`, and any classification warnings. This is
86
+ where the opt-in analyses live:
87
+
88
+ - the select-list aliasing analysis for `INSERT -> incremental` (proving each
89
+ output name is exactly determinable, or refusing);
90
+ - the MERGE shape proof (all six obligations) plus its target-schema
91
+ precondition warning;
92
+ - detection of the forms that must fall back (`LIKE`/`CLONE`/`COPY`, column-list
93
+ CTAS, temp/external/snapshot tables, guarded creates, `INSERT ... VALUES`).
94
+
95
+ ### Reference scanner (`sql2sqlx.refs`)
96
+
97
+ A context-tracked scan for physical table paths in read positions - after
98
+ `FROM`, `JOIN`, from-list commas and `MERGE ... USING` - carrying per-query
99
+ CTE and range-variable scopes. It models recursive versus ordered CTE
100
+ visibility, correlated scalar subqueries versus non-lateral `FROM` subqueries,
101
+ set-operation branches, implicit aliases and `UNNEST` aliases; `EXTRACT(x FROM
102
+ col)` stays inert, and dashed-project adjacency and target skip-spans (e.g. the
103
+ target of `DELETE FROM x`) are handled without ever re-serializing SQL. The
104
+ output is a list of candidate `RefSite`s (character spans plus the decoded,
105
+ still-unresolved `TableName`).
106
+
107
+ ### Whole-file scripts
108
+
109
+ Files that use transactions, temporary objects, variables, procedural control
110
+ flow, or dynamic side effects cannot be safely split into independent Dataform
111
+ actions without changing control-flow or scope semantics, so they collapse
112
+ into a **single whole-file `operations` draft**. A conservative nested scan
113
+ still harvests persistent reference sites and write targets from inside the
114
+ script, so dependency wiring works, while temporary names are kept from
115
+ leaking into the corpus.
116
+
117
+ ## Phase 2 - the linker
118
+
119
+ `_Linker` runs once, single-process, over the lightweight drafts that Phase 1
120
+ produced (parallel workers ship back picklable `ParsedFile`/`ActionDraft`
121
+ objects). In order:
122
+
123
+ 1. one **creator** per resolved target `(project, dataset, table)`; a second
124
+ producer of the same target is demoted to a verbatim `operations` action
125
+ (`DUPLICATE_TARGET`), because Dataform allows exactly one owner per target;
126
+ 2. **writer chains** per table in corpus order (sorted relative path, then
127
+ statement position): every writer depends on its predecessor, and a
128
+ same-file writer also waits for intervening readers, so
129
+ `DROP x; CREATE x; UPDATE x` keeps its order;
130
+ 3. **`hasOutput` election**: an ownerless `operations` action that actually
131
+ creates its configured output (and only such an action - never mutating
132
+ DML) is elected as a producer, gaining `hasOutput: true` and a `${self()}`
133
+ rewrite of its own target path;
134
+ 4. **reference resolution** against the `--default-project/--default-dataset`
135
+ values, rewriting resolved read sites to `${ref(...)}` and adding reader
136
+ dependencies on each table's latest preceding writer;
137
+ 5. **future-owner protection** that leaves an earlier read literal
138
+ (`FUTURE_CREATOR`) rather than reversing corpus order, plus **cycle
139
+ detection** that omits an unsafe edge (`DEPENDENCY_CYCLE`) rather than
140
+ emitting an uncompilable graph;
141
+ 6. unique action naming and collision-free output paths - deterministic, so
142
+ two runs produce identical bytes.
143
+
144
+ See the [dependency model](dependencies.md) for the full semantics of each
145
+ edge and the warnings they raise.
146
+
147
+ ## Phase 3 - emission
148
+
149
+ `sql2sqlx.emitter` renders each linked draft to a `.sqlx` file: it builds the
150
+ canonical `config { ... }` block (fixed key order), applies the accumulated
151
+ span edits to a slice of the original source text, emits literal `${` and
152
+ SQLX-unsafe comments through constant placeholders (while inserted
153
+ `${ref()}`/`${self()}` stay active), strips one trailing semicolon, and
154
+ attaches the provenance comment plus the statement's own leading comments. The
155
+ result is UTF-8 with `\n` newlines. See [core concepts](concepts.md) for the
156
+ output anatomy.
157
+
158
+ ## Performance model
159
+
160
+ Lexing dominates runtime, and it runs at C speed via the master regex pattern.
161
+ Directory conversion parallelizes Phase 1 across a `ProcessPoolExecutor` sized
162
+ by `--jobs` (`0` = one worker per CPU); Phase 2 and Phase 3 are a fast
163
+ single-process metadata pass. Because parsing is embarrassingly parallel and
164
+ linking is cheap, throughput scales with core count on large corpora.
165
+
166
+ Measured on this package's benchmark (`examples/06_benchmark.py`, 120 files /
167
+ 1,056,600 lines / 31.7 MB, single core): **43.8 s ~ 24,100 lines/s**, with
168
+ ~96k actions emitted and ~96k references rewritten. Memory is proportional to
169
+ corpus size, because the original text of each file is retained for span-exact
170
+ emission.
@@ -0,0 +1,211 @@
1
+ # CLI reference
2
+
3
+ ```text
4
+ sql2sqlx INPUT [-o DIR] [options]
5
+ ```
6
+
7
+ The CLI is a thin veneer over [`convert_file`](api.md) and
8
+ [`convert_directory`](api.md). It can also be run as a module:
9
+ `python -m sql2sqlx ...`.
10
+
11
+ ## Input and output modes
12
+
13
+ `INPUT` is either a single `.sql` **file** or a **directory** of `.sql` files.
14
+ What happens next depends on the input kind and whether `--output` is given:
15
+
16
+ :::{list-table}
17
+ :header-rows: 1
18
+ :widths: 22 20 58
19
+
20
+ * - Input
21
+ - `--output`
22
+ - Behavior
23
+ * - file
24
+ - omitted
25
+ - The generated SQLX is printed to **stdout**; the run summary goes to
26
+ **stderr**, so `sql2sqlx model.sql > out.sqlx` is clean.
27
+ * - file
28
+ - given
29
+ - The generated file(s) are written beneath the output directory.
30
+ * - directory
31
+ - given
32
+ - The converted tree is written beneath the output directory.
33
+ * - directory
34
+ - omitted
35
+ - **Error**, unless `--dry-run` is passed (a directory has no meaningful
36
+ stdout form).
37
+ :::
38
+
39
+ When a single file produces **multiple** actions and is printed to stdout, the
40
+ files are separated by a header line so the stream stays parseable:
41
+
42
+ ```text
43
+ -- ===== active.sqlx =====
44
+ config { ... }
45
+ ...
46
+
47
+ -- ===== customers.sqlx =====
48
+ config { ... }
49
+ ...
50
+ ```
51
+
52
+ ## Options
53
+
54
+ :::{list-table}
55
+ :header-rows: 1
56
+ :widths: 34 14 52
57
+
58
+ * - Flag
59
+ - Default
60
+ - Meaning
61
+ * - `-o, --output DIR`
62
+ - -
63
+ - Where to write `.sqlx` files (typically your Dataform `definitions/`).
64
+ * - `--report FILE`
65
+ - -
66
+ - Write the full JSON [conversion report](report.md) to `FILE`.
67
+ * - `--default-project ID`
68
+ - -
69
+ - Project assumed for unqualified table paths (reference resolution only).
70
+ * - `--default-dataset ID`
71
+ - -
72
+ - Dataset assumed for unqualified paths; also resolves single-part
73
+ references to qualified producers.
74
+ * - `--default-location LOCATION`
75
+ - `US`
76
+ - Location written into a scaffolded `workflow_settings.yaml`.
77
+ * - `--layout {mirror,flat}`
78
+ - `mirror`
79
+ - Mirror the input directory tree, or flatten all output into the root.
80
+ * - `--insert-strategy {incremental,operations}`
81
+ - `operations`
82
+ - How `INSERT ... SELECT` converts.
83
+ * - `--merge-strategy {operations,incremental-when-safe}`
84
+ - `operations`
85
+ - How `MERGE` converts.
86
+ * - `--plain-create {operations,declaration}`
87
+ - `operations`
88
+ - How `CREATE TABLE` **without** `AS SELECT` converts.
89
+ * - `--if-not-exists {table,operations}`
90
+ - `operations`
91
+ - How guarded `CREATE TABLE/VIEW IF NOT EXISTS ... AS` converts.
92
+ * - `--declare-external`
93
+ - off
94
+ - Emit `declaration` actions under `sources/` for referenced-but-not-produced
95
+ tables, and `ref()` them too.
96
+ * - `--no-protected`
97
+ - off
98
+ - Do **not** mark converted incrementals `protected: true`.
99
+ * - `--no-annotate`
100
+ - off
101
+ - Omit the `-- source: file:line` provenance comments.
102
+ * - `--tags A,B`
103
+ - -
104
+ - Comma-separated Dataform tags added to every generated action.
105
+ * - `--include GLOB`
106
+ - `*.sql`
107
+ - Filename glob for directory scans (must stay within the input directory).
108
+ * - `--encoding ENC`
109
+ - `utf-8`
110
+ - Encoding used to read input files (must be a text codec).
111
+ * - `-j, --jobs N`
112
+ - `0` (auto)
113
+ - Parser worker processes; `0` = one per CPU, `1` = no multiprocessing.
114
+ * - `--dry-run`
115
+ - off
116
+ - Convert and report, but write no `.sqlx` files.
117
+ * - `--overwrite`
118
+ - off
119
+ - Allow writing into an output directory that already contains `.sqlx` files.
120
+ * - `--init-project`
121
+ - off
122
+ - Also scaffold a `workflow_settings.yaml`.
123
+ * - `-q, --quiet`
124
+ - -
125
+ - Suppress the summary.
126
+ * - `-v, --verbose`
127
+ - -
128
+ - Also list every warning and generated file.
129
+ * - `--version`
130
+ - -
131
+ - Print the version and exit.
132
+ :::
133
+
134
+ ### Strategy flags in depth
135
+
136
+ The four strategy flags each default to the **semantics-preserving** option
137
+ and let you opt into more idiomatic Dataform structure where you accept the
138
+ trade-off. Their full rules, proof obligations and the warnings they emit are
139
+ documented in [conversion rules](conversion_rules.md):
140
+
141
+ - `--insert-strategy incremental` produces `type: "incremental"` from
142
+ `INSERT ... SELECT`, marks it `protected: true` (unless `--no-protected`),
143
+ and reports `INSERT_INCREMENTAL`.
144
+ - `--merge-strategy incremental-when-safe` converts only MERGEs whose shape is
145
+ *provably* equivalent to an incremental upsert; everything else stays
146
+ `operations` with a `MERGE_FALLBACK` reason.
147
+ - `--plain-create declaration` turns bodiless `CREATE TABLE (schema)` DDL into
148
+ a `declaration` (drops the DDL; use for externally managed tables).
149
+ - `--if-not-exists table` converts guarded `... IF NOT EXISTS ... AS`
150
+ statements to typed actions and reports the lost create-if-absent guard.
151
+
152
+ ### The `--init-project` scaffold
153
+
154
+ `--init-project` writes a Dataform-core 3.x `workflow_settings.yaml`. If the
155
+ output directory is named `definitions`, the file is placed in its **parent**
156
+ (the project root); otherwise it is placed inside the output directory. It is
157
+ **never** silently overwritten - pass `--overwrite` to replace an existing one.
158
+
159
+ ```yaml
160
+ defaultProject: "my-gcp-project" # from --default-project, else "your-gcp-project"
161
+ defaultLocation: "US" # from --default-location
162
+ defaultDataset: "analytics" # from --default-dataset, else "dataform"
163
+ defaultAssertionDataset: "dataform_assertions"
164
+ dataformCoreVersion: "3.0.61"
165
+ ```
166
+
167
+ ### The overwrite guard
168
+
169
+ When writing to a directory, if the output path already contains any `.sqlx`
170
+ files and `--overwrite` is not set, `sql2sqlx` stops with exit code `2` rather
171
+ than risk clobbering hand-written actions. Because conversion is deterministic,
172
+ re-running with `--overwrite` in CI produces the same tree and a clean diff.
173
+
174
+ ## Exit codes
175
+
176
+ :::{list-table}
177
+ :header-rows: 1
178
+ :widths: 12 88
179
+
180
+ * - Code
181
+ - Meaning
182
+ * - `0`
183
+ - Converted successfully (there may still be warnings).
184
+ * - `1`
185
+ - One or more input files failed to parse (see `FAILED` lines and the
186
+ report's `failures`).
187
+ * - `2`
188
+ - Usage error: bad arguments, a directory input without `--output`/`--dry-run`,
189
+ the output-directory guard, or an output-write error.
190
+ :::
191
+
192
+ ## Examples
193
+
194
+ ```bash
195
+ # Inspect a single file without writing anything
196
+ sql2sqlx model.sql | less
197
+
198
+ # Full migration with shape-checked MERGE conversion and source declarations
199
+ sql2sqlx ./sql -o ./definitions \
200
+ --merge-strategy incremental-when-safe \
201
+ --declare-external --tags migrated --report report.json
202
+
203
+ # CI-style dry run that only produces the report
204
+ sql2sqlx ./sql --dry-run --report report.json -q
205
+
206
+ # Regenerate an existing project deterministically, verbosely
207
+ sql2sqlx ./sql -o ./definitions --overwrite -v
208
+
209
+ # Convert only staging models, single-process
210
+ sql2sqlx ./sql -o ./definitions --include "stg_*.sql" -j 1
211
+ ```