You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iceberg.apache.org by sz...@apache.org on 2022/04/20 23:57:05 UTC
[iceberg] branch master updated: Spark 3.0: Ignore SQL comments checking for Iceberg commands (#4497)
This is an automated email from the ASF dual-hosted git repository.
szehon pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iceberg.git
The following commit(s) were added to refs/heads/master by this push:
new 0b48f51ee Spark 3.0: Ignore SQL comments checking for Iceberg commands (#4497)
0b48f51ee is described below
commit 0b48f51ee4aa2fb2c33b8d1335abb236a6a8d1c4
Author: Kyle Bendickson <kj...@gmail.com>
AuthorDate: Wed Apr 20 16:57:01 2022 -0700
Spark 3.0: Ignore SQL comments checking for Iceberg commands (#4497)
---
.../IcebergSparkSqlExtensionsParser.scala | 10 +++++-
.../spark/extensions/TestCallStatementParser.java | 27 ++++++++++++++++
.../extensions/TestExpireSnapshotsProcedure.java | 36 ++++++++++++++++++++++
3 files changed, 72 insertions(+), 1 deletion(-)
diff --git a/spark/v3.0/spark-extensions/src/main/scala/org/apache/spark/sql/catalyst/parser/extensions/IcebergSparkSqlExtensionsParser.scala b/spark/v3.0/spark-extensions/src/main/scala/org/apache/spark/sql/catalyst/parser/extensions/IcebergSparkSqlExtensionsParser.scala
index b950bd704..6b6066773 100644
--- a/spark/v3.0/spark-extensions/src/main/scala/org/apache/spark/sql/catalyst/parser/extensions/IcebergSparkSqlExtensionsParser.scala
+++ b/spark/v3.0/spark-extensions/src/main/scala/org/apache/spark/sql/catalyst/parser/extensions/IcebergSparkSqlExtensionsParser.scala
@@ -108,7 +108,15 @@ class IcebergSparkSqlExtensionsParser(delegate: ParserInterface) extends ParserI
}
private def isIcebergCommand(sqlText: String): Boolean = {
- val normalized = sqlText.toLowerCase(Locale.ROOT).trim().replaceAll("\\s+", " ")
+ val normalized = sqlText.toLowerCase(Locale.ROOT).trim()
+ // Strip simple SQL comments that terminate a line, e.g. comments starting with `--`
+ .replaceAll("--.*?\\n", " ")
+ // Strip newlines.
+ .replaceAll("\\s+", " ")
+ // Strip comments of the form /* ... */. This must come after stripping newlines so that
+ // comments that span multiple lines are caught.
+ .replaceAll("/\\*.*?\\*/", " ")
+ .trim()
normalized.startsWith("call") || (
normalized.startsWith("alter table") && (
normalized.contains("add partition field") ||
diff --git a/spark/v3.0/spark-extensions/src/test/java/org/apache/iceberg/spark/extensions/TestCallStatementParser.java b/spark/v3.0/spark-extensions/src/test/java/org/apache/iceberg/spark/extensions/TestCallStatementParser.java
index 99e9d0621..c8c4316a1 100644
--- a/spark/v3.0/spark-extensions/src/test/java/org/apache/iceberg/spark/extensions/TestCallStatementParser.java
+++ b/spark/v3.0/spark-extensions/src/test/java/org/apache/iceberg/spark/extensions/TestCallStatementParser.java
@@ -22,8 +22,10 @@ package org.apache.iceberg.spark.extensions;
import java.math.BigDecimal;
import java.sql.Timestamp;
import java.time.Instant;
+import java.util.List;
import org.apache.iceberg.AssertHelpers;
import org.apache.iceberg.relocated.com.google.common.collect.ImmutableList;
+import org.apache.iceberg.relocated.com.google.common.collect.Lists;
import org.apache.spark.sql.SparkSession;
import org.apache.spark.sql.catalyst.expressions.Expression;
import org.apache.spark.sql.catalyst.expressions.Literal;
@@ -130,6 +132,31 @@ public class TestCallStatementParser {
checkArg(call, 0, "value", DataTypes.StringType);
}
+ @Test
+ public void testCallStripsComments() throws ParseException {
+ List<String> callStatementsWithComments = Lists.newArrayList(
+ "/* bracketed comment */ CALL cat.system.func('${spark.extra.prop}')",
+ "/**/ CALL cat.system.func('${spark.extra.prop}')",
+ "-- single line comment \n CALL cat.system.func('${spark.extra.prop}')",
+ "-- multiple \n-- single line \n-- comments \n CALL cat.system.func('${spark.extra.prop}')",
+ "/* select * from multiline_comment \n where x like '%sql%'; */ CALL cat.system.func('${spark.extra.prop}')",
+ "/* {\"app\": \"dbt\", \"dbt_version\": \"1.0.1\", \"profile_name\": \"profile1\", \"target_name\": \"dev\", " +
+ "\"node_id\": \"model.profile1.stg_users\"} \n*/ CALL cat.system.func('${spark.extra.prop}')",
+ "/* Some multi-line comment \n" +
+ "*/ CALL /* inline comment */ cat.system.func('${spark.extra.prop}') -- ending comment",
+ "CALL -- a line ending comment\n" +
+ "cat.system.func('${spark.extra.prop}')"
+ );
+ for (String sqlText : callStatementsWithComments) {
+ CallStatement call = (CallStatement) parser.parsePlan(sqlText);
+ Assert.assertEquals(ImmutableList.of("cat", "system", "func"), JavaConverters.seqAsJavaList(call.name()));
+
+ Assert.assertEquals(1, call.args().size());
+
+ checkArg(call, 0, "value", DataTypes.StringType);
+ }
+ }
+
@Test
public void testCallParseError() {
AssertHelpers.assertThrows("Should fail with a sensible parse error", IcebergParseException.class,
diff --git a/spark/v3.0/spark-extensions/src/test/java/org/apache/iceberg/spark/extensions/TestExpireSnapshotsProcedure.java b/spark/v3.0/spark-extensions/src/test/java/org/apache/iceberg/spark/extensions/TestExpireSnapshotsProcedure.java
index 881ee5c1d..bc3e5e423 100644
--- a/spark/v3.0/spark-extensions/src/test/java/org/apache/iceberg/spark/extensions/TestExpireSnapshotsProcedure.java
+++ b/spark/v3.0/spark-extensions/src/test/java/org/apache/iceberg/spark/extensions/TestExpireSnapshotsProcedure.java
@@ -249,4 +249,40 @@ public class TestExpireSnapshotsProcedure extends SparkExtensionsTestBase {
catalogName, currentTimestamp, tableIdent);
assertEquals("Procedure output must match", ImmutableList.of(row(0L, 0L, 1L)), output);
}
+
+ @Test
+ public void testExpireSnapshotsProcedureWorksWithSqlComments() {
+ // Ensure that systems such as dbt, that inject comments into the generated SQL files, will
+ // work with Iceberg-specific DDL
+ sql("CREATE TABLE %s (id bigint NOT NULL, data string) USING iceberg", tableName);
+
+ sql("INSERT INTO TABLE %s VALUES (1, 'a')", tableName);
+ sql("INSERT INTO TABLE %s VALUES (2, 'b')", tableName);
+
+ Table table = validationCatalog.loadTable(tableIdent);
+
+ Assert.assertEquals("Should be 2 snapshots", 2, Iterables.size(table.snapshots()));
+
+ waitUntilAfter(table.currentSnapshot().timestampMillis());
+
+ Timestamp currentTimestamp = Timestamp.from(Instant.ofEpochMilli(System.currentTimeMillis()));
+
+ String callStatement =
+ "/* CALL statement is used to expire snapshots */\n" +
+ "-- And we have single line comments as well \n" +
+ "/* And comments that span *multiple* \n" +
+ " lines */ CALL /* this is the actual CALL */ %s.system.expire_snapshots(" +
+ " older_than => TIMESTAMP '%s'," +
+ " table => '%s'," +
+ " retain_last => 1)";
+ List<Object[]> output = sql(
+ callStatement, catalogName, currentTimestamp, tableIdent);
+ assertEquals("Procedure output must match",
+ ImmutableList.of(row(0L, 0L, 1L)),
+ output);
+
+ table.refresh();
+
+ Assert.assertEquals("Should be 1 snapshot remaining", 1, Iterables.size(table.snapshots()));
+ }
}