luoyuxia commented on code in PR #22995:
URL: https://github.com/apache/flink/pull/22995#discussion_r1263660087


##########
docs/layouts/shortcodes/generated/table_config_configuration.html:
##########
@@ -21,10 +21,10 @@
             <td>The name of the default database in the initial catalog to be 
created when instantiating TableEnvironment.</td>
         </tr>
         <tr>
-            <td><h5>table.ctas.atomicity-enabled</h5><br> <span class="label 
label-primary">Batch</span> <span class="label 
label-primary">Streaming</span></td>
+            <td><h5>table.rtas-ctas.atomicity-enabled</h5><br> <span 
class="label label-primary">Batch</span> <span class="label 
label-primary">Streaming</span></td>
             <td style="word-wrap: break-word;">false</td>
             <td>Boolean</td>
-            <td>Specifies if the CREATE TABLE AS SELECT statement is executed 
atomically. By default, the statement is non-atomic. The target table is 
created on the client side, and it will not be dropped even though the job 
fails or is canceled. If set this option to true and the underlying 
DynamicTableSink implements the SupportsStaging interface, the statement is 
expected to be executed atomically, the behavior of which depends on the actual 
DynamicTableSink.</td>
+            <td>Specifies if the CREATE TABLE/REPLACE TABLE/CREATE OR REPLACE 
AS SELECT statement is executed atomically. By default, the statement is 
non-atomic. The target table is created/replaced on the client side, and it 
will not be rollback even though the job fails or is canceled. If set this 
option to true and the underlying DynamicTableSink implements the 
SupportsStaging interface, the statement is expected to be executed atomically, 
the behavior of which depends on the actual DynamicTableSink.</td>

Review Comment:
   ```suggestion
               <td>Specifies if the CREATE TABLE/REPLACE TABLE/CREATE OR 
REPLACE AS SELECT statement is executed atomically. By default, the statement 
is non-atomic. The target table is created/replaced on the client side, and it 
will not be rollbacked even though the job fails or is canceled. If set this 
option to true and the underlying DynamicTableSink implements the 
SupportsStaging interface, the statement is expected to be executed atomically, 
the behavior of which depends on the actual DynamicTableSink.</td>
   ```



##########
flink-table/flink-table-api-java/src/main/java/org/apache/flink/table/api/internal/TableEnvironmentImpl.java:
##########
@@ -833,10 +832,44 @@ public TableResultInternal 
executeInternal(List<ModifyOperation> operations) {
         return executeInternal(transformations, sinkIdentifierNames, 
jobStatusHookList);
     }
 
-    private ModifyOperation getOperation(ReplaceTableAsOperation 
rtasOperation) {
-        // rtas drop table first, then create
+    private ModifyOperation getModifyOperation(
+            ReplaceTableAsOperation rtasOperation, List<JobStatusHook> 
jobStatusHookList) {
         CreateTableOperation createTableOperation = 
rtasOperation.getCreateTableOperation();

Review Comment:
   Get warning from my IDE
   `Duplicated code fragment (7 lines long) `



##########
flink-table/flink-table-api-java/src/main/java/org/apache/flink/table/api/internal/TableEnvironmentImpl.java:
##########
@@ -833,10 +832,44 @@ public TableResultInternal 
executeInternal(List<ModifyOperation> operations) {
         return executeInternal(transformations, sinkIdentifierNames, 
jobStatusHookList);
     }
 
-    private ModifyOperation getOperation(ReplaceTableAsOperation 
rtasOperation) {
-        // rtas drop table first, then create
+    private ModifyOperation getModifyOperation(
+            ReplaceTableAsOperation rtasOperation, List<JobStatusHook> 
jobStatusHookList) {
         CreateTableOperation createTableOperation = 
rtasOperation.getCreateTableOperation();
         ObjectIdentifier tableIdentifier = 
createTableOperation.getTableIdentifier();
+        Catalog catalog = 
catalogManager.getCatalog(tableIdentifier.getCatalogName()).orElse(null);
+        ResolvedCatalogTable catalogTable =
+                
catalogManager.resolveCatalogTable(createTableOperation.getCatalogTable());
+        Optional<DynamicTableSink> stagingDynamicTableSink =
+                getSupportsStagingDynamicTableSink(createTableOperation, 
catalog, catalogTable);
+        if (stagingDynamicTableSink.isPresent()) {
+            // use atomic rtas
+            DynamicTableSink dynamicTableSink = stagingDynamicTableSink.get();
+            if (!rtasOperation.isCreateOrReplace()) {
+                Optional<ContextResolvedTable> driverTable =

Review Comment:
   Why name `driverTable`? It really confused me.
   Use `oldTable`?



##########
flink-table/flink-table-api-java/src/main/java/org/apache/flink/table/api/internal/TableEnvironmentImpl.java:
##########
@@ -833,10 +832,44 @@ public TableResultInternal 
executeInternal(List<ModifyOperation> operations) {
         return executeInternal(transformations, sinkIdentifierNames, 
jobStatusHookList);
     }
 
-    private ModifyOperation getOperation(ReplaceTableAsOperation 
rtasOperation) {
-        // rtas drop table first, then create
+    private ModifyOperation getModifyOperation(
+            ReplaceTableAsOperation rtasOperation, List<JobStatusHook> 
jobStatusHookList) {
         CreateTableOperation createTableOperation = 
rtasOperation.getCreateTableOperation();
         ObjectIdentifier tableIdentifier = 
createTableOperation.getTableIdentifier();
+        Catalog catalog = 
catalogManager.getCatalog(tableIdentifier.getCatalogName()).orElse(null);
+        ResolvedCatalogTable catalogTable =
+                
catalogManager.resolveCatalogTable(createTableOperation.getCatalogTable());
+        Optional<DynamicTableSink> stagingDynamicTableSink =
+                getSupportsStagingDynamicTableSink(createTableOperation, 
catalog, catalogTable);
+        if (stagingDynamicTableSink.isPresent()) {
+            // use atomic rtas
+            DynamicTableSink dynamicTableSink = stagingDynamicTableSink.get();
+            if (!rtasOperation.isCreateOrReplace()) {

Review Comment:
   I'm wondering what's the behavior for staging replace in spark.
   Is it also will check the table exists or not? And if not, will it also 
throw an exception?



##########
flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/runtime/utils/AtomicRtasITCaseBase.java:
##########
@@ -0,0 +1,223 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.flink.table.planner.runtime.utils;
+
+import org.apache.flink.table.api.TableEnvironment;
+import org.apache.flink.table.api.TableException;
+import org.apache.flink.table.api.config.TableConfigOptions;
+import org.apache.flink.table.connector.sink.abilities.SupportsStaging;
+import 
org.apache.flink.table.planner.factories.TestSupportsStagingTableFactory;
+import 
org.apache.flink.table.planner.factories.utils.TestCollectionTableFactory;
+import org.apache.flink.types.Row;
+import org.apache.flink.util.FileUtils;
+import org.apache.flink.util.TestLogger;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+/** The base case of atomic rtas ITCase. */
+public abstract class AtomicRtasITCaseBase extends TestLogger {
+
+    protected TableEnvironment tEnv;
+
+    protected abstract TableEnvironment getTableEnvironment();
+
+    @BeforeEach
+    void setup() {
+        tEnv = getTableEnvironment();
+        List<Row> sourceData = Collections.singletonList(Row.of(1, "ZM"));
+
+        TestCollectionTableFactory.reset();
+        TestCollectionTableFactory.initData(sourceData);
+
+        String sourceDDL = "create table t1(a int, b varchar) with 
('connector' = 'COLLECTION')";
+        tEnv.executeSql(sourceDDL);
+        // clean data
+        TestSupportsStagingTableFactory.JOB_STATUS_CHANGE_PROCESS.clear();
+        TestSupportsStagingTableFactory.STAGING_PURPOSE_LIST.clear();
+    }
+
+    @Test
+    void testAtomicReplaceTableAs(@TempDir Path temporaryFolder) throws 
Exception {
+        commonTestForAtomicReplaceTableAs(
+                "atomic_replace_table", false, true, temporaryFolder.toFile());
+    }
+
+    @Test
+    void testAtomicReplaceTableAsWithDriverTableNotExists(@TempDir Path 
temporaryFolder)
+            throws Exception {
+        commonTestForAtomicReplaceTableAs(
+                "atomic_replace_table_not_exists", false, false, 
temporaryFolder.toFile());
+    }
+
+    @Test
+    void testAtomicCreateOrReplaceTableAs(@TempDir Path temporaryFolder) 
throws Exception {
+        commonTestForAtomicReplaceTableAs(
+                "atomic_create_or_replace_table", true, true, 
temporaryFolder.toFile());
+    }
+
+    @Test
+    void testAtomicCreateOrReplaceTableAsWithDriverTableNotExists(@TempDir 
Path temporaryFolder)
+            throws Exception {
+        commonTestForAtomicReplaceTableAs(
+                "atomic_create_or_replace_table_not_exists", true, false, 
temporaryFolder.toFile());
+    }
+
+    private void commonTestForAtomicReplaceTableAs(
+            String tableName,
+            boolean isCreateOrReplace,
+            boolean createDriverTable,
+            File tmpDataFolder)
+            throws Exception {
+        if (createDriverTable) {
+            tEnv.executeSql("create table " + tableName + " (a int) with 
('connector' = 'PRINT')");
+        }
+
+        
tEnv.getConfig().set(TableConfigOptions.TABLE_RTAS_CTAS_ATOMICITY_ENABLED, 
true);
+        String dataDir = tmpDataFolder.getAbsolutePath();
+        String sqlFragment =

Review Comment:
   nit:
   I think the code lines
   ` String sqlFragment =
                   isCreateOrReplace
                           ? " create or replace table " + tableName
                           : " replace table " + tableName;`
   can be extracted to a method.



##########
flink-table/flink-table-api-java/src/main/java/org/apache/flink/table/api/internal/TableEnvironmentImpl.java:
##########
@@ -833,10 +832,44 @@ public TableResultInternal 
executeInternal(List<ModifyOperation> operations) {
         return executeInternal(transformations, sinkIdentifierNames, 
jobStatusHookList);
     }
 
-    private ModifyOperation getOperation(ReplaceTableAsOperation 
rtasOperation) {
-        // rtas drop table first, then create
+    private ModifyOperation getModifyOperation(
+            ReplaceTableAsOperation rtasOperation, List<JobStatusHook> 
jobStatusHookList) {
         CreateTableOperation createTableOperation = 
rtasOperation.getCreateTableOperation();
         ObjectIdentifier tableIdentifier = 
createTableOperation.getTableIdentifier();
+        Catalog catalog = 
catalogManager.getCatalog(tableIdentifier.getCatalogName()).orElse(null);
+        ResolvedCatalogTable catalogTable =
+                
catalogManager.resolveCatalogTable(createTableOperation.getCatalogTable());
+        Optional<DynamicTableSink> stagingDynamicTableSink =
+                getSupportsStagingDynamicTableSink(createTableOperation, 
catalog, catalogTable);
+        if (stagingDynamicTableSink.isPresent()) {
+            // use atomic rtas
+            DynamicTableSink dynamicTableSink = stagingDynamicTableSink.get();
+            if (!rtasOperation.isCreateOrReplace()) {
+                Optional<ContextResolvedTable> driverTable =
+                        catalogManager.getTable(tableIdentifier);
+                if (!driverTable.isPresent()) {
+                    throw new TableException(
+                            String.format(
+                                    "The table %s to be replaced doesn't 
exist. "
+                                            + "You can try to use CREATE TABLE 
AS statement or "
+                                            + "CREATE OR REPLACE TABLE AS 
statement.",
+                                    tableIdentifier));
+                }
+            }
+            SupportsStaging.StagingPurpose stagingPurpose =
+                    rtasOperation.isCreateOrReplace()
+                            ? 
SupportsStaging.StagingPurpose.CREATE_OR_REPLACE_TABLE_AS
+                            : SupportsStaging.StagingPurpose.REPLACE_TABLE_AS;
+
+            StagedTable stagedTable =
+                    ((SupportsStaging) dynamicTableSink)
+                            .applyStaging(new 
SinkStagingContext(stagingPurpose));
+            AtomicJobStatusHook atomicJobStatusHook = new 
AtomicJobStatusHook(stagedTable);
+            jobStatusHookList.add(atomicJobStatusHook);
+            return rtasOperation.toStagedSinkModifyOperation(
+                    tableIdentifier, catalogTable, catalog, dynamicTableSink);
+        }
+        // non-atomic rtas drop table first, then create
         try {
             catalogManager.dropTable(tableIdentifier, 
rtasOperation.isCreateOrReplace());
         } catch (ValidationException e) {

Review Comment:
   Just notice we will use error message to check whether the table to be 
dropped exists or not. For me, it's hacky and when the error message changes, 
the check may well fail.
   Can we first get the table and then drop?



##########
flink-table/flink-table-api-java/src/main/java/org/apache/flink/table/execution/AtomicJobStatusHook.java:
##########
@@ -23,14 +23,15 @@
 import org.apache.flink.table.catalog.StagedTable;
 
 /**
- * This hook is used to implement atomic semantics for CTAS(CREATE TABLE AS 
SELECT) statement. It'll
- * call the corresponding interfaces of the inner {@link StagedTable} on job 
status changes.
+ * This hook is used to implement atomic semantics for CTAS(CREATE TABLE AS 
SELECT) or RTAS([CREATE
+ * OR] REPLACE TABLE AS SELECT) statement. It'll call the corresponding 
interfaces of the inner
+ * {@link StagedTable} on job status changes.
  */
-public class CtasJobStatusHook implements JobStatusHook {
+public class AtomicJobStatusHook implements JobStatusHook {

Review Comment:
   StagingSinkJobStatusHook?



##########
flink-table/flink-table-api-java/src/main/java/org/apache/flink/table/api/config/TableConfigOptions.java:
##########
@@ -198,13 +198,13 @@ private TableConfigOptions() {}
                             "Local directory that is used by planner for 
storing downloaded resources.");
 
     @Documentation.TableOption(execMode = 
Documentation.ExecMode.BATCH_STREAMING)
-    public static final ConfigOption<Boolean> TABLE_CTAS_ATOMICITY_ENABLED =
-            key("table.ctas.atomicity-enabled")
+    public static final ConfigOption<Boolean> 
TABLE_RTAS_CTAS_ATOMICITY_ENABLED =
+            key("table.rtas-ctas.atomicity-enabled")
                     .booleanType()
                     .defaultValue(false)
                     .withDescription(
-                            "Specifies if the CREATE TABLE AS SELECT statement 
is executed atomically. By default, the statement is non-atomic. "
-                                    + "The target table is created on the 
client side, and it will not be dropped even though the job fails or is 
canceled. "
+                            "Specifies if the CREATE TABLE/REPLACE 
TABLE/CREATE OR REPLACE AS SELECT statement is executed atomically. By default, 
the statement is non-atomic. "
+                                    + "The target table is created/replaced on 
the client side, and it will not be rollback even though the job fails or is 
canceled. "

Review Comment:
   dito



##########
flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/runtime/utils/AtomicRtasITCaseBase.java:
##########
@@ -0,0 +1,223 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.flink.table.planner.runtime.utils;
+
+import org.apache.flink.table.api.TableEnvironment;
+import org.apache.flink.table.api.TableException;
+import org.apache.flink.table.api.config.TableConfigOptions;
+import org.apache.flink.table.connector.sink.abilities.SupportsStaging;
+import 
org.apache.flink.table.planner.factories.TestSupportsStagingTableFactory;
+import 
org.apache.flink.table.planner.factories.utils.TestCollectionTableFactory;
+import org.apache.flink.types.Row;
+import org.apache.flink.util.FileUtils;
+import org.apache.flink.util.TestLogger;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+/** The base case of atomic rtas ITCase. */
+public abstract class AtomicRtasITCaseBase extends TestLogger {
+
+    protected TableEnvironment tEnv;
+
+    protected abstract TableEnvironment getTableEnvironment();
+
+    @BeforeEach
+    void setup() {
+        tEnv = getTableEnvironment();
+        List<Row> sourceData = Collections.singletonList(Row.of(1, "ZM"));
+
+        TestCollectionTableFactory.reset();
+        TestCollectionTableFactory.initData(sourceData);
+
+        String sourceDDL = "create table t1(a int, b varchar) with 
('connector' = 'COLLECTION')";
+        tEnv.executeSql(sourceDDL);
+        // clean data
+        TestSupportsStagingTableFactory.JOB_STATUS_CHANGE_PROCESS.clear();
+        TestSupportsStagingTableFactory.STAGING_PURPOSE_LIST.clear();
+    }
+
+    @Test
+    void testAtomicReplaceTableAs(@TempDir Path temporaryFolder) throws 
Exception {
+        commonTestForAtomicReplaceTableAs(
+                "atomic_replace_table", false, true, temporaryFolder.toFile());
+    }
+
+    @Test
+    void testAtomicReplaceTableAsWithDriverTableNotExists(@TempDir Path 
temporaryFolder)

Review Comment:
   I'm really confused about the naming. 
   Why is `DriverTable`? I mean, why name driver?



##########
flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/runtime/utils/AtomicRtasITCaseBase.java:
##########
@@ -0,0 +1,223 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.flink.table.planner.runtime.utils;
+
+import org.apache.flink.table.api.TableEnvironment;
+import org.apache.flink.table.api.TableException;
+import org.apache.flink.table.api.config.TableConfigOptions;
+import org.apache.flink.table.connector.sink.abilities.SupportsStaging;
+import 
org.apache.flink.table.planner.factories.TestSupportsStagingTableFactory;
+import 
org.apache.flink.table.planner.factories.utils.TestCollectionTableFactory;
+import org.apache.flink.types.Row;
+import org.apache.flink.util.FileUtils;
+import org.apache.flink.util.TestLogger;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+/** The base case of atomic rtas ITCase. */
+public abstract class AtomicRtasITCaseBase extends TestLogger {
+
+    protected TableEnvironment tEnv;
+
+    protected abstract TableEnvironment getTableEnvironment();
+
+    @BeforeEach
+    void setup() {
+        tEnv = getTableEnvironment();
+        List<Row> sourceData = Collections.singletonList(Row.of(1, "ZM"));
+
+        TestCollectionTableFactory.reset();
+        TestCollectionTableFactory.initData(sourceData);
+
+        String sourceDDL = "create table t1(a int, b varchar) with 
('connector' = 'COLLECTION')";
+        tEnv.executeSql(sourceDDL);
+        // clean data
+        TestSupportsStagingTableFactory.JOB_STATUS_CHANGE_PROCESS.clear();
+        TestSupportsStagingTableFactory.STAGING_PURPOSE_LIST.clear();
+    }
+
+    @Test
+    void testAtomicReplaceTableAs(@TempDir Path temporaryFolder) throws 
Exception {
+        commonTestForAtomicReplaceTableAs(
+                "atomic_replace_table", false, true, temporaryFolder.toFile());
+    }
+
+    @Test
+    void testAtomicReplaceTableAsWithDriverTableNotExists(@TempDir Path 
temporaryFolder)

Review Comment:
   If no strong reason for `DriverTable`, I would like to name it as 
`isCreateReplacedTable`



##########
flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/runtime/utils/AtomicRtasITCaseBase.java:
##########
@@ -0,0 +1,223 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.flink.table.planner.runtime.utils;
+
+import org.apache.flink.table.api.TableEnvironment;
+import org.apache.flink.table.api.TableException;
+import org.apache.flink.table.api.config.TableConfigOptions;
+import org.apache.flink.table.connector.sink.abilities.SupportsStaging;
+import 
org.apache.flink.table.planner.factories.TestSupportsStagingTableFactory;
+import 
org.apache.flink.table.planner.factories.utils.TestCollectionTableFactory;
+import org.apache.flink.types.Row;
+import org.apache.flink.util.FileUtils;
+import org.apache.flink.util.TestLogger;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+/** The base case of atomic rtas ITCase. */
+public abstract class AtomicRtasITCaseBase extends TestLogger {
+
+    protected TableEnvironment tEnv;
+
+    protected abstract TableEnvironment getTableEnvironment();
+
+    @BeforeEach
+    void setup() {
+        tEnv = getTableEnvironment();
+        List<Row> sourceData = Collections.singletonList(Row.of(1, "ZM"));
+
+        TestCollectionTableFactory.reset();
+        TestCollectionTableFactory.initData(sourceData);
+
+        String sourceDDL = "create table t1(a int, b varchar) with 
('connector' = 'COLLECTION')";
+        tEnv.executeSql(sourceDDL);
+        // clean data

Review Comment:
   nit:
   for clean, I would like to have a @AfterEach method.
   `AtomicRtasITCaseBase` may also need to update.



##########
flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/runtime/utils/AtomicRtasITCaseBase.java:
##########
@@ -0,0 +1,223 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.flink.table.planner.runtime.utils;
+
+import org.apache.flink.table.api.TableEnvironment;
+import org.apache.flink.table.api.TableException;
+import org.apache.flink.table.api.config.TableConfigOptions;
+import org.apache.flink.table.connector.sink.abilities.SupportsStaging;
+import 
org.apache.flink.table.planner.factories.TestSupportsStagingTableFactory;
+import 
org.apache.flink.table.planner.factories.utils.TestCollectionTableFactory;
+import org.apache.flink.types.Row;
+import org.apache.flink.util.FileUtils;
+import org.apache.flink.util.TestLogger;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+/** The base case of atomic rtas ITCase. */
+public abstract class AtomicRtasITCaseBase extends TestLogger {
+
+    protected TableEnvironment tEnv;
+
+    protected abstract TableEnvironment getTableEnvironment();
+
+    @BeforeEach
+    void setup() {
+        tEnv = getTableEnvironment();
+        List<Row> sourceData = Collections.singletonList(Row.of(1, "ZM"));
+
+        TestCollectionTableFactory.reset();
+        TestCollectionTableFactory.initData(sourceData);
+
+        String sourceDDL = "create table t1(a int, b varchar) with 
('connector' = 'COLLECTION')";
+        tEnv.executeSql(sourceDDL);
+        // clean data
+        TestSupportsStagingTableFactory.JOB_STATUS_CHANGE_PROCESS.clear();
+        TestSupportsStagingTableFactory.STAGING_PURPOSE_LIST.clear();
+    }
+
+    @Test
+    void testAtomicReplaceTableAs(@TempDir Path temporaryFolder) throws 
Exception {
+        commonTestForAtomicReplaceTableAs(
+                "atomic_replace_table", false, true, temporaryFolder.toFile());
+    }
+
+    @Test
+    void testAtomicReplaceTableAsWithDriverTableNotExists(@TempDir Path 
temporaryFolder)
+            throws Exception {
+        commonTestForAtomicReplaceTableAs(
+                "atomic_replace_table_not_exists", false, false, 
temporaryFolder.toFile());
+    }
+
+    @Test
+    void testAtomicCreateOrReplaceTableAs(@TempDir Path temporaryFolder) 
throws Exception {
+        commonTestForAtomicReplaceTableAs(
+                "atomic_create_or_replace_table", true, true, 
temporaryFolder.toFile());
+    }
+
+    @Test
+    void testAtomicCreateOrReplaceTableAsWithDriverTableNotExists(@TempDir 
Path temporaryFolder)
+            throws Exception {
+        commonTestForAtomicReplaceTableAs(
+                "atomic_create_or_replace_table_not_exists", true, false, 
temporaryFolder.toFile());
+    }
+
+    private void commonTestForAtomicReplaceTableAs(
+            String tableName,
+            boolean isCreateOrReplace,
+            boolean createDriverTable,
+            File tmpDataFolder)
+            throws Exception {
+        if (createDriverTable) {
+            tEnv.executeSql("create table " + tableName + " (a int) with 
('connector' = 'PRINT')");
+        }
+
+        
tEnv.getConfig().set(TableConfigOptions.TABLE_RTAS_CTAS_ATOMICITY_ENABLED, 
true);
+        String dataDir = tmpDataFolder.getAbsolutePath();
+        String sqlFragment =
+                isCreateOrReplace
+                        ? " create or replace table " + tableName
+                        : " replace table " + tableName;
+        String sql =
+                sqlFragment
+                        + " with ('connector' = 'test-staging', 'data-dir' = '"
+                        + dataDir
+                        + "') as select * from t1";
+        if (!isCreateOrReplace && !createDriverTable) {
+            assertThatThrownBy(() -> tEnv.executeSql(sql))
+                    .isInstanceOf(TableException.class)
+                    .hasMessage(
+                            "The table `default_catalog`.`default_database`.`"
+                                    + tableName
+                                    + "` to be replaced doesn't exist."
+                                    + " You can try to use CREATE TABLE AS 
statement or CREATE OR REPLACE TABLE AS statement.");
+        } else {
+            tEnv.executeSql(sql).await();
+            if (createDriverTable) {
+                assertThat(tEnv.listTables()).contains(tableName);
+            } else {
+                assertThat(tEnv.listTables()).doesNotContain(tableName);
+            }
+            verifyDataFile(dataDir, "data");
+            
assertThat(TestSupportsStagingTableFactory.JOB_STATUS_CHANGE_PROCESS).hasSize(2);
+            
assertThat(TestSupportsStagingTableFactory.JOB_STATUS_CHANGE_PROCESS)
+                    .contains("begin", "commit");
+            
assertThat(TestSupportsStagingTableFactory.STAGING_PURPOSE_LIST).hasSize(1);
+            if (isCreateOrReplace) {
+                
assertThat(TestSupportsStagingTableFactory.STAGING_PURPOSE_LIST)
+                        
.contains(SupportsStaging.StagingPurpose.CREATE_OR_REPLACE_TABLE_AS);
+            } else {
+                
assertThat(TestSupportsStagingTableFactory.STAGING_PURPOSE_LIST)
+                        
.contains(SupportsStaging.StagingPurpose.REPLACE_TABLE_AS);
+            }
+        }
+    }
+
+    @Test
+    void testAtomicReplaceTableAsWithException(@TempDir Path temporaryFolder) 
throws Exception {
+        commonTestForAtomicReplaceTableAsWithException(
+                "atomic_replace_table_fail", false, temporaryFolder.toFile());
+    }
+
+    @Test
+    void testAtomicCreateOrReplaceTableAsWithException(@TempDir Path 
temporaryFolder)
+            throws Exception {
+        commonTestForAtomicReplaceTableAsWithException(
+                "atomic_create_or_replace_table_fail", false, 
temporaryFolder.toFile());
+    }
+
+    private void commonTestForAtomicReplaceTableAsWithException(
+            String tableName, boolean isCreateOrReplace, File tmpDataFolder) {

Review Comment:
   get warning from my IDE:
   `Actual value of parameter 'isCreateOrReplace' is always 'false' `



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to