This is an automated email from the ASF dual-hosted git repository.
cloud-fan pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/spark.git
The following commit(s) were added to refs/heads/master by this push:
new b50454d6c48b [SPARK-58149][SQL][TEST] Refactor some DSv2 tests to use
SessionQueryTest
b50454d6c48b is described below
commit b50454d6c48b88dbfb4bd1e482126e2e767b636e
Author: Matthis Gördel <[email protected]>
AuthorDate: Mon Jul 20 22:15:33 2026 +0800
[SPARK-58149][SQL][TEST] Refactor some DSv2 tests to use SessionQueryTest
### What changes were proposed in this pull request?
This PR builds upon https://github.com/apache/spark/pull/56190 and
refactors the existing classic/connect-targeting DSv2 tests to use
`SessionQueryTest`.
NOTE: this PR is stacked upon https://github.com/apache/spark/pull/56190
and WIP until that PR is merged and this PR is rebased. The actual changes are
to the files in `sql/core/src/test/scala/org/apache/spark/sql/connector/` and
to
`sql/connect/server/src/test/scala/org/apache/spark/sql/connect/DataSourceV2DataFrameConnectSuite.scala`
(currently ea62c553484b9e293677a7516d8ed0ed3e2d2ba4)
### Why are the changes needed?
This PR reduces the amount of custom test helpers.
### Does this PR introduce _any_ user-facing change?
No
### How was this patch tested?
This patch is test-only
### Was this patch authored or co-authored using generative AI tooling?
No
Closes #56708 from fwc/refactor-some-tests-to-sessionquerytest.
Lead-authored-by: Matthis Gördel <[email protected]>
Co-authored-by: Matthis Gördel <[email protected]>
Signed-off-by: Wenchen Fan <[email protected]>
---
.../DataSourceV2DataFrameConnectSuite.scala | 61 +-
.../sql/connector/DSv2CacheTableReadTests.scala | 322 +++++------
.../connector/DSv2ExternalMutationTestBase.scala | 21 +-
.../DSv2IncrementallyConstructedQueryTests.scala | 641 ++++++++++-----------
.../connector/DSv2RepeatedTableAccessTests.scala | 252 ++++----
.../DSv2TempViewWithStoredPlanTests.scala | 412 ++++++-------
.../sql/connector/DataSourceV2DataFrameSuite.scala | 19 +-
7 files changed, 819 insertions(+), 909 deletions(-)
diff --git
a/sql/connect/server/src/test/scala/org/apache/spark/sql/connect/DataSourceV2DataFrameConnectSuite.scala
b/sql/connect/server/src/test/scala/org/apache/spark/sql/connect/DataSourceV2DataFrameConnectSuite.scala
index 1a31e5f8ac1a..bbff687a16b1 100644
---
a/sql/connect/server/src/test/scala/org/apache/spark/sql/connect/DataSourceV2DataFrameConnectSuite.scala
+++
b/sql/connect/server/src/test/scala/org/apache/spark/sql/connect/DataSourceV2DataFrameConnectSuite.scala
@@ -20,7 +20,8 @@ package org.apache.spark.sql.connect
import scala.reflect.ClassTag
import org.apache.spark.SparkConf
-import org.apache.spark.sql.{DataFrame, QueryTest, Row, SparkSession}
+import org.apache.spark.sql.{classic, connect, SparkSession}
+import org.apache.spark.sql.connect.service.{SessionKey, SparkConnectService}
import org.apache.spark.sql.connector.{DSv2CacheTableReadTests,
DSv2IncrementallyConstructedQueryTests, DSv2RepeatedTableAccessTests,
DSv2TempViewWithStoredPlanTests}
import org.apache.spark.sql.connector.catalog.{CachingInMemoryTableCatalog,
InMemoryTableCatalog, NullTableIdAndNullColumnIdInMemoryTableCatalog,
NullTableIdInMemoryTableCatalog, TableCatalog}
@@ -34,7 +35,7 @@ import
org.apache.spark.sql.connector.catalog.{CachingInMemoryTableCatalog, InMe
* this class only provides the Connect-specific session, catalog access, and
result comparison.
*/
class DataSourceV2DataFrameConnectSuite
- extends SparkConnectServerTest
+ extends SessionQueryTest
with DSv2TempViewWithStoredPlanTests
with DSv2RepeatedTableAccessTests
with DSv2IncrementallyConstructedQueryTests
@@ -53,45 +54,43 @@ class DataSourceV2DataFrameConnectSuite
.set("spark.sql.catalog.nullbothidscat.copyOnLoad", "true")
override protected def testPrefix: String = "[connect] "
- override protected def isConnect: Boolean = true
- override protected def withTestSession(fn: SparkSession => Unit): Unit =
- withSession(fn)
+ // Unlike the classic suite, the connect suite shares a single server-side
session across all
+ // tests in the suite (created once by SparkSessionBinder), so catalog state
is not reset
+ // between tests. Like the classic suite's `after` block, clear the caching
connector's cache
+ // so each test starts from a clean catalog state. Without this,
+ // CachingInMemoryTableCatalog retains stale table entries (it does not
invalidate on drop), so a
+ // later test's CREATE TABLE fails with TABLE_OR_VIEW_ALREADY_EXISTS.
+ override protected def afterEach(): Unit = {
+ val serverSession = getServerSession(spark)
+ if
(serverSession.sessionState.catalogManager.isCatalogRegistered("cachingcat")) {
+ serverSession.sessionState.catalogManager
+ .catalog("cachingcat")
+ .asInstanceOf[CachingInMemoryTableCatalog]
+ .clearCache()
+ }
+ super.afterEach()
+ }
- // Cannot use QueryTest.checkAnswer directly because it accesses
df.logicalPlan,
- // df.queryExecution, and df.materializedRdd, which are not available on
Connect *client*
- // DataFrames (they throw ConnectClientUnsupportedErrors). Note: checkAnswer
IS usable from
- // Connect server tests that operate on classic server-side DataFrames, but
in this suite
- // `df` is a Connect client DataFrame returned by session.table() /
session.sql().
- // Instead, collect the rows and delegate to QueryTest.sameRows, which is
the same
- // value-based, order-agnostic comparison that checkAnswer uses internally.
- override protected def checkRows(df: => DataFrame, expected: Seq[Row]): Unit
=
- QueryTest.sameRows(expected, df.collect().toSeq).foreach(msg => fail(msg))
+ protected def getServerSession(clientSession: SparkSession):
classic.SparkSession = {
+ val connectSession = clientSession.asInstanceOf[connect.SparkSession]
+ val userId = connectSession.client.userId
+ val sessionId = connectSession.sessionId
+ val key = SessionKey(userId, sessionId)
+ SparkConnectService.sessionManager
+ .getIsolatedSessionIfPresent(key)
+ .get
+ .session
+ }
override protected def getTableCatalog[C <: TableCatalog: ClassTag](
session: SparkSession,
catalogName: String): C = {
- val serverSession = getServerSession(session)
- val catalog =
serverSession.sessionState.catalogManager.catalog(catalogName)
+ val catalog =
getServerSession(session).sessionState.catalogManager.catalog(catalogName)
val ct = implicitly[ClassTag[C]]
require(
ct.runtimeClass.isInstance(catalog),
s"Expected ${ct.runtimeClass.getName} but got
${catalog.getClass.getName}")
catalog.asInstanceOf[C]
}
-
- // No explicit clearCache() for cachingcat is needed here, unlike the
classic suite.
- // Each withSession call creates a freshly isolated SparkSession on the
server side
- // (via SparkConnectSessionManager.newIsolatedSession), and afterEach
invalidates all
- // sessions, so the CachingInMemoryTableCatalog instance is per-test.
- override protected def withTestTableAndViews(
- session: SparkSession,
- table: String,
- views: Seq[String] = Seq.empty)(fn: => Unit): Unit = {
- try { fn }
- finally {
- views.foreach(v => session.sql(s"DROP VIEW IF EXISTS $v").collect())
- session.sql(s"DROP TABLE IF EXISTS $table").collect()
- }
- }
}
diff --git
a/sql/core/src/test/scala/org/apache/spark/sql/connector/DSv2CacheTableReadTests.scala
b/sql/core/src/test/scala/org/apache/spark/sql/connector/DSv2CacheTableReadTests.scala
index ac6ffcc6ecc0..cd9673ae7283 100644
---
a/sql/core/src/test/scala/org/apache/spark/sql/connector/DSv2CacheTableReadTests.scala
+++
b/sql/core/src/test/scala/org/apache/spark/sql/connector/DSv2CacheTableReadTests.scala
@@ -17,7 +17,7 @@
package org.apache.spark.sql.connector
-import org.apache.spark.sql.{Row, SparkSession}
+import org.apache.spark.sql.Row
import org.apache.spark.sql.catalyst.InternalRow
import org.apache.spark.sql.connector.catalog.{CachingInMemoryTableCatalog,
Column, InMemoryTableCatalog, TableChange, TableInfo}
import org.apache.spark.sql.types.IntegerType
@@ -49,223 +49,209 @@ import org.apache.spark.sql.types.IntegerType
* (via the CacheManager), making a session drop+recreate scenario trivially
different from
* the external variant.
*
- * NOTE: All `session.sql(...)` calls append `.collect()` because Connect
client DataFrames
+ * NOTE: All `spark.sql(...)` calls append `.collect()` because Connect client
DataFrames
* are lazy and require an action to trigger execution. In classic mode
`.collect()` on
* DDL / DML is a no-op (these execute eagerly), so this is harmless.
*/
trait DSv2CacheTableReadTests extends DSv2ExternalMutationTestBase {
- private def assertTableCached(session: SparkSession, tableName: String):
Unit =
- assert(session.catalog.isCached(tableName))
+ private def assertTableCached(tableName: String): Unit =
+ assert(spark.catalog.isCached(tableName))
test(s"${testPrefix}SPARK-54022: cached table pinned against external data
write") {
- withTestSession { session =>
- withTestTableAndViews(session, testTable) {
- session.sql(s"CREATE TABLE $testTable (id INT, salary INT) USING
foo").collect()
- session.sql(s"INSERT INTO $testTable VALUES (1, 100)").collect()
+ withTable(testTable) {
+ spark.sql(s"CREATE TABLE $testTable (id INT, salary INT) USING
foo").collect()
+ spark.sql(s"INSERT INTO $testTable VALUES (1, 100)").collect()
- session.table(testTable).cache()
- assertTableCached(session, testTable)
- checkRows(session.table(testTable), Seq(Row(1, 100)))
+ spark.table(testTable).cache()
+ assertTableCached(testTable)
+ checkAnswer(spark.table(testTable), Seq(Row(1, 100)))
- val catalog = getTableCatalog[InMemoryTableCatalog](session, "testcat")
- externalAppend(catalog = catalog, ident = testIdent, row =
InternalRow(2, 200))
+ val catalog = getTableCatalog[InMemoryTableCatalog](spark, "testcat")
+ externalAppend(catalog = catalog, ident = testIdent, row =
InternalRow(2, 200))
- assertTableCached(session, testTable)
- checkRows(session.table(testTable), Seq(Row(1, 100)))
+ assertTableCached(testTable)
+ checkAnswer(spark.table(testTable), Seq(Row(1, 100)))
- session.sql(s"REFRESH TABLE $testTable").collect()
- assertTableCached(session, testTable)
- checkRows(session.table(testTable), Seq(Row(1, 100), Row(2, 200)))
- }
+ spark.sql(s"REFRESH TABLE $testTable").collect()
+ assertTableCached(testTable)
+ checkAnswer(spark.table(testTable), Seq(Row(1, 100), Row(2, 200)))
}
}
test(s"${testPrefix}SPARK-54022: connector w/ cache: cached table pinned, " +
"REFRESH clears both layers") {
- withTestSession { session =>
- withTestTableAndViews(session, cachingTestTable) {
- session.sql(s"CREATE TABLE $cachingTestTable (id INT, salary INT)
USING foo").collect()
- session.sql(s"INSERT INTO $cachingTestTable VALUES (1, 100)").collect()
-
- session.table(cachingTestTable).cache()
- assertTableCached(session, cachingTestTable)
- checkRows(session.table(cachingTestTable), Seq(Row(1, 100)))
-
- val catalog =
- getTableCatalog[CachingInMemoryTableCatalog](session, "cachingcat")
- externalAppend(catalog = catalog, ident = testIdent, row =
InternalRow(2, 200))
-
- // Both CacheManager and connector cache are stale: external write
invisible
- assertTableCached(session, cachingTestTable)
- checkRows(session.table(cachingTestTable), Seq(Row(1, 100)))
-
- // REFRESH TABLE calls invalidateTable (clears connector cache) and
rebuilds
- // the CacheManager entry, so the external write becomes visible.
- session.sql(s"REFRESH TABLE $cachingTestTable").collect()
- assertTableCached(session, cachingTestTable)
- checkRows(session.table(cachingTestTable), Seq(Row(1, 100), Row(2,
200)))
- }
+ withTable(cachingTestTable) {
+ spark.sql(s"CREATE TABLE $cachingTestTable (id INT, salary INT) USING
foo").collect()
+ spark.sql(s"INSERT INTO $cachingTestTable VALUES (1, 100)").collect()
+
+ spark.table(cachingTestTable).cache()
+ assertTableCached(cachingTestTable)
+ checkAnswer(spark.table(cachingTestTable), Seq(Row(1, 100)))
+
+ val catalog =
+ getTableCatalog[CachingInMemoryTableCatalog](spark, "cachingcat")
+ externalAppend(catalog = catalog, ident = testIdent, row =
InternalRow(2, 200))
+
+ // Both CacheManager and connector cache are stale: external write
invisible
+ assertTableCached(cachingTestTable)
+ checkAnswer(spark.table(cachingTestTable), Seq(Row(1, 100)))
+
+ // REFRESH TABLE calls invalidateTable (clears connector cache) and
rebuilds
+ // the CacheManager entry, so the external write becomes visible.
+ spark.sql(s"REFRESH TABLE $cachingTestTable").collect()
+ assertTableCached(cachingTestTable)
+ checkAnswer(spark.table(cachingTestTable), Seq(Row(1, 100), Row(2, 200)))
}
}
test(s"${testPrefix}SPARK-54022: session write invalidates cache, " +
"then external write invisible") {
- withTestSession { session =>
- withTestTableAndViews(session, testTable) {
- session.sql(s"CREATE TABLE $testTable (id INT, salary INT) USING
foo").collect()
- session.sql(s"INSERT INTO $testTable VALUES (1, 100)").collect()
+ withTable(testTable) {
+ spark.sql(s"CREATE TABLE $testTable (id INT, salary INT) USING
foo").collect()
+ spark.sql(s"INSERT INTO $testTable VALUES (1, 100)").collect()
- session.table(testTable).cache()
- assertTableCached(session, testTable)
- checkRows(session.table(testTable), Seq(Row(1, 100)))
+ spark.table(testTable).cache()
+ assertTableCached(testTable)
+ checkAnswer(spark.table(testTable), Seq(Row(1, 100)))
- session.sql(s"INSERT INTO $testTable VALUES (2, 200)").collect()
- assertTableCached(session, testTable)
- checkRows(session.table(testTable), Seq(Row(1, 100), Row(2, 200)))
+ spark.sql(s"INSERT INTO $testTable VALUES (2, 200)").collect()
+ assertTableCached(testTable)
+ checkAnswer(spark.table(testTable), Seq(Row(1, 100), Row(2, 200)))
- val catalog = getTableCatalog[InMemoryTableCatalog](session, "testcat")
- externalAppend(catalog = catalog, ident = testIdent, row =
InternalRow(3, 300))
+ val catalog = getTableCatalog[InMemoryTableCatalog](spark, "testcat")
+ externalAppend(catalog = catalog, ident = testIdent, row =
InternalRow(3, 300))
- assertTableCached(session, testTable)
- checkRows(session.table(testTable), Seq(Row(1, 100), Row(2, 200)))
+ assertTableCached(testTable)
+ checkAnswer(spark.table(testTable), Seq(Row(1, 100), Row(2, 200)))
- session.sql(s"REFRESH TABLE $testTable").collect()
- assertTableCached(session, testTable)
- checkRows(session.table(testTable), Seq(Row(1, 100), Row(2, 200),
Row(3, 300)))
- }
+ spark.sql(s"REFRESH TABLE $testTable").collect()
+ assertTableCached(testTable)
+ checkAnswer(spark.table(testTable), Seq(Row(1, 100), Row(2, 200), Row(3,
300)))
}
}
test(s"${testPrefix}SPARK-54022: cached table pinned against external schema
change") {
- withTestSession { session =>
- withTestTableAndViews(session, testTable) {
- session.sql(s"CREATE TABLE $testTable (id INT, salary INT) USING
foo").collect()
- session.sql(s"INSERT INTO $testTable VALUES (1, 100)").collect()
-
- session.table(testTable).cache()
- assertTableCached(session, testTable)
- checkRows(session.table(testTable), Seq(Row(1, 100)))
-
- val catalog = getTableCatalog[InMemoryTableCatalog](session, "testcat")
- val addCol = TableChange.addColumn(Array("new_column"), IntegerType,
true)
- catalog.alterTable(testIdent, addCol)
- externalAppend(catalog = catalog, ident = testIdent, row =
InternalRow(2, 200, -1))
-
- assertTableCached(session, testTable)
- checkRows(session.table(testTable), Seq(Row(1, 100)))
-
- session.sql(s"REFRESH TABLE $testTable").collect()
- assertTableCached(session, testTable)
- checkRows(session.table(testTable), Seq(Row(1, 100, null), Row(2, 200,
-1)))
- }
+ withTable(testTable) {
+ spark.sql(s"CREATE TABLE $testTable (id INT, salary INT) USING
foo").collect()
+ spark.sql(s"INSERT INTO $testTable VALUES (1, 100)").collect()
+
+ spark.table(testTable).cache()
+ assertTableCached(testTable)
+ checkAnswer(spark.table(testTable), Seq(Row(1, 100)))
+
+ val catalog = getTableCatalog[InMemoryTableCatalog](spark, "testcat")
+ val addCol = TableChange.addColumn(Array("new_column"), IntegerType,
true)
+ catalog.alterTable(testIdent, addCol)
+ externalAppend(catalog = catalog, ident = testIdent, row =
InternalRow(2, 200, -1))
+
+ assertTableCached(testTable)
+ checkAnswer(spark.table(testTable), Seq(Row(1, 100)))
+
+ spark.sql(s"REFRESH TABLE $testTable").collect()
+ assertTableCached(testTable)
+ checkAnswer(spark.table(testTable), Seq(Row(1, 100, null), Row(2, 200,
-1)))
}
}
test(s"${testPrefix}SPARK-54022: session schema change invalidates cache, " +
"external write invisible") {
- withTestSession { session =>
- withTestTableAndViews(session, testTable) {
- session.sql(s"CREATE TABLE $testTable (id INT, salary INT) USING
foo").collect()
- session.sql(s"INSERT INTO $testTable VALUES (1, 100)").collect()
+ withTable(testTable) {
+ spark.sql(s"CREATE TABLE $testTable (id INT, salary INT) USING
foo").collect()
+ spark.sql(s"INSERT INTO $testTable VALUES (1, 100)").collect()
- session.table(testTable).cache()
- assertTableCached(session, testTable)
- checkRows(session.table(testTable), Seq(Row(1, 100)))
+ spark.table(testTable).cache()
+ assertTableCached(testTable)
+ checkAnswer(spark.table(testTable), Seq(Row(1, 100)))
- session.sql(s"ALTER TABLE $testTable ADD COLUMN new_column
INT").collect()
- assertTableCached(session, testTable)
- checkRows(session.table(testTable), Seq(Row(1, 100, null)))
+ spark.sql(s"ALTER TABLE $testTable ADD COLUMN new_column INT").collect()
+ assertTableCached(testTable)
+ checkAnswer(spark.table(testTable), Seq(Row(1, 100, null)))
- val catalog = getTableCatalog[InMemoryTableCatalog](session, "testcat")
- externalAppend(catalog = catalog, ident = testIdent, row =
InternalRow(2, 200, -1))
+ val catalog = getTableCatalog[InMemoryTableCatalog](spark, "testcat")
+ externalAppend(catalog = catalog, ident = testIdent, row =
InternalRow(2, 200, -1))
- assertTableCached(session, testTable)
- checkRows(session.table(testTable), Seq(Row(1, 100, null)))
+ assertTableCached(testTable)
+ checkAnswer(spark.table(testTable), Seq(Row(1, 100, null)))
- session.sql(s"REFRESH TABLE $testTable").collect()
- assertTableCached(session, testTable)
- checkRows(session.table(testTable), Seq(Row(1, 100, null), Row(2, 200,
-1)))
- }
+ spark.sql(s"REFRESH TABLE $testTable").collect()
+ assertTableCached(testTable)
+ checkAnswer(spark.table(testTable), Seq(Row(1, 100, null), Row(2, 200,
-1)))
}
}
test(s"${testPrefix}SPARK-54022: cached table after external drop and " +
"recreate sees empty table") {
- withTestSession { session =>
- withTestTableAndViews(session, testTable) {
- session.sql(s"CREATE TABLE $testTable (id INT, salary INT) USING
foo").collect()
- session.sql(s"INSERT INTO $testTable VALUES (1, 100)").collect()
-
- session.table(testTable).cache()
- assertTableCached(session, testTable)
- checkRows(session.table(testTable), Seq(Row(1, 100)))
-
- val catalog = getTableCatalog[InMemoryTableCatalog](session, "testcat")
- val originalTableId = catalog.loadTable(testIdent).id
-
- catalog.dropTable(testIdent)
- catalog.createTable(
- testIdent,
- new TableInfo.Builder()
- .withColumns(Array(
- Column.create("id", IntegerType),
- Column.create("salary", IntegerType)))
- .build())
-
- val newTableId = catalog.loadTable(testIdent).id
- assert(originalTableId != newTableId)
-
- val result = session.table(testTable)
- assert(result.schema.fieldNames.toSeq == Seq("id", "salary"))
- checkRows(result, Seq.empty)
-
- // External drop+recreate produces a new table identity, so the prior
cache entry
- // is unreachable via name lookup (unlike external write/schema change
where the
- // cache stays pinned).
- assert(!session.catalog.isCached(testTable))
-
- session.sql(s"REFRESH TABLE $testTable").collect()
- checkRows(session.table(testTable), Seq.empty)
- }
+ withTable(testTable) {
+ spark.sql(s"CREATE TABLE $testTable (id INT, salary INT) USING
foo").collect()
+ spark.sql(s"INSERT INTO $testTable VALUES (1, 100)").collect()
+
+ spark.table(testTable).cache()
+ assertTableCached(testTable)
+ checkAnswer(spark.table(testTable), Seq(Row(1, 100)))
+
+ val catalog = getTableCatalog[InMemoryTableCatalog](spark, "testcat")
+ val originalTableId = catalog.loadTable(testIdent).id
+
+ catalog.dropTable(testIdent)
+ catalog.createTable(
+ testIdent,
+ new TableInfo.Builder()
+ .withColumns(Array(
+ Column.create("id", IntegerType),
+ Column.create("salary", IntegerType)))
+ .build())
+
+ val newTableId = catalog.loadTable(testIdent).id
+ assert(originalTableId != newTableId)
+
+ val result = spark.table(testTable)
+ assert(result.schema.fieldNames.toSeq == Seq("id", "salary"))
+ checkAnswer(result, Seq.empty)
+
+ // External drop+recreate produces a new table identity, so the prior
cache entry
+ // is unreachable via name lookup (unlike external write/schema change
where the
+ // cache stays pinned).
+ assert(!spark.catalog.isCached(testTable))
+
+ spark.sql(s"REFRESH TABLE $testTable").collect()
+ checkAnswer(spark.table(testTable), Seq.empty)
}
}
test(s"${testPrefix}SPARK-54022: connector w/ cache: cached table stale
after " +
"external drop and recreate") {
- withTestSession { session =>
- withTestTableAndViews(session, cachingTestTable) {
- session.sql(s"CREATE TABLE $cachingTestTable (id INT, salary INT)
USING foo").collect()
- session.sql(s"INSERT INTO $cachingTestTable VALUES (1, 100)").collect()
-
- session.table(cachingTestTable).cache()
- assertTableCached(session, cachingTestTable)
- checkRows(session.table(cachingTestTable), Seq(Row(1, 100)))
-
- val catalog =
- getTableCatalog[CachingInMemoryTableCatalog](session, "cachingcat")
- val originalTableId = catalog.loadTable(testIdent).id
-
- catalog.dropTable(testIdent)
- catalog.createTable(
- testIdent,
- new TableInfo.Builder()
- .withColumns(Array(
- Column.create("id", IntegerType),
- Column.create("salary", IntegerType)))
- .build())
-
- // CachingInMemoryTableCatalog does not invalidate on drop/create, so
loadTable
- // still returns the old cached table object. CacheManager still
matches and
- // serves the stale cached data.
- assertTableCached(session, cachingTestTable)
- checkRows(session.table(cachingTestTable), Seq(Row(1, 100)))
-
- // REFRESH TABLE calls invalidateTable (clears connector cache) and
rebuilds
- // the CacheManager entry, so the new empty table becomes visible.
- session.sql(s"REFRESH TABLE $cachingTestTable").collect()
- checkRows(session.table(cachingTestTable), Seq.empty)
- }
+ withTable(cachingTestTable) {
+ spark.sql(s"CREATE TABLE $cachingTestTable (id INT, salary INT) USING
foo").collect()
+ spark.sql(s"INSERT INTO $cachingTestTable VALUES (1, 100)").collect()
+
+ spark.table(cachingTestTable).cache()
+ assertTableCached(cachingTestTable)
+ checkAnswer(spark.table(cachingTestTable), Seq(Row(1, 100)))
+
+ val catalog =
+ getTableCatalog[CachingInMemoryTableCatalog](spark, "cachingcat")
+ val originalTableId = catalog.loadTable(testIdent).id
+
+ catalog.dropTable(testIdent)
+ catalog.createTable(
+ testIdent,
+ new TableInfo.Builder()
+ .withColumns(Array(
+ Column.create("id", IntegerType),
+ Column.create("salary", IntegerType)))
+ .build())
+
+ // CachingInMemoryTableCatalog does not invalidate on drop/create, so
loadTable
+ // still returns the old cached table object. CacheManager still matches
and
+ // serves the stale cached data.
+ assertTableCached(cachingTestTable)
+ checkAnswer(spark.table(cachingTestTable), Seq(Row(1, 100)))
+
+ // REFRESH TABLE calls invalidateTable (clears connector cache) and
rebuilds
+ // the CacheManager entry, so the new empty table becomes visible.
+ spark.sql(s"REFRESH TABLE $cachingTestTable").collect()
+ checkAnswer(spark.table(cachingTestTable), Seq.empty)
}
}
}
diff --git
a/sql/core/src/test/scala/org/apache/spark/sql/connector/DSv2ExternalMutationTestBase.scala
b/sql/core/src/test/scala/org/apache/spark/sql/connector/DSv2ExternalMutationTestBase.scala
index 0b2a50534447..73c69f8a9de4 100644
---
a/sql/core/src/test/scala/org/apache/spark/sql/connector/DSv2ExternalMutationTestBase.scala
+++
b/sql/core/src/test/scala/org/apache/spark/sql/connector/DSv2ExternalMutationTestBase.scala
@@ -21,7 +21,7 @@ import java.util
import scala.reflect.ClassTag
-import org.apache.spark.sql.{DataFrame, QueryTest, Row, SparkSession}
+import org.apache.spark.sql.{SessionQueryTestBase, SparkSession}
import org.apache.spark.sql.catalyst.InternalRow
import org.apache.spark.sql.connector.catalog.{BufferedRows, CatalogV2Util,
Identifier, InMemoryBaseTable, TableCatalog, TableWritePrivilege}
@@ -37,7 +37,7 @@ import org.apache.spark.sql.connector.catalog.{BufferedRows,
CatalogV2Util, Iden
* [[DSv2TempViewWithStoredPlanTests]], [[DSv2RepeatedTableAccessTests]],
* [[DSv2IncrementallyConstructedQueryTests]], or [[DSv2CacheTableReadTests]].
*/
-trait DSv2ExternalMutationTestBase extends QueryTest {
+trait DSv2ExternalMutationTestBase extends SessionQueryTestBase {
/** Fully qualified table name under the non-caching test catalog. */
protected val testTable: String = "testcat.ns1.ns2.tbl"
@@ -51,17 +51,6 @@ trait DSv2ExternalMutationTestBase extends QueryTest {
/** Prefix for test names, e.g. "" or "[connect] ". */
protected def testPrefix: String
- /** Whether this suite runs under Spark Connect. */
- protected def isConnect: Boolean
-
- /** Execute a test body with a session. */
- protected def withTestSession(fn: SparkSession => Unit): Unit
-
- /**
- * Assert that a DataFrame's rows match the expected rows (order-agnostic).
- */
- protected def checkRows(df: => DataFrame, expected: Seq[Row]): Unit
-
/**
* Get a [[TableCatalog]] by name from the underlying session.
*/
@@ -69,12 +58,6 @@ trait DSv2ExternalMutationTestBase extends QueryTest {
session: SparkSession,
catalogName: String): C
- /** Cleanup wrapper: drop views and the table after the test body, even on
failure. */
- protected def withTestTableAndViews(
- session: SparkSession,
- table: String,
- views: Seq[String] = Seq.empty)(fn: => Unit): Unit
-
/** Appends a row to a DSv2 table via the catalog API, bypassing the
session. */
protected def externalAppend(
catalog: TableCatalog,
diff --git
a/sql/core/src/test/scala/org/apache/spark/sql/connector/DSv2IncrementallyConstructedQueryTests.scala
b/sql/core/src/test/scala/org/apache/spark/sql/connector/DSv2IncrementallyConstructedQueryTests.scala
index 9753f7db2097..aa8c63ff602d 100644
---
a/sql/core/src/test/scala/org/apache/spark/sql/connector/DSv2IncrementallyConstructedQueryTests.scala
+++
b/sql/core/src/test/scala/org/apache/spark/sql/connector/DSv2IncrementallyConstructedQueryTests.scala
@@ -32,7 +32,7 @@ import org.apache.spark.unsafe.types.UTF8String
* mode, resolution is deferred until execution, so both sides of a join
always see the
* latest table state.
*
- * NOTE: All `session.sql(...)` calls append `.collect()` because Connect
client DataFrames
+ * NOTE: All `spark.sql(...)` calls append `.collect()` because Connect client
DataFrames
* are lazy and require an action to trigger execution. In classic mode
`.collect()` on
* eager statements (DDL, INSERT) is a no-op, so this is harmless.
*/
@@ -45,42 +45,38 @@ trait DSv2IncrementallyConstructedQueryTests extends
DSv2ExternalMutationTestBas
test(s"${testPrefix}SPARK-54157: join refreshes both sides after external
insert" +
" (table with both table and column ID support)") {
- withTestSession { session =>
- withTestTableAndViews(session, testTable) {
- session.sql(s"CREATE TABLE $testTable (id INT, salary INT) USING
foo").collect()
- session.sql(s"INSERT INTO $testTable VALUES (1, 100)").collect()
+ withTable(testTable) {
+ spark.sql(s"CREATE TABLE $testTable (id INT, salary INT) USING
foo").collect()
+ spark.sql(s"INSERT INTO $testTable VALUES (1, 100)").collect()
- val df1 = session.table(testTable)
+ val df1 = spark.table(testTable)
- val catalog = getTableCatalog[InMemoryTableCatalog](session, "testcat")
- externalAppend(catalog = catalog, ident = testIdent, row =
InternalRow(2, 200))
+ val catalog = getTableCatalog[InMemoryTableCatalog](spark, "testcat")
+ externalAppend(catalog = catalog, ident = testIdent, row =
InternalRow(2, 200))
- val df2 = session.table(testTable)
+ val df2 = spark.table(testTable)
- checkRows(
- df1.join(df2, df1("id") === df2("id")),
- Seq(Row(1, 100, 1, 100), Row(2, 200, 2, 200)))
- }
+ checkAnswer(
+ df1.join(df2, df1("id") === df2("id")),
+ Seq(Row(1, 100, 1, 100), Row(2, 200, 2, 200)))
}
}
test(s"${testPrefix}SPARK-54157: join refreshes both sides after
same-session insert" +
" (table with both table and column ID support)") {
- withTestSession { session =>
- withTestTableAndViews(session, testTable) {
- session.sql(s"CREATE TABLE $testTable (id INT, salary INT) USING
foo").collect()
- session.sql(s"INSERT INTO $testTable VALUES (1, 100)").collect()
+ withTable(testTable) {
+ spark.sql(s"CREATE TABLE $testTable (id INT, salary INT) USING
foo").collect()
+ spark.sql(s"INSERT INTO $testTable VALUES (1, 100)").collect()
- val df1 = session.table(testTable)
+ val df1 = spark.table(testTable)
- session.sql(s"INSERT INTO $testTable VALUES (2, 200)").collect()
+ spark.sql(s"INSERT INTO $testTable VALUES (2, 200)").collect()
- val df2 = session.table(testTable)
+ val df2 = spark.table(testTable)
- checkRows(
- df1.join(df2, df1("id") === df2("id")),
- Seq(Row(1, 100, 1, 100), Row(2, 200, 2, 200)))
- }
+ checkAnswer(
+ df1.join(df2, df1("id") === df2("id")),
+ Seq(Row(1, 100, 1, 100), Row(2, 200, 2, 200)))
}
}
@@ -92,67 +88,63 @@ trait DSv2IncrementallyConstructedQueryTests extends
DSv2ExternalMutationTestBas
test(s"${testPrefix}SPARK-54157: join after external ADD COLUMN" +
" (table with both table and column ID support)") {
- withTestSession { session =>
- withTestTableAndViews(session, testTable) {
- session.sql(s"CREATE TABLE $testTable (id INT, salary INT) USING
foo").collect()
- session.sql(s"INSERT INTO $testTable VALUES (1, 100)").collect()
-
- val df1 = session.table(testTable)
-
- val catalog = getTableCatalog[InMemoryTableCatalog](session, "testcat")
- catalog.alterTable(
- testIdent, TableChange.addColumn(Array("new_column"), IntegerType,
true))
- externalAppend(
- catalog = catalog, ident = testIdent, row = InternalRow(2, 200, -1))
-
- val df2 = session.table(testTable)
- val selfJoin = df1.join(df2, df1("id") === df2("id"))
-
- if (isConnect) {
- // Connect re-resolves df1 with the new 3-column schema (id, salary,
new_column).
- assert(selfJoin.columns.length == 6,
- s"Expected 6 columns (3 + 3) but got:
${selfJoin.columns.mkString(", ")}")
- checkRows(selfJoin,
- Seq(Row(1, 100, null, 1, 100, null), Row(2, 200, -1, 2, 200, -1)))
- } else {
- // Classic: df1 keeps its original 2-column schema (id, salary).
- assert(selfJoin.columns.length == 5,
- s"Expected 5 columns (2 + 3) but got:
${selfJoin.columns.mkString(", ")}")
- checkRows(selfJoin,
- Seq(Row(1, 100, 1, 100, null), Row(2, 200, 2, 200, -1)))
- }
+ withTable(testTable) {
+ spark.sql(s"CREATE TABLE $testTable (id INT, salary INT) USING
foo").collect()
+ spark.sql(s"INSERT INTO $testTable VALUES (1, 100)").collect()
+
+ val df1 = spark.table(testTable)
+
+ val catalog = getTableCatalog[InMemoryTableCatalog](spark, "testcat")
+ catalog.alterTable(
+ testIdent, TableChange.addColumn(Array("new_column"), IntegerType,
true))
+ externalAppend(
+ catalog = catalog, ident = testIdent, row = InternalRow(2, 200, -1))
+
+ val df2 = spark.table(testTable)
+ val selfJoin = df1.join(df2, df1("id") === df2("id"))
+
+ if (isConnect) {
+ // Connect re-resolves df1 with the new 3-column schema (id, salary,
new_column).
+ assert(selfJoin.columns.length == 6,
+ s"Expected 6 columns (3 + 3) but got: ${selfJoin.columns.mkString(",
")}")
+ checkAnswer(selfJoin,
+ Seq(Row(1, 100, null, 1, 100, null), Row(2, 200, -1, 2, 200, -1)))
+ } else {
+ // Classic: df1 keeps its original 2-column schema (id, salary).
+ assert(selfJoin.columns.length == 5,
+ s"Expected 5 columns (2 + 3) but got: ${selfJoin.columns.mkString(",
")}")
+ checkAnswer(selfJoin,
+ Seq(Row(1, 100, 1, 100, null), Row(2, 200, 2, 200, -1)))
}
}
}
test(s"${testPrefix}SPARK-54157: join after same-session ADD COLUMN" +
" (table with both table and column ID support)") {
- withTestSession { session =>
- withTestTableAndViews(session, testTable) {
- session.sql(s"CREATE TABLE $testTable (id INT, salary INT) USING
foo").collect()
- session.sql(s"INSERT INTO $testTable VALUES (1, 100)").collect()
-
- val df1 = session.table(testTable)
-
- session.sql(s"ALTER TABLE $testTable ADD COLUMN new_column
INT").collect()
- session.sql(s"INSERT INTO $testTable VALUES (2, 200, -1)").collect()
-
- val df2 = session.table(testTable)
- val selfJoin = df1.join(df2, df1("id") === df2("id"))
-
- if (isConnect) {
- // Connect re-resolves df1 with the new 3-column schema (id, salary,
new_column).
- assert(selfJoin.columns.length == 6,
- s"Expected 6 columns (3 + 3) but got:
${selfJoin.columns.mkString(", ")}")
- checkRows(selfJoin,
- Seq(Row(1, 100, null, 1, 100, null), Row(2, 200, -1, 2, 200, -1)))
- } else {
- // Classic: df1 keeps its original 2-column schema (id, salary).
- assert(selfJoin.columns.length == 5,
- s"Expected 5 columns (2 + 3) but got:
${selfJoin.columns.mkString(", ")}")
- checkRows(selfJoin,
- Seq(Row(1, 100, 1, 100, null), Row(2, 200, 2, 200, -1)))
- }
+ withTable(testTable) {
+ spark.sql(s"CREATE TABLE $testTable (id INT, salary INT) USING
foo").collect()
+ spark.sql(s"INSERT INTO $testTable VALUES (1, 100)").collect()
+
+ val df1 = spark.table(testTable)
+
+ spark.sql(s"ALTER TABLE $testTable ADD COLUMN new_column INT").collect()
+ spark.sql(s"INSERT INTO $testTable VALUES (2, 200, -1)").collect()
+
+ val df2 = spark.table(testTable)
+ val selfJoin = df1.join(df2, df1("id") === df2("id"))
+
+ if (isConnect) {
+ // Connect re-resolves df1 with the new 3-column schema (id, salary,
new_column).
+ assert(selfJoin.columns.length == 6,
+ s"Expected 6 columns (3 + 3) but got: ${selfJoin.columns.mkString(",
")}")
+ checkAnswer(selfJoin,
+ Seq(Row(1, 100, null, 1, 100, null), Row(2, 200, -1, 2, 200, -1)))
+ } else {
+ // Classic: df1 keeps its original 2-column schema (id, salary).
+ assert(selfJoin.columns.length == 5,
+ s"Expected 5 columns (2 + 3) but got: ${selfJoin.columns.mkString(",
")}")
+ checkAnswer(selfJoin,
+ Seq(Row(1, 100, 1, 100, null), Row(2, 200, 2, 200, -1)))
}
}
}
@@ -165,68 +157,64 @@ trait DSv2IncrementallyConstructedQueryTests extends
DSv2ExternalMutationTestBas
test(s"${testPrefix}SPARK-54157: join after external DROP COLUMN" +
" (table with both table and column ID support)") {
- withTestSession { session =>
- withTestTableAndViews(session, testTable) {
- session.sql(s"CREATE TABLE $testTable (id INT, salary INT) USING
foo").collect()
- session.sql(s"INSERT INTO $testTable VALUES (1, 100)").collect()
-
- val df1 = session.table(testTable)
-
- val catalog = getTableCatalog[InMemoryTableCatalog](session, "testcat")
- catalog.alterTable(
- testIdent, TableChange.deleteColumn(Array("salary"), false))
- externalAppend(catalog = catalog, ident = testIdent, row =
InternalRow(2))
-
- val df2 = session.table(testTable)
-
- if (isConnect) {
- // Connect re-resolves df1 without the dropped column.
- checkRows(
- df1.join(df2, df1("id") === df2("id")),
- Seq(Row(1, 1), Row(2, 2)))
- } else {
- // Classic: df1 references the dropped column.
- checkError(
- exception = intercept[AnalysisException] {
- df1.join(df2, df1("id") === df2("id")).collect()
- },
- condition =
"INCOMPATIBLE_TABLE_CHANGE_AFTER_ANALYSIS.COLUMNS_MISMATCH",
- matchPVals = true,
- parameters = Map("tableName" -> ".*", "errors" -> "(?s).*"))
- }
+ withTable(testTable) {
+ spark.sql(s"CREATE TABLE $testTable (id INT, salary INT) USING
foo").collect()
+ spark.sql(s"INSERT INTO $testTable VALUES (1, 100)").collect()
+
+ val df1 = spark.table(testTable)
+
+ val catalog = getTableCatalog[InMemoryTableCatalog](spark, "testcat")
+ catalog.alterTable(
+ testIdent, TableChange.deleteColumn(Array("salary"), false))
+ externalAppend(catalog = catalog, ident = testIdent, row =
InternalRow(2))
+
+ val df2 = spark.table(testTable)
+
+ if (isConnect) {
+ // Connect re-resolves df1 without the dropped column.
+ checkAnswer(
+ df1.join(df2, df1("id") === df2("id")),
+ Seq(Row(1, 1), Row(2, 2)))
+ } else {
+ // Classic: df1 references the dropped column.
+ checkError(
+ exception = intercept[AnalysisException] {
+ df1.join(df2, df1("id") === df2("id")).collect()
+ },
+ condition =
"INCOMPATIBLE_TABLE_CHANGE_AFTER_ANALYSIS.COLUMNS_MISMATCH",
+ matchPVals = true,
+ parameters = Map("tableName" -> ".*", "errors" -> "(?s).*"))
}
}
}
test(s"${testPrefix}SPARK-54157: join after same-session DROP COLUMN" +
" (table with both table and column ID support)") {
- withTestSession { session =>
- withTestTableAndViews(session, testTable) {
- session.sql(s"CREATE TABLE $testTable (id INT, salary INT) USING
foo").collect()
- session.sql(s"INSERT INTO $testTable VALUES (1, 100)").collect()
-
- val df1 = session.table(testTable)
-
- session.sql(s"ALTER TABLE $testTable DROP COLUMN salary").collect()
- session.sql(s"INSERT INTO $testTable VALUES (2)").collect()
-
- val df2 = session.table(testTable)
-
- if (isConnect) {
- // Connect re-resolves df1 without the dropped column.
- checkRows(
- df1.join(df2, df1("id") === df2("id")),
- Seq(Row(1, 1), Row(2, 2)))
- } else {
- // Classic: df1 references the dropped column.
- checkError(
- exception = intercept[AnalysisException] {
- df1.join(df2, df1("id") === df2("id")).collect()
- },
- condition =
"INCOMPATIBLE_TABLE_CHANGE_AFTER_ANALYSIS.COLUMNS_MISMATCH",
- matchPVals = true,
- parameters = Map("tableName" -> ".*", "errors" -> "(?s).*"))
- }
+ withTable(testTable) {
+ spark.sql(s"CREATE TABLE $testTable (id INT, salary INT) USING
foo").collect()
+ spark.sql(s"INSERT INTO $testTable VALUES (1, 100)").collect()
+
+ val df1 = spark.table(testTable)
+
+ spark.sql(s"ALTER TABLE $testTable DROP COLUMN salary").collect()
+ spark.sql(s"INSERT INTO $testTable VALUES (2)").collect()
+
+ val df2 = spark.table(testTable)
+
+ if (isConnect) {
+ // Connect re-resolves df1 without the dropped column.
+ checkAnswer(
+ df1.join(df2, df1("id") === df2("id")),
+ Seq(Row(1, 1), Row(2, 2)))
+ } else {
+ // Classic: df1 references the dropped column.
+ checkError(
+ exception = intercept[AnalysisException] {
+ df1.join(df2, df1("id") === df2("id")).collect()
+ },
+ condition =
"INCOMPATIBLE_TABLE_CHANGE_AFTER_ANALYSIS.COLUMNS_MISMATCH",
+ matchPVals = true,
+ parameters = Map("tableName" -> ".*", "errors" -> "(?s).*"))
}
}
}
@@ -240,47 +228,45 @@ trait DSv2IncrementallyConstructedQueryTests extends
DSv2ExternalMutationTestBas
test(s"${testPrefix}SPARK-54157: join after external table drop and
recreate" +
" (table with both table and column ID support)") {
- withTestSession { session =>
- withTestTableAndViews(session, testTable) {
- session.sql(s"CREATE TABLE $testTable (id INT, salary INT) USING
foo").collect()
- session.sql(s"INSERT INTO $testTable VALUES (1, 100)").collect()
-
- val df1 = session.table(testTable)
- val catalog = getTableCatalog[InMemoryTableCatalog](session, "testcat")
- val originTableId = catalog.loadTable(testIdent).id
-
- catalog.dropTable(testIdent)
- catalog.createTable(
- testIdent,
- new TableInfo.Builder()
- .withColumns(Array(
- Column.create("id", IntegerType),
- Column.create("salary", IntegerType)))
- .build())
- externalAppend(catalog = catalog, ident = testIdent, row =
InternalRow(2, 200))
-
- val df2 = session.table(testTable)
- val newTableId = catalog.loadTable(testIdent).id
- assert(originTableId != newTableId)
-
- if (isConnect) {
- // Connect re-resolves both sides to the recreated table.
- checkRows(
- df1.join(df2, df1("id") === df2("id")),
- Seq(Row(2, 200, 2, 200)))
- } else {
- // Classic: table ID changed.
- checkError(
- exception = intercept[AnalysisException] {
- df1.join(df2, df1("id") === df2("id")).collect()
- },
- condition =
"INCOMPATIBLE_TABLE_CHANGE_AFTER_ANALYSIS.TABLE_ID_MISMATCH",
- matchPVals = true,
- parameters = Map(
- "tableName" -> ".*",
- "capturedTableId" -> ".*",
- "currentTableId" -> ".*"))
- }
+ withTable(testTable) {
+ spark.sql(s"CREATE TABLE $testTable (id INT, salary INT) USING
foo").collect()
+ spark.sql(s"INSERT INTO $testTable VALUES (1, 100)").collect()
+
+ val df1 = spark.table(testTable)
+ val catalog = getTableCatalog[InMemoryTableCatalog](spark, "testcat")
+ val originTableId = catalog.loadTable(testIdent).id
+
+ catalog.dropTable(testIdent)
+ catalog.createTable(
+ testIdent,
+ new TableInfo.Builder()
+ .withColumns(Array(
+ Column.create("id", IntegerType),
+ Column.create("salary", IntegerType)))
+ .build())
+ externalAppend(catalog = catalog, ident = testIdent, row =
InternalRow(2, 200))
+
+ val df2 = spark.table(testTable)
+ val newTableId = catalog.loadTable(testIdent).id
+ assert(originTableId != newTableId)
+
+ if (isConnect) {
+ // Connect re-resolves both sides to the recreated table.
+ checkAnswer(
+ df1.join(df2, df1("id") === df2("id")),
+ Seq(Row(2, 200, 2, 200)))
+ } else {
+ // Classic: table ID changed.
+ checkError(
+ exception = intercept[AnalysisException] {
+ df1.join(df2, df1("id") === df2("id")).collect()
+ },
+ condition =
"INCOMPATIBLE_TABLE_CHANGE_AFTER_ANALYSIS.TABLE_ID_MISMATCH",
+ matchPVals = true,
+ parameters = Map(
+ "tableName" -> ".*",
+ "capturedTableId" -> ".*",
+ "currentTableId" -> ".*"))
}
}
}
@@ -288,43 +274,41 @@ trait DSv2IncrementallyConstructedQueryTests extends
DSv2ExternalMutationTestBas
test(s"${testPrefix}SPARK-54157: join after external drop/recreate" +
" (table without table ID support, but with column ID support)") {
val nullIdT = "nullidcat.ns1.ns2.tbl"
- withTestSession { session =>
- withTestTableAndViews(session, nullIdT) {
- session.sql(s"CREATE TABLE $nullIdT (id INT, salary INT) USING
foo").collect()
- session.sql(s"INSERT INTO $nullIdT VALUES (1, 100)").collect()
-
- val df1 = session.table(nullIdT)
- val catalog = getTableCatalog[TableCatalog](session, "nullidcat")
- assert(catalog.loadTable(testIdent).id == null,
- "NullTableIdInMemoryTableCatalog should produce null table IDs")
-
- catalog.dropTable(testIdent)
- catalog.createTable(
- testIdent,
- new TableInfo.Builder()
- .withColumns(Array(
- Column.create("id", IntegerType),
- Column.create("salary", IntegerType)))
- .build())
- externalAppend(catalog = catalog, ident = testIdent, row =
InternalRow(2, 200))
-
- val df2 = session.table(nullIdT)
-
- if (isConnect) {
- // Connect re-resolves both sides to the recreated table.
- checkRows(
- df1.join(df2, df1("id") === df2("id")),
- Seq(Row(2, 200, 2, 200)))
- } else {
- // Classic: column IDs changed.
- checkError(
- exception = intercept[AnalysisException] {
- df1.join(df2, df1("id") === df2("id")).collect()
- },
- condition =
"INCOMPATIBLE_TABLE_CHANGE_AFTER_ANALYSIS.COLUMNS_MISMATCH",
- matchPVals = true,
- parameters = Map("tableName" -> ".*", "errors" -> "(?s).*"))
- }
+ withTable(nullIdT) {
+ spark.sql(s"CREATE TABLE $nullIdT (id INT, salary INT) USING
foo").collect()
+ spark.sql(s"INSERT INTO $nullIdT VALUES (1, 100)").collect()
+
+ val df1 = spark.table(nullIdT)
+ val catalog = getTableCatalog[TableCatalog](spark, "nullidcat")
+ assert(catalog.loadTable(testIdent).id == null,
+ "NullTableIdInMemoryTableCatalog should produce null table IDs")
+
+ catalog.dropTable(testIdent)
+ catalog.createTable(
+ testIdent,
+ new TableInfo.Builder()
+ .withColumns(Array(
+ Column.create("id", IntegerType),
+ Column.create("salary", IntegerType)))
+ .build())
+ externalAppend(catalog = catalog, ident = testIdent, row =
InternalRow(2, 200))
+
+ val df2 = spark.table(nullIdT)
+
+ if (isConnect) {
+ // Connect re-resolves both sides to the recreated table.
+ checkAnswer(
+ df1.join(df2, df1("id") === df2("id")),
+ Seq(Row(2, 200, 2, 200)))
+ } else {
+ // Classic: column IDs changed.
+ checkError(
+ exception = intercept[AnalysisException] {
+ df1.join(df2, df1("id") === df2("id")).collect()
+ },
+ condition =
"INCOMPATIBLE_TABLE_CHANGE_AFTER_ANALYSIS.COLUMNS_MISMATCH",
+ matchPVals = true,
+ parameters = Map("tableName" -> ".*", "errors" -> "(?s).*"))
}
}
}
@@ -332,46 +316,44 @@ trait DSv2IncrementallyConstructedQueryTests extends
DSv2ExternalMutationTestBas
test(s"${testPrefix}SPARK-54157: join does not detect external table drop
and recreate" +
" (table without table ID support and without column ID support)") {
val nullBothT = "nullbothidscat.ns1.ns2.tbl"
- withTestSession { session =>
- withTestTableAndViews(session, nullBothT) {
- session.sql(s"CREATE TABLE $nullBothT (id INT, salary INT) USING
foo").collect()
- session.sql(s"INSERT INTO $nullBothT VALUES (1, 100)").collect()
-
- val df1 = session.table(nullBothT)
- val catalog = getTableCatalog[TableCatalog](
- session, "nullbothidscat")
- assert(catalog.loadTable(testIdent).id == null,
- "NullTableIdAndNullColumnIdInMemoryTableCatalog should produce null
table IDs")
- assert(catalog.loadTable(testIdent).columns().forall(_.id() == null),
- "NullTableIdAndNullColumnIdInMemoryTableCatalog should produce null
column IDs")
-
- catalog.dropTable(testIdent)
- catalog.createTable(
- testIdent,
- new TableInfo.Builder()
- .withColumns(Array(
- Column.create("id", IntegerType),
- Column.create("salary", IntegerType)))
- .build())
- externalAppend(catalog = catalog, ident = testIdent, row =
InternalRow(2, 200))
-
- val df2 = session.table(nullBothT)
-
- if (isConnect) {
- // Connect re-resolves both sides to the recreated table, so the join
- // sees the row appended after recreate.
- checkRows(
- df1.join(df2, df1("id") === df2("id")),
- Seq(Row(2, 200, 2, 200)))
- } else {
- // Classic: neither TABLE_ID_MISMATCH nor COLUMNS_MISMATCH fires, so
the
- // drop and recreate goes undetected. df1 keeps its pre-drop snapshot
- // (1, 100) while df2 reads the recreated table (2, 200), so the
join finds
- // no matching ids and returns no rows.
- checkRows(
- df1.join(df2, df1("id") === df2("id")),
- Seq.empty)
- }
+ withTable(nullBothT) {
+ spark.sql(s"CREATE TABLE $nullBothT (id INT, salary INT) USING
foo").collect()
+ spark.sql(s"INSERT INTO $nullBothT VALUES (1, 100)").collect()
+
+ val df1 = spark.table(nullBothT)
+ val catalog = getTableCatalog[TableCatalog](
+ spark, "nullbothidscat")
+ assert(catalog.loadTable(testIdent).id == null,
+ "NullTableIdAndNullColumnIdInMemoryTableCatalog should produce null
table IDs")
+ assert(catalog.loadTable(testIdent).columns().forall(_.id() == null),
+ "NullTableIdAndNullColumnIdInMemoryTableCatalog should produce null
column IDs")
+
+ catalog.dropTable(testIdent)
+ catalog.createTable(
+ testIdent,
+ new TableInfo.Builder()
+ .withColumns(Array(
+ Column.create("id", IntegerType),
+ Column.create("salary", IntegerType)))
+ .build())
+ externalAppend(catalog = catalog, ident = testIdent, row =
InternalRow(2, 200))
+
+ val df2 = spark.table(nullBothT)
+
+ if (isConnect) {
+ // Connect re-resolves both sides to the recreated table, so the join
+ // sees the row appended after recreate.
+ checkAnswer(
+ df1.join(df2, df1("id") === df2("id")),
+ Seq(Row(2, 200, 2, 200)))
+ } else {
+ // Classic: neither TABLE_ID_MISMATCH nor COLUMNS_MISMATCH fires, so
the
+ // drop and recreate goes undetected. df1 keeps its pre-drop snapshot
+ // (1, 100) while df2 reads the recreated table (2, 200), so the join
finds
+ // no matching ids and returns no rows.
+ checkAnswer(
+ df1.join(df2, df1("id") === df2("id")),
+ Seq.empty)
}
}
}
@@ -385,36 +367,34 @@ trait DSv2IncrementallyConstructedQueryTests extends
DSv2ExternalMutationTestBas
test(s"${testPrefix}SPARK-54157: join after external drop+re-add column" +
" (table without table ID support, but with column ID support)") {
val nullIdT = "nullidcat.ns1.ns2.tbl"
- withTestSession { session =>
- withTestTableAndViews(session, nullIdT) {
- session.sql(s"CREATE TABLE $nullIdT (id INT, salary INT) USING
foo").collect()
- session.sql(s"INSERT INTO $nullIdT VALUES (1, 100)").collect()
-
- val df1 = session.table(nullIdT)
-
- val catalog = getTableCatalog[TableCatalog](session, "nullidcat")
- catalog.alterTable(
- testIdent, TableChange.deleteColumn(Array("salary"), false))
- catalog.alterTable(
- testIdent, TableChange.addColumn(Array("salary"), IntegerType, true))
-
- val df2 = session.table(nullIdT)
-
- if (isConnect) {
- // Connect re-resolves both sides with the new column ID.
- checkRows(
- df1.join(df2, df1("id") === df2("id")),
- Seq(Row(1, null, 1, null)))
- } else {
- // Classic: column ID changed.
- checkError(
- exception = intercept[AnalysisException] {
- df1.join(df2, df1("id") === df2("id")).collect()
- },
- condition =
"INCOMPATIBLE_TABLE_CHANGE_AFTER_ANALYSIS.COLUMNS_MISMATCH",
- matchPVals = true,
- parameters = Map("tableName" -> ".*", "errors" -> "(?s).*"))
- }
+ withTable(nullIdT) {
+ spark.sql(s"CREATE TABLE $nullIdT (id INT, salary INT) USING
foo").collect()
+ spark.sql(s"INSERT INTO $nullIdT VALUES (1, 100)").collect()
+
+ val df1 = spark.table(nullIdT)
+
+ val catalog = getTableCatalog[TableCatalog](spark, "nullidcat")
+ catalog.alterTable(
+ testIdent, TableChange.deleteColumn(Array("salary"), false))
+ catalog.alterTable(
+ testIdent, TableChange.addColumn(Array("salary"), IntegerType, true))
+
+ val df2 = spark.table(nullIdT)
+
+ if (isConnect) {
+ // Connect re-resolves both sides with the new column ID.
+ checkAnswer(
+ df1.join(df2, df1("id") === df2("id")),
+ Seq(Row(1, null, 1, null)))
+ } else {
+ // Classic: column ID changed.
+ checkError(
+ exception = intercept[AnalysisException] {
+ df1.join(df2, df1("id") === df2("id")).collect()
+ },
+ condition =
"INCOMPATIBLE_TABLE_CHANGE_AFTER_ANALYSIS.COLUMNS_MISMATCH",
+ matchPVals = true,
+ parameters = Map("tableName" -> ".*", "errors" -> "(?s).*"))
}
}
}
@@ -422,28 +402,25 @@ trait DSv2IncrementallyConstructedQueryTests extends
DSv2ExternalMutationTestBas
test(s"${testPrefix}SPARK-54157: join does not detect external drop+re-add
column" +
" (table without table ID support and without column ID support)") {
val nullBothT = "nullbothidscat.ns1.ns2.tbl"
- withTestSession { session =>
- withTestTableAndViews(session, nullBothT) {
- session.sql(s"CREATE TABLE $nullBothT (id INT, salary INT) USING
foo").collect()
- session.sql(s"INSERT INTO $nullBothT VALUES (1, 100)").collect()
+ withTable(nullBothT) {
+ spark.sql(s"CREATE TABLE $nullBothT (id INT, salary INT) USING
foo").collect()
+ spark.sql(s"INSERT INTO $nullBothT VALUES (1, 100)").collect()
- val df1 = session.table(nullBothT)
+ val df1 = spark.table(nullBothT)
- val catalog = getTableCatalog[TableCatalog](
- session, "nullbothidscat")
- catalog.alterTable(
- testIdent, TableChange.deleteColumn(Array("salary"), false))
- catalog.alterTable(
- testIdent, TableChange.addColumn(Array("salary"), IntegerType, true))
+ val catalog = getTableCatalog[TableCatalog](spark, "nullbothidscat")
+ catalog.alterTable(
+ testIdent, TableChange.deleteColumn(Array("salary"), false))
+ catalog.alterTable(
+ testIdent, TableChange.addColumn(Array("salary"), IntegerType, true))
- val df2 = session.table(nullBothT)
+ val df2 = spark.table(nullBothT)
- // Neither TABLE_ID_MISMATCH nor COLUMNS_MISMATCH fires.
- // The change goes undetected and the join succeeds.
- checkRows(
- df1.join(df2, df1("id") === df2("id")),
- Seq(Row(1, null, 1, null)))
- }
+ // Neither TABLE_ID_MISMATCH nor COLUMNS_MISMATCH fires.
+ // The change goes undetected and the join succeeds.
+ checkAnswer(
+ df1.join(df2, df1("id") === df2("id")),
+ Seq(Row(1, null, 1, null)))
}
}
@@ -456,38 +433,36 @@ trait DSv2IncrementallyConstructedQueryTests extends
DSv2ExternalMutationTestBas
test(s"${testPrefix}SPARK-54157: join after external drop+re-add
different-type column" +
" (table with both table and column ID support)") {
- withTestSession { session =>
- withTestTableAndViews(session, testTable) {
- session.sql(s"CREATE TABLE $testTable (id INT, salary INT) USING
foo").collect()
- session.sql(s"INSERT INTO $testTable VALUES (1, 100)").collect()
-
- val df1 = session.table(testTable)
-
- val catalog = getTableCatalog[InMemoryTableCatalog](session, "testcat")
- catalog.alterTable(
- testIdent, TableChange.deleteColumn(Array("salary"), false))
- catalog.alterTable(
- testIdent, TableChange.addColumn(Array("salary"), StringType, true))
- externalAppend(catalog = catalog, ident = testIdent,
- row = InternalRow(2, UTF8String.fromString("high")))
-
- val df2 = session.table(testTable)
-
- if (isConnect) {
- // Connect re-resolves both sides with the new column type.
- checkRows(
- df1.join(df2, df1("id") === df2("id")),
- Seq(Row(1, null, 1, null), Row(2, "high", 2, "high")))
- } else {
- // Classic: column ID changed.
- checkError(
- exception = intercept[AnalysisException] {
- df1.join(df2, df1("id") === df2("id")).collect()
- },
- condition =
"INCOMPATIBLE_TABLE_CHANGE_AFTER_ANALYSIS.COLUMNS_MISMATCH",
- matchPVals = true,
- parameters = Map("tableName" -> ".*", "errors" -> "(?s).*"))
- }
+ withTable(testTable) {
+ spark.sql(s"CREATE TABLE $testTable (id INT, salary INT) USING
foo").collect()
+ spark.sql(s"INSERT INTO $testTable VALUES (1, 100)").collect()
+
+ val df1 = spark.table(testTable)
+
+ val catalog = getTableCatalog[InMemoryTableCatalog](spark, "testcat")
+ catalog.alterTable(
+ testIdent, TableChange.deleteColumn(Array("salary"), false))
+ catalog.alterTable(
+ testIdent, TableChange.addColumn(Array("salary"), StringType, true))
+ externalAppend(catalog = catalog, ident = testIdent,
+ row = InternalRow(2, UTF8String.fromString("high")))
+
+ val df2 = spark.table(testTable)
+
+ if (isConnect) {
+ // Connect re-resolves both sides with the new column type.
+ checkAnswer(
+ df1.join(df2, df1("id") === df2("id")),
+ Seq(Row(1, null, 1, null), Row(2, "high", 2, "high")))
+ } else {
+ // Classic: column ID changed.
+ checkError(
+ exception = intercept[AnalysisException] {
+ df1.join(df2, df1("id") === df2("id")).collect()
+ },
+ condition =
"INCOMPATIBLE_TABLE_CHANGE_AFTER_ANALYSIS.COLUMNS_MISMATCH",
+ matchPVals = true,
+ parameters = Map("tableName" -> ".*", "errors" -> "(?s).*"))
}
}
}
diff --git
a/sql/core/src/test/scala/org/apache/spark/sql/connector/DSv2RepeatedTableAccessTests.scala
b/sql/core/src/test/scala/org/apache/spark/sql/connector/DSv2RepeatedTableAccessTests.scala
index 533d10a94979..fb22a8bb7ab7 100644
---
a/sql/core/src/test/scala/org/apache/spark/sql/connector/DSv2RepeatedTableAccessTests.scala
+++
b/sql/core/src/test/scala/org/apache/spark/sql/connector/DSv2RepeatedTableAccessTests.scala
@@ -34,7 +34,7 @@ import org.apache.spark.sql.types.IntegerType
* Each scenario includes a session mutation baseline, an external mutation
test, and a
* caching-connector variant showing stale results until `REFRESH TABLE`.
*
- * NOTE: All `session.sql(...)` calls append `.collect()` because Connect
client DataFrames
+ * NOTE: All `spark.sql(...)` calls append `.collect()` because Connect client
DataFrames
* are lazy and require an action to trigger execution. In classic mode
`.collect()` on
* DDL / DML is a no-op (these execute eagerly), so this is harmless.
*/
@@ -45,178 +45,160 @@ trait DSv2RepeatedTableAccessTests extends
DSv2ExternalMutationTestBase {
// Scenario 1: data changes via writes
test(s"${testPrefix}repeated sql() reflects session write") {
- withTestSession { session =>
- withTestTableAndViews(session, testTable) {
- session.sql(s"CREATE TABLE $testTable (id INT, salary INT) USING
foo").collect()
- session.sql(s"INSERT INTO $testTable VALUES (1, 100)").collect()
- checkRows(session.sql(s"SELECT * FROM $testTable"), Seq(Row(1, 100)))
-
- session.sql(s"INSERT INTO $testTable VALUES (2, 200)").collect()
- checkRows(session.sql(s"SELECT * FROM $testTable"), Seq(Row(1, 100),
Row(2, 200)))
- }
+ withTable(testTable) {
+ spark.sql(s"CREATE TABLE $testTable (id INT, salary INT) USING
foo").collect()
+ spark.sql(s"INSERT INTO $testTable VALUES (1, 100)").collect()
+ checkAnswer(spark.sql(s"SELECT * FROM $testTable"), Seq(Row(1, 100)))
+
+ spark.sql(s"INSERT INTO $testTable VALUES (2, 200)").collect()
+ checkAnswer(spark.sql(s"SELECT * FROM $testTable"), Seq(Row(1, 100),
Row(2, 200)))
}
}
test(s"${testPrefix}repeated sql() reflects external write") {
- withTestSession { session =>
- withTestTableAndViews(session, testTable) {
- session.sql(s"CREATE TABLE $testTable (id INT, salary INT) USING
foo").collect()
- session.sql(s"INSERT INTO $testTable VALUES (1, 100)").collect()
- checkRows(session.sql(s"SELECT * FROM $testTable"), Seq(Row(1, 100)))
+ withTable(testTable) {
+ spark.sql(s"CREATE TABLE $testTable (id INT, salary INT) USING
foo").collect()
+ spark.sql(s"INSERT INTO $testTable VALUES (1, 100)").collect()
+ checkAnswer(spark.sql(s"SELECT * FROM $testTable"), Seq(Row(1, 100)))
- val catalog = getTableCatalog[InMemoryTableCatalog](session, "testcat")
- externalAppend(catalog = catalog, ident = testIdent, row =
InternalRow(2, 200))
+ val catalog = getTableCatalog[InMemoryTableCatalog](spark, "testcat")
+ externalAppend(catalog = catalog, ident = testIdent, row =
InternalRow(2, 200))
- checkRows(session.sql(s"SELECT * FROM $testTable"), Seq(Row(1, 100),
Row(2, 200)))
- }
+ checkAnswer(spark.sql(s"SELECT * FROM $testTable"), Seq(Row(1, 100),
Row(2, 200)))
}
}
test(s"${testPrefix}connector w/ cache: repeated sql() stale after external
write") {
- withTestSession { session =>
- withTestTableAndViews(session, cachingTestTable) {
- session.sql(s"CREATE TABLE $cachingTestTable (id INT, salary INT)
USING foo").collect()
- session.sql(s"INSERT INTO $cachingTestTable VALUES (1, 100)").collect()
- checkRows(session.sql(s"SELECT * FROM $cachingTestTable"), Seq(Row(1,
100)))
-
- val catalog = getTableCatalog[CachingInMemoryTableCatalog](session,
"cachingcat")
- externalAppend(catalog = catalog, ident = testIdent, row =
InternalRow(2, 200))
-
- // Caching connector returns stale table: external write invisible
- checkRows(session.sql(s"SELECT * FROM $cachingTestTable"), Seq(Row(1,
100)))
-
- // REFRESH TABLE invalidates the connector cache, external write
becomes visible
- session.sql(s"REFRESH TABLE $cachingTestTable").collect()
- checkRows(session.sql(s"SELECT * FROM $cachingTestTable"), Seq(Row(1,
100), Row(2, 200)))
- }
+ withTable(cachingTestTable) {
+ spark.sql(s"CREATE TABLE $cachingTestTable (id INT, salary INT) USING
foo").collect()
+ spark.sql(s"INSERT INTO $cachingTestTable VALUES (1, 100)").collect()
+ checkAnswer(spark.sql(s"SELECT * FROM $cachingTestTable"), Seq(Row(1,
100)))
+
+ val catalog = getTableCatalog[CachingInMemoryTableCatalog](spark,
"cachingcat")
+ externalAppend(catalog = catalog, ident = testIdent, row =
InternalRow(2, 200))
+
+ // Caching connector returns stale table: external write invisible
+ checkAnswer(spark.sql(s"SELECT * FROM $cachingTestTable"), Seq(Row(1,
100)))
+
+ // REFRESH TABLE invalidates the connector cache, external write becomes
visible
+ spark.sql(s"REFRESH TABLE $cachingTestTable").collect()
+ checkAnswer(spark.sql(s"SELECT * FROM $cachingTestTable"), Seq(Row(1,
100), Row(2, 200)))
}
}
// Scenario 2: schema changes
test(s"${testPrefix}repeated sql() reflects session schema change") {
- withTestSession { session =>
- withTestTableAndViews(session, testTable) {
- session.sql(s"CREATE TABLE $testTable (id INT, salary INT) USING
foo").collect()
- session.sql(s"INSERT INTO $testTable VALUES (1, 100)").collect()
- checkRows(session.sql(s"SELECT * FROM $testTable"), Seq(Row(1, 100)))
-
- session.sql(s"ALTER TABLE $testTable ADD COLUMN new_col INT").collect()
- session.sql(s"INSERT INTO $testTable VALUES (2, 200, -1)").collect()
- checkRows(
- session.sql(s"SELECT * FROM $testTable"),
- Seq(Row(1, 100, null), Row(2, 200, -1)))
- }
+ withTable(testTable) {
+ spark.sql(s"CREATE TABLE $testTable (id INT, salary INT) USING
foo").collect()
+ spark.sql(s"INSERT INTO $testTable VALUES (1, 100)").collect()
+ checkAnswer(spark.sql(s"SELECT * FROM $testTable"), Seq(Row(1, 100)))
+
+ spark.sql(s"ALTER TABLE $testTable ADD COLUMN new_col INT").collect()
+ spark.sql(s"INSERT INTO $testTable VALUES (2, 200, -1)").collect()
+ checkAnswer(
+ spark.sql(s"SELECT * FROM $testTable"),
+ Seq(Row(1, 100, null), Row(2, 200, -1)))
}
}
test(s"${testPrefix}repeated sql() reflects external schema change") {
- withTestSession { session =>
- withTestTableAndViews(session, testTable) {
- session.sql(s"CREATE TABLE $testTable (id INT, salary INT) USING
foo").collect()
- session.sql(s"INSERT INTO $testTable VALUES (1, 100)").collect()
- checkRows(session.sql(s"SELECT * FROM $testTable"), Seq(Row(1, 100)))
-
- val catalog = getTableCatalog[InMemoryTableCatalog](session, "testcat")
- val addCol = TableChange.addColumn(Array("new_col"), IntegerType, true)
- catalog.alterTable(testIdent, addCol)
-
- externalAppend(catalog = catalog, ident = testIdent, row =
InternalRow(2, 200, -1))
-
- checkRows(
- session.sql(s"SELECT * FROM $testTable"),
- Seq(Row(1, 100, null), Row(2, 200, -1)))
- }
+ withTable(testTable) {
+ spark.sql(s"CREATE TABLE $testTable (id INT, salary INT) USING
foo").collect()
+ spark.sql(s"INSERT INTO $testTable VALUES (1, 100)").collect()
+ checkAnswer(spark.sql(s"SELECT * FROM $testTable"), Seq(Row(1, 100)))
+
+ val catalog = getTableCatalog[InMemoryTableCatalog](spark, "testcat")
+ val addCol = TableChange.addColumn(Array("new_col"), IntegerType, true)
+ catalog.alterTable(testIdent, addCol)
+
+ externalAppend(catalog = catalog, ident = testIdent, row =
InternalRow(2, 200, -1))
+
+ checkAnswer(
+ spark.sql(s"SELECT * FROM $testTable"),
+ Seq(Row(1, 100, null), Row(2, 200, -1)))
}
}
test(s"${testPrefix}connector w/ cache: repeated sql() stale after external
schema change") {
- withTestSession { session =>
- withTestTableAndViews(session, cachingTestTable) {
- session.sql(s"CREATE TABLE $cachingTestTable (id INT, salary INT)
USING foo").collect()
- session.sql(s"INSERT INTO $cachingTestTable VALUES (1, 100)").collect()
- checkRows(session.sql(s"SELECT * FROM $cachingTestTable"), Seq(Row(1,
100)))
-
- val catalog = getTableCatalog[CachingInMemoryTableCatalog](session,
"cachingcat")
- val addCol = TableChange.addColumn(Array("new_col"), IntegerType, true)
- catalog.alterTable(testIdent, addCol)
-
- externalAppend(catalog = catalog, ident = testIdent, row =
InternalRow(2, 200, -1))
-
- // Caching connector returns stale table: external changes invisible
- checkRows(session.sql(s"SELECT * FROM $cachingTestTable"), Seq(Row(1,
100)))
-
- // REFRESH TABLE invalidates the connector cache, schema change + data
visible
- session.sql(s"REFRESH TABLE $cachingTestTable").collect()
- checkRows(
- session.sql(s"SELECT * FROM $cachingTestTable"),
- Seq(Row(1, 100, null), Row(2, 200, -1)))
- }
+ withTable(cachingTestTable) {
+ spark.sql(s"CREATE TABLE $cachingTestTable (id INT, salary INT) USING
foo").collect()
+ spark.sql(s"INSERT INTO $cachingTestTable VALUES (1, 100)").collect()
+ checkAnswer(spark.sql(s"SELECT * FROM $cachingTestTable"), Seq(Row(1,
100)))
+
+ val catalog = getTableCatalog[CachingInMemoryTableCatalog](spark,
"cachingcat")
+ val addCol = TableChange.addColumn(Array("new_col"), IntegerType, true)
+ catalog.alterTable(testIdent, addCol)
+
+ externalAppend(catalog = catalog, ident = testIdent, row =
InternalRow(2, 200, -1))
+
+ // Caching connector returns stale table: external changes invisible
+ checkAnswer(spark.sql(s"SELECT * FROM $cachingTestTable"), Seq(Row(1,
100)))
+
+ // REFRESH TABLE invalidates the connector cache, schema change + data
visible
+ spark.sql(s"REFRESH TABLE $cachingTestTable").collect()
+ checkAnswer(
+ spark.sql(s"SELECT * FROM $cachingTestTable"),
+ Seq(Row(1, 100, null), Row(2, 200, -1)))
}
}
// Scenario 3: drop and recreate table
test(s"${testPrefix}repeated sql() reflects session drop/recreate") {
- withTestSession { session =>
- withTestTableAndViews(session, testTable) {
- session.sql(s"CREATE TABLE $testTable (id INT, salary INT) USING
foo").collect()
- session.sql(s"INSERT INTO $testTable VALUES (1, 100)").collect()
- checkRows(session.sql(s"SELECT * FROM $testTable"), Seq(Row(1, 100)))
-
- session.sql(s"DROP TABLE $testTable").collect()
- session.sql(s"CREATE TABLE $testTable (id INT, salary INT) USING
foo").collect()
- checkRows(session.sql(s"SELECT * FROM $testTable"), Seq.empty)
- }
+ withTable(testTable) {
+ spark.sql(s"CREATE TABLE $testTable (id INT, salary INT) USING
foo").collect()
+ spark.sql(s"INSERT INTO $testTable VALUES (1, 100)").collect()
+ checkAnswer(spark.sql(s"SELECT * FROM $testTable"), Seq(Row(1, 100)))
+
+ spark.sql(s"DROP TABLE $testTable").collect()
+ spark.sql(s"CREATE TABLE $testTable (id INT, salary INT) USING
foo").collect()
+ checkAnswer(spark.sql(s"SELECT * FROM $testTable"), Seq.empty)
}
}
test(s"${testPrefix}repeated sql() reflects external drop/recreate") {
- withTestSession { session =>
- withTestTableAndViews(session, testTable) {
- session.sql(s"CREATE TABLE $testTable (id INT, salary INT) USING
foo").collect()
- session.sql(s"INSERT INTO $testTable VALUES (1, 100)").collect()
- checkRows(session.sql(s"SELECT * FROM $testTable"), Seq(Row(1, 100)))
-
- val catalog = getTableCatalog[InMemoryTableCatalog](session, "testcat")
- catalog.dropTable(testIdent)
- catalog.createTable(
- testIdent,
- new TableInfo.Builder()
- .withColumns(Array(
- Column.create("id", IntegerType),
- Column.create("salary", IntegerType)))
- .build())
-
- checkRows(session.sql(s"SELECT * FROM $testTable"), Seq.empty)
- }
+ withTable(testTable) {
+ spark.sql(s"CREATE TABLE $testTable (id INT, salary INT) USING
foo").collect()
+ spark.sql(s"INSERT INTO $testTable VALUES (1, 100)").collect()
+ checkAnswer(spark.sql(s"SELECT * FROM $testTable"), Seq(Row(1, 100)))
+
+ val catalog = getTableCatalog[InMemoryTableCatalog](spark, "testcat")
+ catalog.dropTable(testIdent)
+ catalog.createTable(
+ testIdent,
+ new TableInfo.Builder()
+ .withColumns(Array(
+ Column.create("id", IntegerType),
+ Column.create("salary", IntegerType)))
+ .build())
+
+ checkAnswer(spark.sql(s"SELECT * FROM $testTable"), Seq.empty)
}
}
test(s"${testPrefix}connector w/ cache: repeated sql() stale after external
drop/recreate") {
- withTestSession { session =>
- withTestTableAndViews(session, cachingTestTable) {
- session.sql(s"CREATE TABLE $cachingTestTable (id INT, salary INT)
USING foo").collect()
- session.sql(s"INSERT INTO $cachingTestTable VALUES (1, 100)").collect()
- checkRows(session.sql(s"SELECT * FROM $cachingTestTable"), Seq(Row(1,
100)))
-
- val catalog = getTableCatalog[CachingInMemoryTableCatalog](session,
"cachingcat")
- catalog.dropTable(testIdent)
- catalog.createTable(
- testIdent,
- new TableInfo.Builder()
- .withColumns(Array(
- Column.create("id", IntegerType),
- Column.create("salary", IntegerType)))
- .build())
-
- // Caching connector returns stale table: drop/recreate invisible
- checkRows(session.sql(s"SELECT * FROM $cachingTestTable"), Seq(Row(1,
100)))
-
- // REFRESH TABLE invalidates the connector cache, new empty table
visible
- session.sql(s"REFRESH TABLE $cachingTestTable").collect()
- checkRows(session.sql(s"SELECT * FROM $cachingTestTable"), Seq.empty)
- }
+ withTable(cachingTestTable) {
+ spark.sql(s"CREATE TABLE $cachingTestTable (id INT, salary INT) USING
foo").collect()
+ spark.sql(s"INSERT INTO $cachingTestTable VALUES (1, 100)").collect()
+ checkAnswer(spark.sql(s"SELECT * FROM $cachingTestTable"), Seq(Row(1,
100)))
+
+ val catalog = getTableCatalog[CachingInMemoryTableCatalog](spark,
"cachingcat")
+ catalog.dropTable(testIdent)
+ catalog.createTable(
+ testIdent,
+ new TableInfo.Builder()
+ .withColumns(Array(
+ Column.create("id", IntegerType),
+ Column.create("salary", IntegerType)))
+ .build())
+
+ // Caching connector returns stale table: drop/recreate invisible
+ checkAnswer(spark.sql(s"SELECT * FROM $cachingTestTable"), Seq(Row(1,
100)))
+
+ // REFRESH TABLE invalidates the connector cache, new empty table visible
+ spark.sql(s"REFRESH TABLE $cachingTestTable").collect()
+ checkAnswer(spark.sql(s"SELECT * FROM $cachingTestTable"), Seq.empty)
}
}
}
diff --git
a/sql/core/src/test/scala/org/apache/spark/sql/connector/DSv2TempViewWithStoredPlanTests.scala
b/sql/core/src/test/scala/org/apache/spark/sql/connector/DSv2TempViewWithStoredPlanTests.scala
index 9f8a93e30550..e473968794c3 100644
---
a/sql/core/src/test/scala/org/apache/spark/sql/connector/DSv2TempViewWithStoredPlanTests.scala
+++
b/sql/core/src/test/scala/org/apache/spark/sql/connector/DSv2TempViewWithStoredPlanTests.scala
@@ -27,7 +27,7 @@ import org.apache.spark.sql.types.{IntegerType, LongType,
StringType}
* backed by DSv2 tables correctly handle data changes, schema changes, and
table recreation,
* both via session SQL and external catalog mutations.
*
- * NOTE: All `session.sql(...)` calls append `.collect()` because Connect
client DataFrames
+ * NOTE: All `spark.sql(...)` calls append `.collect()` because Connect client
DataFrames
* are lazy and require an action to trigger execution. In classic mode
`.collect()` on DDL
* is a no-op (DDL executes eagerly), so this is harmless.
*/
@@ -35,143 +35,143 @@ trait DSv2TempViewWithStoredPlanTests extends
DSv2ExternalMutationTestBase {
// Scenario 1.1 (session write)
test(s"${testPrefix}temp view with stored plan reflects session write") {
- withTestSession { session =>
- withTestTableAndViews(session, testTable, Seq("v")) {
- session.sql(s"CREATE TABLE $testTable (id INT, salary INT) USING
foo").collect()
- session.sql(s"INSERT INTO $testTable VALUES (1, 100), (10,
1000)").collect()
+ withTable(testTable) {
+ withView("v") {
+ spark.sql(s"CREATE TABLE $testTable (id INT, salary INT) USING
foo").collect()
+ spark.sql(s"INSERT INTO $testTable VALUES (1, 100), (10,
1000)").collect()
- session.table(testTable).filter("salary <
999").createOrReplaceTempView("v")
- checkRows(session.table("v"), Seq(Row(1, 100)))
+ spark.table(testTable).filter("salary <
999").createOrReplaceTempView("v")
+ checkAnswer(spark.table("v"), Seq(Row(1, 100)))
- session.sql(s"INSERT INTO $testTable VALUES (2, 200)").collect()
- checkRows(session.table("v"), Seq(Row(1, 100), Row(2, 200)))
+ spark.sql(s"INSERT INTO $testTable VALUES (2, 200)").collect()
+ checkAnswer(spark.table("v"), Seq(Row(1, 100), Row(2, 200)))
}
}
}
// Scenario 1.2 (external write)
test(s"${testPrefix}temp view with stored plan reflects external write") {
- withTestSession { session =>
- withTestTableAndViews(session, testTable, Seq("v")) {
- session.sql(s"CREATE TABLE $testTable (id INT, salary INT) USING
foo").collect()
- session.sql(s"INSERT INTO $testTable VALUES (1, 100), (10,
1000)").collect()
+ withTable(testTable) {
+ withView("v") {
+ spark.sql(s"CREATE TABLE $testTable (id INT, salary INT) USING
foo").collect()
+ spark.sql(s"INSERT INTO $testTable VALUES (1, 100), (10,
1000)").collect()
- session.table(testTable).filter("salary <
999").createOrReplaceTempView("v")
- checkRows(session.table("v"), Seq(Row(1, 100)))
+ spark.table(testTable).filter("salary <
999").createOrReplaceTempView("v")
+ checkAnswer(spark.table("v"), Seq(Row(1, 100)))
- val catalog = getTableCatalog[InMemoryTableCatalog](session, "testcat")
+ val catalog = getTableCatalog[InMemoryTableCatalog](spark, "testcat")
externalAppend(catalog = catalog, ident = testIdent, row =
InternalRow(2, 200))
- checkRows(session.table("v"), Seq(Row(1, 100), Row(2, 200)))
+ checkAnswer(spark.table("v"), Seq(Row(1, 100), Row(2, 200)))
}
}
}
// Scenario 1.2 connector w/ cache (external write, caching connector)
test(s"${testPrefix}connector w/ cache: temp view stale after external
write") {
- withTestSession { session =>
- withTestTableAndViews(session, cachingTestTable, Seq("v")) {
- session.sql(s"CREATE TABLE $cachingTestTable (id INT, salary INT)
USING foo").collect()
- session.sql(s"INSERT INTO $cachingTestTable VALUES (1, 100), (10,
1000)").collect()
+ withTable(cachingTestTable) {
+ withView("v") {
+ spark.sql(s"CREATE TABLE $cachingTestTable (id INT, salary INT) USING
foo").collect()
+ spark.sql(s"INSERT INTO $cachingTestTable VALUES (1, 100), (10,
1000)").collect()
- session.table(cachingTestTable).filter("salary <
999").createOrReplaceTempView("v")
- checkRows(session.table("v"), Seq(Row(1, 100)))
+ spark.table(cachingTestTable).filter("salary <
999").createOrReplaceTempView("v")
+ checkAnswer(spark.table("v"), Seq(Row(1, 100)))
- val catalog = getTableCatalog[CachingInMemoryTableCatalog](session,
"cachingcat")
+ val catalog = getTableCatalog[CachingInMemoryTableCatalog](spark,
"cachingcat")
externalAppend(catalog = catalog, ident = testIdent, row =
InternalRow(2, 200))
// Caching connector returns stale table: external write invisible
- checkRows(session.table("v"), Seq(Row(1, 100)))
+ checkAnswer(spark.table("v"), Seq(Row(1, 100)))
// REFRESH TABLE invalidates the connector cache, external write
becomes visible
- session.sql(s"REFRESH TABLE $cachingTestTable").collect()
- checkRows(session.table("v"), Seq(Row(1, 100), Row(2, 200)))
+ spark.sql(s"REFRESH TABLE $cachingTestTable").collect()
+ checkAnswer(spark.table("v"), Seq(Row(1, 100), Row(2, 200)))
}
}
}
// Scenario 2.1 (session ADD COLUMN)
test(s"${testPrefix}temp view with stored plan preserves schema after
session ADD COLUMN") {
- withTestSession { session =>
- withTestTableAndViews(session, testTable, Seq("v")) {
- session.sql(s"CREATE TABLE $testTable (id INT, salary INT) USING
foo").collect()
- session.sql(s"INSERT INTO $testTable VALUES (1, 100), (10,
1000)").collect()
+ withTable(testTable) {
+ withView("v") {
+ spark.sql(s"CREATE TABLE $testTable (id INT, salary INT) USING
foo").collect()
+ spark.sql(s"INSERT INTO $testTable VALUES (1, 100), (10,
1000)").collect()
- session.table(testTable).filter("salary <
999").createOrReplaceTempView("v")
- checkRows(session.table("v"), Seq(Row(1, 100)))
+ spark.table(testTable).filter("salary <
999").createOrReplaceTempView("v")
+ checkAnswer(spark.table("v"), Seq(Row(1, 100)))
- session.sql(s"ALTER TABLE $testTable ADD COLUMN new_column
INT").collect()
- session.sql(s"INSERT INTO $testTable VALUES (2, 200, -1)").collect()
+ spark.sql(s"ALTER TABLE $testTable ADD COLUMN new_column
INT").collect()
+ spark.sql(s"INSERT INTO $testTable VALUES (2, 200, -1)").collect()
// view preserves original 2-column schema, filter still applied
- checkRows(session.table("v"), Seq(Row(1, 100), Row(2, 200)))
+ checkAnswer(spark.table("v"), Seq(Row(1, 100), Row(2, 200)))
}
}
}
// Scenario 2.2 (external ADD COLUMN)
test(s"${testPrefix}temp view with stored plan preserves schema after
external ADD COLUMN") {
- withTestSession { session =>
- withTestTableAndViews(session, testTable, Seq("v")) {
- session.sql(s"CREATE TABLE $testTable (id INT, salary INT) USING
foo").collect()
- session.sql(s"INSERT INTO $testTable VALUES (1, 100), (10,
1000)").collect()
+ withTable(testTable) {
+ withView("v") {
+ spark.sql(s"CREATE TABLE $testTable (id INT, salary INT) USING
foo").collect()
+ spark.sql(s"INSERT INTO $testTable VALUES (1, 100), (10,
1000)").collect()
- session.table(testTable).filter("salary <
999").createOrReplaceTempView("v")
- checkRows(session.table("v"), Seq(Row(1, 100)))
+ spark.table(testTable).filter("salary <
999").createOrReplaceTempView("v")
+ checkAnswer(spark.table("v"), Seq(Row(1, 100)))
// external schema change via catalog API
- val catalog = getTableCatalog[InMemoryTableCatalog](session, "testcat")
+ val catalog = getTableCatalog[InMemoryTableCatalog](spark, "testcat")
val addCol = TableChange.addColumn(Array("new_column"), IntegerType,
true)
catalog.alterTable(testIdent, addCol)
externalAppend(catalog = catalog, ident = testIdent, row =
InternalRow(2, 200, -1))
// view preserves original 2-column schema, filter still applied
- checkRows(session.table("v"), Seq(Row(1, 100), Row(2, 200)))
+ checkAnswer(spark.table("v"), Seq(Row(1, 100), Row(2, 200)))
}
}
}
// Scenario 2.2 connector w/ cache (external ADD COLUMN, caching connector)
test(s"${testPrefix}connector w/ cache: temp view stale after external ADD
COLUMN") {
- withTestSession { session =>
- withTestTableAndViews(session, cachingTestTable, Seq("v")) {
- session.sql(s"CREATE TABLE $cachingTestTable (id INT, salary INT)
USING foo").collect()
- session.sql(s"INSERT INTO $cachingTestTable VALUES (1, 100), (10,
1000)").collect()
+ withTable(cachingTestTable) {
+ withView("v") {
+ spark.sql(s"CREATE TABLE $cachingTestTable (id INT, salary INT) USING
foo").collect()
+ spark.sql(s"INSERT INTO $cachingTestTable VALUES (1, 100), (10,
1000)").collect()
- session.table(cachingTestTable).filter("salary <
999").createOrReplaceTempView("v")
- checkRows(session.table("v"), Seq(Row(1, 100)))
+ spark.table(cachingTestTable).filter("salary <
999").createOrReplaceTempView("v")
+ checkAnswer(spark.table("v"), Seq(Row(1, 100)))
- val catalog = getTableCatalog[CachingInMemoryTableCatalog](session,
"cachingcat")
+ val catalog = getTableCatalog[CachingInMemoryTableCatalog](spark,
"cachingcat")
val addCol = TableChange.addColumn(Array("new_column"), IntegerType,
true)
catalog.alterTable(testIdent, addCol)
externalAppend(catalog = catalog, ident = testIdent, row =
InternalRow(2, 200, -1))
// Caching connector returns stale table: external changes invisible
- checkRows(session.table("v"), Seq(Row(1, 100)))
+ checkAnswer(spark.table("v"), Seq(Row(1, 100)))
// REFRESH TABLE invalidates the connector cache, view preserves
original 2-column schema
- session.sql(s"REFRESH TABLE $cachingTestTable").collect()
- checkRows(session.table("v"), Seq(Row(1, 100), Row(2, 200)))
+ spark.sql(s"REFRESH TABLE $cachingTestTable").collect()
+ checkAnswer(spark.table("v"), Seq(Row(1, 100), Row(2, 200)))
}
}
}
// Scenario 3.1 (session column removal)
test(s"${testPrefix}temp view with stored plan detects session column
removal") {
- withTestSession { session =>
- withTestTableAndViews(session, testTable, Seq("v")) {
- session.sql(s"CREATE TABLE $testTable (id INT, salary INT) USING
foo").collect()
- session.sql(s"INSERT INTO $testTable VALUES (1, 100), (10,
1000)").collect()
+ withTable(testTable) {
+ withView("v") {
+ spark.sql(s"CREATE TABLE $testTable (id INT, salary INT) USING
foo").collect()
+ spark.sql(s"INSERT INTO $testTable VALUES (1, 100), (10,
1000)").collect()
- session.table(testTable).filter("salary <
999").createOrReplaceTempView("v")
- checkRows(session.table("v"), Seq(Row(1, 100)))
+ spark.table(testTable).filter("salary <
999").createOrReplaceTempView("v")
+ checkAnswer(spark.table("v"), Seq(Row(1, 100)))
- session.sql(s"ALTER TABLE $testTable DROP COLUMN salary").collect()
+ spark.sql(s"ALTER TABLE $testTable DROP COLUMN salary").collect()
checkError(
- exception = intercept[AnalysisException] {
session.table("v").collect() },
+ exception = intercept[AnalysisException] {
spark.table("v").collect() },
condition =
"INCOMPATIBLE_COLUMN_CHANGES_AFTER_VIEW_WITH_PLAN_CREATION",
parameters = Map(
"viewName" -> "`v`",
@@ -184,20 +184,20 @@ trait DSv2TempViewWithStoredPlanTests extends
DSv2ExternalMutationTestBase {
// Scenario 3.2 (external column removal)
test(s"${testPrefix}temp view with stored plan detects external column
removal") {
- withTestSession { session =>
- withTestTableAndViews(session, testTable, Seq("v")) {
- session.sql(s"CREATE TABLE $testTable (id INT, salary INT) USING
foo").collect()
- session.sql(s"INSERT INTO $testTable VALUES (1, 100), (10,
1000)").collect()
+ withTable(testTable) {
+ withView("v") {
+ spark.sql(s"CREATE TABLE $testTable (id INT, salary INT) USING
foo").collect()
+ spark.sql(s"INSERT INTO $testTable VALUES (1, 100), (10,
1000)").collect()
- session.table(testTable).filter("salary <
999").createOrReplaceTempView("v")
- checkRows(session.table("v"), Seq(Row(1, 100)))
+ spark.table(testTable).filter("salary <
999").createOrReplaceTempView("v")
+ checkAnswer(spark.table("v"), Seq(Row(1, 100)))
- val catalog = getTableCatalog[InMemoryTableCatalog](session, "testcat")
+ val catalog = getTableCatalog[InMemoryTableCatalog](spark, "testcat")
val dropCol = TableChange.deleteColumn(Array("salary"), false)
catalog.alterTable(testIdent, dropCol)
checkError(
- exception = intercept[AnalysisException] {
session.table("v").collect() },
+ exception = intercept[AnalysisException] {
spark.table("v").collect() },
condition =
"INCOMPATIBLE_COLUMN_CHANGES_AFTER_VIEW_WITH_PLAN_CREATION",
parameters = Map(
"viewName" -> "`v`",
@@ -210,25 +210,25 @@ trait DSv2TempViewWithStoredPlanTests extends
DSv2ExternalMutationTestBase {
// Scenario 3.2 connector w/ cache (external column removal, caching
connector)
test(s"${testPrefix}connector w/ cache: temp view stale after external
column removal") {
- withTestSession { session =>
- withTestTableAndViews(session, cachingTestTable, Seq("v")) {
- session.sql(s"CREATE TABLE $cachingTestTable (id INT, salary INT)
USING foo").collect()
- session.sql(s"INSERT INTO $cachingTestTable VALUES (1, 100), (10,
1000)").collect()
+ withTable(cachingTestTable) {
+ withView("v") {
+ spark.sql(s"CREATE TABLE $cachingTestTable (id INT, salary INT) USING
foo").collect()
+ spark.sql(s"INSERT INTO $cachingTestTable VALUES (1, 100), (10,
1000)").collect()
- session.table(cachingTestTable).filter("salary <
999").createOrReplaceTempView("v")
- checkRows(session.table("v"), Seq(Row(1, 100)))
+ spark.table(cachingTestTable).filter("salary <
999").createOrReplaceTempView("v")
+ checkAnswer(spark.table("v"), Seq(Row(1, 100)))
- val catalog = getTableCatalog[CachingInMemoryTableCatalog](session,
"cachingcat")
+ val catalog = getTableCatalog[CachingInMemoryTableCatalog](spark,
"cachingcat")
val dropCol = TableChange.deleteColumn(Array("salary"), false)
catalog.alterTable(testIdent, dropCol)
// Caching connector returns stale table: column removal invisible, no
error
- checkRows(session.table("v"), Seq(Row(1, 100)))
+ checkAnswer(spark.table("v"), Seq(Row(1, 100)))
// REFRESH TABLE invalidates the connector cache, column removal
detected
- session.sql(s"REFRESH TABLE $cachingTestTable").collect()
+ spark.sql(s"REFRESH TABLE $cachingTestTable").collect()
checkError(
- exception = intercept[AnalysisException] {
session.table("v").collect() },
+ exception = intercept[AnalysisException] {
spark.table("v").collect() },
condition =
"INCOMPATIBLE_COLUMN_CHANGES_AFTER_VIEW_WITH_PLAN_CREATION",
parameters = Map(
"viewName" -> "`v`",
@@ -241,43 +241,43 @@ trait DSv2TempViewWithStoredPlanTests extends
DSv2ExternalMutationTestBase {
// Scenario 4.1 (session drop and recreate table)
test(s"${testPrefix}temp view with stored plan resolves to session-recreated
table") {
- withTestSession { session =>
- withTestTableAndViews(session, testTable, Seq("v")) {
- session.sql(s"CREATE TABLE $testTable (id INT, salary INT) USING
foo").collect()
- session.sql(s"INSERT INTO $testTable VALUES (1, 100), (10,
1000)").collect()
+ withTable(testTable) {
+ withView("v") {
+ spark.sql(s"CREATE TABLE $testTable (id INT, salary INT) USING
foo").collect()
+ spark.sql(s"INSERT INTO $testTable VALUES (1, 100), (10,
1000)").collect()
- session.table(testTable).filter("salary <
999").createOrReplaceTempView("v")
- checkRows(session.table("v"), Seq(Row(1, 100)))
+ spark.table(testTable).filter("salary <
999").createOrReplaceTempView("v")
+ checkAnswer(spark.table("v"), Seq(Row(1, 100)))
- val catalog = getTableCatalog[InMemoryTableCatalog](session, "testcat")
+ val catalog = getTableCatalog[InMemoryTableCatalog](spark, "testcat")
val originalTableId = catalog.loadTable(testIdent).id
- session.sql(s"DROP TABLE $testTable").collect()
- session.sql(s"CREATE TABLE $testTable (id INT, salary INT) USING
foo").collect()
+ spark.sql(s"DROP TABLE $testTable").collect()
+ spark.sql(s"CREATE TABLE $testTable (id INT, salary INT) USING
foo").collect()
val newTableId = catalog.loadTable(testIdent).id
assert(originalTableId != newTableId)
// view resolves to the new empty table
- checkRows(session.table("v"), Seq.empty)
+ checkAnswer(spark.table("v"), Seq.empty)
- session.sql(s"INSERT INTO $testTable VALUES (2, 200)").collect()
- checkRows(session.table("v"), Seq(Row(2, 200)))
+ spark.sql(s"INSERT INTO $testTable VALUES (2, 200)").collect()
+ checkAnswer(spark.table("v"), Seq(Row(2, 200)))
}
}
}
// Scenario 4.2 (external drop and recreate table)
test(s"${testPrefix}temp view with stored plan resolves to externally
recreated table") {
- withTestSession { session =>
- withTestTableAndViews(session, testTable, Seq("v")) {
- session.sql(s"CREATE TABLE $testTable (id INT, salary INT) USING
foo").collect()
- session.sql(s"INSERT INTO $testTable VALUES (1, 100), (10,
1000)").collect()
+ withTable(testTable) {
+ withView("v") {
+ spark.sql(s"CREATE TABLE $testTable (id INT, salary INT) USING
foo").collect()
+ spark.sql(s"INSERT INTO $testTable VALUES (1, 100), (10,
1000)").collect()
- session.table(testTable).filter("salary <
999").createOrReplaceTempView("v")
- checkRows(session.table("v"), Seq(Row(1, 100)))
+ spark.table(testTable).filter("salary <
999").createOrReplaceTempView("v")
+ checkAnswer(spark.table("v"), Seq(Row(1, 100)))
- val catalog = getTableCatalog[InMemoryTableCatalog](session, "testcat")
+ val catalog = getTableCatalog[InMemoryTableCatalog](spark, "testcat")
val originalTableId = catalog.loadTable(testIdent).id
catalog.dropTable(testIdent)
@@ -293,25 +293,25 @@ trait DSv2TempViewWithStoredPlanTests extends
DSv2ExternalMutationTestBase {
assert(originalTableId != newTableId)
// view resolves to the new empty table
- checkRows(session.table("v"), Seq.empty)
+ checkAnswer(spark.table("v"), Seq.empty)
- session.sql(s"INSERT INTO $testTable VALUES (2, 200)").collect()
- checkRows(session.table("v"), Seq(Row(2, 200)))
+ spark.sql(s"INSERT INTO $testTable VALUES (2, 200)").collect()
+ checkAnswer(spark.table("v"), Seq(Row(2, 200)))
}
}
}
// Scenario 4.2 connector w/ cache (external drop/recreate, caching
connector)
test(s"${testPrefix}connector w/ cache: temp view stale after external
drop/recreate") {
- withTestSession { session =>
- withTestTableAndViews(session, cachingTestTable, Seq("v")) {
- session.sql(s"CREATE TABLE $cachingTestTable (id INT, salary INT)
USING foo").collect()
- session.sql(s"INSERT INTO $cachingTestTable VALUES (1, 100), (10,
1000)").collect()
+ withTable(cachingTestTable) {
+ withView("v") {
+ spark.sql(s"CREATE TABLE $cachingTestTable (id INT, salary INT) USING
foo").collect()
+ spark.sql(s"INSERT INTO $cachingTestTable VALUES (1, 100), (10,
1000)").collect()
- session.table(cachingTestTable).filter("salary <
999").createOrReplaceTempView("v")
- checkRows(session.table("v"), Seq(Row(1, 100)))
+ spark.table(cachingTestTable).filter("salary <
999").createOrReplaceTempView("v")
+ checkAnswer(spark.table("v"), Seq(Row(1, 100)))
- val catalog = getTableCatalog[CachingInMemoryTableCatalog](session,
"cachingcat")
+ val catalog = getTableCatalog[CachingInMemoryTableCatalog](spark,
"cachingcat")
catalog.dropTable(testIdent)
catalog.createTable(
testIdent,
@@ -322,11 +322,11 @@ trait DSv2TempViewWithStoredPlanTests extends
DSv2ExternalMutationTestBase {
.build())
// Caching connector returns stale table: drop/recreate invisible
- checkRows(session.table("v"), Seq(Row(1, 100)))
+ checkAnswer(spark.table("v"), Seq(Row(1, 100)))
// REFRESH TABLE invalidates the connector cache, view resolves to new
empty table
- session.sql(s"REFRESH TABLE $cachingTestTable").collect()
- checkRows(session.table("v"), Seq.empty)
+ spark.sql(s"REFRESH TABLE $cachingTestTable").collect()
+ checkAnswer(spark.table("v"), Seq.empty)
}
}
}
@@ -334,29 +334,29 @@ trait DSv2TempViewWithStoredPlanTests extends
DSv2ExternalMutationTestBase {
// Scenario 5.1 (session drop and re-add column with same type, multiple
views)
test(s"${testPrefix}temp view with stored plan after session drop and re-add
column same type" +
" with unfiltered view") {
- withTestSession { session =>
- withTestTableAndViews(session, testTable, Seq("v", "v_no_filter",
"v_filter_is_null")) {
- session.sql(s"CREATE TABLE $testTable (id INT, salary INT) USING
foo").collect()
- session.sql(s"INSERT INTO $testTable VALUES (1, 100), (10,
1000)").collect()
-
- session.table(testTable).filter("salary <
999").createOrReplaceTempView("v")
- session.table(testTable).createOrReplaceTempView("v_no_filter")
- session.table(testTable).filter("salary IS NULL")
+ withTable(testTable) {
+ withView("v", "v_no_filter", "v_filter_is_null") {
+ spark.sql(s"CREATE TABLE $testTable (id INT, salary INT) USING
foo").collect()
+ spark.sql(s"INSERT INTO $testTable VALUES (1, 100), (10,
1000)").collect()
+
+ spark.table(testTable).filter("salary <
999").createOrReplaceTempView("v")
+ spark.table(testTable).createOrReplaceTempView("v_no_filter")
+ spark.table(testTable).filter("salary IS NULL")
.createOrReplaceTempView("v_filter_is_null")
- checkRows(session.table("v"), Seq(Row(1, 100)))
- checkRows(session.table("v_no_filter"), Seq(Row(1, 100), Row(10,
1000)))
- checkRows(session.table("v_filter_is_null"), Seq.empty)
+ checkAnswer(spark.table("v"), Seq(Row(1, 100)))
+ checkAnswer(spark.table("v_no_filter"), Seq(Row(1, 100), Row(10,
1000)))
+ checkAnswer(spark.table("v_filter_is_null"), Seq.empty)
// drop and re-add column with same name and type
- session.sql(s"ALTER TABLE $testTable DROP COLUMN salary").collect()
- session.sql(s"ALTER TABLE $testTable ADD COLUMN salary INT").collect()
+ spark.sql(s"ALTER TABLE $testTable DROP COLUMN salary").collect()
+ spark.sql(s"ALTER TABLE $testTable ADD COLUMN salary INT").collect()
// salary values are now null, so the filtered view returns nothing
- checkRows(session.table("v"), Seq.empty)
+ checkAnswer(spark.table("v"), Seq.empty)
// unfiltered view returns rows with null salary
- checkRows(session.table("v_no_filter"), Seq(Row(1, null), Row(10,
null)))
+ checkAnswer(spark.table("v_no_filter"), Seq(Row(1, null), Row(10,
null)))
// IS NULL filter now matches all rows
- checkRows(session.table("v_filter_is_null"), Seq(Row(1, null), Row(10,
null)))
+ checkAnswer(spark.table("v_filter_is_null"), Seq(Row(1, null), Row(10,
null)))
}
}
}
@@ -364,31 +364,31 @@ trait DSv2TempViewWithStoredPlanTests extends
DSv2ExternalMutationTestBase {
// Scenario 5.2 (external drop and re-add column with same type)
test(s"${testPrefix}temp view with stored plan after external drop and
re-add column " +
"same type") {
- withTestSession { session =>
- withTestTableAndViews(session, testTable, Seq("v", "v_no_filter",
"v_filter_is_null")) {
- session.sql(s"CREATE TABLE $testTable (id INT, salary INT) USING
foo").collect()
- session.sql(s"INSERT INTO $testTable VALUES (1, 100), (10,
1000)").collect()
-
- session.table(testTable).filter("salary <
999").createOrReplaceTempView("v")
- session.table(testTable).createOrReplaceTempView("v_no_filter")
- session.table(testTable).filter("salary IS NULL")
+ withTable(testTable) {
+ withView("v", "v_no_filter", "v_filter_is_null") {
+ spark.sql(s"CREATE TABLE $testTable (id INT, salary INT) USING
foo").collect()
+ spark.sql(s"INSERT INTO $testTable VALUES (1, 100), (10,
1000)").collect()
+
+ spark.table(testTable).filter("salary <
999").createOrReplaceTempView("v")
+ spark.table(testTable).createOrReplaceTempView("v_no_filter")
+ spark.table(testTable).filter("salary IS NULL")
.createOrReplaceTempView("v_filter_is_null")
- checkRows(session.table("v"), Seq(Row(1, 100)))
- checkRows(session.table("v_no_filter"), Seq(Row(1, 100), Row(10,
1000)))
- checkRows(session.table("v_filter_is_null"), Seq.empty)
+ checkAnswer(spark.table("v"), Seq(Row(1, 100)))
+ checkAnswer(spark.table("v_no_filter"), Seq(Row(1, 100), Row(10,
1000)))
+ checkAnswer(spark.table("v_filter_is_null"), Seq.empty)
// external drop and re-add column via catalog API
- val catalog = getTableCatalog[InMemoryTableCatalog](session, "testcat")
+ val catalog = getTableCatalog[InMemoryTableCatalog](spark, "testcat")
val dropCol = TableChange.deleteColumn(Array("salary"), false)
val addCol = TableChange.addColumn(Array("salary"), IntegerType, true)
catalog.alterTable(testIdent, dropCol, addCol)
// salary values are now null, so the filtered view returns nothing
- checkRows(session.table("v"), Seq.empty)
+ checkAnswer(spark.table("v"), Seq.empty)
// unfiltered view returns rows with null salary
- checkRows(session.table("v_no_filter"), Seq(Row(1, null), Row(10,
null)))
+ checkAnswer(spark.table("v_no_filter"), Seq(Row(1, null), Row(10,
null)))
// IS NULL filter now matches all rows
- checkRows(session.table("v_filter_is_null"), Seq(Row(1, null), Row(10,
null)))
+ checkAnswer(spark.table("v_filter_is_null"), Seq(Row(1, null), Row(10,
null)))
}
}
}
@@ -396,44 +396,44 @@ trait DSv2TempViewWithStoredPlanTests extends
DSv2ExternalMutationTestBase {
// Scenario 5.2 connector w/ cache (external drop/re-add column, caching
connector)
test(s"${testPrefix}connector w/ cache: temp view stale after external
drop/re-add column " +
"same type") {
- withTestSession { session =>
- withTestTableAndViews(session, cachingTestTable, Seq("v")) {
- session.sql(s"CREATE TABLE $cachingTestTable (id INT, salary INT)
USING foo").collect()
- session.sql(s"INSERT INTO $cachingTestTable VALUES (1, 100), (10,
1000)").collect()
+ withTable(cachingTestTable) {
+ withView("v") {
+ spark.sql(s"CREATE TABLE $cachingTestTable (id INT, salary INT) USING
foo").collect()
+ spark.sql(s"INSERT INTO $cachingTestTable VALUES (1, 100), (10,
1000)").collect()
- session.table(cachingTestTable).filter("salary <
999").createOrReplaceTempView("v")
- checkRows(session.table("v"), Seq(Row(1, 100)))
+ spark.table(cachingTestTable).filter("salary <
999").createOrReplaceTempView("v")
+ checkAnswer(spark.table("v"), Seq(Row(1, 100)))
- val catalog = getTableCatalog[CachingInMemoryTableCatalog](session,
"cachingcat")
+ val catalog = getTableCatalog[CachingInMemoryTableCatalog](spark,
"cachingcat")
val dropCol = TableChange.deleteColumn(Array("salary"), false)
val addCol = TableChange.addColumn(Array("salary"), IntegerType, true)
catalog.alterTable(testIdent, dropCol, addCol)
// Caching connector returns stale table: column drop/re-add invisible
- checkRows(session.table("v"), Seq(Row(1, 100)))
+ checkAnswer(spark.table("v"), Seq(Row(1, 100)))
// REFRESH TABLE invalidates the connector cache, salary values are
null
- session.sql(s"REFRESH TABLE $cachingTestTable").collect()
- checkRows(session.table("v"), Seq.empty)
+ spark.sql(s"REFRESH TABLE $cachingTestTable").collect()
+ checkAnswer(spark.table("v"), Seq.empty)
}
}
}
// Scenario 6.1 (session drop and re-add column with different type)
test(s"${testPrefix}temp view with stored plan detects session column type
change") {
- withTestSession { session =>
- withTestTableAndViews(session, testTable, Seq("v")) {
- session.sql(s"CREATE TABLE $testTable (id INT, salary INT) USING
foo").collect()
- session.sql(s"INSERT INTO $testTable VALUES (1, 100), (10,
1000)").collect()
+ withTable(testTable) {
+ withView("v") {
+ spark.sql(s"CREATE TABLE $testTable (id INT, salary INT) USING
foo").collect()
+ spark.sql(s"INSERT INTO $testTable VALUES (1, 100), (10,
1000)").collect()
- session.table(testTable).filter("salary <
999").createOrReplaceTempView("v")
- checkRows(session.table("v"), Seq(Row(1, 100)))
+ spark.table(testTable).filter("salary <
999").createOrReplaceTempView("v")
+ checkAnswer(spark.table("v"), Seq(Row(1, 100)))
- session.sql(s"ALTER TABLE $testTable DROP COLUMN salary").collect()
- session.sql(s"ALTER TABLE $testTable ADD COLUMN salary
STRING").collect()
+ spark.sql(s"ALTER TABLE $testTable DROP COLUMN salary").collect()
+ spark.sql(s"ALTER TABLE $testTable ADD COLUMN salary STRING").collect()
checkError(
- exception = intercept[AnalysisException] {
session.table("v").collect() },
+ exception = intercept[AnalysisException] {
spark.table("v").collect() },
condition =
"INCOMPATIBLE_COLUMN_CHANGES_AFTER_VIEW_WITH_PLAN_CREATION",
parameters = Map(
"viewName" -> "`v`",
@@ -446,21 +446,21 @@ trait DSv2TempViewWithStoredPlanTests extends
DSv2ExternalMutationTestBase {
// Scenario 6.2 (external drop and re-add column with different type)
test(s"${testPrefix}temp view with stored plan detects external column type
change") {
- withTestSession { session =>
- withTestTableAndViews(session, testTable, Seq("v")) {
- session.sql(s"CREATE TABLE $testTable (id INT, salary INT) USING
foo").collect()
- session.sql(s"INSERT INTO $testTable VALUES (1, 100), (10,
1000)").collect()
+ withTable(testTable) {
+ withView("v") {
+ spark.sql(s"CREATE TABLE $testTable (id INT, salary INT) USING
foo").collect()
+ spark.sql(s"INSERT INTO $testTable VALUES (1, 100), (10,
1000)").collect()
- session.table(testTable).filter("salary <
999").createOrReplaceTempView("v")
- checkRows(session.table("v"), Seq(Row(1, 100)))
+ spark.table(testTable).filter("salary <
999").createOrReplaceTempView("v")
+ checkAnswer(spark.table("v"), Seq(Row(1, 100)))
- val catalog = getTableCatalog[InMemoryTableCatalog](session, "testcat")
+ val catalog = getTableCatalog[InMemoryTableCatalog](spark, "testcat")
val dropCol = TableChange.deleteColumn(Array("salary"), false)
val addCol = TableChange.addColumn(Array("salary"), StringType, true)
catalog.alterTable(testIdent, dropCol, addCol)
checkError(
- exception = intercept[AnalysisException] {
session.table("v").collect() },
+ exception = intercept[AnalysisException] {
spark.table("v").collect() },
condition =
"INCOMPATIBLE_COLUMN_CHANGES_AFTER_VIEW_WITH_PLAN_CREATION",
parameters = Map(
"viewName" -> "`v`",
@@ -473,26 +473,26 @@ trait DSv2TempViewWithStoredPlanTests extends
DSv2ExternalMutationTestBase {
// Scenario 6.2 connector w/ cache (external column type change, caching
connector)
test(s"${testPrefix}connector w/ cache: temp view stale after external
column type change") {
- withTestSession { session =>
- withTestTableAndViews(session, cachingTestTable, Seq("v")) {
- session.sql(s"CREATE TABLE $cachingTestTable (id INT, salary INT)
USING foo").collect()
- session.sql(s"INSERT INTO $cachingTestTable VALUES (1, 100), (10,
1000)").collect()
+ withTable(cachingTestTable) {
+ withView("v") {
+ spark.sql(s"CREATE TABLE $cachingTestTable (id INT, salary INT) USING
foo").collect()
+ spark.sql(s"INSERT INTO $cachingTestTable VALUES (1, 100), (10,
1000)").collect()
- session.table(cachingTestTable).filter("salary <
999").createOrReplaceTempView("v")
- checkRows(session.table("v"), Seq(Row(1, 100)))
+ spark.table(cachingTestTable).filter("salary <
999").createOrReplaceTempView("v")
+ checkAnswer(spark.table("v"), Seq(Row(1, 100)))
- val catalog = getTableCatalog[CachingInMemoryTableCatalog](session,
"cachingcat")
+ val catalog = getTableCatalog[CachingInMemoryTableCatalog](spark,
"cachingcat")
val dropCol = TableChange.deleteColumn(Array("salary"), false)
val addCol = TableChange.addColumn(Array("salary"), StringType, true)
catalog.alterTable(testIdent, dropCol, addCol)
// Caching connector returns stale table: type change invisible, no
error
- checkRows(session.table("v"), Seq(Row(1, 100)))
+ checkAnswer(spark.table("v"), Seq(Row(1, 100)))
// REFRESH TABLE invalidates the connector cache, type change detected
- session.sql(s"REFRESH TABLE $cachingTestTable").collect()
+ spark.sql(s"REFRESH TABLE $cachingTestTable").collect()
checkError(
- exception = intercept[AnalysisException] {
session.table("v").collect() },
+ exception = intercept[AnalysisException] {
spark.table("v").collect() },
condition =
"INCOMPATIBLE_COLUMN_CHANGES_AFTER_VIEW_WITH_PLAN_CREATION",
parameters = Map(
"viewName" -> "`v`",
@@ -505,18 +505,18 @@ trait DSv2TempViewWithStoredPlanTests extends
DSv2ExternalMutationTestBase {
// Scenario 7.1 (session type widening from INT to BIGINT)
test(s"${testPrefix}temp view with stored plan detects session type
widening") {
- withTestSession { session =>
- withTestTableAndViews(session, testTable, Seq("v")) {
- session.sql(s"CREATE TABLE $testTable (id INT, salary INT) USING
foo").collect()
- session.sql(s"INSERT INTO $testTable VALUES (1, 100), (10,
1000)").collect()
+ withTable(testTable) {
+ withView("v") {
+ spark.sql(s"CREATE TABLE $testTable (id INT, salary INT) USING
foo").collect()
+ spark.sql(s"INSERT INTO $testTable VALUES (1, 100), (10,
1000)").collect()
- session.table(testTable).filter("salary <
999").createOrReplaceTempView("v")
- checkRows(session.table("v"), Seq(Row(1, 100)))
+ spark.table(testTable).filter("salary <
999").createOrReplaceTempView("v")
+ checkAnswer(spark.table("v"), Seq(Row(1, 100)))
- session.sql(s"ALTER TABLE $testTable ALTER COLUMN salary TYPE
LONG").collect()
+ spark.sql(s"ALTER TABLE $testTable ALTER COLUMN salary TYPE
LONG").collect()
checkError(
- exception = intercept[AnalysisException] {
session.table("v").collect() },
+ exception = intercept[AnalysisException] {
spark.table("v").collect() },
condition =
"INCOMPATIBLE_COLUMN_CHANGES_AFTER_VIEW_WITH_PLAN_CREATION",
parameters = Map(
"viewName" -> "`v`",
@@ -529,20 +529,20 @@ trait DSv2TempViewWithStoredPlanTests extends
DSv2ExternalMutationTestBase {
// Scenario 7.2 (external type widening from INT to BIGINT)
test(s"${testPrefix}temp view with stored plan detects external type
widening") {
- withTestSession { session =>
- withTestTableAndViews(session, testTable, Seq("v")) {
- session.sql(s"CREATE TABLE $testTable (id INT, salary INT) USING
foo").collect()
- session.sql(s"INSERT INTO $testTable VALUES (1, 100), (10,
1000)").collect()
+ withTable(testTable) {
+ withView("v") {
+ spark.sql(s"CREATE TABLE $testTable (id INT, salary INT) USING
foo").collect()
+ spark.sql(s"INSERT INTO $testTable VALUES (1, 100), (10,
1000)").collect()
- session.table(testTable).filter("salary <
999").createOrReplaceTempView("v")
- checkRows(session.table("v"), Seq(Row(1, 100)))
+ spark.table(testTable).filter("salary <
999").createOrReplaceTempView("v")
+ checkAnswer(spark.table("v"), Seq(Row(1, 100)))
- val catalog = getTableCatalog[InMemoryTableCatalog](session, "testcat")
+ val catalog = getTableCatalog[InMemoryTableCatalog](spark, "testcat")
val updateType = TableChange.updateColumnType(Array("salary"),
LongType)
catalog.alterTable(testIdent, updateType)
checkError(
- exception = intercept[AnalysisException] {
session.table("v").collect() },
+ exception = intercept[AnalysisException] {
spark.table("v").collect() },
condition =
"INCOMPATIBLE_COLUMN_CHANGES_AFTER_VIEW_WITH_PLAN_CREATION",
parameters = Map(
"viewName" -> "`v`",
@@ -555,25 +555,25 @@ trait DSv2TempViewWithStoredPlanTests extends
DSv2ExternalMutationTestBase {
// Scenario 7.2 connector w/ cache (external type widening, caching
connector)
test(s"${testPrefix}connector w/ cache: temp view stale after external type
widening") {
- withTestSession { session =>
- withTestTableAndViews(session, cachingTestTable, Seq("v")) {
- session.sql(s"CREATE TABLE $cachingTestTable (id INT, salary INT)
USING foo").collect()
- session.sql(s"INSERT INTO $cachingTestTable VALUES (1, 100), (10,
1000)").collect()
+ withTable(cachingTestTable) {
+ withView("v") {
+ spark.sql(s"CREATE TABLE $cachingTestTable (id INT, salary INT) USING
foo").collect()
+ spark.sql(s"INSERT INTO $cachingTestTable VALUES (1, 100), (10,
1000)").collect()
- session.table(cachingTestTable).filter("salary <
999").createOrReplaceTempView("v")
- checkRows(session.table("v"), Seq(Row(1, 100)))
+ spark.table(cachingTestTable).filter("salary <
999").createOrReplaceTempView("v")
+ checkAnswer(spark.table("v"), Seq(Row(1, 100)))
- val catalog = getTableCatalog[CachingInMemoryTableCatalog](session,
"cachingcat")
+ val catalog = getTableCatalog[CachingInMemoryTableCatalog](spark,
"cachingcat")
val updateType = TableChange.updateColumnType(Array("salary"),
LongType)
catalog.alterTable(testIdent, updateType)
// Caching connector returns stale table: type change invisible, no
error
- checkRows(session.table("v"), Seq(Row(1, 100)))
+ checkAnswer(spark.table("v"), Seq(Row(1, 100)))
// REFRESH TABLE invalidates the connector cache, type change detected
- session.sql(s"REFRESH TABLE $cachingTestTable").collect()
+ spark.sql(s"REFRESH TABLE $cachingTestTable").collect()
checkError(
- exception = intercept[AnalysisException] {
session.table("v").collect() },
+ exception = intercept[AnalysisException] {
spark.table("v").collect() },
condition =
"INCOMPATIBLE_COLUMN_CHANGES_AFTER_VIEW_WITH_PLAN_CREATION",
parameters = Map(
"viewName" -> "`v`",
diff --git
a/sql/core/src/test/scala/org/apache/spark/sql/connector/DataSourceV2DataFrameSuite.scala
b/sql/core/src/test/scala/org/apache/spark/sql/connector/DataSourceV2DataFrameSuite.scala
index 2e2ad7361f5f..c18b8ef09e96 100644
---
a/sql/core/src/test/scala/org/apache/spark/sql/connector/DataSourceV2DataFrameSuite.scala
+++
b/sql/core/src/test/scala/org/apache/spark/sql/connector/DataSourceV2DataFrameSuite.scala
@@ -24,7 +24,7 @@ import scala.jdk.CollectionConverters._
import scala.reflect.ClassTag
import org.apache.spark.{SparkConf, SparkException}
-import org.apache.spark.sql.{AnalysisException, DataFrame, Row, SaveMode,
SparkSession}
+import org.apache.spark.sql.{AnalysisException, DataFrame, Row, SaveMode,
SessionQueryTest, SparkSession}
import org.apache.spark.sql.QueryTest.withQueryExecutionsCaptured
import org.apache.spark.sql.catalyst.analysis.TableAlreadyExistsException
import org.apache.spark.sql.catalyst.plans.logical.{AppendData,
CreateTableAsSelect, LogicalPlan, ReplaceTableAsSelect}
@@ -47,6 +47,7 @@ import org.apache.spark.unsafe.types.UTF8String
class DataSourceV2DataFrameSuite
extends InsertIntoTests(supportsDynamicOverwrite = true, includeSQLOnlyTests
= false)
+ with SessionQueryTest
with DSv2TempViewWithStoredPlanTests
with DSv2RepeatedTableAccessTests
with DSv2IncrementallyConstructedQueryTests
@@ -94,12 +95,6 @@ class DataSourceV2DataFrameSuite
// DSv2ExternalMutationTestBase implementations for classic mode
override protected def testPrefix: String = ""
- override protected def isConnect: Boolean = false
-
- override protected def withTestSession(fn: SparkSession => Unit): Unit =
fn(spark)
-
- override protected def checkRows(df: => DataFrame, expected: Seq[Row]): Unit
=
- checkAnswer(df, expected)
override protected def getTableCatalog[C <: TableCatalog: ClassTag](
session: SparkSession,
@@ -112,16 +107,6 @@ class DataSourceV2DataFrameSuite
c.asInstanceOf[C]
}
- override protected def withTestTableAndViews(
- session: SparkSession,
- table: String,
- views: Seq[String] = Seq.empty)(fn: => Unit): Unit = {
- withTable(table) {
- try { fn }
- finally { views.foreach(v => session.sql(s"DROP VIEW IF EXISTS $v")) }
- }
- }
-
override def verifyTable(tableName: String, expected: DataFrame): Unit = {
checkAnswer(spark.table(tableName), expected)
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]