From 25a91c33224c0d4102f7ae0544d0622dd070054b Mon Sep 17 00:00:00 2001 From: Petru Paler Date: Thu, 1 Jan 2026 16:09:06 +0000 Subject: [PATCH] fix: remove trailing newlines from migrations to prevent SQLITE_MISUSE errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Python's sqlite3.executescript() has a bug where trailing newlines after the final semicolon create empty statements. When APSW's log_sqlite() is enabled (via apswutils, imported by fastmigrate), these cause visible "API called with NULL prepared statement" errors during interpreter shutdown. - Strip trailing newlines from all 9 existing migration files - Update migration template to end with semicolon, no trailing newline - Document the requirement in CLAUDE.md 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- CLAUDE.md | 5 +++++ migrations/0001-reference-tables.sql | 2 +- migrations/0002-event-tables.sql | 2 +- migrations/0003-animal-registry-schema.sql | 2 +- migrations/0004-interval-projections.sql | 2 +- migrations/0005-tag-suggestions.sql | 2 +- migrations/0006-feed-inventory.sql | 2 +- migrations/0007-egg-stats-30d.sql | 2 +- migrations/0008-event-log-by-location.sql | 2 +- migrations/0009-user-defaults.sql | 2 +- src/animaltrack/migrations.py | 7 ++++--- 11 files changed, 18 insertions(+), 12 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 49b0cce..94d5f12 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -80,6 +80,11 @@ When handling animal selections: - Store feed amounts in grams (INTEGER) for precision - Display as kg with 3 decimals +### Migrations +- Migration files must end with `;` and NO trailing newline +- Python's sqlite3.executescript() has a bug: trailing newlines after the final `;` create empty statements that cause SQLITE_MISUSE errors when APSW logging is enabled +- Use `animaltrack create-migration "description"` to create new migrations (template handles this correctly) + ## Refreshing Docs ```bash # FastHTML diff --git a/migrations/0001-reference-tables.sql b/migrations/0001-reference-tables.sql index 9cf49f5..0e51ef5 100644 --- a/migrations/0001-reference-tables.sql +++ b/migrations/0001-reference-tables.sql @@ -49,4 +49,4 @@ CREATE TABLE users ( active INTEGER NOT NULL DEFAULT 1 CHECK(active IN (0, 1)), created_at_utc INTEGER NOT NULL, updated_at_utc INTEGER NOT NULL -); +); \ No newline at end of file diff --git a/migrations/0002-event-tables.sql b/migrations/0002-event-tables.sql index 7a479c4..c7773b9 100644 --- a/migrations/0002-event-tables.sql +++ b/migrations/0002-event-tables.sql @@ -53,4 +53,4 @@ CREATE TABLE event_animals ( ts_utc INTEGER NOT NULL, PRIMARY KEY (event_id, animal_id) ); -CREATE UNIQUE INDEX ux_event_animals_animal_ts ON event_animals(animal_id, ts_utc); +CREATE UNIQUE INDEX ux_event_animals_animal_ts ON event_animals(animal_id, ts_utc); \ No newline at end of file diff --git a/migrations/0003-animal-registry-schema.sql b/migrations/0003-animal-registry-schema.sql index 84f16be..ef43e12 100644 --- a/migrations/0003-animal-registry-schema.sql +++ b/migrations/0003-animal-registry-schema.sql @@ -55,4 +55,4 @@ CREATE TABLE animal_aliases ( merged_at_utc INTEGER NOT NULL ); -CREATE INDEX idx_aa_survivor ON animal_aliases(survivor_animal_id); +CREATE INDEX idx_aa_survivor ON animal_aliases(survivor_animal_id); \ No newline at end of file diff --git a/migrations/0004-interval-projections.sql b/migrations/0004-interval-projections.sql index 62f73f8..c22d86d 100644 --- a/migrations/0004-interval-projections.sql +++ b/migrations/0004-interval-projections.sql @@ -53,4 +53,4 @@ CREATE TABLE animal_attr_intervals ( -- Index for "which animals had attr=value at time T" queries CREATE INDEX idx_aai_attr_time ON animal_attr_intervals( attr, value, start_utc, COALESCE(end_utc, 32503680000000) -); +); \ No newline at end of file diff --git a/migrations/0005-tag-suggestions.sql b/migrations/0005-tag-suggestions.sql index 62e3a4a..0513b4b 100644 --- a/migrations/0005-tag-suggestions.sql +++ b/migrations/0005-tag-suggestions.sql @@ -12,4 +12,4 @@ CREATE TABLE tag_suggestions ( ); -- Index for sorting by popularity -CREATE INDEX idx_tag_suggestions_popularity ON tag_suggestions(active_animals DESC, total_assignments DESC); +CREATE INDEX idx_tag_suggestions_popularity ON tag_suggestions(active_animals DESC, total_assignments DESC); \ No newline at end of file diff --git a/migrations/0006-feed-inventory.sql b/migrations/0006-feed-inventory.sql index 60e5a49..2ea9d93 100644 --- a/migrations/0006-feed-inventory.sql +++ b/migrations/0006-feed-inventory.sql @@ -15,4 +15,4 @@ CREATE TABLE feed_inventory ( ); -- Index for finding when last purchase/given occurred -CREATE INDEX idx_feed_inventory_last_on ON feed_inventory(last_purchase_at_utc, last_given_at_utc); +CREATE INDEX idx_feed_inventory_last_on ON feed_inventory(last_purchase_at_utc, last_given_at_utc); \ No newline at end of file diff --git a/migrations/0007-egg-stats-30d.sql b/migrations/0007-egg-stats-30d.sql index 036a386..dc503f2 100644 --- a/migrations/0007-egg-stats-30d.sql +++ b/migrations/0007-egg-stats-30d.sql @@ -19,4 +19,4 @@ CREATE TABLE egg_stats_30d_by_location ( ); -- Index for finding stale stats that need recomputation -CREATE INDEX idx_egg_stats_updated ON egg_stats_30d_by_location(updated_at_utc); +CREATE INDEX idx_egg_stats_updated ON egg_stats_30d_by_location(updated_at_utc); \ No newline at end of file diff --git a/migrations/0008-event-log-by-location.sql b/migrations/0008-event-log-by-location.sql index 272f880..1bcb822 100644 --- a/migrations/0008-event-log-by-location.sql +++ b/migrations/0008-event-log-by-location.sql @@ -24,4 +24,4 @@ BEGIN ORDER BY ts_utc DESC LIMIT -1 OFFSET 500 ); -END; +END; \ No newline at end of file diff --git a/migrations/0009-user-defaults.sql b/migrations/0009-user-defaults.sql index 39e379f..e6014be 100644 --- a/migrations/0009-user-defaults.sql +++ b/migrations/0009-user-defaults.sql @@ -14,4 +14,4 @@ CREATE TABLE user_defaults ( PRIMARY KEY (username, action) ); -CREATE INDEX idx_user_defaults_username ON user_defaults(username); +CREATE INDEX idx_user_defaults_username ON user_defaults(username); \ No newline at end of file diff --git a/src/animaltrack/migrations.py b/src/animaltrack/migrations.py index 5fec307..4d829db 100644 --- a/src/animaltrack/migrations.py +++ b/src/animaltrack/migrations.py @@ -94,12 +94,13 @@ def create_migration(migrations_dir: str | Path, description: str) -> Path: filepath = migrations_path / filename # SQL template with ABOUTME header + # NOTE: Template must NOT end with trailing newline after final semicolon. + # Python's sqlite3.executescript() has a bug where trailing newlines create + # empty statements that cause SQLITE_MISUSE errors when APSW logging is enabled. template = f"""-- ABOUTME: Migration {index:04d} - {description} -- ABOUTME: Created for AnimalTrack database schema versioning. --- Write your migration SQL here - -""" +-- Write your migration SQL here;""" filepath.write_text(template) return filepath