This is an automated email from the ASF dual-hosted git repository.
JingsongLi pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/paimon.git
The following commit(s) were added to refs/heads/master by this push:
new a3fc1b79ca [spark] Remove upsert-key support (#9093)
a3fc1b79ca is described below
commit a3fc1b79ca8076d05424d61ef57b88ac0e994332
Author: Jingsong Lee <[email protected]>
AuthorDate: Fri Aug 7 16:28:06 2026 +0800
[spark] Remove upsert-key support (#9093)
---
docs/docs/spark/sql-upsert.md | 93 ---------------
docs/generated/core_configuration.html | 6 -
docs/redirects.js | 4 -
docs/sidebars.js | 1 -
.../main/java/org/apache/paimon/CoreOptions.java | 15 ---
.../org/apache/paimon/schema/SchemaValidation.java | 8 --
.../apache/paimon/schema/SchemaValidationTest.java | 19 +--
.../paimon/spark/sql/RewriteUpsertTableTest.scala | 21 ----
.../paimon/spark/sql/RewriteUpsertTableTest.scala | 21 ----
.../paimon/spark/sql/RewriteUpsertTableTest.scala | 21 ----
.../paimon/spark/sql/RewriteUpsertTableTest.scala | 21 ----
.../catalyst/analysis/RewriteUpsertTable.scala | 129 --------------------
.../paimon/spark/sql/RewriteUpsertTableTest.scala | 21 ----
.../paimon/spark/sql/RewriteUpsertTableTest.scala | 21 ----
.../catalyst/analysis/RewriteUpsertTable.scala | 130 ---------------------
.../extensions/PaimonSparkSessionExtensions.scala | 4 +-
.../spark/sql/RewriteUpsertTableTestBase.scala | 89 --------------
17 files changed, 4 insertions(+), 620 deletions(-)
diff --git a/docs/docs/spark/sql-upsert.md b/docs/docs/spark/sql-upsert.md
deleted file mode 100644
index 551d7988a6..0000000000
--- a/docs/docs/spark/sql-upsert.md
+++ /dev/null
@@ -1,93 +0,0 @@
----
-title: "SQL Upsert"
-sidebar_position: 10
----
-
-<!--
-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.
--->
-
-# SQL Upsert
-
-For table without primary key, Paimon supports upsert write mode: If the row
with the same upsert key already exists, perform update; otherwise, perform
insert.
-
-## Usage
-
-Specify the following table properties when creating the table
-
-* `upsert-key`: Defines the key columns used for upsert, cannot be used
together with primary key.
-Unlike primary key, the upsert key value can be `null`, and null-equality
matching is supported.
-Multiple columns separated by commas.
-
-* `sequence.field` (optional): When new record share the same upsert key, the
row with the larger `sequence.field` value is kept as the merge result.
-And it will also deduplicate the data being written.
-If `sequence.field` is not set, new record share the same upsert key simply
update the existing one and no deduplication is performed.
-Multiple columns separated by commas.
-
-## Example
-
-Create table:
-
-```sql
-CREATE TABLE t (k1 INT, k2 INT, ts1 INT, ts2 INT, v STRING)
-TBLPROPERTIES ('upsert-key' = 'k1,k2', 'sequence.field' = 'ts1,ts2')
-```
-
-Insert data1:
-
-```sql
-INSERT INTO t values
-(null, null, 2, 1, 'v1'),
-(null, null, 2, 2, 'v4'),
-(1, null, 1, 1, 'v1'),
-(1, 2, 1, 1, 'v1'),
-(1, 2, 2, 1, 'v2')
-```
-
-Query result:
-
-```sql
-SELECT * FROM t ORDER BY k1, k2
-
--- null, null, 2, 2, "v4"
--- 1, null, 1, 1, "v1"
--- 1, 2, 2, 1, "v2"
-```
-
-Insert data2:
-
-```sql
-INSERT INTO t values
-(null, null, 2, 1, 'v5'),
-(null, 1, 1, 1, 'v1'),
-(1, null, 2, 1, 'v2'),
-(1, 1, 1, 1, 'v1'),
-(1, 2, 2, 0, 'v3')
-```
-
-Query result:
-
-```sql
-SELECT * FROM t ORDER BY k1, k2
-
--- null, null, 2, 2, "v4"
--- null, 1, 1, 1, "v1"
--- 1, null, 2, 1, "v2"
--- 1, 1, 1, 1, "v1"
--- 1, 2, 2, 1, "v2"
-```
diff --git a/docs/generated/core_configuration.html
b/docs/generated/core_configuration.html
index a7c8114dd8..0e399478c8 100644
--- a/docs/generated/core_configuration.html
+++ b/docs/generated/core_configuration.html
@@ -1771,12 +1771,6 @@ If the data size allocated for the sorting task is
uneven,which may lead to perf
<td><p>Enum</p></td>
<td>Type of the table.<br /><br />Possible values:<ul><li>"table":
Normal Paimon table.</li><li>"format-table": A file format table refers to a
directory that contains multiple files of the same
format.</li><li>"materialized-table": A materialized table combines normal
Paimon table and materialized SQL.</li><li>"object-table": An object table
combines normal Paimon table and object location.</li><li>"lance-table": A
lance table, see 'https://lancedb.github.io/lance/'.</li><li [...]
</tr>
- <tr>
- <td><h5>upsert-key</h5></td>
- <td style="word-wrap: break-word;">(none)</td>
- <td>String</td>
- <td>Define upsert key to do MERGE INTO when executing INSERT INTO,
cannot be defined with primary key.</td>
- </tr>
<tr>
<td><h5>variant.inferShreddingSchema</h5></td>
<td style="word-wrap: break-word;">false</td>
diff --git a/docs/redirects.js b/docs/redirects.js
index 57077ff895..9d04563c4d 100644
--- a/docs/redirects.js
+++ b/docs/redirects.js
@@ -476,10 +476,6 @@ module.exports = [
"from": "/spark/sql-query.html",
"to": "/spark/sql-query"
},
- {
- "from": "/spark/sql-upsert.html",
- "to": "/spark/sql-upsert"
- },
{
"from": "/spark/sql-write.html",
"to": "/spark/sql-write"
diff --git a/docs/sidebars.js b/docs/sidebars.js
index 56a9c3e211..be7294db7b 100644
--- a/docs/sidebars.js
+++ b/docs/sidebars.js
@@ -175,7 +175,6 @@ const sidebars = {
"spark/auxiliary",
"spark/default-value",
"spark/dataframe",
- "spark/sql-upsert",
"spark/structured-streaming",
"spark/procedures"
]
diff --git a/paimon-api/src/main/java/org/apache/paimon/CoreOptions.java
b/paimon-api/src/main/java/org/apache/paimon/CoreOptions.java
index 3be4d072e1..0ad4beb001 100644
--- a/paimon-api/src/main/java/org/apache/paimon/CoreOptions.java
+++ b/paimon-api/src/main/java/org/apache/paimon/CoreOptions.java
@@ -595,13 +595,6 @@ public class CoreOptions implements Serializable {
+ " skipped. Set to a larger value to
allow more aggressive"
+ " sort rewriting. The cap only limits
the sorted rewrite portion and full/minor cleanup may still happen beyond it.");
- public static final ConfigOption<String> UPSERT_KEY =
- key("upsert-key")
- .stringType()
- .noDefaultValue()
- .withDescription(
- "Define upsert key to do MERGE INTO when executing
INSERT INTO, cannot be defined with primary key.");
-
public static final ConfigOption<String> PARTITION_DEFAULT_NAME =
key("partition.default-name")
.stringType()
@@ -3152,14 +3145,6 @@ public class CoreOptions implements Serializable {
return options.get(FIELDS_DEFAULT_AGG_FUNC);
}
- public List<String> upsertKey() {
- String upsertKey = options.get(UPSERT_KEY);
- if (StringUtils.isEmpty(upsertKey)) {
- return Collections.emptyList();
- }
- return Arrays.asList(upsertKey.split(","));
- }
-
public static String createCommitUser(Options options) {
String commitUserPrefix = options.get(COMMIT_USER_PREFIX);
return commitUserPrefix == null
diff --git
a/paimon-core/src/main/java/org/apache/paimon/schema/SchemaValidation.java
b/paimon-core/src/main/java/org/apache/paimon/schema/SchemaValidation.java
index 0e57e364b5..fa28403e82 100644
--- a/paimon-core/src/main/java/org/apache/paimon/schema/SchemaValidation.java
+++ b/paimon-core/src/main/java/org/apache/paimon/schema/SchemaValidation.java
@@ -144,14 +144,6 @@ public class SchemaValidation {
validateOnlyContainPrimitiveType(schema.fields(),
schema.primaryKeys(), "primary key");
validateOnlyContainPrimitiveType(schema.fields(),
schema.partitionKeys(), "partition");
- validateOnlyContainPrimitiveType(schema.fields(), options.upsertKey(),
"upsert key");
-
- if (!options.upsertKey().isEmpty() && !schema.primaryKeys().isEmpty())
{
- throw new RuntimeException(
- String.format(
- "Cannot define 'upsert-key' %s with 'primary-key'
%s.",
- options.upsertKey(), schema.primaryKeys()));
- }
validateBucket(schema, options);
diff --git
a/paimon-core/src/test/java/org/apache/paimon/schema/SchemaValidationTest.java
b/paimon-core/src/test/java/org/apache/paimon/schema/SchemaValidationTest.java
index 4d3a71a4f2..e0f141680a 100644
---
a/paimon-core/src/test/java/org/apache/paimon/schema/SchemaValidationTest.java
+++
b/paimon-core/src/test/java/org/apache/paimon/schema/SchemaValidationTest.java
@@ -1251,7 +1251,7 @@ class SchemaValidationTest {
assertThatThrownBy(
() ->
validateTableSchema(
- vectorTypeSchema(emptyList(),
singletonList("f1"), null)))
+ vectorTypeSchema(emptyList(),
singletonList("f1"))))
.isInstanceOf(UnsupportedOperationException.class)
.hasMessage(
"The type %s in primary key field %s is unsupported",
"VectorType", "f1");
@@ -1259,18 +1259,9 @@ class SchemaValidationTest {
assertThatThrownBy(
() ->
validateTableSchema(
- vectorTypeSchema(singletonList("f1"),
emptyList(), null)))
+ vectorTypeSchema(singletonList("f1"),
emptyList())))
.isInstanceOf(UnsupportedOperationException.class)
.hasMessage("The type %s in partition field %s is
unsupported", "VectorType", "f1");
-
- assertThatThrownBy(
- () ->
- validateTableSchema(
- vectorTypeSchema(
- emptyList(), emptyList(),
singletonList("f1"))))
- .isInstanceOf(UnsupportedOperationException.class)
- .hasMessage(
- "The type %s in upsert key field %s is unsupported",
"VectorType", "f1");
}
@Test
@@ -1881,17 +1872,13 @@ class SchemaValidationTest {
new DataField(2, "payload", payloadType));
}
- private TableSchema vectorTypeSchema(
- List<String> partitionKeys, List<String> primaryKeys, List<String>
upsertKeys) {
+ private TableSchema vectorTypeSchema(List<String> partitionKeys,
List<String> primaryKeys) {
List<DataField> fields =
Arrays.asList(
new DataField(0, "f0", DataTypes.INT()),
new DataField(1, "f1", DataTypes.VECTOR(3,
DataTypes.FLOAT())));
Map<String, String> options = new HashMap<>();
options.put(BUCKET.key(), String.valueOf(-1));
- if (upsertKeys != null) {
- options.put(CoreOptions.UPSERT_KEY.key(), String.join(",",
upsertKeys));
- }
return new TableSchema(1, fields, 10, partitionKeys, primaryKeys,
options, "");
}
}
diff --git
a/paimon-spark/paimon-spark-3.2/src/test/scala/org/apache/paimon/spark/sql/RewriteUpsertTableTest.scala
b/paimon-spark/paimon-spark-3.2/src/test/scala/org/apache/paimon/spark/sql/RewriteUpsertTableTest.scala
deleted file mode 100644
index 412aa3b303..0000000000
---
a/paimon-spark/paimon-spark-3.2/src/test/scala/org/apache/paimon/spark/sql/RewriteUpsertTableTest.scala
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * 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.paimon.spark.sql
-
-class RewriteUpsertTableTest extends RewriteUpsertTableTestBase {}
diff --git
a/paimon-spark/paimon-spark-3.3/src/test/scala/org/apache/paimon/spark/sql/RewriteUpsertTableTest.scala
b/paimon-spark/paimon-spark-3.3/src/test/scala/org/apache/paimon/spark/sql/RewriteUpsertTableTest.scala
deleted file mode 100644
index 412aa3b303..0000000000
---
a/paimon-spark/paimon-spark-3.3/src/test/scala/org/apache/paimon/spark/sql/RewriteUpsertTableTest.scala
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * 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.paimon.spark.sql
-
-class RewriteUpsertTableTest extends RewriteUpsertTableTestBase {}
diff --git
a/paimon-spark/paimon-spark-3.4/src/test/scala/org/apache/paimon/spark/sql/RewriteUpsertTableTest.scala
b/paimon-spark/paimon-spark-3.4/src/test/scala/org/apache/paimon/spark/sql/RewriteUpsertTableTest.scala
deleted file mode 100644
index 412aa3b303..0000000000
---
a/paimon-spark/paimon-spark-3.4/src/test/scala/org/apache/paimon/spark/sql/RewriteUpsertTableTest.scala
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * 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.paimon.spark.sql
-
-class RewriteUpsertTableTest extends RewriteUpsertTableTestBase {}
diff --git
a/paimon-spark/paimon-spark-3.5/src/test/scala/org/apache/paimon/spark/sql/RewriteUpsertTableTest.scala
b/paimon-spark/paimon-spark-3.5/src/test/scala/org/apache/paimon/spark/sql/RewriteUpsertTableTest.scala
deleted file mode 100644
index 412aa3b303..0000000000
---
a/paimon-spark/paimon-spark-3.5/src/test/scala/org/apache/paimon/spark/sql/RewriteUpsertTableTest.scala
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * 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.paimon.spark.sql
-
-class RewriteUpsertTableTest extends RewriteUpsertTableTestBase {}
diff --git
a/paimon-spark/paimon-spark-4.0/src/main/scala/org/apache/paimon/spark/catalyst/analysis/RewriteUpsertTable.scala
b/paimon-spark/paimon-spark-4.0/src/main/scala/org/apache/paimon/spark/catalyst/analysis/RewriteUpsertTable.scala
deleted file mode 100644
index fd8776d6db..0000000000
---
a/paimon-spark/paimon-spark-4.0/src/main/scala/org/apache/paimon/spark/catalyst/analysis/RewriteUpsertTable.scala
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * 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.paimon.spark.catalyst.analysis
-
-import org.apache.paimon.spark.SparkTable
-import org.apache.paimon.table.FileStoreTable
-
-import org.apache.spark.sql.SparkSession
-import org.apache.spark.sql.catalyst.analysis.{UnresolvedAttribute,
UnresolvedStar}
-import org.apache.spark.sql.catalyst.expressions.{Alias, And, Attribute,
CurrentRow, Descending, EqualNullSafe, EqualTo, LessThanOrEqual, Literal,
RowFrame, RowNumber, SortOrder, SpecifiedWindowFrame, UnboundedPreceding,
WindowExpression, WindowSpecDefinition}
-import org.apache.spark.sql.catalyst.plans.logical._
-import org.apache.spark.sql.catalyst.rules.Rule
-import org.apache.spark.sql.execution.datasources.v2.DataSourceV2Relation
-import org.apache.spark.sql.paimon.shims.SparkShimLoader
-
-import scala.collection.JavaConverters._
-
-/** Rewrite upsert table to merge into. */
-case class RewriteUpsertTable(spark: SparkSession) extends Rule[LogicalPlan] {
-
- override def apply(plan: LogicalPlan): LogicalPlan =
plan.resolveOperatorsDown {
- case p @ PaimonV2WriteCommand(table) =>
- val (usingUpsert, upsertKey, sequenceField) = usingUpsertTable(table)
- if (!usingUpsert) {
- return p
- }
-
- p match {
- case AppendData(target, source, _, _, _, _) =>
- val deduplicatedSource = if (sequenceField.nonEmpty) {
- deduplicateBySequenceField(source, upsertKey, sequenceField)
- } else {
- source
- }
-
- rewriteToMergeInto(target, deduplicatedSource, upsertKey,
sequenceField)
- case _ => p
- }
- }
-
- private def usingUpsertTable(table: DataSourceV2Relation): (Boolean,
Seq[String], Seq[String]) = {
- table.table match {
- case SparkTable(fileStoreTable: FileStoreTable) =>
- val coreOptions = fileStoreTable.coreOptions()
- val upsertKey = coreOptions.upsertKey().asScala.toSeq
- val sequenceField = coreOptions.sequenceField().asScala.toSeq
- if (fileStoreTable.primaryKeys().isEmpty && upsertKey.nonEmpty) {
- (true, upsertKey, sequenceField)
- } else {
- (false, Seq.empty, Seq.empty)
- }
- case _ => (false, Seq.empty, Seq.empty)
- }
- }
-
- private def deduplicateBySequenceField(
- source: LogicalPlan,
- upsertKey: Seq[String],
- sequenceField: Seq[String]): LogicalPlan = {
- val winSpec = WindowSpecDefinition(
- cols(source.output, upsertKey),
- cols(source.output, sequenceField).map(SortOrder(_, Descending)),
- SpecifiedWindowFrame(RowFrame, UnboundedPreceding, CurrentRow)
- )
- val rnAlias = Alias(WindowExpression(RowNumber(), winSpec), "__rn__")()
- val withRN = Project(UnresolvedStar(None) :: rnAlias :: Nil, source)
- val filtered = Filter(EqualTo(UnresolvedAttribute("__rn__"), Literal(1)),
withRN)
- Project(source.output, filtered)
- }
-
- private def rewriteToMergeInto(
- target: LogicalPlan,
- source: LogicalPlan,
- upsertKey: Seq[String],
- sequenceField: Seq[String]
- ): MergeIntoTable = {
- val mergeCondition = upsertKey
- .map(k => EqualNullSafe(col(target.output, k), col(source.output, k)))
- .reduce(And)
-
- val updateCondiction = if (sequenceField.nonEmpty) {
- Option.apply(
- sequenceField
- .map(s => LessThanOrEqual(col(target.output, s), col(source.output,
s)))
- .reduce(And))
- } else {
- Option.empty
- }
-
- val assignments: Seq[Assignment] =
- target.output.zip(source.output).map(a => Assignment(a._1, a._2))
-
- val mergeActions = Seq(UpdateAction(updateCondiction, assignments))
- val notMatchedActions = Seq(InsertAction(None, assignments))
-
- SparkShimLoader.shim.createMergeIntoTable(
- target,
- source,
- mergeCondition,
- mergeActions,
- notMatchedActions,
- Seq.empty,
- withSchemaEvolution = false)
- }
-
- private def cols(input: Seq[Attribute], colsNames: Seq[String]):
Seq[Attribute] = {
- colsNames.map(c => col(input, c))
- }
-
- private def col(input: Seq[Attribute], colsName: String): Attribute = {
- input.find(_.name == colsName).get
- }
-}
diff --git
a/paimon-spark/paimon-spark-4.0/src/test/scala/org/apache/paimon/spark/sql/RewriteUpsertTableTest.scala
b/paimon-spark/paimon-spark-4.0/src/test/scala/org/apache/paimon/spark/sql/RewriteUpsertTableTest.scala
deleted file mode 100644
index 412aa3b303..0000000000
---
a/paimon-spark/paimon-spark-4.0/src/test/scala/org/apache/paimon/spark/sql/RewriteUpsertTableTest.scala
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * 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.paimon.spark.sql
-
-class RewriteUpsertTableTest extends RewriteUpsertTableTestBase {}
diff --git
a/paimon-spark/paimon-spark-4.1/src/test/scala/org/apache/paimon/spark/sql/RewriteUpsertTableTest.scala
b/paimon-spark/paimon-spark-4.1/src/test/scala/org/apache/paimon/spark/sql/RewriteUpsertTableTest.scala
deleted file mode 100644
index 412aa3b303..0000000000
---
a/paimon-spark/paimon-spark-4.1/src/test/scala/org/apache/paimon/spark/sql/RewriteUpsertTableTest.scala
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * 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.paimon.spark.sql
-
-class RewriteUpsertTableTest extends RewriteUpsertTableTestBase {}
diff --git
a/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/catalyst/analysis/RewriteUpsertTable.scala
b/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/catalyst/analysis/RewriteUpsertTable.scala
deleted file mode 100644
index ba6d0f8efa..0000000000
---
a/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/catalyst/analysis/RewriteUpsertTable.scala
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * 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.paimon.spark.catalyst.analysis
-
-import org.apache.paimon.spark.SparkTable
-import org.apache.paimon.table.FileStoreTable
-
-import org.apache.spark.sql.SparkSession
-import org.apache.spark.sql.catalyst.analysis.{UnresolvedAttribute,
UnresolvedStar}
-import org.apache.spark.sql.catalyst.expressions.{Alias, And, Attribute,
CurrentRow, Descending, EqualNullSafe, EqualTo, LessThanOrEqual, Literal,
RowFrame, RowNumber, SortOrder, SpecifiedWindowFrame, UnboundedPreceding,
WindowExpression, WindowSpecDefinition}
-import org.apache.spark.sql.catalyst.plans.logical._
-import org.apache.spark.sql.catalyst.rules.Rule
-import org.apache.spark.sql.execution.datasources.v2.DataSourceV2Relation
-import org.apache.spark.sql.paimon.shims.SparkShimLoader
-
-import scala.collection.JavaConverters._
-
-/** Rewrite upsert table to merge into. */
-case class RewriteUpsertTable(spark: SparkSession) extends Rule[LogicalPlan] {
-
- override def apply(plan: LogicalPlan): LogicalPlan =
plan.resolveOperatorsDown {
- case p @ PaimonV2WriteCommand(table) =>
- val (usingUpsert, upsertKey, sequenceField) = usingUpsertTable(table)
- if (!usingUpsert) {
- return p
- }
-
- p match {
- case AppendData(target, source, _, _, _, _) =>
- val deduplicatedSource = if (sequenceField.nonEmpty) {
- deduplicateBySequenceField(source, upsertKey, sequenceField)
- } else {
- source
- }
-
- rewriteToMergeInto(target, deduplicatedSource, upsertKey,
sequenceField)
- case _ => p
- }
- }
-
- private def usingUpsertTable(table: DataSourceV2Relation): (Boolean,
Seq[String], Seq[String]) = {
- table.table match {
- case SparkTable(fileStoreTable: FileStoreTable) =>
- val coreOptions = fileStoreTable.coreOptions()
- val upsertKey = coreOptions.upsertKey().asScala.toSeq
- val sequenceField = coreOptions.sequenceField().asScala.toSeq
- if (fileStoreTable.primaryKeys().isEmpty && upsertKey.nonEmpty) {
- (true, upsertKey, sequenceField)
- } else {
- (false, Seq.empty, Seq.empty)
- }
- case _ => (false, Seq.empty, Seq.empty)
- }
- }
-
- private def deduplicateBySequenceField(
- source: LogicalPlan,
- upsertKey: Seq[String],
- sequenceField: Seq[String]): LogicalPlan = {
- val winSpec = WindowSpecDefinition(
- cols(source.output, upsertKey),
- cols(source.output, sequenceField).map(SortOrder(_, Descending)),
- SpecifiedWindowFrame(RowFrame, UnboundedPreceding, CurrentRow)
- )
- val rnAlias = Alias(WindowExpression(RowNumber(), winSpec), "__rn__")()
- val withRN = Project(UnresolvedStar(None) :: rnAlias :: Nil, source)
- val filtered = Filter(EqualTo(UnresolvedAttribute("__rn__"), Literal(1)),
withRN)
- Project(source.output, filtered)
- }
-
- private def rewriteToMergeInto(
- target: LogicalPlan,
- source: LogicalPlan,
- upsertKey: Seq[String],
- sequenceField: Seq[String]
- ): MergeIntoTable = {
- val mergeCondition = upsertKey
- .map(k => EqualNullSafe(col(target.output, k), col(source.output, k)))
- .reduce(And)
-
- val updateCondiction = if (sequenceField.nonEmpty) {
- Option.apply(
- sequenceField
- .map(s => LessThanOrEqual(col(target.output, s), col(source.output,
s)))
- .reduce(And))
- } else {
- Option.empty
- }
-
- val assignments: Seq[Assignment] =
- target.output.zip(source.output).map(a => Assignment(a._1, a._2))
-
- val shim = SparkShimLoader.shim
- val mergeActions = Seq(shim.createUpdateAction(updateCondiction,
assignments))
- val notMatchedActions = Seq(shim.createInsertAction(None, assignments))
-
- SparkShimLoader.shim.createMergeIntoTable(
- target,
- source,
- mergeCondition,
- mergeActions,
- notMatchedActions,
- Seq.empty,
- withSchemaEvolution = false)
- }
-
- private def cols(input: Seq[Attribute], colsNames: Seq[String]):
Seq[Attribute] = {
- colsNames.map(c => col(input, c))
- }
-
- private def col(input: Seq[Attribute], colsName: String): Attribute = {
- input.find(_.name == colsName).get
- }
-}
diff --git
a/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/extensions/PaimonSparkSessionExtensions.scala
b/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/extensions/PaimonSparkSessionExtensions.scala
index e3701a468a..f856d348a4 100644
---
a/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/extensions/PaimonSparkSessionExtensions.scala
+++
b/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/extensions/PaimonSparkSessionExtensions.scala
@@ -18,7 +18,7 @@
package org.apache.paimon.spark.extensions
-import org.apache.paimon.spark.catalyst.analysis.{PaimonAnalysis,
PaimonDeleteTable, PaimonFunctionResolver, PaimonIncompatibleResolutionRules,
PaimonMergeInto, PaimonPostHocResolutionRules, PaimonProcedureResolver,
PaimonUpdateTable, PaimonViewResolver, ReplacePaimonFunctions,
RewriteUpsertTable}
+import org.apache.paimon.spark.catalyst.analysis.{PaimonAnalysis,
PaimonDeleteTable, PaimonFunctionResolver, PaimonIncompatibleResolutionRules,
PaimonMergeInto, PaimonPostHocResolutionRules, PaimonProcedureResolver,
PaimonUpdateTable, PaimonViewResolver, ReplacePaimonFunctions}
import
org.apache.paimon.spark.catalyst.optimizer.{MergePaimonScalarSubqueries,
OptimizeMetadataOnlyDeleteFromPaimonTable, PushDownLateralVectorSearchFilter,
RepartitionLateralVectorSearchInput}
import
org.apache.paimon.spark.catalyst.plans.logical.PaimonTableValuedFunctions
import org.apache.paimon.spark.commands.BucketExpression
@@ -44,8 +44,6 @@ class PaimonSparkSessionExtensions extends
(SparkSessionExtensions => Unit) {
extensions.injectResolutionRule(spark => PaimonFunctionResolver(spark))
extensions.injectResolutionRule(spark =>
SparkShimLoader.shim.createCustomResolution(spark))
extensions.injectResolutionRule(spark =>
PaimonIncompatibleResolutionRules(spark))
- extensions.injectResolutionRule(spark => RewriteUpsertTable(spark))
-
extensions.injectPostHocResolutionRule(spark =>
ReplacePaimonFunctions(spark))
extensions.injectPostHocResolutionRule(spark =>
PaimonPostHocResolutionRules(spark))
diff --git
a/paimon-spark/paimon-spark-ut/src/test/scala/org/apache/paimon/spark/sql/RewriteUpsertTableTestBase.scala
b/paimon-spark/paimon-spark-ut/src/test/scala/org/apache/paimon/spark/sql/RewriteUpsertTableTestBase.scala
deleted file mode 100644
index 7fb2223a98..0000000000
---
a/paimon-spark/paimon-spark-ut/src/test/scala/org/apache/paimon/spark/sql/RewriteUpsertTableTestBase.scala
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * 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.paimon.spark.sql
-
-import org.apache.paimon.spark.PaimonSparkTestBase
-
-import org.apache.spark.sql.Row
-
-abstract class RewriteUpsertTableTestBase extends PaimonSparkTestBase {
-
- test("Rewrite Upsert Table: cannot define with primary key") {
- assert(intercept[Exception] {
- sql("""
- |CREATE TABLE T (k INT, a INT, b STRING)
- |TBLPROPERTIES ('upsert-key' = 'k', 'primary-key' = 'k')
- |""".stripMargin)
- }.getMessage.contains("Cannot define 'upsert-key' [k] with 'primary-key'
[k]."))
- }
-
- test("Rewrite Upsert Table: rewrite insert without sequence field") {
- sql("""
- |CREATE TABLE T (k INT, a INT, b STRING)
- |TBLPROPERTIES ('upsert-key' = 'k')
- |""".stripMargin)
-
- sql("INSERT INTO T values (1, 1, 'c1'), (null, 2, 'c2'), (3, 3, 'c3')")
- sql("INSERT INTO T values (null, 22, 'c22'), (1, 11, 'c11'), (4, 4, 'c4')")
-
- checkAnswer(
- sql("SELECT * FROM T ORDER BY k"),
- Seq(Row(null, 22, "c22"), Row(1, 11, "c11"), Row(3, 3, "c3"), Row(4, 4,
"c4")))
- }
-
- test("Rewrite Upsert Table: rewrite insert with sequence field") {
- sql("""
- |CREATE TABLE T (k1 INT, k2 INT, ts1 INT, ts2 INT, c STRING)
- |TBLPROPERTIES ('upsert-key' = 'k1,k2', 'sequence.field' = 'ts1,ts2')
- |""".stripMargin)
-
- // test insert deduplicate
- sql("""
- |INSERT INTO T values
- |(null, null, 2, 1, 'v3'),
- |(null, null, 2, 2, 'v4'),
- |(null, null, 1, 1, 'v1'),
- |(null, null, 1, 2, 'v2'),
- |(1, null, 1, 1, 'v1'),
- |(1, 2, 1, 1, 'v1'),
- |(1, 2, 2, 1, 'v2')
- |""".stripMargin)
- checkAnswer(
- sql("SELECT * FROM T ORDER BY k1, k2"),
- Seq(Row(null, null, 2, 2, "v4"), Row(1, null, 1, 1, "v1"), Row(1, 2, 2,
1, "v2")))
-
- // test inset with different sequence field
- sql("""
- |INSERT INTO T values
- |(null, null, 2, 1, 'v44'),
- |(1, null, 2, 1, 'v2'),
- |(null, 1, 1, 1, 'v1'),
- |(1, 2, 2, 2, 'v3'),
- |(1, 1, 1, 1, 'v1')
- |""".stripMargin)
- checkAnswer(
- sql("SELECT * FROM T ORDER BY k1, k2"),
- Seq(
- Row(null, null, 2, 2, "v4"),
- Row(null, 1, 1, 1, "v1"),
- Row(1, null, 2, 1, "v2"),
- Row(1, 1, 1, 1, "v1"),
- Row(1, 2, 2, 2, "v3")))
- }
-}