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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 95766ef8e2bf54388e4a139f430c42176760f2b77e0322902445414ffe3d7ddb
4
- data.tar.gz: 35a9fe4b35ff4037299799be9d902e93a29f9d81333ddf084374a4525761a2b1
3
+ metadata.gz: 6cbc8358a3097618cd7acc4e8cf46c013ef4d8b6aefde8e05de0277ca585c0b9
4
+ data.tar.gz: 2ba345ca95455c1acaabf7e2619b81b97b332d1c353bab2de50f700e9ba69148
5
5
  SHA512:
6
- metadata.gz: 93a612e4cfc3454783ba936a19518459fee958e53f8193e9f0561d65961008892a7b8c24a3189e5b2cc8620803e0da1375cd36f5c319af4adf7461656e2e462e
7
- data.tar.gz: 526c8b8ce56b481f5fa98ff1dbd242fc9104665dab6166bd4797514ae187bfe0e9460881dc9f6783e1216323a9a0d701949de2d1972865d464a9a13c51dd4c41
6
+ metadata.gz: a9d3f729bfa2e5a82e78521856b2928887700a67129e47af6eb40d1bf4508b6deb7202928f7626ea3c4f6a424b23df8410756d2333295c1d56fee79b123dc7dd
7
+ data.tar.gz: 6184d4e1dd21bc550ac99285fd379215dcd13b4474aeff39928bdc68cc25ac34b235f68bed03260fae3fea34ef203422e2665bb12c822479e6ce4cc8d54d1f59
@@ -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
- # 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.
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
- 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 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). 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
- ```ruby
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(test: false) # avoid creating a connection
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
- DB = Sequel.postgres(test: false) # for "postgresql" adapter
79
- # or
80
- DB = Sequel.mysql2(test: false) # for "mysql2" adapter
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
- ### Transactions
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
- DB.transaction do
94
- ActiveRecord::Base.transaction do
95
- # this all works
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
- The following Sequel transaction options are currently supported:
88
+ ### Transactions
101
89
 
102
- * `:savepoint`
103
- * `:auto_savepoint`
104
- * `:rollback`
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.transaction(savepoint: true) do # will create a savepoint
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
- The `#in_transaction?` method is supported as well:
101
+ Sequel's transaction API is fully supported:
117
102
 
118
103
  ```rb
119
- ActiveRecord::Base.transaction do
120
- DB.in_transaction? #=> true
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
- Other transaction-related Sequel methods (`#after_commit`, `#after_rollback`
125
- etc) are not supported, because ActiveRecord currently doesn't provide
126
- transactional callbacks on the connection level (only on the model level).
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[:posts].multi_insert [{ id: 1 }, { id: 1 }]
135
- #~> Sequel::UniqueConstraintViolation
118
+ DB.transaction do
119
+ DB.after_commit { ... } # will get executed
120
+ end
136
121
 
137
- DB[:posts].insert(title: nil)
138
- #~> Sequel::NotNullConstraintViolation
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[:posts].insert(author_id: 123)
141
- #~> Sequel::ForeignKeyConstraintViolation
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
- fail Error, "unsupported adapter: #{db.adapter_scheme}"
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
- def transaction(options = {})
25
- %i[isolation num_retries before_retry prepare retry_on].each do |key|
26
- fail Error, "#{key.inspect} transaction option is currently not supported" if options.key?(key)
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
- activerecord_model.transaction(requires_new: !in_transaction? || options[:savepoint] || Thread.current[:sequel_activerecord_auto_savepoint]) do
30
- begin
31
- Thread.current[:sequel_activerecord_auto_savepoint] = true if options[:auto_savepoint]
32
- result = yield
33
- raise ActiveRecord::Rollback if options[:rollback] == :always
34
- result
35
- rescue Sequel::Rollback => exception
36
- raise if options[:rollback] == :reraise
37
- raise ActiveRecord::Rollback, exception.message, exception.backtrace
38
- ensure
39
- Thread.current[:sequel_activerecord_auto_savepoint] = nil if options[:auto_savepoint]
40
- end
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 in_transaction?(*)
45
- activerecord_connection.transaction_open?
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
- %i[after_commit after_rollback rollback_on_exit rollback_checker].each do |meth|
49
- define_method(meth) do |*|
50
- fail Error, "Database##{meth} is currently not supported"
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
- # Avoid calling Sequel's connection pool, instead use ActiveRecord.
55
- def synchronize(*)
56
- if ActiveRecord.version >= Gem::Version.new("5.1.0")
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
- private
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
- def activerecord_raw_connection
68
- activerecord_connection.raw_connection
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 activesupport_interlock(&block)
76
- if ActiveSupport::Dependencies.respond_to?(:interlock)
77
- ActiveSupport::Dependencies.interlock.permit_concurrent_loads(&block)
78
- else
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 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
+ 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 execute(sql, opts=OPTS)
5
- original_query_options = activerecord_raw_connection.query_options.dup
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
- activerecord_raw_connection.query_options.merge!(
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
- result = activerecord_connection.execute(sql)
14
-
15
- if opts[:type] == :select
16
- if block_given?
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 execute(sql, opts=OPTS)
5
- result = activerecord_connection.execute(sql)
4
+ def synchronize(*)
5
+ super do |conn|
6
+ conn.extend(ConnectionMethods)
7
+ conn.instance_variable_set(:@db, self)
6
8
 
7
- if block_given?
8
- yield result
9
- else
10
- result.cmd_tuples
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
- def transaction(options = {})
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 options.key?(key)
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 execute_ddl(sql, opts=OPTS)
11
- execute(sql, opts)
12
- end
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
- if ActiveRecord::VERSION::MAJOR >= 6
24
- activerecord_connection.materialize_transactions
25
- end
14
+ Utils.add_prepared_statements_cache(conn)
26
15
 
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
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.2.6"
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", ">= 4.0", "< 6"
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.2.6
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-07-19 00:00:00.000000000 Z
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: '6'
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: '6'
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.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.