This is an automated email from the ASF dual-hosted git repository.
damccorm pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/beam.git
The following commit(s) were added to refs/heads/master by this push:
new 6c083df5f8e SQL Database DDL & Usability Improvements (#38952)
6c083df5f8e is described below
commit 6c083df5f8ed39b46aa02e2c90594cc3a7599a21
Author: Danny McCormick <[email protected]>
AuthorDate: Fri Jun 12 17:02:12 2026 -0400
SQL Database DDL & Usability Improvements (#38952)
* SQL Database DDL & Usability Improvements: Support CREATE/DROP DATABASE
and USE short syntax
* Address code review feedback: add guard in InMemoryCatalog.dropDatabase
and add tests for DROP DATABASE IF EXISTS
* Address new code review feedback: optimize InMemoryCatalog, add null
check in SqlDdlNodes, and make tests more precise
---
.../sql/src/main/codegen/includes/parserImpls.ftl | 4 +-
.../extensions/sql/impl/parser/SqlDdlNodes.java | 7 ++-
.../sql/meta/catalog/InMemoryCatalog.java | 12 +++--
.../sdk/extensions/sql/BeamSqlCliDatabaseTest.java | 52 ++++++++++++++++++++++
4 files changed, 68 insertions(+), 7 deletions(-)
diff --git a/sdks/java/extensions/sql/src/main/codegen/includes/parserImpls.ftl
b/sdks/java/extensions/sql/src/main/codegen/includes/parserImpls.ftl
index 94c0161c492..cb8eec43872 100644
--- a/sdks/java/extensions/sql/src/main/codegen/includes/parserImpls.ftl
+++ b/sdks/java/extensions/sql/src/main/codegen/includes/parserImpls.ftl
@@ -381,7 +381,7 @@ SqlCreate SqlCreateDatabase(Span s, boolean replace) :
}
/**
- * USE DATABASE ( catalog_name '.' )? database_name
+ * USE [ DATABASE ] ( catalog_name '.' )? database_name
*/
SqlCall SqlUseDatabase(Span s, String scope) :
{
@@ -391,7 +391,7 @@ SqlCall SqlUseDatabase(Span s, String scope) :
<USE> {
s.add(this);
}
- <DATABASE>
+ [ <DATABASE> ]
databaseName = CompoundIdentifier()
{
return new SqlUseDatabase(
diff --git
a/sdks/java/extensions/sql/src/main/java/org/apache/beam/sdk/extensions/sql/impl/parser/SqlDdlNodes.java
b/sdks/java/extensions/sql/src/main/java/org/apache/beam/sdk/extensions/sql/impl/parser/SqlDdlNodes.java
index 6d6be5d5a12..f8d7e6f7385 100644
---
a/sdks/java/extensions/sql/src/main/java/org/apache/beam/sdk/extensions/sql/impl/parser/SqlDdlNodes.java
+++
b/sdks/java/extensions/sql/src/main/java/org/apache/beam/sdk/extensions/sql/impl/parser/SqlDdlNodes.java
@@ -55,7 +55,7 @@ public class SqlDdlNodes {
}
/** Returns the schema in which to create an object. */
- static Pair<CalciteSchema, String> schema(
+ public static Pair<CalciteSchema, String> schema(
CalcitePrepare.Context context, boolean mutable, SqlIdentifier id) {
CalciteSchema rootSchema = mutable ? context.getMutableRootSchema() :
context.getRootSchema();
@Nullable CalciteSchema schema = null;
@@ -72,7 +72,10 @@ public class SqlDdlNodes {
return Pair.of(checkStateNotNull(schema, "Got null sub-schema for path
'%s'", path), name(id));
}
- private static @Nullable CalciteSchema childSchema(CalciteSchema rootSchema,
List<String> path) {
+ public static @Nullable CalciteSchema childSchema(CalciteSchema rootSchema,
List<String> path) {
+ if (path == null) {
+ return null;
+ }
@Nullable CalciteSchema schema = rootSchema;
for (String p : path) {
if (schema == null) {
diff --git
a/sdks/java/extensions/sql/src/main/java/org/apache/beam/sdk/extensions/sql/meta/catalog/InMemoryCatalog.java
b/sdks/java/extensions/sql/src/main/java/org/apache/beam/sdk/extensions/sql/meta/catalog/InMemoryCatalog.java
index cdee6c93022..68e80c2340f 100644
---
a/sdks/java/extensions/sql/src/main/java/org/apache/beam/sdk/extensions/sql/meta/catalog/InMemoryCatalog.java
+++
b/sdks/java/extensions/sql/src/main/java/org/apache/beam/sdk/extensions/sql/meta/catalog/InMemoryCatalog.java
@@ -18,7 +18,6 @@
package org.apache.beam.sdk.extensions.sql.meta.catalog;
import static
org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.base.Preconditions.checkArgument;
-import static
org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.base.Preconditions.checkState;
import java.util.Collection;
import java.util.Collections;
@@ -111,13 +110,20 @@ public class InMemoryCatalog implements Catalog {
@Override
public boolean dropDatabase(String database, boolean cascade) {
- checkState(!cascade, "%s does not support CASCADE.",
getClass().getSimpleName());
+ MetaStore metaStore = metaStores.get(database);
+ if (!cascade && metaStore != null && !metaStore.getTables().isEmpty()) {
+ throw new IllegalStateException("Database '" + database + "' is not
empty.");
+ }
boolean removed = databases.remove(database);
+ if (!removed) {
+ return false;
+ }
if (database.equals(currentDatabase)) {
currentDatabase = null;
}
- return removed;
+ metaStores.remove(database);
+ return true;
}
@Override
diff --git
a/sdks/java/extensions/sql/src/test/java/org/apache/beam/sdk/extensions/sql/BeamSqlCliDatabaseTest.java
b/sdks/java/extensions/sql/src/test/java/org/apache/beam/sdk/extensions/sql/BeamSqlCliDatabaseTest.java
index 588caa78a2b..54682911fe1 100644
---
a/sdks/java/extensions/sql/src/test/java/org/apache/beam/sdk/extensions/sql/BeamSqlCliDatabaseTest.java
+++
b/sdks/java/extensions/sql/src/test/java/org/apache/beam/sdk/extensions/sql/BeamSqlCliDatabaseTest.java
@@ -83,6 +83,15 @@ public class BeamSqlCliDatabaseTest {
assertEquals("my_database2",
catalogManager.currentCatalog().currentDatabase());
}
+ @Test
+ public void testUseDatabaseWithoutDatabaseKeyword() {
+ assertEquals(DEFAULT, catalogManager.currentCatalog().currentDatabase());
+ cli.execute("CREATE DATABASE my_database");
+ assertEquals(DEFAULT, catalogManager.currentCatalog().currentDatabase());
+ cli.execute("USE my_database");
+ assertEquals("my_database",
catalogManager.currentCatalog().currentDatabase());
+ }
+
@Test
public void testUseDatabase_doesNotExist() {
assertEquals(DEFAULT, catalogManager.currentCatalog().currentDatabase());
@@ -126,6 +135,49 @@ public class BeamSqlCliDatabaseTest {
cli.execute("DROP DATABASE my_database");
}
+ @Test
+ public void testDropDatabase_ifExists_nonexistent() {
+ assertFalse(catalogManager.currentCatalog().databaseExists("my_database"));
+ // Should not throw exception
+ cli.execute("DROP DATABASE IF EXISTS my_database");
+ assertFalse(catalogManager.currentCatalog().databaseExists("my_database"));
+ }
+
+ @Test
+ public void testDropDatabase_ifExists_exists() {
+ cli.execute("CREATE DATABASE my_database");
+ assertTrue(catalogManager.currentCatalog().databaseExists("my_database"));
+ cli.execute("DROP DATABASE IF EXISTS my_database");
+ assertFalse(catalogManager.currentCatalog().databaseExists("my_database"));
+ }
+
+ @Test
+ public void testDropDatabase_notEmpty_restrict() {
+ cli.execute("CREATE DATABASE db_1");
+ cli.execute("USE db_1");
+
+ TestTableProvider testTableProvider = new TestTableProvider();
+ catalogManager.registerTableProvider(testTableProvider);
+ cli.execute("CREATE EXTERNAL TABLE person(id int, name varchar, age int)
TYPE 'test'");
+
+ thrown.expect(CalciteContextException.class);
+ thrown.expectMessage("Database 'db_1' is not empty.");
+ cli.execute("DROP DATABASE db_1");
+ }
+
+ @Test
+ public void testDropDatabase_notEmpty_cascade() {
+ cli.execute("CREATE DATABASE db_1");
+ cli.execute("USE db_1");
+
+ TestTableProvider testTableProvider = new TestTableProvider();
+ catalogManager.registerTableProvider(testTableProvider);
+ cli.execute("CREATE EXTERNAL TABLE person(id int, name varchar, age int)
TYPE 'test'");
+
+ cli.execute("DROP DATABASE db_1 CASCADE");
+ assertFalse(catalogManager.currentCatalog().databaseExists("db_1"));
+ }
+
@Test
public void testCreateInsertDropTableUsingDefaultDatabase() {
Catalog catalog = catalogManager.currentCatalog();