Skip to content

docs: note where generated migrations do not escape resource SQL - #880

Merged
zachdaniel merged 1 commit into
ash-project:mainfrom
grempe:migration-sql-escaping-docs
Oct 4, 2026
Merged

zachdaniel merged 1 commit into
ash-project:mainfrom
grempe:migration-sql-escaping-docs

Conversation

@grempe

@grempe grempe commented Oct 4, 2026

Copy link
Copy Markdown
Contributor

Follow-up to #876, as you suggested there: a 3.0: comment at each place the migration generator writes resource SQL into the migration without escaping it, and docs for the options that reach those places. No behaviour changes.

To find the places, I went through everything in lib/migration_generator/ (and the helpers it calls) that writes text from the resource into the migration source, then checked each SQL site against the generator with a backslash, a " and #{ in the input.

3.0: comments (lib/migration_generator/operation.ex)

Site Input Written into
AddCheckConstraint.up/1 check, base_filter_sql check: """ heredoc
RemoveCheckConstraint.down/1 the same the same
AddCustomStatement.up/1, down/1 (code?: false) statement up, down execute(""" heredoc
AddUniqueIndex.up/1, base filter without an identity where base_filter_sql where: "..."
AddUniqueIndex.up/1, temporal exclusion constraint identity_wheres_to_sql, base_filter_sql, calculations_to_sql execute("...")

Docs

check (check constraints), up and down (custom statements), and base_filter_sql, identity_wheres_to_sql and calculations_to_sql (the postgres section) now say how the SQL is written into the migration and how to escape it, with check: ~S(code ~ '^\\d{4}$') as the example. documentation/dsls/DSL-AshPostgres.DataLayer.md is regenerated.

Things that came up, for you to decide

  • Temporal identities, new in 2.14.0. The exclusion constraint goes into execute("...") as is. A quoted identifier in an identity's where SQL makes the generated migration fail to parse, and \s in calculations_to_sql reaches Postgres as a space. The non-temporal branches escape the same SQL through option/2. Since this shipped in 2.14.0, it may be early enough to escape it now rather than in 3.0; I can send that separately if you'd like.
  • base_filter_sql is escaped in some places and not others. It is escaped in custom indexes and in an identity's unique index when the identity has a where, but not in check constraints, an identity's unique index without a where, or temporal identities. So on a resource with both kinds of site, no single way of writing a base filter that contains a backslash comes out right everywhere. The docs say so.
  • For the 3.0 change itself:
    • A ~S""" heredoc still ends at SQL that has """ at the start of a line.
    • SerialSequenceTransition renders through AddCustomStatement and relies on #{prefix()} being interpolated at runtime, so escaping there has to leave those statements alone. The comment there says so.
  • Not marked: identifiers. Table, schema, index, constraint and reference names, match_with columns and extension names are also written raw into some execute("...") and name: "..." strings, for example in AddPrimaryKey, RenameUniqueIndex, AlterDeferrability, AddTemporalForeignKey and the extension migrations. They only matter for names with unusual characters, so I left them out; happy to mark them too.

Checks

Run on main at c0686d2:

  • mix format --check-formatted, mix spark.cheat_sheets --check, mix spark.formatter --check, mix credo --strict and mix sobelow all pass. Dialyzer was not run locally; this change only adds comments and docs.
  • test/migration_generator_test.exs and test/temporal_migration_generator_test.exs pass (120 tests).
  • The full suite: 1135/1139. The four failures (UniqAggregateSortTest x2, JoinSubquerySortTest, AshSql.AggregateTest) fail the same way on main without this change.

Contributor checklist

Leave anything that you believe does not apply unchecked.

  • I accept the AI Policy, or AI was not used in the creation of this PR.
  • Bug fixes include regression tests
  • Chores
  • Documentation changes
  • Features include unit/acceptance tests
  • Refactoring
  • Update dependencies

The migration generator writes some SQL from the resource into the
migration's Elixir source as is, so Elixir reinterprets backslash escapes
and `#{` in it, and a `"` can end a double-quoted string. Fixing that is a
breaking change for anyone who escapes the SQL themselves, so this marks
each site with a `3.0:` comment and documents the current behaviour on the
options that reach them: `check`, custom statement `up` and `down`,
`base_filter_sql`, `identity_wheres_to_sql` and `calculations_to_sql`.

Refs ash-project#876.
@zachdaniel
zachdaniel merged commit 3e60c9c into ash-project:main Oct 4, 2026
121 of 126 checks passed
@zachdaniel

Copy link
Copy Markdown
Contributor

Perfect 👍

🚀 Thank you for your contribution! 🚀

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.

2 participants