sequel-activerecord_connection 0.2.6 → 1.0.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 +4 -4
- data/CHANGELOG.md +56 -0
- data/README.md +48 -66
- data/lib/sequel/extensions/activerecord_connection.rb +116 -37
- 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 -12
- data/lib/sequel/extensions/activerecord_connection/sqlite.rb +6 -33
- data/sequel-activerecord_connection.gemspec +3 -2
- metadata +21 -13
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6cbc8358a3097618cd7acc4e8cf46c013ef4d8b6aefde8e05de0277ca585c0b9
|
|
4
|
+
data.tar.gz: 2ba345ca95455c1acaabf7e2619b81b97b332d1c353bab2de50f700e9ba69148
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a9d3f729bfa2e5a82e78521856b2928887700a67129e47af6eb40d1bf4508b6deb7202928f7626ea3c4f6a424b23df8410756d2333295c1d56fee79b123dc7dd
|
|
7
|
+
data.tar.gz: 6184d4e1dd21bc550ac99285fd379215dcd13b4474aeff39928bdc68cc25ac34b235f68bed03260fae3fea34ef203422e2665bb12c822479e6ce4cc8d54d1f59
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,59 @@
|
|
|
1
|
+
## 1.0.1 (2020-10-28)
|
|
2
|
+
|
|
3
|
+
* Use Active Record connection lock in `Database#synchronize` (@janko)
|
|
4
|
+
|
|
5
|
+
## 1.0.0 (2020-10-25)
|
|
6
|
+
|
|
7
|
+
* Clear AR statement cache on `ActiveRecord::PreparedStatementCacheExpired` when Sequel holds the transaction (@janko)
|
|
8
|
+
|
|
9
|
+
* Pick up `ActiveRecord::Base.default_timezone` being changed on runtime (@janko)
|
|
10
|
+
|
|
11
|
+
* Support prepared statements and bound variables in all adapters (@janko)
|
|
12
|
+
|
|
13
|
+
* Correctly identify identity columns as primary keys in Postgres adapter (@janko)
|
|
14
|
+
|
|
15
|
+
* Avoid using deprecated `sqlite3` API in SQLite adapter (@janko)
|
|
16
|
+
|
|
17
|
+
* Allow using any external Active Record adapters (@janko)
|
|
18
|
+
|
|
19
|
+
* Avoid potential bugs when converting Active Record exceptions into Sequel exceptions (@janko)
|
|
20
|
+
|
|
21
|
+
* Don't use Active Record locks when executing queries with Sequel (@janko)
|
|
22
|
+
|
|
23
|
+
* Support `Database#valid_connection?` in Postgres adapter (@janko)
|
|
24
|
+
|
|
25
|
+
* Fully utilize Sequel's logic for detecting disconnects in Postgres adapter (@janko)
|
|
26
|
+
|
|
27
|
+
* Support `Database#{copy_table,copy_into,listen}` in Postgres adapter (@janko)
|
|
28
|
+
|
|
29
|
+
* Log all queries executed by Sequel (@janko)
|
|
30
|
+
|
|
31
|
+
* Log executed queries to Sequel logger(s) as well (@janko)
|
|
32
|
+
|
|
33
|
+
* Specially label queries executed by Sequel in Active Record logs (@janko)
|
|
34
|
+
|
|
35
|
+
## 0.4.1 (2020-09-28)
|
|
36
|
+
|
|
37
|
+
* Require Sequel version 5.16.0 or above (@janko)
|
|
38
|
+
|
|
39
|
+
## 0.4.0 (2020-09-28)
|
|
40
|
+
|
|
41
|
+
* Return correct result of `Database#in_transaction?` after ActiveRecord transaction exited (@janko)
|
|
42
|
+
|
|
43
|
+
* Make ActiveRecord create a savepoint inside a Sequel transaction with `auto_savepoint: true` (@janko)
|
|
44
|
+
|
|
45
|
+
* Make Sequel create a savepoint inside ActiveRecord transaction with `joinable: false` (@janko)
|
|
46
|
+
|
|
47
|
+
* Improve reliability of nested transactions when combining Sequel and ActiveRecord (@janko)
|
|
48
|
+
|
|
49
|
+
* Raise error when attempting to add an `after_commit`/`after_rollback` hook on ActiveRecord transaction (@janko)
|
|
50
|
+
|
|
51
|
+
* Fix infinite loop that could happen with transactional Rails tests (@janko)
|
|
52
|
+
|
|
53
|
+
## 0.3.0 (2020-07-24)
|
|
54
|
+
|
|
55
|
+
* Fully support Sequel transaction API (all transaction options, transaction/savepoint hooks etc.) (@janko)
|
|
56
|
+
|
|
1
57
|
## 0.2.6 (2020-07-19)
|
|
2
58
|
|
|
3
59
|
* Return block result in `Sequel::Database#transaction` (@zabolotnov87, @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.
|
|
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
7
|
interaction (e.g. [Rodauth]), but you want to avoid creating a separate
|
|
10
8
|
database connection. Or if you're transitioning from ActiveRecord to Sequel,
|
|
11
9
|
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"
|
|
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,70 +72,68 @@ 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
|
-
# or
|
|
82
|
-
DB = Sequel.sqlite(test: false) # for "sqlite3" adapter
|
|
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
|
|
83
78
|
```
|
|
84
79
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
The database extension overrides Sequel transactions to use ActiveRecord
|
|
88
|
-
transcations, which allows using ActiveRecord inside Sequel transactions (and
|
|
89
|
-
vice-versa), and have things like ActiveRecord's transactional callbacks still
|
|
90
|
-
work correctly.
|
|
80
|
+
If you're on JRuby, you should be using the JDBC adapters:
|
|
91
81
|
|
|
92
82
|
```rb
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
end
|
|
97
|
-
end
|
|
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
|
|
98
86
|
```
|
|
99
87
|
|
|
100
|
-
|
|
88
|
+
### Transactions
|
|
101
89
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
90
|
+
This database extension keeps the transaction state of Sequel and ActiveRecord
|
|
91
|
+
in sync, allowing you to use Sequel and ActiveRecord transactions
|
|
92
|
+
interchangeably (including nesting them), and have things like ActiveRecord's
|
|
93
|
+
and Sequel's transactional callbacks still work correctly.
|
|
105
94
|
|
|
106
95
|
```rb
|
|
107
96
|
ActiveRecord::Base.transaction do
|
|
108
|
-
DB.
|
|
109
|
-
DB.transaction do # will not create a savepoint
|
|
110
|
-
# ...
|
|
111
|
-
end
|
|
112
|
-
end
|
|
97
|
+
DB.in_transaction? #=> true
|
|
113
98
|
end
|
|
114
99
|
```
|
|
115
100
|
|
|
116
|
-
|
|
101
|
+
Sequel's transaction API is fully supported:
|
|
117
102
|
|
|
118
103
|
```rb
|
|
119
|
-
|
|
120
|
-
DB.
|
|
104
|
+
DB.transaction(isolation: :serializable) do
|
|
105
|
+
DB.after_commit { ... } # executed after transaction commits
|
|
106
|
+
DB.transaction(savepoint: true) do # creates a savepoint
|
|
107
|
+
DB.after_commit(savepoint: true) { ... } # executed if all enclosing savepoints have been released
|
|
108
|
+
end
|
|
121
109
|
end
|
|
122
110
|
```
|
|
123
111
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
### Exceptions
|
|
129
|
-
|
|
130
|
-
To ensure Sequel compatibility, any `ActiveRecord::StatementInvalid` exceptions
|
|
131
|
-
will be translated into Sequel exceptions:
|
|
112
|
+
One caveat to keep in mind is that using Sequel's transaction/savepoint hooks
|
|
113
|
+
currently don't work if ActiveRecord holds the corresponding
|
|
114
|
+
transaction/savepoint. This is because it's difficult to be notified when
|
|
115
|
+
ActiveRecord commits or rolls back the transaction/savepoint.
|
|
132
116
|
|
|
133
117
|
```rb
|
|
134
|
-
DB
|
|
135
|
-
|
|
118
|
+
DB.transaction do
|
|
119
|
+
DB.after_commit { ... } # will get executed
|
|
120
|
+
end
|
|
136
121
|
|
|
137
|
-
DB
|
|
138
|
-
|
|
122
|
+
DB.transaction do
|
|
123
|
+
DB.transaction(savepoint: true) do
|
|
124
|
+
DB.after_commit(savepoint: true) { ... } # will get executed
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
ActiveRecord::Base.transaction do
|
|
129
|
+
DB.after_commit { ... } # not allowed (will raise Sequel::ActiveRecordConnection::Error)
|
|
130
|
+
end
|
|
139
131
|
|
|
140
|
-
DB
|
|
141
|
-
|
|
132
|
+
DB.transaction do
|
|
133
|
+
ActiveRecord::Base.transaction(requires_new: true) do
|
|
134
|
+
DB.after_commit(savepoint: true) { ... } # not allowed (will raise Sequel::ActiveRecordConnection::Error)
|
|
135
|
+
end
|
|
136
|
+
end
|
|
142
137
|
```
|
|
143
138
|
|
|
144
139
|
### Model
|
|
@@ -156,19 +151,6 @@ end
|
|
|
156
151
|
DB.activerecord_model = MyModel
|
|
157
152
|
```
|
|
158
153
|
|
|
159
|
-
### Timezone
|
|
160
|
-
|
|
161
|
-
Sequel's database timezone will be automatically set to ActiveRecord's default
|
|
162
|
-
timezone (`:utc` by default) when the extension is loaded.
|
|
163
|
-
|
|
164
|
-
If you happen to be changing ActiveRecord's default timezone after you've
|
|
165
|
-
loaded the extension, make sure to reflect that in your Sequel database object,
|
|
166
|
-
for example:
|
|
167
|
-
|
|
168
|
-
```rb
|
|
169
|
-
DB.timezone = :local
|
|
170
|
-
```
|
|
171
|
-
|
|
172
154
|
## Tests
|
|
173
155
|
|
|
174
156
|
You'll first want to run the rake tasks for setting up databases and users:
|
|
@@ -1,16 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Sequel
|
|
2
4
|
module ActiveRecordConnection
|
|
3
5
|
Error = Class.new(Sequel::Error)
|
|
4
6
|
|
|
7
|
+
TRANSACTION_ISOLATION_MAP = {
|
|
8
|
+
uncommitted: :read_uncommitted,
|
|
9
|
+
committed: :read_committed,
|
|
10
|
+
repeatable: :repeatable_read,
|
|
11
|
+
serializable: :serializable,
|
|
12
|
+
}
|
|
13
|
+
|
|
5
14
|
def self.extended(db)
|
|
6
15
|
db.activerecord_model = ActiveRecord::Base
|
|
7
|
-
db.timezone = ActiveRecord::Base.default_timezone
|
|
8
16
|
|
|
9
17
|
begin
|
|
10
18
|
require "sequel/extensions/activerecord_connection/#{db.adapter_scheme}"
|
|
11
19
|
db.extend Sequel::ActiveRecordConnection.const_get(db.adapter_scheme.capitalize)
|
|
12
20
|
rescue LoadError
|
|
13
|
-
|
|
21
|
+
# assume the Sequel adapter already works with Active Record
|
|
14
22
|
end
|
|
15
23
|
end
|
|
16
24
|
|
|
@@ -21,62 +29,133 @@ module Sequel
|
|
|
21
29
|
raise Error, "creating a Sequel connection is not allowed"
|
|
22
30
|
end
|
|
23
31
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
32
|
+
# Avoid calling Sequel's connection pool, instead use Active Record's.
|
|
33
|
+
def synchronize(*)
|
|
34
|
+
activerecord_lock do
|
|
35
|
+
yield activerecord_connection.raw_connection
|
|
27
36
|
end
|
|
37
|
+
end
|
|
28
38
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
# Log executed queries into Active Record logger as well.
|
|
40
|
+
def log_connection_yield(sql, conn, args = nil)
|
|
41
|
+
sql += "; #{args.inspect}" if args
|
|
42
|
+
|
|
43
|
+
activerecord_log(sql) { super }
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Match database timezone with Active Record.
|
|
47
|
+
def timezone
|
|
48
|
+
@timezone || ActiveRecord::Base.default_timezone
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
# Synchronizes transaction state with ActiveRecord. Sequel uses this
|
|
54
|
+
# information to know whether we're in a transaction, whether to create a
|
|
55
|
+
# savepoint, when to run transaction/savepoint hooks etc.
|
|
56
|
+
def _trans(conn)
|
|
57
|
+
hash = super || { savepoints: [], activerecord: true }
|
|
58
|
+
|
|
59
|
+
# add any ActiveRecord transactions/savepoints that have been opened
|
|
60
|
+
# directly via ActiveRecord::Base.transaction
|
|
61
|
+
while hash[:savepoints].length < activerecord_connection.open_transactions
|
|
62
|
+
hash[:savepoints] << { activerecord: true }
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# remove any ActiveRecord transactions/savepoints that have been closed
|
|
66
|
+
# directly via ActiveRecord::Base.transaction
|
|
67
|
+
while hash[:savepoints].length > activerecord_connection.open_transactions && hash[:savepoints].last[:activerecord]
|
|
68
|
+
hash[:savepoints].pop
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# sync knowledge about joinability of current ActiveRecord transaction/savepoint
|
|
72
|
+
if activerecord_connection.transaction_open? && !activerecord_connection.current_transaction.joinable?
|
|
73
|
+
hash[:savepoints].last[:auto_savepoint] = true
|
|
41
74
|
end
|
|
75
|
+
|
|
76
|
+
if hash[:savepoints].empty? && hash[:activerecord]
|
|
77
|
+
Sequel.synchronize { @transactions.delete(conn) }
|
|
78
|
+
else
|
|
79
|
+
Sequel.synchronize { @transactions[conn] = hash }
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
super
|
|
42
83
|
end
|
|
43
84
|
|
|
44
|
-
def
|
|
45
|
-
|
|
85
|
+
def begin_transaction(conn, opts = OPTS)
|
|
86
|
+
isolation = TRANSACTION_ISOLATION_MAP.fetch(opts[:isolation]) if opts[:isolation]
|
|
87
|
+
joinable = !opts[:auto_savepoint]
|
|
88
|
+
|
|
89
|
+
activerecord_connection.begin_transaction(isolation: isolation, joinable: joinable)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def commit_transaction(conn, opts = OPTS)
|
|
93
|
+
activerecord_connection.commit_transaction
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def rollback_transaction(conn, opts = OPTS)
|
|
97
|
+
activerecord_connection.rollback_transaction
|
|
46
98
|
end
|
|
47
99
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
fail Error, "
|
|
100
|
+
def add_transaction_hook(conn, type, block)
|
|
101
|
+
if _trans(conn)[:activerecord]
|
|
102
|
+
fail Error, "cannot add transaction hook when ActiveRecord holds the outer transaction"
|
|
51
103
|
end
|
|
104
|
+
|
|
105
|
+
super
|
|
52
106
|
end
|
|
53
107
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
activerecord_connection.lock.synchronize do
|
|
58
|
-
yield activerecord_raw_connection
|
|
59
|
-
end
|
|
60
|
-
else
|
|
61
|
-
yield activerecord_raw_connection
|
|
108
|
+
def add_savepoint_hook(conn, type, block)
|
|
109
|
+
if _trans(conn)[:savepoints].last[:activerecord]
|
|
110
|
+
fail Error, "cannot add savepoint hook when ActiveRecord holds the current savepoint"
|
|
62
111
|
end
|
|
112
|
+
|
|
113
|
+
super
|
|
63
114
|
end
|
|
64
115
|
|
|
65
|
-
|
|
116
|
+
# Active Record doesn't guarantee that a single connection can only be used
|
|
117
|
+
# by one thread at a time, so we need to use locking, which is what Active
|
|
118
|
+
# Record does internally as well.
|
|
119
|
+
def activerecord_lock
|
|
120
|
+
return yield if ActiveRecord.version < Gem::Version.new("5.1.0")
|
|
66
121
|
|
|
67
|
-
|
|
68
|
-
|
|
122
|
+
activerecord_connection.lock.synchronize do
|
|
123
|
+
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
|
|
124
|
+
yield
|
|
125
|
+
end
|
|
126
|
+
end
|
|
69
127
|
end
|
|
70
128
|
|
|
71
129
|
def activerecord_connection
|
|
72
130
|
activerecord_model.connection
|
|
73
131
|
end
|
|
74
132
|
|
|
75
|
-
def
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
133
|
+
def activerecord_log(sql, &block)
|
|
134
|
+
ActiveSupport::Notifications.instrument(
|
|
135
|
+
"sql.active_record",
|
|
136
|
+
sql: sql,
|
|
137
|
+
name: "Sequel",
|
|
138
|
+
connection: activerecord_connection,
|
|
139
|
+
&block
|
|
140
|
+
)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
module Utils
|
|
144
|
+
def self.set_value(object, name, new_value)
|
|
145
|
+
original_value = object.send(name)
|
|
146
|
+
object.send(:"#{name}=", new_value)
|
|
79
147
|
yield
|
|
148
|
+
ensure
|
|
149
|
+
object.send(:"#{name}=", original_value)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def self.add_prepared_statements_cache(conn)
|
|
153
|
+
return if conn.respond_to?(:prepared_statements)
|
|
154
|
+
|
|
155
|
+
class << conn
|
|
156
|
+
attr_accessor :prepared_statements
|
|
157
|
+
end
|
|
158
|
+
conn.prepared_statements = {}
|
|
80
159
|
end
|
|
81
160
|
end
|
|
82
161
|
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,26 +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::StatementInvalid => exception
|
|
13
|
-
raise_error(exception.cause, classes: database_error_classes)
|
|
14
|
-
ensure
|
|
15
|
-
result.clear if result
|
|
16
15
|
end
|
|
17
16
|
|
|
18
|
-
|
|
17
|
+
# Reject unsupported Postgres-specific transaction options.
|
|
18
|
+
def transaction(opts = OPTS)
|
|
19
19
|
%i[deferrable read_only synchronous].each do |key|
|
|
20
|
-
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)
|
|
21
21
|
end
|
|
22
22
|
|
|
23
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
|
|
24
96
|
end
|
|
25
97
|
end
|
|
26
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 = "0.
|
|
3
|
+
spec.version = "1.0.1"
|
|
4
4
|
spec.authors = ["Janko Marohnić"]
|
|
5
5
|
spec.email = ["janko.marohnic@gmail.com"]
|
|
6
6
|
|
|
@@ -11,10 +11,11 @@ Gem::Specification.new do |spec|
|
|
|
11
11
|
|
|
12
12
|
spec.required_ruby_version = Gem::Requirement.new(">= 2.2.0")
|
|
13
13
|
|
|
14
|
-
spec.add_dependency "sequel", "
|
|
14
|
+
spec.add_dependency "sequel", "~> 5.16"
|
|
15
15
|
spec.add_dependency "activerecord", ">= 4.2", "< 7"
|
|
16
16
|
|
|
17
17
|
spec.add_development_dependency "minitest"
|
|
18
|
+
spec.add_development_dependency "warning" if RUBY_VERSION >= "2.4"
|
|
18
19
|
|
|
19
20
|
spec.files = Dir["README.md", "LICENSE.txt", "CHANGELOG.md", "lib/**/*.rb", "*.gemspec"]
|
|
20
21
|
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: 0.
|
|
4
|
+
version: 1.0.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-
|
|
11
|
+
date: 2020-10-29 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
|
|
@@ -64,6 +58,20 @@ dependencies:
|
|
|
64
58
|
- - ">="
|
|
65
59
|
- !ruby/object:Gem::Version
|
|
66
60
|
version: '0'
|
|
61
|
+
- !ruby/object:Gem::Dependency
|
|
62
|
+
name: warning
|
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - ">="
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '0'
|
|
68
|
+
type: :development
|
|
69
|
+
prerelease: false
|
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - ">="
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '0'
|
|
67
75
|
description: Allows Sequel to use ActiveRecord connection for database interaction.
|
|
68
76
|
email:
|
|
69
77
|
- janko.marohnic@gmail.com
|
|
@@ -99,7 +107,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
99
107
|
- !ruby/object:Gem::Version
|
|
100
108
|
version: '0'
|
|
101
109
|
requirements: []
|
|
102
|
-
rubygems_version: 3.1.
|
|
110
|
+
rubygems_version: 3.1.4
|
|
103
111
|
signing_key:
|
|
104
112
|
specification_version: 4
|
|
105
113
|
summary: Allows Sequel to use ActiveRecord connection for database interaction.
|