sequel-activerecord_connection 0.2.2 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b3007b81d8906ff8ddbac1e5e5adadbc555eddacf6dbd7ef92d9062a72d3a751
4
- data.tar.gz: 9e1ee7db2f4ee482abe17ee893d9f222325ec95f24e6a3ada63b1f5c10f94634
3
+ metadata.gz: d4078a41f54331cbb97fc266abdbe24cf8c297be14bb7f92b3bf605bf9b549bd
4
+ data.tar.gz: af466cd9b062279a7781106136a05151f02339830469136cdf9b58d629397b98
5
5
  SHA512:
6
- metadata.gz: d73aba0aa9bd41b9a66fab9d3da6db349bf0b2ae39fb3354243146485b5b647f00c55d89887d026752dc5b007fc2504f2d69a01e86f1f712d0494277cb8b4af1
7
- data.tar.gz: cfbb41ff961d3c5b83d0ab21cb90706ff5a472e666dc469fbf1f9be4dd4b6d8407b5e93f89cdc6d2778e1fc3a6c0fd99a205e51a49851af129f0455efdb0a1fa
6
+ metadata.gz: e28881de46188d6dc143952a473e00254b0742c497e7888e8d334ff33b627d3d90d9174675809cc3248b15cfcc800908bc83dda5a8da3fee93c4048c44282424
7
+ data.tar.gz: 7c0a46d18a45e76ca4a7af11d467b957035dd4b62adbdcaf64d8e9cafe1bfb406fa40c3d4feadff10c823d2535cab978111203ade32ea519f991e828ed1df033
@@ -0,0 +1,37 @@
1
+ ## 0.3.0 (2020-07-24)
2
+
3
+ * Fully support Sequel transaction API (all transaction options, transaction/savepoint hooks etc.) (@janko)
4
+
5
+ ## 0.2.6 (2020-07-19)
6
+
7
+ * Return block result in `Sequel::Database#transaction` (@zabolotnov87, @janko)
8
+
9
+ * Fix `Sequel::Model#save_changes` or `#save` with additional options not executing (@zabolotnov87, @janko)
10
+
11
+ ## 0.2.5 (2020-06-04)
12
+
13
+ * Use `#current_timestamp_utc` for the JDBC SQLite adapter as well (@HoneyryderChuck)
14
+
15
+ ## 0.2.4 (2020-06-03)
16
+
17
+ * Add JRuby support for ActiveRecord 6.0 and 5.2 (@HoneyryderChuck)
18
+
19
+ * Use `#current_timestamp_utc` setting for SQLite adapter on Sequel >= 5.33 (@HoneyryderChuck)
20
+
21
+ ## 0.2.3 (2020-05-25)
22
+
23
+ * Fix Ruby 2.7 kwargs warnings in `#transaction` (@HoneyryderChuck)
24
+
25
+ ## 0.2.2 (2020-05-02)
26
+
27
+ * Add support for ActiveRecord 4.2 (@janko)
28
+
29
+ ## 0.2.1 (2020-05-02)
30
+
31
+ * Add support for Active Record 5.0, 5.1 and 5.2 (@janko)
32
+
33
+ * Allow Sequel 4.x (@janko)
34
+
35
+ ## 0.2.0 (2020-04-29)
36
+
37
+ * Rename to `sequel-activerecord_connection` and make it a Sequel extension (@janko)
data/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  This is an extension for [Sequel] that allows it to reuse an existing
4
4
  ActiveRecord connection for database interaction. It works on ActiveRecord 4.2
5
5
  or higher, and supports the built-in `postgresql`, `mysql2` and `sqlite3`
6
- adapters.
6
+ adapters, as well as JDBC adapter for JRuby.
7
7
 
8
8
  This can be useful if you're using a library that uses Sequel for database
9
9
  interaction (e.g. [Rodauth]), but you want to avoid creating a separate
@@ -11,15 +11,15 @@ database connection. Or if you're transitioning from ActiveRecord to Sequel,
11
11
  and want the database connection to be shared.
12
12
 
13
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.
14
+ possible. That being said, this implementation passes Rodauth's test suite
15
+ (for all adapters), which has some fairly advanced Sequel usage.
16
16
 
17
17
  ## Installation
18
18
 
19
19
  Add this line to your application's Gemfile:
20
20
 
21
21
  ```ruby
22
- gem "sequel-activerecord_connection"
22
+ gem "sequel-activerecord_connection", "~> 0.3"
23
23
  ```
24
24
 
25
25
  And then execute:
@@ -80,65 +80,62 @@ DB = Sequel.postgres(test: false) # for "postgresql" adapter
80
80
  DB = Sequel.mysql2(test: false) # for "mysql2" adapter
81
81
  # or
82
82
  DB = Sequel.sqlite(test: false) # for "sqlite3" adapter
83
+ # or
84
+ DB = Sequel.jdbc(test: false) # for JDBC adapter
83
85
  ```
84
86
 
85
87
  ### Transactions
86
88
 
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.
89
+ This database extension keeps the transaction state of Sequel and ActiveRecord
90
+ in sync, allowing you to use Sequel and ActiveRecord transactions
91
+ interchangeably (including nesting them), and have things like ActiveRecord's
92
+ and Sequel's transactional callbacks still work correctly.
91
93
 
92
94
  ```rb
93
- DB.transaction do
94
- ActiveRecord::Base.transaction do
95
- # this all works
96
- end
95
+ ActiveRecord::Base.transaction do
96
+ DB.in_transaction? #=> true
97
97
  end
98
98
  ```
99
99
 
100
- The following Sequel transaction options are currently supported:
101
-
102
- * `:savepoint`
103
- * `:auto_savepoint`
104
- * `:rollback`
100
+ Sequel's transaction API is fully supported:
105
101
 
106
102
  ```rb
107
- 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
103
+ DB.transaction(isolation: :serializable) do
104
+ DB.after_commit { ... } # call block after transaction commits
105
+ DB.transaction(savepoint: true) do # creates a savepoint
106
+ # ...
112
107
  end
113
108
  end
114
109
  ```
115
110
 
116
- The `#in_transaction?` method is supported as well:
111
+ One caveat to keep in mind is that Sequel's transaction hooks
112
+ (`after_commit`, `after_rollback`) will *not* run if ActiveRecord holds the
113
+ outer transaction:
117
114
 
118
115
  ```rb
119
- ActiveRecord::Base.transaction do
120
- DB.in_transaction? #=> true
116
+ DB.transaction do
117
+ DB.after_commit { ... } # will get executed
121
118
  end
122
- ```
123
119
 
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).
120
+ ActiveRecord::Base.transaction do
121
+ DB.after_commit { ... } # won't get executed
122
+ end
127
123
 
128
- ### Exceptions
124
+ ActiveRecord::Base.transaction do
125
+ DB.transaction do
126
+ DB.after_commit { ... } # won't get executed
127
+ end
128
+ end
129
+ ```
129
130
 
130
- To ensure Sequel compatibility, any `ActiveRecord::StatementInvalid` exceptions
131
- will be translated into Sequel exceptions:
131
+ Savepoint hooks should still work, though:
132
132
 
133
133
  ```rb
134
- DB[:posts].multi_insert [{ id: 1 }, { id: 1 }]
135
- #~> Sequel::UniqueConstraintViolation
136
-
137
- DB[:posts].insert(title: nil)
138
- #~> Sequel::NotNullConstraintViolation
139
-
140
- DB[:posts].insert(author_id: 123)
141
- #~> Sequel::ForeignKeyConstraintViolation
134
+ ActiveRecord::Base.transaction do
135
+ DB.transaction(savepoint: true) do
136
+ DB.after_commit { ... } # will get executed after savepoint is released
137
+ end
138
+ end
142
139
  ```
143
140
 
144
141
  ### Model
@@ -2,6 +2,13 @@ module Sequel
2
2
  module ActiveRecordConnection
3
3
  Error = Class.new(Sequel::Error)
4
4
 
5
+ TRANSACTION_ISOLATION_MAP = {
6
+ uncommitted: :read_uncommitted,
7
+ committed: :read_committed,
8
+ repeatable: :repeatable_read,
9
+ serializable: :serializable,
10
+ }
11
+
5
12
  def self.extended(db)
6
13
  db.activerecord_model = ActiveRecord::Base
7
14
  db.timezone = ActiveRecord::Base.default_timezone
@@ -21,52 +28,65 @@ module Sequel
21
28
  raise Error, "creating a Sequel connection is not allowed"
22
29
  end
23
30
 
24
- def transaction(savepoint: nil, rollback: nil, auto_savepoint: nil, server: nil, **options)
25
- fail Error, "#{options} transaction options are currently not supported" unless options.empty?
26
-
27
- if in_transaction?
28
- requires_new = savepoint || Thread.current[:sequel_activerecord_auto_savepoint]
31
+ # Avoid calling Sequel's connection pool, instead use ActiveRecord.
32
+ def synchronize(*)
33
+ if ActiveRecord.version >= Gem::Version.new("5.1.0")
34
+ activerecord_connection.lock.synchronize do
35
+ yield activerecord_raw_connection
36
+ end
29
37
  else
30
- requires_new = true
38
+ yield activerecord_raw_connection
31
39
  end
40
+ end
32
41
 
33
- activerecord_model.transaction(requires_new: requires_new) do
34
- begin
35
- Thread.current[:sequel_activerecord_auto_savepoint] = true if auto_savepoint
36
- yield
37
- rescue Sequel::Rollback => exception
38
- raise if rollback == :reraise
39
- raise ActiveRecord::Rollback, exception.message, exception.backtrace
40
- ensure
41
- Thread.current[:sequel_activerecord_auto_savepoint] = nil if auto_savepoint
42
+ private
43
+
44
+ # Backfills any ActiveRecord transactions/savepoints that have been opened
45
+ # directly via ActiveRecord::Base.transaction. Sequel uses this information
46
+ # to know whether we're in a transaction, whether to create a savepoint,
47
+ # when to run transaction/savepoint hooks etc.
48
+ def _trans(conn)
49
+ Sequel.synchronize do
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
42
57
  end
43
58
 
44
- raise ActiveRecord::Rollback if rollback == :always
59
+ @transactions[conn] = result if result
60
+ result
45
61
  end
46
62
  end
47
63
 
48
- def in_transaction?(*)
49
- activerecord_connection.transaction_open?
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
+ super
50
70
  end
51
71
 
52
- %i[after_commit after_rollback rollback_on_exit rollback_checker].each do |meth|
53
- define_method(meth) do |*|
54
- fail Error, "Database##{meth} is currently not supported"
55
- end
72
+ def begin_transaction(conn, opts = {})
73
+ isolation = TRANSACTION_ISOLATION_MAP.fetch(opts[:isolation]) if opts[:isolation]
74
+
75
+ activerecord_connection.begin_transaction(isolation: isolation)
56
76
  end
57
77
 
58
- # Avoid calling Sequel's connection pool, instead use ActiveRecord.
59
- def synchronize(*)
60
- if ActiveRecord.version >= Gem::Version.new("5.1.0")
61
- activerecord_connection.lock.synchronize do
62
- yield activerecord_raw_connection
63
- end
64
- else
65
- yield activerecord_raw_connection
66
- end
78
+ def commit_transaction(conn, opts = {})
79
+ activerecord_connection.commit_transaction
67
80
  end
68
81
 
69
- private
82
+ def rollback_transaction(conn, opts = {})
83
+ 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
+ end
86
+
87
+ def savepoint_level(conn)
88
+ activerecord_connection.open_transactions
89
+ end
70
90
 
71
91
  def activerecord_raw_connection
72
92
  activerecord_connection.raw_connection
@@ -0,0 +1,38 @@
1
+ module Sequel
2
+ module ActiveRecordConnection
3
+ module Jdbc
4
+ def self.extended(db)
5
+ if db.timezone == :utc && db.respond_to?(:current_timestamp_utc)
6
+ db.current_timestamp_utc = true
7
+ end
8
+ end
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
32
+ end
33
+ rescue ActiveRecord::StatementInvalid => exception
34
+ raise_error(exception.cause, classes: database_error_classes)
35
+ end
36
+ end
37
+ end
38
+ end
@@ -9,11 +9,21 @@ module Sequel
9
9
  else
10
10
  result.cmd_tuples
11
11
  end
12
+ rescue ActiveRecord::PreparedStatementCacheExpired
13
+ raise # ActiveRecord's transaction manager needs to handle this exception
12
14
  rescue ActiveRecord::StatementInvalid => exception
13
15
  raise_error(exception.cause, classes: database_error_classes)
14
16
  ensure
15
17
  result.clear if result
16
18
  end
19
+
20
+ def transaction(options = {})
21
+ %i[deferrable read_only synchronous].each do |key|
22
+ fail Error, "#{key.inspect} transaction option is currently not supported" if options.key?(key)
23
+ end
24
+
25
+ super
26
+ end
17
27
  end
18
28
  end
19
29
  end
@@ -1,6 +1,12 @@
1
1
  module Sequel
2
2
  module ActiveRecordConnection
3
3
  module Sqlite
4
+ def self.extended(db)
5
+ if db.timezone == :utc && db.respond_to?(:current_timestamp_utc)
6
+ db.current_timestamp_utc = true
7
+ end
8
+ end
9
+
4
10
  def execute_ddl(sql, opts=OPTS)
5
11
  execute(sql, opts)
6
12
  end
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |spec|
2
2
  spec.name = "sequel-activerecord_connection"
3
- spec.version = "0.2.2"
3
+ spec.version = "0.3.0"
4
4
  spec.authors = ["Janko Marohnić"]
5
5
  spec.email = ["janko.marohnic@gmail.com"]
6
6
 
@@ -14,9 +14,6 @@ Gem::Specification.new do |spec|
14
14
  spec.add_dependency "sequel", ">= 4.0", "< 6"
15
15
  spec.add_dependency "activerecord", ">= 4.2", "< 7"
16
16
 
17
- spec.add_development_dependency "pg", "~> 1.0"
18
- spec.add_development_dependency "mysql2", "~> 0.5"
19
- spec.add_development_dependency "sqlite3", "~> 1.3"
20
17
  spec.add_development_dependency "minitest"
21
18
 
22
19
  spec.files = Dir["README.md", "LICENSE.txt", "CHANGELOG.md", "lib/**/*.rb", "*.gemspec"]
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.2.2
4
+ version: 0.3.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-05-02 00:00:00.000000000 Z
11
+ date: 2020-07-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sequel
@@ -50,48 +50,6 @@ dependencies:
50
50
  - - "<"
51
51
  - !ruby/object:Gem::Version
52
52
  version: '7'
53
- - !ruby/object:Gem::Dependency
54
- name: pg
55
- requirement: !ruby/object:Gem::Requirement
56
- requirements:
57
- - - "~>"
58
- - !ruby/object:Gem::Version
59
- version: '1.0'
60
- type: :development
61
- prerelease: false
62
- version_requirements: !ruby/object:Gem::Requirement
63
- requirements:
64
- - - "~>"
65
- - !ruby/object:Gem::Version
66
- version: '1.0'
67
- - !ruby/object:Gem::Dependency
68
- name: mysql2
69
- requirement: !ruby/object:Gem::Requirement
70
- requirements:
71
- - - "~>"
72
- - !ruby/object:Gem::Version
73
- version: '0.5'
74
- type: :development
75
- prerelease: false
76
- version_requirements: !ruby/object:Gem::Requirement
77
- requirements:
78
- - - "~>"
79
- - !ruby/object:Gem::Version
80
- version: '0.5'
81
- - !ruby/object:Gem::Dependency
82
- name: sqlite3
83
- requirement: !ruby/object:Gem::Requirement
84
- requirements:
85
- - - "~>"
86
- - !ruby/object:Gem::Version
87
- version: '1.3'
88
- type: :development
89
- prerelease: false
90
- version_requirements: !ruby/object:Gem::Requirement
91
- requirements:
92
- - - "~>"
93
- - !ruby/object:Gem::Version
94
- version: '1.3'
95
53
  - !ruby/object:Gem::Dependency
96
54
  name: minitest
97
55
  requirement: !ruby/object:Gem::Requirement
@@ -113,9 +71,11 @@ executables: []
113
71
  extensions: []
114
72
  extra_rdoc_files: []
115
73
  files:
74
+ - CHANGELOG.md
116
75
  - LICENSE.txt
117
76
  - README.md
118
77
  - lib/sequel/extensions/activerecord_connection.rb
78
+ - lib/sequel/extensions/activerecord_connection/jdbc.rb
119
79
  - lib/sequel/extensions/activerecord_connection/mysql2.rb
120
80
  - lib/sequel/extensions/activerecord_connection/postgres.rb
121
81
  - lib/sequel/extensions/activerecord_connection/sqlite.rb