kudu_adapter 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +4 -0
- data/.rubocop.yml +8 -0
- data/Gemfile +9 -0
- data/LICENSE.txt +20 -0
- data/README.md +178 -0
- data/kudu_adapter.gemspec +33 -0
- data/lib/active_record/connection_adapters/kudu/column.rb +17 -0
- data/lib/active_record/connection_adapters/kudu/database_statements.rb +41 -0
- data/lib/active_record/connection_adapters/kudu/quoting.rb +51 -0
- data/lib/active_record/connection_adapters/kudu/schema_creation.rb +89 -0
- data/lib/active_record/connection_adapters/kudu/schema_statements.rb +507 -0
- data/lib/active_record/connection_adapters/kudu/sql_type_metadata.rb +16 -0
- data/lib/active_record/connection_adapters/kudu/table_definition.rb +32 -0
- data/lib/active_record/connection_adapters/kudu/type/big_int.rb +22 -0
- data/lib/active_record/connection_adapters/kudu/type/boolean.rb +23 -0
- data/lib/active_record/connection_adapters/kudu/type/char.rb +17 -0
- data/lib/active_record/connection_adapters/kudu/type/date_time.rb +21 -0
- data/lib/active_record/connection_adapters/kudu/type/double.rb +17 -0
- data/lib/active_record/connection_adapters/kudu/type/float.rb +18 -0
- data/lib/active_record/connection_adapters/kudu/type/integer.rb +22 -0
- data/lib/active_record/connection_adapters/kudu/type/small_int.rb +22 -0
- data/lib/active_record/connection_adapters/kudu/type/string.rb +17 -0
- data/lib/active_record/connection_adapters/kudu/type/time.rb +30 -0
- data/lib/active_record/connection_adapters/kudu/type/tiny_int.rb +22 -0
- data/lib/active_record/connection_adapters/kudu_adapter.rb +173 -0
- data/lib/active_record/tasks/kudu_database_tasks.rb +29 -0
- data/lib/arel/visitors/kudu.rb +7 -0
- data/lib/kudu_adapter/bind_substitution.rb +15 -0
- data/lib/kudu_adapter/table_definition_extensions.rb +28 -0
- data/lib/kudu_adapter/version.rb +5 -0
- data/lib/kudu_adapter.rb +5 -0
- data/spec/spec_config.yaml.template +8 -0
- data/spec/spec_helper.rb +124 -0
- metadata +205 -0
@@ -0,0 +1,507 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_record/connection_adapters/kudu/column'
|
4
|
+
require 'active_record/connection_adapters/kudu/schema_creation'
|
5
|
+
require 'active_record/connection_adapters/kudu/sql_type_metadata'
|
6
|
+
require 'active_record/connection_adapters/kudu/table_definition'
|
7
|
+
require 'active_record/migration/join_table'
|
8
|
+
|
9
|
+
# :nodoc:
|
10
|
+
module ActiveRecord
|
11
|
+
module ConnectionAdapters
|
12
|
+
module Kudu
|
13
|
+
include ::ActiveRecord::Migration::JoinTable
|
14
|
+
|
15
|
+
# TODO methods delegate :quote_column_name, :quote_default_expression, :type_to_sql,
|
16
|
+
# :options_include_default?, :supports_indexes_in_create?, :supports_foreign_keys_in_create?,
|
17
|
+
# :foreign_key_options, to: :@conn
|
18
|
+
# ^^^ THOSE ARE FROM SCHEMACREATION ^^^
|
19
|
+
|
20
|
+
# :nodoc:
|
21
|
+
module SchemaStatements
|
22
|
+
|
23
|
+
def table_options(table_name)
|
24
|
+
nil
|
25
|
+
end
|
26
|
+
|
27
|
+
def table_comment(table_name)
|
28
|
+
raise NotImplementedError, '#table_comment Comments not implemented'
|
29
|
+
end
|
30
|
+
|
31
|
+
def table_alias_for(table_name)
|
32
|
+
table_name.tr('.', '_')
|
33
|
+
end
|
34
|
+
|
35
|
+
def data_sources
|
36
|
+
tables | views
|
37
|
+
end
|
38
|
+
|
39
|
+
def data_source_exists?(name)
|
40
|
+
data_sources.include? name.to_s
|
41
|
+
end
|
42
|
+
|
43
|
+
def tables
|
44
|
+
connection.query('SHOW TABLES').map { |table| table[:name] }
|
45
|
+
end
|
46
|
+
|
47
|
+
def table_exists?(table_name)
|
48
|
+
tables.include? table_name.to_s
|
49
|
+
end
|
50
|
+
|
51
|
+
# Return list of existing views. As we are not supporting them,
|
52
|
+
# this list will be always empty
|
53
|
+
def views
|
54
|
+
[]
|
55
|
+
end
|
56
|
+
|
57
|
+
# Check if given view exists. As we are not supporting views,
|
58
|
+
# it'll be always false
|
59
|
+
def view_exists?(_)
|
60
|
+
false
|
61
|
+
end
|
62
|
+
|
63
|
+
def indexes(table_name, name = nil)
|
64
|
+
[]
|
65
|
+
end
|
66
|
+
|
67
|
+
def index_exists?(table_name, column_name, options = {})
|
68
|
+
raise NotImplementedError, '#index_exists? Indexing not implemented'
|
69
|
+
end
|
70
|
+
|
71
|
+
def columns(table_name)
|
72
|
+
table_structure(table_name).map do |col_def|
|
73
|
+
type = if col_def[:type] == 'int'
|
74
|
+
:integer
|
75
|
+
elsif col_def[:type] == 'bigint' && /_at$/ =~ col_def[:name]
|
76
|
+
:datetime
|
77
|
+
else
|
78
|
+
col_def[:type].to_sym
|
79
|
+
end
|
80
|
+
stm = ::ActiveRecord::ConnectionAdapters::Kudu::SqlTypeMetadata.new(sql_type: col_def[:type], type: type)
|
81
|
+
::ActiveRecord::ConnectionAdapters::Kudu::Column.new(
|
82
|
+
col_def[:name],
|
83
|
+
col_def[:default_value]&.empty? ? nil : col_def[:default_value],
|
84
|
+
stm,
|
85
|
+
col_def[:null] == 'true',
|
86
|
+
table_name,
|
87
|
+
col_def[:comment]
|
88
|
+
)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def column_exists?(table_name, column_name, type = nil, options = {})
|
93
|
+
column_name = column_name.to_s
|
94
|
+
checks = []
|
95
|
+
checks << lambda { |c| c.name == column_name }
|
96
|
+
checks << lambda { |c| c.type == type } if type
|
97
|
+
column_options_keys.each do |attr|
|
98
|
+
checks << lambda { |c| c.send(attr) == options[attr] } if options.key?(attr)
|
99
|
+
end
|
100
|
+
columns(table_name).any? { |c| checks.all? { |check| check[c] } }
|
101
|
+
end
|
102
|
+
|
103
|
+
def primary_key(table_name)
|
104
|
+
pks = table_structure(table_name).select { |col| col[:primary_key] == 'true' }
|
105
|
+
pks.map! { |pk| pk[:name] }
|
106
|
+
pks.size == 1 ? pks.first : pks
|
107
|
+
end
|
108
|
+
|
109
|
+
def create_table(table_name, comment: nil, **options)
|
110
|
+
td = create_table_definition table_name, options[:temporary], options[:options], options[:as], comment: comment
|
111
|
+
if options[:id] != false && !options[:as]
|
112
|
+
pk = options.fetch(:primary_key) do
|
113
|
+
Base.get_primary_key table_name.to_s.singularize
|
114
|
+
end
|
115
|
+
|
116
|
+
if pk.is_a?(Array)
|
117
|
+
td.primary_keys pk
|
118
|
+
else
|
119
|
+
td.primary_key pk, options.fetch(:id, :primary_key), options
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
yield td if block_given?
|
124
|
+
|
125
|
+
options[:force] && drop_table(table_name, **options, if_exists: true)
|
126
|
+
|
127
|
+
result = execute schema_creation.accept td
|
128
|
+
|
129
|
+
unless supports_indexes_in_create?
|
130
|
+
td.indexes.each do |column_name, index_options|
|
131
|
+
add_index(table_name, column_name, index_options)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
if supports_comments? && !supports_comments_in_create?
|
136
|
+
change_table_comment(table_name, comment) if comment.present?
|
137
|
+
td.columns.each do |column|
|
138
|
+
change_column_comment(table_name, column.name, column.comment) if column.comment.present?
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
result
|
143
|
+
end
|
144
|
+
|
145
|
+
def drop_table(table_name, **options)
|
146
|
+
execute "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}"
|
147
|
+
end
|
148
|
+
|
149
|
+
def drop_temp_tables
|
150
|
+
tbl = tables
|
151
|
+
to_delete = tbl.select {|tbl| tbl.include? '_temp' }
|
152
|
+
to_delete.each do |dt|
|
153
|
+
drop_table(dt)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def create_join_table(table_1, table_2, colum_options: {}, **options)
|
158
|
+
join_table_name = find_join_table_name(table_1, table_2, options)
|
159
|
+
|
160
|
+
column_options = options.delete(:column_options) || {}
|
161
|
+
column_options.reverse_merge!(null: false)
|
162
|
+
column_options.merge!(primary_key: true)
|
163
|
+
|
164
|
+
t1_column, t2_column = [table_1, table_2].map{ |t| t.to_s.singularize.foreign_key }
|
165
|
+
|
166
|
+
create_table(join_table_name, options.merge!(id: false)) do |td|
|
167
|
+
td.string t1_column, column_options
|
168
|
+
td.string t2_column, column_options
|
169
|
+
yield td if block_given?
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def drop_join_table(table_1, table_2, options = {})
|
174
|
+
join_table_name = find_join_table_name(table_1, table_2, options)
|
175
|
+
drop_table join_table_name
|
176
|
+
end
|
177
|
+
|
178
|
+
def change_table(table_name, options = {})
|
179
|
+
if supports_bulk_alter? && options[:bulk]
|
180
|
+
recorder = ActiveRecord::Migration::CommandRecorder.new(self)
|
181
|
+
yield update_table_definition(table_name, recorder)
|
182
|
+
bulk_change_table(table_name, recorder.commands)
|
183
|
+
else
|
184
|
+
yield update_table_definition(table_name, self)
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
def rename_table(table_name, new_name)
|
189
|
+
db_name = Rails.configuration.database_configuration[Rails.env]['database']
|
190
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}"
|
191
|
+
execute "ALTER TABLE #{quote_table_name(new_name)} SET TblProperties('kudu.table_name' = 'impala::#{db_name}.#{new_name}')"
|
192
|
+
end
|
193
|
+
|
194
|
+
def add_column(table_name, column_name, type, options = {})
|
195
|
+
if options_has_primary_key(options)
|
196
|
+
# be aware of primary key columns
|
197
|
+
redefine_table_add_primary_key(table_name, column_name, type, options)
|
198
|
+
else
|
199
|
+
at = create_alter_table table_name
|
200
|
+
at.add_column(column_name, type, options)
|
201
|
+
execute schema_creation.accept at
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
def remove_columns(table_name, column_names)
|
206
|
+
raise ArgumentError.new("You must specify at least one column name. Example: remove_columns(:people, :first_name)") if column_names.empty?
|
207
|
+
column_names.each do |column_name|
|
208
|
+
remove_column(table_name, column_name)
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
def remove_column(table_name, column_name, type = nil, options = {})
|
213
|
+
if primary_keys_contain_column_name(table_name, column_name)
|
214
|
+
# be aware of primary key columns
|
215
|
+
#raise ArgumentError.new("You cannot drop primary key fields")
|
216
|
+
redefine_table_drop_primary_key(table_name, column_name, type, options)
|
217
|
+
else
|
218
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} DROP COLUMN #{quote_column_name(column_name)}"
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
def change_column(table_name, column_name, type, options)
|
223
|
+
raise NotImplementedError, '#change_column Altering columns not implemented'
|
224
|
+
end
|
225
|
+
|
226
|
+
def change_column_default(table_name, column_name, default_or_changes)
|
227
|
+
raise NotImplementedError, '#change_column_default Altering column defaults not implemented'
|
228
|
+
end
|
229
|
+
|
230
|
+
def change_column_null(table_name, column_name, null, default = nil)
|
231
|
+
raise NotImplementedError, '#change_column_null Altering column null not implemented'
|
232
|
+
end
|
233
|
+
|
234
|
+
def rename_column(table_name, column_name, new_column_name)
|
235
|
+
raise ArgumentError.new('You cannot rename primary key fields') if primary_keys_contain_column_name(table_name, column_name)
|
236
|
+
column = columns(table_name).find { |c| c.name.to_s == column_name.to_s }
|
237
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} CHANGE #{quote_column_name(column_name)} #{quote_column_name(new_column_name)} #{column.sql_type}"
|
238
|
+
end
|
239
|
+
|
240
|
+
# It will reload all data from temp table name into new one.
|
241
|
+
# We're seeking for table_name_temp while we inserting data with additional new column and it's value.
|
242
|
+
# At the end table_name_temp is dropped indeed.
|
243
|
+
def reload_table_data(table_name, column_name, options = {})
|
244
|
+
temp_table_name = table_name + '_temp'
|
245
|
+
|
246
|
+
# get table structure and remove our column name
|
247
|
+
columns = table_structure table_name
|
248
|
+
columns.reject! { |c| c[:name] == column_name.to_s }
|
249
|
+
|
250
|
+
select_qry = columns.map {|col| col[:name].to_s }.join(',')
|
251
|
+
|
252
|
+
# values with additional column name
|
253
|
+
values = select_qry + ',' + column_name.to_s
|
254
|
+
# fetch values with our column name and value to insert
|
255
|
+
fetch_values = select_qry + ',' + quote(options[:default]) + ' AS ' + column_name.to_s
|
256
|
+
|
257
|
+
insert_qry = "INSERT INTO #{quote_table_name(table_name)} (#{values}) SELECT #{fetch_values} FROM #{quote_table_name(temp_table_name)}"
|
258
|
+
execute insert_qry
|
259
|
+
|
260
|
+
drop_table(temp_table_name)
|
261
|
+
end
|
262
|
+
|
263
|
+
def add_index(table_name, column_name, options = {})
|
264
|
+
p '(add_index) Indexing not supported by Apache KUDU'
|
265
|
+
end
|
266
|
+
|
267
|
+
def remove_index(table_name, options = {})
|
268
|
+
p '(remove_index) Indexing not supported by Apache KUDU'
|
269
|
+
end
|
270
|
+
|
271
|
+
def rename_index(table_name, old_name, new_name)
|
272
|
+
p '(rename_index) Indexing not supported by Apache KUDU'
|
273
|
+
end
|
274
|
+
|
275
|
+
def index_name(table_name, options)
|
276
|
+
p '(index_name) Indexing not supported by Apache KUDU'
|
277
|
+
end
|
278
|
+
|
279
|
+
def index_name_exists?(table_name, index_name, default = nil)
|
280
|
+
p '(index_name_exists?) Indexing not supported by Apache KUDU'
|
281
|
+
end
|
282
|
+
|
283
|
+
def add_reference(table_name, ref_name, **options)
|
284
|
+
p '(add_reference) Traditional referencing not supported by Apache KUDU'
|
285
|
+
end
|
286
|
+
alias add_belongs_to add_reference
|
287
|
+
|
288
|
+
def remove_reference(table_name, ref_name, foreign_key: false, polymorphic: false, **options)
|
289
|
+
p '(remove_reference) Traditional referencing not supported by Apache KUDU'
|
290
|
+
end
|
291
|
+
|
292
|
+
def foreign_keys(table_name)
|
293
|
+
p '(foreign_keys) Foreign keys not supported by Apache KUDU'
|
294
|
+
end
|
295
|
+
|
296
|
+
def add_foreign_key(from_table, to_table, options = {})
|
297
|
+
p '(add_foreign_key) Foreign keys not supported by Apache KUDU'
|
298
|
+
end
|
299
|
+
|
300
|
+
def remove_foreign_key(from_table, options_or_to_table = {})
|
301
|
+
p '(remove_foreign_key) Foreign keys not supported by Apache KUDU'
|
302
|
+
end
|
303
|
+
|
304
|
+
def foreign_key_exists?(from_table, options_or_to_table = {})
|
305
|
+
p '(foreign_key_exists?) Foreign keys not supported by Apache KUDU'
|
306
|
+
end
|
307
|
+
|
308
|
+
def foreign_key_for(from_table, options_or_to_table = {})
|
309
|
+
p '(foreign_key_for?) Foreign keys not supported by Apache KUDU'
|
310
|
+
end
|
311
|
+
|
312
|
+
def foreign_key_for!(from_table, options_or_to_table = {})
|
313
|
+
p '(foreign_key_for!) Foreign keys not supported by Apache KUDU'
|
314
|
+
end
|
315
|
+
|
316
|
+
def foreign_key_for_column_for(table_name)
|
317
|
+
p '(foreign_key_for_column_for) Foreign keys not supported by Apache KUDU'
|
318
|
+
end
|
319
|
+
|
320
|
+
def foreign_key_options(from_table, to_table, options)
|
321
|
+
p '(foreign_key_options) Foreign keys not supported by Apache KUDU'
|
322
|
+
end
|
323
|
+
|
324
|
+
def assume_migrated_upto_version(version, migrations_paths)
|
325
|
+
migrations_paths = Array(migrations_paths)
|
326
|
+
version = version.to_i
|
327
|
+
sm_table = quote_table_name(ActiveRecord::SchemaMigration.table_name)
|
328
|
+
|
329
|
+
migrated = ActiveRecord::SchemaMigration.all_versions.map(&:to_i)
|
330
|
+
versions = ActiveRecord::Migrator.migration_files(migrations_paths).map do |file|
|
331
|
+
ActiveRecord::Migrator.parse_migration_filename(file).first.to_i
|
332
|
+
end
|
333
|
+
|
334
|
+
unless migrated.include?(version)
|
335
|
+
execute "INSERT INTO #{sm_table} (version) VALUES (#{quote(version.to_s)})"
|
336
|
+
end
|
337
|
+
|
338
|
+
inserting = (versions - migrated).select { |v| v < version }
|
339
|
+
if inserting.any?
|
340
|
+
if (duplicate = inserting.detect { |v| inserting.count(v) > 1 })
|
341
|
+
raise "Duplicate migration #{duplicate}. Please renumber your migrations to resolve the conflict."
|
342
|
+
end
|
343
|
+
if supports_multi_insert?
|
344
|
+
execute insert_versions_sql(inserting)
|
345
|
+
else
|
346
|
+
inserting.each do |v|
|
347
|
+
execute insert_versions_sql(v.to_s)
|
348
|
+
end
|
349
|
+
end
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
353
|
+
def type_to_sql(type, limit: nil, precision: nil, scale: nil, **)
|
354
|
+
case type
|
355
|
+
when 'integer'
|
356
|
+
case limit
|
357
|
+
when 1 then 'TINYINT'
|
358
|
+
when 2 then 'SMALLINT'
|
359
|
+
when 3..4, nil then 'INT'
|
360
|
+
when 5..8 then 'BIGINT'
|
361
|
+
else
|
362
|
+
raise(ActiveRecordError, 'Invalid integer precision')
|
363
|
+
end
|
364
|
+
else
|
365
|
+
super
|
366
|
+
end
|
367
|
+
end
|
368
|
+
|
369
|
+
def columns_for_distinct(columns, orders)
|
370
|
+
columns
|
371
|
+
end
|
372
|
+
|
373
|
+
def add_timestamps(table_name, options = {})
|
374
|
+
options[:null] = false if options[:null].nil?
|
375
|
+
add_column table_name, :created_at, :bigint, options
|
376
|
+
add_column table_name, :updated_at, :bigint, options
|
377
|
+
end
|
378
|
+
|
379
|
+
def remove_timestamps(table_name, options = {})
|
380
|
+
remove_column table_name, :updated_at
|
381
|
+
remove_column table_name, :created_at
|
382
|
+
end
|
383
|
+
|
384
|
+
def update_table_definition(table_name, base)
|
385
|
+
Table.new(table_name, base)
|
386
|
+
end
|
387
|
+
|
388
|
+
def add_index_options(table_name, column_name, comment: nil, **options)
|
389
|
+
p '(add_index_options) Indexing not supported by Apache KUDU'
|
390
|
+
end
|
391
|
+
|
392
|
+
def options_include_default?(options)
|
393
|
+
options.include?(:default) && !(options[:null] == false && options[:default].nil?)
|
394
|
+
end
|
395
|
+
|
396
|
+
def change_table_comment(table_name, comment)
|
397
|
+
p '(change_table_comment) Altering table comments not supported by Apache KUDU'
|
398
|
+
end
|
399
|
+
|
400
|
+
def change_column_comment(table_name, column_name, comment)
|
401
|
+
p '(change_column_comment) Altering column comments not supported by Apache KUDU'
|
402
|
+
end
|
403
|
+
|
404
|
+
private
|
405
|
+
|
406
|
+
def schema_creation
|
407
|
+
::ActiveRecord::ConnectionAdapters::Kudu::SchemaCreation.new(self)
|
408
|
+
end
|
409
|
+
|
410
|
+
def create_table_definition(*args)
|
411
|
+
::ActiveRecord::ConnectionAdapters::Kudu::TableDefinition.new(*args)
|
412
|
+
end
|
413
|
+
|
414
|
+
def table_structure(table_name)
|
415
|
+
quoted = quote_table_name table_name
|
416
|
+
connection.query('DESCRIBE ' + quoted)
|
417
|
+
end
|
418
|
+
|
419
|
+
# check if options contains primary_key
|
420
|
+
def options_has_primary_key(options)
|
421
|
+
options[:primary_key] = false if options[:primary_key].nil?
|
422
|
+
options[:primary_key]
|
423
|
+
end
|
424
|
+
|
425
|
+
def primary_keys_contain_column_name(table_name, column_name)
|
426
|
+
pks = primary_key(table_name)
|
427
|
+
pks.include? column_name.to_s
|
428
|
+
end
|
429
|
+
|
430
|
+
def set_options_from_column_definition(column)
|
431
|
+
opt = {}
|
432
|
+
opt[:primary_key] = ActiveModel::Type::Boolean.new.cast(column[:primary_key]) if column[:primary_key].present?
|
433
|
+
opt[:null] = ActiveModel::Type::Boolean.new.cast(column[:nullable]) if column[:nullable].present?
|
434
|
+
opt[:default] = lookup_cast_type(column[:type]).serialize(column[:default_value]) if column[:default_value].present?
|
435
|
+
# TODO: Do we have more options ?
|
436
|
+
opt
|
437
|
+
end
|
438
|
+
|
439
|
+
# This method will copy existing structure of table with added new field.
|
440
|
+
# It works only if we're adding new primary key on existing table.
|
441
|
+
def redefine_table_add_primary_key(table_name, column_name, type, options = {})
|
442
|
+
|
443
|
+
redefined_table_name = table_name + '_redefined'
|
444
|
+
temp_table_name = table_name + '_temp'
|
445
|
+
|
446
|
+
columns = table_structure table_name
|
447
|
+
pk_columns = columns.select {|c| c[:primary_key] == 'true'}
|
448
|
+
non_pk_columns = columns.select {|c| c[:primary_key] == 'false'}
|
449
|
+
|
450
|
+
create_table(redefined_table_name, { id: false }) do |td|
|
451
|
+
|
452
|
+
# existing pk columns
|
453
|
+
pk_columns.each do |col|
|
454
|
+
td.send col[:type].to_sym, col[:name], set_options_from_column_definition(col)
|
455
|
+
end
|
456
|
+
|
457
|
+
# add new column
|
458
|
+
td.send type, column_name, options
|
459
|
+
|
460
|
+
non_pk_columns.each do |col|
|
461
|
+
td.send col[:type].to_sym, col[:name], set_options_from_column_definition(col)
|
462
|
+
end
|
463
|
+
|
464
|
+
end
|
465
|
+
|
466
|
+
# rename existing table to temp
|
467
|
+
rename_table(table_name, temp_table_name)
|
468
|
+
# rename newly created to active one
|
469
|
+
rename_table(redefined_table_name, table_name)
|
470
|
+
|
471
|
+
end
|
472
|
+
|
473
|
+
# This method will copy existing structure of table with primary key field removed.
|
474
|
+
# It works only if we're removing primary key on existing table.
|
475
|
+
def redefine_table_drop_primary_key(table_name, column_name, type, options = {})
|
476
|
+
|
477
|
+
redefined_table_name = table_name + '_redefined'
|
478
|
+
temp_table_name = table_name + '_temp'
|
479
|
+
|
480
|
+
columns = table_structure table_name
|
481
|
+
columns.reject! { |c| c[:name] == column_name.to_s }
|
482
|
+
|
483
|
+
create_table(redefined_table_name, { id: false }) do |td|
|
484
|
+
columns.each do |col|
|
485
|
+
td.send col[:type].to_sym, col[:name], set_options_from_column_definition(col)
|
486
|
+
end
|
487
|
+
end
|
488
|
+
|
489
|
+
# rename existing table to temp
|
490
|
+
rename_table(table_name, temp_table_name)
|
491
|
+
# rename newly created to active one
|
492
|
+
rename_table(redefined_table_name, table_name)
|
493
|
+
|
494
|
+
# copy reduced existing data into new table
|
495
|
+
select_qry = columns.map {|col| col[:name].to_s }.join(',')
|
496
|
+
copy_qry = "INSERT INTO #{quote_table_name(table_name)} (#{select_qry}) SELECT #{select_qry} FROM #{quote_table_name(temp_table_name)}"
|
497
|
+
execute copy_qry
|
498
|
+
|
499
|
+
# finally, drop temp table
|
500
|
+
drop_table(temp_table_name)
|
501
|
+
|
502
|
+
end
|
503
|
+
|
504
|
+
end
|
505
|
+
end
|
506
|
+
end
|
507
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_record/connection_adapters/sql_type_metadata'
|
4
|
+
|
5
|
+
module ActiveRecord
|
6
|
+
module ConnectionAdapters
|
7
|
+
module Kudu
|
8
|
+
# :nodoc:
|
9
|
+
class SqlTypeMetadata < ::ActiveRecord::ConnectionAdapters::SqlTypeMetadata
|
10
|
+
def initialize(sql_type: nil, type: nil)
|
11
|
+
super(sql_type: sql_type, type: type, limit: nil, precision: nil, scale: nil)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_record/connection_adapters/abstract/schema_definitions'
|
4
|
+
|
5
|
+
module ActiveRecord
|
6
|
+
module ConnectionAdapters
|
7
|
+
module Kudu
|
8
|
+
# :nodoc:
|
9
|
+
class TableDefinition < ::ActiveRecord::ConnectionAdapters::TableDefinition
|
10
|
+
attr_reader :external
|
11
|
+
attr_reader :partitions_count
|
12
|
+
attr_reader :partition_columns
|
13
|
+
|
14
|
+
def initialize(name, temporary = false, options = nil, as = nil, comment: nil)
|
15
|
+
super
|
16
|
+
@external = options&.[](:external)
|
17
|
+
@partitions_count = options&.[](:partitions_count)
|
18
|
+
@partition_columns = options&.[](:partition_columns)
|
19
|
+
end
|
20
|
+
|
21
|
+
def double(*args, **options)
|
22
|
+
args.each { |name| column(name, :double, options) }
|
23
|
+
end
|
24
|
+
|
25
|
+
def float(*args, **options)
|
26
|
+
args.each { |name| column(name, :float, options) }
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_model/type/integer'
|
4
|
+
|
5
|
+
module ActiveRecord
|
6
|
+
module ConnectionAdapters
|
7
|
+
module Kudu
|
8
|
+
module Type
|
9
|
+
# :nodoc:
|
10
|
+
class BigInt < ::ActiveModel::Type::Integer
|
11
|
+
def type
|
12
|
+
:bigint
|
13
|
+
end
|
14
|
+
|
15
|
+
def limit
|
16
|
+
8
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_model/type/boolean'
|
4
|
+
|
5
|
+
module ActiveRecord
|
6
|
+
module ConnectionAdapters
|
7
|
+
module Kudu
|
8
|
+
module Type
|
9
|
+
class Boolean < ::ActiveModel::Type::Boolean
|
10
|
+
|
11
|
+
def type
|
12
|
+
:boolean
|
13
|
+
end
|
14
|
+
|
15
|
+
def serialize(value)
|
16
|
+
ActiveModel::Type::Boolean.new.cast(value)
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_model/type/string'
|
4
|
+
|
5
|
+
module ActiveRecord
|
6
|
+
module ConnectionAdapters
|
7
|
+
module Kudu
|
8
|
+
module Type
|
9
|
+
class Char < ::ActiveModel::Type::String
|
10
|
+
def type
|
11
|
+
:char
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_model/type/helpers/time_value'
|
4
|
+
require 'active_record/connection_adapters/kudu/type/time'
|
5
|
+
|
6
|
+
module ActiveRecord
|
7
|
+
module ConnectionAdapters
|
8
|
+
module Kudu
|
9
|
+
module Type
|
10
|
+
include ::ActiveModel::Type::Helpers::TimeValue
|
11
|
+
|
12
|
+
# :nodoc:
|
13
|
+
class DateTime < ::ActiveRecord::ConnectionAdapters::Kudu::Type::Time
|
14
|
+
def type
|
15
|
+
:datetime
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_model/type/float'
|
4
|
+
|
5
|
+
module ActiveRecord
|
6
|
+
module ConnectionAdapters
|
7
|
+
module Kudu
|
8
|
+
module Type
|
9
|
+
class Double < ::ActiveModel::Type::Float
|
10
|
+
def type
|
11
|
+
:double
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_model/type/float'
|
4
|
+
|
5
|
+
module ActiveRecord
|
6
|
+
module ConnectionAdapters
|
7
|
+
module Kudu
|
8
|
+
module Type
|
9
|
+
# :nodoc:
|
10
|
+
class Float < ::ActiveModel::Type::Float
|
11
|
+
def type
|
12
|
+
:float
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|