Skip to content

Commit f63d181

Browse files
committed
fix(spark): drop DataFrame temp views after inserting from them
The Spark and Databricks adapters register a temp view for every DataFrame insert but never drop it, so long sessions accumulate views and keep the DataFrames alive on the driver. Drop the view in the source query cleanup, as the other adapters do for their temp tables. Fixes #6125 Signed-off-by: JanakiRamChittineni <341781521+raams95@users.noreply.github.com>
1 parent cc8d8b0 commit f63d181

4 files changed

Lines changed: 77 additions & 6 deletions

File tree

‎sqlmesh/core/engine_adapter/databricks.py‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -258,13 +258,17 @@ def _df_to_source_queries(
258258
df, target_columns_to_types, source_columns=source_columns
259259
)
260260

261+
temp_table = self._get_temp_table(target_table or "spark", table_only=True)
262+
261263
def query_factory() -> Query:
262-
temp_table = self._get_temp_table(target_table or "spark", table_only=True)
263264
pyspark_df.createOrReplaceTempView(temp_table.sql(dialect=self.dialect))
264265
self._connection_pool.set_attribute("use_spark_engine_adapter", True)
265266
return exp.select(*self._select_columns(target_columns_to_types)).from_(temp_table)
266267

267-
return [SourceQuery(query_factory=query_factory)]
268+
def cleanup() -> None:
269+
self.spark.catalog.dropTempView(temp_table.name)
270+
271+
return [SourceQuery(query_factory=query_factory, cleanup_func=cleanup)]
268272

269273
def _fetch_native_df(
270274
self, query: t.Union[exp.Expr, str], quote_identifiers: bool = False

‎sqlmesh/core/engine_adapter/spark.py‎

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -282,14 +282,18 @@ def _df_to_source_queries(
282282
source_columns: t.Optional[t.List[str]] = None,
283283
) -> t.List[SourceQuery]:
284284
df = self._ensure_pyspark_df(df, target_columns_to_types, source_columns=source_columns)
285+
temp_table = self._get_temp_table(target_table or "spark", table_only=True)
286+
temp_view_name = temp_table.sql(dialect=self.dialect)
287+
temp_table.set("db", "global_temp")
285288

286289
def query_factory() -> Query:
287-
temp_table = self._get_temp_table(target_table or "spark", table_only=True)
288-
df.createOrReplaceGlobalTempView(temp_table.sql(dialect=self.dialect)) # type: ignore
289-
temp_table.set("db", "global_temp")
290+
df.createOrReplaceGlobalTempView(temp_view_name) # type: ignore
290291
return exp.select(*self._select_columns(target_columns_to_types)).from_(temp_table)
291292

292-
return [SourceQuery(query_factory=query_factory)]
293+
def cleanup() -> None:
294+
self.spark.catalog.dropGlobalTempView(temp_table.name)
295+
296+
return [SourceQuery(query_factory=query_factory, cleanup_func=cleanup)]
293297

294298
def _ensure_pyspark_df(
295299
self,

‎tests/core/engine_adapter/test_databricks.py‎

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -837,3 +837,41 @@ def test_databricks_connect_cluster_id_not_overridden_by_falsy_serverless(
837837
token="mytoken",
838838
cluster_id="0123-456789-mycluster",
839839
)
840+
841+
842+
def test_df_source_query_drops_temp_view(
843+
mocker: MockFixture, make_mocked_engine_adapter: t.Callable
844+
):
845+
mocker.patch(
846+
"sqlmesh.core.engine_adapter.databricks.DatabricksEngineAdapter.set_current_catalog"
847+
)
848+
adapter = make_mocked_engine_adapter(DatabricksEngineAdapter, default_catalog="test_catalog")
849+
mocker.patch.object(
850+
DatabricksEngineAdapter,
851+
"_use_spark_session",
852+
new_callable=mocker.PropertyMock,
853+
return_value=True,
854+
)
855+
spark = mocker.Mock()
856+
mocker.patch.object(
857+
DatabricksEngineAdapter, "spark", new_callable=mocker.PropertyMock, return_value=spark
858+
)
859+
pyspark_df = mocker.Mock()
860+
mocker.patch.object(adapter, "_ensure_pyspark_df", return_value=pyspark_df)
861+
mocker.patch.object(
862+
adapter,
863+
"_get_temp_table",
864+
return_value=exp.to_table("`temp_test_abcdefgh`", dialect="databricks"),
865+
)
866+
867+
source_queries = adapter._df_to_source_queries(
868+
pyspark_df, {"a": exp.DataType.build("int")}, batch_size=0, target_table="db.test"
869+
)
870+
assert len(source_queries) == 1
871+
872+
with source_queries[0] as query:
873+
pyspark_df.createOrReplaceTempView.assert_called_once_with("`temp_test_abcdefgh`")
874+
assert query.sql(dialect="databricks") == "SELECT `a` FROM `temp_test_abcdefgh`"
875+
spark.catalog.dropTempView.assert_not_called()
876+
877+
spark.catalog.dropTempView.assert_called_once_with("temp_test_abcdefgh")

‎tests/core/engine_adapter/test_spark.py‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1106,3 +1106,28 @@ def test_get_data_object_wap_branch(make_mocked_engine_adapter: t.Callable, mock
11061106
d.schema_("sqlmesh__test", "catalog"),
11071107
{"test__test_view__630979748"},
11081108
)
1109+
1110+
1111+
def test_df_source_query_drops_global_temp_view(
1112+
make_mocked_engine_adapter: t.Callable, mocker: MockerFixture
1113+
):
1114+
adapter = make_mocked_engine_adapter(SparkEngineAdapter)
1115+
pyspark_df = mocker.Mock()
1116+
mocker.patch.object(adapter, "_ensure_pyspark_df", return_value=pyspark_df)
1117+
mocker.patch.object(
1118+
adapter,
1119+
"_get_temp_table",
1120+
return_value=exp.to_table("`temp_test_abcdefgh`", dialect="spark"),
1121+
)
1122+
1123+
source_queries = adapter._df_to_source_queries(
1124+
pyspark_df, {"a": exp.DataType.build("int")}, batch_size=0, target_table="db.test"
1125+
)
1126+
assert len(source_queries) == 1
1127+
1128+
with source_queries[0] as query:
1129+
pyspark_df.createOrReplaceGlobalTempView.assert_called_once_with("`temp_test_abcdefgh`")
1130+
assert query.sql(dialect="spark") == "SELECT `a` FROM global_temp.`temp_test_abcdefgh`"
1131+
adapter.spark.catalog.dropGlobalTempView.assert_not_called()
1132+
1133+
adapter.spark.catalog.dropGlobalTempView.assert_called_once_with("temp_test_abcdefgh")

0 commit comments

Comments
 (0)