oracle-enhanced icon indicating copy to clipboard operation
oracle-enhanced copied to clipboard

Oracle12 visitor - Combination of limit and lock is not supported

Open akostadinov opened this issue 4 years ago • 6 comments

Oracle12 Arel visitor does not handle sub-queries with LIMIT properly and this makes locking a row impossible. While it works in normal queries, in sub-queries that doesn't seem to be the case.

Steps to reproduce

# just a minimal project `rails new --database=oracle
git clone https://github.com/akostadinov/rails-ora-02014.git
cd rails-ora-02014
vi config/database.yml # set your DB connection details
rails c
> a = Article.create!(title: "test", body: "test body")
> a.with_lock {}

Expected behavior

  Article Load (1.8ms)  SELECT "ARTICLES".* FROM "ARTICLES" WHERE "ARTICLES"."ID" = :a1 AND ROWNUM <= :a2  FOR UPDATE  [["id", 23], ["LIMIT", 1]]
=> nil

Actual behavior

Traceback (most recent call last):
/home/avalon/.asdf/installs/ruby/2.7.3/lib/ruby/gems/2.7.0/gems/activerecord-oracle_enhanced-adapter-7.0.0/lib/arel/visitors/oracle12.rb:10:in `visit_Arel_Nodes_SelectStatement': Combination of limit and lock is not supported. Because generated SQL statements (ArgumentError)
`SELECT FOR UPDATE and FETCH FIRST n ROWS` generates ORA-02014.

Possible workaround - use Arel::Visitors::Oracle

Arel::Visitors::Oracle works fine though because it always translates LIMIT statements to ROWNUM.

The example above properly works, if we add to application.rb:

ActiveSupport.on_load(:active_record) do
  ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.class_eval do
    # Use old visitor for Oracle 12c database
    self.use_old_oracle_visitor = true
  end
end

System configuration

Oracle Enhanced Adapter version 7.0.0

Rails version: Rails 7.0.1

Ruby version: ruby 2.7.3p183 (2021-04-05 revision 6847ee089d) [x86_64-linux]

Oracle version: 19.3.0

akostadinov avatar Jan 07 '22 19:01 akostadinov

Seems to be related also to #1070

akostadinov avatar Feb 02 '22 11:02 akostadinov

@yahonda Can we get a little clarification on this lock subject. Most of the discussions on it are pretty old, and I can't seem to come up with a definitive approach to get row locking to work.

Most of my errors are ArgumentError (Combination of limit and lock is not supported. Because generated SQL statements) SELECT FOR UPDATE and FETCH FIRST n ROWS generates ORA-02014.

Doesn't matter, if I find_by_id, where(id: ) etc etc. Is with_lock just not supported? A little guidance would help. I can volunteer to put in a patch if I know what's the latest state on this and what's the history behind it.

AJ

ajaya avatar Jan 06 '23 17:01 ajaya

@yahonda

I see a possible solution from 2016. https://github.com/rsim/oracle-enhanced/issues/1066

Is this still the current approach?

AJ

ajaya avatar Jan 06 '23 17:01 ajaya

One possible approach is using a query without limits. Assuming you're using where on a unique value it shouldn't really make a difference in amount of objects returned (and if you're not doing it on a unique value then the setting a limit of 1 is not going to reliably work anyway).

a = Article.create!(title: "test", body: "test body")
a = Article.lock(true).where(id: a.id).to_a[0]
...
a.save! # Make sure to unlock the row (if you are running in a transaction then ending the transaction should work too I think)

joerixaop avatar Feb 02 '23 11:02 joerixaop