From a6a679aa686da91a10d941c3c00ee32a97db4e3b Mon Sep 17 00:00:00 2001 From: jeremyhi Date: Thu, 6 Aug 2026 19:59:50 +0800 Subject: [PATCH] test: add Parquet footer compatibility coverage Signed-off-by: jeremyhi --- .github/scripts/run-compat.py | 38 +++++++++++++++++ .../case.toml | 9 ++++ .../setup.sql | 11 +++++ .../verify.result | 20 +++++++++ .../verify.sql | 9 ++++ tests/runner/src/cmd/compat.rs | 42 ++++++++++++++++++- 6 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 tests/compatibility/cases/parquet_copy_footer_compatibility/case.toml create mode 100644 tests/compatibility/cases/parquet_copy_footer_compatibility/setup.sql create mode 100644 tests/compatibility/cases/parquet_copy_footer_compatibility/verify.result create mode 100644 tests/compatibility/cases/parquet_copy_footer_compatibility/verify.sql diff --git a/.github/scripts/run-compat.py b/.github/scripts/run-compat.py index 1b83798b35..8f1245bd39 100644 --- a/.github/scripts/run-compat.py +++ b/.github/scripts/run-compat.py @@ -37,6 +37,7 @@ from pathlib import Path VERSION_RE = re.compile(r"v[0-9]+\.[0-9]+\.[0-9]+") +PARQUET_FOOTER_CASE_FILTER = "^parquet_copy_footer_compatibility$" def parse_args() -> argparse.Namespace: @@ -175,6 +176,22 @@ def run_for_version( preserve_state=preserve_state, ) + parquet_base_command = [ + *base_command, + "--topology", + "standalone", + "--test-filter", + PARQUET_FOOTER_CASE_FILTER, + "--expect-cases", + "1", + ] + run_compat( + base_command=parquet_base_command, + preview_title=f"Preview {from_version} -> current (standalone Parquet footer)", + compatibility_title=f"Compatibility {from_version} -> current (standalone Parquet footer)", + preserve_state=preserve_state, + ) + def run_downgrade_for_version( *, @@ -206,6 +223,27 @@ def run_downgrade_for_version( preserve_state=preserve_state, ) + parquet_base_command = [ + str(runner), + "compat", + "--from-bins-dir", + str(current_bins_dir), + "--to-version", + to_version, + "--topology", + "standalone", + "--test-filter", + PARQUET_FOOTER_CASE_FILTER, + "--expect-cases", + "1", + ] + run_compat( + base_command=parquet_base_command, + preview_title=f"Preview current -> {to_version} (standalone Parquet footer)", + compatibility_title=f"Compatibility current -> {to_version} (standalone Parquet footer)", + preserve_state=preserve_state, + ) + def main() -> int: args = parse_args() diff --git a/tests/compatibility/cases/parquet_copy_footer_compatibility/case.toml b/tests/compatibility/cases/parquet_copy_footer_compatibility/case.toml new file mode 100644 index 0000000000..a0e11d3a5c --- /dev/null +++ b/tests/compatibility/cases/parquet_copy_footer_compatibility/case.toml @@ -0,0 +1,9 @@ +name = "parquet_copy_footer_compatibility" +reason = "Verify both directions: current imports legacy Parquet without greptime.table_name, and v1.1.4 imports current Parquet with the footer key." +introduced_by = "PR #8766" +topologies = ["standalone"] +from_range = ["*"] +to_range = ["*"] +features = ["copy", "parquet"] +owner = "query" +namespace = "parquet_copy_footer_compatibility" diff --git a/tests/compatibility/cases/parquet_copy_footer_compatibility/setup.sql b/tests/compatibility/cases/parquet_copy_footer_compatibility/setup.sql new file mode 100644 index 0000000000..4868c8369f --- /dev/null +++ b/tests/compatibility/cases/parquet_copy_footer_compatibility/setup.sql @@ -0,0 +1,11 @@ +CREATE TABLE parquet_copy_footer_source( + ts TIMESTAMP TIME INDEX, + host STRING PRIMARY KEY, + val INT +); + +INSERT INTO parquet_copy_footer_source VALUES +('2024-02-09 00:00:00+0000', 'host_a', 1), +('2024-02-09 00:01:00+0000', 'host_b', 2); + +COPY parquet_copy_footer_source TO '${SQLNESS_HOME}/parquet_copy_footer_compatibility/source.parquet' WITH (format='parquet'); diff --git a/tests/compatibility/cases/parquet_copy_footer_compatibility/verify.result b/tests/compatibility/cases/parquet_copy_footer_compatibility/verify.result new file mode 100644 index 0000000000..0adbecaec8 --- /dev/null +++ b/tests/compatibility/cases/parquet_copy_footer_compatibility/verify.result @@ -0,0 +1,20 @@ +CREATE TABLE parquet_copy_footer_target( + ts TIMESTAMP TIME INDEX, + host STRING PRIMARY KEY, + val INT +); + +Affected Rows: 0 + +COPY parquet_copy_footer_target FROM '${SQLNESS_HOME}/parquet_copy_footer_compatibility/source.parquet' WITH (format='parquet'); + +Affected Rows: 2 + +SELECT ts, host, val FROM parquet_copy_footer_target ORDER BY ts, host; + ++---------------------+--------+-----+ +| ts | host | val | ++---------------------+--------+-----+ +| 2024-02-09T00:00:00 | host_a | 1 | +| 2024-02-09T00:01:00 | host_b | 2 | ++---------------------+--------+-----+ diff --git a/tests/compatibility/cases/parquet_copy_footer_compatibility/verify.sql b/tests/compatibility/cases/parquet_copy_footer_compatibility/verify.sql new file mode 100644 index 0000000000..b6e48d94cd --- /dev/null +++ b/tests/compatibility/cases/parquet_copy_footer_compatibility/verify.sql @@ -0,0 +1,9 @@ +CREATE TABLE parquet_copy_footer_target( + ts TIMESTAMP TIME INDEX, + host STRING PRIMARY KEY, + val INT +); + +COPY parquet_copy_footer_target FROM '${SQLNESS_HOME}/parquet_copy_footer_compatibility/source.parquet' WITH (format='parquet'); + +SELECT ts, host, val FROM parquet_copy_footer_target ORDER BY ts, host; diff --git a/tests/runner/src/cmd/compat.rs b/tests/runner/src/cmd/compat.rs index ce33673a4a..f422c29a02 100644 --- a/tests/runner/src/cmd/compat.rs +++ b/tests/runner/src/cmd/compat.rs @@ -29,6 +29,7 @@ use crate::util; const COMMENT_PREFIX: &str = "--"; const INTERCEPTOR_PREFIX: &str = "-- SQLNESS"; const QUERY_DELIMITER: char = ';'; +const SQLNESS_HOME_TEMPLATE: &str = "${SQLNESS_HOME}"; #[derive(Debug, Clone, Copy, ValueEnum, PartialEq, Eq)] enum CompatTopology { @@ -564,6 +565,13 @@ enum CompatPhase { Verify, } +fn expand_sqlness_home(sql: &str, sqlness_home: Option<&str>) -> String { + match sqlness_home { + Some(sqlness_home) => sql.replace(SQLNESS_HOME_TEMPLATE, sqlness_home), + None => sql.to_string(), + } +} + /// Create an interceptor registry matching the ordinary sqlness runner. fn create_interceptor_registry() -> Registry { let mut interceptor_registry: Registry = Default::default(); @@ -851,6 +859,8 @@ impl ParsedStatement { db.compat_prepare_query_context(&context).await; run_namespace_prelude(db, namespace, &context).await?; let sql = self.concat_query_lines(); + let sqlness_home = std::env::var("SQLNESS_HOME").ok(); + let sql = expand_sqlness_home(&sql, sqlness_home.as_deref()); let mut results = Vec::new(); for sql in sql.split(TEMPLATE_DELIMITER) { @@ -942,7 +952,37 @@ fn simple_diff(expected: &str, actual: &str) -> String { #[cfg(test)] mod tests { - use super::trim_trailing_blank_lines; + use super::{ParsedStatement, expand_sqlness_home, trim_trailing_blank_lines}; + + #[test] + fn test_expand_sqlness_home_for_execution_only() { + let mut statement = ParsedStatement::new(); + statement.append_query_line( + "COPY source TO '${SQLNESS_HOME}/compat/source.parquet' WITH (format='parquet');", + ); + + let execution_sql = expand_sqlness_home( + &statement.concat_query_lines(), + Some("/tmp/sqlness-compat/copy"), + ); + + assert_eq!( + execution_sql, + "COPY source TO '/tmp/sqlness-compat/copy/compat/source.parquet' WITH (format='parquet');" + ); + assert_eq!( + statement.display_text(), + "COPY source TO '${SQLNESS_HOME}/compat/source.parquet' WITH (format='parquet');\n\n" + ); + } + + #[test] + fn test_expand_sqlness_home_without_value_leaves_execution_sql_unchanged() { + let sql = + "COPY source FROM '${SQLNESS_HOME}/compat/source.parquet' WITH (format='parquet');"; + + assert_eq!(expand_sqlness_home(sql, None), sql); + } #[test] fn test_trim_trailing_blank_lines_preserves_single_final_newline() {