bormashino-sequel-sqljs-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.
@@ -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