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.
- checksums.yaml +4 -4
- data/README.md +8 -8
- data/extconf.rb +93 -93
- data/fb.c +3072 -3072
- data/fb_extensions.rb +21 -21
- data/lib/active_model/type/integer.rb +67 -67
- data/lib/active_record/connection_adapters/rdb/database_limits.rb +35 -35
- data/lib/active_record/connection_adapters/rdb/database_statements.rb +186 -183
- data/lib/active_record/connection_adapters/rdb/quoting.rb +152 -152
- data/lib/active_record/connection_adapters/rdb/schema_creation.rb +52 -52
- data/lib/active_record/connection_adapters/rdb/schema_dumper.rb +23 -23
- data/lib/active_record/connection_adapters/rdb/schema_statements.rb +431 -431
- data/lib/active_record/connection_adapters/rdb/table_definition.rb +28 -28
- data/lib/active_record/connection_adapters/rdb_adapter.rb +163 -163
- data/lib/active_record/connection_adapters/rdb_column.rb +69 -69
- data/lib/active_record/rdb_base.rb +34 -34
- data/lib/active_record/tasks/rdb_database_tasks.rb +78 -78
- data/lib/activerecord-rdb-adapter.rb +10 -10
- data/lib/arel/visitors/rdb_visitor.rb +135 -135
- metadata +3 -4
@@ -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
|