sqlakit 0.22.7__tar.gz → 0.22.9__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.
- {sqlakit-0.22.7 → sqlakit-0.22.9}/PKG-INFO +1 -1
- {sqlakit-0.22.7 → sqlakit-0.22.9}/pyproject.toml +1 -1
- {sqlakit-0.22.7 → sqlakit-0.22.9}/pyproject.toml.orig +1 -1
- {sqlakit-0.22.7 → sqlakit-0.22.9}/sqlakit/_sql.py +72 -3
- {sqlakit-0.22.7 → sqlakit-0.22.9}/LICENSE +0 -0
- {sqlakit-0.22.7 → sqlakit-0.22.9}/README.md +0 -0
- {sqlakit-0.22.7 → sqlakit-0.22.9}/sqlakit/__init__.py +0 -0
- {sqlakit-0.22.7 → sqlakit-0.22.9}/sqlakit/_base.py +0 -0
- {sqlakit-0.22.7 → sqlakit-0.22.9}/sqlakit/_cli.py +0 -0
- {sqlakit-0.22.7 → sqlakit-0.22.9}/sqlakit/_db.py +0 -0
- {sqlakit-0.22.7 → sqlakit-0.22.9}/sqlakit/_discovery.py +0 -0
- {sqlakit-0.22.7 → sqlakit-0.22.9}/sqlakit/_linters.py +0 -0
- {sqlakit-0.22.7 → sqlakit-0.22.9}/sqlakit/_model.py +0 -0
- {sqlakit-0.22.7 → sqlakit-0.22.9}/sqlakit/_project.py +0 -0
- {sqlakit-0.22.7 → sqlakit-0.22.9}/sqlakit/_pycharm.py +0 -0
- {sqlakit-0.22.7 → sqlakit-0.22.9}/sqlakit/_query.py +0 -0
- {sqlakit-0.22.7 → sqlakit-0.22.9}/sqlakit/_recording.py +0 -0
- {sqlakit-0.22.7 → sqlakit-0.22.9}/sqlakit/_registry.py +0 -0
- {sqlakit-0.22.7 → sqlakit-0.22.9}/sqlakit/_render.py +0 -0
- {sqlakit-0.22.7 → sqlakit-0.22.9}/sqlakit/_routing.py +0 -0
- {sqlakit-0.22.7 → sqlakit-0.22.9}/sqlakit/_sql_formatter.py +0 -0
- {sqlakit-0.22.7 → sqlakit-0.22.9}/sqlakit/_static.py +0 -0
- {sqlakit-0.22.7 → sqlakit-0.22.9}/sqlakit/asyncio/__init__.py +0 -0
- {sqlakit-0.22.7 → sqlakit-0.22.9}/sqlakit/asyncio/_db.py +0 -0
- {sqlakit-0.22.7 → sqlakit-0.22.9}/sqlakit/asyncio/_registry.py +0 -0
- {sqlakit-0.22.7 → sqlakit-0.22.9}/sqlakit/asyncio/orm.py +0 -0
- {sqlakit-0.22.7 → sqlakit-0.22.9}/sqlakit/asyncio/sql.py +0 -0
- {sqlakit-0.22.7 → sqlakit-0.22.9}/sqlakit/exceptions.py +0 -0
- {sqlakit-0.22.7 → sqlakit-0.22.9}/sqlakit/orm.py +0 -0
- {sqlakit-0.22.7 → sqlakit-0.22.9}/sqlakit/py.typed +0 -0
- {sqlakit-0.22.7 → sqlakit-0.22.9}/sqlakit/pytest_plugin.py +0 -0
- {sqlakit-0.22.7 → sqlakit-0.22.9}/sqlakit/sql.py +0 -0
- {sqlakit-0.22.7 → sqlakit-0.22.9}/sqlakit/testing.py +0 -0
- {sqlakit-0.22.7 → sqlakit-0.22.9}/sqlakit/types.py +0 -0
|
@@ -10,7 +10,7 @@ from collections.abc import Mapping, Sized
|
|
|
10
10
|
from contextlib import contextmanager
|
|
11
11
|
from contextvars import ContextVar
|
|
12
12
|
from dataclasses import dataclass, is_dataclass
|
|
13
|
-
from functools import cache, cached_property, lru_cache
|
|
13
|
+
from functools import cache, cached_property, lru_cache, partial
|
|
14
14
|
from inspect import iscoroutinefunction
|
|
15
15
|
from pathlib import Path
|
|
16
16
|
from types import SimpleNamespace
|
|
@@ -613,6 +613,9 @@ def sql_macros(path: Path | str, source: str | None = None) -> list[SqlMacro]:
|
|
|
613
613
|
problem = f"its arguments in {path.name}:{line} name one twice: {params}"
|
|
614
614
|
raise MacroDefinitionError(name, problem, line=line)
|
|
615
615
|
named = offset + statement.start("name")
|
|
616
|
+
# The body may start below its `SELECT`, where its lines count from.
|
|
617
|
+
body = statement.group("body")
|
|
618
|
+
body_at = offset + statement.start("body") + len(body) - len(body.lstrip())
|
|
616
619
|
found.append(
|
|
617
620
|
SqlMacro(
|
|
618
621
|
name,
|
|
@@ -621,7 +624,7 @@ def sql_macros(path: Path | str, source: str | None = None) -> list[SqlMacro]:
|
|
|
621
624
|
doc=" ".join(one for one in doc if one),
|
|
622
625
|
path=path,
|
|
623
626
|
line=line,
|
|
624
|
-
body_line=
|
|
627
|
+
body_line=source.count("\n", 0, body_at) + 1,
|
|
625
628
|
name_at=(
|
|
626
629
|
source.count("\n", 0, named) + 1,
|
|
627
630
|
named - (source.rfind("\n", 0, named) + 1),
|
|
@@ -2293,6 +2296,69 @@ def array_contains_all(ctx: Context, array: Sql, other: Sql) -> str:
|
|
|
2293
2296
|
return f"({array} @> {other})"
|
|
2294
2297
|
|
|
2295
2298
|
|
|
2299
|
+
@sql_macro(optional=True)
|
|
2300
|
+
def any_of(ctx: Context, rows: Param, row: Sql, condition: Sql) -> str:
|
|
2301
|
+
"""Whether the condition holds for any of the rows, `row.field` reading a field.
|
|
2302
|
+
|
|
2303
|
+
```sql
|
|
2304
|
+
WHERE tpl.any_of(
|
|
2305
|
+
:cities, city,
|
|
2306
|
+
ST_DISTANCE(p.location, ST_POINT(city.lon, city.lat)) <= city.radius
|
|
2307
|
+
)
|
|
2308
|
+
```
|
|
2309
|
+
|
|
2310
|
+
The condition is written once for each row, its `city.field` bound to the
|
|
2311
|
+
row's field, and the conditions joined with `OR`. A row is an object or a
|
|
2312
|
+
mapping. No rows, or none passed, is `FALSE`: wrap the call in
|
|
2313
|
+
`tpl.if_set(:cities, ...)` for a filter that is off when nothing is passed.
|
|
2314
|
+
|
|
2315
|
+
The SQL grows with the rows, so this is for a few dozen. A condition made
|
|
2316
|
+
of equalities only is better as `EXISTS (SELECT 1 FROM tpl.values(:rows) AS
|
|
2317
|
+
v WHERE t.a = v.column1)`, which the database joins. Snowflake evaluates no
|
|
2318
|
+
subquery tied to the outer row by anything else, a distance, a range or a
|
|
2319
|
+
`LIKE`, and this macro covers those.
|
|
2320
|
+
|
|
2321
|
+
Raises:
|
|
2322
|
+
MacroArgumentError: if ``row`` is not a name, or a row has no field the
|
|
2323
|
+
condition reads.
|
|
2324
|
+
|
|
2325
|
+
"""
|
|
2326
|
+
name = str(row).strip()
|
|
2327
|
+
if not _IDENTIFIER.fullmatch(name):
|
|
2328
|
+
problem = f"`{name}` is not a name to read the fields of a row under"
|
|
2329
|
+
raise MacroArgumentError(any_of.name, problem)
|
|
2330
|
+
field = re.compile(
|
|
2331
|
+
rf"{_LITERAL}|(?<![\w.]){re.escape(name)}\.(?P<field>[A-Za-z_]\w*)\b",
|
|
2332
|
+
re.DOTALL,
|
|
2333
|
+
)
|
|
2334
|
+
prefix = _named(rows)
|
|
2335
|
+
|
|
2336
|
+
def written(item: Any, found: re.Match[str]) -> str: # noqa: ANN401
|
|
2337
|
+
key = found.group("field")
|
|
2338
|
+
if key is None:
|
|
2339
|
+
return found.group()
|
|
2340
|
+
try:
|
|
2341
|
+
value = item[key] if isinstance(item, Mapping) else getattr(item, key)
|
|
2342
|
+
except (KeyError, AttributeError):
|
|
2343
|
+
problem = f"a row of `{_as_written(rows)}` has no field `{key}`"
|
|
2344
|
+
raise MacroArgumentError(any_of.name, problem) from None
|
|
2345
|
+
return ctx.bind(value, f"{prefix}_{key}" if prefix else key)
|
|
2346
|
+
|
|
2347
|
+
text = str(condition)
|
|
2348
|
+
# A `--` comment at the end would take the `)` and the `OR` after it along.
|
|
2349
|
+
if any(
|
|
2350
|
+
found.group().startswith("--") and found.end() == len(text.rstrip())
|
|
2351
|
+
for found in re.finditer(_LITERAL, text, re.DOTALL)
|
|
2352
|
+
):
|
|
2353
|
+
text = text.rstrip() + "\n"
|
|
2354
|
+
conditions = [
|
|
2355
|
+
_grouped(field.sub(partial(written, item), text)) for item in rows.value or ()
|
|
2356
|
+
]
|
|
2357
|
+
if not conditions:
|
|
2358
|
+
return "FALSE"
|
|
2359
|
+
return _grouped(" OR ".join(conditions))
|
|
2360
|
+
|
|
2361
|
+
|
|
2296
2362
|
@sql_macro(name="values")
|
|
2297
2363
|
def values_table(ctx: Context, rows: Param) -> str:
|
|
2298
2364
|
"""Write a small table out in the query, one parameter per value.
|
|
@@ -2310,7 +2376,9 @@ def values_table(ctx: Context, rows: Param) -> str:
|
|
|
2310
2376
|
and PostgreSQL wants one type down a column: cast where the rows mix them.
|
|
2311
2377
|
|
|
2312
2378
|
For a few dozen rows. Hundreds are better sent as one JSON value and
|
|
2313
|
-
unpacked in the database, with `FLATTEN` or `json_array_elements`.
|
|
2379
|
+
unpacked in the database, with `FLATTEN` or `json_array_elements`. In an
|
|
2380
|
+
`EXISTS` tied to the outer row, compare by equalities only: Snowflake
|
|
2381
|
+
refuses any other tie, and `any_of` writes that condition for each row.
|
|
2314
2382
|
|
|
2315
2383
|
Raises:
|
|
2316
2384
|
MacroArgumentError: if there are no rows, or they differ in length.
|
|
@@ -2595,6 +2663,7 @@ BUILTIN_MACROS: Mapping[str, Macro] = {
|
|
|
2595
2663
|
arrays_overlap,
|
|
2596
2664
|
array_contains_all,
|
|
2597
2665
|
values_table,
|
|
2666
|
+
any_of,
|
|
2598
2667
|
)
|
|
2599
2668
|
}
|
|
2600
2669
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|