snowpark-connect 0.28.0__py3-none-any.whl → 0.28.1__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 (22) hide show
  1. snowflake/snowpark_connect/config.py +1 -1
  2. snowflake/snowpark_connect/execute_plan/map_execution_command.py +31 -68
  3. snowflake/snowpark_connect/relation/catalogs/snowflake_catalog.py +207 -20
  4. snowflake/snowpark_connect/relation/map_sql.py +112 -53
  5. snowflake/snowpark_connect/relation/read/map_read_table.py +58 -0
  6. snowflake/snowpark_connect/relation/write/map_write.py +57 -10
  7. snowflake/snowpark_connect/utils/context.py +21 -0
  8. snowflake/snowpark_connect/utils/identifiers.py +8 -2
  9. snowflake/snowpark_connect/utils/temporary_view_cache.py +61 -0
  10. snowflake/snowpark_connect/utils/udf_utils.py +9 -8
  11. snowflake/snowpark_connect/utils/udtf_utils.py +3 -2
  12. snowflake/snowpark_connect/version.py +1 -1
  13. {snowpark_connect-0.28.0.dist-info → snowpark_connect-0.28.1.dist-info}/METADATA +1 -1
  14. {snowpark_connect-0.28.0.dist-info → snowpark_connect-0.28.1.dist-info}/RECORD +22 -21
  15. {snowpark_connect-0.28.0.data → snowpark_connect-0.28.1.data}/scripts/snowpark-connect +0 -0
  16. {snowpark_connect-0.28.0.data → snowpark_connect-0.28.1.data}/scripts/snowpark-session +0 -0
  17. {snowpark_connect-0.28.0.data → snowpark_connect-0.28.1.data}/scripts/snowpark-submit +0 -0
  18. {snowpark_connect-0.28.0.dist-info → snowpark_connect-0.28.1.dist-info}/WHEEL +0 -0
  19. {snowpark_connect-0.28.0.dist-info → snowpark_connect-0.28.1.dist-info}/licenses/LICENSE-binary +0 -0
  20. {snowpark_connect-0.28.0.dist-info → snowpark_connect-0.28.1.dist-info}/licenses/LICENSE.txt +0 -0
  21. {snowpark_connect-0.28.0.dist-info → snowpark_connect-0.28.1.dist-info}/licenses/NOTICE-binary +0 -0
  22. {snowpark_connect-0.28.0.dist-info → snowpark_connect-0.28.1.dist-info}/top_level.txt +0 -0
@@ -283,7 +283,7 @@ class SessionConfig:
283
283
  default_session_config = {
284
284
  "snowpark.connect.sql.identifiers.auto-uppercase": "all_except_columns",
285
285
  "snowpark.connect.sql.passthrough": "false",
286
- "snowpark.connect.cte.optimization_enabled": "true",
286
+ "snowpark.connect.cte.optimization_enabled": "false",
287
287
  "snowpark.connect.udtf.compatibility_mode": "false",
288
288
  "snowpark.connect.views.duplicate_column_names_handling_mode": "rename",
289
289
  "spark.sql.execution.pythonUDTF.arrow.enabled": "false",
@@ -1,18 +1,16 @@
1
1
  #
2
2
  # Copyright (c) 2012-2025 Snowflake Computing Inc. All rights reserved.
3
3
  #
4
- import re
5
- import uuid
6
4
  from collections import Counter
7
5
 
8
6
  import pyspark.sql.connect.proto.base_pb2 as proto_base
9
7
  import pyspark.sql.connect.proto.relations_pb2 as relation_proto
10
8
 
11
- from snowflake.snowpark import DataFrame, Session
12
- from snowflake.snowpark.exceptions import SnowparkSQLException
9
+ from snowflake.snowpark.types import StructField, StructType
13
10
  from snowflake.snowpark_connect.column_name_handler import ColumnNames
14
11
  from snowflake.snowpark_connect.config import global_config, sessions_config
15
12
  from snowflake.snowpark_connect.constants import SERVER_SIDE_SESSION_ID
13
+ from snowflake.snowpark_connect.dataframe_container import DataFrameContainer
16
14
  from snowflake.snowpark_connect.execute_plan.utils import pandas_to_arrow_batches_bytes
17
15
  from snowflake.snowpark_connect.expression import map_udf
18
16
  from snowflake.snowpark_connect.relation import map_udtf
@@ -28,10 +26,7 @@ from snowflake.snowpark_connect.utils.snowpark_connect_logging import logger
28
26
  from snowflake.snowpark_connect.utils.telemetry import (
29
27
  SnowparkConnectNotImplementedError,
30
28
  )
31
-
32
- _INTERNAL_VIEW_PREFIX = "__SC_RENAMED_V_"
33
-
34
- _CREATE_VIEW_PATTERN = re.compile(r"create\s+or\s+replace\s+view", re.IGNORECASE)
29
+ from snowflake.snowpark_connect.utils.temporary_view_cache import register_temp_view
35
30
 
36
31
 
37
32
  def _create_column_rename_map(
@@ -98,32 +93,35 @@ def map_execution_command(
98
93
  input_df = input_df_container.dataframe
99
94
  column_map = input_df_container.column_map
100
95
 
96
+ # TODO: Remove code handling deduplication. When view are not materialized we don't have to care about it.
101
97
  session_config = sessions_config[get_session_id()]
102
98
  duplicate_column_names_handling_mode = session_config[
103
99
  "snowpark.connect.views.duplicate_column_names_handling_mode"
104
100
  ]
105
101
 
102
+ spark_columns = input_df_container.column_map.get_spark_columns()
106
103
  # rename columns to match spark names
107
104
  if duplicate_column_names_handling_mode == "rename":
108
105
  # deduplicate column names by appending _DEDUP_1, _DEDUP_2, etc.
109
- input_df = input_df.rename(
110
- _create_column_rename_map(column_map.columns, True)
111
- )
106
+ rename_map = _create_column_rename_map(column_map.columns, True)
107
+ snowpark_columns = list(rename_map.values())
108
+ input_df = input_df.rename(rename_map)
112
109
  elif duplicate_column_names_handling_mode == "drop":
113
110
  # Drop duplicate column names by removing all but the first occurrence.
114
111
  duplicated_columns, remaining_columns = _find_duplicated_columns(
115
112
  column_map.columns
116
113
  )
114
+ rename_map = _create_column_rename_map(remaining_columns, False)
115
+ snowpark_columns = list(rename_map.values())
116
+ spark_columns = list(dict.fromkeys(spark_columns))
117
117
  if len(duplicated_columns) > 0:
118
118
  input_df = input_df.drop(*duplicated_columns)
119
- input_df = input_df.rename(
120
- _create_column_rename_map(remaining_columns, False)
121
- )
119
+ input_df = input_df.rename(rename_map)
122
120
  else:
123
121
  # rename columns without deduplication
124
- input_df = input_df.rename(
125
- _create_column_rename_map(column_map.columns, False)
126
- )
122
+ rename_map = _create_column_rename_map(column_map.columns, True)
123
+ snowpark_columns = list(rename_map.values())
124
+ input_df = input_df.rename(rename_map)
127
125
 
128
126
  if req.is_global:
129
127
  view_name = [global_config.spark_sql_globalTempDatabase, req.name]
@@ -132,18 +130,23 @@ def map_execution_command(
132
130
  view_name = [
133
131
  spark_to_sf_single_id_with_unquoting(part) for part in view_name
134
132
  ]
133
+ joined_view_name = ".".join(view_name)
135
134
 
136
- if req.replace:
137
- try:
138
- input_df.create_or_replace_temp_view(view_name)
139
- except SnowparkSQLException as exc:
140
- if _is_error_caused_by_view_referencing_itself(exc):
141
- # This error is caused by statement with self reference like `CREATE VIEW A AS SELECT X FROM A`.
142
- _create_chained_view(input_df, view_name)
143
- else:
144
- raise
145
- else:
146
- input_df.create_temp_view(view_name)
135
+ schema = StructType(
136
+ [
137
+ StructField(field.name, field.datatype)
138
+ for field in input_df.schema.fields
139
+ ]
140
+ )
141
+ input_df_container = DataFrameContainer.create_with_column_mapping(
142
+ dataframe=input_df,
143
+ spark_column_names=spark_columns,
144
+ snowpark_column_names=snowpark_columns,
145
+ parent_column_name_map=input_df_container.column_map,
146
+ cached_schema_getter=lambda: schema,
147
+ )
148
+
149
+ register_temp_view(joined_view_name, input_df_container, req.replace)
147
150
  case "write_stream_operation_start":
148
151
  match request.plan.command.write_stream_operation_start.format:
149
152
  case "console":
@@ -207,43 +210,3 @@ def map_execution_command(
207
210
  raise SnowparkConnectNotImplementedError(
208
211
  f"Command type {other} not implemented"
209
212
  )
210
-
211
-
212
- def _generate_random_builtin_view_name() -> str:
213
- return _INTERNAL_VIEW_PREFIX + str(uuid.uuid4()).replace("-", "")
214
-
215
-
216
- def _is_error_caused_by_view_referencing_itself(exc: Exception) -> bool:
217
- return "view definition refers to view being defined" in str(exc).lower()
218
-
219
-
220
- def _create_chained_view(input_df: DataFrame, view_name: str) -> None:
221
- """
222
- In order to create a view, which references itself, Spark would here take the previous
223
- definition of A and paste it in place of `FROM A`. Snowflake would fail in such case, so
224
- as a workaround, we create a chain of internal views instead. This function:
225
- 1. Renames previous definition of A to some internal name (instead of deleting).
226
- 2. Adjusts the DDL of a new statement to reference the name of a renmaed internal view, instead of itself.
227
- """
228
-
229
- session = Session.get_active_session()
230
-
231
- view_name = ".".join(view_name)
232
-
233
- tmp_name = _generate_random_builtin_view_name()
234
- old_name_replacement = _generate_random_builtin_view_name()
235
-
236
- input_df.create_or_replace_temp_view(tmp_name)
237
-
238
- session.sql(f"ALTER VIEW {view_name} RENAME TO {old_name_replacement}").collect()
239
-
240
- ddl: str = session.sql(f"SELECT GET_DDL('VIEW', '{tmp_name}')").collect()[0][0]
241
-
242
- ddl = ddl.replace(view_name, old_name_replacement)
243
-
244
- # GET_DDL result doesn't contain `TEMPORARY`, it's likely a bug.
245
- ddl = _CREATE_VIEW_PATTERN.sub("create or replace temp view", ddl)
246
-
247
- session.sql(ddl).collect()
248
-
249
- session.sql(f"ALTER VIEW {tmp_name} RENAME TO {view_name}").collect()
@@ -13,7 +13,6 @@ from snowflake.core.exceptions import APIError, NotFoundError
13
13
  from snowflake.core.schema import Schema
14
14
  from snowflake.core.table import Table, TableColumn
15
15
 
16
- from snowflake.snowpark import functions
17
16
  from snowflake.snowpark._internal.analyzer.analyzer_utils import (
18
17
  quote_name_without_upper_casing,
19
18
  unquote_if_quoted,
@@ -34,12 +33,19 @@ from snowflake.snowpark_connect.relation.catalogs.abstract_spark_catalog import
34
33
  )
35
34
  from snowflake.snowpark_connect.type_mapping import proto_to_snowpark_type
36
35
  from snowflake.snowpark_connect.utils.identifiers import (
36
+ FQN,
37
+ spark_to_sf_single_id_with_unquoting,
37
38
  split_fully_qualified_spark_name,
38
39
  )
39
40
  from snowflake.snowpark_connect.utils.session import get_or_create_snowpark_session
40
41
  from snowflake.snowpark_connect.utils.telemetry import (
41
42
  SnowparkConnectNotImplementedError,
42
43
  )
44
+ from snowflake.snowpark_connect.utils.temporary_view_cache import (
45
+ get_temp_view,
46
+ get_temp_view_normalized_names,
47
+ unregister_temp_view,
48
+ )
43
49
  from snowflake.snowpark_connect.utils.udf_cache import cached_udf
44
50
 
45
51
 
@@ -203,6 +209,93 @@ class SnowflakeCatalog(AbstractSparkCatalog):
203
209
  exists = False
204
210
  return pandas.DataFrame({"exists": [exists]})
205
211
 
212
+ def _get_temp_view_prefixes(self, spark_dbName: str | None) -> list[str]:
213
+ if spark_dbName is None:
214
+ return []
215
+ return [
216
+ quote_name_without_upper_casing(part)
217
+ for part in split_fully_qualified_spark_name(spark_dbName)
218
+ ]
219
+
220
+ def _list_temp_views(
221
+ self,
222
+ spark_dbName: str | None = None,
223
+ pattern: str | None = None,
224
+ ) -> typing.Tuple[
225
+ list[str | None],
226
+ list[list[str | None]],
227
+ list[str],
228
+ list[str | None],
229
+ list[str | None],
230
+ list[bool],
231
+ ]:
232
+ catalogs: list[str | None] = list()
233
+ namespaces: list[list[str | None]] = list()
234
+ names: list[str] = list()
235
+ descriptions: list[str | None] = list()
236
+ table_types: list[str | None] = list()
237
+ is_temporaries: list[bool] = list()
238
+
239
+ temp_views_prefix = ".".join(self._get_temp_view_prefixes(spark_dbName))
240
+ normalized_spark_dbName = (
241
+ temp_views_prefix.lower()
242
+ if global_config.spark_sql_caseSensitive
243
+ else temp_views_prefix
244
+ )
245
+ normalized_global_temp_database_name = (
246
+ quote_name_without_upper_casing(
247
+ global_config.spark_sql_globalTempDatabase.lower()
248
+ )
249
+ if global_config.spark_sql_caseSensitive
250
+ else quote_name_without_upper_casing(
251
+ global_config.spark_sql_globalTempDatabase
252
+ )
253
+ )
254
+
255
+ temp_views = get_temp_view_normalized_names()
256
+ null_safe_pattern = pattern if pattern is not None else ""
257
+
258
+ for temp_view in temp_views:
259
+ normalized_temp_view = (
260
+ temp_view.lower()
261
+ if global_config.spark_sql_caseSensitive
262
+ else temp_view
263
+ )
264
+ fqn = FQN.from_string(temp_view)
265
+ normalized_schema = (
266
+ fqn.schema.lower()
267
+ if fqn.schema is not None and global_config.spark_sql_caseSensitive
268
+ else fqn.schema
269
+ )
270
+
271
+ is_global_view = normalized_global_temp_database_name == normalized_schema
272
+ is_local_temp_view = fqn.schema is None
273
+ # Temporary views are always shown if they match the pattern
274
+ matches_prefix = (
275
+ normalized_spark_dbName == normalized_schema or is_local_temp_view
276
+ )
277
+ if matches_prefix and bool(
278
+ re.match(null_safe_pattern, normalized_temp_view)
279
+ ):
280
+ names.append(unquote_if_quoted(fqn.name))
281
+ catalogs.append(None)
282
+ namespaces.append(
283
+ [global_config.spark_sql_globalTempDatabase]
284
+ if is_global_view
285
+ else []
286
+ )
287
+ descriptions.append(None)
288
+ table_types.append("TEMPORARY")
289
+ is_temporaries.append(True)
290
+ return (
291
+ catalogs,
292
+ namespaces,
293
+ names,
294
+ descriptions,
295
+ table_types,
296
+ is_temporaries,
297
+ )
298
+
206
299
  def listTables(
207
300
  self,
208
301
  spark_dbName: str | None = None,
@@ -232,8 +325,7 @@ class SnowflakeCatalog(AbstractSparkCatalog):
232
325
  schema=sf_quote(sf_schema),
233
326
  pattern=_normalize_identifier(pattern),
234
327
  )
235
- names: list[str] = list()
236
- catalogs: list[str] = list()
328
+ catalogs: list[str | None] = list()
237
329
  namespaces: list[list[str | None]] = list()
238
330
  names: list[str] = list()
239
331
  descriptions: list[str | None] = list()
@@ -253,6 +345,22 @@ class SnowflakeCatalog(AbstractSparkCatalog):
253
345
  descriptions.append(o[6] if o[6] else None)
254
346
  table_types.append("PERMANENT")
255
347
  is_temporaries.append(False)
348
+
349
+ (
350
+ non_materialized_catalogs,
351
+ non_materialized_namespaces,
352
+ non_materialized_names,
353
+ non_materialized_descriptions,
354
+ non_materialized_table_types,
355
+ non_materialized_is_temporaries,
356
+ ) = self._list_temp_views(spark_dbName, pattern)
357
+ catalogs.extend(non_materialized_catalogs)
358
+ namespaces.extend(non_materialized_namespaces)
359
+ names.extend(non_materialized_names)
360
+ descriptions.extend(non_materialized_descriptions)
361
+ table_types.extend(non_materialized_table_types)
362
+ is_temporaries.extend(non_materialized_is_temporaries)
363
+
256
364
  return pandas.DataFrame(
257
365
  {
258
366
  "name": names,
@@ -297,6 +405,36 @@ class SnowflakeCatalog(AbstractSparkCatalog):
297
405
  spark_tableName: str,
298
406
  ) -> pandas.DataFrame:
299
407
  """Listing a single table/view with provided name that's accessible in Snowflake."""
408
+
409
+ def _get_temp_view():
410
+ spark_table_name_parts = [
411
+ quote_name_without_upper_casing(part)
412
+ for part in split_fully_qualified_spark_name(spark_tableName)
413
+ ]
414
+ spark_view_name = ".".join(spark_table_name_parts)
415
+ temp_view = get_temp_view(spark_view_name)
416
+ if temp_view:
417
+ return pandas.DataFrame(
418
+ {
419
+ "name": [unquote_if_quoted(spark_table_name_parts[-1])],
420
+ "catalog": [None],
421
+ "namespace": [
422
+ [unquote_if_quoted(spark_table_name_parts[-2])]
423
+ if len(spark_table_name_parts) > 1
424
+ else []
425
+ ],
426
+ "description": [None],
427
+ "tableType": ["TEMPORARY"],
428
+ "isTemporary": [True],
429
+ }
430
+ )
431
+ return None
432
+
433
+ # Attempt to get the view from the non materialized views first
434
+ temp_view = _get_temp_view()
435
+ if temp_view is not None:
436
+ return temp_view
437
+
300
438
  sp_catalog = get_or_create_snowpark_session().catalog
301
439
  catalog, sf_database, sf_schema, table_name = _process_multi_layer_identifier(
302
440
  spark_tableName
@@ -360,12 +498,64 @@ class SnowflakeCatalog(AbstractSparkCatalog):
360
498
  exists = False
361
499
  return pandas.DataFrame({"exists": [exists]})
362
500
 
501
+ def _list_temp_view_columns(
502
+ self,
503
+ spark_tableName: str,
504
+ spark_dbName: typing.Optional[str] = None,
505
+ ):
506
+ spark_view_name_parts = [
507
+ quote_name_without_upper_casing(part)
508
+ for part in split_fully_qualified_spark_name(spark_tableName)
509
+ ]
510
+ spark_view_name_parts = (
511
+ self._get_temp_view_prefixes(spark_dbName) + spark_view_name_parts
512
+ )
513
+ spark_view_name = ".".join(spark_view_name_parts)
514
+ temp_view = get_temp_view(spark_view_name)
515
+
516
+ if not temp_view:
517
+ return None
518
+
519
+ names: list[str] = list()
520
+ descriptions: list[str | None] = list()
521
+ data_types: list[str] = list()
522
+ nullables: list[bool] = list()
523
+ is_partitions: list[bool] = list()
524
+ is_buckets: list[bool] = list()
525
+
526
+ for field, spark_column in zip(
527
+ temp_view.dataframe.schema.fields,
528
+ temp_view.column_map.get_spark_columns(),
529
+ ):
530
+ names.append(spark_column)
531
+ descriptions.append(None)
532
+ data_types.append(field.datatype.simpleString())
533
+ nullables.append(field.nullable)
534
+ is_partitions.append(False)
535
+ is_buckets.append(False)
536
+
537
+ return pandas.DataFrame(
538
+ {
539
+ "name": names,
540
+ "description": descriptions,
541
+ "dataType": data_types,
542
+ "nullable": nullables,
543
+ "isPartition": is_partitions,
544
+ "isBucket": is_buckets,
545
+ }
546
+ )
547
+
363
548
  def listColumns(
364
549
  self,
365
550
  spark_tableName: str,
366
551
  spark_dbName: typing.Optional[str] = None,
367
552
  ) -> pandas.DataFrame:
368
553
  """List all columns in a table/view, optionally database name filter can be provided."""
554
+
555
+ temp_view_columns = self._list_temp_view_columns(spark_tableName, spark_dbName)
556
+ if temp_view_columns is not None:
557
+ return temp_view_columns
558
+
369
559
  sp_catalog = get_or_create_snowpark_session().catalog
370
560
  columns: list[TableColumn] | None = None
371
561
  if spark_dbName is None:
@@ -455,17 +645,15 @@ class SnowflakeCatalog(AbstractSparkCatalog):
455
645
  spark_view_name: str,
456
646
  ) -> DataFrameContainer:
457
647
  session = get_or_create_snowpark_session()
458
- schema = global_config.spark_sql_globalTempDatabase
459
- result_df = session.sql(
460
- "drop view if exists identifier(?)",
461
- params=[f"{sf_quote(schema)}.{sf_quote(spark_view_name)}"],
462
- )
463
- result_df = result_df.select(
464
- functions.contains('"status"', functions.lit("successfully dropped")).alias(
465
- "value"
648
+ if not spark_view_name == "":
649
+ schema = global_config.spark_sql_globalTempDatabase
650
+ result = unregister_temp_view(
651
+ f"{spark_to_sf_single_id_with_unquoting(schema)}.{spark_to_sf_single_id_with_unquoting(spark_view_name)}"
466
652
  )
467
- )
653
+ else:
654
+ result = False
468
655
  columns = ["value"]
656
+ result_df = session.createDataFrame([result], schema=columns)
469
657
  return DataFrameContainer.create_with_column_mapping(
470
658
  dataframe=result_df,
471
659
  spark_column_names=columns,
@@ -479,15 +667,14 @@ class SnowflakeCatalog(AbstractSparkCatalog):
479
667
  ) -> DataFrameContainer:
480
668
  """Drop the current temporary view."""
481
669
  session = get_or_create_snowpark_session()
482
- result = session.sql(
483
- "drop view if exists identifier(?)",
484
- params=[sf_quote(spark_view_name)],
485
- ).collect()
486
- view_was_dropped = (
487
- len(result) == 1 and "successfully dropped" in result[0]["status"]
488
- )
489
- result_df = session.createDataFrame([(view_was_dropped,)], schema=["value"])
490
670
  columns = ["value"]
671
+ if spark_view_name:
672
+ result = unregister_temp_view(
673
+ spark_to_sf_single_id_with_unquoting(spark_view_name)
674
+ )
675
+ else:
676
+ result = False
677
+ result_df = session.createDataFrame([result], schema=columns)
491
678
  return DataFrameContainer.create_with_column_mapping(
492
679
  dataframe=result_df,
493
680
  spark_column_names=columns,
@@ -61,6 +61,7 @@ from snowflake.snowpark_connect.utils.context import (
61
61
  get_session_id,
62
62
  get_sql_plan,
63
63
  push_evaluating_sql_scope,
64
+ push_processed_view,
64
65
  push_sql_scope,
65
66
  set_plan_id_map,
66
67
  set_sql_args,
@@ -80,7 +81,16 @@ from ..expression.map_sql_expression import (
80
81
  map_logical_plan_expression,
81
82
  sql_parser,
82
83
  )
83
- from ..utils.identifiers import spark_to_sf_single_id
84
+ from ..utils.identifiers import (
85
+ spark_to_sf_single_id,
86
+ spark_to_sf_single_id_with_unquoting,
87
+ )
88
+ from ..utils.temporary_view_cache import (
89
+ get_temp_view,
90
+ register_temp_view,
91
+ unregister_temp_view,
92
+ )
93
+ from .catalogs import SNOWFLAKE_CATALOG
84
94
 
85
95
  _ctes = ContextVar[dict[str, relation_proto.Relation]]("_ctes", default={})
86
96
  _cte_definitions = ContextVar[dict[str, any]]("_cte_definitions", default={})
@@ -403,6 +413,7 @@ def map_sql_to_pandas_df(
403
413
  ) == "UnresolvedHint":
404
414
  logical_plan = logical_plan.child()
405
415
 
416
+ # TODO: Add support for temporary views for SQL cases such as ShowViews, ShowColumns ect. (Currently the cases are not compatible with Spark, returning raw Snowflake rows)
406
417
  match class_name:
407
418
  case "AddColumns":
408
419
  # Handle ALTER TABLE ... ADD COLUMNS (col_name data_type) -> ADD COLUMN col_name data_type
@@ -577,6 +588,23 @@ def map_sql_to_pandas_df(
577
588
  )
578
589
  snowflake_sql = parsed_sql.sql(dialect="snowflake")
579
590
  session.sql(f"{snowflake_sql}{empty_select}").collect()
591
+ spark_view_name = next(
592
+ sqlglot.parse_one(sql_string, dialect="spark").find_all(
593
+ sqlglot.exp.Table
594
+ )
595
+ ).name
596
+ snowflake_view_name = spark_to_sf_single_id_with_unquoting(
597
+ spark_view_name
598
+ )
599
+ temp_view = get_temp_view(snowflake_view_name)
600
+ if temp_view is not None and not logical_plan.replace():
601
+ raise AnalysisException(
602
+ f"[TEMP_TABLE_OR_VIEW_ALREADY_EXISTS] Cannot create the temporary view `{spark_view_name}` because it already exists."
603
+ )
604
+ else:
605
+ unregister_temp_view(
606
+ spark_to_sf_single_id_with_unquoting(spark_view_name)
607
+ )
580
608
  case "CreateView":
581
609
  current_schema = session.connection.schema
582
610
  if (
@@ -613,50 +641,60 @@ def map_sql_to_pandas_df(
613
641
  else None,
614
642
  )
615
643
  case "CreateViewCommand":
616
- df_container = execute_logical_plan(logical_plan.plan())
617
- df = df_container.dataframe
618
- tmp_views = _get_current_temp_objects()
619
- tmp_views.add(
620
- (
621
- CURRENT_CATALOG_NAME,
622
- session.connection.schema,
623
- str(logical_plan.name().identifier()),
644
+ with push_processed_view(logical_plan.name().identifier()):
645
+ df_container = execute_logical_plan(logical_plan.plan())
646
+ df = df_container.dataframe
647
+ user_specified_spark_column_names = [
648
+ str(col._1())
649
+ for col in as_java_list(logical_plan.userSpecifiedColumns())
650
+ ]
651
+ df_container = DataFrameContainer.create_with_column_mapping(
652
+ dataframe=df,
653
+ spark_column_names=user_specified_spark_column_names
654
+ if user_specified_spark_column_names
655
+ else df_container.column_map.get_spark_columns(),
656
+ snowpark_column_names=df_container.column_map.get_snowpark_columns(),
657
+ parent_column_name_map=df_container.column_map,
624
658
  )
625
- )
626
-
627
- name = str(logical_plan.name().identifier())
628
- name = spark_to_sf_single_id(name)
629
- if isinstance(
630
- logical_plan.viewType(),
631
- jpype.JClass(
632
- "org.apache.spark.sql.catalyst.analysis.GlobalTempView$"
633
- ),
634
- ):
635
- name = f"{global_config.spark_sql_globalTempDatabase}.{name}"
636
- comment = logical_plan.comment()
637
- maybe_comment = (
638
- _escape_sql_comment(str(comment.get()))
639
- if comment.isDefined()
640
- else None
641
- )
642
-
643
- df = _rename_columns(
644
- df, logical_plan.userSpecifiedColumns(), df_container.column_map
645
- )
646
-
647
- if logical_plan.replace():
648
- df.create_or_replace_temp_view(
649
- name,
650
- comment=maybe_comment,
659
+ is_global = isinstance(
660
+ logical_plan.viewType(),
661
+ jpype.JClass(
662
+ "org.apache.spark.sql.catalyst.analysis.GlobalTempView$"
663
+ ),
651
664
  )
652
- else:
653
- df.create_temp_view(
654
- name,
655
- comment=maybe_comment,
665
+ if is_global:
666
+ view_name = [
667
+ global_config.spark_sql_globalTempDatabase,
668
+ logical_plan.name().quotedString(),
669
+ ]
670
+ else:
671
+ view_name = [logical_plan.name().quotedString()]
672
+ view_name = [
673
+ spark_to_sf_single_id_with_unquoting(part) for part in view_name
674
+ ]
675
+ joined_view_name = ".".join(view_name)
676
+
677
+ register_temp_view(
678
+ joined_view_name,
679
+ df_container,
680
+ logical_plan.replace(),
681
+ )
682
+ tmp_views = _get_current_temp_objects()
683
+ tmp_views.add(
684
+ (
685
+ CURRENT_CATALOG_NAME,
686
+ session.connection.schema,
687
+ str(logical_plan.name().identifier()),
688
+ )
656
689
  )
657
690
  case "DescribeColumn":
658
- name = get_relation_identifier_name(logical_plan.column())
691
+ name = get_relation_identifier_name_without_uppercasing(
692
+ logical_plan.column()
693
+ )
694
+ if get_temp_view(name):
695
+ return SNOWFLAKE_CATALOG.listColumns(unquote_if_quoted(name)), ""
659
696
  # todo double check if this is correct
697
+ name = get_relation_identifier_name(logical_plan.column())
660
698
  rows = session.sql(f"DESCRIBE TABLE {name}").collect()
661
699
  case "DescribeNamespace":
662
700
  name = get_relation_identifier_name(logical_plan.namespace(), True)
@@ -731,9 +769,13 @@ def map_sql_to_pandas_df(
731
769
  if_exists = "IF EXISTS " if logical_plan.ifExists() else ""
732
770
  session.sql(f"DROP TABLE {if_exists}{name}").collect()
733
771
  case "DropView":
734
- name = get_relation_identifier_name(logical_plan.child())
735
- if_exists = "IF EXISTS " if logical_plan.ifExists() else ""
736
- session.sql(f"DROP VIEW {if_exists}{name}").collect()
772
+ temporary_view_name = get_relation_identifier_name_without_uppercasing(
773
+ logical_plan.child()
774
+ )
775
+ if not unregister_temp_view(temporary_view_name):
776
+ name = get_relation_identifier_name(logical_plan.child())
777
+ if_exists = "IF EXISTS " if logical_plan.ifExists() else ""
778
+ session.sql(f"DROP VIEW {if_exists}{name}").collect()
737
779
  case "ExplainCommand":
738
780
  inner_plan = logical_plan.logicalPlan()
739
781
  logical_plan_name = inner_plan.nodeName()
@@ -2173,21 +2215,38 @@ def map_logical_plan_relation(
2173
2215
  return proto
2174
2216
 
2175
2217
 
2176
- def get_relation_identifier_name(name_obj, is_multi_part: bool = False) -> str:
2218
+ def _get_relation_identifier(name_obj) -> str:
2219
+ # IDENTIFIER(<table_name>), or IDENTIFIER(<method name>)
2220
+ expr_proto = map_logical_plan_expression(name_obj.identifierExpr())
2221
+ session = snowpark.Session.get_active_session()
2222
+ m = ColumnNameMap([], [], None)
2223
+ expr = map_single_column_expression(
2224
+ expr_proto, m, ExpressionTyper.dummy_typer(session)
2225
+ )
2226
+ return spark_to_sf_single_id(session.range(1).select(expr[1].col).collect()[0][0])
2227
+
2228
+
2229
+ def get_relation_identifier_name_without_uppercasing(name_obj) -> str:
2177
2230
  if name_obj.getClass().getSimpleName() in (
2178
2231
  "PlanWithUnresolvedIdentifier",
2179
2232
  "ExpressionWithUnresolvedIdentifier",
2180
2233
  ):
2181
- # IDENTIFIER(<table_name>), or IDENTIFIER(<method name>)
2182
- expr_proto = map_logical_plan_expression(name_obj.identifierExpr())
2183
- session = snowpark.Session.get_active_session()
2184
- m = ColumnNameMap([], [], None)
2185
- expr = map_single_column_expression(
2186
- expr_proto, m, ExpressionTyper.dummy_typer(session)
2187
- )
2188
- name = spark_to_sf_single_id(
2189
- session.range(1).select(expr[1].col).collect()[0][0]
2234
+ return _get_relation_identifier(name_obj)
2235
+ else:
2236
+ name = ".".join(
2237
+ quote_name_without_upper_casing(str(part))
2238
+ for part in as_java_list(name_obj.nameParts())
2190
2239
  )
2240
+
2241
+ return name
2242
+
2243
+
2244
+ def get_relation_identifier_name(name_obj, is_multi_part: bool = False) -> str:
2245
+ if name_obj.getClass().getSimpleName() in (
2246
+ "PlanWithUnresolvedIdentifier",
2247
+ "ExpressionWithUnresolvedIdentifier",
2248
+ ):
2249
+ return _get_relation_identifier(name_obj)
2191
2250
  else:
2192
2251
  if is_multi_part:
2193
2252
  try:
@@ -11,11 +11,17 @@ from snowflake.snowpark._internal.analyzer.analyzer_utils import (
11
11
  unquote_if_quoted,
12
12
  )
13
13
  from snowflake.snowpark.exceptions import SnowparkSQLException
14
+ from snowflake.snowpark.types import StructField, StructType
15
+ from snowflake.snowpark_connect.column_name_handler import (
16
+ ColumnNameMap,
17
+ make_column_names_snowpark_compatible,
18
+ )
14
19
  from snowflake.snowpark_connect.config import auto_uppercase_non_column_identifiers
15
20
  from snowflake.snowpark_connect.dataframe_container import DataFrameContainer
16
21
  from snowflake.snowpark_connect.relation.read.utils import (
17
22
  rename_columns_as_snowflake_standard,
18
23
  )
24
+ from snowflake.snowpark_connect.utils.context import get_processed_views
19
25
  from snowflake.snowpark_connect.utils.identifiers import (
20
26
  split_fully_qualified_spark_name,
21
27
  )
@@ -23,6 +29,7 @@ from snowflake.snowpark_connect.utils.session import _get_current_snowpark_sessi
23
29
  from snowflake.snowpark_connect.utils.telemetry import (
24
30
  SnowparkConnectNotImplementedError,
25
31
  )
32
+ from snowflake.snowpark_connect.utils.temporary_view_cache import get_temp_view
26
33
 
27
34
 
28
35
  def post_process_df(
@@ -64,15 +71,66 @@ def post_process_df(
64
71
  raise
65
72
 
66
73
 
74
+ def _get_temporary_view(
75
+ temp_view: DataFrameContainer, table_name: str, plan_id: int
76
+ ) -> DataFrameContainer:
77
+ fields_names = [field.name for field in temp_view.dataframe.schema.fields]
78
+ fields_types = [field.datatype for field in temp_view.dataframe.schema.fields]
79
+
80
+ snowpark_column_names = make_column_names_snowpark_compatible(fields_names, plan_id)
81
+ # Rename columns in dataframe to prevent conflicting names during joins
82
+ renamed_df = temp_view.dataframe.select(
83
+ *(
84
+ temp_view.dataframe.col(orig).alias(alias)
85
+ for orig, alias in zip(fields_names, snowpark_column_names)
86
+ )
87
+ )
88
+
89
+ new_column_map = ColumnNameMap(
90
+ spark_column_names=temp_view.column_map.get_spark_columns(),
91
+ snowpark_column_names=snowpark_column_names,
92
+ column_metadata=temp_view.column_map.column_metadata,
93
+ column_qualifiers=[split_fully_qualified_spark_name(table_name)]
94
+ * len(temp_view.column_map.get_spark_columns()),
95
+ parent_column_name_map=temp_view.column_map.get_parent_column_name_map(),
96
+ )
97
+
98
+ schema = StructType(
99
+ [
100
+ StructField(name, type, _is_column=False)
101
+ for name, type in zip(snowpark_column_names, fields_types)
102
+ ]
103
+ )
104
+ return DataFrameContainer(
105
+ dataframe=renamed_df,
106
+ column_map=new_column_map,
107
+ table_name=temp_view.table_name,
108
+ alias=temp_view.alias,
109
+ partition_hint=temp_view.partition_hint,
110
+ cached_schema_getter=lambda: schema,
111
+ )
112
+
113
+
67
114
  def get_table_from_name(
68
115
  table_name: str, session: snowpark.Session, plan_id: int
69
116
  ) -> DataFrameContainer:
70
117
  """Get table from name returning a container."""
118
+
119
+ # Verify if recursive view read is not attempted
120
+ if table_name in get_processed_views():
121
+ raise AnalysisException(
122
+ f"[RECURSIVE_VIEW] Recursive view `{table_name}` detected (cycle: `{table_name}` -> `{table_name}`)"
123
+ )
124
+
71
125
  snowpark_name = ".".join(
72
126
  quote_name_without_upper_casing(part)
73
127
  for part in split_fully_qualified_spark_name(table_name)
74
128
  )
75
129
 
130
+ temp_view = get_temp_view(snowpark_name)
131
+ if temp_view:
132
+ return _get_temporary_view(temp_view, table_name, plan_id)
133
+
76
134
  if auto_uppercase_non_column_identifiers():
77
135
  snowpark_name = snowpark_name.upper()
78
136
 
@@ -218,8 +218,9 @@ def map_write(request: proto_base.ExecutePlanRequest):
218
218
  },
219
219
  "overwrite": overwrite,
220
220
  }
221
- # By default, download from the same prefix we wrote to.
222
- download_stage_path = temp_file_prefix_on_stage
221
+ # Download from the base write path to ensure we fetch whatever Snowflake produced.
222
+ # Using the base avoids coupling to exact filenames/prefixes.
223
+ download_stage_path = write_path
223
224
 
224
225
  # Check for partition hint early to determine precedence over single option
225
226
  partition_hint = result.partition_hint
@@ -238,13 +239,19 @@ def map_write(request: proto_base.ExecutePlanRequest):
238
239
  raise SnowparkConnectNotImplementedError(
239
240
  "Partitioning is only supported for parquet format"
240
241
  )
241
- partitioning_columns = [f'"{c}"' for c in write_op.partitioning_columns]
242
- if len(partitioning_columns) > 1:
243
- raise SnowparkConnectNotImplementedError(
244
- "Multiple partitioning columns are not yet supported"
245
- )
246
- else:
247
- parameters["partition_by"] = partitioning_columns[0]
242
+ # Build Spark-style directory structure: col1=value1/col2=value2/...
243
+ # Example produced expression (Snowflake SQL):
244
+ # 'department=' || TO_VARCHAR("department") || '/' || 'region=' || TO_VARCHAR("region")
245
+ partitioning_column_names = list(write_op.partitioning_columns)
246
+ partition_expr_parts: list[str] = []
247
+ for col_name in partitioning_column_names:
248
+ quoted = f'"{col_name}"'
249
+ segment = f"'{col_name}=' || COALESCE(TO_VARCHAR({quoted}), '__HIVE_DEFAULT_PARTITION__')"
250
+ partition_expr_parts.append(segment)
251
+ parameters["partition_by"] = " || '/' || ".join(partition_expr_parts)
252
+ # When using PARTITION BY, Snowflake writes into subdirectories under the base path.
253
+ # Download from the base write path to preserve partition directories locally.
254
+ download_stage_path = write_path
248
255
 
249
256
  # If a partition hint is present (from DataFrame.repartition(n)), optionally split the
250
257
  # write into n COPY INTO calls by assigning a synthetic partition id. Controlled by config.
@@ -978,7 +985,47 @@ def store_files_locally(
978
985
  )
979
986
  if overwrite and os.path.isdir(target_path):
980
987
  _truncate_directory(real_path)
981
- snowpark.file_operation.FileOperation(session).get(stage_path, str(real_path))
988
+ # Per Snowflake docs: "The command does not preserve stage directory structure when transferring files to your client machine"
989
+ # https://docs.snowflake.com/en/sql-reference/sql/get
990
+ # Preserve directory structure under stage_path by listing files and
991
+ # downloading each into its corresponding local subdirectory when partition subdirs exist.
992
+ # Otherwise, fall back to a direct GET which flattens.
993
+
994
+ # TODO(SNOW-2326973): This can be parallelized further. Its not done here because it only affects
995
+ # write to local storage.
996
+
997
+ ls_dataframe = session.sql(f"LS {stage_path}")
998
+ ls_iterator = ls_dataframe.toLocalIterator()
999
+
1000
+ # Build a normalized base prefix from stage_path to compute relatives
1001
+ # Example: stage_path='@MY_STAGE/prefix' -> base_prefix='my_stage/prefix/'
1002
+ base_prefix = stage_path.lstrip("@").rstrip("/") + "/"
1003
+ base_prefix_lower = base_prefix.lower()
1004
+
1005
+ # Group by parent directory under the base prefix, then issue a GET per directory.
1006
+ # This gives a small parallelism advantage if we have many files per partition directory.
1007
+ parent_dirs: set[str] = set()
1008
+ for row in ls_iterator:
1009
+ name: str = row[0]
1010
+ name_lower = name.lower()
1011
+ rel_start = name_lower.find(base_prefix_lower)
1012
+ relative = name[rel_start + len(base_prefix) :] if rel_start != -1 else name
1013
+ parent_dir = os.path.dirname(relative)
1014
+ if parent_dir and parent_dir != ".":
1015
+ parent_dirs.add(parent_dir)
1016
+
1017
+ # If no parent directories were discovered (non-partitioned unload prefix), use direct GET.
1018
+ if not parent_dirs:
1019
+ snowpark.file_operation.FileOperation(session).get(stage_path, str(real_path))
1020
+ return
1021
+
1022
+ file_op = snowpark.file_operation.FileOperation(session)
1023
+ for parent_dir in sorted(parent_dirs):
1024
+ local_dir = real_path / parent_dir
1025
+ os.makedirs(local_dir, exist_ok=True)
1026
+
1027
+ src_dir = f"@{base_prefix}{parent_dir}"
1028
+ file_op.get(src_dir, str(local_dir))
982
1029
 
983
1030
 
984
1031
  def _truncate_directory(directory_path: Path) -> None:
@@ -70,6 +70,26 @@ _lca_alias_map: ContextVar[dict[str, TypedColumn]] = ContextVar(
70
70
  default={},
71
71
  )
72
72
 
73
+ _view_process_context = ContextVar("_view_process_context", default=[])
74
+
75
+
76
+ @contextmanager
77
+ def push_processed_view(name: str):
78
+ _view_process_context.set(_view_process_context.get() + [name])
79
+ yield
80
+ _view_process_context.set(_view_process_context.get()[:-1])
81
+
82
+
83
+ def get_processed_views() -> list[str]:
84
+ return _view_process_context.get()
85
+
86
+
87
+ def register_processed_view(name: str) -> None:
88
+ context = _view_process_context.get()
89
+ context.append(name)
90
+ _view_process_context.set(context)
91
+
92
+
73
93
  # Context variable to track current grouping columns for grouping_id() function
74
94
  _current_grouping_columns: ContextVar[list[str]] = ContextVar(
75
95
  "_current_grouping_columns",
@@ -387,6 +407,7 @@ def clear_context_data() -> None:
387
407
  _plan_id_map.set({})
388
408
  _alias_map.set({})
389
409
 
410
+ _view_process_context.set([])
390
411
  _next_sql_plan_id.set(_STARTING_SQL_PLAN_ID)
391
412
  _sql_plan_name_map.set({})
392
413
  _map_partitions_stack.set(0)
@@ -28,12 +28,18 @@ def unquote_spark_identifier_if_quoted(spark_name: str) -> str:
28
28
  raise AnalysisException(f"Invalid name: {spark_name}")
29
29
 
30
30
 
31
- def spark_to_sf_single_id_with_unquoting(name: str) -> str:
31
+ def spark_to_sf_single_id_with_unquoting(
32
+ name: str, use_auto_upper_case: bool = False
33
+ ) -> str:
32
34
  """
33
35
  Transforms a spark name to a valid snowflake name by quoting and potentially uppercasing it.
34
36
  Unquotes the spark name if necessary. Will raise an AnalysisException if given name is not valid.
35
37
  """
36
- return spark_to_sf_single_id(unquote_spark_identifier_if_quoted(name))
38
+ return (
39
+ spark_to_sf_single_id(unquote_spark_identifier_if_quoted(name))
40
+ if use_auto_upper_case
41
+ else quote_name_without_upper_casing(unquote_spark_identifier_if_quoted(name))
42
+ )
37
43
 
38
44
 
39
45
  def spark_to_sf_single_id(name: str, is_column: bool = False) -> str:
@@ -0,0 +1,61 @@
1
+ #
2
+ # Copyright (c) 2012-2025 Snowflake Computing Inc. All rights reserved.
3
+ #
4
+
5
+ from typing import Optional, Tuple
6
+
7
+ from pyspark.errors import AnalysisException
8
+
9
+ from snowflake.snowpark_connect.dataframe_container import DataFrameContainer
10
+ from snowflake.snowpark_connect.utils.concurrent import SynchronizedDict
11
+ from snowflake.snowpark_connect.utils.context import get_session_id
12
+
13
+ _temp_views = SynchronizedDict[Tuple[str, str], DataFrameContainer]()
14
+
15
+
16
+ def register_temp_view(name: str, df: DataFrameContainer, replace: bool) -> None:
17
+ normalized_name = _normalize(name)
18
+ current_session_id = get_session_id()
19
+ for key in list(_temp_views.keys()):
20
+ if _normalize(key[0]) == normalized_name and key[1] == current_session_id:
21
+ if replace:
22
+ _temp_views.remove(key)
23
+ break
24
+ else:
25
+ raise AnalysisException(
26
+ f"[TEMP_TABLE_OR_VIEW_ALREADY_EXISTS] Cannot create the temporary view `{name}` because it already exists."
27
+ )
28
+
29
+ _temp_views[(name, current_session_id)] = df
30
+
31
+
32
+ def unregister_temp_view(name: str) -> bool:
33
+ normalized_name = _normalize(name)
34
+
35
+ for key in _temp_views.keys():
36
+ normalized_key = _normalize(key[0])
37
+ if normalized_name == normalized_key and key[1] == get_session_id():
38
+ pop_result = _temp_views.remove(key)
39
+ return pop_result is not None
40
+ return False
41
+
42
+
43
+ def get_temp_view(name: str) -> Optional[DataFrameContainer]:
44
+ normalized_name = _normalize(name)
45
+ for key in _temp_views.keys():
46
+ normalized_key = _normalize(key[0])
47
+ if normalized_name == normalized_key and key[1] == get_session_id():
48
+ return _temp_views.get(key)
49
+ return None
50
+
51
+
52
+ def get_temp_view_normalized_names() -> list[str]:
53
+ return [
54
+ _normalize(key[0]) for key in _temp_views.keys() if key[1] == get_session_id()
55
+ ]
56
+
57
+
58
+ def _normalize(name: str) -> str:
59
+ from snowflake.snowpark_connect.config import global_config
60
+
61
+ return name if global_config.spark_sql_caseSensitive else name.lower()
@@ -176,14 +176,6 @@ class ProcessCommonInlineUserDefinedFunction:
176
176
  tar_ref.extractall(archive[: -len(".archive")])
177
177
  os.remove(archive)
178
178
 
179
- def callable_func(*args, **kwargs):
180
- import_staged_files()
181
- return original_callable(*args, **kwargs)
182
-
183
- callable_func.__signature__ = inspect.signature(original_callable)
184
- if hasattr(original_callable, "__annotations__"):
185
- callable_func.__annotations__ = original_callable.__annotations__
186
-
187
179
  if self._udf_packages:
188
180
  packages = [p.strip() for p in self._udf_packages.strip("[]").split(",")]
189
181
  else:
@@ -193,6 +185,15 @@ class ProcessCommonInlineUserDefinedFunction:
193
185
  else:
194
186
  imports = []
195
187
 
188
+ def callable_func(*args, **kwargs):
189
+ if imports:
190
+ import_staged_files()
191
+ return original_callable(*args, **kwargs)
192
+
193
+ callable_func.__signature__ = inspect.signature(original_callable)
194
+ if hasattr(original_callable, "__annotations__"):
195
+ callable_func.__annotations__ = original_callable.__annotations__
196
+
196
197
  update_none_input_types()
197
198
 
198
199
  needs_struct_conversion = isinstance(self._original_return_type, StructType)
@@ -38,8 +38,9 @@ def create_udtf(
38
38
  # We subtract one here since UDTF functions are class methods and always have "self" as the first parameter.
39
39
  input_types = [VariantType()] * (len(func_signature.parameters) - 1)
40
40
 
41
- # Wrapp callable to allow reading imported files
42
- callable_func = artifacts_reader_wrapper(callable_func)
41
+ if imports:
42
+ # Wrapp callable to allow reading imported files
43
+ callable_func = artifacts_reader_wrapper(callable_func)
43
44
 
44
45
  if is_arrow_enabled:
45
46
  callable_func = spark_compatible_udtf_wrapper_with_arrow(
@@ -2,4 +2,4 @@
2
2
  #
3
3
  # Copyright (c) 2012-2025 Snowflake Computing Inc. All rights reserved.
4
4
  #
5
- VERSION = (0,28,0)
5
+ VERSION = (0,28,1)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: snowpark-connect
3
- Version: 0.28.0
3
+ Version: 0.28.1
4
4
  Summary: Snowpark Connect for Spark
5
5
  Author: Snowflake, Inc
6
6
  License: Apache License, Version 2.0
@@ -1,6 +1,6 @@
1
1
  snowflake/snowpark_connect/__init__.py,sha256=Sml4x1LTNnxZyw6nnDeJrZWUi3eUAR46Rsw6N-wHUSA,605
2
2
  snowflake/snowpark_connect/column_name_handler.py,sha256=_Z2j4teR_3nsCLjMxhyChGmds_1v6tP51OfkEpmWXWk,27164
3
- snowflake/snowpark_connect/config.py,sha256=vfuM2TBuuoBe_y9Y_utPelU-9hLoVE-lJszmN8vT5qw,28145
3
+ snowflake/snowpark_connect/config.py,sha256=B2TeNqpfFy2ObjuUhgBNOhQ0Knxc7ZYiI7doOSkuSFw,28146
4
4
  snowflake/snowpark_connect/constants.py,sha256=FBDxNUxdqWxnf6d5-eanHlYdFFyQqCqvNyZG-uOiO6Q,598
5
5
  snowflake/snowpark_connect/control_server.py,sha256=mz3huYo84hgqUB6maZxu3LYyGq7vVL1nv7-7-MjuSYY,1956
6
6
  snowflake/snowpark_connect/dataframe_container.py,sha256=0ozyUXrWErzM7Gltnb-i2o5ZyXVVeT_HCqpuYliQXwc,8798
@@ -13,7 +13,7 @@ snowflake/snowpark_connect/start_server.py,sha256=udegO0rk2FeSnXsIcCIYQW3VRlGDjB
13
13
  snowflake/snowpark_connect/tcm.py,sha256=ftncZFbVO-uyWMhF1_HYKQykB7KobHEYoyQsYbQj1EM,203
14
14
  snowflake/snowpark_connect/type_mapping.py,sha256=6Hg-h1iVzVB_FnwG3Sjl-UGr2Itrs4LxVb2Pz5Ue-YA,41566
15
15
  snowflake/snowpark_connect/typed_column.py,sha256=Tavii8b4zMj5IWOvN6tlOVmC80W6eQct0pC_tF2nlhU,3867
16
- snowflake/snowpark_connect/version.py,sha256=h25IrA984uyrAk0fG8Iy3TJC7b-VG1YTDkoW-sfpxDU,118
16
+ snowflake/snowpark_connect/version.py,sha256=174Rf6GxIIk1u2aUG821BNtN-OKC59qCc6Nc3xldSWw,118
17
17
  snowflake/snowpark_connect/analyze_plan/__init__.py,sha256=xsIE96jDASko3F-MeNf4T4Gg5ufthS8CejeiJDfri0M,76
18
18
  snowflake/snowpark_connect/analyze_plan/map_tree_string.py,sha256=Q3ZD-Z7uForrF7W3mSAjwaiEcIv2KDXr5jPfVbromVg,1470
19
19
  snowflake/snowpark_connect/error/__init__.py,sha256=oQo6k4zztLmNF1c5IvJLcS99J6RWY9KBTN3RJ2pKimg,249
@@ -21,7 +21,7 @@ snowflake/snowpark_connect/error/error_mapping.py,sha256=vdnLOU1Sqpocpu_uCXjfhiv
21
21
  snowflake/snowpark_connect/error/error_utils.py,sha256=Sb_p5dsrFZsLrR9B_Tp8d80Z6KcPtz9OM40lNbgsgRI,14863
22
22
  snowflake/snowpark_connect/error/exceptions.py,sha256=EOnTDiJZuVJ9dNBzy5cK0OBprbYCD3gWTCCLITjd1mY,677
23
23
  snowflake/snowpark_connect/execute_plan/__init__.py,sha256=xsIE96jDASko3F-MeNf4T4Gg5ufthS8CejeiJDfri0M,76
24
- snowflake/snowpark_connect/execute_plan/map_execution_command.py,sha256=Sm3xF9-pQJMmPQY97-BXxfV7oytY_aPLViBDOeE34nQ,10354
24
+ snowflake/snowpark_connect/execute_plan/map_execution_command.py,sha256=j3XjBAwqOHY3D2cwu7Ez2jSZq57UUbal-1ynwXnrOHM,9355
25
25
  snowflake/snowpark_connect/execute_plan/map_execution_root.py,sha256=wFxeSxtuQY1OmI_BRLOzudrGwkJsmbBUOFQfsxxvRrU,7461
26
26
  snowflake/snowpark_connect/execute_plan/utils.py,sha256=OsjEd-WnQEX2oNVvlzGR6rpJVYyfxx1LACP09k1Y4lk,7830
27
27
  snowflake/snowpark_connect/expression/__init__.py,sha256=xsIE96jDASko3F-MeNf4T4Gg5ufthS8CejeiJDfri0M,76
@@ -408,7 +408,7 @@ snowflake/snowpark_connect/relation/map_relation.py,sha256=etQ_SGXAAXNZxqfG6ouh6
408
408
  snowflake/snowpark_connect/relation/map_row_ops.py,sha256=x1Jqircy4I0iiSljx3zbq0YxwGvGzPcXIY8_nhtl2PM,30528
409
409
  snowflake/snowpark_connect/relation/map_sample_by.py,sha256=8ALQbeUsB89sI3uiUFqG3w1A4TtOzOAL4umdKp6-c38,1530
410
410
  snowflake/snowpark_connect/relation/map_show_string.py,sha256=GgKg0qp1pGqSC7TuFedTU4IYaIm-Fx23OJ1LfkcGOHw,3382
411
- snowflake/snowpark_connect/relation/map_sql.py,sha256=BvGVUG2fH_oyym8DVoMXnJy4Zzi_hSy-wa-x1xcapTE,101175
411
+ snowflake/snowpark_connect/relation/map_sql.py,sha256=7LeobApI0Pfvb1D2rSJYFMjyD6PMJ8Ig4Frcxv_k0Bc,104069
412
412
  snowflake/snowpark_connect/relation/map_stats.py,sha256=kqRYvix8RfluTKx1cAy9JhBUv6arYQHgfxpP1R4QwBM,13985
413
413
  snowflake/snowpark_connect/relation/map_subquery_alias.py,sha256=rHgE9XUzuWWkjNPtJz3Sxzz2aFo690paHKZh9frqPXk,1456
414
414
  snowflake/snowpark_connect/relation/map_udtf.py,sha256=cfDnbZ3TRJ6eb0EVResu6GL-OwQpaEabWLbrhgWnkRw,13316
@@ -416,7 +416,7 @@ snowflake/snowpark_connect/relation/stage_locator.py,sha256=c30Z4N_xFavaL5XhIl9s
416
416
  snowflake/snowpark_connect/relation/utils.py,sha256=AhE58g0Zz2DWY9gW4JnB_iBU-r4RMnWCj4okQdHSz_4,8398
417
417
  snowflake/snowpark_connect/relation/catalogs/__init__.py,sha256=0yJ5Nfg7SIxudI0P7_U5EWPyiTpkMet8tSq-IwutSZo,265
418
418
  snowflake/snowpark_connect/relation/catalogs/abstract_spark_catalog.py,sha256=wujeuKotJZRzpPp_WqVB9TkfprFU2tIXTQJk_eFOR0o,9826
419
- snowflake/snowpark_connect/relation/catalogs/snowflake_catalog.py,sha256=bsE4XSYd0AjMTAQh8M6NIiOidlCylcQOaIFjGm0EkRw,21802
419
+ snowflake/snowpark_connect/relation/catalogs/snowflake_catalog.py,sha256=e9rXL0DOAf4VBZfa-idqehLAEZoCgIoE4IDiLVvR4dQ,28475
420
420
  snowflake/snowpark_connect/relation/catalogs/utils.py,sha256=AgiBkK9Xwm9ZyjsKZYK9eTb4YZ9C5dAimD9DLlft-tY,1753
421
421
  snowflake/snowpark_connect/relation/read/__init__.py,sha256=5J3IOTKu4Qmenouz1Oz_bUu_4c6KpxtaC63mPUGLyeY,132
422
422
  snowflake/snowpark_connect/relation/read/jdbc_read_dbapi.py,sha256=nwvBYwdDTRQc8ljt4CLLaVl5M2uei1TDICJkz7CDSG8,24608
@@ -426,24 +426,24 @@ snowflake/snowpark_connect/relation/read/map_read_jdbc.py,sha256=rfuf2Gp89v-9YFh
426
426
  snowflake/snowpark_connect/relation/read/map_read_json.py,sha256=z6xdXNCFOfRbZ-6J4QQ2wB7ad6vEm8S1H7T3Xx5CBck,12371
427
427
  snowflake/snowpark_connect/relation/read/map_read_parquet.py,sha256=VIUR5GWP6DBaYCDUsrDJzHQOM3y4hIbtWiCx0f4mczs,7168
428
428
  snowflake/snowpark_connect/relation/read/map_read_socket.py,sha256=yg7aO7jXMOyLQ9k3-abywNN2VAgvtvix2V8LsR_XQO4,2267
429
- snowflake/snowpark_connect/relation/read/map_read_table.py,sha256=gdUgdXVNKugLFHec2guP0eRd4Lu-XUOujLvyCKvrr7w,4354
429
+ snowflake/snowpark_connect/relation/read/map_read_table.py,sha256=mlbC4mbsV4_mHxYpxkOJ8nr8aNwEh2xBzfzC07XXaCQ,6607
430
430
  snowflake/snowpark_connect/relation/read/map_read_text.py,sha256=Vcm6EY3gyRPucdAXcXcu2mREcgq4N_4h3NDKNPhqsHY,3434
431
431
  snowflake/snowpark_connect/relation/read/reader_config.py,sha256=PMh1R5IjqqTwiAAqvDRhnTx8Jxnhq8wVCmpZRqqKD3E,16437
432
432
  snowflake/snowpark_connect/relation/read/utils.py,sha256=rIIM6d2WXHh7MLGyHNiRc9tS8b0dmyFQr7rHepIYJOU,4111
433
433
  snowflake/snowpark_connect/relation/write/__init__.py,sha256=xsIE96jDASko3F-MeNf4T4Gg5ufthS8CejeiJDfri0M,76
434
434
  snowflake/snowpark_connect/relation/write/jdbc_write_dbapi.py,sha256=GI9FyGZuQQNV-6Q8Ob-Xr0im3iAPdH-Jkyx8bjwbOuE,11931
435
- snowflake/snowpark_connect/relation/write/map_write.py,sha256=zHWjjzYxj4rNPzybFYPnCK3mUSJMjmXv2FMiMAG0krY,43014
435
+ snowflake/snowpark_connect/relation/write/map_write.py,sha256=swxSLYq4P1B3Yj7Spruw-vaBE1z-Q-er4iScuPqVyxc,45606
436
436
  snowflake/snowpark_connect/relation/write/map_write_jdbc.py,sha256=1nOWRgjtZzfRwnSRGFP9V6mqBVlGhSBr2KHGHbe4JMU,1404
437
437
  snowflake/snowpark_connect/resources/java_udfs-1.0-SNAPSHOT.jar,sha256=tVyOp6tXxu9nm6SDufwQiGzfH3pnuh_7PowsMZxOolY,9773
438
438
  snowflake/snowpark_connect/utils/__init__.py,sha256=xsIE96jDASko3F-MeNf4T4Gg5ufthS8CejeiJDfri0M,76
439
439
  snowflake/snowpark_connect/utils/artifacts.py,sha256=TkHZ2uNfZiphgtG91V1_c_h9yP9dP677BXUMymboCss,2498
440
440
  snowflake/snowpark_connect/utils/cache.py,sha256=bAyoNBW6Z1ui9BuppDywbQeG6fdju4L-owFHzySOTnk,3382
441
441
  snowflake/snowpark_connect/utils/concurrent.py,sha256=BTbUmvupLzUSRd6L7kKk9yIXFdqlDOkXebVMaECRD-A,3653
442
- snowflake/snowpark_connect/utils/context.py,sha256=W9j9eC-lbGp7tfXWhnvI88CVOcLGspYEhEgxGPYVbYE,13288
442
+ snowflake/snowpark_connect/utils/context.py,sha256=yJ0pun5I69xu3__s39XX5LdQjqIEezFCgo1A-FhiWJ8,13829
443
443
  snowflake/snowpark_connect/utils/describe_query_cache.py,sha256=HByBsP-XJ1PAk414OQtwIqcFnqpC2zq-ZK_YxR4O5gg,9100
444
444
  snowflake/snowpark_connect/utils/env_utils.py,sha256=g__Uio5ae20Tm1evahIHdJUXQYPmjNUT_kYPSIy5JDU,1488
445
445
  snowflake/snowpark_connect/utils/external_udxf_cache.py,sha256=eSZHMbjTxnkg78IlbG5P1Vno6j5ag_FSI0c4Xi2UyPs,1044
446
- snowflake/snowpark_connect/utils/identifiers.py,sha256=A65-aQpuhTjOjGv0cmlE8fTNCI9_ONn-AYatKb_VfGM,7635
446
+ snowflake/snowpark_connect/utils/identifiers.py,sha256=yHsOklj6SM2re5auhSSMLPSvCYhpR8Cgy-Kgevf8gDc,7810
447
447
  snowflake/snowpark_connect/utils/interrupt.py,sha256=_awhdrzF1KQO-EQThneEcfMg3Zxed4p3HtMpkcAb6ek,2790
448
448
  snowflake/snowpark_connect/utils/io_utils.py,sha256=xu0AMrHy-qsY7TfdIxzWChf0hU_7bnvm3Ruk0XScRns,1781
449
449
  snowflake/snowpark_connect/utils/pandas_udtf_utils.py,sha256=3WA_9IVRZL8fnwIHo048LTg62-bPGfCDUZzYd-zjzQQ,7564
@@ -452,11 +452,12 @@ snowflake/snowpark_connect/utils/scala_udf_utils.py,sha256=RFDDMmgQ_xBWk98kdfWaw
452
452
  snowflake/snowpark_connect/utils/session.py,sha256=BWwpIbhdplQOhvY4GpbLgZ-BrJLidrWR6A_cJmGFCq8,7372
453
453
  snowflake/snowpark_connect/utils/snowpark_connect_logging.py,sha256=23bvbALGqixJ3Ap9QWM3OpcKNK-sog2mr9liSmvwqYU,1123
454
454
  snowflake/snowpark_connect/utils/telemetry.py,sha256=4TImrE4XY5rfiesHyPQQ0xtU63qnN1wUpDXUP12PAvA,22259
455
+ snowflake/snowpark_connect/utils/temporary_view_cache.py,sha256=M1czwb-uv0nP9w1meB72NR7cj0iryJc_OYFPztx5XuU,2080
455
456
  snowflake/snowpark_connect/utils/udf_cache.py,sha256=8K7kASEhvpnp-l1hjzovjyboUzKctDq7PiGXRcNv6Lg,12125
456
457
  snowflake/snowpark_connect/utils/udf_helper.py,sha256=g-TxTs4ARyJWYgADrosfQQG-ykBBQdm1g5opslxJq_E,12563
457
- snowflake/snowpark_connect/utils/udf_utils.py,sha256=pxERcJKum2M5jHxPqsl1NgHFAqZV4RxoEnSLxJV5ups,12009
458
+ snowflake/snowpark_connect/utils/udf_utils.py,sha256=aQX58P2f9WuJDAuoT5Ag5MNTZVxcx4NIZg_UJhcfW7k,12037
458
459
  snowflake/snowpark_connect/utils/udtf_helper.py,sha256=9B_1iOckfFXQfVv-UHerIJ32fDd4qucKaHGqxtBEi4w,14969
459
- snowflake/snowpark_connect/utils/udtf_utils.py,sha256=wHO5V0BXRQOLqAYos1vGt8bbdH7jBvD2gwspWywjTtY,33110
460
+ snowflake/snowpark_connect/utils/udtf_utils.py,sha256=7pYIOHUn7BLzQPqOqGaBOlV4fOnVIrWpd61quYuZ9PI,33134
460
461
  snowflake/snowpark_connect/utils/udxf_import_utils.py,sha256=pPtcaGsyh0tUdy0aAvNqTj04jqPKlEcGmvaZDP9O8Gc,536
461
462
  snowflake/snowpark_connect/utils/xxhash64.py,sha256=ysJRxhBPf25LeNhM1RK_H36MWl6q6C6vBRHa-jIna_A,7477
462
463
  snowflake/snowpark_decoder/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -464,17 +465,17 @@ snowflake/snowpark_decoder/dp_session.py,sha256=HIr3TfKgYl5zqaGR5xpFU9ZVkcaTB9I8
464
465
  snowflake/snowpark_decoder/spark_decoder.py,sha256=EQiCvBiqB736Bc17o3gnYGtcYVcyfGxroO5e1kbe1Co,2885
465
466
  snowflake/snowpark_decoder/_internal/proto/generated/DataframeProcessorMsg_pb2.py,sha256=2eSDqeyfMvmIJ6_rF663DrEe1dg_anrP4OpVJNTJHaQ,2598
466
467
  snowflake/snowpark_decoder/_internal/proto/generated/DataframeProcessorMsg_pb2.pyi,sha256=aIH23k52bXdw5vO3RtM5UcOjDPaWsJFx1SRUSk3qOK8,6142
467
- snowpark_connect-0.28.0.data/scripts/snowpark-connect,sha256=yZ94KqbWACxnwV8mpg8NjILvvRNjnF8B3cs3ZFNuIM4,1546
468
- snowpark_connect-0.28.0.data/scripts/snowpark-session,sha256=NMAHSonTo-nmOZSkQNlszUC0jLJ8QWEDUsUmMe2UAOw,190
469
- snowpark_connect-0.28.0.data/scripts/snowpark-submit,sha256=Zd98H9W_d0dIqMSkQLdHyW5G3myxF0t4c3vNBt2nD6A,12056
470
- snowpark_connect-0.28.0.dist-info/licenses/LICENSE-binary,sha256=fmBlX39HwTlBUyiKEznaLZGuxQy-7ndLLG_rTXjF02Y,22916
471
- snowpark_connect-0.28.0.dist-info/licenses/LICENSE.txt,sha256=Ff9cPv4xu0z7bnMTHzo4vDncOShsy33w4oJMA2xjn6c,11365
472
- snowpark_connect-0.28.0.dist-info/licenses/NOTICE-binary,sha256=elMF8brgGNJwOz8YdorzBF6-U8ZhR8F-77FfGkZng7U,57843
468
+ snowpark_connect-0.28.1.data/scripts/snowpark-connect,sha256=yZ94KqbWACxnwV8mpg8NjILvvRNjnF8B3cs3ZFNuIM4,1546
469
+ snowpark_connect-0.28.1.data/scripts/snowpark-session,sha256=NMAHSonTo-nmOZSkQNlszUC0jLJ8QWEDUsUmMe2UAOw,190
470
+ snowpark_connect-0.28.1.data/scripts/snowpark-submit,sha256=Zd98H9W_d0dIqMSkQLdHyW5G3myxF0t4c3vNBt2nD6A,12056
471
+ snowpark_connect-0.28.1.dist-info/licenses/LICENSE-binary,sha256=fmBlX39HwTlBUyiKEznaLZGuxQy-7ndLLG_rTXjF02Y,22916
472
+ snowpark_connect-0.28.1.dist-info/licenses/LICENSE.txt,sha256=Ff9cPv4xu0z7bnMTHzo4vDncOShsy33w4oJMA2xjn6c,11365
473
+ snowpark_connect-0.28.1.dist-info/licenses/NOTICE-binary,sha256=elMF8brgGNJwOz8YdorzBF6-U8ZhR8F-77FfGkZng7U,57843
473
474
  spark/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
474
475
  spark/connect/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
475
476
  spark/connect/envelope_pb2.py,sha256=7Gc6OUA3vaCuTCIKamb_Iiw7W9jPTcWNEv1im20eWHM,2726
476
477
  spark/connect/envelope_pb2.pyi,sha256=VXTJSPpcxzB_dWqVdvPY4KkPhJfh0WmkX7SNHWoLhx0,3358
477
- snowpark_connect-0.28.0.dist-info/METADATA,sha256=ZYd6i8wRgv9zajEKwzZ_v8jMcLYvPwN4yOGeLF6pj7g,1594
478
- snowpark_connect-0.28.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
479
- snowpark_connect-0.28.0.dist-info/top_level.txt,sha256=ExnWqVpoTHRG99fu_AxXZVOz8c-De7nNu0yFCGylM8I,16
480
- snowpark_connect-0.28.0.dist-info/RECORD,,
478
+ snowpark_connect-0.28.1.dist-info/METADATA,sha256=VYJ19DYnoBCToiaU__mf5hnm3hBrzN0bBv89QDVre8E,1594
479
+ snowpark_connect-0.28.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
480
+ snowpark_connect-0.28.1.dist-info/top_level.txt,sha256=ExnWqVpoTHRG99fu_AxXZVOz8c-De7nNu0yFCGylM8I,16
481
+ snowpark_connect-0.28.1.dist-info/RECORD,,