activerecord-yugabytedb-adapter 7.0.4.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.
Files changed (54) hide show
  1. checksums.yaml +7 -0
  2. data/.rubocop.yml +13 -0
  3. data/CHANGELOG.md +5 -0
  4. data/CODE_OF_CONDUCT.md +84 -0
  5. data/Gemfile +10 -0
  6. data/Gemfile.lock +67 -0
  7. data/README.md +31 -0
  8. data/Rakefile +8 -0
  9. data/activerecord-yugabytedb-adapter.gemspec +39 -0
  10. data/lib/active_record/connection_adapters/yugabytedb/column.rb +69 -0
  11. data/lib/active_record/connection_adapters/yugabytedb/database_statements.rb +156 -0
  12. data/lib/active_record/connection_adapters/yugabytedb/database_tasks.rb +19 -0
  13. data/lib/active_record/connection_adapters/yugabytedb/explain_pretty_printer.rb +44 -0
  14. data/lib/active_record/connection_adapters/yugabytedb/oid/array.rb +91 -0
  15. data/lib/active_record/connection_adapters/yugabytedb/oid/bit.rb +53 -0
  16. data/lib/active_record/connection_adapters/yugabytedb/oid/bit_varying.rb +15 -0
  17. data/lib/active_record/connection_adapters/yugabytedb/oid/bytea.rb +17 -0
  18. data/lib/active_record/connection_adapters/yugabytedb/oid/cidr.rb +48 -0
  19. data/lib/active_record/connection_adapters/yugabytedb/oid/date.rb +31 -0
  20. data/lib/active_record/connection_adapters/yugabytedb/oid/date_time.rb +36 -0
  21. data/lib/active_record/connection_adapters/yugabytedb/oid/decimal.rb +15 -0
  22. data/lib/active_record/connection_adapters/yugabytedb/oid/enum.rb +20 -0
  23. data/lib/active_record/connection_adapters/yugabytedb/oid/hstore.rb +109 -0
  24. data/lib/active_record/connection_adapters/yugabytedb/oid/inet.rb +15 -0
  25. data/lib/active_record/connection_adapters/yugabytedb/oid/interval.rb +49 -0
  26. data/lib/active_record/connection_adapters/yugabytedb/oid/jsonb.rb +15 -0
  27. data/lib/active_record/connection_adapters/yugabytedb/oid/legacy_point.rb +44 -0
  28. data/lib/active_record/connection_adapters/yugabytedb/oid/macaddr.rb +25 -0
  29. data/lib/active_record/connection_adapters/yugabytedb/oid/money.rb +41 -0
  30. data/lib/active_record/connection_adapters/yugabytedb/oid/oid.rb +15 -0
  31. data/lib/active_record/connection_adapters/yugabytedb/oid/point.rb +64 -0
  32. data/lib/active_record/connection_adapters/yugabytedb/oid/range.rb +115 -0
  33. data/lib/active_record/connection_adapters/yugabytedb/oid/specialized_string.rb +18 -0
  34. data/lib/active_record/connection_adapters/yugabytedb/oid/timestamp.rb +15 -0
  35. data/lib/active_record/connection_adapters/yugabytedb/oid/timestamp_with_time_zone.rb +30 -0
  36. data/lib/active_record/connection_adapters/yugabytedb/oid/type_map_initializer.rb +125 -0
  37. data/lib/active_record/connection_adapters/yugabytedb/oid/uuid.rb +35 -0
  38. data/lib/active_record/connection_adapters/yugabytedb/oid/vector.rb +28 -0
  39. data/lib/active_record/connection_adapters/yugabytedb/oid/xml.rb +30 -0
  40. data/lib/active_record/connection_adapters/yugabytedb/oid.rb +38 -0
  41. data/lib/active_record/connection_adapters/yugabytedb/quoting.rb +205 -0
  42. data/lib/active_record/connection_adapters/yugabytedb/referential_integrity.rb +77 -0
  43. data/lib/active_record/connection_adapters/yugabytedb/schema_creation.rb +100 -0
  44. data/lib/active_record/connection_adapters/yugabytedb/schema_definitions.rb +243 -0
  45. data/lib/active_record/connection_adapters/yugabytedb/schema_dumper.rb +74 -0
  46. data/lib/active_record/connection_adapters/yugabytedb/schema_statements.rb +812 -0
  47. data/lib/active_record/connection_adapters/yugabytedb/type_metadata.rb +44 -0
  48. data/lib/active_record/connection_adapters/yugabytedb/utils.rb +80 -0
  49. data/lib/active_record/connection_adapters/yugabytedb_adapter.rb +1069 -0
  50. data/lib/activerecord-yugabytedb-adapter.rb +11 -0
  51. data/lib/arel/visitors/yugabytedb.rb +99 -0
  52. data/lib/version.rb +5 -0
  53. data/sig/activerecord-yugabytedb-adapter.rbs +4 -0
  54. metadata +124 -0
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module YugabyteDB
6
+ module ReferentialIntegrity # :nodoc:
7
+ def disable_referential_integrity # :nodoc:
8
+ original_exception = nil
9
+
10
+ begin
11
+ transaction(requires_new: true) do
12
+ execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";"))
13
+ end
14
+ rescue ActiveRecord::ActiveRecordError => e
15
+ original_exception = e
16
+ end
17
+
18
+ begin
19
+ yield
20
+ rescue ActiveRecord::InvalidForeignKey => e
21
+ warn <<-WARNING
22
+ WARNING: Rails was not able to disable referential integrity.
23
+
24
+ This is most likely caused due to missing permissions.
25
+ Rails needs superuser privileges to disable referential integrity.
26
+
27
+ cause: #{original_exception&.message}
28
+
29
+ WARNING
30
+ raise e
31
+ end
32
+
33
+ begin
34
+ transaction(requires_new: true) do
35
+ execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(";"))
36
+ end
37
+ rescue ActiveRecord::ActiveRecordError
38
+ end
39
+ end
40
+
41
+ def all_foreign_keys_valid? # :nodoc:
42
+ sql = <<~SQL
43
+ do $$
44
+ declare r record;
45
+ BEGIN
46
+ FOR r IN (
47
+ SELECT FORMAT(
48
+ 'UPDATE pg_constraint SET convalidated=false WHERE conname = ''%I'' AND connamespace::regnamespace = ''%I''::regnamespace; ALTER TABLE %I.%I VALIDATE CONSTRAINT %I;',
49
+ constraint_name,
50
+ table_schema,
51
+ table_schema,
52
+ table_name,
53
+ constraint_name
54
+ ) AS constraint_check
55
+ FROM information_schema.table_constraints WHERE constraint_type = 'FOREIGN KEY'
56
+ )
57
+ LOOP
58
+ EXECUTE (r.constraint_check);
59
+ END LOOP;
60
+ END;
61
+ $$;
62
+ SQL
63
+
64
+ begin
65
+ transaction(requires_new: true) do
66
+ execute(sql)
67
+ end
68
+
69
+ true
70
+ rescue ActiveRecord::StatementInvalid
71
+ false
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module YugabyteDB
6
+ class SchemaCreation < SchemaCreation # :nodoc:
7
+ private
8
+ def visit_AlterTable(o)
9
+ super << o.constraint_validations.map { |fk| visit_ValidateConstraint fk }.join(" ")
10
+ end
11
+
12
+ def visit_AddForeignKey(o)
13
+ super.dup.tap do |sql|
14
+ if o.deferrable
15
+ sql << " DEFERRABLE"
16
+ sql << " INITIALLY #{o.deferrable.to_s.upcase}" unless o.deferrable == true
17
+ end
18
+
19
+ sql << " NOT VALID" unless o.validate?
20
+ end
21
+ end
22
+
23
+ def visit_CheckConstraintDefinition(o)
24
+ super.dup.tap { |sql| sql << " NOT VALID" unless o.validate? }
25
+ end
26
+
27
+ def visit_ValidateConstraint(name)
28
+ "VALIDATE CONSTRAINT #{quote_column_name(name)}"
29
+ end
30
+
31
+ def visit_ChangeColumnDefinition(o)
32
+ column = o.column
33
+ column.sql_type = type_to_sql(column.type, **column.options)
34
+ quoted_column_name = quote_column_name(o.name)
35
+
36
+ change_column_sql = +"ALTER COLUMN #{quoted_column_name} TYPE #{column.sql_type}"
37
+
38
+ options = column_options(column)
39
+
40
+ if options[:collation]
41
+ change_column_sql << " COLLATE \"#{options[:collation]}\""
42
+ end
43
+
44
+ if options[:using]
45
+ change_column_sql << " USING #{options[:using]}"
46
+ elsif options[:cast_as]
47
+ cast_as_type = type_to_sql(options[:cast_as], **options)
48
+ change_column_sql << " USING CAST(#{quoted_column_name} AS #{cast_as_type})"
49
+ end
50
+
51
+ if options.key?(:default)
52
+ if options[:default].nil?
53
+ change_column_sql << ", ALTER COLUMN #{quoted_column_name} DROP DEFAULT"
54
+ else
55
+ quoted_default = quote_default_expression(options[:default], column)
56
+ change_column_sql << ", ALTER COLUMN #{quoted_column_name} SET DEFAULT #{quoted_default}"
57
+ end
58
+ end
59
+
60
+ if options.key?(:null)
61
+ change_column_sql << ", ALTER COLUMN #{quoted_column_name} #{options[:null] ? 'DROP' : 'SET'} NOT NULL"
62
+ end
63
+
64
+ change_column_sql
65
+ end
66
+
67
+ def add_column_options!(sql, options)
68
+ if options[:collation]
69
+ sql << " COLLATE \"#{options[:collation]}\""
70
+ end
71
+
72
+ if as = options[:as]
73
+ sql << " GENERATED ALWAYS AS (#{as})"
74
+
75
+ if options[:stored]
76
+ sql << " STORED"
77
+ else
78
+ raise ArgumentError, <<~MSG
79
+ PostgreSQL currently does not support VIRTUAL (not persisted) generated columns.
80
+ Specify 'stored: true' option for '#{options[:column].name}'
81
+ MSG
82
+ end
83
+ end
84
+ super
85
+ end
86
+
87
+ # Returns any SQL string to go between CREATE and TABLE. May be nil.
88
+ def table_modifier_in_create(o)
89
+ # A table cannot be both TEMPORARY and UNLOGGED, since all TEMPORARY
90
+ # tables are already UNLOGGED.
91
+ if o.temporary
92
+ " TEMPORARY"
93
+ elsif o.unlogged
94
+ " UNLOGGED"
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,243 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module YugabyteDB
6
+ module ColumnMethods
7
+ extend ActiveSupport::Concern
8
+
9
+ # Defines the primary key field.
10
+ # Use of the native PostgreSQL UUID type is supported, and can be used
11
+ # by defining your tables as such:
12
+ #
13
+ # create_table :stuffs, id: :uuid do |t|
14
+ # t.string :content
15
+ # t.timestamps
16
+ # end
17
+ #
18
+ # By default, this will use the <tt>gen_random_uuid()</tt> function from the
19
+ # +pgcrypto+ extension. As that extension is only available in
20
+ # PostgreSQL 9.4+, for earlier versions an explicit default can be set
21
+ # to use <tt>uuid_generate_v4()</tt> from the +uuid-ossp+ extension instead:
22
+ #
23
+ # create_table :stuffs, id: false do |t|
24
+ # t.primary_key :id, :uuid, default: "uuid_generate_v4()"
25
+ # t.uuid :foo_id
26
+ # t.timestamps
27
+ # end
28
+ #
29
+ # To enable the appropriate extension, which is a requirement, use
30
+ # the +enable_extension+ method in your migrations.
31
+ #
32
+ # To use a UUID primary key without any of the extensions, set the
33
+ # +:default+ option to +nil+:
34
+ #
35
+ # create_table :stuffs, id: false do |t|
36
+ # t.primary_key :id, :uuid, default: nil
37
+ # t.uuid :foo_id
38
+ # t.timestamps
39
+ # end
40
+ #
41
+ # You may also pass a custom stored procedure that returns a UUID or use a
42
+ # different UUID generation function from another library.
43
+ #
44
+ # Note that setting the UUID primary key default value to +nil+ will
45
+ # require you to assure that you always provide a UUID value before saving
46
+ # a record (as primary keys cannot be +nil+). This might be done via the
47
+ # +SecureRandom.uuid+ method and a +before_save+ callback, for instance.
48
+ def primary_key(name, type = :primary_key, **options)
49
+ if type == :uuid
50
+ options[:default] = options.fetch(:default, "gen_random_uuid()")
51
+ end
52
+
53
+ super
54
+ end
55
+
56
+ ##
57
+ # :method: bigserial
58
+ # :call-seq: bigserial(*names, **options)
59
+
60
+ ##
61
+ # :method: bit
62
+ # :call-seq: bit(*names, **options)
63
+
64
+ ##
65
+ # :method: bit_varying
66
+ # :call-seq: bit_varying(*names, **options)
67
+
68
+ ##
69
+ # :method: cidr
70
+ # :call-seq: cidr(*names, **options)
71
+
72
+ ##
73
+ # :method: citext
74
+ # :call-seq: citext(*names, **options)
75
+
76
+ ##
77
+ # :method: daterange
78
+ # :call-seq: daterange(*names, **options)
79
+
80
+ ##
81
+ # :method: hstore
82
+ # :call-seq: hstore(*names, **options)
83
+
84
+ ##
85
+ # :method: inet
86
+ # :call-seq: inet(*names, **options)
87
+
88
+ ##
89
+ # :method: interval
90
+ # :call-seq: interval(*names, **options)
91
+
92
+ ##
93
+ # :method: int4range
94
+ # :call-seq: int4range(*names, **options)
95
+
96
+ ##
97
+ # :method: int8range
98
+ # :call-seq: int8range(*names, **options)
99
+
100
+ ##
101
+ # :method: jsonb
102
+ # :call-seq: jsonb(*names, **options)
103
+
104
+ ##
105
+ # :method: ltree
106
+ # :call-seq: ltree(*names, **options)
107
+
108
+ ##
109
+ # :method: macaddr
110
+ # :call-seq: macaddr(*names, **options)
111
+
112
+ ##
113
+ # :method: money
114
+ # :call-seq: money(*names, **options)
115
+
116
+ ##
117
+ # :method: numrange
118
+ # :call-seq: numrange(*names, **options)
119
+
120
+ ##
121
+ # :method: oid
122
+ # :call-seq: oid(*names, **options)
123
+
124
+ ##
125
+ # :method: point
126
+ # :call-seq: point(*names, **options)
127
+
128
+ ##
129
+ # :method: line
130
+ # :call-seq: line(*names, **options)
131
+
132
+ ##
133
+ # :method: lseg
134
+ # :call-seq: lseg(*names, **options)
135
+
136
+ ##
137
+ # :method: box
138
+ # :call-seq: box(*names, **options)
139
+
140
+ ##
141
+ # :method: path
142
+ # :call-seq: path(*names, **options)
143
+
144
+ ##
145
+ # :method: polygon
146
+ # :call-seq: polygon(*names, **options)
147
+
148
+ ##
149
+ # :method: circle
150
+ # :call-seq: circle(*names, **options)
151
+
152
+ ##
153
+ # :method: serial
154
+ # :call-seq: serial(*names, **options)
155
+
156
+ ##
157
+ # :method: tsrange
158
+ # :call-seq: tsrange(*names, **options)
159
+
160
+ ##
161
+ # :method: tstzrange
162
+ # :call-seq: tstzrange(*names, **options)
163
+
164
+ ##
165
+ # :method: tsvector
166
+ # :call-seq: tsvector(*names, **options)
167
+
168
+ ##
169
+ # :method: uuid
170
+ # :call-seq: uuid(*names, **options)
171
+
172
+ ##
173
+ # :method: xml
174
+ # :call-seq: xml(*names, **options)
175
+
176
+ ##
177
+ # :method: timestamptz
178
+ # :call-seq: timestamptz(*names, **options)
179
+
180
+ ##
181
+ # :method: enum
182
+ # :call-seq: enum(*names, **options)
183
+
184
+ included do
185
+ define_column_methods :bigserial, :bit, :bit_varying, :cidr, :citext, :daterange,
186
+ :hstore, :inet, :interval, :int4range, :int8range, :jsonb, :ltree, :macaddr,
187
+ :money, :numrange, :oid, :point, :line, :lseg, :box, :path, :polygon, :circle,
188
+ :serial, :tsrange, :tstzrange, :tsvector, :uuid, :xml, :timestamptz, :enum
189
+ end
190
+ end
191
+
192
+ class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
193
+ include ColumnMethods
194
+
195
+ attr_reader :unlogged
196
+
197
+ def initialize(*, **)
198
+ super
199
+ @unlogged = ActiveRecord::ConnectionAdapters::YugabyteDBAdapter.create_unlogged_tables
200
+ end
201
+
202
+ def new_column_definition(name, type, **options) # :nodoc:
203
+ case type
204
+ when :virtual
205
+ type = options[:type]
206
+ end
207
+
208
+ super
209
+ end
210
+
211
+ private
212
+ def aliased_types(name, fallback)
213
+ fallback
214
+ end
215
+
216
+ def integer_like_primary_key_type(type, options)
217
+ if type == :bigint || options[:limit] == 8
218
+ :bigserial
219
+ else
220
+ :serial
221
+ end
222
+ end
223
+ end
224
+
225
+ class Table < ActiveRecord::ConnectionAdapters::Table
226
+ include ColumnMethods
227
+ end
228
+
229
+ class AlterTable < ActiveRecord::ConnectionAdapters::AlterTable
230
+ attr_reader :constraint_validations
231
+
232
+ def initialize(td)
233
+ super
234
+ @constraint_validations = []
235
+ end
236
+
237
+ def validate_constraint(name)
238
+ @constraint_validations << name
239
+ end
240
+ end
241
+ end
242
+ end
243
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module YugabyteDB
6
+ class SchemaDumper < ConnectionAdapters::SchemaDumper # :nodoc:
7
+ private
8
+ def extensions(stream)
9
+ extensions = @connection.extensions
10
+ if extensions.any?
11
+ stream.puts " # These are extensions that must be enabled in order to support this database"
12
+ extensions.sort.each do |extension|
13
+ stream.puts " enable_extension #{extension.inspect}"
14
+ end
15
+ stream.puts
16
+ end
17
+ end
18
+
19
+ def types(stream)
20
+ types = @connection.enum_types
21
+ if types.any?
22
+ stream.puts " # Custom types defined in this database."
23
+ stream.puts " # Note that some types may not work with other database engines. Be careful if changing database."
24
+ types.sort.each do |name, values|
25
+ stream.puts " create_enum #{name.inspect}, #{values.split(",").inspect}"
26
+ end
27
+ stream.puts
28
+ end
29
+ end
30
+
31
+ def prepare_column_options(column)
32
+ spec = super
33
+ spec[:array] = "true" if column.array?
34
+
35
+ if @connection.supports_virtual_columns? && column.virtual?
36
+ spec[:as] = extract_expression_for_virtual_column(column)
37
+ spec[:stored] = true
38
+ spec = { type: schema_type(column).inspect }.merge!(spec)
39
+ end
40
+
41
+ spec[:enum_type] = "\"#{column.sql_type}\"" if column.enum?
42
+
43
+ spec
44
+ end
45
+
46
+ def default_primary_key?(column)
47
+ schema_type(column) == :bigserial
48
+ end
49
+
50
+ def explicit_primary_key_default?(column)
51
+ column.type == :uuid || (column.type == :integer && !column.serial?)
52
+ end
53
+
54
+ def schema_type(column)
55
+ return super unless column.serial?
56
+
57
+ if column.bigint?
58
+ :bigserial
59
+ else
60
+ :serial
61
+ end
62
+ end
63
+
64
+ def schema_expression(column)
65
+ super unless column.serial?
66
+ end
67
+
68
+ def extract_expression_for_virtual_column(column)
69
+ column.default_function.inspect
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end