activerecord-cubrid2-adapter 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +19 -0
  3. data/LICENSE +21 -0
  4. data/README.md +63 -0
  5. data/Rakefile +11 -0
  6. data/VERSION +1 -0
  7. data/activerecord-cubrid2-adapter.gemspec +26 -0
  8. data/lib/active_record/connection_adapters/abstract_cubrid2_adapter.rb +750 -0
  9. data/lib/active_record/connection_adapters/cubrid2/column.rb +28 -0
  10. data/lib/active_record/connection_adapters/cubrid2/database_statements.rb +192 -0
  11. data/lib/active_record/connection_adapters/cubrid2/explain_pretty_printer.rb +71 -0
  12. data/lib/active_record/connection_adapters/cubrid2/quoting.rb +118 -0
  13. data/lib/active_record/connection_adapters/cubrid2/schema_creation.rb +98 -0
  14. data/lib/active_record/connection_adapters/cubrid2/schema_definitions.rb +81 -0
  15. data/lib/active_record/connection_adapters/cubrid2/schema_dumper.rb +31 -0
  16. data/lib/active_record/connection_adapters/cubrid2/schema_statements.rb +276 -0
  17. data/lib/active_record/connection_adapters/cubrid2/type_metadata.rb +31 -0
  18. data/lib/active_record/connection_adapters/cubrid2/version.rb +7 -0
  19. data/lib/active_record/connection_adapters/cubrid2_adapter.rb +169 -0
  20. data/lib/activerecord-cubrid2-adapter.rb +4 -0
  21. data/lib/arel/visitors/cubrid.rb +67 -0
  22. data/lib/cubrid2/client.rb +93 -0
  23. data/lib/cubrid2/console.rb +5 -0
  24. data/lib/cubrid2/error.rb +86 -0
  25. data/lib/cubrid2/field.rb +3 -0
  26. data/lib/cubrid2/result.rb +7 -0
  27. data/lib/cubrid2/statement.rb +11 -0
  28. data/lib/cubrid2/version.rb +3 -0
  29. data/lib/cubrid2.rb +76 -0
  30. data/tests/Gemfile +10 -0
  31. data/tests/test_activerecord.rb +109 -0
  32. metadata +102 -0
@@ -0,0 +1,750 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_record/connection_adapters/abstract_adapter'
4
+ require 'active_record/connection_adapters/statement_pool'
5
+ require 'active_record/connection_adapters/cubrid2/column'
6
+ require 'active_record/connection_adapters/cubrid2/explain_pretty_printer'
7
+ require 'active_record/connection_adapters/cubrid2/quoting'
8
+ require 'active_record/connection_adapters/cubrid2/schema_creation'
9
+ require 'active_record/connection_adapters/cubrid2/schema_definitions'
10
+ require 'active_record/connection_adapters/cubrid2/schema_dumper'
11
+ require 'active_record/connection_adapters/cubrid2/schema_statements'
12
+ require 'active_record/connection_adapters/cubrid2/type_metadata'
13
+ require 'active_record/connection_adapters/cubrid2/version'
14
+ require 'arel/visitors/cubrid'
15
+
16
+ module ActiveRecord
17
+ module ConnectionAdapters
18
+ class AbstractCubrid2Adapter < AbstractAdapter
19
+ include Cubrid2::Quoting
20
+ include Cubrid2::SchemaStatements
21
+
22
+ ##
23
+ # :singleton-method:
24
+ # By default, the CubridAdapter will consider all columns of type <tt>tinyint(1)</tt>
25
+ # as boolean. If you wish to disable this emulation you can add the following line
26
+ # to your application.rb file:
27
+ #
28
+ # ActiveRecord::ConnectionAdapters::CubridAdapter.emulate_booleans = false
29
+ class_attribute :emulate_booleans, default: true
30
+
31
+ NATIVE_DATABASE_TYPES = {
32
+ primary_key: 'bigint auto_increment PRIMARY KEY',
33
+ string: { name: 'varchar', limit: 255 }, # 1_073_741_823
34
+ text: { name: 'text' },
35
+ integer: { name: 'int', limit: 4 },
36
+ float: { name: 'float', limit: 24 },
37
+ decimal: { name: 'decimal' },
38
+ datetime: { name: 'datetime' },
39
+ timestamp: { name: 'timestamp' },
40
+ time: { name: 'time' },
41
+ date: { name: 'date' },
42
+ binary: { name: 'blob' },
43
+ blob: { name: 'blob' },
44
+ boolean: { name: 'smallint' },
45
+ json: { name: 'json' }
46
+ }
47
+
48
+ class StatementPool < ConnectionAdapters::StatementPool # :nodoc:
49
+ private
50
+
51
+ def dealloc(stmt)
52
+ stmt.close
53
+ end
54
+ end
55
+
56
+ def initialize(connection, logger, _connection_options, config)
57
+ super(connection, logger, config)
58
+ end
59
+
60
+ def get_database_version # :nodoc:
61
+ full_version_string = get_full_version
62
+ version_string = version_string(full_version_string)
63
+ Version.new(version_string, full_version_string)
64
+ end
65
+
66
+ def supports_bulk_alter?
67
+ true
68
+ end
69
+
70
+ def supports_index_sort_order?
71
+ false
72
+ end
73
+
74
+ def supports_expression_index?
75
+ false
76
+ end
77
+
78
+ def supports_transaction_isolation?
79
+ true
80
+ end
81
+
82
+ def supports_explain?
83
+ true
84
+ end
85
+
86
+ def supports_indexes_in_create?
87
+ true
88
+ end
89
+
90
+ def supports_foreign_keys?
91
+ true
92
+ end
93
+
94
+ def supports_views?
95
+ true
96
+ end
97
+
98
+ def supports_datetime_with_precision?
99
+ false
100
+ end
101
+
102
+ def supports_virtual_columns?
103
+ true
104
+ end
105
+
106
+ def supports_optimizer_hints?
107
+ false
108
+ end
109
+
110
+ def supports_common_table_expressions?
111
+ database_version >= '10.2'
112
+ end
113
+
114
+ def supports_advisory_locks?
115
+ false
116
+ end
117
+
118
+ def supports_insert_on_duplicate_skip?
119
+ true
120
+ end
121
+
122
+ def supports_insert_on_duplicate_update?
123
+ true
124
+ end
125
+
126
+ def supports_rename_column?
127
+ true
128
+ end
129
+
130
+ # In cubrid: locking is done automatically
131
+ # See: https://www.cubrid.org/manual/en/11.2/sql/transaction.html#id13
132
+ def get_advisory_lock(_lock_name, _timeout = 0) # :nodoc:
133
+ # query_value("SELECT GET_LOCK(#{quote(lock_name.to_s)}, #{timeout})") == 1
134
+ true
135
+ end
136
+
137
+ def release_advisory_lock(_lock_name) # :nodoc:
138
+ # query_value("SELECT RELEASE_LOCK(#{quote(lock_name.to_s)})") == 1
139
+ true
140
+ end
141
+
142
+ def native_database_types
143
+ NATIVE_DATABASE_TYPES
144
+ end
145
+
146
+ def index_algorithms
147
+ { }
148
+ end
149
+
150
+ # HELPER METHODS ===========================================
151
+
152
+ # The two drivers have slightly different ways of yielding hashes of results, so
153
+ # this method must be implemented to provide a uniform interface.
154
+ def each_hash(result) # :nodoc:
155
+ raise NotImplementedError
156
+ end
157
+
158
+ # Must return the Cubrid error number from the exception, if the exception has an
159
+ # error number.
160
+ def error_number(exception) # :nodoc:
161
+ raise NotImplementedError
162
+ end
163
+
164
+ # REFERENTIAL INTEGRITY ====================================
165
+
166
+ # def disable_referential_integrity # :nodoc:
167
+ # old = query_value('SELECT @@FOREIGN_KEY_CHECKS')
168
+
169
+ # begin
170
+ # update('SET FOREIGN_KEY_CHECKS = 0')
171
+ # yield
172
+ # ensure
173
+ # update("SET FOREIGN_KEY_CHECKS = #{old}")
174
+ # end
175
+ # end
176
+
177
+ # CONNECTION MANAGEMENT ====================================
178
+
179
+ def clear_cache! # :nodoc:
180
+ reload_type_map
181
+ super
182
+ end
183
+
184
+ #--
185
+ # DATABASE STATEMENTS ======================================
186
+ #++
187
+
188
+ def explain(arel, binds = [])
189
+ sql = "EXPLAIN #{to_sql(arel, binds)}"
190
+ start = Concurrent.monotonic_time
191
+ result = exec_query(sql, 'EXPLAIN', binds)
192
+ elapsed = Concurrent.monotonic_time - start
193
+
194
+ Cubrid2::ExplainPrettyPrinter.new.pp(result, elapsed)
195
+ end
196
+
197
+ # Executes the SQL statement in the context of this connection.
198
+ def execute(sql, name = nil)
199
+ materialize_transactions
200
+
201
+ stmt = nil
202
+
203
+ log(sql, name) do
204
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
205
+ stmt = @connection.query(sql)
206
+ end
207
+ end
208
+
209
+ stmt
210
+ end
211
+
212
+ # CubridAdapter doesn't have to free a result after using it, but we use this method
213
+ # to write stuff in an abstract way without concerning ourselves about whether it
214
+ # needs to be explicitly freed or not.
215
+ def execute_and_free(sql, name = nil) # :nodoc:
216
+ yield execute(sql, name)
217
+ end
218
+
219
+ def begin_db_transaction
220
+ # NOTE: no begin statement in cubrid
221
+ # execute "BEGIN"
222
+ end
223
+
224
+ def begin_isolated_db_transaction(isolation)
225
+ execute "SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}"
226
+ begin_db_transaction
227
+ end
228
+
229
+ def commit_db_transaction # :nodoc:
230
+ execute 'COMMIT'
231
+ end
232
+
233
+ def exec_rollback_db_transaction # :nodoc:
234
+ execute 'ROLLBACK'
235
+ end
236
+
237
+ def empty_insert_statement_value(_primary_key = nil)
238
+ 'VALUES ()'
239
+ end
240
+
241
+ # SCHEMA STATEMENTS ========================================
242
+
243
+ # Drops the database: not supported now
244
+ def recreate_database(_name, _options = {})
245
+ raise 'In Cubrid create/drop database not supported'
246
+ end
247
+
248
+ # Create a new Cubrid database: not supported now
249
+ def create_database(_name, _options = {})
250
+ raise 'In Cubrid create/drop database not supported'
251
+ end
252
+
253
+ # Drops a Cubrid database: not supported now
254
+ def drop_database(_name) # :nodoc:
255
+ raise 'In Cubrid create/drop database not supported'
256
+ end
257
+
258
+ def current_database
259
+ query_value('SELECT database()', 'SCHEMA')
260
+ end
261
+
262
+ # Returns the database character set.
263
+ def charset
264
+ # check character set:
265
+ # See: https://www.cubrid.com/qna/3802763
266
+ @charset ||= query_value("select charset('ABC')", 'SCHEMA')
267
+ end
268
+
269
+ # Returns the database collation strategy.
270
+ def collation
271
+ # check collation set:
272
+ # See: https://www.cubrid.com/qna/3802763
273
+ @collation ||= query_value("select collation('ABC')", 'SCHEMA')
274
+ end
275
+
276
+ def table_comment(table_name) # :nodoc:
277
+ raise 'table comment not supported' unless supports_comments?
278
+
279
+ query_value(<<~SQL, 'SCHEMA').presence
280
+ SELECT comment
281
+ FROM db_class
282
+ WHERE owner_name = 'PUBLIC'
283
+ AND class_type = 'CLASS'
284
+ AND is_system_class = 'NO'
285
+ AND class_name = #{quote_table_name(table_name)}
286
+ SQL
287
+ end
288
+
289
+ def change_table_comment(table_name, comment_or_changes) # :nodoc:
290
+ comment = extract_new_comment_value(comment_or_changes)
291
+ comment = '' if comment.nil?
292
+ execute("ALTER TABLE #{quote_table_name(table_name)} COMMENT=#{quote(comment)}")
293
+ end
294
+
295
+ # Renames a table.
296
+ #
297
+ # Example:
298
+ # rename_table('octopuses', 'octopi')
299
+ def rename_table(table_name, new_name)
300
+ execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
301
+ rename_table_indexes(table_name, new_name)
302
+ end
303
+
304
+ # Drops a table from the database.
305
+ #
306
+ # [<tt>:force</tt>]
307
+ # Set to +:cascade+ to drop dependent objects as well.
308
+ # Defaults to false.
309
+ # [<tt>:if_exists</tt>]
310
+ # Set to +true+ to only drop the table if it exists.
311
+ # Defaults to false.
312
+ # [<tt>:temporary</tt>]
313
+ # Set to +true+ to drop temporary table.
314
+ # Defaults to false.
315
+ #
316
+ # Although this command ignores most +options+ and the block if one is given,
317
+ # it can be helpful to provide these in a migration's +change+ method so it can be reverted.
318
+ # In that case, +options+ and the block will be used by create_table.
319
+ def drop_table(table_name, options = {})
320
+ if_exists = (options[:if_exists] ? 'IF EXISTS' : '')
321
+ cascade = (options[:force] == :cascade ? 'CASCADE CONSTRAINTS' : '')
322
+ execute "DROP TABLE #{if_exists} #{quote_table_name(table_name)} #{cascade}"
323
+ end
324
+
325
+ def rename_index(table_name, old_name, new_name)
326
+ if supports_rename_index?
327
+ validate_index_length!(table_name, new_name)
328
+
329
+ execute "ALTER TABLE #{quote_table_name(table_name)} RENAME INDEX #{quote_table_name(old_name)} TO #{quote_table_name(new_name)}"
330
+ else
331
+ super
332
+ end
333
+ end
334
+
335
+ def change_column_default(table_name, column_name, default_or_changes) # :nodoc:
336
+ default = extract_new_default_value(default_or_changes)
337
+ change_column table_name, column_name, nil, default: default
338
+ end
339
+
340
+ def change_column_null(table_name, column_name, null, default = nil) # :nodoc:
341
+ unless null || default.nil?
342
+ execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
343
+ end
344
+
345
+ change_column table_name, column_name, nil, null: null
346
+ end
347
+
348
+ def change_column_comment(table_name, column_name, comment_or_changes) # :nodoc:
349
+ comment = extract_new_comment_value(comment_or_changes)
350
+ change_column table_name, column_name, nil, comment: comment
351
+ end
352
+
353
+ def change_column(table_name, column_name, type, options = {}) # :nodoc:
354
+ execute("ALTER TABLE #{quote_table_name(table_name)} #{change_column_for_alter(table_name, column_name, type, **options)}")
355
+ end
356
+
357
+ def rename_column(table_name, column_name, new_column_name) # :nodoc:
358
+ execute("ALTER TABLE #{quote_table_name(table_name)} #{rename_column_for_alter(table_name, column_name, new_column_name)}")
359
+ rename_column_indexes(table_name, column_name, new_column_name)
360
+ end
361
+
362
+ def add_index(table_name, column_name, options = {}) # :nodoc:
363
+ index, algorithm, if_not_exists = add_index_options(table_name, column_name, **options)
364
+
365
+ return if if_not_exists && index_exists?(table_name, column_name, name: index.name)
366
+
367
+ create_index = CreateIndexDefinition.new(index, algorithm)
368
+ execute schema_creation.accept(create_index)
369
+ end
370
+
371
+ def add_sql_comment!(sql, comment) # :nodoc:
372
+ return sql unless supports_comments?
373
+
374
+ sql << " COMMENT #{quote(comment)}" if comment.present?
375
+ sql
376
+ end
377
+
378
+ def foreign_keys(table_name)
379
+ raise ArgumentError unless table_name.present?
380
+
381
+ # In Cubrid, we cannot know referencing table that foreign key indicates from the system catalog.
382
+ # See: https://www.cubrid.com/qna/3822484
383
+ # So we use the raw sql generated by 'SHOW CREATE TABLE ...'
384
+
385
+ tableinfo = create_table_info(table_name)
386
+ lines = tableinfo.gsub('CONSTRAINT', "\nCONSTRAINT").split('CONSTRAINT')
387
+
388
+ fkeys = []
389
+ lines.each do |line|
390
+ fk_matches = line.match(/(.*) FOREIGN KEY (.*)/)
391
+ next if fk_matches.nil?
392
+
393
+ name = _strip_key_str(fk_matches[1])
394
+ detail_match = fk_matches[2].match(/(.*) REFERENCES (.*) ON DELETE (.*) ON UPDATE (.*)\s*/)
395
+
396
+ column = _strip_key_str(detail_match[1])
397
+ to_table_match = detail_match[2]&.match(/(.*)\s+\((.*)\)/)
398
+
399
+ to_table = _strip_key_str(to_table_match[1])
400
+ primary_key = _strip_key_str(to_table_match[2])
401
+
402
+ options = {
403
+ name: name,
404
+ column: column,
405
+ primary_key: primary_key
406
+ }
407
+
408
+ options[:on_update] = extract_foreign_key_action(_strip_left_str(detail_match[3]))
409
+ options[:on_delete] = extract_foreign_key_action(_strip_left_str(detail_match[4]))
410
+
411
+ fkeys << ForeignKeyDefinition.new(table_name, to_table, options)
412
+ end
413
+
414
+ fkeys
415
+ end
416
+
417
+ def _strip_key_str(str)
418
+ str.gsub(/[\[\]]/, '')
419
+ .gsub(/[()]/, '')
420
+ .gsub(/^\s+/, '').gsub(/\s+$/, '')
421
+ end
422
+
423
+ def _strip_left_str(str)
424
+ str.gsub(/([;,)].*)$/, '')
425
+ end
426
+
427
+ def table_options(table_name) # :nodoc:
428
+ table_options = {}
429
+
430
+ tableinfo = create_table_info(table_name)
431
+
432
+ # strip create_definitions and partition_options
433
+ # Be aware that `create_table_info` might not include any table options due to `NO_TABLE_OPTIONS` sql mode.
434
+ raw_table_options = tableinfo.sub(/\A.*\n\) ?/m, '').sub(%r{\n/\*!.*\*/\n\z}m, '').strip
435
+
436
+ table_options[:options] = raw_table_options unless raw_table_options.blank?
437
+
438
+ # strip COMMENT
439
+ table_options[:comment] = table_comment(table_name) if raw_table_options.sub!(/ COMMENT='.+'/, '')
440
+
441
+ table_options
442
+ end
443
+
444
+ # SHOW VARIABLES LIKE 'name'
445
+ def show_variable(_name)
446
+ raise 'Not supported'
447
+ end
448
+
449
+ def primary_keys(table_name) # :nodoc:
450
+ raise ArgumentError unless table_name.present?
451
+
452
+ prikeys = []
453
+ column_definitions(table_name).each do |col|
454
+ prikeys << col[:Field] if col[:Key] == 'PRI'
455
+ end
456
+ prikeys
457
+ end
458
+
459
+ def default_uniqueness_comparison(attribute, value, klass) # :nodoc:
460
+ column = column_for_attribute(attribute)
461
+
462
+ if column.collation && !column.case_sensitive? && !value.nil?
463
+ ActiveSupport::Deprecation.warn(<<~MSG.squish)
464
+ Uniqueness validator will no longer enforce case sensitive comparison in Rails 6.1.
465
+ To continue case sensitive comparison on the :#{attribute.name} attribute in #{klass} model,
466
+ pass `case_sensitive: true` option explicitly to the uniqueness validator.
467
+ MSG
468
+ attribute.eq(Arel::Nodes::Bin.new(value))
469
+ else
470
+ super
471
+ end
472
+ end
473
+
474
+ def case_sensitive_comparison(attribute, value) # :nodoc:
475
+ column = column_for_attribute(attribute)
476
+
477
+ if column.collation && !column.case_sensitive?
478
+ attribute.eq(Arel::Nodes::Bin.new(value))
479
+ else
480
+ super
481
+ end
482
+ end
483
+
484
+ def can_perform_case_insensitive_comparison_for?(column)
485
+ column.case_sensitive?
486
+ end
487
+ private :can_perform_case_insensitive_comparison_for?
488
+
489
+ def columns_for_distinct(columns, orders) # :nodoc:
490
+ order_columns = orders.reject(&:blank?).map do |s|
491
+ # Convert Arel node to string
492
+ s = visitor.compile(s) unless s.is_a?(String)
493
+ # Remove any ASC/DESC modifiers
494
+ s.gsub(/\s+(?:ASC|DESC)\b/i, '')
495
+ end.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" }
496
+
497
+ (order_columns << super).join(', ')
498
+ end
499
+
500
+ # def strict_mode?
501
+ # self.class.type_cast_config_to_boolean(@config.fetch(:strict, true))
502
+ # end
503
+
504
+ def default_index_type?(index) # :nodoc:
505
+ index.using == :btree || super
506
+ end
507
+
508
+ def build_insert_sql(insert) # :nodoc:
509
+ sql = +"INSERT #{insert.into} #{insert.values_list}"
510
+
511
+ if insert.skip_duplicates?
512
+ no_op_column = quote_column_name(insert.keys.first)
513
+ sql << " ON DUPLICATE KEY UPDATE #{no_op_column}=#{no_op_column}"
514
+ elsif insert.update_duplicates?
515
+ sql << ' ON DUPLICATE KEY UPDATE '
516
+ sql << insert.updatable_columns.map { |column| "#{column}=VALUES(#{column})" }.join(',')
517
+ end
518
+
519
+ sql
520
+ end
521
+
522
+ def check_version # :nodoc:
523
+ if database_version < '9.0'
524
+ raise "Your version of Cubrid (#{database_version}) is too old. Active Record supports Cubrid >= 9.0."
525
+ end
526
+ end
527
+
528
+ private
529
+
530
+ def initialize_type_map(m = type_map)
531
+ super
532
+
533
+ register_class_with_limit m, /char/i, CubridString
534
+
535
+ m.register_type(/blob/i, Type::Binary.new(limit: 2**30 - 1))
536
+ m.register_type(/clob/i, Type::Binary.new(limit: 2**30 - 1))
537
+ m.register_type(/^float/i, Type::Float.new(limit: 24))
538
+ m.register_type(/^double/i, Type::Float.new(limit: 53))
539
+
540
+ register_integer_type m, /^bigint/i, limit: 8
541
+ register_integer_type m, /^int/i, limit: 4
542
+ register_integer_type m, /^smallint/i, limit: 2
543
+
544
+ m.register_type(/^smallint\(1\)/i, Type::Boolean.new) if emulate_booleans
545
+ m.alias_type(/year/i, 'integer')
546
+ m.alias_type(/bit/i, 'binary')
547
+
548
+ m.register_type(/enum/i) do |sql_type|
549
+ limit = sql_type[/^enum\s*\((.+)\)/i, 1]
550
+ .split(',').map { |enum| enum.strip.length - 2 }.max
551
+ CubridString.new(limit: limit)
552
+ # String.new(limit: limit)
553
+ end
554
+
555
+ m.register_type(/^set/i) do |sql_type|
556
+ limit = sql_type[/^set\s*\((.+)\)/i, 1]
557
+ .split(',').map { |set| set.strip.length - 1 }.sum - 1
558
+ CubridString.new(limit: limit)
559
+ # String.new(limit: limit)
560
+ end
561
+ end
562
+
563
+ def register_integer_type(mapping, key, **options)
564
+ mapping.register_type(key) do |_sql_type|
565
+ # if /\bunsigned\b/.match?(sql_type)
566
+ # Type::UnsignedInteger.new(**options)
567
+ # else
568
+ Type::Integer.new(**options)
569
+ # end
570
+ end
571
+ end
572
+
573
+ def extract_precision(sql_type)
574
+ if /\A(?:date)?time(?:stamp)?\b/.match?(sql_type)
575
+ super || 0
576
+ else
577
+ super
578
+ end
579
+ end
580
+
581
+ # See https://www.cubrid.com/tutorial/3793681
582
+ # ER_FILSORT_ABORT = 1028
583
+ ER_DUP_ENTRY = 212
584
+ ER_NOT_NULL_VIOLATION = 631
585
+ # ER_NO_REFERENCED_ROW = 1216
586
+ # ER_ROW_IS_REFERENCED = 1217
587
+ ER_DO_NOT_HAVE_DEFAULT = 1364
588
+ # ER_ROW_IS_REFERENCED_2 = 1451
589
+ # ER_NO_REFERENCED_ROW_2 = 1452
590
+ ER_DATA_TOO_LONG = 781, 531
591
+ ER_OUT_OF_RANGE = 935
592
+ ER_LOCK_DEADLOCK = [72..76]
593
+ ER_CANNOT_ADD_FOREIGN = [920, 921, 922, 927]
594
+ ER_CANNOT_CREATE_TABLE = 65,
595
+ # ER_LOCK_WAIT_TIMEOUT = 1205
596
+ ER_QUERY_INTERRUPTED = 790
597
+ # ER_QUERY_TIMEOUT = 3024
598
+ ER_FK_INCOMPATIBLE_COLUMNS = [918, 923]
599
+
600
+ def translate_exception(exception, message:, sql:, binds:)
601
+ case error_number(exception)
602
+ when ER_DUP_ENTRY
603
+ RecordNotUnique.new(message, sql: sql, binds: binds)
604
+ # when ER_NO_REFERENCED_ROW, ER_ROW_IS_REFERENCED, ER_ROW_IS_REFERENCED_2, ER_NO_REFERENCED_ROW_2
605
+ # InvalidForeignKey.new(message, sql: sql, binds: binds)
606
+ when ER_CANNOT_ADD_FOREIGN, ER_FK_INCOMPATIBLE_COLUMNS
607
+ mismatched_foreign_key(message, sql: sql, binds: binds)
608
+ when ER_CANNOT_CREATE_TABLE
609
+ super
610
+ when ER_DATA_TOO_LONG
611
+ ValueTooLong.new(message, sql: sql, binds: binds)
612
+ when ER_OUT_OF_RANGE
613
+ RangeError.new(message, sql: sql, binds: binds)
614
+ when ER_NOT_NULL_VIOLATION, ER_DO_NOT_HAVE_DEFAULT
615
+ NotNullViolation.new(message, sql: sql, binds: binds)
616
+ when ER_LOCK_DEADLOCK
617
+ Deadlocked.new(message, sql: sql, binds: binds)
618
+ # when ER_LOCK_WAIT_TIMEOUT
619
+ # LockWaitTimeout.new(message, sql: sql, binds: binds)
620
+ # when ER_QUERY_TIMEOUT #, ER_FILSORT_ABORT
621
+ # StatementTimeout.new(message, sql: sql, binds: binds)
622
+ when ER_QUERY_INTERRUPTED
623
+ QueryCanceled.new(message, sql: sql, binds: binds)
624
+ else
625
+ super
626
+ end
627
+ end
628
+
629
+ def change_column_for_alter(table_name, column_name, type, options = {})
630
+ column = column_for(table_name, column_name)
631
+ type ||= column.sql_type
632
+
633
+ options[:default] = column.default unless options.key?(:default)
634
+ options[:null] = column.null unless options.key?(:null)
635
+ options[:comment] = column.comment unless options.key?(:comment)
636
+
637
+ td = create_table_definition(table_name)
638
+ cd = td.new_column_definition(column.name, type, **options)
639
+ schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
640
+ end
641
+
642
+ def rename_column_for_alter(table_name, column_name, new_column_name)
643
+ return rename_column_sql(table_name, column_name, new_column_name) if supports_rename_column?
644
+
645
+ column = column_for(table_name, column_name)
646
+ options = {
647
+ default: column.default,
648
+ null: column.null,
649
+ auto_increment: column.auto_increment?,
650
+ comment: column.comment
651
+ }
652
+
653
+ current_type = exec_query("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE #{quote(column_name)}", "SCHEMA").first["Type"]
654
+ td = create_table_definition(table_name)
655
+ cd = td.new_column_definition(new_column_name, current_type, **options)
656
+ schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
657
+ end
658
+
659
+ def add_index_for_alter(table_name, column_name, **options)
660
+ index, algorithm, _ = add_index_options(table_name, column_name, **options)
661
+ algorithm = ", #{algorithm}" if algorithm
662
+
663
+ "ADD #{schema_creation.accept(index)}#{algorithm}"
664
+ end
665
+
666
+ def remove_index_for_alter(table_name, column_name = nil, **options)
667
+ index_name = index_name_for_remove(table_name, column_name, options)
668
+ "DROP INDEX #{quote_column_name(index_name)}"
669
+ end
670
+
671
+ def supports_rename_index?
672
+ # https://www.cubrid.org/manual/en/10.0/sql/schema/index_stmt.html#alter-index
673
+ database_version >= '10.0'
674
+ end
675
+
676
+ def configure_connection; end
677
+
678
+ def column_definitions(table_name) # :nodoc:
679
+ execute_and_free("EXPLAIN #{quote_table_name(table_name)}", 'SCHEMA') do |result|
680
+ each_hash(result)
681
+ end
682
+ end
683
+
684
+ def create_table_info(table_name) # :nodoc:
685
+ res = exec_query("SHOW CREATE TABLE #{quote_table_name(table_name)}", 'SCHEMA')
686
+ res.first['CREATE TABLE']
687
+ end
688
+
689
+ def arel_visitor
690
+ Arel::Visitors::Cubrid.new(self)
691
+ end
692
+
693
+ def build_statement_pool
694
+ StatementPool.new(self.class.type_cast_config_to_integer(@config[:statement_limit]))
695
+ end
696
+
697
+ def mismatched_foreign_key(message, sql:, binds:)
698
+ q1 = '[`"\[]'
699
+ q2 = '[`"\]]'
700
+ match = /
701
+ (?:CREATE|ALTER)\s+TABLE\s*(?:#{q1}?\w+#{q2}?\.)?#{q1}?(?<table>\w+)#{q2}?.+?
702
+ FOREIGN\s+KEY\s*\(#{q1}?(?<foreign_key>\w+)#{q2}?\)\s*
703
+ REFERENCES\s*(#{q1}?(?<target_table>\w+)#{q2}?)\s*\(#{q1}?(?<primary_key>\w+)#{q2}?\)
704
+ /xmi.match(sql)
705
+
706
+ options = {
707
+ message: message,
708
+ sql: sql,
709
+ binds: binds
710
+ }
711
+
712
+ if match
713
+ options[:table] = match[:table]
714
+ options[:foreign_key] = match[:foreign_key]
715
+ options[:target_table] = match[:target_table]
716
+ options[:primary_key] = match[:primary_key]
717
+ options[:primary_key_column] = column_for(match[:target_table], match[:primary_key])
718
+ end
719
+
720
+ MismatchedForeignKey.new(**options)
721
+ end
722
+
723
+ def version_string(full_version_string)
724
+ full_version_string.match(/^(?:5\.5\.5-)?(\d+\.\d+\.\d+.\d+)/)[1]
725
+ end
726
+
727
+ class CubridString < Type::String # :nodoc:
728
+ def serialize(value)
729
+ case value
730
+ when true then '1'
731
+ when false then '0'
732
+ else super
733
+ end
734
+ end
735
+
736
+ private
737
+
738
+ def cast_value(value)
739
+ case value
740
+ when true then '1'
741
+ when false then '0'
742
+ else super
743
+ end
744
+ end
745
+ end
746
+
747
+ ActiveRecord::Type.register(:string, CubridString, adapter: :cubrid)
748
+ end
749
+ end
750
+ end