diff --git a/bench/ctable/bench_persistency.py b/bench/ctable/bench_persistency.py index 71d26bee..9cf4a54f 100644 --- a/bench/ctable/bench_persistency.py +++ b/bench/ctable/bench_persistency.py @@ -182,10 +182,10 @@ def bench_append_file(): t_ro = blosc2.CTable.open(path, mode="r") def bench_read_mem(t=t_mem_table): - _ = t["id"].to_numpy() + _ = t["id"][:] def bench_read_file(t=t_ro): - _ = t["id"].to_numpy() + _ = t["id"][:] t_m = tmin(bench_read_mem) t_f = tmin(bench_read_file) diff --git a/bench/ctable/compact.py b/bench/ctable/compact.py index a4817b0a..5ac36ade 100644 --- a/bench/ctable/compact.py +++ b/bench/ctable/compact.py @@ -9,7 +9,7 @@ # of varying fractions of the table. from dataclasses import dataclass -from time import time +from time import perf_counter as time import numpy as np diff --git a/bench/ctable/ctable_v_pandas.py b/bench/ctable/ctable_v_pandas.py index 3b7a6d52..c63eaf53 100644 --- a/bench/ctable/ctable_v_pandas.py +++ b/bench/ctable/ctable_v_pandas.py @@ -12,7 +12,7 @@ # 4. Row iteration from dataclasses import dataclass -from time import time +from time import perf_counter as time import numpy as np import pandas as pd @@ -75,7 +75,7 @@ class Row: # 2.5 Column access (full column) t0 = time() -arr = ct["score"].to_numpy() +arr = ct["score"][:] t_ct_col = time() - t0 t0 = time() @@ -86,7 +86,7 @@ class Row: # 3. Filtering t0 = time() -result_ct = ct.where((ct["id"] > 250_000) & (ct["id"] < 750_000)) +result_ct = ct.where((ct.id > 250_000) & (ct.id < 750_000)) t_ct_filter = time() - t0 t0 = time() diff --git a/bench/ctable/delete.py b/bench/ctable/delete.py index 79f59580..13db3fd4 100644 --- a/bench/ctable/delete.py +++ b/bench/ctable/delete.py @@ -9,7 +9,7 @@ # int, slice, and list — with varying sizes. from dataclasses import dataclass -from time import time +from time import perf_counter as time import numpy as np diff --git a/bench/ctable/expected_size.py b/bench/ctable/expected_size.py index e199d589..107e47a5 100644 --- a/bench/ctable/expected_size.py +++ b/bench/ctable/expected_size.py @@ -9,7 +9,7 @@ # is too small (M rows) vs correctly sized (N rows) during extend(). from dataclasses import dataclass -from time import time +from time import perf_counter as time import numpy as np diff --git a/bench/ctable/extend.py b/bench/ctable/extend.py index 5e1090ba..2f5657fa 100644 --- a/bench/ctable/extend.py +++ b/bench/ctable/extend.py @@ -11,7 +11,7 @@ # 3. An existing CTable (previously created from Python lists, 1M rows) from dataclasses import dataclass -from time import time +from time import perf_counter as time import numpy as np diff --git a/bench/ctable/extend_vs_append.py b/bench/ctable/extend_vs_append.py index db63206b..bd5617d8 100644 --- a/bench/ctable/extend_vs_append.py +++ b/bench/ctable/extend_vs_append.py @@ -5,72 +5,55 @@ # SPDX-License-Identifier: BSD-3-Clause ####################################################################### -# Benchmark for comparing append() (row by row) vs extend() (bulk), -# to find the crossover point where extend() becomes worth it. +# Benchmark: append() row-by-row vs extend() bulk insert. +# +# Compares three strategies at increasing N to find where extend() wins: +# 1. append() x N — one call per row, Pydantic path +# 2. extend() x N — extend([row]) per row, one at a time +# 3. extend() x 1 — single bulk call with all N rows from dataclasses import dataclass -from time import time +from time import perf_counter import blosc2 @dataclass class Row: - id: int = blosc2.field(blosc2.int64(ge=0)) - c_val: complex = blosc2.field(blosc2.complex128(), default=0j) - score: float = blosc2.field(blosc2.float64(ge=0, le=100), default=0.0) - active: bool = blosc2.field(blosc2.bool(), default=True) + id: int = blosc2.field(blosc2.int64(ge=0)) + score: float = blosc2.field(blosc2.float64(ge=0, le=100), default=0.0) + active: bool = blosc2.field(blosc2.bool(), default=True) -# Parameter — change N to test different crossover points -N = 2 -print("append() vs extend() benchmark") -for i in range(6): - print("\n") - print("%" * 100) +SIZES = [10, 100, 1_000, 10_000, 100_000] +print(f"append() vs extend() | sizes: {SIZES}") +print() +print(f"{'N':>10} {'append×N (s)':>14} {'extend×N (s)':>14} {'extend×1 (s)':>14} {'speedup bulk':>13}") +print(f"{'─'*10} {'─'*14} {'─'*14} {'─'*14} {'─'*13}") - # Base data generation - data_list = [ - [i, complex(i * 0.1, i * 0.01), 10.0 + (i % 100) * 0.4, i % 3 == 0] for i in range(N) - ] +for N in SIZES: + data = [[i, float(i % 100), i % 2 == 0] for i in range(N)] - # 1. N individual append() calls - print(f"{N} individual append() calls") - ct_append = blosc2.CTable(Row, expected_size=N) - t0 = time() - for row in data_list: - ct_append.append(row) - t_append = time() - t0 - print(f" Time: {t_append:.6f} s") - print(f" Rows: {len(ct_append):,}") + ct = blosc2.CTable(Row, expected_size=N) + t0 = perf_counter() + for row in data: + ct.append(row) + t_append = perf_counter() - t0 - # 2. N individual extend() calls (one row at a time) - print(f"{N} individual extend() calls (one row at a time)") - ct_extend_one = blosc2.CTable(Row, expected_size=N) - t0 = time() - for row in data_list: - ct_extend_one.extend([row]) - t_extend_one = time() - t0 - print(f" Time: {t_extend_one:.6f} s") - print(f" Rows: {len(ct_extend_one):,}") + ct = blosc2.CTable(Row, expected_size=N) + t0 = perf_counter() + for row in data: + ct.extend([row]) + t_extend_one = perf_counter() - t0 - # 3. Single extend() call with all N rows at once - print(f"Single extend() call with all {N} rows at once") - ct_extend_bulk = blosc2.CTable(Row, expected_size=N) - t0 = time() - ct_extend_bulk.extend(data_list) - t_extend_bulk = time() - t0 - print(f" Time: {t_extend_bulk:.6f} s") - print(f" Rows: {len(ct_extend_bulk):,}") + ct = blosc2.CTable(Row, expected_size=N) + t0 = perf_counter() + ct.extend(data) + t_extend_bulk = perf_counter() - t0 - # Summary - print("=" * 70) - print(f"{'METHOD':<35} {'TIME (s)':>12} {'SPEEDUP vs append':>20}") - print("-" * 70) - print(f"{'append() x N':<35} {t_append:>12.6f} {'1.00x':>20}") - print(f"{'extend() x N (one row each)':<35} {t_extend_one:>12.6f} {t_append / t_extend_one:>19.2f}x") - print(f"{'extend() x 1 (all at once)':<35} {t_extend_bulk:>12.6f} {t_append / t_extend_bulk:>19.2f}x") - print("-" * 70) + speedup = t_append / t_extend_bulk if t_extend_bulk > 0 else float("inf") + print(f"{N:>10,} {t_append:>14.6f} {t_extend_one:>14.6f} {t_extend_bulk:>14.6f} {speedup:>12.1f}×") - N=N*2 +print() +print("speedup bulk = append×N time / extend×1 time (higher is better for extend)") diff --git a/bench/ctable/indexin.md b/bench/ctable/indexin.md new file mode 100644 index 00000000..9b06218d --- /dev/null +++ b/bench/ctable/indexin.md @@ -0,0 +1,191 @@ +# CTable Index Benchmark | N=1,000,000 REPS=5 + +> Random data: sensor_id uniform random in [0, 100,000) +> Sorted data: sensor_id = 0,0,…,1,1,…,2,2,… (clustered, ~10 rows/value) + + +## BUCKET + +> Stores min/max per chunk. Can skip chunks whose range doesn't overlap the +> query. Only effective when data is sorted/clustered. Useless on random data. + +### Range query — random data +``` +────────────────────────────────────────────────────────────────────── + Random data — BUCKET index +────────────────────────────────────────────────────────────────────── + SELECTIVITY ROWS SCAN(ms) IDX(ms) SPEEDUP + ────────────── ───────── ───────── ───────── ──────── + 0.1% 922 12.8 10.1 1.3× + 1% 9,879 13.7 14.7 0.9× + 5% 49,991 17.1 17.9 1.0× + 10% 99,775 19.8 21.0 0.9× + 25% 249,376 24.0 25.0 1.0× + 50% 499,826 24.0 27.2 0.9× (slower) + 75% 749,665 23.2 27.5 0.8× (slower) +────────────────────────────────────────────────────────────────────── +``` + +### Range query — sorted data +``` +────────────────────────────────────────────────────────────────────── + Sorted data — BUCKET index +────────────────────────────────────────────────────────────────────── + SELECTIVITY ROWS SCAN(ms) IDX(ms) SPEEDUP + ────────────── ───────── ───────── ───────── ──────── + 0.1% 990 11.9 2.5 4.8× ← + 1% 9,990 11.9 2.2 5.5× ← + 5% 49,990 12.0 3.1 3.9× ← + 10% 99,990 12.1 5.1 2.4× ← + 25% 249,990 11.7 9.3 1.3× + 50% 499,990 12.3 19.0 0.6× (slower) + 75% 749,990 11.9 35.9 0.3× (slower) +────────────────────────────────────────────────────────────────────── +``` + + +## PARTIAL + +> Stores exact row positions. Works on any data layout. +> Smaller index than FULL; slightly less overhead to build. + +### Range query — random data +``` +────────────────────────────────────────────────────────────────────── + Random data — PARTIAL index +────────────────────────────────────────────────────────────────────── + SELECTIVITY ROWS SCAN(ms) IDX(ms) SPEEDUP + ────────────── ───────── ───────── ───────── ──────── + 0.1% 922 12.4 1.9 6.4× ← + 1% 9,879 14.4 2.5 5.8× ← + 5% 49,991 17.3 5.3 3.3× ← + 10% 99,775 20.1 8.8 2.3× ← + 25% 249,376 23.6 21.4 1.1× + 50% 499,826 26.2 46.4 0.6× (slower) + 75% 749,665 22.8 75.2 0.3× (slower) +────────────────────────────────────────────────────────────────────── +``` + +### Range query — sorted data +``` +────────────────────────────────────────────────────────────────────── + Sorted data — PARTIAL index +────────────────────────────────────────────────────────────────────── + SELECTIVITY ROWS SCAN(ms) IDX(ms) SPEEDUP + ────────────── ───────── ───────── ───────── ──────── + 0.1% 990 13.2 2.4 5.5× ← + 1% 9,990 12.8 2.0 6.4× ← + 5% 49,990 12.5 2.6 4.9× ← + 10% 99,990 12.7 4.0 3.1× ← + 25% 249,990 12.0 8.1 1.5× + 50% 499,990 11.9 18.5 0.6× (slower) + 75% 749,990 13.1 33.4 0.4× (slower) +────────────────────────────────────────────────────────────────────── +``` + +### Equality query — random data +``` + VALUE ROWS SCAN(ms) IDX(ms) SPEEDUP + ──────────── ────── ───────── ───────── ──────── + ==0 12 12.6 2.0 6.3× ← + ==25,000 13 14.2 1.9 7.5× ← + ==50,000 9 12.6 1.9 6.7× ← + ==99,999 4 12.4 1.9 6.7× ← +``` + +### Equality query — sorted data +``` + VALUE ROWS SCAN(ms) IDX(ms) SPEEDUP + ──────────── ────── ───────── ───────── ──────── + ==0 10 11.8 1.9 6.3× ← + ==25,000 10 11.7 1.8 6.7× ← + ==50,000 10 12.0 1.7 7.0× ← + ==99,999 10 12.1 1.7 7.1× ← +``` + + +## FULL + +> Stores exact row positions with full chunk coverage. +> Best query performance; larger index than PARTIAL. + +### Range query — random data +``` +────────────────────────────────────────────────────────────────────── + Random data — FULL index +────────────────────────────────────────────────────────────────────── + SELECTIVITY ROWS SCAN(ms) IDX(ms) SPEEDUP + ────────────── ───────── ───────── ───────── ──────── + 0.1% 922 13.2 2.1 6.4× ← + 1% 9,879 15.3 2.8 5.5× ← + 5% 49,991 18.1 5.1 3.5× ← + 10% 99,775 20.5 11.0 1.9× + 25% 249,376 23.5 21.5 1.1× + 50% 499,826 25.4 46.1 0.6× (slower) + 75% 749,665 23.2 86.9 0.3× (slower) +────────────────────────────────────────────────────────────────────── +``` + +### Range query — sorted data +``` +────────────────────────────────────────────────────────────────────── + Sorted data — FULL index +────────────────────────────────────────────────────────────────────── + SELECTIVITY ROWS SCAN(ms) IDX(ms) SPEEDUP + ────────────── ───────── ───────── ───────── ──────── + 0.1% 990 12.0 1.9 6.4× ← + 1% 9,990 12.0 2.0 6.1× ← + 5% 49,990 11.5 2.8 4.1× ← + 10% 99,990 12.0 4.2 2.9× ← + 25% 249,990 11.9 7.8 1.5× + 50% 499,990 11.8 18.5 0.6× (slower) + 75% 749,990 11.5 44.5 0.3× (slower) +────────────────────────────────────────────────────────────────────── +``` + +### Equality query — random data +``` + VALUE ROWS SCAN(ms) IDX(ms) SPEEDUP + ──────────── ────── ───────── ───────── ──────── + ==0 12 12.1 2.5 4.8× ← + ==25,000 13 12.0 2.0 6.1× ← + ==50,000 9 12.4 2.0 6.2× ← + ==99,999 4 12.6 2.0 6.4× ← +``` + +### Equality query — sorted data +``` + VALUE ROWS SCAN(ms) IDX(ms) SPEEDUP + ──────────── ────── ───────── ───────── ──────── + ==0 10 11.7 1.8 6.5× ← + ==25,000 10 11.5 1.7 6.6× ← + ==50,000 10 12.4 1.7 7.1× ← + ==99,999 10 12.3 1.8 7.0× ← +``` + +### Cardinality comparison — sorted data, FULL index + +> Shows how repetition level affects speedup (data always sorted). +``` + CARDINALITY 0.1% sel 1% sel 5% sel 10% sel + ────────────────────────────────────────────────────────────────────── + High rep (10 uniq) 9.1× 9.6× 8.9× 10.1× + Med rep (1k uniq) 8.5× 6.2× 4.3× 3.5× + Low rep (1M uniq) 6.4× 5.9× 4.2× 3.2× + ────────────────────────────────────────────────────────────────────── + (speedup — higher is better) +``` + +### Compound filter — sorted data, FULL index + +> sensor_id > X AND region == Y | region in [0,8) → ~12.5% per value +``` +──────────────────────────────────────────────────────────────────────────────── + QUERY ROWS NO IDX IDX:sid IDX:reg 2 IDX BEST + ────────────── ──────── ───────── ───────── ───────── ───────── ──────────── + 0.1%+12.5% 127 14.6ms 2.6ms 15.0ms 14.4ms sid(5.6×) + 1%+12.5% 1,297 14.7ms 2.6ms 15.2ms 17.2ms sid(5.7×) + 5%+12.5% 6,268 16.2ms 4.5ms 16.8ms 20.3ms sid(3.6×) + 10%+12.5% 12,377 19.5ms 6.2ms 19.6ms 21.0ms sid(3.2×) +──────────────────────────────────────────────────────────────────────────────── +``` diff --git a/bench/ctable/indexin.py b/bench/ctable/indexin.py new file mode 100644 index 00000000..c022a3e0 --- /dev/null +++ b/bench/ctable/indexin.py @@ -0,0 +1,302 @@ +####################################################################### +# Copyright (c) 2019-present, Blosc Development Team +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause +####################################################################### + +# Benchmark: CTable index kinds vs full-scan speedup. +# +# Sections +# ──────── +# ## BUCKET — min/max per chunk; only helps on sorted/clustered data +# ## PARTIAL — exact positions; works on any data layout +# ## FULL — exact positions, best performance; works on any data layout +# +# Each section benchmarks range queries (sensor_id > X) on random and +# sorted data, plus equality queries (sensor_id == X) where applicable. +# FULL also covers cardinality and compound filters. + +from dataclasses import dataclass +from time import perf_counter + +import numpy as np + +import blosc2 + +# ── Config ──────────────────────────────────────────────────────────────────── + +N = 1_000_000 +REPS = 5 + +# ── Schema ──────────────────────────────────────────────────────────────────── + +@dataclass +class Row: + sensor_id: int = blosc2.field(blosc2.int32()) + temperature: float = blosc2.field(blosc2.float64()) + region: int = blosc2.field(blosc2.int32()) + +np_dtype = np.dtype([ + ("sensor_id", np.int32), + ("temperature", np.float64), + ("region", np.int32), +]) + +rng = np.random.default_rng(42) + +SID_MAX = N // 10 # 100_000 unique sensor_id values, ~10 rows each + +# ── Helpers ─────────────────────────────────────────────────────────────────── + +def make_table(sensor_ids): + DATA = np.empty(N, dtype=np_dtype) + DATA["sensor_id"] = sensor_ids + DATA["temperature"] = 15.0 + rng.random(N) * 25 + DATA["region"] = rng.integers(0, 8, size=N, dtype=np.int32) + ct = blosc2.CTable(Row, expected_size=N) + ct.extend(DATA) + return ct + +def bench_gt(table, threshold, reps=REPS): + times = [] + for _ in range(reps): + t0 = perf_counter() + result = table.where(table.sensor_id > threshold) + times.append(perf_counter() - t0) + return float(np.median(times)), len(result) + +def bench_eq(table, value, reps=REPS): + times = [] + for _ in range(reps): + t0 = perf_counter() + result = table.where(table.sensor_id == value) + times.append(perf_counter() - t0) + return float(np.median(times)), len(result) + +def bench_compound(table, threshold, region, reps=REPS): + times = [] + for _ in range(reps): + t0 = perf_counter() + result = table.where( + (table.sensor_id > threshold) & (table.region == region) + ) + times.append(perf_counter() - t0) + return float(np.median(times)), len(result) + +def drop_all_indexes(ct): + for col in ("sensor_id", "region"): + try: + ct.drop_index(col) + except Exception: + pass + +FRACS = [0.999, 0.99, 0.95, 0.90, 0.75, 0.50, 0.25] +LABELS = ["0.1%", "1%", "5%", "10%", "25%", "50%", "75%"] + +def print_range_table(results, title, width=70): + print("```") + print(f"{'─' * width}") + print(f" {title}") + print(f"{'─' * width}") + print(f" {'SELECTIVITY':<14} {'ROWS':>9} {'SCAN(ms)':>9} {'IDX(ms)':>9} {'SPEEDUP':>8}") + print(f" {'─'*14} {'─'*9} {'─'*9} {'─'*9} {'─'*8}") + for label, n, t_scan, t_idx in results: + speedup = t_scan / t_idx if t_idx > 0 else float("inf") + marker = " ←" if speedup >= 2.0 else (" (slower)" if speedup < 0.9 else "") + print(f" {label:<14} {n:>9,} {t_scan*1e3:>9.1f} {t_idx*1e3:>9.1f} {speedup:>7.1f}×{marker}") + print(f"{'─' * width}") + print("```") + +def bench_range_section(ct, kind, label): + thresholds = [(lbl, int(SID_MAX * f)) for lbl, f in zip(LABELS, FRACS)] + drop_all_indexes(ct) + scan = {lbl: bench_gt(ct, thr) for lbl, thr in thresholds} + ct.create_index("sensor_id", kind=kind) + results = [] + for lbl, thr in thresholds: + t_idx, n = bench_gt(ct, thr) + t_scan, _ = scan[lbl] + results.append((lbl, n, t_scan, t_idx)) + print_range_table(results, label) + +def bench_eq_section(ct, kind): + EQ_VALS = [0, SID_MAX // 4, SID_MAX // 2, SID_MAX - 1] + drop_all_indexes(ct) + scan_eq = {v: bench_eq(ct, v) for v in EQ_VALS} + ct.create_index("sensor_id", kind=kind) + idx_eq = {v: bench_eq(ct, v) for v in EQ_VALS} + print("```") + print(f" {'VALUE':<12} {'ROWS':>6} {'SCAN(ms)':>9} {'IDX(ms)':>9} {'SPEEDUP':>8}") + print(f" {'─'*12} {'─'*6} {'─'*9} {'─'*9} {'─'*8}") + for v in EQ_VALS: + t_s, n = scan_eq[v] + t_i, _ = idx_eq[v] + spd = t_s / t_i if t_i > 0 else float("inf") + marker = " ←" if spd >= 2.0 else "" + print(f" =={v:<10,} {n:>6,} {t_s*1e3:>9.1f} {t_i*1e3:>9.1f} {spd:>7.1f}×{marker}") + print("```") + +# ── Data ────────────────────────────────────────────────────────────────────── + +rand_ids = rng.integers(0, SID_MAX, size=N, dtype=np.int32) +sorted_ids = np.repeat(np.arange(SID_MAX, dtype=np.int32), N // SID_MAX) + +ct_rand = make_table(rand_ids) +ct_sorted = make_table(sorted_ids) + +print(f"# CTable Index Benchmark | N={N:,} REPS={REPS}") +print(f"\n> Random data: sensor_id uniform random in [0, {SID_MAX:,})") +print(f"> Sorted data: sensor_id = 0,0,…,1,1,…,2,2,… (clustered, ~10 rows/value)") + + +# ══════════════════════════════════════════════════════════════════════════════ +# ## BUCKET +# ══════════════════════════════════════════════════════════════════════════════ + +print("\n\n## BUCKET") +print("\n> Stores min/max per chunk. Can skip chunks whose range doesn't overlap the") +print("> query. Only effective when data is sorted/clustered. Useless on random data.") + +print("\n### Range query — random data") +bench_range_section(ct_rand, blosc2.IndexKind.BUCKET, "Random data — BUCKET index") + +print("\n### Range query — sorted data") +bench_range_section(ct_sorted, blosc2.IndexKind.BUCKET, "Sorted data — BUCKET index") + + +# ══════════════════════════════════════════════════════════════════════════════ +# ## PARTIAL +# ══════════════════════════════════════════════════════════════════════════════ + +print("\n\n## PARTIAL") +print("\n> Stores exact row positions. Works on any data layout.") +print("> Smaller index than FULL; slightly less overhead to build.") + +print("\n### Range query — random data") +bench_range_section(ct_rand, blosc2.IndexKind.PARTIAL, "Random data — PARTIAL index") + +print("\n### Range query — sorted data") +bench_range_section(ct_sorted, blosc2.IndexKind.PARTIAL, "Sorted data — PARTIAL index") + +print("\n### Equality query — random data") +drop_all_indexes(ct_rand) +bench_eq_section(ct_rand, blosc2.IndexKind.PARTIAL) + +print("\n### Equality query — sorted data") +drop_all_indexes(ct_sorted) +bench_eq_section(ct_sorted, blosc2.IndexKind.PARTIAL) + + +# ══════════════════════════════════════════════════════════════════════════════ +# ## FULL +# ══════════════════════════════════════════════════════════════════════════════ + +print("\n\n## FULL") +print("\n> Stores exact row positions with full chunk coverage.") +print("> Best query performance; larger index than PARTIAL.") + +print("\n### Range query — random data") +bench_range_section(ct_rand, blosc2.IndexKind.FULL, "Random data — FULL index") + +print("\n### Range query — sorted data") +bench_range_section(ct_sorted, blosc2.IndexKind.FULL, "Sorted data — FULL index") + +print("\n### Equality query — random data") +drop_all_indexes(ct_rand) +bench_eq_section(ct_rand, blosc2.IndexKind.FULL) + +print("\n### Equality query — sorted data") +drop_all_indexes(ct_sorted) +bench_eq_section(ct_sorted, blosc2.IndexKind.FULL) + +# ── Cardinality (FULL, sorted) ──────────────────────────────────────────────── + +print("\n### Cardinality comparison — sorted data, FULL index") +print("\n> Shows how repetition level affects speedup (data always sorted).") + +CARD_CASES = [ + ("High rep (10 uniq)", 10), + ("Med rep (1k uniq)", 1_000), + ("Low rep (1M uniq)", N), +] +SEL_FRACS = [0.999, 0.99, 0.95, 0.90] +SEL_LABELS = ["0.1%", "1%", "5%", "10%"] + +W2 = 72 +print("```") +print(f" {'CARDINALITY':<24}", end="") +for lbl in SEL_LABELS: + print(f" {lbl+' sel':>12}", end="") +print() +print(" " + "─" * (W2 - 2)) + +for case_lbl, n_unique in CARD_CASES: + reps_per_val = N // n_unique + ids = np.repeat(np.arange(n_unique, dtype=np.int32), reps_per_val) + if len(ids) < N: + ids = np.concatenate([ids, np.zeros(N - len(ids), dtype=np.int32)]) + sid_max = n_unique + ct_c = make_table(ids) + thr_list = [(lbl, int(sid_max * f)) for lbl, f in zip(SEL_LABELS, SEL_FRACS)] + scan_c = {lbl: bench_gt(ct_c, thr) for lbl, thr in thr_list} + ct_c.create_index("sensor_id", kind=blosc2.IndexKind.FULL) + print(f" {case_lbl:<24}", end="") + for lbl, thr in thr_list: + t_idx, _ = bench_gt(ct_c, thr) + t_scan, _ = scan_c[lbl] + spd = t_scan / t_idx if t_idx > 0 else float("inf") + print(f" {spd:>10.1f}× ", end="") + print() + +print(" " + "─" * (W2 - 2)) +print(" (speedup — higher is better)") +print("```") + +# ── Compound filters (FULL, sorted) ────────────────────────────────────────── + +print("\n### Compound filter — sorted data, FULL index") +print("\n> sensor_id > X AND region == Y | region in [0,8) → ~12.5% per value") + +REGION_TARGET = 3 +COMPOUND_THRESHOLDS = [ + ("0.1%+12.5%", int(SID_MAX * 0.999)), + ("1%+12.5%", int(SID_MAX * 0.99)), + ("5%+12.5%", int(SID_MAX * 0.95)), + ("10%+12.5%", int(SID_MAX * 0.90)), +] + +drop_all_indexes(ct_sorted) +no_idx = {lbl: bench_compound(ct_sorted, thr, REGION_TARGET) for lbl, thr in COMPOUND_THRESHOLDS} +ct_sorted.create_index("sensor_id", kind=blosc2.IndexKind.FULL) +one_idx_sid = {lbl: bench_compound(ct_sorted, thr, REGION_TARGET) for lbl, thr in COMPOUND_THRESHOLDS} +ct_sorted.drop_index("sensor_id") +ct_sorted.create_index("region", kind=blosc2.IndexKind.FULL) +one_idx_reg = {lbl: bench_compound(ct_sorted, thr, REGION_TARGET) for lbl, thr in COMPOUND_THRESHOLDS} +ct_sorted.create_index("sensor_id", kind=blosc2.IndexKind.FULL) +two_idx = {lbl: bench_compound(ct_sorted, thr, REGION_TARGET) for lbl, thr in COMPOUND_THRESHOLDS} + +W3 = 80 +print("```") +print(f"{'─' * W3}") +print(f" {'QUERY':<14} {'ROWS':>8} {'NO IDX':>9} {'IDX:sid':>9} {'IDX:reg':>9} {'2 IDX':>9} {'BEST'}") +print(f" {'─'*14} {'─'*8} {'─'*9} {'─'*9} {'─'*9} {'─'*9} {'─'*12}") +for lbl, thr in COMPOUND_THRESHOLDS: + n = no_idx[lbl][1] + t_none = no_idx[lbl][0] + t_sid = one_idx_sid[lbl][0] + t_reg = one_idx_reg[lbl][0] + t_two = two_idx[lbl][0] + best_t = min(t_none, t_sid, t_reg, t_two) + spd = t_none / best_t + winner = ["none", "sid", "reg", "2idx"][[t_none, t_sid, t_reg, t_two].index(best_t)] + print( + f" {lbl:<14} {n:>8,}" + f" {t_none*1e3:>8.1f}ms" + f" {t_sid*1e3:>8.1f}ms" + f" {t_reg*1e3:>8.1f}ms" + f" {t_two*1e3:>8.1f}ms" + f" {winner}({spd:.1f}×)" + ) +print(f"{'─' * W3}") +print("```") diff --git a/bench/ctable/iter_rows.py b/bench/ctable/iter_rows.py index 51203ba4..7271048a 100644 --- a/bench/ctable/iter_rows.py +++ b/bench/ctable/iter_rows.py @@ -5,93 +5,54 @@ # SPDX-License-Identifier: BSD-3-Clause ####################################################################### +# Benchmark: row iteration cost with different access patterns. +# +# Measures: +# 1. Iteration without column access (loop overhead only) +# 2. Iteration accessing 1 column per row +# 3. Iteration accessing 3 columns per row +# 4. Iteration with deleted rows (holes in _valid_rows) + from dataclasses import dataclass -from time import time +from time import perf_counter import blosc2 -from blosc2 import CTable @dataclass class Row: - id: int = blosc2.field(blosc2.int64(ge=0)) - score: float = blosc2.field(blosc2.float64(ge=0, le=100)) - active: bool = blosc2.field(blosc2.bool(), default=True) + id: int = blosc2.field(blosc2.int64(ge=0)) + score: float = blosc2.field(blosc2.float64(ge=0, le=100)) + active: bool = blosc2.field(blosc2.bool(), default=True) -N = 1_000 # start small, increase when confident +N = 100_000 data = [(i, float(i % 100), i % 2 == 0) for i in range(N)] -tabla = CTable(Row, new_data=data) - -print(f"Table created with {len(tabla)} rows\n") - -# ------------------------------------------------------------------- -# Test 1: iterate without accessing any column (minimum cost) -# ------------------------------------------------------------------- -t0 = time() -for _row in tabla: - pass -t1 = time() -print(f"[Test 1] Iter without accessing columns: {(t1 - t0)*1000:.3f} ms") - -# ------------------------------------------------------------------- -# Test 2: iterate accessing a single column (real_pos cached once) -# ------------------------------------------------------------------- -t0 = time() -for row in tabla: - _ = row["id"] -t1 = time() -print(f"[Test 2] Iter accessing 'id': {(t1 - t0)*1000:.3f} ms") - -# ------------------------------------------------------------------- -# Test 3: iterate accessing all columns (real_pos cached once per row) -# ------------------------------------------------------------------- -t0 = time() -for row in tabla: - _ = row["id"] - _ = row["score"] - _ = row["active"] -t1 = time() -print(f"[Test 3] Iter accessing 3 columns: {(t1 - t0)*1000:.3f} ms") - -# ------------------------------------------------------------------- -# Test 4: correctness — values match expected -# ------------------------------------------------------------------- -errors = 0 -for row in tabla: - if row["id"] != row._nrow: - errors += 1 - if row["score"] != float(row._nrow % 100): - errors += 1 - if row["active"] != (row._nrow % 2 == 0): - errors += 1 - -print(f"\n[Test 4] Correctness errors: {errors} (expected: 0)") - -# ------------------------------------------------------------------- -# Test 5: with holes (deleted rows) -# ------------------------------------------------------------------- -tabla2 = CTable(Row, new_data=data) -tabla2.delete(list(range(0, N, 2))) # delete even rows, keep odd ones - -print(f"\nTable with holes: {len(tabla2)} rows (expected: {N // 2})") - -t0 = time() -ids = [] -for row in tabla2: - ids.append(row["id"]) -t1 = time() - -expected_ids = [i for i in range(N) if i % 2 != 0] -ok = ids == expected_ids -print(f"[Test 5] Iter with holes ({N//2} rows): {(t1 - t0)*1000:.3f} ms | correctness: {ok}") - -# ------------------------------------------------------------------- -# Test 6: real_pos is cached correctly (not recomputed) -# ------------------------------------------------------------------- -row0 = next(iter(tabla)) -assert row0._real_pos is None, "real_pos should be None before first access" -_ = row0["id"] -assert row0._real_pos is not None, "real_pos should be cached after first access" -print(f"\n[Test 6] real_pos caching: OK (real_pos={row0._real_pos})") +ct = blosc2.CTable(Row, expected_size=N) +ct.extend(data) + +ct_holes = blosc2.CTable(Row, expected_size=N) +ct_holes.extend(data) +ct_holes.delete(list(range(0, N, 2))) # keep only odd rows + +print(f"Row iteration benchmark | N = {N:,} | holes table: {len(ct_holes):,} rows") +print() +print(f" {'PATTERN':<35} {'TIME (ms)':>10} {'µs/row':>8}") +print(f" {'─'*35} {'─'*10} {'─'*8}") + +cases = [ + ("no column access", ct, lambda row: None), + ("access 1 col (id)", ct, lambda row: row["id"]), + ("access 3 cols", ct, lambda row: (row["id"], row["score"], row["active"])), + ("access 1 col — with holes", ct_holes, lambda row: row["id"]), +] + +for label, table, accessor in cases: + n = len(table) + t0 = perf_counter() + for row in table: + accessor(row) + elapsed = perf_counter() - t0 + us = elapsed / n * 1e6 + print(f" {label:<35} {elapsed*1e3:>10.2f} {us:>8.2f}") diff --git a/bench/ctable/iteration_column.py b/bench/ctable/iteration_column.py index b1ac3703..a5544052 100644 --- a/bench/ctable/iteration_column.py +++ b/bench/ctable/iteration_column.py @@ -11,7 +11,7 @@ # 3. ct["score"][0:N].to_array() — slice view + to_array() from dataclasses import dataclass -from time import time +from time import perf_counter as time import numpy as np @@ -68,11 +68,11 @@ class Row: # 3. slice view + to_array() t0 = time() -arr = col[0:N].to_numpy() +arr = col[0:N] for _val in arr: pass t_toarray = time() - t0 -print(f"col[0:N].to_array(): {t_toarray:.4f} s") +print(f"col[0:N] (full slice): {t_toarray:.4f} s") print("=" * 60) print(f"Speedup to_array vs iter: {t_iter / t_toarray:.2f}x") diff --git a/bench/ctable/print.py b/bench/ctable/print.py index 6efb80bf..fe7577b9 100644 --- a/bench/ctable/print.py +++ b/bench/ctable/print.py @@ -5,11 +5,14 @@ # SPDX-License-Identifier: BSD-3-Clause ####################################################################### -# Benchmark: iterative ingestion comparison — Pandas vs CTable -# Data source: randomly generated numpy structured array +# Benchmark: CTable str() / head() rendering time vs pandas. +# +# Measures how long it takes to render the first 10 rows as a table +# for both CTable (head()) and pandas (DataFrame.to_string()), +# plus ingestion time and memory footprint comparison. -import time from dataclasses import dataclass +from time import perf_counter import numpy as np import pandas as pd @@ -19,8 +22,8 @@ @dataclass class Row: - id: int = blosc2.field(blosc2.int64()) - name: str = blosc2.field(blosc2.string(max_length=9), default="") + id: int = blosc2.field(blosc2.int64()) + name: str = blosc2.field(blosc2.string(max_length=9), default="") score: float = blosc2.field(blosc2.float64(ge=0), default=0.0) @@ -41,68 +44,38 @@ def make_data(n: int) -> np.ndarray: return arr -print(f"=== BENCHMARK: Iterative Ingestion ({N:,} rows) ===\n") - -# ───────────────────────────────────────────────────────────── -# 1. PANDAS -# ───────────────────────────────────────────────────────────── -print("--- 1. PANDAS (structured array -> DataFrame) ---") data = make_data(N) -t0 = time.perf_counter() +t0 = perf_counter() df = pd.DataFrame(data) -t_pandas = time.perf_counter() - t0 - -mem_pandas = df.memory_usage(deep=True).sum() / (1024 ** 2) -print(f"Total time: {t_pandas:.4f} s") -print(f"Memory (RAM): {mem_pandas:.2f} MB") - -print("\n--- PANDAS: First 10 rows ---") -t0_print = time.perf_counter() -print(df.head(10).to_string()) -t_print_pandas = time.perf_counter() - t0_print -print(f"\nPrint time: {t_print_pandas:.6f} s") - -# ───────────────────────────────────────────────────────────── -# 2. BLOSC2 CTable -# ───────────────────────────────────────────────────────────── -print("\n" + "=" * 60) -print("--- 2. BLOSC2 CTable (structured array -> extend) ---") -data = make_data(N) +t_pandas = perf_counter() - t0 -t0 = time.perf_counter() +t0 = perf_counter() ct = blosc2.CTable(Row, expected_size=N) ct.extend(data) -t_blosc = time.perf_counter() - t0 - -fields = ct.col_names -mem_blosc_c = (sum(col.cbytes for col in ct._cols.values()) + ct._valid_rows.cbytes) / (1024 ** 2) -mem_blosc_uc = (sum(col.nbytes for col in ct._cols.values()) + ct._valid_rows.nbytes) / (1024 ** 2) - -print(f"Total time: {t_blosc:.4f} s") -print(f"Memory (uncompressed): {mem_blosc_uc:.2f} MB") -print(f"Memory (compressed): {mem_blosc_c:.2f} MB") - -print("\n--- BLOSC2: First 10 rows ---") -t0_print = time.perf_counter() -print(ct.head(10)) -t_print_blosc = time.perf_counter() - t0_print -print(f"\nPrint time: {t_print_blosc:.6f} s") - -# ───────────────────────────────────────────────────────────── -# SUMMARY -# ───────────────────────────────────────────────────────────── -print("\n" + "=" * 60) -print("--- SUMMARY ---") -speedup = t_pandas / t_blosc -direction = "faster" if t_blosc < t_pandas else "slower" - -print(f"{'METRIC':<30} {'Pandas':>12} {'Blosc2':>12}") -print("-" * 55) -print(f"{'Ingestion time (s)':<30} {t_pandas:>12.4f} {t_blosc:>12.4f}") -print(f"{'Memory (MB)':<30} {mem_pandas:>12.2f} {mem_blosc_c:>12.2f}") -print(f"{'Print time (s)':<30} {t_print_pandas:>12.6f} {t_print_blosc:>12.6f}") -print("-" * 55) -print(f"\nSpeedup: {speedup:.2f}x {direction}") -print(f"Compression ratio: {mem_blosc_uc / mem_blosc_c:.2f}x") -print(f"Blosc2 vs Pandas size: {mem_blosc_c / mem_pandas * 100:.1f}%") +t_blosc = perf_counter() - t0 + +t0 = perf_counter() +_ = df.head(10).to_string() +t_print_pandas = perf_counter() - t0 + +t0 = perf_counter() +_ = ct.head(10) +t_print_blosc = perf_counter() - t0 + +mem_pandas = df.memory_usage(deep=True).sum() / 1024**2 +mem_blosc_c = (sum(c.cbytes for c in ct._cols.values()) + ct._valid_rows.cbytes) / 1024**2 +mem_blosc_uc = (sum(c.nbytes for c in ct._cols.values()) + ct._valid_rows.nbytes) / 1024**2 + +print(f"CTable vs pandas — ingestion + print | N = {N:,}") +print() +print(f" {'METRIC':<30} {'pandas':>10} {'CTable':>10}") +print(f" {'─'*30} {'─'*10} {'─'*10}") +print(f" {'Ingestion time (s)':<30} {t_pandas:>10.4f} {t_blosc:>10.4f}") +print(f" {'Memory compressed (MB)':<30} {mem_pandas:>10.2f} {mem_blosc_c:>10.2f}") +print(f" {'Memory uncompressed (MB)':<30} {mem_pandas:>10.2f} {mem_blosc_uc:>10.2f}") +print(f" {'head(10) render time (s)':<30} {t_print_pandas:>10.6f} {t_print_blosc:>10.6f}") +print() +print(f" Ingestion speedup CTable vs pandas: {t_pandas / t_blosc:.2f}×") +print(f" Compression ratio CTable: {mem_blosc_uc / mem_blosc_c:.2f}×") +print(f" CTable compressed vs pandas RAM: {mem_blosc_c / mem_pandas * 100:.1f}%") diff --git a/bench/ctable/row_access.py b/bench/ctable/row_access.py index 050d0309..8238c70f 100644 --- a/bench/ctable/row_access.py +++ b/bench/ctable/row_access.py @@ -9,7 +9,7 @@ # testing access at different positions across the array. from dataclasses import dataclass -from time import time +from time import perf_counter as time import numpy as np diff --git a/bench/ctable/slice.py b/bench/ctable/slice.py index a41c50a6..ad1b60bf 100644 --- a/bench/ctable/slice.py +++ b/bench/ctable/slice.py @@ -9,7 +9,7 @@ # sizes and positions: small, large, and middle of the array. from dataclasses import dataclass -from time import time +from time import perf_counter as time import numpy as np diff --git a/bench/ctable/slice_steps.py b/bench/ctable/slice_steps.py index 0a3fb358..0bd219a4 100644 --- a/bench/ctable/slice_steps.py +++ b/bench/ctable/slice_steps.py @@ -8,7 +8,7 @@ # Benchmark for measuring Column[::step].to_array() with varying step sizes. from dataclasses import dataclass -from time import time +from time import perf_counter as time import numpy as np @@ -54,7 +54,7 @@ class Row: col = ct["score"] for step in steps: t0 = time() - arr = col[::step].to_numpy() + arr = col[::step] t_total = time() - t0 print(f"::{ step:<8} {len(arr):>15,} {t_total:>12.6f}") diff --git a/bench/ctable/slice_to_array.py b/bench/ctable/slice_to_array.py index 7c58080e..2b21d5d3 100644 --- a/bench/ctable/slice_to_array.py +++ b/bench/ctable/slice_to_array.py @@ -9,7 +9,7 @@ # different sizes and positions: small, large, and middle of the array. from dataclasses import dataclass -from time import time +from time import perf_counter as time import numpy as np @@ -63,7 +63,7 @@ class Row: col = ct["score"] for label, s in slices: t0 = time() - arr = col[s].to_numpy() + arr = col[s] t_total = time() - t0 n_rows = s.stop - s.start print(f"{label:<25} {n_rows:>8,} {t_total:>12.6f}") diff --git a/bench/ctable/sort_by.py b/bench/ctable/sort_by.py new file mode 100644 index 00000000..5c5d5e06 --- /dev/null +++ b/bench/ctable/sort_by.py @@ -0,0 +1,158 @@ +####################################################################### +# Copyright (c) 2019-present, Blosc Development Team +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause +####################################################################### + +# Benchmark: sort_by() performance. +# +# Sections: +# 1. Single-column sort at increasing N (random data) +# 2. Multi-column sort (1, 2, 3 keys) at fixed N +# 3. Already-sorted vs random vs reverse-sorted input +# 4. sort_by() with deletions (holes in _valid_rows) +# 5. inplace=True vs inplace=False + +from dataclasses import dataclass +from time import perf_counter + +import numpy as np + +import blosc2 + + +@dataclass +class Row: + sensor_id: int = blosc2.field(blosc2.int32()) + temperature: float = blosc2.field(blosc2.float64()) + region: int = blosc2.field(blosc2.int32()) + active: bool = blosc2.field(blosc2.bool()) + +np_dtype = np.dtype([ + ("sensor_id", np.int32), + ("temperature", np.float64), + ("region", np.int32), + ("active", np.bool_), +]) + +rng = np.random.default_rng(42) + +W = 70 + +def sep(title): + print(f"\n{'─' * W}") + print(f" {title}") + print(f"{'─' * W}") + +def make_table(n, sensor_ids=None): + data = np.empty(n, dtype=np_dtype) + data["sensor_id"] = rng.integers(0, n // 10, size=n, dtype=np.int32) if sensor_ids is None else sensor_ids + data["temperature"] = 15.0 + rng.random(n) * 25 + data["region"] = rng.integers(0, 8, size=n, dtype=np.int32) + data["active"] = rng.integers(0, 2, size=n, dtype=np.bool_) + ct = blosc2.CTable(Row, expected_size=n) + ct.extend(data) + return ct + + +# ── 1. Single-column sort at increasing N ──────────────────────────────────── + +sep("1. Single-column sort (sensor_id, random input)") +print(f" {'N':>12} {'TIME (s)':>10} {'ms/Mrow':>10}") +print(f" {'─'*12} {'─'*10} {'─'*10}") + +for n in [10_000, 100_000, 500_000, 1_000_000]: + ct = make_table(n) + t0 = perf_counter() + ct.sort_by(["sensor_id"], inplace=True) + elapsed = perf_counter() - t0 + ms_per_mrow = elapsed / n * 1e9 / 1e3 + print(f" {n:>12,} {elapsed:>10.4f} {ms_per_mrow:>10.1f}") + + +# ── 2. Multi-column sort at fixed N ────────────────────────────────────────── + +N = 1_000_000 +sep(f"2. Multi-column sort (N={N:,})") +print(f" {'KEYS':<30} {'TIME (s)':>10} {'SPEEDUP vs 1-key':>18}") +print(f" {'─'*30} {'─'*10} {'─'*18}") + +ct_base = make_table(N) +results = {} + +for keys in [ + ["sensor_id"], + ["sensor_id", "region"], + ["sensor_id", "region", "active"], +]: + ct = make_table(N) + t0 = perf_counter() + ct.sort_by(keys, inplace=True) + elapsed = perf_counter() - t0 + results[len(keys)] = elapsed + spd = results[1] / elapsed if elapsed > 0 else float("inf") + label = " + ".join(keys) + print(f" {label:<30} {elapsed:>10.4f} {spd:>17.2f}×") + + +# ── 3. Input order: random vs sorted vs reverse ─────────────────────────────── + +sep(f"3. Input order effect (N={N:,}, sort by sensor_id)") +print(f" {'INPUT ORDER':<20} {'TIME (s)':>10}") +print(f" {'─'*20} {'─'*10}") + +sid_max = N // 10 + +rand_ids = rng.integers(0, sid_max, size=N, dtype=np.int32) +sorted_ids = np.repeat(np.arange(sid_max, dtype=np.int32), N // sid_max) +reverse_ids = sorted_ids[::-1].copy() + +for label, ids in [ + ("random", rand_ids), + ("sorted", sorted_ids), + ("reversed", reverse_ids), +]: + ct = make_table(N, sensor_ids=ids) + t0 = perf_counter() + ct.sort_by(["sensor_id"], inplace=True) + elapsed = perf_counter() - t0 + print(f" {label:<20} {elapsed:>10.4f}") + + +# ── 4. Sort with deletions (holes) ──────────────────────────────────────────── + +sep(f"4. Sort with deletions (N={N:,}, sort by sensor_id)") +print(f" {'DELETED':>10} {'LIVE ROWS':>10} {'TIME (s)':>10}") +print(f" {'─'*10} {'─'*10} {'─'*10}") + +for frac in [0.0, 0.1, 0.25, 0.5]: + ct = make_table(N) + n_del = int(N * frac) + if n_del: + ct.delete(list(range(0, N, max(1, N // n_del)))[:n_del]) + live = len(ct) + t0 = perf_counter() + ct.sort_by(["sensor_id"], inplace=True) + elapsed = perf_counter() - t0 + print(f" {frac*100:>9.0f}% {live:>10,} {elapsed:>10.4f}") + + +# ── 5. inplace=True vs inplace=False ───────────────────────────────────────── + +sep(f"5. inplace=True vs inplace=False (N={N:,})") +print(f" {'MODE':<20} {'TIME (s)':>10} {'NOTE'}") +print(f" {'─'*20} {'─'*10} {'─'*20}") + +ct = make_table(N) +t0 = perf_counter() +ct.sort_by(["sensor_id"], inplace=True) +t_inplace = perf_counter() - t0 +print(f" {'inplace=True':<20} {t_inplace:>10.4f} modifies table in-place") + +ct = make_table(N) +t0 = perf_counter() +ct2 = ct.sort_by(["sensor_id"], inplace=False) +t_copy = perf_counter() - t0 +print(f" {'inplace=False':<20} {t_copy:>10.4f} returns new table") +print(f"\n Copy overhead: {t_copy / t_inplace:.2f}×") diff --git a/bench/ctable/speed_iter.py b/bench/ctable/speed_iter.py index 10afdc36..0363de72 100644 --- a/bench/ctable/speed_iter.py +++ b/bench/ctable/speed_iter.py @@ -5,36 +5,45 @@ # SPDX-License-Identifier: BSD-3-Clause ####################################################################### +# Benchmark: row iteration speed — how fast is iterating over CTable rows +# when most rows are skipped (only accessing every K-th row's value). +# +# Models a real-world "sample scan" pattern where not every row needs +# a column read, but you still iterate the whole table. + from dataclasses import dataclass -from time import time +from time import perf_counter import blosc2 -from blosc2 import CTable @dataclass class Row: - id: int = blosc2.field(blosc2.int64(ge=0)) - score: float = blosc2.field(blosc2.float64(ge=0, le=100)) - active: bool = blosc2.field(blosc2.bool(), default=True) + id: int = blosc2.field(blosc2.int64(ge=0)) + score: float = blosc2.field(blosc2.float64(ge=0, le=100)) + active: bool = blosc2.field(blosc2.bool(), default=True) -N = 1_000_000 # start small, increase when confident +N = 1_000_000 +SAMPLE_EVERY = [1, 10, 100, 1_000, 10_000] data = [(i, float(i % 100), i % 2 == 0) for i in range(N)] -tabla = CTable(Row, new_data=data) - -print(f"Table created with {len(tabla)} rows\n") - -# ------------------------------------------------------------------- -# Test 1: iterate without accessing any column (minimum cost) -# ------------------------------------------------------------------- -i=0 -t0 = time() -for row in tabla: - i=(i+1)%10000 - if i==0: - _ = row["score"] - -t1 = time() -print(f"[Test 1] Iter without accessing columns: {(t1 - t0):.3f} s") +ct = blosc2.CTable(Row, expected_size=N) +ct.extend(data) + +print(f"Row iteration sample-scan benchmark | N = {N:,}") +print() +print(f" {'SAMPLE_EVERY':>13} {'READS':>9} {'TIME (s)':>10} {'µs/read':>9}") +print(f" {'─'*13} {'─'*9} {'─'*10} {'─'*9}") + +for k in SAMPLE_EVERY: + n_reads = N // k + i = 0 + t0 = perf_counter() + for row in ct: + i = (i + 1) % k + if i == 0: + _ = row["score"] + elapsed = perf_counter() - t0 + us_per_read = elapsed / n_reads * 1e6 if n_reads > 0 else 0 + print(f" {k:>13,} {n_reads:>9,} {elapsed:>10.4f} {us_per_read:>9.2f}") diff --git a/bench/ctable/varlen.py b/bench/ctable/varlen.py new file mode 100644 index 00000000..549f8a08 --- /dev/null +++ b/bench/ctable/varlen.py @@ -0,0 +1,257 @@ +####################################################################### +# Copyright (c) 2019-present, Blosc Development Team +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause +####################################################################### + +"""Benchmark variable-length CTable columns. + +Covers: +1. append / extend performance +2. full-scan query performance +3. getitem performance for single rows and small slices + +Examples +-------- +python bench/ctable/varlen.py --rows 200000 --batch-size 1000 +python bench/ctable/varlen.py --rows 500000 --storages batch vl --repeats 5 +""" + +from __future__ import annotations + +import argparse +import gc +import statistics +import time +from dataclasses import dataclass + +import blosc2 + + +@dataclass +class RowBatch: + id: int = blosc2.field(blosc2.int64()) + group: int = blosc2.field(blosc2.int32()) + score: float = blosc2.field(blosc2.float64()) + tags: list[str] = blosc2.field( # noqa: RUF009 + blosc2.list(blosc2.string(max_length=24), nullable=True, storage="batch", batch_rows=1024) + ) + + +@dataclass +class RowVL: + id: int = blosc2.field(blosc2.int64()) + group: int = blosc2.field(blosc2.int32()) + score: float = blosc2.field(blosc2.float64()) + tags: list[str] = blosc2.field( # noqa: RUF009 + blosc2.list(blosc2.string(max_length=24), nullable=True, storage="vl") + ) + + +def make_row(i: int) -> tuple[int, int, float, list[str] | None]: + group = i % 97 + score = float((i * 13) % 1000) / 10.0 + mod = i % 11 + if mod == 0: + tags = None + elif mod == 1: + tags = [] + elif mod <= 4: + tags = [f"t{i % 1000}"] + elif mod <= 7: + tags = [f"t{i % 1000}", f"g{group}"] + else: + tags = [f"t{i % 1000}", f"g{group}", f"s{int(score)}"] + return i, group, score, tags + + +def make_rows(nrows: int) -> list[tuple[int, int, float, list[str] | None]]: + return [make_row(i) for i in range(nrows)] + + +def choose_row_type(storage: str): + if storage == "batch": + return RowBatch + if storage == "vl": + return RowVL + raise ValueError(f"Unsupported storage: {storage!r}") + + +def best_time(fn, *, repeats: int) -> float: + best = float("inf") + for _ in range(repeats): + gc.collect() + t0 = time.perf_counter() + fn() + dt = time.perf_counter() - t0 + best = min(best, dt) + return best + + +def median_time(fn, *, repeats: int) -> float: + samples = [] + for _ in range(repeats): + gc.collect() + t0 = time.perf_counter() + fn() + samples.append(time.perf_counter() - t0) + return statistics.median(samples) + + +def build_table_by_append(row_type, rows) -> blosc2.CTable: + t = blosc2.CTable(row_type, expected_size=len(rows)) + for row in rows: + t.append(row) + return t + + +def build_table_by_extend(row_type, rows, batch_size: int) -> blosc2.CTable: + t = blosc2.CTable(row_type, expected_size=len(rows)) + for start in range(0, len(rows), batch_size): + t.extend(rows[start : start + batch_size]) + return t + + +def query_count(table: blosc2.CTable) -> int: + view = table.where((table.group >= 10) & (table.group < 50) & (table.score >= 25.0)) + return len(view) + + +def query_with_list_touch(table: blosc2.CTable) -> int: + view = table.where((table.group >= 10) & (table.group < 50) & (table.score >= 25.0)) + total = 0 + for cell in view.tags: + total += 0 if cell is None else len(cell) + return total + + +def bench_getitem_single(table: blosc2.CTable, indices: list[int]) -> int: + total = 0 + col = table.tags + for idx in indices: + cell = col[idx] + total += 0 if cell is None else len(cell) + return total + + +def bench_getitem_slices(table: blosc2.CTable, starts: list[int], width: int) -> int: + total = 0 + col = table.tags + for start in starts: + cells = col[start : start + width] + for cell in cells: + total += 0 if cell is None else len(cell) + return total + + +def format_rate(n: int, seconds: float) -> str: + if seconds <= 0: + return "inf" + return f"{n / seconds:,.0f}/s" + + +def run_storage_bench(storage: str, rows, *, batch_size: int, repeats: int, nsamples: int, slice_width: int) -> None: + row_type = choose_row_type(storage) + print(f"\n=== storage={storage} ===") + + append_time = best_time(lambda: build_table_by_append(row_type, rows), repeats=repeats) + extend_time = best_time(lambda: build_table_by_extend(row_type, rows, batch_size), repeats=repeats) + + table = build_table_by_extend(row_type, rows, batch_size) + + q1 = query_count(table) + scan_count_time = median_time(lambda: query_count(table), repeats=repeats) + + q2 = query_with_list_touch(table) + scan_touch_time = median_time(lambda: query_with_list_touch(table), repeats=repeats) + + max_start = max(1, len(table) - slice_width - 1) + indices = [((i * 104729) % max_start) for i in range(nsamples)] + starts = [((i * 8191) % max_start) for i in range(nsamples)] + clustered_indices = [i % min(max_start, 4096) for i in range(nsamples)] + clustered_starts = [i % min(max_start, 2048) for i in range(nsamples)] + + single_sum = bench_getitem_single(table, indices) + single_time = median_time(lambda: bench_getitem_single(table, indices), repeats=repeats) + single_clustered_time = median_time( + lambda: bench_getitem_single(table, clustered_indices), repeats=repeats + ) + + slice_sum = bench_getitem_slices(table, starts, slice_width) + slice_time = median_time(lambda: bench_getitem_slices(table, starts, slice_width), repeats=repeats) + slice_clustered_time = median_time( + lambda: bench_getitem_slices(table, clustered_starts, slice_width), repeats=repeats + ) + + print("append/extend") + print(f" append rows: {append_time:8.4f} s {format_rate(len(rows), append_time)}") + print(f" extend rows: {extend_time:8.4f} s {format_rate(len(rows), extend_time)}") + print(f" append/extend: {append_time / extend_time:8.2f}x slower") + + print("scan queries") + print(f" count only: {scan_count_time:8.4f} s matches={q1:,}") + print(f" count+list use: {scan_touch_time:8.4f} s payload={q2:,}") + + print("getitem") + print( + f" single row random: {single_time:8.4f} s " + f"{format_rate(nsamples, single_time)} checksum={single_sum}" + ) + print( + f" single row local: {single_clustered_time:8.4f} s " + f"{format_rate(nsamples, single_clustered_time)}" + ) + print( + f" slice[{slice_width}] random: {slice_time:8.4f} s " + f"{format_rate(nsamples, slice_time)} checksum={slice_sum}" + ) + print( + f" slice[{slice_width}] local: {slice_clustered_time:8.4f} s " + f"{format_rate(nsamples, slice_clustered_time)}" + ) + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--rows", type=int, default=200_000, help="Number of rows to generate.") + parser.add_argument("--batch-size", type=int, default=1_000, help="Batch size used for extend().") + parser.add_argument("--repeats", type=int, default=5, help="Repetitions per benchmark.") + parser.add_argument( + "--storages", + nargs="+", + choices=("batch", "vl"), + default=["batch", "vl"], + help="List-column storage backends to benchmark.", + ) + parser.add_argument( + "--getitem-samples", + type=int, + default=20_000, + help="Number of random single-row / slice probes.", + ) + parser.add_argument("--slice-width", type=int, default=8, help="Width of small-slice getitem benchmark.") + return parser.parse_args() + + +def main() -> None: + args = parse_args() + rows = make_rows(args.rows) + + print("CTable variable-length column benchmark") + print(f"rows={args.rows:,} batch_size={args.batch_size:,} repeats={args.repeats}") + print(f"getitem_samples={args.getitem_samples:,} slice_width={args.slice_width}") + + for storage in args.storages: + run_storage_bench( + storage, + rows, + batch_size=args.batch_size, + repeats=args.repeats, + nsamples=args.getitem_samples, + slice_width=args.slice_width, + ) + + +if __name__ == "__main__": + main() diff --git a/bench/ctable/where_chain.py b/bench/ctable/where_chain.py index d2a6092d..15332da7 100644 --- a/bench/ctable/where_chain.py +++ b/bench/ctable/where_chain.py @@ -9,7 +9,7 @@ # Filters: 250k < id < 750k, active == False, 25.0 < score < 75.0 from dataclasses import dataclass -from time import time +from time import perf_counter as time import numpy as np @@ -51,20 +51,20 @@ class Row: # 1. Three chained where() calls t0 = time() -r1 = ct.where(ct["id"] > 250_000) -r2 = r1.where(ct["id"] < 750_000) -r3 = r2.where(ct["score"] > 25.0) -r4 = r3.where(ct["score"] < 75.0) -r5 = r4.where(not ct["active"]) +r1 = ct.where(ct.id > 250_000) +r2 = r1.where(ct.id < 750_000) +r3 = r2.where(ct.score > 25.0) +r4 = r3.where(ct.score < 75.0) +r5 = r4.where(ct.active == False) t_chained = time() - t0 print(f"Chained where() (5 calls): {t_chained:.6f} s rows: {len(r5):,}") # 2. Single combined where() call t0 = time() result = ct.where( - (ct["id"] > 250_000) & (ct["id"] < 750_000) & - (not ct["active"]) & - (ct["score"] > 25.0) & (ct["score"] < 75.0) + (ct.id > 250_000) & (ct.id < 750_000) & + (ct.active == False) & + (ct.score > 25.0) & (ct.score < 75.0) ) t_combined = time() - t0 print(f"Combined where() (1 call): {t_combined:.6f} s rows: {len(result):,}") diff --git a/bench/ctable/where_selective.py b/bench/ctable/where_selective.py index c0ba6f78..155f2d4a 100644 --- a/bench/ctable/where_selective.py +++ b/bench/ctable/where_selective.py @@ -9,7 +9,7 @@ # Filter: id < threshold, with thresholds covering 1%, 10%, 50%, 90%, 100% from dataclasses import dataclass -from time import time +from time import perf_counter as time import numpy as np @@ -54,7 +54,7 @@ class Row: for threshold in thresholds: t0 = time() - result = ct.where(ct["id"] < threshold) + result = ct.where(ct.id < threshold) t_where = time() - t0 selectivity = threshold / N * 100 print(f"id < {threshold:<10,} {len(result):>15,} {selectivity:>12.1f}% {t_where:>12.6f}") diff --git a/doc/reference/classes.rst b/doc/reference/classes.rst index 39e22b6a..933da9ef 100644 --- a/doc/reference/classes.rst +++ b/doc/reference/classes.rst @@ -15,6 +15,7 @@ Main Classes C2Array Array BatchArray + ListArray VLArray SChunk DictStore @@ -41,6 +42,7 @@ Main Classes tree_store embed_store batch_array + list_array vlarray ndfield ref diff --git a/doc/reference/ctable.rst b/doc/reference/ctable.rst index 286778c7..45571340 100644 --- a/doc/reference/ctable.rst +++ b/doc/reference/ctable.rst @@ -3,10 +3,12 @@ CTable ====== -A columnar compressed table backed by one :class:`~blosc2.NDArray` per column. -Each column is stored, compressed, and queried independently; rows are never -materialised in their entirety unless you explicitly call :meth:`~blosc2.CTable.to_arrow` -or iterate with :meth:`~blosc2.CTable.__iter__`. +A columnar compressed table backed by one physical container per column. +Scalar columns use :class:`~blosc2.NDArray`; list-valued columns use +:class:`~blosc2.ListArray`. Each column is stored, compressed, and queried +independently; rows are never materialised in their entirety unless you +explicitly call :meth:`~blosc2.CTable.to_arrow` or iterate with +:meth:`~blosc2.CTable.__iter__`. .. currentmodule:: blosc2 @@ -40,15 +42,63 @@ Construction CTable.open CTable.load CTable.from_arrow + CTable.from_parquet CTable.from_csv .. automethod:: CTable.__init__ .. automethod:: CTable.open .. automethod:: CTable.load .. automethod:: CTable.from_arrow +.. automethod:: CTable.from_parquet .. automethod:: CTable.from_csv +Null policy +----------- + +Nullable scalar CTable columns are represented with per-column sentinel values, +not native validity bitmaps. When CTable has to infer those sentinels, the +selection can be customized with :class:`NullPolicy` and scoped with +:func:`null_policy`:: + + policy = blosc2.NullPolicy( + signed_int_strategy="max", + string_value="", + column_null_values={"user_id": -1, "country": "NA"}, + ) + + with blosc2.null_policy(policy): + table = blosc2.CTable.from_parquet("data.parquet") + +The same policy is used by explicit nullable schema specs when no +``null_value`` is supplied:: + + from dataclasses import dataclass + + @dataclass + class Row: + user_id: int = blosc2.field(blosc2.int64(nullable=True)) + country: str = blosc2.field(blosc2.string(nullable=True)) + + with blosc2.null_policy(policy): + table = blosc2.CTable(Row) + +Sentinels are resolved in this order: explicit ``null_value`` in the schema, +``NullPolicy.column_null_values`` for a matching column, then the type-wide +``NullPolicy`` default. Columns without ``nullable=True`` or an explicit +``null_value`` are not nullable. + +.. autosummary:: + + NullPolicy + null_policy + get_null_policy + +.. autoclass:: NullPolicy +.. autofunction:: null_policy +.. autofunction:: get_null_policy + + Attributes ---------- @@ -86,6 +136,27 @@ Inserting data Querying -------- +Boolean expressions +~~~~~~~~~~~~~~~~~~~ + +Use bitwise operators (``&``, ``|``, ``~``) or string expressions for +row-wise boolean logic. Python's logical operators ``and``, ``or`` and +``not`` cannot be overloaded and therefore do not build lazy column +expressions. + +Use column expressions with explicit parentheses around comparisons:: + + t.where((t.amount > 100) & (t.region == "North")) + t.where(~t.returned) + +or use string expressions when that reads better:: + + t.where("amount > 100 and region == 'North'") + t.where("not returned") + +The last two forms for negating a boolean column are equivalent: ``t.where(~t.returned)`` +and ``t.where("not returned")``. + .. autosummary:: CTable.where @@ -364,6 +435,27 @@ Text & binary string bytes + list .. autoclass:: string .. autoclass:: bytes +.. autofunction:: list + +List columns +------------ + +List columns are declared with :func:`blosc2.list`, for example:: + + from dataclasses import dataclass + import blosc2 as b2 + + @dataclass + class Product: + code: str = b2.field(b2.string(max_length=8)) + tags: list[str] = b2.field(b2.list(b2.string(), nullable=True)) + +Whole-cell replacement is supported, so users should reassign modified lists:: + + row_tags = table.tags[0] + row_tags.append("extra") # local Python list only + table.tags[0] = row_tags # explicit write-back diff --git a/doc/reference/list_array.rst b/doc/reference/list_array.rst new file mode 100644 index 00000000..1b2c9f5d --- /dev/null +++ b/doc/reference/list_array.rst @@ -0,0 +1,78 @@ +.. _ListArray: + +ListArray +========= + +Overview +-------- +ListArray is a row-oriented container for variable-length list cells. +It is the natural public container for list-valued :class:`blosc2.CTable` +columns, but it is also useful on its own whenever you want typed, +row-addressable list data. + +Internally, ListArray uses one of two lower-level backends: + +- :class:`blosc2.BatchArray` for append/scan-oriented workloads +- :class:`blosc2.VLArray` for simpler row-level replacement semantics + +Quick example +------------- + +.. code-block:: python + + import blosc2 + + arr = blosc2.ListArray( + item_spec=blosc2.string(max_length=16), + nullable=True, + storage="batch", + urlpath="ingredients.b2b", + mode="w", + ) + arr.append(["salt", "sugar"]) + arr.append([]) + arr.append(None) + + print(arr[0]) + print(arr[1:]) + + reopened = blosc2.open("ingredients.b2b", mode="r") + print(type(reopened).__name__) + +.. note:: + Returned Python lists are detached values. Mutating them locally does not + write back to the container; reassign the whole cell instead. + +.. currentmodule:: blosc2 + +.. autoclass:: ListArray + + Constructors + ------------ + .. automethod:: __init__ + .. automethod:: from_arrow + + Row Interface + ------------- + .. automethod:: __getitem__ + .. automethod:: __setitem__ + .. automethod:: __len__ + .. automethod:: __iter__ + + Mutation + -------- + .. automethod:: append + .. automethod:: extend + .. automethod:: flush + .. automethod:: copy + .. automethod:: close + + Context Manager + --------------- + .. automethod:: __enter__ + .. automethod:: __exit__ + + Public Members + -------------- + .. automethod:: to_arrow + .. automethod:: to_cframe diff --git a/examples/ctable/arrow_interop.py b/examples/ctable/arrow_interop.py index 0139e0ef..d4769c19 100644 --- a/examples/ctable/arrow_interop.py +++ b/examples/ctable/arrow_interop.py @@ -40,7 +40,7 @@ class Stock: at = t.to_arrow() print(f"Arrow table: {len(at)} rows, schema={at.schema}\n") -# -- from_arrow(): schema is inferred from Arrow types --------------------- +# -- from_arrow(): import an Arrow schema and record batches --------------- at2 = pa.table( { "x": pa.array([1.0, 2.0, 3.0], type=pa.float32()), @@ -48,7 +48,7 @@ class Stock: "label": pa.array(["a", "bb", "ccc"], type=pa.string()), } ) -t2 = blosc2.CTable.from_arrow(at2) +t2 = blosc2.CTable.from_arrow(at2.schema, at2.to_batches()) print("CTable from Arrow (inferred schema):") print(t2) print(f" label dtype: {t2['label'].dtype} (max_length inferred from data)") @@ -69,7 +69,8 @@ class Stock: print(df_original) # pandas → Arrow → CTable - t_from_pd = blosc2.CTable.from_arrow(pa.Table.from_pandas(df_original, preserve_index=False)) + at_pd = pa.Table.from_pandas(df_original, preserve_index=False) + t_from_pd = blosc2.CTable.from_arrow(at_pd.schema, at_pd.to_batches()) print("\nCTable from pandas:") print(t_from_pd) diff --git a/examples/ctable/computed-columns.py b/examples/ctable/computed-columns.py index a9352512..fa3d7e46 100644 --- a/examples/ctable/computed-columns.py +++ b/examples/ctable/computed-columns.py @@ -130,16 +130,13 @@ class Trade: # 5. Filtering via a computed column # --------------------------------------------------------------------------- -# Build a filter expression from the computed column's underlying LazyExpr -lazy_mv = t.computed_columns["market_value"]["lazy"] - -big_trades = t.where(lazy_mv >= 30_000) +# Build a filter expression by referencing the computed column directly +big_trades = t.where(t.market_value >= 30_000) print(f"Trades worth ≥ $30 000 : {len(big_trades)}") print(big_trades) # Compound filter: large trades AND low fee -lazy_fee_pct = t._cols["fee_pct"] -cheap_big = t.where((lazy_mv >= 20_000) & (lazy_fee_pct <= 0.10)) +cheap_big = t.where((t.market_value >= 20_000) & (t.fee_pct <= 0.10)) print(f"\nLarge trades (≥ $20 000) with fee ≤ 0.10 % : {len(cheap_big)}") print(cheap_big) @@ -212,7 +209,7 @@ class Trade: xt.add_computed_column("market_value", lambda c: c["price"] * c["shares"]) xt.create_index(expression="price * shares", kind=blosc2.IndexKind.FULL, name="mv_expr") -expr_view = xt.where((xt._cols["price"] * xt._cols["shares"]) >= 30_000) +expr_view = xt.where((xt.price * xt.shares) >= 30_000) print("\nRows matched via direct expression index:") print(expr_view.select(["ticker", "market_value"])) diff --git a/examples/ctable/csv_interop.py b/examples/ctable/csv_interop.py index 41a76fd9..d2a766ac 100644 --- a/examples/ctable/csv_interop.py +++ b/examples/ctable/csv_interop.py @@ -59,7 +59,7 @@ class WeatherReading: print(t.head()) # -- apply a filter before exporting ---------------------------------------- -cold_days = t.where(t["temperature"] < 0) +cold_days = t.where(t.temperature < 0) print(f"\nCold days (temp < 0°C): {len(cold_days)} rows") print(cold_days.head()) diff --git a/examples/ctable/ctable_tutorial.ipynb b/examples/ctable/ctable_tutorial.ipynb index 0564beac..53bd834d 100644 --- a/examples/ctable/ctable_tutorial.ipynb +++ b/examples/ctable/ctable_tutorial.ipynb @@ -7,7 +7,7 @@ "source": [ "# CTable Tutorial\n", "\n", - "**CTable** is a columnar compressed table built on top of `blosc2.NDArray`. \n", + "**CTable** is a columnar compressed table built on top of `blosc2.NDArray`.\n", "It stores each column independently as a compressed array, giving you:\n", "\n", "- **Compression** — data lives compressed in RAM and on disk.\n", @@ -20,21 +20,19 @@ }, { "cell_type": "code", - "execution_count": 36, "id": "a4073a3e", "metadata": { - "ExecuteTime": { - "end_time": "2026-04-14T12:39:24.795843222Z", - "start_time": "2026-04-14T12:39:24.555798389Z" - }, "execution": { "iopub.execute_input": "2026-04-07T12:06:13.708034Z", "iopub.status.busy": "2026-04-07T12:06:13.707898Z", "iopub.status.idle": "2026-04-07T12:06:14.162620Z", "shell.execute_reply": "2026-04-07T12:06:14.161981Z" + }, + "ExecuteTime": { + "end_time": "2026-05-01T05:57:10.223073Z", + "start_time": "2026-05-01T05:57:09.983961Z" } }, - "outputs": [], "source": [ "from dataclasses import dataclass\n", "\n", @@ -43,7 +41,9 @@ "\n", "import blosc2\n", "from blosc2 import CTable" - ] + ], + "outputs": [], + "execution_count": 1 }, { "cell_type": "markdown", @@ -61,29 +61,19 @@ }, { "cell_type": "code", - "execution_count": 37, "id": "c97f9123", "metadata": { - "ExecuteTime": { - "end_time": "2026-04-14T12:39:25.040587457Z", - "start_time": "2026-04-14T12:39:24.896936343Z" - }, "execution": { "iopub.execute_input": "2026-04-07T12:06:14.164585Z", "iopub.status.busy": "2026-04-07T12:06:14.164404Z", "iopub.status.idle": "2026-04-07T12:06:14.168886Z", "shell.execute_reply": "2026-04-07T12:06:14.168381Z" + }, + "ExecuteTime": { + "end_time": "2026-05-01T05:57:10.376646Z", + "start_time": "2026-05-01T05:57:10.257821Z" } }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Empty table: 0 rows, columns: ['id', 'location', 'temperature', 'active']\n" - ] - } - ], "source": [ "@dataclass\n", "class Sensor:\n", @@ -96,7 +86,17 @@ "# Create an empty in-memory table\n", "t = CTable(Sensor, expected_size=50)\n", "print(f\"Empty table: {len(t)} rows, columns: {t.col_names}\")" - ] + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Empty table: 0 rows, columns: ['id', 'location', 'temperature', 'active']\n" + ] + } + ], + "execution_count": 2 }, { "cell_type": "markdown", @@ -110,42 +110,42 @@ }, { "cell_type": "code", - "execution_count": 38, "id": "fdc64a5b", "metadata": { - "ExecuteTime": { - "end_time": "2026-04-14T12:39:25.223196169Z", - "start_time": "2026-04-14T12:39:25.094103593Z" - }, "execution": { "iopub.execute_input": "2026-04-07T12:06:14.170432Z", "iopub.status.busy": "2026-04-07T12:06:14.170315Z", "iopub.status.idle": "2026-04-07T12:06:14.231985Z", "shell.execute_reply": "2026-04-07T12:06:14.231362Z" + }, + "ExecuteTime": { + "end_time": "2026-05-01T05:57:10.524391Z", + "start_time": "2026-05-01T05:57:10.384338Z" } }, + "source": [ + "t.append(Sensor(id=0, location=\"roof\", temperature=22.5, active=True))\n", + "t.append(Sensor(id=1, location=\"basement\", temperature=18.1, active=True))\n", + "t.append(Sensor(id=2, location=\"outdoor\", temperature=-3.2, active=False))\n", + "print(t)" + ], "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - " id location temperature active \n", - " int32 70)\n", + "warm_f = t.where(t.temperature_f > 70)\n", "print(\"\\nRows above 70 °F:\")\n", "print(warm_f.select([\"location\", \"temperature\", \"temperature_f\"]))" - ] + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " location temperature temperature_f \n", + " U16 (Unicode, max 16 chars) float64 float64 \n", + "───────────────────────────── ───────────────── ─────────────────\n", + " roof 22.5 72.5 \n", + " basement 18.1 64.58 \n", + " outdoor -3.2 26.24000000000… \n", + " lab-A 20.0 68.0 \n", + " lab-B 21.5 70.7 \n", + " server 35.8 96.44 \n", + " garden -1.0 30.2 \n", + "───────────────────────────── ───────────────── ─────────────────\n", + "7 rows × 3 columns\n", + "\n", + "Mean temperature in °F: 61.2 °F\n", + "\n", + "Rows above 70 °F:\n", + " location temperature temperature_f \n", + " U16 (Unicode, max 16 chars) float64 float64 \n", + "───────────────────────────── ───────────────── ─────────────────\n", + " roof 22.5 72.5 \n", + " lab-B 21.5 70.7 \n", + " server 35.8 96.44 \n", + "───────────────────────────── ───────────────── ─────────────────\n", + "3 rows × 3 columns\n" + ] + } + ], + "execution_count": 8 }, { "cell_type": "markdown", @@ -401,7 +437,7 @@ "\n", "Enough warm-up. Let's do something real.\n", "\n", - "We will simulate **one full year of daily weather readings** for **10 world cities**. \n", + "We will simulate **one full year of daily weather readings** for **10 world cities**.\n", "Each row is one day at one city: temperature, humidity, wind speed, atmospheric pressure.\n", "\n", "| City | Climate | Twist |\n", @@ -420,21 +456,19 @@ }, { "cell_type": "code", - "execution_count": 43, "id": "ce65cad9", "metadata": { - "ExecuteTime": { - "end_time": "2026-04-14T12:39:25.965548320Z", - "start_time": "2026-04-14T12:39:25.937711110Z" - }, "execution": { "iopub.execute_input": "2026-04-07T12:06:14.279795Z", "iopub.status.busy": "2026-04-07T12:06:14.279663Z", "iopub.status.idle": "2026-04-07T12:06:14.283068Z", "shell.execute_reply": "2026-04-07T12:06:14.282640Z" + }, + "ExecuteTime": { + "end_time": "2026-05-01T05:57:11.076167Z", + "start_time": "2026-05-01T05:57:11.046215Z" } }, - "outputs": [], "source": [ "@dataclass\n", "class WeatherReading:\n", @@ -444,60 +478,25 @@ " humidity: float = blosc2.field(blosc2.float32(ge=0.0, le=100.0), default=50.0)\n", " wind_speed: float = blosc2.field(blosc2.float32(ge=0.0, le=200.0), default=0.0)\n", " pressure: float = blosc2.field(blosc2.float32(ge=800.0, le=1100.0), default=1013.0)" - ] + ], + "outputs": [], + "execution_count": 9 }, { "cell_type": "code", - "execution_count": 44, "id": "0627de42", "metadata": { - "ExecuteTime": { - "end_time": "2026-04-14T12:39:26.061360828Z", - "start_time": "2026-04-14T12:39:25.968553107Z" - }, "execution": { "iopub.execute_input": "2026-04-07T12:06:14.284454Z", "iopub.status.busy": "2026-04-07T12:06:14.284339Z", "iopub.status.idle": "2026-04-07T12:06:14.322297Z", "shell.execute_reply": "2026-04-07T12:06:14.321695Z" + }, + "ExecuteTime": { + "end_time": "2026-05-01T05:57:11.156627Z", + "start_time": "2026-05-01T05:57:11.086168Z" } }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Climate table: 3,650 rows × 6 columns\n", - "Compressed: 42.1 KB (uncompressed: 295.8 KB)\n", - " city day temperature humidity wind_speed pressure \n", - " 35)\n", + "print(f\"Days above 35 °C: {len(very_hot)} ({len(very_hot) / len(climate) * 100:.1f}% of all readings)\")\n", + "print(very_hot.head(8))" + ], "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Days above 35 °C: 49 (1.3% of all readings)\n", - " city day temperature humidity wind_speed pressure \n", - " 35)\n", - "print(f\"Days above 35 °C: {len(very_hot)} ({len(very_hot) / len(climate) * 100:.1f}% of all readings)\")\n", - "print(very_hot.head(8))" - ] + "execution_count": 11 }, { "cell_type": "code", - "execution_count": 46, "id": "ba2d719b", "metadata": { - "ExecuteTime": { - "end_time": "2026-04-14T12:39:26.233532577Z", - "start_time": "2026-04-14T12:39:26.164808834Z" - }, "execution": { "iopub.execute_input": "2026-04-07T12:06:14.342416Z", "iopub.status.busy": "2026-04-07T12:06:14.342298Z", "iopub.status.idle": "2026-04-07T12:06:14.358545Z", "shell.execute_reply": "2026-04-07T12:06:14.357991Z" + }, + "ExecuteTime": { + "end_time": "2026-05-01T05:57:11.314268Z", + "start_time": "2026-05-01T05:57:11.262185Z" } }, + "source": [ + "# Moscow in winter (below freezing)\n", + "moscow_frozen = climate.where((climate.city == \"Moscow\") & (climate.temperature < 0))\n", + "print(f\"Moscow below freezing: {len(moscow_frozen)} days out of 365\")\n", + "print(moscow_frozen.head())" + ], "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Moscow below freezing: 148 days out of 365\n", - " city day temperature humidity wind_speed pressure \n", - " 10} {'Min':>7} {'Max':>7} {'Std':>7}\")\n", + "print(\"-\" * 50)\n", + "for city in CITY_PROFILES:\n", + " v = climate.where(climate.city == city)\n", + " col = v[\"temperature\"]\n", + " print(f\"{city:<12} {col.mean():>9.1f}° {col.min():>6.1f}° {col.max():>6.1f}° {col.std():>6.1f}°\")" + ], "outputs": [ { "name": "stdout", @@ -927,14 +970,7 @@ ] } ], - "source": [ - "print(f\"{'City':<12} {'Mean temp':>10} {'Min':>7} {'Max':>7} {'Std':>7}\")\n", - "print(\"-\" * 50)\n", - "for city in CITY_PROFILES:\n", - " v = climate.where(climate[\"city\"] == city)\n", - " col = v[\"temperature\"]\n", - " print(f\"{city:<12} {col.mean():>9.1f}° {col.min():>6.1f}° {col.max():>6.1f}° {col.std():>6.1f}°\")" - ] + "execution_count": 16 }, { "cell_type": "markdown", @@ -946,20 +982,23 @@ }, { "cell_type": "code", - "execution_count": 51, "id": "7254f3b1", "metadata": { - "ExecuteTime": { - "end_time": "2026-04-14T12:39:26.710706134Z", - "start_time": "2026-04-14T12:39:26.628790601Z" - }, "execution": { "iopub.execute_input": "2026-04-07T12:06:14.520839Z", "iopub.status.busy": "2026-04-07T12:06:14.520722Z", "iopub.status.idle": "2026-04-07T12:06:14.542317Z", "shell.execute_reply": "2026-04-07T12:06:14.541649Z" + }, + "ExecuteTime": { + "end_time": "2026-05-01T05:57:11.921424Z", + "start_time": "2026-05-01T05:57:11.814144Z" } }, + "source": [ + "# describe() on a select() view — only numeric columns\n", + "climate.select([\"temperature\", \"humidity\", \"wind_speed\", \"pressure\"]).describe()" + ], "outputs": [ { "name": "stdout", @@ -998,10 +1037,7 @@ ] } ], - "source": [ - "# describe() on a select() view — only numeric columns\n", - "climate.select([\"temperature\", \"humidity\", \"wind_speed\", \"pressure\"]).describe()" - ] + "execution_count": 17 }, { "cell_type": "markdown", @@ -1010,26 +1046,43 @@ "source": [ "### 4.3 Covariance matrix\n", "\n", - "`cov()` requires all columns to be numeric (int, float, or bool). \n", + "`cov()` requires all columns to be numeric (int, float, or bool).\n", "It returns a standard `numpy.ndarray`." ] }, { "cell_type": "code", - "execution_count": 52, "id": "6d0dd2c1", "metadata": { - "ExecuteTime": { - "end_time": "2026-04-14T12:39:26.797016145Z", - "start_time": "2026-04-14T12:39:26.714612755Z" - }, "execution": { "iopub.execute_input": "2026-04-07T12:06:14.543869Z", "iopub.status.busy": "2026-04-07T12:06:14.543748Z", "iopub.status.idle": "2026-04-07T12:06:14.559277Z", "shell.execute_reply": "2026-04-07T12:06:14.558718Z" + }, + "ExecuteTime": { + "end_time": "2026-05-01T05:57:12.092759Z", + "start_time": "2026-05-01T05:57:11.933495Z" } }, + "source": [ + "numeric = climate.select([\"temperature\", \"humidity\", \"wind_speed\", \"pressure\"])\n", + "cov = numeric.cov()\n", + "\n", + "labels = [\"temp\", \"humidity\", \"wind\", \"pressure\"]\n", + "col_w = 12\n", + "print(\"Covariance matrix (all cities, full year):\")\n", + "print(\" \" * 10 + \"\".join(f\"{lbl:>{col_w}}\" for lbl in labels))\n", + "for i, lbl in enumerate(labels):\n", + " print(f\"{lbl:<10}\" + \"\".join(f\"{cov[i, j]:>{col_w}.3f}\" for j in range(4)))\n", + "\n", + "# And the correlation matrix for easier interpretation\n", + "corr = np.corrcoef(np.stack([numeric[c][:] for c in [\"temperature\", \"humidity\", \"wind_speed\", \"pressure\"]]))\n", + "print(\"\\nCorrelation matrix:\")\n", + "print(\" \" * 10 + \"\".join(f\"{lbl:>{col_w}}\" for lbl in labels))\n", + "for i, lbl in enumerate(labels):\n", + " print(f\"{lbl:<10}\" + \"\".join(f\"{corr[i, j]:>{col_w}.3f}\" for j in range(4)))" + ], "outputs": [ { "name": "stdout", @@ -1051,24 +1104,7 @@ ] } ], - "source": [ - "numeric = climate.select([\"temperature\", \"humidity\", \"wind_speed\", \"pressure\"])\n", - "cov = numeric.cov()\n", - "\n", - "labels = [\"temp\", \"humidity\", \"wind\", \"pressure\"]\n", - "col_w = 12\n", - "print(\"Covariance matrix (all cities, full year):\")\n", - "print(\" \" * 10 + \"\".join(f\"{lbl:>{col_w}}\" for lbl in labels))\n", - "for i, lbl in enumerate(labels):\n", - " print(f\"{lbl:<10}\" + \"\".join(f\"{cov[i, j]:>{col_w}.3f}\" for j in range(4)))\n", - "\n", - "# And the correlation matrix for easier interpretation\n", - "corr = np.corrcoef(np.stack([numeric[c][:] for c in [\"temperature\", \"humidity\", \"wind_speed\", \"pressure\"]]))\n", - "print(\"\\nCorrelation matrix:\")\n", - "print(\" \" * 10 + \"\".join(f\"{lbl:>{col_w}}\" for lbl in labels))\n", - "for i, lbl in enumerate(labels):\n", - " print(f\"{lbl:<10}\" + \"\".join(f\"{corr[i, j]:>{col_w}.3f}\" for j in range(4)))" - ] + "execution_count": 18 }, { "cell_type": "markdown", @@ -1078,7 +1114,7 @@ "---\n", "## Part 5 — Analysis: Summer in Madrid\n", "\n", - "Summer in the northern hemisphere runs roughly from the **summer solstice (day 172, June 21)** \n", + "Summer in the northern hemisphere runs roughly from the **summer solstice (day 172, June 21)**\n", "to the **autumnal equinox (day 264, September 22)**.\n", "\n", "Let's zoom in on Madrid during those months and compare it with a few other cities." @@ -1086,20 +1122,32 @@ }, { "cell_type": "code", - "execution_count": 53, "id": "89e89177", "metadata": { - "ExecuteTime": { - "end_time": "2026-04-14T12:39:26.878728686Z", - "start_time": "2026-04-14T12:39:26.800640315Z" - }, "execution": { "iopub.execute_input": "2026-04-07T12:06:14.560797Z", "iopub.status.busy": "2026-04-07T12:06:14.560666Z", "iopub.status.idle": "2026-04-07T12:06:14.576880Z", "shell.execute_reply": "2026-04-07T12:06:14.576245Z" + }, + "ExecuteTime": { + "end_time": "2026-05-01T05:57:12.158697Z", + "start_time": "2026-05-01T05:57:12.106140Z" } }, + "source": [ + "SUMMER_START = 172 # June 21\n", + "SUMMER_END = 264 # September 22\n", + "\n", + "madrid = climate.where(climate.city == \"Madrid\")\n", + "madrid_summer = madrid.where((madrid.day >= SUMMER_START) & (madrid.day <= SUMMER_END))\n", + "\n", + "print(f\"Madrid summer readings : {len(madrid_summer)} days\")\n", + "print(f\" mean temperature : {madrid_summer.temperature.mean():.1f} °C\")\n", + "print(f\" max temperature : {madrid_summer.temperature.max():.1f} °C\")\n", + "print(f\" mean humidity : {madrid_summer['humidity'].mean():.1f} %\")\n", + "print(f\" mean wind speed : {madrid_summer['wind_speed'].mean():.1f} km/h\")" + ], "outputs": [ { "name": "stdout", @@ -1113,52 +1161,23 @@ ] } ], - "source": [ - "SUMMER_START = 172 # June 21\n", - "SUMMER_END = 264 # September 22\n", - "\n", - "madrid = climate.where(climate[\"city\"] == \"Madrid\")\n", - "madrid_summer = madrid.where((madrid[\"day\"] >= SUMMER_START) & (madrid[\"day\"] <= SUMMER_END))\n", - "\n", - "print(f\"Madrid summer readings : {len(madrid_summer)} days\")\n", - "print(f\" mean temperature : {madrid_summer['temperature'].mean():.1f} °C\")\n", - "print(f\" max temperature : {madrid_summer['temperature'].max():.1f} °C\")\n", - "print(f\" mean humidity : {madrid_summer['humidity'].mean():.1f} %\")\n", - "print(f\" mean wind speed : {madrid_summer['wind_speed'].mean():.1f} km/h\")" - ] + "execution_count": 19 }, { "cell_type": "code", - "execution_count": 54, "id": "a439fecd", "metadata": { - "ExecuteTime": { - "end_time": "2026-04-14T12:39:27.020397605Z", - "start_time": "2026-04-14T12:39:26.880962534Z" - }, "execution": { "iopub.execute_input": "2026-04-07T12:06:14.578621Z", "iopub.status.busy": "2026-04-07T12:06:14.578475Z", "iopub.status.idle": "2026-04-07T12:06:14.693971Z", "shell.execute_reply": "2026-04-07T12:06:14.693318Z" + }, + "ExecuteTime": { + "end_time": "2026-05-01T05:57:12.293317Z", + "start_time": "2026-05-01T05:57:12.171562Z" } }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "City Summer mean Summer max Summer humidity\n", - "----------------------------------------------------------\n", - "Madrid 25.8°C 31.4°C 43.8% \n", - "London 16.5°C 22.7°C 74.6% \n", - "Cairo 33.5°C 39.7°C 34.4% \n", - "Moscow 20.1°C 26.3°C 69.3% \n", - "Tokyo 25.1°C 31.0°C 73.0% \n", - "Sydney 24.6°C 30.9°C 63.8% (S. summer)\n" - ] - } - ], "source": [ "# Compare summer stats across several cities\n", "compare_cities = [\"Madrid\", \"London\", \"Cairo\", \"Moscow\", \"Tokyo\", \"Sydney\"]\n", @@ -1166,68 +1185,85 @@ "print(f\"{'City':<12} {'Summer mean':>12} {'Summer max':>11} {'Summer humidity':>16}\")\n", "print(\"-\" * 58)\n", "for city in compare_cities:\n", - " v = climate.where(climate[\"city\"] == city)\n", + " v = climate.where(climate.city == city)\n", " # For Sydney (S. hemisphere) 'summer' is Jan-Mar, i.e. days 1-80 or 355-365\n", " if city == \"Sydney\":\n", - " s = v.where((v[\"day\"] <= 80) | (v[\"day\"] >= 355))\n", + " s = v.where((v.day <= 80) | (v.day >= 355))\n", " label = \"(S. summer)\"\n", " else:\n", - " s = v.where((v[\"day\"] >= SUMMER_START) & (v[\"day\"] <= SUMMER_END))\n", + " s = v.where((v.day >= SUMMER_START) & (v.day <= SUMMER_END))\n", " label = \"\"\n", " mean_t = s[\"temperature\"].mean()\n", " max_t = s[\"temperature\"].max()\n", " mean_h = s[\"humidity\"].mean()\n", " print(f\"{city:<12} {mean_t:>10.1f}°C {max_t:>9.1f}°C {mean_h:>14.1f}% {label}\")" - ] + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "City Summer mean Summer max Summer humidity\n", + "----------------------------------------------------------\n", + "Madrid 25.8°C 31.4°C 43.8% \n", + "London 16.5°C 22.7°C 74.6% \n", + "Cairo 33.5°C 39.7°C 34.4% \n", + "Moscow 20.1°C 26.3°C 69.3% \n", + "Tokyo 25.1°C 31.0°C 73.0% \n", + "Sydney 24.6°C 30.9°C 63.8% (S. summer)\n" + ] + } + ], + "execution_count": 20 }, { "cell_type": "code", - "execution_count": 55, "id": "4e2161ee", "metadata": { - "ExecuteTime": { - "end_time": "2026-04-14T12:39:27.105416029Z", - "start_time": "2026-04-14T12:39:27.022601226Z" - }, "execution": { "iopub.execute_input": "2026-04-07T12:06:14.696603Z", "iopub.status.busy": "2026-04-07T12:06:14.695965Z", "iopub.status.idle": "2026-04-07T12:06:14.752771Z", "shell.execute_reply": "2026-04-07T12:06:14.751433Z" + }, + "ExecuteTime": { + "end_time": "2026-05-01T05:57:12.413861Z", + "start_time": "2026-05-01T05:57:12.294601Z" } }, + "source": [ + "# Top 10 hottest days in Madrid across the whole year\n", + "# Sort the full table, then filter — views cannot be sorted directly\n", + "hottest_all = climate.sort_by(\"temperature\", ascending=False)\n", + "madrid_sorted = hottest_all.where(hottest_all.city == \"Madrid\")\n", + "print(\"10 hottest days in Madrid:\")\n", + "print(madrid_sorted.select([\"city\", \"day\", \"temperature\", \"humidity\"]).head(10))" + ], "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "10 hottest days in Madrid:\n", - " city day temperature humidity \n", - " " - ] - }, - "jetTransient": { - "display_id": null - }, - "metadata": {}, - "output_type": "display_data" - } - ], "source": [ "plot_cities = {\n", " \"Madrid\": \"#e63946\",\n", @@ -1282,8 +1302,8 @@ "fig, ax = plt.subplots(figsize=(12, 5))\n", "\n", "for city, color in plot_cities.items():\n", - " v = climate.where(climate[\"city\"] == city)\n", - " d = v[\"day\"][:].astype(int)\n", + " v = climate.where(climate.city == city)\n", + " d = v.day[:].astype(int)\n", " t = v[\"temperature\"][:]\n", " order = np.argsort(d)\n", " ax.plot(d[order], t[order], label=city, color=color, linewidth=1.5, alpha=0.85)\n", @@ -1296,7 +1316,23 @@ "ax.grid(True, linestyle=\"--\", alpha=0.4)\n", "plt.tight_layout()\n", "plt.show()" - ] + ], + "outputs": [ + { + "data": { + "text/plain": [ + "
" + ], + "image/png": "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" + }, + "metadata": {}, + "output_type": "display_data", + "jetTransient": { + "display_id": null + } + } + ], + "execution_count": 22 }, { "cell_type": "markdown", @@ -1310,42 +1346,26 @@ }, { "cell_type": "code", - "execution_count": 57, "id": "94e141a4", "metadata": { - "ExecuteTime": { - "end_time": "2026-04-14T12:39:27.585476061Z", - "start_time": "2026-04-14T12:39:27.354159759Z" - }, "execution": { "iopub.execute_input": "2026-04-07T12:06:15.038502Z", "iopub.status.busy": "2026-04-07T12:06:15.038229Z", "iopub.status.idle": "2026-04-07T12:06:15.564804Z", "shell.execute_reply": "2026-04-07T12:06:15.563854Z" + }, + "ExecuteTime": { + "end_time": "2026-05-01T05:57:13.329232Z", + "start_time": "2026-05-01T05:57:13.134933Z" } }, - "outputs": [ - { - "data": { - "image/png": "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Ki4GoLqmhmeefoqjjjqS339f5Hx+QIA/8+fN56GHJwLwzvTpTJ/+NrfccjNvvvkWlVVVO11TakoKF19yGT///LPLe7vgwpEuX6zefmc6M2e8xw03XO/s5H373Xfcc8/dVFZWurXNzvJUxx133E5yUhKnDx1Gbm4e4MhUycYSbr75Jl599TWKizd4XF5kZCS33HwTP/z4I1deebVz+r333sMdt9+2zVoGDhxAUlIS199wI1999bVz+pY/KHzyyac89eQT5K9f3+l7X7MmnTG3bvu1NktLG8zQYWfy999/A/C//33OL7/8zN13jee662/YzrNd7Uhtm0VGRnLrrWOYP/9nLr3scuePCllZ2UyZMokLzj+fD2fPds7fp08st91+Bx9/7Ngz+f77H/DnH79z8cUXGf5l+auvvmbypMcZNmwon3zyKSeccDyRkZF8+tn/uGj0KLf5J095wiXjM2fOIi8vjwkT7iU+Ls75eXzggfsoKyvn3PPOp66uDoBFvy/igw9mUVDgvle2T58+DDnmOCorK53TPI0KubPL3dJ3331Pc3Mz55w9wqWTd/aI4dhsNr744gsAjj/+OPz8/LjsssuprKra5jJ31n77pTF61EhmzpzF3fc49gxOn/4uFeUV3HzzTQwZcjS//fa7c/7+/ftz7nkX8OeffwLw+RdfsPivP7ho9Cgee9zRKR0z5haioqI486wRLF++HIDZc+bw68JfXF67J+VWxCx0uKZID/DLggWMOPtcvv/+B/bbbz/GjLmF99+fydIlf3H6aaft1rJnzfrAedtms7FixUqsVivvv//P9NraWrKzs0nq29ft+R999JFLR3PpsmVYrVY++PBDl/mWLltOXFwcXl5eAJx55hlYrVa++OJLIiMinH9lpaXk5uZyzJCjXZ7f3NzMhx/OZncMH34WNTU1/PzLLy6v+ffKldTX1zNkq9fMyMhwdvA2vzeAhb/+6vxCCbBs0/Skvklur/n2O++43n97On5+fhx33HG7VFN+fr5bBw9w+fIbEhJCZEQEvy9aRHJykvOQxa7kqY7hw8/ijz/+pKa6xuW9LFi4EG9vb7fDGbd0/HGOL7dvvfWOy/TXX39ju7XU1jq+cJ94wgkEeDh0b0e9+96MHZ538eLFzg4eQFFxMd9//z0nnngCVg97gbrK5nZ6/Y03XPYaz5w1i9raWk459WSX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9m3KppiRcilmpFyKGSmXPduO/N+aupMH8Nrrr/P8tKmsWLmSZcuWc/311xIYEMAHH842ujQRERERERHTMX0n7/PPv6BXZCR33zWe6OhoVq9ew6WXXU55ebnRpYmIiIiIiJiO6Tt5AG+/M52335ludBldprW1leeem+p2WKmI0ZRNMSPlUsxIuRQzUi5lM0ufuITOx90WERERERGRbsXUF0MXERERERGRnaNOnoiIiIiISA+iTp6IiIiIiEgPok6eAa668kr+WPQbOdmZfPnF5xx88MFGlyT7kCOPPJLp77zF0iWLKS4qYNjQoW7z3H3XeJYtXUx2ViYffjCLlJTkvV+o7FNuvXUMX3/1Jesy0lm5YhlvvfkG/fqluszj5+fHlMmTWLVqJZnr1vL6a68SFRVlUMWyL7jiisv58YfvyVi7hoy1a/j888846aQTnY8rk2IGt465heKiAh59dKJzmrIp6uTtZWefPYKJEx9i6tTnGTrsTNasWcOsme/Rq1cvo0uTfURgYACr16Rz/wMPenx8zC03c801VzNhwv0MHzGCxsYmZs2cgZ+f316uVPYlRx91FO9Mn87wEedw0cWX4O3jzfuzZhIQEOCc55FHJnLaaady4403cf4FI4mJjeHNN14zsGrp6TZs2MCUJ55g2BlncsaZZ/Hrr7/x9ltvMnDgQECZFOP961//4rLLLmX1mjUu05VN0eiae9mXX3zOihUreODBhwCwWCws/utP3n77bV586WWDq5N9TXFRAddccx3ffvedc9qypYt59dXXeeXVVwEICQlhxfKljB07nv99/rlRpco+JjIyklV/r+C88y/kjz/+ICQkhL9XLmfMrbfx1VdfA9C/Xz9++WU+w0eczdKlywyuWPYVq1f9zaRJk/jyq6+VSTFUYGAg3333Dfff/wB33H47q9esZuLER7W+FEB78vYqHx8fDjroQBYsWOicZrfbWbBwAYcddpiBlYk49O3bl5iYGBYsXOCcVldXx7JlyznssEMNrEz2NaGhoQBUV1cDcNBBB+Lr6+uy/szKzqawsFDrT9krrFYr55x9NoGBASxeslSZFMNNmTKJuXN/cskgaH0pDt3iYug9RWRkJN7e3pSVl7lMLy8rp3+//gZVJfKP3r2jASgrK3eZXlZeRu/evY0o6f/bu/eoKMs8DuBfLiMwIJoynMRhQVNywbRs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", - "text/plain": [ - "
" - ] - }, - "jetTransient": { - "display_id": null - }, - "metadata": {}, - "output_type": "display_data" - } - ], "source": [ "madrid_s = climate.where(\n", - " (climate[\"city\"] == \"Madrid\") & (climate[\"day\"] >= SUMMER_START) & (climate[\"day\"] <= SUMMER_END)\n", + " (climate.city == \"Madrid\") & (climate.day >= SUMMER_START) & (climate.day <= SUMMER_END)\n", ")[\"temperature\"][:]\n", "\n", "london_s = climate.where(\n", - " (climate[\"city\"] == \"London\") & (climate[\"day\"] >= SUMMER_START) & (climate[\"day\"] <= SUMMER_END)\n", + " (climate.city == \"London\") & (climate.day >= SUMMER_START) & (climate.day <= SUMMER_END)\n", ")[\"temperature\"][:]\n", "\n", "fig, ax = plt.subplots(figsize=(9, 4))\n", @@ -1373,7 +1393,23 @@ "ax.grid(True, linestyle=\"--\", alpha=0.4)\n", "plt.tight_layout()\n", "plt.show()" - ] + ], + "outputs": [ + { + "data": { + "text/plain": [ + "
" + ], + "image/png": "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" + }, + "metadata": {}, + "output_type": "display_data", + "jetTransient": { + "display_id": null + } + } + ], + "execution_count": 23 }, { "cell_type": "markdown", @@ -1385,43 +1421,27 @@ }, { "cell_type": "code", - "execution_count": 58, "id": "334a833a", "metadata": { - "ExecuteTime": { - "end_time": "2026-04-14T12:39:27.853545427Z", - "start_time": "2026-04-14T12:39:27.605095719Z" - }, "execution": { "iopub.execute_input": "2026-04-07T12:06:15.573465Z", "iopub.status.busy": "2026-04-07T12:06:15.573114Z", "iopub.status.idle": "2026-04-07T12:06:16.558429Z", "shell.execute_reply": "2026-04-07T12:06:16.555709Z" + }, + "ExecuteTime": { + "end_time": "2026-05-01T05:57:13.471867Z", + "start_time": "2026-05-01T05:57:13.330446Z" } }, - "outputs": [ - { - "data": { - "image/png": "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", - "text/plain": [ - "
" - ] - }, - "jetTransient": { - "display_id": null - }, - "metadata": {}, - "output_type": "display_data" - } - ], "source": [ "city_summer_means = {}\n", "for city in CITY_PROFILES:\n", - " v = climate.where(climate[\"city\"] == city)\n", + " v = climate.where(climate.city == city)\n", " if city == \"Sydney\" or city == \"Sao Paulo\":\n", - " s = v.where((v[\"day\"] <= 80) | (v[\"day\"] >= 355))\n", + " s = v.where((v.day <= 80) | (v.day >= 355))\n", " else:\n", - " s = v.where((v[\"day\"] >= SUMMER_START) & (v[\"day\"] <= SUMMER_END))\n", + " s = v.where((v.day >= SUMMER_START) & (v.day <= SUMMER_END))\n", " city_summer_means[city] = s[\"temperature\"].mean()\n", "\n", "sorted_cities = sorted(city_summer_means.items(), key=lambda x: x[1], reverse=True)\n", @@ -1438,7 +1458,23 @@ "ax.grid(True, axis=\"x\", linestyle=\"--\", alpha=0.4)\n", "plt.tight_layout()\n", "plt.show()" - ] + ], + "outputs": [ + { + "data": { + "text/plain": [ + "
" + ], + "image/png": "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" + }, + "metadata": {}, + "output_type": "display_data", + "jetTransient": { + "display_id": null + } + } + ], + "execution_count": 24 }, { "cell_type": "markdown", @@ -1453,46 +1489,24 @@ }, { "cell_type": "code", - "execution_count": 59, "id": "2c3a977b", "metadata": { - "ExecuteTime": { - "end_time": "2026-04-14T12:39:27.972265822Z", - "start_time": "2026-04-14T12:39:27.857019185Z" - }, "execution": { "iopub.execute_input": "2026-04-07T12:06:16.566822Z", "iopub.status.busy": "2026-04-07T12:06:16.563241Z", "iopub.status.idle": "2026-04-07T12:06:16.715871Z", "shell.execute_reply": "2026-04-07T12:06:16.712493Z" + }, + "ExecuteTime": { + "end_time": "2026-05-01T05:57:13.526988Z", + "start_time": "2026-05-01T05:57:13.473475Z" } }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Table with feels_like column:\n", - " city day temperature humidity wind_speed pressure feels_like \n", - " 100.0", lambda ct: ct.where(ct.value > 100.0)), + ("value > 120.0", lambda ct: ct.where(ct.value > 120.0)), + ("value between 0 and 10", lambda ct: ct.where((ct.value >= 0.0) & (ct.value <= 10.0))), + ("timestamp > 450_000", lambda ct: ct.where(ct.timestamp > 4_500_000)), + ("timestamp > 4_999_000", lambda ct: ct.where(ct.timestamp > 4_999_000)), +] + + +def bench(fn, reps=5): + times = [0.0] * reps + for i in range(reps): + t = time.perf_counter() + result = fn() + times[i] = (time.perf_counter() - t) * 1000 + return min(times), result + + +print("\n--- Full scan (no index) ---") +baseline = {} +ct = blosc2.CTable.open(B2Z, mode="r") +assert not ct.indexes, "expected no indexes yet" +for label, fn in QUERIES: + ms, view = bench(lambda fn=fn: fn(ct)) + baseline[label] = (ms, len(view)) + print(f" {label:<38} {ms:7.1f} ms {len(view):>8,} rows") +ct.close() + +# --------------------------------------------------------------------------- +# 4. Build indexes (append mode → rezip on close) +# --------------------------------------------------------------------------- + +print("\nBuilding indexes (mode='a') ...") +ct = blosc2.CTable.open(B2Z, mode="a") + +t0 = time.perf_counter() +ct.create_index("value", kind=blosc2.IndexKind.FULL) +print(f" value FULL index {(time.perf_counter() - t0) * 1000:.0f} ms") + +t0 = time.perf_counter() +ct.create_index("timestamp", kind=blosc2.IndexKind.FULL) +print(f" timestamp FULL index {(time.perf_counter() - t0) * 1000:.0f} ms") + +t0 = time.perf_counter() +ct.close() +print( + f" closed + rezipped {(time.perf_counter() - t0) * 1000:.0f} ms " + f"({os.path.getsize(B2Z) / 1e6:.1f} MB)" +) + +# --------------------------------------------------------------------------- +# 5. Read-only: verify indexes survived, benchmark +# --------------------------------------------------------------------------- + +print("\nReopening .b2z read-only ...") +ct = blosc2.CTable.open(B2Z, mode="r") +found = [idx.col_name for idx in ct.indexes] +print(f" indexes present: {found}") +assert "value" in found, "index for 'value' missing after round-trip!" +assert "timestamp" in found, "index for 'timestamp' missing after round-trip!" + +print() +print(f"{'query':<38} {'no index':>9} {'indexed':>9} {'speedup':>8} {'rows':>8}") +print("-" * 78) + +for label, fn in QUERIES: + i_ms, view = bench(lambda fn=fn: fn(ct)) + b_ms, b_n = baseline[label] + sp = b_ms / i_ms if i_ms > 0 else float("inf") + assert len(view) == b_n, f"row count mismatch for {label!r}" + print(f" {label:<38} {b_ms:8.1f}ms {i_ms:8.1f}ms {sp:7.1f}x {len(view):>8,}") + +ct.close() + +# --------------------------------------------------------------------------- +# 6. Cleanup +# --------------------------------------------------------------------------- + +os.remove(B2Z) +print("\nDone.") diff --git a/examples/ctable/indexing.py b/examples/ctable/indexing.py index 961371de..f6737840 100644 --- a/examples/ctable/indexing.py +++ b/examples/ctable/indexing.py @@ -54,7 +54,7 @@ def load_rows(table: blosc2.CTable, nrows: int = 240) -> None: print("active stale?", idx_active.stale) # Queries can combine indexed and non-indexed predicates. - recent_active = pt.where((pt["sensor_id"] >= 180) & pt["active"] & (pt["region"] == "north")) + recent_active = pt.where((pt.sensor_id >= 180) & pt.active & (pt.region == "north")) print("\nLive rows with sensor_id >= 180, active=True, region='north':", len(recent_active)) print("sensor_ids:", recent_active["sensor_id"]) print("statuses:", recent_active["status"][:]) @@ -79,7 +79,7 @@ def load_rows(table: blosc2.CTable, nrows: int = 240) -> None: print("Indexes after reopen from .b2z:", packed.indexes) # Query directly against the .b2z bundle; no unpack step is needed. - warm_active = packed.where(packed["active"] & (packed["status"] == "warm") & (packed["sensor_id"] > 100)) + warm_active = packed.where(packed.active & (packed.status == "warm") & (packed.sensor_id > 100)) print("\nRows from .b2z with active=True, status='warm', sensor_id > 100:", len(warm_active)) print("sensor_ids:", warm_active["sensor_id"]) print("regions:", warm_active["region"][:]) diff --git a/examples/ctable/nullable.py b/examples/ctable/nullable.py index 8c9974f9..79503108 100644 --- a/examples/ctable/nullable.py +++ b/examples/ctable/nullable.py @@ -5,17 +5,18 @@ # SPDX-License-Identifier: BSD-3-Clause ####################################################################### -# Nullable columns: null_value sentinels, null-aware aggregates, -# is_null / notnull, sort nulls-last, Arrow null masking, CSV empty cells. +# Nullable columns: null_value sentinels, nullable=True, NullPolicy, +# null-aware aggregates, is_null / notnull, sort nulls-last, Arrow null masking, +# and CSV empty cells. # # CTable does not have a built-in "missing" bit per row like pandas does. -# Instead it uses a *sentinel value* approach: you choose a specific value -# that represents "null" for a column, and the library treats it -# transparently in aggregates, sorting, unique(), value_counts(), and -# Arrow export. +# Instead it uses a *sentinel value* approach: each nullable column stores a +# specific value that represents "null". The library treats that value +# transparently in aggregates, sorting, unique(), value_counts(), and Arrow +# export. # -# This is especially useful for integer and string columns that have no -# natural null (unlike float, which can use NaN). +# You can either choose sentinels explicitly with null_value=, or ask CTable to +# choose them from the active NullPolicy with nullable=True. import os import tempfile @@ -24,24 +25,57 @@ import blosc2 # --------------------------------------------------------------------------- -# Schema with nullable columns +# Schema with explicit null_value sentinels # --------------------------------------------------------------------------- -# Use null_value= on any spec to declare the sentinel. -# The sentinel bypasses validation constraints (ge/le etc.) so you can -# store it even when it would otherwise violate them. +# Use null_value= on any spec to declare the sentinel. The sentinel bypasses +# validation constraints (ge/le etc.) so you can store it even when it would +# otherwise violate them. @dataclass class Reading: sensor_id: int = blosc2.field(blosc2.int32(ge=0)) # -999 is "no reading" for temperature (normally ge=-50, le=60) - temperature: float = blosc2.field(blosc2.float64(ge=-50.0, le=60.0, null_value=-999.0), default=-999.0) + temperature: float = blosc2.field(blosc2.float64(ge=-50.0, le=60.0, null_value=-999.0)) # "" is "unknown" for location (string) - location: str = blosc2.field(blosc2.string(max_length=16, null_value=""), default="") + location: str = blosc2.field(blosc2.string(max_length=16, null_value="")) # -1 is "not measured" for signal strength (normally ge=0, le=100) - signal: int = blosc2.field(blosc2.int8(ge=0, le=100, null_value=-1), default=-1) + signal: int = blosc2.field(blosc2.int8(ge=0, le=100, null_value=-1)) +# --------------------------------------------------------------------------- +# Schema using nullable=True and NullPolicy +# --------------------------------------------------------------------------- +# nullable=True means "make this column nullable and choose the sentinel from +# the current NullPolicy". column_null_values overrides the type-wide policy for +# specific columns. + + +@dataclass +class AutoReading: + sensor_id: int = blosc2.field(blosc2.int32(ge=0)) + temperature: float = blosc2.field(blosc2.float64(ge=-50.0, le=60.0, nullable=True)) + location: str = blosc2.field(blosc2.string(max_length=16, nullable=True)) + signal: int = blosc2.field(blosc2.int8(ge=0, le=100, nullable=True)) + + +policy = blosc2.NullPolicy( + float_value=-999.0, + string_value="", + column_null_values={"signal": -1}, +) +with blosc2.null_policy(policy): + auto = blosc2.CTable(AutoReading) + +print("NullPolicy + nullable=True selected these sentinels:") +print(f"temperature: {auto['temperature'].null_value!r}") +print(f"location : {auto['location'].null_value!r}") +print(f"signal : {auto['signal'].null_value!r}") + +# --------------------------------------------------------------------------- +# Work with nullable columns +# --------------------------------------------------------------------------- + data = [ (0, 22.3, "roof", 87), (1, -999.0, "cellar", 41), # temperature unknown @@ -52,7 +86,7 @@ class Reading: ] t = blosc2.CTable(Reading, new_data=data) -print("Table with nullable columns:") +print("\nTable with nullable columns:") print(t) # --------------------------------------------------------------------------- @@ -74,7 +108,7 @@ class Reading: # Null-aware aggregates # --------------------------------------------------------------------------- print("\n--- Aggregates skip null sentinels ---") -print(f"temperature.mean() = {t['temperature'].mean():.2f} (only 3 non-null readings)") +print(f"temperature.mean() = {t['temperature'].mean():.2f} (only 4 non-null readings)") print(f"temperature.min() = {t['temperature'].min():.2f}") print(f"temperature.max() = {t['temperature'].max():.2f}") print(f"signal.sum() = {t['signal'].sum()} (non-null: 87+41+62+95 = 285)") diff --git a/examples/ctable/querying.py b/examples/ctable/querying.py index 433c5110..99c3e579 100644 --- a/examples/ctable/querying.py +++ b/examples/ctable/querying.py @@ -36,16 +36,16 @@ class Sale: t = blosc2.CTable(Sale, new_data=data) # -- where(): row filter ---------------------------------------------------- -high_value = t.where(t["amount"] > 200) +high_value = t.where(t.amount > 200) print(f"Sales > $200: {len(high_value)} rows") print(high_value) -not_returned = t.where(not t["returned"]) +not_returned = t.where("not returned") print(f"Not returned: {len(not_returned)} rows") # -- chained filters (views are composable) --------------------------------- -north = t.where(t["region"] == "North") -north_big = north.where(north["amount"] > 100) +north = t.where(t.region == "North") +north_big = north.where(north.amount > 100) print(f"North region + amount > 100: {len(north_big)} rows") print(north_big) @@ -55,6 +55,6 @@ class Sale: print(slim) # -- combined: select columns, then filter rows ----------------------------- -result = t.select(["region", "amount"]).where(not t["returned"]) +result = t.where("not returned").select(["region", "amount"]) print("Region + amount for non-returned sales:") print(result) diff --git a/examples/ctable/real_world.py b/examples/ctable/real_world.py index 927f05bd..a94b6628 100644 --- a/examples/ctable/real_world.py +++ b/examples/ctable/real_world.py @@ -71,15 +71,15 @@ class WeatherReading: t.info() # -- Filter to station 3 ---------------------------------------------------- -station3 = t.where(t["station_id"] == 3) +station3 = t.where(t.station_id == 3) print(f"Station 3: {len(station3)} readings") -print(f" mean temperature : {station3['temperature'].mean():.1f} °C") -print(f" mean humidity : {station3['humidity'].mean():.1f} %") -print(f" mean wind speed : {station3['wind_speed'].mean():.1f} km/h\n") +print(f" mean temperature : {station3.temperature.mean():.1f} °C") +print(f" mean humidity : {station3.humidity.mean():.1f} %") +print(f" mean wind speed : {station3.wind_speed.mean():.1f} km/h\n") # -- 5 hottest days at station 3 (sort full table, then filter) ------------ sorted_by_temp = t.sort_by("temperature", ascending=False) -hottest_s3 = sorted_by_temp.where(sorted_by_temp["station_id"] == 3) +hottest_s3 = sorted_by_temp.where(sorted_by_temp.station_id == 3) print("5 hottest days at station 3:") print(hottest_s3.head(5)) @@ -98,7 +98,8 @@ class WeatherReading: path = f"{tmpdir}/station3" try: # Views cannot be sorted or saved directly — materialise via Arrow first - s3_copy = blosc2.CTable.from_arrow(station3.to_arrow()) + arrow = station3.to_arrow() + s3_copy = blosc2.CTable.from_arrow(arrow.schema, arrow.to_batches()) s3_copy.sort_by("day_of_year", inplace=True) sorted_s3 = s3_copy sorted_s3.save(path, overwrite=True) diff --git a/examples/ctable/varlen_columns.py b/examples/ctable/varlen_columns.py new file mode 100644 index 00000000..1a666133 --- /dev/null +++ b/examples/ctable/varlen_columns.py @@ -0,0 +1,38 @@ +from __future__ import annotations + +from dataclasses import dataclass + +import blosc2 as b2 + + +@dataclass +class Product: + code: str = b2.field(b2.string(max_length=8)) + ingredients: list[str] = b2.field( # noqa: RUF009 + b2.list(b2.string(max_length=16), nullable=True, batch_rows=2) + ) + + +products = b2.CTable(Product) +products.append(("A1", ["salt", "water"])) +products.append(("B2", [])) +products.append(("C3", None)) +products.extend( + [ + ("D4", ["flour", "oil"]), + ("E5", ["cocoa"]), + ] +) + +print("ingredients:", products.ingredients[:]) +print("tail:", products.tail(2).ingredients[:]) + +# Whole-cell replacement is explicit. +ing = products.ingredients[0] +ing.append("pepper") +products.ingredients[0] = ing +print("updated first row:", products.ingredients[0]) + +standalone = b2.ListArray(item_spec=b2.string(max_length=16), nullable=True) +standalone.extend([["a", "b"], [], None]) +print("standalone list array:", standalone[:]) diff --git a/off/parquet-to-blosc2.py b/off/parquet-to-blosc2.py new file mode 100644 index 00000000..1c0722b5 --- /dev/null +++ b/off/parquet-to-blosc2.py @@ -0,0 +1,764 @@ +#!/usr/bin/env python3 +####################################################################### +# Copyright (c) 2019-present, Blosc Development Team +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause +####################################################################### + +"""Import/export Parquet datasets through a CTable store. + +Default mode imports parquet -> .b2z/.b2d using CTable.from_arrow(). +The output extension selects the storage layout: .b2z is compact/zip-backed, +.b2d is sparse directory-backed. Additional modes: + +* --export: export an existing .b2z/.b2d -> parquet. +* --roundtrip: import parquet -> .b2z/.b2d -> parquet and assess differences. + +For large files, scalar string sizing defaults to sampling plus slack. If a +later batch contains a string that does not fit in the sampled fixed-width +schema, the offending column is promoted to list, the partial output is +removed, and the import restarts from the beginning. +""" + +from __future__ import annotations + +import argparse +import base64 +import contextlib +import gc +import os +import re +import resource +import shutil +import sys +import time +from pathlib import Path + +import blosc2 +from blosc2.schema_compiler import schema_to_dict + +DEFAULT_INPUT = Path(__file__).with_name("off-1pct.parquet") +DEFAULT_B2Z = Path(__file__).with_name("off-1pct-gpt.b2z") +DEFAULT_ROUNDTRIP_PARQUET = Path(__file__).with_name("off-1pct-gpt-roundtrip.parquet") +DEFAULT_BATCH_SIZE = 2048 +DEFAULT_STRING_FIXED_THRESHOLD = 64 +DEFAULT_SAMPLE_ROWS = 100_000 +DEFAULT_SAMPLE_ROW_GROUPS = 16 +DEFAULT_STRING_SLACK = 1.5 +DEFAULT_STRING_MIN = 32 +DEFAULT_STRING_PROMOTE_RATIO = 0.75 +DEFAULT_STRING_SCAN_COLUMNS = 8 +DEFAULT_MAX_RESTARTS = 3 + + +def require_pyarrow(): + try: + import pyarrow as pa + import pyarrow.compute as pc + import pyarrow.parquet as pq + except ImportError as exc: + raise ImportError( + "parquet-to-b2z.py requires pyarrow; install it with: pip install pyarrow" + ) from exc + return pa, pc, pq + + +def _format_bytes(n: int | None) -> str: + if n is None: + return "n/a" + value = float(n) + for unit in ("B", "KiB", "MiB", "GiB", "TiB"): + if abs(value) < 1024 or unit == "TiB": + return f"{value:.1f} {unit}" + value /= 1024 + return f"{value:.1f} TiB" + + +def _peak_rss_bytes() -> int: + peak = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss + if sys.platform == "darwin": + return int(peak) + return int(peak) * 1024 + + +def _current_rss_bytes() -> int | None: + try: + import psutil + except ImportError: + return None + return int(psutil.Process(os.getpid()).memory_info().rss) + + +def memory_report(label: str, pa=None) -> None: + arrow_allocated = None + arrow_pool = None + if pa is not None: + try: + arrow_allocated = int(pa.total_allocated_bytes()) + arrow_pool = int(pa.default_memory_pool().bytes_allocated()) + except Exception: + pass + parts = [ + f"[mem] {label}", + f"rss={_format_bytes(_current_rss_bytes())}", + f"peak={_format_bytes(_peak_rss_bytes())}", + ] + if arrow_allocated is not None: + parts.append(f"arrow_total={_format_bytes(arrow_allocated)}") + if arrow_pool is not None: + parts.append(f"arrow_pool={_format_bytes(arrow_pool)}") + print(" ".join(parts), flush=True) + + +def maybe_memory_report(args, label: str, pa=None) -> None: + if args.mem_report: + memory_report(label, pa) + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser( + description="Import/export Parquet datasets via CTable (.b2z compact or .b2d sparse).", + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + ) + mode = parser.add_mutually_exclusive_group() + mode.add_argument("--export", action="store_true", help="Export input .b2z to output parquet.") + mode.add_argument("--roundtrip", action="store_true", help="Run parquet -> .b2z -> parquet and compare.") + parser.add_argument("input_path", nargs="?", type=Path, default=DEFAULT_INPUT) + parser.add_argument("output_path", nargs="?", type=Path, default=None) + parser.add_argument("--batch-size", type=int, default=DEFAULT_BATCH_SIZE) + parser.add_argument( + "--string-fixed-threshold", + type=int, + default=DEFAULT_STRING_FIXED_THRESHOLD, + help="Scalar strings with estimated max length above this are stored as nullable list.", + ) + parser.add_argument( + "--string-scan", + choices=["head", "spread", "full"], + default="spread", + help="How to estimate scalar string max lengths before import.", + ) + parser.add_argument("--sample-rows", type=int, default=DEFAULT_SAMPLE_ROWS) + parser.add_argument( + "--sample-row-groups", + type=int, + default=DEFAULT_SAMPLE_ROW_GROUPS, + help="Maximum number of evenly spread row groups to sample when --string-scan=spread.", + ) + parser.add_argument("--string-slack", type=float, default=DEFAULT_STRING_SLACK) + parser.add_argument("--string-min", type=int, default=DEFAULT_STRING_MIN) + parser.add_argument( + "--string-promote-ratio", + type=float, + default=DEFAULT_STRING_PROMOTE_RATIO, + help="Promote sampled scalar strings to list when estimated length reaches this fraction of the fixed threshold.", + ) + parser.add_argument( + "--string-scan-columns", + type=int, + default=DEFAULT_STRING_SCAN_COLUMNS, + help="Maximum scalar string columns decoded at once during the preliminary length scan.", + ) + parser.add_argument( + "--force-list-string", + action="append", + default=[], + metavar="NAME[,NAME...]", + help="Force scalar string columns to be imported as list. Can be repeated or comma-separated.", + ) + parser.add_argument( + "--max-restarts", + type=int, + default=DEFAULT_MAX_RESTARTS, + help="Maximum automatic restarts after promoting overflowing string columns to list.", + ) + parser.add_argument("--codec", type=str, default="ZSTD", choices=[c.name for c in blosc2.Codec]) + parser.add_argument("--clevel", type=int, default=5) + parser.add_argument( + "--mem-report", + action="store_true", + help="Print process/Arrow memory diagnostics at import phases and during batch processing.", + ) + parser.add_argument( + "--mem-every", + type=int, + default=1, + help="With --mem-report, print batch memory diagnostics every N batches.", + ) + parser.add_argument( + "--batch-report-every", + type=int, + default=1, + help="Print progress every N batches; the final batch is always reported.", + ) + parser.add_argument("--overwrite", action="store_true") + return parser + + +def prepare_output(path: Path, overwrite: bool) -> None: + if not path.exists(): + return + if not overwrite: + raise FileExistsError(f"Output already exists: {path} (use --overwrite to replace)") + if path.is_dir(): + shutil.rmtree(path) + else: + path.unlink() + + +def remove_partial_output(path: Path) -> None: + if path.exists(): + if path.is_dir(): + shutil.rmtree(path) + else: + path.unlink() + + +def encode_arrow_schema(schema) -> str: + return base64.b64encode(schema.serialize().to_pybytes()).decode("ascii") + + +def decode_arrow_schema(pa, encoded: str): + return pa.ipc.read_schema(pa.BufferReader(base64.b64decode(encoded))) + + +def scalar_string_names(pa, schema) -> list[str]: + return [ + field.name + for field in schema + if pa.types.is_string(field.type) or pa.types.is_large_string(field.type) + ] + + +def _update_string_lengths(pc, batch, names: list[str], lengths: dict[str, int]) -> None: + for name in names: + arr = batch.column(name).drop_null() + observed = int(pc.max(pc.utf8_length(arr)).as_py() or 0) if len(arr) else 0 + lengths[name] = max(lengths[name], observed) + + +def _spread_row_groups(num_row_groups: int, max_groups: int) -> list[int]: + count = min(num_row_groups, max(max_groups, 1)) + if count <= 1: + return [0] + return sorted({round(i * (num_row_groups - 1) / (count - 1)) for i in range(count)}) + + +def _chunks(seq: list[str], size: int): + for start in range(0, len(seq), size): + yield seq[start : start + size] + + +def _release_arrow_temporaries(pa) -> None: + gc.collect() + with contextlib.suppress(Exception): + pa.default_memory_pool().release_unused() + + +def scan_string_max_lengths(pa, pc, pq, input_path: Path, args) -> dict[str, int]: + pf = pq.ParquetFile(input_path) + names = scalar_string_names(pa, pf.schema_arrow) + if not names: + return {} + + lengths = dict.fromkeys(names, 0) + col_groups = list(_chunks(names, args.string_scan_columns)) + rows_seen = 0 + if args.string_scan == "spread" and pf.metadata.num_row_groups > 0: + row_groups = _spread_row_groups(pf.metadata.num_row_groups, args.sample_row_groups) + rows_per_group = max(1, (args.sample_rows + len(row_groups) - 1) // len(row_groups)) + scan_batch_size = max(1, min(args.batch_size, rows_per_group)) + for row_group in row_groups: + group_rows_seen = 0 + for col_names in col_groups: + batch_iter = pf.iter_batches( + batch_size=scan_batch_size, columns=col_names, row_groups=[row_group] + ) + batch = next(batch_iter, None) + if batch is None: + continue + _update_string_lengths(pc, batch, col_names, lengths) + group_rows_seen = max(group_rows_seen, len(batch)) + del batch, batch_iter + _release_arrow_temporaries(pa) + rows_seen += group_rows_seen + maybe_memory_report(args, f"string scan row_group {row_group} rows_seen={rows_seen:,}", pa) + if rows_seen >= args.sample_rows: + break + else: + rows_seen = 0 + for col_names in col_groups: + col_rows_seen = 0 + for scan_batch_n, batch in enumerate( + pf.iter_batches(batch_size=args.batch_size, columns=col_names), start=1 + ): + _update_string_lengths(pc, batch, col_names, lengths) + col_rows_seen += len(batch) + if args.mem_report and scan_batch_n % args.mem_every == 0: + memory_report(f"string scan columns {col_names[0]}.. rows_seen={col_rows_seen:,}", pa) + del batch + _release_arrow_temporaries(pa) + if args.string_scan == "head" and col_rows_seen >= args.sample_rows: + break + rows_seen = max(rows_seen, col_rows_seen) + + result = {} + for name, observed in lengths.items(): + estimated = int(observed * args.string_slack) if args.string_scan != "full" else observed + result[name] = max(estimated, args.string_min) + return result + + +def classify_columns( + pa, + schema, + string_max_lengths: dict[str, int], + string_fixed_threshold: int, + force_list_strings: set[str], + string_promote_ratio: float, +): + fixed_cols: dict[str, object] = {} + list_wrap_cols: dict[str, object] = {} + conversions: dict[str, dict] = {} + nullable_scalars: list[str] = [] + + for field in schema: + t = field.type + if pa.types.is_struct(t): + list_wrap_cols[field.name] = pa.list_(t) + conversions[field.name] = {"conversion": "struct_wrapped_as_singleton_list"} + continue + if pa.types.is_list(t) or pa.types.is_large_list(t): + value_type = t.value_type + if pa.types.is_list(value_type) or pa.types.is_large_list(value_type): + conversions[field.name] = {"conversion": "skipped", "reason": f"nested list: {t}"} + else: + fixed_cols[field.name] = field + continue + if pa.types.is_boolean(t): + fixed_cols[field.name] = field + if field.nullable: + nullable_scalars.append(field.name) + conversions[field.name] = {"conversion": "nullable_scalar_sentinel"} + continue + if pa.types.is_integer(t) or pa.types.is_floating(t): + fixed_cols[field.name] = field + if field.nullable: + nullable_scalars.append(field.name) + conversions[field.name] = {"conversion": "nullable_scalar_sentinel"} + continue + if pa.types.is_string(t) or pa.types.is_large_string(t): + max_len = string_max_lengths.get(field.name, 1) + promote_cutoff = string_fixed_threshold * string_promote_ratio + if field.name in force_list_strings or max_len >= promote_cutoff: + list_wrap_cols[field.name] = pa.list_(pa.string()) + reason = ( + "scalar_string_promoted_after_overflow" + if field.name in force_list_strings + else "long_nullable_scalar_wrapped_as_singleton_list" + ) + conversions[field.name] = {"conversion": reason} + else: + fixed_cols[field.name] = field + if field.nullable: + nullable_scalars.append(field.name) + conversions[field.name] = {"conversion": "nullable_scalar_sentinel"} + continue + conversions[field.name] = {"conversion": "skipped", "reason": f"unsupported: {t}"} + + return fixed_cols, list_wrap_cols, conversions, nullable_scalars + + +def build_arrow_schema(pa, original_schema, fixed_cols: dict, list_wrap_cols: dict): + fields = [] + for field in original_schema: + if field.name in list_wrap_cols: + fields.append(pa.field(field.name, list_wrap_cols[field.name], nullable=True)) + elif field.name in fixed_cols: + fields.append(field) + return pa.schema(fields) + + +def transform_batch(pa, batch, selected_cols: list[str], list_wrap_cols: dict): + if not list_wrap_cols: + return batch + arrays = list(batch.columns) + for name, target_type in list_wrap_cols.items(): + try: + idx = batch.schema.get_field_index(name) + except KeyError: + continue + if idx < 0: + continue + arr = batch.column(idx) + arrays[idx] = pa.array([[v] if v is not None else None for v in arr.to_pylist()], type=target_type) + return pa.record_batch(arrays, names=selected_cols) + + +def store_original_arrow_metadata(ct, original_schema, imported_schema, conversions: dict) -> None: + fields_meta = {} + for field in original_schema: + entry = conversions.get(field.name) + if entry is None: + continue + entry = dict(entry) + entry["original_arrow_type"] = str(field.type) + if field.name in imported_schema.names: + entry["ctable_arrow_type"] = str(imported_schema.field(field.name).type) + fields_meta[field.name] = entry + ct._schema.metadata = { + "arrow": { + "schema_ipc_base64": encode_arrow_schema(original_schema), + "schema_string": original_schema.to_string(), + "imported_schema_ipc_base64": encode_arrow_schema(imported_schema), + "fields": fields_meta, + } + } + ct._storage.save_schema(schema_to_dict(ct._schema)) + + +def ctable_store_kind(path: Path) -> str: + if path.suffix == ".b2d": + return "sparse directory (.b2d)" + if path.suffix == ".b2z": + return "compact zip (.b2z)" + return f"unknown ({path.suffix or 'no suffix'})" + + +def print_import_plan( + pa, + args, + input_path, + output_path, + pf, + parquet_schema, + fixed_cols, + list_wrap_cols, + conversions, + nullable_scalars, + force_list_strings, +): + long_strings = [name for name, typ in list_wrap_cols.items() if typ == pa.list_(pa.string())] + wrapped_structs = [name for name in list_wrap_cols if name not in long_strings] + skipped = {name: entry for name, entry in conversions.items() if entry["conversion"] == "skipped"} + print(f"Input: {input_path} ({input_path.stat().st_size / 1e6:.1f} MB)") + print(f"Output: {output_path}") + print(f"CTable store: {ctable_store_kind(output_path)}") + print(f"Rows: {pf.metadata.num_rows:,}") + print(f"Parquet columns: {len(parquet_schema)}") + print(f"Imported columns: {len(fixed_cols) + len(list_wrap_cols)}") + print(f" Direct/fixed: {len(fixed_cols)}") + print(f" Struct→list: {len(wrapped_structs)}") + print(f" String→list: {len(long_strings)}") + print(f" Forced string→list: {len(force_list_strings)}") + print(f" Nullable scalars: {len(nullable_scalars)}") + print(f" Skipped unsupported: {len(skipped)}") + for name, entry in skipped.items(): + print(f" - {name}: {entry['reason']}") + print(f"Batch size: {args.batch_size:,}") + print(f"String scan: {args.string_scan}") + if args.string_scan in {"head", "spread"}: + print(f"Sample rows/slack: {args.sample_rows:,} / {args.string_slack}") + if args.string_scan == "spread": + print(f"Sample row groups: {args.sample_row_groups}") + print(f"String min: {args.string_min}") + print(f"String fixed thresh: {args.string_fixed_threshold}") + print(f"String promote ratio: {args.string_promote_ratio}") + print(f"String scan columns: {args.string_scan_columns}") + print(f"Codec / level: {args.codec} / {args.clevel}") + print() + + +def progress_batches(pa, pf, args, selected_cols, list_wrap_cols): + rows_done = 0 + t0 = time.perf_counter() + total = pf.metadata.num_rows + for batch_n, raw_batch in enumerate( + pf.iter_batches(batch_size=args.batch_size, columns=selected_cols), start=1 + ): + report_batch_mem = args.mem_report and batch_n % args.mem_every == 0 + if report_batch_mem: + memory_report(f"batch {batch_n} after parquet read", pa) + batch = transform_batch(pa, raw_batch, selected_cols, list_wrap_cols) + if report_batch_mem: + memory_report(f"batch {batch_n} after transform", pa) + rows_done += len(batch) + elapsed = time.perf_counter() - t0 + rate = rows_done / elapsed if elapsed > 0 else 0.0 + eta = (total - rows_done) / rate if rate > 0 else 0.0 + if batch_n % args.batch_report_every == 0 or rows_done >= total: + print( + f" batch {batch_n:4d} {rows_done:>12,}/{total:,} " + f"{elapsed:7.1f}s {rate / 1e3:7.1f}k rows/s ETA {eta:6.0f}s", + flush=True, + ) + if report_batch_mem: + memory_report(f"batch {batch_n} before ctable write", pa) + yield batch + if report_batch_mem: + memory_report(f"batch {batch_n} after ctable write", pa) + + +def overflowing_string_column(exc: Exception) -> str | None: + match = re.search(r"Column '([^']+)' contains values longer than max_length", str(exc)) + return match.group(1) if match else None + + +def import_once(args, input_path: Path, output_path: Path, force_list_strings: set[str]): + pa, pc, pq = require_pyarrow() + maybe_memory_report(args, "after pyarrow import", pa) + pf = pq.ParquetFile(input_path) + maybe_memory_report(args, "after ParquetFile open", pa) + parquet_schema = pf.schema_arrow + + print( + "Estimating scalar string lengths" + f" ({args.string_scan}{', rows=' + format(args.sample_rows, ',') if args.string_scan in {'head', 'spread'} else ''})…" + ) + string_max_lengths = scan_string_max_lengths(pa, pc, pq, input_path, args) + maybe_memory_report(args, "after string length scan", pa) + fixed_cols, list_wrap_cols, conversions, nullable_scalars = classify_columns( + pa, + parquet_schema, + string_max_lengths, + args.string_fixed_threshold, + force_list_strings, + args.string_promote_ratio, + ) + maybe_memory_report(args, "after column classification", pa) + selected_cols = [f.name for f in parquet_schema if f.name in fixed_cols or f.name in list_wrap_cols] + arrow_schema = build_arrow_schema(pa, parquet_schema, fixed_cols, list_wrap_cols) + maybe_memory_report(args, "after import schema build", pa) + print_import_plan( + pa, + args, + input_path, + output_path, + pf, + parquet_schema, + fixed_cols, + list_wrap_cols, + conversions, + nullable_scalars, + force_list_strings, + ) + + t0 = time.perf_counter() + maybe_memory_report(args, "before CTable import", pa) + ct = blosc2.CTable.from_arrow( + arrow_schema, + progress_batches(pa, pf, args, selected_cols, list_wrap_cols), + urlpath=str(output_path), + mode="w", + cparams=blosc2.CParams(codec=blosc2.Codec[args.codec], clevel=args.clevel), + capacity_hint=pf.metadata.num_rows, + string_max_length=args.string_fixed_threshold, + auto_null_sentinels=True, + ) + maybe_memory_report(args, "after CTable import", pa) + store_original_arrow_metadata(ct, parquet_schema, arrow_schema, conversions) + maybe_memory_report(args, "after metadata save", pa) + elapsed = time.perf_counter() - t0 + rows = len(ct) + cols = len(ct.col_names) + ct.close() + maybe_memory_report(args, "after CTable close", pa) + + output_size = ( + output_path.stat().st_size + if output_path.is_file() + else sum(f.stat().st_size for f in output_path.rglob("*") if f.is_file()) + ) + print(f"Done in {elapsed:.2f}s") + print(f"Rows imported: {rows:,}") + print(f"Columns imported: {cols}") + print(f"Output size: {output_size / 1e6:.1f} MB") + return selected_cols + + +def parse_force_list_strings(values: list[str]) -> set[str]: + names: set[str] = set() + for value in values: + for name in value.split(","): + name = name.strip() + if name: + names.add(name) + return names + + +def import_parquet_to_ctable(args, input_path: Path, output_path: Path): + if args.batch_size <= 0: + raise ValueError("--batch-size must be positive") + if args.sample_rows <= 0: + raise ValueError("--sample-rows must be positive") + if args.string_slack < 1: + raise ValueError("--string-slack must be >= 1") + if args.sample_row_groups <= 0: + raise ValueError("--sample-row-groups must be positive") + if args.string_scan_columns <= 0: + raise ValueError("--string-scan-columns must be positive") + if not (0 < args.string_promote_ratio <= 1): + raise ValueError("--string-promote-ratio must be in the interval (0, 1]") + if args.mem_every <= 0: + raise ValueError("--mem-every must be positive") + if args.batch_report_every <= 0: + raise ValueError("--batch-report-every must be positive") + if args.output_path is not None and output_path.suffix not in {".b2z", ".b2d"}: + raise ValueError("output_path must use the .b2z (compact) or .b2d (sparse) extension") + if not input_path.exists(): + raise FileNotFoundError(f"Input file not found: {input_path}") + prepare_output(output_path, args.overwrite) + + force_list_strings = parse_force_list_strings(args.force_list_string) + for attempt in range(args.max_restarts + 1): + remove_partial_output(output_path) + try: + return import_once(args, input_path, output_path, force_list_strings) + except ValueError as exc: + col = overflowing_string_column(exc) + if col is None or col in force_list_strings or attempt >= args.max_restarts: + raise + print( + f"\nString overflow in column {col!r}; promoting it to list " + f"and restarting import ({attempt + 1}/{args.max_restarts})…\n" + ) + force_list_strings.add(col) + raise RuntimeError("unreachable") + + +def original_schema_from_ctable(pa, ct): + arrow_meta = ct._schema.metadata.get("arrow", {}) + encoded = arrow_meta.get("schema_ipc_base64") + if encoded: + return decode_arrow_schema(pa, encoded) + return None + + +def unwrap_singleton_list(pa, arr, arrow_type): + return pa.array( + [None if cell is None or len(cell) == 0 else cell[0] for cell in arr.to_pylist()], type=arrow_type + ) + + +def export_ctable_to_parquet(input_path: Path, output_path: Path, *, batch_size: int, overwrite: bool): + pa, _, pq = require_pyarrow() + if batch_size <= 0: + raise ValueError("--batch-size must be positive") + prepare_output(output_path, overwrite) + ct = blosc2.CTable.open(str(input_path)) + original_schema = original_schema_from_ctable(pa, ct) + fields_meta = ct._schema.metadata.get("arrow", {}).get("fields", {}) + export_names = [ + name + for name in (original_schema.names if original_schema is not None else ct.col_names) + if name in ct.col_names + ] + export_schema = ( + pa.schema([original_schema.field(name) for name in export_names]) + if original_schema is not None + else ct._arrow_schema_for_columns(export_names) + ) + + t0 = time.perf_counter() + with pq.ParquetWriter(output_path, export_schema, compression="zstd") as writer: + for batch in ct.iter_arrow_batches(columns=export_names, batch_size=batch_size): + arrays = [] + for name in export_names: + arr = batch.column(name) + meta = fields_meta.get(name, {}) + field = export_schema.field(name) + if meta.get("conversion") in { + "struct_wrapped_as_singleton_list", + "nullable_scalar_wrapped_as_singleton_list", + "long_nullable_scalar_wrapped_as_singleton_list", + "scalar_string_promoted_after_overflow", + }: + arr = unwrap_singleton_list(pa, arr, field.type) + elif str(arr.type) != str(field.type): + arr = pa.array(arr.to_pylist(), type=field.type) + arrays.append(arr) + out_batch = pa.record_batch(arrays, schema=export_schema) + writer.write_table(pa.Table.from_batches([out_batch]), row_group_size=len(out_batch)) + elapsed = time.perf_counter() - t0 + rows = len(ct) + ct.close() + print(f"Exported {rows:,} rows and {len(export_names)} columns to {output_path} in {elapsed:.2f}s") + return export_names + + +def assess_parquet_difference(original_path: Path, roundtrip_path: Path, exported_cols: list[str]): + pa, _, pq = require_pyarrow() + orig_pf = pq.ParquetFile(original_path) + rt_pf = pq.ParquetFile(roundtrip_path) + original_schema = orig_pf.schema_arrow + roundtrip_schema = rt_pf.schema_arrow + common = [ + name for name in exported_cols if name in original_schema.names and name in roundtrip_schema.names + ] + missing = [name for name in original_schema.names if name not in roundtrip_schema.names] + + orig = pq.read_table(original_path, columns=common) + rt = pq.read_table(roundtrip_path, columns=common) + differing = [] + type_diffs = [] + null_diffs = [] + for name in common: + if str(original_schema.field(name).type) != str(roundtrip_schema.field(name).type): + type_diffs.append(name) + if orig[name].null_count != rt[name].null_count: + null_diffs.append((name, orig[name].null_count, rt[name].null_count)) + if not orig[name].equals(rt[name]): + differing.append(name) + + print("\nRoundtrip assessment") + print(f" Original rows: {orig_pf.metadata.num_rows:,}") + print(f" Roundtrip rows: {rt_pf.metadata.num_rows:,}") + print(f" Original columns: {len(original_schema)}") + print(f" Roundtrip columns: {len(roundtrip_schema)}") + print(f" Missing columns: {len(missing)}") + for name in missing: + print(f" - {name}: not imported/exported") + print(f" Type differences: {len(type_diffs)}") + for name in type_diffs: + print(f" - {name}: {original_schema.field(name).type} -> {roundtrip_schema.field(name).type}") + print(f" Null-count diffs: {len(null_diffs)}") + for name, a, b in null_diffs[:20]: + print(f" - {name}: {a} -> {b}") + if len(null_diffs) > 20: + print(f" ... {len(null_diffs) - 20} more") + print(f" Value differences: {len(differing)} of {len(common)} compared columns") + if differing: + print(" First value-different columns:") + for name in differing[:20]: + print(f" - {name}") + print(f" Original size: {original_path.stat().st_size / 1e6:.1f} MB") + print(f" Roundtrip size: {roundtrip_path.stat().st_size / 1e6:.1f} MB") + + +def main() -> None: + args = build_parser().parse_args() + if args.export: + input_path = args.input_path + output_path = args.output_path or input_path.with_suffix(".parquet") + export_ctable_to_parquet( + input_path, output_path, batch_size=args.batch_size, overwrite=args.overwrite + ) + return + if args.roundtrip: + input_path = args.input_path + b2z_path = args.output_path or DEFAULT_B2Z + roundtrip_path = DEFAULT_ROUNDTRIP_PARQUET + selected = import_parquet_to_ctable(args, input_path, b2z_path) + exported = export_ctable_to_parquet( + b2z_path, roundtrip_path, batch_size=args.batch_size, overwrite=True + ) + assess_parquet_difference(input_path, roundtrip_path, exported or selected) + return + + output_path = args.output_path or DEFAULT_B2Z + import_parquet_to_ctable(args, args.input_path, output_path) + + +if __name__ == "__main__": + main() diff --git a/plans/ctable-nulls.md b/plans/ctable-nulls.md new file mode 100644 index 00000000..6847a3b3 --- /dev/null +++ b/plans/ctable-nulls.md @@ -0,0 +1,894 @@ +# CTable Nullable Scalar / Parquet Fidelity Plan + +## Summary + +Improve CTable scalar null handling so Parquet nullable scalar columns can round-trip with high fidelity: + +```text +Parquet null -> CTable null sentinel -> Parquet null +``` + +This plan focuses on the remaining fidelity gaps after batch-wise Parquet import/export and Arrow schema metadata support: + +- nullable numeric scalar columns; +- nullable string/bytes scalar columns; +- nullable bool scalar columns. + +The guiding approach is to continue using CTable's existing in-band `null_value` sentinel model for scalar columns, while adding sensible default sentinel choices for Parquet imports and a special physical representation for nullable bools. + +--- + +## Decisions + +### 1. Automatic sentinel inference defaults to enabled for Parquet imports + +`CTable.from_parquet()` should default to automatic scalar null sentinels: + +```python +CTable.from_parquet(path, auto_null_sentinels=True) +``` + +That is, the public default should become effectively `True` for Parquet imports because preserving Parquet nulls is the expected behavior for interchange. + +For lower-level Arrow batch imports, default can be considered separately, but the recommended behavior is also to make it available and likely default-on when importing Arrow schemas from Parquet. + +### 2. String/bytes sentinels are configurable public options + +Default string/bytes sentinels: + +```python +string_null_value = "__BLOSC2_NULL__" +bytes_null_value = b"__BLOSC2_NULL__" +``` + +Expose them on import APIs: + +```python +CTable.from_parquet( + path, + auto_null_sentinels=True, + string_null_value="__BLOSC2_NULL__", + bytes_null_value=b"__BLOSC2_NULL__", +) +``` + +and similarly for `from_arrow_batches()` where appropriate. + +No full collision scan is performed by default. This avoids whole-column scans for large datasets. Users who know their data can override the sentinel values. + +### 3. Nullable bool API supports both explicit sentinel and convenience nullable flag + +Support both: + +```python +blosc2.bool(null_value=255) +blosc2.bool(nullable=True) +``` + +`nullable=True` is shorthand for: + +```python +null_value = 255 +``` + +Physical representation: + +```text +False = 0 +True = 1 +Null = 255 +``` + +A nullable bool column is logically a bool column but physically stored as `uint8` so the sentinel is preserved. + +### 4. Nullable bool expression rewrites use a conservative scope + +Initial expression support should be: + +- equality/inequality rewrites everywhere CTable column expressions are built; +- bare `flag` and `~flag` rewrites only in filter contexts. + +For nullable bool encoded as `0/1/255`: + +```text +flag == True -> raw == 1 +flag == False -> raw == 0 +flag != True -> raw == 0 +flag != False -> raw == 1 +``` + +In filter contexts: + +```text +flag -> raw == 1 +~flag -> raw == 0 +``` + +This excludes nulls from both true and false selections, matching common tabular filtering expectations. + +--- + +## Goals + +1. Preserve nullable numeric, string, bytes, and bool Parquet columns through CTable round-trips. +2. Avoid whole-column pre-scans for sentinel selection. +3. Keep using the current scalar sentinel null model for consistency with existing nullable columns. +4. Add general nullable bool column support to CTable, not just a Parquet-specific workaround. +5. Preserve batch-wise import/export behavior. +6. Maintain clear, predictable raw storage semantics. +7. Keep nullable bool expression/filter behavior useful without implementing full three-valued logic in V1. + +--- + +## Non-goals + +- Add separate validity bitmap storage for scalar CTable columns. +- Implement full SQL/Arrow/Pandas Kleene three-valued boolean algebra in V1. +- Guarantee no sentinel collision for string/bytes columns without user-provided sentinels. +- Hide all sentinels from raw column reads. +- Automatically infer collision-free string/bytes sentinels by scanning entire columns. + +--- + +## Current behavior and gap + +CTable already supports scalar nulls via `null_value` sentinels, e.g.: + +```python +blosc2.int64(null_value=-1) +blosc2.float64(null_value=float("nan")) +blosc2.string(max_length=16, null_value="") +``` + +Arrow/Parquet export already maps sentinel values back to Arrow nulls in the scalar export path: + +```text +sentinel in CTable -> Arrow null bitmap -> Parquet null +``` + +The missing pieces are: + +1. During Parquet import, infer and attach appropriate sentinels automatically. +2. During batch writes, replace Arrow nulls with those sentinels before writing to CTable storage. +3. For bool columns, introduce a physical representation that can actually preserve a sentinel. +4. For strings/bytes, choose practical default sentinels without full scans. +5. Avoid lossy importer workarounds such as filling nulls with `0`, `NaN`, or `""` without recording them as actual `null_value` sentinels. + +--- + +## Sentinel policy + +### Numeric columns + +For nullable Arrow/Parquet numeric fields, if `auto_null_sentinels=True`: + +```text +int8 -> np.iinfo(np.int8).min +int16 -> np.iinfo(np.int16).min +int32 -> np.iinfo(np.int32).min +int64 -> np.iinfo(np.int64).min +uint8 -> np.iinfo(np.uint8).max +uint16 -> np.iinfo(np.uint16).max +uint32 -> np.iinfo(np.uint32).max +uint64 -> np.iinfo(np.uint64).max +float32 -> NaN +float64 -> NaN +``` + +These become the column spec's `null_value`. + +Example: + +```python +pa.field("score", pa.int32(), nullable=True) +``` + +maps to: + +```python +blosc2.int32(null_value=np.iinfo(np.int32).min) +``` + +### String columns + +For nullable Arrow string/large_string fields: + +```python +blosc2.string(max_length=..., null_value="__BLOSC2_NULL__") +``` + +The selected `max_length` must be large enough to store both actual values and the sentinel. Therefore: + +```python +max_length = max(inferred_or_configured_max_length, len(string_null_value)) +``` + +No collision scan is performed by default. + +### Bytes columns + +For nullable Arrow binary/large_binary fields: + +```python +blosc2.bytes(max_length=..., null_value=b"__BLOSC2_NULL__") +``` + +The selected `max_length` must be at least: + +```python +len(bytes_null_value) +``` + +No collision scan is performed by default. + +### Bool columns + +For nullable Arrow bool fields: + +```python +blosc2.bool(nullable=True) +``` + +or equivalently: + +```python +blosc2.bool(null_value=255) +``` + +Physical storage dtype: + +```python +np.uint8 +``` + +Encoding: + +```text +0 false +1 true +255 null +``` + +Non-null Arrow bool values are converted as: + +```text +False -> 0 +True -> 1 +``` + +Arrow nulls are converted as: + +```text +Null -> 255 +``` + +--- + +## Schema changes + +### `blosc2.schema.bool` + +Current `bool` spec has fixed dtype: + +```python +dtype = np.dtype(np.bool_) +``` + +Change it to support nullable bools: + +```python +class bool(SchemaSpec): + python_type = builtins.bool + + def __init__(self, *, nullable: bool = False, null_value=None): + if nullable and null_value is None: + null_value = 255 + if null_value is not None and null_value != 255: + raise ValueError("Nullable bool null_value must be 255") + self.null_value = null_value + self.nullable = null_value is not None + self.dtype = np.dtype(np.uint8) if self.nullable else np.dtype(np.bool_) +``` + +Metadata: + +```json +{"kind": "bool"} +``` + +or: + +```json +{"kind": "bool", "nullable": true, "null_value": 255} +``` + +### Display/type labels + +A nullable bool column should still display as a logical bool column, possibly with a nullable marker: + +```text +bool? +``` + +or: + +```text +bool(nullable) +``` + +Internally, physical dtype is `uint8`. + +--- + +## Arrow/Parquet import changes + +### API additions + +Add/import parameters: + +```python +@classmethod +def from_parquet( + cls, + path, + *, + columns=None, + batch_size=65_536, + urlpath=None, + mode="w", + cparams=None, + dparams=None, + validate=False, + auto_null_sentinels=True, + string_null_value="__BLOSC2_NULL__", + bytes_null_value=b"__BLOSC2_NULL__", + **kwargs, +): ... +``` + +For `from_arrow_batches()`: + +```python +def from_arrow_batches( + cls, + schema, + batches, + *, + urlpath=None, + mode="w", + auto_null_sentinels=True, + string_null_value="__BLOSC2_NULL__", + bytes_null_value=b"__BLOSC2_NULL__", +): ... +``` + +If there is concern about changing `from_arrow_batches()` defaults, keep that default as `False` initially but make `from_parquet()` pass `True`. + +### Type mapping + +Add helper: + +```python +def _auto_null_sentinel(pa, pa_type, *, string_null_value, bytes_null_value): ... +``` + +Return appropriate sentinel for supported nullable scalar types. + +When building specs from Arrow fields: + +```python +if auto_null_sentinels and field.nullable: + null_value = _auto_null_sentinel(...) +else: + null_value = None +``` + +Then pass `null_value` into the corresponding SchemaSpec constructor. + +For list and struct fields, do not use scalar sentinels; their nested nulls are handled in the ListArray/Python-object layer. + +--- + +## Batch write behavior + +When writing an Arrow column into CTable storage: + +### List columns + +Unchanged: + +```python +list_col.extend(arrow_col.to_pylist()) +``` + +### String/bytes columns + +If Arrow nulls exist and the CTable spec has a `null_value`, replace `None` with the sentinel before building the NumPy array: + +```python +values = arrow_col.to_pylist() +if null_value is not None: + values = [null_value if v is None else v for v in values] +arr = np.array(values, dtype=col.dtype) +``` + +If Arrow nulls exist and no sentinel is configured, raise a clear error. + +### Numeric columns + +Use Arrow null mask: + +```python +arr = arrow_col.to_numpy(zero_copy_only=False).astype(col.dtype) +if arrow_col.null_count: + arr[np.asarray(arrow_col.is_null())] = null_value +``` + +For floats, Arrow may produce `NaN` for nulls already, but still explicitly applying the sentinel is clearer and consistent. + +### Nullable bool columns + +If physical dtype is `uint8`: + +```python +values = arrow_col.to_numpy(zero_copy_only=False) +arr = values.astype(np.uint8) # False -> 0, True -> 1 +if arrow_col.null_count: + arr[np.asarray(arrow_col.is_null())] = 255 +``` + +Need care because Arrow `to_numpy()` on nullable bool may produce object arrays or fail in some versions. Fallback: + +```python +py_values = arrow_col.to_pylist() +arr = np.array([255 if v is None else int(v) for v in py_values], dtype=np.uint8) +``` + +This fallback is acceptable for bool columns. + +--- + +## Arrow/Parquet export behavior + +The existing sentinel-to-Arrow-null logic should be extended to nullable bool physical `uint8` columns. + +For scalar export: + +```python +arr = col[:] +nv = col.null_value +null_mask = col._null_mask_for(arr) if nv is not None else None +``` + +For nullable bool: + +```python +values = arr == 1 +pa.array(values, mask=null_mask, type=pa.bool_()) +``` + +Important: do not export nullable bool physical `uint8` as Arrow uint8. The logical Arrow type should be bool. + +For non-nullable bool, keep current behavior: + +```python +pa.array(arr) # Arrow bool +``` + +--- + +## Nullable bool raw access semantics + +Raw reads expose the physical sentinel representation: + +```text +False -> 0 +True -> 1 +Null -> 255 +``` + +Example: + +```python +t["flag"][:] # np.array([1, 0, 255], dtype=uint8) +``` + +This is consistent with CTable's existing nullable scalar model, where raw reads expose sentinel values. + +`Column.is_null()`, `Column.notnull()`, and `Column.null_count()` should work normally. + +--- + +## Nullable bool expression/filter semantics + +### Equality/inequality rewrites + +For nullable bool columns, rewrite these comparisons before they reach the generic LazyExpr mechanism: + +```text +flag == True -> raw == 1 +flag == False -> raw == 0 +flag != True -> raw == 0 +flag != False -> raw == 1 +``` + +This ensures nulls do not match either true or false predicates. + +These rewrites can apply everywhere CTable column expressions are built. + +### Bare bool in filter context + +In filter contexts: + +```python +ct.where(ct.flag) +``` + +rewrite as: + +```python +ct.where(ct.flag == 1) +``` + +### Negation in filter context + +In filter contexts: + +```python +ct.where(~ct.flag) +``` + +rewrite as: + +```python +ct.where(ct.flag == 0) +``` + +This prevents nulls from being included by `~(raw == 1)` semantics. + +### Temporary logical bool arrays + +If needed for expression support, create temporary in-memory bool NDArrays scoped to the operation: + +```python +flag_true = raw == 1 +flag_false = raw == 0 +``` + +These temporaries: + +- are not persisted; +- are not added to `ct._cols`; +- live only during operations like `ct.where(...)`; +- can be compressed if materialized as Blosc2 NDArrays; +- should be avoided when a simple comparison rewrite is enough. + +### Explicitly out of scope for V1 + +Full nullable boolean algebra, e.g. preserving nulls in value-producing expressions: + +```text +~flag -> [False, True, Null] +flag & other_nullable_flag +flag | other_nullable_flag +``` + +can be deferred. V1 focuses on practical filtering semantics. + +--- + +## Sorting and index interaction + +Nullable scalar sentinels must not leak into user-visible sort/filter semantics. + +### Sorting behavior + +Nulls should participate in sorting but always sort last, regardless of sort direction: + +```python +ct.sort_by("score", ascending=True) # non-null ascending, nulls last +ct.sort_by("score", ascending=False) # non-null descending, nulls last +``` + +The current non-indexed sort path already uses a null-indicator key: + +```text +0 = non-null +1 = null +``` + +as a more significant lexsort key, which gives nulls-last behavior. + +However, FULL-index sort fast paths can be wrong for nullable columns because they sort raw sentinels: + +```text +signed int sentinel = dtype min -> sorts first ascending +uint/bool sentinel = dtype max/255 -> order depends on direction +string sentinel = lexicographic -> order depends on value +``` + +V1 rule: + +> If the sort key column has `null_value`, do not use the FULL-index sort fast path unless the index is explicitly marked null-aware and nulls-last. Fall back to the null-aware lexsort path. + +Future index metadata can include: + +```json +{ + "null_aware": true, + "null_order": "last" +} +``` + +so sorted index paths can safely support nullable columns. + +### Indexed `where()` behavior + +Normal comparisons should not match nulls: + +```python +ct.where(ct.score > 10) # null score rows excluded +ct.where(ct.score < 10) # null score rows excluded +ct.where(ct.score == 10) # null score rows excluded +ct.where(ct.score != 10) # null score rows excluded +``` + +Explicit null selection should use: + +```python +ct.where(ct.score.is_null()) +``` + +For index-accelerated `where()`, raw sentinel values can otherwise produce incorrect matches. Examples: + +```text +uint null sentinel = max_uint -> may match score > 10 +int null sentinel = min_int -> may match score < 0 +string sentinel -> may match lexicographic ranges +``` + +V1 rule: + +> If an indexed query references nullable columns, post-filter index-produced candidate positions with the relevant `notnull` physical masks before returning them. + +For simple comparisons and AND-only expressions: + +```python +ct.where((ct.score > 10) & (ct.age < 20)) +``` + +index result positions should be filtered as: + +```python +positions = positions[score_notnull[positions] & age_notnull[positions]] +``` + +This is simple, safe, and avoids null sentinel false positives. + +### OR expressions + +Global null-mask post-filtering is not correct for OR expressions. Example: + +```python +ct.where((ct.score > 10) | (ct.category == 3)) +``` + +A row with `score == null` and `category == 3` should match. A global mask: + +```python +score.notnull() & category.notnull() +``` + +would wrongly drop it. + +V1 rule: + +> For indexed expressions containing OR over nullable columns, fall back to full scan unless the expression has been rewritten with branch-local null checks. + +Future branch-local AST rewrite: + +```python +(score > 10) | (category == 3) +``` + +becomes: + +```python +((score > 10) & score.notnull()) | ((category == 3) & category.notnull()) +``` + +This lets the existing planner see a semantically correct predicate. The planner may still need post-filtering or index support for the temporary null-mask operands, but correctness no longer depends on a global mask. + +### Does this remove the need for a nullable-aware planner? + +Partly. Injecting/post-filtering null masks handles many cases without changing the planner: + +- simple comparisons; +- range predicates; +- AND-only combinations. + +A planner is still useful for: + +- deciding when an expression is AND-only vs contains OR; +- identifying referenced nullable columns; +- combining indexed predicates efficiently; +- supporting future branch-local null rewrites. + +So the V1 approach is not a full nullable-aware planner rewrite. It is a correctness layer around existing index results. + +--- + +## Interaction with schema metadata + +The CTable schema already stores each column's `null_value` in the column spec metadata. + +For diagnostics and Parquet fidelity metadata, Arrow metadata can optionally record import-time sentinel decisions: + +```json +{ + "metadata": { + "arrow": { + "fields": { + "score": { + "original_arrow_type": "int32", + "null_sentinel": -2147483648 + }, + "label": { + "original_arrow_type": "string", + "null_sentinel": "__BLOSC2_NULL__" + }, + "flag": { + "original_arrow_type": "bool", + "null_sentinel": 255, + "physical_dtype": "uint8" + } + } + } + } +} +``` + +This is optional because the CTable schema itself is sufficient to export sentinels as nulls. + +--- + +## Impact on `off/import-to-b2z-gpt.py` + +Once nullable scalar support is implemented, the OFF importer should stop manually filling nullable scalar nulls. + +Current workaround: + +```text +nullable numeric/string -> fill with 0/NaN/"" +nullable bool -> wrap as list +``` + +Future behavior: + +```text +nullable numeric -> scalar with auto sentinel +nullable string -> scalar with "__BLOSC2_NULL__" sentinel +nullable bytes -> scalar with b"__BLOSC2_NULL__" sentinel +nullable bool -> scalar nullable bool, physical uint8 sentinel 255 +``` + +Long strings may still be wrapped as `list` for variable-length storage, but that becomes a storage choice, not a null-preservation workaround. + +Expected OFF roundtrip improvement: + +- null-count differences for numeric/string/bool scalar columns should drop to zero; +- value differences caused by filled nulls should disappear; +- remaining differences should be due to intentional schema/storage transformations, if any. + +--- + +## Tests + +### Numeric nulls + +1. Nullable int Parquet column round-trips null counts and values. +2. Nullable uint Parquet column round-trips null counts and values. +3. Nullable float Parquet column round-trips null counts and values using NaN sentinel. +4. Exported Parquet contains real nulls, not sentinel values. + +### String/bytes nulls + +1. Nullable string Parquet column imports with `null_value="__BLOSC2_NULL__"`. +2. Export maps sentinel back to Parquet nulls. +3. `max_length` is at least `len("__BLOSC2_NULL__")`. +4. Nullable bytes column behaves similarly with `b"__BLOSC2_NULL__"`. +5. User-provided `string_null_value` / `bytes_null_value` are respected. + +### Bool nulls + +1. `blosc2.bool(nullable=True)` uses physical `uint8` dtype. +2. `blosc2.bool(null_value=255)` is equivalent. +3. Nullable bool Parquet imports as `0/1/255` raw values. +4. Export maps `255` back to Parquet nulls and emits Arrow bool type. +5. `is_null()`, `notnull()`, `null_count()` work. + +### Bool filtering + +For raw values: + +```text +[1, 0, 255] +``` + +Verify: + +```python +ct.where(ct.flag == True) # true row only +ct.where(ct.flag == False) # false row only +ct.where(ct.flag != True) # false row only +ct.where(ct.flag != False) # true row only +ct.where(ct.flag) # true row only +ct.where(~ct.flag) # false row only +``` + +### OFF roundtrip + +Run: + +```bash +python off/import-to-b2z-gpt.py --roundtrip --overwrite +``` + +Expected: + +- same row count; +- same column count; +- same Arrow types for imported/exported columns; +- zero null-count differences for scalar columns using auto sentinels; +- significantly fewer value differences than current baseline. + +--- + +## Implementation milestones + +### Milestone 1: Port numeric auto sentinel support + +- Add `_auto_null_sentinel()` helper. +- Add `auto_null_sentinels` to `from_arrow_batches()` and `from_parquet()`. +- Replace Arrow nulls with numeric sentinels during batch writes. +- Add numeric nullable Parquet tests. + +### Milestone 2: String/bytes sentinels + +- Add `string_null_value` and `bytes_null_value` parameters. +- Include sentinel length in max length calculation. +- Replace Arrow nulls with configured sentinel during batch writes. +- Export sentinels as Arrow nulls. +- Add string/bytes nullable tests. + +### Milestone 3: Nullable bool schema/storage + +- Extend `blosc2.bool()` with `nullable=True` and `null_value=255`. +- Use `np.uint8` physical dtype for nullable bool. +- Adjust display/type helpers where needed. +- Add import/export conversion for nullable bool. +- Add nullable bool tests. + +### Milestone 4: Nullable bool filter rewrites + +- Detect nullable bool columns in expression-building paths. +- Rewrite equality/inequality comparisons. +- Rewrite bare nullable bool and `~nullable_bool` in filter contexts. +- Add filter tests. + +### Milestone 5: Update OFF importer and assess + +- Remove scalar null filling workaround. +- Stop wrapping nullable bools as `list`. +- Keep long string wrapping only when desired for storage efficiency. +- Run roundtrip assessment and document remaining differences. + +--- + +## Open questions + +1. Should `from_arrow_batches()` default `auto_null_sentinels=True`, or only `from_parquet()`? +2. Should `blosc2.bool(null_value=255)` allow any other sentinel in the future, or always enforce `255`? +3. Should raw nullable bool display show `True`/`False`/`NULL` even though raw reads expose `0/1/255`? +4. Should string/bytes sentinel collision checking be offered as an optional slow mode? +5. Should singleton-list long strings eventually be replaced by true variable-length scalar string storage? diff --git a/plans/ctable-parquet-metadata.md b/plans/ctable-parquet-metadata.md new file mode 100644 index 00000000..84ec9fe5 --- /dev/null +++ b/plans/ctable-parquet-metadata.md @@ -0,0 +1,404 @@ +# CTable Parquet/Arrow Metadata Plan + +## Summary + +Add an optional, internal metadata section to the persistent `CTable` schema dict so Arrow/Parquet import paths can preserve enough original Arrow schema information for better future round-tripping. + +The immediate motivation is supporting nested Parquet columns such as: + +```text +struct +list> +``` + +The target design is full internal `StructSpec` support, including `list>`, with metadata preserving the original Arrow schema so `parquet -> CTable -> parquet` can round-trip supported nested columns without schema loss. + +This is intended as an implementation detail, not a public `t.metadata` API. + +--- + +## Goals + +1. Preserve original Arrow/Parquet schema information when importing into CTable. +2. Keep the metadata optional and backward-compatible. +3. Avoid changing the physical data layout for existing columns. +4. Avoid exposing a public metadata API for now. +5. Enable Parquet round-tripping for nested columns, including `struct` and `list` columns. +6. Implement full internal `StructSpec` support for Arrow/Parquet nested schemas. +7. Preserve enough Arrow schema fidelity so supported nested columns can round-trip through `parquet -> CTable -> parquet` without schema loss. +8. Ensure old tables without metadata continue loading unchanged. +9. Ensure readers that do not understand the metadata can ignore it safely. + +--- + +## Non-goals + +- Expose user-facing metadata management methods. +- Guarantee exact Arrow schema reconstruction for all possible Arrow schemas outside the supported nested V1 scope. +- Store large amounts of per-row or per-column statistics in schema metadata. + +--- + +## Proposed schema dict extension + +Current persistent CTable schema serialization stores column definitions in a JSON-like dict. Extend this dict with an optional top-level `metadata` field: + +```json +{ + "columns": [ + {"name": "code", "spec": {"kind": "string", "max_length": 32}}, + {"name": "generic_name", "spec": {"kind": "list", "item": {"kind": "struct", "fields": [...]}}} + ], + "metadata": { + "arrow": { + "schema_ipc_base64": "...", + "fields": { + "generic_name": { + "original_arrow_type": "list>", + "ctable_storage": "list>", + "conversion": "native_struct" + } + } + } + } +} +``` + +The exact top-level schema dict may contain additional existing keys; this plan only adds an optional sibling key named `metadata`. + +### Compatibility rules + +- If `metadata` is absent, treat it as `{}`. +- If `metadata` is present but contains unknown keys, preserve them when possible. +- If `metadata.arrow` is absent, Arrow/Parquet code falls back to ordinary inference. +- Existing table loading should not require or validate Arrow metadata. + +--- + +## Internal representation + +Prefer storing metadata on `CompiledSchema`: + +```python +@dataclass +class CompiledSchema: + row_cls: type | None + columns: list[CompiledColumn] + columns_by_name: dict[str, CompiledColumn] + metadata: dict[str, Any] = field(default_factory=dict) +``` + +No public property is required. CTable internals can access: + +```python +self._schema.metadata +``` + +or helper methods if needed: + +```python +def _schema_metadata(self) -> dict[str, Any]: + return self._schema.metadata +``` + +Do not expose `t.metadata` initially. + +--- + +## Serialization changes + +### `schema_to_dict()` + +Emit metadata only when non-empty: + +```python +def schema_to_dict(schema: CompiledSchema) -> dict: + d = {...} + if schema.metadata: + d["metadata"] = schema.metadata + return d +``` + +### `schema_from_dict()` + +Read metadata defensively: + +```python +def schema_from_dict(d: dict) -> CompiledSchema: + return CompiledSchema( + row_cls=None, + columns=columns, + columns_by_name=columns_by_name, + metadata=dict(d.get("metadata", {})), + ) +``` + +### Existing code paths + +Update any manual `CompiledSchema(...)` construction to pass no metadata or explicitly preserve it. Because `metadata` defaults to `{}`, most call sites should continue to work. + +Important places to check: + +- dataclass schema compilation +- Pydantic schema compilation +- `CTable.open()` +- `CTable.load()` +- `CTable.save()` +- `CTable.select()` / views +- `CTable.from_arrow()` +- `CTable.from_arrow_batches()` +- `CTable.from_parquet()` +- schema mutation methods + +For views/selections, metadata should probably be filtered to selected columns where practical, but this can be deferred if metadata remains internal. + +--- + +## Arrow schema encoding + +Do not rely solely on `schema.to_string()` for round-trip fidelity. Store a serialized Arrow schema when possible. + +Potential encoding: + +```python +import base64 +import pyarrow as pa + +sink = pa.BufferOutputStream() +with pa.ipc.new_stream(sink, schema): + pass +schema_ipc = sink.getvalue().to_pybytes() +schema_ipc_base64 = base64.b64encode(schema_ipc).decode("ascii") +``` + +Store under: + +```json +{ + "metadata": { + "arrow": { + "schema_ipc_base64": "...", + "schema_string": "... optional debug string ..." + } + } +} +``` + +Open question: verify the best PyArrow API for schema-only IPC serialization. If `pa.ipc` exposes a direct schema serialization API, prefer that. + +--- + +## Column-level Arrow metadata + +For fields that are transformed during import, store explicit conversion notes: + +```json +{ + "metadata": { + "arrow": { + "fields": { + "generic_name": { + "original_arrow_type": "list>", + "ctable_storage": "list>", + "conversion": "native_struct" + }, + "no_nutrition_data": { + "original_arrow_type": "bool", + "ctable_storage": "list", + "conversion": "nullable_scalar_wrapped_as_singleton_list" + }, + "ecoscore_data": { + "original_arrow_type": "string", + "ctable_storage": "list", + "conversion": "long_nullable_scalar_wrapped_as_singleton_list" + } + } + } + } +} +``` + +This lets exporters distinguish native CTable schemas from import-time adaptations. + +--- + +## StructSpec and nested list support + +Add a first-class internal `StructSpec` so CTable can represent Arrow structs directly: + +```python +blosc2.struct( + { + "lang": blosc2.string(), + "text": blosc2.string(), + } +) +blosc2.list(blosc2.struct({"lang": blosc2.string(), "text": blosc2.string()})) +``` + +Then import can map: + +```text +struct<...> -> blosc2.struct(...) +list> -> blosc2.list(blosc2.struct(...), nullable=True) +``` + +For `ListArray`, list cells can continue to be stored row-wise as Python values such as `list[dict]`, but the `ListSpec.item_spec` should be a typed `StructSpec`, not an opaque object spec. Coercion validates each dict-like item against the struct fields, and Arrow export uses the saved/original Arrow struct schema where available. + +Metadata records the original Arrow field and the native struct conversion: + +```json +{ + "original_arrow_type": "list>", + "ctable_storage": "list>", + "conversion": "native_struct" +} +``` + +Opaque object support can remain a fallback for Arrow types outside the supported nested V1 scope, but the primary goal is native `StructSpec` support and full Parquet round-tripping for supported nested columns. + +--- + +## Import behavior proposal + +When `CTable.from_parquet()` or `CTable.from_arrow_batches()` receives an Arrow schema: + +1. Preserve the original Arrow schema IPC bytes in schema metadata. +2. For columns imported without transformation, no field-level entry is required. +3. For transformed columns, add a field-level entry describing the conversion. +4. For unsupported columns that are skipped by a custom importer, optionally record them under `metadata.arrow.skipped_fields` if the importer chooses to. + +Potential internal helper: + +```python +def _arrow_metadata_from_schema( + schema, *, field_conversions=None, skipped_fields=None +) -> dict: + return { + "arrow": { + "schema_ipc_base64": encode_arrow_schema(schema), + "schema_string": schema.to_string(), + "fields": field_conversions or {}, + "skipped_fields": skipped_fields or {}, + } + } +``` + +--- + +## Export behavior proposal + +`CTable.to_parquet()` should check: + +```python +arrow_meta = self._schema.metadata.get("arrow", {}) +``` + +For ordinary columns: + +- Continue using normal CTable → Arrow conversion. + +For columns with known conversions: + +- `struct` and `list` columns: + - Convert Python dict/list-of-dict values to `pa.array(..., type=original_arrow_type)`. +- singleton-list wrapped nullable scalars: + - Optionally unwrap back to nullable scalar Arrow columns. +- long string wrapped as `list`: + - Optionally unwrap back to original nullable scalar string column if metadata says so. + +If metadata is missing, malformed, or incompatible with current data, fall back to safe behavior or raise a clear error depending on export mode. + +Possible control flag: + +```python +t.to_parquet(path, use_original_arrow_schema=True) +``` + +For supported nested columns, the implementation goal is to use the original Arrow schema by default when metadata is available and compatible. If metadata is unavailable or incompatible, fail clearly or fall back according to the selected export mode. + +--- + +## Tests + +### Schema metadata persistence + +1. Creating a CTable without metadata produces a schema dict without `metadata` or with an empty metadata field omitted. +2. Loading old schema dicts without `metadata` works. +3. Saving and opening a CTable with metadata preserves the metadata exactly. +4. `CTable.save()` and `CTable.load()` preserve metadata. + +### Arrow metadata + +1. `from_parquet()` stores original Arrow schema metadata when enabled. +2. `from_arrow_batches()` stores original Arrow schema metadata when given a schema. +3. Metadata survives close/open for `.b2z` and directory-backed stores. +4. Unknown metadata keys survive round-trip. + +### Struct/nested round-trip tests + +1. Import a top-level Arrow `struct` column as `StructSpec`. +2. Import a `list` column as `list(StructSpec)`. +3. Stored values are Python dicts or lists of dicts with validated fields. +4. Metadata records the original Arrow type. +5. Export reconstructs the original `struct` / `list` Arrow type. +6. Parquet → CTable → Parquet preserves schema and values for supported nested fields. + +--- + +## Migration/backward compatibility + +This change should be backward-compatible because: + +- `metadata` is optional. +- Existing schema dicts remain valid. +- Existing code paths can ignore unknown metadata. +- No physical storage format changes are required. +- Public APIs do not change. + +Potential compatibility concern: + +- If older versions of python-blosc2 strictly reject unknown top-level schema keys, tables written with metadata may not load in old versions. Check existing `schema_from_dict()` behavior. If necessary, store metadata in a nested location old readers already ignore, or accept that forward compatibility to older versions is limited. + +--- + +## Open questions + +1. What exact PyArrow API should be used for schema-only serialization? +2. Should metadata be preserved exactly or normalized/sanitized on save? +3. Should selected views filter metadata to selected columns? +4. Should `from_parquet()` always store Arrow metadata, or only when nested/transformed columns are present? +5. Should metadata be compressed if the Arrow schema is large? +6. Should there eventually be a public metadata API, or should this remain strictly internal? +7. For singleton-list wrapped scalars, should `to_parquet()` unwrap by default when original Arrow metadata is present? + +--- + +## Suggested milestones + +### Milestone 1: Generic schema metadata plumbing + +- Add optional `metadata` to `CompiledSchema`. +- Update `schema_to_dict()` and `schema_from_dict()`. +- Ensure save/open/load preserve metadata. +- Add tests for backward compatibility and metadata preservation. + +### Milestone 2: Arrow schema metadata helpers + +- Add private helpers to encode/decode Arrow schema metadata. +- Store Arrow schema metadata in `from_arrow_batches()` / `from_parquet()`. +- Add tests using simple Arrow schemas. + +### Milestone 3: StructSpec and ListArray nested support + +- Add internal `StructSpec` with field specs, metadata serialization, and validation/coercion. +- Allow `ListSpec(StructSpec)` for msgpack-backed ListArray. +- Map Arrow `struct` and `list` to `StructSpec` / `list(StructSpec)` in import. +- Store field conversion metadata. + +### Milestone 4: Metadata-aware Parquet export + +- Teach `to_parquet()` to consult original Arrow metadata. +- Reconstruct `struct` and `list` arrays from stored dict/list-of-dict values. +- Optionally unwrap singleton-list transformed scalar columns. +- Add Parquet → CTable → Parquet round-trip tests for selected nested fields. diff --git a/plans/ctable-varlen-cols.md b/plans/ctable-varlen-cols.md new file mode 100644 index 00000000..451c4885 --- /dev/null +++ b/plans/ctable-varlen-cols.md @@ -0,0 +1,1080 @@ +# CTable Variable-Length Columns Implementation Plan + +## Summary + +Add support for variable-length list columns to `CTable` via a new logical list type: + +- public schema API: `b2.list(...)` +- physical row-oriented container: `blosc2.ListArray` +- internal storage backends: + - `VLArray` for row-oriented point updates + - `BatchArray` for append/read efficiency + +The design goal is to let users declare typed list columns in a `CTable` schema without exposing backend details unless they want to tune them. + +`ListArray` should also be treated as a first-class public container for row-oriented list-valued data, not merely as an internal `CTable` helper. At the same time, it should not be positioned as a replacement for `VLArray` or `BatchArray`; those remain the lower-level variable-length building blocks for more generic or explicitly batch-oriented workloads. + +Example target API: + +```python +from dataclasses import dataclass +import blosc2 as b2 + + +@dataclass +class Product: + code: str = b2.field(b2.string(max_length=32)) + ingredients: list[str] = b2.field(b2.list(b2.string(), nullable=True)) + allergens: list[str] = b2.field( + b2.list(b2.string(), storage="batch", serializer="msgpack") + ) +``` + +--- + +## Final decisions already made + +### Public API + +- Use `b2.list(...)`, not `b2.list_(...)`. +- Use `cell` only as a documentation/design term when helpful, not as a formal API term. +- `ListArray` is the public row-oriented container abstraction for variable-length list columns. +- `ListArray` should be documented as a first-class container in its own right, useful both inside and outside `CTable`. +- `ListArray` should not be presented as replacing `VLArray` or `BatchArray`; instead, it should be positioned as the natural high-level container for row-oriented list data, while `VLArray` and `BatchArray` remain the lower-level building blocks. + +### Defaults + +- default `storage="batch"` +- default `serializer="msgpack"` +- default `nullable=False` +- `serializer="arrow"` remains optional and must not introduce a hard `pyarrow` dependency +- `serializer="arrow"` is only allowed with `storage="batch"` + +### Null semantics + +For V1, distinguish: + +- `None` → null list cell +- `[]` → empty list cell + +Do not support nullable items inside the list by default. + +So V1 supports: + +- `nullable=True|False` for the whole list cell +- no `item_nullable=True` behavior yet + +### Update semantics + +Support **explicit whole-cell replacement only**: + +```python +t.ingredients[5] = ["salt", "sugar"] +``` + +Do not support implicit write-through mutation of returned Python objects: + +```python +x = t.ingredients[5] +x.append("salt") # local only +# user must reassign +``` + +### Batch layout policy + +- default `batch_rows` should follow the column chunk size +- batch-backed list columns should use an internal append buffer in `ListArray` +- buffering lives in `ListArray`, not in `BatchArray` +- flushes occur: + - when buffer reaches `batch_rows` + - on explicit `flush()` + - on persistence boundaries such as `save()` / `close()` + - before exports that must observe all rows (e.g. `to_arrow()`) + +### V1 scope + +Support in V1: + +- schema declaration via `b2.list(...)` +- append / extend +- row reads +- whole-cell replacement +- persistence (`save`, `open`, `load`) +- standalone `ListArray` reopen through `blosc2.open()` / `blosc2.from_cframe()` +- `head`, `tail`, `select`, and scalar-driven `where()`/view operations +- `compact()` +- `to_arrow()` / `from_arrow()` +- display / info support + +Explicitly out of scope for V1: + +- indexes on list columns +- computed columns over list columns +- sorting by list columns +- list-aware predicates such as contains / overlaps +- nullable items inside a list +- nested list-of-list / struct / map types +- standalone insert/delete API on `ListArray` + +--- + +## Main design principle + +Do **not** force list columns into the current scalar `np.dtype` model. + +Today the schema/compiler/storage path assumes that every column: + +- has a scalar `np.dtype` +- is physically stored as an `NDArray` +- can be coerced with scalar NumPy conversion rules + +That is not true for list columns. + +Instead, the refactor should distinguish: + +- logical scalar columns +- logical list columns + +and separately distinguish their physical storage: + +- scalar column → `NDArray` +- list column → `ListArray` + +This keeps the scalar path fast and clean while adding a first-class path for variable-length lists. + +--- + +## High-level architecture + +### Logical layer + +Add a new schema descriptor: + +- `ListSpec` + +Keep existing scalar specs, but conceptually move toward: + +- `ColumnSpec` + - `ScalarSpec` + - `ListSpec` + +This can be implemented either by introducing explicit base classes or by broadening the meaning of the current spec system. The important part is that `CompiledColumn` and `CTable` must stop assuming every spec has a scalar `dtype`. + +### Physical layer + +Add a new container: + +- `blosc2.ListArray` + +`ListArray` is cell-oriented: + +- `arr[i]` returns one list cell or `None` +- `arr[i:j]` returns a Python `list` of cells +- `arr[i] = value` replaces one cell +- `append(value)` appends one cell +- `extend(values)` appends many cells + +Internally it wraps one of: + +- `VLArray` +- `BatchArray` + +### CTable layer + +Teach `CTable` to manage two families of physical columns: + +- scalar columns backed by `NDArray` +- list columns backed by `ListArray` + +`CTable` should understand list columns at the schema and storage levels, but should not need backend-specific logic beyond creation/open/flush/update hooks. + +--- + +## Phase 1: Schema system changes + +## 1.1 Add `b2.list(...)` + +Add a new public builder in `src/blosc2/schema.py`: + +```python +def list( + item_spec, + *, + nullable=False, + storage="batch", + serializer="msgpack", + batch_rows=None, + items_per_block=None, +): ... +``` + +Initial accepted parameters: + +- `item_spec` + - typically a scalar spec such as `b2.string()` or `b2.int32()` +- `nullable` + - whether the whole list cell may be `None` +- `storage` + - `"batch"`, `"vl"` +- `serializer` + - `"msgpack"`, `"arrow"` +- `batch_rows` + - optional row count per persisted batch for batch backend +- `items_per_block` + - forwarded to `BatchArray` when backend is batch + +Validation rules for V1: + +- `storage` must be `"batch"` or `"vl"` +- `serializer` must be `"msgpack"` or `"arrow"` +- if `storage == "vl"`, serializer must be `"msgpack"` +- if `serializer == "arrow"`, storage must be `"batch"` +- `item_spec` should initially be restricted to scalar specs + +## 1.2 Introduce `ListSpec` + +Add a new schema descriptor class with at least: + +- `python_type = list` +- `item_spec` +- `nullable` +- `storage` +- `serializer` +- `batch_rows` +- `items_per_block` + +Methods analogous to existing specs: + +- `to_metadata_dict()` +- optional `display_label()` helper or equivalent + +Suggested serialized form: + +```json +{ + "kind": "list", + "item": {"kind": "string", "max_length": 64}, + "nullable": true, + "storage": "batch", + "serializer": "msgpack", + "batch_rows": 65536, + "items_per_block": 256 +} +``` + +## 1.3 Broaden `field()` acceptance + +`b2.field(...)` should accept any valid column spec, not just scalar specs. + +The implementation contract becomes: + +- scalar field spec allowed +- list field spec allowed + +No user-visible API change beyond this. + +--- + +## Phase 2: Schema compiler changes + +## 2.1 Relax `CompiledColumn` + +`CompiledColumn` currently assumes a scalar `dtype`. Refactor it so list columns are first-class. + +Target shape: + +- `name` +- `py_type` +- `spec` +- `default` +- `config` +- `display_width` +- optional scalar dtype information only when applicable + +There are two acceptable implementation styles: + +### Option A: minimal change + +Keep `dtype` on `CompiledColumn`, but allow it to be `None` for non-scalar columns. + +### Option B: cleaner long-term change + +Replace mandatory `dtype` with something like: + +- `storage_dtype: np.dtype | None` +- `logical_kind` +- convenience properties such as `is_scalar`, `is_list` + +Recommendation: choose the smallest refactor that avoids fake object dtypes. + +## 2.2 Update annotation validation + +Current annotation validation is scalar-oriented. Extend it to support: + +```python +ingredients: list[str] = b2.field(b2.list(b2.string())) +``` + +Compiler responsibilities: + +- inspect `typing.get_origin(annotation)` +- inspect `typing.get_args(annotation)` +- validate that `list[...]` annotations match `ListSpec` +- validate that the item annotation matches `item_spec` + +For V1, support: + +- built-in `list[T]` +- likely `typing.List[T]` as a compatibility path if desired + +Restrict V1 item annotations to scalar item types. + +## 2.3 Schema serialization/deserialization + +Extend `schema_to_dict()` / `schema_from_dict()` so list specs round-trip through stored schema metadata. + +This includes: + +- emitting `kind="list"` +- recursively serializing `item_spec` +- restoring `ListSpec` on reopen/load + +--- + +## Phase 3: Add `ListArray` + +## 3.1 Public role + +Create a new file: + +- `src/blosc2/list_array.py` + +And export it from: + +- `src/blosc2/__init__.py` + +`ListArray` should be the row-oriented facade used by `CTable` and also a standalone public container for users working with row-oriented list-valued data. + +It should not expose `BatchArray`'s native batch-oriented semantics. + +Documentation should encourage `ListArray` for typed, row-oriented list data, while still keeping `VLArray` and `BatchArray` visible as the lower-level containers for arbitrary object payloads and explicitly batch-oriented workflows. + +## 3.2 Core API + +Initial API: + +- constructor taking list spec / backend hints / storage kwargs +- `append(value)` +- `extend(values)` +- `flush()` +- `close()` +- `__enter__()` / `__exit__()` +- `__getitem__(index|slice)` +- `__setitem__(index, value)` +- `__len__()` +- `__iter__()` +- `copy(**kwargs)` +- `info` +- `to_arrow()` when possible +- `from_arrow()` if useful as a constructor helper + +V1 read behavior: + +- `arr[i]` → `list | None` +- `arr[i:j]` → `list[list | None]` + +No item-level API like `arr.items` is required for V1. + +## 3.3 Validation/coercion inside `ListArray` + +`ListArray` should validate cell values against the provided `ListSpec`. + +Rules for V1: + +- `None` allowed only if `nullable=True` +- otherwise value must be list-like +- strings / bytes are not accepted as list-like cells +- each item must satisfy `item_spec` +- `None` items rejected for V1 + +The goal is that `ListArray` can be safely used both inside and outside `CTable`. + +## 3.4 Backend selection + +Selection policy: + +- `storage="batch"` → batch backend +- `storage="vl"` → VL backend +- `serializer="arrow"` only valid with batch backend +- default backend = batch +- default serializer = msgpack + +Implementation note: backend choice should be explicit in metadata and persisted schema, not inferred later from heuristics. + +--- + +## Phase 4: `ListArray` backend implementation + +## 4.1 VL backend + +Map one logical cell to one VLArray entry. + +Properties: + +- simplest implementation +- natural row-level replacement +- no internal buffer needed + +Implementation behavior: + +- `append(cell)` → `VLArray.append(cell)` +- `extend(cells)` → `VLArray.extend(cells)` +- `__getitem__(i)` → `VLArray[i]` +- `__setitem__(i, cell)` → `VLArray[i] = cell` + +This backend is the easiest one and should be implemented first to stabilize list semantics. + +## 4.2 Batch backend + +Map many logical cells to one persisted batch in `BatchArray`. + +Persisted representation for V1 msgpack path: + +- one batch = list of cells +- each cell = `None` or Python list of scalar items + +Arrow-serializer path, implemented after the msgpack path but within the same overall design: + +- one batch = Arrow array where each slot corresponds to one list cell +- type = `list` + +## 4.3 Internal append buffer + +`ListArray(storage="batch")` maintains: + +- persisted batches in a `BatchArray` +- a pending in-memory Python list of cells not yet flushed + +Suggested internal state: + +- `_store`: `BatchArray` +- `_pending_cells: list` +- `_batch_rows: int` +- mapping helpers derived from persisted batch lengths + +Append flow: + +- validate cell +- append to `_pending_cells` +- if `len(_pending_cells) >= batch_rows`, flush full batches + +Extend flow: + +- validate each cell +- fill pending buffer +- flush full batches as needed + +Flush flow: + +- write full `batch_rows` groups to `BatchArray.append(...)` +- keep any tail cells pending unless caller requested a full final flush +- on explicit final flush / close / save, write tail as last batch + +## 4.4 Logical indexing in batch backend + +`ListArray` must expose row-level indexing across: + +- persisted cells in `BatchArray` +- pending cells in `_pending_cells` + +This requires row → batch lookup for persisted rows. + +Suggested approach: + +- rely on `BatchArray`'s stored batch lengths and prefix sums for persisted portion +- append pending tail logically after persisted rows + +Point update behavior: + +- if target row is in pending cells: replace in memory +- if target row is persisted: load batch, replace cell, rewrite entire batch + +This should be supported but documented as more expensive than VL-backed updates. + +## 4.5 Flush semantics + +`ListArray.flush()` should: + +- write all pending cells, including the tail +- leave `_pending_cells` empty + +Automatic flushes should occur on: + +- buffer full +- explicit `flush()` +- `close()` / context-manager exit +- `CTable.save()` +- `CTable.to_arrow()` +- any persistence-sensitive operation that must see all rows on disk + +--- + +## Phase 5: `CTable` storage abstraction changes + +## 5.1 Broaden `TableStorage` + +Current `TableStorage.create_column()` / `open_column()` assume `NDArray`. Extend storage abstraction to support list columns. + +Recommended shape: + +- `create_scalar_column(...)` +- `open_scalar_column(...)` +- `create_list_column(...)` +- `open_list_column(...)` + +Alternative minimal path: + +- keep `create_column(...)` but branch on compiled spec kind + +Recommendation: use explicit separate methods if the diff stays manageable; it makes the abstraction cleaner. + +## 5.2 File-backed layout + +Current scalar layout is under: + +- `/_cols/` + +Keep that logical namespace for list columns too. + +Possible physical forms: + +- `/_cols/` points directly to the underlying backend object (`VLArray` or `BatchArray`), tagged so it reopens logically as `ListArray` +- `/_cols/` plus optional side metadata stored in schema + +For V1, prefer storing backend configuration in the schema metadata and keeping the on-disk object itself as the concrete backend container. + +## 5.3 In-memory layout + +In-memory `CTable` should keep list columns as live `ListArray` objects. + +No persistence is needed there beyond normal `save()` behavior. + +--- + +## Phase 6: `CTable` core changes + +## 6.1 Column creation/opening + +During table creation/open/open-from-schema: + +- scalar compiled columns create/open `NDArray` +- list compiled columns create/open `ListArray` + +`self._cols` may continue to map names to physical column objects, but code using `self._cols[name]` must stop assuming every entry is an `NDArray`. + +### 6.1.1 Physical length vs capacity + +Scalar columns remain capacity-based and grow with `_valid_rows`. + +List columns should instead be treated as append-sized physical stores: + +- their physical length tracks the number of written physical rows +- they are not preallocated out to `len(_valid_rows)` +- `_grow()` should continue to resize scalar columns and `_valid_rows`, but should not pad list columns with placeholder cells + +This means `CTable` internals must stop assuming every stored column has physical length equal to `len(_valid_rows)`. + +Logical row resolution should always go through physical row positions that are actually written. + +## 6.2 Row coercion path + +Current `_coerce_row_to_storage()` is scalar-only. Refactor it to branch on column kind. + +For scalar columns: + +- keep current NumPy scalar coercion + +For list columns: + +- validate and normalize through list-spec logic +- store Python list / `None` as-is for handoff to `ListArray` + +## 6.3 Append path + +Current `append()` loops over columns and assigns directly into scalar arrays. Refactor: + +- scalar column: `ndarray[pos] = scalar` +- list column: `list_array.append(cell)` + +Important invariants: + +- all stored columns must stay logically aligned by row index +- list columns append one physical cell per newly written physical row position +- `_last_pos` remains the source of truth for the next physical row id + +This means `append()` must ensure each list column receives exactly one new cell per appended row. + +### Write coordination / partial-failure safety + +This deserves explicit care because mixed scalar/list writes are no longer a single homogeneous NDArray operation. + +V1 should guarantee at least: + +- validate and normalize the full incoming row or batch before mutating storage +- mark `_valid_rows` only after all column writes for the row(s) succeed +- avoid leaving logically visible partial rows after a failure + +For list columns in particular, the implementation should prefer staging writes in memory where possible, or otherwise provide best-effort rollback for per-row appends that fail after some columns were already updated. + +## 6.4 Extend path + +Current `extend()` materializes column arrays and writes slices into NDArrays. For list columns, this should become backend-aware. + +Suggested behavior: + +- scalar columns: keep vectorized path +- list columns: collect Python list of cells and call `ListArray.extend(cells)` + +This may be less vectorized than the scalar path, which is acceptable for V1. + +## 6.5 Delete behavior + +No special physical delete support is required for V1. + +`CTable.delete()` already uses `_valid_rows`, so list columns can simply remain append-only physically while logical row deletion is controlled by the validity mask. + +## 6.6 Row reads + +`_Row.__getitem__` and column reads must support list columns. + +Expected behavior: + +- row access on a list column returns the corresponding list cell or `None` +- sliced column access on a list column returns a Python `list` of cells + +## 6.7 Column wrapper behavior + +Current `Column` logic is NumPy/NDArray-centric. For V1, update it carefully for list columns. + +Key points: + +- `Column.dtype` is not meaningful for list columns and should return `None` rather than a fake NumPy dtype +- many scalar comparison/aggregate methods should reject list columns cleanly +- `Column.__getitem__` must work for logical row indexing on list columns +- `Column.__setitem__` should support whole-cell replacement for list columns + +Recommendation: + +- branch internally on compiled spec kind +- do not try to make list columns mimic NumPy arrays + +## 6.8 String representation and info + +Update display and info methods so list columns show a logical type label like: + +- `list[string]` +- `list[int32]` + +For previews, show Python repr-style cells with truncation. + +Statistics in `describe()` for list columns can be omitted in V1 or show a message like: + +- `(stats not available for list columns)` + +--- + +## Phase 7: Arrow interoperability + +## 7.1 Keep Arrow optional + +`pyarrow` must remain optional. + +Rules: + +- importing / using list columns with msgpack default must not require `pyarrow` +- `serializer="arrow"` should raise a clear `ImportError` when `pyarrow` is absent +- `CTable.to_arrow()` / `from_arrow()` may already be optional-import based and should stay that way + +## 7.2 `CTable.to_arrow()` + +Extend `to_arrow()` so list columns export to Arrow list arrays. + +For msgpack-backed list columns: + +- materialize Python cells +- build `pa.array(values)` with appropriate list type when possible + +For Arrow-backed batch list columns later: + +- a faster path may reuse Arrow-native data more directly if feasible + +V1 success criterion: + +- correct Arrow output, even if implemented through Python materialization + +## 7.3 `CTable.from_arrow()` + +Extend Arrow import to recognize Arrow list fields and create `ListSpec` columns. + +Initial supported Arrow list cases for V1: + +- `list` +- possibly `large_list` if trivial to normalize +- optionally numeric item types if easy to map consistently + +Import policy: + +- create `b2.list(item_spec, storage="batch", serializer="msgpack")` by default unless caller later gains a way to override +- append row cells into `ListArray` + +The important part for V1 is round-tripping list columns through `CTable`. + +--- + +## Phase 8: Persistence and reopen behavior + +## 8.1 Schema metadata + +Persist list-specific metadata in the table schema. + +Each list column should carry at least: + +- `kind="list"` +- serialized `item_spec` +- `nullable` +- `storage` +- `serializer` +- `batch_rows` if relevant +- `items_per_block` if relevant + +## 8.2 Container reopen behavior + +When a table is reopened: + +- compiled schema reconstructs `ListSpec` +- storage layer opens the concrete list column backend +- `CTable` re-wraps it as `ListArray` + +For standalone use, persistent `ListArray` containers should also reopen as `ListArray` through the generic dispatch path. + +### 8.2.1 Layered metadata tagging + +`ListArray` should have its own explicit container tag in fixed metadata, while preserving the underlying backend tag. + +Examples: + +- batch-backed `ListArray`: + - `meta["batcharray"] = {...}` + - `meta["listarray"] = {...}` +- VL-backed `ListArray`: + - `meta["vlarray"] = {...}` + - `meta["listarray"] = {...}` + +This keeps both identities available: + +- physical identity: the underlying storage container (`BatchArray` or `VLArray`) +- logical identity: the object should reopen as `ListArray` + +Suggested `meta["listarray"]` payload: + +```json +{ + "version": 1, + "backend": "batch", + "serializer": "msgpack", + "nullable": false, + "item_spec": {"kind": "string", "max_length": 64}, + "batch_rows": 65536, + "items_per_block": 256 +} +``` + +The exact payload can be trimmed, but it should at least record: + +- format version +- backend kind +- serializer +- nullability +- serialized item spec +- backend-specific layout hints when relevant + +Recommendation: store this in `schunk.meta`, not only in `vlmeta`, because it defines the container kind used for reopen dispatch. + +### 8.2.2 Generic reopen dispatch + +`blosc2.open()` and `blosc2.from_cframe()` should prefer `ListArray` when `meta["listarray"]` is present. + +Suggested dispatch priority: + +1. `listarray` +2. `batcharray` +3. `vlarray` +4. existing fallback behavior + +This makes the generic open path return the logical container type rather than exposing the raw backend by default. + +Advanced users can still reach the lower-level backend explicitly if needed. + +### 8.2.3 `ListArray` reopen constructor + +`ListArray` should support an internal reopen hook such as: + +```python +ListArray(_from_schunk=schunk) +``` + +This path should: + +- validate the `listarray` tag +- validate consistency with the backend tag (`batcharray` or `vlarray`) +- reconstruct the correct backend wrapper +- return a row-oriented `ListArray` object + +## 8.3 Save/load and flush coordination + +Ensure all list columns are flushed before: + +- saving to disk +- serializing table data for export if needed +- closing persistent stores + +Add a helper on `CTable` such as an internal `_flush_varlen_columns()` used by: + +- `save()` +- `to_arrow()` +- close/discard paths + +--- + +## Phase 9: Testing plan + +Add focused tests rather than trying to cover the entire matrix at once. + +## 9.1 Schema/compiler tests + +New tests for: + +- `b2.list(...)` construction +- invalid storage/serializer combinations +- annotation matching for `list[str]` +- schema serialization/deserialization of `ListSpec` + +## 9.2 `ListArray` tests + +Standalone tests covering both backends: + +- append / extend +- `None` vs `[]` +- reject nullable items in V1 +- row reads +- slice reads +- whole-cell replacement +- negative indexing +- flush behavior, including `close()` / context-manager flush-on-exit +- reopen behavior for persistent stores +- `blosc2.open()` / `blosc2.from_cframe()` dispatch returning `ListArray` +- read-only handling where applicable + +Batch backend specific tests: + +- pending-buffer reads before flush +- automatic flush on buffer full +- update in pending region +- update in persisted region +- correct length across persisted + pending regions + +VL backend specific tests: + +- direct row replacement + +## 9.3 `CTable` tests + +Add `CTable` tests for: + +- schema with one list column +- schema with scalar + list columns mixed +- append row with list column +- extend rows with list column +- read rows back +- `head`, `tail`, `select` +- scalar-driven `where()` / view operations with list columns carried through correctly +- `compact()` with list columns +- row deletion via `_valid_rows` +- reopen persistent table +- `save()` / `load()` round-trip +- `to_arrow()` / `from_arrow()` with list columns + +## 9.4 Non-goal tests for V1 + +Do not add tests for unsupported features such as: + +- list column indexes +- sorting by list column +- computed expressions over lists +- nullable items inside lists + +Instead, add clear failure-path tests where appropriate. + +--- + +## Phase 10: Documentation plan + +Update documentation incrementally. + +## 10.1 Reference docs + +Add docs for: + +- `b2.list(...)` +- `ListArray` +- `CTable` list column support + +## 10.2 Tutorial/examples + +Add at least one example such as: + +- products with `ingredients: list[str]` + +Show: + +- schema declaration +- append / extend +- distinction between `None` and `[]` +- whole-cell replacement +- save/reopen +- Arrow export if `pyarrow` is installed + +## 10.3 Design notes in docs + +Explain briefly: + +- `cell` as a descriptive concept only +- batch vs VL backend tradeoffs +- why `msgpack` is the default +- why returned Python lists must be reassigned after mutation + +--- + +## Recommended implementation order + +To reduce risk, implement in this order: + +1. **Schema groundwork** + - add `ListSpec` + - add `b2.list(...)` + - update schema serialization and compiler + +2. **Standalone `ListArray` with VL backend first** + - easiest path to stabilize list semantics + - validates `None` vs `[]`, whole-cell replacement, persistence + - includes layered tagging and standalone reopen through `blosc2.open()` / `from_cframe()` + +3. **`CTable` integration for VL-backed list columns** + - proves scalar/list coexistence in compiler and core table paths + +4. **Batch-backed `ListArray` with pending buffer** + - implement `batch_rows` buffering + - add flush semantics and persisted/pending indexing + +5. **`CTable` integration for batch-backed list columns** + - update save/open/load and flush coordination + +6. **Arrow import/export support for list columns** + - keep optional + - start with materialization-based path + +7. **Docs and broader test coverage** + +This staged rollout makes it easier to separate: + +- logical list semantics +- `CTable` schema/compiler changes +- batch buffering complexity + +--- + +## Practical code touch points + +Expected Python files to update or add: + +### New files + +- `src/blosc2/list_array.py` +- `tests/test_list_array.py` +- `tests/test_ctable_varlen.py` or equivalent +- `doc/reference/list_array.rst` or a combined varlen/list reference page +- example file under `examples/ctable/` + +### Existing files likely to change + +- `src/blosc2/__init__.py` +- `src/blosc2/schema.py` +- `src/blosc2/schema_compiler.py` +- `src/blosc2/ctable.py` +- `src/blosc2/ctable_storage.py` +- likely `src/blosc2/schema_validation.py` +- likely `src/blosc2/schema_vectorized.py` +- `src/blosc2/schunk.py` for standalone reopen dispatch +- `src/blosc2/core.py` for generic `open()` / `from_cframe()` dispatch for `ListArray` +- docs under `doc/reference/ctable.rst` and related tutorial pages + +--- + +## Open follow-up items after V1 + +These are intentionally postponed, not rejected: + +- `item_nullable=True` +- nested list-of-list +- struct / map item types +- list-aware query predicates +- indexing for membership tests +- sorting/grouping semantics for list columns +- optimized Arrow-native batch representation +- convenience APIs for explicitly opening the raw backend (`VLArray` / `BatchArray`) behind a `ListArray` when advanced users want to bypass the logical container + +--- + +## Short rationale for the chosen defaults + +### Why `b2.list(...)` + +It reads naturally next to `list[str]` annotations and fits the rest of the schema-builder API. + +### Why `ListArray` should be first-class + +It gives users a clear row-oriented abstraction for list-valued data that is useful both on its own and as the natural physical model for list columns inside `CTable`, while still preserving `VLArray` and `BatchArray` as lower-level containers. + +### Why `msgpack` default + +It avoids a hard `pyarrow` dependency and works uniformly with both `VLArray` and `BatchArray`. + +### Why `storage="batch"` default + +It better matches append/scan-oriented table workloads and Parquet-like usage, while `VLArray` remains available for update-heavy cases. + +### Why whole-cell replacement only + +It keeps behavior explicit and avoids surprising write-through mutation of returned Python lists. + +### Why `None` vs `[]` + +The distinction is valuable and common, while item-level nullability can be added later without breaking the model. + +--- + +## Outcome expected from V1 + +After this work: + +- `CTable` should be able to host list columns in a way that is: + - typed at the schema level + - persistent + - row-addressable + - backend-tunable + - install-light by default + - compatible with optional Arrow export/import + +- `ListArray` should stand as a public reusable container for row-oriented list-valued data. + +This should happen without compromising the current scalar-column fast path or displacing `VLArray` / `BatchArray` from their lower-level roles. diff --git a/pyproject.toml b/pyproject.toml index d5c1cc2a..2e82d78b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -129,6 +129,7 @@ ignore = [ "RUF015", "RUF059", "SIM108", + "SIM117", ] [tool.ruff.lint.extend-per-file-ignores] diff --git a/src/blosc2/__init__.py b/src/blosc2/__init__.py index 6e64f2fb..15e7cc45 100644 --- a/src/blosc2/__init__.py +++ b/src/blosc2/__init__.py @@ -567,6 +567,7 @@ def _raise(exc): from .dict_store import DictStore from .tree_store import TreeStore from .batch_array import Batch, BatchArray +from .list_array import ListArray from .vlarray import VLArray, vlarray_from_cframe from .ref import Ref from .b2objects import open_b2object @@ -632,7 +633,7 @@ def _raise(exc): # Delayed imports for avoiding overwriting of python builtins. # Note: bool, bytes, string shadow builtins in the blosc2 namespace by design — # they are schema spec constructors (b2.bool(), b2.bytes(), etc.). -from .ctable import Column, CTable +from .ctable import DEFAULT_NULL_POLICY, Column, CTable, NullPolicy, get_null_policy, null_policy from .ndarray import ( abs, acos, @@ -746,7 +747,9 @@ def _raise(exc): int16, int32, int64, + list, string, + struct, uint8, uint16, uint32, @@ -766,6 +769,7 @@ def _raise(exc): "DEFAULT_FLOAT", "DEFAULT_INDEX", "DEFAULT_INT", + "DEFAULT_NULL_POLICY", # Mathematical constants "e", "pi", @@ -784,7 +788,9 @@ def _raise(exc): "int16", "int32", "int64", + "list", "string", + "struct", "uint8", "uint16", "uint32", @@ -806,6 +812,8 @@ def _raise(exc): "DSLSyntaxError", "LazyExpr", "LazyUDF", + "ListArray", + "NullPolicy", "NDArray", "NDField", "Operand", @@ -1023,4 +1031,6 @@ def _raise(exc): "where", "zeros", "zeros_like", + "get_null_policy", + "null_policy", ] diff --git a/src/blosc2/blosc2_ext.pyx b/src/blosc2/blosc2_ext.pyx index d1e65037..bddb5a0a 100644 --- a/src/blosc2/blosc2_ext.pyx +++ b/src/blosc2/blosc2_ext.pyx @@ -1963,6 +1963,13 @@ cdef class SChunk: raise RuntimeError("Could not update the desired chunk") return rc + def reorder_offsets(self, order): + cdef np.ndarray[np.int64_t, ndim=1] offsets_order = np.ascontiguousarray(order, dtype=np.int64) + rc = blosc2_schunk_reorder_offsets(self.schunk, offsets_order.data) + if rc < 0: + raise RuntimeError("Could not reorder the chunk offsets") + return None + def update_data(self, nchunk, data, copy): cdef Py_buffer buf PyObject_GetBuffer(data, &buf, PyBUF_SIMPLE) diff --git a/src/blosc2/core.py b/src/blosc2/core.py index ceb78acd..ffe7b7da 100644 --- a/src/blosc2/core.py +++ b/src/blosc2/core.py @@ -1922,7 +1922,13 @@ def ndarray_from_cframe(cframe: bytes | str, copy: bool = False) -> blosc2.NDArr def from_cframe( cframe: bytes | str, copy: bool = True ) -> ( - blosc2.EmbedStore | blosc2.NDArray | blosc2.SChunk | blosc2.BatchArray | blosc2.VLArray | blosc2.C2Array + blosc2.EmbedStore + | blosc2.NDArray + | blosc2.SChunk + | blosc2.ListArray + | blosc2.BatchArray + | blosc2.VLArray + | blosc2.C2Array ): """Create a :ref:`EmbedStore `, :ref:`NDArray `, :ref:`SChunk `, :ref:`BatchArray ` or :ref:`VLArray ` instance @@ -1957,6 +1963,8 @@ def from_cframe( # Check the metalayer to determine the type if "b2embed" in schunk.meta: return blosc2.estore_from_cframe(cframe, copy=copy) + if "listarray" in schunk.meta: + return blosc2.ListArray(_from_schunk=schunk_from_cframe(cframe, copy=copy)) if "batcharray" in schunk.meta: return blosc2.BatchArray(_from_schunk=schunk_from_cframe(cframe, copy=copy)) if "vlarray" in schunk.meta: diff --git a/src/blosc2/ctable.py b/src/blosc2/ctable.py index 7f8bcd18..55a84359 100644 --- a/src/blosc2/ctable.py +++ b/src/blosc2/ctable.py @@ -12,6 +12,7 @@ import ast import contextlib +import contextvars import dataclasses import itertools import os @@ -19,33 +20,23 @@ import re import shutil import weakref -from collections.abc import Iterable -from dataclasses import MISSING +from collections.abc import Iterable, Mapping +from dataclasses import MISSING, dataclass +from dataclasses import field as dataclass_field from textwrap import TextWrapper -from typing import Any, Generic, TypeVar +from typing import Any, Generic, Literal, TypeVar import numpy as np +import blosc2 from blosc2 import compute_chunks_blocks from blosc2.ctable_storage import FileTableStorage, InMemoryTableStorage, TableStorage -from blosc2.schema_compiler import schema_from_dict, schema_to_dict - -try: - from line_profiler import profile -except ImportError: - - def profile(func): - def wrapper(*args, **kwargs): - return func(*args, **kwargs) - - wrapper.__name__ = func.__name__ - return wrapper - - -import blosc2 from blosc2.info import InfoReporter, format_nbytes_info +from blosc2.list_array import ListArray, coerce_list_cell from blosc2.schema import ( + ListSpec, SchemaSpec, + StructSpec, complex64, complex128, float32, @@ -73,8 +64,110 @@ def wrapper(*args, **kwargs): _validate_column_name, compile_schema, compute_display_width, + schema_from_dict, + schema_to_dict, ) + +@dataclass(frozen=True) +class NullPolicy: + """Default sentinels for inferred CTable scalar nulls. + + CTable nullable scalar columns are represented with per-column sentinel + values. This policy is used when CTable has to infer those sentinels, such + as when importing nullable scalar Arrow or Parquet columns without an + explicit column-level null sentinel. The selected sentinel is stored in the + resulting CTable schema, so existing tables remain self-describing. + + Examples + -------- + Use :func:`blosc2.null_policy` to apply a policy while creating a CTable + from data with nullable scalar columns:: + + policy = blosc2.NullPolicy( + signed_int_strategy="max", + string_value="", + column_null_values={"user_id": -1, "country": "NA"}, + ) + + with blosc2.null_policy(policy): + table = blosc2.CTable.from_parquet("data.parquet") + + The same policy is used for explicit nullable schema specs:: + + @dataclass + class Row: + user_id: int = blosc2.field(blosc2.int64(nullable=True)) + country: str = blosc2.field(blosc2.string(nullable=True)) + + with blosc2.null_policy(policy): + table = blosc2.CTable(Row) + + ``column_null_values`` takes precedence over the type-wide defaults in the + policy. This is useful when a particular column needs a sentinel that is + known not to collide with its real values. + """ + + string_value: str = "__BLOSC2_NULL__" + bytes_value: bytes = b"__BLOSC2_NULL__" + float_value: float = float("nan") + bool_value: int = 255 + signed_int_strategy: Literal["min", "max"] = "min" + unsigned_int_strategy: Literal["min", "max"] = "max" + column_null_values: Mapping[str, Any] = dataclass_field(default_factory=dict) + + def sentinel_for_arrow_type(self, pa, pa_type): + """Return the default sentinel for *pa_type*, or ``None`` if unsupported.""" + signed_ints = [ + (pa.int8(), np.int8), + (pa.int16(), np.int16), + (pa.int32(), np.int32), + (pa.int64(), np.int64), + ] + unsigned_ints = [ + (pa.uint8(), np.uint8), + (pa.uint16(), np.uint16), + (pa.uint32(), np.uint32), + (pa.uint64(), np.uint64), + ] + for arrow_type, dtype in signed_ints: + if pa_type == arrow_type: + info = np.iinfo(dtype) + return info.min if self.signed_int_strategy == "min" else info.max + for arrow_type, dtype in unsigned_ints: + if pa_type == arrow_type: + info = np.iinfo(dtype) + return info.min if self.unsigned_int_strategy == "min" else info.max + if pa_type in (pa.float32(), pa.float64()): + return self.float_value + if pa_type == pa.bool_(): + return self.bool_value + if pa_type in (pa.string(), pa.large_string(), pa.utf8(), pa.large_utf8()): + return self.string_value + if pa.types.is_binary(pa_type) or pa.types.is_large_binary(pa_type): + return self.bytes_value + return None + + +DEFAULT_NULL_POLICY = NullPolicy() +_NULL_POLICY = contextvars.ContextVar("blosc2_null_policy", default=DEFAULT_NULL_POLICY) + + +def get_null_policy() -> NullPolicy: + """Return the current default null policy.""" + return _NULL_POLICY.get() + + +@contextlib.contextmanager +def null_policy(policy: NullPolicy): + """Temporarily set the default policy for CTable null sentinel inference.""" + token = _NULL_POLICY.set(policy) + try: + yield + finally: + _NULL_POLICY.reset(token) + + # --------------------------------------------------------------------------- # Index proxy and CTableIndex # --------------------------------------------------------------------------- @@ -578,6 +671,11 @@ def is_computed(self) -> bool: """True if this column is a virtual computed column (read-only).""" return self._col_name in self._table._computed_cols + @property + def is_list(self) -> bool: + col = self._table._schema.columns_by_name.get(self._col_name) + return col is not None and isinstance(col.spec, ListSpec) + @property def _valid_rows(self): if self._mask is None: @@ -597,7 +695,7 @@ def __getitem__(self, key: int | slice | list | np.ndarray): """ return self._values_from_key(key) - def _values_from_key(self, key): + def _values_from_key(self, key): # noqa: C901 """Materialise values for a logical index key.""" if isinstance(key, int): n_rows = len(self) @@ -613,12 +711,14 @@ def _values_from_key(self, key): real_pos = blosc2.where(valid, _arange(len(valid))).compute() start, stop, step = key.indices(len(real_pos)) if start >= stop: - return np.array([], dtype=self.dtype) + return [] if self.is_list else np.array([], dtype=self.dtype) selected_pos = real_pos[start:stop:step] # physical row positions if self.is_computed: lo, hi = int(selected_pos.min()), int(selected_pos.max()) chunk = np.asarray(self._raw_col[lo : hi + 1]) return chunk[selected_pos - lo] + if self.is_list: + return self._raw_col[selected_pos] return np.asarray(self._raw_col[selected_pos]) elif isinstance(key, np.ndarray) and key.dtype == np.bool_: @@ -632,6 +732,8 @@ def _values_from_key(self, key): if self.is_computed: raw_np = np.asarray(self._raw_col[:]) return raw_np[phys_indices] + if self.is_list: + return self._raw_col[phys_indices] return self._raw_col[phys_indices] elif isinstance(key, (list, tuple, np.ndarray)): @@ -640,6 +742,8 @@ def _values_from_key(self, key): if self.is_computed: raw_np = np.asarray(self._raw_col[:]) return raw_np[phys_indices] + if self.is_list: + return self._raw_col[phys_indices] return self._raw_col[phys_indices] raise TypeError(f"Invalid index type: {type(key)}") @@ -708,7 +812,7 @@ def view(self) -> ColumnViewIndexer: """ return ColumnViewIndexer(self) - def __setitem__(self, key: int | slice | list | np.ndarray, value): + def __setitem__(self, key: int | slice | list | np.ndarray, value): # noqa: C901 if self._table._read_only: raise ValueError("Table is read-only (opened with mode='r').") if self.is_computed: @@ -723,7 +827,6 @@ def __setitem__(self, key: int | slice | list | np.ndarray, value): self._raw_col[int(pos_true)] = value elif isinstance(key, np.ndarray) and key.dtype == np.bool_: - # Boolean mask in logical space. n_live = len(self) if len(key) != n_live: raise IndexError( @@ -731,9 +834,15 @@ def __setitem__(self, key: int | slice | list | np.ndarray, value): ) all_pos = np.where(self._valid_rows[:])[0] phys_indices = all_pos[key] - if isinstance(value, (list, tuple)): - value = np.array(value, dtype=self._raw_col.dtype) - self._raw_col[phys_indices] = value + if self.is_list: + if len(value) != len(phys_indices): + raise ValueError("Length mismatch in list-column assignment") + for pos, cell in zip(phys_indices, value, strict=True): + self._raw_col[int(pos)] = cell + else: + if isinstance(value, (list, tuple)): + value = np.array(value, dtype=self._raw_col.dtype) + self._raw_col[phys_indices] = value elif isinstance(key, (slice, list, tuple, np.ndarray)): real_pos = blosc2.where(self._valid_rows, _arange(len(self._valid_rows))).compute() @@ -743,9 +852,15 @@ def __setitem__(self, key: int | slice | list | np.ndarray, value): else: phys_indices = np.array([real_pos[i] for i in key], dtype=np.int64) - if isinstance(value, (list, tuple)): - value = np.array(value, dtype=self._raw_col.dtype) - self._raw_col[phys_indices] = value + if self.is_list: + if len(value) != len(phys_indices): + raise ValueError("Length mismatch in list-column assignment") + for pos, cell in zip(phys_indices, value, strict=True): + self._raw_col[int(pos)] = cell + else: + if isinstance(value, (list, tuple)): + value = np.array(value, dtype=self._raw_col.dtype) + self._raw_col[phys_indices] = value else: raise TypeError(f"Invalid index type: {type(key)}") @@ -755,6 +870,9 @@ def __iter__(self): if self.is_computed: yield from self._iter_chunks_computed(size=None) return + if self.is_list: + yield from self._raw_col[np.where(self._valid_rows[:])[0]] + return arr = self._valid_rows chunk_size = arr.chunks[0] @@ -794,27 +912,133 @@ def __repr__(self) -> str: def __len__(self): return blosc2.count_nonzero(self._valid_rows) + @property + def shape(self) -> tuple[int]: + """Logical shape of the live column values.""" + return (len(self),) + + @property + def ndim(self) -> int: + """Number of logical dimensions.""" + return 1 + + @property + def size(self) -> int: + """Number of live values in the column.""" + return len(self) + + @staticmethod + def _unwrap_operand(other): + return other._raw_col if isinstance(other, Column) else other + + @property + def _is_nullable_bool(self) -> bool: + col = self._table._schema.columns_by_name.get(self._col_name) + return ( + col is not None + and col.spec.to_metadata_dict().get("kind") == "bool" + and getattr(col.spec, "null_value", None) is not None + ) + + def __neg__(self): + return -self._raw_col + + def __pos__(self): + return +self._raw_col + + def __abs__(self): + return abs(self._raw_col) + + def __add__(self, other): + return self._raw_col + self._unwrap_operand(other) + + def __radd__(self, other): + return self._unwrap_operand(other) + self._raw_col + + def __sub__(self, other): + return self._raw_col - self._unwrap_operand(other) + + def __rsub__(self, other): + return self._unwrap_operand(other) - self._raw_col + + def __mul__(self, other): + return self._raw_col * self._unwrap_operand(other) + + def __rmul__(self, other): + return self._unwrap_operand(other) * self._raw_col + + def __truediv__(self, other): + return self._raw_col / self._unwrap_operand(other) + + def __rtruediv__(self, other): + return self._unwrap_operand(other) / self._raw_col + + def __floordiv__(self, other): + return self._raw_col // self._unwrap_operand(other) + + def __rfloordiv__(self, other): + return self._unwrap_operand(other) // self._raw_col + + def __mod__(self, other): + return self._raw_col % self._unwrap_operand(other) + + def __rmod__(self, other): + return self._unwrap_operand(other) % self._raw_col + + def __pow__(self, other): + return self._raw_col ** self._unwrap_operand(other) + + def __rpow__(self, other): + return self._unwrap_operand(other) ** self._raw_col + + def __and__(self, other): + return self._raw_col & self._unwrap_operand(other) + + def __rand__(self, other): + return self._unwrap_operand(other) & self._raw_col + + def __or__(self, other): + return self._raw_col | self._unwrap_operand(other) + + def __ror__(self, other): + return self._unwrap_operand(other) | self._raw_col + + def __xor__(self, other): + return self._raw_col ^ self._unwrap_operand(other) + + def __rxor__(self, other): + return self._unwrap_operand(other) ^ self._raw_col + + def __invert__(self): + if self._is_nullable_bool: + return self._raw_col == 0 + return ~self._raw_col + def __lt__(self, other): - return self._raw_col < other + return self._raw_col < self._unwrap_operand(other) def __le__(self, other): - return self._raw_col <= other + return self._raw_col <= self._unwrap_operand(other) def __eq__(self, other): - return self._raw_col == other + if self._is_nullable_bool and isinstance(other, (bool, np.bool_)): + return self._raw_col == int(other) + return self._raw_col == self._unwrap_operand(other) def __ne__(self, other): - return self._raw_col != other + if self._is_nullable_bool and isinstance(other, (bool, np.bool_)): + return self._raw_col == int(not other) + return self._raw_col != self._unwrap_operand(other) def __gt__(self, other): - return self._raw_col > other + return self._raw_col > self._unwrap_operand(other) def __ge__(self, other): - return self._raw_col >= other + return self._raw_col >= self._unwrap_operand(other) @property def dtype(self): - return self._raw_col.dtype + return getattr(self._raw_col, "dtype", None) def iter_chunks(self, size: int = 65536): """Iterate over live column values in chunks of *size* rows. @@ -840,6 +1064,8 @@ def iter_chunks(self, size: int = 65536): if self.is_computed: yield from self._iter_chunks_computed(size=size) return + if self.is_list: + raise TypeError("Column.iter_chunks() is not supported for list columns in V1.") valid = self._valid_rows raw = self._raw_col arr_len = len(valid) @@ -950,6 +1176,15 @@ def assign(self, data) -> None: raise ValueError("Table is read-only (opened with mode='r').") if self.is_computed: raise ValueError(f"Column {self._col_name!r} is a computed column and cannot be written to.") + if self.is_list: + values = list(data) + if len(values) != len(self): + raise ValueError(f"assign() requires {len(self)} values (live rows), got {len(values)}.") + live_pos = np.where(self._valid_rows[:])[0] + for pos, cell in zip(live_pos, values, strict=True): + self._raw_col[int(pos)] = cell + self._table._root_table._mark_all_indexes_stale() + return n_live = len(self) arr = np.asarray(data) if len(arr) != n_live: @@ -1082,28 +1317,41 @@ def _require_kind(self, kinds: str, op: str) -> None: # Aggregates # ------------------------------------------------------------------ - def sum(self): + def sum(self, dtype=None): """Sum of all live, non-null values. Supported dtypes: bool, int, uint, float, complex. Bool values are counted as 0 / 1. Null sentinel values are skipped. + + Parameters + ---------- + dtype: + Optional accumulator dtype. When omitted, float columns use + ``np.float64``, complex columns use ``np.complex128``, and integer + / bool columns use ``np.int64``. """ self._require_kind("biufc", "sum") self._require_nonempty("sum") # Use a wide accumulator to reduce overflow risk - acc_dtype = ( - np.float64 - if self.dtype.kind == "f" - else ( - np.complex128 if self.dtype.kind == "c" else np.int64 if self.dtype.kind in "biu" else None + acc_dtype = np.dtype(dtype).type if dtype is not None else None + if acc_dtype is None: + acc_dtype = ( + np.float64 + if self.dtype.kind == "f" + else ( + np.complex128 + if self.dtype.kind == "c" + else np.int64 + if self.dtype.kind in "biu" + else None + ) ) - ) result = acc_dtype(0) for chunk in self._nonnull_chunks(): result += chunk.sum(dtype=acc_dtype) - # Return in the column's natural dtype when it fits, else keep wide - if self.dtype.kind in "biu": + # Return in the column's natural dtype when it fits, else keep the requested/wide dtype + if dtype is None and self.dtype.kind in "biu": return int(result) return result @@ -1286,7 +1534,7 @@ def __init__( self._validate = validate self._table_cparams = cparams self._table_dparams = dparams - self._cols: dict[str, blosc2.NDArray] = {} + self._cols: dict[str, blosc2.NDArray | ListArray] = {} self._computed_cols: dict[str, dict] = {} # virtual/computed columns self._materialized_cols: dict[str, dict] = {} # stored columns auto-filled from expressions self._expr_index_arrays: dict[str, blosc2.NDArray] = {} @@ -1326,9 +1574,12 @@ def __init__( self.col_names = [c["name"] for c in schema_dict["columns"]] self._valid_rows = storage.open_valid_rows() for name in self.col_names: - col = storage.open_column(name) - self._cols[name] = col cc = self._schema.columns_by_name[name] + if self._is_list_column(cc): + col = storage.open_list_column(name) + else: + col = storage.open_column(name) + self._cols[name] = col self._col_widths[name] = max(len(name), cc.display_width) self._n_rows = int(blosc2.count_nonzero(self._valid_rows)) self._last_pos = None # resolve lazily on first write @@ -1348,6 +1599,7 @@ def __init__( self._schema = compile_schema(row_type) else: self._schema = _compile_pydantic_schema(row_type) + self._resolve_nullable_specs(self._schema) self._n_rows = 0 self._last_pos = 0 @@ -1366,6 +1618,8 @@ def __init__( def close(self) -> None: """Close any persistent backing store held by this table.""" + with contextlib.suppress(Exception): + self._flush_varlen_columns() storage = getattr(self, "_storage", None) if storage is not None and hasattr(storage, "close"): storage.close() @@ -1385,20 +1639,125 @@ def __del__(self): elif storage is not None and hasattr(storage, "close"): storage.close() + @staticmethod + def _is_list_column(col: CompiledColumn) -> bool: + return isinstance(col.spec, ListSpec) + + @staticmethod + def _is_list_spec(spec: SchemaSpec) -> bool: + return isinstance(spec, ListSpec) + + @staticmethod + def _policy_null_value_for_spec(spec: SchemaSpec, policy: NullPolicy): + if isinstance(spec, (int8, int16, int32, int64)): + info = np.iinfo(spec.dtype) + return info.min if policy.signed_int_strategy == "min" else info.max + if isinstance(spec, (uint8, uint16, uint32, uint64)): + info = np.iinfo(spec.dtype) + return info.min if policy.unsigned_int_strategy == "min" else info.max + if isinstance(spec, (float32, float64)): + return policy.float_value + if isinstance(spec, b2_bool): + return policy.bool_value + if isinstance(spec, string): + return policy.string_value + if isinstance(spec, b2_bytes): + return policy.bytes_value + return None + + @staticmethod + def _validate_null_value_for_spec(name: str, spec: SchemaSpec, null_value) -> None: + if isinstance(spec, (int8, int16, int32, int64, uint8, uint16, uint32, uint64)): + if isinstance(null_value, (bool, np.bool_)) or not isinstance(null_value, (int, np.integer)): + raise TypeError(f"Null sentinel for column {name!r} must be an integer") + info = np.iinfo(spec.dtype) + if not info.min <= int(null_value) <= info.max: + raise ValueError( + f"Null sentinel for column {name!r}={null_value!r} is outside {spec.dtype} range" + ) + return + if isinstance(spec, (float32, float64)): + if not isinstance(null_value, (int, float, np.integer, np.floating)): + raise TypeError(f"Null sentinel for column {name!r} must be numeric") + return + if isinstance(spec, b2_bool): + if null_value != 255: + raise ValueError(f"Null sentinel for nullable bool column {name!r} must be 255") + return + if isinstance(spec, string): + if not isinstance(null_value, str): + raise TypeError(f"Null sentinel for string column {name!r} must be str") + return + if isinstance(spec, b2_bytes) and not isinstance(null_value, bytes): + raise TypeError(f"Null sentinel for bytes column {name!r} must be bytes") + + @classmethod + def _resolve_nullable_specs( + cls, schema: CompiledSchema, *, validate_column_null_values: bool = True + ) -> None: + policy = get_null_policy() + schema_names = {col.name for col in schema.columns} + unknown_null_values = set(policy.column_null_values) - schema_names + if validate_column_null_values and unknown_null_values: + names = ", ".join(sorted(unknown_null_values)) + raise KeyError(f"column_null_values contains unknown columns: {names}") + for col in schema.columns: + spec = col.spec + if isinstance(spec, ListSpec) or getattr(spec, "null_value", None) is not None: + continue + if not getattr(spec, "nullable", False): + continue + null_value = policy.column_null_values.get(col.name) + if null_value is None: + null_value = cls._policy_null_value_for_spec(spec, policy) + if null_value is None: + raise TypeError(f"Column {col.name!r} is nullable, but no null policy sentinel is available") + cls._validate_null_value_for_spec(col.name, spec, null_value) + spec.null_value = null_value + if isinstance(spec, string): + spec.max_length = max(spec.max_length, len(null_value), 1) + spec.dtype = np.dtype(f"U{spec.max_length}") + elif isinstance(spec, b2_bytes): + spec.max_length = max(spec.max_length, len(null_value), 1) + spec.dtype = np.dtype(f"S{spec.max_length}") + elif isinstance(spec, b2_bool): + spec.dtype = np.dtype(np.uint8) + col.dtype = getattr(spec, "dtype", None) + col.display_width = compute_display_width(spec) + + def _flush_varlen_columns(self) -> None: + for col in self._schema.columns: + if self._is_list_column(col): + self._cols[col.name].flush() + def _init_columns( self, expected_size: int, default_chunks, default_blocks, storage: TableStorage ) -> None: - """Create one NDArray per column using the compiled schema.""" + """Create one physical column per compiled schema column.""" for col in self._schema.columns: self.col_names.append(col.name) self._col_widths[col.name] = max(len(col.name), col.display_width) col_storage = self._resolve_column_storage(col, default_chunks, default_blocks) + if self._is_list_column(col): + self._cols[col.name] = storage.create_list_column( + col.name, + spec=col.spec, + cparams=col_storage.get("cparams"), + dparams=col_storage.get("dparams"), + ) + continue + # Recompute chunks/blocks using the actual dtype so that wide + # string columns (e.g. U183642) don't produce multi-GB chunks. + chunks = col_storage["chunks"] + blocks = col_storage["blocks"] + if col.config.chunks is None and col.config.blocks is None: + chunks, blocks = compute_chunks_blocks((expected_size,), dtype=col.dtype) self._cols[col.name] = storage.create_column( col.name, dtype=col.dtype, shape=(expected_size,), - chunks=col_storage["chunks"], - blocks=col_storage["blocks"], + chunks=chunks, + blocks=blocks, cparams=col_storage.get("cparams"), dparams=col_storage.get("dparams"), ) @@ -1442,17 +1801,22 @@ def _normalize_row_input(self, data: Any) -> dict[str, Any]: return dict(zip(stored, data, strict=False)) if dataclasses.is_dataclass(data) and not isinstance(data, type): return dataclasses.asdict(data) + if isinstance(data, _Row): + return {name: data[name] for name in stored} if isinstance(data, (np.void, np.record)): return {name: data[name] for name in stored} # Fallback: try positional indexing return {name: data[i] for i, name in enumerate(stored)} def _coerce_row_to_storage(self, row: dict[str, Any]) -> dict[str, Any]: - """Coerce each value in *row* to the column's storage dtype.""" + """Coerce each value in *row* to the column's storage representation.""" result = {} for col in self._schema.columns: val = row[col.name] - result[col.name] = np.array(val, dtype=col.dtype).item() + if self._is_list_column(col): + result[col.name] = coerce_list_cell(col.spec, val) + else: + result[col.name] = np.array(val, dtype=col.dtype).item() return result def _resolve_last_pos(self) -> int: @@ -1494,9 +1858,11 @@ def _resolve_last_pos(self) -> int: return self._last_pos def _grow(self) -> None: - """Double the physical capacity of all columns and the valid_rows mask.""" + """Double the scalar-column capacity and the valid_rows mask.""" c = len(self._valid_rows) - for col_arr in self._cols.values(): + for name, col_arr in self._cols.items(): + if self._is_list_column(self._schema.columns_by_name[name]): + continue col_arr.resize((c * 2,)) self._valid_rows.resize((c * 2,)) @@ -1526,10 +1892,9 @@ def __str__(self) -> str: # -- per-column display widths -- widths: dict[str, int] = {} for name in self.col_names: - widths[name] = max( - self._col_widths[name], - len(str(self._col_dtype(name))), - ) + spec = self._schema.columns_by_name.get(name) + dtype_label = self._dtype_info_label(self._col_dtype(name), spec.spec if spec else None) + widths[name] = max(self._col_widths[name], len(dtype_label)) sep = " ".join("─" * (w + 2) for w in widths.values()) @@ -1547,11 +1912,26 @@ def rows_to_dicts(positions) -> list[dict]: if len(positions) == 0: return [] col_data = {n: self._fetch_col_at_positions(n, positions) for n in self.col_names} - return [{n: col_data[n][i].item() for n in self.col_names} for i in range(len(positions))] + rows = [] + for i in range(len(positions)): + row = {} + for n in self.col_names: + value = col_data[n][i] + row[n] = value.item() if isinstance(value, np.generic) else value + rows.append(row) + return rows lines = [ fmt_row({n: n for n in self.col_names}), - fmt_row({n: str(self._col_dtype(n)) for n in self.col_names}), + fmt_row( + { + n: self._dtype_info_label( + self._col_dtype(n), + self._schema.columns_by_name[n].spec if n in self._schema.columns_by_name else None, + ) + for n in self.col_names + } + ), sep, ] @@ -1583,6 +1963,79 @@ def __iter__(self): for i in range(self.nrows): yield _Row(self, i) + def iter_sorted( + self, + cols: str | list[str], + ascending: bool | list[bool] = True, + *, + start: int | None = None, + stop: int | None = None, + step: int | None = None, + batch_size: int = 4096, + ): + """Iterate rows in sorted order without materializing a full copy. + + Uses a FULL index when available (no sort needed); otherwise falls + back to ``np.lexsort`` on live physical positions. Yields :class:`_Row` + objects in the same way as ``__iter__``. + + The sorted positions array is stored as a compressed ``blosc2.NDArray`` + to keep RAM usage low for large tables. ``batch_size`` positions are + decompressed at a time during iteration. + + Parameters + ---------- + cols: + Column name or list of column names to sort by. + ascending: + Sort direction. A single bool applies to all keys; a list must + have the same length as *cols*. + start, stop, step: + Optional slice applied to the sorted sequence before iteration. + E.g. ``stop=10`` yields only the top-10 rows; ``step=2`` yields + every other row in sorted order. + batch_size: + Number of positions decompressed per iteration step. Larger + values reduce decompression overhead; smaller values use less + transient RAM. Default is 4096. + """ + cols, ascending = self._normalise_sort_keys(cols, ascending) + + valid_np = self._valid_rows[:] + live_pos = np.where(valid_np)[0] + n = len(live_pos) + + if n == 0: + return + + sorted_pos = None + if len(cols) == 1: + sorted_pos = self._sorted_positions_from_full_index(cols[0], ascending[0]) + if sorted_pos is not None and len(sorted_pos) != n: + sorted_pos = None + + if sorted_pos is None: + order = np.lexsort(self._build_lex_keys(cols, ascending, live_pos, n)) + sorted_pos = live_pos[order] + + if start is not None or stop is not None or step is not None: + sorted_pos = sorted_pos[start:stop:step] + + # Compress positions into an NDArray to reduce RAM usage for large tables. + # The uncompressed numpy array is released immediately after. + sorted_pos_nd = blosc2.asarray(np.asarray(sorted_pos, dtype=np.int64)) + del sorted_pos + + # physical → logical index mapping + phys_to_logical = np.empty(valid_np.shape[0], dtype=np.intp) + phys_to_logical[live_pos] = np.arange(n, dtype=np.intp) + + total = len(sorted_pos_nd) + for i in range(0, total, batch_size): + chunk = sorted_pos_nd[i : i + batch_size] + for phys in chunk: + yield _Row(self, int(phys_to_logical[phys])) + # ------------------------------------------------------------------ # Open existing table (classmethod) # ------------------------------------------------------------------ @@ -1632,8 +2085,11 @@ def open(cls, urlpath: str, *, mode: str = "r") -> CTable: obj._valid_rows = storage.open_valid_rows() for name in col_names: - obj._cols[name] = storage.open_column(name) cc = schema.columns_by_name[name] + if obj._is_list_column(cc): + obj._cols[name] = storage.open_list_column(name) + else: + obj._cols[name] = storage.open_column(name) obj._col_widths[name] = max(len(name), cc.display_width) obj._n_rows = int(blosc2.count_nonzero(obj._valid_rows)) @@ -1676,6 +2132,8 @@ def save(self, urlpath: str, *, overwrite: bool = False) -> None: else: os.remove(target_path) + self._flush_varlen_columns() + # Collect live physical positions valid_np = self._valid_rows[:] live_pos = np.where(valid_np)[0] @@ -1696,8 +2154,17 @@ def save(self, urlpath: str, *, overwrite: bool = False) -> None: # --- columns --- for col in self._schema.columns: name = col.name - # Use dtype-aware defaults so large-itemsize columns (e.g. U4096) get - # sensible chunk/block sizes rather than the uint8-based defaults. + if self._is_list_column(col): + disk_col = file_storage.create_list_column( + name, + spec=col.spec, + cparams=col.config.cparams if col.config.cparams is not None else self._table_cparams, + dparams=col.config.dparams if col.config.dparams is not None else self._table_dparams, + ) + if n_live > 0: + disk_col.extend(self._cols[name][int(pos)] for pos in live_pos) + disk_col.flush() + continue dtype_chunks, dtype_blocks = compute_chunks_blocks((capacity,), dtype=col.dtype) col_storage = self._resolve_column_storage(col, dtype_chunks, dtype_blocks) disk_col = file_storage.create_column( @@ -1744,7 +2211,12 @@ def load(cls, urlpath: str) -> CTable: col_names = [c["name"] for c in schema_dict["columns"]] disk_valid = file_storage.open_valid_rows() - disk_cols = {name: file_storage.open_column(name) for name in col_names} + disk_cols = {} + for col in schema.columns: + if cls._is_list_column(col): + disk_cols[col.name] = file_storage.open_list_column(col.name) + else: + disk_cols[col.name] = file_storage.open_column(col.name) phys_size = len(disk_valid) n_live = int(blosc2.count_nonzero(disk_valid)) capacity = max(phys_size, 1) @@ -1760,9 +2232,15 @@ def load(cls, urlpath: str) -> CTable: if phys_size > 0: mem_valid[:phys_size] = disk_valid[:] - mem_cols: dict[str, blosc2.NDArray] = {} + mem_cols: dict[str, blosc2.NDArray | ListArray] = {} for col in schema.columns: name = col.name + if cls._is_list_column(col): + mem_col = mem_storage.create_list_column(name, spec=col.spec, cparams=None, dparams=None) + mem_col.extend(disk_cols[name][:]) + mem_col.flush() + mem_cols[name] = mem_col + continue col_chunks, col_blocks = compute_chunks_blocks((capacity,), dtype=col.dtype) mem_col = mem_storage.create_column( name, @@ -2000,7 +2478,9 @@ def describe(self) -> None: for name in self.col_names: col = self[name] dtype = col.dtype - lines.append(f" {name} [{dtype}]") + spec = self._schema.columns_by_name.get(name) + label = self._dtype_info_label(dtype, spec.spec if spec else None) + lines.append(f" {name} [{label}]") if n == 0: lines.append(" (empty)") @@ -2010,7 +2490,10 @@ def describe(self) -> None: nc = col.null_count() n_nonnull = n - nc - if dtype.kind in "biufc" and dtype.kind != "c": + if isinstance(spec.spec, ListSpec) if spec is not None else False: + lines.append(f" count : {n:,}") + lines.append(" (stats not available for list columns)") + elif dtype.kind in "biufc" and dtype.kind != "c": # numeric + bool if dtype.kind == "b": arr = col[:] @@ -2082,7 +2565,7 @@ def cov(self) -> np.ndarray: """ for name in self.col_names: dtype = self._col_dtype(name) - if not ( + if dtype is None or not ( np.issubdtype(dtype, np.integer) or np.issubdtype(dtype, np.floating) or dtype == np.bool_ ): raise TypeError( @@ -2127,206 +2610,547 @@ def cov(self) -> np.ndarray: # Arrow interop # ------------------------------------------------------------------ - def to_arrow(self): - """Convert all live rows to a :class:`pyarrow.Table`. - - Each column is materialized via ``col[:]`` and wrapped - in a ``pyarrow.array``. String columns are emitted as ``pa.string()`` - (variable-length UTF-8); bytes columns as ``pa.large_binary()``. - - Raises - ------ - ImportError - If ``pyarrow`` is not installed. - """ + @staticmethod + def _require_pyarrow(context: str): try: import pyarrow as pa except ImportError: raise ImportError( - "pyarrow is required for to_arrow(). Install it with: pip install pyarrow" + f"pyarrow is required for {context}. Install it with: pip install pyarrow" ) from None + return pa - arrays = {} - for name in self.col_names: - col = self[name] - arr = col[:] - # Only compute null mask when a sentinel is actually configured — - # avoids allocating a 1M-element zeros array for every non-nullable column. - nv = col.null_value - if nv is not None: - null_mask = col._null_mask_for(arr) - has_nulls = bool(null_mask.any()) - else: - null_mask = None - has_nulls = False - kind = arr.dtype.kind - if kind == "U": - values = arr.tolist() - if has_nulls: - values = [None if null_mask[i] else v for i, v in enumerate(values)] - pa_arr = pa.array(values, type=pa.string()) - elif kind == "S": - values = arr.tolist() - if has_nulls: - values = [None if null_mask[i] else v for i, v in enumerate(values)] - pa_arr = pa.array(values, type=pa.large_binary()) - else: - pa_arr = pa.array(arr, mask=null_mask if has_nulls else None) - arrays[name] = pa_arr - - return pa.table(arrays) - - @classmethod - def from_arrow(cls, arrow_table) -> CTable: - """Build a :class:`CTable` from a :class:`pyarrow.Table`. - - Schema is inferred from the Arrow field types. String columns - (``pa.string()``, ``pa.large_string()``) are stored with - ``max_length`` set to the longest value found in the data. - - Parameters - ---------- - arrow_table: - A ``pyarrow.Table`` instance. - - Returns - ------- - CTable - A new in-memory CTable containing all rows from *arrow_table*. - - Raises - ------ - ImportError - If ``pyarrow`` is not installed. - TypeError - If an Arrow field type has no corresponding blosc2 spec. - """ + @staticmethod + def _require_pyarrow_parquet(context: str): try: - import pyarrow as pa + import pyarrow.parquet as pq except ImportError: raise ImportError( - "pyarrow is required for from_arrow(). Install it with: pip install pyarrow" + f"pyarrow is required for {context}. Install it with: pip install pyarrow" ) from None + return pq - import blosc2.schema as b2s + @staticmethod + def _validate_arrow_batch_size(batch_size: int) -> None: + if batch_size <= 0: + raise ValueError("batch_size must be greater than 0") + + def _resolve_arrow_columns(self, columns, include_computed: bool = True) -> list[str]: + if columns is None: + names = list(self.col_names) + if not include_computed: + names = [name for name in names if name not in self._computed_cols] + else: + names = list(columns) + if len(set(names)) != len(names): + raise ValueError("columns must be unique") + for name in names: + if name not in self.col_names: + raise KeyError(f"No column named {name!r}. Available: {self.col_names}") + return names - def _arrow_type_to_spec(pa_type, arrow_col): - """Map a pyarrow DataType to a blosc2 SchemaSpec.""" - mapping = [ - (pa.int8(), b2s.int8), - (pa.int16(), b2s.int16), - (pa.int32(), b2s.int32), - (pa.int64(), b2s.int64), - (pa.uint8(), b2s.uint8), - (pa.uint16(), b2s.uint16), - (pa.uint32(), b2s.uint32), - (pa.uint64(), b2s.uint64), - (pa.float32(), b2s.float32), - (pa.float64(), b2s.float64), - (pa.bool_(), b2s.bool), - ] - for arrow_t, spec_cls in mapping: - if pa_type == arrow_t: - return spec_cls() + @staticmethod + def _pa_type_from_spec(pa, spec): + if isinstance(spec, ListSpec): + return pa.list_(CTable._pa_type_from_spec(pa, spec.item_spec)) + if isinstance(spec, StructSpec): + return pa.struct( + [pa.field(name, CTable._pa_type_from_spec(pa, child)) for name, child in spec.fields.items()] + ) + if spec.to_metadata_dict().get("kind") == "bool": + return pa.bool_() + dtype = getattr(spec, "dtype", None) + if dtype is None: + raise TypeError(f"No Arrow type for blosc2 spec {spec!r}") + kind = dtype.kind + if kind == "U": + return pa.string() + if kind == "S": + return pa.large_binary() + return pa.from_numpy_dtype(dtype) + + def _arrow_schema_for_columns(self, columns=None, *, include_computed: bool = True): + pa = self._require_pyarrow("to_arrow()/to_parquet()") + names = self._resolve_arrow_columns(columns, include_computed=include_computed) + fields = [] + for name in names: + cc = self._schema.columns_by_name.get(name) + if cc is not None: + pa_type = self._pa_type_from_spec(pa, cc.spec) + else: + pa_type = pa.from_numpy_dtype(np.asarray(self[name][:0]).dtype) + fields.append(pa.field(name, pa_type)) + return pa.schema(fields) - # String types: determine max_length from the data - if pa_type in (pa.string(), pa.large_string(), pa.utf8(), pa.large_utf8()): - values = [v for v in arrow_col.to_pylist() if v is not None] - max_len = max((len(v) for v in values), default=1) - return b2s.string(max_length=max(max_len, 1)) + def iter_arrow_batches( + self, + *, + columns: list[str] | None = None, + batch_size: int = 65_536, + include_computed: bool = True, + ): + """Yield live rows as bounded-size :class:`pyarrow.RecordBatch` objects.""" + pa = self._require_pyarrow("iter_arrow_batches()") + self._validate_arrow_batch_size(batch_size) + self._flush_varlen_columns() + names = self._resolve_arrow_columns(columns, include_computed=include_computed) + + for start in range(0, self._n_rows, batch_size): + stop = min(start + batch_size, self._n_rows) + arrays = [] + for name in names: + col = self[name] + if col.is_list: + spec = self._schema.columns_by_name[name].spec + arrays.append(pa.array(col[start:stop], type=self._pa_type_from_spec(pa, spec))) + continue + arr = np.asarray(col[start:stop]) + nv = col.null_value + null_mask = col._null_mask_for(arr) if nv is not None else None + has_nulls = null_mask is not None and bool(null_mask.any()) + if arr.dtype.kind == "U": + values = arr.tolist() + if has_nulls: + values = [None if null_mask[i] else v for i, v in enumerate(values)] + arrays.append(pa.array(values, type=pa.string())) + elif arr.dtype.kind == "S": + values = arr.tolist() + if has_nulls: + values = [None if null_mask[i] else v for i, v in enumerate(values)] + arrays.append(pa.array(values, type=pa.large_binary())) + elif ( + self._schema.columns_by_name.get(name) is not None + and self._schema.columns_by_name[name].spec.to_metadata_dict().get("kind") == "bool" + ): + arrays.append(pa.array(arr == 1, mask=null_mask if has_nulls else None, type=pa.bool_())) + else: + arrays.append(pa.array(arr, mask=null_mask if has_nulls else None)) + yield pa.RecordBatch.from_arrays(arrays, names=names) - raise TypeError( - f"No blosc2 spec for Arrow type {pa_type!r}. " - "Supported: int8/16/32/64, uint8/16/32/64, float32/64, bool, string." + def to_arrow(self): + """Convert all live rows to a :class:`pyarrow.Table`.""" + pa = self._require_pyarrow("to_arrow()") + batches = list(self.iter_arrow_batches()) + schema = self._arrow_schema_for_columns() + return pa.Table.from_batches(batches, schema=schema) + + @staticmethod + def _auto_null_sentinel(pa, pa_type, *, null_policy: NullPolicy): + return null_policy.sentinel_for_arrow_type(pa, pa_type) + + @staticmethod + def _arrow_type_to_spec( # noqa: C901 + pa, + pa_type, + arrow_col=None, + *, + string_max_length=None, + null_value=None, + ): + import blosc2.schema as b2s + + mapping = [ + (pa.int8(), b2s.int8), + (pa.int16(), b2s.int16), + (pa.int32(), b2s.int32), + (pa.int64(), b2s.int64), + (pa.uint8(), b2s.uint8), + (pa.uint16(), b2s.uint16), + (pa.uint32(), b2s.uint32), + (pa.uint64(), b2s.uint64), + (pa.float32(), b2s.float32), + (pa.float64(), b2s.float64), + (pa.bool_(), b2s.bool), + ] + for arrow_t, spec_cls in mapping: + if pa_type == arrow_t: + if null_value is not None and hasattr(spec_cls(), "null_value"): + return spec_cls(null_value=null_value) + if null_value is not None and spec_cls is b2s.bool: + return spec_cls(null_value=null_value) + return spec_cls() + + if pa.types.is_list(pa_type) or pa.types.is_large_list(pa_type): + if arrow_col is not None: + py_values = arrow_col.to_pylist() + flat_values = [item for cell in py_values if cell is not None for item in cell] + item_arrow_col = pa.array(flat_values, type=pa_type.value_type) + nullable = any(v is None for v in py_values) + else: + item_arrow_col = None + nullable = True + item_string_max_length = string_max_length + if pa_type.value_type in (pa.string(), pa.large_string(), pa.utf8(), pa.large_utf8()): + item_string_max_length = max(string_max_length or 1, 1_000_000) + item_spec = CTable._arrow_type_to_spec( + pa, pa_type.value_type, item_arrow_col, string_max_length=item_string_max_length ) + return b2s.list(item_spec, nullable=nullable, storage="batch", serializer="msgpack") + + if pa.types.is_struct(pa_type): + fields = {} + for field in pa_type: + child_col = None + if arrow_col is not None: + child_col = arrow_col.field(field.name) + child_string_max_length = string_max_length + if field.type in (pa.string(), pa.large_string(), pa.utf8(), pa.large_utf8()): + child_string_max_length = max(string_max_length or 1, 1_000_000) + fields[field.name] = CTable._arrow_type_to_spec( + pa, field.type, child_col, string_max_length=child_string_max_length + ) + return b2s.struct(fields) + + if pa_type in (pa.string(), pa.large_string(), pa.utf8(), pa.large_utf8()): + if string_max_length is None: + values = arrow_col.to_pylist() if arrow_col is not None else [] + string_max_length = max((len(v) for v in values if v is not None), default=1) + max_length = max(string_max_length, len(null_value) if null_value is not None else 1, 1) + return b2s.string(max_length=max_length, null_value=null_value) + + if pa.types.is_binary(pa_type) or pa.types.is_large_binary(pa_type): + if string_max_length is None: + values = arrow_col.to_pylist() if arrow_col is not None else [] + string_max_length = max((len(v) for v in values if v is not None), default=1) + max_length = max(string_max_length, len(null_value) if null_value is not None else 1, 1) + return b2s.bytes(max_length=max_length, null_value=null_value) + + raise TypeError( + f"No blosc2 spec for Arrow type {pa_type!r}. Supported: int8/16/32/64, " + "uint8/16/32/64, float32/64, bool, string, binary, and list." + ) - # Build CompiledSchema from Arrow schema + @classmethod + def _compiled_columns_from_arrow( + cls, + pa, + schema, + table_for_inference, + string_max_length, + *, + auto_null_sentinels: bool, + ): + null_policy = get_null_policy() + column_null_values = null_policy.column_null_values + schema_names = set(schema.names) + unknown_null_values = set(column_null_values) - schema_names + if unknown_null_values: + names = ", ".join(sorted(unknown_null_values)) + raise KeyError(f"column_null_values contains unknown columns: {names}") columns: list[CompiledColumn] = [] - for field in arrow_table.schema: + for field in schema: name = field.name _validate_column_name(name) - spec = _arrow_type_to_spec(field.type, arrow_table.column(name)) - col_config = ColumnConfig(cparams=None, dparams=None, chunks=None, blocks=None) - columns.append( - CompiledColumn( - name=name, - py_type=spec.python_type, - spec=spec, - dtype=spec.dtype, - default=MISSING, - config=col_config, - display_width=compute_display_width(spec), + arrow_col = table_for_inference.column(name) if table_for_inference is not None else None + field_is_list = pa.types.is_list(field.type) or pa.types.is_large_list(field.type) + field_is_struct = pa.types.is_struct(field.type) + null_value = None + has_null_value_override = name in column_null_values + if has_null_value_override and (field_is_list or field_is_struct): + raise TypeError(f"column_null_values only supports scalar columns; {name!r} is not scalar") + if has_null_value_override: + null_value = column_null_values[name] + elif auto_null_sentinels and field.nullable and not (field_is_list or field_is_struct): + null_value = cls._auto_null_sentinel(pa, field.type, null_policy=null_policy) + if ( + arrow_col is not None + and arrow_col.null_count + and not (field_is_list or field_is_struct) + and null_value is None + ): + raise TypeError( + f"Column {name!r} contains Parquet nulls. Provide a CTable schema with a " + "null_value sentinel for this column." ) + spec = cls._arrow_type_to_spec( + pa, field.type, arrow_col, string_max_length=string_max_length, null_value=null_value ) + if null_value is not None and not (field_is_list or field_is_struct): + cls._validate_null_value_for_spec(name, spec, null_value) + columns.append(cls._compiled_column_from_spec(name, spec)) + return columns - schema = CompiledSchema( - row_cls=None, - columns=columns, - columns_by_name={col.name: col for col in columns}, + @classmethod + def _compiled_column_from_spec(cls, name: str, spec: SchemaSpec) -> CompiledColumn: + col_config = ColumnConfig(cparams=None, dparams=None, chunks=None, blocks=None) + return CompiledColumn( + name=name, + py_type=spec.python_type, + spec=spec, + dtype=getattr(spec, "dtype", None), + default=MISSING, + config=col_config, + display_width=compute_display_width(spec), ) - n = len(arrow_table) - capacity = max(n, 1) - default_chunks, default_blocks = compute_chunks_blocks((capacity,)) - mem_storage = InMemoryTableStorage() + @staticmethod + def _storage_for_arrow_import(urlpath: str | None, mode: str) -> TableStorage: + if urlpath is None: + return InMemoryTableStorage() + if mode == "w" and os.path.exists(urlpath): + if os.path.isdir(urlpath): + shutil.rmtree(urlpath) + else: + os.remove(urlpath) + return FileTableStorage(urlpath, mode) - new_valid = mem_storage.create_valid_rows( - shape=(capacity,), - chunks=default_chunks, - blocks=default_blocks, + @classmethod + def _create_arrow_import_columns( + cls, storage: TableStorage, columns: list[CompiledColumn], capacity: int, cparams, dparams + ): + default_chunks, default_blocks = compute_chunks_blocks((capacity,)) + new_valid = storage.create_valid_rows( + shape=(capacity,), chunks=default_chunks, blocks=default_blocks ) - new_cols: dict[str, blosc2.NDArray] = {} + new_cols: dict[str, blosc2.NDArray | ListArray] = {} for col in columns: - new_cols[col.name] = mem_storage.create_column( - col.name, - dtype=col.dtype, - shape=(capacity,), - chunks=default_chunks, - blocks=default_blocks, - cparams=None, - dparams=None, - ) + if cls._is_list_column(col): + new_cols[col.name] = storage.create_list_column( + col.name, spec=col.spec, cparams=cparams, dparams=dparams + ) + else: + chunks, blocks = default_chunks, default_blocks + if col.dtype is not None: + chunks, blocks = compute_chunks_blocks((capacity,), dtype=col.dtype) + new_cols[col.name] = storage.create_column( + col.name, + dtype=col.dtype, + shape=(capacity,), + chunks=chunks, + blocks=blocks, + cparams=cparams, + dparams=dparams, + ) + return new_cols, new_valid + @classmethod + def _new_arrow_import_ctable( + cls, compiled, storage, new_cols, new_valid, columns, *, cparams, dparams, validate + ): obj = cls.__new__(cls) obj._row_type = None - obj._validate = False - obj._table_cparams = None - obj._table_dparams = None - obj._storage = mem_storage - obj._read_only = False - obj._schema = schema + obj._validate = validate + obj._table_cparams = cparams + obj._table_dparams = dparams + obj._storage = storage + obj._read_only = storage.is_read_only() + obj._schema = compiled obj._cols = new_cols obj._col_widths = {col.name: max(len(col.name), col.display_width) for col in columns} obj.col_names = [col.name for col in columns] obj.row = _RowIndexer(obj) obj.auto_compact = False obj.base = None - obj._computed_cols = {} # from_arrow creates no computed columns + obj._computed_cols = {} obj._materialized_cols = {} obj._expr_index_arrays = {} obj._valid_rows = new_valid obj._n_rows = 0 obj._last_pos = 0 + return obj - if n > 0: - # Write each column directly — one bulk slice assignment per column. - # String columns (dtype.kind == 'U') can't go through Arrow's zero-copy - # path, so we convert via to_pylist() and let NumPy handle the - # fixed-width unicode coercion. All other types use zero-copy numpy. - for col in columns: - arrow_col = arrow_table.column(col.name) - if col.dtype.kind in "US": - arr = np.array(arrow_col.to_pylist(), dtype=col.dtype) - else: - arr = arrow_col.to_numpy(zero_copy_only=False).astype(col.dtype) - new_cols[col.name][:n] = arr + @classmethod + def _write_arrow_batches(cls, obj, batches, columns, new_cols, new_valid) -> None: + pos = 0 + for batch in batches: + end = pos + len(batch) + while end > len(new_valid): + obj._grow() + new_valid = obj._valid_rows + pos = cls._write_arrow_batch(batch, columns, new_cols, new_valid, pos) + for col in columns: + if cls._is_list_column(col): + new_cols[col.name].flush() + obj._n_rows = pos + obj._last_pos = pos - new_valid[:n] = True - obj._n_rows = n - obj._last_pos = n + @classmethod + def _write_arrow_batch(cls, batch, columns, new_cols, new_valid, pos: int) -> int: + m = len(batch) + if m == 0: + return pos + for col in columns: + arrow_col = batch.column(batch.schema.get_field_index(col.name)) + if cls._is_list_column(col): + new_cols[col.name].extend(arrow_col.to_pylist()) + else: + new_cols[col.name][pos : pos + m] = cls._arrow_column_to_numpy(arrow_col, col) + new_valid[pos : pos + m] = True + return pos + m + + @staticmethod + def _arrow_column_to_numpy(arrow_col, col: CompiledColumn) -> np.ndarray: + nv = getattr(col.spec, "null_value", None) + if col.spec.to_metadata_dict().get("kind") == "bool" and col.dtype == np.dtype(np.uint8): + return np.array([nv if v is None else int(v) for v in arrow_col.to_pylist()], dtype=np.uint8) + if col.dtype.kind in "US": + values = arrow_col.to_pylist() + if nv is not None: + values = [nv if v is None else v for v in values] + max_len = col.spec.max_length + too_long = [v for v in values if v is not None and len(v) > max_len] + if too_long: + raise ValueError(f"Column {col.name!r} contains values longer than max_length={max_len}.") + return np.array(values, dtype=col.dtype) + if arrow_col.null_count: + if nv is None: + raise TypeError( + f"Column {col.name!r} contains Arrow/Parquet nulls. Provide a CTable schema " + "with a null_value sentinel for this column." + ) + arrow_col = arrow_col.fill_null(nv) + return arrow_col.to_numpy(zero_copy_only=False).astype(col.dtype) + + @staticmethod + def _arrow_schema_metadata(schema) -> dict[str, Any]: + import base64 + + try: + schema_ipc = schema.serialize().to_pybytes() + schema_ipc_base64 = base64.b64encode(schema_ipc).decode("ascii") + except Exception: + schema_ipc_base64 = None + arrow_meta = {"schema_string": schema.to_string()} + if schema_ipc_base64 is not None: + arrow_meta["schema_ipc_base64"] = schema_ipc_base64 + return {"arrow": arrow_meta} + + @classmethod + def from_arrow( + cls, + schema, + batches, + *, + urlpath: str | None = None, + mode: str = "w", + cparams=None, + dparams=None, + validate: bool = False, + capacity_hint: int | None = None, + string_max_length: int | None = None, + auto_null_sentinels: bool = True, + list_batch_rows: int | None = 2048, + ) -> CTable: + """Build a :class:`CTable` from an Arrow schema and iterable of record batches. + ``list_batch_rows`` controls how many rows are buffered before list-valued + columns are flushed to their backend. Set it to ``None`` to keep list + columns pending until the final flush. + """ + pa = cls._require_pyarrow("from_arrow()") + if list_batch_rows is not None and list_batch_rows <= 0: + raise ValueError("list_batch_rows must be a positive integer or None") + batches = iter(batches) + first_batch = None + table_for_inference = None + if string_max_length is None: + first_batch = next(batches, None) + if first_batch is not None: + table_for_inference = pa.Table.from_batches([first_batch], schema=schema) + columns = cls._compiled_columns_from_arrow( + pa, + schema, + table_for_inference, + string_max_length, + auto_null_sentinels=auto_null_sentinels, + ) + if list_batch_rows is not None: + for col in columns: + if cls._is_list_column(col) and getattr(col.spec, "storage", None) == "batch": + col.spec.batch_rows = list_batch_rows + compiled = CompiledSchema( + row_cls=None, + columns=columns, + columns_by_name={col.name: col for col in columns}, + metadata=cls._arrow_schema_metadata(schema), + ) + if first_batch is not None: + import itertools as _it + + batches = _it.chain([first_batch], batches) + capacity = max(capacity_hint or 1, 1) + storage = cls._storage_for_arrow_import(urlpath, mode) + new_cols, new_valid = cls._create_arrow_import_columns(storage, columns, capacity, cparams, dparams) + storage.save_schema(schema_to_dict(compiled)) + obj = cls._new_arrow_import_ctable( + compiled, + storage, + new_cols, + new_valid, + columns, + cparams=cparams, + dparams=dparams, + validate=validate, + ) + cls._write_arrow_batches(obj, batches, columns, new_cols, new_valid) return obj + def to_parquet( + self, + path, + *, + columns: list[str] | None = None, + batch_size: int = 65_536, + compression: str | None = "zstd", + row_group_size: int | None = None, + include_computed: bool = True, + **kwargs, + ) -> None: + """Write this table to a Parquet file batch-wise using pyarrow.""" + pq = self._require_pyarrow_parquet("to_parquet()") + pa = self._require_pyarrow("to_parquet()") + self._validate_arrow_batch_size(batch_size) + schema = self._arrow_schema_for_columns(columns, include_computed=include_computed) + with pq.ParquetWriter(path, schema, compression=compression, **kwargs) as writer: + for batch in self.iter_arrow_batches( + columns=columns, batch_size=batch_size, include_computed=include_computed + ): + table = pa.Table.from_batches([batch], schema=batch.schema) + writer.write_table(table, row_group_size=row_group_size or len(batch)) + + @classmethod + def from_parquet( + cls, + path, + *, + columns: list[str] | None = None, + batch_size: int = 65_536, + urlpath: str | None = None, + mode: str = "w", + cparams=None, + dparams=None, + validate: bool = False, + auto_null_sentinels: bool = True, + list_batch_rows: int | None = 2048, + **kwargs, + ) -> CTable: + """Read a Parquet file into a :class:`CTable` batch-wise using pyarrow.""" + pq = cls._require_pyarrow_parquet("from_parquet()") + pa = cls._require_pyarrow("from_parquet()") + cls._validate_arrow_batch_size(batch_size) + string_max_length = kwargs.pop("string_max_length", None) + pf = pq.ParquetFile(path, **kwargs) + arrow_schema = pf.schema_arrow + if columns is not None: + if len(set(columns)) != len(columns): + raise ValueError("columns must be unique") + fields = [arrow_schema.field(name) for name in columns] + arrow_schema = pa.schema(fields) + batches = pf.iter_batches(batch_size=batch_size, columns=columns) + return cls.from_arrow( + arrow_schema, + batches, + urlpath=urlpath, + mode=mode, + cparams=cparams, + dparams=dparams, + validate=validate, + capacity_hint=pf.metadata.num_rows if pf.metadata is not None else None, + string_max_length=string_max_length, + auto_null_sentinels=auto_null_sentinels, + list_batch_rows=list_batch_rows, + ) + # ------------------------------------------------------------------ # CSV interop # ------------------------------------------------------------------ @@ -2420,6 +3244,7 @@ def from_csv( import csv schema = compile_schema(row_cls) + cls._resolve_nullable_specs(schema) ncols = len(schema.columns) # Accumulate values per column as Python lists (one pass through file) @@ -2532,6 +3357,12 @@ def add_column( if name in self._computed_cols: raise ValueError(f"A computed column named {name!r} already exists.") + compiled_col = self._compiled_column_from_spec(name, spec) + self._resolve_nullable_specs( + CompiledSchema(row_cls=None, columns=[compiled_col], columns_by_name={name: compiled_col}), + validate_column_null_values=False, + ) + spec = compiled_col.spec try: default_val = spec.dtype.type(default) except (ValueError, OverflowError) as exc: @@ -2553,15 +3384,8 @@ def add_column( if len(live_pos) > 0: new_col[live_pos] = default_val - compiled_col = CompiledColumn( - name=name, - py_type=spec.python_type, - spec=spec, - dtype=spec.dtype, - default=default, - config=ColumnConfig(cparams=cparams, dparams=None, chunks=None, blocks=None), - display_width=compute_display_width(spec), - ) + compiled_col.default = default + compiled_col.config = ColumnConfig(cparams=cparams, dparams=None, chunks=None, blocks=None) self._cols[name] = new_col self.col_names.append(name) self._col_widths[name] = max(len(name), compiled_col.display_width) @@ -2735,12 +3559,12 @@ def computed_columns(self) -> dict[str, dict]: """ return dict(self._computed_cols) # shallow copy so callers can't mutate - def _col_dtype(self, name: str) -> np.dtype: + def _col_dtype(self, name: str) -> np.dtype | None: """Return the dtype for *name*, routing through computed cols.""" cc = self._computed_cols.get(name) if cc is not None: return cc["dtype"] - return self._cols[name].dtype + return getattr(self._cols[name], "dtype", None) @staticmethod def _readable_computed_expr(cc: dict) -> str: @@ -2758,18 +3582,21 @@ def _sub(m: re.Match) -> str: return re.sub(r"\bo(\d+)\b", _sub, cc["expression"]) - def _fetch_col_at_positions(self, name: str, positions: np.ndarray) -> np.ndarray: + def _fetch_col_at_positions(self, name: str, positions: np.ndarray): """Fetch values at *positions* (physical indices) — used for display.""" cc = self._computed_cols.get(name) if cc is not None: if len(positions) == 0: return np.array([], dtype=cc["dtype"]) - # Evaluate element-by-element for scattered display positions (max ~20). return np.array( [np.asarray(cc["lazy"][int(p)]).ravel()[0] for p in positions], dtype=cc["dtype"], ) - return self._cols[name][positions] + col = self._cols[name] + spec = self._schema.columns_by_name[name].spec + if self._is_list_spec(spec): + return col[positions] + return col[positions] def _schema_dict_with_computed(self) -> dict: """Return the schema dict extended with computed/materialized metadata.""" @@ -3172,19 +3999,29 @@ def compact(self): raise ValueError("Table is read-only (opened with mode='r').") if self.base is not None: raise ValueError("Cannot compact a view.") + self._flush_varlen_columns() real_poss = blosc2.where(self._valid_rows, np.array(range(len(self._valid_rows)))).compute() - start = 0 - block_size = self._valid_rows.blocks[0] - end = min(block_size, self._n_rows) - while start < end: - for _k, v in self._cols.items(): + for col in self._schema.columns: + name = col.name + v = self._cols[name] + if self._is_list_column(col): + compacted = [v[int(pos)] for pos in real_poss[: self._n_rows]] + replacement = ListArray(spec=col.spec) + replacement.extend(compacted) + replacement.flush() + self._cols[name] = replacement + continue + start = 0 + block_size = self._valid_rows.blocks[0] + end = min(block_size, self._n_rows) + while start < end: v[start:end] = v[real_poss[start:end]] - start += block_size - end = min(end + block_size, self._n_rows) + start += block_size + end = min(end + block_size, self._n_rows) self._valid_rows[: self._n_rows] = True self._valid_rows[self._n_rows :] = False - self._last_pos = self._n_rows # next write goes right after live rows + self._last_pos = self._n_rows self._mark_all_indexes_stale() def _normalise_sort_keys( @@ -3205,6 +4042,10 @@ def _normalise_sort_keys( if name not in self._cols and name not in self._computed_cols: raise KeyError(f"No column named {name!r}. Available: {self.col_names}") dtype = self._col_dtype(name) + if dtype is None: + raise TypeError( + f"Column {name!r} is a list column and does not support sort ordering in V1." + ) if np.issubdtype(dtype, np.complexfloating): raise TypeError( f"Column {name!r} has complex dtype {dtype} which does not support ordering." @@ -3212,27 +4053,26 @@ def _normalise_sort_keys( return cols, ascending def _sorted_positions_from_full_index(self, name: str, ascending: bool) -> np.ndarray | None: - """Return live physical positions from a matching FULL index, if available.""" - from blosc2.indexing import ordered_indices + """Return live physical positions from a matching FULL index, if available. + Reads the pre-sorted positions sidecar directly rather than going through + the ordered_indices query machinery, which is optimised for selective range + queries and is much slower for full-table streaming. + """ root = self._root_table catalog = root._storage.load_index_catalog() descriptor = None - target_arr = None if name in root._cols: + col_info = root._schema.columns_by_name.get(name) + if col_info is not None and getattr(col_info.spec, "null_value", None) is not None: + return None descriptor = catalog.get(name) - if ( - descriptor is not None - and descriptor.get("kind") == "full" - and not descriptor.get("stale", False) - ): - target_arr = root._cols[name] - else: + if descriptor is None or descriptor.get("kind") != "full" or descriptor.get("stale", False): descriptor = None elif name in root._computed_cols: cc = root._computed_cols[name] - for lookup_key, candidate in catalog.items(): + for _lookup_key, candidate in catalog.items(): target = candidate.get("target") or {} if ( target.get("source") == "expression" @@ -3242,14 +4082,32 @@ def _sorted_positions_from_full_index(self, name: str, ascending: bool) -> np.nd and list(target.get("dependencies", [])) == list(cc["col_deps"]) ): descriptor = candidate - target_arr = root._index_target_array(lookup_key, candidate) break - if descriptor is None or target_arr is None: + if descriptor is None: return None - positions = ordered_indices(target_arr, require_full=True) - if positions is None: - return None + positions_path = descriptor.get("full", {}).get("positions_path") + + # Read pre-sorted positions directly — bypasses the ordered_indices query + # machinery which is built for selective range queries and is ~70x slower + # for full-table streaming. + if positions_path is not None: + # Persistent table: positions live in a sidecar .b2nd file. + positions_nd = blosc2.open(positions_path, mode="r") + else: + # In-memory table: positions live in the sidecar handle cache. + from blosc2.indexing import _SIDECAR_HANDLE_CACHE, _sidecar_handle_cache_key + + target_arr = root._cols.get(name) + if target_arr is None: + return None + token = descriptor["token"] + cache_key = _sidecar_handle_cache_key(target_arr, token, "full", "positions") + positions_nd = _SIDECAR_HANDLE_CACHE.get(cache_key) + if positions_nd is None: + return None + + positions = np.asarray(positions_nd[:], dtype=np.int64) valid = root._valid_rows[:] positions = np.asarray(positions, dtype=np.int64) positions = positions[(positions >= 0) & (positions < len(valid))] @@ -3343,8 +4201,10 @@ def sort_by( If a column used as a sort key does not support ordering (e.g. complex numbers). """ - if self.base is not None: - raise ValueError("Cannot sort a view. Materialise it first with .to_table() or sort the parent.") + if self.base is not None and inplace: + raise ValueError( + "Cannot sort a view inplace (would modify shared column data). Use sort_by(inplace=False) to get a sorted copy." + ) if inplace and self._read_only: raise ValueError("Table is read-only (opened with mode='r').") @@ -3371,8 +4231,15 @@ def sort_by( sorted_pos = live_pos[order] if inplace: - for _col_name, arr in self._cols.items(): - arr[:n] = arr[sorted_pos] + for col in self._schema.columns: + arr = self._cols[col.name] + if self._is_list_column(col): + new_arr = ListArray(spec=col.spec) + new_arr.extend((arr[int(pos)] for pos in sorted_pos), validate=False) + new_arr.flush() + self._cols[col.name] = new_arr + else: + arr[:n] = arr[sorted_pos] self._valid_rows[:n] = True self._valid_rows[n:] = False self._n_rows = n @@ -3382,19 +4249,80 @@ def sort_by( else: # Build a new in-memory table with the sorted rows result = self._empty_copy() - for col_name, arr in self._cols.items(): - result._cols[col_name][:n] = arr[sorted_pos] + for col in self._schema.columns: + col_name = col.name + arr = self._cols[col_name] + if self._is_list_column(col): + result._cols[col_name].extend((arr[int(pos)] for pos in sorted_pos), validate=False) + result._cols[col_name].flush() + else: + result._cols[col_name][:n] = arr[sorted_pos] result._valid_rows[:n] = True result._valid_rows[n:] = False result._n_rows = n result._last_pos = n return result - def _empty_copy(self) -> CTable: + def copy(self, compact: bool = True) -> CTable: + """Return a new standalone in-memory copy of this table. + + Parameters + ---------- + compact: + If ``True`` (default), only live (non-deleted) rows are copied. + The result is a dense table with no tombstones and no parent + dependency — ideal for materialising a filtered view. + If ``False``, all physical slots are copied including deleted gaps, + preserving the tombstone state exactly. + """ + valid_np = self._valid_rows[:] + live_pos = np.where(valid_np)[0] + n_live = len(live_pos) + + if compact: + n = n_live + else: + # High watermark: number of slots ever written. + # List columns are written sequentially with no gaps — their length + # is the exact high watermark. For scalar-only tables fall back to + # the last live position + 1 (writes are always sequential so no + # deleted slot can exist beyond the last live one). + n = 0 + for col in self._schema.columns: + if self._is_list_column(col): + n = len(self._cols[col.name]) + break + if n == 0: + n = int(live_pos[-1]) + 1 if n_live > 0 else 0 + + result = self._empty_copy(capacity=n) + + for col in self._schema.columns: + col_name = col.name + arr = self._cols[col_name] + if self._is_list_column(col): + src = (arr[int(pos)] for pos in live_pos) if compact else (arr[i] for i in range(n)) + result._cols[col_name].extend(src, validate=False) + result._cols[col_name].flush() + else: + result._cols[col_name][:n] = arr[live_pos] if compact else arr[:n] + + if compact: + result._valid_rows[:n] = True + result._n_rows = n + result._last_pos = n - 1 if n > 0 else None + else: + result._valid_rows[:n] = valid_np[:n] + result._n_rows = n_live + result._last_pos = None # recomputed lazily on next append + + return result + + def _empty_copy(self, capacity: int | None = None) -> CTable: """Return a new empty in-memory CTable with the same schema and capacity.""" from blosc2 import compute_chunks_blocks - capacity = max(self._n_rows, 1) + capacity = max(capacity if capacity is not None else self._n_rows, 1) default_chunks, default_blocks = compute_chunks_blocks((capacity,)) mem_storage = InMemoryTableStorage() @@ -3406,15 +4334,23 @@ def _empty_copy(self) -> CTable: new_cols = {} for col in self._schema.columns: col_storage = self._resolve_column_storage(col, default_chunks, default_blocks) - new_cols[col.name] = mem_storage.create_column( - col.name, - dtype=col.dtype, - shape=(capacity,), - chunks=col_storage["chunks"], - blocks=col_storage["blocks"], - cparams=col_storage.get("cparams"), - dparams=col_storage.get("dparams"), - ) + if self._is_list_column(col): + new_cols[col.name] = mem_storage.create_list_column( + col.name, + spec=col.spec, + cparams=col_storage.get("cparams"), + dparams=col_storage.get("dparams"), + ) + else: + new_cols[col.name] = mem_storage.create_column( + col.name, + dtype=col.dtype, + shape=(capacity,), + chunks=col_storage["chunks"], + blocks=col_storage["blocks"], + cparams=col_storage.get("cparams"), + dparams=col_storage.get("dparams"), + ) obj = CTable.__new__(CTable) obj._schema = self._schema @@ -3993,6 +4929,8 @@ def create_index( # noqa: C901 ) col_arr = self._cols[col_name] + if isinstance(self._schema.columns_by_name[col_name].spec, ListSpec): + raise ValueError(f"Cannot create an index on list column {col_name!r} in V1.") is_persistent = self._storage.index_anchor_path(col_name) is not None if is_persistent: @@ -4216,7 +5154,7 @@ def _find_indexed_columns(root_cols, catalog, operands): seen.add(col_name) return indexed - def _try_index_where(self, expr_result: blosc2.LazyExpr) -> np.ndarray | None: + def _try_index_where(self, expr_result: blosc2.LazyExpr) -> np.ndarray | None: # noqa: C901 """Attempt to resolve *expr_result* via a column index. Returns a 1-D int64 array of physical row positions that satisfy the @@ -4251,12 +5189,29 @@ def _try_index_where(self, expr_result: blosc2.LazyExpr) -> np.ndarray | None: return None primary_col_name, primary_col_arr, _ = indexed_columns[0] + nullable_indexed = [ + name + for name, _arr, _descriptor in indexed_columns + if getattr(root._schema.columns_by_name[name].spec, "null_value", None) is not None + ] + + # Global null post-filtering is not correct for OR expressions. + if nullable_indexed and ("|" in expr_result.expression or " or " in expr_result.expression): + return None # Inject every usable table-owned descriptor so plan_query can combine them. + # In .b2z read mode all columns share the same urlpath, so _array_key() + # returns the same key for every column — causing _SIDECAR_HANDLE_CACHE + # collisions across queries. Clear stale handles before each injection so + # the upcoming query always loads the correct sidecar for this column. + from blosc2.indexing import _clear_cached_data + for _col_name, col_arr, descriptor in indexed_columns: arr_key = _array_key(col_arr) if _is_persistent_array(col_arr): store = _PERSISTENT_INDEXES.get(arr_key) or _default_index_store() + if store["indexes"].get(descriptor["token"]) is not descriptor: + _clear_cached_data(col_arr, descriptor["token"]) store["indexes"][descriptor["token"]] = descriptor _PERSISTENT_INDEXES[arr_key] = store else: @@ -4271,20 +5226,33 @@ def _try_index_where(self, expr_result: blosc2.LazyExpr) -> np.ndarray | None: if not plan.usable: return None + def _exclude_null_positions(positions): + positions = np.asarray(positions, dtype=np.int64) + for name in nullable_indexed: + col = root._schema.columns_by_name[name] + raw = root._cols[name][positions] + nv = getattr(col.spec, "null_value", None) + if isinstance(nv, float) and np.isnan(nv): + keep = ~np.isnan(raw) + else: + keep = raw != nv + positions = positions[keep] + return positions + if plan.exact_positions is not None: - return np.asarray(plan.exact_positions, dtype=np.int64) + return _exclude_null_positions(plan.exact_positions) if plan.bucket_masks is not None: _, positions = evaluate_bucket_query( expression, merged_operands, {}, where_dict, plan, return_positions=True ) - return np.asarray(positions, dtype=np.int64) + return _exclude_null_positions(positions) if plan.candidate_units is not None and plan.segment_len is not None: _, positions = evaluate_segment_query( expression, merged_operands, {}, where_dict, plan, return_positions=True ) - return np.asarray(positions, dtype=np.int64) + return _exclude_null_positions(positions) return None @@ -4301,7 +5269,12 @@ def info_items(self) -> list[tuple[str, object]]: f"{cc['dtype']} (computed: {self._readable_computed_expr(cc)})" ) else: - schema_summary[name] = _InfoLiteral(self._dtype_info_label(self._cols[name].dtype)) + col_meta = self._schema.columns_by_name.get(name) + schema_summary[name] = _InfoLiteral( + self._dtype_info_label( + getattr(self._cols[name], "dtype", None), col_meta.spec if col_meta else None + ) + ) index_summary = {} for idx in self.indexes: @@ -4339,8 +5312,12 @@ def info_items(self) -> list[tuple[str, object]]: return items @staticmethod - def _dtype_info_label(dtype: np.dtype) -> str: + def _dtype_info_label(dtype: np.dtype | None, spec: SchemaSpec | None = None) -> str: """Return a compact dtype label for info reports.""" + if isinstance(spec, ListSpec): + return spec.display_label() + if dtype is None: + return "None" if dtype.kind == "U": nchars = dtype.itemsize // 4 return f"U{nchars} (Unicode, max {nchars} chars)" @@ -4401,8 +5378,13 @@ def append(self, data: list | np.void | np.ndarray) -> None: if pos >= len(self._valid_rows): self._grow() - for name, col_array in self._cols.items(): - col_array[pos] = row[name] + for col in self._schema.columns: + name = col.name + col_array = self._cols[name] + if self._is_list_column(col): + col_array.append(row[name]) + else: + col_array[pos] = row[name] self._valid_rows[pos] = True self._last_pos = pos + 1 @@ -4431,7 +5413,7 @@ def delete(self, ind: int | slice | str | Iterable) -> None: self._last_pos = None # recalculate on next write self._storage.bump_visibility_epoch() - def extend(self, data: list | CTable | Any, *, validate: bool | None = None) -> None: + def extend(self, data: list | CTable | Any, *, validate: bool | None = None) -> None: # noqa: C901 if self._read_only: raise ValueError("Table is read-only (opened with mode='r').") if self.base is not None: @@ -4457,7 +5439,11 @@ def extend(self, data: list | CTable | Any, *, validate: bool | None = None) -> raw_columns[name] = data._cols[name][: data._n_rows] provided_names.add(name) else: - if isinstance(data, np.ndarray) and data.dtype.names is not None: + if isinstance(data, dict): + provided_names = set(data) & set(current_col_names) + new_nrows = len(next(iter(data.values()))) + raw_columns = {name: data[name] for name in provided_names} + elif isinstance(data, np.ndarray) and data.dtype.names is not None: new_nrows = len(data) raw_columns = {name: data[name] for name in data.dtype.names if name in current_col_names} provided_names = set(raw_columns) @@ -4480,11 +5466,15 @@ def extend(self, data: list | CTable | Any, *, validate: bool | None = None) -> validate_column_batch(self._schema, raw_columns) - processed_cols = [] + scalar_processed_cols: dict[str, blosc2.NDArray] = {} + list_processed_cols: dict[str, list] = {} for name in current_col_names: - target_dtype = self._cols[name].dtype - b2_arr = blosc2.asarray(raw_columns[name], dtype=target_dtype) - processed_cols.append(b2_arr) + col_meta = self._schema.columns_by_name[name] + if self._is_list_column(col_meta): + list_processed_cols[name] = list(raw_columns[name]) + else: + target_dtype = self._cols[name].dtype + scalar_processed_cols[name] = np.ascontiguousarray(raw_columns[name], dtype=target_dtype) end_pos = start_pos + new_nrows @@ -4496,8 +5486,12 @@ def extend(self, data: list | CTable | Any, *, validate: bool | None = None) -> while end_pos > len(self._valid_rows): self._grow() - for j, name in enumerate(current_col_names): - self._cols[name][start_pos:end_pos] = processed_cols[j][:] + for name in current_col_names: + col_meta = self._schema.columns_by_name[name] + if self._is_list_column(col_meta): + self._cols[name].extend(list_processed_cols[name], validate=do_validate) + else: + self._cols[name][start_pos:end_pos] = scalar_processed_cols[name][:] self._valid_rows[start_pos:end_pos] = True self._last_pos = end_pos @@ -4508,12 +5502,97 @@ def extend(self, data: list | CTable | Any, *, validate: bool | None = None) -> # Filtering # ------------------------------------------------------------------ - @profile - def where(self, expr_result) -> CTable: + def _where_expression_operands(self) -> dict[str, blosc2.NDArray | blosc2.LazyExpr]: + operands = dict(self._cols) + operands.update({name: cc["lazy"] for name, cc in self._computed_cols.items()}) + return operands + + def where( + self, + expr_result: str | np.ndarray | blosc2.NDArray | blosc2.LazyExpr | Column, + ) -> CTable: + """Return a row-filtered view matching a boolean predicate. + + Signature:: + + where(expr_result) -> CTable + + The predicate can be supplied as a boolean :class:`blosc2.LazyExpr`, + a boolean :class:`blosc2.NDArray`, a boolean NumPy array, a boolean + ``Column``, or a string expression evaluated against this table's + columns. String expressions can reference stored and computed columns + directly by name. + + The returned object is a :class:`CTable` view sharing the original + column data. The row-selection mask is evaluated immediately and + intersected with the table's current live rows; selected column data is + not copied. + + Parameters + ---------- + expr_result: + Boolean predicate selecting rows. Strings are converted to a + lazy expression with table columns as operands, e.g. + ``"value * category >= 150"``. Column objects can also be used in + Python expressions, e.g. ``(t.value * t.category) >= 150``. + + Returns + ------- + CTable + A view over the same columns containing only rows where the + predicate is true and the source row is live. + + Raises + ------ + TypeError + If *expr_result* does not evaluate to a boolean Blosc2/NumPy + array or lazy expression. + + Examples + -------- + Filter using a string expression:: + + view = t.where("value * category >= 150") + + Filter using column arithmetic:: + + view = t.where((t.value * t.category) >= 150) + + Blosc2 lazy functions can be used in column expressions:: + + view = t.where(((t.value + 2) * blosc2.sin(t.category)) >= 10) + + For column names that are not valid Python identifiers, use item + access:: + + view = t.where((t["unit price"] * t["quantity"]) > 100) + + Notes + ----- + Use bitwise operators (``&``, ``|``, ``~``) or string expressions for + element-wise boolean logic. Python's logical operators ``and``, ``or`` + and ``not`` cannot be overloaded and therefore do not build lazy column + expressions. + + Use:: + + t.where((t.x > 0) & (t.y < 10)) + t.where(~t.returned) + t.where("not returned") + + not:: + + t.where((t.x > 0) and (t.y < 10)) + t.where(not t.returned) + """ + if isinstance(expr_result, str): + expr_result = blosc2.lazyexpr(expr_result, self._where_expression_operands()) if isinstance(expr_result, np.ndarray) and expr_result.dtype == np.bool_: expr_result = blosc2.asarray(expr_result) if isinstance(expr_result, Column): - expr_result = expr_result._raw_col + expr_result = ( + expr_result._raw_col == 1 if expr_result._is_nullable_bool else expr_result._raw_col + ) if not ( isinstance(expr_result, (blosc2.NDArray, blosc2.LazyExpr)) diff --git a/src/blosc2/ctable_storage.py b/src/blosc2/ctable_storage.py index 54efa59b..1b3cf784 100644 --- a/src/blosc2/ctable_storage.py +++ b/src/blosc2/ctable_storage.py @@ -22,11 +22,16 @@ import copy import json import os -from typing import Any +from typing import TYPE_CHECKING, Any import numpy as np import blosc2 +from blosc2.list_array import ListArray +from blosc2.schunk import process_opened_object + +if TYPE_CHECKING: + from blosc2.schema import ListSpec # Directory inside the table root that holds per-column index sidecar files. _INDEXES_DIR = "_indexes" @@ -56,6 +61,19 @@ def create_column( def open_column(self, name: str) -> blosc2.NDArray: raise NotImplementedError + def create_list_column( + self, + name: str, + *, + spec: ListSpec, + cparams: dict[str, Any] | None, + dparams: dict[str, Any] | None, + ) -> ListArray: + raise NotImplementedError + + def open_list_column(self, name: str) -> ListArray: + raise NotImplementedError + def create_valid_rows( self, *, @@ -151,6 +169,17 @@ def create_column(self, name, *, dtype, shape, chunks, blocks, cparams, dparams) def open_column(self, name): raise RuntimeError("In-memory tables have no on-disk representation to open.") + def create_list_column(self, name, *, spec, cparams, dparams): + kwargs = {} + if cparams is not None: + kwargs["cparams"] = cparams + if dparams is not None: + kwargs["dparams"] = dparams + return ListArray(spec=spec, **kwargs) + + def open_list_column(self, name): + raise RuntimeError("In-memory tables have no on-disk representation to open.") + def create_valid_rows(self, *, shape, chunks, blocks): return blosc2.zeros(shape, dtype=np.bool_, chunks=chunks, blocks=blocks) @@ -249,6 +278,12 @@ def _valid_rows_path(self) -> str: def _col_path(self, name: str) -> str: return self._key_to_path(self._col_key(name)) + def _list_col_path(self, name: str) -> str: + rel_key = self._col_key(name).lstrip("/") + # Use working_dir so .b2z stores write into their temp dir (gets zipped on close). + # For .b2d, working_dir == self._root, so behaviour is unchanged. + return os.path.join(self._open_store().working_dir, rel_key + ".b2b") + def _col_key(self, name: str) -> str: return f"/{_COLS_DIR}/{name}" @@ -299,6 +334,25 @@ def create_column(self, name, *, dtype, shape, chunks, blocks, cparams, dparams) def open_column(self, name: str) -> blosc2.NDArray: return self._open_store()[self._col_key(name)] + def create_list_column(self, name, *, spec, cparams, dparams): + kwargs: dict[str, Any] = {"urlpath": self._list_col_path(name), "mode": "w", "contiguous": True} + if cparams is not None: + kwargs["cparams"] = cparams + if dparams is not None: + kwargs["dparams"] = dparams + return ListArray(spec=spec, **kwargs) + + def open_list_column(self, name: str) -> ListArray: + store = self._open_store() + if store.is_zip_store and self._mode == "r": + # In read mode, .b2z is never extracted — read the member at its zip offset directly. + rel = f"{_COLS_DIR}/{name}.b2b" + if rel not in store.offsets: + raise KeyError(f"List column {name!r} not found in {self._root!r}") + opened = blosc2.blosc2_ext.open(store.b2z_path, mode="r", offset=store.offsets[rel]["offset"]) + return process_opened_object(opened) + return blosc2.open(self._list_col_path(name), mode=self._mode) + def create_valid_rows(self, *, shape, chunks, blocks): valid_rows = blosc2.zeros( shape, @@ -358,15 +412,31 @@ def column_names_from_schema(self) -> list[str]: return [c["name"] for c in d["columns"]] def delete_column(self, name: str) -> None: - del self._open_store()[self._col_key(name)] + key = self._col_key(name) + if key in self._open_store(): + del self._open_store()[key] + return + list_path = self._list_col_path(name) + if os.path.exists(list_path): + blosc2.remove_urlpath(list_path) + return + raise KeyError(name) - def rename_column(self, old: str, new: str) -> blosc2.NDArray: + def rename_column(self, old: str, new: str): store = self._open_store() old_key = self._col_key(old) new_key = self._col_key(new) - store[new_key] = store[old_key] - del store[old_key] - return store[new_key] + if old_key in store: + store[new_key] = store[old_key] + del store[old_key] + return store[new_key] + old_path = self._list_col_path(old) + new_path = self._list_col_path(new) + if os.path.exists(old_path): + os.makedirs(os.path.dirname(new_path), exist_ok=True) + os.replace(old_path, new_path) + return blosc2.open(new_path, mode=self._mode) + raise KeyError(old) def close(self) -> None: if self._store is not None: @@ -383,16 +453,87 @@ def discard(self) -> None: # -- Index catalog and epoch helpers ------------------------------------- + @staticmethod + def _walk_descriptor_paths(descriptor: dict): + """Yield (obj, key) for every string value that looks like a file path.""" + _PATH_KEYS = {"path", "values_path", "positions_path", "l1_path", "l2_path"} + stack = [descriptor] + while stack: + obj = stack.pop() + if isinstance(obj, dict): + for k, v in obj.items(): + if k in _PATH_KEYS and isinstance(v, str): + yield obj, k + elif isinstance(v, (dict, list)): + stack.append(v) + elif isinstance(obj, list): + for item in obj: + if isinstance(item, (dict, list)): + stack.append(item) + + @staticmethod + def _relativize_descriptor(descriptor: dict, working_dir: str) -> dict: + """Replace absolute paths inside *working_dir* with ``_indexes/…`` relative paths.""" + prefix = working_dir.rstrip("/") + "/" + d = copy.deepcopy(descriptor) + for obj, key in FileTableStorage._walk_descriptor_paths(d): + v = obj[key] + if v.startswith(prefix): + obj[key] = v[len(prefix) :] + return d + + @staticmethod + def _absolutize_descriptor(descriptor: dict, working_dir: str) -> dict: + """Expand ``_indexes/…`` relative paths back to absolute using *working_dir*.""" + d = copy.deepcopy(descriptor) + for obj, key in FileTableStorage._walk_descriptor_paths(d): + v = obj[key] + if v.startswith(_INDEXES_DIR + "/") or v.startswith(_INDEXES_DIR + os.sep): + obj[key] = os.path.join(working_dir, v) + return d + + def _ensure_index_files_extracted(self, store, rel_paths: list[str]) -> None: + """Extract *rel_paths* from the zip into the working_dir (read mode only).""" + import zipfile + + for rel in rel_paths: + dest = os.path.join(store.working_dir, rel) + if os.path.exists(dest): + continue + os.makedirs(os.path.dirname(dest), exist_ok=True) + info = store.offsets.get(rel) + if info is None: + continue + with zipfile.ZipFile(store.b2z_path, "r") as zf, zf.open(rel) as src, open(dest, "wb") as dst: + dst.write(src.read()) + def load_index_catalog(self) -> dict: meta = self._open_meta() raw = meta.vlmeta.get("index_catalog") - if isinstance(raw, dict): - return copy.deepcopy(raw) - return {} + if not isinstance(raw, dict): + return {} + catalog = copy.deepcopy(raw) + store = self._open_store() + working_dir = store.working_dir + # Expand relative paths and, for b2z read mode, extract sidecar files. + rel_paths_needed = [] + for col_name, descriptor in catalog.items(): + catalog[col_name] = self._absolutize_descriptor(descriptor, working_dir) + if store.is_zip_store and self._mode == "r": + for obj, key in self._walk_descriptor_paths(catalog[col_name]): + v = obj[key] + rel = os.path.relpath(v, working_dir) + if not os.path.exists(v): + rel_paths_needed.append(rel.replace(os.sep, "/")) + if rel_paths_needed and store.is_zip_store and self._mode == "r": + self._ensure_index_files_extracted(store, rel_paths_needed) + return catalog def save_index_catalog(self, catalog: dict) -> None: meta = self._open_meta() - meta.vlmeta["index_catalog"] = copy.deepcopy(catalog) + working_dir = self._open_store().working_dir + relativized = {col: self._relativize_descriptor(desc, working_dir) for col, desc in catalog.items()} + meta.vlmeta["index_catalog"] = relativized def get_epoch_counters(self) -> tuple[int, int]: meta = self._open_meta() @@ -413,4 +554,4 @@ def bump_visibility_epoch(self) -> int: return vis_e def index_anchor_path(self, col_name: str) -> str | None: - return os.path.join(self._root, _INDEXES_DIR, col_name, "_anchor") + return os.path.join(self._open_store().working_dir, _INDEXES_DIR, col_name, "_anchor") diff --git a/src/blosc2/dict_store.py b/src/blosc2/dict_store.py index 3166bfeb..69dfae70 100644 --- a/src/blosc2/dict_store.py +++ b/src/blosc2/dict_store.py @@ -231,7 +231,7 @@ def _expected_ext_from_kind(kind: str) -> str: """Return the canonical write-time suffix for a supported external leaf kind.""" if kind == "ndarray": return ".b2nd" - if kind == "batcharray": + if kind in ("batcharray", "listarray"): return ".b2b" return ".b2f" @@ -260,6 +260,8 @@ def _opened_external_kind( kind = "ndarray" elif isinstance(processed, SChunk): kind = "schunk" + elif processed_name == "ListArray": + kind = "listarray" else: warnings.warn( f"Ignoring unsupported Blosc2 object at '{rel_path}' during DictStore discovery: " diff --git a/src/blosc2/lazyexpr.py b/src/blosc2/lazyexpr.py index baaab395..1826c36f 100644 --- a/src/blosc2/lazyexpr.py +++ b/src/blosc2/lazyexpr.py @@ -3077,6 +3077,8 @@ def __init__(self, new_op): # noqa: C901 self.operands = {} return value1, op, value2 = new_op + value1 = value1._raw_col if hasattr(value1, "_raw_col") else value1 + value2 = value2._raw_col if hasattr(value2, "_raw_col") else value2 dtype_ = check_dtype(op, value1, value2) # perform some checks # Check that operands are proper Operands, LazyArray or scalars; if not, convert to NDArray objects value1 = ( @@ -3170,6 +3172,8 @@ def update_expr(self, new_op): # One of the two operands are LazyExpr instances try: value1, op, value2 = new_op + value1 = value1._raw_col if hasattr(value1, "_raw_col") else value1 + value2 = value2._raw_col if hasattr(value2, "_raw_col") else value2 dtype_ = check_dtype(op, value1, value2) # conserve dtype # The new expression and operands expression = None diff --git a/src/blosc2/list_array.py b/src/blosc2/list_array.py new file mode 100644 index 00000000..de559dc0 --- /dev/null +++ b/src/blosc2/list_array.py @@ -0,0 +1,676 @@ +####################################################################### +# Copyright (c) 2019-present, Blosc Development Team +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause +####################################################################### + +from __future__ import annotations + +import copy +from bisect import bisect_right +from collections import defaultdict +from collections.abc import Iterable, Iterator +from functools import lru_cache +from typing import Any + +import numpy as np + +import blosc2 +from blosc2.batch_array import BatchArray +from blosc2.info import InfoReporter, format_nbytes_info +from blosc2.schema import ListSpec, SchemaSpec, StructSpec +from blosc2.schema import list as list_spec_builder +from blosc2.vlarray import VLArray + +_SUPPORTED_SERIALIZERS = {"msgpack", "arrow"} +_SUPPORTED_STORAGES = {"batch", "vl"} + + +def _spec_label(spec: SchemaSpec) -> str: + if isinstance(spec, ListSpec): + return spec.display_label() + meta = spec.to_metadata_dict() + kind = meta.get("kind", type(spec).__name__) + if kind == "string": + return "string" + if kind == "bytes": + return "bytes" + return str(kind) + + +@lru_cache(maxsize=1) +def _require_pyarrow(): + try: + import pyarrow as pa + except ImportError as exc: + raise ImportError("ListArray serializer='arrow' requires pyarrow") from exc + return pa + + +def _arrow_type_for_spec(pa, spec: SchemaSpec): + if isinstance(spec, StructSpec): + return pa.struct( + [pa.field(name, _arrow_type_for_spec(pa, child)) for name, child in spec.fields.items()] + ) + mapping = { + "int8": pa.int8(), + "int16": pa.int16(), + "int32": pa.int32(), + "int64": pa.int64(), + "uint8": pa.uint8(), + "uint16": pa.uint16(), + "uint32": pa.uint32(), + "uint64": pa.uint64(), + "float32": pa.float32(), + "float64": pa.float64(), + "bool": pa.bool_(), + "string": pa.string(), + "bytes": pa.large_binary(), + } + return mapping.get(spec.to_metadata_dict()["kind"]) + + +def _arrow_list_item_type_to_spec(pa, value_type): + import blosc2.schema as b2s + + mapping = { + pa.int8(): b2s.int8(), + pa.int16(): b2s.int16(), + pa.int32(): b2s.int32(), + pa.int64(): b2s.int64(), + pa.uint8(): b2s.uint8(), + pa.uint16(): b2s.uint16(), + pa.uint32(): b2s.uint32(), + pa.uint64(): b2s.uint64(), + pa.float32(): b2s.float32(), + pa.float64(): b2s.float64(), + pa.bool_(): b2s.bool(), + pa.string(): b2s.string(), + pa.large_string(): b2s.string(), + pa.binary(): b2s.bytes(), + pa.large_binary(): b2s.bytes(), + } + if pa.types.is_struct(value_type): + return b2s.struct( + {field.name: _arrow_list_item_type_to_spec(pa, field.type) for field in value_type} + ) + return mapping.get(value_type) + + +def _validate_list_spec(spec: ListSpec) -> None: + if spec.storage not in _SUPPORTED_STORAGES: + raise ValueError(f"Unsupported list storage: {spec.storage!r}") + if spec.serializer not in _SUPPORTED_SERIALIZERS: + raise ValueError(f"Unsupported list serializer: {spec.serializer!r}") + if spec.storage == "vl" and spec.serializer != "msgpack": + raise ValueError("ListArray storage='vl' only supports serializer='msgpack'") + if spec.serializer == "arrow" and spec.storage != "batch": + raise ValueError("ListArray serializer='arrow' requires storage='batch'") + if isinstance(spec.item_spec, ListSpec): + raise TypeError("Nested list item specs are not supported in V1") + if spec.batch_rows is not None and spec.batch_rows <= 0: + raise ValueError("batch_rows must be a positive integer") + if spec.items_per_block is not None and spec.items_per_block <= 0: + raise ValueError("items_per_block must be a positive integer") + + +def _coerce_struct_item(spec: StructSpec, value: Any) -> dict[str, Any]: + if not isinstance(value, dict): + value = dict(value) + result = {} + for name, child_spec in spec.fields.items(): + if name not in value: + raise ValueError(f"Struct list item is missing field {name!r}") + result[name] = None if value[name] is None else _coerce_scalar_item(child_spec, value[name]) + return result + + +def _coerce_scalar_item(spec: SchemaSpec, value: Any) -> Any: # noqa: C901 + if value is None: + raise ValueError("ListArray does not support nullable items inside a list in V1") + + if isinstance(spec, StructSpec): + return _coerce_struct_item(spec, value) + + if getattr(spec, "python_type", None) is str: + if not isinstance(value, str): + value = str(value) + elif getattr(spec, "python_type", None) is bytes: + if isinstance(value, str): + value = value.encode() + elif not isinstance(value, (bytes, bytearray, memoryview)): + value = bytes(value) + value = bytes(value) + else: + dtype = getattr(spec, "dtype", None) + if dtype is None: + raise TypeError(f"Unsupported list item spec {type(spec).__name__!r}") + value = np.array(value, dtype=dtype).item() + + ge = getattr(spec, "ge", None) + if ge is not None and value < ge: + raise ValueError(f"List item {value!r} violates ge={ge}") + gt = getattr(spec, "gt", None) + if gt is not None and value <= gt: + raise ValueError(f"List item {value!r} violates gt={gt}") + le = getattr(spec, "le", None) + if le is not None and value > le: + raise ValueError(f"List item {value!r} violates le={le}") + lt = getattr(spec, "lt", None) + if lt is not None and value >= lt: + raise ValueError(f"List item {value!r} violates lt={lt}") + + max_length = getattr(spec, "max_length", None) + min_length = getattr(spec, "min_length", None) + if max_length is not None and len(value) > max_length: + raise ValueError(f"List item {value!r} exceeds max_length={max_length}") + if min_length is not None and len(value) < min_length: + raise ValueError(f"List item {value!r} is shorter than min_length={min_length}") + return value + + +def coerce_list_cell(spec: ListSpec, value: Any) -> list[Any] | None: + _validate_list_spec(spec) + if value is None: + if not spec.nullable: + raise ValueError("Null list cells are not allowed for this column") + return None + if isinstance(value, (str, bytes, bytearray, memoryview)): + raise TypeError("ListArray cells must be list-like, not strings or bytes") + if not isinstance(value, Iterable): + raise TypeError("ListArray cells must be list-like") + return [_coerce_scalar_item(spec.item_spec, item) for item in list(value)] + + +class ListArray: + """A row-oriented container for list-valued data. + + Backed internally by either :class:`blosc2.VLArray` or + :class:`blosc2.BatchArray`. + """ + + def __init__( + self, + spec: ListSpec | None = None, + *, + item_spec: SchemaSpec | None = None, + nullable: bool = False, + storage: str = "batch", + serializer: str = "msgpack", + batch_rows: int | None = None, + items_per_block: int | None = None, + _from_schunk=None, + **kwargs: Any, + ) -> None: + if _from_schunk is not None: + if spec is not None or item_spec is not None or kwargs: + raise ValueError("Cannot pass schema/storage arguments together with _from_schunk") + self._init_from_schunk(_from_schunk) + return + + if spec is None: + if item_spec is None: + raise ValueError("ListArray requires either spec=... or item_spec=...") + spec = list_spec_builder( + item_spec, + nullable=nullable, + storage=storage, + serializer=serializer, + batch_rows=batch_rows, + items_per_block=items_per_block, + ) + self.spec = spec + _validate_list_spec(self.spec) + self._pending_cells: list[list[Any] | None] = [] + self._persisted_row_count = 0 + self._persisted_prefix_cache: list[int] | None = None + self._cached_batch_index: int | None = None + self._cached_batch_values: list[list[Any] | None] | None = None + + storage_obj = self._coerce_storage(kwargs) + fixed_meta = dict(storage_obj.meta or {}) + fixed_meta["listarray"] = self.spec.to_listarray_metadata() + storage_obj.meta = fixed_meta + + if self.spec.storage == "vl": + self._backend = VLArray(storage=storage_obj, **kwargs) + else: + self._backend = BatchArray( + storage=storage_obj, + serializer=self.spec.serializer, + items_per_block=self.spec.items_per_block, + **kwargs, + ) + self._persisted_row_count = self._persisted_rows_count() + + @staticmethod + def _coerce_storage(kwargs: dict[str, Any]) -> blosc2.Storage: + storage = kwargs.pop("storage", None) + if storage is None: + storage_kwargs = { + name: kwargs.pop(name) for name in list(blosc2.Storage.__annotations__) if name in kwargs + } + return blosc2.Storage(**storage_kwargs) + if isinstance(storage, blosc2.Storage): + return copy.deepcopy(storage) + return blosc2.Storage(**storage) + + def _init_from_schunk(self, schunk) -> None: + meta = schunk.meta + if "listarray" not in meta: + raise ValueError("The supplied SChunk is not tagged as a ListArray") + la_meta = meta["listarray"] + self.spec = ListSpec.from_metadata_dict(la_meta) + self._pending_cells = [] + self._persisted_prefix_cache = None + self._cached_batch_index = None + self._cached_batch_values = None + if self.spec.storage == "vl": + if "vlarray" not in meta: + raise ValueError("ListArray metadata says backend='vl' but VLArray tag is missing") + self._backend = VLArray(_from_schunk=schunk) + self._persisted_row_count = len(self._backend) + else: + if "batcharray" not in meta: + raise ValueError("ListArray metadata says backend='batch' but BatchArray tag is missing") + self._backend = BatchArray(_from_schunk=schunk) + self._persisted_row_count = self._persisted_rows_count() + + def _invalidate_batch_caches(self) -> None: + self._persisted_prefix_cache = None + self._cached_batch_index = None + self._cached_batch_values = None + + def _persisted_rows_count(self) -> int: + if self.spec.storage == "vl": + return len(self._backend) + lengths = self._backend._load_or_compute_batch_lengths() + return int(sum(lengths)) + + def _persisted_prefix_sums(self) -> list[int]: + if self._persisted_prefix_cache is not None: + return self._persisted_prefix_cache + lengths = self._backend._load_or_compute_batch_lengths() + prefix = [0] + total = 0 + for length in lengths: + total += int(length) + prefix.append(total) + self._persisted_prefix_cache = prefix + return prefix + + def _get_batch_values(self, batch_index: int) -> list[list[Any] | None]: + if self._cached_batch_index == batch_index and self._cached_batch_values is not None: + return self._cached_batch_values + batch_values = self._backend[batch_index][:] + self._cached_batch_index = batch_index + self._cached_batch_values = batch_values + return batch_values + + def _normalize_index(self, index: int) -> int: + if not isinstance(index, int): + raise TypeError("ListArray indices must be integers") + n = len(self) + if index < 0: + index += n + if index < 0 or index >= n: + raise IndexError("ListArray index out of range") + return index + + def _normalize_indices(self, indices: Iterable[int]) -> list[int]: + return [self._normalize_index(int(index)) for index in indices] + + def _locate_persisted_row(self, row_index: int) -> tuple[int, int]: + prefix = self._persisted_prefix_sums() + batch_index = bisect_right(prefix, row_index) - 1 + inner_index = row_index - prefix[batch_index] + return batch_index, inner_index + + def _flush_full_batches(self) -> None: + if self.spec.storage != "batch": + return + batch_rows = self.batch_rows + if batch_rows is None: + return + while len(self._pending_cells) >= batch_rows: + batch = self._pending_cells[:batch_rows] + self._backend.append(batch) + self._pending_cells = self._pending_cells[batch_rows:] + self._persisted_row_count += len(batch) + self._invalidate_batch_caches() + + def append(self, value: Any) -> int: + cell = coerce_list_cell(self.spec, value) + if self.spec.storage == "vl": + self._backend.append(cell) + self._persisted_row_count = len(self._backend) + return len(self) + self._pending_cells.append(cell) + self._flush_full_batches() + return len(self) + + def extend(self, values: Iterable[Any], *, validate: bool = True) -> None: + if validate: + cells = [coerce_list_cell(self.spec, v) for v in values] + else: + cells = [v if v is not None else [] for v in values] + if self.spec.storage == "vl": + self._backend.extend(iter(cells)) + self._persisted_row_count = len(self._backend) + return + batch_rows = self.batch_rows + if batch_rows is None: + self._pending_cells.extend(cells) + return + self._pending_cells.extend(cells) + self._flush_full_batches() + + def flush(self) -> None: + if self.spec.storage != "batch": + return + if self._pending_cells: + batch = list(self._pending_cells) + self._backend.append(batch) + self._persisted_row_count += len(batch) + self._pending_cells.clear() + self._invalidate_batch_caches() + + def close(self) -> None: + self.flush() + + def _get_many_monotonic(self, indices: list[int]) -> list[Any]: + out: list[Any] = [None] * len(indices) + prefix = self._persisted_prefix_sums() + batch_index = 0 + batch_values: list[list[Any] | None] | None = None + + i = 0 + while i < len(indices): + index = indices[i] + if index >= self._persisted_row_count: + pending_start = index - self._persisted_row_count + j = i + 1 + while ( + j < len(indices) + and indices[j] >= self._persisted_row_count + and indices[j] == indices[j - 1] + 1 + ): + j += 1 + span = j - i + out[i:j] = self._pending_cells[pending_start : pending_start + span] + i = j + continue + + while batch_index + 1 < len(prefix) and index >= prefix[batch_index + 1]: + batch_index += 1 + batch_values = None + if batch_values is None: + batch_values = self._get_batch_values(batch_index) + + batch_start = prefix[batch_index] + batch_end = prefix[batch_index + 1] + local_start = index - batch_start + j = i + 1 + while j < len(indices) and indices[j] == indices[j - 1] + 1 and indices[j] < batch_end: + j += 1 + span = j - i + out[i:j] = batch_values[local_start : local_start + span] + i = j + + return out + + def _get_many_grouped(self, indices: list[int]) -> list[Any]: + out: list[Any] = [None] * len(indices) + grouped: dict[int, list[tuple[int, int]]] = defaultdict(list) + for out_i, index in enumerate(indices): + if index >= self._persisted_row_count: + out[out_i] = self._pending_cells[index - self._persisted_row_count] + else: + batch_index, inner_index = self._locate_persisted_row(index) + grouped[batch_index].append((out_i, inner_index)) + + for batch_index, refs in grouped.items(): + batch_values = self._get_batch_values(batch_index) + for out_i, inner_index in refs: + out[out_i] = batch_values[inner_index] + return out + + def _get_many(self, indices: list[int]) -> list[Any]: + if self.spec.storage == "vl": + return [self._backend[index] for index in indices] + if len(indices) <= 1: + return self._get_many_grouped(indices) + monotonic = True + prev = indices[0] + for index in indices[1:]: + if index < prev: + monotonic = False + break + prev = index + if monotonic: + return self._get_many_monotonic(indices) + return self._get_many_grouped(indices) + + def __getitem__(self, index: int | slice | list[int] | tuple[int, ...] | np.ndarray) -> Any: + if isinstance(index, slice): + indices = list(range(*index.indices(len(self)))) + return self._get_many(indices) + if isinstance(index, np.ndarray): + if index.dtype == np.bool_: + if len(index) != len(self): + raise IndexError( + f"Boolean mask length {len(index)} does not match ListArray length {len(self)}" + ) + return self._get_many(np.flatnonzero(index).tolist()) + return self._get_many(self._normalize_indices(index.tolist())) + if isinstance(index, (list, tuple)): + return self._get_many(self._normalize_indices(index)) + index = self._normalize_index(index) + if self.spec.storage == "vl": + return self._backend[index] + if index >= self._persisted_row_count: + return self._pending_cells[index - self._persisted_row_count] + batch_index, inner_index = self._locate_persisted_row(index) + return self._get_batch_values(batch_index)[inner_index] + + def __setitem__(self, index: int, value: Any) -> None: + cell = coerce_list_cell(self.spec, value) + index = self._normalize_index(index) + if self.spec.storage == "vl": + self._backend[index] = cell + return + if index >= self._persisted_row_count: + self._pending_cells[index - self._persisted_row_count] = cell + return + batch_index, inner_index = self._locate_persisted_row(index) + batch = self._get_batch_values(batch_index).copy() + batch[inner_index] = cell + self._backend[batch_index] = batch + self._cached_batch_index = batch_index + self._cached_batch_values = batch + + def __len__(self) -> int: + if self.spec.storage == "vl": + return len(self._backend) + return self._persisted_row_count + len(self._pending_cells) + + def __iter__(self) -> Iterator[Any]: + yield from self[:] + + def copy(self, **kwargs: Any) -> ListArray: + out = ListArray(spec=self.spec, **kwargs) + out.extend(self) + if self.spec.storage == "batch": + out.flush() + return out + + @property + def schunk(self): + return self._backend.schunk + + @property + def meta(self): + return self._backend.meta + + @property + def vlmeta(self): + return self._backend.vlmeta + + @property + def cparams(self): + return self._backend.cparams + + @property + def dparams(self): + return self._backend.dparams + + @property + def urlpath(self) -> str | None: + return self._backend.urlpath + + @property + def contiguous(self) -> bool: + return self._backend.contiguous + + @property + def batch_rows(self) -> int | None: + if self.spec.batch_rows is not None: + return self.spec.batch_rows + return None + + @property + def nbytes(self) -> int: + return self._backend.nbytes + + @property + def cbytes(self) -> int: + return self._backend.cbytes + + @property + def cratio(self) -> float: + return self._backend.cratio + + @property + def info(self) -> InfoReporter: + return InfoReporter(self) + + @property + def info_items(self) -> list: + return [ + ("type", "ListArray"), + ("logical_type", self.spec.display_label()), + ("backend", self.spec.storage), + ("serializer", self.spec.serializer), + ("rows", len(self)), + ("pending_rows", len(self._pending_cells) if self.spec.storage == "batch" else 0), + ("nbytes", format_nbytes_info(self.nbytes)), + ("cbytes", format_nbytes_info(self.cbytes)), + ("cratio", f"{self.cratio:.2f}"), + ] + + def to_cframe(self) -> bytes: + self.flush() + return self._backend.to_cframe() + + def _arrow_item_type(self): + pa = _require_pyarrow() + kind = self.spec.item_spec.to_metadata_dict()["kind"] + mapping = { + "int8": pa.int8(), + "int16": pa.int16(), + "int32": pa.int32(), + "int64": pa.int64(), + "uint8": pa.uint8(), + "uint16": pa.uint16(), + "uint32": pa.uint32(), + "uint64": pa.uint64(), + "float32": pa.float32(), + "float64": pa.float64(), + "bool": pa.bool_(), + "string": pa.string(), + "bytes": pa.large_binary(), + } + if isinstance(self.spec.item_spec, StructSpec): + return pa.struct( + [ + pa.field(name, _arrow_type_for_spec(pa, child_spec)) + for name, child_spec in self.spec.item_spec.fields.items() + ] + ) + return mapping.get(kind) + + def to_arrow(self): + pa = _require_pyarrow() + self.flush() + item_type = self._arrow_item_type() + if item_type is not None: + return pa.array(list(self), type=pa.list_(item_type)) + return pa.array(list(self)) + + @classmethod + def from_arrow( + cls, + arrow_array, + *, + item_spec: SchemaSpec | None = None, + nullable: bool = True, + storage: str = "batch", + serializer: str = "msgpack", + batch_rows: int | None = None, + items_per_block: int | None = None, + **kwargs: Any, + ) -> ListArray: + pa = _require_pyarrow() + if isinstance(arrow_array, pa.ChunkedArray): + arrow_array = arrow_array.combine_chunks() + if item_spec is None: + value_type = arrow_array.type.value_type + import blosc2.schema as b2s + + mapping = { + pa.int8(): b2s.int8(), + pa.int16(): b2s.int16(), + pa.int32(): b2s.int32(), + pa.int64(): b2s.int64(), + pa.uint8(): b2s.uint8(), + pa.uint16(): b2s.uint16(), + pa.uint32(): b2s.uint32(), + pa.uint64(): b2s.uint64(), + pa.float32(): b2s.float32(), + pa.float64(): b2s.float64(), + pa.bool_(): b2s.bool(), + pa.string(): b2s.string(), + pa.large_string(): b2s.string(), + pa.binary(): b2s.bytes(), + pa.large_binary(): b2s.bytes(), + } + if pa.types.is_struct(value_type): + item_spec = b2s.struct( + {field.name: _arrow_list_item_type_to_spec(pa, field.type) for field in value_type} + ) + else: + item_spec = mapping.get(value_type) + if item_spec is None: + raise TypeError(f"Unsupported Arrow list item type {value_type!r}") + arr = cls( + item_spec=item_spec, + nullable=nullable, + storage=storage, + serializer=serializer, + batch_rows=batch_rows, + items_per_block=items_per_block, + **kwargs, + ) + arr.extend(arrow_array.to_pylist()) + return arr + + def __enter__(self) -> ListArray: + return self + + def __exit__(self, exc_type, exc_val, exc_tb) -> bool: + self.close() + return False + + def __repr__(self) -> str: + return f"ListArray(type={self.spec.display_label()}, len={len(self)}, urlpath={self.urlpath!r})" diff --git a/src/blosc2/schema.py b/src/blosc2/schema.py index 560d7117..1b0945d1 100644 --- a/src/blosc2/schema.py +++ b/src/blosc2/schema.py @@ -22,6 +22,7 @@ # after our spec classes shadow them. _builtin_bool = bool _builtin_bytes = bytes +_builtin_list = list # --------------------------------------------------------------------------- @@ -73,15 +74,21 @@ def to_metadata_dict(self) -> dict[str, Any]: class _NumericSpec(SchemaSpec): - """Mixin for numeric specs that support ge / gt / le / lt constraints.""" + """Mixin for numeric specs that support constraints and null sentinels. + + ``nullable=True`` asks CTable to choose a null sentinel from the current + null policy when the schema is compiled. An explicit ``null_value`` takes + precedence. + """ _kind: str # set by each concrete subclass - def __init__(self, *, ge=None, gt=None, le=None, lt=None, null_value=None): + def __init__(self, *, ge=None, gt=None, le=None, lt=None, nullable: bool = False, null_value=None): self.ge = ge self.gt = gt self.le = le self.lt = lt + self.nullable = nullable or null_value is not None self.null_value = null_value def to_pydantic_kwargs(self) -> dict[str, Any]: @@ -94,6 +101,8 @@ def to_pydantic_kwargs(self) -> dict[str, Any]: def to_metadata_dict(self) -> dict[str, Any]: d: dict[str, Any] = {"kind": self._kind, **self.to_pydantic_kwargs()} + if self.nullable: + d["nullable"] = True if self.null_value is not None: d["null_value"] = self.null_value return d @@ -221,19 +230,31 @@ def to_metadata_dict(self) -> dict[str, Any]: class bool(SchemaSpec): - """Boolean column.""" + """Boolean column. + + Nullable bool columns use uint8 physical storage with values + ``0`` (false), ``1`` (true), and ``255`` (null). + """ dtype = np.dtype(np.bool_) python_type = _builtin_bool - def __init__(self): - pass + def __init__(self, *, nullable: bool = False, null_value=None): + if null_value is not None and null_value != 255: + raise ValueError("Nullable bool null_value must be 255") + self.nullable = nullable or null_value is not None + self.null_value = null_value + self.dtype = np.dtype(np.uint8) if self.nullable else np.dtype(np.bool_) def to_pydantic_kwargs(self) -> dict[str, Any]: return {} def to_metadata_dict(self) -> dict[str, Any]: - return {"kind": "bool"} + d: dict[str, Any] = {"kind": "bool"} + if self.nullable: + d["nullable"] = True + d["null_value"] = self.null_value + return d # --------------------------------------------------------------------------- @@ -253,15 +274,23 @@ class string(SchemaSpec): Minimum number of characters (validation only, no effect on dtype). pattern: Regex pattern the value must match (validation only). + nullable: + If ``True`` and ``null_value`` is not set, choose a null sentinel from + the current CTable null policy when the schema is compiled. + null_value: + Explicit null sentinel. Takes precedence over ``nullable=True``. """ python_type = str _DEFAULT_MAX_LENGTH = 32 - def __init__(self, *, min_length=None, max_length=None, pattern=None, null_value=None): + def __init__( + self, *, min_length=None, max_length=None, pattern=None, nullable: bool = False, null_value=None + ): self.min_length = min_length self.max_length = max_length if max_length is not None else self._DEFAULT_MAX_LENGTH self.pattern = pattern + self.nullable = nullable or null_value is not None self.null_value = null_value self.dtype = np.dtype(f"U{self.max_length}") @@ -277,6 +306,8 @@ def to_pydantic_kwargs(self) -> dict[str, Any]: def to_metadata_dict(self) -> dict[str, Any]: d: dict[str, Any] = {"kind": "string", **self.to_pydantic_kwargs()} + if self.nullable: + d["nullable"] = True if self.null_value is not None: d["null_value"] = self.null_value return d @@ -292,14 +323,20 @@ class bytes(SchemaSpec): Defaults to 32 if not specified. min_length: Minimum number of bytes (validation only, no effect on dtype). + nullable: + If ``True`` and ``null_value`` is not set, choose a null sentinel from + the current CTable null policy when the schema is compiled. + null_value: + Explicit null sentinel. Takes precedence over ``nullable=True``. """ python_type = _builtin_bytes _DEFAULT_MAX_LENGTH = 32 - def __init__(self, *, min_length=None, max_length=None, null_value=None): + def __init__(self, *, min_length=None, max_length=None, nullable: bool = False, null_value=None): self.min_length = min_length self.max_length = max_length if max_length is not None else self._DEFAULT_MAX_LENGTH + self.nullable = nullable or null_value is not None self.null_value = null_value self.dtype = np.dtype(f"S{self.max_length}") @@ -313,11 +350,159 @@ def to_pydantic_kwargs(self) -> dict[str, Any]: def to_metadata_dict(self) -> dict[str, Any]: d: dict[str, Any] = {"kind": "bytes", **self.to_pydantic_kwargs()} + if self.nullable: + d["nullable"] = True if self.null_value is not None: d["null_value"] = self.null_value return d +# --------------------------------------------------------------------------- +# List spec +# --------------------------------------------------------------------------- + + +class StructSpec(SchemaSpec): + """Logical schema descriptor for dict-like structured values.""" + + python_type = dict + dtype = None + + def __init__(self, fields: dict[str, SchemaSpec]): + if not isinstance(fields, dict) or not fields: + raise TypeError("StructSpec fields must be a non-empty dict") + for name, spec in fields.items(): + if not isinstance(name, str): + raise TypeError("StructSpec field names must be strings") + if not isinstance(spec, SchemaSpec): + raise TypeError("StructSpec field values must be SchemaSpec instances") + self.fields = dict(fields) + + def to_pydantic_kwargs(self) -> dict[str, Any]: + return {} + + def to_metadata_dict(self) -> dict[str, Any]: + return { + "kind": "struct", + "fields": [{"name": name, **spec.to_metadata_dict()} for name, spec in self.fields.items()], + } + + def display_label(self) -> str: + return "struct[" + ", ".join(self.fields) + "]" + + @classmethod + def from_metadata_dict(cls, data: dict[str, Any]) -> StructSpec: + from blosc2.schema_compiler import spec_from_metadata_dict + + fields = {} + for field in data["fields"]: + field = dict(field) + name = field.pop("name") + fields[name] = spec_from_metadata_dict(field) + return cls(fields) + + +class ListSpec(SchemaSpec): + """Logical schema descriptor for a list-valued column.""" + + python_type = _builtin_list + dtype = None + + def __init__( + self, + item_spec: SchemaSpec, + *, + nullable: bool = False, + storage: str = "batch", + serializer: str = "msgpack", + batch_rows: int | None = None, + items_per_block: int | None = None, + ): + if not isinstance(item_spec, SchemaSpec): + raise TypeError("ListSpec item_spec must be a SchemaSpec instance") + if isinstance(item_spec, ListSpec): + raise TypeError("Nested list item specs are not supported in V1") + if storage not in {"batch", "vl"}: + raise ValueError("storage must be 'batch' or 'vl'") + if serializer not in {"msgpack", "arrow"}: + raise ValueError("serializer must be 'msgpack' or 'arrow'") + if storage == "vl" and serializer != "msgpack": + raise ValueError("storage='vl' only supports serializer='msgpack'") + if serializer == "arrow" and storage != "batch": + raise ValueError("serializer='arrow' requires storage='batch'") + self.item_spec = item_spec + self.nullable = nullable + self.storage = storage + self.serializer = serializer + self.batch_rows = batch_rows + self.items_per_block = items_per_block + + def to_pydantic_kwargs(self) -> dict[str, Any]: + return {} + + def to_metadata_dict(self) -> dict[str, Any]: + d = { + "kind": "list", + "item": self.item_spec.to_metadata_dict(), + "nullable": self.nullable, + "storage": self.storage, + "serializer": self.serializer, + } + if self.batch_rows is not None: + d["batch_rows"] = self.batch_rows + if self.items_per_block is not None: + d["items_per_block"] = self.items_per_block + return d + + def to_listarray_metadata(self) -> dict[str, Any]: + d = {"version": 1, **self.to_metadata_dict()} + d["backend"] = d.pop("storage") + return d + + def display_label(self) -> str: + item_kind = self.item_spec.to_metadata_dict().get("kind", type(self.item_spec).__name__) + return f"list[{item_kind}]" + + @classmethod + def from_metadata_dict(cls, data: dict[str, Any]) -> ListSpec: + from blosc2.schema_compiler import spec_from_metadata_dict + + backend = data.get("backend") + return cls( + spec_from_metadata_dict(data["item_spec"] if "item_spec" in data else data["item"]), + nullable=data.get("nullable", False), + storage=backend if backend is not None else data.get("storage", "batch"), + serializer=data.get("serializer", "msgpack"), + batch_rows=data.get("batch_rows"), + items_per_block=data.get("items_per_block"), + ) + + +def struct(fields: dict[str, SchemaSpec]) -> StructSpec: + """Build a structured schema descriptor for nested CTable values.""" + return StructSpec(fields) + + +def list( + item_spec: SchemaSpec, + *, + nullable: bool = False, + storage: str = "batch", + serializer: str = "msgpack", + batch_rows: int | None = None, + items_per_block: int | None = None, +) -> ListSpec: + """Build a list-valued schema descriptor for CTable and ListArray.""" + return ListSpec( + item_spec, + nullable=nullable, + storage=storage, + serializer=serializer, + batch_rows=batch_rows, + items_per_block=items_per_block, + ) + + # --------------------------------------------------------------------------- # Field helper # --------------------------------------------------------------------------- diff --git a/src/blosc2/schema_compiler.py b/src/blosc2/schema_compiler.py index 19a3d0c1..108aa42a 100644 --- a/src/blosc2/schema_compiler.py +++ b/src/blosc2/schema_compiler.py @@ -10,6 +10,7 @@ from __future__ import annotations +import copy import dataclasses import typing from dataclasses import MISSING @@ -19,7 +20,9 @@ from blosc2.schema import ( BLOSC2_FIELD_METADATA_KEY, + ListSpec, SchemaSpec, + StructSpec, complex64, complex128, float32, @@ -88,6 +91,8 @@ def compute_display_width(spec: SchemaSpec) -> int: """Return a reasonable terminal display width for *spec*'s column.""" + if isinstance(spec, ListSpec): + return max(18, len(spec.display_label()) + 4) dtype = spec.dtype if dtype.kind == "U": # fixed-width unicode (string spec) return max(10, min(dtype.itemsize // 4, 50)) @@ -132,7 +137,7 @@ class CompiledColumn: name: str py_type: Any spec: SchemaSpec - dtype: np.dtype + dtype: np.dtype | None default: Any # MISSING means required (no default) config: ColumnConfig display_width: int = 20 # terminal column width for __str__ / info() @@ -151,6 +156,7 @@ class CompiledSchema: columns: list[CompiledColumn] columns_by_name: dict[str, CompiledColumn] validator_model: type[Any] | None = None # filled in by schema_validation + metadata: dict[str, Any] = dataclasses.field(default_factory=dict) # --------------------------------------------------------------------------- @@ -183,17 +189,25 @@ def infer_spec_from_annotation(annotation: Any) -> SchemaSpec: def validate_annotation_matches_spec(name: str, annotation: Any, spec: SchemaSpec) -> None: - """Raise :exc:`TypeError` if *annotation* is incompatible with *spec*. + """Raise :exc:`TypeError` if *annotation* is incompatible with *spec*.""" + if isinstance(spec, ListSpec): + origin = typing.get_origin(annotation) + if origin not in (list, list): + raise TypeError( + f"Column {name!r}: annotation {annotation!r} is incompatible with list spec; expected list[T]." + ) + args = typing.get_args(annotation) + if len(args) != 1: + raise TypeError(f"Column {name!r}: list annotations must specify exactly one item type.") + item_annotation = args[0] + expected = spec.item_spec.python_type + if item_annotation is not expected: + raise TypeError( + f"Column {name!r}: list item annotation {item_annotation!r} is incompatible with " + f"item spec {type(spec.item_spec).__name__!r} (expected {expected.__name__!r})." + ) + return - Parameters - ---------- - name: - Column name, used only in the error message. - annotation: - The resolved Python type from the dataclass field. - spec: - The :class:`SchemaSpec` attached via ``b2.field(...)``. - """ expected = spec.python_type if annotation is not expected: raise TypeError( @@ -274,7 +288,7 @@ def compile_schema(row_cls: type[Any]) -> CompiledSchema: if meta is not None: # Explicit b2.field(...) path - spec = meta["spec"] + spec = copy.deepcopy(meta["spec"]) if not isinstance(spec, SchemaSpec): raise TypeError( f"Column {name!r}: b2.field() requires a SchemaSpec as its first " @@ -305,7 +319,7 @@ def compile_schema(row_cls: type[Any]) -> CompiledSchema: name=name, py_type=annotation, spec=spec, - dtype=spec.dtype, + dtype=getattr(spec, "dtype", None), default=default, config=config, display_width=compute_display_width(spec), @@ -342,6 +356,21 @@ def _default_from_json(value: Any) -> Any: return value +def spec_from_metadata_dict(data: dict[str, Any]) -> SchemaSpec: + """Reconstruct one SchemaSpec from serialized metadata.""" + data = dict(data) + kind = data.pop("kind") + if kind == "list": + item_spec = spec_from_metadata_dict(data.pop("item")) + return ListSpec(item_spec, **data) + if kind == "struct": + return StructSpec.from_metadata_dict({"fields": data.pop("fields")}) + spec_cls = _KIND_TO_SPEC.get(kind) + if spec_cls is None: + raise ValueError(f"Unknown column kind {kind!r}") + return spec_cls(**data) + + def schema_to_dict(schema: CompiledSchema) -> dict[str, Any]: """Serialize *schema* to a JSON-compatible dict. @@ -376,11 +405,14 @@ def schema_to_dict(schema: CompiledSchema) -> dict[str, Any]: entry["blocks"] = list(col.config.blocks) cols.append(entry) - return { + result = { "version": 1, "row_cls": schema.row_cls.__name__ if schema.row_cls is not None else None, "columns": cols, } + if schema.metadata: + result["metadata"] = schema.metadata + return result def schema_from_dict(data: dict[str, Any]) -> CompiledSchema: @@ -410,19 +442,14 @@ def schema_from_dict(data: dict[str, Any]) -> CompiledSchema: chunks = tuple(entry.pop("chunks")) if "chunks" in entry else None blocks = tuple(entry.pop("blocks")) if "blocks" in entry else None - spec_cls = _KIND_TO_SPEC.get(kind) - if spec_cls is None: - raise ValueError(f"Unknown column kind {kind!r}") - - # Remaining keys in entry are constraint kwargs (ge, le, max_length, …) - spec = spec_cls(**entry) + spec = spec_from_metadata_dict({"kind": kind, **entry}) columns.append( CompiledColumn( name=name, py_type=spec.python_type, spec=spec, - dtype=spec.dtype, + dtype=getattr(spec, "dtype", None), default=default, config=ColumnConfig(cparams=cparams, dparams=dparams, chunks=chunks, blocks=blocks), display_width=compute_display_width(spec), @@ -433,4 +460,5 @@ def schema_from_dict(data: dict[str, Any]) -> CompiledSchema: row_cls=None, columns=columns, columns_by_name={col.name: col for col in columns}, + metadata=dict(data.get("metadata", {})), ) diff --git a/src/blosc2/schema_validation.py b/src/blosc2/schema_validation.py index 91f157f7..88260701 100644 --- a/src/blosc2/schema_validation.py +++ b/src/blosc2/schema_validation.py @@ -19,6 +19,8 @@ from pydantic import BaseModel, Field, ValidationError, create_model +from blosc2.list_array import coerce_list_cell +from blosc2.schema import ListSpec from blosc2.schema_compiler import CompiledSchema # noqa: TC001 @@ -41,8 +43,14 @@ def build_validator_model(schema: CompiledSchema) -> type[BaseModel]: field_definitions: dict[str, Any] = {} for col in schema.columns: pydantic_kwargs = col.spec.to_pydantic_kwargs() - is_nullable = getattr(col.spec, "null_value", None) is not None - py_type = col.py_type | None if is_nullable else col.py_type + is_nullable = getattr(col.spec, "null_value", None) is not None or ( + isinstance(col.spec, ListSpec) and col.spec.nullable + ) + if isinstance(col.spec, ListSpec): + item_type = col.spec.item_spec.python_type + py_type = list[item_type] | None if is_nullable else list[item_type] + else: + py_type = col.py_type | None if is_nullable else col.py_type if col.default is MISSING: default = None if is_nullable else MISSING @@ -118,7 +126,11 @@ def validate_row(schema: CompiledSchema, row: dict[str, Any]) -> dict[str, Any]: name and the violated constraint. """ model_cls = build_validator_model(schema) - masked_row, nulled = _mask_nulls(schema, row) + normalized = dict(row) + for col in schema.columns: + if isinstance(col.spec, ListSpec) and col.name in normalized: + normalized[col.name] = coerce_list_cell(col.spec, normalized[col.name]) + masked_row, nulled = _mask_nulls(schema, normalized) try: instance = model_cls(**masked_row) except ValidationError as exc: @@ -148,7 +160,11 @@ def validate_rows_rowwise(schema: CompiledSchema, rows: list[dict[str, Any]]) -> model_cls = build_validator_model(schema) result = [] for i, row in enumerate(rows): - masked_row, nulled = _mask_nulls(schema, row) + normalized = dict(row) + for col in schema.columns: + if isinstance(col.spec, ListSpec) and col.name in normalized: + normalized[col.name] = coerce_list_cell(col.spec, normalized[col.name]) + masked_row, nulled = _mask_nulls(schema, normalized) try: instance = model_cls(**masked_row) except ValidationError as exc: diff --git a/src/blosc2/schema_vectorized.py b/src/blosc2/schema_vectorized.py index c15f2dd2..e2e6de6b 100644 --- a/src/blosc2/schema_vectorized.py +++ b/src/blosc2/schema_vectorized.py @@ -19,6 +19,8 @@ import numpy as np +from blosc2.list_array import coerce_list_cell +from blosc2.schema import ListSpec from blosc2.schema_compiler import CompiledColumn, CompiledSchema # noqa: TC001 @@ -75,6 +77,11 @@ def validate_column_values(col: CompiledColumn, values: Any) -> None: If any value violates a constraint declared on the column's spec. """ spec = col.spec + if isinstance(spec, ListSpec): + for value in values: + coerce_list_cell(spec, value) + return + arr = np.asarray(values) # Compute null mask so sentinels bypass constraint checks diff --git a/src/blosc2/schunk.py b/src/blosc2/schunk.py index 55bf31db..7ede9bd0 100644 --- a/src/blosc2/schunk.py +++ b/src/blosc2/schunk.py @@ -876,6 +876,35 @@ def update_chunk(self, nchunk: int, chunk: bytes) -> int: blosc2_ext.check_access_mode(self.urlpath, self.mode) return super().update_chunk(nchunk, chunk) + def reorder_offsets(self, order: Any) -> None: + """Reorder the chunk offsets of the SChunk in place. + + This is a low-level storage operation that changes the physical chunk + order of the underlying SChunk. Higher-level containers backed by this + SChunk will observe the reordered chunk traversal afterwards. + + Parameters + ---------- + order: array-like of int + A one-dimensional permutation of ``range(self.nchunks)`` describing + the new chunk order. + + Raises + ------ + ValueError + If ``order`` is not one-dimensional or its length does not match + the number of chunks in the SChunk. + RuntimeError + If the underlying reorder operation fails. + """ + blosc2_ext.check_access_mode(self.urlpath, self.mode) + order = np.asarray(order, dtype=np.int64) + if order.ndim != 1: + raise ValueError("`order` must be a one-dimensional sequence") + if len(order) != self.nchunks: + raise ValueError("`order` must have exactly `self.nchunks` elements") + super().reorder_offsets(order) + def update_data(self, nchunk: int, data: object, copy: bool) -> int: """Update the chunk in the specified position with the given data. @@ -1653,6 +1682,11 @@ def process_opened_object(res): if "b2o" in meta: return blosc2.open_b2object(res) + if "listarray" in meta: + from blosc2.list_array import ListArray + + return ListArray(_from_schunk=getattr(res, "schunk", res)) + if "vlarray" in meta: from blosc2.vlarray import VLArray diff --git a/tests/ctable/test_arrow_interop.py b/tests/ctable/test_arrow_interop.py index e016e031..57d153c3 100644 --- a/tests/ctable/test_arrow_interop.py +++ b/tests/ctable/test_arrow_interop.py @@ -118,49 +118,49 @@ def test_to_arrow_where_view(): def test_from_arrow_returns_ctable(): t = CTable(Row, new_data=DATA10) at = t.to_arrow() - t2 = CTable.from_arrow(at) + t2 = CTable.from_arrow(at.schema, at.to_batches()) assert isinstance(t2, CTable) def test_from_arrow_row_count(): t = CTable(Row, new_data=DATA10) at = t.to_arrow() - t2 = CTable.from_arrow(at) + t2 = CTable.from_arrow(at.schema, at.to_batches()) assert len(t2) == 10 def test_from_arrow_column_names(): t = CTable(Row, new_data=DATA10) at = t.to_arrow() - t2 = CTable.from_arrow(at) + t2 = CTable.from_arrow(at.schema, at.to_batches()) assert t2.col_names == ["id", "score", "active", "label"] def test_from_arrow_int_values(): t = CTable(Row, new_data=DATA10) at = t.to_arrow() - t2 = CTable.from_arrow(at) + t2 = CTable.from_arrow(at.schema, at.to_batches()) np.testing.assert_array_equal(t2["id"][:], t["id"][:]) def test_from_arrow_float_values(): t = CTable(Row, new_data=DATA10) at = t.to_arrow() - t2 = CTable.from_arrow(at) + t2 = CTable.from_arrow(at.schema, at.to_batches()) np.testing.assert_allclose(t2["score"][:], t["score"][:]) def test_from_arrow_bool_values(): t = CTable(Row, new_data=DATA10) at = t.to_arrow() - t2 = CTable.from_arrow(at) + t2 = CTable.from_arrow(at.schema, at.to_batches()) np.testing.assert_array_equal(t2["active"][:], t["active"][:]) def test_from_arrow_string_values(): t = CTable(Row, new_data=DATA10) at = t.to_arrow() - t2 = CTable.from_arrow(at) + t2 = CTable.from_arrow(at.schema, at.to_batches()) assert t2["label"][:].tolist() == t["label"][:].tolist() @@ -172,7 +172,7 @@ def test_from_arrow_empty_table(): ] ) at = pa.table({"id": pa.array([], type=pa.int64()), "val": pa.array([], type=pa.float64())}) - t = CTable.from_arrow(at) + t = CTable.from_arrow(at.schema, at.to_batches()) assert len(t) == 0 assert t.col_names == ["id", "val"] @@ -180,7 +180,8 @@ def test_from_arrow_empty_table(): def test_from_arrow_roundtrip(): """to_arrow then from_arrow preserves all values.""" t = CTable(Row, new_data=DATA10) - t2 = CTable.from_arrow(t.to_arrow()) + at = t.to_arrow() + t2 = CTable.from_arrow(at.schema, at.to_batches()) for name in ["id", "score", "active"]: np.testing.assert_array_equal(t2[name][:], t[name][:]) assert t2["label"][:].tolist() == t["label"][:].tolist() @@ -202,7 +203,7 @@ def test_from_arrow_all_numeric_types(): "f64": pa.array([1.0, 2.0, 3.0], type=pa.float64()), } ) - t = CTable.from_arrow(at) + t = CTable.from_arrow(at.schema, at.to_batches()) assert len(t) == 3 assert t.col_names == list(at.column_names) @@ -210,7 +211,7 @@ def test_from_arrow_all_numeric_types(): def test_from_arrow_string_max_length(): """String max_length is set from the longest value in the data.""" at = pa.table({"name": pa.array(["hi", "hello world", "!"], type=pa.string())}) - t = CTable.from_arrow(at) + t = CTable.from_arrow(at.schema, at.to_batches()) # "hello world" is 11 chars — stored dtype must accommodate it assert t["name"].dtype.itemsize // 4 >= 11 @@ -218,7 +219,7 @@ def test_from_arrow_string_max_length(): def test_from_arrow_unsupported_type_raises(): at = pa.table({"ts": pa.array([1, 2, 3], type=pa.timestamp("s"))}) with pytest.raises(TypeError, match="No blosc2 spec"): - CTable.from_arrow(at) + CTable.from_arrow(at.schema, at.to_batches()) if __name__ == "__main__": diff --git a/tests/ctable/test_column_ndarray_like.py b/tests/ctable/test_column_ndarray_like.py new file mode 100644 index 00000000..d12de3e7 --- /dev/null +++ b/tests/ctable/test_column_ndarray_like.py @@ -0,0 +1,51 @@ +from __future__ import annotations + +from dataclasses import dataclass + +import numpy as np + +import blosc2 + + +@dataclass +class Row: + x: int = blosc2.field(blosc2.int32()) + y: int = blosc2.field(blosc2.int32()) + flag: bool = blosc2.field(blosc2.bool()) + + +DATA = [(1, 5, True), (-1, 5, True), (2, 20, False), (3, 7, False)] + + +def test_column_logical_metadata(): + t = blosc2.CTable(Row, new_data=DATA) + view = t.where(t.x > 0) + + assert view.x.shape == (3,) + assert view.x.ndim == 1 + assert view.x.size == 3 + + +def test_column_boolean_operators_build_lazy_expressions(): + t = blosc2.CTable(Row, new_data=DATA) + + view = t.where(t.flag & (t.x > 0)) + + np.testing.assert_array_equal(view.x[:], np.array([1], dtype=np.int32)) + + +def test_column_boolean_invert_builds_lazy_expression(): + t = blosc2.CTable(Row, new_data=DATA) + + view = t.where(~t.flag) + + np.testing.assert_array_equal(view.x[:], np.array([2, 3], dtype=np.int32)) + + +def test_column_sum_accepts_dtype(): + t = blosc2.CTable(Row, new_data=DATA) + + result = t.x.sum(dtype=np.float64) + + assert isinstance(result, np.floating) + assert result == 5.0 diff --git a/tests/ctable/test_nullable.py b/tests/ctable/test_nullable.py index 3f88b8fe..07d9d447 100644 --- a/tests/ctable/test_nullable.py +++ b/tests/ctable/test_nullable.py @@ -95,6 +95,83 @@ def test_null_value_string(): assert t["label"].null_value == "" +def test_nullable_true_uses_default_null_policy(): + @dataclass + class Row: + i: int = blosc2.field(blosc2.int32(nullable=True)) + u: int = blosc2.field(blosc2.uint32(nullable=True)) + f: float = blosc2.field(blosc2.float64(nullable=True)) + flag: bool = blosc2.field(blosc2.bool(nullable=True)) + s: str = blosc2.field(blosc2.string(max_length=4, nullable=True)) + b: bytes = blosc2.field(blosc2.bytes(max_length=4, nullable=True)) + + t = CTable(Row) + assert t["i"].null_value == np.iinfo(np.int32).min + assert t["u"].null_value == np.iinfo(np.uint32).max + assert math.isnan(t["f"].null_value) + assert t["flag"].null_value == 255 + assert t["s"].null_value == "__BLOSC2_NULL__" + assert t["b"].null_value == b"__BLOSC2_NULL__" + assert t["s"].dtype.itemsize // 4 >= len("__BLOSC2_NULL__") + assert t["b"].dtype.itemsize >= len(b"__BLOSC2_NULL__") + + +def test_nullable_true_uses_null_policy_context_and_column_null_values(): + @dataclass + class Row: + i: int = blosc2.field(blosc2.int32(nullable=True)) + s: str = blosc2.field(blosc2.string(max_length=4, nullable=True)) + + policy = blosc2.NullPolicy( + signed_int_strategy="max", string_value="", column_null_values={"i": -1} + ) + with blosc2.null_policy(policy): + t = CTable(Row) + + assert t["i"].null_value == -1 + assert t["s"].null_value == "" + + +def test_explicit_null_value_overrides_nullable_policy(): + @dataclass + class Row: + i: int = blosc2.field(blosc2.int32(nullable=True, null_value=-5)) + + policy = blosc2.NullPolicy(signed_int_strategy="max") + with blosc2.null_policy(policy): + t = CTable(Row) + + assert t["i"].null_value == -5 + + +def test_add_column_nullable_true_uses_null_policy(): + t = CTable(IntRow) + with blosc2.null_policy(blosc2.NullPolicy(signed_int_strategy="max")): + t.add_column("extra", blosc2.int32(nullable=True), 0) + + assert t["extra"].null_value == np.iinfo(np.int32).max + + +def test_nullable_policy_rejects_out_of_range_integer_sentinel(): + @dataclass + class Row: + x: int = blosc2.field(blosc2.int8(nullable=True)) + + with blosc2.null_policy(blosc2.NullPolicy(column_null_values={"x": 1000})): + with pytest.raises(ValueError, match="outside int8 range"): + CTable(Row) + + +def test_nullable_policy_rejects_wrong_string_sentinel_type(): + @dataclass + class Row: + s: str = blosc2.field(blosc2.string(nullable=True)) + + with blosc2.null_policy(blosc2.NullPolicy(column_null_values={"s": b"NA"})): + with pytest.raises(TypeError, match="must be str"): + CTable(Row) + + # =========================================================================== # is_null / notnull / null_count # =========================================================================== diff --git a/tests/ctable/test_parquet_interop.py b/tests/ctable/test_parquet_interop.py new file mode 100644 index 00000000..cf4733a8 --- /dev/null +++ b/tests/ctable/test_parquet_interop.py @@ -0,0 +1,556 @@ +####################################################################### +# Copyright (c) 2019-present, Blosc Development Team +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause +####################################################################### + +"""Tests for CTable.to_parquet(), from_parquet(), iter_arrow_batches(), +and from_arrow().""" + +from dataclasses import dataclass + +import numpy as np +import pytest + +import blosc2 +from blosc2 import CTable + +pa = pytest.importorskip("pyarrow") +pq = pytest.importorskip("pyarrow.parquet") + + +# --------------------------------------------------------------------------- +# Shared fixtures / dataclasses +# --------------------------------------------------------------------------- + + +@dataclass +class Row: + id: int = blosc2.field(blosc2.int64(ge=0)) + score: float = blosc2.field(blosc2.float64(ge=0, le=100), default=0.0) + active: bool = blosc2.field(blosc2.bool(), default=True) + label: str = blosc2.field(blosc2.string(max_length=32), default="") + + +DATA10 = [(i, float(i * 10 % 100), i % 2 == 0, f"row{i}") for i in range(10)] + + +# --------------------------------------------------------------------------- +# iter_arrow_batches +# --------------------------------------------------------------------------- + + +class TestIterArrowBatches: + def test_yields_record_batches(self): + t = CTable(Row, new_data=DATA10) + batches = list(t.iter_arrow_batches()) + assert all(isinstance(b, pa.RecordBatch) for b in batches) + + def test_total_row_count(self): + t = CTable(Row, new_data=DATA10) + total = sum(len(b) for b in t.iter_arrow_batches()) + assert total == 10 + + def test_batching_splits_correctly(self): + t = CTable(Row, new_data=DATA10) + batches = list(t.iter_arrow_batches(batch_size=3)) + sizes = [len(b) for b in batches] + assert sizes == [3, 3, 3, 1] + + def test_column_names(self): + t = CTable(Row, new_data=DATA10) + (batch,) = t.iter_arrow_batches() + assert batch.schema.names == ["id", "score", "active", "label"] + + def test_int_values(self): + t = CTable(Row, new_data=DATA10) + (batch,) = t.iter_arrow_batches() + np.testing.assert_array_equal(batch.column("id").to_pylist(), [r[0] for r in DATA10]) + + def test_float_values(self): + t = CTable(Row, new_data=DATA10) + (batch,) = t.iter_arrow_batches() + np.testing.assert_allclose(batch.column("score").to_pylist(), [r[1] for r in DATA10]) + + def test_bool_values(self): + t = CTable(Row, new_data=DATA10) + (batch,) = t.iter_arrow_batches() + assert batch.column("active").to_pylist() == [r[2] for r in DATA10] + + def test_string_values(self): + t = CTable(Row, new_data=DATA10) + (batch,) = t.iter_arrow_batches() + assert batch.column("label").to_pylist() == [r[3] for r in DATA10] + + def test_empty_table_yields_nothing(self): + t = CTable(Row) + batches = list(t.iter_arrow_batches()) + assert batches == [] + + def test_column_projection(self): + t = CTable(Row, new_data=DATA10) + (batch,) = t.iter_arrow_batches(columns=["id", "score"]) + assert batch.schema.names == ["id", "score"] + assert len(batch) == 10 + + def test_column_projection_unknown_raises(self): + t = CTable(Row, new_data=DATA10) + with pytest.raises(KeyError, match="nope"): + list(t.iter_arrow_batches(columns=["nope"])) + + def test_skips_deleted_rows(self): + t = CTable(Row, new_data=DATA10) + t.delete([0, 1, 2]) + total = sum(len(b) for b in t.iter_arrow_batches()) + assert total == 7 + + def test_include_computed_false(self): + t = CTable(Row, new_data=DATA10) + t.add_computed_column("double_id", "id * 2") + (batch,) = t.iter_arrow_batches(include_computed=False) + assert "double_id" not in batch.schema.names + + def test_computed_column_values(self): + t = CTable(Row, new_data=DATA10) + t.add_computed_column("double_id", "id * 2") + (batch,) = t.iter_arrow_batches() + assert batch.column("double_id").to_pylist() == [i * 2 for i in range(10)] + + def test_invalid_batch_size(self): + t = CTable(Row, new_data=DATA10) + with pytest.raises(ValueError, match="batch_size"): + list(t.iter_arrow_batches(batch_size=0)) + + +# --------------------------------------------------------------------------- +# to_parquet / from_parquet round-trips +# --------------------------------------------------------------------------- + + +class TestParquetRoundTrip: + def test_basic_roundtrip(self, tmp_path): + t = CTable(Row, new_data=DATA10) + path = tmp_path / "basic.parquet" + t.to_parquet(path) + t2 = CTable.from_parquet(path) + assert len(t2) == 10 + assert t2.col_names == ["id", "score", "active", "label"] + np.testing.assert_array_equal(t2["id"][:], t["id"][:]) + np.testing.assert_allclose(t2["score"][:], t["score"][:]) + np.testing.assert_array_equal(t2["active"][:], t["active"][:]) + assert t2["label"][:].tolist() == t["label"][:].tolist() + + def test_roundtrip_all_numeric_types(self, tmp_path): + at = pa.table( + { + "i8": pa.array([1, 2, 3], type=pa.int8()), + "i16": pa.array([1, 2, 3], type=pa.int16()), + "i32": pa.array([1, 2, 3], type=pa.int32()), + "i64": pa.array([1, 2, 3], type=pa.int64()), + "u8": pa.array([1, 2, 3], type=pa.uint8()), + "u16": pa.array([1, 2, 3], type=pa.uint16()), + "u32": pa.array([1, 2, 3], type=pa.uint32()), + "u64": pa.array([1, 2, 3], type=pa.uint64()), + "f32": pa.array([1.0, 2.0, 3.0], type=pa.float32()), + "f64": pa.array([1.0, 2.0, 3.0], type=pa.float64()), + } + ) + t = CTable.from_arrow(at.schema, at.to_batches()) + path = tmp_path / "numeric.parquet" + t.to_parquet(path) + t2 = CTable.from_parquet(path) + assert t2.col_names == list(at.column_names) + assert len(t2) == 3 + + def test_roundtrip_bool(self, tmp_path): + at = pa.table({"flag": pa.array([True, False, True], type=pa.bool_())}) + t = CTable.from_arrow(at.schema, at.to_batches()) + path = tmp_path / "bool.parquet" + t.to_parquet(path) + t2 = CTable.from_parquet(path) + assert t2["flag"][:].tolist() == [True, False, True] + + def test_roundtrip_strings(self, tmp_path): + at = pa.table({"name": pa.array(["alice", "bob", "carol"], type=pa.string())}) + t = CTable.from_arrow(at.schema, at.to_batches()) + path = tmp_path / "strings.parquet" + t.to_parquet(path) + t2 = CTable.from_parquet(path) + assert t2["name"][:].tolist() == ["alice", "bob", "carol"] + + def test_roundtrip_bytes(self, tmp_path): + at = pa.table({"data": pa.array([b"hello", b"world", b"foo"], type=pa.large_binary())}) + t = CTable.from_arrow(at.schema, at.to_batches()) + path = tmp_path / "bytes.parquet" + t.to_parquet(path) + t2 = CTable.from_parquet(path) + raw = t2["data"][:] + assert [raw[i].tobytes().rstrip(b"\x00") for i in range(3)] == [ + b"hello", + b"world", + b"foo", + ] + + def test_roundtrip_list_column(self, tmp_path): + @dataclass + class ListRow: + vals: list[int] = blosc2.field( # noqa: RUF009 + blosc2.list(blosc2.int64(), storage="batch", serializer="msgpack") + ) + + data = [([1, 2, 3],), ([4, 5],), ([],)] + t = CTable(ListRow, new_data=data) + path = tmp_path / "lists.parquet" + t.to_parquet(path) + t2 = CTable.from_parquet(path) + assert len(t2) == 3 + assert t2["vals"][0] == [1, 2, 3] + assert t2["vals"][1] == [4, 5] + assert t2["vals"][2] == [] + + def test_roundtrip_list_struct_column(self, tmp_path): + struct_type = pa.struct([pa.field("a", pa.int32()), pa.field("b", pa.string())]) + at = pa.table( + { + "items": pa.array( + [[{"a": 1, "b": "x"}], None, [{"a": 2, "b": "yy"}]], + type=pa.list_(struct_type), + ) + } + ) + path = tmp_path / "list_struct.parquet" + pq.write_table(at, path) + t = CTable.from_parquet(path) + assert t["items"][0] == [{"a": 1, "b": "x"}] + assert t["items"][1] is None + out = tmp_path / "list_struct_out.parquet" + t.to_parquet(out) + rt = pq.read_table(out) + assert rt.schema == at.schema + assert rt["items"].to_pylist() == at["items"].to_pylist() + assert "arrow" in t._schema.metadata + + def test_empty_table_export_import(self, tmp_path): + t = CTable(Row) + path = tmp_path / "empty.parquet" + t.to_parquet(path) + t2 = CTable.from_parquet(path) + assert len(t2) == 0 + assert t2.col_names == ["id", "score", "active", "label"] + + def test_column_projection_export(self, tmp_path): + t = CTable(Row, new_data=DATA10) + path = tmp_path / "proj.parquet" + t.to_parquet(path, columns=["id", "score"]) + t2 = CTable.from_parquet(path) + assert t2.col_names == ["id", "score"] + assert len(t2) == 10 + + def test_column_projection_import(self, tmp_path): + t = CTable(Row, new_data=DATA10) + path = tmp_path / "full.parquet" + t.to_parquet(path) + t2 = CTable.from_parquet(path, columns=["id", "label"]) + assert t2.col_names == ["id", "label"] + assert len(t2) == 10 + + def test_computed_column_exported_as_values(self, tmp_path): + t = CTable(Row, new_data=DATA10) + t.add_computed_column("double_id", "id * 2") + path = tmp_path / "computed.parquet" + t.to_parquet(path) + t2 = CTable.from_parquet(path) + assert "double_id" in t2.col_names + # double_id is stored, not computed in t2 + assert "double_id" not in t2._computed_cols + np.testing.assert_array_equal( + t2["double_id"][:], np.array([i * 2 for i in range(10)], dtype=np.int64) + ) + + def test_exclude_computed_columns(self, tmp_path): + t = CTable(Row, new_data=DATA10) + t.add_computed_column("double_id", "id * 2") + path = tmp_path / "no_computed.parquet" + t.to_parquet(path, include_computed=False) + t2 = CTable.from_parquet(path) + assert "double_id" not in t2.col_names + + def test_only_live_rows_exported(self, tmp_path): + t = CTable(Row, new_data=DATA10) + t.delete([0, 1]) + path = tmp_path / "deleted.parquet" + t.to_parquet(path) + t2 = CTable.from_parquet(path) + assert len(t2) == 8 + np.testing.assert_array_equal(t2["id"][:], list(range(2, 10))) + + def test_multiple_batches_written(self, tmp_path): + t = CTable(Row, new_data=DATA10) + path = tmp_path / "multi.parquet" + t.to_parquet(path, batch_size=3) + meta = pq.read_metadata(path) + assert meta.num_row_groups == 4 # 3+3+3+1 + + def test_persistent_urlpath(self, tmp_path): + t = CTable(Row, new_data=DATA10) + parquet_path = tmp_path / "data.parquet" + ctable_path = str(tmp_path / "data.b2d") + t.to_parquet(parquet_path) + t2 = CTable.from_parquet(parquet_path, urlpath=ctable_path) + assert len(t2) == 10 + import os + + assert os.path.exists(ctable_path) + + def test_different_compression(self, tmp_path): + t = CTable(Row, new_data=DATA10) + for codec in ["snappy", "lz4", None]: + path = tmp_path / f"{codec}.parquet" + t.to_parquet(path, compression=codec) + t2 = CTable.from_parquet(path) + assert len(t2) == 10 + + def test_roundtrip_larger_table_batch_import(self, tmp_path): + """Verify correct row count with batch_size < n_rows on import.""" + t = CTable(Row, new_data=DATA10) + path = tmp_path / "large.parquet" + t.to_parquet(path) + t2 = CTable.from_parquet(path, batch_size=3) + assert len(t2) == 10 + np.testing.assert_array_equal(t2["id"][:], t["id"][:]) + + def test_interop_read_with_pyarrow(self, tmp_path): + """CTable-written Parquet is readable by PyArrow.""" + t = CTable(Row, new_data=DATA10) + path = tmp_path / "compat.parquet" + t.to_parquet(path) + at = pq.read_table(path) + assert len(at) == 10 + assert at.column_names == ["id", "score", "active", "label"] + + def test_interop_write_with_pyarrow(self, tmp_path): + """PyArrow-written Parquet is readable by CTable.""" + at = pa.table( + { + "x": pa.array([1, 2, 3], type=pa.int32()), + "y": pa.array([1.1, 2.2, 3.3], type=pa.float64()), + } + ) + path = tmp_path / "pyarrow_written.parquet" + pq.write_table(at, path) + t = CTable.from_parquet(path) + assert len(t) == 3 + assert t.col_names == ["x", "y"] + + def test_from_arrow_list_batch_rows_default(self): + at = pa.table({"vals": pa.array([[1], [2, 3]], type=pa.list_(pa.int64()))}) + t = CTable.from_arrow(at.schema, at.to_batches()) + assert t._schema.columns_by_name["vals"].spec.batch_rows == 2048 + assert t["vals"][0] == [1] + assert t["vals"][1] == [2, 3] + + def test_from_arrow_list_batch_rows_override_and_none(self): + at = pa.table({"vals": pa.array([[1], [2], [3]], type=pa.list_(pa.int64()))}) + t = CTable.from_arrow(at.schema, at.to_batches(max_chunksize=1), list_batch_rows=2) + assert t._schema.columns_by_name["vals"].spec.batch_rows == 2 + + t2 = CTable.from_arrow(at.schema, at.to_batches(max_chunksize=1), list_batch_rows=None) + assert t2._schema.columns_by_name["vals"].spec.batch_rows is None + + def test_from_arrow_invalid_list_batch_rows_raises(self): + at = pa.table({"vals": pa.array([[1]], type=pa.list_(pa.int64()))}) + with pytest.raises(ValueError, match="list_batch_rows"): + CTable.from_arrow(at.schema, at.to_batches(), list_batch_rows=0) + + +# --------------------------------------------------------------------------- +# Null handling +# --------------------------------------------------------------------------- + + +class TestNullHandling: + def test_nullable_list_column_roundtrip(self, tmp_path): + @dataclass + class NullableListRow: + vals: list[int] = blosc2.field( # noqa: RUF009 + blosc2.list(blosc2.int64(), nullable=True, storage="batch", serializer="msgpack") + ) + + data = [([1, 2],), (None,), ([3],)] + t = CTable(NullableListRow, new_data=data) + path = tmp_path / "nullable_list.parquet" + t.to_parquet(path) + t2 = CTable.from_parquet(path) + assert len(t2) == 3 + assert t2["vals"][0] == [1, 2] + assert t2["vals"][1] is None + assert t2["vals"][2] == [3] + + def test_scalar_null_no_sentinel_raises(self, tmp_path): + """Importing Parquet scalar nulls without a null_value sentinel fails.""" + at = pa.table({"score": pa.array([1.0, None, 3.0], type=pa.float64())}) + path = tmp_path / "nulls.parquet" + pq.write_table(at, path) + with pytest.raises(TypeError, match="null_value sentinel"): + CTable.from_parquet(path, auto_null_sentinels=False) + + def test_scalar_null_exported_as_parquet_null(self, tmp_path): + """Sentinel values become Parquet nulls on export.""" + + @dataclass + class NullRow: + score: float = blosc2.field(blosc2.float64(null_value=float("nan")), default=float("nan")) + + t = CTable(NullRow, new_data=[(1.0,), (float("nan"),), (3.0,)]) + path = tmp_path / "null_export.parquet" + t.to_parquet(path) + at = pq.read_table(path) + nulls = at["score"].is_null().to_pylist() + assert nulls == [False, True, False] + + def test_auto_nullable_scalars_roundtrip(self, tmp_path): + at = pa.table( + { + "i": pa.array([1, None, 3], type=pa.int32()), + "f": pa.array([1.0, None, 3.0], type=pa.float64()), + "s": pa.array(["a", None, "c"], type=pa.string()), + "b": pa.array([b"a", None, b"c"], type=pa.large_binary()), + "flag": pa.array([True, None, False], type=pa.bool_()), + } + ) + path = tmp_path / "nullable_scalars.parquet" + pq.write_table(at, path) + t = CTable.from_parquet(path) + assert t["i"].null_count() == 1 + assert t["f"].null_count() == 1 + assert t["s"].null_count() == 1 + assert t["b"].null_count() == 1 + assert t["flag"].null_count() == 1 + assert t["flag"][:].tolist() == [1, 255, 0] + out = tmp_path / "nullable_scalars_out.parquet" + t.to_parquet(out) + rt = pq.read_table(out) + assert rt.schema == at.schema + assert rt.to_pylist() == at.to_pylist() + + def test_null_policy_controls_default_sentinels(self): + at = pa.table( + { + "i": pa.array([1, None, 3], type=pa.int32()), + "s": pa.array(["a", None, "c"], type=pa.string()), + } + ) + policy = blosc2.NullPolicy(signed_int_strategy="max", string_value="") + with blosc2.null_policy(policy): + t = CTable.from_arrow(at.schema, at.to_batches()) + assert blosc2.get_null_policy() is blosc2.DEFAULT_NULL_POLICY + assert t._schema.columns_by_name["i"].spec.null_value == np.iinfo(np.int32).max + assert t._schema.columns_by_name["s"].spec.null_value == "" + assert t["i"].null_count() == 1 + assert t["s"].null_count() == 1 + + def test_null_values_override_policy_and_auto_false(self, tmp_path): + at = pa.table( + { + "i": pa.array([1, None, 3], type=pa.int32()), + "s": pa.array(["a", None, "c"], type=pa.string()), + } + ) + policy = blosc2.NullPolicy(column_null_values={"i": -1, "s": "NA"}) + with blosc2.null_policy(policy): + t = CTable.from_arrow(at.schema, at.to_batches(), auto_null_sentinels=False) + assert t._schema.columns_by_name["i"].spec.null_value == -1 + assert t._schema.columns_by_name["s"].spec.null_value == "NA" + assert t["i"].null_count() == 1 + assert t["s"].null_count() == 1 + + path = tmp_path / "null_values.parquet" + pq.write_table(at, path) + with blosc2.null_policy(policy): + t2 = CTable.from_parquet(path, auto_null_sentinels=False) + assert t2._schema.columns_by_name["i"].spec.null_value == -1 + assert t2._schema.columns_by_name["s"].spec.null_value == "NA" + + def test_null_policy_unknown_column_raises(self): + at = pa.table({"i": pa.array([1, None], type=pa.int32())}) + policy = blosc2.NullPolicy(column_null_values={"missing": -1}) + with blosc2.null_policy(policy), pytest.raises(KeyError, match="unknown columns"): + CTable.from_arrow(at.schema, at.to_batches()) + + def test_null_policy_rejects_list_columns(self): + at = pa.table({"vals": pa.array([[1], None], type=pa.list_(pa.int64()))}) + policy = blosc2.NullPolicy(column_null_values={"vals": []}) + with blosc2.null_policy(policy), pytest.raises(TypeError, match="only supports scalar columns"): + CTable.from_arrow(at.schema, at.to_batches()) + + def test_nullable_bool_filter_semantics(self, tmp_path): + at = pa.table({"flag": pa.array([True, None, False], type=pa.bool_())}) + path = tmp_path / "nullable_bool.parquet" + pq.write_table(at, path) + t = CTable.from_parquet(path) + assert t.where(t.flag).flag[:].tolist() == [1] + assert t.where(~t.flag).flag[:].tolist() == [0] + assert t.where(t.flag == True).flag[:].tolist() == [1] # noqa: E712 + assert t.where(t.flag == False).flag[:].tolist() == [0] # noqa: E712 + assert t.where(t.flag != True).flag[:].tolist() == [0] # noqa: E712 + assert t.where(t.flag != False).flag[:].tolist() == [1] # noqa: E712 + + +# --------------------------------------------------------------------------- +# Error handling +# --------------------------------------------------------------------------- + + +class TestErrors: + def test_missing_pyarrow_to_parquet(self, tmp_path, monkeypatch): + """to_parquet raises ImportError when pyarrow is not available.""" + import sys + + monkeypatch.setitem(sys.modules, "pyarrow.parquet", None) + t = CTable(Row, new_data=DATA10) + with pytest.raises(ImportError, match="pyarrow"): + t.to_parquet(tmp_path / "x.parquet") + + def test_missing_pyarrow_from_parquet(self, tmp_path, monkeypatch): + """from_parquet raises ImportError when pyarrow is not available.""" + import sys + + t = CTable(Row, new_data=DATA10) + path = tmp_path / "x.parquet" + t.to_parquet(path) + monkeypatch.setitem(sys.modules, "pyarrow.parquet", None) + with pytest.raises(ImportError, match="pyarrow"): + CTable.from_parquet(path) + + def test_unsupported_arrow_type(self, tmp_path): + at = pa.table({"ts": pa.array([1, 2, 3], type=pa.timestamp("s"))}) + path = tmp_path / "ts.parquet" + pq.write_table(at, path) + with pytest.raises(TypeError, match="No blosc2 spec"): + CTable.from_parquet(path) + + def test_invalid_batch_size_to_parquet(self, tmp_path): + t = CTable(Row, new_data=DATA10) + with pytest.raises(ValueError, match="batch_size"): + t.to_parquet(tmp_path / "x.parquet", batch_size=0) + + def test_invalid_batch_size_from_parquet(self, tmp_path): + t = CTable(Row, new_data=DATA10) + path = tmp_path / "x.parquet" + t.to_parquet(path) + with pytest.raises(ValueError, match="batch_size"): + CTable.from_parquet(path, batch_size=0) + + def test_string_truncation_error(self, tmp_path): + """Importing longer strings than max_length raises ValueError.""" + at = pa.table({"name": pa.array(["a" * 300, "b"], type=pa.string())}) + path = tmp_path / "long_str.parquet" + pq.write_table(at, path) + # Explicit small max_length should raise on import + with pytest.raises(ValueError, match="max_length"): + CTable.from_parquet(path, string_max_length=10) + + +if __name__ == "__main__": + pytest.main(["-v", __file__]) diff --git a/tests/ctable/test_sort_by.py b/tests/ctable/test_sort_by.py index cf4b335e..4ae03ae5 100644 --- a/tests/ctable/test_sort_by.py +++ b/tests/ctable/test_sort_by.py @@ -221,11 +221,19 @@ def test_sort_reverse_sorted(): # =========================================================================== -def test_sort_view_raises(): +def test_sort_view_inplace_raises(): t = CTable(Row, new_data=DATA) view = t.where(t["id"] > 2) - with pytest.raises(ValueError, match="view"): - view.sort_by("id") + with pytest.raises(ValueError, match="inplace"): + view.sort_by("id", inplace=True) + + +def test_sort_view_copy_works(): + t = CTable(Row, new_data=DATA) + view = t.where(t["id"] > 2) + sorted_view = view.sort_by("id", ascending=False) + ids = [sorted_view["id"][i] for i in range(len(sorted_view))] + assert ids == sorted(ids, reverse=True) def test_sort_unknown_column_raises(): diff --git a/tests/ctable/test_varlen_columns.py b/tests/ctable/test_varlen_columns.py new file mode 100644 index 00000000..9012feaf --- /dev/null +++ b/tests/ctable/test_varlen_columns.py @@ -0,0 +1,82 @@ +from __future__ import annotations + +from dataclasses import dataclass + +import pytest + +import blosc2 + + +@dataclass +class Product: + code: str = blosc2.field(blosc2.string(max_length=8)) + qty: int = blosc2.field(blosc2.int32()) + tags: list[str] = blosc2.field( # noqa: RUF009 + blosc2.list(blosc2.string(max_length=16), nullable=True, batch_rows=2) + ) + + +DATA = [ + ("a", 1, ["x", "y"]), + ("b", 2, []), + ("c", 3, None), + ("d", 4, ["z"]), +] + + +def test_ctable_varlen_append_extend_and_reads(): + t = blosc2.CTable(Product) + t.append(DATA[0]) + t.extend(DATA[1:]) + + assert len(t) == 4 + assert t.tags[0] == ["x", "y"] + assert t.tags[1:4] == [[], None, ["z"]] + assert t.row[2].tags[0] is None + + t.tags[2] = ["r", "s"] + assert t.tags[2] == ["r", "s"] + + +def test_ctable_varlen_where_select_head_tail_and_compact(): + t = blosc2.CTable(Product, new_data=DATA) + view = t.where(t.qty >= 2) + assert view.tags[:] == [[], None, ["z"]] + sel = t.select(["code", "tags"]) + assert sel.tags[:] == [["x", "y"], [], None, ["z"]] + assert t.head(2).tags[:] == [["x", "y"], []] + assert t.tail(2).tags[:] == [None, ["z"]] + + t.delete([1]) + t.compact() + assert t.tags[:] == [["x", "y"], None, ["z"]] + + +def test_ctable_varlen_persistence_save_load_open(tmp_path): + path = tmp_path / "products.b2d" + t = blosc2.CTable(Product, new_data=DATA, urlpath=str(path), mode="w") + t.close() + + opened = blosc2.CTable.open(str(path), mode="r") + assert opened.tags[:] == [["x", "y"], [], None, ["z"]] + + loaded = blosc2.CTable.load(str(path)) + assert loaded.tags[:] == [["x", "y"], [], None, ["z"]] + loaded.tags[1] = ["changed"] + assert loaded.tags[1] == ["changed"] + + save_path = tmp_path / "products-save.b2d" + loaded.save(str(save_path)) + reopened = blosc2.CTable.open(str(save_path), mode="r") + assert reopened.tags[:] == [["x", "y"], ["changed"], None, ["z"]] + + +def test_ctable_varlen_arrow_roundtrip(): + pytest.importorskip("pyarrow") + + t = blosc2.CTable(Product, new_data=DATA) + arrow = t.to_arrow() + assert arrow.column("tags").to_pylist() == [["x", "y"], [], None, ["z"]] + + roundtrip = blosc2.CTable.from_arrow(arrow.schema, arrow.to_batches()) + assert roundtrip.tags[:] == [["x", "y"], [], None, ["z"]] diff --git a/tests/ctable/test_varlen_schema_compiler.py b/tests/ctable/test_varlen_schema_compiler.py new file mode 100644 index 00000000..80e8d3cb --- /dev/null +++ b/tests/ctable/test_varlen_schema_compiler.py @@ -0,0 +1,48 @@ +from __future__ import annotations + +from dataclasses import dataclass + +import pytest + +import blosc2 +from blosc2.schema import ListSpec +from blosc2.schema_compiler import compile_schema, schema_from_dict, schema_to_dict + + +@dataclass +class Product: + code: str = blosc2.field(blosc2.string(max_length=8)) + tags: list[str] = blosc2.field( # noqa: RUF009 + blosc2.list(blosc2.string(max_length=16), nullable=True, batch_rows=32) + ) + + +def test_list_builder_and_compile_schema(): + spec = blosc2.list(blosc2.string(max_length=10), nullable=True, storage="batch", serializer="msgpack") + assert isinstance(spec, ListSpec) + assert spec.nullable is True + assert spec.display_label() == "list[string]" + + schema = compile_schema(Product) + assert isinstance(schema.columns_by_name["tags"].spec, ListSpec) + assert schema.columns_by_name["tags"].dtype is None + + +def test_list_schema_roundtrip(): + schema = compile_schema(Product) + d = schema_to_dict(schema) + tags = next(c for c in d["columns"] if c["name"] == "tags") + assert tags["kind"] == "list" + assert tags["item"]["kind"] == "string" + restored = schema_from_dict(d) + assert isinstance(restored.columns_by_name["tags"].spec, ListSpec) + assert restored.columns_by_name["tags"].spec.batch_rows == 32 + + +def test_list_annotation_mismatch_rejected(): + @dataclass + class Bad: + tags: str = blosc2.field(blosc2.list(blosc2.string())) + + with pytest.raises(TypeError, match="list spec"): + compile_schema(Bad) diff --git a/tests/ctable/test_where_expressions.py b/tests/ctable/test_where_expressions.py new file mode 100644 index 00000000..0850ef9a --- /dev/null +++ b/tests/ctable/test_where_expressions.py @@ -0,0 +1,75 @@ +from __future__ import annotations + +from dataclasses import dataclass + +import numpy as np +import pytest + +import blosc2 + + +@dataclass +class Row: + value: int = blosc2.field(blosc2.int32()) + category: int = blosc2.field(blosc2.int32()) + + +DATA = [(10, 1), (20, 8), (30, 5), (2, 99)] + + +def test_where_accepts_string_expression(): + t = blosc2.CTable(Row, new_data=DATA) + + view = t.where("value * category >= 150") + + np.testing.assert_array_equal(view.value[:], np.array([20, 30, 2], dtype=np.int32)) + np.testing.assert_array_equal(view.category[:], np.array([8, 5, 99], dtype=np.int32)) + + +def test_where_accepts_column_arithmetic_expression(): + t = blosc2.CTable(Row, new_data=DATA) + + view = t.where((t.value * t.category) >= 150) + + np.testing.assert_array_equal(view.value[:], np.array([20, 30, 2], dtype=np.int32)) + np.testing.assert_array_equal(view.category[:], np.array([8, 5, 99], dtype=np.int32)) + + +def test_where_column_arithmetic_can_be_composed(): + t = blosc2.CTable(Row, new_data=DATA) + + view = t.where(((t.value + 2) * t.category) >= 100) + + np.testing.assert_array_equal(view.value[:], np.array([20, 30, 2], dtype=np.int32)) + + +def test_where_column_expression_accepts_transcendental_functions(): + t = blosc2.CTable(Row, new_data=DATA) + + view = t.where(((t.value + 2) * blosc2.sin(t.category)) >= 10) + + np.testing.assert_array_equal(view.value[:], np.array([10, 20], dtype=np.int32)) + + +def test_where_string_expression_accepts_transcendental_functions(): + t = blosc2.CTable(Row, new_data=DATA) + + view = t.where("(value + 2) * sin(category) >= 10") + + np.testing.assert_array_equal(view.value[:], np.array([10, 20], dtype=np.int32)) + + +def test_where_string_expression_can_reference_computed_columns(): + t = blosc2.CTable(Row, new_data=DATA) + t.add_computed_column("score", "value * category") + + view = t.where("score >= 150") + + np.testing.assert_array_equal(view.value[:], np.array([20, 30, 2], dtype=np.int32)) + + +def test_where_string_expression_must_be_boolean(): + t = blosc2.CTable(Row, new_data=DATA) + + with pytest.raises(TypeError, match="Expected boolean"): + t.where("value * category") diff --git a/tests/test_list_array.py b/tests/test_list_array.py new file mode 100644 index 00000000..649762a2 --- /dev/null +++ b/tests/test_list_array.py @@ -0,0 +1,84 @@ +####################################################################### +# Copyright (c) 2019-present, Blosc Development Team +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause +####################################################################### + +from __future__ import annotations + +import pytest + +import blosc2 + + +@pytest.mark.parametrize("storage", ["vl", "batch"]) +def test_listarray_append_extend_and_replace(storage, tmp_path): + urlpath = tmp_path / f"values-{storage}.b2b" + arr = blosc2.ListArray( + item_spec=blosc2.string(max_length=16), + nullable=True, + storage=storage, + batch_rows=2, + urlpath=str(urlpath), + mode="w", + ) + arr.append(["a", "b"]) + arr.append([]) + arr.append(None) + arr.extend([["c"], ["d", "e"]]) + + assert len(arr) == 5 + assert arr[0] == ["a", "b"] + assert arr[1] == [] + assert arr[2] is None + assert arr[1:4] == [[], None, ["c"]] + assert arr[[0, 2, 4]] == [["a", "b"], None, ["d", "e"]] + + arr[3] = ["x", "y"] + assert arr[3] == ["x", "y"] + + arr.flush() + reopened = blosc2.open(str(urlpath), mode="r") + assert isinstance(reopened, blosc2.ListArray) + assert reopened[:] == [["a", "b"], [], None, ["x", "y"], ["d", "e"]] + + restored = blosc2.from_cframe(arr.to_cframe()) + assert isinstance(restored, blosc2.ListArray) + assert restored[:] == reopened[:] + + +def test_listarray_batch_pending_rows_visible_before_flush(): + arr = blosc2.ListArray(item_spec=blosc2.int32(), storage="batch", batch_rows=4) + arr.append([1, 2]) + arr.append([]) + arr.append([3]) + + assert len(arr) == 3 + assert arr[:] == [[1, 2], [], [3]] + + +def test_listarray_rejects_invalid_cells(): + arr = blosc2.ListArray(item_spec=blosc2.int32(), nullable=False) + with pytest.raises(ValueError): + arr.append(None) + with pytest.raises(TypeError): + arr.append("abc") + with pytest.raises(ValueError): + arr.append([1, None]) + + +def test_listarray_boolean_fancy_indexing(): + arr = blosc2.ListArray(item_spec=blosc2.int32(), nullable=True, storage="batch", batch_rows=2) + arr.extend([[1], None, [], [2, 3]]) + assert arr[[3, 0]] == [[2, 3], [1]] + assert arr[blosc2.asarray([True, False, True, False])[:]] == [[1], []] + + +def test_listarray_arrow_roundtrip(): + pa = pytest.importorskip("pyarrow") + + values = pa.array([["a"], None, ["b", "c"]]) + arr = blosc2.ListArray.from_arrow(values, item_spec=blosc2.string(), nullable=True) + assert arr[:] == [["a"], None, ["b", "c"]] + assert arr.to_arrow().to_pylist() == [["a"], None, ["b", "c"]] diff --git a/tests/test_schunk_reorder_offsets.py b/tests/test_schunk_reorder_offsets.py new file mode 100644 index 00000000..16db3782 --- /dev/null +++ b/tests/test_schunk_reorder_offsets.py @@ -0,0 +1,73 @@ +####################################################################### +# Copyright (c) 2019-present, Blosc Development Team +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause +####################################################################### + +import numpy as np +import pytest + +import blosc2 + + +@pytest.mark.parametrize("contiguous", [True, False]) +@pytest.mark.parametrize("urlpath", [None, "reorder_offsets.b2frame"]) +@pytest.mark.parametrize("nchunks", [1, 5, 12]) +def test_schunk_reorder_offsets(contiguous, urlpath, nchunks): + blosc2.remove_urlpath(urlpath) + schunk = blosc2.SChunk( + chunksize=200 * 1000 * 4, + contiguous=contiguous, + urlpath=urlpath, + cparams={"typesize": 4, "nthreads": 2}, + dparams={"nthreads": 2}, + ) + + for i in range(nchunks): + buffer = np.arange(200 * 1000, dtype=np.int32) + i * 200 * 1000 + assert schunk.append_data(buffer) == (i + 1) + + order = np.array([(i + 3) % nchunks for i in range(nchunks)], dtype=np.int64) + schunk.reorder_offsets(order) + + for i in range(nchunks): + expected = np.arange(200 * 1000, dtype=np.int32) + order[i] * 200 * 1000 + dest = np.empty(200 * 1000, dtype=np.int32) + schunk.decompress_chunk(i, dest) + assert np.array_equal(dest, expected) + + blosc2.remove_urlpath(urlpath) + + +@pytest.mark.parametrize( + "order", + [ + [[0, 1]], + [0, 1], + [0, 0, 1], + [0, 1, 3], + ], +) +def test_schunk_reorder_offsets_invalid_order(order): + schunk = blosc2.SChunk(chunksize=16, cparams={"typesize": 1}) + for payload in (b"a" * 16, b"b" * 16, b"c" * 16): + schunk.append_data(payload) + + if order == [[0, 1]] or order == [0, 1]: + with pytest.raises(ValueError): + schunk.reorder_offsets(order) + else: + with pytest.raises(RuntimeError): + schunk.reorder_offsets(order) + + +def test_schunk_reorder_offsets_read_only(tmp_path): + urlpath = tmp_path / "reorder_offsets_read_only.b2frame" + schunk = blosc2.SChunk(chunksize=16, urlpath=urlpath, contiguous=True, cparams={"typesize": 1}) + schunk.append_data(b"a" * 16) + schunk.append_data(b"b" * 16) + + reopened = blosc2.open(urlpath, mode="r") + with pytest.raises(ValueError, match="reading mode"): + reopened.reorder_offsets([1, 0])