blob: 6e30fff6b72c51cf0cba78c096137ca8f3c379e8 [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.
"""Integration tests for the one-shot limit-based BatchScanner.
Mirrors crates/fluss/tests/integration/batch_scanner.rs.
"""
import pyarrow as pa
import pytest
import fluss
async def test_returns_appended_rows_then_none(connection, admin):
table_path = fluss.TablePath("fluss", "py_test_bs_log")
await admin.drop_table(table_path, ignore_if_not_exists=True)
schema = pa.schema([pa.field("c1", pa.int32()), pa.field("c2", pa.string())])
table_descriptor = fluss.TableDescriptor(
fluss.Schema(schema), bucket_count=1, bucket_keys=["c1"]
)
await admin.create_table(table_path, table_descriptor, ignore_if_exists=False)
table = await connection.get_table(table_path)
append_writer = table.new_append().create_writer()
append_writer.write_arrow_batch(
pa.RecordBatch.from_arrays(
[
pa.array([1, 2, 3, 4, 5], pa.int32()),
pa.array(["a", "b", "c", "d", "e"]),
],
schema=schema,
)
)
await append_writer.flush()
bucket = fluss.TableBucket(table.get_table_info().table_id, 0)
scanner = table.new_scan().limit(3).create_bucket_batch_scanner(bucket)
assert scanner.bucket == bucket
first = await scanner.next_batch()
assert first is not None
assert first.bucket == bucket
# The server may return fewer rows than the limit, but never more.
assert 0 < first.batch.num_rows <= 3
assert await scanner.next_batch() is None
await admin.drop_table(table_path, ignore_if_not_exists=False)
async def test_reads_primary_key_table(connection, admin):
table_path = fluss.TablePath("fluss", "py_test_bs_pk")
await admin.drop_table(table_path, ignore_if_not_exists=True)
schema = fluss.Schema(
pa.schema([pa.field("id", pa.int32()), pa.field("name", pa.string())]),
primary_keys=["id"],
)
table_descriptor = fluss.TableDescriptor(schema, bucket_count=1)
await admin.create_table(table_path, table_descriptor, ignore_if_exists=False)
table = await connection.get_table(table_path)
upsert_writer = table.new_upsert().create_writer()
expected = {1: "a", 2: "b", 3: "c", 4: "d", 5: "e"}
for id_, name in expected.items():
upsert_writer.upsert({"id": id_, "name": name})
await upsert_writer.flush()
bucket = fluss.TableBucket(table.get_table_info().table_id, 0)
scanner = table.new_scan().limit(3).create_bucket_batch_scanner(bucket)
first = await scanner.next_batch()
assert first is not None
rows = first.batch.to_pydict()
assert 0 < len(rows["id"]) <= 3
assert all(expected[i] == name for i, name in zip(rows["id"], rows["name"]))
assert await scanner.next_batch() is None
await admin.drop_table(table_path, ignore_if_not_exists=False)
async def test_to_arrow_and_collect(connection, admin):
table_path = fluss.TablePath("fluss", "py_test_bs_to_arrow")
await admin.drop_table(table_path, ignore_if_not_exists=True)
schema = pa.schema([pa.field("c1", pa.int32()), pa.field("c2", pa.string())])
table_descriptor = fluss.TableDescriptor(
fluss.Schema(schema), bucket_count=1, bucket_keys=["c1"]
)
await admin.create_table(table_path, table_descriptor, ignore_if_exists=False)
table = await connection.get_table(table_path)
append_writer = table.new_append().create_writer()
append_writer.write_arrow_batch(
pa.RecordBatch.from_arrays(
[pa.array([10, 20], pa.int32()), pa.array(["x", "y"])], schema=schema
)
)
await append_writer.flush()
table_id = table.get_table_info().table_id
batches = (
await table.new_scan()
.limit(10)
.create_bucket_batch_scanner(fluss.TableBucket(table_id, 0))
.collect_all_batches()
)
assert [b.batch.num_rows for b in batches] == [2]
arrow = (
await table.new_scan()
.limit(10)
.create_bucket_batch_scanner(fluss.TableBucket(table_id, 0))
.to_arrow()
)
assert arrow.to_pydict() == {"c1": [10, 20], "c2": ["x", "y"]}
await admin.drop_table(table_path, ignore_if_not_exists=False)
async def test_projection_skips_middle_column(connection, admin):
table_path = fluss.TablePath("fluss", "py_test_bs_projection")
await admin.drop_table(table_path, ignore_if_not_exists=True)
schema = pa.schema(
[
pa.field("c1", pa.int32()),
pa.field("c2", pa.string()),
pa.field("c3", pa.int64()),
]
)
table_descriptor = fluss.TableDescriptor(
fluss.Schema(schema), bucket_count=1, bucket_keys=["c1"]
)
await admin.create_table(table_path, table_descriptor, ignore_if_exists=False)
table = await connection.get_table(table_path)
append_writer = table.new_append().create_writer()
append_writer.write_arrow_batch(
pa.RecordBatch.from_arrays(
[
pa.array([1, 2], pa.int32()),
pa.array(["a", "b"]),
pa.array([100, 200], pa.int64()),
],
schema=schema,
)
)
await append_writer.flush()
bucket = fluss.TableBucket(table.get_table_info().table_id, 0)
arrow = (
await table.new_scan()
.project_by_name(["c1", "c3"])
.limit(10)
.create_bucket_batch_scanner(bucket)
.to_arrow()
)
assert arrow.to_pydict() == {"c1": [1, 2], "c3": [100, 200]}
await admin.drop_table(table_path, ignore_if_not_exists=False)
async def test_construction_errors(connection, admin):
table_path = fluss.TablePath("fluss", "py_test_bs_errors")
await admin.drop_table(table_path, ignore_if_not_exists=True)
schema = fluss.Schema(pa.schema([pa.field("c1", pa.int32())]))
table_descriptor = fluss.TableDescriptor(schema, bucket_count=1, bucket_keys=["c1"])
await admin.create_table(table_path, table_descriptor, ignore_if_exists=False)
table = await connection.get_table(table_path)
table_id = table.get_table_info().table_id
for bad in (0, -5):
with pytest.raises(fluss.FlussError):
table.new_scan().limit(bad)
with pytest.raises(fluss.FlussError):
table.new_scan().create_bucket_batch_scanner(fluss.TableBucket(table_id, 0))
for bad_bucket in (
fluss.TableBucket(table_id + 9999, 0),
fluss.TableBucket(table_id, 99),
):
with pytest.raises(fluss.FlussError):
table.new_scan().limit(1).create_bucket_batch_scanner(bad_bucket)
with pytest.raises(fluss.FlussError):
await table.new_scan().limit(5).create_log_scanner()
with pytest.raises(fluss.FlussError):
await table.new_scan().limit(5).create_record_batch_log_scanner()
await admin.drop_table(table_path, ignore_if_not_exists=False)
async def test_rejects_non_arrow_log_format(connection, admin):
table_path = fluss.TablePath("fluss", "py_test_bs_indexed")
await admin.drop_table(table_path, ignore_if_not_exists=True)
schema = fluss.Schema(pa.schema([pa.field("c1", pa.int32())]))
table_descriptor = fluss.TableDescriptor(
schema, bucket_count=1, bucket_keys=["c1"], log_format="INDEXED"
)
await admin.create_table(table_path, table_descriptor, ignore_if_exists=False)
table = await connection.get_table(table_path)
bucket = fluss.TableBucket(table.get_table_info().table_id, 0)
with pytest.raises(fluss.FlussError):
table.new_scan().limit(1).create_bucket_batch_scanner(bucket)
await admin.drop_table(table_path, ignore_if_not_exists=False)