sequel 3.1.0 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. data/CHANGELOG +76 -0
  2. data/Rakefile +2 -2
  3. data/bin/sequel +9 -4
  4. data/doc/opening_databases.rdoc +279 -0
  5. data/doc/release_notes/3.2.0.txt +268 -0
  6. data/doc/virtual_rows.rdoc +42 -51
  7. data/lib/sequel/adapters/ado.rb +2 -5
  8. data/lib/sequel/adapters/db2.rb +5 -0
  9. data/lib/sequel/adapters/do.rb +3 -0
  10. data/lib/sequel/adapters/firebird.rb +6 -4
  11. data/lib/sequel/adapters/informix.rb +5 -3
  12. data/lib/sequel/adapters/jdbc.rb +10 -8
  13. data/lib/sequel/adapters/jdbc/h2.rb +17 -4
  14. data/lib/sequel/adapters/mysql.rb +6 -19
  15. data/lib/sequel/adapters/odbc.rb +14 -18
  16. data/lib/sequel/adapters/openbase.rb +8 -0
  17. data/lib/sequel/adapters/shared/mssql.rb +14 -8
  18. data/lib/sequel/adapters/shared/mysql.rb +53 -28
  19. data/lib/sequel/adapters/shared/oracle.rb +21 -12
  20. data/lib/sequel/adapters/shared/postgres.rb +46 -26
  21. data/lib/sequel/adapters/shared/progress.rb +10 -5
  22. data/lib/sequel/adapters/shared/sqlite.rb +28 -12
  23. data/lib/sequel/adapters/sqlite.rb +4 -3
  24. data/lib/sequel/adapters/utils/stored_procedures.rb +18 -9
  25. data/lib/sequel/connection_pool.rb +4 -3
  26. data/lib/sequel/database.rb +110 -10
  27. data/lib/sequel/database/schema_sql.rb +12 -3
  28. data/lib/sequel/dataset.rb +40 -3
  29. data/lib/sequel/dataset/convenience.rb +0 -11
  30. data/lib/sequel/dataset/graph.rb +25 -11
  31. data/lib/sequel/dataset/sql.rb +176 -68
  32. data/lib/sequel/extensions/migration.rb +37 -21
  33. data/lib/sequel/extensions/schema_dumper.rb +8 -61
  34. data/lib/sequel/model.rb +3 -3
  35. data/lib/sequel/model/associations.rb +9 -1
  36. data/lib/sequel/model/base.rb +8 -1
  37. data/lib/sequel/plugins/single_table_inheritance.rb +1 -1
  38. data/lib/sequel/sql.rb +125 -18
  39. data/lib/sequel/version.rb +1 -1
  40. data/spec/adapters/ado_spec.rb +1 -0
  41. data/spec/adapters/firebird_spec.rb +1 -0
  42. data/spec/adapters/informix_spec.rb +1 -0
  43. data/spec/adapters/mysql_spec.rb +23 -8
  44. data/spec/adapters/oracle_spec.rb +1 -0
  45. data/spec/adapters/postgres_spec.rb +52 -4
  46. data/spec/adapters/spec_helper.rb +2 -2
  47. data/spec/adapters/sqlite_spec.rb +2 -1
  48. data/spec/core/connection_pool_spec.rb +16 -0
  49. data/spec/core/database_spec.rb +174 -0
  50. data/spec/core/dataset_spec.rb +121 -26
  51. data/spec/core/expression_filters_spec.rb +156 -0
  52. data/spec/core/object_graph_spec.rb +20 -1
  53. data/spec/core/schema_spec.rb +5 -5
  54. data/spec/extensions/migration_spec.rb +140 -74
  55. data/spec/extensions/schema_dumper_spec.rb +3 -69
  56. data/spec/extensions/single_table_inheritance_spec.rb +6 -0
  57. data/spec/integration/dataset_test.rb +84 -2
  58. data/spec/integration/schema_test.rb +24 -5
  59. data/spec/integration/spec_helper.rb +8 -6
  60. data/spec/model/eager_loading_spec.rb +9 -0
  61. data/spec/model/record_spec.rb +35 -8
  62. metadata +8 -7
  63. data/lib/sequel/adapters/utils/date_format.rb +0 -21
  64. data/lib/sequel/adapters/utils/savepoint_transactions.rb +0 -80
  65. data/lib/sequel/adapters/utils/unsupported.rb +0 -50
@@ -1,10 +1,4 @@
1
- Sequel.require %w'unsupported savepoint_transactions', 'adapters/utils'
2
-
3
1
  module Sequel
4
- class Database
5
- # Keep default column_references_sql for add_foreign_key support
6
- alias default_column_references_sql column_references_sql
7
- end
8
2
  module MySQL
9
3
  class << self
10
4
  # Set the default options used for CREATE TABLE
@@ -14,8 +8,6 @@ module Sequel
14
8
  # Methods shared by Database instances that connect to MySQL,
15
9
  # currently supported by the native and JDBC adapters.
16
10
  module DatabaseMethods
17
- include Sequel::Database::SavepointTransactions
18
-
19
11
  AUTO_INCREMENT = 'AUTO_INCREMENT'.freeze
20
12
  CAST_TYPES = {String=>:CHAR, Integer=>:SIGNED, Time=>:DATETIME, DateTime=>:DATETIME, Numeric=>:DECIMAL, BigDecimal=>:DECIMAL, File=>:BINARY}
21
13
  PRIMARY = 'PRIMARY'.freeze
@@ -68,6 +60,11 @@ module Sequel
68
60
  metadata_dataset.with_sql('SHOW TABLES').server(opts[:server]).map{|r| m.call(r.values.first)}
69
61
  end
70
62
 
63
+ # MySQL supports savepoints
64
+ def supports_savepoints?
65
+ true
66
+ end
67
+
71
68
  # Changes the database in use by issuing a USE statement. I would be
72
69
  # very careful if I used this.
73
70
  def use(db_name)
@@ -87,7 +84,7 @@ module Sequel
87
84
  if related = op.delete(:table)
88
85
  sql = super(table, op)
89
86
  op[:table] = related
90
- [sql, "ALTER TABLE #{quote_schema_table(table)} ADD FOREIGN KEY (#{quote_identifier(op[:name])})#{default_column_references_sql(op)}"]
87
+ [sql, "ALTER TABLE #{quote_schema_table(table)} ADD FOREIGN KEY (#{quote_identifier(op[:name])})#{column_references_sql(op)}"]
91
88
  else
92
89
  super(table, op)
93
90
  end
@@ -98,8 +95,8 @@ module Sequel
98
95
  name = o == :rename_column ? op[:new_name] : op[:name]
99
96
  type = o == :set_column_type ? op[:type] : old_opts[:db_type]
100
97
  null = o == :set_column_null ? op[:null] : old_opts[:allow_null]
101
- default = o == :set_column_default ? op[:default] : (old_opts[:default].lit if old_opts[:default])
102
- "ALTER TABLE #{quote_schema_table(table)} CHANGE COLUMN #{quote_identifier(op[:name])} #{column_definition_sql(:name=>name, :type=>type, :null=>null, :default=>default)}"
98
+ default = o == :set_column_default ? op[:default] : old_opts[:ruby_default]
99
+ "ALTER TABLE #{quote_schema_table(table)} CHANGE COLUMN #{quote_identifier(op[:name])} #{column_definition_sql(op.merge(:name=>name, :type=>type, :null=>null, :default=>default))}"
103
100
  when :drop_index
104
101
  "#{drop_index_sql(table, op)} ON #{quote_schema_table(table)}"
105
102
  else
@@ -119,9 +116,9 @@ module Sequel
119
116
  super
120
117
  end
121
118
 
122
- # Handle MySQL specific syntax for column references
123
- def column_references_sql(column)
124
- "#{", FOREIGN KEY (#{quote_identifier(column[:name])})" unless column[:type] == :check}#{super(column)}"
119
+ # MySQL doesn't handle references as column constraints, it must use a separate table constraint
120
+ def column_references_column_constraint_sql(column)
121
+ "#{", FOREIGN KEY (#{quote_identifier(column[:name])})" unless column[:type] == :check}#{column_references_sql(column)}"
125
122
  end
126
123
 
127
124
  # Use MySQL specific syntax for engine type and character encoding
@@ -199,12 +196,11 @@ module Sequel
199
196
 
200
197
  # Dataset methods shared by datasets that use MySQL databases.
201
198
  module DatasetMethods
202
- include Dataset::UnsupportedIntersectExcept
203
-
204
199
  BOOL_TRUE = '1'.freeze
205
200
  BOOL_FALSE = '0'.freeze
206
201
  TIMESTAMP_FORMAT = "'%Y-%m-%d %H:%M:%S'".freeze
207
202
  COMMA_SEPARATOR = ', '.freeze
203
+ SELECT_CLAUSE_ORDER = %w'distinct columns from join where group having compounds order limit'.freeze
208
204
 
209
205
  # MySQL specific syntax for LIKE/REGEXP searches, as well as
210
206
  # string concatenation.
@@ -231,12 +227,6 @@ module Sequel
231
227
  sql
232
228
  end
233
229
 
234
- # MySQL doesn't support DISTINCT ON
235
- def distinct(*columns)
236
- raise(Error, "DISTINCT ON not supported by MySQL") unless columns.empty?
237
- super
238
- end
239
-
240
230
  # Adds full text filter
241
231
  def full_text_search(cols, terms, opts = {})
242
232
  filter(full_text_sql(cols, terms, opts))
@@ -313,15 +303,11 @@ module Sequel
313
303
  def on_duplicate_key_update(*args)
314
304
  clone(:on_duplicate_key_update => args)
315
305
  end
316
-
306
+
317
307
  # MySQL specific syntax for inserting multiple values at once.
318
308
  def multi_insert_sql(columns, values)
319
- if update_cols = opts[:on_duplicate_key_update]
320
- update_cols = columns if update_cols.empty?
321
- update_string = update_cols.map{|c| "#{quote_identifier(c)}=VALUES(#{quote_identifier(c)})"}.join(COMMA_SEPARATOR)
322
- end
323
309
  values = values.map {|r| literal(Array(r))}.join(COMMA_SEPARATOR)
324
- ["#{insert_sql_base}#{source_list(@opts[:from])} (#{identifier_list(columns)}) VALUES #{values}#{" ON DUPLICATE KEY UPDATE #{update_string}" if update_string}"]
310
+ ["#{insert_sql_base}#{source_list(@opts[:from])} (#{identifier_list(columns)}) VALUES #{values}#{insert_sql_suffix}"]
325
311
  end
326
312
 
327
313
  # MySQL uses the nonstandard ` (backtick) for quoting identifiers.
@@ -365,6 +351,16 @@ module Sequel
365
351
  end
366
352
  end
367
353
 
354
+ # does not support DISTINCT ON
355
+ def supports_distinct_on?
356
+ false
357
+ end
358
+
359
+ # MySQL does not support INTERSECT or EXCEPT
360
+ def supports_intersect_except?
361
+ false
362
+ end
363
+
368
364
  # MySQL supports ORDER and LIMIT clauses in UPDATE statements.
369
365
  def update_sql(values)
370
366
  sql = super
@@ -380,6 +376,11 @@ module Sequel
380
376
  "INSERT #{'IGNORE ' if opts[:insert_ignore]}INTO "
381
377
  end
382
378
 
379
+ # MySQL supports INSERT ... ON DUPLICATE KEY UPDATE
380
+ def insert_sql_suffix
381
+ on_duplicate_key_update_sql if opts[:on_duplicate_key_update]
382
+ end
383
+
383
384
  # MySQL doesn't use the SQL standard DEFAULT VALUES.
384
385
  def insert_default_values_sql
385
386
  "#{insert_sql_base}#{source_list(@opts[:from])} () VALUES ()"
@@ -404,6 +405,30 @@ module Sequel
404
405
  def literal_true
405
406
  BOOL_TRUE
406
407
  end
408
+
409
+ # MySQL specific syntax for ON DUPLICATE KEY UPDATE
410
+ def on_duplicate_key_update_sql
411
+ if update_cols = opts[:on_duplicate_key_update]
412
+ update_vals = nil
413
+
414
+ if update_cols.empty?
415
+ update_cols = columns
416
+ elsif update_cols.last.is_a?(Hash)
417
+ update_vals = update_cols.last
418
+ update_cols = update_cols[0..-2]
419
+ end
420
+
421
+ updating = update_cols.map{|c| "#{quote_identifier(c)}=VALUES(#{quote_identifier(c)})" }
422
+ updating += update_vals.map{|c,v| "#{quote_identifier(c)}=#{literal(v)}" } if update_vals
423
+
424
+ " ON DUPLICATE KEY UPDATE #{updating.join(COMMA_SEPARATOR)}"
425
+ end
426
+ end
427
+
428
+ # MySQL does not support the SQL WITH clause
429
+ def select_clause_order
430
+ SELECT_CLAUSE_ORDER
431
+ end
407
432
  end
408
433
  end
409
434
  end
@@ -1,5 +1,3 @@
1
- Sequel.require %w'date_format unsupported', 'adapters/utils'
2
-
3
1
  module Sequel
4
2
  module Oracle
5
3
  module DatabaseMethods
@@ -96,16 +94,7 @@ module Sequel
96
94
  end
97
95
 
98
96
  module DatasetMethods
99
- include Dataset::UnsupportedIntersectExceptAll
100
- include Dataset::SQLStandardDateFormat
101
-
102
- SELECT_CLAUSE_ORDER = %w'distinct columns from join where group having compounds order limit'.freeze
103
-
104
- # Oracle doesn't support DISTINCT ON
105
- def distinct(*columns)
106
- raise(Error, "DISTINCT ON not supported by Oracle") unless columns.empty?
107
- super
108
- end
97
+ SELECT_CLAUSE_ORDER = %w'with distinct columns from join where group having compounds order limit'.freeze
109
98
 
110
99
  # Oracle uses MINUS instead of EXCEPT, and doesn't support EXCEPT ALL
111
100
  def except(dataset, all = false)
@@ -117,6 +106,26 @@ module Sequel
117
106
  db[:dual].where(exists).get(1) == nil
118
107
  end
119
108
 
109
+ # Oracle requires SQL standard datetimes
110
+ def requires_sql_standard_datetimes?
111
+ true
112
+ end
113
+
114
+ # Oracle does not support DISTINCT ON
115
+ def supports_distinct_on?
116
+ false
117
+ end
118
+
119
+ # Oracle does not support INTERSECT ALL or EXCEPT ALL
120
+ def supports_intersect_except_all?
121
+ false
122
+ end
123
+
124
+ # Oracle supports window functions
125
+ def supports_window_functions?
126
+ true
127
+ end
128
+
120
129
  private
121
130
 
122
131
  # Oracle doesn't support the use of AS when aliasing a dataset. It doesn't require
@@ -1,5 +1,3 @@
1
- Sequel.require 'adapters/utils/savepoint_transactions'
2
-
3
1
  module Sequel
4
2
  # Top level module for holding all PostgreSQL-related modules and classes
5
3
  # for Sequel. There are a few module level accessors that are added via
@@ -165,13 +163,8 @@ module Sequel
165
163
 
166
164
  # Methods shared by Database instances that connect to PostgreSQL.
167
165
  module DatabaseMethods
168
- include Sequel::Database::SavepointTransactions
169
-
170
166
  PREPARED_ARG_PLACEHOLDER = LiteralString.new('$').freeze
171
167
  RE_CURRVAL_ERROR = /currval of sequence "(.*)" is not yet defined in this session|relation "(.*)" does not exist/.freeze
172
- SQL_BEGIN = 'BEGIN'.freeze
173
- SQL_COMMIT = 'COMMIT'.freeze
174
- SQL_ROLLBACK = 'ROLLBACK'.freeze
175
168
  SYSTEM_TABLE_REGEXP = /^pg|sql/.freeze
176
169
 
177
170
  # Creates the function in the database. Arguments:
@@ -277,16 +270,20 @@ module Sequel
277
270
  m = output_identifier_meth
278
271
  im = input_identifier_meth
279
272
  schema, table = schema_and_table(table)
273
+ range = 0...32
274
+ attnums = server_version >= 80100 ? SQL::Function.new(:ANY, :ind__indkey) : range.map{|x| SQL::Subscript.new(:ind__indkey, [x])}
280
275
  ds = metadata_dataset.
281
276
  from(:pg_class___tab).
282
277
  join(:pg_index___ind, :indrelid=>:oid, im.call(table)=>:relname).
283
278
  join(:pg_class___indc, :oid=>:indexrelid).
284
- join(:pg_attribute___att, :attrelid=>:tab__oid, :attnum=>SQL::Function.new(:ANY, :ind__indkey)).
285
- filter(:indc__relkind=>'i', :ind__indisprimary=>false, :indexprs=>nil, :indpred=>nil, :indisvalid=>true, :indisready=>true, :indcheckxmin=>false).
286
- order(:indc__relname, (0...32).map{|x| [SQL::Subscript.new(:ind__indkey, [x]), x]}.case(32, :att__attnum)).
279
+ join(:pg_attribute___att, :attrelid=>:tab__oid, :attnum=>attnums).
280
+ filter(:indc__relkind=>'i', :ind__indisprimary=>false, :indexprs=>nil, :indpred=>nil).
281
+ order(:indc__relname, range.map{|x| [SQL::Subscript.new(:ind__indkey, [x]), x]}.case(32, :att__attnum)).
287
282
  select(:indc__relname___name, :ind__indisunique___unique, :att__attname___column)
288
283
 
289
- ds.join!(:pg_namespace___nsp, :oid=>:tab__relnamespace, :nspname=>schema) if schema
284
+ ds.join!(:pg_namespace___nsp, :oid=>:tab__relnamespace, :nspname=>schema.to_s) if schema
285
+ ds.filter!(:indisvalid=>true) if server_version >= 80200
286
+ ds.filter!(:indisready=>true, :indcheckxmin=>false) if server_version >= 80300
290
287
 
291
288
  indexes = {}
292
289
  ds.each do |r|
@@ -346,12 +343,17 @@ module Sequel
346
343
  (conn.server_version rescue nil) if conn.respond_to?(:server_version)
347
344
  end
348
345
  unless @server_version
349
- m = /PostgreSQL (\d+)\.(\d+)\.(\d+)/.match(get(SQL::Function.new(:version)))
346
+ m = /PostgreSQL (\d+)\.(\d+)(?:(?:rc\d+)|\.(\d+))?/.match(fetch('SELECT version()').single_value)
350
347
  @server_version = (m[1].to_i * 10000) + (m[2].to_i * 100) + m[3].to_i
351
348
  end
352
349
  @server_version
353
350
  end
354
351
 
352
+ # PostgreSQL supports savepoints
353
+ def supports_savepoints?
354
+ true
355
+ end
356
+
355
357
  # Whether the given table exists in the database
356
358
  #
357
359
  # Options:
@@ -451,7 +453,11 @@ module Sequel
451
453
  def index_definition_sql(table_name, index)
452
454
  cols = index[:columns]
453
455
  index_name = index[:name] || default_index_name(table_name, cols)
454
- expr = literal(Array(cols))
456
+ expr = if o = index[:opclass]
457
+ "(#{Array(cols).map{|c| "#{literal(c)} #{o}"}.join(', ')})"
458
+ else
459
+ literal(Array(cols))
460
+ end
455
461
  unique = "UNIQUE " if index[:unique]
456
462
  index_type = index[:type]
457
463
  filter = index[:where] || index[:filter]
@@ -582,9 +588,11 @@ module Sequel
582
588
  ROW_EXCLUSIVE = 'ROW EXCLUSIVE'.freeze
583
589
  ROW_SHARE = 'ROW SHARE'.freeze
584
590
  SELECT_CLAUSE_ORDER = %w'distinct columns from join where group having compounds order limit lock'.freeze
591
+ SELECT_CLAUSE_ORDER_84 = %w'with distinct columns from join where group having window compounds order limit lock'.freeze
585
592
  SHARE = 'SHARE'.freeze
586
593
  SHARE_ROW_EXCLUSIVE = 'SHARE ROW EXCLUSIVE'.freeze
587
594
  SHARE_UPDATE_EXCLUSIVE = 'SHARE UPDATE EXCLUSIVE'.freeze
595
+ SQL_WITH_RECURSIVE = "WITH RECURSIVE ".freeze
588
596
 
589
597
  # Shared methods for prepared statements when used with PostgreSQL databases.
590
598
  module PreparedStatementMethods
@@ -611,12 +619,8 @@ module Sequel
611
619
  end
612
620
 
613
621
  # Return the results of an ANALYZE query as a string
614
- def analyze(opts = nil)
615
- analysis = []
616
- fetch_rows(EXPLAIN_ANALYZE + select_sql(opts)) do |r|
617
- analysis << r[QUERY_PLAN]
618
- end
619
- analysis.join("\r\n")
622
+ def analyze
623
+ explain(:analyze=>true)
620
624
  end
621
625
 
622
626
  # Disable the use of INSERT RETURNING, even if the server supports it
@@ -625,12 +629,8 @@ module Sequel
625
629
  end
626
630
 
627
631
  # Return the results of an EXPLAIN query as a string
628
- def explain(opts = nil)
629
- analysis = []
630
- fetch_rows(EXPLAIN + select_sql(opts)) do |r|
631
- analysis << r[QUERY_PLAN]
632
- end
633
- analysis.join("\r\n")
632
+ def explain(opts={})
633
+ with_sql((opts[:analyze] ? EXPLAIN_ANALYZE : EXPLAIN) + select_sql).map(QUERY_PLAN).join("\r\n")
634
634
  end
635
635
 
636
636
  # Return a cloned dataset with a :share lock type.
@@ -693,6 +693,16 @@ module Sequel
693
693
  ["#{insert_sql_base}#{source_list(@opts[:from])} (#{identifier_list(columns)}) VALUES #{values}"]
694
694
  end
695
695
 
696
+ # PostgreSQL 8.4+ supports window functions
697
+ def supports_window_functions?
698
+ server_version >= 80400
699
+ end
700
+
701
+ # Return a clone of the dataset with an addition named window that can be referenced in window functions.
702
+ def window(name, opts)
703
+ clone(:window=>(@opts[:windows]||[]) + [[name, SQL::Window.new(opts)]])
704
+ end
705
+
696
706
  private
697
707
 
698
708
  # Use the RETURNING clause to return the primary key of the inserted record, if it exists
@@ -733,7 +743,12 @@ module Sequel
733
743
 
734
744
  # The order of clauses in the SELECT SQL statement
735
745
  def select_clause_order
736
- SELECT_CLAUSE_ORDER
746
+ server_version >= 80400 ? SELECT_CLAUSE_ORDER_84 : SELECT_CLAUSE_ORDER
747
+ end
748
+
749
+ # SQL fragment for named window specifications
750
+ def select_window_sql(sql)
751
+ sql << " WINDOW #{@opts[:window].map{|name, window| "#{literal(name)} AS #{literal(window)}"}.join(', ')}" if @opts[:window]
737
752
  end
738
753
 
739
754
  # Support lock mode, allowing FOR SHARE and FOR UPDATE queries.
@@ -746,6 +761,11 @@ module Sequel
746
761
  end
747
762
  end
748
763
 
764
+ # Use WITH RECURSIVE instead of WITH if any of the CTEs is recursive
765
+ def select_with_sql_base
766
+ opts[:with].any?{|w| w[:recursive]} ? SQL_WITH_RECURSIVE : super
767
+ end
768
+
749
769
  # The version of the database server
750
770
  def server_version
751
771
  db.server_version(@opts[:server])
@@ -1,5 +1,3 @@
1
- Sequel.require %w'date_format unsupported', 'adapters/utils'
2
-
3
1
  module Sequel
4
2
  module Progress
5
3
  module DatabaseMethods
@@ -17,11 +15,18 @@ module Sequel
17
15
  end
18
16
 
19
17
  module DatasetMethods
20
- include Dataset::UnsupportedIntersectExcept
21
- include Dataset::SQLStandardDateFormat
22
-
23
18
  SELECT_CLAUSE_ORDER = %w'limit distinct columns from join where group order having compounds'.freeze
24
19
 
20
+ # Progress requires SQL standard datetimes
21
+ def requires_sql_standard_datetimes?
22
+ true
23
+ end
24
+
25
+ # Progress does not support INTERSECT or EXCEPT
26
+ def supports_intersect_except?
27
+ false
28
+ end
29
+
25
30
  private
26
31
 
27
32
  def select_clause_order
@@ -1,10 +1,6 @@
1
- Sequel.require %w'savepoint_transactions unsupported', 'adapters/utils'
2
-
3
1
  module Sequel
4
2
  module SQLite
5
3
  module DatabaseMethods
6
- include Sequel::Database::SavepointTransactions
7
-
8
4
  AUTO_VACUUM = [:none, :full, :incremental].freeze
9
5
  PRIMARY_KEY_INDEX_RE = /\Asqlite_autoindex_/.freeze
10
6
  SYNCHRONOUS = [:off, :normal, :full].freeze
@@ -67,6 +63,11 @@ module Sequel
67
63
  execute_ddl("PRAGMA #{name} = #{value}")
68
64
  end
69
65
 
66
+ # SQLite supports savepoints
67
+ def supports_savepoints?
68
+ true
69
+ end
70
+
70
71
  # A symbol signifying the value of the synchronous PRAGMA.
71
72
  def synchronous
72
73
  SYNCHRONOUS[pragma_get(:synchronous).to_i]
@@ -233,9 +234,8 @@ module Sequel
233
234
 
234
235
  # Instance methods for datasets that connect to an SQLite database
235
236
  module DatasetMethods
236
- include Dataset::UnsupportedIntersectExceptAll
237
- include Dataset::UnsupportedIsTrue
238
-
237
+ SELECT_CLAUSE_ORDER = %w'distinct columns from join where group having compounds order limit'.freeze
238
+
239
239
  # SQLite does not support pattern matching via regular expressions.
240
240
  # SQLite is case insensitive (depending on pragma), so use LIKE for
241
241
  # ILIKE.
@@ -277,19 +277,35 @@ module Sequel
277
277
  "`#{c}`"
278
278
  end
279
279
 
280
- private
280
+ # SQLite does not support INTERSECT ALL or EXCEPT ALL
281
+ def supports_intersect_except_all?
282
+ false
283
+ end
281
284
 
282
- def literal_blob(v)
283
- blob = ''
284
- v.each_byte{|x| blob << sprintf('%02x', x)}
285
- "X'#{blob}'"
285
+ # SQLite does not support IS TRUE
286
+ def supports_is_true?
287
+ false
286
288
  end
289
+
290
+ private
287
291
 
288
292
  # SQLite uses string literals instead of identifiers in AS clauses.
289
293
  def as_sql(expression, aliaz)
290
294
  aliaz = aliaz.value if aliaz.is_a?(SQL::Identifier)
291
295
  "#{expression} AS #{literal(aliaz.to_s)}"
292
296
  end
297
+
298
+ # SQLite uses a preceding X for hex escaping strings
299
+ def literal_blob(v)
300
+ blob = ''
301
+ v.each_byte{|x| blob << sprintf('%02x', x)}
302
+ "X'#{blob}'"
303
+ end
304
+
305
+ # SQLite does not support the SQL WITH clause
306
+ def select_clause_order
307
+ SELECT_CLAUSE_ORDER
308
+ end
293
309
  end
294
310
  end
295
311
  end