|
| 1 | +"""Regression tests for Hive DESCRIBE field discovery (issue #1250).""" |
| 2 | + |
| 3 | +import ast |
| 4 | +import json |
| 5 | +from contextlib import nullcontext |
| 6 | +from pathlib import Path |
| 7 | +from types import SimpleNamespace |
| 8 | +from unittest.mock import Mock |
| 9 | + |
| 10 | +import pytest |
| 11 | + |
| 12 | +BACKEND_DIR = Path(__file__).resolve().parents[1] |
| 13 | + |
| 14 | + |
| 15 | +def _load_symbol(relative_path, name, namespace): |
| 16 | + # As in test_connection_pool_manager, avoid importing unrelated DB drivers. |
| 17 | + path = BACKEND_DIR / relative_path |
| 18 | + tree = ast.parse(path.read_text(encoding="utf-8")) |
| 19 | + node = next(node for node in tree.body if getattr(node, "name", None) == name) |
| 20 | + module = ast.Module(body=[node], type_ignores=[]) |
| 21 | + exec(compile(module, str(path), "exec"), namespace) |
| 22 | + return namespace[name] |
| 23 | + |
| 24 | + |
| 25 | +@pytest.fixture |
| 26 | +def get_hive_fields(): |
| 27 | + cursor = Mock() |
| 28 | + connection = SimpleNamespace(cursor=lambda: nullcontext(cursor)) |
| 29 | + pool = SimpleNamespace(connection=lambda: nullcontext(connection)) |
| 30 | + namespace = { |
| 31 | + "json": json, |
| 32 | + "CoreDatasource": SimpleNamespace, |
| 33 | + "DatasourceConf": SimpleNamespace, |
| 34 | + "aes_decrypt": lambda value: value, |
| 35 | + "DB": SimpleNamespace( |
| 36 | + get_db=lambda _: SimpleNamespace(connect_type="driver") |
| 37 | + ), |
| 38 | + "ConnectType": SimpleNamespace(sqlalchemy="sqlalchemy"), |
| 39 | + "get_driver_pool": lambda _: pool, |
| 40 | + "get_field_sql": lambda *args: ("DESCRIBE sample", None, None), |
| 41 | + } |
| 42 | + _load_symbol("common/utils/utils.py", "equals_ignore_case", namespace) |
| 43 | + _load_symbol("apps/datasource/models/datasource.py", "ColumnSchema", namespace) |
| 44 | + get_fields = _load_symbol("apps/db/db.py", "get_fields", namespace) |
| 45 | + |
| 46 | + def discover(rows): |
| 47 | + cursor.fetchall.return_value = rows |
| 48 | + fields = get_fields(SimpleNamespace(type="hive", configuration="{}"), "sample") |
| 49 | + return [(field.fieldName, field.fieldType, field.fieldComment) for field in fields] |
| 50 | + |
| 51 | + return discover |
| 52 | + |
| 53 | + |
| 54 | +@pytest.mark.parametrize("repeat_partition", [False, True]) |
| 55 | +def test_partition_fields_are_retained_once_without_metadata(get_hive_fields, repeat_partition): |
| 56 | + rows = [("id", "int", "identifier")] |
| 57 | + if repeat_partition: |
| 58 | + rows.append(("dt", "string", "partition date")) |
| 59 | + rows.extend([ |
| 60 | + ("", "", ""), |
| 61 | + ("# Partition Information", "", ""), |
| 62 | + ("# col_name", "data_type", "comment"), |
| 63 | + ("dt", "string", "partition date"), |
| 64 | + ]) |
| 65 | + |
| 66 | + assert get_hive_fields(rows) == [ |
| 67 | + ("id", "int", "identifier"), |
| 68 | + ("dt", "string", "partition date"), |
| 69 | + ] |
| 70 | + |
| 71 | + |
| 72 | +def test_duplicate_fields_preserve_first_occurrence_and_order(get_hive_fields): |
| 73 | + assert get_hive_fields([ |
| 74 | + ("id", "int", "identifier"), |
| 75 | + ("dt", "string", "first comment"), |
| 76 | + ("region", "string", "region"), |
| 77 | + ("dt", "string", "repeated comment"), |
| 78 | + ]) == [ |
| 79 | + ("id", "int", "identifier"), |
| 80 | + ("dt", "string", "first comment"), |
| 81 | + ("region", "string", "region"), |
| 82 | + ] |
| 83 | + |
| 84 | + |
| 85 | +def test_blank_names_and_padded_metadata_are_ignored(get_hive_fields): |
| 86 | + assert get_hive_fields([ |
| 87 | + (None, None, None), |
| 88 | + (" ", "", ""), |
| 89 | + (" # Partition Information ", None, None), |
| 90 | + (" # col_name ", "data_type ", "comment"), |
| 91 | + ("dt", "string", None), |
| 92 | + ]) == [("dt", "string", None)] |
| 93 | + |
| 94 | + |
| 95 | +def test_regular_columns_preserve_names_types_and_comments(get_hive_fields): |
| 96 | + assert get_hive_fields([ |
| 97 | + ("id", "bigint", None), |
| 98 | + ("amount", "decimal(10,2)", b"amount"), |
| 99 | + ("#tag", "string", "tag"), |
| 100 | + ("# Partition Information", "string", "a real column"), |
| 101 | + (" spaced name ", "string", "keep the original name"), |
| 102 | + ]) == [ |
| 103 | + ("id", "bigint", None), |
| 104 | + ("amount", "decimal(10,2)", "amount"), |
| 105 | + ("#tag", "string", "tag"), |
| 106 | + ("# Partition Information", "string", "a real column"), |
| 107 | + (" spaced name ", "string", "keep the original name"), |
| 108 | + ] |
| 109 | + |
| 110 | + |
| 111 | +def test_extra_driver_columns_remain_supported(get_hive_fields): |
| 112 | + assert get_hive_fields([ |
| 113 | + ("id", "int", "identifier", "extra", 1, None), |
| 114 | + ("# Partition Information", None, None, "extra", 2, None), |
| 115 | + ("dt", "string", "partition date", "extra", 3, None), |
| 116 | + ]) == [ |
| 117 | + ("id", "int", "identifier"), |
| 118 | + ("dt", "string", "partition date"), |
| 119 | + ] |
| 120 | + |
| 121 | + |
| 122 | +@pytest.mark.parametrize("rows", [[], [("", "", ""), ("# col_name", "data_type", "comment")]]) |
| 123 | +def test_no_real_columns_returns_empty_list(get_hive_fields, rows): |
| 124 | + assert get_hive_fields(rows) == [] |
0 commit comments