From 453416640a9176fb604bd12a0c27530fef2f44bb Mon Sep 17 00:00:00 2001 From: Krzysztof Gadomski Date: Wed, 6 May 2026 20:30:21 +0200 Subject: [PATCH 1/2] fix(types): handle connector >=4.3.0 interval display format snowflake-connector-python v4.3.0 changed the string representation of INTERVAL YEAR and INTERVAL MONTH values. When months=0, the connector now returns '+N' (just years) instead of '+N-0' (compound year-month). format_year_month_interval_for_display previously only treated a simple '+N' value as years when the type was exactly INTERVAL YEAR (start=YEAR, end=YEAR). For INTERVAL YEAR TO MONTH (start=YEAR, end=MONTH), it incorrectly treated '+N' as months, producing e.g. "INTERVAL '0-4'" instead of the correct "INTERVAL '4-0'" for 4 years. Fix: when start_field is YEAR, always interpret a simple '+N' value as years regardless of end_field. This is correct for both old connector (which always uses compound '+Y-M' format, so this branch is never hit) and new connector >=4.3.0 (which uses '+N' when the months component is zero). Co-authored-by: Cursor --- .../snowpark/_internal/type_utils.py | 10 +- tests/unit/test_datatype_mapper.py | 101 ++++++++++++++++++ 2 files changed, 106 insertions(+), 5 deletions(-) diff --git a/src/snowflake/snowpark/_internal/type_utils.py b/src/snowflake/snowpark/_internal/type_utils.py index c016e0f0f0..b08e07d3c9 100644 --- a/src/snowflake/snowpark/_internal/type_utils.py +++ b/src/snowflake/snowpark/_internal/type_utils.py @@ -1764,11 +1764,11 @@ def format_year_month_interval_for_display( years = str(int(parts[0])) months = str(int(parts[1])) else: - # Format like "+2" or "-3" - if ( - start_field == YearMonthIntervalType.YEAR - and end_field == YearMonthIntervalType.YEAR - ): + # Format like "+2" or "-3" — a single number without a year-month dash. + # When start_field is YEAR the value represents years (connector >=4.3.0 + # omits the "-0" suffix when months=0). Only treat as months when the + # interval type starts at MONTH. + if start_field == YearMonthIntervalType.YEAR: years = str(int(remaining)) else: months = str(int(remaining)) diff --git a/tests/unit/test_datatype_mapper.py b/tests/unit/test_datatype_mapper.py index 62548a0a6f..69d64d0191 100644 --- a/tests/unit/test_datatype_mapper.py +++ b/tests/unit/test_datatype_mapper.py @@ -809,3 +809,104 @@ def test_schema_expression(): schema_expression(DayTimeIntervalType(), False) == "INTERVAL '1 01:01:01.0001' DAY TO SECOND" ) + + +class TestFormatYearMonthIntervalForDisplay: + """Tests for format_year_month_interval_for_display. + + Validates correct behavior with both old connector format ('+Y-M' compound) + and new connector >=4.3.0 format ('+N' when months=0 or years=0). + """ + + @pytest.mark.parametrize( + "cell, start_field, end_field, expected", + [ + # Compound format (both old and new connector use this when months != 0) + ( + "+1-6", + YearMonthIntervalType.YEAR, + YearMonthIntervalType.MONTH, + "INTERVAL '1-6' YEAR TO MONTH", + ), + ( + "-2-3", + YearMonthIntervalType.YEAR, + YearMonthIntervalType.MONTH, + "INTERVAL '-2-3' YEAR TO MONTH", + ), + ( + "+0-5", + YearMonthIntervalType.YEAR, + YearMonthIntervalType.MONTH, + "INTERVAL '0-5' YEAR TO MONTH", + ), + # New connector format: '+N' with no dash when months=0 for YEAR TO MONTH type + ( + "+4", + YearMonthIntervalType.YEAR, + YearMonthIntervalType.MONTH, + "INTERVAL '4-0' YEAR TO MONTH", + ), + ( + "-7", + YearMonthIntervalType.YEAR, + YearMonthIntervalType.MONTH, + "INTERVAL '-7-0' YEAR TO MONTH", + ), + ( + "+12", + YearMonthIntervalType.YEAR, + YearMonthIntervalType.MONTH, + "INTERVAL '12-0' YEAR TO MONTH", + ), + # YEAR-only type (both connector versions use '+N' format) + ( + "+4", + YearMonthIntervalType.YEAR, + YearMonthIntervalType.YEAR, + "INTERVAL '4' YEAR", + ), + ( + "-1", + YearMonthIntervalType.YEAR, + YearMonthIntervalType.YEAR, + "INTERVAL '-1' YEAR", + ), + # MONTH-only type (both connector versions use '+N' format) + ( + "+5", + YearMonthIntervalType.MONTH, + YearMonthIntervalType.MONTH, + "INTERVAL '5' MONTH", + ), + ( + "-12", + YearMonthIntervalType.MONTH, + YearMonthIntervalType.MONTH, + "INTERVAL '-12' MONTH", + ), + # Compound format with YEAR-only output (connector returns '+5-0') + ( + "+5-0", + YearMonthIntervalType.YEAR, + YearMonthIntervalType.YEAR, + "INTERVAL '5' YEAR", + ), + # Compound format with MONTH-only output (connector returns '+0-3') + ( + "+0-3", + YearMonthIntervalType.MONTH, + YearMonthIntervalType.MONTH, + "INTERVAL '3' MONTH", + ), + ], + ) + def test_format_year_month_interval(self, cell, start_field, end_field, expected): + from snowflake.snowpark._internal.type_utils import ( + format_year_month_interval_for_display, + ) + + assert ( + format_year_month_interval_for_display(cell, start_field, end_field) + == expected + ) From bfcac424eff3849644566c8f979681b971d8244b Mon Sep 17 00:00:00 2001 From: Krzysztof Gadomski Date: Tue, 12 May 2026 09:57:07 +0200 Subject: [PATCH 2/2] address PR review: refactor test style and update changelog --- CHANGELOG.md | 1 + tests/unit/test_datatype_mapper.py | 183 +++++++++++++---------------- 2 files changed, 85 insertions(+), 99 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e0e6fef13..1ff3650e9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ #### Bug Fixes - Fixed a bug where `AsyncJob.result("no_result")` sometimes silently returned without raising error for failed queries. +- Fixed a bug where `INTERVAL YEAR TO MONTH` values with zero months were displayed incorrectly (e.g. `INTERVAL '0-4' YEAR TO MONTH` instead of `INTERVAL '4-0' YEAR TO MONTH`) when using `snowflake-connector-python>=4.3.0`. #### Improvements diff --git a/tests/unit/test_datatype_mapper.py b/tests/unit/test_datatype_mapper.py index 69d64d0191..3df7de666a 100644 --- a/tests/unit/test_datatype_mapper.py +++ b/tests/unit/test_datatype_mapper.py @@ -16,6 +16,9 @@ to_sql, to_sql_no_cast, ) +from snowflake.snowpark._internal.type_utils import ( + format_year_month_interval_for_display, +) from snowflake.snowpark._internal.udf_utils import generate_call_python_sp_sql from snowflake.snowpark.types import ( ArrayType, @@ -811,102 +814,84 @@ def test_schema_expression(): ) -class TestFormatYearMonthIntervalForDisplay: - """Tests for format_year_month_interval_for_display. - - Validates correct behavior with both old connector format ('+Y-M' compound) - and new connector >=4.3.0 format ('+N' when months=0 or years=0). - """ - - @pytest.mark.parametrize( - "cell, start_field, end_field, expected", - [ - # Compound format (both old and new connector use this when months != 0) - ( - "+1-6", - YearMonthIntervalType.YEAR, - YearMonthIntervalType.MONTH, - "INTERVAL '1-6' YEAR TO MONTH", - ), - ( - "-2-3", - YearMonthIntervalType.YEAR, - YearMonthIntervalType.MONTH, - "INTERVAL '-2-3' YEAR TO MONTH", - ), - ( - "+0-5", - YearMonthIntervalType.YEAR, - YearMonthIntervalType.MONTH, - "INTERVAL '0-5' YEAR TO MONTH", - ), - # New connector format: '+N' with no dash when months=0 for YEAR TO MONTH type - ( - "+4", - YearMonthIntervalType.YEAR, - YearMonthIntervalType.MONTH, - "INTERVAL '4-0' YEAR TO MONTH", - ), - ( - "-7", - YearMonthIntervalType.YEAR, - YearMonthIntervalType.MONTH, - "INTERVAL '-7-0' YEAR TO MONTH", - ), - ( - "+12", - YearMonthIntervalType.YEAR, - YearMonthIntervalType.MONTH, - "INTERVAL '12-0' YEAR TO MONTH", - ), - # YEAR-only type (both connector versions use '+N' format) - ( - "+4", - YearMonthIntervalType.YEAR, - YearMonthIntervalType.YEAR, - "INTERVAL '4' YEAR", - ), - ( - "-1", - YearMonthIntervalType.YEAR, - YearMonthIntervalType.YEAR, - "INTERVAL '-1' YEAR", - ), - # MONTH-only type (both connector versions use '+N' format) - ( - "+5", - YearMonthIntervalType.MONTH, - YearMonthIntervalType.MONTH, - "INTERVAL '5' MONTH", - ), - ( - "-12", - YearMonthIntervalType.MONTH, - YearMonthIntervalType.MONTH, - "INTERVAL '-12' MONTH", - ), - # Compound format with YEAR-only output (connector returns '+5-0') - ( - "+5-0", - YearMonthIntervalType.YEAR, - YearMonthIntervalType.YEAR, - "INTERVAL '5' YEAR", - ), - # Compound format with MONTH-only output (connector returns '+0-3') - ( - "+0-3", - YearMonthIntervalType.MONTH, - YearMonthIntervalType.MONTH, - "INTERVAL '3' MONTH", - ), - ], - ) - def test_format_year_month_interval(self, cell, start_field, end_field, expected): - from snowflake.snowpark._internal.type_utils import ( - format_year_month_interval_for_display, - ) - - assert ( - format_year_month_interval_for_display(cell, start_field, end_field) - == expected - ) +@pytest.mark.parametrize( + "cell, start_field, end_field, expected", + [ + ( + "+1-6", + YearMonthIntervalType.YEAR, + YearMonthIntervalType.MONTH, + "INTERVAL '1-6' YEAR TO MONTH", + ), + ( + "-2-3", + YearMonthIntervalType.YEAR, + YearMonthIntervalType.MONTH, + "INTERVAL '-2-3' YEAR TO MONTH", + ), + ( + "+0-5", + YearMonthIntervalType.YEAR, + YearMonthIntervalType.MONTH, + "INTERVAL '0-5' YEAR TO MONTH", + ), + ( + "+4", + YearMonthIntervalType.YEAR, + YearMonthIntervalType.MONTH, + "INTERVAL '4-0' YEAR TO MONTH", + ), + ( + "-7", + YearMonthIntervalType.YEAR, + YearMonthIntervalType.MONTH, + "INTERVAL '-7-0' YEAR TO MONTH", + ), + ( + "+12", + YearMonthIntervalType.YEAR, + YearMonthIntervalType.MONTH, + "INTERVAL '12-0' YEAR TO MONTH", + ), + ( + "+4", + YearMonthIntervalType.YEAR, + YearMonthIntervalType.YEAR, + "INTERVAL '4' YEAR", + ), + ( + "-1", + YearMonthIntervalType.YEAR, + YearMonthIntervalType.YEAR, + "INTERVAL '-1' YEAR", + ), + ( + "+5", + YearMonthIntervalType.MONTH, + YearMonthIntervalType.MONTH, + "INTERVAL '5' MONTH", + ), + ( + "-12", + YearMonthIntervalType.MONTH, + YearMonthIntervalType.MONTH, + "INTERVAL '-12' MONTH", + ), + ( + "+5-0", + YearMonthIntervalType.YEAR, + YearMonthIntervalType.YEAR, + "INTERVAL '5' YEAR", + ), + ( + "+0-3", + YearMonthIntervalType.MONTH, + YearMonthIntervalType.MONTH, + "INTERVAL '3' MONTH", + ), + ], +) +def test_format_year_month_interval_for_display(cell, start_field, end_field, expected): + assert ( + format_year_month_interval_for_display(cell, start_field, end_field) == expected + )