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.

Files changed (81) hide show
  1. snowflake/snowpark_connect/column_name_handler.py +150 -25
  2. snowflake/snowpark_connect/config.py +54 -16
  3. snowflake/snowpark_connect/date_time_format_mapping.py +71 -13
  4. snowflake/snowpark_connect/error/error_codes.py +50 -0
  5. snowflake/snowpark_connect/error/error_utils.py +142 -22
  6. snowflake/snowpark_connect/error/exceptions.py +13 -4
  7. snowflake/snowpark_connect/execute_plan/map_execution_command.py +5 -1
  8. snowflake/snowpark_connect/execute_plan/map_execution_root.py +5 -1
  9. snowflake/snowpark_connect/execute_plan/utils.py +5 -1
  10. snowflake/snowpark_connect/expression/function_defaults.py +9 -2
  11. snowflake/snowpark_connect/expression/literal.py +7 -1
  12. snowflake/snowpark_connect/expression/map_cast.py +17 -5
  13. snowflake/snowpark_connect/expression/map_expression.py +48 -4
  14. snowflake/snowpark_connect/expression/map_extension.py +25 -5
  15. snowflake/snowpark_connect/expression/map_sql_expression.py +65 -30
  16. snowflake/snowpark_connect/expression/map_udf.py +10 -2
  17. snowflake/snowpark_connect/expression/map_unresolved_attribute.py +33 -9
  18. snowflake/snowpark_connect/expression/map_unresolved_function.py +627 -205
  19. snowflake/snowpark_connect/expression/map_unresolved_star.py +5 -1
  20. snowflake/snowpark_connect/expression/map_update_fields.py +14 -4
  21. snowflake/snowpark_connect/expression/map_window_function.py +18 -3
  22. snowflake/snowpark_connect/proto/snowflake_expression_ext_pb2_grpc.py +4 -0
  23. snowflake/snowpark_connect/proto/snowflake_relation_ext_pb2_grpc.py +4 -0
  24. snowflake/snowpark_connect/relation/catalogs/abstract_spark_catalog.py +65 -17
  25. snowflake/snowpark_connect/relation/catalogs/snowflake_catalog.py +34 -12
  26. snowflake/snowpark_connect/relation/catalogs/utils.py +12 -4
  27. snowflake/snowpark_connect/relation/io_utils.py +66 -4
  28. snowflake/snowpark_connect/relation/map_catalog.py +5 -1
  29. snowflake/snowpark_connect/relation/map_column_ops.py +88 -56
  30. snowflake/snowpark_connect/relation/map_extension.py +28 -8
  31. snowflake/snowpark_connect/relation/map_join.py +21 -10
  32. snowflake/snowpark_connect/relation/map_local_relation.py +5 -1
  33. snowflake/snowpark_connect/relation/map_relation.py +33 -7
  34. snowflake/snowpark_connect/relation/map_row_ops.py +36 -9
  35. snowflake/snowpark_connect/relation/map_sql.py +91 -24
  36. snowflake/snowpark_connect/relation/map_stats.py +25 -6
  37. snowflake/snowpark_connect/relation/map_udtf.py +14 -4
  38. snowflake/snowpark_connect/relation/read/jdbc_read_dbapi.py +49 -13
  39. snowflake/snowpark_connect/relation/read/map_read.py +24 -3
  40. snowflake/snowpark_connect/relation/read/map_read_csv.py +11 -3
  41. snowflake/snowpark_connect/relation/read/map_read_jdbc.py +17 -5
  42. snowflake/snowpark_connect/relation/read/map_read_json.py +8 -2
  43. snowflake/snowpark_connect/relation/read/map_read_parquet.py +13 -3
  44. snowflake/snowpark_connect/relation/read/map_read_socket.py +11 -3
  45. snowflake/snowpark_connect/relation/read/map_read_table.py +15 -5
  46. snowflake/snowpark_connect/relation/read/map_read_text.py +5 -1
  47. snowflake/snowpark_connect/relation/read/metadata_utils.py +5 -1
  48. snowflake/snowpark_connect/relation/stage_locator.py +5 -1
  49. snowflake/snowpark_connect/relation/utils.py +19 -2
  50. snowflake/snowpark_connect/relation/write/jdbc_write_dbapi.py +19 -3
  51. snowflake/snowpark_connect/relation/write/map_write.py +146 -63
  52. snowflake/snowpark_connect/relation/write/map_write_jdbc.py +8 -2
  53. snowflake/snowpark_connect/resources_initializer.py +5 -1
  54. snowflake/snowpark_connect/server.py +72 -19
  55. snowflake/snowpark_connect/type_mapping.py +54 -17
  56. snowflake/snowpark_connect/utils/context.py +42 -1
  57. snowflake/snowpark_connect/utils/describe_query_cache.py +3 -0
  58. snowflake/snowpark_connect/utils/env_utils.py +5 -1
  59. snowflake/snowpark_connect/utils/identifiers.py +11 -3
  60. snowflake/snowpark_connect/utils/pandas_udtf_utils.py +8 -4
  61. snowflake/snowpark_connect/utils/profiling.py +25 -8
  62. snowflake/snowpark_connect/utils/scala_udf_utils.py +11 -3
  63. snowflake/snowpark_connect/utils/session.py +5 -2
  64. snowflake/snowpark_connect/utils/telemetry.py +81 -18
  65. snowflake/snowpark_connect/utils/temporary_view_cache.py +5 -1
  66. snowflake/snowpark_connect/utils/udf_cache.py +5 -3
  67. snowflake/snowpark_connect/utils/udf_helper.py +20 -6
  68. snowflake/snowpark_connect/utils/udf_utils.py +4 -4
  69. snowflake/snowpark_connect/utils/udtf_helper.py +5 -1
  70. snowflake/snowpark_connect/utils/udtf_utils.py +34 -26
  71. snowflake/snowpark_connect/version.py +1 -1
  72. {snowpark_connect-0.30.0.dist-info → snowpark_connect-0.31.0.dist-info}/METADATA +3 -2
  73. {snowpark_connect-0.30.0.dist-info → snowpark_connect-0.31.0.dist-info}/RECORD +81 -78
  74. {snowpark_connect-0.30.0.data → snowpark_connect-0.31.0.data}/scripts/snowpark-connect +0 -0
  75. {snowpark_connect-0.30.0.data → snowpark_connect-0.31.0.data}/scripts/snowpark-session +0 -0
  76. {snowpark_connect-0.30.0.data → snowpark_connect-0.31.0.data}/scripts/snowpark-submit +0 -0
  77. {snowpark_connect-0.30.0.dist-info → snowpark_connect-0.31.0.dist-info}/WHEEL +0 -0
  78. {snowpark_connect-0.30.0.dist-info → snowpark_connect-0.31.0.dist-info}/licenses/LICENSE-binary +0 -0
  79. {snowpark_connect-0.30.0.dist-info → snowpark_connect-0.31.0.dist-info}/licenses/LICENSE.txt +0 -0
  80. {snowpark_connect-0.30.0.dist-info → snowpark_connect-0.31.0.dist-info}/licenses/NOTICE-binary +0 -0
  81. {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
- # Get compression from options for proper filename generation
180
- compression_option = write_op.options.get("compression", "none")
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=compression_option,
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=compression_option,
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
- raise SnowparkConnectNotImplementedError(
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=compression_option,
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
- raise AnalysisException(
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
- raise AnalysisException(
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
- raise AnalysisException(
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
- raise SnowparkConnectNotImplementedError(
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
- raise SnowparkConnectNotImplementedError(
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
- raise AnalysisException(
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
- raise AnalysisException(
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
- raise SnowparkConnectNotImplementedError(
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
- raise SnowparkConnectNotImplementedError(
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
- raise AnalysisException(
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
- raise AnalysisException(
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
- raise AnalysisException(
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
- raise AnalysisException(
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
- raise AnalysisException(
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
- raise AnalysisException(
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
- raise SnowparkConnectNotImplementedError(
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
- raise AnalysisException(
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
- raise AnalysisException(
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
- raise AnalysisException(
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
- raise AnalysisException(
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
- raise AnalysisException(
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
- raise SnowparkConnectNotImplementedError(
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.write
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.write
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 not global_config.spark_sql_caseSensitive:
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).lower()
833
- == unquote_if_quoted(col_name).lower()
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 global_config.spark_sql_caseSensitive else col.lower()
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
- raise AnalysisException(
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
- raise AnalysisException(
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
- raise AnalysisException(
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
- raise SnowparkConnectNotImplementedError(
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
- raise FileNotFoundError(
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
- raise ValueError("Include dbtable is required option")
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
- raise Exception(f"Error accessing JDBC datasource for write: {e}")
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
- raise RuntimeError(
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: