- Overview (PROLOGUS)
- Features (VIRTUTES)
- Installing
- Usage (PRAECEPTA)
- How it works (ARCHITECTURA)
- Licensing
- REPL: Interactive workflow, developer power
- Code of conduct
Relational Schema Migrations Powered by Lisp
🏛️ M U T A S T R U C T U R A 🏛️
_ _ V E N I • V I D I • L I S P _ _
Overview (PROLOGUS)
This tool ensures your relational schema and database state is exactly as you decree, incrementally.
Compatible with SQLite3, MySQL and PostgreSQL. Write your schema migrations and seeding easily, and in your native database dialect.
Features (VIRTUTES)
Mutastructura provides a declarative, transactional approach to managing your database states:
- Written purely in Guile Scheme, bringing functional programming and S-expression elegance to your database migrations.
- Native
dbisupport:sqlite3(TO FINALIZE! TBD! PostgreSQL and MySQL) - Auto-installs the schema management
migrations_history. - Every migration is executed within a
with-transactionblock and so, transactionally (fromBEGINtoCOMMITyou could say). - If a migration fails, the legion retreats (
ROLLBACK), ensuring your database is never left in a corrupted, half-migrated state. - Intelligent Parsing with a finite-state-machine-based SQL lexer (
string-split-to-queries). - Computes
SHA256checksums of your migration files and tracks them in amigrations_historytable, ensuring your schema evolution is strictly linear and tamper-evident.
Installing
mutastructura is officially distributed via:
- Guix package manager - as
mutastructurafrom(gnu packages databases)- or from the source code here - Podman/Docker images.
That being said, feel free to use it as you wish, within the terms of the GNU Affero General Public License v3 or newer.
On Guix
Requirements:
- Guix: The Guix package manager will ensure a reproducible working software, and will manage all needed dependencies for you.
- Guile Scheme: This entire program is written using the official GNU extension language, Guile Scheme.
If you just want to quickly install it to your profile:
guix package --install-from-file=./guix.scm
Check the maak.scm and the guix.scm and manifest.scm for more details.
For example, to enter an environment shell with mutastructura temporarily you can use:
guix time-machine --channels=channels.scm -- shell -f guix.scm
You can also chain commands to it:
guix time-machine --channels=channels.scm -- \
shell -f guix.scm -- mutastructura \
--database sqlite3 \
--connection ./lucidplan-test.sqlite \
--migrations ./migrations/lucidplan
You can also run a dev shell (with manifest)
guix time-machine --channels=channels.scm -- \
shell -m manifest.scm -- guile -L ./src -c '((@(mutastructura main) main))' \
--database sqlite3 \
--connection ./lucidplan-test.sqlite \
--migrations ./migrations/lucidplan \
--resources ./resources
mutastructura offers a --help / -h flag which will explain all options (also visible in resources/help.txt). You should point mutastructura to the path to your migrations.
On Podman/Docker
mutastructura is also available as a Docker container, from DockerHub (also compatible with Podman).
https://hub.docker.com/repository/docker/jjba23/mutastructura/general
You can also build images of mutastructura yourself, using guix pack. See the maak.scm file for more.
To load these tarball images, you can do podman load < my.tar.gz
You can run mutastructura from the container, and bind your local filesystem to give access, for example:
docker container run -v /home/joe:/home/joe \
docker.io/jjba23/mutastructura:latest mutastructura --help
Usage (PRAECEPTA)
The project is written mostly in Lisp (Guile Scheme) and uses Maak, the infinitely extensible command-runner as automation system with GNU Guix package manager and build system, making reproducibility and devex first-class concerns, not afterthoughts.
See a quick help page:
Run easily and reproducibly with Maak + Guix, or use directly Guile, or load a REPL (like Arei)
maak run # or maak dev-run
# or
guix time-machine --channels=channels.scm -- \
shell -f guix.scm -- mutastructura \
--database sqlite3 \
--connection ./lucidplan-test.sqlite \
--migrations ./migrations/lucidplan
Argument Details
-
-d/--database - The engine you are targeting (
sqlite3,postgresql,mysql). -
-c/--connection - The connection string or path to your database.
-
-m/--migrations - The path to your
*.sqlmigration files directory. -
-r/--resources - (optional) Path to internal application resources (used for development and installation, bundled from ./resources dir).
How it works (ARCHITECTURA)
When invoked, Mutastructura:
- Opens a connection to your specified database engine.
- Queries the historical ledger (
migrations_history) to see which migrations have already run, and crossed the Rubicon. - If no history exists, it initializes the schema via platform-specific genesis files (
mutastructura-init.sql). - Scans your provided migrations directory, and gets migrations to run, in chronological + alphabetical order based on filename (anything works, but the best naming for this purpose is in the shape of
YYYY-MM-DD_HH:MM:SS_some-name.sql, e.g.2026-04-01_21:54:00_init.sql) - Generates SHA256 hashes for each file. Changing the file contents means changing the SHA and will trigger a WARNING
- Safely splits the SQL files into individual statements.
- Executes the statements synchronously.
- Commits the transaction and gracefully closes the connection.
Mutastructura follows a classic functional-core, imperative-shell architecture, divided into distinct modules that handle specific responsibilities.
Migrations whose name ends in *.repeatable.sql will be deemed as repeatable and thus always executed.
The Imperative Shell (CLI & Orchestration)
Located in (mutastructura main), this is the commander of the program. It deals with all the "side-effects": reading command-line arguments, opening the database connection, reading from the file system, and printing outputs to the terminal.
The Migration Dance
Ensure the migrations_history table exists (or initialize it). This will act as our ledger.
Fetch the database state (contents from migrations_history) and the available local migration files to run.
Compare them to find unapplied migrations.
Hand the unapplied files to the persistence layer for execution.
The Persistence Layer (Transactions & Execution)
Where the database interactions happen via the dbi library.
Its most critical architectural feature is the with-transaction higher-order function. By wrapping all database executions inside a BEGIN and COMMIT block—and catching exceptions to trigger a ROLLBACK it guarantees that the database state is treated as atomic. It either succeeds entirely or fails cleanly.
The Lexical State Machine (SQL Parser)
Also housed in the persistence layer, the string-split-to-queries function is arguably the most advanced architectural component of the persistence layer. Rather than relying on fragile regular expressions to split SQL by semicolons, it uses a true Finite State Machine (FSM). It parses the SQL files for queries character-by-character.
Licensing
mutastructura and all of its source code are free software, licensed under the GNU Affero General Public License v3 (or newer at your convenience).
https://www.gnu.org/licenses/agpl-3.0.html
The documentation and examples, including this document, which are provided with mutastructura, are all licensed under the GNU Free Documentation License v1.3 (or newer at your convenience).
https://www.gnu.org/licenses/fdl-1.3.html
REPL: Interactive workflow, developer power
A REPL (Read-Eval-Print Loop) is an interactive environment, which can be used connected to your console, running application, language compiler and more, which gives you superpowers as an engineer 🦸🏼.
Lisp dialects, more specifically Guile Scheme, have great support for this. I personally of course like to do this with Guix, Emacs, (Arei/Ares + sesman) you can get an ultimate extensible powerful editor experience, miles ahead of traditional IDEs 🐂 .
It fundamentally changes the development workflow by eliminating the slow edit, save, compile, run cycle. Instead of writing a whole program and then running it to see what happens, you get a fast, conversational workflow. What does this mean for in practice?
- Incremental Development: Write, test, inspect, evaluate one function or even one line at a time. Get immediate feedback without running the entire app.
- Powerful Debugging: Forget adding
printstatements and restarting. You can pause, inspect objects, change values, and even redefine a broken function on the fly to test a fix in any environment (yes even in production, while running). - Fast Prototyping & Learning: Instantly experiment with a new library or API. Just load it and start calling functions to see how they work, which is much faster than only reading documentation.
When integrated into your code editor, you can execute any piece of code (a line, a selection, or a file) with a keyboard shortcut and see the result instantly, creating a seamless and powerful development experience.
Code of conduct
This project adheres to the jointhefreeworld code of conduct. Find it here:
https://jointhefreeworld.org/blog/articles/personal/jointhefreeworld-code-of-conduct/index.html
In summary, we foster an inclusive, respectful, and cooperative environment for all contributors and users of this free software project. Inspired by the ideals of the GNU Project, we strive to uphold freedom, equality, and community as guiding principles. We believe that collaboration in a community of mutual respect is essential to creating excellent free software.