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.
Files changed (108) hide show
  1. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/CHANGELOG.md +2 -0
  2. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/PKG-INFO +1 -1
  3. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/pyproject.toml +1 -1
  4. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/_decode.py +3 -0
  5. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/results.py +17 -1
  6. chtypes-2.0.0.dev14/tests/abi2/test_null_input.py +153 -0
  7. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/uv.lock +1 -1
  8. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/.gitignore +0 -0
  9. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/LICENSE +0 -0
  10. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/README.md +0 -0
  11. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/__init__.py +0 -0
  12. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/__main__.py +0 -0
  13. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/_abi2/__init__.py +0 -0
  14. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/_abi2/_decls.py +0 -0
  15. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/_abi2/_errmap.py +0 -0
  16. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/_abi2/_errors.py +0 -0
  17. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/_abi2/_loader.py +0 -0
  18. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/_abi2/_vocab.py +0 -0
  19. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/_advisory.py +0 -0
  20. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/_codes.py +0 -0
  21. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/_ed25519.py +0 -0
  22. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/_guard.py +0 -0
  23. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/_input.py +0 -0
  24. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/_ocifetch/__init__.py +0 -0
  25. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/_ocifetch/_channel.py +0 -0
  26. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/_ocifetch/_constants.py +0 -0
  27. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/_ocifetch/_dsse.py +0 -0
  28. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/_ocifetch/_ensure.py +0 -0
  29. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/_ocifetch/_errors.py +0 -0
  30. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/_ocifetch/_faults.py +0 -0
  31. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/_ocifetch/_goldens.py +0 -0
  32. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/_ocifetch/_hold.py +0 -0
  33. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/_ocifetch/_http.py +0 -0
  34. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/_ocifetch/_layout.py +0 -0
  35. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/_ocifetch/_lock.py +0 -0
  36. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/_ocifetch/_oci.py +0 -0
  37. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/_ocifetch/_prune.py +0 -0
  38. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/_ocifetch/_referrers.py +0 -0
  39. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/_ocifetch/_resolve.py +0 -0
  40. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/_ocifetch/_retired.py +0 -0
  41. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/_ocifetch/_revocation.py +0 -0
  42. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/_ocifetch/_unpack.py +0 -0
  43. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/_setup.py +0 -0
  44. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/errors.py +0 -0
  45. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/library.py +0 -0
  46. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/py.typed +0 -0
  47. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/src/chtypes/registry.py +0 -0
  48. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/abi2/__init__.py +0 -0
  49. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/abi2/_stub_tree.py +0 -0
  50. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/abi2/conftest.py +0 -0
  51. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/abi2/test_conformance.py +0 -0
  52. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/abi2/test_dev_fingerprint.py +0 -0
  53. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/abi2/test_goldens_runner_stub.py +0 -0
  54. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/abi2/test_loader.py +0 -0
  55. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/abi2/test_public_library.py +0 -0
  56. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/abi2/test_public_open.py +0 -0
  57. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/abi2/test_public_registry.py +0 -0
  58. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/abi2/test_public_server.py +0 -0
  59. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/abi2/test_public_setup_cases.py +0 -0
  60. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/abi2/test_reader_rules.py +0 -0
  61. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/abi2/test_registry_fetch.py +0 -0
  62. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/abi2/test_registry_flight.py +0 -0
  63. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/api/__init__.py +0 -0
  64. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/api/test_decode.py +0 -0
  65. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/api/test_input.py +0 -0
  66. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/api/test_trusted_keys.py +0 -0
  67. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/api/test_vocab.py +0 -0
  68. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/cli/__init__.py +0 -0
  69. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/cli/conftest.py +0 -0
  70. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/cli/test_devchannel.py +0 -0
  71. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/cli/test_main.py +0 -0
  72. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/cli/test_resolve_prune.py +0 -0
  73. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/cli/test_revocation_cli.py +0 -0
  74. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/conftest.py +0 -0
  75. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/goldens_v1/__init__.py +0 -0
  76. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/goldens_v1/data/stub-goldens.json +0 -0
  77. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/goldens_v1/test_runner.py +0 -0
  78. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/ocifetch/__init__.py +0 -0
  79. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/ocifetch/_bundle_support.py +0 -0
  80. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/ocifetch/_registry_support.py +0 -0
  81. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/ocifetch/_sign_support.py +0 -0
  82. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/ocifetch/conftest.py +0 -0
  83. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/ocifetch/test_cache_roots.py +0 -0
  84. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/ocifetch/test_cache_strict.py +0 -0
  85. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/ocifetch/test_conformance.py +0 -0
  86. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/ocifetch/test_devchannel.py +0 -0
  87. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/ocifetch/test_dsse.py +0 -0
  88. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/ocifetch/test_ensure.py +0 -0
  89. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/ocifetch/test_goldens.py +0 -0
  90. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/ocifetch/test_http.py +0 -0
  91. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/ocifetch/test_layout.py +0 -0
  92. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/ocifetch/test_lock.py +0 -0
  93. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/ocifetch/test_oci.py +0 -0
  94. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/ocifetch/test_preseed_scan.py +0 -0
  95. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/ocifetch/test_prodv2_smoke.py +0 -0
  96. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/ocifetch/test_prodv2channel.py +0 -0
  97. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/ocifetch/test_retired.py +0 -0
  98. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/ocifetch/test_revocation.py +0 -0
  99. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/ocifetch/test_revocation_statement.py +0 -0
  100. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/ocifetch/test_seam_gate.py +0 -0
  101. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/ocifetch/test_unpack.py +0 -0
  102. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/test_abi_identity.py +0 -0
  103. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/test_cache_roots_api.py +0 -0
  104. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/test_devchannel_registry.py +0 -0
  105. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/test_ed25519.py +0 -0
  106. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/test_examples_tour.py +0 -0
  107. {chtypes-2.0.0.dev13 → chtypes-2.0.0.dev14}/tests/test_fetch_options.py +0 -0
  108. {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.dev13
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "chtypes"
3
- version = "2.0.0.dev13"
3
+ version = "2.0.0.dev14"
4
4
  description = "Python binding for the chtypes artifacts: ClickHouse's own type system, per version, behind the frozen chs_* C ABI"
5
5
  readme = "README.md"
6
6
  license = "Apache-2.0"
@@ -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
+ )
@@ -74,7 +74,7 @@ wheels = [
74
74
 
75
75
  [[package]]
76
76
  name = "chtypes"
77
- version = "2.0.0.dev13"
77
+ version = "2.0.0.dev14"
78
78
  source = { editable = "." }
79
79
  dependencies = [
80
80
  { name = "backports-zstd", marker = "python_full_version < '3.14'" },
File without changes
File without changes
File without changes