Skip to content

fix(db): make alembic check clean on SQLite - #883

Merged
antosubash merged 8 commits into
mainfrom
fix/issue-869-mixed-expression-index
Nov 7, 2026
Merged

antosubash merged 8 commits into
mainfrom
fix/issue-869-mixed-expression-index

Conversation

@antosubash

Copy link
Copy Markdown
Owner

Summary

Closes #869.

On a SQLite database migrated to heads, alembic check failed, and every SQLite revision --autogenerate wrote the same two false operations into the new revision. CI never saw it, because its only alembic check ran on Postgres.

  • file_storage browse index:
    • It was written as sa.text("created_at DESC"), which SQLite reflects as a plain column. It is now sa.desc(sa.column("created_at")).
    • The DDL is identical on SQLite and Postgres, so no migration is needed.
    • sa.desc("created_at") with a string is not enough: SQLAlchemy wraps it as a text label, and Alembic still treats that as an expression.
  • users_access_token.expires_at: it was declared TIMESTAMPAware while its migration created DateTime(timezone=True), and SQLite reported a type change. It now uses AwareDateTime(), the repo convention. It still reads back aware UTC, and every writer already passes UTC.
  • Guard for other modules (new simple_module_db/_sqlite_index_compare.py):
    • On SQLite only, make_include_object skips an index pair whose only difference is an ordered column written as text or as sa.desc("col").
    • Still reported: a different column list, a uniqueness change, a real expression such as lower(email), and any partial index.
    • Existing hosts get it without editing env.py.
  • SM026 now counts a column wrapped in desc()/asc() as a column, so the browse index is no longer listed as unverifiable. A labelled or cast expression still counts as an expression.
  • make ci-doctor now runs alembic check against its throwaway SQLite database.

Design: docs/superpowers/specs/2026-11-07-sqlite-alembic-check-design.md. Plan: docs/superpowers/plans/2026-11-07-sqlite-alembic-check.md.

Verification

  • Local CI:
    • make lint green.
    • make test-py: 8,235 passed, 28 skipped, with Redis.
    • make test-js: 900 passed.
  • make ci-doctor: alembic check on SQLite reported no new upgrade operations; doctor gave 0 errors.
  • make migrations-roundtrip-pg on a scratch database: no new upgrade operations.
  • The new guard tests fail without the guard (14 of them), and the partial-index test fails without its fix.
  • Implementation: Codex wrote the guard. I moved it into its own module, so migrations.py keeps its docstrings, and extended it to cover the label form.
  • Review: three passes, with two Sonnet reviewers (correctness, maintainability) and Codex.
    • They caught a labelled-expression regression in SM026 and a partial-index predicate the guard would have hidden. Both are fixed, with tests.
    • Other findings were fixed or rejected with evidence.
  • Verification report: https://claude.ai/artifact/WnBCr9S1Mds1oKGkajfoQ1

Report links are Claude Artifacts and start private; the author can share them from claude.ai/code/artifacts if reviewers need access.

Test plan

  • On SQLite: alembic upgrade heads then alembic check reports no new operations.
  • CI is green, including doctor (now runs alembic check) and migrations-roundtrip-pg.

https://claude.ai/code/session_01M9neheZZEe3sVpDi2S3zT4

…(sa.column())

sa.desc("created_at") wraps a text label, so Alembic still treated the index
as an expression and SQLite still reported it as changed. SM026's expression
test now reads an ordered column as a column, so the index is no longer
listed as unverifiable.

Claude-Session: https://claude.ai/code/session_01M9neheZZEe3sVpDi2S3zT4
… ordering

make_include_object now skips, on SQLite only, an index pair whose only
difference is an ordered column written as sa.text("col DESC") or
sa.desc("col"): SQLite reflects it as a plain column, so autogenerate wrote a
drop and re-create and alembic check failed (GH #869). A different column list,
a uniqueness change or a real expression is still reported. Guard by Codex;
moved into its own module so migrations.py keeps its docstrings.

Claude-Session: https://claude.ai/code/session_01M9neheZZEe3sVpDi2S3zT4
Never suppress a partial-index comparison (Codex): the SQLite ordering guard
compares columns only, so a changed WHERE predicate would have been hidden.

Claude-Session: https://claude.ai/code/session_01M9neheZZEe3sVpDi2S3zT4
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 7, 2026 •

Copy link
Copy Markdown
Codex Review Summary: ✅ Completed

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
🔒 Security Review ✅ Completed 2026-11-07T15:29:51.555341Z 0395adb PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying simple-module-python with  Cloudflare Pages  Cloudflare Pages

Latest commit: 0395adb
Status: ✅  Deploy successful!
Preview URL: https://4735c515.simple-module-python.pages.dev
Branch Preview URL: https://fix-issue-869-mixed-expressi.simple-module-python.pages.dev

View logs

@antosubash
antosubash merged commit 4e3c7b8 into main Nov 7, 2026
17 checks passed
@antosubash
antosubash deleted the fix/issue-869-mixed-expression-index branch November 7, 2026 15:35
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.

db: an index mixing columns and an expression is reported as changed on every SQLite autogenerate

1 participant