activerecord-rdb-adapter 0.9.4 → 0.9.5

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.
@@ -1,431 +1,431 @@
1
- module ActiveRecord
2
- module ConnectionAdapters
3
- module Rdb
4
- module SchemaStatements # :nodoc:
5
- methods_to_commit = %i[add_column
6
- create_table
7
- rename_column
8
- remove_column
9
- change_column
10
- change_column_default
11
- change_column_null
12
- remove_index
13
- remove_index!
14
- drop_table
15
- create_sequence
16
- drop_sequence
17
- drop_trigger]
18
-
19
- def tables(_name = nil)
20
- @connection.table_names
21
- end
22
-
23
- def views
24
- @connection.view_names
25
- end
26
-
27
- def indexes(table_name, _name = nil)
28
- @connection.indexes.values.map do |ix|
29
- IndexDefinition.new(table_name, ix.index_name, ix.unique, ix.columns) if ix.table_name == table_name.to_s && ix.index_name !~ /^rdb\$/
30
- end.compact
31
- end
32
-
33
- def index_name_exists?(table_name, index_name)
34
- index_name = index_name.to_s.upcase
35
- indexes(table_name).detect { |i| i.name.upcase == index_name }
36
- end
37
-
38
- def columns(table_name, _name = nil)
39
- @col_definitions ||= {}
40
- @col_definitions[table_name] = column_definitions(table_name).map do |field|
41
- sql_type_metadata = column_type_for(field)
42
- rdb_opt = { domain: field[:domain], sub_type: field[:sql_subtype] }
43
- RdbColumn.new(field[:name], field[:default], sql_type_metadata, field[:nullable], table_name, rdb_opt)
44
- end
45
- end
46
-
47
- def create_table(name, options = {}) # :nodoc:
48
- raise ActiveRecordError, 'Firebird does not support temporary tables' if options.key? :temporary
49
-
50
- raise ActiveRecordError, 'Firebird does not support creating tables with a select' if options.key? :as
51
-
52
- drop_table name, if_exists: true if options.key? :force
53
-
54
- needs_sequence = options[:id]
55
-
56
- super name, options do |table_def|
57
- yield table_def if block_given?
58
- needs_sequence ||= table_def.needs_sequence
59
- end
60
-
61
- return if options[:sequence] == false || !needs_sequence
62
-
63
- create_sequence(options[:sequence] || default_sequence_name(name))
64
- trg_sql = <<-END_SQL
65
- CREATE TRIGGER N$#{name.upcase} FOR #{name.upcase}
66
- ACTIVE BEFORE INSERT
67
- AS
68
- declare variable gen_val bigint;
69
- BEGIN
70
- if (new.ID is null) then
71
- new.ID = next value for #{options[:sequence] || default_sequence_name(name)};
72
- else begin
73
- gen_val = gen_id(#{options[:sequence] || default_sequence_name(name)}, 1);
74
- if (new.ID > gen_val) then
75
- gen_val = gen_id(#{options[:sequence] || default_sequence_name(name)}, new.ID - gen_val);
76
- end
77
- END
78
- END_SQL
79
- execute(trg_sql)
80
- end
81
-
82
- def drop_table(name, options = {}) # :nodoc:
83
- drop_sql = "DROP TABLE #{quote_table_name(name)}"
84
- drop = if options[:if_exists]
85
- !execute(squish_sql(<<-END_SQL))
86
- select 1 from rdb$relations where rdb$relation_name = #{quote_table_name(name).tr('"', '\'')}
87
- END_SQL
88
- .empty?
89
- else
90
- false
91
- end
92
-
93
- trigger_name = "N$#{name.upcase}"
94
- drop_trigger(trigger_name) if trigger_exists?(trigger_name)
95
-
96
- sequence_name = options[:sequence] || default_sequence_name(name)
97
- drop_sequence(sequence_name) if sequence_exists?(sequence_name)
98
-
99
- execute(drop_sql) if drop
100
- end
101
-
102
- def create_sequence(sequence_name)
103
- execute("CREATE SEQUENCE #{sequence_name}")
104
- rescue StandardError
105
- nil
106
- end
107
-
108
- def drop_sequence(sequence_name)
109
- execute("DROP SEQUENCE #{sequence_name}")
110
- rescue StandardError
111
- nil
112
- end
113
-
114
- def drop_trigger(trigger_name)
115
- execute("DROP TRIGGER #{trigger_name}")
116
- rescue StandardError
117
- nil
118
- end
119
-
120
- def sequence_exists?(sequence_name)
121
- @connection.generator_names.include?(sequence_name)
122
- end
123
-
124
- def trigger_exists?(trigger_name)
125
- !execute(squish_sql(<<-END_SQL))
126
- select 1
127
- from rdb$triggers
128
- where rdb$trigger_name = '#{trigger_name}'
129
- END_SQL
130
- .empty?
131
- end
132
-
133
- def add_column(table_name, column_name, type, options = {})
134
- super
135
-
136
- create_sequence(options[:sequence] || default_sequence_name(table_name)) if type == :primary_key && options[:sequence] != false
137
-
138
- return unless options[:position]
139
-
140
- # position is 1-based but add 1 to skip id column
141
- execute(squish_sql(<<-END_SQL))
142
- ALTER TABLE #{quote_table_name(table_name)}
143
- ALTER COLUMN #{quote_column_name(column_name)}
144
- POSITION #{options[:position] + 1}
145
- END_SQL
146
- end
147
-
148
- def remove_column(table_name, column_name, type = nil, options = {})
149
- indexes(table_name).each do |i|
150
- remove_index! i.table, i.name if i.columns.any? { |c| c == column_name.to_s }
151
- end
152
-
153
- column_exist = !execute(squish_sql(<<-END_SQL))
154
- select 1 from RDB$RELATION_FIELDS rf
155
- where lower(rf.RDB$RELATION_NAME) = '#{table_name.downcase}' and lower(rf.RDB$FIELD_NAME) = '#{column_name.downcase}'
156
- END_SQL
157
- .empty?
158
- super if column_exist
159
- end
160
-
161
- def remove_column_for_alter(_table_name, column_name, _type = nil, _options = {})
162
- "DROP #{quote_column_name(column_name)}"
163
- end
164
-
165
- def change_column(table_name, column_name, type, options = {})
166
- type_sql = type_to_sql(type, *options.values_at(:limit, :precision, :scale))
167
-
168
- if %i[text string].include?(type)
169
- copy_column = 'c_temp'
170
- add_column table_name, copy_column, type, options
171
- execute(squish_sql(<<-END_SQL))
172
- UPDATE #{table_name} SET #{quote_column_name(copy_column)} = #{quote_column_name(column_name)};
173
- END_SQL
174
- remove_column table_name, column_name
175
- rename_column table_name, copy_column, column_name
176
- else
177
- execute(squish_sql(<<-END_SQL))
178
- ALTER TABLE #{quote_table_name(table_name)}
179
- ALTER COLUMN #{quote_column_name(column_name)} TYPE #{type_sql}
180
- END_SQL
181
- end
182
- change_column_null(table_name, column_name, !!options[:null]) if options.key?(:null)
183
- change_column_default(table_name, column_name, options[:default]) if options.key?(:default)
184
- end
185
-
186
- def change_column_default(table_name, column_name, default)
187
- execute(squish_sql(<<-END_SQL))
188
- ALTER TABLE #{quote_table_name(table_name)}
189
- ALTER #{quote_column_name(column_name)}
190
- SET DEFAULT #{quote(default)}
191
- END_SQL
192
- end
193
-
194
- def change_column_null(table_name, column_name, null, default = nil)
195
- change_column_default(table_name, column_name, default) if default
196
-
197
- db_column = columns(table_name).find { |c| c.name == column_name.to_s }
198
- options = { null: null }
199
- options[:default] = db_column.default if !default && db_column.default
200
- options[:default] = default if default
201
- ar_type = db_column.type
202
- type = type_to_sql(ar_type.type, ar_type.limit, ar_type.precision, ar_type.scale)
203
-
204
- copy_column = 'c_temp'
205
- add_column table_name, copy_column, type, options
206
- execute(squish_sql(<<-END_SQL))
207
- UPDATE #{table_name} SET #{quote_column_name(copy_column)} = #{quote_column_name(column_name)};
208
- END_SQL
209
- remove_column table_name, column_name
210
- rename_column table_name, copy_column, column_name
211
- end
212
-
213
- def rename_column(table_name, column_name, new_column_name)
214
- execute(squish_sql(<<-END_SQL))
215
- ALTER TABLE #{quote_table_name(table_name)}
216
- ALTER #{quote_column_name(column_name)}
217
- TO #{quote_column_name(new_column_name)}
218
- END_SQL
219
-
220
- rename_column_indexes(table_name, column_name, new_column_name)
221
- end
222
-
223
- def remove_index!(_table_name, index_name)
224
- execute "DROP INDEX #{quote_column_name(index_name)}"
225
- end
226
-
227
- def remove_index(table_name, options = {})
228
- index_name = index_name(table_name, options)
229
- execute "DROP INDEX #{quote_column_name(index_name)}"
230
- end
231
-
232
- def index_name(table_name, options) #:nodoc:
233
- if options.respond_to?(:keys) # legacy support
234
- if options[:column]
235
- index_name = "#{table_name}_#{Array.wrap(options[:column]) * '_'}"
236
- if index_name.length > 31
237
- "IDX_#{Digest::SHA256.hexdigest(index_name)[0..22]}"
238
- else
239
- index_name
240
- end
241
- elsif options[:name]
242
- options[:name]
243
- else
244
- raise ArgumentError 'You must specify the index name'
245
- end
246
- else
247
- index_name(table_name, column: options)
248
- end
249
- end
250
-
251
- def index_exists?(table_name, column_name, options = {})
252
- column_names = Array(column_name).map(&:to_s)
253
- checks = []
254
- checks << lambda { |i| i.columns == column_names }
255
- checks << lambda(&:unique) if options[:unique]
256
- checks << lambda { |i| i.name.upcase == options[:name].to_s.upcase } if options[:name]
257
-
258
- indexes(table_name).any? { |i| checks.all? { |check| check[i] } }
259
- end
260
-
261
- def type_to_sql(type, limit = nil, precision = nil, scale = nil, **args)
262
- if !args.nil? && !args.empty?
263
- limit = args[:limit] if limit.nil?
264
- precision = args[:precision] if precision.nil?
265
- scale = args[:scale] if scale.nil?
266
- end
267
- case type
268
- when :integer
269
- integer_to_sql(limit)
270
- when :float
271
- float_to_sql(limit)
272
- when :text
273
- text_to_sql(limit)
274
- # when :blob
275
- # binary_to_sql(limit)
276
- when :string
277
- string_to_sql(limit)
278
- else
279
- type = type.to_sym if type
280
- native = native_database_types[type]
281
- if native
282
- column_type_sql = (native.is_a?(Hash) ? native[:name] : native).dup
283
-
284
- if type == :decimal # ignore limit, use precision and scale
285
- scale ||= native[:scale]
286
-
287
- if precision ||= native[:precision]
288
- column_type_sql << if scale
289
- "(#{precision},#{scale})"
290
- else
291
- "(#{precision})"
292
- end
293
- elsif scale
294
- raise ArgumentError, 'Error adding decimal column: precision cannot be empty if scale is specified'
295
- end
296
-
297
- elsif %i[datetime timestamp time interval].include?(type) && precision ||= native[:precision]
298
- if (0..6) === precision
299
- column_type_sql << "(#{precision})"
300
- else
301
- raise(ActiveRecordError, "No #{native[:name]} type has precision of #{precision}. The allowed range of precision is from 0 to 6")
302
- end
303
- elsif (type != :primary_key) && (limit ||= native.is_a?(Hash) && native[:limit])
304
- column_type_sql << "(#{limit})"
305
- end
306
-
307
- column_type_sql
308
- else
309
- type.to_s
310
- end
311
- end
312
- end
313
-
314
- def primary_key(table_name)
315
- row = @connection.query(<<-END_SQL)
316
- SELECT s.rdb$field_name
317
- FROM rdb$indices i
318
- JOIN rdb$index_segments s ON i.rdb$index_name = s.rdb$index_name
319
- LEFT JOIN rdb$relation_constraints c ON i.rdb$index_name = c.rdb$index_name
320
- WHERE i.rdb$relation_name = '#{ar_to_rdb_case(table_name)}'
321
- AND c.rdb$constraint_type = 'PRIMARY KEY';
322
- END_SQL
323
-
324
- row.first && rdb_to_ar_case(row.first[0].rstrip)
325
- end
326
-
327
- def native_database_types
328
- @native_database_types ||= initialize_native_database_types.freeze
329
- end
330
-
331
- def create_schema_dumper(options)
332
- Rdb::SchemaDumper.create(self, options)
333
- end
334
-
335
- private
336
-
337
- def column_definitions(table_name)
338
- @connection.columns(table_name)
339
- end
340
-
341
- def new_column_from_field(table_name, field)
342
- type_metadata = fetch_type_metadata(field['sql_type'])
343
- ActiveRecord::ConnectionAdapters::Column.new(field['name'], field['default'], type_metadata, field['nullable'], table_name)
344
- end
345
-
346
- def column_type_for(field)
347
- sql_type = RdbColumn.sql_type_for(field)
348
- type = lookup_cast_type(sql_type)
349
- { type: type, sql_type: type.type }
350
- end
351
-
352
- def integer_to_sql(limit)
353
- return 'integer' if limit.nil?
354
-
355
- case limit
356
- when 1..2 then
357
- 'smallint'
358
- when 3..4 then
359
- 'integer'
360
- when 5..8 then
361
- 'bigint'
362
- else
363
- raise ActiveRecordError "No integer type has byte size #{limit}. " \
364
- 'Use a NUMERIC with PRECISION 0 instead.'
365
- end
366
- end
367
-
368
- def float_to_sql(limit)
369
- limit.nil? || limit <= 4 ? 'float' : 'double precision'
370
- end
371
-
372
- def text_to_sql(limit)
373
- if limit && limit > 0
374
- "VARCHAR(#{limit})"
375
- else
376
- 'BLOB SUB_TYPE TEXT'
377
- end
378
- end
379
-
380
- def string_to_sql(limit)
381
- if limit && limit > 0 && limit < 255
382
- "VARCHAR(#{limit})"
383
- else
384
- 'VARCHAR(255)'
385
- end
386
- end
387
-
388
- def initialize_native_database_types
389
- { primary_key: 'integer not null primary key',
390
- string: { name: 'varchar', limit: 255 },
391
- text: { name: 'blob sub_type text' },
392
- integer: { name: 'integer' },
393
- bigint: { name: 'bigint' },
394
- float: { name: 'float' },
395
- decimal: { name: 'decimal' },
396
- datetime: { name: 'timestamp' },
397
- timestamp: { name: 'timestamp' },
398
- time: { name: 'time' },
399
- date: { name: 'date' },
400
- binary: { name: 'blob' },
401
- boolean: { name: 'boolean' } }
402
- end
403
-
404
- def create_table_definition(*args)
405
- Rdb::TableDefinition.new(*args)
406
- end
407
-
408
- def squish_sql(sql)
409
- sql.strip.gsub(/\s+/, ' ')
410
- end
411
-
412
- class << self
413
- def after(*names)
414
- names.flatten.each do |name|
415
- m = ActiveRecord::ConnectionAdapters::Rdb::SchemaStatements.instance_method(name)
416
- define_method(name) do |*args, &block|
417
- m.bind(self).call(*args, &block)
418
- yield
419
- commit_db_transaction
420
- end
421
- end
422
- end
423
- end
424
-
425
- after(methods_to_commit) do
426
- puts 'Commiting transaction'
427
- end
428
- end
429
- end
430
- end
431
- end
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module Rdb
4
+ module SchemaStatements # :nodoc:
5
+ methods_to_commit = %i[add_column
6
+ create_table
7
+ rename_column
8
+ remove_column
9
+ change_column
10
+ change_column_default
11
+ change_column_null
12
+ remove_index
13
+ remove_index!
14
+ drop_table
15
+ create_sequence
16
+ drop_sequence
17
+ drop_trigger]
18
+
19
+ def tables(_name = nil)
20
+ @connection.table_names
21
+ end
22
+
23
+ def views
24
+ @connection.view_names
25
+ end
26
+
27
+ def indexes(table_name, _name = nil)
28
+ @connection.indexes.values.map do |ix|
29
+ IndexDefinition.new(table_name, ix.index_name, ix.unique, ix.columns) if ix.table_name == table_name.to_s && ix.index_name !~ /^rdb\$/
30
+ end.compact
31
+ end
32
+
33
+ def index_name_exists?(table_name, index_name)
34
+ index_name = index_name.to_s.upcase
35
+ indexes(table_name).detect { |i| i.name.upcase == index_name }
36
+ end
37
+
38
+ def columns(table_name, _name = nil)
39
+ @col_definitions ||= {}
40
+ @col_definitions[table_name] = column_definitions(table_name).map do |field|
41
+ sql_type_metadata = column_type_for(field)
42
+ rdb_opt = { domain: field[:domain], sub_type: field[:sql_subtype] }
43
+ RdbColumn.new(field[:name], field[:default], sql_type_metadata, field[:nullable], table_name, rdb_opt)
44
+ end
45
+ end
46
+
47
+ def create_table(name, options = {}) # :nodoc:
48
+ raise ActiveRecordError, 'Firebird does not support temporary tables' if options.key? :temporary
49
+
50
+ raise ActiveRecordError, 'Firebird does not support creating tables with a select' if options.key? :as
51
+
52
+ drop_table name, if_exists: true if options.key? :force
53
+
54
+ needs_sequence = options[:id]
55
+
56
+ super name, options do |table_def|
57
+ yield table_def if block_given?
58
+ needs_sequence ||= table_def.needs_sequence
59
+ end
60
+
61
+ return if options[:sequence] == false || !needs_sequence
62
+
63
+ create_sequence(options[:sequence] || default_sequence_name(name))
64
+ trg_sql = <<-END_SQL
65
+ CREATE TRIGGER N$#{name.upcase} FOR #{name.upcase}
66
+ ACTIVE BEFORE INSERT
67
+ AS
68
+ declare variable gen_val bigint;
69
+ BEGIN
70
+ if (new.ID is null) then
71
+ new.ID = next value for #{options[:sequence] || default_sequence_name(name)};
72
+ else begin
73
+ gen_val = gen_id(#{options[:sequence] || default_sequence_name(name)}, 1);
74
+ if (new.ID > gen_val) then
75
+ gen_val = gen_id(#{options[:sequence] || default_sequence_name(name)}, new.ID - gen_val);
76
+ end
77
+ END
78
+ END_SQL
79
+ execute(trg_sql)
80
+ end
81
+
82
+ def drop_table(name, options = {}) # :nodoc:
83
+ drop_sql = "DROP TABLE #{quote_table_name(name)}"
84
+ drop = if options[:if_exists]
85
+ !execute(squish_sql(<<-END_SQL))
86
+ select 1 from rdb$relations where rdb$relation_name = #{quote_table_name(name).tr('"', '\'')}
87
+ END_SQL
88
+ .empty?
89
+ else
90
+ false
91
+ end
92
+
93
+ trigger_name = "N$#{name.upcase}"
94
+ drop_trigger(trigger_name) if trigger_exists?(trigger_name)
95
+
96
+ sequence_name = options[:sequence] || default_sequence_name(name)
97
+ drop_sequence(sequence_name) if sequence_exists?(sequence_name)
98
+
99
+ execute(drop_sql) if drop
100
+ end
101
+
102
+ def create_sequence(sequence_name)
103
+ execute("CREATE SEQUENCE #{sequence_name}")
104
+ rescue StandardError
105
+ nil
106
+ end
107
+
108
+ def drop_sequence(sequence_name)
109
+ execute("DROP SEQUENCE #{sequence_name}")
110
+ rescue StandardError
111
+ nil
112
+ end
113
+
114
+ def drop_trigger(trigger_name)
115
+ execute("DROP TRIGGER #{trigger_name}")
116
+ rescue StandardError
117
+ nil
118
+ end
119
+
120
+ def sequence_exists?(sequence_name)
121
+ @connection.generator_names.include?(sequence_name)
122
+ end
123
+
124
+ def trigger_exists?(trigger_name)
125
+ !execute(squish_sql(<<-END_SQL))
126
+ select 1
127
+ from rdb$triggers
128
+ where rdb$trigger_name = '#{trigger_name}'
129
+ END_SQL
130
+ .empty?
131
+ end
132
+
133
+ def add_column(table_name, column_name, type, options = {})
134
+ super
135
+
136
+ create_sequence(options[:sequence] || default_sequence_name(table_name)) if type == :primary_key && options[:sequence] != false
137
+
138
+ return unless options[:position]
139
+
140
+ # position is 1-based but add 1 to skip id column
141
+ execute(squish_sql(<<-END_SQL))
142
+ ALTER TABLE #{quote_table_name(table_name)}
143
+ ALTER COLUMN #{quote_column_name(column_name)}
144
+ POSITION #{options[:position] + 1}
145
+ END_SQL
146
+ end
147
+
148
+ def remove_column(table_name, column_name, type = nil, options = {})
149
+ indexes(table_name).each do |i|
150
+ remove_index! i.table, i.name if i.columns.any? { |c| c == column_name.to_s }
151
+ end
152
+
153
+ column_exist = !execute(squish_sql(<<-END_SQL))
154
+ select 1 from RDB$RELATION_FIELDS rf
155
+ where lower(rf.RDB$RELATION_NAME) = '#{table_name.downcase}' and lower(rf.RDB$FIELD_NAME) = '#{column_name.downcase}'
156
+ END_SQL
157
+ .empty?
158
+ super if column_exist
159
+ end
160
+
161
+ def remove_column_for_alter(_table_name, column_name, _type = nil, _options = {})
162
+ "DROP #{quote_column_name(column_name)}"
163
+ end
164
+
165
+ def change_column(table_name, column_name, type, options = {})
166
+ type_sql = type_to_sql(type, *options.values_at(:limit, :precision, :scale))
167
+
168
+ if %i[text string].include?(type)
169
+ copy_column = 'c_temp'
170
+ add_column table_name, copy_column, type, options
171
+ execute(squish_sql(<<-END_SQL))
172
+ UPDATE #{table_name} SET #{quote_column_name(copy_column)} = #{quote_column_name(column_name)};
173
+ END_SQL
174
+ remove_column table_name, column_name
175
+ rename_column table_name, copy_column, column_name
176
+ else
177
+ execute(squish_sql(<<-END_SQL))
178
+ ALTER TABLE #{quote_table_name(table_name)}
179
+ ALTER COLUMN #{quote_column_name(column_name)} TYPE #{type_sql}
180
+ END_SQL
181
+ end
182
+ change_column_null(table_name, column_name, !!options[:null]) if options.key?(:null)
183
+ change_column_default(table_name, column_name, options[:default]) if options.key?(:default)
184
+ end
185
+
186
+ def change_column_default(table_name, column_name, default)
187
+ execute(squish_sql(<<-END_SQL))
188
+ ALTER TABLE #{quote_table_name(table_name)}
189
+ ALTER #{quote_column_name(column_name)}
190
+ SET DEFAULT #{quote(default)}
191
+ END_SQL
192
+ end
193
+
194
+ def change_column_null(table_name, column_name, null, default = nil)
195
+ change_column_default(table_name, column_name, default) if default
196
+
197
+ db_column = columns(table_name).find { |c| c.name == column_name.to_s }
198
+ options = { null: null }
199
+ options[:default] = db_column.default if !default && db_column.default
200
+ options[:default] = default if default
201
+ ar_type = db_column.type
202
+ type = type_to_sql(ar_type.type, ar_type.limit, ar_type.precision, ar_type.scale)
203
+
204
+ copy_column = 'c_temp'
205
+ add_column table_name, copy_column, type, options
206
+ execute(squish_sql(<<-END_SQL))
207
+ UPDATE #{table_name} SET #{quote_column_name(copy_column)} = #{quote_column_name(column_name)};
208
+ END_SQL
209
+ remove_column table_name, column_name
210
+ rename_column table_name, copy_column, column_name
211
+ end
212
+
213
+ def rename_column(table_name, column_name, new_column_name)
214
+ execute(squish_sql(<<-END_SQL))
215
+ ALTER TABLE #{quote_table_name(table_name)}
216
+ ALTER #{quote_column_name(column_name)}
217
+ TO #{quote_column_name(new_column_name)}
218
+ END_SQL
219
+
220
+ rename_column_indexes(table_name, column_name, new_column_name)
221
+ end
222
+
223
+ def remove_index!(_table_name, index_name)
224
+ execute "DROP INDEX #{quote_column_name(index_name)}"
225
+ end
226
+
227
+ def remove_index(table_name, options = {})
228
+ index_name = index_name(table_name, options)
229
+ execute "DROP INDEX #{quote_column_name(index_name)}"
230
+ end
231
+
232
+ def index_name(table_name, options) #:nodoc:
233
+ if options.respond_to?(:keys) # legacy support
234
+ if options[:column]
235
+ index_name = "#{table_name}_#{Array.wrap(options[:column]) * '_'}"
236
+ if index_name.length > 31
237
+ "IDX_#{Digest::SHA256.hexdigest(index_name)[0..22]}"
238
+ else
239
+ index_name
240
+ end
241
+ elsif options[:name]
242
+ options[:name]
243
+ else
244
+ raise ArgumentError 'You must specify the index name'
245
+ end
246
+ else
247
+ index_name(table_name, column: options)
248
+ end
249
+ end
250
+
251
+ def index_exists?(table_name, column_name, options = {})
252
+ column_names = Array(column_name).map(&:to_s)
253
+ checks = []
254
+ checks << lambda { |i| i.columns == column_names }
255
+ checks << lambda(&:unique) if options[:unique]
256
+ checks << lambda { |i| i.name.upcase == options[:name].to_s.upcase } if options[:name]
257
+
258
+ indexes(table_name).any? { |i| checks.all? { |check| check[i] } }
259
+ end
260
+
261
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil, **args)
262
+ if !args.nil? && !args.empty?
263
+ limit = args[:limit] if limit.nil?
264
+ precision = args[:precision] if precision.nil?
265
+ scale = args[:scale] if scale.nil?
266
+ end
267
+ case type
268
+ when :integer
269
+ integer_to_sql(limit)
270
+ when :float
271
+ float_to_sql(limit)
272
+ when :text
273
+ text_to_sql(limit)
274
+ # when :blob
275
+ # binary_to_sql(limit)
276
+ when :string
277
+ string_to_sql(limit)
278
+ else
279
+ type = type.to_sym if type
280
+ native = native_database_types[type]
281
+ if native
282
+ column_type_sql = (native.is_a?(Hash) ? native[:name] : native).dup
283
+
284
+ if type == :decimal # ignore limit, use precision and scale
285
+ scale ||= native[:scale]
286
+
287
+ if precision ||= native[:precision]
288
+ column_type_sql << if scale
289
+ "(#{precision},#{scale})"
290
+ else
291
+ "(#{precision})"
292
+ end
293
+ elsif scale
294
+ raise ArgumentError, 'Error adding decimal column: precision cannot be empty if scale is specified'
295
+ end
296
+
297
+ elsif %i[datetime timestamp time interval].include?(type) && precision ||= native[:precision]
298
+ if (0..6) === precision
299
+ column_type_sql << "(#{precision})"
300
+ else
301
+ raise(ActiveRecordError, "No #{native[:name]} type has precision of #{precision}. The allowed range of precision is from 0 to 6")
302
+ end
303
+ elsif (type != :primary_key) && (limit ||= native.is_a?(Hash) && native[:limit])
304
+ column_type_sql << "(#{limit})"
305
+ end
306
+
307
+ column_type_sql
308
+ else
309
+ type.to_s
310
+ end
311
+ end
312
+ end
313
+
314
+ def primary_key(table_name)
315
+ row = @connection.query(<<-END_SQL)
316
+ SELECT s.rdb$field_name
317
+ FROM rdb$indices i
318
+ JOIN rdb$index_segments s ON i.rdb$index_name = s.rdb$index_name
319
+ LEFT JOIN rdb$relation_constraints c ON i.rdb$index_name = c.rdb$index_name
320
+ WHERE i.rdb$relation_name = '#{ar_to_rdb_case(table_name)}'
321
+ AND c.rdb$constraint_type = 'PRIMARY KEY';
322
+ END_SQL
323
+
324
+ row.first && rdb_to_ar_case(row.first[0].rstrip)
325
+ end
326
+
327
+ def native_database_types
328
+ @native_database_types ||= initialize_native_database_types.freeze
329
+ end
330
+
331
+ def create_schema_dumper(options)
332
+ Rdb::SchemaDumper.create(self, options)
333
+ end
334
+
335
+ private
336
+
337
+ def column_definitions(table_name)
338
+ @connection.columns(table_name)
339
+ end
340
+
341
+ def new_column_from_field(table_name, field)
342
+ type_metadata = fetch_type_metadata(field['sql_type'])
343
+ ActiveRecord::ConnectionAdapters::Column.new(field['name'], field['default'], type_metadata, field['nullable'], table_name)
344
+ end
345
+
346
+ def column_type_for(field)
347
+ sql_type = RdbColumn.sql_type_for(field)
348
+ type = lookup_cast_type(sql_type)
349
+ { type: type, sql_type: type.type }
350
+ end
351
+
352
+ def integer_to_sql(limit)
353
+ return 'integer' if limit.nil?
354
+
355
+ case limit
356
+ when 1..2 then
357
+ 'smallint'
358
+ when 3..4 then
359
+ 'integer'
360
+ when 5..8 then
361
+ 'bigint'
362
+ else
363
+ raise ActiveRecordError "No integer type has byte size #{limit}. " \
364
+ 'Use a NUMERIC with PRECISION 0 instead.'
365
+ end
366
+ end
367
+
368
+ def float_to_sql(limit)
369
+ limit.nil? || limit <= 4 ? 'float' : 'double precision'
370
+ end
371
+
372
+ def text_to_sql(limit)
373
+ if limit && limit > 0
374
+ "VARCHAR(#{limit})"
375
+ else
376
+ 'BLOB SUB_TYPE TEXT'
377
+ end
378
+ end
379
+
380
+ def string_to_sql(limit)
381
+ if limit && limit > 0 && limit < 255
382
+ "VARCHAR(#{limit})"
383
+ else
384
+ 'VARCHAR(255)'
385
+ end
386
+ end
387
+
388
+ def initialize_native_database_types
389
+ { primary_key: 'integer not null primary key',
390
+ string: { name: 'varchar', limit: 255 },
391
+ text: { name: 'blob sub_type text' },
392
+ integer: { name: 'integer' },
393
+ bigint: { name: 'bigint' },
394
+ float: { name: 'float' },
395
+ decimal: { name: 'decimal' },
396
+ datetime: { name: 'timestamp' },
397
+ timestamp: { name: 'timestamp' },
398
+ time: { name: 'time' },
399
+ date: { name: 'date' },
400
+ binary: { name: 'blob' },
401
+ boolean: { name: 'boolean' } }
402
+ end
403
+
404
+ def create_table_definition(*args)
405
+ Rdb::TableDefinition.new(*args)
406
+ end
407
+
408
+ def squish_sql(sql)
409
+ sql.strip.gsub(/\s+/, ' ')
410
+ end
411
+
412
+ class << self
413
+ def after(*names)
414
+ names.flatten.each do |name|
415
+ m = ActiveRecord::ConnectionAdapters::Rdb::SchemaStatements.instance_method(name)
416
+ define_method(name) do |*args, &block|
417
+ m.bind(self).call(*args, &block)
418
+ yield
419
+ commit_db_transaction
420
+ end
421
+ end
422
+ end
423
+ end
424
+
425
+ after(methods_to_commit) do
426
+ puts 'Commiting transaction'
427
+ end
428
+ end
429
+ end
430
+ end
431
+ end