Skip to content

feat(migrations): atomic core + extension migrations#1768

Merged
c-thiel merged 10 commits into
mainfrom
christian/vak-407-extension-table-convention-postmigratehook
May 23, 2026
Merged

feat(migrations): atomic core + extension migrations#1768
c-thiel merged 10 commits into
mainfrom
christian/vak-407-extension-table-convention-postmigratehook

Conversation

@c-thiel

@c-thiel c-thiel commented May 22, 2026

Copy link
Copy Markdown
Member

BEGIN_COMMIT_OVERRIDE
feat(migrations): atomic core + extension migrations

chore: Bump MSRV to 1.94
chore(deps): sqlx v0.9.0
END_COMMIT_OVERRIDE

Add ExtensionMigrations registration to upstream's migrate() so downstream crates can contribute SQL migrations that run inside the same outer transaction as core. Either every migration commits or the upgrade rolls back — partial state across core and extensions is
impossible.

    pub async fn migrate(
        pool: &PgPool,
        extensions: Vec<ExtensionMigrations>,
    ) -> anyhow::Result<ServerId>;

Each extension tracks applied migrations in its own ext_<name>_sqlx_migrations table; core keeps _sqlx_migrations. Existing callers use the back-compat migrate_core_only(pool) shim.

Reserves the ext_* table prefix:

  • Core never creates ext_* objects.
  • FKs from ext_* to upstream must be ON DELETE CASCADE or
    ON DELETE SET NULL; upstream never FKs into ext_*.
  • Prefix covers tables + their owned indexes/sequences only.

Recovery procedure (DO block scanning pg_class/pg_trigger/ pg_type/pg_proc) documented in the developer guide.

Tests cover the happy path, cross-source atomicity on failure, the no-ext_*-in-core invariant, and the FK direction/CASCADE rule.

Prerequisite: sqlx 0.8.6 → 0.9.0 (the Migrate trait now accepts an explicit table_name, enabling per-source trackers). Bump fallout limited to nullability annotations on two queries, one QueryBuilder lifetime drop, and AssertSqlSafe wraps in test-only format queries.

Summary by CodeRabbit

  • New Features

    • Extensions can register migrations to run atomically with core; added a core-only migration entry point and per-extension migration registry with checksum-patch support.
  • Chores

    • Rust toolchain requirement bumped to 1.94
    • sqlx dependency upgraded to 0.9.0
  • Documentation

    • New developer guide section on extension-owned tables, migration registration, and recovery
  • Tests

    • Test harness now boots with core-only migrations; added fixtures for extension migrations, atomic rollback, and checksum-patch scenarios
  • Bug Fixes

    • Stricter handling and error reporting for checksum-patch operations

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 22, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8e904071-7e4e-41b6-8dfc-daae167f080c

📥 Commits

Reviewing files that changed from the base of the PR and between c91a970 and 3ce2226.

⛔ Files ignored due to path filters (1)
  • .sqlx/query-0bb4f7510d1b9507d70aa9afc0eabac4d2afeb476e556d5feeb57c6abef740a6.json is excluded by !.sqlx/**
📒 Files selected for processing (2)
  • crates/lakekeeper/src/implementations/postgres/migrations/mod.rs
  • docs/docs/developer-guide.md
✅ Files skipped from review due to trivial changes (1)
  • docs/docs/developer-guide.md

Walkthrough

Upgrade sqlx to 0.9.0 and bump rust-version to 1.94; refactor Postgres migrations to support per-extension tracker tables with migrate_core_only(pool) and an extensions-aware migrate(pool, extensions); update callers, tests, fixtures, patch helper, and developer docs.

Changes

Extension-scoped migration tracking system

Layer / File(s) Summary
sqlx 0.9.0 compatibility and query typing
Cargo.toml, crates/lakekeeper/src/implementations/postgres/tabular/table/commit.rs, crates/lakekeeper/src/implementations/postgres/namespace.rs
Workspace rust-version bumped to 1.94; sqlx updated to 0.9.0; QueryBuilder lifetimes changed to QueryBuilder<Postgres>; explicit sqlx::query_as! type/nullability annotations added to namespace query projections.
Core vs. extension migration orchestration
crates/lakekeeper/src/implementations/postgres/migrations/mod.rs, crates/lakekeeper/src/implementations/postgres/migrations/patch_migration_hash.rs
Adds CORE_MIGRATIONS_TABLE, ExtensionMigrations struct and migrate_core_only(pool) API; replaces prior migrate(pool) with migrate(pool, extensions) that validates extension names, performs tracker-scoped pre-flight checks, merges core+extension timelines, applies migrations into per-source tracker tables under one outer transaction/advisory lock, and generalizes checksum patching to target a specified tracker table.
Calling code updated to new migration API
crates/lakekeeper-bin/src/main.rs, crates/lakekeeper/src/implementations/postgres/bootstrap.rs, crates/lakekeeper/src/tests/mod.rs
Main binary, bootstrap tests, and test harness now call migrate_core_only() for initialization instead of the old migrate() call.
Extension migration tests and low-privilege regression
crates/lakekeeper/src/implementations/postgres/migrations/mod.rs (tests)
Test module expanded with helpers and tests verifying extension migration atomicity, tracker table separation, SHA patch behavior, and rollback atomicity; low-privilege custom-schema test updated to pre-install objects, wrap dynamic SQL in AssertSqlSafe, call migrate_core_only(), and update cleanup.
Extension test fixtures for atomicity and rollback validation
crates/lakekeeper/tests/extension_migrations_fixture/*, crates/lakekeeper/tests/extension_migrations_fixture_invalid/*, crates/lakekeeper/tests/extension_migrations_fixture_patched/*
Adds ext_demo_state fixture, a valid/invalid extension migration pair to exercise rollback behavior, and a patched fixture to exercise SHA patch rewriting.
Developer guide: extension table namespace contract
docs/docs/developer-guide.md
New section documents ext_* ownership and FK/cascade rules, migration registration/execution model (data_hooks, sha_patches), and recovery SQL to remove extension state and trackers.

Sequence Diagram

sequenceDiagram
  participant Caller
  participant Migrate
  participant Database
  Caller->>Migrate: migrate(pool, extensions)
  Migrate->>Database: BEGIN + advisory lock
  Migrate->>Database: apply core using CORE_MIGRATIONS_TABLE
  loop per extension
    Migrate->>Database: apply extension using ext_<name>_sqlx_migrations
    Migrate->>Database: run data_hooks for applied versions
  end
  Migrate->>Database: COMMIT
  Caller->>Migrate: migrate_core_only(pool)
  Migrate->>Database: apply core only
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~65 minutes

Possibly related PRs

  • lakekeeper/lakekeeper#1714: Related Postgres migrations refactor that interacts with low-privilege regression test setup and migrate_core_only usage.

"🐰 I hopped through schemas, migrations in tow,
Core runs first, then extensions in row,
With sqlx polished to ninety-point-nine,
Trackers split clean — the DB sings in time,
Tests and docs updated as I go!"

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding support for atomic core and extension migrations with a new registration mechanism.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch christian/vak-407-extension-table-convention-postmigratehook

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
docs/docs/developer-guide.md (1)

185-239: ⚡ Quick win

Consider adding view cleanup to the recovery script.

Line 161 states that "views attached to upstream-owned objects are not permitted under the prefix," but the recovery SQL script (lines 191-236) does not explicitly handle views with the ext_* prefix. Although the convention forbids such views, a defensive sweep for them would align with the pattern established for triggers, types, and functions.

🔍 Proposed addition to handle views

Add the following block after the function sweep (after line 234):

    -- Views (defensive sweep)
    FOR r IN SELECT c.relname
             FROM pg_class c
             JOIN pg_namespace n ON n.oid = c.relnamespace
             WHERE n.nspname = current_schema()
               AND c.relkind = 'v'
               AND c.relname LIKE 'ext\_%' ESCAPE '\'
    LOOP
        EXECUTE format('DROP VIEW %I CASCADE', r.relname);
    END LOOP;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/docs/developer-guide.md` around lines 185 - 239, The recovery DO $$
block doesn't remove views with the ext_* prefix; add a defensive sweep after
the function sweep in the same DO block that queries pg_class for c.relkind =
'v' in the current_schema() with c.relname LIKE 'ext\_%' ESCAPE '\' and then
EXECUTE a DROP VIEW %I CASCADE for each matching r.relname so views named with
the ext_ prefix are also removed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/lakekeeper/src/implementations/postgres/migrations/mod.rs`:
- Around line 54-56: The tracker_table builder uses ExtensionMigrations::name
verbatim which can produce invalid SQL identifiers or collisions; update the
code to first validate the extension name (e.g., require it match an identifier
regex like starting with letter/underscore and containing only
letters/digits/underscores) and surface a clear error when the name is invalid,
and for nonconforming names produce a deterministic safe identifier (for example
normalize/replace invalid chars or fallback to a hashed suffix derived from the
original name) so tracker_table() returns a SQL-safe, collision-resistant
string; apply the same validation/normalization wherever
ExtensionMigrations::name is used (including the other location referenced) so
all tracker-table names are validated or deterministically sanitized.

In `@docs/docs/developer-guide.md`:
- Around line 171-180: The docs snippet doesn't explain the data_hooks field on
ExtensionMigrations; update the guidance near the shown snippet to say that
data_hooks is a HashMap<i64, Box<dyn MigrationHook>> on ExtensionMigrations used
to run a MigrationHook immediately after a specific extension migration version
is applied (inside the same transaction), and that using HashMap::new() is
appropriate when no hooks are needed; reference ExtensionMigrations, data_hooks,
MigrationHook and the migrate(&pool, extensions) call so readers can locate the
example.

---

Nitpick comments:
In `@docs/docs/developer-guide.md`:
- Around line 185-239: The recovery DO $$ block doesn't remove views with the
ext_* prefix; add a defensive sweep after the function sweep in the same DO
block that queries pg_class for c.relkind = 'v' in the current_schema() with
c.relname LIKE 'ext\_%' ESCAPE '\' and then EXECUTE a DROP VIEW %I CASCADE for
each matching r.relname so views named with the ext_ prefix are also removed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 32f50a72-4045-43d1-b3ea-9b7a88d106d1

📥 Commits

Reviewing files that changed from the base of the PR and between bf77a59 and 5a9e726.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (11)
  • Cargo.toml
  • crates/lakekeeper-bin/src/main.rs
  • crates/lakekeeper/src/implementations/postgres/bootstrap.rs
  • crates/lakekeeper/src/implementations/postgres/migrations/mod.rs
  • crates/lakekeeper/src/implementations/postgres/namespace.rs
  • crates/lakekeeper/src/implementations/postgres/tabular/table/commit.rs
  • crates/lakekeeper/src/tests/mod.rs
  • crates/lakekeeper/tests/extension_migrations_fixture/20260101000000_create_ext_demo.sql
  • crates/lakekeeper/tests/extension_migrations_fixture_invalid/20260101000000_create_ok.sql
  • crates/lakekeeper/tests/extension_migrations_fixture_invalid/20260101000001_fail.sql
  • docs/docs/developer-guide.md

Comment thread crates/lakekeeper/src/implementations/postgres/migrations/mod.rs
Comment thread docs/docs/developer-guide.md

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Cargo.toml`:
- Line 16: The rust-version field was bumped to "1.94" without justification;
inspect the change of rust-version and either revert it to the true minimum
(e.g., "1.86" to match sqlx v0.9.0 or the previous "1.92") or add a short
justification in the PR describing which dependency or language feature requires
1.94; update the rust-version line (rust-version = "1.94") accordingly and, if
keeping 1.94, list the exact symbol(s) or crate(s) (e.g., any upgraded crate or
use of new std/library features) that mandate the bump so reviewers can verify
the need.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 49b0b512-eb43-460f-8fa4-02299065a55e

📥 Commits

Reviewing files that changed from the base of the PR and between 5a9e726 and 38e68d8.

📒 Files selected for processing (1)
  • Cargo.toml

Comment thread Cargo.toml
@c-thiel

c-thiel commented May 23, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented May 23, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Full review triggered.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/lakekeeper/src/implementations/postgres/migrations/mod.rs (1)

121-128: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Update the rustdoc to match the actual ordering contract.

The implementation does not run extensions “after all core migrations”; it merges all sources by version and only uses source index as a tie-breaker. Leaving the current docs in place gives extension authors the wrong sequencing guarantee.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/lakekeeper/src/implementations/postgres/migrations/mod.rs` around
lines 121 - 128, The rustdoc block in migrations/mod.rs incorrectly states
"Extensions are applied in registration order, after all core migrations";
update the doc to reflect the actual ordering: explain that core and extension
migrations are merged by migration version and that the implementation
interleaves sources by version, using source index/registration order only as a
tie-breaker for equal versions (so extensions are not guaranteed to run strictly
after all core migrations). Mention the per-extension tracking table name
pattern (`ext_<name>_sqlx_migrations`) and keep the transaction guarantee text
intact.
♻️ Duplicate comments (1)
crates/lakekeeper/src/implementations/postgres/migrations/mod.rs (1)

136-140: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reject duplicate extension names as well.

This only validates the identifier shape. Two registered extensions can still reuse the same name, which makes them share one ext_<name>_sqlx_migrations table and mixes pre-flight/apply state across distinct sources.

Suggested fix
 pub async fn migrate(
     pool: &sqlx::PgPool,
     mut extensions: Vec<ExtensionMigrations>,
 ) -> anyhow::Result<ServerId> {
+    let mut seen = HashSet::with_capacity(extensions.len());
     // Fail fast on misconfigured extension names — before any DB work, before
     // the advisory lock is acquired. Catches typos in caller crate source.
     for ext in &extensions {
         ext.validate_name()?;
+        if !seen.insert(ext.name) {
+            return Err(anyhow!("duplicate extension migration name {:?}", ext.name));
+        }
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/lakekeeper/src/implementations/postgres/migrations/mod.rs` around
lines 136 - 140, The loop currently calls ext.validate_name() for each item but
doesn’t detect duplicate extension names, which can cause different extensions
to share the same ext_<name>_sqlx_migrations table; after validating shapes,
iterate the same extensions collection (variable extensions) to collect each
ext.name (or ext.get_name()) into a set/map and fail fast with an error if any
name is seen twice (return a clear Err mentioning the duplicate name), so ensure
you run this duplicate-name check immediately after validate_name() and before
acquiring the advisory lock or proceeding with migrations.
🧹 Nitpick comments (2)
docs/docs/developer-guide.md (1)

160-160: ⚡ Quick win

Use normative wording for the FK rule (must, not should).

This line documents a hard contract, so should be is too soft and can be interpreted as optional. Please change it to must be ON DELETE CASCADE or ON DELETE SET NULL.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/docs/developer-guide.md` at line 160, Update the normative FK rule text
under the "CASCADE required" entry so it uses mandatory language: replace "Every
FK from an `ext_*` table to an upstream table should be `ON DELETE CASCADE` or
`ON DELETE SET NULL`." with wording that says it must be `ON DELETE CASCADE` or
`ON DELETE SET NULL` (e.g., "Every FK from an `ext_*` table to an upstream table
must be ON DELETE CASCADE or ON DELETE SET NULL."), keeping the note about
enforcing this in the extension crate's CI intact; locate the line labeled
"CASCADE required" in the developer-guide.md to make the change.
crates/lakekeeper/src/implementations/postgres/migrations/mod.rs (1)

628-637: ⚡ Quick win

Strengthen the “core tracker untouched” assertion.

Counting NULL core checksums will miss accidental rewrites to a different non-NULL checksum. Snapshot the core tracker’s (version, checksum) rows before the patched rerun and assert they are byte-for-byte identical afterward.

Suggested test shape
let core_before: Vec<(i64, Vec<u8>)> = sqlx::query_as(
    "SELECT version, checksum FROM _sqlx_migrations ORDER BY version",
)
.fetch_all(&pool)
.await
.unwrap();

// ... run Phase 3 ...

let core_after: Vec<(i64, Vec<u8>)> = sqlx::query_as(
    "SELECT version, checksum FROM _sqlx_migrations ORDER BY version",
)
.fetch_all(&pool)
.await
.unwrap();

assert_eq!(core_after, core_before, "extension sha patch must not touch core tracker");
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/lakekeeper/src/implementations/postgres/migrations/mod.rs` around
lines 628 - 637, Replace the weak NULL-count check of the core tracker with a
byte-for-byte snapshot comparison: before running the Phase 3/patch logic, read
and store the ordered rows (version, checksum) from the table `_sqlx_migrations`
(use sqlx::query_as to fetch Vec<(i64, Vec<u8>)> into a variable like
core_before), then after the patch re-read the same ordered rows into core_after
and assert_eq!(core_after, core_before, "extension sha patch must not touch core
tracker"); this ensures any non-NULL checksum changes are detected.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@crates/lakekeeper/src/implementations/postgres/migrations/patch_migration_hash.rs`:
- Around line 39-50: The current logic in patch_migration_hash.rs treats an
UPDATE that affects 0 rows as a success, but since the migration row is known to
exist this indicates the checksum was not patched; change the branch handling
q.rows_affected() so that: if q.rows_affected() > 1 keep the existing error, if
q.rows_affected() == 1 keep the info log for success, and if q.rows_affected()
== 0 return an Err (with a clear message including table_name and version)
instead of logging "No rows were updated", ensuring the caller does not consume
the SHA patch as successful; update the tracing::info/tracing::error and the
returned anyhow::anyhow accordingly around the q.rows_affected() checks.

In `@docs/docs/developer-guide.md`:
- Line 175: Update the example extension name to match runtime validation rules:
inspect the ExtensionMigrations struct and its name validation logic (search for
ExtensionMigrations, validate, or ext_<name>_sqlx_migrations in the migrations
code) and choose an example that passes those rules (e.g., snake_case with
allowed charset); then update the docs entry ("my-lakekeeper-extension") to the
validated form and, if helpful, add a short note indicating the charset/format
required by the ExtensionMigrations validation logic.

---

Outside diff comments:
In `@crates/lakekeeper/src/implementations/postgres/migrations/mod.rs`:
- Around line 121-128: The rustdoc block in migrations/mod.rs incorrectly states
"Extensions are applied in registration order, after all core migrations";
update the doc to reflect the actual ordering: explain that core and extension
migrations are merged by migration version and that the implementation
interleaves sources by version, using source index/registration order only as a
tie-breaker for equal versions (so extensions are not guaranteed to run strictly
after all core migrations). Mention the per-extension tracking table name
pattern (`ext_<name>_sqlx_migrations`) and keep the transaction guarantee text
intact.

---

Duplicate comments:
In `@crates/lakekeeper/src/implementations/postgres/migrations/mod.rs`:
- Around line 136-140: The loop currently calls ext.validate_name() for each
item but doesn’t detect duplicate extension names, which can cause different
extensions to share the same ext_<name>_sqlx_migrations table; after validating
shapes, iterate the same extensions collection (variable extensions) to collect
each ext.name (or ext.get_name()) into a set/map and fail fast with an error if
any name is seen twice (return a clear Err mentioning the duplicate name), so
ensure you run this duplicate-name check immediately after validate_name() and
before acquiring the advisory lock or proceeding with migrations.

---

Nitpick comments:
In `@crates/lakekeeper/src/implementations/postgres/migrations/mod.rs`:
- Around line 628-637: Replace the weak NULL-count check of the core tracker
with a byte-for-byte snapshot comparison: before running the Phase 3/patch
logic, read and store the ordered rows (version, checksum) from the table
`_sqlx_migrations` (use sqlx::query_as to fetch Vec<(i64, Vec<u8>)> into a
variable like core_before), then after the patch re-read the same ordered rows
into core_after and assert_eq!(core_after, core_before, "extension sha patch
must not touch core tracker"); this ensures any non-NULL checksum changes are
detected.

In `@docs/docs/developer-guide.md`:
- Line 160: Update the normative FK rule text under the "CASCADE required" entry
so it uses mandatory language: replace "Every FK from an `ext_*` table to an
upstream table should be `ON DELETE CASCADE` or `ON DELETE SET NULL`." with
wording that says it must be `ON DELETE CASCADE` or `ON DELETE SET NULL` (e.g.,
"Every FK from an `ext_*` table to an upstream table must be ON DELETE CASCADE
or ON DELETE SET NULL."), keeping the note about enforcing this in the extension
crate's CI intact; locate the line labeled "CASCADE required" in the
developer-guide.md to make the change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a3f85d05-78ac-418c-b7ef-f00da43f8cd7

📥 Commits

Reviewing files that changed from the base of the PR and between 41cba54 and c0b969f.

📒 Files selected for processing (4)
  • crates/lakekeeper/src/implementations/postgres/migrations/mod.rs
  • crates/lakekeeper/src/implementations/postgres/migrations/patch_migration_hash.rs
  • crates/lakekeeper/tests/extension_migrations_fixture_patched/20260101000000_create_ext_demo.sql
  • docs/docs/developer-guide.md
✅ Files skipped from review due to trivial changes (1)
  • crates/lakekeeper/tests/extension_migrations_fixture_patched/20260101000000_create_ext_demo.sql

Comment thread crates/lakekeeper/src/implementations/postgres/migrations/patch_migration_hash.rs Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
crates/lakekeeper/src/implementations/postgres/migrations/mod.rs (1)

136-140: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reject duplicate extension names before pre-flight.

Two entries with the same ExtensionMigrations::name target the same tracker table and can collide during merged application. Fail fast on duplicates before any DB work.

Suggested fix
 pub async fn migrate(
     pool: &sqlx::PgPool,
     mut extensions: Vec<ExtensionMigrations>,
 ) -> anyhow::Result<ServerId> {
@@
-    for ext in &extensions {
-        ext.validate_name()?;
-    }
+    let mut seen = HashSet::with_capacity(extensions.len());
+    for ext in &extensions {
+        ext.validate_name()?;
+        anyhow::ensure!(
+            seen.insert(ext.name),
+            "duplicate extension migration name {:?}",
+            ext.name
+        );
+    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/lakekeeper/src/implementations/postgres/migrations/mod.rs` around
lines 136 - 140, The code currently validates each extension name via
ext.validate_name() but does not reject duplicate ExtensionMigrations::name
entries; add a pre-flight duplicate check on the extensions collection (before
calling validate_name() and before any DB work/advisory lock) that collects
ExtensionMigrations::name values and returns an error if any name appears more
than once so duplicate tracker-table targets are rejected early; locate the loop
over extensions and the ExtensionMigrations type/name accessor to implement a
fast set/count check and produce a clear error identifying the duplicated
extension name(s).
crates/lakekeeper/src/implementations/postgres/migrations/patch_migration_hash.rs (1)

47-50: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Treat zero-row checksum patches as failure.

If no row is updated, the checksum correction did not happen and migration flow should fail instead of logging success-like progress.

Suggested fix
-    if q.rows_affected() == 1 {
-        tracing::info!("Patched checksum in {table_name} for version {version}");
-    } else {
-        tracing::info!("No rows were updated in {table_name} for version {version}");
-    }
+    match q.rows_affected() {
+        1 => tracing::info!("Patched checksum in {table_name} for version {version}"),
+        0 => {
+            let msg =
+                format!("Checksum patch updated no rows in {table_name} for version {version}");
+            tracing::error!("{msg}");
+            return Err(anyhow::anyhow!(msg));
+        }
+        _ => unreachable!("handled by the >1 guard above"),
+    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@crates/lakekeeper/src/implementations/postgres/migrations/patch_migration_hash.rs`
around lines 47 - 50, The current logic treats zero-row updates as informational
success; change it so a zero rows_affected() is treated as a failure: in the
else branch (the check using q.rows_affected()) replace the tracing::info(...)
with returning an Err that describes the failed checksum patch and includes
table_name and version (e.g., construct an anyhow/error with context and return
it from the function patch_migration_hash or the containing function), leaving
the success branch to continue returning Ok; ensure the error propagates up
instead of silently logging.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/docs/developer-guide.md`:
- Line 175: The example extension name "my-lakekeeper-extension" is invalid
because ExtensionMigrations::name must match the regex [a-z_][a-z0-9_]{0,40};
update the example in docs/docs/developer-guide.md to a compliant identifier
such as "my_lakekeeper_extension" (replace hyphens with underscores) and
optionally add a short note mentioning the allowed character set and that
migrate() uses validate_name to enforce it.

---

Duplicate comments:
In `@crates/lakekeeper/src/implementations/postgres/migrations/mod.rs`:
- Around line 136-140: The code currently validates each extension name via
ext.validate_name() but does not reject duplicate ExtensionMigrations::name
entries; add a pre-flight duplicate check on the extensions collection (before
calling validate_name() and before any DB work/advisory lock) that collects
ExtensionMigrations::name values and returns an error if any name appears more
than once so duplicate tracker-table targets are rejected early; locate the loop
over extensions and the ExtensionMigrations type/name accessor to implement a
fast set/count check and produce a clear error identifying the duplicated
extension name(s).

In
`@crates/lakekeeper/src/implementations/postgres/migrations/patch_migration_hash.rs`:
- Around line 47-50: The current logic treats zero-row updates as informational
success; change it so a zero rows_affected() is treated as a failure: in the
else branch (the check using q.rows_affected()) replace the tracing::info(...)
with returning an Err that describes the failed checksum patch and includes
table_name and version (e.g., construct an anyhow/error with context and return
it from the function patch_migration_hash or the containing function), leaving
the success branch to continue returning Ok; ensure the error propagates up
instead of silently logging.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: bf34acc4-5de4-482a-9cb1-17eee06ac192

📥 Commits

Reviewing files that changed from the base of the PR and between bf77a59 and c0b969f.

⛔ Files ignored due to path filters (88)
  • .sqlx/query-0086856941a82aa66a3006d5686fe6c34bed4571ffed5303998e655eae253581.json is excluded by !.sqlx/**
  • .sqlx/query-04c6fd4a25005469a8342fe751db0302f8ff81b7046efaa73c0d2433e1867780.json is excluded by !.sqlx/**
  • .sqlx/query-06344d50decb31166e6649525cce0cc8c263c7ad164d86148bf0b99c12b08007.json is excluded by !.sqlx/**
  • .sqlx/query-08a9a63f9d7887c221ae406839a1cd9e76bae529948e76288748e4232f4b9ef5.json is excluded by !.sqlx/**
  • .sqlx/query-0902b3c098641f60bcaf012a39082c879fd0b4ad7b2928c7c1ea33798216c053.json is excluded by !.sqlx/**
  • .sqlx/query-097caa255e91c68d4590df9cd4370e4dcd902ad7d6f87a6de20cab523455bc19.json is excluded by !.sqlx/**
  • .sqlx/query-11ef42e7d1210142e5a832c6217755e6f772d84b54bfa08b77b6c2f94ce5fd0e.json is excluded by !.sqlx/**
  • .sqlx/query-12dba7d7e089c0c495fcd9e8d623fde4f3d3b5a2061bd40aea94c78df537276c.json is excluded by !.sqlx/**
  • .sqlx/query-17e54b156bb6d9eee483e8ad4a2150526ee047a05358fa0adddb42c2dad0a746.json is excluded by !.sqlx/**
  • .sqlx/query-18fb2ceff5fe5062c535dc1f918441580ea4a2aaf1fea9859d7cf4a5c1d079e1.json is excluded by !.sqlx/**
  • .sqlx/query-1c1a32de33e3f54464a1107f7d4d4b8cbd99dd69aae1c740561c3cbbea2eaa19.json is excluded by !.sqlx/**
  • .sqlx/query-1e36c7781b1223f732691c4d2e705521d3d078d90adedc3d183068526cf8c025.json is excluded by !.sqlx/**
  • .sqlx/query-22ff599e546c056efbd47d933d78e92b224afd1d4b43e81b37c91480c2389c60.json is excluded by !.sqlx/**
  • .sqlx/query-28268ee66edc1f897450ec98ca58a34f70cabbf2aba1eb604c5005c2f6f1acf7.json is excluded by !.sqlx/**
  • .sqlx/query-298c38a2042a2b7d19ec8b3ea880e71dfdc087eb0b298637578c141e738cf0d2.json is excluded by !.sqlx/**
  • .sqlx/query-2b838db363de7027ecb22b4f4d7d92ab06f417dcf82d587f08906d9fafae7cb8.json is excluded by !.sqlx/**
  • .sqlx/query-2c33b05a0995b5390079e3039553a194b1cc7535e0a2f19653f00dd78c387e79.json is excluded by !.sqlx/**
  • .sqlx/query-302f4eddf6068e4b36a6e2cf88724fc05463006008722a654a54ca59f6fafeab.json is excluded by !.sqlx/**
  • .sqlx/query-32335b00416cdef4f4979f83eb7f6f9e67771f815b02eca66bf0805340eb7a27.json is excluded by !.sqlx/**
  • .sqlx/query-3ccea3bc52e21ddb7d395ab7c7a6171bcb64ad2d33a6159e72f6a147dea3ae0f.json is excluded by !.sqlx/**
  • .sqlx/query-3cd6aecd2f5bf47061065497d3cb7443c070533b15a4ac10649875443efbe6aa.json is excluded by !.sqlx/**
  • .sqlx/query-3ea1e4d0a2a32c7e5131b3b518f0291e5fe844821e74e3acd4100f312158d400.json is excluded by !.sqlx/**
  • .sqlx/query-3ec4e3932d25c44bf92556cf1323ff761c463285832bf78886730c7d1638623e.json is excluded by !.sqlx/**
  • .sqlx/query-3f118743d59e0e035f57c9106816533c092fead83b8420cdab3149b040664910.json is excluded by !.sqlx/**
  • .sqlx/query-415502c8533199d7246c64eeac7f2677f10c62f0fae1c48de99bf5471c1f2fb8.json is excluded by !.sqlx/**
  • .sqlx/query-470b25047d1c0936dad4b560df9a0c3b40d501ec8ecb8f3823ea05dae4c3b68a.json is excluded by !.sqlx/**
  • .sqlx/query-48475e35dc4e5363310fbc65d192a8e2f2b4f68e5239ebddbdfb74218b388266.json is excluded by !.sqlx/**
  • .sqlx/query-4d30952e19a84710348564dcd0aa04fb7dbacfc6ffa3f163c8375f4cb990e7f0.json is excluded by !.sqlx/**
  • .sqlx/query-4ecd137d2abeaf58285165aeec7a0374b34baf029d7c572b5247dfb29463a949.json is excluded by !.sqlx/**
  • .sqlx/query-5691c6fcc6ff1ea68bcd4601370e857df265a645246572031bdf7b4265c0f575.json is excluded by !.sqlx/**
  • .sqlx/query-56d8028355ea7526d7ea058fd5df0421f6917d9c9d03e311f6e07bfe29b84710.json is excluded by !.sqlx/**
  • .sqlx/query-5a450894a677d31c8a80122908dbf49ad77d804682936d96a9142028ce5372f7.json is excluded by !.sqlx/**
  • .sqlx/query-5bd88e2962ade07ae621b69644240f5c5e48aefc4990b3804bf23c8ee6c9df53.json is excluded by !.sqlx/**
  • .sqlx/query-5bdeb64b6e903d29cb916d8503d7fab61f49359c1e0c79cfe1b380dfbe9e16b2.json is excluded by !.sqlx/**
  • .sqlx/query-5bfefb745f98ac0bbbb7abaa6fb8ce979007e57adbad3992710106d1b6c32705.json is excluded by !.sqlx/**
  • .sqlx/query-60e3a10848aa3053c13dfad61a4471bf5b34a9403713822863115071a9e895fb.json is excluded by !.sqlx/**
  • .sqlx/query-62b9aeb0ed103e7f63a42f246d713b7727307c032df2c983e7ab8a72d569ddb1.json is excluded by !.sqlx/**
  • .sqlx/query-63abbd953774fcc023cb26064d11a96dc35789e1b9ef84825208a4a2b02c58f8.json is excluded by !.sqlx/**
  • .sqlx/query-6404eaa43100990cc71c82fad60e2c5303aa7bfa218324e34a5fb1d8f902d0a2.json is excluded by !.sqlx/**
  • .sqlx/query-6b784d53e21df29bcfba893706b19cd31fcac2def0a8059dc1f509c16820a1b6.json is excluded by !.sqlx/**
  • .sqlx/query-6e9d1aa75b2aac3abfceb93e949f30c3bfcd6d00f2f11122e03a7041e22c73dd.json is excluded by !.sqlx/**
  • .sqlx/query-7006c3203e878fdde07018127ebe9067b77b99aae37fe6797a1a38988c24cc56.json is excluded by !.sqlx/**
  • .sqlx/query-740f7b4789465c898d2461093a927462835c08bd027d1e8fbb1b9fd64d702583.json is excluded by !.sqlx/**
  • .sqlx/query-763b066e0be1435303b875e81fe228e2aa7e421e9c65624695c8078f83d6b011.json is excluded by !.sqlx/**
  • .sqlx/query-793788700b022096e85b161d3207b374cf941dc832f6e055b938e2fe7d02c819.json is excluded by !.sqlx/**
  • .sqlx/query-7b380cbadf9c4987f27b4ff8d34f7341962a5a472ed80fb016ef826ca7f8a9d0.json is excluded by !.sqlx/**
  • .sqlx/query-7ccf866841e291289987c72fd8cebdeec715e90d37db01f518b16893e3b74c58.json is excluded by !.sqlx/**
  • .sqlx/query-80b88583b4395d85b351b1ffa80156b4514856e8bde1475e9fcab906cd45b670.json is excluded by !.sqlx/**
  • .sqlx/query-81a5b11c053d23d4717e45bdc70c9d4a19716a40c994cfe952d78e30bccf53bb.json is excluded by !.sqlx/**
  • .sqlx/query-859adda46e98e6b145d11a1d8d68cefdccb601de8b3d08c4b16ecd9591d599a0.json is excluded by !.sqlx/**
  • .sqlx/query-866c21d361cb3edcb12f126e822c6b90ca6cdbc4b59429d04d2309d2367b9b3a.json is excluded by !.sqlx/**
  • .sqlx/query-897cfb6691b671d2dba0472279a7b20274a5d3327374d55b92479387f8def564.json is excluded by !.sqlx/**
  • .sqlx/query-92b403b07793061ed856e5db770137b5e076514670a5c45491feb00b8b82d3f4.json is excluded by !.sqlx/**
  • .sqlx/query-99b67458585575905661f7a95662d17e4c50a1efbf96b0fb22b8b774045a934a.json is excluded by !.sqlx/**
  • .sqlx/query-99e4e108d554390e9b6d96270cfbc29710601746acb3b6e4e085a9019dfa85aa.json is excluded by !.sqlx/**
  • .sqlx/query-99e60de0f6aed1a2e16aa330765626307b20d78da6110e9b9721dca9e93d04fa.json is excluded by !.sqlx/**
  • .sqlx/query-9a1d9b777d4d91c44114e91bbe76e8759d9b0133f4bc278266d3ed7521324a61.json is excluded by !.sqlx/**
  • .sqlx/query-9c3a9664e2775d6cdbf1bff4941d5e5f09819823b70cbec2f077e32cb83acfda.json is excluded by !.sqlx/**
  • .sqlx/query-9f25b4c1ec0e8b957922d23fec3d9eb01234ccd926961554ae25c238d1d990fa.json is excluded by !.sqlx/**
  • .sqlx/query-a02df0ff30895ec09168d38c713f34b0966fdd3290a8fee6ac26323cd311808a.json is excluded by !.sqlx/**
  • .sqlx/query-a31269da65f9ecbb7fbf9f75686e887d4f61e700a747d52856326fa620494347.json is excluded by !.sqlx/**
  • .sqlx/query-a4c9aa19924c03216a18bcad5bc64ad14614ecf903eefb7c2330d7eb307a8c8b.json is excluded by !.sqlx/**
  • .sqlx/query-a90b3a153a66147d554945f8ae2cb0b1e73400e846d3d0ca617604fc30fea185.json is excluded by !.sqlx/**
  • .sqlx/query-abdbc91a03d5a5564ba259916c20563103fe2adb2daa6b50eafceec4f8afab99.json is excluded by !.sqlx/**
  • .sqlx/query-b03b867cae1000c1aa7dd2da639731991cf91ec0c9c4c01bbdff4ece5ed5e025.json is excluded by !.sqlx/**
  • .sqlx/query-b0a2bdb25f4d0e34016f2e596d6a3a59e81cdea114afcc11d60120b99802f673.json is excluded by !.sqlx/**
  • .sqlx/query-b104f2da1e2ca05bdc4666363587c34601c97dc68635af8d901791be0aa356b2.json is excluded by !.sqlx/**
  • .sqlx/query-b18be0a4a957c1b6c14cffc5ecdbd949c1f6dd29ff8d8e5a76c7442f4afbef2f.json is excluded by !.sqlx/**
  • .sqlx/query-c0d53c1c4b9e30e933ada122f4f3fad8f7d01e5ddeb468ab9393f85845bf4df7.json is excluded by !.sqlx/**
  • .sqlx/query-c131c632f9bfff038c5b7040c4f7de497151fde0f06dd2e701465a1b8a092361.json is excluded by !.sqlx/**
  • .sqlx/query-cd40a8b8dd3e13b62aa248b2aaf00756c9e56e86a48538229b523e6a8b372b05.json is excluded by !.sqlx/**
  • .sqlx/query-cddd1db59ca6ad6e3b662a23e5d95f87fd443302c8a520a51c74ecd6c36c8b2b.json is excluded by !.sqlx/**
  • .sqlx/query-d4c2147c4c61845ec9f0f35727eca5b08987de0b06611eabb088beb042f77dbb.json is excluded by !.sqlx/**
  • .sqlx/query-d55ad8ebe461380e54acc1c5ee93f02fa6006d29293a77bf201841eceb7522cd.json is excluded by !.sqlx/**
  • .sqlx/query-d7dd04f6a1cd6cfc7d6105516f8dc4230ebc3d20fb42bc4ef718745eb7d4059e.json is excluded by !.sqlx/**
  • .sqlx/query-d7de2e54a6361b87925688339671f84c7bcab3acaa11b5fa254d0e5290284c87.json is excluded by !.sqlx/**
  • .sqlx/query-dd25d34dfbcd5a60c02d02b396040c0728f48297ee938a39c388260ac32ab58e.json is excluded by !.sqlx/**
  • .sqlx/query-ddcd04a1474282004db831e24cac62ef3b642a81fafce2dd574cec9bd3463d57.json is excluded by !.sqlx/**
  • .sqlx/query-de8db3913d1926ff41275b2611d6be2b0a67e204d9c37d17ebd3b0bf8bd29cb1.json is excluded by !.sqlx/**
  • .sqlx/query-e037d81f4b4079f028ff434a6e3408491813c3428c813d2974606c95cd8a8085.json is excluded by !.sqlx/**
  • .sqlx/query-e34c836cd691b4f3888052a016999ee88017cdbf7c8dd1b174401f6f81b5e60a.json is excluded by !.sqlx/**
  • .sqlx/query-e811cd8f04a94ea594b81896b0ac22b6f63bef0094d17b14e28d69ebaf26259a.json is excluded by !.sqlx/**
  • .sqlx/query-f01580cb4b0a2f3dbe051041b5c931e9e3a11d3a9187c75eef5eeffa37547f20.json is excluded by !.sqlx/**
  • .sqlx/query-f0f4d3b6d69711b2bb9c94ea2ed752dfb1b220cf4ddb02f02c6a3b6ebebdf5d6.json is excluded by !.sqlx/**
  • .sqlx/query-f546866de3d80ab873776e8c49a88e0f0918804478edc0b793b62980bc69f4a1.json is excluded by !.sqlx/**
  • .sqlx/query-f6130cf73a09eaff96b23ad91ba846b641fce5e2dbb7f64fedfdfed487779672.json is excluded by !.sqlx/**
  • .sqlx/query-fbc4ce3f128847954fff3b367a196bf3c5948c5036ccb8ef612e1c2d9d035f28.json is excluded by !.sqlx/**
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (13)
  • Cargo.toml
  • crates/lakekeeper-bin/src/main.rs
  • crates/lakekeeper/src/implementations/postgres/bootstrap.rs
  • crates/lakekeeper/src/implementations/postgres/migrations/mod.rs
  • crates/lakekeeper/src/implementations/postgres/migrations/patch_migration_hash.rs
  • crates/lakekeeper/src/implementations/postgres/namespace.rs
  • crates/lakekeeper/src/implementations/postgres/tabular/table/commit.rs
  • crates/lakekeeper/src/tests/mod.rs
  • crates/lakekeeper/tests/extension_migrations_fixture/20260101000000_create_ext_demo.sql
  • crates/lakekeeper/tests/extension_migrations_fixture_invalid/20260101000000_create_ok.sql
  • crates/lakekeeper/tests/extension_migrations_fixture_invalid/20260101000001_fail.sql
  • crates/lakekeeper/tests/extension_migrations_fixture_patched/20260101000000_create_ext_demo.sql
  • docs/docs/developer-guide.md

Comment thread docs/docs/developer-guide.md Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/lakekeeper/src/implementations/postgres/migrations/mod.rs (1)

149-151: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Missing duplicate extension name check.

The loop validates each extension's name format individually but doesn't detect duplicates. If two ExtensionMigrations with identical names are passed (e.g., both "demo"), they would both target the same tracker table ext_demo_sqlx_migrations, causing version conflicts or migrations incorrectly marked as already-applied.

Proposed fix
     for ext in &extensions {
         ext.validate_name()?;
     }
+    let mut seen_names = std::collections::HashSet::new();
+    for ext in &extensions {
+        if !seen_names.insert(ext.name) {
+            return Err(anyhow!(
+                "duplicate extension migration name {:?}",
+                ext.name
+            ));
+        }
+    }

Or combine into a single loop:

+    let mut seen_names = std::collections::HashSet::new();
     for ext in &extensions {
         ext.validate_name()?;
+        if !seen_names.insert(ext.name) {
+            return Err(anyhow!(
+                "duplicate extension migration name {:?}",
+                ext.name
+            ));
+        }
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/lakekeeper/src/implementations/postgres/migrations/mod.rs` around
lines 149 - 151, Loop currently only calls ext.validate_name() for each
ExtensionMigrations and doesn't detect duplicate extension names; update the
loop over extensions to both validate and check for duplicates by tracking seen
names (e.g., use a HashSet<String>), and return an error if insert into the set
fails (duplicate found). Reference the extensions iterator and
ext.validate_name() and ensure the error message mentions the conflicting
extension name (which would map to the tracker table ext_<name>_sqlx_migrations)
so duplicates are rejected early.
🧹 Nitpick comments (1)
docs/docs/developer-guide.md (1)

238-244: ⚡ Quick win

Consider using regprocedure for robust function drops.

The current DROP FUNCTION %I CASCADE statement uses only r.proname, which will fail if a non-conforming extension created overloaded functions (same name, different signatures). Although the contract forbids extensions from creating functions under ext_* and this is defensive cleanup code, using the function OID with a regprocedure cast would make the recovery procedure more robust.

♻️ Suggested improvement
-    FOR r IN SELECT p.proname FROM pg_proc p
+    FOR r IN SELECT p.oid::regprocedure AS proc_sig FROM pg_proc p
              JOIN pg_namespace n ON n.oid = p.pronamespace
              WHERE n.nspname = current_schema()
                AND p.proname LIKE 'ext\_%' ESCAPE '\'
     LOOP
-        EXECUTE format('DROP FUNCTION %I CASCADE', r.proname);
+        EXECUTE format('DROP FUNCTION %s CASCADE', r.proc_sig);
     END LOOP;

The regprocedure cast produces a fully-qualified signature that unambiguously identifies the function even when overloaded.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/docs/developer-guide.md` around lines 238 - 244, The DROP loop currently
uses r.proname and format('DROP FUNCTION %I CASCADE', r.proname) which will fail
for overloaded functions; change the DROP target to use the function's OID cast
to regprocedure (e.g., r.oid::regprocedure) so the DROP references the
fully-qualified signature, locate the loop that iterates over pg_proc
(r.proname) and replace the format/identifier approach with a command that drops
the function by its regprocedure representation to unambiguously identify
overloaded functions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@crates/lakekeeper/src/implementations/postgres/migrations/mod.rs`:
- Around line 149-151: Loop currently only calls ext.validate_name() for each
ExtensionMigrations and doesn't detect duplicate extension names; update the
loop over extensions to both validate and check for duplicates by tracking seen
names (e.g., use a HashSet<String>), and return an error if insert into the set
fails (duplicate found). Reference the extensions iterator and
ext.validate_name() and ensure the error message mentions the conflicting
extension name (which would map to the tracker table ext_<name>_sqlx_migrations)
so duplicates are rejected early.

---

Nitpick comments:
In `@docs/docs/developer-guide.md`:
- Around line 238-244: The DROP loop currently uses r.proname and format('DROP
FUNCTION %I CASCADE', r.proname) which will fail for overloaded functions;
change the DROP target to use the function's OID cast to regprocedure (e.g.,
r.oid::regprocedure) so the DROP references the fully-qualified signature,
locate the loop that iterates over pg_proc (r.proname) and replace the
format/identifier approach with a command that drops the function by its
regprocedure representation to unambiguously identify overloaded functions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1f44cc07-7ec0-4668-b0d0-915a902c33a8

📥 Commits

Reviewing files that changed from the base of the PR and between c0b969f and c6b6176.

📒 Files selected for processing (3)
  • crates/lakekeeper/src/implementations/postgres/migrations/mod.rs
  • crates/lakekeeper/src/implementations/postgres/migrations/patch_migration_hash.rs
  • docs/docs/developer-guide.md

coderabbitai[bot]
coderabbitai Bot previously approved these changes May 23, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/lakekeeper/src/implementations/postgres/migrations/mod.rs`:
- Around line 60-63: The doc comment currently advertises a 41-character pattern
but validate_name() actually rejects 41-character identifiers; update the
comment to document the real enforced limit (max 40 characters total) and the
exact character rules instead of the wider regex—refer to validate_name() and
migrate() so readers know this is the runtime-enforced constraint (e.g., "Must
be 1–40 characters: first char [a-z_], remaining chars [a-z0-9_]").
- Around line 8-11: The doc comment incorrectly implies consumers can use
sqlx::migrate! via this crate; update the comment around the re-exported
SqlxMigrator to clearly state that only the Migrator type is re-exported and
that the sqlx::migrate! macro must still be added as a direct dependency (or
explicitly re-exported) in downstream crates; reference the symbol SqlxMigrator
and the macro sqlx::migrate! in the updated wording so readers know the
limitation and the correct remedy.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: de703fd6-5654-4877-a0ea-15acd8ec2fb2

📥 Commits

Reviewing files that changed from the base of the PR and between c0b969f and c5335bd.

📒 Files selected for processing (3)
  • crates/lakekeeper/src/implementations/postgres/migrations/mod.rs
  • crates/lakekeeper/src/implementations/postgres/migrations/patch_migration_hash.rs
  • docs/docs/developer-guide.md
✅ Files skipped from review due to trivial changes (1)
  • docs/docs/developer-guide.md

Comment thread crates/lakekeeper/src/implementations/postgres/migrations/mod.rs Outdated
Comment thread crates/lakekeeper/src/implementations/postgres/migrations/mod.rs Outdated
@c-thiel

c-thiel commented May 23, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 23, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@c-thiel

c-thiel commented May 23, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai resolve

coderabbitai[bot]
coderabbitai Bot previously approved these changes May 23, 2026
@coderabbitai

coderabbitai Bot commented May 23, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Comments resolved and changes approved.

@github-actions

Copy link
Copy Markdown
Contributor

✅ PR Title Formatted Correctly

The title of this PR match the correct format. Thank you!

@c-thiel c-thiel enabled auto-merge (squash) May 23, 2026 08:56
@c-thiel c-thiel merged commit aa734bf into main May 23, 2026
70 checks passed
@c-thiel c-thiel deleted the christian/vak-407-extension-table-convention-postmigratehook branch May 23, 2026 09:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant