blob: 8c5199eefff85a7cf6bf423ac01070d7513476f8 [file]
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import json
import os
import shutil
import sys
import tempfile
import tracemalloc
import unittest
from datetime import datetime, timedelta
from unittest import mock
import numpy as np
import pyarrow as pa
import pytest
import pypaimon.multimodal as pmm
from pypaimon.multimodal import temporal
from pypaimon.catalog.table_query_auth import TableQueryAuthResult
from pypaimon.execution.python.read.reader.format_pyarrow_reader import (FormatPyArrowReader)
from pypaimon.read.table_scan import TableScan
class MultimodalTemporalTest(unittest.TestCase):
def setUp(self):
self.temp_dir = tempfile.mkdtemp(prefix="pypaimon_temporal_")
self.conn = pmm.connect(options={
"warehouse": os.path.join(self.temp_dir, "warehouse"),
})
def tearDown(self):
shutil.rmtree(self.temp_dir, ignore_errors=True)
def test_alignment_requires_an_explicit_group_boundary(self):
table = self._table("missing_group", {
"event_time": pa.int64(),
"value": pa.int32(),
})
with self.assertRaisesRegex(ValueError, "grouping column"):
pmm.join_asof(
table.scan(),
table.scan(),
on="event_time",
by=(),
direction="nearest",
tolerance=0,
)
def test_alignment_preserves_payload_names(self):
anchors = self._table("audit_anchors", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
})
samples = self._table("audit_samples", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
"valid": pa.bool_(),
"matched_time": pa.int64(),
"time_delta": pa.int64(),
})
anchors.add([{"episode_id": 1, "event_time": 100}])
samples.add([{
"episode_id": 1,
"event_time": 100,
"valid": False,
"matched_time": 7,
"time_delta": 8,
}])
row = pmm.join_asof(
anchors.scan(),
samples.scan().select([
"valid", "matched_time", "time_delta"
]),
on="event_time",
by="episode_id",
direction="nearest",
tolerance=0,
).to_list()[0]
self.assertFalse(row["valid"])
self.assertEqual(7, row["matched_time"])
self.assertEqual(8, row["time_delta"])
def test_alignment_handles_duplicate_right_timestamps(self):
anchors = self._table("duplicate_anchors", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
})
samples = self._table("duplicate_samples", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
"value": pa.int32(),
})
anchors.add([{"episode_id": 1, "event_time": 100}])
samples.add([
{"episode_id": 1, "event_time": 100, "value": 1},
{"episode_id": 1, "event_time": 100, "value": 2},
])
for direction, expected in (
("backward", 2), ("forward", 1), ("nearest", 2)):
with self.subTest(direction=direction):
row = pmm.join_asof(
anchors.scan(), samples.scan().select("value"),
on="event_time", by="episode_id",
direction=direction, tolerance=0,
).to_list()[0]
self.assertEqual(expected, row["value"])
def test_alignment_treats_signed_zero_as_one_group(self):
schema = {"group": pa.float64(), "event_time": pa.int64()}
anchors = self._table("zero_anchors", schema)
samples = self._table("zero_samples", {**schema, "value": pa.int32()})
anchors.add([{"group": 0.0, "event_time": time} for time in (1, 2)])
samples.add(
[
{"group": -0.0, "event_time": 1, "value": 7},
{"group": 0.0, "event_time": 2, "value": 8},
]
)
rows = pmm.join_asof(
anchors.scan(),
samples.scan().select("value"),
on="event_time",
by="group",
tolerance=0,
).to_list()
self.assertEqual({1: 7, 2: 8}, {row["event_time"]: row["value"] for row in rows})
def test_alignment_preserves_composite_group_boundaries(self):
schema = {"host": pa.string(), "sensor": pa.int32(), "event_time": pa.int64()}
anchors = self._table("composite_anchors", schema)
samples = self._table("composite_samples", {**schema, "value": pa.int32()})
keys = [("a", 1), ("a", 2), ("b", 1)]
anchors.add([{"host": host, "sensor": sensor, "event_time": 10} for host, sensor in keys])
samples.add(
[
{"host": host, "sensor": sensor, "event_time": 9, "value": index}
for index, (host, sensor) in enumerate(keys)
]
)
rows = pmm.join_asof(
anchors.scan(),
samples.scan().select("value"),
on="event_time",
by=["host", "sensor"],
).to_list()
self.assertEqual(dict(zip(keys, range(3))), {(row["host"], row["sensor"]): row["value"] for row in rows})
def test_alignment_without_run_end_kernel(self):
schema = {"group": pa.string(), "event_time": pa.int64()}
anchors = self._table("fallback_anchors", schema)
samples = self._table("fallback_samples", {**schema, "value": pa.int32()})
anchors.add([{"group": "a", "event_time": 1}])
samples.add(
[
{"group": "a", "event_time": 1, "value": 7},
{"group": "b", "event_time": 1, "value": 8},
]
)
for kernel in (None, mock.Mock(side_effect=pa.ArrowNotImplementedError("No kernel"))):
with self.subTest(kernel=kernel), mock.patch.object(temporal.pc, "run_end_encode", kernel, create=True):
rows = pmm.join_asof(
anchors.scan(),
samples.scan().select("value"),
on="event_time",
by="group",
tolerance=0,
).to_list()
self.assertEqual([7], [row["value"] for row in rows])
def test_asof_excludes_exact_matches_within_group(self):
anchors = self._table("strict_anchors", {
"episode_id": pa.int32(), "event_time": pa.int64(),
})
samples = self._table("strict_samples", {
"episode_id": pa.int32(), "event_time": pa.int64(),
"value": pa.int32(),
})
groups = [
[9, 9, 10, 10, 11, 11],
[10, 10],
[10, 10, 11],
[9, 10, 10],
[],
[8, 11],
]
anchors.add([
{"episode_id": group, "event_time": 10}
for group in range(len(groups))
])
samples.add([
{"episode_id": group, "event_time": time, "value": index}
for group, times in enumerate(groups)
for index, time in enumerate(times)
])
for direction, expected in (
("backward", [1, None, None, 0, None, 0]),
("forward", [4, None, 2, None, None, 1]),
("nearest", [1, None, 2, 0, None, 1])):
with self.subTest(direction=direction):
rows = pmm.join_asof(
anchors.scan(), samples.scan().select("value"),
on="event_time", by="episode_id", direction=direction,
allow_exact_matches=False,
).to_list()
self.assertEqual(dict(enumerate(expected)), {
row["episode_id"]: row["value"] for row in rows
})
def test_asof_exact_match_options_are_independent_in_chain(self):
anchors = self._table("strict_chain_anchors", {
"episode_id": pa.int32(), "event_time": pa.int64(),
})
samples = self._table("strict_chain_samples", {
"episode_id": pa.int32(), "sample_time": pa.int64(),
"value": pa.int32(),
})
anchors.add([{"episode_id": 1, "event_time": 10}])
samples.add([
{"episode_id": 1, "sample_time": time, "value": time}
for time in [9, 10, 10, 11]
])
for direction, expected in (
("backward", 9), ("forward", 11), ("nearest", 9)):
for tolerance in (0, 0.5, 1, None):
with self.subTest(direction=direction, tolerance=tolerance):
aligned = pmm.join_asof(
anchors.scan(), samples.scan().select("value"),
on="event_time", by="episode_id",
right_on="sample_time", direction=direction,
tolerance=tolerance, allow_exact_matches=False,
)
row = aligned.join_asof(
samples.scan().select("value"),
right_on="sample_time", allow_exact_matches=True,
).join_asof(
samples.scan().select("value"),
right_on="sample_time", allow_exact_matches=False,
suffix="_strict",
).to_list()[0]
self.assertEqual(
expected if tolerance in (1, None) else None,
row["value"],
)
self.assertEqual(10, row["value_right"])
self.assertEqual(9, row["value_strict"])
def test_asof_excludes_exact_nanosecond_timestamps(self):
anchors = self._table("strict_ns_anchors", {
"episode_id": pa.int32(), "event_time": pa.timestamp("ns"),
})
samples = self._table("strict_ns_samples", {
"episode_id": pa.int32(), "event_time": pa.timestamp("ns"),
"value": pa.int32(),
})
anchors.add(pa.table({
"episode_id": pa.array([1], type=pa.int32()),
"event_time": pa.array(
[1_000_000_001], type=pa.int64()).cast(pa.timestamp("ns")),
}))
samples.add(pa.table({
"episode_id": pa.array([1, 1, 1], type=pa.int32()),
"event_time": pa.array(
[1_000_000_000, 1_000_000_001, 1_000_000_002],
type=pa.int64()).cast(pa.timestamp("ns")),
"value": pa.array([9, 10, 11], type=pa.int32()),
}))
for direction, expected in (
("backward", 9), ("forward", 11), ("nearest", 9)):
for tolerance in (timedelta(0), timedelta(microseconds=1)):
with self.subTest(direction=direction, tolerance=tolerance):
row = pmm.join_asof(
anchors.scan(), samples.scan().select("value"),
on="event_time", by="episode_id",
direction=direction, tolerance=tolerance,
allow_exact_matches=False,
).to_list()[0]
self.assertEqual(
expected if tolerance else None, row["value"])
def test_asof_validates_allow_exact_matches(self):
table = self._table("strict_options", {
"episode_id": pa.int32(), "event_time": pa.int64(),
})
aligned = pmm.join_asof(
table.scan(), table.scan(), on="event_time", by="episode_id")
for value in (None, 0, 1, "false"):
with self.subTest(value=value):
with self.assertRaisesRegex(TypeError, "allow_exact_matches"):
pmm.join_asof(
table.scan(), table.scan(),
on="event_time", by="episode_id",
allow_exact_matches=value,
)
with self.assertRaisesRegex(TypeError, "allow_exact_matches"):
aligned.join_asof(table.scan(), allow_exact_matches=value)
def test_nearest_uses_candidate_side_for_duplicate_timestamps(self):
anchors = self._table("duplicate_nearest_anchors", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
})
samples = self._table("duplicate_nearest_samples", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
"value": pa.int32(),
})
anchors.add([
{"episode_id": 1, "event_time": 90},
{"episode_id": 1, "event_time": 110},
])
samples.add([
{"episode_id": 1, "event_time": 100, "value": 1},
{"episode_id": 1, "event_time": 100, "value": 2},
])
rows = pmm.join_asof(
anchors.scan(), samples.scan().select("value"),
on="event_time", by="episode_id",
direction="nearest", tolerance=20,
).to_list()
self.assertEqual(
{90: 1, 110: 2},
{row["event_time"]: row["value"] for row in rows},
)
def test_linear_interpolation_stays_in_group_without_extrapolation(self):
anchors = self._table("linear_anchors", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
})
states = self._table("linear_states", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
"value": pa.int32(),
})
anchors.add([
{"episode_id": 1, "event_time": 5},
{"episode_id": 1, "event_time": 10},
{"episode_id": 1, "event_time": 20},
{"episode_id": 2, "event_time": 5},
])
states.add([
{"episode_id": 1, "event_time": 0, "value": 0},
{"episode_id": 1, "event_time": 10, "value": 20},
{"episode_id": 1, "event_time": 10, "value": 30},
{"episode_id": 2, "event_time": 0, "value": 100},
{"episode_id": 2, "event_time": 10, "value": 120},
])
result = pmm.interpolate(
anchors.scan(), states.scan().select("value"),
on="event_time", by="episode_id", tolerance=5,
)
rows = sorted(
result.to_list(),
key=lambda row: (row["episode_id"], row["event_time"]),
)
self.assertIsInstance(result, pmm.TemporalAlignment)
self.assertEqual(pa.float64(), result.schema.field("value").type)
self.assertEqual([10.0, 30.0, None, 110.0], [
row["value"] for row in rows
])
def test_window_join_stays_in_group_and_skips_nulls(self):
anchors = self._table("window_anchors", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
})
samples = self._table("window_samples", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
"value": pa.int32(),
"label": pa.string(),
})
anchors.add([
{"episode_id": 1, "event_time": 10},
{"episode_id": 2, "event_time": 10},
{"episode_id": 3, "event_time": 10},
])
samples.add([
{"episode_id": 1, "event_time": 5,
"value": 1, "label": "a"},
{"episode_id": 1, "event_time": 10,
"value": None, "label": None},
{"episode_id": 1, "event_time": 15,
"value": 5, "label": "c"},
{"episode_id": 2, "event_time": 10,
"value": 100, "label": "z"},
])
result = pmm.join_window(
anchors.scan(),
samples.scan().select(["value", "label"]),
on="event_time",
by="episode_id",
preceding=5,
following=5,
aggregations={
"average": ("value", "mean"),
"minimum": ("value", "min"),
"maximum": ("value", "max"),
"first_label": ("label", "first"),
"last_label": ("label", "last"),
"valid_count": ("label", "count"),
},
)
rows = sorted(result.to_list(), key=lambda row: row["episode_id"])
self.assertIsInstance(result, pmm.TemporalAlignment)
self.assertEqual(pa.float64(), result.schema.field("average").type)
self.assertEqual(pa.string(), result.schema.field("first_label").type)
self.assertEqual(pa.int64(), result.schema.field("valid_count").type)
self.assertEqual(
(3.0, 1, 5, "a", "c", 2),
tuple(rows[0][name] for name in (
"average", "minimum", "maximum", "first_label",
"last_label", "valid_count")),
)
self.assertEqual(100.0, rows[1]["average"])
self.assertIsNone(rows[2]["average"])
self.assertEqual(0, rows[2]["valid_count"])
def test_window_join_reuses_arrow_slices(self):
source = temporal._WindowJoinRight.__new__(temporal._WindowJoinRight)
source.by = ("group",)
source._index = {(1,): (0, 3), (2,): (3, 4)}
source._time_keys = np.array([5, 10, 15, 10], dtype=np.int64)
source._row_ids = pa.array([11, 12, 13, 14], type=pa.int64())
source.time_type = pa.int64()
source._preceding_key = 5
source._following_key = 0
source.closed = "both"
specifications = (
("average", "value", "mean"),
("first_label", "label", "first"),
("minimum", "value", "min"),
("last_label", "label", "last"),
("maximum", "value", "max"),
("valid_count", "label", "count"),
)
values = pa.table({
"value": pa.array([1, None, 5, None], type=pa.int32()),
"label": pa.array(["a", None, "c", None], type=pa.string()),
})
anchors = [
{"group": group, temporal._TIME_KEY: time}
for group, time in [(1, 10), (1, 15), (2, 10), (3, 10)]
]
expected = {
"mean": pa.array([1.0, 5.0, None, None], type=pa.float64()),
"min": pa.array([1, 5, None, None], type=pa.int32()),
"max": pa.array([1, 5, None, None], type=pa.int32()),
"first": pa.array(["a", "c", None, None], type=pa.string()),
"last": pa.array(["a", "c", None, None], type=pa.string()),
"count": pa.array([1, 1, 0, 0], type=pa.int64()),
}
for aggregations in (specifications[:1], specifications[::2],
specifications):
with self.subTest(aggregations=aggregations):
source.aggregations = aggregations
fetcher = mock.Mock(schema=values.schema)
fetcher.fetch.return_value = values
with mock.patch.object(
temporal.pc, "take", wraps=temporal.pc.take) as take:
arrays = source.build_arrays(anchors, fetcher)
fetcher.fetch.assert_called_once_with([11, 12, 13, 14])
self.assertEqual(len(aggregations), len(arrays))
for (_, _, operation), array in zip(aggregations, arrays):
self.assertEqual(expected[operation].type, array.type)
self.assertEqual(expected[operation], array)
take.assert_not_called()
def test_window_join_preserves_order_across_disjoint_ranges(self):
anchors = self._table("range_anchors", {
"episode_id": pa.int32(), "event_time": pa.int64(),
})
samples = self._table("range_samples", {
"episode_id": pa.int32(), "event_time": pa.int64(),
"label": pa.string(),
})
# Physical row IDs are neither contiguous nor ordered within a window.
samples.add([
{"episode_id": 1, "event_time": 12, "label": "late"},
{"episode_id": 2, "event_time": 2, "label": "other"},
{"episode_id": 1, "event_time": 2, "label": "first"},
{"episode_id": 1, "event_time": 6, "label": "excluded"},
{"episode_id": 1, "event_time": 2, "label": None},
{"episode_id": 1, "event_time": 2, "label": "last"},
{"episode_id": 1, "event_time": 1, "label": "early"},
])
anchors.add([
{"episode_id": group, "event_time": time}
for group, time in [(1, 12), (1, 3), (2, 2), (1, 2),
(1, 2), (3, 2), (1, 20)]
])
query = pmm.join_window(
anchors.scan(), samples.scan().select("label"),
on="event_time", by="episode_id", preceding=1,
aggregations={
"first": ("label", "first"),
"last": ("label", "last"),
"count": ("label", "count"),
})
rows = query.to_list()
self.assertEqual([
("late", "late", 1), ("first", "last", 2),
("other", "other", 1), ("early", "last", 3),
("early", "last", 3), (None, None, 0), (None, None, 0),
], [(row["first"], row["last"], row["count"]) for row in rows])
def test_window_join_bounds_overlapping_window_memory(self):
size = 1024
source = temporal._WindowJoinRight.__new__(temporal._WindowJoinRight)
source.by = ("group",)
source._index = {(1,): (0, size)}
source._time_keys = np.arange(size, dtype=np.int64)
source._row_ids = pa.array(range(10000, 10000 + size), type=pa.int64())
source.time_type = pa.int64()
source._preceding_key = size - 1
source._following_key = 0
source.closed = "both"
source.aggregations = (("count", "value", "count"),)
values = pa.table({"value": pa.array(range(size), type=pa.int64())})
fetcher = mock.Mock(schema=values.schema)
fetcher.fetch.return_value = values
anchors = [{"group": 1, temporal._TIME_KEY: size - 1}] * 256
tracemalloc.start()
try:
arrays = source.build_arrays(anchors, fetcher)
_, peak = tracemalloc.get_traced_memory()
finally:
tracemalloc.stop()
self.assertEqual([size] * len(anchors), arrays[0].to_pylist())
fetcher.fetch.assert_called_once_with(list(range(10000, 10000 + size)))
# Per-window row-ID and position lists alone exceed this budget.
self.assertLess(peak, 4 * 1024 * 1024)
def test_window_endpoints_preserve_values_across_chunks(self):
cases = [
(pa.string(), [None, "", "last", None], "", "last"),
(pa.binary(), [None, b"", b"last", None], b"", b"last"),
(pa.bool_(), [None, False, True, None], False, True),
(pa.int64(), [None, 0, -1, None], 0, -1),
(pa.float64(), [None, float("nan"), 1.0, None], float("nan"), 1.0),
(pa.list_(pa.int64()), [None, [], [1, None], None], [], [1, None]),
(pa.struct([pa.field("a", pa.int64())]),
[None, {"a": None}, {"a": 0}, None], {"a": None}, {"a": 0}),
(pa.string(), [None, None], None, None),
(pa.string(), [], None, None),
]
for data_type, values, first, last in cases:
array = pa.array(values, type=data_type)
empty = pa.array([], type=data_type)
padded = pa.concat_arrays([
pa.nulls(1, type=data_type), array,
pa.nulls(1, type=data_type)])
chunks = pa.chunked_array([
empty, array.slice(0, 1), empty, array.slice(1), empty])
sliced_chunks = pa.chunked_array([
padded.slice(0, 2), empty, padded.slice(2), empty,
]).slice(1, len(array))
for selected in (array, padded.slice(1, len(array)),
chunks, sliced_chunks):
for operation, expected in (("first", first), ("last", last)):
with self.subTest(data_type=data_type, operation=operation,
selected=selected):
result = temporal._aggregate_values(selected, operation)
if isinstance(expected, float) and np.isnan(expected):
self.assertTrue(np.isnan(result))
else:
self.assertEqual(expected, result)
def test_window_endpoints_skip_null_dictionary_values(self):
array = pa.DictionaryArray.from_arrays(
pa.array([0, 1, 0], type=pa.int8()),
pa.array([None, "value"], type=pa.string()))
for selected in (array, pa.chunked_array([array.slice(0, 1), array.slice(1)])):
for operation in ("first", "last"):
with self.subTest(operation=operation, selected=selected):
self.assertEqual(
"value", temporal._aggregate_values(selected, operation))
def test_window_endpoints_do_not_materialize_full_payload(self):
payload = b"x" * 1024
selected = pa.chunked_array([
pa.array([payload] * 8192, type=pa.binary())])
for operation in ("first", "last"):
with self.subTest(operation=operation):
tracemalloc.start()
try:
result = temporal._aggregate_values(selected, operation)
_, peak = tracemalloc.get_traced_memory()
finally:
tracemalloc.stop()
self.assertEqual(payload, result)
# Converting the entire window creates over 8 MiB of Python bytes.
self.assertLess(peak, 256 * 1024)
def test_window_join_supports_asymmetric_timestamp_bounds(self):
anchors = self._table("window_timestamp_anchors", {
"episode_id": pa.int32(),
"event_time": pa.timestamp("ms"),
})
samples = self._table("window_timestamp_samples", {
"episode_id": pa.int32(),
"captured_at": pa.timestamp("ms"),
"value": pa.float32(),
})
anchor = datetime(2026, 9, 1, 12, 0, 0)
anchors.add([{"episode_id": 1, "event_time": anchor}])
samples.add([
{"episode_id": 1,
"captured_at": anchor + timedelta(milliseconds=offset),
"value": value}
for offset, value in ((-11, 100.0), (-10, 1.0),
(0, 2.0), (5, 3.0), (6, 100.0))
])
row = pmm.join_window(
anchors.scan(), samples.scan().select("value"),
on="event_time", right_on="captured_at", by="episode_id",
preceding=timedelta(milliseconds=10),
following=timedelta(milliseconds=5),
aggregations={"value": "mean"},
).to_list()[0]
self.assertEqual(2.0, row["value"])
right_closed = pmm.join_window(
anchors.scan(), samples.scan().select("value"),
on="event_time", right_on="captured_at", by="episode_id",
preceding=timedelta(milliseconds=10),
following=timedelta(milliseconds=5),
aggregations={"value": "mean"},
closed="right",
).to_list()[0]
self.assertEqual(2.5, right_closed["value"])
def test_window_join_preserves_subunit_timestamp_bounds(self):
anchors = self._table("window_subunit_timestamp_anchors", {
"episode_id": pa.int32(),
"event_time": pa.timestamp("ms"),
})
samples = self._table("window_subunit_timestamp_samples", {
"episode_id": pa.int32(),
"event_time": pa.timestamp("ms"),
"value": pa.int32(),
})
anchor = datetime(2026, 9, 1, 12, 0, 0)
anchors.add([{"episode_id": 1, "event_time": anchor}])
samples.add([
{"episode_id": 1,
"event_time": anchor + timedelta(milliseconds=offset),
"value": offset}
for offset in (-1, 0, 1)
])
exact = pmm.join_window(
anchors.scan(), samples.scan().select("value"),
on="event_time", by="episode_id",
preceding=timedelta(microseconds=500),
following=timedelta(0), closed="right",
aggregations={"matches": ("value", "count")},
).to_list()[0]
open_window = pmm.join_window(
anchors.scan(), samples.scan().select("value"),
on="event_time", by="episode_id",
preceding=timedelta(microseconds=1_500),
following=timedelta(microseconds=1_500), closed="neither",
aggregations={"matches": ("value", "count")},
).to_list()[0]
self.assertEqual(1, exact["matches"])
self.assertEqual(3, open_window["matches"])
def test_window_join_normalizes_numpy_float_bounds(self):
anchors = self._table("window_numpy_float_anchors", {
"episode_id": pa.int32(),
"event_time": pa.float64(),
})
samples = self._table("window_numpy_float_samples", {
"episode_id": pa.int32(),
"event_time": pa.float64(),
"value": pa.int32(),
})
timestamp = 1_700_000_000.001
anchors.add([{"episode_id": 1, "event_time": timestamp}])
samples.add([{
"episode_id": 1, "event_time": timestamp, "value": 7,
}])
row = pmm.join_window(
anchors.scan(), samples.scan().select("value"),
on="event_time", by="episode_id",
preceding=0, following=np.float32(0),
aggregations={"matches": ("value", "count")},
).to_list()[0]
self.assertEqual(1, row["matches"])
def test_window_join_keeps_numeric_bounds_exact_for_integer_time(self):
anchors = self._table("window_integer_bound_anchors", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
})
samples = self._table("window_integer_bound_samples", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
"value": pa.int32(),
})
timestamp = 1_700_000_000_000_000_001
anchors.add(pa.Table.from_pydict({
"episode_id": [1], "event_time": [timestamp],
}))
samples.add(pa.Table.from_pydict({
"episode_id": [1, 1, 1],
"event_time": [timestamp - 1, timestamp, timestamp + 1],
"value": [1, 2, 3],
}))
for preceding, following in (
(0.0, 0.0), (0.125, 0.125),
(np.int64(0), 0), (0, np.int64(0)),
(np.int64(0), np.int64(0))):
with self.subTest(preceding=preceding, following=following):
row = pmm.join_window(
anchors.scan(), samples.scan().select("value"),
on="event_time", by="episode_id",
preceding=preceding, following=following,
aggregations={
"matches": ("value", "count"),
"first_value": ("value", "first"),
},
).to_list()[0]
self.assertEqual(1, row["matches"])
self.assertEqual(2, row["first_value"])
def test_window_join_clips_bounds_to_integer_time_range(self):
anchors = self._table("window_range_anchors", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
})
samples = self._table("window_range_samples", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
"value": pa.int32(),
})
minimum, maximum = -(1 << 63), (1 << 63) - 1
anchors.add(pa.Table.from_pydict({
"episode_id": [1, 2], "event_time": [minimum, maximum],
}))
times = {1: [minimum, minimum + 1], 2: [maximum - 1, maximum]}
samples.add(pa.Table.from_pydict({
"episode_id": [1, 1, 2, 2],
"event_time": times[1] + times[2],
"value": [1, 2, 3, 4],
}))
for preceding, following in ((0, 0), (0, 1), (1, 0), (1, 1)):
for closed in ("both", "left", "right", "neither"):
with self.subTest(
preceding=preceding, following=following,
closed=closed):
rows = pmm.join_window(
anchors.scan(), samples.scan().select("value"),
on="event_time", by="episode_id",
preceding=preceding, following=following,
closed=closed,
aggregations={"matches": ("value", "count")},
).to_list()
for row in rows:
left = row["event_time"] - preceding
right = row["event_time"] + following
expected = sum(
(time >= left if closed in ("both", "left")
else time > left)
and (time <= right if closed in ("both", "right")
else time < right)
for time in times[row["episode_id"]]
)
self.assertEqual(expected, row["matches"])
@pytest.mark.python_read
def test_window_join_prunes_unaggregated_right_columns(self):
anchors = self._table("window_projection_anchors", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
})
samples = self._table("window_projection_samples", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
"payload": pa.struct([
pa.field("unused_nested", pa.int32()),
pa.field("value", pa.int32()),
]),
"unused": pa.string(),
})
anchors.add([{"episode_id": 1, "event_time": 10}])
samples.add([{
"episode_id": 1,
"event_time": 10,
"payload": {"value": 7, "unused_nested": 8},
"unused": "not read",
}])
payload_reads = []
original = FormatPyArrowReader._read_parquet_row_group_batches
def tracked(reader, row_group, columns):
if columns is not None and "payload" in columns:
payload_reads.append(tuple(columns))
yield from original(reader, row_group, columns)
with mock.patch.object(
FormatPyArrowReader,
"_read_parquet_row_group_batches", tracked):
row = pmm.join_window(
anchors.scan(),
samples.scan().select(["payload.value", "unused"]),
on="event_time", by="episode_id", preceding=0,
aggregations={"payload_value": "mean"},
).to_list()[0]
self.assertEqual(7.0, row["payload_value"])
self.assertTrue(payload_reads)
self.assertNotIn("unused", {
name for columns in payload_reads for name in columns
})
def test_window_join_preserves_nested_projection_aliases_when_pruned(self):
anchors = self._table("window_alias_anchors", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
})
samples = self._table("window_alias_samples", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
"a_b": pa.int32(),
"a": pa.struct([pa.field("b", pa.int32())]),
})
anchors.add([{"episode_id": 1, "event_time": 10}])
samples.add([{
"episode_id": 1,
"event_time": 10,
"a_b": 3,
"a": {"b": 7},
}])
row = pmm.join_window(
anchors.scan(), samples.scan().select(["a_b", "a.b"]),
on="event_time", by="episode_id", preceding=0,
aggregations={"nested_mean": ("a_b__0", "mean")},
).to_list()[0]
self.assertEqual(7.0, row["nested_mean"])
def test_window_join_preserves_masked_alias_types_when_pruned(self):
anchors = self._table("window_masked_alias_anchors", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
})
samples = self._table("window_masked_alias_samples", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
"a_b": pa.int32(),
"a": pa.struct([pa.field("b", pa.int32())]),
})
anchors.add(pa.Table.from_pydict({
"episode_id": [1, 1], "event_time": [10, 20],
}))
samples.add(pa.Table.from_pydict({
"episode_id": [1], "event_time": [10],
"a_b": [3],
"a": pa.array([{"b": 7}],
type=pa.struct([pa.field("b", pa.int32())])),
}))
auth = TableQueryAuthResult(
filter=None,
column_masking={"a_b": json.dumps({
"name": "CAST",
"fieldRef": {"index": 2, "name": "a_b", "type": "INT"},
"type": "STRING",
})},
)
samples.raw_table.catalog_environment.table_query_auth = (
lambda options, identifier: lambda select: auth)
for projection, source in (
(["a_b"], "a_b"), (["a.b", "a_b"], "a_b__0")):
with self.subTest(projection=projection):
result = pmm.join_window(
anchors.scan(), samples.scan().select(projection),
on="event_time", by="episode_id", preceding=0,
aggregations={
"first_value": (source, "first"),
"last_value": (source, "last"),
},
).to_arrow()
for name in ("first_value", "last_value"):
self.assertEqual(pa.string(), result[name].type)
self.assertEqual(["3", None], result[name].to_pylist())
for operation in ("mean", "min", "max"):
with self.subTest(projection=projection, operation=operation):
with self.assertRaisesRegex(
TypeError, "requires an integer or floating"):
pmm.join_window(
anchors.scan(), samples.scan().select(projection),
on="event_time", by="episode_id", preceding=0,
aggregations={"value": (source, operation)},
).to_arrow()
def test_window_join_matches_masks_by_original_nested_path(self):
anchors = self._table("window_nested_mask_anchors", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
})
samples = self._table("window_nested_mask_samples", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
"a_b": pa.int32(),
"a": pa.struct([pa.field("b", pa.int32())]),
})
anchors.add([{"episode_id": 1, "event_time": 10}])
samples.add([{
"episode_id": 1, "event_time": 10,
"a_b": 3, "a": {"b": 7},
}])
auth = TableQueryAuthResult(
filter=None,
column_masking={"a_b": json.dumps({"name": "NULL"})},
)
samples.raw_table.catalog_environment.table_query_auth = (
lambda options, identifier: lambda select: auth)
row = pmm.join_window(
anchors.scan(), samples.scan().select(["a_b", "a.b"]),
on="event_time", by="episode_id", preceding=0,
aggregations={"nested_mean": ("a_b__0", "mean")},
).to_list()[0]
self.assertEqual(7.0, row["nested_mean"])
def test_window_mean_uses_masked_numeric_alias_type_when_pruned(self):
anchors = self._table("window_numeric_alias_anchors", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
})
samples = self._table("window_numeric_alias_samples", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
"a_b": pa.string(),
"a": pa.struct([pa.field("b", pa.int32())]),
})
anchors.add(pa.Table.from_pydict({
"episode_id": [1, 1], "event_time": [10, 20],
}))
samples.add(pa.Table.from_pydict({
"episode_id": [1, 1], "event_time": [9, 11],
"a_b": ["6", "10"],
"a": pa.array([{"b": 1}, {"b": 2}],
type=pa.struct([pa.field("b", pa.int32())])),
}))
auth = TableQueryAuthResult(
filter=None,
column_masking={"a_b": json.dumps({
"name": "CAST",
"fieldRef": {"index": 2, "name": "a_b", "type": "STRING"},
"type": "DOUBLE",
})},
)
samples.raw_table.catalog_environment.table_query_auth = (
lambda options, identifier: lambda select: auth)
for projection, source in (
(["a_b"], "a_b"), (["a.b", "a_b"], "a_b__0")):
with self.subTest(projection=projection):
result = pmm.join_window(
anchors.scan(), samples.scan().select(projection),
on="event_time", by="episode_id", preceding=1, following=1,
aggregations={"value": (source, "mean")},
).to_arrow()
self.assertEqual(pa.float64(), result["value"].type)
self.assertEqual([8.0, None], result["value"].to_pylist())
def test_window_mean_avoids_numeric_overflow_and_integer_rounding(self):
anchors = self._table("window_numeric_anchors", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
})
samples = self._table("window_numeric_samples", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
"integer_value": pa.int64(),
"float_value": pa.float64(),
})
anchors.add([{"episode_id": 1, "event_time": 1}])
samples.add([
{"episode_id": 1, "event_time": 0,
"integer_value": -(1 << 63),
"float_value": sys.float_info.max},
{"episode_id": 1, "event_time": 2,
"integer_value": (1 << 63) - 1,
"float_value": sys.float_info.max},
])
row = pmm.join_window(
anchors.scan(),
samples.scan().select(["integer_value", "float_value"]),
on="event_time", by="episode_id", preceding=1, following=1,
aggregations={
"integer_value": "mean",
"float_value": "mean",
},
).to_list()[0]
self.assertEqual(-0.5, row["integer_value"])
self.assertEqual(sys.float_info.max, row["float_value"])
def test_window_mean_preserves_finite_float_cancellation(self):
anchors = self._table("window_float_mean_anchors", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
})
samples = self._table("window_float_mean_samples", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
"value": pa.float64(),
})
anchors.add([{"episode_id": 1, "event_time": 1}])
samples.add([
{"episode_id": 1, "event_time": 0, "value": 1e16},
{"episode_id": 1, "event_time": 2, "value": -1e16 + 2},
])
row = pmm.join_window(
anchors.scan(), samples.scan().select("value"),
on="event_time", by="episode_id", preceding=1, following=1,
aggregations={"value": "mean"},
).to_list()[0]
self.assertEqual(1.0, row["value"])
def test_window_join_can_follow_an_asof_join(self):
anchors = self._table("window_chain_anchors", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
})
images = self._table("window_chain_images", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
"image": pa.string(),
})
imu = self._table("window_chain_imu", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
"acceleration": pa.float32(),
})
anchors.add([{"episode_id": 1, "event_time": 10}])
images.add([{"episode_id": 1, "event_time": 9, "image": "frame"}])
imu.add([
{"episode_id": 1, "event_time": 8, "acceleration": 1.0},
{"episode_id": 1, "event_time": 10, "acceleration": 3.0},
])
row = pmm.join_asof(
anchors.scan(), images.scan().select("image"),
on="event_time", by="episode_id", direction="nearest",
).join_window(
imu.scan().select("acceleration"),
preceding=2,
aggregations={"acceleration": "mean"},
).to_list()[0]
self.assertEqual("frame", row["image"])
self.assertEqual(2.0, row["acceleration"])
def test_window_join_validates_options(self):
table = self._table("window_validation", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
"value": pa.int32(),
"text": pa.string(),
})
def scan():
return table.scan().select("value")
with self.assertRaisesRegex(ValueError, "non-negative"):
pmm.join_window(
scan(), scan(), on="event_time", by="episode_id",
preceding=-1, aggregations={"value": "mean"})
with self.assertRaisesRegex(ValueError, "Unsupported aggregation"):
pmm.join_window(
scan(), scan(), on="event_time", by="episode_id",
preceding=1, aggregations={"value": "median"})
with self.assertRaisesRegex(ValueError, "closed must be"):
pmm.join_window(
scan(), scan(), on="event_time", by="episode_id",
preceding=1, aggregations={"value": "mean"},
closed="middle")
with self.assertRaisesRegex(ValueError, "missing aggregation columns"):
pmm.join_window(
scan(), scan(), on="event_time", by="episode_id",
preceding=1, aggregations={"missing": "mean"})
with self.assertRaisesRegex(
TypeError, "requires an integer or floating"):
pmm.join_window(
scan(), table.scan().select("text"),
on="event_time", by="episode_id", preceding=1,
aggregations={"text": "mean"}).to_arrow()
def test_linear_interpolation_preserves_an_exact_infinite_float(self):
anchors = self._table("linear_exact_anchors", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
})
states = self._table("linear_exact_states", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
"value": pa.float64(),
})
anchors.add([{"episode_id": 1, "event_time": 10}])
states.add([{
"episode_id": 1, "event_time": 10, "value": float("inf")
}])
row = pmm.interpolate(
anchors.scan(), states.scan().select("value"),
on="event_time", by="episode_id",
).to_list()[0]
self.assertEqual(float("inf"), row["value"])
def test_linear_interpolation_requires_both_neighbors_in_tolerance(self):
anchors = self._table("linear_tolerance_anchors", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
})
states = self._table("linear_tolerance_states", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
"value": pa.float64(),
})
anchors.add([{"episode_id": 1, "event_time": 9}])
states.add([
{"episode_id": 1, "event_time": 0, "value": 0.0},
{"episode_id": 1, "event_time": 10, "value": 10.0},
])
row = pmm.interpolate(
anchors.scan(), states.scan().select("value"),
on="event_time", by="episode_id", tolerance=8,
).to_list()[0]
self.assertIsNone(row["value"])
def test_linear_interpolation_supports_fixed_size_numeric_lists(self):
vector = pa.list_(pa.float32(), 2)
anchors = self._table("linear_vector_anchors", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
})
states = self._table("linear_vector_states", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
"state": vector,
})
anchors.add([{"episode_id": 1, "event_time": 5}])
states.add([
{"episode_id": 1, "event_time": 0, "state": [0.0, 10.0]},
{"episode_id": 1, "event_time": 10, "state": [10.0, 20.0]},
])
result = pmm.interpolate(
anchors.scan(), states.scan().select("state"),
on="event_time", by="episode_id",
)
self.assertEqual(vector, result.schema.field("state").type)
self.assertEqual([5.0, 15.0], result.to_list()[0]["state"])
def test_linear_interpolation_preserves_bigint_precision(self):
anchors = self._table("linear_bigint_anchors", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
})
states = self._table("linear_bigint_states", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
"value": pa.int64(),
"extreme": pa.int64(),
})
anchors.add([
{"episode_id": 1, "event_time": 1},
{"episode_id": 1, "event_time": 2},
])
states.add([
{"episode_id": 1, "event_time": 0,
"value": (1 << 53) + 1, "extreme": -(1 << 63)},
{"episode_id": 1, "event_time": 4,
"value": (1 << 53) + 3, "extreme": (1 << 63) - 1},
])
rows = pmm.interpolate(
anchors.scan(), states.scan().select(["value", "extreme"]),
on="event_time", by="episode_id",
).to_list()
by_time = {row["event_time"]: row for row in rows}
self.assertEqual(float((1 << 53) + 2), by_time[1]["value"])
self.assertEqual(-0.5, by_time[2]["extreme"])
def test_linear_interpolation_scales_extreme_float_time_axis(self):
anchors = self._table("linear_extreme_time_anchors", {
"episode_id": pa.int32(),
"event_time": pa.float64(),
})
states = self._table("linear_extreme_time_states", {
"episode_id": pa.int32(),
"event_time": pa.float64(),
"value": pa.float64(),
})
anchors.add([{"episode_id": 1, "event_time": 0.0}])
states.add([
{"episode_id": 1, "event_time": -sys.float_info.max,
"value": 0.0},
{"episode_id": 1, "event_time": sys.float_info.max,
"value": 10.0},
])
row = pmm.interpolate(
anchors.scan(), states.scan().select("value"),
on="event_time", by="episode_id",
).to_list()[0]
self.assertEqual(5.0, row["value"])
def test_linear_interpolation_preserves_float_time_differences(self):
anchors = self._table("linear_nearby_time_anchors", {
"episode_id": pa.int32(),
"event_time": pa.float64(),
})
states = self._table("linear_nearby_time_states", {
"episode_id": pa.int32(),
"event_time": pa.float64(),
"value": pa.float64(),
})
base = 1_700_000_000.0
step = 2 ** -21
anchors.add([{"episode_id": 1, "event_time": base + step}])
states.add([
{"episode_id": 1, "event_time": base, "value": 0.0},
{"episode_id": 1, "event_time": base + 2 * step,
"value": 10.0},
])
row = pmm.interpolate(
anchors.scan(), states.scan().select("value"),
on="event_time", by="episode_id",
).to_list()[0]
self.assertEqual(5.0, row["value"])
def test_float_time_preserves_bigint_interpolation_precision(self):
anchors = self._table("linear_float_bigint_anchors", {
"episode_id": pa.int32(),
"event_time": pa.float64(),
})
states = self._table("linear_float_bigint_states", {
"episode_id": pa.int32(),
"event_time": pa.float64(),
"value": pa.int64(),
})
anchors.add([{"episode_id": 1, "event_time": 2.0}])
states.add([
{"episode_id": 1, "event_time": 0.0,
"value": -(1 << 63)},
{"episode_id": 1, "event_time": 3.0,
"value": 1 << 62},
])
row = pmm.interpolate(
anchors.scan(), states.scan().select("value"),
on="event_time", by="episode_id",
).to_list()[0]
self.assertEqual(0.0, row["value"])
def test_linear_interpolation_handles_extreme_float_payloads(self):
anchors = self._table("linear_extreme_value_anchors", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
})
states = self._table("linear_extreme_value_states", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
"value": pa.float64(),
"infinite": pa.float64(),
})
anchors.add([{"episode_id": 1, "event_time": 5}])
states.add([
{"episode_id": 1, "event_time": 0,
"value": -sys.float_info.max, "infinite": float("inf")},
{"episode_id": 1, "event_time": 10,
"value": sys.float_info.max, "infinite": float("inf")},
])
row = pmm.interpolate(
anchors.scan(), states.scan().select(["value", "infinite"]),
on="event_time", by="episode_id",
).to_list()[0]
self.assertEqual(0.0, row["value"])
self.assertEqual(float("inf"), row["infinite"])
def test_linear_interpolation_uses_effective_masked_payload_type(self):
anchors = self._table("linear_masked_anchors", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
})
states = self._table("linear_masked_states", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
"value": pa.string(),
})
anchors.add([{"episode_id": 1, "event_time": 5}])
states.add([
{"episode_id": 1, "event_time": 0, "value": "0"},
{"episode_id": 1, "event_time": 10, "value": "10"},
])
auth = TableQueryAuthResult(
filter=None,
column_masking={"value": json.dumps({
"name": "CAST",
"fieldRef": {
"index": 2, "name": "value", "type": "STRING",
},
"type": "DOUBLE",
})},
)
states.raw_table.catalog_environment.table_query_auth = (
lambda options, identifier: lambda select: auth)
row = pmm.interpolate(
anchors.scan(), states.scan().select("value"),
on="event_time", by="episode_id",
).to_list()[0]
self.assertEqual(5.0, row["value"])
def test_linear_interpolation_rejects_masked_non_numeric_payload(self):
anchors = self._table("linear_invalid_mask_anchors", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
})
states = self._table("linear_invalid_mask_states", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
"value": pa.int32(),
})
anchors.add([{"episode_id": 1, "event_time": 5}])
states.add([
{"episode_id": 1, "event_time": 0, "value": 0},
{"episode_id": 1, "event_time": 10, "value": 10},
])
auth = TableQueryAuthResult(
filter=None,
column_masking={"value": json.dumps({
"name": "CAST",
"fieldRef": {
"index": 2, "name": "value", "type": "INT",
},
"type": "STRING",
})},
)
states.raw_table.catalog_environment.table_query_auth = (
lambda options, identifier: lambda select: auth)
aligned = pmm.interpolate(
anchors.scan(), states.scan().select("value"),
on="event_time", by="episode_id",
)
with self.assertRaisesRegex(TypeError, "requires integer or floating"):
aligned.to_arrow()
def test_linear_interpolation_rejects_decimal_payloads(self):
anchors = self._table("linear_invalid_anchors", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
})
decimals = self._table("linear_invalid_decimals", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
"value": pa.decimal128(10, 2),
})
with self.assertRaisesRegex(TypeError, "requires integer or floating"):
pmm.interpolate(
anchors.scan(), decimals.scan().select("value"),
on="event_time", by="episode_id",
).to_arrow()
def test_linear_interpolation_can_follow_an_asof_join(self):
anchors = self._table("linear_chain_anchors", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
})
images = self._table("linear_chain_images", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
"image": pa.string(),
})
states = self._table("linear_chain_states", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
"state": pa.float32(),
})
anchors.add([{"episode_id": 1, "event_time": 5}])
images.add([{"episode_id": 1, "event_time": 4, "image": "frame"}])
states.add([
{"episode_id": 1, "event_time": 0, "state": 0.0},
{"episode_id": 1, "event_time": 10, "state": 10.0},
])
row = pmm.join_asof(
anchors.scan(), images.scan().select("image"),
on="event_time", by="episode_id",
direction="nearest", tolerance=2,
).interpolate(
states.scan().select("state"), tolerance=5,
).to_list()[0]
self.assertEqual("frame", row["image"])
self.assertEqual(5.0, row["state"])
def test_alignment_can_return_matched_timestamp(self):
anchors = self._table("timestamp_output_anchors", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
})
samples = self._table("timestamp_output_samples", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
"value": pa.int32(),
})
anchors.add([{"episode_id": 1, "event_time": 100}])
samples.add([
{"episode_id": 1, "event_time": 95, "value": 7},
])
row = pmm.join_asof(
anchors.scan(),
samples.scan().select(["event_time", "value"]),
on="event_time", by="episode_id",
direction="backward", suffix="_matched",
).to_list()[0]
self.assertEqual(95, row["event_time_matched"])
self.assertEqual(5, row["event_time"] - row["event_time_matched"])
def test_alignment_rejects_nested_group_keys(self):
group_type = pa.struct([pa.field("part", pa.int32())])
anchors = self._table("nested_group_anchors", {
"group": group_type,
"event_time": pa.int64(),
})
samples = self._table("nested_group_samples", {
"group": group_type,
"event_time": pa.int64(),
})
with self.assertRaisesRegex(TypeError, "must have a scalar type"):
pmm.join_asof(
anchors.scan(),
samples.scan(),
on="event_time",
by="group",
direction="nearest",
tolerance=0,
)
def test_aligns_named_sources_in_episode_local_batches(self):
actions = self._table("actions", {
"episode_id": pa.string(),
"event_time": pa.int64(),
"action": pa.int32(),
})
images = self._table("images", {
"episode_id": pa.string(),
"event_time": pa.int64(),
"camera": pa.string(),
"image": pa.string(),
})
states = self._table("states", {
"episode_id": pa.string(),
"event_time": pa.int64(),
"state": pa.int32(),
})
commands = self._table("commands", {
"episode_id": pa.string(),
"event_time": pa.int64(),
"command": pa.string(),
})
actions.add([
{"episode_id": "ep-2", "event_time": 100, "action": 4},
{"episode_id": "ep-1", "event_time": 300, "action": 3},
{"episode_id": "ep-1", "event_time": 100, "action": 1},
{"episode_id": "ep-1", "event_time": 200, "action": 2},
])
images.add([
{"episode_id": "ep-1", "event_time": 90,
"camera": "left", "image": "early"},
{"episode_id": "ep-1", "event_time": 90,
"camera": "right", "image": "ignored"},
{"episode_id": "ep-1", "event_time": 110,
"camera": "left", "image": "late"},
{"episode_id": "ep-1", "event_time": 215,
"camera": "left", "image": "middle"},
{"episode_id": "ep-2", "event_time": 99,
"camera": "left", "image": "other"},
])
states.add([
{"episode_id": "ep-1", "event_time": 80, "state": 8},
{"episode_id": "ep-1", "event_time": 190, "state": 19},
{"episode_id": "ep-2", "event_time": 95, "state": 95},
])
commands.add([
{"episode_id": "ep-1", "event_time": 100, "command": "open"},
{"episode_id": "ep-1", "event_time": 220, "command": "close"},
{"episode_id": "ep-2", "event_time": 100, "command": "hold"},
])
aligned = pmm.join_asof(
actions.scan().select(["episode_id", "event_time", "action"]),
images.scan().where("camera = 'left'").select("image"),
on="event_time",
by="episode_id",
direction="nearest",
tolerance=20,
).join_asof(
states.scan().select("state"),
direction="backward",
tolerance=25,
).join_asof(
commands.scan().select("command"),
direction="nearest",
tolerance=0,
).join_asof(
commands.scan().select("command"),
direction="forward",
tolerance=25,
suffix="_next",
)
reader = aligned.to_arrow_batch_reader(batch_size=2)
batches = list(reader)
rows = pa.Table.from_batches(batches).to_pylist()
self.assertEqual([2, 2], [batch.num_rows for batch in batches])
rows.sort(key=lambda row: (row["episode_id"], row["event_time"]))
self.assertEqual(
[("ep-1", 100), ("ep-1", 200), ("ep-1", 300), ("ep-2", 100)],
[(row["episode_id"], row["event_time"]) for row in rows],
)
# Equal-distance nearest ties select the earlier row.
self.assertEqual(
["early", "middle", None, "other"],
[row["image"] for row in rows],
)
self.assertEqual([8, 19, None, 95], [
row["state"] for row in rows
])
self.assertEqual(["open", None, None, "hold"], [
row["command"] for row in rows
])
self.assertEqual(["open", "close", None, "hold"], [
row["command_next"] for row in rows
])
def test_alignment_pins_each_scan_snapshot(self):
anchors = self._table("pinned_anchors", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
"value": pa.string(),
})
secondary = self._table("pinned_secondary", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
"value": pa.string(),
})
anchors.add([{"episode_id": 1, "event_time": 100, "value": "old"}])
secondary.add([
{"episode_id": 1, "event_time": 90, "value": "old-match"}
])
aligned = pmm.join_asof(
anchors.scan(),
secondary.scan(),
on="event_time",
by="episode_id",
direction="nearest",
tolerance=20,
suffix="_secondary",
)
snapshots = aligned.resolved_snapshots
anchors.add([{"episode_id": 1, "event_time": 200, "value": "new"}])
secondary.add([
{"episode_id": 1, "event_time": 100, "value": "new-match"}
])
self.assertEqual([{
"episode_id": 1,
"event_time": 100,
"value": "old",
"value_secondary": "old-match",
}], aligned.to_list())
self.assertEqual(1, snapshots["left"]["snapshot_id"])
self.assertEqual(1, snapshots["right_1"]["snapshot_id"])
self.assertTrue(
snapshots["left"]["table"].endswith("pinned_anchors"))
self.assertTrue(
snapshots["right_1"]["table"].endswith("pinned_secondary"))
def test_alignment_reads_tag_after_snapshot_file_expires(self):
anchors = self._table("tagged_anchors", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
"value": pa.string(),
})
secondary = self._table("tagged_secondary", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
"value": pa.string(),
})
anchors.add([{"episode_id": 1, "event_time": 100, "value": "old"}])
secondary.add([
{"episode_id": 1, "event_time": 100, "value": "old-match"}
])
anchors.raw_table.create_tag("v1")
secondary.raw_table.create_tag("v1")
aligned = pmm.join_asof(
anchors.scan(tag_name="v1"),
secondary.scan(tag_name="v1"),
on="event_time",
by="episode_id",
direction="nearest",
tolerance=0,
)
anchors.add([{"episode_id": 1, "event_time": 200, "value": "new"}])
secondary.add([
{"episode_id": 1, "event_time": 200, "value": "new-match"}
])
for table in (anchors.raw_table, secondary.raw_table):
manager = table.snapshot_manager()
table.file_io.delete(manager.get_snapshot_path(1))
self.assertEqual([100], [
row["event_time"] for row in aligned.to_list()
])
self.assertEqual(
"v1", aligned.resolved_snapshots["left"]["tag_name"])
def test_alignment_rejects_a_changed_tag(self):
table = self._table("changed_tag", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
})
table.add([{"episode_id": 1, "event_time": 100}])
table.raw_table.create_tag("v1")
aligned = pmm.join_asof(
table.scan(tag_name="v1"), table.scan(tag_name="v1"),
on="event_time", by="episode_id",
direction="nearest", tolerance=0,
)
table.add([{"episode_id": 1, "event_time": 200}])
table.raw_table.replace_tag("v1")
with self.assertRaisesRegex(RuntimeError, "Tag 'v1' changed"):
aligned.to_list()
def test_alignment_rejects_tag_replacement_during_planning(self):
anchors = self._table("raced_tag_anchors", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
})
source = self._table("raced_tag_source", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
"value": pa.int32(),
})
anchors.add([{"episode_id": 1, "event_time": 100}])
source.add([{
"episode_id": 1, "event_time": 100, "value": 7,
}])
source.raw_table.create_tag("v1")
source.add([{
"episode_id": 1, "event_time": 200, "value": 99,
}])
aligned = pmm.join_asof(
anchors.scan(), source.scan(tag_name="v1").select("value"),
on="event_time", by="episode_id",
direction="nearest", tolerance=0,
)
original = temporal._validate_pinned_tag
replaced = []
def replace_after_validation(query):
original(query)
if query._temporal_tag_name == "v1" and not replaced:
source.raw_table.replace_tag("v1", snapshot_id=2)
replaced.append(True)
with mock.patch.object(
temporal, "_validate_pinned_tag",
side_effect=replace_after_validation):
with self.assertRaisesRegex(
RuntimeError, "changed from snapshot 1 to 2"):
aligned.to_list()
def test_alignment_normalizes_scan_mode_when_pinning(self):
anchors = self.conn.create_table(
"latest_full_anchors",
schema=pa.schema([
pa.field("episode_id", pa.int32()),
pa.field("event_time", pa.int64()),
]),
options={"scan.mode": "latest-full"},
)
secondary = self._table("latest_full_secondary", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
"value": pa.int32(),
})
anchors.add([{"episode_id": 1, "event_time": 100}])
secondary.add([
{"episode_id": 1, "event_time": 100, "value": 7}
])
row = pmm.join_asof(
anchors.scan(),
secondary.scan(),
on="event_time",
by="episode_id",
direction="nearest",
tolerance=0,
).to_list()[0]
self.assertEqual(7, row["value"])
def test_alignment_rejects_non_finite_temporal_values(self):
for value in (float("nan"), float("inf"), float("-inf")):
with self.subTest(value=value):
anchors = self._table("float_anchor_%s" % id(value), {
"episode_id": pa.int32(),
"event_time": pa.float64(),
})
secondary = self._table("float_source_%s" % id(value), {
"episode_id": pa.int32(),
"event_time": pa.float64(),
"value": pa.int32(),
})
anchors.add([{"episode_id": 1, "event_time": 100.0}])
secondary.add([
{"episode_id": 1, "event_time": value, "value": 7}
])
aligned = pmm.join_asof(
anchors.scan(),
secondary.scan(),
on="event_time",
by="episode_id",
direction="nearest",
tolerance=1.0,
)
with self.assertRaisesRegex(ValueError, "must be finite"):
aligned.to_list()
def test_alignment_validates_tolerance_type_and_value(self):
table = self._table("tolerance", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
"value": pa.int32(),
})
for tolerance in (float("nan"), float("inf"), -1):
with self.subTest(tolerance=tolerance):
with self.assertRaises((TypeError, ValueError)):
pmm.join_asof(
table.scan(), table.scan(),
on="event_time", by="episode_id",
direction="nearest", tolerance=tolerance,
)
with self.assertRaisesRegex(TypeError, "Numeric alignment"):
pmm.join_asof(
table.scan(), table.scan(),
on="event_time", by="episode_id",
direction="nearest",
tolerance=timedelta(milliseconds=1),
)
with self.assertRaisesRegex(ValueError, "int64 maximum"):
pmm.join_asof(
table.scan(), table.scan(),
on="event_time", by="episode_id",
direction="nearest", tolerance=1 << 63,
)
with self.assertRaisesRegex(ValueError, "direction"):
pmm.join_asof(
table.scan(), table.scan(),
on="event_time", by="episode_id", direction="exact",
)
def test_alignment_keeps_internal_row_id_out_of_query_auth(self):
anchors = self._table("masked_row_id_anchors", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
})
source = self._table("masked_row_id_source", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
"value": pa.int32(),
})
anchors.add([{"episode_id": 1, "event_time": 100}])
source.add([{"episode_id": 1, "event_time": 100, "value": 7}])
selected = []
def query_auth(select):
selected.append(select)
self.assertTrue(select is None or "_ROW_ID" not in select)
return None
for table in (anchors.raw_table, source.raw_table):
table.catalog_environment.table_query_auth = (
lambda options, identifier: query_auth)
rows = pmm.join_asof(
anchors.scan(), source.scan(),
on="event_time", by="episode_id",
direction="nearest", tolerance=0,
).to_list()
self.assertEqual(7, rows[0]["value"])
self.assertTrue(selected)
def test_alignment_rejects_masked_internal_row_ids(self):
anchors = self._table("masked_internal_id_anchors", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
})
source = self._table("masked_internal_id_source", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
"value": pa.int32(),
})
anchors.add([{"episode_id": 1, "event_time": 100}])
source.add([{"episode_id": 1, "event_time": 100, "value": 7}])
auth = TableQueryAuthResult(
filter=None,
column_masking={"_ROW_ID": json.dumps({"name": "NULL"})},
)
anchors.raw_table.catalog_environment.table_query_auth = (
lambda options, identifier: lambda select: auth)
aligned = pmm.join_asof(
anchors.scan(), source.scan(),
on="event_time", by="episode_id",
direction="nearest", tolerance=0,
)
with self.assertRaisesRegex(ValueError, "masks _ROW_ID"):
aligned.to_list()
def test_alignment_preserves_masked_output_schema(self):
anchors = self.conn.create_table(
"masked_schema_anchors",
schema=pa.schema([
pa.field("episode_id", pa.int32()),
pa.field("event_time", pa.int64()),
pa.field("secret", pa.int32(), nullable=False),
]),
)
source = self._table("masked_schema_source", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
"sample": pa.int32(),
})
anchors.add([{
"episode_id": 1, "event_time": 100, "secret": 7,
}])
source.add([{
"episode_id": 1, "event_time": 100, "sample": 1,
}])
auth = TableQueryAuthResult(
filter=None,
column_masking={"secret": json.dumps({"name": "NULL"})},
)
anchors.raw_table.catalog_environment.table_query_auth = (
lambda options, identifier: lambda select: auth)
reader = pmm.join_asof(
anchors.scan(), source.scan().select("sample"),
on="event_time", by="episode_id",
direction="nearest", tolerance=0,
).to_arrow_batch_reader()
result = reader.read_all()
self.assertTrue(result.schema.field("secret").nullable)
self.assertIsNone(result["secret"][0].as_py())
def test_alignment_preserves_type_changing_masked_output(self):
anchors = self._table("cast_mask_anchors", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
"anchor_value": pa.int32(),
})
source = self._table("cast_mask_source", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
"source_value": pa.int32(),
})
anchors.add([{
"episode_id": 1, "event_time": 100, "anchor_value": 7,
}])
source.add([{
"episode_id": 1, "event_time": 100, "source_value": 8,
}])
for table, name in (
(anchors.raw_table, "anchor_value"),
(source.raw_table, "source_value")):
auth = TableQueryAuthResult(
filter=None,
column_masking={name: json.dumps({
"name": "CAST",
"fieldRef": {"index": 2, "name": name, "type": "INT"},
"type": "STRING",
})},
)
table.catalog_environment.table_query_auth = (
lambda options, identifier, result=auth:
lambda select: result)
result = pmm.join_asof(
anchors.scan(), source.scan().select("source_value"),
on="event_time", by="episode_id",
direction="nearest", tolerance=0,
).to_arrow()
self.assertEqual(pa.string(), result.schema.field("anchor_value").type)
self.assertEqual(pa.string(), result.schema.field("source_value").type)
self.assertEqual("7", result["anchor_value"][0].as_py())
self.assertEqual("8", result["source_value"][0].as_py())
def test_alignment_preserves_masked_schema_for_empty_source(self):
anchors = self._table("empty_mask_anchors", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
})
source = self._table("empty_mask_source", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
"value": pa.int32(),
})
anchors.add([{"episode_id": 1, "event_time": 100}])
auth = TableQueryAuthResult(
filter=None,
column_masking={"value": json.dumps({
"name": "CAST",
"fieldRef": {
"index": 2, "name": "value", "type": "INT",
},
"type": "STRING",
})},
)
source.raw_table.catalog_environment.table_query_auth = (
lambda options, identifier: lambda select: auth)
result = pmm.join_asof(
anchors.scan(), source.scan().select("value"),
on="event_time", by="episode_id",
direction="nearest", tolerance=0,
).to_arrow()
self.assertEqual(pa.string(), result.schema.field("value").type)
self.assertIsNone(result["value"][0].as_py())
def test_alignment_rejects_type_changing_key_masks(self):
anchors = self._table("cast_key_anchors", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
})
source = self._table("cast_key_source", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
"value": pa.int32(),
})
anchors.add([{"episode_id": 1, "event_time": 100}])
source.add([{
"episode_id": 1, "event_time": 100, "value": 7,
}])
for name, index, field_type in (
("episode_id", 0, "INT"),
("event_time", 1, "BIGINT")):
with self.subTest(name=name):
auth = TableQueryAuthResult(
filter=None,
column_masking={name: json.dumps({
"name": "CAST",
"fieldRef": {
"index": index, "name": name, "type": field_type,
},
"type": "STRING",
})},
)
source.raw_table.catalog_environment.table_query_auth = (
lambda options, identifier, result=auth:
lambda select: result)
with self.assertRaisesRegex(TypeError, name):
pmm.join_asof(
anchors.scan(), source.scan().select("value"),
on="event_time", by="episode_id",
direction="nearest", tolerance=0,
).to_list()
def test_alignment_supports_cross_column_key_masks(self):
anchors = self._table("cross_mask_anchors", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
})
source = self._table("cross_mask_source", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
"allowed_episode_id": pa.int32(),
"value": pa.int32(),
})
anchors.add([{"episode_id": 1, "event_time": 100}])
source.add([{
"episode_id": 99,
"event_time": 100,
"allowed_episode_id": 1,
"value": 7,
}])
auth = TableQueryAuthResult(
filter=None,
column_masking={"episode_id": json.dumps({
"name": "FIELD_REF",
"fieldRef": {
"index": 2,
"name": "allowed_episode_id",
"type": "INT",
},
})},
)
selected = []
def query_auth(select):
selected.append(select)
return auth
source.raw_table.catalog_environment.table_query_auth = (
lambda options, identifier: query_auth)
rows = pmm.join_asof(
anchors.scan(), source.scan().select("value"),
on="event_time", by="episode_id",
direction="nearest", tolerance=0,
).to_list()
self.assertEqual(7, rows[0]["value"])
self.assertTrue(selected)
self.assertTrue(all(
select is None or "allowed_episode_id" not in select
for select in selected
))
def test_alignment_rejects_a_mask_reading_a_masked_dependency(self):
anchors = self._table("dependency_mask_anchors", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
"allowed_episode_id": pa.int32(),
})
source = self._table("dependency_mask_source", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
"value": pa.int32(),
})
anchors.add([{
"episode_id": 99,
"event_time": 100,
"allowed_episode_id": 1,
}])
source.add([{
"episode_id": 1, "event_time": 100, "value": 7,
}])
auth = TableQueryAuthResult(
filter=None,
column_masking={
"episode_id": json.dumps({
"name": "FIELD_REF",
"fieldRef": {
"index": 2,
"name": "allowed_episode_id",
"type": "INT",
},
}),
"allowed_episode_id": json.dumps({
"name": "CAST",
"fieldRef": {
"index": 2,
"name": "allowed_episode_id",
"type": "INT",
},
"type": "STRING",
}),
},
)
anchors.raw_table.catalog_environment.table_query_auth = (
lambda options, identifier: lambda select: auth)
with self.assertRaisesRegex(ValueError, "which is masked too"):
pmm.join_asof(
anchors.scan(), source.scan().select("value"),
on="event_time", by="episode_id",
direction="nearest", tolerance=0,
).to_arrow()
def test_alignment_rejects_cross_column_and_json_null_masks(self):
anchors = self._table("mask_semantics_anchors", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
})
source = self._table("mask_semantics_source", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
"first": pa.string(),
"second": pa.string(),
})
anchors.add([{"episode_id": 1, "event_time": 100}])
source.add([{
"episode_id": 1,
"event_time": 100,
"first": "a",
"second": "b",
}])
auth = [TableQueryAuthResult(
filter=None,
column_masking={
"first": json.dumps({
"name": "FIELD_REF",
"fieldRef": {
"index": 3, "name": "second", "type": "STRING",
},
}),
"second": json.dumps({
"name": "FIELD_REF",
"fieldRef": {
"index": 2, "name": "first", "type": "STRING",
},
}),
},
)]
source.raw_table.catalog_environment.table_query_auth = (
lambda options, identifier: lambda select: auth[0])
with self.assertRaisesRegex(ValueError, "which is masked too"):
pmm.join_asof(
anchors.scan(), source.scan().select(["first", "second"]),
on="event_time", by="episode_id",
direction="nearest", tolerance=0,
).to_list()
auth[0] = TableQueryAuthResult(
filter=None, column_masking={"first": "null"})
with self.assertRaisesRegex(ValueError, "JSON null"):
pmm.join_asof(
anchors.scan(), source.scan().select("first"),
on="event_time", by="episode_id",
direction="nearest", tolerance=0,
).to_list()
def test_alignment_rejects_incremental_scans(self):
anchors = self.conn.create_table(
"incremental_anchors",
schema=pa.schema([
pa.field("episode_id", pa.int32()),
pa.field("event_time", pa.int64()),
]),
options={
"scan.mode": "incremental",
"incremental-between-timestamp": "0,9999999999999",
},
)
source = self._table("incremental_source", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
"value": pa.int32(),
})
anchors.add([{"episode_id": 1, "event_time": 100}])
source.add([{
"episode_id": 1, "event_time": 100, "value": 7,
}])
with self.assertRaisesRegex(
ValueError, "Temporal alignment.*incremental"):
pmm.join_asof(
anchors.scan(), source.scan().select("value"),
on="event_time", by="episode_id",
direction="nearest", tolerance=0,
).to_list()
def test_alignment_supports_zero_output_columns(self):
anchors = self._table("zero_output_anchors", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
})
source = self._table("zero_output_source", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
})
anchors.add([{"episode_id": 1, "event_time": 100}])
source.add([{"episode_id": 1, "event_time": 100}])
result = pmm.join_asof(
anchors.scan().select("missing"),
source.scan().select("episode_id"),
on="event_time", by="episode_id",
direction="nearest", tolerance=0,
)
self.assertEqual([], result.schema.names)
self.assertEqual([{}], result.to_list())
def test_alignment_supports_nested_projections(self):
anchors = self._table("nested_anchors", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
"metadata": pa.struct([pa.field("value", pa.int32())]),
})
secondary = self._table("nested_secondary", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
"payload": pa.struct([pa.field("value", pa.int32())]),
})
anchors.add([
{"episode_id": 1, "event_time": 100,
"metadata": {"value": 1}},
{"episode_id": 1, "event_time": 200, "metadata": None},
])
secondary.add([
{"episode_id": 1, "event_time": 100,
"payload": {"value": 7}},
])
rows = pmm.join_asof(
anchors.scan().select([
"episode_id", "event_time", "metadata.value"]),
secondary.scan().select("payload.value"),
on="event_time",
by="episode_id",
direction="nearest",
tolerance=0,
).to_list()
self.assertEqual([1, None], [row["metadata_value"] for row in rows])
self.assertEqual(
[7, None], [row["payload_value"] for row in rows])
auth = TableQueryAuthResult(
filter=None,
column_masking={"payload": json.dumps({"name": "NULL"})},
)
selected = []
def query_auth(select):
selected.append(select)
return auth
secondary.raw_table.catalog_environment.table_query_auth = (
lambda options, identifier: query_auth)
masked = pmm.join_asof(
anchors.scan().select([
"episode_id", "event_time", "metadata.value"]),
secondary.scan().select("payload.value"),
on="event_time",
by="episode_id",
direction="nearest",
tolerance=0,
).to_arrow()
self.assertEqual([None, None], masked["payload_value"].to_pylist())
self.assertTrue(masked.schema.field("payload_value").nullable)
self.assertIn(["payload"], selected)
self.assertTrue(all(
select is None or "payload_value" not in select
for select in selected
))
def test_nested_projection_cannot_shadow_internal_row_id(self):
anchors = self._table("nested_row_id_anchors", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
"_ROW": pa.struct([pa.field("ID", pa.int32())]),
})
samples = self._table("nested_row_id_samples", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
"value": pa.int32(),
})
anchors.add([
{"episode_id": 1, "event_time": 10, "_ROW": {"ID": 1}},
{"episode_id": 1, "event_time": 20, "_ROW": {"ID": 0}},
])
samples.add([
{"episode_id": 1, "event_time": 10, "value": 7},
{"episode_id": 1, "event_time": 20, "value": 9},
])
rows = pmm.join_asof(
anchors.scan().select(["_ROW.ID", "event_time"]),
samples.scan().select("value"),
on="event_time", by="episode_id",
direction="nearest", tolerance=0,
).to_list()
self.assertEqual([1, 0], [row["_ROW_ID"] for row in rows])
self.assertEqual([7, 9], [row["value"] for row in rows])
def test_nested_payload_name_cannot_shadow_group_key(self):
anchors = self._table("nested_key_anchors", {
"payload_value": pa.int32(),
"event_time": pa.int64(),
})
samples = self._table("nested_key_samples", {
"payload_value": pa.int32(),
"event_time": pa.int64(),
"payload": pa.struct([pa.field("value", pa.int32())]),
})
anchors.add([{"payload_value": 1, "event_time": 10}])
samples.add([{
"payload_value": 1,
"event_time": 10,
"payload": {"value": 7},
}])
nested_only = pmm.join_asof(
anchors.scan(), samples.scan().select("payload.value"),
on="event_time", by="payload_value",
direction="nearest", tolerance=0,
).to_list()[0]
self.assertEqual(7, nested_only["payload_value_right"])
both = pmm.join_asof(
anchors.scan(),
samples.scan().select(["payload.value", "payload_value"]),
on="event_time", by="payload_value",
direction="nearest", tolerance=0,
).to_list()[0]
self.assertEqual(7, both["payload_value_right"])
self.assertNotIn("payload_value__0", both)
def test_alignment_rejects_unbound_nested_projection_masks(self):
anchors = self._table("nested_mask_anchors", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
})
source = self._table("nested_mask_source", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
"payload": pa.struct([pa.field("value", pa.int32())]),
})
anchors.add([{"episode_id": 1, "event_time": 100}])
source.add([{
"episode_id": 1,
"event_time": 100,
"payload": {"value": 7},
}])
auth = TableQueryAuthResult(
filter=None,
column_masking={"payload_value": json.dumps({"name": "NULL"})},
)
source.raw_table.catalog_environment.table_query_auth = (
lambda options, identifier: lambda select: auth)
with self.assertRaisesRegex(ValueError, "does not exist in table schema"):
pmm.join_asof(
anchors.scan(), source.scan().select("payload.value"),
on="event_time", by="episode_id",
direction="nearest", tolerance=0,
).to_list()
def test_alignment_reuses_payload_scan_plans_across_batches(self):
anchors = self._table("planned_anchors", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
})
secondary = self._table("planned_secondary", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
"value": pa.int32(),
})
anchors.add([
{"episode_id": 1, "event_time": value}
for value in range(8)
])
secondary.add([
{"episode_id": 1, "event_time": value, "value": value}
for value in range(8)
])
aligned = pmm.join_asof(
anchors.scan(), secondary.scan(),
on="event_time", by="episode_id",
direction="nearest", tolerance=0,
)
original_plan = TableScan.plan
with mock.patch.object(
TableScan, "plan", autospec=True,
side_effect=original_plan) as plan:
reader = aligned.to_arrow_batch_reader(batch_size=1)
self.assertEqual(8, sum(batch.num_rows for batch in reader))
self.assertEqual(4, plan.call_count)
def test_empty_source_stays_pinned_after_first_append(self):
anchors = self._table("pinned_empty_anchors", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
})
samples = self._table("pinned_empty_samples", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
"value": pa.int32(),
})
anchors.add([{"episode_id": 1, "event_time": 10}])
aligned = pmm.join_asof(
anchors.scan(), samples.scan().select("value"),
on="event_time", by="episode_id",
direction="nearest", tolerance=0,
)
samples.add([{
"episode_id": 1, "event_time": 10, "value": 7,
}])
self.assertIsNone(aligned.resolved_snapshots["right_1"]["snapshot_id"])
self.assertIsNone(aligned.to_list()[0]["value"])
@pytest.mark.python_read
def test_alignment_reuses_decoded_parquet_row_groups_across_batches(self):
anchors = self._table("cached_anchors", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
})
samples = self._table("cached_samples", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
"value": pa.int32(),
})
row_count = 8192
rows = [
{"episode_id": 1, "event_time": value}
for value in range(row_count)
]
anchors.add(rows)
samples.add([
dict(row, value=row["event_time"])
for row in rows
])
original = FormatPyArrowReader._read_parquet_row_group_batches
for batch_size in (128, 1024, row_count):
with self.subTest(batch_size=batch_size):
decoded_rows = []
def tracked(reader, row_group, columns):
for batch in original(reader, row_group, columns):
if "value" in reader.existing_fields:
decoded_rows.append(batch.num_rows)
yield batch
with mock.patch.object(
FormatPyArrowReader,
"_read_parquet_row_group_batches", tracked):
aligned = pmm.join_asof(
anchors.scan(), samples.scan().select("value"),
on="event_time", by="episode_id",
direction="nearest", tolerance=0,
)
reader = aligned.to_arrow_batch_reader(
batch_size=batch_size)
result = pa.Table.from_batches(list(reader))
self.assertEqual(
list(range(row_count)), result["value"].to_pylist())
self.assertEqual(row_count, sum(decoded_rows))
def test_alignment_streams_anchor_metadata(self):
anchors = self._table("streamed_anchors", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
})
samples = self._table("streamed_samples", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
"value": pa.int32(),
})
anchors.add([
{"episode_id": 1, "event_time": value}
for value in range(4)
])
samples.add([
{"episode_id": 1, "event_time": value, "value": value}
for value in range(4)
])
aligned = pmm.join_asof(
anchors.scan(), samples.scan(),
on="event_time", by="episode_id",
direction="nearest", tolerance=0,
)
with mock.patch.object(
temporal, "_metadata_table",
wraps=temporal._metadata_table) as metadata_table:
self.assertEqual(4, len(aligned.to_list()))
# Only the indexed right side is collected into one metadata table.
self.assertEqual(1, metadata_table.call_count)
def test_alignment_splits_batches_before_arrow_offset_overflow(self):
child_count = 1 << 30
chunk = pa.ListArray.from_arrays(
pa.array([0, child_count], type=pa.int32()),
pa.nulls(child_count),
)
schema = pa.schema([pa.field("payload", chunk.type)])
class Fetcher:
def fetch(self, row_ids):
return pa.Table.from_arrays([
pa.chunked_array([chunk for _ in row_ids])
], schema=schema)
aligned = object.__new__(temporal.TemporalAlignment)
aligned._anchor_schema = schema
aligned._sources = ()
rows = [{temporal._ROW_ID: value} for value in range(2)]
batches = list(aligned._build_batches(
rows, Fetcher(), [], schema))
self.assertEqual([1, 1], [batch.num_rows for batch in batches])
self.assertTrue(all(batch.validate() is None for batch in batches))
self.assertEqual(schema, batches[0].schema)
def test_alignment_closes_anchor_stream_when_reader_closes(self):
anchors = self._table("closable_anchors", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
})
samples = self._table("closable_samples", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
"value": pa.int32(),
})
anchors.add([
{"episode_id": 1, "event_time": value}
for value in range(2)
])
samples.add([
{"episode_id": 1, "event_time": value, "value": value}
for value in range(2)
])
closed = []
original = temporal._metadata_batches
def tracked_batches(*args):
try:
for batch in original(*args):
yield batch
finally:
closed.append(True)
with mock.patch.object(
temporal, "_metadata_batches", tracked_batches):
reader = pmm.join_asof(
anchors.scan(), samples.scan().select("value"),
on="event_time", by="episode_id",
direction="nearest", tolerance=0,
).to_arrow_batch_reader(batch_size=1)
next(reader)
reader.close()
self.assertEqual([True], closed)
def test_alignment_keeps_blob_payloads_as_descriptors(self):
anchors = self._table("blob_anchors", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
})
images = self._table("blob_images", {
"episode_id": pa.int32(),
"event_time": pa.int64(),
"image": pa.large_binary(),
})
anchors.add([{"episode_id": 1, "event_time": 100}])
images.add([{
"episode_id": 1,
"event_time": 100,
"image": b"encoded-image",
}])
row = pmm.join_asof(
anchors.scan(),
images.scan().select("image"),
on="event_time",
by="episode_id",
direction="nearest",
tolerance=0,
).to_list()[0]
descriptor = pmm.BlobDescriptor.deserialize(row["image"])
self.assertTrue(descriptor.uri.endswith(".blob"))
self.assertEqual(len(b"encoded-image"), descriptor.length)
def test_alignment_supports_timestamp_columns_with_different_names(self):
anchors = self._table("timestamp_anchors", {
"episode_id": pa.int32(),
"event_time": pa.timestamp("ms"),
})
samples = self._table("timestamp_samples", {
"episode_id": pa.int32(),
"captured_at": pa.timestamp("ms"),
"value": pa.int32(),
})
anchor_time = datetime(2026, 9, 1, 12, 0, 0, 100000)
sample_time = anchor_time - timedelta(milliseconds=5)
anchors.add([{"episode_id": 1, "event_time": anchor_time}])
samples.add([{
"episode_id": 1,
"captured_at": sample_time,
"value": 7,
}])
row = pmm.join_asof(
anchors.scan(),
samples.scan().select("value"),
on="event_time",
by="episode_id",
direction="nearest",
right_on="captured_at",
tolerance=timedelta(milliseconds=10),
).to_list()[0]
self.assertEqual(7, row["value"])
def test_alignment_preserves_nanosecond_timestamp_precision(self):
anchors = self._table("nanosecond_anchors", {
"episode_id": pa.int32(),
"event_time": pa.timestamp("ns"),
})
samples = self._table("nanosecond_samples", {
"episode_id": pa.int32(),
"event_time": pa.timestamp("ns"),
"value": pa.int32(),
})
anchors.add(pa.table({
"episode_id": pa.array([1], type=pa.int32()),
"event_time": pa.array(
[1_000_000_001], type=pa.int64()).cast(pa.timestamp("ns")),
}))
samples.add(pa.table({
"episode_id": pa.array([1, 1], type=pa.int32()),
"event_time": pa.array(
[1_000_000_000, 1_000_000_001],
type=pa.int64()).cast(pa.timestamp("ns")),
"value": pa.array([1, 2], type=pa.int32()),
}))
row = pmm.join_asof(
anchors.scan(), samples.scan().select("value"),
on="event_time", by="episode_id",
direction="nearest", tolerance=timedelta(0),
).to_list()[0]
self.assertEqual(2, row["value"])
def _table(self, name, fields):
return self.conn.create_table(name, schema=pa.schema([
pa.field(field_name, field_type)
for field_name, field_type in fields.items()
]))
if __name__ == "__main__":
unittest.main()