blob: c89ce69cc324bf3bbf39dbb4949f8c074b8905d1 [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.
"""Local MERGE execution must not import distributed Ray orchestration."""
import importlib.util
import subprocess
import sys
import pytest
@pytest.mark.parametrize('engine', ['python', 'native'])
def test_merge_execution_does_not_load_ray_or_the_other_backend(tmp_path, engine):
if sys.version_info < (3, 10):
pytest.skip('MERGE condition expressions require Python 3.10')
if engine == 'python' and importlib.util.find_spec('datafusion') is None:
pytest.skip('Python MERGE conditions require the datafusion extra')
source = r"""
import importlib.abc
import sys
from unittest.mock import Mock, patch
import pyarrow as pa
engine, warehouse = sys.argv[1:]
blocked = 'pypaimon.execution.' + ('native' if engine == 'python' else 'python')
class BlockExecution(importlib.abc.MetaPathFinder):
def find_spec(self, fullname, path=None, target=None):
if (fullname == 'ray' or fullname.startswith('ray.')
or fullname.startswith('pypaimon.ray')
or fullname == blocked or fullname.startswith(blocked + '.')
or (engine == 'python' and fullname.startswith('pypaimon_rust'))):
raise AssertionError('Unexpected execution import: ' + fullname)
sys.meta_path.insert(0, BlockExecution())
from pypaimon import CatalogFactory, Schema
from pypaimon.table.data_evolution_merge_into import merge_into, WhenMatched, WhenNotMatched
from pypaimon.write.merge_types import source_col
catalog = CatalogFactory.create({'warehouse': warehouse})
catalog.create_database('default', True)
schema = pa.schema([('id', pa.int64()), ('value', pa.int32())])
catalog.create_table('default.data', Schema.from_pyarrow_schema(schema, options={
'row-tracking.enabled': 'true', 'data-evolution.enabled': 'true',
'write.native.enabled': str(engine == 'native').lower(), 'commit.native.enabled': 'false',
'scan.native-plan.enabled': 'false', 'read.native.enabled': 'false',
}), False)
table = catalog.get_table('default.data')
data = pa.table({'id': [1, 2], 'value': [9, 7]}, schema=schema)
matched = [WhenMatched.update({'value': source_col('value')}, condition='s.value > t.value')]
if engine == 'python':
builder = table.new_batch_write_builder()
writer = builder.new_write()
try:
writer.write_arrow(pa.table({'id': [1], 'value': [3]}, schema=schema))
builder.new_commit().commit(writer.prepare_commit())
finally:
writer.close()
builder = table.new_batch_write_builder()
messages = merge_into(table, data, on=['id'], when_matched=matched,
when_not_matched=[WhenNotMatched('*')], commit_user=builder.commit_user)
builder.new_commit().commit(messages)
read = table.new_read_builder()
assert read.new_read().to_arrow(read.new_scan().plan().splits()).sort_by('id').to_pylist() == [
{'id': 1, 'value': 9}, {'id': 2, 'value': 7}]
else:
from pypaimon.tests.merge_sdk_test_support import merge_sdk_module
sys.modules['pypaimon_rust.datafusion'] = merge_sdk_module()
core = Mock()
builder = core.new_batch_write_builder.return_value
builder.with_commit_user.return_value = builder
writer = builder.new_update.return_value
writer.merge_into.return_value = []
def assignment(batch):
assert batch.column_names == ['s.id', 's.value', 't.id', 't.value']
return batch.column('s.value')
matched = [WhenMatched.update({'value': assignment}, condition='s.value > t.value')]
with patch('pypaimon.execution.native.write.native_merge_into._native_row_id_table', return_value=core), \
patch('pypaimon.execution.native.write.native_merge_into.from_native_commit_messages',
side_effect=lambda table, messages: messages):
assert merge_into(table, data, on=['id'], when_matched=matched, commit_user='test') == []
writer.merge_into.assert_called_once()
callback = writer.merge_into.call_args[1]['when_matched'][0].assignments['value'].value
assert callback(pa.table({'t.id': [1], 't.value': [3], 's.value': [9]})).to_pylist() == [9]
assert writer.merge_into.call_args[1]['when_matched'][0].condition == '\"s.value\" > \"t.value\"'
assert not any(name.startswith('pypaimon.ray') or name == blocked
or name.startswith(blocked + '.') for name in sys.modules)
"""
result = subprocess.run(
[sys.executable, '-c', source, engine, str(tmp_path / 'warehouse')],
stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, timeout=45)
assert result.returncode == 0, result.stdout + result.stderr