Broken interpolation of variables on 10.0.

#431 ยท open ยท 3 comments

View on GitHub โ†—

Jack12816

Hey there ๐Ÿ‘‹ I've observed a bug which broke my application code while migrating from activerecord-postgis-adapter 9.0 to 10.0. Consider this: ```ruby # in a scope.. where("ST_Distance(lonlat, 'POINT(? ?)') < ?", lon, lat, meter_radius) ``` produces out of a sudden: ``` ActiveRecord::StatementInvalid: PG::InternalError: ERROR: parse error - invalid geometry LINE 1: ...NT(*) FROM "properties" WHERE ST_Distance(lonlat, 'POINT($1 ... ^ HINT: "POINT($1" <-- parse error at position 8 within geometry # /usr/local/bundle/gems/activerecord-7.2.2.1/lib/active_record/connection_adapters/postgresql_adapter.rb:894:in 'PG::Connection#exec_params' # /usr/local/bundle/gems/activerecord-7.2.2.1/lib/active_record/connection_adapters/postgresql_adapter.rb:894:in 'block (2 levels) in ActiveRecord::ConnectionAdapters::PostgreSQLAdapter#exec_no_cache' # /usr/local/bundle/gems/activerecord-7.2.2.1/lib/active_record/connection_adapters/abstract_adapter.rb:1004:in 'block in ActiveRecord::ConnectionAdapters::AbstractAdapter#with_raw_connection' # /usr/local/bundle/gems/activesupport-7.2.2.1/lib/active_support/concurrency/load_interlock_aware_monitor.rb:23:in 'Thread.handle_interrupt' # /usr/local/bundle/gems/activesupport-7.2.2.1/lib/active_support/concurrency/load_interlock_aware_monitor.rb:23:in 'block in ActiveSupport::Concurrency::LoadInterlockAwareMonitorMixin#synchronize' # /usr/local/bundle/gems/activesupport-7.2.2.1/lib/active_support/concurrency/load_interlock_aware_monitor.rb:19:in 'Thread.handle_interrupt' # /usr/local/bundle/gems/activesupport-7.2.2.1/lib/active_support/concurrency/load_interlock_aware_monitor.rb:19:in 'ActiveSupport::Concurrency::LoadInterlockAwareMonitorMixin#synchronize' # /usr/local/bundle/gems/activerecord-7.2.2.1/lib/active_record/connection_adapters/abstract_adapter.rb:976:in 'ActiveRecord::ConnectionAdapters::AbstractAdapter#with_raw_connection' # /usr/local/bundle/gems/activerecord-7.2.2.1/lib/active_record/connection_adapters/postgresql_adapter.rb:893:in 'block in ActiveRecord::ConnectionAdapters::PostgreSQLAdapter#exec_no_cache' # /usr/local/bundle/gems/activesupport-7.2.2.1/lib/active_support/notifications/instrumenter.rb:58:in 'ActiveSupport::Notifications::Instrumenter#instrument' # /usr/local/bundle/gems/activerecord-7.2.2.1/lib/active_record/connection_adapters/abstract_adapter.rb:1119:in 'ActiveRecord::ConnectionAdapters::AbstractAdapter#log' # /usr/local/bundle/gems/activerecord-7.2.2.1/lib/active_record/connection_adapters/postgresql_adapter.rb:892:in 'ActiveRecord::ConnectionAdapters::PostgreSQLAdapter#exec_no_cache' # /usr/local/bundle/gems/activerecord-7.2.2.1/lib/active_record/connection_adapters/postgresql_adapter.rb:872:in 'ActiveRecord::ConnectionAdapters::PostgreSQLAdapter#execute_and_clear' # /usr/local/bundle/gems/marginalia-1.11.1/lib/marginalia.rb:91:in 'Marginalia::ActiveRecordInstrumentation#execute_and_clear_with_marginalia' # /usr/local/bundle/gems/activerecord-7.2.2.1/lib/active_record/connection_adapters/postgresql/database_statements.rb:66:in 'ActiveRecord::ConnectionAdapters::PostgreSQL::DatabaseStatements#internal_exec_query' # /usr/local/bundle/gems/activerecord-7.2.2.1/lib/active_record/connection_adapters/abstract/database_statements.rb:647:in 'ActiveRecord::ConnectionAdapters::DatabaseStatements#select' # /usr/local/bundle/gems/activerecord-7.2.2.1/lib/active_record/connection_adapters/abstract/database_statements.rb:73:in 'ActiveRecord::ConnectionAdapters::DatabaseStatements#select_all' # /usr/local/bundle/gems/activerecord-7.2.2.1/lib/active_record/connection_adapters/abstract/query_cache.rb:251:in 'ActiveRecord::ConnectionAdapters::QueryCache#select_all' # /usr/local/bundle/gems/activerecord-7.2.2.1/lib/active_record/relation/calculations.rb:486:in 'block (2 levels) in ActiveRecord::Calculations#execute_simple_calculation' # /usr/local/bundle/gems/activerecord-7.2.2.1/lib/active_record/connection_adapters/abstract/connection_pool.rb:415:in 'ActiveRecord::ConnectionAdapters::ConnectionPool#with_connection' # /usr/local/bundle/gems/activerecord-7.2.2.1/lib/active_record/connection_handling.rb:296:in 'ActiveRecord::ConnectionHandling#with_connection' # /usr/local/bundle/gems/activerecord-7.2.2.1/lib/active_record/relation/calculations.rb:485:in 'block in ActiveRecord::Calculations#execute_simple_calculation' # /usr/local/bundle/gems/activerecord-7.2.2.1/lib/active_record/relation.rb:1452:in 'ActiveRecord::Relation#skip_query_cache_if_necessary' # /usr/local/bundle/gems/activerecord-7.2.2.1/lib/active_record/relation/calculations.rb:484:in 'ActiveRecord::Calculations#execute_simple_calculation' # /usr/local/bundle/gems/activerecord-7.2.2.1/lib/active_record/relation/calculations.rb:441:in 'ActiveRecord::Calculations#perform_calculation' # /usr/local/bundle/gems/activerecord-7.2.2.1/lib/active_record/relation/calculations.rb:245:in 'ActiveRecord::Calculations#calculate' # /usr/local/bundle/gems/activerecord-7.2.2.1/lib/active_record/relation/calculations.rb:102:in 'ActiveRecord::Calculations#count' ``` The produced log looks interesting: ``` D, [2025-06-26T12:22:52.265829 #817] DEBUG -- : Property Count (0.5ms) /*line:spec/models/property/search_spec.rb:106*/ SELECT COUNT(*) FROM "properties" WHERE ST_Distance(lonlat, 'POINT($1 $2)') < $3 [[nil, 12.2821163], [nil, 51.36048700000001], [nil, 5000]] ^-- this should be $1, $2, $n.., but its nil instead ``` When I generate the sql only everything looks fine: ```ruby where("ST_Distance(lonlat, 'POINT(? ?)') < ?", lon, lat, meter_radius).to_sql # SELECT "properties".* FROM "properties" # WHERE ST_Distance(lonlat, 'POINT(12.2821163 51.36048700000001)') < 5000 # ORDER BY "properties"."created_at" ASC ``` To get it back working I had to fiddle around with rgeo to generate the point representation as string which I can pass to AR: ```ruby where('ST_Distance(lonlat, ?) < ?', RGeo::Geographic::Factory.new('Spherical').point(lon, lat).to_s, meter_radius) ``` but this looks like a regression to me, because there is no changelog here which points this out. --- Env details: Ruby: 3.4.4 (2025-05-14 revision a38531fd3f) +PRISM [x86_64-linux] Rails: 7.2.2.1 activerecord-postgis-adapter: 10.0.1 PostgreSQL: 17.5 (Debian 17.5-1.pgdg120+1) PostGiS: 3.5.2+dfsg-1.pgdg120+1 (postgresql-17-postgis-3)

Comments

pedros007

Just curious if this still happens if you convert to strings: ```ruby where("ST_Distance(lonlat, 'POINT(? ?)') < ?", lon.to_s, lat.to_s, meter_radius.to_s) ``` If that fixes things perhaps [this string quoting patch](https://github.com/rgeo/activerecord-postgis-adapter/blob/5d380dad7ab1edbd8331ef9aa9077c2a91d66ee8/lib/active_record/connection_adapters/postgis/quoting.rb#L12) needs some work.

Jack12816

Tested the string casting of the parameters this morning like suggest, but it didn't do the trick. The same error remains.

pedros007

Huh, that is a weird one! I just tried myself ```ruby x = 12.2821163; y = 51.36048700000001; z = 5000 SomeModel.where("ST_Distance('POINT(12.2821163 51.36048700000001)', 'POINT(? ?)') < ?", x, y, z) SELECT "some_models".* FROM "some_models" WHERE (ST_Distance('POINT(12.2821163 51.36048700000001)', 'POINT($1 $2)') < $3) /* loading for pp */ LIMIT $4 [[nil, 12.2821163], [nil, 51.36048700000001], [nil, 5000], ["LIMIT", 11]] An error occurred when inspecting the object: #<ActiveRecord::StatementInvalid:"PG::IndeterminateDatatype: ERROR: could not determine data type of parameter $1\n"> ``` but doing unsafe string interpolation directly works: ```ruby x = 12.2821163; y = 51.36048700000001; z = 5000 SomeModel.where("ST_Distance('POINT(12.2821163 51.36048700000001)', 'POINT(#{x} #{y})') < #{z}") ``` activerecord-postgis-adapter (11.0.0) Ruby: ruby 3.3.8 (2025-04-09 revision b200bad6cd) [x86_64-linux] Rails: 8.0.2 activerecord-postgis-adapter: 10.0.1 PostgreSQL: 13.15 on x86_64-pc-linux-musl PostGiS: 3.4.2 Sharing this brain dump in case it's helpful for someone else to continue debugging. I tried the following in a rails console: ```ruby trace = TracePoint.new(:line) do |tp| if tp.path == "/usr/local/bundle/gems/activerecord-postgis-adapter-11.0.0/lib/active_record/connection_adapters/postgis/quoting.rb" && tp.lineno == 8 binding.break end end trace.enable x = 12.2821163; y = 51.36048700000001; z = 5000 SomeModel.where("ST_Distance('POINT(12.2821163 51.36048700000001)', 'POINT(? ?)') < ?", x, y, z) ``` and then stepping through it does look like the quoting is being delegated to https://github.com/rails/rails/blob/7f309f0d481b8b6cd74e36eb16c2c031ba57bd76/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb#L102 However, the query ultimately winds up with that first `$1` argument resolving to `[nil, 12.2821163]` when it should be something like `["SOME_STRING", 12.2821163]`. The closest issue I could discover in ActiveRecord is https://github.com/rails/rails/issues/52643, but maybe that's some entirely different problem. I'm confused why it's resolving to [the abstract adapter](https://github.com/rails/rails/blob/main/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb) and not [the postgres adapter](https://github.com/rails/rails/blob/7f309f0d481b8b6cd74e36eb16c2c031ba57bd76/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb#L111-L116), which has its own serialization method for Numeric types. If it go to that Postgres adapter perhaps it would just work? One common pattern between this issue & other recent quoting issues might be around [ActiveRecord-7.2](https://github.com/rails/rails/blob/v7.2.0/activerecord/CHANGELOG.md), but I am not able to try and bisect the specific commit/version that caused probems and nothing jumps out in the AR-7.2 changelog.