blob: 2404eadc7401dadb42979cf785a22d82512ae8b8 [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.
from pathlib import PosixPath
from typing import Any
import pyarrow as pa
import pytest
from pyiceberg.conversions import to_bytes
from pyiceberg.manifest import DataFile, DataFileContent
from pyiceberg.partitioning import PartitionField, PartitionSpec
from pyiceberg.schema import Schema
from pyiceberg.table.inspect import InspectTable, _readable_bound
from pyiceberg.table.snapshots import Snapshot
from pyiceberg.transforms import IdentityTransform
from pyiceberg.typedef import Record
from pyiceberg.types import NestedField, StringType
from tests.catalog.test_base import InMemoryCatalog
def test_readable_bound_with_empty_bytes() -> None:
assert _readable_bound(StringType(), to_bytes(StringType(), "")) == ""
def test_readable_bound_without_bound() -> None:
assert _readable_bound(StringType(), None) is None
@pytest.fixture
def catalog(tmp_path: PosixPath) -> InMemoryCatalog:
cat = InMemoryCatalog("test.in_memory.catalog", warehouse=tmp_path.absolute().as_posix())
cat.create_namespace("default")
return cat
def test_inspect_entries_and_files_render_empty_string_bound(catalog: InMemoryCatalog) -> None:
schema = Schema(NestedField(1, "s", StringType(), required=False))
tbl = catalog.create_table("default.empty_string_bound", schema)
tbl.append(pa.table({"s": [""]}, schema=pa.schema([pa.field("s", pa.large_string(), nullable=True)])))
entries_metrics = tbl.inspect.entries().to_pydict()["readable_metrics"][0]["s"]
assert entries_metrics["lower_bound"] == ""
assert entries_metrics["upper_bound"] == ""
files_metrics = tbl.inspect.files().to_pydict()["readable_metrics"][0]["s"]
assert files_metrics["lower_bound"] == ""
assert files_metrics["upper_bound"] == ""
def test_inspect_entries_and_files_render_null_bound(catalog: InMemoryCatalog) -> None:
schema = Schema(NestedField(1, "s", StringType(), required=False))
tbl = catalog.create_table("default.null_bound", schema)
tbl.append(pa.table({"s": [None]}, schema=pa.schema([pa.field("s", pa.large_string(), nullable=True)])))
entries_metrics = tbl.inspect.entries().to_pydict()["readable_metrics"][0]["s"]
assert entries_metrics["lower_bound"] is None
assert entries_metrics["upper_bound"] is None
files_metrics = tbl.inspect.files().to_pydict()["readable_metrics"][0]["s"]
assert files_metrics["lower_bound"] is None
assert files_metrics["upper_bound"] is None
@pytest.mark.parametrize("newest_first", [False, True])
def test_partitions_last_updated_uses_latest_snapshot_regardless_of_order(newest_first: bool) -> None:
# Manifest entries are visited in manifest order, which is not chronological, so the
# `partitions` metadata table must keep the snapshot with the highest commit timestamp
# per partition regardless of the order in which the entries are aggregated.
older = Snapshot(snapshot_id=6446744073709551000, timestamp_ms=1000, manifest_list="file:///dev/null")
newer = Snapshot(snapshot_id=8446744073709551111, timestamp_ms=5000, manifest_list="file:///dev/null")
data_file = DataFile.from_args(content=DataFileContent.DATA, record_count=1, file_size_in_bytes=1, partition=Record("a"))
data_file.spec_id = 0
inspect = InspectTable.__new__(InspectTable)
partitions_map: dict[tuple[str, Any], Any] = {}
for snapshot in [newer, older] if newest_first else [older, newer]:
inspect._update_partitions_map_from_manifest_entry(partitions_map, data_file, {"part": "a"}, snapshot)
(partition_row,) = partitions_map.values()
assert partition_row["last_updated_at"] == newer.timestamp_ms
assert partition_row["last_updated_snapshot_id"] == newer.snapshot_id
def test_inspect_manifests_preserves_empty_string_bounds(catalog: InMemoryCatalog) -> None:
schema = Schema(NestedField(1, "s", StringType()))
spec = PartitionSpec(PartitionField(1, 1000, IdentityTransform(), "s"))
tbl = catalog.create_table("default.empty_string_partition", schema, partition_spec=spec)
tbl.append(pa.table({"s": [""]}, schema=pa.schema([pa.field("s", pa.large_string())])))
partition_summary = tbl.inspect.manifests().to_pydict()["partition_summaries"][0][0]
assert partition_summary["lower_bound"] == ""
assert partition_summary["upper_bound"] == ""