sequel-activerecord_connection 1.0.0 → 1.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
  - - ">="