This is an automated email from the ASF dual-hosted git repository.
github-merge-queue[bot] pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/texera.git
The following commit(s) were added to refs/heads/main by this push:
new e30eb17518 feat(storage): add the user_warehouse table schema (#7386)
e30eb17518 is described below
commit e30eb17518dec86b522a2715f6b73c431ed39097
Author: Meng Wang <[email protected]>
AuthorDate: Sun Aug 9 07:56:35 2026 -0700
feat(storage): add the user_warehouse table schema (#7386)
### What changes were proposed in this PR?
Adds the `user_warehouse` table (umbrella #6870): one row per warehouse
a user registers. The DDL below is the full shape — base columns only;
the assume-role (BYO-S3) columns come in a later change.
```sql
CREATE TABLE IF NOT EXISTS user_warehouse
(
whid SERIAL PRIMARY KEY,
uid INT NOT NULL,
name VARCHAR(128) NOT NULL,
warehouse_name VARCHAR(255) NOT NULL UNIQUE,
lakekeeper_warehouse_id UUID,
flavor VARCHAR(32) NOT NULL,
s3_bucket VARCHAR(255),
s3_endpoint VARCHAR(255),
s3_region VARCHAR(64),
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
UNIQUE (uid, name),
FOREIGN KEY (uid) REFERENCES "user" (uid) ON DELETE CASCADE
);
```
Schema only — nothing reads or writes the table yet. The DDL, the
incremental migration (`sql/updates/32.sql`), and the changelog
registration ship together; jOOQ classes are generated from the live
database at build time as usual.
### Any related issues, documentation, discussions?
Closes #6931. Part of #6870 (design discussions #5293 and #6040).
### How was this PR tested?
A new `UserWarehouseSpec` (MockTexeraDB, embedded Postgres) pins the
DDL's structural properties against the generated jOOQ classes:
insert/read-back of a registered warehouse, the per-user name
uniqueness, and the ownership cascade. Verified locally with `sbt
"DAO/testOnly *UserWarehouseSpec"` plus scalafmt/scalafix; the cascade
case was deliberately broken once to confirm it fails red.
### Was this PR authored or co-authored using generative AI tooling?
Generated-by: Claude Code (claude-fable-5)
---
.../org/apache/texera/dao/UserWarehouseSpec.scala | 114 +++++++++++++++++++++
sql/changelog.xml | 5 +
sql/texera_ddl.sql | 19 ++++
sql/updates/32.sql | 46 +++++++++
4 files changed, 184 insertions(+)
diff --git
a/common/dao/src/test/scala/org/apache/texera/dao/UserWarehouseSpec.scala
b/common/dao/src/test/scala/org/apache/texera/dao/UserWarehouseSpec.scala
new file mode 100644
index 0000000000..23c549d059
--- /dev/null
+++ b/common/dao/src/test/scala/org/apache/texera/dao/UserWarehouseSpec.scala
@@ -0,0 +1,114 @@
+/*
+ * 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.texera.dao
+
+import org.apache.texera.dao.jooq.generated.Tables.{USER, USER_WAREHOUSE}
+import org.apache.texera.dao.jooq.generated.enums.UserWarehouseFlavorEnum
+import org.jooq.exception.DataAccessException
+
+import java.util.UUID
+import org.scalatest.BeforeAndAfterAll
+import org.scalatest.flatspec.AnyFlatSpec
+import org.scalatest.matchers.should.Matchers
+
+/**
+ * Spec for the `user_warehouse` table (#6931). Schema only — nothing reads
or writes the
+ * table in production yet — so this pins the DDL's structural properties
(columns, the
+ * per-user name uniqueness, and the ownership cascade) against the generated
jOOQ classes.
+ */
+class UserWarehouseSpec extends AnyFlatSpec with Matchers with
BeforeAndAfterAll with MockTexeraDB {
+
+ override protected def beforeAll(): Unit = {
+ super.beforeAll()
+ initializeDBAndReplaceDSLContext()
+ }
+
+ override protected def afterAll(): Unit =
+ try closeConnectionPool()
+ finally super.afterAll()
+
+ private def insertUser(name: String): Integer =
+ getDSLContext
+ .insertInto(USER, USER.NAME, USER.PASSWORD)
+ .values(name, "password")
+ .returning(USER.UID)
+ .fetchOne()
+ .getUid
+
+ private def insertWarehouse(uid: Integer, name: String, warehouseName:
String): Integer =
+ getDSLContext
+ .insertInto(
+ USER_WAREHOUSE,
+ USER_WAREHOUSE.UID,
+ USER_WAREHOUSE.NAME,
+ USER_WAREHOUSE.WAREHOUSE_NAME,
+ USER_WAREHOUSE.LAKEKEEPER_WAREHOUSE_ID,
+ USER_WAREHOUSE.FLAVOR
+ )
+ .values(uid, name, warehouseName, UUID.randomUUID(),
UserWarehouseFlavorEnum.local)
+ .returning(USER_WAREHOUSE.WHID)
+ .fetchOne()
+ .getWhid
+
+ "user_warehouse" should "store a registered warehouse and return it by
owner" in {
+ val uid = insertUser("warehouse-owner")
+ insertWarehouse(uid, "mybucket", s"user-$uid-mybucket")
+
+ val row = getDSLContext
+ .selectFrom(USER_WAREHOUSE)
+ .where(USER_WAREHOUSE.UID.eq(uid))
+ .fetchOne()
+ row.getName shouldBe "mybucket"
+ row.getWarehouseName shouldBe s"user-$uid-mybucket"
+ row.getFlavor shouldBe UserWarehouseFlavorEnum.local
+ row.getLakekeeperWarehouseId should not be null
+ row.getCreatedAt should not be null
+ }
+
+ it should "reject a duplicate display name for the same user" in {
+ val uid = insertUser("duplicate-name-owner")
+ insertWarehouse(uid, "dup", s"user-$uid-dup")
+
+ a[DataAccessException] should be thrownBy
+ insertWarehouse(uid, "dup", s"user-$uid-dup-2")
+ }
+
+ it should "reject a duplicate warehouse_name across users" in {
+ val first = insertUser("catalog-name-owner")
+ val second = insertUser("catalog-name-intruder")
+ insertWarehouse(first, "shared", "user-shared-catalog-name")
+
+ a[DataAccessException] should be thrownBy
+ insertWarehouse(second, "unrelated", "user-shared-catalog-name")
+ }
+
+ it should "cascade-delete a user's warehouses with the user" in {
+ val uid = insertUser("cascade-owner")
+ insertWarehouse(uid, "doomed", s"user-$uid-doomed")
+
+ getDSLContext.deleteFrom(USER).where(USER.UID.eq(uid)).execute()
+
+ getDSLContext
+ .selectFrom(USER_WAREHOUSE)
+ .where(USER_WAREHOUSE.UID.eq(uid))
+ .fetch()
+ .size shouldBe 0
+ }
+}
diff --git a/sql/changelog.xml b/sql/changelog.xml
index bdba73339d..f6daf9dc33 100644
--- a/sql/changelog.xml
+++ b/sql/changelog.xml
@@ -68,6 +68,11 @@
<sqlFile path="sql/updates/31.sql"/>
</changeSet>
+ <!-- Per-user warehouse registrations (#6870) -->
+ <changeSet id="32" author="mengw15">
+ <sqlFile path="sql/updates/32.sql"/>
+ </changeSet>
+
<!-- example changeSet
<changeSet id="1" author="author">
<sqlFile path="sql/updates/1.sql"/>
diff --git a/sql/texera_ddl.sql b/sql/texera_ddl.sql
index 5b62f45edd..e90b79226a 100644
--- a/sql/texera_ddl.sql
+++ b/sql/texera_ddl.sql
@@ -92,6 +92,7 @@ CREATE TYPE user_role_enum AS ENUM ('INACTIVE', 'RESTRICTED',
'REGULAR', 'ADMIN'
CREATE TYPE action_enum AS ENUM ('like', 'unlike', 'view', 'clone');
CREATE TYPE privilege_enum AS ENUM ('NONE', 'READ', 'WRITE');
CREATE TYPE workflow_computing_unit_type_enum AS ENUM ('local', 'kubernetes');
+CREATE TYPE user_warehouse_flavor_enum AS ENUM ('local', 'aws');
-- ============================================
-- 5. Create tables
@@ -239,6 +240,24 @@ CREATE TABLE IF NOT EXISTS workflow_computing_unit
FOREIGN KEY (uid) REFERENCES "user"(uid) ON DELETE CASCADE
);
+-- Per-user warehouse registrations (#6870): one row per warehouse a user
registered.
+-- Base columns only; the assume-role (BYO-S3) columns come in a later change.
+CREATE TABLE IF NOT EXISTS user_warehouse
+(
+ whid SERIAL PRIMARY KEY,
+ uid INT NOT NULL,
+ name VARCHAR(128) NOT NULL,
+ warehouse_name VARCHAR(255) NOT NULL UNIQUE,
+ lakekeeper_warehouse_id UUID NOT NULL,
+ flavor user_warehouse_flavor_enum NOT NULL,
+ s3_bucket VARCHAR(255),
+ s3_endpoint VARCHAR(255),
+ s3_region VARCHAR(64),
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
+ UNIQUE (uid, name),
+ FOREIGN KEY (uid) REFERENCES "user" (uid) ON DELETE CASCADE
+);
+
-- virtual_environments table
CREATE TABLE IF NOT EXISTS virtual_environments
(
diff --git a/sql/updates/32.sql b/sql/updates/32.sql
new file mode 100644
index 0000000000..e25c25b1fe
--- /dev/null
+++ b/sql/updates/32.sql
@@ -0,0 +1,46 @@
+/*
+ * 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.
+ */
+
+\c texera_db
+
+SET search_path TO texera_db;
+
+BEGIN;
+
+CREATE TYPE user_warehouse_flavor_enum AS ENUM ('local', 'aws');
+
+-- Per-user warehouse registrations (#6870): one row per warehouse a user
registered.
+-- Base columns only; the assume-role (BYO-S3) columns come in a later change.
+CREATE TABLE IF NOT EXISTS user_warehouse
+(
+ whid SERIAL PRIMARY KEY,
+ uid INT NOT NULL,
+ name VARCHAR(128) NOT NULL,
+ warehouse_name VARCHAR(255) NOT NULL UNIQUE,
+ lakekeeper_warehouse_id UUID NOT NULL,
+ flavor user_warehouse_flavor_enum NOT NULL,
+ s3_bucket VARCHAR(255),
+ s3_endpoint VARCHAR(255),
+ s3_region VARCHAR(64),
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
+ UNIQUE (uid, name),
+ FOREIGN KEY (uid) REFERENCES "user" (uid) ON DELETE CASCADE
+);
+
+COMMIT;