sequel-activerecord_connection 1.0.0 → 1.2.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 929f5047395464d1d3616aeab41742b6766ae14b96ca1f5b7ac47bb1991f3391
4
- data.tar.gz: c931c23d71b73ce2bb35118e1d02ecc2df529f2c297b995c9c5ee46c5b0bfe85
3
+ metadata.gz: 4bb2b81760b45990a9bea2801f8a243cfc180853ad450d652cf888cd45844033
4
+ data.tar.gz: 73ae4c6341a3c694423b4e20da86f01cce3324d37a9a29143eb2f2e420d45288
5
5
  SHA512:
6
- metadata.gz: 062f919b4268a57e392bad5dd7d4a988adc90a8df2d4076f5abfa9afe7c125534f9e7ad1ca16733d0539e622f2b988f95d4653d6b076cd356809d95daf1c24d2
7
- data.tar.gz: 1f1efabd6b3ae74c6d35b3d0a23ad87a9d1c939e209afc2f24267840c6c72b2ff60fb1ca86010335b65a251c3c57fa81b300aa4cf38b9ad49df53b00f931d89f
6
+ metadata.gz: 4e27142472fabc36703d3cca6828e005858d60088a88bbb8f78c58dba5f6e9a2b2ae349ff6d39451558d7dd3e4e32e92d49818af88af9bb6bc18a522c283bc38
7
+ data.tar.gz: 6dff47d6fe0adf995dc3738e932bda92b15279b56bdf22750e6c42058031ca2b2f152e26cd80b2a09e3be460a1be8695a3645df8a2bcd4a87430ebfc3243fa98
@@ -1,3 +1,31 @@
1
+ ## 1.2.2 (2021-01-11)
2
+
3
+ * Ensure Active Record queries inside a Sequel transaction are typemapped correctly in postgres adapter (@janko)
4
+
5
+ * Fix executing Active Record queries inside a Sequel transaction not working in mysql2 adapter (@janko)
6
+
7
+ ## 1.2.1 (2021-01-10)
8
+
9
+ * Fix original mysql2 query options not being restored after nested `DB#synchronize` calls, e.g. when using Sequel transactions (@janko)
10
+
11
+ ## 1.2.0 (2020-11-15)
12
+
13
+ * Attempt support for [activerecord-sqlserver-adapter](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter) (@janko)
14
+
15
+ * Attempt support for [oracle-enhanced](https://github.com/rsim/oracle-enhanced) Active Record adapter (@janko)
16
+
17
+ ## 1.1.0 (2020-11-08)
18
+
19
+ * Drop support for Ruby 2.2 (@janko)
20
+
21
+ * Support transaction/savepoint hooks even when Active Record holds the transaction/savepoint (@janko)
22
+
23
+ * Don't test the connection on `Sequel.connect` by default (@janko)
24
+
25
+ ## 1.0.1 (2020-10-28)
26
+
27
+ * Use Active Record connection lock in `Database#synchronize` (@janko)
28
+
1
29
  ## 1.0.0 (2020-10-25)
2
30
 
3
31
  * Clear AR statement cache on `ActiveRecord::PreparedStatementCacheExpired` when Sequel holds the transaction (@janko)
data/README.md CHANGED
@@ -1,25 +1,25 @@
1
- # Sequel::ActiveRecordConnection
1
+ # sequel-activerecord_connection
2
2
 
3
3
  This is an extension for [Sequel] that allows it to reuse an existing
4
- ActiveRecord connection for database interaction. It works on ActiveRecord 4.2
5
- or higher, and supports the built-in `postgresql`, `mysql2` and `sqlite3`
6
- adapters, as well as JDBC adapter for JRuby.
4
+ ActiveRecord connection for database interaction.
7
5
 
8
6
  This can be useful if you're using a library that uses Sequel for database
9
- interaction (e.g. [Rodauth]), but you want to avoid creating a separate
10
- database connection. Or if you're transitioning from ActiveRecord to Sequel,
11
- and want the database connection to be shared.
7
+ interaction (e.g. [Rodauth] or [rom-sql]), but you want to avoid creating a
8
+ separate database connection. Or if you're transitioning from ActiveRecord to
9
+ Sequel, and want the database connection to be shared.
12
10
 
13
- Note that this is a best-effort implementation, so some discrepancies are still
14
- possible. That being said, this implementation passes Rodauth's test suite
15
- (for all adapters), which has some fairly advanced Sequel usage.
11
+ It works on ActiveRecord 4.2+ and fully supports PostgresSQL, MySQL and SQLite
12
+ adapters, both the native ones and JDBC (JRuby). There is attempted suppport
13
+ for [Oracle enhanced] and [SQL Server] Active Record adapters (`oracle` and
14
+ `tinytds` in Sequel). Other adapters might work too, but their integration
15
+ hasn't been tested.
16
16
 
17
17
  ## Installation
18
18
 
19
19
  Add this line to your application's Gemfile:
20
20
 
21
- ```ruby
22
- gem "sequel-activerecord_connection", "~> 0.3"
21
+ ```rb
22
+ gem "sequel-activerecord_connection", "~> 1.0"
23
23
  ```
24
24
 
25
25
  And then execute:
@@ -111,31 +111,71 @@ DB.transaction(isolation: :serializable) do
111
111
  end
112
112
  ```
113
113
 
114
- One caveat to keep in mind is that using Sequel's transaction/savepoint hooks
115
- currently don't work if ActiveRecord holds the corresponding
116
- transaction/savepoint. This is because it's difficult to be notified when
117
- ActiveRecord commits or rolls back the transaction/savepoint.
114
+ When registering transaction hooks, they will be registered on Sequel
115
+ transactions when possible, in which case they will behave as described in the
116
+ [Sequel docs][sequel transaction hooks].
118
117
 
119
118
  ```rb
119
+ # Sequel: An after_commit transaction hook will always get executed if the outer
120
+ # transaction commits, even if it's added inside a savepoint that's rolled back.
120
121
  DB.transaction do
121
- DB.after_commit { ... } # will get executed
122
+ ActiveRecord::Base.transaction(requires_new: true) do
123
+ DB.after_commit { puts "after commit" }
124
+ raise ActiveRecord::Rollback
125
+ end
122
126
  end
123
-
124
- DB.transaction do
125
- DB.transaction(savepoint: true) do
126
- DB.after_commit(savepoint: true) { ... } # will get executed
127
+ #>> BEGIN
128
+ #>> SAVEPOINT active_record_1
129
+ #>> ROLLBACK TO SAVEPOINT active_record_1
130
+ #>> COMMIT
131
+ #>> after commit
132
+
133
+ # Sequel: An after_commit savepoint hook will get executed only after the outer
134
+ # transaction commits, given that all enclosing savepoints have been released.
135
+ DB.transaction(auto_savepoint: true) do
136
+ DB.transaction do
137
+ DB.after_commit(savepoint: true) { puts "after commit" }
138
+ raise Sequel::Rollback
127
139
  end
128
140
  end
141
+ #>> BEGIN
142
+ #>> SAVEPOINT active_record_1
143
+ #>> RELEASE SAVEPOINT active_record_1
144
+ #>> COMMIT
145
+ #>> after commit
146
+ ```
129
147
 
148
+ In case of (a) adding a transaction hook while Active Record holds the
149
+ transaction, or (b) adding a savepoint hook when Active Record holds any
150
+ enclosing savepoint, Active Record transaction callbacks will be used instead
151
+ of Sequel hooks, which have slightly different behaviour in some circumstances.
152
+
153
+ ```rb
154
+ # ActiveRecord: An after_commit transaction callback is not executed if any
155
+ # if the enclosing savepoints have been rolled back
130
156
  ActiveRecord::Base.transaction do
131
- DB.after_commit { ... } # not allowed (will raise Sequel::ActiveRecordConnection::Error)
157
+ DB.transaction(savepoint: true) do
158
+ DB.after_commit { puts "after commit" }
159
+ raise Sequel::Rollback
160
+ end
132
161
  end
133
-
134
- DB.transaction do
135
- ActiveRecord::Base.transaction(requires_new: true) do
136
- DB.after_commit(savepoint: true) { ... } # not allowed (will raise Sequel::ActiveRecordConnection::Error)
162
+ #>> BEGIN
163
+ #>> SAVEPOINT active_record_1
164
+ #>> ROLLBACK TO SAVEPOINT active_record_1
165
+ #>> COMMIT
166
+
167
+ # ActiveRecord: An after_commit transaction callback can be executed already
168
+ # after a savepoint is released, if the enclosing transaction is not joinable.
169
+ ActiveRecord::Base.transaction(joinable: false) do
170
+ DB.transaction do
171
+ DB.after_commit { puts "after commit" }
137
172
  end
138
173
  end
174
+ #>> BEGIN
175
+ #>> SAVEPOINT active_record_1
176
+ #>> RELEASE SAVEPOINT active_record_1
177
+ #>> after commit
178
+ #>> COMMIT
139
179
  ```
140
180
 
141
181
  ### Model
@@ -185,3 +225,7 @@ Everyone interacting in this project's codebases, issue trackers, chat rooms and
185
225
 
186
226
  [Sequel]: https://github.com/jeremyevans/sequel
187
227
  [Rodauth]: https://github.com/jeremyevans/rodauth
228
+ [rom-sql]: https://github.com/rom-rb/rom-sql
229
+ [sequel transaction hooks]: http://sequel.jeremyevans.net/rdoc/files/doc/transactions_rdoc.html#label-Transaction+Hooks
230
+ [Oracle enhanced]: https://github.com/rsim/oracle-enhanced
231
+ [SQL Server]: https://github.com/rails-sqlserver/activerecord-sqlserver-adapter
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "after_commit_everywhere"
4
+
3
5
  module Sequel
4
6
  module ActiveRecordConnection
5
7
  Error = Class.new(Sequel::Error)
@@ -11,8 +13,11 @@ module Sequel
11
13
  serializable: :serializable,
12
14
  }
13
15
 
16
+ ACTIVERECORD_CALLBACKS = Object.new.extend(AfterCommitEverywhere)
17
+
14
18
  def self.extended(db)
15
19
  db.activerecord_model = ActiveRecord::Base
20
+ db.opts[:test] = false unless db.opts.key?(:test)
16
21
 
17
22
  begin
18
23
  require "sequel/extensions/activerecord_connection/#{db.adapter_scheme}"
@@ -31,7 +36,9 @@ module Sequel
31
36
 
32
37
  # Avoid calling Sequel's connection pool, instead use Active Record's.
33
38
  def synchronize(*)
34
- yield activerecord_connection.raw_connection
39
+ activerecord_lock do
40
+ yield activerecord_connection.raw_connection
41
+ end
35
42
  end
36
43
 
37
44
  # Log executed queries into Active Record logger as well.
@@ -95,20 +102,41 @@ module Sequel
95
102
  activerecord_connection.rollback_transaction
96
103
  end
97
104
 
105
+ # When Active Record holds the transaction, we cannot use Sequel hooks,
106
+ # because Sequel doesn't have knowledge of when the transaction is
107
+ # committed. So in this case we register an Active Record hook using the
108
+ # after_commit_everywhere gem.
98
109
  def add_transaction_hook(conn, type, block)
99
110
  if _trans(conn)[:activerecord]
100
- fail Error, "cannot add transaction hook when ActiveRecord holds the outer transaction"
111
+ ACTIVERECORD_CALLBACKS.public_send(type, &block)
112
+ else
113
+ super
101
114
  end
102
-
103
- super
104
115
  end
105
116
 
117
+ # When Active Record holds the savepoint, we cannot use Sequel hooks,
118
+ # because Sequel doesn't have knowledge of when the savepoint is
119
+ # released. So in this case we register an Active Record hook using the
120
+ # after_commit_everywhere gem.
106
121
  def add_savepoint_hook(conn, type, block)
107
122
  if _trans(conn)[:savepoints].last[:activerecord]
108
- fail Error, "cannot add savepoint hook when ActiveRecord holds the current savepoint"
123
+ ACTIVERECORD_CALLBACKS.public_send(type, &block)
124
+ else
125
+ super
109
126
  end
127
+ end
110
128
 
111
- super
129
+ # Active Record doesn't guarantee that a single connection can only be used
130
+ # by one thread at a time, so we need to use locking, which is what Active
131
+ # Record does internally as well.
132
+ def activerecord_lock
133
+ return yield if ActiveRecord.version < Gem::Version.new("5.1.0")
134
+
135
+ activerecord_connection.lock.synchronize do
136
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
137
+ yield
138
+ end
139
+ end
112
140
  end
113
141
 
114
142
  def activerecord_connection
@@ -124,25 +152,6 @@ module Sequel
124
152
  &block
125
153
  )
126
154
  end
127
-
128
- module Utils
129
- def self.set_value(object, name, new_value)
130
- original_value = object.send(name)
131
- object.send(:"#{name}=", new_value)
132
- yield
133
- ensure
134
- object.send(:"#{name}=", original_value)
135
- end
136
-
137
- def self.add_prepared_statements_cache(conn)
138
- return if conn.respond_to?(:prepared_statements)
139
-
140
- class << conn
141
- attr_accessor :prepared_statements
142
- end
143
- conn.prepared_statements = {}
144
- end
145
- end
146
155
  end
147
156
 
148
157
  Database.register_extension(:activerecord_connection, ActiveRecordConnection)
@@ -9,7 +9,11 @@ module Sequel
9
9
 
10
10
  def synchronize(*)
11
11
  super do |conn|
12
- yield conn.connection
12
+ if database_type == :oracle
13
+ yield conn.raw_connection
14
+ else
15
+ yield conn.connection
16
+ end
13
17
  end
14
18
  end
15
19
  end
@@ -1,19 +1,30 @@
1
+ require_relative "utils"
2
+
1
3
  module Sequel
2
4
  module ActiveRecordConnection
3
5
  module Mysql2
4
6
  def synchronize(*)
5
7
  super do |conn|
6
8
  # required for prepared statements
7
- conn.instance_variable_set(:@sequel_default_query_options, conn.query_options.dup)
8
9
  Utils.add_prepared_statements_cache(conn)
9
10
 
10
- conn.query_options.merge!(as: :hash, symbolize_keys: true, cache_rows: false)
11
+ yield conn
12
+ end
13
+ end
14
+
15
+ private
16
+
17
+ def _execute(conn, sql, opts)
18
+ if conn.instance_variable_defined?(:@sequel_default_query_options)
19
+ return super
20
+ end
11
21
 
12
- begin
13
- yield conn
14
- ensure
15
- conn.query_options.replace(conn.instance_variable_get(:@sequel_default_query_options))
16
- end
22
+ conn.instance_variable_set(:@sequel_default_query_options, conn.query_options.dup)
23
+ conn.query_options.merge!(as: :hash, symbolize_keys: true, cache_rows: false)
24
+ begin
25
+ super
26
+ ensure
27
+ conn.query_options.replace(conn.remove_instance_variable(:@sequel_default_query_options))
17
28
  end
18
29
  end
19
30
  end
@@ -0,0 +1,16 @@
1
+ require_relative "utils"
2
+
3
+ module Sequel
4
+ module ActiveRecordConnection
5
+ module Oracle
6
+ def synchronize(*)
7
+ super do |conn|
8
+ # required for prepared statements
9
+ Utils.add_prepared_statements_cache(conn.raw_oci_connection)
10
+
11
+ yield conn.raw_oci_connection
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -1,3 +1,5 @@
1
+ require_relative "utils"
2
+
1
3
  module Sequel
2
4
  module ActiveRecordConnection
3
5
  module Postgres
@@ -8,9 +10,7 @@ module Sequel
8
10
 
9
11
  Utils.add_prepared_statements_cache(conn)
10
12
 
11
- Utils.set_value(conn, :type_map_for_results, PG::TypeMapAllStrings.new) do
12
- yield conn
13
- end
13
+ yield conn
14
14
  end
15
15
  end
16
16
 
@@ -30,20 +30,12 @@ module Sequel
30
30
  module ConnectionMethods
31
31
  # The underlying exception classes to reraise as disconnect errors
32
32
  # instead of regular database errors.
33
- DISCONNECT_ERROR_CLASSES = [IOError, Errno::EPIPE, Errno::ECONNRESET, ::PG::ConnectionBad].freeze
33
+ DISCONNECT_ERROR_CLASSES = Sequel::Postgres::Adapter::DISCONNECT_ERROR_CLASSES
34
34
 
35
35
  # Since exception class based disconnect checking may not work,
36
36
  # also trying parsing the exception message to look for disconnect
37
37
  # errors.
38
- DISCONNECT_ERROR_REGEX = /\A#{Regexp.union([
39
- "ERROR: cached plan must not change result type",
40
- "could not receive data from server",
41
- "no connection to the server",
42
- "connection not open",
43
- "connection is closed",
44
- "terminating connection due to administrator command",
45
- "PQconsumeInput() "
46
- ])}/
38
+ DISCONNECT_ERROR_REGEX = Sequel::Postgres::Adapter::DISCONNECT_ERROR_RE
47
39
 
48
40
  def async_exec_params(sql, args)
49
41
  defined?(super) ? super : async_exec(sql, args)
@@ -90,7 +82,9 @@ module Sequel
90
82
  # Return the PG::Result containing the query results.
91
83
  def execute_query(sql, args)
92
84
  @db.log_connection_yield(sql, self, args) do
93
- args ? async_exec_params(sql, args) : async_exec(sql)
85
+ Utils.set_value(self, :type_map_for_results, PG::TypeMapAllStrings.new) do
86
+ args ? async_exec_params(sql, args) : async_exec(sql)
87
+ end
94
88
  end
95
89
  end
96
90
  end
@@ -1,3 +1,5 @@
1
+ require_relative "utils"
2
+
1
3
  module Sequel
2
4
  module ActiveRecordConnection
3
5
  module Sqlite
@@ -13,8 +15,16 @@ module Sequel
13
15
 
14
16
  Utils.add_prepared_statements_cache(conn)
15
17
 
18
+ yield conn
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def _execute(type, sql, opts, &block)
25
+ synchronize(opts[:server]) do |conn|
16
26
  Utils.set_value(conn, :results_as_hash, nil) do
17
- yield conn
27
+ super
18
28
  end
19
29
  end
20
30
  end
@@ -0,0 +1,19 @@
1
+ require_relative "utils"
2
+
3
+ module Sequel
4
+ module ActiveRecordConnection
5
+ module Tinytds
6
+ def synchronize(*)
7
+ super do |conn|
8
+ conn.query_options.merge!(cache_rows: false)
9
+
10
+ begin
11
+ yield conn
12
+ ensure
13
+ conn.query_options.merge!(cache_rows: true)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,22 @@
1
+ module Sequel
2
+ module ActiveRecordConnection
3
+ module Utils
4
+ def self.set_value(object, name, new_value)
5
+ original_value = object.send(name)
6
+ object.send(:"#{name}=", new_value)
7
+ yield
8
+ ensure
9
+ object.send(:"#{name}=", original_value)
10
+ end
11
+
12
+ def self.add_prepared_statements_cache(conn)
13
+ return if conn.respond_to?(:prepared_statements)
14
+
15
+ class << conn
16
+ attr_accessor :prepared_statements
17
+ end
18
+ conn.prepared_statements = {}
19
+ end
20
+ end
21
+ end
22
+ end
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |spec|
2
2
  spec.name = "sequel-activerecord_connection"
3
- spec.version = "1.0.0"
3
+ spec.version = "1.2.2"
4
4
  spec.authors = ["Janko Marohnić"]
5
5
  spec.email = ["janko.marohnic@gmail.com"]
6
6
 
@@ -9,11 +9,13 @@ Gem::Specification.new do |spec|
9
9
  spec.homepage = "https://github.com/janko/sequel-activerecord_connection"
10
10
  spec.license = "MIT"
11
11
 
12
- spec.required_ruby_version = Gem::Requirement.new(">= 2.2.0")
12
+ spec.required_ruby_version = ">= 2.3"
13
13
 
14
14
  spec.add_dependency "sequel", "~> 5.16"
15
15
  spec.add_dependency "activerecord", ">= 4.2", "< 7"
16
+ spec.add_dependency "after_commit_everywhere", "~> 0.1.5"
16
17
 
18
+ spec.add_development_dependency "sequel", "~> 5.38"
17
19
  spec.add_development_dependency "minitest"
18
20
  spec.add_development_dependency "warning" if RUBY_VERSION >= "2.4"
19
21
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sequel-activerecord_connection
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Janko Marohnić
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-10-25 00:00:00.000000000 Z
11
+ date: 2021-01-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sequel
@@ -44,6 +44,34 @@ dependencies:
44
44
  - - "<"
45
45
  - !ruby/object:Gem::Version
46
46
  version: '7'
47
+ - !ruby/object:Gem::Dependency
48
+ name: after_commit_everywhere
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: 0.1.5
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: 0.1.5
61
+ - !ruby/object:Gem::Dependency
62
+ name: sequel
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '5.38'
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '5.38'
47
75
  - !ruby/object:Gem::Dependency
48
76
  name: minitest
49
77
  requirement: !ruby/object:Gem::Requirement
@@ -85,8 +113,11 @@ files:
85
113
  - lib/sequel/extensions/activerecord_connection.rb
86
114
  - lib/sequel/extensions/activerecord_connection/jdbc.rb
87
115
  - lib/sequel/extensions/activerecord_connection/mysql2.rb
116
+ - lib/sequel/extensions/activerecord_connection/oracle.rb
88
117
  - lib/sequel/extensions/activerecord_connection/postgres.rb
89
118
  - lib/sequel/extensions/activerecord_connection/sqlite.rb
119
+ - lib/sequel/extensions/activerecord_connection/tinytds.rb
120
+ - lib/sequel/extensions/activerecord_connection/utils.rb
90
121
  - sequel-activerecord_connection.gemspec
91
122
  homepage: https://github.com/janko/sequel-activerecord_connection
92
123
  licenses:
@@ -100,7 +131,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
100
131
  requirements:
101
132
  - - ">="
102
133
  - !ruby/object:Gem::Version
103
- version: 2.2.0
134
+ version: '2.3'
104
135
  required_rubygems_version: !ruby/object:Gem::Requirement
105
136
  requirements:
106
137
  - - ">="