sqlleaf 0.0.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.
- sqlleaf/__init__.py +4 -0
- sqlleaf/dialects/__init__.py +0 -0
- sqlleaf/dialects/plpgsql/__init__.py +0 -0
- sqlleaf/dialects/plpgsql/p_classes.py +275 -0
- sqlleaf/dialects/plpgsql/p_dialect.py +148 -0
- sqlleaf/dialects/plpgsql/p_generator.py +538 -0
- sqlleaf/dialects/plpgsql/p_parser.py +1244 -0
- sqlleaf/dialects/plpgsql/p_tokenizer.py +41 -0
- sqlleaf/exception.py +67 -0
- sqlleaf/lineage.py +326 -0
- sqlleaf/mappings.py +376 -0
- sqlleaf/models/__init__.py +0 -0
- sqlleaf/models/context.py +153 -0
- sqlleaf/models/hook.py +9 -0
- sqlleaf/models/node/__init__.py +27 -0
- sqlleaf/models/node/base.py +148 -0
- sqlleaf/models/node/column.py +345 -0
- sqlleaf/models/node/dynamodb.py +39 -0
- sqlleaf/models/node/function.py +33 -0
- sqlleaf/models/node/interval.py +21 -0
- sqlleaf/models/node/json.py +57 -0
- sqlleaf/models/node/literal.py +17 -0
- sqlleaf/models/node/null.py +14 -0
- sqlleaf/models/node/pivot.py +34 -0
- sqlleaf/models/node/program.py +27 -0
- sqlleaf/models/node/sequence.py +18 -0
- sqlleaf/models/node/star.py +11 -0
- sqlleaf/models/node/stream.py +14 -0
- sqlleaf/models/node/user_defined_function.py +33 -0
- sqlleaf/models/node/var.py +7 -0
- sqlleaf/models/node/variable.py +8 -0
- sqlleaf/models/node/window.py +22 -0
- sqlleaf/models/query/__init__.py +33 -0
- sqlleaf/models/query/base.py +248 -0
- sqlleaf/models/query/call.py +111 -0
- sqlleaf/models/query/copy.py +144 -0
- sqlleaf/models/query/ctas.py +61 -0
- sqlleaf/models/query/database.py +48 -0
- sqlleaf/models/query/delete.py +26 -0
- sqlleaf/models/query/execute.py +66 -0
- sqlleaf/models/query/holder.py +49 -0
- sqlleaf/models/query/insert.py +69 -0
- sqlleaf/models/query/merge.py +29 -0
- sqlleaf/models/query/multitable_insert.py +42 -0
- sqlleaf/models/query/prepare.py +61 -0
- sqlleaf/models/query/procedure.py +72 -0
- sqlleaf/models/query/put.py +38 -0
- sqlleaf/models/query/replace.py +32 -0
- sqlleaf/models/query/schema.py +47 -0
- sqlleaf/models/query/select.py +27 -0
- sqlleaf/models/query/sequence.py +33 -0
- sqlleaf/models/query/set.py +74 -0
- sqlleaf/models/query/stage.py +78 -0
- sqlleaf/models/query/table.py +220 -0
- sqlleaf/models/query/trigger.py +42 -0
- sqlleaf/models/query/type.py +42 -0
- sqlleaf/models/query/unload.py +64 -0
- sqlleaf/models/query/update.py +45 -0
- sqlleaf/models/query/user_defined_function.py +297 -0
- sqlleaf/models/query/values.py +20 -0
- sqlleaf/models/query/view.py +41 -0
- sqlleaf/path.py +194 -0
- sqlleaf/processors/__init__.py +0 -0
- sqlleaf/processors/collector/__init__.py +0 -0
- sqlleaf/processors/collector/collector.py +827 -0
- sqlleaf/processors/collector/substitute.py +396 -0
- sqlleaf/processors/generator/__init__.py +0 -0
- sqlleaf/processors/generator/dialects/__init__.py +5 -0
- sqlleaf/processors/generator/dialects/athena.py +45 -0
- sqlleaf/processors/generator/dialects/base.py +572 -0
- sqlleaf/processors/generator/dialects/mysql.py +19 -0
- sqlleaf/processors/generator/dialects/postgres.py +182 -0
- sqlleaf/processors/generator/dialects/redshift.py +192 -0
- sqlleaf/processors/generator/dialects/snowflake.py +90 -0
- sqlleaf/processors/generator/generator.py +495 -0
- sqlleaf/processors/transformer/__init__.py +31 -0
- sqlleaf/processors/transformer/base.py +899 -0
- sqlleaf/processors/transformer/call.py +9 -0
- sqlleaf/processors/transformer/copy.py +63 -0
- sqlleaf/processors/transformer/ctas.py +39 -0
- sqlleaf/processors/transformer/delete.py +14 -0
- sqlleaf/processors/transformer/execute.py +9 -0
- sqlleaf/processors/transformer/expressions/__init__.py +11 -0
- sqlleaf/processors/transformer/expressions/functional_notation.py +22 -0
- sqlleaf/processors/transformer/expressions/row.py +451 -0
- sqlleaf/processors/transformer/expressions/values.py +318 -0
- sqlleaf/processors/transformer/insert.py +125 -0
- sqlleaf/processors/transformer/merge.py +14 -0
- sqlleaf/processors/transformer/multitable_insert.py +14 -0
- sqlleaf/processors/transformer/replace.py +23 -0
- sqlleaf/processors/transformer/transformer.py +141 -0
- sqlleaf/processors/transformer/udf.py +235 -0
- sqlleaf/processors/transformer/unload.py +30 -0
- sqlleaf/processors/transformer/update.py +174 -0
- sqlleaf/processors/transformer/values.py +10 -0
- sqlleaf/settings/__init__.py +19 -0
- sqlleaf/settings/athena.py +23 -0
- sqlleaf/settings/base.py +37 -0
- sqlleaf/settings/postgres.py +52 -0
- sqlleaf/system_tables/load.py +0 -0
- sqlleaf/typing.py +93 -0
- sqlleaf/util/__init__.py +34 -0
- sqlleaf/util/expression.py +282 -0
- sqlleaf/util/graph.py +116 -0
- sqlleaf/util/helpers.py +42 -0
- sqlleaf/util/iterators.py +37 -0
- sqlleaf-0.0.1.dist-info/METADATA +46 -0
- sqlleaf-0.0.1.dist-info/RECORD +111 -0
- sqlleaf-0.0.1.dist-info/WHEEL +5 -0
- sqlleaf-0.0.1.dist-info/licenses/LICENSE +21 -0
- sqlleaf-0.0.1.dist-info/top_level.txt +1 -0
sqlleaf/__init__.py
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
from sqlleaf.lineage import Lineage as Lineage
|
|
2
|
+
from sqlleaf.processors.collector.collector import collect_queries as collect_queries
|
|
3
|
+
from sqlleaf.processors.generator.generator import generate_lineage_for_query as generate_lineage_for_query
|
|
4
|
+
from sqlleaf.processors.transformer.transformer import transform_query as transform_query
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from sqlglot import exp
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class PGBlock(exp.Block):
|
|
7
|
+
"""A BEGIN ... END block."""
|
|
8
|
+
arg_types = {
|
|
9
|
+
"expressions": False,
|
|
10
|
+
"begin": False,
|
|
11
|
+
"declare": False,
|
|
12
|
+
"exception": False,
|
|
13
|
+
"label": False,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class PGPerform(exp.Select):
|
|
18
|
+
"""A PERFORM statement."""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class PGDeclareItem(exp.DeclareItem):
|
|
22
|
+
"""A variable declaration item inside a DECLARE section."""
|
|
23
|
+
arg_types = {
|
|
24
|
+
**exp.DeclareItem.arg_types,
|
|
25
|
+
"expression": False,
|
|
26
|
+
"value": False,
|
|
27
|
+
"assign": False,
|
|
28
|
+
"constant": False,
|
|
29
|
+
"collate": False,
|
|
30
|
+
"not_null": False,
|
|
31
|
+
"alias_for": False,
|
|
32
|
+
"cursor": False,
|
|
33
|
+
"scroll": False,
|
|
34
|
+
"cursor_args": False,
|
|
35
|
+
"query": False,
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class PGTypeRef(exp.Expression):
|
|
40
|
+
"""A `<reference>%TYPE` or `<reference>%ROWTYPE` type expression used in DECLARE items."""
|
|
41
|
+
arg_types = {"this": True, "array": False, "rowtype": False}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class PGCursorArg(exp.Expression):
|
|
45
|
+
"""A cursor argument declaration."""
|
|
46
|
+
arg_types = {"this": True, "kind": True}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class PGException(exp.Expression):
|
|
50
|
+
"""An EXCEPTION section."""
|
|
51
|
+
arg_types = {"whens": True}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class PGWhen(exp.Expression):
|
|
55
|
+
"""A WHEN ... THEN ... entry inside an EXCEPTION section."""
|
|
56
|
+
arg_types = {"condition": True, "then": True}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class PGCase(exp.CaseStatement):
|
|
60
|
+
"""A CASE statement."""
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class PGIfBranch(exp.Expression):
|
|
64
|
+
"""An IF/ELSIF branch."""
|
|
65
|
+
arg_types = {"condition": True, "then": True}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class PGIf(exp.CaseStatement):
|
|
69
|
+
"""An IF statement."""
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class PGLoop(exp.LoopBlock):
|
|
73
|
+
"""A LOOP statement."""
|
|
74
|
+
|
|
75
|
+
arg_types = {
|
|
76
|
+
"body": True,
|
|
77
|
+
"label": False,
|
|
78
|
+
"end_label": False,
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class PGForIn(exp.Expression):
|
|
83
|
+
"""A FOR statement."""
|
|
84
|
+
arg_types = {
|
|
85
|
+
"this": True,
|
|
86
|
+
"query": False,
|
|
87
|
+
"reverse": False,
|
|
88
|
+
"start": False,
|
|
89
|
+
"end": False,
|
|
90
|
+
"step": False,
|
|
91
|
+
"expressions": False,
|
|
92
|
+
"label": False,
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class PGCursorCall(exp.Expression):
|
|
97
|
+
"""A bound cursor invocation."""
|
|
98
|
+
arg_types = {
|
|
99
|
+
"this": True,
|
|
100
|
+
"expressions": False,
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class PGForEach(exp.Expression):
|
|
105
|
+
"""A FOREACH statement."""
|
|
106
|
+
arg_types = {
|
|
107
|
+
"this": True,
|
|
108
|
+
"slice": False,
|
|
109
|
+
"expression": True,
|
|
110
|
+
"expressions": False,
|
|
111
|
+
"label": False,
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class PGExit(exp.Leave):
|
|
116
|
+
"""An EXIT statement."""
|
|
117
|
+
arg_types = {"this": False, "when": False}
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class PGContinue(exp.Iterate):
|
|
121
|
+
"""A CONTINUE statement.
|
|
122
|
+
"""
|
|
123
|
+
arg_types = {"this": False, "when": False}
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class PGRaise(exp.Expression):
|
|
127
|
+
"""A RAISE statement."""
|
|
128
|
+
|
|
129
|
+
arg_types = {
|
|
130
|
+
"level": False,
|
|
131
|
+
"message": False,
|
|
132
|
+
"expressions": False,
|
|
133
|
+
"condition": False,
|
|
134
|
+
"sqlstate": False,
|
|
135
|
+
"using": False,
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class PGAssert(exp.Expression):
|
|
140
|
+
"""An ASSERT statement."""
|
|
141
|
+
arg_types = {"condition": True, "message": False}
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class PGGetDiagnostics(exp.Expression):
|
|
145
|
+
"""A GET DIAGNOSTICS statement."""
|
|
146
|
+
arg_types = {"current": False, "stacked": False, "expressions": True}
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
class PGOpenCursor(exp.Expression):
|
|
150
|
+
"""An OPEN cursor statement."""
|
|
151
|
+
arg_types = {"this": True, "expression": False, "scroll": False, "expressions": False}
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class PGFetch(exp.Fetch):
|
|
155
|
+
"""A FETCH statement."""
|
|
156
|
+
arg_types = {
|
|
157
|
+
**exp.Fetch.arg_types,
|
|
158
|
+
"this": True,
|
|
159
|
+
"expressions": True,
|
|
160
|
+
"preposition": False,
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
class PGExecute(exp.Execute):
|
|
165
|
+
"""A dynamic EXECUTE statement."""
|
|
166
|
+
arg_types = {
|
|
167
|
+
**exp.Execute.arg_types,
|
|
168
|
+
"strict": False,
|
|
169
|
+
"using": False,
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
class PGInto(exp.Into):
|
|
174
|
+
"""An INTO clause."""
|
|
175
|
+
arg_types = {
|
|
176
|
+
**exp.Into.arg_types,
|
|
177
|
+
"strict": False,
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
class PGReturning(exp.Returning):
|
|
182
|
+
"""A RETURNING clause that may carry an INTO [STRICT] target list.
|
|
183
|
+
|
|
184
|
+
The existing `into` argument will hold a `PGInto` instance when present.
|
|
185
|
+
"""
|
|
186
|
+
# Inherit arg_types from exp.Returning
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
class PGMove(exp.Expression):
|
|
190
|
+
"""A MOVE statement."""
|
|
191
|
+
arg_types = {
|
|
192
|
+
"this": True,
|
|
193
|
+
"direction": False,
|
|
194
|
+
"preposition": False,
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
class PGClose(exp.Expression):
|
|
199
|
+
"""A CLOSE cursor statement."""
|
|
200
|
+
arg_types = {"this": True}
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
class PGNext(exp.Expression):
|
|
204
|
+
"""The NEXT direction."""
|
|
205
|
+
arg_types = {"this": False}
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
class PGPrior(exp.Expression):
|
|
209
|
+
"""The PRIOR direction."""
|
|
210
|
+
arg_types = {"this": False}
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
class PGFirst(exp.Expression):
|
|
214
|
+
"""The FIRST direction."""
|
|
215
|
+
arg_types = {"this": False}
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
class PGLast(exp.Expression):
|
|
219
|
+
"""The LAST direction."""
|
|
220
|
+
arg_types = {"this": False}
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
class PGForward(exp.Expression):
|
|
224
|
+
"""The FORWARD direction."""
|
|
225
|
+
arg_types = {"this": False, "all": False}
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
class PGBackward(exp.Expression):
|
|
229
|
+
"""The BACKWARD direction."""
|
|
230
|
+
arg_types = {"this": False, "all": False}
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
class PGAbsolute(exp.Expression):
|
|
234
|
+
"""The ABSOLUTE <count> direction."""
|
|
235
|
+
arg_types = {"this": True}
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
class PGRelative(exp.Expression):
|
|
239
|
+
"""The RELATIVE <count> direction."""
|
|
240
|
+
arg_types = {"this": True}
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
class PGAll(exp.Expression):
|
|
244
|
+
"""The ALL direction."""
|
|
245
|
+
arg_types = {"this": False}
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
class PGSqlState(exp.Expression):
|
|
249
|
+
"""The SQLSTATE statement."""
|
|
250
|
+
arg_types = {"this": False}
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
class PGFound(exp.Expression):
|
|
254
|
+
"""The special PL/pgSQL boolean status variable FOUND."""
|
|
255
|
+
arg_types = {"this": False}
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
class PGUpdate(exp.Update):
|
|
259
|
+
"""An UPDATE statement with optional WHERE CURRENT OF cursor."""
|
|
260
|
+
arg_types = {**exp.Update.arg_types, "current_of": False}
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
class PGDelete(exp.Delete):
|
|
264
|
+
"""A DELETE statement with optional WHERE CURRENT OF cursor."""
|
|
265
|
+
arg_types = {**exp.Delete.arg_types, "current_of": False}
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
class PGReturn(exp.Return):
|
|
269
|
+
"""A RETURN statement."""
|
|
270
|
+
arg_types = {"this": False, "query": False, "next": False}
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
class PGOthers(exp.Expression):
|
|
274
|
+
"""The OTHERS keyword in EXCEPTION WHEN OTHERS THEN ..."""
|
|
275
|
+
arg_types = {"this": False}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from sqlglot.dialects.postgres import Postgres
|
|
4
|
+
from sqlglot.tokens import Token
|
|
5
|
+
|
|
6
|
+
from sqlleaf.dialects.plpgsql.p_generator import PlPgSQLGenerator
|
|
7
|
+
from sqlleaf.dialects.plpgsql.p_parser import PlPgSQLParser
|
|
8
|
+
from sqlleaf.dialects.plpgsql.p_tokenizer import PLPGSQL_KEYWORD_TOKEN_NAMES, TokenType
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class PlPgSQL(Postgres):
|
|
12
|
+
"""
|
|
13
|
+
A custom dialect that extends Postgres to parse PL/pgSQL.
|
|
14
|
+
"""
|
|
15
|
+
class Tokenizer(Postgres.Tokenizer):
|
|
16
|
+
KEYWORDS = {
|
|
17
|
+
**Postgres.Tokenizer.KEYWORDS,
|
|
18
|
+
**{keyword: getattr(TokenType, keyword) for keyword in PLPGSQL_KEYWORD_TOKEN_NAMES},
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
# In base sqlglot, FETCH and EXECUTE are treated as COMMANDs, which swallow the rest
|
|
22
|
+
# of the statement as a single STRING token, so we exclude them.
|
|
23
|
+
COMMANDS = Postgres.Tokenizer.COMMANDS - {TokenType.FETCH, TokenType.EXECUTE}
|
|
24
|
+
|
|
25
|
+
def tokenize(self, sql: str) -> list[Token]:
|
|
26
|
+
tokens = super().tokenize(sql)
|
|
27
|
+
return self._merge_range_tokens(tokens)
|
|
28
|
+
|
|
29
|
+
def _merge_range_tokens(self, tokens: list[Token]) -> list[Token]:
|
|
30
|
+
def adjacent(a: Token, b: Token) -> bool:
|
|
31
|
+
return a.end + 1 == b.start
|
|
32
|
+
|
|
33
|
+
result: list[Token] = []
|
|
34
|
+
i = 0
|
|
35
|
+
n = len(tokens)
|
|
36
|
+
while i < n:
|
|
37
|
+
tok = tokens[i]
|
|
38
|
+
nxt = tokens[i + 1] if i + 1 < n else None
|
|
39
|
+
|
|
40
|
+
# Pattern 0: LABEL full pattern << id >>
|
|
41
|
+
# Merge only when we see LT LT <identifier-like> GT GT with strict adjacency
|
|
42
|
+
if (
|
|
43
|
+
i + 4 < n
|
|
44
|
+
and tok.token_type == TokenType.LT
|
|
45
|
+
and nxt is not None and nxt.token_type == TokenType.LT and adjacent(tok, nxt)
|
|
46
|
+
):
|
|
47
|
+
ident_tok = tokens[i + 2]
|
|
48
|
+
gt1 = tokens[i + 3]
|
|
49
|
+
gt2 = tokens[i + 4]
|
|
50
|
+
|
|
51
|
+
if (
|
|
52
|
+
ident_tok is not None
|
|
53
|
+
and ident_tok.text
|
|
54
|
+
and (ident_tok.text[0].isalpha() or ident_tok.text[0] == "_")
|
|
55
|
+
and all(ch.isalnum() or ch == "_" for ch in ident_tok.text)
|
|
56
|
+
and gt1.token_type == TokenType.GT
|
|
57
|
+
and gt2.token_type == TokenType.GT
|
|
58
|
+
and adjacent(nxt, ident_tok)
|
|
59
|
+
and adjacent(ident_tok, gt1)
|
|
60
|
+
and adjacent(gt1, gt2)
|
|
61
|
+
):
|
|
62
|
+
# Create LABEL_BEGIN spanning the two '<'
|
|
63
|
+
lbegin = Token(
|
|
64
|
+
TokenType.LABEL_BEGIN,
|
|
65
|
+
"<<",
|
|
66
|
+
line=tok.line,
|
|
67
|
+
col=tok.col,
|
|
68
|
+
start=tok.start,
|
|
69
|
+
end=nxt.end,
|
|
70
|
+
comments=tok.comments,
|
|
71
|
+
)
|
|
72
|
+
# Keep the identifier token as-is
|
|
73
|
+
# Create LABEL_END spanning the two '>'
|
|
74
|
+
lend = Token(
|
|
75
|
+
TokenType.LABEL_END,
|
|
76
|
+
">>",
|
|
77
|
+
line=gt1.line,
|
|
78
|
+
col=gt1.col,
|
|
79
|
+
start=gt1.start,
|
|
80
|
+
end=gt2.end,
|
|
81
|
+
comments=gt1.comments,
|
|
82
|
+
)
|
|
83
|
+
result.append(lbegin)
|
|
84
|
+
result.append(ident_tok)
|
|
85
|
+
result.append(lend)
|
|
86
|
+
i += 5
|
|
87
|
+
continue
|
|
88
|
+
|
|
89
|
+
# Pattern 1: NUMBER('...' endswith '.') immediately followed by DOT -> DDOT
|
|
90
|
+
if (
|
|
91
|
+
nxt is not None
|
|
92
|
+
and tok.token_type == TokenType.NUMBER
|
|
93
|
+
and tok.text.endswith(".")
|
|
94
|
+
and nxt.token_type == TokenType.DOT
|
|
95
|
+
and adjacent(tok, nxt)
|
|
96
|
+
):
|
|
97
|
+
number = Token(
|
|
98
|
+
TokenType.NUMBER,
|
|
99
|
+
tok.text[:-1],
|
|
100
|
+
line=tok.line,
|
|
101
|
+
col=tok.col,
|
|
102
|
+
start=tok.start,
|
|
103
|
+
end=tok.end - 1,
|
|
104
|
+
comments=tok.comments,
|
|
105
|
+
)
|
|
106
|
+
ddot = Token(
|
|
107
|
+
TokenType.DDOT,
|
|
108
|
+
"..",
|
|
109
|
+
line=nxt.line,
|
|
110
|
+
col=nxt.col,
|
|
111
|
+
start=tok.end,
|
|
112
|
+
end=nxt.end,
|
|
113
|
+
comments=nxt.comments,
|
|
114
|
+
)
|
|
115
|
+
result.append(number)
|
|
116
|
+
result.append(ddot)
|
|
117
|
+
i += 2
|
|
118
|
+
continue
|
|
119
|
+
|
|
120
|
+
# Pattern 2: DOT immediately followed by DOT -> DDOT
|
|
121
|
+
if (
|
|
122
|
+
nxt is not None
|
|
123
|
+
and tok.token_type == TokenType.DOT
|
|
124
|
+
and nxt.token_type == TokenType.DOT
|
|
125
|
+
and adjacent(tok, nxt)
|
|
126
|
+
):
|
|
127
|
+
ddot = Token(
|
|
128
|
+
TokenType.DDOT,
|
|
129
|
+
"..",
|
|
130
|
+
line=tok.line,
|
|
131
|
+
col=tok.col,
|
|
132
|
+
start=tok.start,
|
|
133
|
+
end=nxt.end,
|
|
134
|
+
comments=tok.comments,
|
|
135
|
+
)
|
|
136
|
+
result.append(ddot)
|
|
137
|
+
i += 2
|
|
138
|
+
continue
|
|
139
|
+
|
|
140
|
+
result.append(tok)
|
|
141
|
+
i += 1
|
|
142
|
+
|
|
143
|
+
return result
|
|
144
|
+
|
|
145
|
+
Generator = PlPgSQLGenerator
|
|
146
|
+
Parser = PlPgSQLParser
|
|
147
|
+
|
|
148
|
+
plpgsql = PlPgSQL
|