sequel-activerecord_connection 0.4.1 → 1.0.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 +30 -0
- data/README.md +11 -20
- data/lib/sequel/extensions/activerecord_connection.rb +43 -22
- 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 +2 -1
- metadata +17 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 929f5047395464d1d3616aeab41742b6766ae14b96ca1f5b7ac47bb1991f3391
|
4
|
+
data.tar.gz: c931c23d71b73ce2bb35118e1d02ecc2df529f2c297b995c9c5ee46c5b0bfe85
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 062f919b4268a57e392bad5dd7d4a988adc90a8df2d4076f5abfa9afe7c125534f9e7ad1ca16733d0539e622f2b988f95d4653d6b076cd356809d95daf1c24d2
|
7
|
+
data.tar.gz: 1f1efabd6b3ae74c6d35b3d0a23ad87a9d1c939e209afc2f24267840c6c72b2ff60fb1ca86010335b65a251c3c57fa81b300aa4cf38b9ad49df53b00f931d89f
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,33 @@
|
|
1
|
+
## 1.0.0 (2020-10-25)
|
2
|
+
|
3
|
+
* Clear AR statement cache on `ActiveRecord::PreparedStatementCacheExpired` when Sequel holds the transaction (@janko)
|
4
|
+
|
5
|
+
* Pick up `ActiveRecord::Base.default_timezone` being changed on runtime (@janko)
|
6
|
+
|
7
|
+
* Support prepared statements and bound variables in all adapters (@janko)
|
8
|
+
|
9
|
+
* Correctly identify identity columns as primary keys in Postgres adapter (@janko)
|
10
|
+
|
11
|
+
* Avoid using deprecated `sqlite3` API in SQLite adapter (@janko)
|
12
|
+
|
13
|
+
* Allow using any external Active Record adapters (@janko)
|
14
|
+
|
15
|
+
* Avoid potential bugs when converting Active Record exceptions into Sequel exceptions (@janko)
|
16
|
+
|
17
|
+
* Don't use Active Record locks when executing queries with Sequel (@janko)
|
18
|
+
|
19
|
+
* Support `Database#valid_connection?` in Postgres adapter (@janko)
|
20
|
+
|
21
|
+
* Fully utilize Sequel's logic for detecting disconnects in Postgres adapter (@janko)
|
22
|
+
|
23
|
+
* Support `Database#{copy_table,copy_into,listen}` in Postgres adapter (@janko)
|
24
|
+
|
25
|
+
* Log all queries executed by Sequel (@janko)
|
26
|
+
|
27
|
+
* Log executed queries to Sequel logger(s) as well (@janko)
|
28
|
+
|
29
|
+
* Specially label queries executed by Sequel in Active Record logs (@janko)
|
30
|
+
|
1
31
|
## 0.4.1 (2020-09-28)
|
2
32
|
|
3
33
|
* Require Sequel version 5.16.0 or above (@janko)
|
data/README.md
CHANGED
@@ -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
|
-
|
78
|
-
#
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
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
|
@@ -149,19 +153,6 @@ end
|
|
149
153
|
DB.activerecord_model = MyModel
|
150
154
|
```
|
151
155
|
|
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
156
|
## Tests
|
166
157
|
|
167
158
|
You'll first want to run the rake tasks for setting up databases and users:
|
@@ -13,13 +13,12 @@ module Sequel
|
|
13
13
|
|
14
14
|
def self.extended(db)
|
15
15
|
db.activerecord_model = ActiveRecord::Base
|
16
|
-
db.timezone = ActiveRecord::Base.default_timezone
|
17
16
|
|
18
17
|
begin
|
19
18
|
require "sequel/extensions/activerecord_connection/#{db.adapter_scheme}"
|
20
19
|
db.extend Sequel::ActiveRecordConnection.const_get(db.adapter_scheme.capitalize)
|
21
20
|
rescue LoadError
|
22
|
-
|
21
|
+
# assume the Sequel adapter already works with Active Record
|
23
22
|
end
|
24
23
|
end
|
25
24
|
|
@@ -30,15 +29,21 @@ module Sequel
|
|
30
29
|
raise Error, "creating a Sequel connection is not allowed"
|
31
30
|
end
|
32
31
|
|
33
|
-
# Avoid calling Sequel's connection pool, instead use
|
32
|
+
# Avoid calling Sequel's connection pool, instead use Active Record's.
|
34
33
|
def synchronize(*)
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
34
|
+
yield activerecord_connection.raw_connection
|
35
|
+
end
|
36
|
+
|
37
|
+
# Log executed queries into Active Record logger as well.
|
38
|
+
def log_connection_yield(sql, conn, args = nil)
|
39
|
+
sql += "; #{args.inspect}" if args
|
40
|
+
|
41
|
+
activerecord_log(sql) { super }
|
42
|
+
end
|
43
|
+
|
44
|
+
# Match database timezone with Active Record.
|
45
|
+
def timezone
|
46
|
+
@timezone || ActiveRecord::Base.default_timezone
|
42
47
|
end
|
43
48
|
|
44
49
|
private
|
@@ -75,20 +80,19 @@ module Sequel
|
|
75
80
|
super
|
76
81
|
end
|
77
82
|
|
78
|
-
def begin_transaction(conn, opts =
|
83
|
+
def begin_transaction(conn, opts = OPTS)
|
79
84
|
isolation = TRANSACTION_ISOLATION_MAP.fetch(opts[:isolation]) if opts[:isolation]
|
80
85
|
joinable = !opts[:auto_savepoint]
|
81
86
|
|
82
87
|
activerecord_connection.begin_transaction(isolation: isolation, joinable: joinable)
|
83
88
|
end
|
84
89
|
|
85
|
-
def commit_transaction(conn, opts =
|
90
|
+
def commit_transaction(conn, opts = OPTS)
|
86
91
|
activerecord_connection.commit_transaction
|
87
92
|
end
|
88
93
|
|
89
|
-
def rollback_transaction(conn, opts =
|
94
|
+
def rollback_transaction(conn, opts = OPTS)
|
90
95
|
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
96
|
end
|
93
97
|
|
94
98
|
def add_transaction_hook(conn, type, block)
|
@@ -107,19 +111,36 @@ module Sequel
|
|
107
111
|
super
|
108
112
|
end
|
109
113
|
|
110
|
-
def activerecord_raw_connection
|
111
|
-
activerecord_connection.raw_connection
|
112
|
-
end
|
113
|
-
|
114
114
|
def activerecord_connection
|
115
115
|
activerecord_model.connection
|
116
116
|
end
|
117
117
|
|
118
|
-
def
|
119
|
-
|
120
|
-
|
121
|
-
|
118
|
+
def activerecord_log(sql, &block)
|
119
|
+
ActiveSupport::Notifications.instrument(
|
120
|
+
"sql.active_record",
|
121
|
+
sql: sql,
|
122
|
+
name: "Sequel",
|
123
|
+
connection: activerecord_connection,
|
124
|
+
&block
|
125
|
+
)
|
126
|
+
end
|
127
|
+
|
128
|
+
module Utils
|
129
|
+
def self.set_value(object, name, new_value)
|
130
|
+
original_value = object.send(name)
|
131
|
+
object.send(:"#{name}=", new_value)
|
122
132
|
yield
|
133
|
+
ensure
|
134
|
+
object.send(:"#{name}=", original_value)
|
135
|
+
end
|
136
|
+
|
137
|
+
def self.add_prepared_statements_cache(conn)
|
138
|
+
return if conn.respond_to?(:prepared_statements)
|
139
|
+
|
140
|
+
class << conn
|
141
|
+
attr_accessor :prepared_statements
|
142
|
+
end
|
143
|
+
conn.prepared_statements = {}
|
123
144
|
end
|
124
145
|
end
|
125
146
|
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 = "0.
|
3
|
+
spec.version = "1.0.0"
|
4
4
|
spec.authors = ["Janko Marohnić"]
|
5
5
|
spec.email = ["janko.marohnic@gmail.com"]
|
6
6
|
|
@@ -15,6 +15,7 @@ Gem::Specification.new do |spec|
|
|
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,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
|
+
version: 1.0.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-10-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: sequel
|
@@ -58,6 +58,20 @@ dependencies:
|
|
58
58
|
- - ">="
|
59
59
|
- !ruby/object:Gem::Version
|
60
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'
|
61
75
|
description: Allows Sequel to use ActiveRecord connection for database interaction.
|
62
76
|
email:
|
63
77
|
- janko.marohnic@gmail.com
|
@@ -93,7 +107,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
93
107
|
- !ruby/object:Gem::Version
|
94
108
|
version: '0'
|
95
109
|
requirements: []
|
96
|
-
rubygems_version: 3.1.
|
110
|
+
rubygems_version: 3.1.4
|
97
111
|
signing_key:
|
98
112
|
specification_version: 4
|
99
113
|
summary: Allows Sequel to use ActiveRecord connection for database interaction.
|