Schedule icon
Query icon
If icon
Log icon
SlackIncomingWebhook icon

SurrealDB multi-model schema migration preflight audit and conditional release gate

Audit SurrealDB database schema state and conditionally execute table and index migration statements with Slack reporting.

Categories
CoreDataInfrastructure

Running DEFINE TABLE/DEFINE FIELD/DEFINE INDEX statements directly against a SurrealDB database has no built-in dry-run: there is no safe way to confirm the database is even reachable and healthy before mutating its schema. This blueprint splits that into two stages. It first runs a read-only SurrealQL preflight audit (INFO FOR DB;), which returns the database's current schema metadata without changing anything. Only if that audit actually returned a row, and only if apply_migration was explicitly set to true, does it run the DEFINE statements for real. Every run (audited, gated, or migrated) ends with one Slack message.

How it works

  1. weekly_schema_audit (io.kestra.plugin.core.trigger.Schedule) runs once a week. Shipped disabled so you can validate a manual run first.
  2. preflight_schema_audit (io.kestra.plugin.surrealdb.Query) connects with host/port/username/password/namespace/database and runs INFO FOR DB; with fetchType: FETCH_ONE, which populates the row output with the database's schema metadata object (tables, users, functions, params).
  3. evaluate_migration_gate (io.kestra.plugin.core.flow.If) branches on inputs.apply_migration and outputs.preflight_schema_audit.row is defined. exitCode is not part of this condition: io.kestra.plugin.surrealdb.Query has no such property, and neither is result/results: FETCH_ONE populates row, not result.
    • then: apply_schema_migration re-runs with the same connection and fetchType: NONE, executing DEFINE TABLE OVERWRITE user SCHEMAFULL; DEFINE FIELD OVERWRITE email ON TABLE user TYPE string; DEFINE INDEX OVERWRITE user_email ON TABLE user COLUMNS email UNIQUE;.
    • else: log_audit_only records why nothing ran: audit failed to return a row, or it succeeded but apply_migration was false.
  4. notify_slack posts one summary message every run: whether the audit returned a row, the apply_migration input value, and whether the migration task actually executed.

What you get

  • A read-only preflight check before any schema-mutating statement runs against SurrealDB.
  • A single boolean gate (apply_migration) standing between "audited" and "migrated", instead of trusting a DEFINE statement to be safe to run unattended.
  • Idempotent OVERWRITE migration statements that can be re-run without first checking whether the table/field/index already exists.
  • One Slack message per run covering all three outcomes: audit-only, gated-and-skipped, and migrated.

Who it's for

  • Platform and data engineering teams running SurrealDB who want a reviewable gate before schema changes land.
  • Teams who want DEFINE migrations to require a conscious flag, not an automatic side effect of a scheduled job.
  • Anyone who has had a schema migration run against the wrong namespace/database because nothing checked connectivity first.

Why orchestrate this with Kestra

SurrealQL has no concept of "audit first, then ask before mutating" across two separate statements: that sequencing, the gate in between, and the audit trail of which branch ran are not things a raw surreal sql script tracks for you. Kestra's If task turns the gate into a first-class, inspectable branch; the schedule, the explicit apply_migration input, and the execution history make every audit-only or migrated run reviewable after the fact.

Prerequisites

  • A reachable SurrealDB instance. Locally: docker run -d -p 8000:8000 surrealdb/surrealdb:latest start --user root --pass root.
  • A root-capable user (or a scoped user with permission to run INFO FOR DB and DEFINE statements) in the target namespace/database.
  • A Slack incoming webhook for the summary notification.

Secrets

  • SURREALDB_PASSWORD: password for the username (root by default) used by both Query tasks.
  • SLACK_WEBHOOK_URL: Slack incoming webhook used by notify_slack.
  • In Kestra OSS (no Enterprise secrets backend), secrets are supplied as environment variables prefixed SECRET_, base64-encoded, and read back in flows with {{ secret('NAME') }} - for example SECRET_SURREALDB_PASSWORD=$(echo -n 'root' | base64). This keeps credentials out of the flow YAML but, per Kestra's own documentation, offers no encryption at rest or access control beyond the host environment; use the Enterprise secrets backend for stronger guarantees.

Inputs

  • apply_migration (BOOL, default false): must be explicitly true for the migration branch to run, even if the audit succeeds.
  • surrealdb_host (STRING, default localhost): bare hostname, no scheme or path.
  • surrealdb_namespace (STRING, default production): target namespace.
  • surrealdb_database (STRING, default app_data): target database.

Outputs

  • outputs.preflight_schema_audit.row: the INFO FOR DB metadata object (tables, users, functions, params) when the audit succeeds.
  • outputs.preflight_schema_audit.size: row count from the audit query (always present).
  • outputs.apply_schema_migration: present only when the migration branch ran; fetchType: NONE means it carries no row/rows/uri, only confirms the task executed.

Quick start

  1. Start SurrealDB locally with the command above, or point at an existing instance.
  2. Add the SURREALDB_PASSWORD and SLACK_WEBHOOK_URL secrets.
  3. Run the flow manually with the defaults and confirm preflight_schema_audit returns a row and, with apply_migration: false, only log_audit_only and notify_slack run.
  4. Run once with apply_migration: true against a namespace/database you are comfortable testing on, and confirm the user table, email field, and user_email index are created.
  5. Enable weekly_schema_audit once you trust the audit step; keep apply_migration as a manual, reviewed flag rather than enabling it on the schedule.

How to extend

  • Parse outputs.preflight_schema_audit.row.tables to branch differently depending on which tables already exist, instead of a flat true/false gate.
  • Add a Pause approval task between audit and migration instead of (or in addition to) the static apply_migration input.
  • Add more DEFINE statements to apply_schema_migration's query as your schema grows, keeping the OVERWRITE clause so re-runs stay idempotent.
  • Route to a ticketing system in addition to Slack when the audit fails to return a row.

Pitfalls

  • SurrealDB Query port property requires a literal integer. The SurrealDB plugin's port property is declared as a plain int in Java without a dynamic/templatable marker. Using a literal integer 8000 directly on the tasks prevents String-to-Int deserialization errors.
  • The real connection shape is host/port, not a single URL. io.kestra.plugin.surrealdb.Query has no url property; it opens new SurrealWebSocketConnection(host, port, useTls) internally. A ws://host:port/rpc-style value in host would be passed through as a literal hostname string, not parsed.
  • FETCH_ONE populates row, not result. Checking outputs.preflight_schema_audit.result is defined (or exitCode, which does not exist on this task) would always be false; this blueprint checks .row is defined instead, which is the verified output field for fetchType: FETCH_ONE.
  • OVERWRITE makes migration statements idempotent, but destructive on structural changes. DEFINE ... OVERWRITE replaces the existing definition rather than erroring if it already exists, which is what makes repeated runs safe - but changing a field's TYPE or an index's columns on a table with existing data can still reject or silently coerce rows that violate the new definition; test against representative data first.

Links

See How

New to Kestra?

Use blueprints to kickstart your first workflows.