blob: 223362a957a959aa2f0b038f3078987bd5b28fb2 [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.
"""Lance exports distinguish logical schema changes from data snapshots."""
import pyarrow as pa
import pytest
import pypaimon.multimodal as pmm
from pypaimon.schema.data_types import AtomicType
from pypaimon.schema.schema_change import SchemaChange
lance = pytest.importorskip("lance", minversion="13.0.0", reason="pylance >= 13.0.0 is required for Lance exports")
_ORIGINAL_ROWS = [{"id": 1, "text": "original"}]
@pytest.fixture(params=["add", "drop", "rename"])
def schema_only_alter(request, tmp_path):
conn = pmm.connect(options={"warehouse": str(tmp_path / "warehouse")})
table = conn.create_table(
"schema_only", schema=pa.schema([("id", pa.int32()), ("text", pa.string())]),
data=_ORIGINAL_ROWS, options={"file.format": "parquet"})
snapshot = table.raw_table.snapshot_manager().get_latest_snapshot()
table.raw_table.create_tag("before-alter", snapshot_id=snapshot.id)
captured = table.scan().where("id = 1")
if request.param == "add":
change = SchemaChange.add_column("note", AtomicType("STRING"))
expected = [{"id": 1, "text": "original", "note": None}]
projection = ["note", "id"]
elif request.param == "drop":
change = SchemaChange.drop_column("text")
expected = [{"id": 1}]
projection = ["id"]
else:
change = SchemaChange.rename_column("text", "renamed")
expected = [{"id": 1, "renamed": "original"}]
projection = ["renamed", "id"]
conn.catalog.alter_table("default.schema_only", [change])
current = conn.get_table("schema_only")
assert current.raw_table.table_schema.id > snapshot.schema_id
assert current.raw_table.snapshot_manager().get_latest_snapshot().id == snapshot.id
return current, captured, snapshot.id, expected, projection
def test_export_lance_preserves_latest_schema_only_alter(schema_only_alter, tmp_path):
current, _, _, expected, projection = schema_only_alter
logical = current.scan().to_arrow()
assert logical.to_pylist() == expected
exported = current.scan().export_lance(str(tmp_path / "latest.lance"))
assert exported.schema.names == logical.schema.names
assert exported.to_table().to_pylist() == expected
projected = current.scan().select(projection)
projected_logical = projected.to_arrow()
projected_export = projected.export_lance(str(tmp_path / "projected.lance"))
assert projected_export.schema.names == projection
assert projected_export.to_table().to_pylist() == projected_logical.to_pylist()
@pytest.mark.parametrize("view", ["snapshot", "tag"])
def test_export_lance_schema_only_alter_retains_explicit_history(schema_only_alter, tmp_path, view):
current, _, snapshot_id, _, _ = schema_only_alter
selector = {"snapshot_id": snapshot_id} if view == "snapshot" else {"tag_name": "before-alter"}
query = current.scan(**selector)
assert query.to_list() == _ORIGINAL_ROWS
exported = query.export_lance(str(tmp_path / (view + ".lance")))
assert exported.schema.names == ["id", "text"]
assert exported.to_table().to_pylist() == _ORIGINAL_ROWS
def test_export_lance_scan_captured_before_schema_only_alter(schema_only_alter, tmp_path):
_, captured, _, _, _ = schema_only_alter
assert captured.to_list() == _ORIGINAL_ROWS
exported = captured.export_lance(str(tmp_path / "captured.lance"))
assert exported.schema.names == ["id", "text"]
assert exported.to_table().to_pylist() == _ORIGINAL_ROWS
assert captured.to_list() == _ORIGINAL_ROWS