Skip to content

Commit

Permalink
Support :pg_auto_parameterize_min_array_size Database option in pg_au…
Browse files Browse the repository at this point in the history
…to_parameterize_in_array extension to control minimum array size to handle

The default of converting only arrays with 2 or more elements was
to match PostgreSQL's handling of IN (value_list), where a single
element is converted to a scalar = expression, and multiple elements
are converted to a = ANY(ARRAY[expr1, expr2, ...]) expression.

Unfortunately, not doing the conversion for single element arrays
has the negative side effect of missing cases that you would
want to handle specially, if your tests only test cases where the
array has one element and not multiple elements.

This allows the user to choose the minimum size of the array to
be converted.  You could set it to 0 and not 1, but that wouldn't
convert the empty array case, as the type is not known.
  • Loading branch information
jeremyevans committed Feb 7, 2025
1 parent e1145bb commit 03d32d2
Show file tree
Hide file tree
Showing 3 changed files with 24 additions and 6 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
=== master

* Support :pg_auto_parameterize_min_array_size Database option in pg_auto_parameterize_in_array extension to control minimum array size to handle (jeremyevans)

* Add pg_eager_any_typed_array plugin, automatically transform eager loads to use = ANY(array_expr::type[]) instead of IN (value_list) (jeremyevans)

* Support :eager_loading_predicate_transform association option (jeremyevans)
Expand Down
13 changes: 9 additions & 4 deletions lib/sequel/extensions/pg_auto_parameterize_in_array.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,14 @@
# treating strings as text can break programs, since the type for
# literal strings in PostgreSQL is +unknown+, not +text+.
#
# The conversion is only done for single dimensional arrays that have more
# than two elements, where all elements are of the same class (other than
# nil values).
# The conversion is only done for single dimensional arrays that have two or
# more elements, where all elements are of the same class (other than
# +nil+ values). You can also do the conversion for arrays of 1 element by setting
# <tt>pg_auto_parameterize_min_array_size: 1</tt> Database option. This makes
# finding cases that need special handling easier, but it doesn't match
# how PostgreSQL internally converts the expression (PostgreSQL converts
# <tt>IN (single_value)</tt> to <tt>= single_value</tt>, not
# <tt>= ANY(ARRAY[single_value])</tt>).
#
# Related module: Sequel::Postgres::AutoParameterizeInArray

Expand Down Expand Up @@ -68,7 +73,7 @@ def complex_expression_sql_append(sql, op, args)
# The bound variable type string to use for the bound variable array.
# Returns nil if a bound variable should not be used for the array.
def _bound_variable_type_for_array(r)
return unless Array === r && r.size > 1
return unless Array === r && r.size >= (db.typecast_value(:integer, db.opts[:pg_auto_parameterize_min_array_size]) || 2)
classes = r.map(&:class)
classes.uniq!
classes.delete(NilClass)
Expand Down
15 changes: 13 additions & 2 deletions spec/extensions/pg_auto_parameterize_in_array_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -128,11 +128,22 @@
sql.must_equal 'SELECT * FROM "table" WHERE ("a" IN ($1::numeric))'
sql.args.must_equal [1]

sql = @db[:table].where(:a=>[1.0]).sql
sql.must_equal 'SELECT * FROM "table" WHERE ("a" IN ($1::numeric))'
sql = @db[:table].exclude(:a=>[1.0]).sql
sql.must_equal 'SELECT * FROM "table" WHERE ("a" NOT IN ($1::numeric))'
sql.args.must_equal [1]
end

it "should convert single value expressions in pg_auto_parameterize_min_array_size: 1" do
@db.opts[:pg_auto_parameterize_min_array_size] = 1
sql = @db[:table].where(:a=>[1.0]).sql
sql.must_equal 'SELECT * FROM "table" WHERE ("a" = ANY($1::numeric[]))'
sql.args.must_equal [[1]]

sql = @db[:table].exclude(:a=>[1.0]).sql
sql.must_equal 'SELECT * FROM "table" WHERE ("a" != ALL($1::numeric[]))'
sql.args.must_equal [[1]]
end

it "should not convert expressions with mixed types" do
sql = @db[:table].where(:a=>[1, 2.0]).sql
sql.must_equal 'SELECT * FROM "table" WHERE ("a" IN ($1::int4, $2::numeric))'
Expand Down

0 comments on commit 03d32d2

Please sign in to comment.