Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -178,13 +178,8 @@ class QueryExecution(
// with the rest of processing of the root plan being just outputting command results,
// for eagerly executed commands we mark this place as beginning of execution.
tracker.setReadyForExecution()
val qe = new QueryExecution(sparkSession, p, mode = mode,
shuffleCleanupMode = shuffleCleanupMode, refreshPhaseEnabled = refreshPhaseEnabled)
val result = QueryExecution.withInternalError(s"Eagerly executed $name failed.") {
SQLExecution.withNewExecutionId(qe, Some(name)) {
qe.executedPlan.executeCollect()
}
}
val (qe, result) = QueryExecution.runCommand(
sparkSession, p, name, refreshPhaseEnabled, mode, Some(shuffleCleanupMode))
CommandResult(
qe.analyzed.output,
qe.commandExecuted,
Expand Down Expand Up @@ -763,4 +758,28 @@ object QueryExecution {
case _ => false
}
}

def runCommand(
sparkSession: SparkSession,
command: LogicalPlan,
name: String,
refreshPhaseEnabled: Boolean = true,
mode: CommandExecutionMode.Value = CommandExecutionMode.SKIP,
shuffleCleanupModeOpt: Option[ShuffleCleanupMode] = None)
: (QueryExecution, Array[InternalRow]) = {
val shuffleCleanupMode = shuffleCleanupModeOpt.getOrElse(
determineShuffleCleanupMode(sparkSession.sessionState.conf))
val qe = new QueryExecution(
sparkSession,
command,
mode = mode,
shuffleCleanupMode = shuffleCleanupMode,
refreshPhaseEnabled = refreshPhaseEnabled)
val result = QueryExecution.withInternalError(s"Executed $name failed.") {
SQLExecution.withNewExecutionId(qe, Some(name)) {
qe.executedPlan.executeCollect()
}
}
(qe, result)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import org.apache.spark.sql.classic.ClassicConversions.castToImpl
import org.apache.spark.sql.classic.Dataset
import org.apache.spark.sql.connector.catalog.TableProvider
import org.apache.spark.sql.errors.{QueryCompilationErrors, QueryExecutionErrors}
import org.apache.spark.sql.execution.QueryExecution
import org.apache.spark.sql.execution.command.DataWritingCommand
import org.apache.spark.sql.execution.datasources.csv.CSVFileFormat
import org.apache.spark.sql.execution.datasources.jdbc.JdbcRelationProvider
Expand Down Expand Up @@ -531,8 +532,7 @@ case class DataSource(
disallowWritingIntervals(
outputColumns.toStructType.asNullable, format.toString, forbidAnsiIntervals = false)
val cmd = planForWritingFileFormat(format, mode, data)
val qe = sessionState(sparkSession).executePlan(cmd)
qe.assertCommandExecuted()
QueryExecution.runCommand(sparkSession, cmd, "file source write")
// Replace the schema with that of the DataFrame we just wrote out to avoid re-inferring
copy(userSpecifiedSchema = Some(outputColumns.toStructType.asNullable)).resolveRelation()
case _ => throw SparkException.internalError(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -737,8 +737,11 @@ private[v2] trait V2CreateTableAsSelectBaseExec extends LeafV2CommandExec {
} else {
AppendData.byPosition(relation, query, writeOptions)
}
val qe = QueryExecution.create(session, writeCommand, refreshPhaseEnabled)
qe.assertCommandExecuted()
QueryExecution.runCommand(
Comment thread
cloud-fan marked this conversation as resolved.
session,
writeCommand,
"inner data writing for CTAS/RTAS",
refreshPhaseEnabled)
DataSourceV2Utils.commitStagedChanges(sparkContext, table, metrics)
Nil
})(catchBlock = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2053,4 +2053,39 @@ class DataSourceV2DataFrameSuite
case _ => fail(s"can't pin $ident in $catalogName")
}
}

test("CTAS/RTAS should trigger two query executions") {
// CTAS/RTAS triggers 2 query executions:
// 1. The outer CTAS/RTAS command execution
// 2. The inner AppendData/OverwriteByExpression execution
var executionCount = 0
val listener = new QueryExecutionListener {
override def onSuccess(funcName: String, qe: QueryExecution, durationNs: Long): Unit = {
executionCount += 1
}
override def onFailure(funcName: String, qe: QueryExecution, exception: Exception): Unit = {}
}

try {
spark.listenerManager.register(listener)
val t = "testcat.ns1.ns2.tbl"
withTable(t) {
// Test CTAS (CreateTableAsSelect)
executionCount = 0
sql(s"CREATE TABLE $t USING foo AS SELECT 1 as id, 'a' as data")
sparkContext.listenerBus.waitUntilEmpty()
assert(executionCount == 2,
s"CTAS should trigger 2 executions, got $executionCount")

// Test RTAS (ReplaceTableAsSelect)
executionCount = 0
sql(s"CREATE OR REPLACE TABLE $t USING foo AS SELECT 2 as id, 'b' as data")
sparkContext.listenerBus.waitUntilEmpty()
assert(executionCount == 2,
s"RTAS should trigger 2 executions, got $executionCount")
}
} finally {
spark.listenerManager.unregister(listener)
}
}
}