sql2sqlx 0.1.0__py3-none-any.whl → 0.1.1__py3-none-any.whl
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.
- sql2sqlx/converter.py +16 -4
- sql2sqlx/emitter.py +59 -21
- sql2sqlx/version.py +1 -1
- {sql2sqlx-0.1.0.dist-info → sql2sqlx-0.1.1.dist-info}/METADATA +2 -3
- {sql2sqlx-0.1.0.dist-info → sql2sqlx-0.1.1.dist-info}/RECORD +9 -9
- {sql2sqlx-0.1.0.dist-info → sql2sqlx-0.1.1.dist-info}/WHEEL +0 -0
- {sql2sqlx-0.1.0.dist-info → sql2sqlx-0.1.1.dist-info}/entry_points.txt +0 -0
- {sql2sqlx-0.1.0.dist-info → sql2sqlx-0.1.1.dist-info}/licenses/LICENSE +0 -0
- {sql2sqlx-0.1.0.dist-info → sql2sqlx-0.1.1.dist-info}/top_level.txt +0 -0
sql2sqlx/converter.py
CHANGED
|
@@ -46,7 +46,13 @@ from concurrent.futures import ProcessPoolExecutor
|
|
|
46
46
|
from pathlib import Path, PurePosixPath
|
|
47
47
|
from typing import Any, Dict, List, Optional, Sequence, Set, Tuple
|
|
48
48
|
|
|
49
|
-
from sql2sqlx.emitter import
|
|
49
|
+
from sql2sqlx.emitter import (
|
|
50
|
+
apply_edits_escaped,
|
|
51
|
+
build_sqlx,
|
|
52
|
+
normalize_sqlx_comment,
|
|
53
|
+
ref_expr,
|
|
54
|
+
sqlx_escape_edits,
|
|
55
|
+
)
|
|
50
56
|
from sql2sqlx.errors import ConversionError, LexError
|
|
51
57
|
from sql2sqlx.lexer import IDENT, OP, LineIndex, Token, tokenize
|
|
52
58
|
from sql2sqlx.model import (
|
|
@@ -1058,12 +1064,12 @@ class _Linker:
|
|
|
1058
1064
|
leading = None
|
|
1059
1065
|
spans = [(a, b) for a, b in self._comments(f) if prev_end <= a and b <= d.stmt_start]
|
|
1060
1066
|
if spans:
|
|
1061
|
-
leading = "\n".join(f.text[a:b] for a, b in spans)
|
|
1067
|
+
leading = "\n".join(normalize_sqlx_comment(f.text[a:b]) for a, b in spans)
|
|
1062
1068
|
trailing = None
|
|
1063
1069
|
if is_last_in_file:
|
|
1064
1070
|
tail_spans = [(a, b) for a, b in self._comments(f) if a >= d.stmt_end]
|
|
1065
1071
|
if tail_spans:
|
|
1066
|
-
trailing = "\n".join(f.text[a:b] for a, b in tail_spans)
|
|
1072
|
+
trailing = "\n".join(normalize_sqlx_comment(f.text[a:b]) for a, b in tail_spans)
|
|
1067
1073
|
body = None
|
|
1068
1074
|
if d.action_type is not ActionType.DECLARATION:
|
|
1069
1075
|
# Semantic rewrites own their complete source spans. In
|
|
@@ -1077,11 +1083,17 @@ class _Linker:
|
|
|
1077
1083
|
semantic[0] < escape[1] and escape[0] < semantic[1] for semantic in d.edits
|
|
1078
1084
|
)
|
|
1079
1085
|
]
|
|
1086
|
+
comment_edits = []
|
|
1087
|
+
for a, b in self._comments(f):
|
|
1088
|
+
if d.body_start <= a and b <= d.body_end:
|
|
1089
|
+
normalized = normalize_sqlx_comment(f.text[a:b])
|
|
1090
|
+
if normalized != f.text[a:b]:
|
|
1091
|
+
comment_edits.append((a, b, normalized))
|
|
1080
1092
|
body = apply_edits_escaped(
|
|
1081
1093
|
f.text,
|
|
1082
1094
|
d.body_start,
|
|
1083
1095
|
d.body_end,
|
|
1084
|
-
d.edits + escape_edits,
|
|
1096
|
+
d.edits + escape_edits + comment_edits,
|
|
1085
1097
|
)
|
|
1086
1098
|
content = build_sqlx(config, body, annotation, leading, trailing)
|
|
1087
1099
|
|
sql2sqlx/emitter.py
CHANGED
|
@@ -12,12 +12,15 @@ select-list aliases, ``${self()}`` substitutions) to slices of the
|
|
|
12
12
|
**original** source text - tokens are never re-serialized, so the user's
|
|
13
13
|
formatting, casing and inline comments survive after input decoding.
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
SQLX-specific safety rules live here:
|
|
16
16
|
|
|
17
17
|
* Any literal ``${`` in a SQL string or quoted identifier is emitted through
|
|
18
18
|
a constant JavaScript placeholder. Dataform does not provide a backslash
|
|
19
19
|
escape for SQL placeholders, so ``\\${`` is insufficient. Replacement text
|
|
20
20
|
inserted *by* sql2sqlx (``${ref(...)}``, ``${self()}``) stays active.
|
|
21
|
+
* GoogleSQL comments with SQLX-specific lexical hazards (``#`` comments and
|
|
22
|
+
exact ``---`` separator-looking comments) are normalized without changing
|
|
23
|
+
BigQuery semantics.
|
|
21
24
|
* Operations bodies have exactly one trailing semicolon stripped:
|
|
22
25
|
Dataform executes the body as a BigQuery script, and a trailing empty
|
|
23
26
|
statement is at best noise.
|
|
@@ -29,7 +32,7 @@ import json
|
|
|
29
32
|
import re
|
|
30
33
|
from typing import Any, Dict, Iterable, List, Optional, Sequence, Tuple
|
|
31
34
|
|
|
32
|
-
from sql2sqlx.lexer import BACKTICK, PARAM, STRING, Token
|
|
35
|
+
from sql2sqlx.lexer import BACKTICK, COMMENT, PARAM, STRING, Token
|
|
33
36
|
from sql2sqlx.model import TableName
|
|
34
37
|
|
|
35
38
|
#: Canonical top-level config key order (unknown keys follow, insertion-ordered).
|
|
@@ -62,6 +65,12 @@ _BQ_ORDER = (
|
|
|
62
65
|
)
|
|
63
66
|
|
|
64
67
|
_JS_IDENT_RE = re.compile(r"^[A-Za-z_$][A-Za-z0-9_$]*$")
|
|
68
|
+
_SEPARATOR_COMMENT_RE = re.compile(r"^---[^\S\r\n]*$")
|
|
69
|
+
#: Control characters that JSON encoding turns into ``\n``/``\t``/``\uXXXX``
|
|
70
|
+
#: escapes. Dataform's placeholder-string lexer accepts only ``\"`` and ``\\``
|
|
71
|
+
#: escapes (``/"(?:\\["\\]|[^\n"\\])*"/``), so a whole-string placeholder that
|
|
72
|
+
#: embeds one of these characters cannot be lexed by Dataform.
|
|
73
|
+
_JS_STRING_UNSAFE_RE = re.compile(r"[\x00-\x1f]")
|
|
65
74
|
|
|
66
75
|
|
|
67
76
|
def _json(value: str) -> str:
|
|
@@ -74,32 +83,61 @@ def sqlx_constant(value: str) -> str:
|
|
|
74
83
|
return "${" + _json(value) + "}"
|
|
75
84
|
|
|
76
85
|
|
|
77
|
-
def
|
|
78
|
-
"""
|
|
86
|
+
def normalize_sqlx_comment(comment: str) -> str:
|
|
87
|
+
"""Return a SQLX-safe spelling of a single GoogleSQL comment token.
|
|
88
|
+
|
|
89
|
+
Dataform SQLX treats ``---`` at the start of a line as a statement
|
|
90
|
+
separator and does not recognize GoogleSQL ``#`` line comments. The
|
|
91
|
+
rewrites here preserve BigQuery semantics while preventing comment text
|
|
92
|
+
from being parsed as SQLX syntax.
|
|
93
|
+
"""
|
|
94
|
+
if comment.startswith("#"):
|
|
95
|
+
# Rewriting ``#`` to ``--`` can itself produce a ``---`` separator
|
|
96
|
+
# line (e.g. the comment ``#-``), so fall through to the separator
|
|
97
|
+
# guard below instead of returning early.
|
|
98
|
+
comment = "--" + comment[1:]
|
|
99
|
+
if _SEPARATOR_COMMENT_RE.match(comment):
|
|
100
|
+
return "-- " + comment
|
|
101
|
+
return comment
|
|
79
102
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
103
|
+
|
|
104
|
+
def sqlx_escape_edits(tokens: Sequence[Token]) -> List[Tuple[int, int, str]]:
|
|
105
|
+
"""Build edits that keep literal SQL safe when parsed as SQLX.
|
|
106
|
+
|
|
107
|
+
Literal ``${`` in SQL strings, quoted identifiers, and quoted parameters
|
|
108
|
+
is emitted through a constant JavaScript placeholder because Dataform has
|
|
109
|
+
no SQL-level escape for placeholders. String literals are replaced as
|
|
110
|
+
whole tokens so safety does not depend on Dataform's per-quote lexer
|
|
111
|
+
states. A string that carries a control character (for example a
|
|
112
|
+
multi-line triple-quoted literal) cannot be embedded whole - JSON encoding
|
|
113
|
+
would introduce a ``\\n``/``\\t`` escape that Dataform's placeholder-string
|
|
114
|
+
lexer rejects - so each ``${`` in such a string is escaped in place, where
|
|
115
|
+
the surrounding SQL string state carries the raw characters unchanged.
|
|
116
|
+
GoogleSQL comments that are unsafe in SQLX are normalized as token edits.
|
|
85
117
|
"""
|
|
86
118
|
edits: List[Tuple[int, int, str]] = []
|
|
87
119
|
for token in tokens:
|
|
88
|
-
if
|
|
120
|
+
if token.kind == COMMENT:
|
|
121
|
+
normalized = normalize_sqlx_comment(token.text)
|
|
122
|
+
if normalized != token.text:
|
|
123
|
+
edits.append((token.start, token.end, normalized))
|
|
89
124
|
continue
|
|
90
|
-
if
|
|
91
|
-
edits.append((token.start, token.end, sqlx_constant(token.text)))
|
|
125
|
+
if "${" not in token.text:
|
|
92
126
|
continue
|
|
93
|
-
if token.kind
|
|
127
|
+
if token.kind == STRING and _JS_STRING_UNSAFE_RE.search(token.text):
|
|
128
|
+
offset = 0
|
|
129
|
+
while True:
|
|
130
|
+
offset = token.text.find("${", offset)
|
|
131
|
+
if offset < 0:
|
|
132
|
+
break
|
|
133
|
+
start = token.start + offset
|
|
134
|
+
edits.append((start, start + 2, sqlx_constant("${")))
|
|
135
|
+
offset += 2
|
|
94
136
|
continue
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
break
|
|
100
|
-
start = token.start + offset
|
|
101
|
-
edits.append((start, start + 2, sqlx_constant("${")))
|
|
102
|
-
offset += 2
|
|
137
|
+
if token.kind in {BACKTICK, STRING} or (
|
|
138
|
+
token.kind == PARAM and token.text.startswith("@`")
|
|
139
|
+
):
|
|
140
|
+
edits.append((token.start, token.end, sqlx_constant(token.text)))
|
|
103
141
|
return edits
|
|
104
142
|
|
|
105
143
|
|
sql2sqlx/version.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sql2sqlx
|
|
3
|
-
Version: 0.1.
|
|
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
|
-
[](https://github.com/neuralsorcerer/sql2sqlx/releases)
|
|
60
58
|
[](https://pypi.org/project/sql2sqlx/)
|
|
61
59
|
[](https://www.python.org/downloads/)
|
|
62
60
|
[](https://github.com/neuralsorcerer/sql2sqlx/actions/workflows/test_ubuntu.yml?query=branch%3Amain)
|
|
63
61
|
[](https://github.com/neuralsorcerer/sql2sqlx/actions/workflows/test_windows.yml?query=branch%3Amain)
|
|
64
62
|
[](https://github.com/neuralsorcerer/sql2sqlx/actions/workflows/test_macos.yml?query=branch%3Amain)
|
|
65
63
|
[](https://github.com/neuralsorcerer/sql2sqlx/actions/workflows/lints.yml?query=branch%3Amain)
|
|
64
|
+
[](https://github.com/neuralsorcerer/sql2sqlx/actions/workflows/codeql.yml?query=branch%3Amain)
|
|
66
65
|
[](https://github.com/neuralsorcerer/sql2sqlx/actions/workflows/dataform_compile.yml?query=branch%3Amain)
|
|
67
66
|
[](https://github.com/neuralsorcerer/sql2sqlx/actions/workflows/build.yml?query=branch%3Amain)
|
|
68
67
|
[](https://neuralsorcerer.github.io/sql2sqlx/)
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
sql2sqlx/__init__.py,sha256=kT1TsG_BQaD9I0nMhQJOIIHbmQ_AHeyLFENt_g5wAFY,2101
|
|
2
2
|
sql2sqlx/__main__.py,sha256=_w-oHujFK7NsgnIQmZ9RW22bvH9YZrbajk6YSS3csYE,363
|
|
3
3
|
sql2sqlx/cli.py,sha256=jBZ6QeLs-to8vjhY_vbiTreE-svBrIcYsGGSXZjhuPQ,11850
|
|
4
|
-
sql2sqlx/converter.py,sha256=
|
|
5
|
-
sql2sqlx/emitter.py,sha256=
|
|
4
|
+
sql2sqlx/converter.py,sha256=7QLLnnCD7hOQKtmx3a4iKxdagDepPcCuucrpZENpA1I,50903
|
|
5
|
+
sql2sqlx/emitter.py,sha256=wjykaralrmwSvuc1c-9jg0MrqFJXNUY7mfT3c6nFKQg,11563
|
|
6
6
|
sql2sqlx/errors.py,sha256=XpNecYBPylxO_PevnGeJLjrO6n4zYnf9U3QeK9yTvXs,3231
|
|
7
7
|
sql2sqlx/keywords.py,sha256=4ofwRHXp2gDn_z1dNoti2_kaOiHEcdNOgdzadW7imSQ,3559
|
|
8
8
|
sql2sqlx/lexer.py,sha256=bVMDwcSgGN_k9eCQa-rziyQPqdx17OMkiPeEx9Ub26E,18837
|
|
@@ -11,10 +11,10 @@ sql2sqlx/parser.py,sha256=yKs5FujN_9OgbSoKqU3ioN0PKJXgzAT2TDS9BrQZdYs,77049
|
|
|
11
11
|
sql2sqlx/py.typed,sha256=daEdpEyAJIa8b2VkCqSKcw8PaExcB6Qro80XNes_sHA,2
|
|
12
12
|
sql2sqlx/refs.py,sha256=UZtom1SYw_QWYVwigniy882eBaL6M9orz_y9lPBRYVI,33916
|
|
13
13
|
sql2sqlx/splitter.py,sha256=Ppo_gxaWmzZO6iYO_0Tc28W1bI4pbP1I3EF1juAw4Yg,14735
|
|
14
|
-
sql2sqlx/version.py,sha256=
|
|
15
|
-
sql2sqlx-0.1.
|
|
16
|
-
sql2sqlx-0.1.
|
|
17
|
-
sql2sqlx-0.1.
|
|
18
|
-
sql2sqlx-0.1.
|
|
19
|
-
sql2sqlx-0.1.
|
|
20
|
-
sql2sqlx-0.1.
|
|
14
|
+
sql2sqlx/version.py,sha256=p3LTeKIkF_Myl1AFF5RPWlTN28MJvmQHvBnzwyT6YOo,476
|
|
15
|
+
sql2sqlx-0.1.1.dist-info/licenses/LICENSE,sha256=eK7fnHYjw-1st_pakYslBS41i1dEaHkvmtJzEVoubcE,11347
|
|
16
|
+
sql2sqlx-0.1.1.dist-info/METADATA,sha256=B86YhahfoD9Z3WgDejmjTUwytgc7GDGZzY1gdWWhM9M,28792
|
|
17
|
+
sql2sqlx-0.1.1.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
18
|
+
sql2sqlx-0.1.1.dist-info/entry_points.txt,sha256=n30wxr_Q4nH5h7tTEZO5ohXdJWqePYesmwpA32D4ygk,47
|
|
19
|
+
sql2sqlx-0.1.1.dist-info/top_level.txt,sha256=nbDaiS9bRjW8S9naDOgtVvFBiFhiXgX9k8PcKCF_goI,9
|
|
20
|
+
sql2sqlx-0.1.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|