blob: c5fdfaca2b53855b62771569dc538710131e080c [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.
"""Schema cast validation describes capabilities without loading execution."""
from pathlib import Path
import subprocess
import sys
import textwrap
import pyarrow as pa
import pytest
from pypaimon.schema.data_type_casts import can_execute_cast
from pypaimon.schema.data_types import AtomicType, PyarrowFieldParser
_LEAF_TYPES = [
'CHAR(3)', 'VARCHAR(5)', 'BOOLEAN', 'BINARY(3)', 'VARBINARY(5)',
'DECIMAL(4,2)', 'DECIMAL(38,18)', 'TINYINT', 'SMALLINT', 'INT', 'BIGINT',
'FLOAT', 'DOUBLE', 'DATE', 'TIME', 'TIME(3)',
'TIMESTAMP(0)', 'TIMESTAMP(3)', 'TIMESTAMP(6)', 'TIMESTAMP(9)',
'TIMESTAMP_LTZ(0)', 'TIMESTAMP_LTZ(3)', 'TIMESTAMP_LTZ(6)', 'TIMESTAMP_LTZ(9)',
'TIMESTAMP(6) WITH LOCAL TIME ZONE', 'BLOB', 'VARIANT',
]
@pytest.mark.parametrize('source', _LEAF_TYPES)
def test_declared_cast_capabilities_match_existing_read_materialization(source):
from pypaimon.execution.python.casting.schema_evolution import cast_array_for_schema_evolution
source_type = AtomicType(source)
values = pa.nulls(1, type=PyarrowFieldParser.from_paimon_type(source_type))
for target in _LEAF_TYPES:
target_type = AtomicType(target)
try:
cast_array_for_schema_evolution(
values, PyarrowFieldParser.from_paimon_type(target_type), source_type, target_type)
executable = True
except (pa.ArrowNotImplementedError, pa.ArrowInvalid, pa.ArrowTypeError,
ValueError, NotImplementedError):
executable = False
# TIME precision is not represented by its Arrow storage unit, so
# changing it is rejected independently of Arrow's identity kernel.
if (source, target) in (('TIME', 'TIME(3)'), ('TIME(3)', 'TIME')):
executable = False
assert can_execute_cast(source_type, target_type) == executable, '{} -> {}'.format(source, target)
@pytest.mark.skipif(sys.version_info < (3, 7), reason='Python 3.6 retains eager package exports')
def test_schema_type_changes_do_not_import_or_execute_either_backend():
script = r"""
import importlib.abc
import sys
from unittest.mock import patch
class RejectExecution(importlib.abc.MetaPathFinder):
def find_spec(self, fullname, path=None, target=None):
if fullname == 'pypaimon.execution' or fullname.startswith('pypaimon.execution.'):
raise AssertionError('Schema validation imported execution: ' + fullname)
sys.meta_path.insert(0, RejectExecution())
from pypaimon.schema.data_type_casts import can_execute_cast
from pypaimon.schema.data_types import AtomicType, DataField
from pypaimon.schema.schema_change import SchemaChange
from pypaimon.schema.schema_manager import SchemaManager
from pypaimon.schema.table_schema import TableSchema
manager = SchemaManager(None, '/unused-schema-capabilities')
with patch('pyarrow.nulls', side_effect=AssertionError('Schema validation executed a cast probe')):
for source, target, supported in [
('INT', 'BIGINT', True), ('INT', 'TIMESTAMP(3)', True),
('BIGINT', 'TIMESTAMP_LTZ(6)', True), ('INT', 'DECIMAL(4,2)', True),
('TIMESTAMP_LTZ(9)', 'TIMESTAMP(3)', True),
('TIMESTAMP(3)', 'DECIMAL(10,2)', False), ('BOOLEAN', 'DECIMAL(4,2)', False),
('TIME', 'TIMESTAMP(6)', False), ('TIME(3)', 'TIME(1)', False),
]:
source_type, target_type = AtomicType(source), AtomicType(target)
assert can_execute_cast(source_type, target_type) == supported, (source, target)
schema = TableSchema(fields=[DataField(0, 'v', source_type)])
change = SchemaChange.update_column_type('v', target_type)
if supported:
changed = manager._generate_table_schema(schema, [change])
assert changed.fields[0].id == 0 and changed.fields[0].type == target_type
assert schema.fields[0].type == source_type
else:
try:
manager._generate_table_schema(schema, [change])
except ValueError as error:
assert 'no executable cast' in str(error)
else:
raise AssertionError('Unsupported schema cast was accepted: ' + str((source, target)))
assert not any(name == 'pypaimon.execution' or name.startswith('pypaimon.execution.')
for name in sys.modules)
"""
result = subprocess.run(
[sys.executable, '-c', textwrap.dedent(script)],
cwd=str(Path(__file__).resolve().parents[2]),
stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True,
)
assert result.returncode == 0, result.stdout + result.stderr