Skip to content

Table creation statement always runs #103

@disconsented

Description

@disconsented

Hi,

I've been digging into why surrealdb-migrations was adding about a minute to my startup process.

2025-06-02T01:20:37.985151Z DEBUG surrealdb_migrations::apply: stmt: [Define(Table(DefineTableStatement { id: None, name: Ident("item_dependencies"), drop: false, full: true, view: None, permissions: Permissions { select: None, create: None, update: None, delete: None }, changefeed: None, comment: None, if_not_exists: false, kind: Relation(Relation { from: Some(Record([Table("workshop_items")])), to: Some(Record([Table("workshop_items")])), enforced: false }), overwrite: true, cache_fields_ts: 00000000-0000-0000-0000-000000000000, cache_events_ts: 00000000-0000-0000-0000-000000000000, cache_tables_ts: 00000000-0000-0000-0000-000000000000, cache_indexes_ts: 00000000-0000-0000-0000-000000000000 })), Define(Field(DefineFieldStatement { name: Idiom([Field(Ident("in"))]), what: Ident("item_dependencies"), flex: false, kind: Some(Record([Table("workshop_items")])), readonly: false, value: None, assert: None, default: None, permissions: Permissions { select: Full, create: Full, update: Full, delete: Full }, comment: None, if_not_exists: false, overwrite: true, reference: None, default_always: false })), Define(Field(DefineFieldStatement { name: Idiom([Field(Ident("out"))]), what: Ident("item_dependencies"), flex: false, kind: Some(Record([Table("workshop_items")])), readonly: false, value: None, assert: None, default: None, permissions: Permissions { select: Full, create: Full, update: Full, delete: Full }, comment: None, if_not_exists: false, overwrite: true, reference: None, default_always: false })), Define(Index(DefineIndexStatement { name: Ident("dep_out"), what: Ident("item_dependencies"), cols: Idioms([Idiom([Field(Ident("out"))])]), index: Idx, comment: None, if_not_exists: false, overwrite: true, concurrently: false })), Define(Table(DefineTableStatement { id: None, name: Ident("script_migration"), drop: false, full: true, view: None, permissions: Permissions { select: Full, create: None, update: None, delete: None }, changefeed: None, comment: None, if_not_exists: false, kind: Normal, overwrite: true, cache_fields_ts: 00000000-0000-0000-0000-000000000000, cache_events_ts: 00000000-0000-0000-0000-000000000000, cache_tables_ts: 00000000-0000-0000-0000-000000000000, cache_indexes_ts: 00000000-0000-0000-0000-000000000000 })), Define(Field(DefineFieldStatement { name: Idiom([Field(Ident("script_name"))]), what: Ident("script_migration"), flex: false, kind: Some(String), readonly: false, value: None, assert: None, default: None, permissions: Permissions { select: Full, create: Full, update: Full, delete: Full }, comment: None, if_not_exists: false, overwrite: true, reference: None, default_always: false })), Define(Field(DefineFieldStatement { name: Idiom([Field(Ident("executed_at"))]), what: Ident("script_migration"), flex: false, kind: Some(Datetime), readonly: true, value: Some(Function(Normal("time::now", []))), assert: None, default: None, permissions: Permissions { select: Full, create: Full, update: Full, delete: Full }, comment: None, if_not_exists: false, overwrite: true, reference: None, default_always: false })), Define(Table(DefineTableStatement { id: None, name: Ident("tags"), drop: false, full: true, view: None, permissions: Permissions { select: None, create: None, update: None, delete: None }, changefeed: None, comment: None, if_not_exists: false, kind: Normal, overwrite: true, cache_fields_ts: 00000000-0000-0000-0000-000000000000, cache_events_ts: 00000000-0000-0000-0000-000000000000, cache_tables_ts: 00000000-0000-0000-0000-000000000000, cache_indexes_ts: 00000000-0000-0000-0000-000000000000 })), Define(Field(DefineFieldStatement { name: Idiom([Field(Ident("app_id"))]), what: Ident("tags"), flex: false, kind: Some(Int), readonly: false, value: None, assert: None, default: None, permissions: Permissions { select: Full, create: Full, update: Full, delete: Full }, comment: None, if_not_exists: false, overwrite: true, reference: None, default_always: false })), Define(Field(DefineFieldStatement { name: Idiom([Field(Ident("display_name"))]), what: Ident("tags"), flex: false, kind: Some(String), readonly: false, value: None, assert: None, default: None, permissions: Permissions { select: Full, create: Full, update: Full, delete: Full }, comment: None, if_not_exists: false, overwrite: true, reference: None, default_always: false })), Define(Field(DefineFieldStatement { name: Idiom([Field(Ident("id"))]), what: Ident("tags"), flex: false, kind: Some(String), readonly: false, value: None, assert: None, default: None, permissions: Permissions { select: Full, create: Full, update: Full, delete: Full }, comment: None, if_not_exists: false, overwrite: true, reference: None, default_always: false })), Define(Index(DefineIndexStatement { name: Ident("field_app_id_tag"), what: Ident("tags"), cols: Idioms([Idiom([Field(Ident("app_id"))]), Idiom([Field(Ident("display_name"))])]), index: Idx, comment: None, if_not_exists: true, overwrite: false, concurrently: false })), Define(Table(DefineTableStatement { id: None, name: Ident("workshop_items"), drop: false, full: true, view: None, permissions: Permissions { select: None, create: None, update: None, delete: None }, changefeed: None, comment: None, if_not_exists: false, kind: Normal, overwrite: true, cache_fields_ts: 00000000-0000-0000-0000-000000000000, cache_events_ts: 00000000-0000-0000-0000-000000000000, cache_tables_ts: 00000000-0000-0000-0000-000000000000, cache_indexes_ts: 00000000-0000-0000-0000-000000000000 })), Define(Field(DefineFieldStatement { name: Idiom([Field(Ident("appid"))]), what: Ident("workshop_items"), flex: false, kind: Some(Int), readonly: false, value: None, assert: None, default: None, permissions: Permissions { select: Full, create: Full, update: Full, delete: Full }, comment: None, if_not_exists: false, overwrite: true, reference: None, default_always: false })), Define(Field(DefineFieldStatement { name: Idiom([Field(Ident("preview_url"))]), what: Ident("workshop_items"), flex: false, kind: Some(Option(String)), readonly: false, value: None, assert: None, default: None, permissions: Permissions { select: Full, create: Full, update: Full, delete: Full }, comment: None, if_not_exists: false, overwrite: true, reference: None, default_always: false })), Define(Field(DefineFieldStatement { name: Idiom([Field(Ident("id"))]), what: Ident("workshop_items"), flex: false, kind: Some(String), readonly: false, value: None, assert: None, default: None, permissions: Permissions { select: Full, create: Full, update: Full, delete: Full }, comment: None, if_not_exists: false, overwrite: true, reference: None, default_always: false })), Define(Field(DefineFieldStatement { name: Idiom([Field(Ident("author"))]), what: Ident("workshop_items"), flex: false, kind: Some(String), readonly: false, value: None, assert: None, default: None, permissions: Permissions { select: Full, create: Full, update: Full, delete: Full }, comment: None, if_not_exists: false, overwrite: true, reference: None, default_always: false })), Define(Field(DefineFieldStatement { name: Idiom([Field(Ident("description"))]), what: Ident("workshop_items"), flex: false, kind: Some(String), readonly: false, value: None, assert: None, default: None, permissions: Permissions { select: Full, create: Full, update: Full, delete: Full }, comment: None, if_not_exists: false, overwrite: true, reference: None, default_always: false })), Define(Field(DefineFieldStatement { name: Idiom([Field(Ident("languages"))]), what: Ident("workshop_items"), flex: false, kind: Some(Set(Int, None)), readonly: false, value: None, assert: None, default: None, permissions: Permissions { select: Full, create: Full, update: Full, delete: Full }, comment: None, if_not_exists: false, overwrite: true, reference: None, default_always: false })), Define(Field(DefineFieldStatement { name: Idiom([Field(Ident("last_updated"))]), what: Ident("workshop_items"), flex: false, kind: Some(Int), readonly: false, value: None, assert: None, default: None, permissions: Permissions { select: Full, create: Full, update: Full, delete: Full }, comment: None, if_not_exists: false, overwrite: true, reference: None, default_always: false })), Define(Field(DefineFieldStatement { name: Idiom([Field(Ident("tags"))]), what: Ident("workshop_items"), flex: false, kind: Some(Set(Record([Table("tags")]), None)), readonly: false, value: None, assert: None, default: None, permissions: Permissions { select: Full, create: Full, update: Full, delete: Full }, comment: None, if_not_exists: false, overwrite: true, reference: None, default_always: false })), Define(Field(DefineFieldStatement { name: Idiom([Field(Ident("tags")), All]), what: Ident("workshop_items"), flex: false, kind: Some(Record([Table("tags")])), readonly: false, value: None, assert: None, default: None, permissions: Permissions { select: Full, create: Full, update: Full, delete: Full }, comment: None, if_not_exists: false, overwrite: true, reference: None, default_always: false })), Define(Field(DefineFieldStatement { name: Idiom([Field(Ident("title"))]), what: Ident("workshop_items"), flex: false, kind: Some(String), readonly: false, value: None, assert: None, default: None, permissions: Permissions { select: Full, create: Full, update: Full, delete: Full }, comment: None, if_not_exists: false, overwrite: true, reference: None, default_always: false })), Define(Field(DefineFieldStatement { name: Idiom([Field(Ident("score"))]), what: Ident("workshop_items"), flex: false, kind: Some(Float), readonly: false, value: None, assert: None, default: None, permissions: Permissions { select: Full, create: Full, update: Full, delete: Full }, comment: None, if_not_exists: false, overwrite: true, reference: None, default_always: false })), Define(Index(DefineIndexStatement { name: Ident("item_updated"), what: Ident("workshop_items"), cols: Idioms([Idiom([Field(Ident("last_updated"))])]), index: Idx, comment: None, if_not_exists: false, overwrite: true, concurrently: false })), Define(Index(DefineIndexStatement { name: Ident("item_language"), what: Ident("workshop_items"), cols: Idioms([Idiom([Field(Ident("languages"))])]), index: Idx, comment: None, if_not_exists: false, overwrite: true, concurrently: false })), Define(Index(DefineIndexStatement { name: Ident("item_title"), what: Ident("workshop_items"), cols: Idioms([Idiom([Field(Ident("title"))])]), index: Idx, comment: None, if_not_exists: false, overwrite: true, concurrently: false })), Define(Index(DefineIndexStatement { name: Ident("item_score"), what: Ident("workshop_items"), cols: Idioms([Idiom([Field(Ident("score"))])]), index: Idx, comment: None, if_not_exists: false, overwrite: true, concurrently: false }))], action: Commit
2025-06-02T01:21:33.945504Z DEBUG surrealdb_migrations::apply: surrealdb-migrations/src/apply.rs:485
Subject: [PATCH] stuff
---
Index: src/apply.rs
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/apply.rs b/src/apply.rs
--- a/src/apply.rs	(revision 4820eea96ae1af98e50dfc09c05344492ab15c6c)
+++ b/src/apply.rs	(revision 278d8df0046c62d1c1e70ed9dc18ea5ecd1a13e2)
@@ -476,8 +476,10 @@
                 .collect::<Vec<_>>();
 
             let transaction_action = get_transaction_action(dry_run);
+            debug!("{}:{}", file!(), line!());
+            debug!("stmt: {statements:?}, action: {transaction_action:?}");
             surrealdb::apply_in_transaction(client, statements, transaction_action).await?;
-
+            debug!("{}:{}", file!(), line!());
             if !current_definition.schemas.is_empty() {
                 has_applied_schemas = true;
             }

This isn't a particularly large database either, at about 100 MiB & about 75k rows.

Anyway, the fix for this is pretty straightforward:

Subject: [PATCH] fix: Running migrations unnecessarily
---
Index: src/apply.rs
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/apply.rs b/src/apply.rs
--- a/src/apply.rs	(revision 278d8df0046c62d1c1e70ed9dc18ea5ecd1a13e2)
+++ b/src/apply.rs	(revision 3a9b2937439f827d0c186e5621134f2fcc213d7b)
@@ -436,7 +436,7 @@
 ) -> Result<()> {
     let mut has_applied_schemas = false;
     let mut has_applied_events = false;
-    let has_applied_migrations = !&migration_files_to_execute.is_empty();
+    let pending_migrations = !migration_files_to_execute.is_empty();
     let mut current_definition: SchemaMigrationDefinition = Default::default();
 
     if use_migration_definitions {
@@ -453,6 +453,7 @@
         }?;
 
         if !has_applied_migrations {
+        if pending_migrations {
             let schemas_statements = current_definition.schemas.to_string();
             let events_statements = current_definition.events.to_string();
 

Please don't hesitate to pinch that patch; otherwise, I can submit it as a pull request. That said, I would very much like to see this as an opportunity for adding some form of observability into this crate, given that it won't always be possible to debug this problem like I have.

Personally, I'm a big fan of working within the tracing ecosystem, however, something as simple as descriptive logging would work as well.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions