activerecord-cubrid2-adapter 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +19 -0
- data/LICENSE +21 -0
- data/README.md +63 -0
- data/Rakefile +11 -0
- data/VERSION +1 -0
- data/activerecord-cubrid2-adapter.gemspec +26 -0
- data/lib/active_record/connection_adapters/abstract_cubrid2_adapter.rb +750 -0
- data/lib/active_record/connection_adapters/cubrid2/column.rb +28 -0
- data/lib/active_record/connection_adapters/cubrid2/database_statements.rb +192 -0
- data/lib/active_record/connection_adapters/cubrid2/explain_pretty_printer.rb +71 -0
- data/lib/active_record/connection_adapters/cubrid2/quoting.rb +118 -0
- data/lib/active_record/connection_adapters/cubrid2/schema_creation.rb +98 -0
- data/lib/active_record/connection_adapters/cubrid2/schema_definitions.rb +81 -0
- data/lib/active_record/connection_adapters/cubrid2/schema_dumper.rb +31 -0
- data/lib/active_record/connection_adapters/cubrid2/schema_statements.rb +276 -0
- data/lib/active_record/connection_adapters/cubrid2/type_metadata.rb +31 -0
- data/lib/active_record/connection_adapters/cubrid2/version.rb +7 -0
- data/lib/active_record/connection_adapters/cubrid2_adapter.rb +169 -0
- data/lib/activerecord-cubrid2-adapter.rb +4 -0
- data/lib/arel/visitors/cubrid.rb +67 -0
- data/lib/cubrid2/client.rb +93 -0
- data/lib/cubrid2/console.rb +5 -0
- data/lib/cubrid2/error.rb +86 -0
- data/lib/cubrid2/field.rb +3 -0
- data/lib/cubrid2/result.rb +7 -0
- data/lib/cubrid2/statement.rb +11 -0
- data/lib/cubrid2/version.rb +3 -0
- data/lib/cubrid2.rb +76 -0
- data/tests/Gemfile +10 -0
- data/tests/test_activerecord.rb +109 -0
- 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
|