snowpark-connect 0.30.0__py3-none-any.whl → 0.31.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of snowpark-connect might be problematic. Click here for more details.
- snowflake/snowpark_connect/column_name_handler.py +150 -25
- snowflake/snowpark_connect/config.py +54 -16
- snowflake/snowpark_connect/date_time_format_mapping.py +71 -13
- snowflake/snowpark_connect/error/error_codes.py +50 -0
- snowflake/snowpark_connect/error/error_utils.py +142 -22
- snowflake/snowpark_connect/error/exceptions.py +13 -4
- snowflake/snowpark_connect/execute_plan/map_execution_command.py +5 -1
- snowflake/snowpark_connect/execute_plan/map_execution_root.py +5 -1
- snowflake/snowpark_connect/execute_plan/utils.py +5 -1
- snowflake/snowpark_connect/expression/function_defaults.py +9 -2
- snowflake/snowpark_connect/expression/literal.py +7 -1
- snowflake/snowpark_connect/expression/map_cast.py +17 -5
- snowflake/snowpark_connect/expression/map_expression.py +48 -4
- snowflake/snowpark_connect/expression/map_extension.py +25 -5
- snowflake/snowpark_connect/expression/map_sql_expression.py +65 -30
- snowflake/snowpark_connect/expression/map_udf.py +10 -2
- snowflake/snowpark_connect/expression/map_unresolved_attribute.py +33 -9
- snowflake/snowpark_connect/expression/map_unresolved_function.py +627 -205
- snowflake/snowpark_connect/expression/map_unresolved_star.py +5 -1
- snowflake/snowpark_connect/expression/map_update_fields.py +14 -4
- snowflake/snowpark_connect/expression/map_window_function.py +18 -3
- snowflake/snowpark_connect/proto/snowflake_expression_ext_pb2_grpc.py +4 -0
- snowflake/snowpark_connect/proto/snowflake_relation_ext_pb2_grpc.py +4 -0
- snowflake/snowpark_connect/relation/catalogs/abstract_spark_catalog.py +65 -17
- snowflake/snowpark_connect/relation/catalogs/snowflake_catalog.py +34 -12
- snowflake/snowpark_connect/relation/catalogs/utils.py +12 -4
- snowflake/snowpark_connect/relation/io_utils.py +66 -4
- snowflake/snowpark_connect/relation/map_catalog.py +5 -1
- snowflake/snowpark_connect/relation/map_column_ops.py +88 -56
- snowflake/snowpark_connect/relation/map_extension.py +28 -8
- snowflake/snowpark_connect/relation/map_join.py +21 -10
- snowflake/snowpark_connect/relation/map_local_relation.py +5 -1
- snowflake/snowpark_connect/relation/map_relation.py +33 -7
- snowflake/snowpark_connect/relation/map_row_ops.py +36 -9
- snowflake/snowpark_connect/relation/map_sql.py +91 -24
- snowflake/snowpark_connect/relation/map_stats.py +25 -6
- snowflake/snowpark_connect/relation/map_udtf.py +14 -4
- snowflake/snowpark_connect/relation/read/jdbc_read_dbapi.py +49 -13
- snowflake/snowpark_connect/relation/read/map_read.py +24 -3
- snowflake/snowpark_connect/relation/read/map_read_csv.py +11 -3
- snowflake/snowpark_connect/relation/read/map_read_jdbc.py +17 -5
- snowflake/snowpark_connect/relation/read/map_read_json.py +8 -2
- snowflake/snowpark_connect/relation/read/map_read_parquet.py +13 -3
- snowflake/snowpark_connect/relation/read/map_read_socket.py +11 -3
- snowflake/snowpark_connect/relation/read/map_read_table.py +15 -5
- snowflake/snowpark_connect/relation/read/map_read_text.py +5 -1
- snowflake/snowpark_connect/relation/read/metadata_utils.py +5 -1
- snowflake/snowpark_connect/relation/stage_locator.py +5 -1
- snowflake/snowpark_connect/relation/utils.py +19 -2
- snowflake/snowpark_connect/relation/write/jdbc_write_dbapi.py +19 -3
- snowflake/snowpark_connect/relation/write/map_write.py +146 -63
- snowflake/snowpark_connect/relation/write/map_write_jdbc.py +8 -2
- snowflake/snowpark_connect/resources_initializer.py +5 -1
- snowflake/snowpark_connect/server.py +72 -19
- snowflake/snowpark_connect/type_mapping.py +54 -17
- snowflake/snowpark_connect/utils/context.py +42 -1
- snowflake/snowpark_connect/utils/describe_query_cache.py +3 -0
- snowflake/snowpark_connect/utils/env_utils.py +5 -1
- snowflake/snowpark_connect/utils/identifiers.py +11 -3
- snowflake/snowpark_connect/utils/pandas_udtf_utils.py +8 -4
- snowflake/snowpark_connect/utils/profiling.py +25 -8
- snowflake/snowpark_connect/utils/scala_udf_utils.py +11 -3
- snowflake/snowpark_connect/utils/session.py +5 -2
- snowflake/snowpark_connect/utils/telemetry.py +81 -18
- snowflake/snowpark_connect/utils/temporary_view_cache.py +5 -1
- snowflake/snowpark_connect/utils/udf_cache.py +5 -3
- snowflake/snowpark_connect/utils/udf_helper.py +20 -6
- snowflake/snowpark_connect/utils/udf_utils.py +4 -4
- snowflake/snowpark_connect/utils/udtf_helper.py +5 -1
- snowflake/snowpark_connect/utils/udtf_utils.py +34 -26
- snowflake/snowpark_connect/version.py +1 -1
- {snowpark_connect-0.30.0.dist-info → snowpark_connect-0.31.0.dist-info}/METADATA +3 -2
- {snowpark_connect-0.30.0.dist-info → snowpark_connect-0.31.0.dist-info}/RECORD +81 -78
- {snowpark_connect-0.30.0.data → snowpark_connect-0.31.0.data}/scripts/snowpark-connect +0 -0
- {snowpark_connect-0.30.0.data → snowpark_connect-0.31.0.data}/scripts/snowpark-session +0 -0
- {snowpark_connect-0.30.0.data → snowpark_connect-0.31.0.data}/scripts/snowpark-submit +0 -0
- {snowpark_connect-0.30.0.dist-info → snowpark_connect-0.31.0.dist-info}/WHEEL +0 -0
- {snowpark_connect-0.30.0.dist-info → snowpark_connect-0.31.0.dist-info}/licenses/LICENSE-binary +0 -0
- {snowpark_connect-0.30.0.dist-info → snowpark_connect-0.31.0.dist-info}/licenses/LICENSE.txt +0 -0
- {snowpark_connect-0.30.0.dist-info → snowpark_connect-0.31.0.dist-info}/licenses/NOTICE-binary +0 -0
- {snowpark_connect-0.30.0.dist-info → snowpark_connect-0.31.0.dist-info}/top_level.txt +0 -0
|
@@ -28,16 +28,18 @@ from snowflake.snowpark.types import (
|
|
|
28
28
|
_NumericType,
|
|
29
29
|
)
|
|
30
30
|
from snowflake.snowpark_connect.config import (
|
|
31
|
+
auto_uppercase_column_identifiers,
|
|
31
32
|
global_config,
|
|
32
33
|
sessions_config,
|
|
33
34
|
str_to_bool,
|
|
34
35
|
)
|
|
35
36
|
from snowflake.snowpark_connect.dataframe_container import DataFrameContainer
|
|
37
|
+
from snowflake.snowpark_connect.error.error_codes import ErrorCodes
|
|
38
|
+
from snowflake.snowpark_connect.error.error_utils import attach_custom_error_code
|
|
36
39
|
from snowflake.snowpark_connect.relation.io_utils import (
|
|
37
40
|
convert_file_prefix_path,
|
|
41
|
+
get_compression_for_source_and_options,
|
|
38
42
|
is_cloud_path,
|
|
39
|
-
is_supported_compression,
|
|
40
|
-
supported_compressions_for_format,
|
|
41
43
|
)
|
|
42
44
|
from snowflake.snowpark_connect.relation.map_relation import map_relation
|
|
43
45
|
from snowflake.snowpark_connect.relation.read.metadata_utils import (
|
|
@@ -147,6 +149,11 @@ def map_write(request: proto_base.ExecutePlanRequest):
|
|
|
147
149
|
|
|
148
150
|
session: snowpark.Session = get_or_create_snowpark_session()
|
|
149
151
|
|
|
152
|
+
# Check for partition hint early to determine precedence over single option
|
|
153
|
+
partition_hint = (
|
|
154
|
+
result.partition_hint if hasattr(result, "partition_hint") else None
|
|
155
|
+
)
|
|
156
|
+
|
|
150
157
|
# Snowflake saveAsTable doesn't support format
|
|
151
158
|
if (
|
|
152
159
|
write_op.HasField("table")
|
|
@@ -176,8 +183,11 @@ def map_write(request: proto_base.ExecutePlanRequest):
|
|
|
176
183
|
# Generate Spark-compatible filename with proper extension
|
|
177
184
|
extension = write_op.source if write_op.source != "text" else "txt"
|
|
178
185
|
|
|
179
|
-
|
|
180
|
-
|
|
186
|
+
compression = get_compression_for_source_and_options(
|
|
187
|
+
write_op.source, write_op.options, from_read=False
|
|
188
|
+
)
|
|
189
|
+
if compression is not None:
|
|
190
|
+
write_op.options["compression"] = compression
|
|
181
191
|
|
|
182
192
|
# Generate Spark-compatible filename or prefix
|
|
183
193
|
# we need a random prefix to support "append" mode
|
|
@@ -203,12 +213,12 @@ def map_write(request: proto_base.ExecutePlanRequest):
|
|
|
203
213
|
except Exception as e:
|
|
204
214
|
logger.warning(f"Could not clear directory {write_path}: {e}")
|
|
205
215
|
|
|
206
|
-
if should_write_to_single_file:
|
|
216
|
+
if should_write_to_single_file and partition_hint is None:
|
|
207
217
|
# Single file: generate complete filename with extension
|
|
208
218
|
spark_filename = generate_spark_compatible_filename(
|
|
209
219
|
task_id=0,
|
|
210
220
|
attempt_number=0,
|
|
211
|
-
compression=
|
|
221
|
+
compression=compression,
|
|
212
222
|
format_ext=extension,
|
|
213
223
|
)
|
|
214
224
|
temp_file_prefix_on_stage = f"{write_path}/{spark_filename}"
|
|
@@ -217,29 +227,11 @@ def map_write(request: proto_base.ExecutePlanRequest):
|
|
|
217
227
|
spark_filename_prefix = generate_spark_compatible_filename(
|
|
218
228
|
task_id=0,
|
|
219
229
|
attempt_number=0,
|
|
220
|
-
compression=
|
|
230
|
+
compression=None,
|
|
221
231
|
format_ext="", # No extension for prefix
|
|
222
232
|
)
|
|
223
233
|
temp_file_prefix_on_stage = f"{write_path}/{spark_filename_prefix}"
|
|
224
234
|
|
|
225
|
-
default_compression = "NONE" if write_op.source != "parquet" else "snappy"
|
|
226
|
-
compression = write_op.options.get(
|
|
227
|
-
"compression", default_compression
|
|
228
|
-
).upper()
|
|
229
|
-
|
|
230
|
-
if not is_supported_compression(write_op.source, compression):
|
|
231
|
-
supported_compressions = supported_compressions_for_format(
|
|
232
|
-
write_op.source
|
|
233
|
-
)
|
|
234
|
-
raise AnalysisException(
|
|
235
|
-
f"Compression {compression} is not supported for {write_op.source} format. "
|
|
236
|
-
+ (
|
|
237
|
-
f"Supported compressions: {sorted(supported_compressions)}"
|
|
238
|
-
if supported_compressions
|
|
239
|
-
else "No compression supported for this format."
|
|
240
|
-
)
|
|
241
|
-
)
|
|
242
|
-
|
|
243
235
|
parameters = {
|
|
244
236
|
"location": temp_file_prefix_on_stage,
|
|
245
237
|
"file_format_type": write_op.source
|
|
@@ -254,9 +246,6 @@ def map_write(request: proto_base.ExecutePlanRequest):
|
|
|
254
246
|
# Using the base avoids coupling to exact filenames/prefixes.
|
|
255
247
|
download_stage_path = write_path
|
|
256
248
|
|
|
257
|
-
# Check for partition hint early to determine precedence over single option
|
|
258
|
-
partition_hint = result.partition_hint
|
|
259
|
-
|
|
260
249
|
# Apply max_file_size for both single and multi-file scenarios
|
|
261
250
|
# This helps control when Snowflake splits files into multiple parts
|
|
262
251
|
if max_file_size:
|
|
@@ -268,9 +257,13 @@ def map_write(request: proto_base.ExecutePlanRequest):
|
|
|
268
257
|
get_param_from_options(parameters, write_op.options, write_op.source)
|
|
269
258
|
if write_op.partitioning_columns:
|
|
270
259
|
if write_op.source != "parquet":
|
|
271
|
-
|
|
260
|
+
exception = SnowparkConnectNotImplementedError(
|
|
272
261
|
"Partitioning is only supported for parquet format"
|
|
273
262
|
)
|
|
263
|
+
attach_custom_error_code(
|
|
264
|
+
exception, ErrorCodes.UNSUPPORTED_OPERATION
|
|
265
|
+
)
|
|
266
|
+
raise exception
|
|
274
267
|
# Build Spark-style directory structure: col1=value1/col2=value2/...
|
|
275
268
|
# Example produced expression (Snowflake SQL):
|
|
276
269
|
# 'department=' || TO_VARCHAR("department") || '/' || 'region=' || TO_VARCHAR("region")
|
|
@@ -314,7 +307,7 @@ def map_write(request: proto_base.ExecutePlanRequest):
|
|
|
314
307
|
per_part_prefix = generate_spark_compatible_filename(
|
|
315
308
|
task_id=part_idx,
|
|
316
309
|
attempt_number=0,
|
|
317
|
-
compression=
|
|
310
|
+
compression=None,
|
|
318
311
|
format_ext="", # prefix only; Snowflake appends extension/counters
|
|
319
312
|
)
|
|
320
313
|
part_params["location"] = f"{write_path}/{per_part_prefix}"
|
|
@@ -355,9 +348,13 @@ def map_write(request: proto_base.ExecutePlanRequest):
|
|
|
355
348
|
snowpark_table_name, session
|
|
356
349
|
)
|
|
357
350
|
if isinstance(table_schema_or_error, DataType): # Table exists
|
|
358
|
-
|
|
351
|
+
exception = AnalysisException(
|
|
359
352
|
f"Table {snowpark_table_name} already exists"
|
|
360
353
|
)
|
|
354
|
+
attach_custom_error_code(
|
|
355
|
+
exception, ErrorCodes.INVALID_OPERATION
|
|
356
|
+
)
|
|
357
|
+
raise exception
|
|
361
358
|
create_iceberg_table(
|
|
362
359
|
snowpark_table_name=snowpark_table_name,
|
|
363
360
|
location=write_op.options.get("location", None),
|
|
@@ -380,9 +377,13 @@ def map_write(request: proto_base.ExecutePlanRequest):
|
|
|
380
377
|
"ICEBERG",
|
|
381
378
|
"TABLE",
|
|
382
379
|
):
|
|
383
|
-
|
|
380
|
+
exception = AnalysisException(
|
|
384
381
|
f"Table {snowpark_table_name} is not an iceberg table"
|
|
385
382
|
)
|
|
383
|
+
attach_custom_error_code(
|
|
384
|
+
exception, ErrorCodes.INVALID_OPERATION
|
|
385
|
+
)
|
|
386
|
+
raise exception
|
|
386
387
|
else:
|
|
387
388
|
create_iceberg_table(
|
|
388
389
|
snowpark_table_name=snowpark_table_name,
|
|
@@ -426,9 +427,13 @@ def map_write(request: proto_base.ExecutePlanRequest):
|
|
|
426
427
|
"ICEBERG",
|
|
427
428
|
"TABLE",
|
|
428
429
|
):
|
|
429
|
-
|
|
430
|
+
exception = AnalysisException(
|
|
430
431
|
f"Table {snowpark_table_name} is not an iceberg table"
|
|
431
432
|
)
|
|
433
|
+
attach_custom_error_code(
|
|
434
|
+
exception, ErrorCodes.INVALID_OPERATION
|
|
435
|
+
)
|
|
436
|
+
raise exception
|
|
432
437
|
else:
|
|
433
438
|
create_iceberg_table(
|
|
434
439
|
snowpark_table_name=snowpark_table_name,
|
|
@@ -444,9 +449,13 @@ def map_write(request: proto_base.ExecutePlanRequest):
|
|
|
444
449
|
column_order=_column_order_for_write,
|
|
445
450
|
)
|
|
446
451
|
case _:
|
|
447
|
-
|
|
452
|
+
exception = SnowparkConnectNotImplementedError(
|
|
448
453
|
f"Write mode {write_mode} is not supported"
|
|
449
454
|
)
|
|
455
|
+
attach_custom_error_code(
|
|
456
|
+
exception, ErrorCodes.UNSUPPORTED_OPERATION
|
|
457
|
+
)
|
|
458
|
+
raise exception
|
|
450
459
|
case _:
|
|
451
460
|
snowpark_table_name = _spark_to_snowflake(write_op.table.table_name)
|
|
452
461
|
save_method = write_op.table.save_method
|
|
@@ -462,9 +471,13 @@ def map_write(request: proto_base.ExecutePlanRequest):
|
|
|
462
471
|
if len(write_op.table.table_name) == 0:
|
|
463
472
|
dbtable_name = write_op.options.get("dbtable", "")
|
|
464
473
|
if len(dbtable_name) == 0:
|
|
465
|
-
|
|
474
|
+
exception = SnowparkConnectNotImplementedError(
|
|
466
475
|
"Save command is not supported without a table name"
|
|
467
476
|
)
|
|
477
|
+
attach_custom_error_code(
|
|
478
|
+
exception, ErrorCodes.UNSUPPORTED_OPERATION
|
|
479
|
+
)
|
|
480
|
+
raise exception
|
|
468
481
|
else:
|
|
469
482
|
snowpark_table_name = _spark_to_snowflake(dbtable_name)
|
|
470
483
|
|
|
@@ -482,9 +495,13 @@ def map_write(request: proto_base.ExecutePlanRequest):
|
|
|
482
495
|
"NORMAL",
|
|
483
496
|
"TABLE",
|
|
484
497
|
):
|
|
485
|
-
|
|
498
|
+
exception = AnalysisException(
|
|
486
499
|
f"Table {snowpark_table_name} is not a FDN table"
|
|
487
500
|
)
|
|
501
|
+
attach_custom_error_code(
|
|
502
|
+
exception, ErrorCodes.INVALID_OPERATION
|
|
503
|
+
)
|
|
504
|
+
raise exception
|
|
488
505
|
write_mode = "truncate"
|
|
489
506
|
_validate_schema_and_get_writer(
|
|
490
507
|
input_df,
|
|
@@ -508,9 +525,13 @@ def map_write(request: proto_base.ExecutePlanRequest):
|
|
|
508
525
|
"NORMAL",
|
|
509
526
|
"TABLE",
|
|
510
527
|
):
|
|
511
|
-
|
|
528
|
+
exception = AnalysisException(
|
|
512
529
|
f"Table {snowpark_table_name} is not a FDN table"
|
|
513
530
|
)
|
|
531
|
+
attach_custom_error_code(
|
|
532
|
+
exception, ErrorCodes.INVALID_OPERATION
|
|
533
|
+
)
|
|
534
|
+
raise exception
|
|
514
535
|
|
|
515
536
|
_validate_schema_and_get_writer(
|
|
516
537
|
input_df,
|
|
@@ -542,9 +563,11 @@ def map_write(request: proto_base.ExecutePlanRequest):
|
|
|
542
563
|
column_order=_column_order_for_write,
|
|
543
564
|
)
|
|
544
565
|
else:
|
|
545
|
-
|
|
566
|
+
exception = SnowparkConnectNotImplementedError(
|
|
546
567
|
f"Save command not supported: {save_method}"
|
|
547
568
|
)
|
|
569
|
+
attach_custom_error_code(exception, ErrorCodes.UNSUPPORTED_OPERATION)
|
|
570
|
+
raise exception
|
|
548
571
|
|
|
549
572
|
|
|
550
573
|
def map_write_v2(request: proto_base.ExecutePlanRequest):
|
|
@@ -569,9 +592,11 @@ def map_write_v2(request: proto_base.ExecutePlanRequest):
|
|
|
569
592
|
session: snowpark.Session = get_or_create_snowpark_session()
|
|
570
593
|
|
|
571
594
|
if write_op.table_name is None or write_op.table_name == "":
|
|
572
|
-
|
|
595
|
+
exception = SnowparkConnectNotImplementedError(
|
|
573
596
|
"Write operation V2 only support table writing now"
|
|
574
597
|
)
|
|
598
|
+
attach_custom_error_code(exception, ErrorCodes.UNSUPPORTED_OPERATION)
|
|
599
|
+
raise exception
|
|
575
600
|
|
|
576
601
|
if write_op.provider.lower() == "iceberg":
|
|
577
602
|
match write_op.mode:
|
|
@@ -580,9 +605,11 @@ def map_write_v2(request: proto_base.ExecutePlanRequest):
|
|
|
580
605
|
snowpark_table_name, session
|
|
581
606
|
)
|
|
582
607
|
if isinstance(table_schema_or_error, DataType): # Table exists
|
|
583
|
-
|
|
608
|
+
exception = AnalysisException(
|
|
584
609
|
f"Table {snowpark_table_name} already exists"
|
|
585
610
|
)
|
|
611
|
+
attach_custom_error_code(exception, ErrorCodes.INVALID_OPERATION)
|
|
612
|
+
raise exception
|
|
586
613
|
create_iceberg_table(
|
|
587
614
|
snowpark_table_name=snowpark_table_name,
|
|
588
615
|
location=write_op.table_properties.get("location"),
|
|
@@ -601,16 +628,20 @@ def map_write_v2(request: proto_base.ExecutePlanRequest):
|
|
|
601
628
|
snowpark_table_name, session
|
|
602
629
|
)
|
|
603
630
|
if not isinstance(table_schema_or_error, DataType): # Table not exists
|
|
604
|
-
|
|
631
|
+
exception = AnalysisException(
|
|
605
632
|
f"[TABLE_OR_VIEW_NOT_FOUND] The table or view `{write_op.table_name}` cannot be found."
|
|
606
633
|
)
|
|
634
|
+
attach_custom_error_code(exception, ErrorCodes.INTERNAL_ERROR)
|
|
635
|
+
raise exception
|
|
607
636
|
if get_table_type(snowpark_table_name, session) not in (
|
|
608
637
|
"ICEBERG",
|
|
609
638
|
"TABLE",
|
|
610
639
|
):
|
|
611
|
-
|
|
640
|
+
exception = AnalysisException(
|
|
612
641
|
f"Table {snowpark_table_name} is not an iceberg table"
|
|
613
642
|
)
|
|
643
|
+
attach_custom_error_code(exception, ErrorCodes.INVALID_OPERATION)
|
|
644
|
+
raise exception
|
|
614
645
|
_validate_schema_and_get_writer(
|
|
615
646
|
input_df, "append", snowpark_table_name, table_schema_or_error
|
|
616
647
|
).saveAsTable(
|
|
@@ -628,13 +659,19 @@ def map_write_v2(request: proto_base.ExecutePlanRequest):
|
|
|
628
659
|
"ICEBERG",
|
|
629
660
|
"TABLE",
|
|
630
661
|
):
|
|
631
|
-
|
|
662
|
+
exception = AnalysisException(
|
|
632
663
|
f"Table {snowpark_table_name} is not an iceberg table"
|
|
633
664
|
)
|
|
665
|
+
attach_custom_error_code(
|
|
666
|
+
exception, ErrorCodes.INVALID_OPERATION
|
|
667
|
+
)
|
|
668
|
+
raise exception
|
|
634
669
|
else:
|
|
635
|
-
|
|
670
|
+
exception = AnalysisException(
|
|
636
671
|
f"[TABLE_OR_VIEW_NOT_FOUND] Table {snowpark_table_name} does not exist"
|
|
637
672
|
)
|
|
673
|
+
attach_custom_error_code(exception, ErrorCodes.INVALID_OPERATION)
|
|
674
|
+
raise exception
|
|
638
675
|
_validate_schema_and_get_writer(
|
|
639
676
|
input_df, "truncate", snowpark_table_name, table_schema_or_error
|
|
640
677
|
).saveAsTable(
|
|
@@ -655,9 +692,11 @@ def map_write_v2(request: proto_base.ExecutePlanRequest):
|
|
|
655
692
|
mode="replace",
|
|
656
693
|
)
|
|
657
694
|
else:
|
|
658
|
-
|
|
695
|
+
exception = AnalysisException(
|
|
659
696
|
f"Table {snowpark_table_name} does not exist"
|
|
660
697
|
)
|
|
698
|
+
attach_custom_error_code(exception, ErrorCodes.INVALID_OPERATION)
|
|
699
|
+
raise exception
|
|
661
700
|
_validate_schema_and_get_writer(
|
|
662
701
|
input_df, "replace", snowpark_table_name, table_schema_or_error
|
|
663
702
|
).saveAsTable(
|
|
@@ -681,9 +720,11 @@ def map_write_v2(request: proto_base.ExecutePlanRequest):
|
|
|
681
720
|
column_order=_column_order_for_write,
|
|
682
721
|
)
|
|
683
722
|
case _:
|
|
684
|
-
|
|
723
|
+
exception = SnowparkConnectNotImplementedError(
|
|
685
724
|
f"Write mode {commands_proto.WriteOperationV2.Mode.Name(write_op.mode)} is not supported"
|
|
686
725
|
)
|
|
726
|
+
attach_custom_error_code(exception, ErrorCodes.UNSUPPORTED_OPERATION)
|
|
727
|
+
raise exception
|
|
687
728
|
else:
|
|
688
729
|
match write_op.mode:
|
|
689
730
|
case commands_proto.WriteOperationV2.MODE_CREATE:
|
|
@@ -699,16 +740,20 @@ def map_write_v2(request: proto_base.ExecutePlanRequest):
|
|
|
699
740
|
snowpark_table_name, session
|
|
700
741
|
)
|
|
701
742
|
if not isinstance(table_schema_or_error, DataType): # Table not exists
|
|
702
|
-
|
|
743
|
+
exception = AnalysisException(
|
|
703
744
|
f"[TABLE_OR_VIEW_NOT_FOUND] The table or view `{write_op.table_name}` cannot be found."
|
|
704
745
|
)
|
|
746
|
+
attach_custom_error_code(exception, ErrorCodes.INVALID_OPERATION)
|
|
747
|
+
raise exception
|
|
705
748
|
if get_table_type(snowpark_table_name, session) not in (
|
|
706
749
|
"NORMAL",
|
|
707
750
|
"TABLE",
|
|
708
751
|
):
|
|
709
|
-
|
|
752
|
+
exception = AnalysisException(
|
|
710
753
|
f"Table {snowpark_table_name} is not a FDN table"
|
|
711
754
|
)
|
|
755
|
+
attach_custom_error_code(exception, ErrorCodes.INVALID_OPERATION)
|
|
756
|
+
raise exception
|
|
712
757
|
_validate_schema_and_get_writer(
|
|
713
758
|
input_df, "append", snowpark_table_name, table_schema_or_error
|
|
714
759
|
).saveAsTable(
|
|
@@ -726,13 +771,19 @@ def map_write_v2(request: proto_base.ExecutePlanRequest):
|
|
|
726
771
|
"NORMAL",
|
|
727
772
|
"TABLE",
|
|
728
773
|
):
|
|
729
|
-
|
|
774
|
+
exception = AnalysisException(
|
|
730
775
|
f"Table {snowpark_table_name} is not a FDN table"
|
|
731
776
|
)
|
|
777
|
+
attach_custom_error_code(
|
|
778
|
+
exception, ErrorCodes.INVALID_OPERATION
|
|
779
|
+
)
|
|
780
|
+
raise exception
|
|
732
781
|
else:
|
|
733
|
-
|
|
782
|
+
exception = AnalysisException(
|
|
734
783
|
f"[TABLE_OR_VIEW_NOT_FOUND] Table {snowpark_table_name} does not exist"
|
|
735
784
|
)
|
|
785
|
+
attach_custom_error_code(exception, ErrorCodes.TABLE_NOT_FOUND)
|
|
786
|
+
raise exception
|
|
736
787
|
_validate_schema_and_get_writer(
|
|
737
788
|
input_df, "truncate", snowpark_table_name, table_schema_or_error
|
|
738
789
|
).saveAsTable(
|
|
@@ -745,9 +796,11 @@ def map_write_v2(request: proto_base.ExecutePlanRequest):
|
|
|
745
796
|
snowpark_table_name, session
|
|
746
797
|
)
|
|
747
798
|
if not isinstance(table_schema_or_error, DataType): # Table not exists
|
|
748
|
-
|
|
799
|
+
exception = AnalysisException(
|
|
749
800
|
f"Table {snowpark_table_name} does not exist"
|
|
750
801
|
)
|
|
802
|
+
attach_custom_error_code(exception, ErrorCodes.TABLE_NOT_FOUND)
|
|
803
|
+
raise exception
|
|
751
804
|
_validate_schema_and_get_writer(
|
|
752
805
|
input_df, "replace", snowpark_table_name, table_schema_or_error
|
|
753
806
|
).saveAsTable(
|
|
@@ -764,9 +817,11 @@ def map_write_v2(request: proto_base.ExecutePlanRequest):
|
|
|
764
817
|
column_order=_column_order_for_write,
|
|
765
818
|
)
|
|
766
819
|
case _:
|
|
767
|
-
|
|
820
|
+
exception = SnowparkConnectNotImplementedError(
|
|
768
821
|
f"Write mode {commands_proto.WriteOperationV2.Mode.Name(write_op.mode)} is not supported"
|
|
769
822
|
)
|
|
823
|
+
attach_custom_error_code(exception, ErrorCodes.UNSUPPORTED_OPERATION)
|
|
824
|
+
raise exception
|
|
770
825
|
|
|
771
826
|
|
|
772
827
|
def _get_table_schema_or_error(
|
|
@@ -778,6 +833,20 @@ def _get_table_schema_or_error(
|
|
|
778
833
|
return e
|
|
779
834
|
|
|
780
835
|
|
|
836
|
+
def _get_writer_for_table_creation(df: snowpark.DataFrame) -> snowpark.DataFrameWriter:
|
|
837
|
+
# When creating a new table, if case sensitivity is not enabled, we need to rename the columns
|
|
838
|
+
# to upper case so they are case-insensitive in Snowflake.
|
|
839
|
+
if auto_uppercase_column_identifiers():
|
|
840
|
+
for field in df.schema.fields:
|
|
841
|
+
col_name = field.name
|
|
842
|
+
# Uppercasing is fine, regardless of whether the original name was quoted or not.
|
|
843
|
+
# In Snowflake these are equivalent "COL" == COL == col == coL
|
|
844
|
+
uppercased_name = col_name.upper()
|
|
845
|
+
if col_name != uppercased_name:
|
|
846
|
+
df = df.withColumnRenamed(col_name, uppercased_name)
|
|
847
|
+
return df.write
|
|
848
|
+
|
|
849
|
+
|
|
781
850
|
def _validate_schema_and_get_writer(
|
|
782
851
|
input_df: snowpark.DataFrame,
|
|
783
852
|
write_mode: str,
|
|
@@ -788,7 +857,7 @@ def _validate_schema_and_get_writer(
|
|
|
788
857
|
"replace",
|
|
789
858
|
"create_or_replace",
|
|
790
859
|
):
|
|
791
|
-
return input_df
|
|
860
|
+
return _get_writer_for_table_creation(input_df)
|
|
792
861
|
|
|
793
862
|
table_schema = None
|
|
794
863
|
if table_schema_or_error is not None:
|
|
@@ -797,6 +866,9 @@ def _validate_schema_and_get_writer(
|
|
|
797
866
|
if "SQL compilation error" in msg and "does not exist" in msg:
|
|
798
867
|
pass
|
|
799
868
|
else:
|
|
869
|
+
attach_custom_error_code(
|
|
870
|
+
table_schema_or_error, ErrorCodes.INTERNAL_ERROR
|
|
871
|
+
)
|
|
800
872
|
raise table_schema_or_error
|
|
801
873
|
elif isinstance(table_schema_or_error, DataType):
|
|
802
874
|
table_schema = table_schema_or_error
|
|
@@ -810,16 +882,17 @@ def _validate_schema_and_get_writer(
|
|
|
810
882
|
if "SQL compilation error" in msg and "does not exist" in msg:
|
|
811
883
|
pass
|
|
812
884
|
else:
|
|
885
|
+
attach_custom_error_code(e, ErrorCodes.INTERNAL_ERROR)
|
|
813
886
|
raise e
|
|
814
887
|
|
|
815
888
|
if table_schema is None:
|
|
816
889
|
# If table does not exist, we can skip the schema validation
|
|
817
|
-
return input_df
|
|
890
|
+
return _get_writer_for_table_creation(input_df)
|
|
818
891
|
|
|
819
892
|
_validate_schema_for_append(table_schema, input_df.schema, snowpark_table_name)
|
|
820
893
|
|
|
821
894
|
# if table exists and case sensitivity is not enabled, we need to rename the columns to match existing table schema
|
|
822
|
-
if
|
|
895
|
+
if auto_uppercase_column_identifiers():
|
|
823
896
|
|
|
824
897
|
for field in input_df.schema.fields:
|
|
825
898
|
# Find the matching field in the table schema (case-insensitive)
|
|
@@ -829,8 +902,8 @@ def _validate_schema_and_get_writer(
|
|
|
829
902
|
(
|
|
830
903
|
f
|
|
831
904
|
for f in table_schema.fields
|
|
832
|
-
if unquote_if_quoted(f.name).
|
|
833
|
-
== unquote_if_quoted(col_name).
|
|
905
|
+
if unquote_if_quoted(f.name).upper()
|
|
906
|
+
== unquote_if_quoted(col_name).upper()
|
|
834
907
|
),
|
|
835
908
|
None,
|
|
836
909
|
)
|
|
@@ -865,21 +938,25 @@ def _validate_schema_for_append(
|
|
|
865
938
|
case (StructType() as table_struct, StructType() as data_struct):
|
|
866
939
|
|
|
867
940
|
def _comparable_col_name(col: str) -> str:
|
|
868
|
-
name = col if
|
|
941
|
+
name = col.upper() if auto_uppercase_column_identifiers() else col
|
|
869
942
|
if compare_structs:
|
|
870
943
|
return name
|
|
871
944
|
else:
|
|
872
945
|
return unquote_if_quoted(name)
|
|
873
946
|
|
|
874
947
|
def invalid_struct_schema():
|
|
875
|
-
|
|
948
|
+
exception = AnalysisException(
|
|
876
949
|
f"Cannot resolve columns for the existing table {snowpark_table_name} ({table_schema.simple_string()}) with the data schema ({data_schema.simple_string()})."
|
|
877
950
|
)
|
|
951
|
+
attach_custom_error_code(exception, ErrorCodes.INVALID_OPERATION)
|
|
952
|
+
raise exception
|
|
878
953
|
|
|
879
954
|
if len(table_struct.fields) != len(data_struct.fields):
|
|
880
|
-
|
|
955
|
+
exception = AnalysisException(
|
|
881
956
|
f"The column number of the existing table {snowpark_table_name} ({table_schema.simple_string()}) doesn't match the data schema ({data_schema.simple_string()}).)"
|
|
882
957
|
)
|
|
958
|
+
attach_custom_error_code(exception, ErrorCodes.INVALID_OPERATION)
|
|
959
|
+
raise exception
|
|
883
960
|
|
|
884
961
|
table_field_names = {
|
|
885
962
|
_comparable_col_name(field.name) for field in table_struct.fields
|
|
@@ -942,9 +1019,11 @@ def _validate_schema_for_append(
|
|
|
942
1019
|
case (DateType(), _) if isinstance(data_schema, (DateType, TimestampType)):
|
|
943
1020
|
return
|
|
944
1021
|
case (_, _):
|
|
945
|
-
|
|
1022
|
+
exception = AnalysisException(
|
|
946
1023
|
f"[INCOMPATIBLE_DATA_FOR_TABLE.CANNOT_SAFELY_CAST] Cannot write incompatible data for the table {snowpark_table_name}: Cannot safely cast {data_schema.simple_string()} to {table_schema.simple_string()}"
|
|
947
1024
|
)
|
|
1025
|
+
attach_custom_error_code(exception, ErrorCodes.INVALID_OPERATION)
|
|
1026
|
+
raise exception
|
|
948
1027
|
|
|
949
1028
|
|
|
950
1029
|
def create_iceberg_table(
|
|
@@ -984,9 +1063,11 @@ def create_iceberg_table(
|
|
|
984
1063
|
case "create_or_replace":
|
|
985
1064
|
create_sql = "CREATE OR REPLACE"
|
|
986
1065
|
case _:
|
|
987
|
-
|
|
1066
|
+
exception = SnowparkConnectNotImplementedError(
|
|
988
1067
|
f"Write mode {mode} is not supported for iceberg table"
|
|
989
1068
|
)
|
|
1069
|
+
attach_custom_error_code(exception, ErrorCodes.UNSUPPORTED_OPERATION)
|
|
1070
|
+
raise exception
|
|
990
1071
|
sql = f"""
|
|
991
1072
|
{create_sql} ICEBERG TABLE {snowpark_table_name} ({",".join(table_schema)})
|
|
992
1073
|
CATALOG = 'SNOWFLAKE'
|
|
@@ -1093,9 +1174,11 @@ def store_files_locally(
|
|
|
1093
1174
|
|
|
1094
1175
|
def _truncate_directory(directory_path: Path) -> None:
|
|
1095
1176
|
if not directory_path.exists():
|
|
1096
|
-
|
|
1177
|
+
exception = FileNotFoundError(
|
|
1097
1178
|
f"The specified directory {directory_path} does not exist."
|
|
1098
1179
|
)
|
|
1180
|
+
attach_custom_error_code(exception, ErrorCodes.INVALID_INPUT)
|
|
1181
|
+
raise exception
|
|
1099
1182
|
# Iterate over all the files and directories in the specified directory
|
|
1100
1183
|
for file in directory_path.iterdir():
|
|
1101
1184
|
# Check if it is a file or directory and remove it
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
from snowflake import snowpark
|
|
6
6
|
from snowflake.snowpark_connect.dataframe_container import DataFrameContainer
|
|
7
|
+
from snowflake.snowpark_connect.error.error_codes import ErrorCodes
|
|
8
|
+
from snowflake.snowpark_connect.error.error_utils import attach_custom_error_code
|
|
7
9
|
from snowflake.snowpark_connect.relation.read.map_read_jdbc import (
|
|
8
10
|
close_connection,
|
|
9
11
|
create_connection,
|
|
@@ -35,7 +37,9 @@ def map_write_jdbc(
|
|
|
35
37
|
dbtable = None
|
|
36
38
|
|
|
37
39
|
if dbtable is None:
|
|
38
|
-
|
|
40
|
+
exception = ValueError("Include dbtable is required option")
|
|
41
|
+
attach_custom_error_code(exception, ErrorCodes.INVALID_INPUT)
|
|
42
|
+
raise exception
|
|
39
43
|
|
|
40
44
|
try:
|
|
41
45
|
JdbcDataFrameWriter(session, jdbc_options).jdbc_write_dbapi(
|
|
@@ -46,4 +50,6 @@ def map_write_jdbc(
|
|
|
46
50
|
write_mode=write_mode,
|
|
47
51
|
)
|
|
48
52
|
except Exception as e:
|
|
49
|
-
|
|
53
|
+
exception = Exception(f"Error accessing JDBC datasource for write: {e}")
|
|
54
|
+
attach_custom_error_code(exception, ErrorCodes.INTERNAL_ERROR)
|
|
55
|
+
raise exception
|
|
@@ -5,6 +5,8 @@ import pathlib
|
|
|
5
5
|
import threading
|
|
6
6
|
import time
|
|
7
7
|
|
|
8
|
+
from snowflake.snowpark_connect.error.error_codes import ErrorCodes
|
|
9
|
+
from snowflake.snowpark_connect.error.error_utils import attach_custom_error_code
|
|
8
10
|
from snowflake.snowpark_connect.utils.session import get_or_create_snowpark_session
|
|
9
11
|
from snowflake.snowpark_connect.utils.snowpark_connect_logging import logger
|
|
10
12
|
|
|
@@ -119,9 +121,11 @@ def wait_for_resource_initialization() -> None:
|
|
|
119
121
|
logger.error(
|
|
120
122
|
"Resource initialization failed - initializer thread has been running for over 300 seconds."
|
|
121
123
|
)
|
|
122
|
-
|
|
124
|
+
exception = RuntimeError(
|
|
123
125
|
"Resource initialization failed - initializer thread has been running for over 300 seconds."
|
|
124
126
|
)
|
|
127
|
+
attach_custom_error_code(exception, ErrorCodes.RESOURCE_INITIALIZATION_FAILED)
|
|
128
|
+
raise exception
|
|
125
129
|
|
|
126
130
|
|
|
127
131
|
def set_upload_jars(upload: bool) -> None:
|