activerecord-enhancedsqlite3-adapter 0.1.0 → 0.2.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: 2f3c0a5d2a1ceae77db506215c35aa217521e43d152a1ca312e7b2f52887b803
4
- data.tar.gz: c5d6f2ac4e34b9181ac7dc6417176fbd5b5dfd5d20376ba6b91731faca73093a
3
+ metadata.gz: f138b7e075e546691856c8e45f22413a1e381df3024c6ef30e467dbde830c896
4
+ data.tar.gz: 0e6bde9f85883cacc12893f8ab2b1ca5820deea6d6a1357a262fb24401ed4980
5
5
  SHA512:
6
- metadata.gz: c26a1f83ede20d480687b01a8d1ab39b7d5c854e16fe7d3c3a6c5c984bd23812f3c5d40c531c3a964e032bc0dba1bc33f8de43a3d85c3b4eade0bd30a251f091
7
- data.tar.gz: fdf1d73eaa883f932f0cbb5cb6c4defc6addcfbbf4a8059a634a08f0b773a7b854b1e85c719e2b978a5708c1263dec3901a6c7d6e289eabe8421fdbdfc55bdb6
6
+ metadata.gz: dafd2a35c78565843b9aa384fb5988818c48bdc6852da55363c7e18e447a752b8194ef08ea4a40c9b482f30317cd7afc8e9501e22cb38abbab51dd16f3d6c7a7
7
+ data.tar.gz: 5c0d6b07eb0b3e90c4fd6f8859f90d944e33d90e100ee96981f24b4d1cb1212266045f81abe935b4ad1913caf22d43cb3386a73bdb0670b15f8686e680619a77
@@ -6,6 +6,7 @@
6
6
 
7
7
  require "active_record/connection_adapters/sqlite3_adapter"
8
8
  require "enhanced_sqlite3/supports_virtual_columns"
9
+ require "enhanced_sqlite3/supports_deferrable_constraints"
9
10
 
10
11
  module EnhancedSQLite3
11
12
  module Adapter
@@ -19,29 +20,17 @@ module EnhancedSQLite3
19
20
  #
20
21
  # extends https://github.com/rails/rails/blob/main/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb#L691
21
22
  def configure_connection
22
- configure_busy_handler
23
-
24
23
  super
25
24
 
26
25
  configure_pragmas
27
26
  configure_extensions
28
27
 
29
28
  EnhancedSQLite3::SupportsVirtualColumns.apply!
29
+ EnhancedSQLite3::SupportsDeferrableConstraints.apply!
30
30
  end
31
31
 
32
32
  private
33
33
 
34
- def configure_busy_handler
35
- if @config[:timeout] && @config[:retries]
36
- raise ArgumentError, "Cannot specify both timeout and retries arguments"
37
- elsif @config[:retries]
38
- # see: https://www.sqlite.org/c3ref/busy_handler.html
39
- @raw_connection.busy_handler do |count|
40
- count <= @config[:retries]
41
- end
42
- end
43
- end
44
-
45
34
  def configure_pragmas
46
35
  @config.fetch(:pragmas, []).each do |key, value|
47
36
  execute("PRAGMA #{key} = #{value}", "SCHEMA")
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ # see: https://github.com/rails/rails/pull/49376
4
+ module EnhancedSQLite3
5
+ module SupportsDeferrableConstraints
6
+ def self.apply!
7
+ EnhancedSQLite3::Adapter.include(Adapter)
8
+ ActiveRecord::ConnectionAdapters::SQLite3::SchemaCreation.include(SchemaCreation)
9
+ end
10
+
11
+ module Adapter
12
+ def supports_deferrable_constraints?
13
+ true
14
+ end
15
+
16
+ FK_REGEX = /.*FOREIGN KEY\s+\("(\w+)"\)\s+REFERENCES\s+"(\w+)"\s+\("(\w+)"\)/
17
+ DEFERRABLE_REGEX = /DEFERRABLE INITIALLY (\w+)/
18
+ def foreign_keys(table_name)
19
+ # SQLite returns 1 row for each column of composite foreign keys.
20
+ fk_info = internal_exec_query("PRAGMA foreign_key_list(#{quote(table_name)})", "SCHEMA")
21
+ # Deferred or immediate foreign keys can only be seen in the CREATE TABLE sql
22
+ fk_defs = table_structure_sql(table_name)
23
+ .select do |column_string|
24
+ column_string.start_with?("CONSTRAINT") &&
25
+ column_string.include?("FOREIGN KEY")
26
+ end
27
+ .to_h do |fk_string|
28
+ _, from, table, to = fk_string.match(FK_REGEX).to_a
29
+ _, mode = fk_string.match(DEFERRABLE_REGEX).to_a
30
+ deferred = mode&.downcase&.to_sym || false
31
+ [[table, from, to], deferred]
32
+ end
33
+
34
+ grouped_fk = fk_info.group_by { |row| row["id"] }.values.each { |group| group.sort_by! { |row| row["seq"] } }
35
+ grouped_fk.map do |group|
36
+ row = group.first
37
+ options = {
38
+ on_delete: extract_foreign_key_action(row["on_delete"]),
39
+ on_update: extract_foreign_key_action(row["on_update"]),
40
+ deferrable: fk_defs[[row["table"], row["from"], row["to"]]]
41
+ }
42
+
43
+ if group.one?
44
+ options[:column] = row["from"]
45
+ options[:primary_key] = row["to"]
46
+ else
47
+ options[:column] = group.map { |row| row["from"] }
48
+ options[:primary_key] = group.map { |row| row["to"] }
49
+ end
50
+ ActiveRecord::ConnectionAdapters::ForeignKeyDefinition.new(table_name, row["table"], options)
51
+ end
52
+ end
53
+
54
+ def add_foreign_key(from_table, to_table, **options)
55
+ if options[:deferrable] == true
56
+ ActiveRecord.deprecator.warn(<<~MSG)
57
+ `deferrable: true` is deprecated in favor of `deferrable: :immediate`, and will be removed in Rails 7.2.
58
+ MSG
59
+
60
+ options[:deferrable] = :immediate
61
+ end
62
+
63
+ assert_valid_deferrable(options[:deferrable])
64
+
65
+ super
66
+ end
67
+
68
+ def table_structure_sql(table_name)
69
+ sql = <<~SQL
70
+ SELECT sql FROM
71
+ (SELECT * FROM sqlite_master UNION ALL
72
+ SELECT * FROM sqlite_temp_master)
73
+ WHERE type = 'table' AND name = #{quote(table_name)}
74
+ SQL
75
+
76
+ # Result will have following sample string
77
+ # CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
78
+ # "password_digest" varchar COLLATE "NOCASE");
79
+ result = query_value(sql, "SCHEMA")
80
+
81
+ return [] unless result
82
+
83
+ # Splitting with left parentheses and discarding the first part will return all
84
+ # columns separated with comma(,).
85
+ columns_string = result.split("(", 2).last
86
+
87
+ columns_string.split(",").map(&:strip)
88
+ end
89
+
90
+ def assert_valid_deferrable(deferrable)
91
+ return if !deferrable || %i[immediate deferred].include?(deferrable)
92
+
93
+ raise ArgumentError, "deferrable must be `:immediate` or `:deferred`, got: `#{deferrable.inspect}`"
94
+ end
95
+ end
96
+
97
+ module SchemaCreation
98
+ def visit_AddForeignKey(o)
99
+ super.dup.tap do |sql|
100
+ sql << " DEFERRABLE INITIALLY #{o.options[:deferrable].to_s.upcase}" if o.deferrable
101
+ end
102
+ end
103
+
104
+ def visit_ForeignKeyDefinition(o)
105
+ super.dup.tap do |sql|
106
+ sql << " DEFERRABLE INITIALLY #{o.deferrable.to_s.upcase}" if o.deferrable
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module EnhancedSQLite3
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord-enhancedsqlite3-adapter
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stephen Margheim
@@ -111,6 +111,7 @@ files:
111
111
  - lib/activerecord-enhancedsqlite3-adapter.rb
112
112
  - lib/enhanced_sqlite3/adapter.rb
113
113
  - lib/enhanced_sqlite3/railtie.rb
114
+ - lib/enhanced_sqlite3/supports_deferrable_constraints.rb
114
115
  - lib/enhanced_sqlite3/supports_virtual_columns.rb
115
116
  - lib/enhanced_sqlite3/version.rb
116
117
  homepage: https://github.com/fractaledmind/activerecord-enhancedsqlite3-adapter