bormashino-sequel-sqljs-adapter 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,1068 @@
1
+ # frozen-string-literal: true
2
+
3
+ require 'sequel/adapters/utils/replace'
4
+ require 'sequel/adapters/utils/unmodified_identifiers'
5
+
6
+ module Sequel
7
+ module Sqljs
8
+ Sequel::Database.set_shared_adapter_scheme(:sqljs, self)
9
+
10
+ def self.mock_adapter_setup(db)
11
+ db.instance_exec do
12
+ @sqlite_version = 30903
13
+
14
+ def schema_parse_table(*)
15
+ []
16
+ end
17
+ singleton_class.send(:private, :schema_parse_table)
18
+ end
19
+ end
20
+
21
+ # No matter how you connect to SQLite, the following Database options
22
+ # can be used to set PRAGMAs on connections in a thread-safe manner:
23
+ # :auto_vacuum, :foreign_keys, :synchronous, and :temp_store.
24
+ module DatabaseMethods
25
+ include UnmodifiedIdentifiers::DatabaseMethods
26
+
27
+ AUTO_VACUUM = %i[none full incremental].freeze
28
+ SYNCHRONOUS = %i[off normal full].freeze
29
+ TEMP_STORE = %i[default file memory].freeze
30
+ TRANSACTION_MODE = {
31
+ :deferred => 'BEGIN DEFERRED TRANSACTION',
32
+ :immediate => 'BEGIN IMMEDIATE TRANSACTION',
33
+ :exclusive => 'BEGIN EXCLUSIVE TRANSACTION',
34
+ nil => 'BEGIN',
35
+ }.freeze
36
+
37
+ # Whether to use integers for booleans in the database. SQLite recommends
38
+ # booleans be stored as integers, but historically Sequel has used 't'/'f'.
39
+ attr_accessor :integer_booleans
40
+
41
+ # Whether to keep CURRENT_TIMESTAMP and similar expressions in UTC. By
42
+ # default, the expressions are converted to localtime.
43
+ attr_accessor :current_timestamp_utc
44
+
45
+ # A symbol signifying the value of the default transaction mode
46
+ attr_reader :transaction_mode
47
+
48
+ # Set the default transaction mode.
49
+ def transaction_mode=(value)
50
+ if TRANSACTION_MODE.include?(value)
51
+ @transaction_mode = value
52
+ else
53
+ raise Error, 'Invalid value for transaction_mode. Please specify one of :deferred, :immediate, :exclusive, nil'
54
+ end
55
+ end
56
+
57
+ # SQLite uses the :sqlite database type.
58
+ def database_type
59
+ :sqlite
60
+ end
61
+
62
+ # Set the integer_booleans option using the passed in :integer_boolean option.
63
+ def set_integer_booleans
64
+ @integer_booleans = @opts.key?(:integer_booleans) ? typecast_value_boolean(@opts[:integer_booleans]) : true
65
+ end
66
+
67
+ # Return the array of foreign key info hashes using the foreign_key_list PRAGMA,
68
+ # including information for the :on_update and :on_delete entries.
69
+ def foreign_key_list(table, _opts = OPTS)
70
+ m = output_identifier_meth
71
+ h = {}
72
+ _foreign_key_list_ds(table).each do |row|
73
+ if r = h[row[:id]]
74
+ r[:columns] << m.call(row[:from])
75
+ r[:key] << m.call(row[:to]) if r[:key]
76
+ else
77
+ h[row[:id]] =
78
+ { columns: [m.call(row[:from])], table: m.call(row[:table]), key: ([m.call(row[:to])] if row[:to]),
79
+ on_update: on_delete_sql_to_sym(row[:on_update]), on_delete: on_delete_sql_to_sym(row[:on_delete]) }
80
+ end
81
+ end
82
+ h.values
83
+ end
84
+
85
+ def freeze
86
+ sqlite_version
87
+ use_timestamp_timezones?
88
+ super
89
+ end
90
+
91
+ # Use the index_list and index_info PRAGMAs to determine the indexes on the table.
92
+ def indexes(table, opts = OPTS)
93
+ m = output_identifier_meth
94
+ im = input_identifier_meth
95
+ indexes = {}
96
+ table = table.value if table.is_a?(Sequel::SQL::Identifier)
97
+ metadata_dataset.with_sql('PRAGMA index_list(?)', im.call(table)).each do |r|
98
+ if opts[:only_autocreated]
99
+ # If specifically asked for only autocreated indexes, then return those an only those
100
+ next unless r[:name] =~ /\Asqlite_autoindex_/
101
+ elsif r.key?(:origin)
102
+ # If origin is set, then only exclude primary key indexes and partial indexes
103
+ next if r[:origin] == 'pk'
104
+ next if r[:partial].to_i == 1
105
+ elsif r[:name] =~ /\Asqlite_autoindex_/
106
+ next
107
+ end
108
+ # When :origin key not present, assume any autoindex could be a primary key one and exclude it
109
+
110
+ indexes[m.call(r[:name])] = { unique: r[:unique].to_i == 1 }
111
+ end
112
+ indexes.each do |k, v|
113
+ v[:columns] = metadata_dataset.with_sql('PRAGMA index_info(?)', im.call(k)).map(:name).map { |x| m.call(x) }
114
+ end
115
+ indexes
116
+ end
117
+
118
+ # The version of the server as an integer, where 3.6.19 = 30619.
119
+ # If the server version can't be determined, 0 is used.
120
+ def sqlite_version
121
+ return @sqlite_version if defined?(@sqlite_version)
122
+
123
+ @sqlite_version = begin
124
+ v = fetch('SELECT sqlite_version()').single_value
125
+ [10000, 100, 1].zip(v.split('.')).inject(0) { |a, m| a + (m[0] * Integer(m[1])) }
126
+ rescue StandardError
127
+ 0
128
+ end
129
+ end
130
+
131
+ # SQLite supports CREATE TABLE IF NOT EXISTS syntax since 3.3.0.
132
+ def supports_create_table_if_not_exists?
133
+ sqlite_version >= 30300
134
+ end
135
+
136
+ # SQLite 3.6.19+ supports deferrable foreign key constraints.
137
+ def supports_deferrable_foreign_key_constraints?
138
+ sqlite_version >= 30619
139
+ end
140
+
141
+ # SQLite 3.8.0+ supports partial indexes.
142
+ def supports_partial_indexes?
143
+ sqlite_version >= 30800
144
+ end
145
+
146
+ # SQLite 3.6.8+ supports savepoints.
147
+ def supports_savepoints?
148
+ sqlite_version >= 30608
149
+ end
150
+
151
+ # Override the default setting for whether to use timezones in timestamps.
152
+ # It is set to +false+ by default, as SQLite's date/time methods do not
153
+ # support timezones in timestamps.
154
+ attr_writer :use_timestamp_timezones
155
+
156
+ # SQLite supports timezones in timestamps, since it just stores them as strings,
157
+ # but it breaks the usage of SQLite's datetime functions.
158
+ def use_timestamp_timezones?
159
+ defined?(@use_timestamp_timezones) ? @use_timestamp_timezones : (@use_timestamp_timezones = false)
160
+ end
161
+
162
+ # Array of symbols specifying the table names in the current database.
163
+ #
164
+ # Options:
165
+ # :server :: Set the server to use.
166
+ def tables(opts = OPTS)
167
+ tables_and_views(Sequel.~(name: 'sqlite_sequence') & { type: 'table' }, opts)
168
+ end
169
+
170
+ # Creates a dataset that uses the VALUES clause:
171
+ #
172
+ # DB.values([[1, 2], [3, 4]])
173
+ # # VALUES ((1, 2), (3, 4))
174
+ def values(v)
175
+ @default_dataset.clone(values: v)
176
+ end
177
+
178
+ # Array of symbols specifying the view names in the current database.
179
+ #
180
+ # Options:
181
+ # :server :: Set the server to use.
182
+ def views(opts = OPTS)
183
+ tables_and_views({ type: 'view' }, opts)
184
+ end
185
+
186
+ private
187
+
188
+ # Dataset used for parsing foreign key lists
189
+ def _foreign_key_list_ds(table)
190
+ metadata_dataset.with_sql('PRAGMA foreign_key_list(?)', input_identifier_meth.call(table))
191
+ end
192
+
193
+ # Dataset used for parsing schema
194
+ def _parse_pragma_ds(table_name, opts)
195
+ metadata_dataset.with_sql("PRAGMA table_#{'x' if sqlite_version > 33100}info(?)", input_identifier_meth(opts[:dataset]).call(table_name))
196
+ end
197
+
198
+ # Run all alter_table commands in a transaction. This is technically only
199
+ # needed for drop column.
200
+ def apply_alter_table(table, ops)
201
+ fks = fetch('PRAGMA foreign_keys')
202
+ if fks
203
+ run 'PRAGMA foreign_keys = 0'
204
+ run 'PRAGMA legacy_alter_table = 1' if sqlite_version >= 32600
205
+ end
206
+ transaction do
207
+ if ops.length > 1 && ops.all? { |op| op[:op] == :add_constraint || op[:op] == :set_column_null }
208
+ null_ops, ops = ops.partition { |op| op[:op] == :set_column_null }
209
+
210
+ # Apply NULL/NOT NULL ops first, since those should be purely idependent of the constraints.
211
+ null_ops.each { |op| alter_table_sql_list(table, [op]).flatten.each { |sql| execute_ddl(sql) } }
212
+
213
+ # If you are just doing constraints, apply all of them at the same time,
214
+ # as otherwise all but the last one get lost.
215
+ alter_table_sql_list(table, [{ op: :add_constraints, ops: }]).flatten.each { |sql| execute_ddl(sql) }
216
+ else
217
+ # Run each operation separately, as later operations may depend on the
218
+ # results of earlier operations.
219
+ ops.each { |op| alter_table_sql_list(table, [op]).flatten.each { |sql| execute_ddl(sql) } }
220
+ end
221
+ end
222
+ remove_cached_schema(table)
223
+ ensure
224
+ if fks
225
+ run 'PRAGMA foreign_keys = 1'
226
+ run 'PRAGMA legacy_alter_table = 0' if sqlite_version >= 32600
227
+ end
228
+ end
229
+
230
+ # SQLite supports limited table modification. You can add a column
231
+ # or an index. Dropping columns is supported by copying the table into
232
+ # a temporary table, dropping the table, and creating a new table without
233
+ # the column inside of a transaction.
234
+ def alter_table_sql(table, op)
235
+ case op[:op]
236
+ when :add_index, :drop_index
237
+ super
238
+ when :add_column
239
+ if op[:unique] || op[:primary_key]
240
+ duplicate_table(table) { |columns| columns.push(op) }
241
+ else
242
+ super
243
+ end
244
+ when :drop_column
245
+ if sqlite_version >= 33500
246
+ super
247
+ else
248
+ ocp = ->(oc) { oc.delete_if { |c| c.to_s == op[:name].to_s } }
249
+ duplicate_table(table, old_columns_proc: ocp) { |columns| columns.delete_if { |s| s[:name].to_s == op[:name].to_s } }
250
+ end
251
+ when :rename_column
252
+ if sqlite_version >= 32500
253
+ super
254
+ else
255
+ ncp = ->(nc) { nc.map! { |c| c.to_s == op[:name].to_s ? op[:new_name] : c } }
256
+ duplicate_table(table, new_columns_proc: ncp) { |columns| columns.each { |s| s[:name] = op[:new_name] if s[:name].to_s == op[:name].to_s } }
257
+ end
258
+ when :set_column_default
259
+ duplicate_table(table) { |columns| columns.each { |s| s[:default] = op[:default] if s[:name].to_s == op[:name].to_s } }
260
+ when :set_column_null
261
+ duplicate_table(table) { |columns| columns.each { |s| s[:null] = op[:null] if s[:name].to_s == op[:name].to_s } }
262
+ when :set_column_type
263
+ duplicate_table(table) { |columns| columns.each { |s| s.merge!(op) if s[:name].to_s == op[:name].to_s } }
264
+ when :drop_constraint
265
+ case op[:type]
266
+ when :primary_key
267
+ duplicate_table(table) do |columns|
268
+ columns.each do |s|
269
+ s[:unique] = false if s[:primary_key]
270
+ s[:primary_key] = s[:auto_increment] = nil
271
+ end
272
+ end
273
+ when :foreign_key
274
+ if op[:columns]
275
+ duplicate_table(table, skip_foreign_key_columns: op[:columns])
276
+ else
277
+ duplicate_table(table, no_foreign_keys: true)
278
+ end
279
+ when :unique
280
+ duplicate_table(table, no_unique: true)
281
+ else
282
+ duplicate_table(table)
283
+ end
284
+ when :add_constraint
285
+ duplicate_table(table, constraints: [op])
286
+ when :add_constraints
287
+ duplicate_table(table, constraints: op[:ops])
288
+ else
289
+ raise Error, "Unsupported ALTER TABLE operation: #{op[:op].inspect}"
290
+ end
291
+ end
292
+
293
+ def begin_new_transaction(conn, opts)
294
+ mode = opts[:mode] || @transaction_mode
295
+ sql = TRANSACTION_MODE[mode] or raise Error, 'transaction :mode must be one of: :deferred, :immediate, :exclusive, nil'
296
+ log_connection_execute(conn, sql)
297
+ set_transaction_isolation(conn, opts)
298
+ end
299
+
300
+ # A name to use for the backup table
301
+ def backup_table_name(table, opts = OPTS)
302
+ table = table.gsub('`', '')
303
+ (opts[:times] || 1000).times do |i|
304
+ table_name = "#{table}_backup#{i}"
305
+ return table_name unless table_exists?(table_name)
306
+ end
307
+ end
308
+
309
+ # SQLite allows adding primary key constraints on NULLABLE columns, but then
310
+ # does not enforce NOT NULL for such columns, so force setting the columns NOT NULL.
311
+ def can_add_primary_key_constraint_on_nullable_columns?
312
+ false
313
+ end
314
+
315
+ # Surround default with parens to appease SQLite. Add support for GENERATED ALWAYS AS.
316
+ def column_definition_default_sql(sql, column)
317
+ sql << " DEFAULT (#{literal(column[:default])})" if column.include?(:default)
318
+ if (generated = column[:generated_always_as])
319
+ if (generated_type = column[:generated_type]) && %i[stored virtual].include?(generated_type)
320
+ generated_type = generated_type.to_s.upcase
321
+ end
322
+ sql << " GENERATED ALWAYS AS (#{literal(generated)}) #{generated_type}"
323
+ end
324
+ end
325
+
326
+ # Array of PRAGMA SQL statements based on the Database options that should be applied to
327
+ # new connections.
328
+ def connection_pragmas
329
+ ps = []
330
+ v = typecast_value_boolean(opts.fetch(:foreign_keys, 1))
331
+ ps << "PRAGMA foreign_keys = #{v ? 1 : 0}"
332
+ v = typecast_value_boolean(opts.fetch(:case_sensitive_like, 1))
333
+ ps << "PRAGMA case_sensitive_like = #{v ? 1 : 0}"
334
+ [[:auto_vacuum, AUTO_VACUUM], [:synchronous, SYNCHRONOUS], [:temp_store, TEMP_STORE]].each do |prag, con|
335
+ next unless v = opts[prag]
336
+ raise(Error, "Value for PRAGMA #{prag} not supported, should be one of #{con.join(', ')}") unless v = con.index(v.to_sym)
337
+
338
+ ps << "PRAGMA #{prag} = #{v}"
339
+ end
340
+ ps
341
+ end
342
+
343
+ # Support creating STRICT tables via :strict option
344
+ def create_table_sql(name, generator, options)
345
+ "#{super}#{' STRICT' if options[:strict]}"
346
+ end
347
+
348
+ # SQLite support creating temporary views.
349
+ def create_view_prefix_sql(name, options)
350
+ create_view_sql_append_columns("CREATE #{'TEMPORARY ' if options[:temp]}VIEW #{quote_schema_table(name)}", options[:columns])
351
+ end
352
+
353
+ DATABASE_ERROR_REGEXPS = {
354
+ /(is|are) not unique\z|PRIMARY KEY must be unique\z|UNIQUE constraint failed: .+\z/ => UniqueConstraintViolation,
355
+ /foreign key constraint failed\z/i => ForeignKeyConstraintViolation,
356
+ /\A(SQLITE ERROR 275 \(CONSTRAINT_CHECK\) : )?CHECK constraint failed/ => CheckConstraintViolation,
357
+ /\A(SQLITE ERROR 19 \(CONSTRAINT\) : )?constraint failed\z/ => ConstraintViolation,
358
+ /\Acannot store [A-Z]+ value in [A-Z]+ column / => ConstraintViolation,
359
+ /\A[A-Z]+ constraint failed: / => ConstraintViolation,
360
+ /may not be NULL\z|NOT NULL constraint failed: .+\z/ => NotNullConstraintViolation,
361
+ /\ASQLITE ERROR \d+ \(\) : CHECK constraint failed: / => CheckConstraintViolation,
362
+ }.freeze
363
+ def database_error_regexps
364
+ DATABASE_ERROR_REGEXPS
365
+ end
366
+
367
+ # Recognize SQLite error codes if the exception provides access to them.
368
+ def database_specific_error_class(exception, opts)
369
+ case sqlite_error_code(exception)
370
+ when 1299
371
+ NotNullConstraintViolation
372
+ when 1555, 2067, 2579
373
+ UniqueConstraintViolation
374
+ when 787
375
+ ForeignKeyConstraintViolation
376
+ when 275
377
+ CheckConstraintViolation
378
+ when 19
379
+ ConstraintViolation
380
+ when 517
381
+ SerializationFailure
382
+ else
383
+ super
384
+ end
385
+ end
386
+
387
+ # The array of column schema hashes for the current columns in the table
388
+ def defined_columns_for(table)
389
+ cols = parse_pragma(table, OPTS)
390
+ cols.each do |c|
391
+ c[:default] = LiteralString.new(c[:default]) if c[:default]
392
+ c[:type] = c[:db_type]
393
+ end
394
+ cols
395
+ end
396
+
397
+ # Duplicate an existing table by creating a new table, copying all records
398
+ # from the existing table into the new table, deleting the existing table
399
+ # and renaming the new table to the existing table's name.
400
+ def duplicate_table(table, opts = OPTS)
401
+ remove_cached_schema(table)
402
+ def_columns = defined_columns_for(table)
403
+ old_columns = def_columns.map { |element| element[:name] }
404
+ opts[:old_columns_proc]&.call(old_columns)
405
+
406
+ yield def_columns if defined?(yield)
407
+
408
+ constraints = (opts[:constraints] || []).dup
409
+ pks = []
410
+ def_columns.each { |c| pks << c[:name] if c[:primary_key] }
411
+ if pks.length > 1
412
+ constraints << { type: :primary_key, columns: pks }
413
+ def_columns.each { |c| c[:primary_key] = false if c[:primary_key] }
414
+ end
415
+
416
+ # If dropping a foreign key constraint, drop all foreign key constraints,
417
+ # as there is no way to determine which one to drop.
418
+ unless opts[:no_foreign_keys]
419
+ fks = foreign_key_list(table)
420
+
421
+ # If dropping a column, if there is a foreign key with that
422
+ # column, don't include it when building a copy of the table.
423
+ if ocp = opts[:old_columns_proc]
424
+ fks.delete_if { |c| ocp.call(c[:columns].dup) != c[:columns] }
425
+ end
426
+
427
+ # Skip any foreign key columns where a constraint for those
428
+ # foreign keys is being dropped.
429
+ if sfkc = opts[:skip_foreign_key_columns]
430
+ fks.delete_if { |c| c[:columns] == sfkc }
431
+ end
432
+
433
+ constraints.concat(fks.each { |h| h[:type] = :foreign_key })
434
+ end
435
+
436
+ # Determine unique constraints and make sure the new columns have them
437
+ unique_columns = []
438
+ skip_indexes = []
439
+ indexes(table, only_autocreated: true).each do |name, h|
440
+ skip_indexes << name
441
+ if h[:unique] && !opts[:no_unique]
442
+ if h[:columns].length == 1
443
+ unique_columns.concat(h[:columns])
444
+ elsif h[:columns].map(&:to_s) != pks
445
+ constraints << { type: :unique, columns: h[:columns] }
446
+ end
447
+ end
448
+ end
449
+ unique_columns -= pks
450
+ unless unique_columns.empty?
451
+ unique_columns.map! { |c| quote_identifier(c) }
452
+ def_columns.each do |c|
453
+ c[:unique] = true if unique_columns.include?(quote_identifier(c[:name])) && c[:unique] != false
454
+ end
455
+ end
456
+
457
+ def_columns_str = (def_columns.map { |c| column_definition_sql(c) } + constraints.map { |c| constraint_definition_sql(c) }).join(', ')
458
+ new_columns = old_columns.dup
459
+ opts[:new_columns_proc]&.call(new_columns)
460
+
461
+ qt = quote_schema_table(table)
462
+ bt = quote_identifier(backup_table_name(qt))
463
+ a = [
464
+ "ALTER TABLE #{qt} RENAME TO #{bt}",
465
+ "CREATE TABLE #{qt}(#{def_columns_str})",
466
+ "INSERT INTO #{qt}(#{dataset.send(:identifier_list, new_columns)}) SELECT #{dataset.send(:identifier_list, old_columns)} FROM #{bt}",
467
+ "DROP TABLE #{bt}",
468
+ ]
469
+ indexes(table).each do |name, h|
470
+ next if skip_indexes.include?(name)
471
+
472
+ if (h[:columns].map(&:to_s) - new_columns).empty?
473
+ a << alter_table_sql(table, h.merge(op: :add_index, name:))
474
+ end
475
+ end
476
+ a
477
+ end
478
+
479
+ DELETE_SQL_SYMS = {
480
+ 'RESTRICT' => :restrict,
481
+ 'CASCADE' => :cascade,
482
+ 'SET NULL' => :set_null,
483
+ 'SET DEFAULT' => :set_default,
484
+ 'NO ACTION' => :no_action,
485
+ }.freeze
486
+ # Does the reverse of on_delete_clause, eg. converts strings like +'SET NULL'+
487
+ # to symbols +:set_null+.
488
+ def on_delete_sql_to_sym(str)
489
+ DELETE_SQL_SYMS[str]
490
+ end
491
+
492
+ # Parse the output of the table_info pragma
493
+ def parse_pragma(table_name, opts)
494
+ pks = 0
495
+ sch = _parse_pragma_ds(table_name, opts).map do |row|
496
+ # table_xinfo PRAGMA used, remove hidden columns
497
+ # that are not generated columns
498
+ if sqlite_version > 33100 && (row[:generated] = (row.delete(:hidden) != 0))
499
+ next unless row[:type].end_with?(' GENERATED ALWAYS')
500
+
501
+ row[:type] = row[:type].sub(' GENERATED ALWAYS', '')
502
+ end
503
+
504
+ row.delete(:cid)
505
+ row[:allow_null] = row.delete(:notnull).to_i.zero?
506
+ row[:default] = row.delete(:dflt_value)
507
+ row[:default] = nil if blank_object?(row[:default]) || row[:default] == 'NULL'
508
+ row[:db_type] = row.delete(:type)
509
+ if row[:primary_key] = row.delete(:pk).to_i.positive?
510
+ pks += 1
511
+ # Guess that an integer primary key uses auto increment,
512
+ # since that is Sequel's default and SQLite does not provide
513
+ # a way to introspect whether it is actually autoincrementing.
514
+ row[:auto_increment] = row[:db_type].downcase == 'integer'
515
+ end
516
+ row[:type] = schema_column_type(row[:db_type])
517
+ row
518
+ end
519
+
520
+ sch.compact!
521
+
522
+ if pks > 1
523
+ # SQLite does not allow use of auto increment for tables
524
+ # with composite primary keys, so remove auto_increment
525
+ # if composite primary keys are detected.
526
+ sch.each { |r| r.delete(:auto_increment) }
527
+ end
528
+
529
+ sch
530
+ end
531
+
532
+ # SQLite supports schema parsing using the table_info PRAGMA, so
533
+ # parse the output of that into the format Sequel expects.
534
+ def schema_parse_table(table_name, opts)
535
+ m = output_identifier_meth(opts[:dataset])
536
+ parse_pragma(table_name, opts).map do |row|
537
+ [m.call(row.delete(:name)), row]
538
+ end
539
+ end
540
+
541
+ # Don't support SQLite error codes for exceptions by default.
542
+ def sqlite_error_code(_exception)
543
+ nil
544
+ end
545
+
546
+ # Backbone of the tables and views support.
547
+ def tables_and_views(filter, opts)
548
+ m = output_identifier_meth
549
+ metadata_dataset.from(:sqlite_master).server(opts[:server]).where(filter).map { |r| m.call(r[:name]) }
550
+ end
551
+
552
+ # SQLite only supports AUTOINCREMENT on integer columns, not
553
+ # bigint columns, so use integer instead of bigint for those
554
+ # columns.
555
+ def type_literal_generic_bignum_symbol(column)
556
+ column[:auto_increment] ? :integer : super
557
+ end
558
+ end
559
+
560
+ module DatasetMethods
561
+ include Dataset::Replace
562
+ include UnmodifiedIdentifiers::DatasetMethods
563
+
564
+ # The allowed values for insert_conflict
565
+ INSERT_CONFLICT_RESOLUTIONS = %w[ROLLBACK ABORT FAIL IGNORE REPLACE].each(&:freeze).freeze
566
+
567
+ CONSTANT_MAP = { CURRENT_DATE: "date(CURRENT_TIMESTAMP, 'localtime')", CURRENT_TIMESTAMP: "datetime(CURRENT_TIMESTAMP, 'localtime')",
568
+ CURRENT_TIME: "time(CURRENT_TIMESTAMP, 'localtime')" }.freeze
569
+ EXTRACT_MAP = { year: "'%Y'", month: "'%m'", day: "'%d'", hour: "'%H'", minute: "'%M'", second: "'%f'" }.freeze
570
+ EXTRACT_MAP.each_value(&:freeze)
571
+
572
+ Dataset.def_sql_method(self, :delete,
573
+ [['if db.sqlite_version >= 33500',
574
+ %w[with delete from where returning]],
575
+ ['elsif db.sqlite_version >= 30803', %w[with delete from where]],
576
+ ['else', %w[delete from where]]])
577
+ Dataset.def_sql_method(self, :insert,
578
+ [['if db.sqlite_version >= 33500',
579
+ %w[with insert conflict into columns values on_conflict returning]],
580
+ ['elsif db.sqlite_version >= 30803', %w[with insert conflict into columns values on_conflict]],
581
+ ['else', %w[insert conflict into columns values]]])
582
+ Dataset.def_sql_method(self, :select,
583
+ [['if opts[:values]',
584
+ %w[with values compounds]],
585
+ ['else', %w[with select distinct columns from join where group having window compounds order limit lock]]])
586
+ Dataset.def_sql_method(self, :update,
587
+ [[
588
+ 'if db.sqlite_version >= 33500',
589
+ %w[with update table set from where returning],
590
+ ],
591
+ ['elsif db.sqlite_version >= 33300', %w[with update table set from where]],
592
+ ['elsif db.sqlite_version >= 30803', %w[with update table set where]],
593
+ ['else', %w[update table set where]]])
594
+
595
+ def cast_sql_append(sql, expr, type)
596
+ if (type == Time) || (type == DateTime)
597
+ sql << 'datetime('
598
+ literal_append(sql, expr)
599
+ sql << ')'
600
+ elsif type == Date
601
+ sql << 'date('
602
+ literal_append(sql, expr)
603
+ sql << ')'
604
+ else
605
+ super
606
+ end
607
+ end
608
+
609
+ # SQLite doesn't support a NOT LIKE b, you need to use NOT (a LIKE b).
610
+ # It doesn't support xor, power, or the extract function natively, so those have to be emulated.
611
+ def complex_expression_sql_append(sql, op, args)
612
+ case op
613
+ when :'NOT LIKE', :'NOT ILIKE'
614
+ sql << 'NOT '
615
+ complex_expression_sql_append(sql, (op == :'NOT ILIKE' ? :ILIKE : :LIKE), args)
616
+ when :^
617
+ complex_expression_arg_pairs_append(sql, args) { |a, b| Sequel.lit(['((~(', ' & ', ')) & (', ' | ', '))'], a, b, a, b) }
618
+ when :**
619
+ unless (exp = args[1]).is_a?(Integer)
620
+ raise(Sequel::Error, "can only emulate exponentiation on SQLite if exponent is an integer, given #{exp.inspect}")
621
+ end
622
+
623
+ case exp
624
+ when 0
625
+ sql << '1'
626
+ else
627
+ sql << '('
628
+ arg = args[0]
629
+ if exp.negative?
630
+ invert = true
631
+ exp = exp.abs
632
+ sql << '(1.0 / ('
633
+ end
634
+ (exp - 1).times do
635
+ literal_append(sql, arg)
636
+ sql << ' * '
637
+ end
638
+ literal_append(sql, arg)
639
+ sql << ')'
640
+ if invert
641
+ sql << '))'
642
+ end
643
+ end
644
+ when :extract
645
+ part = args[0]
646
+ raise(Sequel::Error, "unsupported extract argument: #{part.inspect}") unless format = EXTRACT_MAP[part]
647
+
648
+ sql << 'CAST(strftime(' << format << ', '
649
+ literal_append(sql, args[1])
650
+ sql << ') AS ' << (part == :second ? 'NUMERIC' : 'INTEGER') << ')'
651
+ else
652
+ super
653
+ end
654
+ end
655
+
656
+ # SQLite has CURRENT_TIMESTAMP and related constants in UTC instead
657
+ # of in localtime, so convert those constants to local time.
658
+ def constant_sql_append(sql, constant)
659
+ if (c = CONSTANT_MAP[constant]) && !db.current_timestamp_utc
660
+ sql << c
661
+ else
662
+ super
663
+ end
664
+ end
665
+
666
+ # SQLite performs a TRUNCATE style DELETE if no filter is specified.
667
+ # Since we want to always return the count of records, add a condition
668
+ # that is always true and then delete.
669
+ def delete(&)
670
+ @opts[:where] ? super : where(1 => 1).delete(&)
671
+ end
672
+
673
+ # Return an array of strings specifying a query explanation for a SELECT of the
674
+ # current dataset. Currently, the options are ignored, but it accepts options
675
+ # to be compatible with other adapters.
676
+ def explain(_opts = nil)
677
+ # Load the PrettyTable class, needed for explain output
678
+ Sequel.extension(:_pretty_table) unless defined?(Sequel::PrettyTable)
679
+
680
+ ds = db.send(:metadata_dataset).clone(sql: "EXPLAIN #{select_sql}")
681
+ rows = ds.all
682
+ Sequel::PrettyTable.string(rows, ds.columns)
683
+ end
684
+
685
+ # HAVING requires GROUP BY on SQLite
686
+ def having(*cond)
687
+ raise(InvalidOperation, 'Can only specify a HAVING clause on a grouped dataset') if !@opts[:group] && db.sqlite_version < 33900
688
+
689
+ super
690
+ end
691
+
692
+ # Support insert select for associations, so that the model code can use
693
+ # returning instead of a separate query.
694
+ def insert_select(*values)
695
+ return unless supports_insert_select?
696
+
697
+ # Handle case where query does not return a row
698
+ server?(:default).with_sql_first(insert_select_sql(*values)) || false
699
+ end
700
+
701
+ # The SQL to use for an insert_select, adds a RETURNING clause to the insert
702
+ # unless the RETURNING clause is already present.
703
+ def insert_select_sql(*values)
704
+ ds = opts[:returning] ? self : returning
705
+ ds.insert_sql(*values)
706
+ end
707
+
708
+ # SQLite uses the nonstandard ` (backtick) for quoting identifiers.
709
+ def quoted_identifier_append(sql, c)
710
+ sql << '`' << c.to_s.gsub('`', '``') << '`'
711
+ end
712
+
713
+ # When a qualified column is selected on SQLite and the qualifier
714
+ # is a subselect, the column name used is the full qualified name
715
+ # (including the qualifier) instead of just the column name. To
716
+ # get correct column names, you must use an alias.
717
+ def select(*cols)
718
+ if ((f = @opts[:from]) && f.any? { |t|
719
+ t.is_a?(Dataset) || (t.is_a?(SQL::AliasedExpression) && t.expression.is_a?(Dataset))
720
+ }) || ((j = @opts[:join]) && j.any? { |t|
721
+ t.table.is_a?(Dataset)
722
+ })
723
+ super(*cols.map { |c| alias_qualified_column(c) })
724
+ else
725
+ super
726
+ end
727
+ end
728
+
729
+ # Handle uniqueness violations when inserting, by using a specified
730
+ # resolution algorithm. With no options, uses INSERT OR REPLACE. SQLite
731
+ # supports the following conflict resolution algoriths: ROLLBACK, ABORT,
732
+ # FAIL, IGNORE and REPLACE.
733
+ #
734
+ # On SQLite 3.24.0+, you can pass a hash to use an ON CONFLICT clause.
735
+ # With out :update option, uses ON CONFLICT DO NOTHING. Options:
736
+ #
737
+ # :conflict_where :: The index filter, when using a partial index to determine uniqueness.
738
+ # :target :: The column name or expression to handle uniqueness violations on.
739
+ # :update :: A hash of columns and values to set. Uses ON CONFLICT DO UPDATE.
740
+ # :update_where :: A WHERE condition to use for the update.
741
+ #
742
+ # Examples:
743
+ #
744
+ # DB[:table].insert_conflict.insert(a: 1, b: 2)
745
+ # # INSERT OR IGNORE INTO TABLE (a, b) VALUES (1, 2)
746
+ #
747
+ # DB[:table].insert_conflict(:replace).insert(a: 1, b: 2)
748
+ # # INSERT OR REPLACE INTO TABLE (a, b) VALUES (1, 2)
749
+ #
750
+ # DB[:table].insert_conflict({}).insert(a: 1, b: 2)
751
+ # # INSERT INTO TABLE (a, b) VALUES (1, 2)
752
+ # # ON CONFLICT DO NOTHING
753
+ #
754
+ # DB[:table].insert_conflict(target: :a).insert(a: 1, b: 2)
755
+ # # INSERT INTO TABLE (a, b) VALUES (1, 2)
756
+ # # ON CONFLICT (a) DO NOTHING
757
+ #
758
+ # DB[:table].insert_conflict(target: :a, conflict_where: {c: true}).insert(a: 1, b: 2)
759
+ # # INSERT INTO TABLE (a, b) VALUES (1, 2)
760
+ # # ON CONFLICT (a) WHERE (c IS TRUE) DO NOTHING
761
+ #
762
+ # DB[:table].insert_conflict(target: :a, update: {b: Sequel[:excluded][:b]}).insert(a: 1, b: 2)
763
+ # # INSERT INTO TABLE (a, b) VALUES (1, 2)
764
+ # # ON CONFLICT (a) DO UPDATE SET b = excluded.b
765
+ #
766
+ # DB[:table].insert_conflict(target: :a,
767
+ # update: {b: Sequel[:excluded][:b]}, update_where: {Sequel[:table][:status_id] => 1}).insert(a: 1, b: 2)
768
+ # # INSERT INTO TABLE (a, b) VALUES (1, 2)
769
+ # # ON CONFLICT (a) DO UPDATE SET b = excluded.b WHERE (table.status_id = 1)
770
+ def insert_conflict(opts = :ignore)
771
+ case opts
772
+ when Symbol, String
773
+ unless INSERT_CONFLICT_RESOLUTIONS.include?(opts.to_s.upcase)
774
+ # rubocop:disable Layout/LineLength
775
+ raise Error,
776
+ "Invalid symbol or string passed to Dataset#insert_conflict: #{opts.inspect}. The allowed values are: :rollback, :abort, :fail, :ignore, or :replace"
777
+ # rubocop:enable Layout/LineLength
778
+ end
779
+
780
+ clone(insert_conflict: opts)
781
+ when Hash
782
+ clone(insert_on_conflict: opts)
783
+ else
784
+ raise Error, "Invalid value passed to Dataset#insert_conflict: #{opts.inspect}, should use a symbol or a hash"
785
+ end
786
+ end
787
+
788
+ # Ignore uniqueness/exclusion violations when inserting, using INSERT OR IGNORE.
789
+ # Exists mostly for compatibility to MySQL's insert_ignore. Example:
790
+ #
791
+ # DB[:table].insert_ignore.insert(a: 1, b: 2)
792
+ # # INSERT OR IGNORE INTO TABLE (a, b) VALUES (1, 2)
793
+ def insert_ignore
794
+ insert_conflict(:ignore)
795
+ end
796
+
797
+ # Automatically add aliases to RETURNING values to work around SQLite bug.
798
+ def returning(*values)
799
+ return super if values.empty?
800
+ raise Error, "RETURNING is not supported on #{db.database_type}" unless supports_returning?(:insert)
801
+
802
+ clone(returning: _returning_values(values).freeze)
803
+ end
804
+
805
+ # SQLite 3.8.3+ supports common table expressions.
806
+ def supports_cte?(_type = :select)
807
+ db.sqlite_version >= 30803
808
+ end
809
+
810
+ # SQLite supports CTEs in subqueries if it supports CTEs.
811
+ def supports_cte_in_subqueries?
812
+ supports_cte?
813
+ end
814
+
815
+ # SQLite does not support table aliases with column aliases
816
+ def supports_derived_column_lists?
817
+ false
818
+ end
819
+
820
+ # SQLite does not support deleting from a joined dataset
821
+ def supports_deleting_joins?
822
+ false
823
+ end
824
+
825
+ # SQLite does not support INTERSECT ALL or EXCEPT ALL
826
+ def supports_intersect_except_all?
827
+ false
828
+ end
829
+
830
+ # SQLite does not support IS TRUE
831
+ def supports_is_true?
832
+ false
833
+ end
834
+
835
+ # SQLite 3.33.0 supports modifying joined datasets
836
+ def supports_modifying_joins?
837
+ db.sqlite_version >= 33300
838
+ end
839
+
840
+ # SQLite does not support multiple columns for the IN/NOT IN operators
841
+ def supports_multiple_column_in?
842
+ false
843
+ end
844
+
845
+ # SQLite 3.35.0 supports RETURNING on INSERT/UPDATE/DELETE.
846
+ def supports_returning?(_)
847
+ db.sqlite_version >= 33500
848
+ end
849
+
850
+ # SQLite supports timezones in literal timestamps, since it stores them
851
+ # as text. But using timezones in timestamps breaks SQLite datetime
852
+ # functions, so we allow the user to override the default per database.
853
+ def supports_timestamp_timezones?
854
+ db.use_timestamp_timezones?
855
+ end
856
+
857
+ # SQLite cannot use WHERE 't'.
858
+ def supports_where_true?
859
+ false
860
+ end
861
+
862
+ # SQLite 3.28+ supports the WINDOW clause.
863
+ def supports_window_clause?
864
+ db.sqlite_version >= 32800
865
+ end
866
+
867
+ # SQLite 3.25+ supports window functions. However, support is only enabled
868
+ # on SQLite 3.26.0+ because internal Sequel usage of window functions
869
+ # to implement eager loading of limited associations triggers
870
+ # an SQLite crash bug in versions 3.25.0-3.25.3.
871
+ def supports_window_functions?
872
+ db.sqlite_version >= 32600
873
+ end
874
+
875
+ # SQLite 3.28.0+ supports all window frame options that Sequel supports
876
+ def supports_window_function_frame_option?(option)
877
+ db.sqlite_version >= 32800 ? true : super
878
+ end
879
+
880
+ private
881
+
882
+ # Add aliases to symbols and identifiers to work around SQLite bug.
883
+ def _returning_values(values)
884
+ values.map do |v|
885
+ case v
886
+ when Symbol
887
+ _, c, a = split_symbol(v)
888
+ a ? v : Sequel.as(v, c)
889
+ when SQL::Identifier, SQL::QualifiedIdentifier
890
+ Sequel.as(v, unqualified_column_for(v))
891
+ else
892
+ v
893
+ end
894
+ end
895
+ end
896
+
897
+ # SQLite uses string literals instead of identifiers in AS clauses.
898
+ def as_sql_append(sql, aliaz, column_aliases = nil)
899
+ raise Error, 'sqlite does not support derived column lists' if column_aliases
900
+
901
+ aliaz = aliaz.value if aliaz.is_a?(SQL::Identifier)
902
+ sql << ' AS '
903
+ literal_append(sql, aliaz.to_s)
904
+ end
905
+
906
+ # If col is a qualified column, alias it to the same as the column name
907
+ def alias_qualified_column(col)
908
+ case col
909
+ when Symbol
910
+ t, c, a = split_symbol(col)
911
+ if t && !a
912
+ alias_qualified_column(SQL::QualifiedIdentifier.new(t, c))
913
+ else
914
+ col
915
+ end
916
+ when SQL::QualifiedIdentifier
917
+ SQL::AliasedExpression.new(col, col.column)
918
+ else
919
+ col
920
+ end
921
+ end
922
+
923
+ # Raise an InvalidOperation exception if insert is not allowed for this dataset.
924
+ def check_insert_allowed!
925
+ raise(InvalidOperation, 'Grouped datasets cannot be modified') if opts[:group]
926
+ raise(InvalidOperation, 'Joined datasets cannot be modified') if joined_dataset?
927
+ end
928
+ alias check_delete_allowed! check_insert_allowed!
929
+
930
+ # SQLite supports a maximum of 500 rows in a VALUES clause.
931
+ def default_import_slice
932
+ 500
933
+ end
934
+
935
+ # SQL fragment specifying a list of identifiers
936
+ def identifier_list(columns)
937
+ columns.map { |i| quote_identifier(i) }.join(', ')
938
+ end
939
+
940
+ # Add OR clauses to SQLite INSERT statements
941
+ def insert_conflict_sql(sql)
942
+ if resolution = @opts[:insert_conflict]
943
+ sql << ' OR ' << resolution.to_s.upcase
944
+ end
945
+ end
946
+
947
+ # Add ON CONFLICT clause if it should be used
948
+ def insert_on_conflict_sql(sql)
949
+ if opts = @opts[:insert_on_conflict]
950
+ sql << ' ON CONFLICT'
951
+
952
+ if target = opts[:constraint]
953
+ sql << ' ON CONSTRAINT '
954
+ identifier_append(sql, target)
955
+ elsif target = opts[:target]
956
+ sql << ' '
957
+ identifier_append(sql, Array(target))
958
+ if conflict_where = opts[:conflict_where]
959
+ sql << ' WHERE '
960
+ literal_append(sql, conflict_where)
961
+ end
962
+ end
963
+
964
+ if values = opts[:update]
965
+ sql << ' DO UPDATE SET '
966
+ update_sql_values_hash(sql, values)
967
+ if update_where = opts[:update_where]
968
+ sql << ' WHERE '
969
+ literal_append(sql, update_where)
970
+ end
971
+ else
972
+ sql << ' DO NOTHING'
973
+ end
974
+ end
975
+ end
976
+
977
+ # SQLite uses a preceding X for hex escaping strings
978
+ def literal_blob_append(sql, v)
979
+ sql << "X'" << v.unpack1('H*') << "'"
980
+ end
981
+
982
+ # Respect the database integer_booleans setting, using 0 or 'f'.
983
+ def literal_false
984
+ @db.integer_booleans ? '0' : "'f'"
985
+ end
986
+
987
+ # Respect the database integer_booleans setting, using 1 or 't'.
988
+ def literal_true
989
+ @db.integer_booleans ? '1' : "'t'"
990
+ end
991
+
992
+ # SQLite only supporting multiple rows in the VALUES clause
993
+ # starting in 3.7.11. On older versions, fallback to using a UNION.
994
+ def multi_insert_sql_strategy
995
+ db.sqlite_version >= 30711 ? :values : :union
996
+ end
997
+
998
+ # Emulate the char_length function with length
999
+ def native_function_name(emulated_function)
1000
+ if emulated_function == :char_length
1001
+ 'length'
1002
+ else
1003
+ super
1004
+ end
1005
+ end
1006
+
1007
+ # SQLite supports NULLS FIRST/LAST natively in 3.30+.
1008
+ def requires_emulating_nulls_first?
1009
+ db.sqlite_version < 33000
1010
+ end
1011
+
1012
+ # SQLite does not support FOR UPDATE, but silently ignore it
1013
+ # instead of raising an error for compatibility with other
1014
+ # databases.
1015
+ def select_lock_sql(sql)
1016
+ super unless @opts[:lock] == :update
1017
+ end
1018
+
1019
+ def select_only_offset_sql(sql)
1020
+ sql << ' LIMIT -1 OFFSET '
1021
+ literal_append(sql, @opts[:offset])
1022
+ end
1023
+
1024
+ # Support VALUES clause instead of the SELECT clause to return rows.
1025
+ def select_values_sql(sql)
1026
+ sql << 'VALUES '
1027
+ expression_list_append(sql, opts[:values])
1028
+ end
1029
+
1030
+ # SQLite does not support CTEs directly inside UNION/INTERSECT/EXCEPT.
1031
+ def supports_cte_in_compounds?
1032
+ false
1033
+ end
1034
+
1035
+ # SQLite 3.30 supports the FILTER clause for aggregate functions.
1036
+ def supports_filtered_aggregates?
1037
+ db.sqlite_version >= 33000
1038
+ end
1039
+
1040
+ # SQLite supports quoted function names.
1041
+ def supports_quoted_function_names?
1042
+ true
1043
+ end
1044
+
1045
+ # SQLite treats a DELETE with no WHERE clause as a TRUNCATE
1046
+ def _truncate_sql(table)
1047
+ "DELETE FROM #{table}"
1048
+ end
1049
+
1050
+ # Use FROM to specify additional tables in an update query
1051
+ def update_from_sql(sql)
1052
+ if (from = @opts[:from][1..]).empty?
1053
+ raise(Error, 'Need multiple FROM tables if updating/deleting a dataset with JOINs') if @opts[:join]
1054
+ else
1055
+ sql << ' FROM '
1056
+ source_list_append(sql, from)
1057
+ select_join_sql(sql)
1058
+ end
1059
+ end
1060
+
1061
+ # Only include the primary table in the main update clause
1062
+ def update_table_sql(sql)
1063
+ sql << ' '
1064
+ source_list_append(sql, @opts[:from][0..0])
1065
+ end
1066
+ end
1067
+ end
1068
+ end