New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow PredicateBuilder to recognize schema namespaced table names #48171
Conversation
904e308
to
0fbcb51
Compare
@@ -30,7 +30,7 @@ def self.references(attributes) | |||
if value.is_a?(Hash) | |||
result << Arel.sql(key) | |||
elsif key.include?(".") | |||
result << Arel.sql(key.split(".").first) | |||
result << Arel.sql(key.split(".")[0..-2].join(".")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think [0..-2]
is a bit hard to grasp. Given we do this in two places it might make sense to extract a small private method here, e.g.
def split_column_name(key)
*table, column = key.split(".")
[table.join("."), column]
end
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At a glance I feel key.split(".")[0..-2].join(".")
is a bit expensive. How about String#[]
like idx = key.rindex("."); key[0, idx]
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was a little meh on the split as well. I like the rindex idea, can rewrite that way. I just kept it since it was originally written that way. We could extract a method, but in one usage we need to return both the table and column, but in the other the column is ignored, so it might be wasteful to make an array and another string that we'd throw away.
4a8899e
to
1a06b1e
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please squash your commits.
dot_notation = attributes.each_with_object({}) do |(k, v), dn| | ||
next if v.is_a?(Hash) | ||
idx = k.rindex(".") | ||
dn[k] = idx if idx | ||
end | ||
|
||
dot_notation.each_key do |key| | ||
table_name, column_name = key.split(".") | ||
dot_notation.each do |key, idx| | ||
table_name, column_name = key[0, idx], key[idx + 1, key.length] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't we merge these two loops and avoid the temporary Hash?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can't mutate the original attributes hash while iterating over it. But maybe we could save a hash allocation on L163 with #merge!
? I'm always hesitant to mutate things getting passed in if I don't know all of the contexts where it would get used, but if we're already mutating attributes
that are passed to #build_from_hash
maybe that's already fine?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if we're already mutating attributes that are passed to #build_from_hash maybe that's already fine?
Yeah that was my point. But also I don't think we need to mutate attributes
at all, just iterate once over it and create the returned hash as we go.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah I gotcha. Updated to build a converted hash in one pass.
781c082
to
dcc429f
Compare
if !value.is_a?(Hash) && (idx = key.rindex(".")) | ||
table_name, column_name = key[0, idx], key[idx + 1, key.length] | ||
converted[table_name] ||= {} | ||
converted[table_name] = converted[table_name].merge(column_name => value) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason why you don't do:
converted[table_name] = converted[table_name].merge(column_name => value) | |
converted[table_name][column_name => value] |
? That would avoid lots of Hash allocations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Beyond the hash allocations, this single pass rewrite is breaking other tests. It's past midnight, going to revert and try again later. The way I'm attempting a single pass is causing keys to be overwritten. For example this is failing: https://github.com/sholden/rails/blob/predicate-builder-schemas/activerecord/test/cases/finder_test.rb#L1120
One comment, other than that LGTM. |
04ab481
to
8b8d15b
Compare
Ok, I think this latest version is optimal as far as creating the converted hash in one pass with as few hash allocations as possible, albeit a little less readable. If it looks good I'll squash and clean up. I also added a test to ensure that we don't accidentally mutate the arguments originally passed to |
Yeah, looks good now 👍. Thanks a lot. |
9c82468
to
c5a99e0
Compare
No problem. Think we're good to go then! |
Motivation / Background
ActiveRecord::PredicateBuilder
currently assumes that a column name will only be specified in dot notation with a single period. If a column is specified in dot notation for a table that is namespaced in a schema, it will use the schema name as the table name. This PR allows columns to specified in the formatschema.table.column
as well astable.column
.This fixes #48172
Detail
This Pull Request changes the behavior of
ActiveRecord::PredicateBuilder.references
andActiveRecord::PredicateBuilder#convert_dot_notation_to_hash
I didn't add this to the CHANGELOG, but if a reviewer thinks it's significant enough I'm happy to do so.
Checklist
Before submitting the PR make sure the following are checked:
[Fix #issue-number]