activerecord-rdb-adapter 0.9.4 → 0.9.5

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