duckdb-kql 0.0.1.dev1__tar.gz → 0.0.1.dev3__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.
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/CONTRIBUTING.md +11 -1
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/PKG-INFO +24 -5
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/README.md +19 -3
- duckdb_kql-0.0.1.dev3/demo/demo.ipynb +503 -0
- duckdb_kql-0.0.1.dev3/demo/demo.kql +3 -0
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/docs/api.md +12 -6
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/docs/cli.md +6 -1
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/docs/code-review/public-api-and-typing.md +2 -2
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/docs/getting-started.md +7 -7
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/docs/implementation-plan.md +1 -1
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/docs/kql-support.md +4 -4
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/docs/kusto-client.md +39 -10
- duckdb_kql-0.0.1.dev3/docs/kusto-server.md +166 -0
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/docs/test-plan.md +2 -2
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/pyproject.toml +26 -6
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/src/duckdb_kql/__init__.py +28 -6
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/src/duckdb_kql/_version.py +2 -2
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/src/duckdb_kql/cli.py +90 -2
- duckdb_kql-0.0.1.dev3/src/duckdb_kql/control.py +387 -0
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/src/duckdb_kql/engine.py +23 -19
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/src/duckdb_kql/ir.py +30 -0
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/src/duckdb_kql/kusto/_models.py +5 -52
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/src/duckdb_kql/kusto/client.py +28 -46
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/src/duckdb_kql/lower.py +3 -0
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/src/duckdb_kql/parser.py +13 -0
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/src/duckdb_kql/schema.py +5 -1
- duckdb_kql-0.0.1.dev3/src/duckdb_kql/server.py +650 -0
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/src/duckdb_kql/translate/__init__.py +35 -0
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/src/duckdb_kql/translate/functions.py +1 -1
- duckdb_kql-0.0.1.dev3/src/duckdb_kql/types.py +149 -0
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/tests/test_behavior.py +2 -2
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/tests/test_cli.py +53 -0
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/tests/test_column_order_and_null_sort.py +9 -9
- duckdb_kql-0.0.1.dev3/tests/test_control_commands.py +405 -0
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/tests/test_datetime_traps.py +1 -1
- duckdb_kql-0.0.1.dev3/tests/test_demo_notebook.py +151 -0
- duckdb_kql-0.0.1.dev3/tests/test_devcontainers.py +130 -0
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/tests/test_docs.py +34 -0
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/tests/test_dynamic.py +3 -3
- duckdb_kql-0.0.1.dev3/tests/test_getschema.py +135 -0
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/tests/test_join.py +1 -1
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/tests/test_let.py +2 -2
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/tests/test_null_semantics.py +3 -3
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/tests/test_query_parameters.py +13 -13
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/tests/test_range_in_render.py +4 -4
- duckdb_kql-0.0.1.dev3/tests/test_server.py +529 -0
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/tests/test_summarize.py +1 -1
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/tests/test_typing.py +3 -3
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/tests/test_workflows.py +44 -7
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/tools/gen_support_matrix.py +3 -3
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/.gitignore +0 -0
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/CODE_OF_CONDUCT.md +0 -0
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/LICENSE +0 -0
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/SECURITY.md +0 -0
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/THIRD-PARTY-NOTICES.md +0 -0
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/docs/TRANSLATION.md +0 -0
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/docs/ai-cost-strategy.md +0 -0
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/docs/azure-monitor-profile.md +0 -0
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/docs/code-review/README.md +0 -0
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/docs/code-review/kusto-client-compat.md +0 -0
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/docs/code-review/review-2026-08-04.md +0 -0
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/docs/code-review/security-and-injection.md +0 -0
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/docs/code-review/testing-oracle-and-fixtures.md +0 -0
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/docs/code-review/tooling-packaging-ci-docs.md +0 -0
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/docs/code-review/translation-correctness.md +0 -0
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/docs/frequency-scan-results.md +0 -0
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/docs/implementation-options.md +0 -0
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/docs/kql-on-duckdb-landscape.md +0 -0
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/docs/lessons-from-bun-rewrite.md +0 -0
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/docs/licensing.md +0 -0
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/docs/m0-grammar-spike.md +0 -0
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/docs/oracle-harness.md +0 -0
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/grammar/Kql.g4 +0 -0
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/grammar/KqlTokens.g4 +0 -0
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/grammar/UPSTREAM.md +0 -0
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/src/duckdb_kql/__main__.py +0 -0
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/src/duckdb_kql/_antlr/Kql.interp +0 -0
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/src/duckdb_kql/_antlr/Kql.tokens +0 -0
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/src/duckdb_kql/_antlr/KqlLexer.interp +0 -0
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/src/duckdb_kql/_antlr/KqlLexer.py +0 -0
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/src/duckdb_kql/_antlr/KqlLexer.tokens +0 -0
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/src/duckdb_kql/_antlr/KqlListener.py +0 -0
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/src/duckdb_kql/_antlr/KqlParser.py +0 -0
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/src/duckdb_kql/_antlr/KqlVisitor.py +0 -0
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/src/duckdb_kql/_antlr/__init__.py +0 -0
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/src/duckdb_kql/comparison.py +0 -0
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/src/duckdb_kql/errors.py +0 -0
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/src/duckdb_kql/fixtures.py +0 -0
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/src/duckdb_kql/kusto/__init__.py +0 -0
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/src/duckdb_kql/kusto/client_request_properties.py +0 -0
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/src/duckdb_kql/kusto/exceptions.py +0 -0
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/src/duckdb_kql/kusto/helpers.py +0 -0
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/src/duckdb_kql/kusto/response.py +0 -0
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/src/duckdb_kql/oracle.py +0 -0
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/src/duckdb_kql/params.py +0 -0
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/src/duckdb_kql/py.typed +0 -0
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/tests/conftest.py +0 -0
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/tests/profiles/azure-monitor.json +0 -0
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/tests/test_comparison.py +0 -0
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/tests/test_corpus.py +0 -0
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/tests/test_fixtures.py +0 -0
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/tests/test_kusto_client.py +0 -0
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/tests/test_parse.py +0 -0
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/tests/test_profile_azure_monitor.py +0 -0
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/tests/test_support_matrix.py +0 -0
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/tools/check_profile.py +0 -0
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/tools/frequency_scan.py +0 -0
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/tools/harvest_docs.py +0 -0
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/tools/make_fixtures.py +0 -0
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/tools/regen_expectations.py +0 -0
- {duckdb_kql-0.0.1.dev1 → duckdb_kql-0.0.1.dev3}/tools/regen_parser.sh +0 -0
|
@@ -24,7 +24,7 @@ Docker, DuckDB and the emulator yourself.
|
|
|
24
24
|
Before opening a pull request:
|
|
25
25
|
|
|
26
26
|
```bash
|
|
27
|
-
ruff check src tools tests
|
|
27
|
+
ruff check src tools tests demo
|
|
28
28
|
mypy
|
|
29
29
|
pytest
|
|
30
30
|
```
|
|
@@ -87,6 +87,7 @@ report waiting to be filed against someone else's report.
|
|
|
87
87
|
| Links in the docs resolve | Including the absolute ones the README uses for PyPI. |
|
|
88
88
|
| A consumer's type checker sees real types | The package ships `py.typed`, so `Any` in a public signature is a promise broken silently. `tests/test_typing.py` checks from the outside; `mypy` alone cannot. |
|
|
89
89
|
| The committed parser matches `grammar/Kql.g4` | The generated parser is committed so installs need no Java. |
|
|
90
|
+
| The demo notebook still executes | `demo/` ships with its outputs, so a broken demo keeps looking authoritative. |
|
|
90
91
|
|
|
91
92
|
## The emulator
|
|
92
93
|
|
|
@@ -155,6 +156,15 @@ The whole procedure:
|
|
|
155
156
|
1. Go to **Releases → Draft a new release**, create the tag `vX.Y.Z` on `main`,
|
|
156
157
|
click **Generate release notes**, and publish.
|
|
157
158
|
|
|
159
|
+
**The tag is used verbatim**, so any PEP 440 version works — `v0.2.0`,
|
|
160
|
+
`v0.2.0rc1`, `v0.0.1.dev1`. This is `version_scheme = "only-version"` in
|
|
161
|
+
`pyproject.toml`, and it is deliberate: `setuptools_scm`'s default invents a
|
|
162
|
+
version for untagged commits by guessing the next release and appending
|
|
163
|
+
`.dev<distance>`, which makes a tag that already ends in `.devN` unbuildable and
|
|
164
|
+
breaks *every commit after it*. Taking the tag as written avoids the guess
|
|
165
|
+
entirely; untagged builds are told apart by a local segment naming the commit
|
|
166
|
+
(`0.0.1.dev1+gfe285ea`) rather than by counting commits.
|
|
167
|
+
|
|
158
168
|
That is it. The release workflow builds the sdist and wheel, verifies the built
|
|
159
169
|
version equals the tag, smoke-tests the wheel in a clean environment, uploads to
|
|
160
170
|
PyPI via [Trusted Publishing][oidc], and attaches the artifacts to the release.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: duckdb-kql
|
|
3
|
-
Version: 0.0.1.
|
|
3
|
+
Version: 0.0.1.dev3
|
|
4
4
|
Summary: Run Kusto KQL queries on DuckDB, from Python
|
|
5
5
|
Project-URL: Homepage, https://github.com/mmaitre314/duckdb-kql
|
|
6
6
|
Project-URL: Documentation, https://github.com/mmaitre314/duckdb-kql/blob/main/docs/getting-started.md
|
|
@@ -58,7 +58,10 @@ Provides-Extra: arrow
|
|
|
58
58
|
Requires-Dist: pyarrow; extra == 'arrow'
|
|
59
59
|
Provides-Extra: dev
|
|
60
60
|
Requires-Dist: duckdb>=0.10; extra == 'dev'
|
|
61
|
+
Requires-Dist: ipykernel; extra == 'dev'
|
|
61
62
|
Requires-Dist: mypy>=1.11; extra == 'dev'
|
|
63
|
+
Requires-Dist: nbclient; extra == 'dev'
|
|
64
|
+
Requires-Dist: nbformat; extra == 'dev'
|
|
62
65
|
Requires-Dist: pandas; extra == 'dev'
|
|
63
66
|
Requires-Dist: pandas-stubs; extra == 'dev'
|
|
64
67
|
Requires-Dist: pyarrow; extra == 'dev'
|
|
@@ -135,6 +138,20 @@ duckdb-kql queries/ -o build/sql/ --check # fails the build if a .sql is stale
|
|
|
135
138
|
|
|
136
139
|
See [Build-time translation](https://github.com/mmaitre314/duckdb-kql/blob/main/docs/cli.md).
|
|
137
140
|
|
|
141
|
+
## Query it from the Azure Data Explorer UI
|
|
142
|
+
|
|
143
|
+
`duckdb-kql serve` puts a local Kusto REST endpoint in front of a DuckDB file,
|
|
144
|
+
so Kusto's own tools can query it. Standard library only — no new dependency.
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
duckdb-kql serve analytics.duckdb # http://127.0.0.1:31415
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Open <https://dataexplorer.azure.com>, choose **Add connection**, and paste that
|
|
151
|
+
URL. It listens on loopback only and cannot be told otherwise, because it
|
|
152
|
+
answers unauthenticated queries: see
|
|
153
|
+
[A local Kusto endpoint](https://github.com/mmaitre314/duckdb-kql/blob/main/docs/kusto-server.md).
|
|
154
|
+
|
|
138
155
|
## Three layers
|
|
139
156
|
|
|
140
157
|
| Layer | Import | Needs | For |
|
|
@@ -163,7 +180,7 @@ runs without a database.
|
|
|
163
180
|
import duckdb_kql
|
|
164
181
|
|
|
165
182
|
con = duckdb_kql.connect("analytics.duckdb") # duckdb.connect + TimeZone=UTC
|
|
166
|
-
rel = duckdb_kql.
|
|
183
|
+
rel = duckdb_kql.kql(con, "StormEvents | summarize n = count() by State")
|
|
167
184
|
rel.fetchall()
|
|
168
185
|
```
|
|
169
186
|
|
|
@@ -198,7 +215,7 @@ values; they are bound as values, so the generated SQL contains no
|
|
|
198
215
|
caller-controlled text at all.
|
|
199
216
|
|
|
200
217
|
```python
|
|
201
|
-
duckdb_kql.
|
|
218
|
+
duckdb_kql.kql(con, """
|
|
202
219
|
declare query_parameters(state:string);
|
|
203
220
|
StormEvents | where State == state
|
|
204
221
|
""", {"state": user_input}) # safe whatever user_input contains
|
|
@@ -212,7 +229,7 @@ Measured against the real KQL engine (the Kusto Emulator), not asserted.
|
|
|
212
229
|
|---|---|
|
|
213
230
|
| Doc-corpus cases matching ground truth | **245** of 1036 (0 mismatches) |
|
|
214
231
|
| [Azure Monitor's published KQL subset](https://github.com/mmaitre314/duckdb-kql/blob/main/docs/azure-monitor-profile.md) | **114 / 119 (96%)** |
|
|
215
|
-
| Tabular operators | **
|
|
232
|
+
| Tabular operators | **16 / 41** |
|
|
216
233
|
| Scalar functions / aggregates / binary operators | **110 / 19 / 33** |
|
|
217
234
|
|
|
218
235
|
Supported operators: `where`, `project`, `project-away`, `project-rename`,
|
|
@@ -245,6 +262,7 @@ approximation.
|
|
|
245
262
|
| [Getting started](https://github.com/mmaitre314/duckdb-kql/blob/main/docs/getting-started.md) | Install, first query, the three layers |
|
|
246
263
|
| [**KQL support matrix**](https://github.com/mmaitre314/duckdb-kql/blob/main/docs/kql-support.md) | Every operator and function, supported or not, each with its gotchas |
|
|
247
264
|
| [Build-time CLI](https://github.com/mmaitre314/duckdb-kql/blob/main/docs/cli.md) | Translating `.kql` to `.sql` in CI, to avoid a runtime dependency |
|
|
265
|
+
| [Local Kusto endpoint](https://github.com/mmaitre314/duckdb-kql/blob/main/docs/kusto-server.md) | `duckdb-kql serve` — query a DuckDB file from the Azure Data Explorer UI |
|
|
248
266
|
| [API reference](https://github.com/mmaitre314/duckdb-kql/blob/main/docs/api.md) | Every public function and type |
|
|
249
267
|
| [Kusto SDK compatibility](https://github.com/mmaitre314/duckdb-kql/blob/main/docs/kusto-client.md) | What Layer 2 implements, no-ops, and refuses |
|
|
250
268
|
| [Azure Monitor profile](https://github.com/mmaitre314/duckdb-kql/blob/main/docs/azure-monitor-profile.md) | Coverage against a published KQL subset |
|
|
@@ -256,6 +274,7 @@ approximation.
|
|
|
256
274
|
| [`docs/m0-grammar-spike.md`](https://github.com/mmaitre314/duckdb-kql/blob/main/docs/m0-grammar-spike.md) | Grammar viability result |
|
|
257
275
|
| [`docs/frequency-scan-results.md`](https://github.com/mmaitre314/duckdb-kql/blob/main/docs/frequency-scan-results.md) | What KQL constructs actually get used |
|
|
258
276
|
| [`docs/licensing.md`](https://github.com/mmaitre314/duckdb-kql/blob/main/docs/licensing.md) | Third-party licensing review |
|
|
277
|
+
| [demo/](https://github.com/mmaitre314/duckdb-kql/blob/main/demo/demo.ipynb) | Notebook tour of all three layers, with outputs |
|
|
259
278
|
| [CONTRIBUTING.md](https://github.com/mmaitre314/duckdb-kql/blob/main/CONTRIBUTING.md) | How to add a mapping, and when not to |
|
|
260
279
|
| [SECURITY.md](https://github.com/mmaitre314/duckdb-kql/blob/main/SECURITY.md) | Reporting vulnerabilities; what is in scope |
|
|
261
280
|
| [Releases](https://github.com/mmaitre314/duckdb-kql/releases) | What changed, and when |
|
|
@@ -57,6 +57,20 @@ duckdb-kql queries/ -o build/sql/ --check # fails the build if a .sql is stale
|
|
|
57
57
|
|
|
58
58
|
See [Build-time translation](https://github.com/mmaitre314/duckdb-kql/blob/main/docs/cli.md).
|
|
59
59
|
|
|
60
|
+
## Query it from the Azure Data Explorer UI
|
|
61
|
+
|
|
62
|
+
`duckdb-kql serve` puts a local Kusto REST endpoint in front of a DuckDB file,
|
|
63
|
+
so Kusto's own tools can query it. Standard library only — no new dependency.
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
duckdb-kql serve analytics.duckdb # http://127.0.0.1:31415
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Open <https://dataexplorer.azure.com>, choose **Add connection**, and paste that
|
|
70
|
+
URL. It listens on loopback only and cannot be told otherwise, because it
|
|
71
|
+
answers unauthenticated queries: see
|
|
72
|
+
[A local Kusto endpoint](https://github.com/mmaitre314/duckdb-kql/blob/main/docs/kusto-server.md).
|
|
73
|
+
|
|
60
74
|
## Three layers
|
|
61
75
|
|
|
62
76
|
| Layer | Import | Needs | For |
|
|
@@ -85,7 +99,7 @@ runs without a database.
|
|
|
85
99
|
import duckdb_kql
|
|
86
100
|
|
|
87
101
|
con = duckdb_kql.connect("analytics.duckdb") # duckdb.connect + TimeZone=UTC
|
|
88
|
-
rel = duckdb_kql.
|
|
102
|
+
rel = duckdb_kql.kql(con, "StormEvents | summarize n = count() by State")
|
|
89
103
|
rel.fetchall()
|
|
90
104
|
```
|
|
91
105
|
|
|
@@ -120,7 +134,7 @@ values; they are bound as values, so the generated SQL contains no
|
|
|
120
134
|
caller-controlled text at all.
|
|
121
135
|
|
|
122
136
|
```python
|
|
123
|
-
duckdb_kql.
|
|
137
|
+
duckdb_kql.kql(con, """
|
|
124
138
|
declare query_parameters(state:string);
|
|
125
139
|
StormEvents | where State == state
|
|
126
140
|
""", {"state": user_input}) # safe whatever user_input contains
|
|
@@ -134,7 +148,7 @@ Measured against the real KQL engine (the Kusto Emulator), not asserted.
|
|
|
134
148
|
|---|---|
|
|
135
149
|
| Doc-corpus cases matching ground truth | **245** of 1036 (0 mismatches) |
|
|
136
150
|
| [Azure Monitor's published KQL subset](https://github.com/mmaitre314/duckdb-kql/blob/main/docs/azure-monitor-profile.md) | **114 / 119 (96%)** |
|
|
137
|
-
| Tabular operators | **
|
|
151
|
+
| Tabular operators | **16 / 41** |
|
|
138
152
|
| Scalar functions / aggregates / binary operators | **110 / 19 / 33** |
|
|
139
153
|
|
|
140
154
|
Supported operators: `where`, `project`, `project-away`, `project-rename`,
|
|
@@ -167,6 +181,7 @@ approximation.
|
|
|
167
181
|
| [Getting started](https://github.com/mmaitre314/duckdb-kql/blob/main/docs/getting-started.md) | Install, first query, the three layers |
|
|
168
182
|
| [**KQL support matrix**](https://github.com/mmaitre314/duckdb-kql/blob/main/docs/kql-support.md) | Every operator and function, supported or not, each with its gotchas |
|
|
169
183
|
| [Build-time CLI](https://github.com/mmaitre314/duckdb-kql/blob/main/docs/cli.md) | Translating `.kql` to `.sql` in CI, to avoid a runtime dependency |
|
|
184
|
+
| [Local Kusto endpoint](https://github.com/mmaitre314/duckdb-kql/blob/main/docs/kusto-server.md) | `duckdb-kql serve` — query a DuckDB file from the Azure Data Explorer UI |
|
|
170
185
|
| [API reference](https://github.com/mmaitre314/duckdb-kql/blob/main/docs/api.md) | Every public function and type |
|
|
171
186
|
| [Kusto SDK compatibility](https://github.com/mmaitre314/duckdb-kql/blob/main/docs/kusto-client.md) | What Layer 2 implements, no-ops, and refuses |
|
|
172
187
|
| [Azure Monitor profile](https://github.com/mmaitre314/duckdb-kql/blob/main/docs/azure-monitor-profile.md) | Coverage against a published KQL subset |
|
|
@@ -178,6 +193,7 @@ approximation.
|
|
|
178
193
|
| [`docs/m0-grammar-spike.md`](https://github.com/mmaitre314/duckdb-kql/blob/main/docs/m0-grammar-spike.md) | Grammar viability result |
|
|
179
194
|
| [`docs/frequency-scan-results.md`](https://github.com/mmaitre314/duckdb-kql/blob/main/docs/frequency-scan-results.md) | What KQL constructs actually get used |
|
|
180
195
|
| [`docs/licensing.md`](https://github.com/mmaitre314/duckdb-kql/blob/main/docs/licensing.md) | Third-party licensing review |
|
|
196
|
+
| [demo/](https://github.com/mmaitre314/duckdb-kql/blob/main/demo/demo.ipynb) | Notebook tour of all three layers, with outputs |
|
|
181
197
|
| [CONTRIBUTING.md](https://github.com/mmaitre314/duckdb-kql/blob/main/CONTRIBUTING.md) | How to add a mapping, and when not to |
|
|
182
198
|
| [SECURITY.md](https://github.com/mmaitre314/duckdb-kql/blob/main/SECURITY.md) | Reporting vulnerabilities; what is in scope |
|
|
183
199
|
| [Releases](https://github.com/mmaitre314/duckdb-kql/releases) | What changed, and when |
|
|
@@ -0,0 +1,503 @@
|
|
|
1
|
+
{
|
|
2
|
+
"cells": [
|
|
3
|
+
{
|
|
4
|
+
"cell_type": "markdown",
|
|
5
|
+
"id": "915bc73e",
|
|
6
|
+
"metadata": {},
|
|
7
|
+
"source": [
|
|
8
|
+
"# duckdb-kql — run KQL queries on DuckDB\n",
|
|
9
|
+
"\n",
|
|
10
|
+
"[](https://codespaces.new/mmaitre314/duckdb-kql?devcontainer_path=.devcontainer%2Fdemo%2Fdevcontainer.json&quickstart=1)\n",
|
|
11
|
+
"\n",
|
|
12
|
+
"[`duckdb-kql`](https://github.com/mmaitre314/duckdb-kql) translates\n",
|
|
13
|
+
"[Kusto Query Language](https://learn.microsoft.com/azure/data-explorer/kusto/query/) (KQL)\n",
|
|
14
|
+
"into [DuckDB](https://duckdb.org/) SQL, so you can run KQL against local files and in-process data with\n",
|
|
15
|
+
"no cluster and no network.\n",
|
|
16
|
+
"\n",
|
|
17
|
+
"To get started, either open the GitHub Codespace above or install Python followed by `pip install duckdb-kql[all]`."
|
|
18
|
+
]
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"cell_type": "code",
|
|
22
|
+
"execution_count": 1,
|
|
23
|
+
"id": "0a703ebc",
|
|
24
|
+
"metadata": {
|
|
25
|
+
"execution": {
|
|
26
|
+
"iopub.execute_input": "2026-08-14T00:22:04.749501Z",
|
|
27
|
+
"iopub.status.busy": "2026-08-14T00:22:04.749303Z",
|
|
28
|
+
"iopub.status.idle": "2026-08-14T00:22:04.811776Z",
|
|
29
|
+
"shell.execute_reply": "2026-08-14T00:22:04.810477Z"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"outputs": [
|
|
33
|
+
{
|
|
34
|
+
"name": "stdout",
|
|
35
|
+
"output_type": "stream",
|
|
36
|
+
"text": [
|
|
37
|
+
"duckdb-kql 0.0.1.dev2\n"
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
],
|
|
41
|
+
"source": [
|
|
42
|
+
"import duckdb_kql\n",
|
|
43
|
+
"\n",
|
|
44
|
+
"print(\"duckdb-kql\", duckdb_kql.__version__)"
|
|
45
|
+
]
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"cell_type": "markdown",
|
|
49
|
+
"id": "adbf3927",
|
|
50
|
+
"metadata": {},
|
|
51
|
+
"source": [
|
|
52
|
+
"## Sample data\n",
|
|
53
|
+
"\n",
|
|
54
|
+
"Connect to an in-memory instance of DuckDB and populate a sample table `Requests`."
|
|
55
|
+
]
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"cell_type": "code",
|
|
59
|
+
"execution_count": 2,
|
|
60
|
+
"id": "9e490c5b",
|
|
61
|
+
"metadata": {
|
|
62
|
+
"execution": {
|
|
63
|
+
"iopub.execute_input": "2026-08-14T00:22:04.813929Z",
|
|
64
|
+
"iopub.status.busy": "2026-08-14T00:22:04.813705Z",
|
|
65
|
+
"iopub.status.idle": "2026-08-14T00:22:06.117089Z",
|
|
66
|
+
"shell.execute_reply": "2026-08-14T00:22:06.115877Z"
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
"outputs": [
|
|
70
|
+
{
|
|
71
|
+
"name": "stdout",
|
|
72
|
+
"output_type": "stream",
|
|
73
|
+
"text": [
|
|
74
|
+
"{'Requests': ['Timestamp', 'Service', 'Region', 'Status', 'LatencyMs', 'UserId']}\n"
|
|
75
|
+
]
|
|
76
|
+
}
|
|
77
|
+
],
|
|
78
|
+
"source": [
|
|
79
|
+
"import random\n",
|
|
80
|
+
"from datetime import datetime, timedelta\n",
|
|
81
|
+
"\n",
|
|
82
|
+
"con = duckdb_kql.connect()\n",
|
|
83
|
+
"\n",
|
|
84
|
+
"start = datetime(2026, 3, 1)\n",
|
|
85
|
+
"\n",
|
|
86
|
+
"rows = []\n",
|
|
87
|
+
"for i in range(2_000):\n",
|
|
88
|
+
" service = random.choice([\"checkout\", \"search\", \"auth\", \"catalog\"])\n",
|
|
89
|
+
" status = random.choice([200, 200, 200, 500] if service == \"auth\" else [200, 200, 200, 200, 503])\n",
|
|
90
|
+
" rows.append((\n",
|
|
91
|
+
" start + timedelta(seconds=i * 37),\n",
|
|
92
|
+
" service,\n",
|
|
93
|
+
" random.choice([\"us-west-2\", \"us-east-1\", \"eu-west-1\"]),\n",
|
|
94
|
+
" status,\n",
|
|
95
|
+
" round(random.lognormvariate(3.2, 0.6), 1),\n",
|
|
96
|
+
" f\"user-{random.randint(1, 120):03d}\",\n",
|
|
97
|
+
" ))\n",
|
|
98
|
+
"\n",
|
|
99
|
+
"con.execute(\"\"\"\n",
|
|
100
|
+
" CREATE TABLE Requests (\n",
|
|
101
|
+
" Timestamp TIMESTAMP, Service VARCHAR, Region VARCHAR,\n",
|
|
102
|
+
" Status INTEGER, LatencyMs DOUBLE, UserId VARCHAR\n",
|
|
103
|
+
" )\n",
|
|
104
|
+
"\"\"\")\n",
|
|
105
|
+
"con.executemany(\"INSERT INTO Requests VALUES (?, ?, ?, ?, ?, ?)\", rows)\n",
|
|
106
|
+
"\n",
|
|
107
|
+
"print(duckdb_kql.engine.schema(con))"
|
|
108
|
+
]
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"cell_type": "markdown",
|
|
112
|
+
"id": "f570c3d5",
|
|
113
|
+
"metadata": {},
|
|
114
|
+
"source": [
|
|
115
|
+
"## Basic query"
|
|
116
|
+
]
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
"cell_type": "markdown",
|
|
120
|
+
"id": "3a08280a",
|
|
121
|
+
"metadata": {},
|
|
122
|
+
"source": [
|
|
123
|
+
"Run a simple query."
|
|
124
|
+
]
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
"cell_type": "code",
|
|
128
|
+
"execution_count": 3,
|
|
129
|
+
"id": "885b458a",
|
|
130
|
+
"metadata": {
|
|
131
|
+
"execution": {
|
|
132
|
+
"iopub.execute_input": "2026-08-14T00:22:06.119481Z",
|
|
133
|
+
"iopub.status.busy": "2026-08-14T00:22:06.119136Z",
|
|
134
|
+
"iopub.status.idle": "2026-08-14T00:22:06.588029Z",
|
|
135
|
+
"shell.execute_reply": "2026-08-14T00:22:06.586666Z"
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
"outputs": [
|
|
139
|
+
{
|
|
140
|
+
"data": {
|
|
141
|
+
"text/plain": [
|
|
142
|
+
"┌──────────┬───────────┬──────────┬───────┐\n",
|
|
143
|
+
"│ Service │ Region │ Failures │ Users │\n",
|
|
144
|
+
"│ varchar │ varchar │ int64 │ int64 │\n",
|
|
145
|
+
"├──────────┼───────────┼──────────┼───────┤\n",
|
|
146
|
+
"│ auth │ us-east-1 │ 60 │ 47 │\n",
|
|
147
|
+
"│ auth │ us-west-2 │ 41 │ 34 │\n",
|
|
148
|
+
"│ search │ us-west-2 │ 40 │ 34 │\n",
|
|
149
|
+
"│ checkout │ us-west-2 │ 39 │ 36 │\n",
|
|
150
|
+
"│ auth │ eu-west-1 │ 36 │ 31 │\n",
|
|
151
|
+
"└──────────┴───────────┴──────────┴───────┘"
|
|
152
|
+
]
|
|
153
|
+
},
|
|
154
|
+
"execution_count": 3,
|
|
155
|
+
"metadata": {},
|
|
156
|
+
"output_type": "execute_result"
|
|
157
|
+
}
|
|
158
|
+
],
|
|
159
|
+
"source": [
|
|
160
|
+
"duckdb_kql.kql(con, \"\"\"\n",
|
|
161
|
+
" Requests\n",
|
|
162
|
+
" | where Status >= 500\n",
|
|
163
|
+
" | summarize Failures = count(), Users = dcount(UserId) by Service, Region\n",
|
|
164
|
+
" | sort by Failures desc\n",
|
|
165
|
+
" | take 5\n",
|
|
166
|
+
"\"\"\")"
|
|
167
|
+
]
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
"cell_type": "markdown",
|
|
171
|
+
"id": "fd37be76",
|
|
172
|
+
"metadata": {},
|
|
173
|
+
"source": [
|
|
174
|
+
"## Query with parameters"
|
|
175
|
+
]
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
"cell_type": "markdown",
|
|
179
|
+
"id": "91860b14",
|
|
180
|
+
"metadata": {},
|
|
181
|
+
"source": [
|
|
182
|
+
"Run a query with parameters (preventing query injection)."
|
|
183
|
+
]
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
"cell_type": "code",
|
|
187
|
+
"execution_count": 5,
|
|
188
|
+
"id": "ec5d5b75",
|
|
189
|
+
"metadata": {
|
|
190
|
+
"execution": {
|
|
191
|
+
"iopub.execute_input": "2026-08-14T00:22:06.590536Z",
|
|
192
|
+
"iopub.status.busy": "2026-08-14T00:22:06.590303Z",
|
|
193
|
+
"iopub.status.idle": "2026-08-14T00:22:06.624113Z",
|
|
194
|
+
"shell.execute_reply": "2026-08-14T00:22:06.622769Z"
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
"outputs": [
|
|
198
|
+
{
|
|
199
|
+
"data": {
|
|
200
|
+
"text/plain": [
|
|
201
|
+
"┌───────┐\n",
|
|
202
|
+
"│ Count │\n",
|
|
203
|
+
"│ int64 │\n",
|
|
204
|
+
"├───────┤\n",
|
|
205
|
+
"│ 137 │\n",
|
|
206
|
+
"└───────┘"
|
|
207
|
+
]
|
|
208
|
+
},
|
|
209
|
+
"execution_count": 5,
|
|
210
|
+
"metadata": {},
|
|
211
|
+
"output_type": "execute_result"
|
|
212
|
+
}
|
|
213
|
+
],
|
|
214
|
+
"source": [
|
|
215
|
+
"duckdb_kql.kql(con, \"\"\"\n",
|
|
216
|
+
" declare query_parameters(service:string, floor:long);\n",
|
|
217
|
+
" \n",
|
|
218
|
+
" Requests\n",
|
|
219
|
+
" | where Service == service and Status >= floor\n",
|
|
220
|
+
" | count\n",
|
|
221
|
+
"\"\"\",\n",
|
|
222
|
+
" {\"service\": \"auth\", \"floor\": 500})"
|
|
223
|
+
]
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
"cell_type": "markdown",
|
|
227
|
+
"id": "0a9d90b5",
|
|
228
|
+
"metadata": {},
|
|
229
|
+
"source": [
|
|
230
|
+
"## KustoClient-compatible query\n",
|
|
231
|
+
"\n",
|
|
232
|
+
"Run a query using APIs compatible with Kusto client SDK ([azure-kusto-data](https://github.com/Azure/azure-kusto-python)). Useful to unit-test code with KQL queries meant to run against actual Kusto clusters."
|
|
233
|
+
]
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
"cell_type": "code",
|
|
237
|
+
"execution_count": 6,
|
|
238
|
+
"id": "f0323551",
|
|
239
|
+
"metadata": {
|
|
240
|
+
"execution": {
|
|
241
|
+
"iopub.execute_input": "2026-08-14T00:22:06.626383Z",
|
|
242
|
+
"iopub.status.busy": "2026-08-14T00:22:06.626164Z",
|
|
243
|
+
"iopub.status.idle": "2026-08-14T00:22:06.657424Z",
|
|
244
|
+
"shell.execute_reply": "2026-08-14T00:22:06.656176Z"
|
|
245
|
+
}
|
|
246
|
+
},
|
|
247
|
+
"outputs": [
|
|
248
|
+
{
|
|
249
|
+
"name": "stdout",
|
|
250
|
+
"output_type": "stream",
|
|
251
|
+
"text": [
|
|
252
|
+
"columns : [('Service', 'string'), ('Errors', 'long')]\n",
|
|
253
|
+
"raw_rows : [['auth', 137], ['search', 100], ['catalog', 98], ['checkout', 96]]\n",
|
|
254
|
+
"\n"
|
|
255
|
+
]
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
"data": {
|
|
259
|
+
"text/html": [
|
|
260
|
+
"<div>\n",
|
|
261
|
+
"<style scoped>\n",
|
|
262
|
+
" .dataframe tbody tr th:only-of-type {\n",
|
|
263
|
+
" vertical-align: middle;\n",
|
|
264
|
+
" }\n",
|
|
265
|
+
"\n",
|
|
266
|
+
" .dataframe tbody tr th {\n",
|
|
267
|
+
" vertical-align: top;\n",
|
|
268
|
+
" }\n",
|
|
269
|
+
"\n",
|
|
270
|
+
" .dataframe thead th {\n",
|
|
271
|
+
" text-align: right;\n",
|
|
272
|
+
" }\n",
|
|
273
|
+
"</style>\n",
|
|
274
|
+
"<table border=\"1\" class=\"dataframe\">\n",
|
|
275
|
+
" <thead>\n",
|
|
276
|
+
" <tr style=\"text-align: right;\">\n",
|
|
277
|
+
" <th></th>\n",
|
|
278
|
+
" <th>Service</th>\n",
|
|
279
|
+
" <th>Errors</th>\n",
|
|
280
|
+
" </tr>\n",
|
|
281
|
+
" </thead>\n",
|
|
282
|
+
" <tbody>\n",
|
|
283
|
+
" <tr>\n",
|
|
284
|
+
" <th>0</th>\n",
|
|
285
|
+
" <td>auth</td>\n",
|
|
286
|
+
" <td>137</td>\n",
|
|
287
|
+
" </tr>\n",
|
|
288
|
+
" <tr>\n",
|
|
289
|
+
" <th>1</th>\n",
|
|
290
|
+
" <td>search</td>\n",
|
|
291
|
+
" <td>100</td>\n",
|
|
292
|
+
" </tr>\n",
|
|
293
|
+
" <tr>\n",
|
|
294
|
+
" <th>2</th>\n",
|
|
295
|
+
" <td>catalog</td>\n",
|
|
296
|
+
" <td>98</td>\n",
|
|
297
|
+
" </tr>\n",
|
|
298
|
+
" <tr>\n",
|
|
299
|
+
" <th>3</th>\n",
|
|
300
|
+
" <td>checkout</td>\n",
|
|
301
|
+
" <td>96</td>\n",
|
|
302
|
+
" </tr>\n",
|
|
303
|
+
" </tbody>\n",
|
|
304
|
+
"</table>\n",
|
|
305
|
+
"</div>"
|
|
306
|
+
],
|
|
307
|
+
"text/plain": [
|
|
308
|
+
" Service Errors\n",
|
|
309
|
+
"0 auth 137\n",
|
|
310
|
+
"1 search 100\n",
|
|
311
|
+
"2 catalog 98\n",
|
|
312
|
+
"3 checkout 96"
|
|
313
|
+
]
|
|
314
|
+
},
|
|
315
|
+
"execution_count": 6,
|
|
316
|
+
"metadata": {},
|
|
317
|
+
"output_type": "execute_result"
|
|
318
|
+
}
|
|
319
|
+
],
|
|
320
|
+
"source": [
|
|
321
|
+
"from duckdb_kql.kusto import KustoClient\n",
|
|
322
|
+
"from duckdb_kql.kusto.helpers import dataframe_from_result_table\n",
|
|
323
|
+
"\n",
|
|
324
|
+
"client = KustoClient(con)\n",
|
|
325
|
+
"\n",
|
|
326
|
+
"response = client.execute(\"NetDefaultDB\", \"\"\"\n",
|
|
327
|
+
" Requests\n",
|
|
328
|
+
" | where Status >= 500\n",
|
|
329
|
+
" | summarize Errors = count() by Service\n",
|
|
330
|
+
" | sort by Errors desc\n",
|
|
331
|
+
"\"\"\")\n",
|
|
332
|
+
"\n",
|
|
333
|
+
"table = response.primary_results[0]\n",
|
|
334
|
+
"print(\"columns :\", [(c.column_name, c.column_type) for c in table.columns])\n",
|
|
335
|
+
"print(\"raw_rows :\", table.raw_rows)\n",
|
|
336
|
+
"print()\n",
|
|
337
|
+
"dataframe_from_result_table(table)"
|
|
338
|
+
]
|
|
339
|
+
},
|
|
340
|
+
{
|
|
341
|
+
"cell_type": "markdown",
|
|
342
|
+
"id": "792b4716",
|
|
343
|
+
"metadata": {},
|
|
344
|
+
"source": [
|
|
345
|
+
"## KQL query translation\n",
|
|
346
|
+
"\n",
|
|
347
|
+
"Pre-compute the SQL-equivalent query. This removes the runtime dependency on `duckdb_kql`."
|
|
348
|
+
]
|
|
349
|
+
},
|
|
350
|
+
{
|
|
351
|
+
"cell_type": "code",
|
|
352
|
+
"execution_count": 7,
|
|
353
|
+
"id": "edad6fb7",
|
|
354
|
+
"metadata": {
|
|
355
|
+
"execution": {
|
|
356
|
+
"iopub.execute_input": "2026-08-14T00:22:06.659549Z",
|
|
357
|
+
"iopub.status.busy": "2026-08-14T00:22:06.659325Z",
|
|
358
|
+
"iopub.status.idle": "2026-08-14T00:22:07.027790Z",
|
|
359
|
+
"shell.execute_reply": "2026-08-14T00:22:07.026542Z"
|
|
360
|
+
}
|
|
361
|
+
},
|
|
362
|
+
"outputs": [
|
|
363
|
+
{
|
|
364
|
+
"name": "stdout",
|
|
365
|
+
"output_type": "stream",
|
|
366
|
+
"text": [
|
|
367
|
+
"WITH _s0 AS (SELECT * FROM \"Requests\"),\n",
|
|
368
|
+
" _s1 AS (SELECT * FROM _s0 WHERE ((\"Status\" >= CAST(500 AS BIGINT)) AND coalesce(regexp_matches(\"Region\", '(?i)\\b' || regexp_escape('west') || '\\b'), FALSE))),\n",
|
|
369
|
+
" _s2 AS (SELECT \"Service\" AS \"Service\", count(*) AS \"Failures\", count(DISTINCT \"UserId\") AS \"Users\" FROM _s1 GROUP BY \"Service\"),\n",
|
|
370
|
+
" _s3 AS (SELECT * FROM _s2 ORDER BY \"Failures\" DESC NULLS LAST),\n",
|
|
371
|
+
" _s4 AS (SELECT * FROM _s3 LIMIT 3)\n",
|
|
372
|
+
"SELECT * FROM _s4\n"
|
|
373
|
+
]
|
|
374
|
+
}
|
|
375
|
+
],
|
|
376
|
+
"source": [
|
|
377
|
+
"print(duckdb_kql.to_sql(\"\"\"\n",
|
|
378
|
+
" Requests\n",
|
|
379
|
+
" | where Status >= 500 and Region has \"west\"\n",
|
|
380
|
+
" | summarize Failures = count(), Users = dcount(UserId) by Service\n",
|
|
381
|
+
" | sort by Failures desc\n",
|
|
382
|
+
" | take 3\n",
|
|
383
|
+
"\"\"\"))"
|
|
384
|
+
]
|
|
385
|
+
},
|
|
386
|
+
{
|
|
387
|
+
"cell_type": "markdown",
|
|
388
|
+
"id": "58785e21",
|
|
389
|
+
"metadata": {},
|
|
390
|
+
"source": [
|
|
391
|
+
"Validate the syntax of queries."
|
|
392
|
+
]
|
|
393
|
+
},
|
|
394
|
+
{
|
|
395
|
+
"cell_type": "code",
|
|
396
|
+
"execution_count": 7,
|
|
397
|
+
"id": "7754401b",
|
|
398
|
+
"metadata": {
|
|
399
|
+
"execution": {
|
|
400
|
+
"iopub.execute_input": "2026-08-14T00:22:07.030028Z",
|
|
401
|
+
"iopub.status.busy": "2026-08-14T00:22:07.029791Z",
|
|
402
|
+
"iopub.status.idle": "2026-08-14T00:22:07.035687Z",
|
|
403
|
+
"shell.execute_reply": "2026-08-14T00:22:07.033881Z"
|
|
404
|
+
}
|
|
405
|
+
},
|
|
406
|
+
"outputs": [
|
|
407
|
+
{
|
|
408
|
+
"name": "stdout",
|
|
409
|
+
"output_type": "stream",
|
|
410
|
+
"text": [
|
|
411
|
+
"1:9: mismatched input '|' expecting {<EOF>, ';'}\n"
|
|
412
|
+
]
|
|
413
|
+
}
|
|
414
|
+
],
|
|
415
|
+
"source": [
|
|
416
|
+
"for diagnostic in duckdb_kql.validate(\"Requests | wehr Status == 500\"):\n",
|
|
417
|
+
" print(diagnostic)"
|
|
418
|
+
]
|
|
419
|
+
},
|
|
420
|
+
{
|
|
421
|
+
"cell_type": "markdown",
|
|
422
|
+
"id": "2a0aa6d2",
|
|
423
|
+
"metadata": {},
|
|
424
|
+
"source": [
|
|
425
|
+
"Translate KQL queries using the CLI, to run query translation as part of build pipelines for instance."
|
|
426
|
+
]
|
|
427
|
+
},
|
|
428
|
+
{
|
|
429
|
+
"cell_type": "code",
|
|
430
|
+
"execution_count": 8,
|
|
431
|
+
"id": "ef8136f0",
|
|
432
|
+
"metadata": {
|
|
433
|
+
"execution": {
|
|
434
|
+
"iopub.execute_input": "2026-08-14T00:22:07.038043Z",
|
|
435
|
+
"iopub.status.busy": "2026-08-14T00:22:07.037826Z",
|
|
436
|
+
"iopub.status.idle": "2026-08-14T00:22:07.555577Z",
|
|
437
|
+
"shell.execute_reply": "2026-08-14T00:22:07.554223Z"
|
|
438
|
+
}
|
|
439
|
+
},
|
|
440
|
+
"outputs": [
|
|
441
|
+
{
|
|
442
|
+
"name": "stdout",
|
|
443
|
+
"output_type": "stream",
|
|
444
|
+
"text": [
|
|
445
|
+
"-- Generated by duckdb-kql from demo.kql. Do not edit.\r\n",
|
|
446
|
+
"--\r\n",
|
|
447
|
+
"-- Run with TimeZone set to UTC: SET TimeZone='UTC';\r\n",
|
|
448
|
+
"-- KQL datetimes are UTC, and DuckDB reads the session zone when casting\r\n",
|
|
449
|
+
"-- text without an offset. Without it, datetimes are silently shifted\r\n",
|
|
450
|
+
"-- rather than rejected.\r\n",
|
|
451
|
+
"\r\n",
|
|
452
|
+
"WITH _s0 AS (SELECT * FROM \"Requests\"),\r\n",
|
|
453
|
+
" _s1 AS (SELECT * FROM _s0 WHERE (\"Status\" >= CAST(500 AS BIGINT))),\r\n",
|
|
454
|
+
" _s2 AS (SELECT \"Service\" AS \"Service\", count(*) AS \"Errors\" FROM _s1 GROUP BY \"Service\")\r\n",
|
|
455
|
+
"SELECT * FROM _s2\r\n"
|
|
456
|
+
]
|
|
457
|
+
}
|
|
458
|
+
],
|
|
459
|
+
"source": [
|
|
460
|
+
"!duckdb-kql demo.kql"
|
|
461
|
+
]
|
|
462
|
+
},
|
|
463
|
+
{
|
|
464
|
+
"cell_type": "markdown",
|
|
465
|
+
"id": "fd8ed70c",
|
|
466
|
+
"metadata": {},
|
|
467
|
+
"source": [
|
|
468
|
+
"## Next steps\n",
|
|
469
|
+
"\n",
|
|
470
|
+
"Documentation:\n",
|
|
471
|
+
"- **[Getting started](https://github.com/mmaitre314/duckdb-kql/blob/main/docs/getting-started.md)**\n",
|
|
472
|
+
"- **[API reference](https://github.com/mmaitre314/duckdb-kql/blob/main/docs/api.md)**\n",
|
|
473
|
+
"- **[KQL support matrix](https://github.com/mmaitre314/duckdb-kql/blob/main/docs/kql-support.md)**\n",
|
|
474
|
+
"- **[Kusto SDK compatibility](https://github.com/mmaitre314/duckdb-kql/blob/main/docs/kusto-client.md)**\n",
|
|
475
|
+
"- **[Build-time translation](https://github.com/mmaitre314/duckdb-kql/blob/main/docs/cli.md)**.\n",
|
|
476
|
+
"\n",
|
|
477
|
+
"Found a query that returns something different from Kusto?\n",
|
|
478
|
+
"[Open an issue](https://github.com/mmaitre314/duckdb-kql/issues)."
|
|
479
|
+
]
|
|
480
|
+
}
|
|
481
|
+
],
|
|
482
|
+
"metadata": {
|
|
483
|
+
"kernelspec": {
|
|
484
|
+
"display_name": "Python 3",
|
|
485
|
+
"language": "python",
|
|
486
|
+
"name": "python3"
|
|
487
|
+
},
|
|
488
|
+
"language_info": {
|
|
489
|
+
"codemirror_mode": {
|
|
490
|
+
"name": "ipython",
|
|
491
|
+
"version": 3
|
|
492
|
+
},
|
|
493
|
+
"file_extension": ".py",
|
|
494
|
+
"mimetype": "text/x-python",
|
|
495
|
+
"name": "python",
|
|
496
|
+
"nbconvert_exporter": "python",
|
|
497
|
+
"pygments_lexer": "ipython3",
|
|
498
|
+
"version": "3.13.5"
|
|
499
|
+
}
|
|
500
|
+
},
|
|
501
|
+
"nbformat": 4,
|
|
502
|
+
"nbformat_minor": 5
|
|
503
|
+
}
|