activerecord-enhancedsqlite3-adapter 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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