sequel-activerecord_connection 0.3.0 → 1.1.0
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 +4 -4
- data/CHANGELOG.md +60 -0
- data/README.md +74 -49
- data/lib/sequel/extensions/activerecord_connection.rb +111 -43
- data/lib/sequel/extensions/activerecord_connection/jdbc.rb +4 -25
- data/lib/sequel/extensions/activerecord_connection/mysql2.rb +10 -24
- data/lib/sequel/extensions/activerecord_connection/postgres.rb +84 -14
- data/lib/sequel/extensions/activerecord_connection/sqlite.rb +6 -33
- data/sequel-activerecord_connection.gemspec +6 -3
- metadata +50 -14
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fba99c145ebf98ed9c3e26fdc96afc3bc5fa7820e01d466ab10320c72765c8ae
|
|
4
|
+
data.tar.gz: ae36bef3a63280cf54f1a92e814c1bf4928dc8050f572694aac4340389115f9b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 073babf3f4e9d60dcb439eb0d9a7b801b9cf23b9be53366382cdd9e18234de66c329d0a975e68fb57d78cbf80f26ddafcd3f1bc90e8282a9a24b90cd1f8c0a43
|
|
7
|
+
data.tar.gz: 420a6326c6c0e0b85874b52c89e747a59702280e763d9f6a090dd649b17718771cefaa1d9d72d4244a92bcfbee7ff2c186480ad0005d17685abcd0a595ccb97c
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,63 @@
|
|
|
1
|
+
## 1.1.0 (2020-11-08)
|
|
2
|
+
|
|
3
|
+
* Drop support for Ruby 2.2 (@janko)
|
|
4
|
+
|
|
5
|
+
* Support transaction/savepoint hooks even when Active Record holds the transaction/savepoint (@janko)
|
|
6
|
+
|
|
7
|
+
* Don't test the connection on `Sequel.connect` by default (@janko)
|
|
8
|
+
|
|
9
|
+
## 1.0.1 (2020-10-28)
|
|
10
|
+
|
|
11
|
+
* Use Active Record connection lock in `Database#synchronize` (@janko)
|
|
12
|
+
|
|
13
|
+
## 1.0.0 (2020-10-25)
|
|
14
|
+
|
|
15
|
+
* Clear AR statement cache on `ActiveRecord::PreparedStatementCacheExpired` when Sequel holds the transaction (@janko)
|
|
16
|
+
|
|
17
|
+
* Pick up `ActiveRecord::Base.default_timezone` being changed on runtime (@janko)
|
|
18
|
+
|
|
19
|
+
* Support prepared statements and bound variables in all adapters (@janko)
|
|
20
|
+
|
|
21
|
+
* Correctly identify identity columns as primary keys in Postgres adapter (@janko)
|
|
22
|
+
|
|
23
|
+
* Avoid using deprecated `sqlite3` API in SQLite adapter (@janko)
|
|
24
|
+
|
|
25
|
+
* Allow using any external Active Record adapters (@janko)
|
|
26
|
+
|
|
27
|
+
* Avoid potential bugs when converting Active Record exceptions into Sequel exceptions (@janko)
|
|
28
|
+
|
|
29
|
+
* Don't use Active Record locks when executing queries with Sequel (@janko)
|
|
30
|
+
|
|
31
|
+
* Support `Database#valid_connection?` in Postgres adapter (@janko)
|
|
32
|
+
|
|
33
|
+
* Fully utilize Sequel's logic for detecting disconnects in Postgres adapter (@janko)
|
|
34
|
+
|
|
35
|
+
* Support `Database#{copy_table,copy_into,listen}` in Postgres adapter (@janko)
|
|
36
|
+
|
|
37
|
+
* Log all queries executed by Sequel (@janko)
|
|
38
|
+
|
|
39
|
+
* Log executed queries to Sequel logger(s) as well (@janko)
|
|
40
|
+
|
|
41
|
+
* Specially label queries executed by Sequel in Active Record logs (@janko)
|
|
42
|
+
|
|
43
|
+
## 0.4.1 (2020-09-28)
|
|
44
|
+
|
|
45
|
+
* Require Sequel version 5.16.0 or above (@janko)
|
|
46
|
+
|
|
47
|
+
## 0.4.0 (2020-09-28)
|
|
48
|
+
|
|
49
|
+
* Return correct result of `Database#in_transaction?` after ActiveRecord transaction exited (@janko)
|
|
50
|
+
|
|
51
|
+
* Make ActiveRecord create a savepoint inside a Sequel transaction with `auto_savepoint: true` (@janko)
|
|
52
|
+
|
|
53
|
+
* Make Sequel create a savepoint inside ActiveRecord transaction with `joinable: false` (@janko)
|
|
54
|
+
|
|
55
|
+
* Improve reliability of nested transactions when combining Sequel and ActiveRecord (@janko)
|
|
56
|
+
|
|
57
|
+
* Raise error when attempting to add an `after_commit`/`after_rollback` hook on ActiveRecord transaction (@janko)
|
|
58
|
+
|
|
59
|
+
* Fix infinite loop that could happen with transactional Rails tests (@janko)
|
|
60
|
+
|
|
1
61
|
## 0.3.0 (2020-07-24)
|
|
2
62
|
|
|
3
63
|
* Fully support Sequel transaction API (all transaction options, transaction/savepoint hooks etc.) (@janko)
|
data/README.md
CHANGED
|
@@ -1,25 +1,23 @@
|
|
|
1
|
-
#
|
|
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.
|
|
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
|
|
10
|
-
database connection. Or if you're transitioning from ActiveRecord to
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
It works on ActiveRecord 4.2+ and fully supports PostgresSQL, MySQL and SQLite
|
|
12
|
+
adapters, both the native ones and JDBC (JRuby). Other adapters might work too,
|
|
13
|
+
but their integration hasn't been tested.
|
|
16
14
|
|
|
17
15
|
## Installation
|
|
18
16
|
|
|
19
17
|
Add this line to your application's Gemfile:
|
|
20
18
|
|
|
21
|
-
```
|
|
22
|
-
gem "sequel-activerecord_connection", "~> 0
|
|
19
|
+
```rb
|
|
20
|
+
gem "sequel-activerecord_connection", "~> 1.0"
|
|
23
21
|
```
|
|
24
22
|
|
|
25
23
|
And then execute:
|
|
@@ -42,8 +40,7 @@ appropriate Sequel adapter and load the `activerecord_connection` extension:
|
|
|
42
40
|
```rb
|
|
43
41
|
require "sequel"
|
|
44
42
|
|
|
45
|
-
DB = Sequel.postgres(
|
|
46
|
-
DB.extension :activerecord_connection
|
|
43
|
+
DB = Sequel.postgres(extensions: :activerecord_connection)
|
|
47
44
|
```
|
|
48
45
|
|
|
49
46
|
Now any Sequel operations that you make will internaly be done using the
|
|
@@ -75,13 +72,17 @@ ActiveRecord adapters, just make sure to initialize the corresponding Sequel
|
|
|
75
72
|
adapter before loading the extension.
|
|
76
73
|
|
|
77
74
|
```rb
|
|
78
|
-
|
|
79
|
-
#
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
75
|
+
Sequel.postgres(extensions: :activerecord_connection) # for "postgresql" adapter
|
|
76
|
+
Sequel.mysql2(extensions: :activerecord_connection) # for "mysql2" adapter
|
|
77
|
+
Sequel.sqlite(extensions: :activerecord_connection) # for "sqlite3" adapter
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
If you're on JRuby, you should be using the JDBC adapters:
|
|
81
|
+
|
|
82
|
+
```rb
|
|
83
|
+
Sequel.connect("jdbc:postgresql://", extensions: :activerecord_connection) # for "jdbcpostgresql" adapter
|
|
84
|
+
Sequel.connect("jdbc:mysql://", extensions: :activerecord_connection) # for "jdbcmysql" adapter
|
|
85
|
+
Sequel.connect("jdbc:sqlite://", extensions: :activerecord_connection) # for "jdbcsqlite3" adapter
|
|
85
86
|
```
|
|
86
87
|
|
|
87
88
|
### Transactions
|
|
@@ -101,41 +102,77 @@ Sequel's transaction API is fully supported:
|
|
|
101
102
|
|
|
102
103
|
```rb
|
|
103
104
|
DB.transaction(isolation: :serializable) do
|
|
104
|
-
DB.after_commit { ... } #
|
|
105
|
+
DB.after_commit { ... } # executed after transaction commits
|
|
105
106
|
DB.transaction(savepoint: true) do # creates a savepoint
|
|
106
|
-
#
|
|
107
|
+
DB.after_commit(savepoint: true) { ... } # executed if all enclosing savepoints have been released
|
|
107
108
|
end
|
|
108
109
|
end
|
|
109
110
|
```
|
|
110
111
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
outer transaction:
|
|
112
|
+
When combining Active Record and Sequel transactions, Sequel transaction hook
|
|
113
|
+
functionality will be utilized when possible.
|
|
114
114
|
|
|
115
115
|
```rb
|
|
116
|
+
# Sequel: An after_commit transaction hook will always get executed if the outer
|
|
117
|
+
# transaction commits, even if it's added inside a savepoint that's rolled back.
|
|
116
118
|
DB.transaction do
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
DB.after_commit { ... } # won't get executed
|
|
119
|
+
ActiveRecord::Base.transaction(requires_new: true) do
|
|
120
|
+
DB.after_commit { puts "after commit" }
|
|
121
|
+
raise ActiveRecord::Rollback
|
|
122
|
+
end
|
|
122
123
|
end
|
|
123
|
-
|
|
124
|
-
|
|
124
|
+
#>> BEGIN
|
|
125
|
+
#>> SAVEPOINT active_record_1
|
|
126
|
+
#>> ROLLBACK TO SAVEPOINT active_record_1
|
|
127
|
+
#>> COMMIT
|
|
128
|
+
#>> after commit
|
|
129
|
+
|
|
130
|
+
# Sequel: An after_commit savepoint hook will get executed only after the outer
|
|
131
|
+
# transaction commits, given that all enclosing savepoints have been released.
|
|
132
|
+
DB.transaction(auto_savepoint: true) do
|
|
125
133
|
DB.transaction do
|
|
126
|
-
DB.after_commit {
|
|
134
|
+
DB.after_commit(savepoint: true) { puts "after commit" }
|
|
135
|
+
raise Sequel::Rollback
|
|
127
136
|
end
|
|
128
137
|
end
|
|
138
|
+
#>> BEGIN
|
|
139
|
+
#>> SAVEPOINT active_record_1
|
|
140
|
+
#>> RELEASE SAVEPOINT active_record_1
|
|
141
|
+
#>> COMMIT
|
|
142
|
+
#>> after commit
|
|
129
143
|
```
|
|
130
144
|
|
|
131
|
-
|
|
145
|
+
In case of (a) adding a transaction hook while Active Record holds the
|
|
146
|
+
transaction, or (b) adding a savepoint hook when Active Record holds any
|
|
147
|
+
enclosing savepoint, Active Record transaction callbacks will be used instead
|
|
148
|
+
of Sequel hooks, which have slightly different behaviour in some circumstances.
|
|
132
149
|
|
|
133
150
|
```rb
|
|
151
|
+
# ActiveRecord: An after_commit transaction callback is not executed if any
|
|
152
|
+
# if the enclosing savepoints have been rolled back
|
|
134
153
|
ActiveRecord::Base.transaction do
|
|
135
154
|
DB.transaction(savepoint: true) do
|
|
136
|
-
DB.after_commit {
|
|
155
|
+
DB.after_commit { puts "after commit" }
|
|
156
|
+
raise Sequel::Rollback
|
|
137
157
|
end
|
|
138
158
|
end
|
|
159
|
+
#>> BEGIN
|
|
160
|
+
#>> SAVEPOINT active_record_1
|
|
161
|
+
#>> ROLLBACK TO SAVEPOINT active_record_1
|
|
162
|
+
#>> COMMIT
|
|
163
|
+
|
|
164
|
+
# ActiveRecord: An after_commit transaction callback can be executed already
|
|
165
|
+
# after a savepoint is released, if the enclosing transaction is not joinable.
|
|
166
|
+
ActiveRecord::Base.transaction(joinable: false) do
|
|
167
|
+
DB.transaction do
|
|
168
|
+
DB.after_commit { puts "after commit" }
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
#>> BEGIN
|
|
172
|
+
#>> SAVEPOINT active_record_1
|
|
173
|
+
#>> RELEASE SAVEPOINT active_record_1
|
|
174
|
+
#>> after commit
|
|
175
|
+
#>> COMMIT
|
|
139
176
|
```
|
|
140
177
|
|
|
141
178
|
### Model
|
|
@@ -153,19 +190,6 @@ end
|
|
|
153
190
|
DB.activerecord_model = MyModel
|
|
154
191
|
```
|
|
155
192
|
|
|
156
|
-
### Timezone
|
|
157
|
-
|
|
158
|
-
Sequel's database timezone will be automatically set to ActiveRecord's default
|
|
159
|
-
timezone (`:utc` by default) when the extension is loaded.
|
|
160
|
-
|
|
161
|
-
If you happen to be changing ActiveRecord's default timezone after you've
|
|
162
|
-
loaded the extension, make sure to reflect that in your Sequel database object,
|
|
163
|
-
for example:
|
|
164
|
-
|
|
165
|
-
```rb
|
|
166
|
-
DB.timezone = :local
|
|
167
|
-
```
|
|
168
|
-
|
|
169
193
|
## Tests
|
|
170
194
|
|
|
171
195
|
You'll first want to run the rake tasks for setting up databases and users:
|
|
@@ -198,3 +222,4 @@ Everyone interacting in this project's codebases, issue trackers, chat rooms and
|
|
|
198
222
|
|
|
199
223
|
[Sequel]: https://github.com/jeremyevans/sequel
|
|
200
224
|
[Rodauth]: https://github.com/jeremyevans/rodauth
|
|
225
|
+
[rom-sql]: https://github.com/rom-rb/rom-sql
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "after_commit_everywhere"
|
|
4
|
+
|
|
1
5
|
module Sequel
|
|
2
6
|
module ActiveRecordConnection
|
|
3
7
|
Error = Class.new(Sequel::Error)
|
|
@@ -9,15 +13,17 @@ module Sequel
|
|
|
9
13
|
serializable: :serializable,
|
|
10
14
|
}
|
|
11
15
|
|
|
16
|
+
ACTIVERECORD_CALLBACKS = Object.new.extend(AfterCommitEverywhere)
|
|
17
|
+
|
|
12
18
|
def self.extended(db)
|
|
13
19
|
db.activerecord_model = ActiveRecord::Base
|
|
14
|
-
db.
|
|
20
|
+
db.opts[:test] = false unless db.opts.key?(:test)
|
|
15
21
|
|
|
16
22
|
begin
|
|
17
23
|
require "sequel/extensions/activerecord_connection/#{db.adapter_scheme}"
|
|
18
24
|
db.extend Sequel::ActiveRecordConnection.const_get(db.adapter_scheme.capitalize)
|
|
19
25
|
rescue LoadError
|
|
20
|
-
|
|
26
|
+
# assume the Sequel adapter already works with Active Record
|
|
21
27
|
end
|
|
22
28
|
end
|
|
23
29
|
|
|
@@ -28,79 +34,141 @@ module Sequel
|
|
|
28
34
|
raise Error, "creating a Sequel connection is not allowed"
|
|
29
35
|
end
|
|
30
36
|
|
|
31
|
-
# Avoid calling Sequel's connection pool, instead use
|
|
37
|
+
# Avoid calling Sequel's connection pool, instead use Active Record's.
|
|
32
38
|
def synchronize(*)
|
|
33
|
-
|
|
34
|
-
activerecord_connection.
|
|
35
|
-
yield activerecord_raw_connection
|
|
36
|
-
end
|
|
37
|
-
else
|
|
38
|
-
yield activerecord_raw_connection
|
|
39
|
+
activerecord_lock do
|
|
40
|
+
yield activerecord_connection.raw_connection
|
|
39
41
|
end
|
|
40
42
|
end
|
|
41
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
|
+
|
|
42
56
|
private
|
|
43
57
|
|
|
44
|
-
#
|
|
45
|
-
#
|
|
46
|
-
#
|
|
47
|
-
# when to run transaction/savepoint hooks etc.
|
|
58
|
+
# Synchronizes transaction state with ActiveRecord. Sequel uses this
|
|
59
|
+
# information to know whether we're in a transaction, whether to create a
|
|
60
|
+
# savepoint, when to run transaction/savepoint hooks etc.
|
|
48
61
|
def _trans(conn)
|
|
49
|
-
|
|
50
|
-
result = @transactions[conn]
|
|
51
|
-
|
|
52
|
-
if activerecord_connection.transaction_open?
|
|
53
|
-
result ||= { savepoints: [] }
|
|
54
|
-
while result[:savepoints].length < activerecord_connection.open_transactions
|
|
55
|
-
result[:savepoints].unshift({ activerecord: true })
|
|
56
|
-
end
|
|
57
|
-
end
|
|
62
|
+
hash = super || { savepoints: [], activerecord: true }
|
|
58
63
|
|
|
59
|
-
|
|
60
|
-
|
|
64
|
+
# add any ActiveRecord transactions/savepoints that have been opened
|
|
65
|
+
# directly via ActiveRecord::Base.transaction
|
|
66
|
+
while hash[:savepoints].length < activerecord_connection.open_transactions
|
|
67
|
+
hash[:savepoints] << { activerecord: true }
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# remove any ActiveRecord transactions/savepoints that have been closed
|
|
71
|
+
# directly via ActiveRecord::Base.transaction
|
|
72
|
+
while hash[:savepoints].length > activerecord_connection.open_transactions && hash[:savepoints].last[:activerecord]
|
|
73
|
+
hash[:savepoints].pop
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# sync knowledge about joinability of current ActiveRecord transaction/savepoint
|
|
77
|
+
if activerecord_connection.transaction_open? && !activerecord_connection.current_transaction.joinable?
|
|
78
|
+
hash[:savepoints].last[:auto_savepoint] = true
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
if hash[:savepoints].empty? && hash[:activerecord]
|
|
82
|
+
Sequel.synchronize { @transactions.delete(conn) }
|
|
83
|
+
else
|
|
84
|
+
Sequel.synchronize { @transactions[conn] = hash }
|
|
61
85
|
end
|
|
62
|
-
end
|
|
63
86
|
|
|
64
|
-
# First delete any transactions/savepoints opened directly via
|
|
65
|
-
# ActiveRecord::Base.transaction, so that Sequel can detect when the last
|
|
66
|
-
# Sequel transaction has been closed and clear transaction information.
|
|
67
|
-
def transaction_finished?(conn)
|
|
68
|
-
_trans(conn)[:savepoints].shift while _trans(conn)[:savepoints].first[:activerecord]
|
|
69
87
|
super
|
|
70
88
|
end
|
|
71
89
|
|
|
72
|
-
def begin_transaction(conn, opts =
|
|
90
|
+
def begin_transaction(conn, opts = OPTS)
|
|
73
91
|
isolation = TRANSACTION_ISOLATION_MAP.fetch(opts[:isolation]) if opts[:isolation]
|
|
92
|
+
joinable = !opts[:auto_savepoint]
|
|
74
93
|
|
|
75
|
-
activerecord_connection.begin_transaction(isolation: isolation)
|
|
94
|
+
activerecord_connection.begin_transaction(isolation: isolation, joinable: joinable)
|
|
76
95
|
end
|
|
77
96
|
|
|
78
|
-
def commit_transaction(conn, opts =
|
|
97
|
+
def commit_transaction(conn, opts = OPTS)
|
|
79
98
|
activerecord_connection.commit_transaction
|
|
80
99
|
end
|
|
81
100
|
|
|
82
|
-
def rollback_transaction(conn, opts =
|
|
101
|
+
def rollback_transaction(conn, opts = OPTS)
|
|
83
102
|
activerecord_connection.rollback_transaction
|
|
84
|
-
activerecord_connection.transaction_manager.send(:after_failure_actions, activerecord_connection.current_transaction, $!) if activerecord_connection.transaction_manager.respond_to?(:after_failure_actions)
|
|
85
103
|
end
|
|
86
104
|
|
|
87
|
-
|
|
88
|
-
|
|
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.
|
|
109
|
+
def add_transaction_hook(conn, type, block)
|
|
110
|
+
if _trans(conn)[:activerecord]
|
|
111
|
+
ACTIVERECORD_CALLBACKS.public_send(type, &block)
|
|
112
|
+
else
|
|
113
|
+
super
|
|
114
|
+
end
|
|
89
115
|
end
|
|
90
116
|
|
|
91
|
-
|
|
92
|
-
|
|
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.
|
|
121
|
+
def add_savepoint_hook(conn, type, block)
|
|
122
|
+
if _trans(conn)[:savepoints].last[:activerecord]
|
|
123
|
+
ACTIVERECORD_CALLBACKS.public_send(type, &block)
|
|
124
|
+
else
|
|
125
|
+
super
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
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
|
|
93
140
|
end
|
|
94
141
|
|
|
95
142
|
def activerecord_connection
|
|
96
143
|
activerecord_model.connection
|
|
97
144
|
end
|
|
98
145
|
|
|
99
|
-
def
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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
|
+
)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
module Utils
|
|
157
|
+
def self.set_value(object, name, new_value)
|
|
158
|
+
original_value = object.send(name)
|
|
159
|
+
object.send(:"#{name}=", new_value)
|
|
103
160
|
yield
|
|
161
|
+
ensure
|
|
162
|
+
object.send(:"#{name}=", original_value)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def self.add_prepared_statements_cache(conn)
|
|
166
|
+
return if conn.respond_to?(:prepared_statements)
|
|
167
|
+
|
|
168
|
+
class << conn
|
|
169
|
+
attr_accessor :prepared_statements
|
|
170
|
+
end
|
|
171
|
+
conn.prepared_statements = {}
|
|
104
172
|
end
|
|
105
173
|
end
|
|
106
174
|
end
|
|
@@ -7,32 +7,11 @@ module Sequel
|
|
|
7
7
|
end
|
|
8
8
|
end
|
|
9
9
|
|
|
10
|
-
def
|
|
11
|
-
|
|
12
|
-
|
|
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
|
+
yield conn.connection
|
|
32
13
|
end
|
|
33
|
-
rescue ActiveRecord::StatementInvalid => exception
|
|
34
|
-
raise_error(exception.cause, classes: database_error_classes)
|
|
35
14
|
end
|
|
36
15
|
end
|
|
37
16
|
end
|
|
38
|
-
end
|
|
17
|
+
end
|
|
@@ -1,34 +1,20 @@
|
|
|
1
1
|
module Sequel
|
|
2
2
|
module ActiveRecordConnection
|
|
3
3
|
module Mysql2
|
|
4
|
-
def
|
|
5
|
-
|
|
4
|
+
def synchronize(*)
|
|
5
|
+
super do |conn|
|
|
6
|
+
# required for prepared statements
|
|
7
|
+
conn.instance_variable_set(:@sequel_default_query_options, conn.query_options.dup)
|
|
8
|
+
Utils.add_prepared_statements_cache(conn)
|
|
6
9
|
|
|
7
|
-
|
|
8
|
-
as: :hash,
|
|
9
|
-
symbolize_keys: true,
|
|
10
|
-
cache_rows: false,
|
|
11
|
-
)
|
|
10
|
+
conn.query_options.merge!(as: :hash, symbolize_keys: true, cache_rows: false)
|
|
12
11
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
yield result
|
|
18
|
-
else
|
|
19
|
-
result
|
|
12
|
+
begin
|
|
13
|
+
yield conn
|
|
14
|
+
ensure
|
|
15
|
+
conn.query_options.replace(conn.instance_variable_get(:@sequel_default_query_options))
|
|
20
16
|
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
17
|
end
|
|
30
|
-
ensure
|
|
31
|
-
activerecord_raw_connection.query_options.replace(original_query_options)
|
|
32
18
|
end
|
|
33
19
|
end
|
|
34
20
|
end
|
|
@@ -1,28 +1,98 @@
|
|
|
1
1
|
module Sequel
|
|
2
2
|
module ActiveRecordConnection
|
|
3
3
|
module Postgres
|
|
4
|
-
def
|
|
5
|
-
|
|
4
|
+
def synchronize(*)
|
|
5
|
+
super do |conn|
|
|
6
|
+
conn.extend(ConnectionMethods)
|
|
7
|
+
conn.instance_variable_set(:@db, self)
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
Utils.add_prepared_statements_cache(conn)
|
|
10
|
+
|
|
11
|
+
Utils.set_value(conn, :type_map_for_results, PG::TypeMapAllStrings.new) do
|
|
12
|
+
yield conn
|
|
13
|
+
end
|
|
11
14
|
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
15
|
end
|
|
19
16
|
|
|
20
|
-
|
|
17
|
+
# Reject unsupported Postgres-specific transaction options.
|
|
18
|
+
def transaction(opts = OPTS)
|
|
21
19
|
%i[deferrable read_only synchronous].each do |key|
|
|
22
|
-
fail Error, "#{key.inspect} transaction option is currently not supported" if
|
|
20
|
+
fail Error, "#{key.inspect} transaction option is currently not supported" if opts.key?(key)
|
|
23
21
|
end
|
|
24
22
|
|
|
25
23
|
super
|
|
24
|
+
rescue => e
|
|
25
|
+
activerecord_connection.clear_cache! if e.class.name == "ActiveRecord::PreparedStatementCacheExpired" && !in_transaction?
|
|
26
|
+
raise
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Copy-pasted from Sequel::Postgres::Adapter.
|
|
30
|
+
module ConnectionMethods
|
|
31
|
+
# The underlying exception classes to reraise as disconnect errors
|
|
32
|
+
# instead of regular database errors.
|
|
33
|
+
DISCONNECT_ERROR_CLASSES = [IOError, Errno::EPIPE, Errno::ECONNRESET, ::PG::ConnectionBad].freeze
|
|
34
|
+
|
|
35
|
+
# Since exception class based disconnect checking may not work,
|
|
36
|
+
# also trying parsing the exception message to look for disconnect
|
|
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
|
+
])}/
|
|
47
|
+
|
|
48
|
+
def async_exec_params(sql, args)
|
|
49
|
+
defined?(super) ? super : async_exec(sql, args)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Raise a Sequel::DatabaseDisconnectError if a one of the disconnect
|
|
53
|
+
# error classes is raised, or a PG::Error is raised and the connection
|
|
54
|
+
# status cannot be determined or it is not OK.
|
|
55
|
+
def check_disconnect_errors
|
|
56
|
+
begin
|
|
57
|
+
yield
|
|
58
|
+
rescue *DISCONNECT_ERROR_CLASSES => e
|
|
59
|
+
disconnect = true
|
|
60
|
+
raise(Sequel.convert_exception_class(e, Sequel::DatabaseDisconnectError))
|
|
61
|
+
rescue PG::Error => e
|
|
62
|
+
disconnect = false
|
|
63
|
+
begin
|
|
64
|
+
s = status
|
|
65
|
+
rescue PG::Error
|
|
66
|
+
disconnect = true
|
|
67
|
+
end
|
|
68
|
+
status_ok = (s == PG::CONNECTION_OK)
|
|
69
|
+
disconnect ||= !status_ok
|
|
70
|
+
disconnect ||= e.message =~ DISCONNECT_ERROR_REGEX
|
|
71
|
+
disconnect ? raise(Sequel.convert_exception_class(e, Sequel::DatabaseDisconnectError)) : raise
|
|
72
|
+
ensure
|
|
73
|
+
block if status_ok && !disconnect
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Execute the given SQL with this connection. If a block is given,
|
|
78
|
+
# yield the results, otherwise, return the number of changed rows.
|
|
79
|
+
def execute(sql, args = nil)
|
|
80
|
+
args = args.map { |v| @db.bound_variable_arg(v, self) } if args
|
|
81
|
+
result = check_disconnect_errors { execute_query(sql, args) }
|
|
82
|
+
|
|
83
|
+
block_given? ? yield(result) : result.cmd_tuples
|
|
84
|
+
ensure
|
|
85
|
+
result.clear if result
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
private
|
|
89
|
+
|
|
90
|
+
# Return the PG::Result containing the query results.
|
|
91
|
+
def execute_query(sql, args)
|
|
92
|
+
@db.log_connection_yield(sql, self, args) do
|
|
93
|
+
args ? async_exec_params(sql, args) : async_exec(sql)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
26
96
|
end
|
|
27
97
|
end
|
|
28
98
|
end
|
|
@@ -7,43 +7,16 @@ module Sequel
|
|
|
7
7
|
end
|
|
8
8
|
end
|
|
9
9
|
|
|
10
|
-
def
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
private
|
|
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
|
|
10
|
+
def synchronize(*)
|
|
11
|
+
super do |conn|
|
|
12
|
+
conn.extended_result_codes = true if conn.respond_to?(:extended_result_codes=)
|
|
22
13
|
|
|
23
|
-
|
|
24
|
-
activerecord_connection.materialize_transactions
|
|
25
|
-
end
|
|
14
|
+
Utils.add_prepared_statements_cache(conn)
|
|
26
15
|
|
|
27
|
-
|
|
28
|
-
|
|
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
|
|
16
|
+
Utils.set_value(conn, :results_as_hash, nil) do
|
|
17
|
+
yield conn
|
|
39
18
|
end
|
|
40
19
|
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
20
|
end
|
|
48
21
|
end
|
|
49
22
|
end
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Gem::Specification.new do |spec|
|
|
2
2
|
spec.name = "sequel-activerecord_connection"
|
|
3
|
-
spec.version = "
|
|
3
|
+
spec.version = "1.1.0"
|
|
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 =
|
|
12
|
+
spec.required_ruby_version = ">= 2.3"
|
|
13
13
|
|
|
14
|
-
spec.add_dependency "sequel", "
|
|
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,35 +1,29 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: sequel-activerecord_connection
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 1.1.0
|
|
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-
|
|
11
|
+
date: 2020-11-08 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: sequel
|
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
|
16
16
|
requirements:
|
|
17
|
-
- - "
|
|
18
|
-
- !ruby/object:Gem::Version
|
|
19
|
-
version: '4.0'
|
|
20
|
-
- - "<"
|
|
17
|
+
- - "~>"
|
|
21
18
|
- !ruby/object:Gem::Version
|
|
22
|
-
version: '
|
|
19
|
+
version: '5.16'
|
|
23
20
|
type: :runtime
|
|
24
21
|
prerelease: false
|
|
25
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
26
23
|
requirements:
|
|
27
|
-
- - "
|
|
28
|
-
- !ruby/object:Gem::Version
|
|
29
|
-
version: '4.0'
|
|
30
|
-
- - "<"
|
|
24
|
+
- - "~>"
|
|
31
25
|
- !ruby/object:Gem::Version
|
|
32
|
-
version: '
|
|
26
|
+
version: '5.16'
|
|
33
27
|
- !ruby/object:Gem::Dependency
|
|
34
28
|
name: activerecord
|
|
35
29
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -50,6 +44,34 @@ dependencies:
|
|
|
50
44
|
- - "<"
|
|
51
45
|
- !ruby/object:Gem::Version
|
|
52
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'
|
|
53
75
|
- !ruby/object:Gem::Dependency
|
|
54
76
|
name: minitest
|
|
55
77
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -64,6 +86,20 @@ dependencies:
|
|
|
64
86
|
- - ">="
|
|
65
87
|
- !ruby/object:Gem::Version
|
|
66
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'
|
|
67
103
|
description: Allows Sequel to use ActiveRecord connection for database interaction.
|
|
68
104
|
email:
|
|
69
105
|
- janko.marohnic@gmail.com
|
|
@@ -92,14 +128,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
92
128
|
requirements:
|
|
93
129
|
- - ">="
|
|
94
130
|
- !ruby/object:Gem::Version
|
|
95
|
-
version: 2.
|
|
131
|
+
version: '2.3'
|
|
96
132
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
97
133
|
requirements:
|
|
98
134
|
- - ">="
|
|
99
135
|
- !ruby/object:Gem::Version
|
|
100
136
|
version: '0'
|
|
101
137
|
requirements: []
|
|
102
|
-
rubygems_version: 3.1.
|
|
138
|
+
rubygems_version: 3.1.4
|
|
103
139
|
signing_key:
|
|
104
140
|
specification_version: 4
|
|
105
141
|
summary: Allows Sequel to use ActiveRecord connection for database interaction.
|