@@ -35,61 +35,69 @@ def validate_join_cols(df: pyarrow_table, join_cols: list[str], table_schema: pa
3535 """Validate join-key presence and types before Arrow comparison or hashing."""
3636 if not isinstance (join_cols , (list , tuple )):
3737 raise ValueError (f"join_cols must be a list of column names, got { type (join_cols ).__name__ } ." )
38+
3839 duplicates = sorted (col for col , count in Counter (join_cols ).items () if count > 1 )
3940 if duplicates :
4041 raise ValueError (f"join_cols contains duplicates: { ', ' .join (duplicates )} ." )
4142
42- df_column_names = set (df .schema .names )
43-
4443 for col in join_cols :
45- if col not in table_schema .names :
46- parent = col .split ("." , 1 )[0 ]
47- if "." in col and parent in table_schema .names and pa .types .is_nested (table_schema .field (parent ).type ):
48- raise ValueError (f"Join column '{ col } ' is a field inside struct '{ parent } ' and cannot be used as a join key." )
49- raise ValueError (
50- f"Join column '{ col } ' does not exist in the table. Available columns: { ', ' .join (table_schema .names )} ."
51- )
52- table_field = table_schema .field (col )
53- # Table-level rejections: These types are fundamentally unreliable or
54- # unsupported as join keys regardless of the input data format.
55- if pa .types .is_floating (table_field .type ):
56- raise ValueError (
57- f"Join column '{ col } ' is floating point and cannot be used as a join key "
58- "because floating point equality is unreliable."
59- )
60- if pa .types .is_nested (table_field .type ):
61- raise ValueError (
62- f"Join column '{ col } ' has nested type '{ table_field .type } '; only primitive columns can be join keys."
63- )
64- if isinstance (table_field .type , pa .BaseExtensionType ):
65- raise NotImplementedError (
66- f"Join column '{ col } ' has type '{ table_field .type } ', which is not yet supported as a join key."
67- )
68-
69- # Schema compatibility permits missing optional fields, but upsert needs every join key.
70- if col not in df_column_names :
71- raise ValueError (f"Join column '{ col } ' is missing from the input." )
72- # Some input representations are unsupported even when the table type is valid.
73- arr = df .column (col )
74- if pa .types .is_dictionary (arr .type ):
75- raise NotImplementedError (
76- f"Input column '{ col } ' is dictionary-encoded, which is not yet supported for join keys. Decode it first."
77- )
78- if pa .types .is_null (arr .type ):
79- raise ValueError (f"Input column '{ col } ' has the null type and cannot be used as a join key." )
80- if pa .types .is_string_view (arr .type ) or pa .types .is_binary_view (arr .type ):
81- plain = "string" if pa .types .is_string_view (arr .type ) else "binary"
82- raise NotImplementedError (
83- f"Input column '{ col } ' has type '{ arr .type } ', which is not yet supported for join keys. "
84- f"Cast it to '{ plain } ' first."
85- )
86- if isinstance (arr .type , pa .BaseExtensionType ):
87- raise NotImplementedError (
88- f"Input column '{ col } ' has extension type '{ arr .type } ', which is not yet supported for join keys."
89- )
90- # Null keys cannot be expressed as Iceberg literals in the match filter.
91- if arr .null_count > 0 :
92- raise ValueError (f"Input column '{ col } ' contains null values, which cannot be join keys." )
44+ _validate_table_join_col (col , table_schema )
45+ _validate_input_join_col (col , df )
46+
47+
48+ def _validate_table_join_col (col : str , table_schema : pa .Schema ) -> None :
49+ """Reject types that are unreliable or unsupported as join keys regardless of the input."""
50+ if col not in table_schema .names :
51+ parent = col .split ("." , 1 )[0 ]
52+ if "." in col and parent in table_schema .names and pa .types .is_nested (table_schema .field (parent ).type ):
53+ raise ValueError (f"Join column '{ col } ' is a field inside struct '{ parent } ' and cannot be used as a join key." )
54+ raise ValueError (f"Join column '{ col } ' does not exist in the table. Available columns: { ', ' .join (table_schema .names )} ." )
55+
56+ field_type = table_schema .field (col ).type
57+
58+ if pa .types .is_floating (field_type ):
59+ raise ValueError (
60+ f"Join column '{ col } ' is floating point and cannot be used as a join key "
61+ "because floating point equality is unreliable."
62+ )
63+
64+ if pa .types .is_nested (field_type ):
65+ raise ValueError (f"Join column '{ col } ' has nested type '{ field_type } '; only primitive columns can be join keys." )
66+
67+ if isinstance (field_type , pa .BaseExtensionType ):
68+ raise NotImplementedError (f"Join column '{ col } ' has type '{ field_type } ', which is not yet supported as a join key." )
69+
70+
71+ def _validate_input_join_col (col : str , df : pyarrow_table ) -> None :
72+ """Reject input representations that are unsupported even when the table type is valid."""
73+ # Schema compatibility permits missing optional fields, but upsert needs every join key.
74+ if col not in df .schema .names :
75+ raise ValueError (f"Join column '{ col } ' is missing from the input." )
76+
77+ arr = df .column (col )
78+
79+ if pa .types .is_dictionary (arr .type ):
80+ raise NotImplementedError (
81+ f"Input column '{ col } ' is dictionary-encoded, which is not yet supported for join keys. Decode it first."
82+ )
83+
84+ if pa .types .is_null (arr .type ):
85+ raise ValueError (f"Input column '{ col } ' has the null type and cannot be used as a join key." )
86+
87+ if pa .types .is_string_view (arr .type ) or pa .types .is_binary_view (arr .type ):
88+ plain = "string" if pa .types .is_string_view (arr .type ) else "binary"
89+ raise NotImplementedError (
90+ f"Input column '{ col } ' has type '{ arr .type } ', which is not yet supported for join keys. Cast it to '{ plain } ' first."
91+ )
92+
93+ if isinstance (arr .type , pa .BaseExtensionType ):
94+ raise NotImplementedError (
95+ f"Input column '{ col } ' has extension type '{ arr .type } ', which is not yet supported for join keys."
96+ )
97+
98+ # Null keys cannot be expressed as Iceberg literals in the match filter.
99+ if arr .null_count > 0 :
100+ raise ValueError (f"Input column '{ col } ' contains null values, which cannot be join keys." )
93101
94102
95103def create_match_filter (df : pyarrow_table , join_cols : list [str ]) -> BooleanExpression :
0 commit comments