blob: dc24199c91bcb0230cc2bf30167e250df912d478 [file]
#!/usr/bin/env python3
#
# 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.
#
# Provisions Paimon tables into the warehouse (file:/tmp/paimon-warehouse)
# for paimon-rust integration tests to read.
from pyspark.sql import SparkSession
def main():
spark = SparkSession.builder.getOrCreate()
# Use Paimon catalog (configured in spark-defaults.conf with warehouse file:/tmp/paimon-warehouse)
spark.sql("USE paimon.default")
# Table: simple log table for read tests
spark.sql(
"""
CREATE TABLE IF NOT EXISTS simple_log_table (
id INT,
name STRING
) USING paimon
"""
)
spark.sql("INSERT INTO simple_log_table VALUES (1, 'alice'), (2, 'bob'), (3, 'carol')")
# Spark SQL here does not accept table constraints like
# PRIMARY KEY (id) NOT ENFORCED inside the column list, so use
# Paimon table properties instead.
spark.sql(
"""
CREATE TABLE IF NOT EXISTS simple_pk_table (
id INT,
name STRING
) USING paimon
TBLPROPERTIES (
'primary-key' = 'id',
'bucket' = '1'
)
"""
)
spark.sql(
"""
INSERT INTO simple_pk_table VALUES
(1, 'alice'),
(2, 'bob'),
(3, 'carol')
"""
)
# Table: primary key table with deletion vectors enabled.
# Re-inserting the same keys with newer values creates deleted historical
# rows that readers must filter via deletion vectors.
spark.sql(
"""
CREATE TABLE IF NOT EXISTS simple_dv_pk_table (
id INT,
name STRING
) USING paimon
TBLPROPERTIES (
'primary-key' = 'id',
'bucket' = '2',
'deletion-vectors.enabled' = 'true'
)
"""
)
spark.sql(
"""
INSERT INTO simple_dv_pk_table VALUES
(1, 'alice-v1'),
(2, 'bob-v1'),
(3, 'carol-v1'),
(5, 'eve-v1')
"""
)
spark.sql(
"""
INSERT INTO simple_dv_pk_table VALUES
(2, 'bob-v2'),
(3, 'carol-v2'),
(4, 'dave-v1'),
(6, 'frank-v1')
"""
)
spark.sql(
"""
INSERT INTO simple_dv_pk_table VALUES
(1, 'alice-v2'),
(4, 'dave-v2'),
(5, 'eve-v2')
"""
)
# ===== Partitioned table: single partition key (dt) =====
spark.sql(
"""
CREATE TABLE IF NOT EXISTS partitioned_log_table (
id INT,
name STRING,
dt STRING
) USING paimon
PARTITIONED BY (dt)
"""
)
spark.sql(
"""
INSERT INTO partitioned_log_table VALUES
(1, 'alice', '2024-01-01'),
(2, 'bob', '2024-01-01'),
(3, 'carol', '2024-01-02')
"""
)
# ===== Partitioned table: multiple partition keys (dt, hr) =====
spark.sql(
"""
CREATE TABLE IF NOT EXISTS multi_partitioned_log_table (
id INT,
name STRING,
dt STRING,
hr INT
) USING paimon
PARTITIONED BY (dt, hr)
"""
)
spark.sql(
"""
INSERT INTO multi_partitioned_log_table VALUES
(1, 'alice', '2024-01-01', 10),
(2, 'bob', '2024-01-01', 10),
(3, 'carol', '2024-01-01', 20),
(4, 'dave', '2024-01-02', 10)
"""
)
# ===== Partitioned table: PK + DV enabled =====
spark.sql(
"""
CREATE TABLE IF NOT EXISTS partitioned_dv_pk_table (
id INT,
name STRING,
dt STRING
) USING paimon
PARTITIONED BY (dt)
TBLPROPERTIES (
'primary-key' = 'id,dt',
'bucket' = '1',
'deletion-vectors.enabled' = 'true'
)
"""
)
spark.sql(
"""
INSERT INTO partitioned_dv_pk_table VALUES
(1, 'alice-v1', '2024-01-01'),
(2, 'bob-v1', '2024-01-01'),
(1, 'alice-v1', '2024-01-02'),
(3, 'carol-v1', '2024-01-02')
"""
)
spark.sql(
"""
INSERT INTO partitioned_dv_pk_table VALUES
(1, 'alice-v2', '2024-01-01'),
(3, 'carol-v2', '2024-01-02'),
(4, 'dave-v1', '2024-01-02')
"""
)
spark.sql(
"""
INSERT INTO partitioned_dv_pk_table VALUES
(2, 'bob-v2', '2024-01-01'),
(4, 'dave-v2', '2024-01-02')
"""
)
# ===== Data Evolution table: append-only with row tracking =====
# data-evolution.enabled + row-tracking.enabled allows partial column updates
# via MERGE INTO. This produces files with different write_cols covering the
# same row ID ranges, exercising the column-wise merge read path.
spark.sql(
"""
CREATE TABLE IF NOT EXISTS data_evolution_table (
id INT,
name STRING,
value INT
) USING paimon
TBLPROPERTIES (
'row-tracking.enabled' = 'true',
'data-evolution.enabled' = 'true'
)
"""
)
# First batch: rows with row_id 0, 1, 2 — all columns written
spark.sql(
"""
INSERT INTO data_evolution_table VALUES
(1, 'alice', 100),
(2, 'bob', 200),
(3, 'carol', 300)
"""
)
# Second batch: rows with row_id 3, 4
spark.sql(
"""
INSERT INTO data_evolution_table VALUES
(4, 'dave', 400),
(5, 'eve', 500)
"""
)
# MERGE INTO: partial column update on existing rows.
# This writes new files containing only the updated column (name) with the
# same first_row_id, so the reader must merge columns from multiple files.
# Paimon 1.3.1 requires the source table to be a Paimon table.
spark.sql(
"""
CREATE TABLE IF NOT EXISTS data_evolution_updates (
id INT,
name STRING
) USING paimon
"""
)
spark.sql(
"""
INSERT INTO data_evolution_updates VALUES (1, 'alice-v2'), (3, 'carol-v2')
"""
)
spark.sql(
"""
MERGE INTO data_evolution_table t
USING data_evolution_updates s
ON t.id = s.id
WHEN MATCHED THEN UPDATE SET t.name = s.name
"""
)
spark.sql("DROP TABLE data_evolution_updates")
# ===== Schema Evolution: Add Column =====
# Old files have (id, name); after ALTER TABLE ADD COLUMNS, new files have (id, name, age).
# Reader must fill nulls for 'age' when reading old files.
spark.sql(
"""
CREATE TABLE IF NOT EXISTS schema_evolution_add_column (
id INT,
name STRING
) USING paimon
"""
)
spark.sql(
"INSERT INTO schema_evolution_add_column VALUES (1, 'alice'), (2, 'bob')"
)
spark.sql("ALTER TABLE schema_evolution_add_column ADD COLUMNS (age INT)")
spark.sql(
"INSERT INTO schema_evolution_add_column VALUES (3, 'carol', 30), (4, 'dave', 40)"
)
# ===== Schema Evolution: Type Promotion (INT -> BIGINT) =====
# Old files have value as INT; after ALTER TABLE, new files have value as BIGINT.
# Reader must cast INT to BIGINT when reading old files.
spark.sql(
"""
CREATE TABLE IF NOT EXISTS schema_evolution_type_promotion (
id INT,
value INT
) USING paimon
"""
)
spark.sql(
"INSERT INTO schema_evolution_type_promotion VALUES (1, 100), (2, 200)"
)
spark.sql(
"ALTER TABLE schema_evolution_type_promotion ALTER COLUMN value TYPE BIGINT"
)
spark.sql(
"INSERT INTO schema_evolution_type_promotion VALUES (3, 3000000000)"
)
# ===== Data Evolution + Schema Evolution: Add Column =====
# Combines data-evolution (row-tracking + MERGE INTO) with ALTER TABLE ADD COLUMNS.
# Old files lack the new column; MERGE INTO produces partial-column files.
# Reader must fill nulls for missing columns AND merge columns across files.
spark.sql(
"""
CREATE TABLE IF NOT EXISTS data_evolution_add_column (
id INT,
name STRING,
value INT
) USING paimon
TBLPROPERTIES (
'row-tracking.enabled' = 'true',
'data-evolution.enabled' = 'true'
)
"""
)
spark.sql(
"""
INSERT INTO data_evolution_add_column VALUES
(1, 'alice', 100),
(2, 'bob', 200)
"""
)
spark.sql("ALTER TABLE data_evolution_add_column ADD COLUMNS (extra STRING)")
spark.sql(
"""
INSERT INTO data_evolution_add_column VALUES
(3, 'carol', 300, 'new'),
(4, 'dave', 400, 'new')
"""
)
# MERGE INTO to trigger merge_files_by_columns with schema evolution.
spark.sql(
"""
CREATE TABLE IF NOT EXISTS data_evolution_add_column_updates (
id INT,
name STRING
) USING paimon
"""
)
spark.sql(
"INSERT INTO data_evolution_add_column_updates VALUES (1, 'alice-v2')"
)
spark.sql(
"""
MERGE INTO data_evolution_add_column t
USING data_evolution_add_column_updates s
ON t.id = s.id
WHEN MATCHED THEN UPDATE SET t.name = s.name
"""
)
spark.sql("DROP TABLE data_evolution_add_column_updates")
# ===== Data Evolution + Schema Evolution: Type Promotion =====
# Combines data-evolution with ALTER TABLE ALTER COLUMN TYPE (INT -> BIGINT).
# Old files have INT; new files have BIGINT. MERGE INTO updates some rows.
# Reader must cast old INT columns to BIGINT AND merge columns across files.
spark.sql(
"""
CREATE TABLE IF NOT EXISTS data_evolution_type_promotion (
id INT,
value INT
) USING paimon
TBLPROPERTIES (
'row-tracking.enabled' = 'true',
'data-evolution.enabled' = 'true'
)
"""
)
spark.sql(
"INSERT INTO data_evolution_type_promotion VALUES (1, 100), (2, 200)"
)
spark.sql(
"ALTER TABLE data_evolution_type_promotion ALTER COLUMN value TYPE BIGINT"
)
spark.sql(
"INSERT INTO data_evolution_type_promotion VALUES (3, 3000000000)"
)
# MERGE INTO to trigger merge_files_by_columns with type promotion.
spark.sql(
"""
CREATE TABLE IF NOT EXISTS data_evolution_type_promotion_updates (
id INT,
value BIGINT
) USING paimon
"""
)
spark.sql(
"INSERT INTO data_evolution_type_promotion_updates VALUES (1, 999)"
)
spark.sql(
"""
MERGE INTO data_evolution_type_promotion t
USING data_evolution_type_promotion_updates s
ON t.id = s.id
WHEN MATCHED THEN UPDATE SET t.value = s.value
"""
)
spark.sql("DROP TABLE data_evolution_type_promotion_updates")
# ===== Schema Evolution: Drop Column =====
# Old files have (id, name, score); after ALTER TABLE DROP COLUMN, table has (id, name).
# Reader should ignore the dropped column when reading old files.
spark.sql(
"""
CREATE TABLE IF NOT EXISTS schema_evolution_drop_column (
id INT,
name STRING,
score INT
) USING paimon
"""
)
spark.sql(
"""
INSERT INTO schema_evolution_drop_column VALUES
(1, 'alice', 100),
(2, 'bob', 200)
"""
)
spark.sql("ALTER TABLE schema_evolution_drop_column DROP COLUMN score")
spark.sql(
"""
INSERT INTO schema_evolution_drop_column VALUES
(3, 'carol'),
(4, 'dave')
"""
)
if __name__ == "__main__":
main()