Add syntax for CTE materialization hint to ActiveRecord#48305
Add syntax for CTE materialization hint to ActiveRecord#4830597jaz wants to merge 1 commit intorails:mainfrom
Conversation
Following on the Arel work in rails#48261, this commit adds the ability to provide a materialization hint to a CTE in ActiveRecord. To support the hint, a more verbose syntax for CTE values is introduced: Post.with(t: { query: Post.where("comments_count > ?", 0), materialized: true }) The CTE value is described as a hash that contains a required :query key and an optional :materialized key. The set of available options could be expanded in the future to accomodate, for example, postgres's various options for recursive CTEs.
|
I'm not a huge fan of the nested Here's an alternative idea: Post.with(posts_with_comments: Post.where("comments_count > ?", 0), materialized: true)This would materialize all CTEs mentioned in this particular Post.with(posts_with_comments: Post.where("comments_count > ?", 0), materialized: true)
.with(posts_without_comments: Post.where("comments_count = ?", 0), materialized: false)It reads a bit nicer in my opinion. However, it also makes it impossible to use |
Yeah, this is why I chose the more verbose syntax (which, I agree is ungainly). In particular, conflating the namespace for CTE names with the namespace for options makes adding new options backwards-incompatible. Strictly speaking, we could use a mixture of hash and keyword arguments, like: def with(*args, materialized: nil)
[...]
endIn which case, a caller can distinguish between use of the keyword and use of the hash by being more explicit with the hash syntax: x.with({ materialized: [...] }, materialized: true)... but, then, it would still be dangerous to add new options, since we wouldn't expect programmers to use the explicit (braced) syntax most of the time. |
|
Another option in the design space is to change the definition of def with(cte_hash, opts = {})That is, change x.with(materialized: Post.all, { materialized: true })This is, however, incompatible with the current definition, which takes a variable number of CTE hashes. |
|
@matthewd Don't know if you have an opinion on this, but since you commented on the arel part, I figured I'd ask. |
|
So I've been pondering this, and I find myself drawn in somewhat the opposite direction I pushed with #48261... 🤔 I kinda like this:
If we did go this way (a The particular reason I'm revisiting the idea of a Relation#materialized is not CTEs at all. Rather, I'm exploring the thought of trying to support it, where feasible, as a general optimization barrier. Where the query is being passed into a CTE, we can obviously achieve that via our new Thoughts? Does this sound useful, or just a fruitless mental detour? |
|
I see the appeal of |
|
Hello @97jaz! Are there plans to move this forward? |
|
@erick-c-mc After my last comment, I was waiting to see if @matthewd had a response, and then I kind of forgot about it. |
|
I ended up needing this feature in one of my projects recently, reading through the conversations I was wondering if we could consider accepting It would look like this: Post.with(Post.where("comments_count > ?", 0).as_cte(:post_with_comment))
Post.with(Post.where("comments_count > ?", 0).as_cte(:post_with_comment, materialized: true))
Post.with(Post.where("comments_count > ?", 0).as_cte(:post_with_comment, materialized: false))
Post.with_recursive(Post.where("comments_count > ?", 0).as_cte(:post_with_comment))
Post.with_recursive(Post.where("comments_count > ?", 0).as_cte(:post_with_comment, materialized: true))
Post.with_recursive(Post.where("comments_count > ?", 0).as_cte(:post_with_comment, materialized: false))I have a PoC here: https://github.com/Jell/rails/pull/1/files if you think that could be a good approach I could try to polish it and make a PR, or feel free to steal my code if you think it's a reasonable approach! |
|
that approach would be totally backward compatible, and allow mixing Post.with(
Post.where("comments_count > ?", 0).as_cte(:post_with_comment, materialized: true),
posts_without_comments: Post.where("comments_count = ?", 0)
) |
|
@Jell Sorry -- I haven't looked at this, but I like the idea. |
|
@Jell Okay, now I have looked at it, and I like it even more. |
|
@Jell Are you planning to put up a PR? |
I wasn't but I can give it a shot now! It should just be a matter of rebasing the PR I made, split the change into a better commit history and write some tests... |
Motivation / Background
Following on the Arel work in #48261, this commit adds the ability to provide a materialization hint to a CTE in ActiveRecord. To support the hint, a more verbose syntax for CTE values is introduced:
The CTE value is described as a hash that contains a required
:querykey and an optional:materializedkey. The set of available options could be expanded in the future to accommodate, for example, postgres's various options for recursive CTEs.Additional information
I don't know whether there's any appetite within the core team to expose this functionality in AR's public API. But if there is, this approach seemed like the most plausible choice, given the current
#withinterface. That said, I'm more than happy to talk about alternatives.Checklist
Before submitting the PR make sure the following are checked:
[Fix #issue-number]