Skip to content
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

Make preload query to prepared statements #26098

Closed
wants to merge 1 commit into from

Conversation

kamipo
Copy link
Member

@kamipo kamipo commented Aug 9, 2016

Currently preload query cannot be prepared statements even if
prepared_statements: true. This commit fixes the issue.

@rails-bot
Copy link

r? @eileencodes

(@rails-bot has picked a reviewer for you, use r? to override)

@sgrif
Copy link
Contributor

sgrif commented Aug 11, 2016

Instead of handling it there, can we fix the predicate builder to use prepared statements any time it's passed an array of one item?

@kamipo
Copy link
Member Author

kamipo commented Aug 11, 2016

It is possible, but more complicated. To decouple building Arel ASTs from uniqueness validator, the predicate builder needs to support array and range types. #26072
It seems the handling of arrays in the predicate builder becomes complicated.

@kamipo kamipo force-pushed the make_preload_query_to_preparable branch from 0487606 to 3d1c960 Compare August 11, 2016 19:59
@kamipo
Copy link
Member Author

kamipo commented Aug 11, 2016

I tried to fix the predicate builder to handle an array of one item. Now the predicate builder uses prepared statements any time when passed an array of one item. Thanks!

@rafaelfranca
Copy link
Member

r? @sgrif

@rails-bot rails-bot assigned sgrif and unassigned eileencodes Aug 16, 2016
@sgrif
Copy link
Contributor

sgrif commented Aug 16, 2016

Can you rebase?

@kamipo kamipo force-pushed the make_preload_query_to_preparable branch from 3d1c960 to ec38743 Compare August 16, 2016 09:44
@kamipo
Copy link
Member Author

kamipo commented Aug 16, 2016

Rebased!

@matthewd
Copy link
Member

Does this conflict with my desire to use prepared statements for all array predicates, by binding the array as a single value, on array-supporting RDBMSes? 😕

when value.is_a?(Array) && !table.type(column_name).respond_to?(:subtype)
if value.size == 1 && can_be_bound?(column_name, value.first)
result[column_name] = Arel::Nodes::BindParam.new
binds << build_bind_param(column_name, value.first)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If RDBMSes supports an array type and the column is an array type, it is bound as an array value.

  PgArray.find_by(tags: ["black"])
  # => SELECT  "pg_arrays".* FROM "pg_arrays" WHERE "pg_arrays"."tags" = $1 LIMIT $2  [["tags", "{black}"], ["LIMIT", 1]]

@matthewd
Copy link
Member

@kamipo I mean where we currently do:

User.where(id: [1, 2])
# => SELECT ... WHERE id IN (1, 2)

I'd like to instead produce:

User.where(id: [1, 2])
# => SELECT ... WHERE id = ANY($1)   ["{1,2}"]

.. as this allows us to prepare a statement once, and not need a new one for different array lengths.

It's currently just a future thought, but I wondered if introducing a condition here might make it harder to do the above (which obviously must be adapter specific) later.

@kamipo
Copy link
Member Author

kamipo commented Aug 17, 2016

I think it is easy to support the above later by the condition.

--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -96,7 +96,7 @@ def create_binds_for_hash(attributes)
           when value.is_a?(Relation)
             binds += value.bound_attributes
           when value.is_a?(Array) && !table.type(column_name).respond_to?(:subtype)
-            if value.size == 1 && can_be_bound?(column_name, value.first)
+            if !supports_array_types? && value.size == 1 && can_be_bound?(column_name, value.first)
               result[column_name] = Arel::Nodes::BindParam.new
               binds << build_bind_param(column_name, value.first)
             end

@kamipo kamipo force-pushed the make_preload_query_to_preparable branch 2 times, most recently from 4736e3d to 384268a Compare September 11, 2016 05:59
@@ -95,6 +95,11 @@ def create_binds_for_hash(attributes)
next
when value.is_a?(Relation)
binds += value.bound_attributes
when value.is_a?(Array) && !table.type(column_name).respond_to?(:subtype)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should not be relying on the internals of type objects here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If removed the type condition, test_where_by_attribute_with_array fails.
https://github.com/rails/rails/blob/master/activerecord/test/cases/adapters/postgresql/array_test.rb#L293-L297

  def test_where_by_attribute_with_array
    tags = ["black", "blue"]
    record = PgArray.create!(tags: tags)
    assert_equal record, PgArray.where(tags: tags).take
  end
# correct
SELECT  "pg_arrays".* FROM "pg_arrays" WHERE "pg_arrays"."tags" = $1 LIMIT $2  [["tags", "{black,blue}"], ["LIMIT", 1]]

# removed type condition
SELECT  "pg_arrays".* FROM "pg_arrays" WHERE "pg_arrays"."tags" IN ('black', 'blue') LIMIT $1  [["LIMIT", 1]]

@sgrif
Copy link
Contributor

sgrif commented Dec 8, 2016 via email

@kamipo kamipo force-pushed the make_preload_query_to_preparable branch 4 times, most recently from 64c5735 to fe930aa Compare March 28, 2017 04:01
@kamipo kamipo force-pushed the make_preload_query_to_preparable branch from fe930aa to 2f72c1c Compare April 15, 2017 08:38
@kamipo kamipo force-pushed the make_preload_query_to_preparable branch from 2f72c1c to 955bf9c Compare May 6, 2017 16:42
Currently preload query cannot be prepared statements even if
`prepared_statements: true`. This commit fixes the issue.
@kamipo kamipo force-pushed the make_preload_query_to_preparable branch from 955bf9c to da389e4 Compare May 7, 2017 21:46
@@ -112,6 +112,12 @@ def create_binds_for_hash(attributes)
binds.concat(bvs)
attrs
end
when value.is_a?(Array) && !table.type(column_name).respond_to?(:subtype)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd really prefer not to add more code which relies on type internals in this way. I think the code below this which does the same is a major smell. There's a greater problem with the structure here that we should address first.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that there's a problem with the structure.
How about the alternative #29033?
#29033 is a simple one line fix and we already have a similar code (for keeping existing expectation:

associated_table.association_foreign_key.to_s => ids.size > 1 ? ids : ids.first
).
I'd like to add the expectation that typical generated queries will be preparable even if association queries.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I like #29033. Should we close this one?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I'm closing this for now.

@kamipo
Copy link
Member Author

kamipo commented Jul 18, 2017

Closing in favor of #29033.

@kamipo kamipo closed this Jul 18, 2017
@kamipo kamipo deleted the make_preload_query_to_preparable branch July 18, 2017 19:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants