chtypes 2.0.0.dev13__tar.gz → 2.0.0.dev14__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.
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/CHANGELOG.md +2 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/PKG-INFO +1 -1
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/pyproject.toml +1 -1
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/_decode.py +3 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/results.py +17 -1
- chtypes-2.0.0.dev14/tests/abi2/test_null_input.py +153 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/uv.lock +1 -1
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/.gitignore +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/LICENSE +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/README.md +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/__init__.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/__main__.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/_abi2/__init__.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/_abi2/_decls.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/_abi2/_errmap.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/_abi2/_errors.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/_abi2/_loader.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/_abi2/_vocab.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/_advisory.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/_codes.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/_ed25519.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/_guard.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/_input.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/_ocifetch/__init__.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/_ocifetch/_channel.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/_ocifetch/_constants.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/_ocifetch/_dsse.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/_ocifetch/_ensure.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/_ocifetch/_errors.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/_ocifetch/_faults.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/_ocifetch/_goldens.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/_ocifetch/_hold.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/_ocifetch/_http.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/_ocifetch/_layout.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/_ocifetch/_lock.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/_ocifetch/_oci.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/_ocifetch/_prune.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/_ocifetch/_referrers.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/_ocifetch/_resolve.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/_ocifetch/_retired.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/_ocifetch/_revocation.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/_ocifetch/_unpack.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/_setup.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/errors.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/library.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/py.typed +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/registry.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/abi2/__init__.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/abi2/_stub_tree.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/abi2/conftest.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/abi2/test_conformance.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/abi2/test_dev_fingerprint.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/abi2/test_goldens_runner_stub.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/abi2/test_loader.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/abi2/test_public_library.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/abi2/test_public_open.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/abi2/test_public_registry.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/abi2/test_public_server.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/abi2/test_public_setup_cases.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/abi2/test_reader_rules.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/abi2/test_registry_fetch.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/abi2/test_registry_flight.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/api/__init__.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/api/test_decode.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/api/test_input.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/api/test_trusted_keys.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/api/test_vocab.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/cli/__init__.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/cli/conftest.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/cli/test_devchannel.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/cli/test_main.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/cli/test_resolve_prune.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/cli/test_revocation_cli.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/conftest.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/goldens_v1/__init__.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/goldens_v1/data/stub-goldens.json +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/goldens_v1/test_runner.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/ocifetch/__init__.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/ocifetch/_bundle_support.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/ocifetch/_registry_support.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/ocifetch/_sign_support.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/ocifetch/conftest.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/ocifetch/test_cache_roots.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/ocifetch/test_cache_strict.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/ocifetch/test_conformance.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/ocifetch/test_devchannel.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/ocifetch/test_dsse.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/ocifetch/test_ensure.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/ocifetch/test_goldens.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/ocifetch/test_http.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/ocifetch/test_layout.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/ocifetch/test_lock.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/ocifetch/test_oci.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/ocifetch/test_preseed_scan.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/ocifetch/test_prodv2_smoke.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/ocifetch/test_prodv2channel.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/ocifetch/test_retired.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/ocifetch/test_revocation.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/ocifetch/test_revocation_statement.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/ocifetch/test_seam_gate.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/ocifetch/test_unpack.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/test_abi_identity.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/test_cache_roots_api.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/test_devchannel_registry.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/test_ed25519.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/test_examples_tour.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/test_fetch_options.py +0 -0
- {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/test_support_v2_gen.py +0 -0
|
@@ -8,6 +8,8 @@ The four bindings in this repository are released together and give one answer,
|
|
|
8
8
|
|
|
9
9
|
### Added
|
|
10
10
|
|
|
11
|
+
- `Value.null_input` (ABI v2): whether the input field was the format's NULL literal (a JSON `null`, a TSV `\N`), decoded from `cols[].null_input` in the row and batch documents; false when the document carries none. It differs from `null` where a NULL literal is read into a column that is not Nullable. Decode cells over the ABI v2 stub (variant `null-input`: a NULL literal into a non-Nullable UUID, FixedString, Int32, DateTime, LowCardinality(String) and Enum8 column, in JSONEachRow and TSV) run in all four bindings.
|
|
12
|
+
- `Value.poison` and `Value.dup_dropped` (ABI v2): whether the stored value is one the server cannot read back (the batch is then `accepted_poisoned` and exports nothing), and whether the reader dropped a duplicate key, decoded from `cols[].poison` and `cols[].dup_dropped` in the row and batch documents; false when the document carries none. Decode cells over the ABI v2 stub (variant `value-flags`: a NULL read into a non-Nullable Enum8 in JSONEachRow and TSV, and a repeated top-level JSONEachRow key that arrives after every column has been read, which the reader skips so the first value is stored, with controls holding each member false and absent) run in all four bindings.
|
|
11
13
|
- Tests: the shared revocation-statement table (`tests/fixtures/revocation-statement/cases.json`) runs against `verify_revocation` and `check_installed`: `superseded_by` must be a string (`""` = no fix), and the first verifying revocation in a listing wins. No behavior changed.
|
|
12
14
|
- `column_defaults` is covered by unit tests (ABI v2): the exact `insert_options` JSON for a UTF-8 value, a non-UTF-8 value (`value_b64`), an empty value (sent as `""`) and two entries in order, and the decode of a `default_override` cell (`Source.DEFAULT_OVERRIDE`, stored). The batches guide gains a usage section. No behavior changed.
|
|
13
15
|
- Docs: `docs/guides/batches.md` shows how a `column_defaults` value is read, in the body format's own field encoding (CSV quoting, TSV escapes, a JSON string or number), with a value that doesn't read refused at the call before any body byte.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: chtypes
|
|
3
|
-
Version: 2.0.0.
|
|
3
|
+
Version: 2.0.0.dev14
|
|
4
4
|
Summary: Python binding for the chtypes artifacts: ClickHouse's own type system, per version, behind the frozen chs_* C ABI
|
|
5
5
|
Project-URL: Homepage, https://github.com/wave-rf/chtypes
|
|
6
6
|
Project-URL: Repository, https://github.com/wave-rf/chtypes
|
|
@@ -322,6 +322,9 @@ def _value(obj: dict[str, Any], where: str) -> Value:
|
|
|
322
322
|
source=source,
|
|
323
323
|
is_stored=Source.is_stored(source),
|
|
324
324
|
value=_opt_b64(obj, "value_b64", where),
|
|
325
|
+
null_input=_bool(obj, "null_input", where),
|
|
326
|
+
poison=_bool(obj, "poison", where),
|
|
327
|
+
dup_dropped=_bool(obj, "dup_dropped", where),
|
|
325
328
|
)
|
|
326
329
|
|
|
327
330
|
|
|
@@ -73,7 +73,20 @@ class Value:
|
|
|
73
73
|
bytes of a scalar String or FixedString leaf (from `value_b64`), None where
|
|
74
74
|
the document carries none. `null`: the library's verdict, poison included.
|
|
75
75
|
`source`: the `value_src` spelling (from `src`); `Source` lists them.
|
|
76
|
-
`is_stored`: the description's own fact for `source`.
|
|
76
|
+
`is_stored`: the description's own fact for `source`. `null_input`: whether
|
|
77
|
+
the input field was the format's NULL literal (a JSON null, a TSV `\\N`),
|
|
78
|
+
from `null_input`, False where the document carries none; a fact about the
|
|
79
|
+
input, where `null` is one about the stored value, so the two differ where
|
|
80
|
+
a NULL literal is read into a column that is not Nullable. `poison`: the
|
|
81
|
+
stored value is one the server cannot read back (the batch is then
|
|
82
|
+
accepted_poisoned and exports nothing), from `poison`; `dup_dropped`: the
|
|
83
|
+
reader dropped a duplicate key for this column, from `dup_dropped` (at default
|
|
84
|
+
settings a repeated top-level JSONEachRow key that arrives after every column
|
|
85
|
+
has been read is skipped: the first value is stored, this is True, and a
|
|
86
|
+
`duplicate_key_dropped` transform is reported; a repeat while a column is
|
|
87
|
+
still unread is refused with 117; a repeated key inside a Map value keeps
|
|
88
|
+
both keys). Each is
|
|
89
|
+
False where the document carries none.
|
|
77
90
|
"""
|
|
78
91
|
|
|
79
92
|
column: bytes
|
|
@@ -82,6 +95,9 @@ class Value:
|
|
|
82
95
|
source: str
|
|
83
96
|
is_stored: bool
|
|
84
97
|
value: bytes | None = None
|
|
98
|
+
null_input: bool = False
|
|
99
|
+
poison: bool = False
|
|
100
|
+
dup_dropped: bool = False
|
|
85
101
|
|
|
86
102
|
|
|
87
103
|
@dataclass(frozen=True, slots=True)
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"""The null_input decode cells, through the public API, over the generated v2
|
|
2
|
+
stub (variant "null-input").
|
|
3
|
+
|
|
4
|
+
The stub answers scripts/abi-v1/emit/_stubshared.py's NULL_INPUT_DOCS: for a
|
|
5
|
+
JSON null and a TSV `\\N` into each of six non-Nullable types (UUID,
|
|
6
|
+
FixedString(4), Int32, DateTime, LowCardinality(String), Enum8), the entry
|
|
7
|
+
carries `null_input` true; two controls carry it false and absent. Each entry's
|
|
8
|
+
expected value is read off its name (a NULL_INPUT_CONTROL_PREFIX name expects
|
|
9
|
+
False), and the cell count is the generator's NULL_INPUT_CELL_COUNT, so a
|
|
10
|
+
document that lost its cells fails. The other three bindings pin that count as
|
|
11
|
+
12; `test_null_input_cell_count_is_pinned` fails here first if the generator
|
|
12
|
+
changes it. Without `$CHTYPES_ABI2_STUBS` the stub tests skip loudly by name.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import shutil
|
|
18
|
+
import sys
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
import pytest
|
|
22
|
+
|
|
23
|
+
from chtypes import Format, Value, library
|
|
24
|
+
|
|
25
|
+
from .conftest import REPO_ROOT, stub_path
|
|
26
|
+
|
|
27
|
+
PINNED_CELL_COUNT = 12 # Go, TypeScript and Rust pin the same number
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _shared():
|
|
31
|
+
sys.path.insert(0, str(REPO_ROOT / "scripts" / "abi-v1"))
|
|
32
|
+
from emit import _stubshared
|
|
33
|
+
|
|
34
|
+
return _stubshared
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@pytest.fixture
|
|
38
|
+
def null_input_library(
|
|
39
|
+
stubs_dir: Path, stubs_manifest: dict, tmp_path: Path, clean_process, monkeypatch
|
|
40
|
+
):
|
|
41
|
+
monkeypatch.setenv("CHTYPES_ALLOW_UNVERIFIED_LIBRARY", "1")
|
|
42
|
+
target = tmp_path / "null-input.so"
|
|
43
|
+
shutil.copyfile(stub_path(stubs_dir, stubs_manifest["variants"]["null-input"]), target)
|
|
44
|
+
target.chmod(0o755)
|
|
45
|
+
with pytest.warns(UserWarning, match="UNVERIFIED"):
|
|
46
|
+
return library.open_unverified(target, allow=True)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _check(where: str, cols: tuple[Value, ...], prefix: bytes) -> int:
|
|
50
|
+
cells = 0
|
|
51
|
+
for c in cols:
|
|
52
|
+
want = not c.column.startswith(prefix)
|
|
53
|
+
cells += want
|
|
54
|
+
assert c.null_input is want, (
|
|
55
|
+
f"{where}: column {c.column!r}: null_input = {c.null_input}, want {want}"
|
|
56
|
+
)
|
|
57
|
+
# A NULL into a non-Nullable Enum8 stores 0, outside the enum: poisoned.
|
|
58
|
+
want_poison = want and c.column.endswith(b".enum8")
|
|
59
|
+
assert c.poison is want_poison, (
|
|
60
|
+
f"{where}: column {c.column!r}: poison = {c.poison}, want {want_poison}"
|
|
61
|
+
)
|
|
62
|
+
return cells
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def test_null_input_cell_count_is_pinned() -> None:
|
|
66
|
+
assert _shared().NULL_INPUT_CELL_COUNT == PINNED_CELL_COUNT
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def test_stub_null_input(null_input_library) -> None:
|
|
70
|
+
shared = _shared()
|
|
71
|
+
prefix = shared.NULL_INPUT_CONTROL_PREFIX.encode()
|
|
72
|
+
schema = null_input_library.compile_table(b"CREATE TABLE t (x Int32)")
|
|
73
|
+
|
|
74
|
+
row = schema.row(Format.JSON_EACH_ROW, b'{"x":null}')
|
|
75
|
+
assert _check("row", row.columns, prefix) == shared.NULL_INPUT_CELL_COUNT
|
|
76
|
+
|
|
77
|
+
batch = schema.rows(Format.TSV, b"\\N\n")
|
|
78
|
+
cells = sum(_check(f"batch.rows[{i}]", r.columns, prefix) for i, r in enumerate(batch.rows))
|
|
79
|
+
assert cells == shared.NULL_INPUT_CELL_COUNT
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
# The poison and dup_dropped cells, variant "value-flags"
|
|
83
|
+
# (_stubshared.VALUE_FLAGS_DOCS): a JSON null or TSV \N into a non-Nullable
|
|
84
|
+
# Enum8 (poison, in JSONEachRow and TSV) and one canned entry that carries
|
|
85
|
+
# dup_dropped, plus four controls that hold each member false and absent. A
|
|
86
|
+
# "poison." entry expects poison True and dup_dropped False, a "dup." entry the
|
|
87
|
+
# reverse, a "control." entry both False, so a binding that swaps the members
|
|
88
|
+
# fails. The other three bindings pin the counts as 2 and 1.
|
|
89
|
+
PINNED_POISON_CELLS = 2
|
|
90
|
+
PINNED_DUP_CELLS = 1
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@pytest.fixture
|
|
94
|
+
def value_flags_library(
|
|
95
|
+
stubs_dir: Path, stubs_manifest: dict, tmp_path: Path, clean_process, monkeypatch
|
|
96
|
+
):
|
|
97
|
+
monkeypatch.setenv("CHTYPES_ALLOW_UNVERIFIED_LIBRARY", "1")
|
|
98
|
+
target = tmp_path / "value-flags.so"
|
|
99
|
+
shutil.copyfile(stub_path(stubs_dir, stubs_manifest["variants"]["value-flags"]), target)
|
|
100
|
+
target.chmod(0o755)
|
|
101
|
+
with pytest.warns(UserWarning, match="UNVERIFIED"):
|
|
102
|
+
return library.open_unverified(target, allow=True)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _check_flags(where: str, cols: tuple[Value, ...], shared) -> tuple[int, int]:
|
|
106
|
+
poison = dup = 0
|
|
107
|
+
for c in cols:
|
|
108
|
+
name = c.column.decode()
|
|
109
|
+
want_p = name.startswith(shared.VALUE_FLAGS_POISON_PREFIX)
|
|
110
|
+
want_d = name.startswith(shared.VALUE_FLAGS_DUP_PREFIX)
|
|
111
|
+
if not (want_p or want_d):
|
|
112
|
+
assert name.startswith(shared.VALUE_FLAGS_CONTROL_PREFIX), (
|
|
113
|
+
f"{where}: unexpected {name!r}"
|
|
114
|
+
)
|
|
115
|
+
poison += want_p
|
|
116
|
+
dup += want_d
|
|
117
|
+
if want_d:
|
|
118
|
+
# The producing input: a repeated top-level JSONEachRow key after
|
|
119
|
+
# every column was read; the first value is stored.
|
|
120
|
+
assert (name, c.text) == ("dup.JSONEachRow.repeated_top_key", b"1"), where
|
|
121
|
+
assert (c.poison, c.dup_dropped) == (want_p, want_d), (
|
|
122
|
+
f"{where}: column {name!r}: poison = {c.poison}, dup_dropped = {c.dup_dropped}, "
|
|
123
|
+
f"want {want_p}, {want_d}"
|
|
124
|
+
)
|
|
125
|
+
return poison, dup
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def test_value_flags_cell_counts_are_pinned() -> None:
|
|
129
|
+
shared = _shared()
|
|
130
|
+
assert shared.VALUE_FLAGS_POISON_CELLS == PINNED_POISON_CELLS
|
|
131
|
+
assert shared.VALUE_FLAGS_DUP_CELLS == PINNED_DUP_CELLS
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def test_stub_value_flags(value_flags_library) -> None:
|
|
135
|
+
shared = _shared()
|
|
136
|
+
schema = value_flags_library.compile_table(b"CREATE TABLE t (x Int32)")
|
|
137
|
+
|
|
138
|
+
row = schema.row(Format.JSON_EACH_ROW, b'{"a":1,"a":2}')
|
|
139
|
+
assert _check_flags("row", row.columns, shared) == (PINNED_POISON_CELLS, PINNED_DUP_CELLS)
|
|
140
|
+
|
|
141
|
+
dup_transforms = [
|
|
142
|
+
t
|
|
143
|
+
for t in row.transformed
|
|
144
|
+
if t.reason == "duplicate_key_dropped" and t.column == b"dup.JSONEachRow.repeated_top_key"
|
|
145
|
+
]
|
|
146
|
+
assert len(dup_transforms) == 1
|
|
147
|
+
|
|
148
|
+
batch = schema.rows(Format.TSV, b"1\n")
|
|
149
|
+
counts = [_check_flags(f"batch.rows[{i}]", r.columns, shared) for i, r in enumerate(batch.rows)]
|
|
150
|
+
assert (sum(p for p, _ in counts), sum(d for _, d in counts)) == (
|
|
151
|
+
PINNED_POISON_CELLS,
|
|
152
|
+
PINNED_DUP_CELLS,
|
|
153
|
+
)
|
|
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
|
|
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
|
|
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
|
|
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
|