duckdb-kql 0.0.1.dev1__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.
- duckdb_kql/__init__.py +205 -0
- duckdb_kql/__main__.py +11 -0
- duckdb_kql/_antlr/Kql.interp +949 -0
- duckdb_kql/_antlr/Kql.tokens +620 -0
- duckdb_kql/_antlr/KqlLexer.interp +984 -0
- duckdb_kql/_antlr/KqlLexer.py +1993 -0
- duckdb_kql/_antlr/KqlLexer.tokens +620 -0
- duckdb_kql/_antlr/KqlListener.py +2748 -0
- duckdb_kql/_antlr/KqlParser.py +26549 -0
- duckdb_kql/_antlr/KqlVisitor.py +1533 -0
- duckdb_kql/_antlr/__init__.py +0 -0
- duckdb_kql/_version.py +24 -0
- duckdb_kql/cli.py +335 -0
- duckdb_kql/comparison.py +775 -0
- duckdb_kql/engine.py +177 -0
- duckdb_kql/errors.py +81 -0
- duckdb_kql/fixtures.py +310 -0
- duckdb_kql/ir.py +373 -0
- duckdb_kql/kusto/__init__.py +77 -0
- duckdb_kql/kusto/_models.py +481 -0
- duckdb_kql/kusto/client.py +588 -0
- duckdb_kql/kusto/client_request_properties.py +239 -0
- duckdb_kql/kusto/exceptions.py +96 -0
- duckdb_kql/kusto/helpers.py +150 -0
- duckdb_kql/kusto/response.py +67 -0
- duckdb_kql/lower.py +1178 -0
- duckdb_kql/oracle.py +197 -0
- duckdb_kql/params.py +360 -0
- duckdb_kql/parser.py +112 -0
- duckdb_kql/py.typed +0 -0
- duckdb_kql/schema.py +138 -0
- duckdb_kql/translate/__init__.py +1352 -0
- duckdb_kql/translate/functions.py +549 -0
- duckdb_kql-0.0.1.dev1.dist-info/METADATA +286 -0
- duckdb_kql-0.0.1.dev1.dist-info/RECORD +38 -0
- duckdb_kql-0.0.1.dev1.dist-info/WHEEL +4 -0
- duckdb_kql-0.0.1.dev1.dist-info/entry_points.txt +2 -0
- duckdb_kql-0.0.1.dev1.dist-info/licenses/LICENSE +21 -0
duckdb_kql/__init__.py
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
"""duckdb-kql — run Kusto KQL queries on DuckDB, from Python.
|
|
2
|
+
|
|
3
|
+
**Status: pre-alpha.** Translation is being built wave by wave
|
|
4
|
+
(``docs/implementation-plan.md``). Anything outside the covered surface raises
|
|
5
|
+
``KqlUnsupportedError`` — deliberately, rather than returning something
|
|
6
|
+
plausible and wrong.
|
|
7
|
+
|
|
8
|
+
The API comes in three layers, each adding one dependency. Import only the layer
|
|
9
|
+
you need and you pay only for that layer::
|
|
10
|
+
|
|
11
|
+
Layer 0 duckdb_kql KQL text in, DuckDB SQL out. antlr4 only.
|
|
12
|
+
Layer 1 duckdb_kql.engine Run it. + duckdb
|
|
13
|
+
Layer 2 duckdb_kql.kusto KustoClient drop-in. + pandas
|
|
14
|
+
|
|
15
|
+
Layer 0 — translate and inspect, no database anywhere::
|
|
16
|
+
|
|
17
|
+
>>> import duckdb_kql
|
|
18
|
+
>>> duckdb_kql.to_sql("print x = 1 + 1")
|
|
19
|
+
'SELECT (CAST(1 AS BIGINT) + CAST(1 AS BIGINT)) AS "x"'
|
|
20
|
+
>>> duckdb_kql.parse('Logs | where Level == "Error" | take 10').ok
|
|
21
|
+
True
|
|
22
|
+
>>> duckdb_kql.validate("Logs | wherex") # doctest: +ELLIPSIS
|
|
23
|
+
[...]
|
|
24
|
+
|
|
25
|
+
Layer 1 — execute against a DuckDB connection::
|
|
26
|
+
|
|
27
|
+
import duckdb, duckdb_kql
|
|
28
|
+
con = duckdb_kql.connect() # or duckdb.connect(), TimeZone=UTC
|
|
29
|
+
con.sql("CREATE TABLE Logs AS SELECT 'Error' AS Level")
|
|
30
|
+
duckdb_kql.sql(con, "Logs | where Level == 'Error'").fetchall()
|
|
31
|
+
|
|
32
|
+
Layer 2 — the ``azure-kusto-data`` shape, for code already written against it::
|
|
33
|
+
|
|
34
|
+
from duckdb_kql.kusto import KustoClient, ClientRequestProperties
|
|
35
|
+
from duckdb_kql.kusto.helpers import dataframe_from_result_table
|
|
36
|
+
|
|
37
|
+
client = KustoClient("mydata.duckdb")
|
|
38
|
+
response = client.execute("db", "Logs | take 10")
|
|
39
|
+
df = dataframe_from_result_table(response.primary_results[0])
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
from __future__ import annotations
|
|
43
|
+
|
|
44
|
+
from typing import TYPE_CHECKING, Any
|
|
45
|
+
|
|
46
|
+
from .errors import (
|
|
47
|
+
Diagnostic,
|
|
48
|
+
KqlError,
|
|
49
|
+
KqlSchemaError,
|
|
50
|
+
KqlSyntaxError,
|
|
51
|
+
KqlUnsupportedError,
|
|
52
|
+
SourceSpan,
|
|
53
|
+
)
|
|
54
|
+
from .params import ParameterDeclaration
|
|
55
|
+
from .parser import ParseResult, parse, validate
|
|
56
|
+
|
|
57
|
+
if TYPE_CHECKING:
|
|
58
|
+
# Layer 1 is resolved lazily at runtime (see ``__getattr__``), which leaves
|
|
59
|
+
# a type checker with nothing to go on — every re-export would be ``Any``,
|
|
60
|
+
# and a caller would be told their code is fine when it is not. Importing
|
|
61
|
+
# the real names here, for the checker only, keeps the laziness without
|
|
62
|
+
# paying for it in the signatures.
|
|
63
|
+
from .engine import Parameters, arrow, connect, df, execute, sql
|
|
64
|
+
from .schema import Schema
|
|
65
|
+
from .translate import TranslationResult
|
|
66
|
+
|
|
67
|
+
try:
|
|
68
|
+
# Written by hatch-vcs at build time from the git tag (pyproject.toml).
|
|
69
|
+
from ._version import __version__
|
|
70
|
+
except ImportError: # pragma: no cover - a source tree that was never built
|
|
71
|
+
# Running straight out of `src/` with nothing installed — several of the
|
|
72
|
+
# dev tools do exactly that. Ask the installed distribution, and if there
|
|
73
|
+
# isn't one, say so with a version that could not be mistaken for a release
|
|
74
|
+
# rather than inventing a plausible number.
|
|
75
|
+
from importlib.metadata import PackageNotFoundError
|
|
76
|
+
from importlib.metadata import version as _installed_version
|
|
77
|
+
|
|
78
|
+
try:
|
|
79
|
+
__version__ = _installed_version("duckdb-kql")
|
|
80
|
+
except PackageNotFoundError:
|
|
81
|
+
__version__ = "0.0.0.dev0+unbuilt"
|
|
82
|
+
|
|
83
|
+
__all__ = [
|
|
84
|
+
# Layer 0 — KQL text, no database
|
|
85
|
+
"parse",
|
|
86
|
+
"validate",
|
|
87
|
+
"to_sql",
|
|
88
|
+
"query_parameters",
|
|
89
|
+
"ParseResult",
|
|
90
|
+
"ParameterDeclaration",
|
|
91
|
+
"Diagnostic",
|
|
92
|
+
"SourceSpan",
|
|
93
|
+
# errors (Layer 0)
|
|
94
|
+
"KqlError",
|
|
95
|
+
"KqlSyntaxError",
|
|
96
|
+
"KqlUnsupportedError",
|
|
97
|
+
"KqlSchemaError",
|
|
98
|
+
# Layer 1 — requires duckdb
|
|
99
|
+
"connect",
|
|
100
|
+
"sql",
|
|
101
|
+
"execute",
|
|
102
|
+
"df",
|
|
103
|
+
"arrow",
|
|
104
|
+
# type aliases used in the public signatures above, so callers can annotate
|
|
105
|
+
"TranslationResult",
|
|
106
|
+
"Schema",
|
|
107
|
+
"Parameters",
|
|
108
|
+
"__version__",
|
|
109
|
+
]
|
|
110
|
+
|
|
111
|
+
#: Layer 1 names, re-exported here for convenience but defined in ``engine``.
|
|
112
|
+
#: Resolved on first access so that importing this package never imports duckdb.
|
|
113
|
+
_LAYER1 = frozenset({"connect", "sql", "execute", "df", "arrow"})
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
#: Types named in the public signatures. Callers need them to annotate their own
|
|
117
|
+
#: code, and a name that only exists under TYPE_CHECKING cannot be imported.
|
|
118
|
+
#: Resolved from the Layer 0 modules that define them, so this stays duckdb-free.
|
|
119
|
+
_LAYER0_TYPES = {"TranslationResult": "translate", "Parameters": "translate",
|
|
120
|
+
"Schema": "schema"}
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def __getattr__(name: str) -> Any:
|
|
124
|
+
if name in _LAYER1:
|
|
125
|
+
from . import engine
|
|
126
|
+
|
|
127
|
+
return getattr(engine, name)
|
|
128
|
+
if name in _LAYER0_TYPES:
|
|
129
|
+
import importlib
|
|
130
|
+
|
|
131
|
+
module = importlib.import_module(f".{_LAYER0_TYPES[name]}", __name__)
|
|
132
|
+
return getattr(module, name)
|
|
133
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def __dir__() -> list[str]:
|
|
137
|
+
return sorted(__all__)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
# ---------------------------------------------------------------------------
|
|
141
|
+
# Layer 0
|
|
142
|
+
# ---------------------------------------------------------------------------
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def to_sql(
|
|
146
|
+
kql: str,
|
|
147
|
+
schema: Schema | None = None,
|
|
148
|
+
parameters: Parameters | None = None,
|
|
149
|
+
) -> TranslationResult:
|
|
150
|
+
"""Translate *kql* to DuckDB SQL. Requires no connection and no database.
|
|
151
|
+
|
|
152
|
+
Returns a ``str`` subclass that also carries ``.parameters`` — the values
|
|
153
|
+
for the placeholders a ``declare query_parameters`` query renders. Pass
|
|
154
|
+
those to DuckDB alongside the SQL; :func:`duckdb_kql.sql` does it for you.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
kql: the query text.
|
|
158
|
+
schema: table name -> column names. Only ``join`` consults it.
|
|
159
|
+
parameters: values for the query's declared parameters, by KQL name.
|
|
160
|
+
They are bound as values, never spliced into the SQL, so a value may
|
|
161
|
+
contain any text at all without changing what the query does.
|
|
162
|
+
|
|
163
|
+
.. important::
|
|
164
|
+
KQL datetimes are UTC (``docs/TRANSLATION.md`` R8), and DuckDB reads the
|
|
165
|
+
**session** ``TimeZone`` when casting a string that carries no offset. Run
|
|
166
|
+
the returned SQL on a connection with ``SET TimeZone='UTC'`` or datetime
|
|
167
|
+
values will be silently shifted. :func:`duckdb_kql.sql` and
|
|
168
|
+
:func:`duckdb_kql.connect` do this for you; the requirement only falls to
|
|
169
|
+
callers who execute the SQL themselves.
|
|
170
|
+
|
|
171
|
+
Raises:
|
|
172
|
+
KqlSyntaxError: the query does not parse.
|
|
173
|
+
KqlUnsupportedError: it parses but uses a construct outside this wave.
|
|
174
|
+
KqlSchemaError: a supplied value does not match its declared type, or
|
|
175
|
+
names a parameter the query does not declare.
|
|
176
|
+
|
|
177
|
+
Translating without supplying every declared value is allowed — the SQL is
|
|
178
|
+
worth reading on its own. The names still missing are listed in
|
|
179
|
+
``.unbound``, and executing is what turns them into an error.
|
|
180
|
+
"""
|
|
181
|
+
from .lower import lower
|
|
182
|
+
from .params import bind
|
|
183
|
+
from .translate import to_sql as _emit
|
|
184
|
+
|
|
185
|
+
query = lower(kql)
|
|
186
|
+
result = _emit(query, schema)
|
|
187
|
+
if query.parameters or parameters:
|
|
188
|
+
bound, unbound = bind(query.parameters, parameters)
|
|
189
|
+
return result.with_parameters(bound, unbound, tuple(query.parameters))
|
|
190
|
+
return result
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def query_parameters(kql: str) -> list[ParameterDeclaration]:
|
|
194
|
+
"""The parameters *kql* declares, in declaration order.
|
|
195
|
+
|
|
196
|
+
Lets a caller discover what a query expects before deciding what to supply::
|
|
197
|
+
|
|
198
|
+
>>> [(p.name, p.type) for p in query_parameters(
|
|
199
|
+
... "declare query_parameters(state:string);"
|
|
200
|
+
... " StormEvents | where State == state")]
|
|
201
|
+
[('state', 'string')]
|
|
202
|
+
"""
|
|
203
|
+
from .lower import query_parameters as _declared
|
|
204
|
+
|
|
205
|
+
return _declared(kql)
|
duckdb_kql/__main__.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""``python -m duckdb_kql`` — the same command as the ``duckdb-kql`` script.
|
|
2
|
+
|
|
3
|
+
Present so the CLI is reachable when the console script is not on PATH, which
|
|
4
|
+
is the normal situation inside a virtualenv a CI job did not activate.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from .cli import main
|
|
10
|
+
|
|
11
|
+
raise SystemExit(main())
|