Support partial indexes with a WHERE clause#1154
Conversation
jwoertink
left a comment
There was a problem hiding this comment.
Thanks for taking this on! This will be a neat feature to get added in.
I do feel like some of this code is a bit unnecessary though. Since you're allowing both raw strings and a query object into the where, you don't know if a WHERE statement is being passed in or not, so you're having to move a ton of stuff around just to account for that. Then we now have these new bulky named methods that feel a bit out of place... Also, it sort of breaks a bit of the principle of Avram to try and catch errors at compile-time with the raw strings being front and center like that...
What if we did this... The where argument takes the query object and that's it. Then a second arg called where_raw takes in a raw string. Now the method knows when you're doing one over the other which could either call where.to_prepared_sql or WHERE #{where_raw}. With that, all of the changes to queryable and query_builder would just go away. That makes the primary target something that's compile-time safety, and we keep the escape hatch available while also making it very clear it's a bit more "dangerous". What are your thoughts on that?
| predicate = clone.where_predicate_sql! | ||
| if predicate.nil? | ||
| raise Avram::InvalidQueryError.new("Cannot build a partial index predicate: the query has no `where` conditions") | ||
| end |
There was a problem hiding this comment.
The raised error here points specifically towards an index, but this is now a public method on Builder which someone could run anywhere.
Add where: and where_raw: options to create_index and add_index so migrations can create PostgreSQL partial indexes, including conditional unique indexes (CREATE UNIQUE INDEX ... WHERE ...). Resolves issue luckyframework#1023. where: takes an Avram::Queryable for the compile-time-safe path; its WHERE conditions are rendered with values inlined as literals, since DDL cannot use bind parameters. where_raw: is the labeled escape hatch for a raw SQL predicate String. Passing both raises. To support the query form, QueryBuilder gains to_prepared_where_sql, which returns the WHERE conditions as a bare, inlined predicate. wheres_sql is refactored onto a shared where_predicate_sql so the predicate has a single source of truth.
0b967bc to
227bfd1
Compare
|
Went with the split: One caveat on the "changes to queryable and query_builder just go away" part. They shrink, but don't fully disappear. |
|
Ah, ok. Yeah, that makes sense then. |
Adds a
where:option tocreate_indexandadd_indexso migrations can create PostgreSQL partial indexes — including conditional unique indexes (CREATE UNIQUE INDEX ... WHERE ...), the original ask.Fixes #1023
API
where:accepts a raw SQL predicateStringor anAvram::Queryable:DDL predicates can't use bind parameters, so a query is rendered with its values inlined (new
QueryBuilder#to_prepared_where_sql). Only the query'swhereconditions are used (select/order/limitare ignored); a query with no conditions raises. The raw-Stringform avoids coupling a migration to an app query class; the query form is offered for ergonomics.Implementation
QueryBuilder#to_prepared_where_sqlreturns theWHEREconditions as a bare, inlined predicate;wheres_sqlnow builds on the samewhere_predicate_sql!, so there's a single source of truth.CreateIndexStatementemitsWHERE <predicate>after the column list (composes withunique,concurrently, customname, all index types).create_index/add_indexnormalize theString | Avram::Queryableoption through one shared helper.Tests
Specs cover both forms through
create_indexandadd_index, theQueryBuilder/Queryablepredicate extraction (boolean inlining,ORconjunctions,order/limitignored, raises-on-empty), and the conditional-unique SQL.🤖 PR Body Generated with Claude Code