sequel-activerecord_connection 0.4.1 → 1.2.1

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: bb35cdbc0ade81ae15042834733f9ec4683b6889eab2c35412710d631a02de53
4
- data.tar.gz: 75f97d5bee8522c12a7a3c65e05e1511f40ca4eccab8bc4b8cb2e1c3ece1a44a
3
+ metadata.gz: 72a4a1bf22a4a83664c7925b1d0b78ee5d20777eb12ffec4df9bd4b0c60a9930
4
+ data.tar.gz: 58c32e5d224d136cb797453bd8ac70f40d964160c4996377909349c8bb324af5
5
5
  SHA512:
6
- metadata.gz: 4fbf8edd6c9a61a8761431d33ba89c63a2aa444417d5ebec7b42ec9d3ef8aa5c509b28d35d708af223289c8e085b62204c3af690dc18c13e0df69f61a837b41d
7
- data.tar.gz: 5f4408be86e1d09a856215ca6e1fd4ef594955549e52987107ff33fc8efb24c68f4fdaf03c0132bb22fe4e2023b31d5526dc77f225d476718528d101c2f6bb8c
6
+ metadata.gz: 7cc94ff1690360f805eec685404d9017b9df0f327c32110e54694dd1da148f1141990a468b69787896ea5e9a4b13d84c41783d4dcee0fd8b1f020defd9288961
7
+ data.tar.gz: b67647d492d7d31478ca6db7ba4b107914d8ba692bd41bf4596cab5f06a16ee62b2f19ab199dfce9566234ac876b349c212199a23cbec2c15884108cf1e41f79
@@ -1,3 +1,55 @@
1
+ ## 1.2.1 (2020-01-10)
2
+
3
+ * Fix original mysql2 query options not being restored after nested `DB#synchronize` calls, e.g. when using Sequel transactions (@janko)
4
+
5
+ ## 1.2.0 (2020-11-15)
6
+
7
+ * Attempt support for [activerecord-sqlserver-adapter](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter) (@janko)
8
+
9
+ * Attempt support for [oracle-enhanced](https://github.com/rsim/oracle-enhanced) Active Record adapter (@janko)
10
+
11
+ ## 1.1.0 (2020-11-08)
12
+
13
+ * Drop support for Ruby 2.2 (@janko)
14
+
15
+ * Support transaction/savepoint hooks even when Active Record holds the transaction/savepoint (@janko)
16
+
17
+ * Don't test the connection on `Sequel.connect` by default (@janko)
18
+
19
+ ## 1.0.1 (2020-10-28)
20
+
21
+ * Use Active Record connection lock in `Database#synchronize` (@janko)
22
+
23
+ ## 1.0.0 (2020-10-25)
24
+
25
+ * Clear AR statement cache on `ActiveRecord::PreparedStatementCacheExpired` when Sequel holds the transaction (@janko)
26
+
27
+ * Pick up `ActiveRecord::Base.default_timezone` being changed on runtime (@janko)
28
+
29
+ * Support prepared statements and bound variables in all adapters (@janko)
30
+
31
+ * Correctly identify identity columns as primary keys in Postgres adapter (@janko)
32
+
33
+ * Avoid using deprecated `sqlite3` API in SQLite adapter (@janko)
34
+
35
+ * Allow using any external Active Record adapters (@janko)
36
+
37
+ * Avoid potential bugs when converting Active Record exceptions into Sequel exceptions (@janko)
38
+
39
+ * Don't use Active Record locks when executing queries with Sequel (@janko)
40
+
41
+ * Support `Database#valid_connection?` in Postgres adapter (@janko)
42
+
43
+ * Fully utilize Sequel's logic for detecting disconnects in Postgres adapter (@janko)
44
+
45
+ * Support `Database#{copy_table,copy_into,listen}` in Postgres adapter (@janko)
46
+
47
+ * Log all queries executed by Sequel (@janko)
48
+
49
+ * Log executed queries to Sequel logger(s) as well (@janko)
50
+
51
+ * Specially label queries executed by Sequel in Active Record logs (@janko)
52
+
1
53
  ## 0.4.1 (2020-09-28)
2
54
 
3
55
  * Require Sequel version 5.16.0 or above (@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:
@@ -74,13 +74,17 @@ ActiveRecord adapters, just make sure to initialize the corresponding Sequel
74
74
  adapter before loading the extension.
75
75
 
76
76
  ```rb
77
- DB = Sequel.postgres(extensions: :activerecord_connection) # for "postgresql" adapter
78
- # or
79
- DB = Sequel.mysql2(extensions: :activerecord_connection) # for "mysql2" adapter
80
- # or
81
- DB = Sequel.sqlite(extensions: :activerecord_connection) # for "sqlite3" adapter
82
- # or
83
- DB = Sequel.jdbc(extensions: :activerecord_connection) # for JDBC adapter
77
+ Sequel.postgres(extensions: :activerecord_connection) # for "postgresql" adapter
78
+ Sequel.mysql2(extensions: :activerecord_connection) # for "mysql2" adapter
79
+ Sequel.sqlite(extensions: :activerecord_connection) # for "sqlite3" adapter
80
+ ```
81
+
82
+ If you're on JRuby, you should be using the JDBC adapters:
83
+
84
+ ```rb
85
+ Sequel.connect("jdbc:postgresql://", extensions: :activerecord_connection) # for "jdbcpostgresql" adapter
86
+ Sequel.connect("jdbc:mysql://", extensions: :activerecord_connection) # for "jdbcmysql" adapter
87
+ Sequel.connect("jdbc:sqlite://", extensions: :activerecord_connection) # for "jdbcsqlite3" adapter
84
88
  ```
85
89
 
86
90
  ### Transactions
@@ -107,31 +111,71 @@ DB.transaction(isolation: :serializable) do
107
111
  end
108
112
  ```
109
113
 
110
- One caveat to keep in mind is that using Sequel's transaction/savepoint hooks
111
- currently don't work if ActiveRecord holds the corresponding
112
- transaction/savepoint. This is because it's difficult to be notified when
113
- 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].
114
117
 
115
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.
116
121
  DB.transaction do
117
- 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
118
126
  end
119
-
120
- DB.transaction do
121
- DB.transaction(savepoint: true) do
122
- 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
123
139
  end
124
140
  end
141
+ #>> BEGIN
142
+ #>> SAVEPOINT active_record_1
143
+ #>> RELEASE SAVEPOINT active_record_1
144
+ #>> COMMIT
145
+ #>> after commit
146
+ ```
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.
125
152
 
153
+ ```rb
154
+ # ActiveRecord: An after_commit transaction callback is not executed if any
155
+ # if the enclosing savepoints have been rolled back
126
156
  ActiveRecord::Base.transaction do
127
- 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
128
161
  end
129
-
130
- DB.transaction do
131
- ActiveRecord::Base.transaction(requires_new: true) do
132
- 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" }
133
172
  end
134
173
  end
174
+ #>> BEGIN
175
+ #>> SAVEPOINT active_record_1
176
+ #>> RELEASE SAVEPOINT active_record_1
177
+ #>> after commit
178
+ #>> COMMIT
135
179
  ```
136
180
 
137
181
  ### Model
@@ -149,19 +193,6 @@ end
149
193
  DB.activerecord_model = MyModel
150
194
  ```
151
195
 
152
- ### Timezone
153
-
154
- Sequel's database timezone will be automatically set to ActiveRecord's default
155
- timezone (`:utc` by default) when the extension is loaded.
156
-
157
- If you happen to be changing ActiveRecord's default timezone after you've
158
- loaded the extension, make sure to reflect that in your Sequel database object,
159
- for example:
160
-
161
- ```rb
162
- DB.timezone = :local
163
- ```
164
-
165
196
  ## Tests
166
197
 
167
198
  You'll first want to run the rake tasks for setting up databases and users:
@@ -194,3 +225,7 @@ Everyone interacting in this project's codebases, issue trackers, chat rooms and
194
225
 
195
226
  [Sequel]: https://github.com/jeremyevans/sequel
196
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,15 +13,17 @@ 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
16
- db.timezone = ActiveRecord::Base.default_timezone
20
+ db.opts[:test] = false unless db.opts.key?(:test)
17
21
 
18
22
  begin
19
23
  require "sequel/extensions/activerecord_connection/#{db.adapter_scheme}"
20
24
  db.extend Sequel::ActiveRecordConnection.const_get(db.adapter_scheme.capitalize)
21
25
  rescue LoadError
22
- fail Error, "unsupported adapter: #{db.adapter_scheme}"
26
+ # assume the Sequel adapter already works with Active Record
23
27
  end
24
28
  end
25
29
 
@@ -30,17 +34,25 @@ module Sequel
30
34
  raise Error, "creating a Sequel connection is not allowed"
31
35
  end
32
36
 
33
- # Avoid calling Sequel's connection pool, instead use ActiveRecord.
37
+ # Avoid calling Sequel's connection pool, instead use Active Record's.
34
38
  def synchronize(*)
35
- if ActiveRecord.version >= Gem::Version.new("5.1.0")
36
- activerecord_connection.lock.synchronize do
37
- yield activerecord_raw_connection
38
- end
39
- else
40
- yield activerecord_raw_connection
39
+ activerecord_lock do
40
+ yield activerecord_connection.raw_connection
41
41
  end
42
42
  end
43
43
 
44
+ # Log executed queries into Active Record logger as well.
45
+ def log_connection_yield(sql, conn, args = nil)
46
+ sql += "; #{args.inspect}" if args
47
+
48
+ activerecord_log(sql) { super }
49
+ end
50
+
51
+ # Match database timezone with Active Record.
52
+ def timezone
53
+ @timezone || ActiveRecord::Base.default_timezone
54
+ end
55
+
44
56
  private
45
57
 
46
58
  # Synchronizes transaction state with ActiveRecord. Sequel uses this
@@ -75,52 +87,70 @@ module Sequel
75
87
  super
76
88
  end
77
89
 
78
- def begin_transaction(conn, opts = {})
90
+ def begin_transaction(conn, opts = OPTS)
79
91
  isolation = TRANSACTION_ISOLATION_MAP.fetch(opts[:isolation]) if opts[:isolation]
80
92
  joinable = !opts[:auto_savepoint]
81
93
 
82
94
  activerecord_connection.begin_transaction(isolation: isolation, joinable: joinable)
83
95
  end
84
96
 
85
- def commit_transaction(conn, opts = {})
97
+ def commit_transaction(conn, opts = OPTS)
86
98
  activerecord_connection.commit_transaction
87
99
  end
88
100
 
89
- def rollback_transaction(conn, opts = {})
101
+ def rollback_transaction(conn, opts = OPTS)
90
102
  activerecord_connection.rollback_transaction
91
- activerecord_connection.transaction_manager.send(:after_failure_actions, activerecord_connection.current_transaction, $!) if activerecord_connection.transaction_manager.respond_to?(:after_failure_actions)
92
103
  end
93
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.
94
109
  def add_transaction_hook(conn, type, block)
95
110
  if _trans(conn)[:activerecord]
96
- fail Error, "cannot add transaction hook when ActiveRecord holds the outer transaction"
111
+ ACTIVERECORD_CALLBACKS.public_send(type, &block)
112
+ else
113
+ super
97
114
  end
98
-
99
- super
100
115
  end
101
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.
102
121
  def add_savepoint_hook(conn, type, block)
103
122
  if _trans(conn)[:savepoints].last[:activerecord]
104
- fail Error, "cannot add savepoint hook when ActiveRecord holds the current savepoint"
123
+ ACTIVERECORD_CALLBACKS.public_send(type, &block)
124
+ else
125
+ super
105
126
  end
106
-
107
- super
108
127
  end
109
128
 
110
- def activerecord_raw_connection
111
- activerecord_connection.raw_connection
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
115
143
  activerecord_model.connection
116
144
  end
117
145
 
118
- def activesupport_interlock(&block)
119
- if ActiveSupport::Dependencies.respond_to?(:interlock)
120
- ActiveSupport::Dependencies.interlock.permit_concurrent_loads(&block)
121
- else
122
- yield
123
- end
146
+ def activerecord_log(sql, &block)
147
+ ActiveSupport::Notifications.instrument(
148
+ "sql.active_record",
149
+ sql: sql,
150
+ name: "Sequel",
151
+ connection: activerecord_connection,
152
+ &block
153
+ )
124
154
  end
125
155
  end
126
156
 
@@ -7,32 +7,15 @@ module Sequel
7
7
  end
8
8
  end
9
9
 
10
- def statement(conn)
11
- stmt = activerecord_raw_connection.connection.createStatement
12
- yield stmt
13
- rescue ActiveRecord::StatementInvalid => exception
14
- raise_error(exception.cause, classes: database_error_classes)
15
- rescue *database_error_classes => e
16
- raise_error(e, classes: database_error_classes)
17
- ensure
18
- stmt.close if stmt
19
- end
20
-
21
- def execute(sql, opts=OPTS)
22
- activerecord_connection.send(:log, sql) do
23
- super
24
- end
25
- rescue ActiveRecord::StatementInvalid => exception
26
- raise_error(exception.cause, classes: database_error_classes)
27
- end
28
-
29
- def execute_dui(sql, opts=OPTS)
30
- activerecord_connection.send(:log, sql) do
31
- super
10
+ def synchronize(*)
11
+ super do |conn|
12
+ if database_type == :oracle
13
+ yield conn.raw_connection
14
+ else
15
+ yield conn.connection
16
+ end
32
17
  end
33
- rescue ActiveRecord::StatementInvalid => exception
34
- raise_error(exception.cause, classes: database_error_classes)
35
18
  end
36
19
  end
37
20
  end
38
- end
21
+ end
@@ -1,34 +1,26 @@
1
+ require_relative "utils"
2
+
1
3
  module Sequel
2
4
  module ActiveRecordConnection
3
5
  module Mysql2
4
- def execute(sql, opts=OPTS)
5
- original_query_options = activerecord_raw_connection.query_options.dup
6
+ def synchronize(*)
7
+ super do |conn|
8
+ if conn.instance_variable_defined?(:@sequel_default_query_options)
9
+ return yield(conn)
10
+ end
6
11
 
7
- activerecord_raw_connection.query_options.merge!(
8
- as: :hash,
9
- symbolize_keys: true,
10
- cache_rows: false,
11
- )
12
+ # required for prepared statements
13
+ conn.instance_variable_set(:@sequel_default_query_options, conn.query_options.dup)
14
+ Utils.add_prepared_statements_cache(conn)
12
15
 
13
- result = activerecord_connection.execute(sql)
16
+ conn.query_options.merge!(as: :hash, symbolize_keys: true, cache_rows: false)
14
17
 
15
- if opts[:type] == :select
16
- if block_given?
17
- yield result
18
- else
19
- result
18
+ begin
19
+ yield conn
20
+ ensure
21
+ conn.query_options.replace(conn.remove_instance_variable(:@sequel_default_query_options))
20
22
  end
21
- elsif block_given?
22
- yield activerecord_raw_connection
23
- end
24
- rescue ActiveRecord::StatementInvalid => exception
25
- if exception.cause.is_a?(::Mysql2::Error)
26
- raise_error(exception.cause)
27
- else
28
- raise
29
23
  end
30
- ensure
31
- activerecord_raw_connection.query_options.replace(original_query_options)
32
24
  end
33
25
  end
34
26
  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,28 +1,92 @@
1
+ require_relative "utils"
2
+
1
3
  module Sequel
2
4
  module ActiveRecordConnection
3
5
  module Postgres
4
- def execute(sql, opts=OPTS)
5
- result = activerecord_connection.execute(sql)
6
+ def synchronize(*)
7
+ super do |conn|
8
+ conn.extend(ConnectionMethods)
9
+ conn.instance_variable_set(:@db, self)
10
+
11
+ Utils.add_prepared_statements_cache(conn)
6
12
 
7
- if block_given?
8
- yield result
9
- else
10
- result.cmd_tuples
13
+ Utils.set_value(conn, :type_map_for_results, PG::TypeMapAllStrings.new) do
14
+ yield conn
15
+ end
11
16
  end
12
- rescue ActiveRecord::PreparedStatementCacheExpired
13
- raise # ActiveRecord's transaction manager needs to handle this exception
14
- rescue ActiveRecord::StatementInvalid => exception
15
- raise_error(exception.cause, classes: database_error_classes)
16
- ensure
17
- result.clear if result
18
17
  end
19
18
 
20
- def transaction(options = {})
19
+ # Reject unsupported Postgres-specific transaction options.
20
+ def transaction(opts = OPTS)
21
21
  %i[deferrable read_only synchronous].each do |key|
22
- fail Error, "#{key.inspect} transaction option is currently not supported" if options.key?(key)
22
+ fail Error, "#{key.inspect} transaction option is currently not supported" if opts.key?(key)
23
23
  end
24
24
 
25
25
  super
26
+ rescue => e
27
+ activerecord_connection.clear_cache! if e.class.name == "ActiveRecord::PreparedStatementCacheExpired" && !in_transaction?
28
+ raise
29
+ end
30
+
31
+ # Copy-pasted from Sequel::Postgres::Adapter.
32
+ module ConnectionMethods
33
+ # The underlying exception classes to reraise as disconnect errors
34
+ # instead of regular database errors.
35
+ DISCONNECT_ERROR_CLASSES = Sequel::Postgres::Adapter::DISCONNECT_ERROR_CLASSES
36
+
37
+ # Since exception class based disconnect checking may not work,
38
+ # also trying parsing the exception message to look for disconnect
39
+ # errors.
40
+ DISCONNECT_ERROR_REGEX = Sequel::Postgres::Adapter::DISCONNECT_ERROR_RE
41
+
42
+ def async_exec_params(sql, args)
43
+ defined?(super) ? super : async_exec(sql, args)
44
+ end
45
+
46
+ # Raise a Sequel::DatabaseDisconnectError if a one of the disconnect
47
+ # error classes is raised, or a PG::Error is raised and the connection
48
+ # status cannot be determined or it is not OK.
49
+ def check_disconnect_errors
50
+ begin
51
+ yield
52
+ rescue *DISCONNECT_ERROR_CLASSES => e
53
+ disconnect = true
54
+ raise(Sequel.convert_exception_class(e, Sequel::DatabaseDisconnectError))
55
+ rescue PG::Error => e
56
+ disconnect = false
57
+ begin
58
+ s = status
59
+ rescue PG::Error
60
+ disconnect = true
61
+ end
62
+ status_ok = (s == PG::CONNECTION_OK)
63
+ disconnect ||= !status_ok
64
+ disconnect ||= e.message =~ DISCONNECT_ERROR_REGEX
65
+ disconnect ? raise(Sequel.convert_exception_class(e, Sequel::DatabaseDisconnectError)) : raise
66
+ ensure
67
+ block if status_ok && !disconnect
68
+ end
69
+ end
70
+
71
+ # Execute the given SQL with this connection. If a block is given,
72
+ # yield the results, otherwise, return the number of changed rows.
73
+ def execute(sql, args = nil)
74
+ args = args.map { |v| @db.bound_variable_arg(v, self) } if args
75
+ result = check_disconnect_errors { execute_query(sql, args) }
76
+
77
+ block_given? ? yield(result) : result.cmd_tuples
78
+ ensure
79
+ result.clear if result
80
+ end
81
+
82
+ private
83
+
84
+ # Return the PG::Result containing the query results.
85
+ def execute_query(sql, args)
86
+ @db.log_connection_yield(sql, self, args) do
87
+ args ? async_exec_params(sql, args) : async_exec(sql)
88
+ end
89
+ end
26
90
  end
27
91
  end
28
92
  end
@@ -1,3 +1,5 @@
1
+ require_relative "utils"
2
+
1
3
  module Sequel
2
4
  module ActiveRecordConnection
3
5
  module Sqlite
@@ -7,43 +9,16 @@ module Sequel
7
9
  end
8
10
  end
9
11
 
10
- def execute_ddl(sql, opts=OPTS)
11
- execute(sql, opts)
12
- end
13
-
14
- private
12
+ def synchronize(*)
13
+ super do |conn|
14
+ conn.extended_result_codes = true if conn.respond_to?(:extended_result_codes=)
15
15
 
16
- # ActiveRecord doesn't send SQLite methods Sequel expects, so we need to
17
- # try to replicate what ActiveRecord does around connection excecution.
18
- def _execute(type, sql, opts, &block)
19
- if activerecord_raw_connection.respond_to?(:extended_result_codes=)
20
- activerecord_raw_connection.extended_result_codes = true
21
- end
16
+ Utils.add_prepared_statements_cache(conn)
22
17
 
23
- if ActiveRecord::VERSION::MAJOR >= 6
24
- activerecord_connection.materialize_transactions
25
- end
26
-
27
- activerecord_connection.send(:log, sql) do
28
- activesupport_interlock do
29
- case type
30
- when :select
31
- activerecord_raw_connection.query(sql, &block)
32
- when :insert
33
- activerecord_raw_connection.execute(sql)
34
- activerecord_raw_connection.last_insert_row_id
35
- when :update
36
- activerecord_raw_connection.execute_batch(sql)
37
- activerecord_raw_connection.changes
38
- end
18
+ Utils.set_value(conn, :results_as_hash, nil) do
19
+ yield conn
39
20
  end
40
21
  end
41
- rescue ActiveRecord::StatementInvalid => exception
42
- if exception.cause.is_a?(SQLite3::Exception)
43
- raise_error(exception.cause)
44
- else
45
- raise exception
46
- end
47
22
  end
48
23
  end
49
24
  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 = "0.4.1"
3
+ spec.version = "1.2.1"
4
4
  spec.authors = ["Janko Marohnić"]
5
5
  spec.email = ["janko.marohnic@gmail.com"]
6
6
 
@@ -9,12 +9,15 @@ 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"
20
+ spec.add_development_dependency "warning" if RUBY_VERSION >= "2.4"
18
21
 
19
22
  spec.files = Dir["README.md", "LICENSE.txt", "CHANGELOG.md", "lib/**/*.rb", "*.gemspec"]
20
23
  spec.require_paths = ["lib"]
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: 0.4.1
4
+ version: 1.2.1
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-09-28 00:00:00.000000000 Z
11
+ date: 2021-01-10 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
@@ -58,6 +86,20 @@ dependencies:
58
86
  - - ">="
59
87
  - !ruby/object:Gem::Version
60
88
  version: '0'
89
+ - !ruby/object:Gem::Dependency
90
+ name: warning
91
+ requirement: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ type: :development
97
+ prerelease: false
98
+ version_requirements: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
61
103
  description: Allows Sequel to use ActiveRecord connection for database interaction.
62
104
  email:
63
105
  - janko.marohnic@gmail.com
@@ -71,8 +113,11 @@ files:
71
113
  - lib/sequel/extensions/activerecord_connection.rb
72
114
  - lib/sequel/extensions/activerecord_connection/jdbc.rb
73
115
  - lib/sequel/extensions/activerecord_connection/mysql2.rb
116
+ - lib/sequel/extensions/activerecord_connection/oracle.rb
74
117
  - lib/sequel/extensions/activerecord_connection/postgres.rb
75
118
  - lib/sequel/extensions/activerecord_connection/sqlite.rb
119
+ - lib/sequel/extensions/activerecord_connection/tinytds.rb
120
+ - lib/sequel/extensions/activerecord_connection/utils.rb
76
121
  - sequel-activerecord_connection.gemspec
77
122
  homepage: https://github.com/janko/sequel-activerecord_connection
78
123
  licenses:
@@ -86,14 +131,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
86
131
  requirements:
87
132
  - - ">="
88
133
  - !ruby/object:Gem::Version
89
- version: 2.2.0
134
+ version: '2.3'
90
135
  required_rubygems_version: !ruby/object:Gem::Requirement
91
136
  requirements:
92
137
  - - ">="
93
138
  - !ruby/object:Gem::Version
94
139
  version: '0'
95
140
  requirements: []
96
- rubygems_version: 3.1.1
141
+ rubygems_version: 3.1.4
97
142
  signing_key:
98
143
  specification_version: 4
99
144
  summary: Allows Sequel to use ActiveRecord connection for database interaction.