sequel 3.3.0 → 3.4.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 (58) hide show
  1. data/CHANGELOG +62 -0
  2. data/README.rdoc +4 -4
  3. data/doc/release_notes/3.3.0.txt +1 -1
  4. data/doc/release_notes/3.4.0.txt +325 -0
  5. data/doc/sharding.rdoc +3 -3
  6. data/lib/sequel/adapters/amalgalite.rb +1 -1
  7. data/lib/sequel/adapters/firebird.rb +4 -9
  8. data/lib/sequel/adapters/jdbc.rb +21 -7
  9. data/lib/sequel/adapters/mysql.rb +2 -1
  10. data/lib/sequel/adapters/odbc.rb +7 -21
  11. data/lib/sequel/adapters/oracle.rb +1 -1
  12. data/lib/sequel/adapters/postgres.rb +6 -1
  13. data/lib/sequel/adapters/shared/mssql.rb +11 -0
  14. data/lib/sequel/adapters/shared/mysql.rb +8 -12
  15. data/lib/sequel/adapters/shared/oracle.rb +13 -0
  16. data/lib/sequel/adapters/shared/postgres.rb +5 -10
  17. data/lib/sequel/adapters/shared/sqlite.rb +21 -1
  18. data/lib/sequel/adapters/sqlite.rb +2 -2
  19. data/lib/sequel/core.rb +147 -11
  20. data/lib/sequel/database.rb +21 -9
  21. data/lib/sequel/dataset.rb +31 -6
  22. data/lib/sequel/dataset/convenience.rb +1 -1
  23. data/lib/sequel/dataset/sql.rb +76 -18
  24. data/lib/sequel/extensions/inflector.rb +2 -51
  25. data/lib/sequel/model.rb +16 -10
  26. data/lib/sequel/model/associations.rb +4 -1
  27. data/lib/sequel/model/base.rb +13 -6
  28. data/lib/sequel/model/default_inflections.rb +46 -0
  29. data/lib/sequel/model/inflections.rb +1 -51
  30. data/lib/sequel/plugins/boolean_readers.rb +52 -0
  31. data/lib/sequel/plugins/instance_hooks.rb +57 -0
  32. data/lib/sequel/plugins/lazy_attributes.rb +13 -1
  33. data/lib/sequel/plugins/nested_attributes.rb +171 -0
  34. data/lib/sequel/plugins/serialization.rb +35 -16
  35. data/lib/sequel/plugins/timestamps.rb +87 -0
  36. data/lib/sequel/plugins/validation_helpers.rb +8 -1
  37. data/lib/sequel/sql.rb +33 -0
  38. data/lib/sequel/version.rb +1 -1
  39. data/spec/adapters/sqlite_spec.rb +11 -6
  40. data/spec/core/core_sql_spec.rb +29 -0
  41. data/spec/core/database_spec.rb +16 -7
  42. data/spec/core/dataset_spec.rb +264 -20
  43. data/spec/extensions/boolean_readers_spec.rb +86 -0
  44. data/spec/extensions/inflector_spec.rb +67 -4
  45. data/spec/extensions/instance_hooks_spec.rb +133 -0
  46. data/spec/extensions/lazy_attributes_spec.rb +45 -5
  47. data/spec/extensions/nested_attributes_spec.rb +272 -0
  48. data/spec/extensions/serialization_spec.rb +64 -1
  49. data/spec/extensions/timestamps_spec.rb +150 -0
  50. data/spec/extensions/validation_helpers_spec.rb +18 -0
  51. data/spec/integration/dataset_test.rb +79 -2
  52. data/spec/integration/schema_test.rb +17 -0
  53. data/spec/integration/timezone_test.rb +55 -0
  54. data/spec/model/associations_spec.rb +19 -7
  55. data/spec/model/model_spec.rb +29 -0
  56. data/spec/model/record_spec.rb +36 -0
  57. data/spec/spec_config.rb +1 -1
  58. metadata +14 -2
@@ -29,7 +29,7 @@ module Sequel
29
29
 
30
30
  # Return datetime types as instances of Sequel.datetime_class
31
31
  def datetime(s)
32
- Sequel.string_to_datetime(s)
32
+ Sequel.database_to_application_timestamp(s)
33
33
  end
34
34
 
35
35
  # Don't raise an error if the value is a string and the declared
@@ -202,7 +202,6 @@ module Sequel
202
202
  BOOL_FALSE = '0'.freeze
203
203
  NULL = LiteralString.new('NULL').freeze
204
204
  COMMA_SEPARATOR = ', '.freeze
205
- FIREBIRD_TIMESTAMP_FORMAT = "TIMESTAMP '%Y-%m-%d %H:%M:%S".freeze
206
205
  SELECT_CLAUSE_ORDER = %w'with distinct limit columns from join where group having compounds order'.freeze
207
206
 
208
207
  # Yield all rows returned by executing the given SQL and converting
@@ -248,6 +247,10 @@ module Sequel
248
247
  def insert_select(*values)
249
248
  naked.clone(default_server_opts(:sql=>insert_returning_sql(nil, *values))).single_record
250
249
  end
250
+
251
+ def requires_sql_standard_datetimes?
252
+ true
253
+ end
251
254
 
252
255
  # The order of clauses in the SELECT SQL statement
253
256
  def select_clause_order
@@ -273,18 +276,10 @@ module Sequel
273
276
  end
274
277
  end
275
278
 
276
- def literal_datetime(v)
277
- "#{v.strftime(FIREBIRD_TIMESTAMP_FORMAT)}.#{sprintf("%04d",(v.sec_fraction * 864000000))}'"
278
- end
279
-
280
279
  def literal_false
281
280
  BOOL_FALSE
282
281
  end
283
282
 
284
- def literal_time(v)
285
- "#{v.strftime(FIREBIRD_TIMESTAMP_FORMAT)}.#{sprintf("%04d",v.usec / 100)}'"
286
- end
287
-
288
283
  def literal_true
289
284
  BOOL_TRUE
290
285
  end
@@ -197,13 +197,18 @@ module Sequel
197
197
  # Values are subhashes with two keys, :columns and :unique. The value of :columns
198
198
  # is an array of symbols of column names. The value of :unique is true or false
199
199
  # depending on if the index is unique.
200
- def indexes(table)
201
- indexes = {}
200
+ def indexes(table, opts={})
202
201
  m = output_identifier_meth
203
- metadata(:getIndexInfo, nil, nil, input_identifier_meth.call(table), false, true) do |r|
202
+ im = input_identifier_meth
203
+ schema, table = schema_and_table(table)
204
+ schema ||= opts[:schema]
205
+ schema = im.call(schema) if schema
206
+ table = im.call(table)
207
+ indexes = {}
208
+ metadata(:getIndexInfo, nil, schema, table, false, true) do |r|
204
209
  next unless name = r[:column_name]
205
210
  next if respond_to?(:primary_key_index_re, true) and r[:index_name] =~ primary_key_index_re
206
- i = indexes[m.call(r[:index_name])] ||= {:columns=>[], :unique=>!r[:non_unique]}
211
+ i = indexes[m.call(r[:index_name])] ||= {:columns=>[], :unique=>[false, 0].include?(r[:non_unique])}
207
212
  i[:columns] << m.call(name)
208
213
  end
209
214
  indexes
@@ -324,6 +329,8 @@ module Sequel
324
329
  case arg
325
330
  when Integer
326
331
  cps.setInt(i, arg)
332
+ when Sequel::SQL::Blob
333
+ cps.setBytes(i, arg.to_java_bytes)
327
334
  when String
328
335
  cps.setString(i, arg)
329
336
  when Date, Java::JavaSql::Date
@@ -332,8 +339,12 @@ module Sequel
332
339
  cps.setTimestamp(i, arg)
333
340
  when Float
334
341
  cps.setDouble(i, arg)
342
+ when TrueClass, FalseClass
343
+ cps.setBoolean(i, arg)
335
344
  when nil
336
345
  cps.setNull(i, JavaSQL::Types::NULL)
346
+ else
347
+ cps.setObject(i, arg)
337
348
  end
338
349
  end
339
350
 
@@ -348,12 +359,13 @@ module Sequel
348
359
  conn
349
360
  end
350
361
 
351
- # All tables in this database
362
+ # Parse the table schema for the given table.
352
363
  def schema_parse_table(table, opts={})
353
364
  m = output_identifier_meth
354
365
  im = input_identifier_meth
355
366
  ds = dataset
356
367
  schema, table = schema_and_table(table)
368
+ schema ||= opts[:schema]
357
369
  schema = im.call(schema) if schema
358
370
  table = im.call(table)
359
371
  pks, ts = [], []
@@ -361,7 +373,7 @@ module Sequel
361
373
  pks << h[:column_name]
362
374
  end
363
375
  metadata(:getColumns, nil, schema, table, nil) do |h|
364
- ts << [m.call(h[:column_name]), {:type=>schema_column_type(h[:type_name]), :db_type=>h[:type_name], :default=>(h[:column_def] == '' ? nil : h[:column_def]), :allow_null=>(h[:nullable] != 0), :primary_key=>pks.include?(h[:column_name])}]
376
+ ts << [m.call(h[:column_name]), {:type=>schema_column_type(h[:type_name]), :db_type=>h[:type_name], :default=>(h[:column_def] == '' ? nil : h[:column_def]), :allow_null=>(h[:nullable] != 0), :primary_key=>pks.include?(h[:column_name]), :column_size=>h[:column_size]}]
365
377
  end
366
378
  ts
367
379
  end
@@ -459,7 +471,7 @@ module Sequel
459
471
  def convert_type(v)
460
472
  case v
461
473
  when Java::JavaSQL::Timestamp, Java::JavaSQL::Time
462
- Sequel.string_to_datetime(v.to_string)
474
+ Sequel.database_to_application_timestamp(v.to_string)
463
475
  when Java::JavaSQL::Date
464
476
  Sequel.string_to_date(v.to_string)
465
477
  when Java::JavaIo::BufferedReader
@@ -468,6 +480,8 @@ module Sequel
468
480
  lines.join("\n")
469
481
  when Java::JavaMath::BigDecimal
470
482
  BigDecimal.new(v.to_string)
483
+ when Java::byte[]
484
+ Sequel::SQL::Blob.new(String.from_java_bytes(v))
471
485
  else
472
486
  v
473
487
  end
@@ -1,4 +1,5 @@
1
1
  require 'mysql'
2
+ raise(LoadError, "require 'mysql' did not define Mysql::CLIENT_MULTI_RESULTS!\n You are probably using the pure ruby mysql.rb driver,\n which Sequel does not support. You need to install\n the C based adapter, and make sure that the mysql.so\n file is loaded instead of the mysql.rb file.\n") unless defined?(Mysql::CLIENT_MULTI_RESULTS)
2
3
  Sequel.require %w'shared/mysql utils/stored_procedures', 'adapters'
3
4
 
4
5
  module Sequel
@@ -28,7 +29,7 @@ module Sequel
28
29
  [2, 3, 8, 9, 13, 247, 248] => lambda{|v| v.to_i}, # integer
29
30
  [4, 5] => lambda{|v| v.to_f}, # float
30
31
  [10, 14] => lambda{|v| convert_date_time(:string_to_date, v)}, # date
31
- [7, 12] => lambda{|v| convert_date_time(:string_to_datetime, v)}, # datetime
32
+ [7, 12] => lambda{|v| convert_date_time(:database_to_application_timestamp, v)}, # datetime
32
33
  [11] => lambda{|v| convert_date_time(:string_to_time, v)}, # time
33
34
  [249, 250, 251, 252] => lambda{|v| Sequel::SQL::Blob.new(v)} # blob
34
35
  }
@@ -89,9 +89,8 @@ module Sequel
89
89
  class Dataset < Sequel::Dataset
90
90
  BOOL_TRUE = '1'.freeze
91
91
  BOOL_FALSE = '0'.freeze
92
- ODBC_TIMESTAMP_FORMAT = "{ts '%Y-%m-%d %H:%M:%S'}".freeze
93
- ODBC_TIMESTAMP_FORMAT_USEC = "{ts '%Y-%m-%d %H:%M:%S.%%i'}".freeze
94
92
  ODBC_DATE_FORMAT = "{d '%Y-%m-%d'}".freeze
93
+ TIMESTAMP_FORMAT="{ts '%Y-%m-%d %H:%M:%S%N'}".freeze
95
94
 
96
95
  def fetch_rows(sql, &block)
97
96
  execute(sql) do |s|
@@ -110,14 +109,6 @@ module Sequel
110
109
  end
111
110
 
112
111
  private
113
-
114
- def _literal_datetime(v, usec)
115
- if usec >= 1000
116
- v.strftime(ODBC_TIMESTAMP_FORMAT_USEC) % (usec.to_f/1000).round
117
- else
118
- v.strftime(ODBC_TIMESTAMP_FORMAT)
119
- end
120
- end
121
112
 
122
113
  def convert_odbc_value(v)
123
114
  # When fetching a result set, the Ruby ODBC driver converts all ODBC
@@ -128,25 +119,24 @@ module Sequel
128
119
  # ODBCColumn#mapSqlTypeToGenericType and Column#klass.
129
120
  case v
130
121
  when ::ODBC::TimeStamp
131
- DateTime.new(v.year, v.month, v.day, v.hour, v.minute, v.second)
122
+ Sequel.database_to_application_timestamp([v.year, v.month, v.day, v.hour, v.minute, v.second])
132
123
  when ::ODBC::Time
133
- now = DateTime.now
134
- Time.gm(now.year, now.month, now.day, v.hour, v.minute, v.second)
124
+ Sequel.database_to_application_timestamp([now.year, now.month, now.day, v.hour, v.minute, v.second])
135
125
  when ::ODBC::Date
136
126
  Date.new(v.year, v.month, v.day)
137
127
  else
138
128
  v
139
129
  end
140
130
  end
131
+
132
+ def default_timestamp_format
133
+ TIMESTAMP_FORMAT
134
+ end
141
135
 
142
136
  def literal_date(v)
143
137
  v.strftime(ODBC_DATE_FORMAT)
144
138
  end
145
139
 
146
- def literal_datetime(v)
147
- _literal_datetime(v, v.sec_fraction * 86400000000)
148
- end
149
-
150
140
  def literal_false
151
141
  BOOL_FALSE
152
142
  end
@@ -154,10 +144,6 @@ module Sequel
154
144
  def literal_true
155
145
  BOOL_TRUE
156
146
  end
157
-
158
- def literal_time(v)
159
- _literal_datetime(v, v.usec)
160
- end
161
147
  end
162
148
  end
163
149
  end
@@ -129,7 +129,7 @@ module Sequel
129
129
  def literal_other(v)
130
130
  case v
131
131
  when OraDate
132
- literal_time(Time.local(*v.to_a))
132
+ literal(Sequel.database_to_application_timestamp(v))
133
133
  else
134
134
  super
135
135
  end
@@ -65,6 +65,11 @@ rescue LoadError => e
65
65
  unless defined?(CONNECTION_OK)
66
66
  CONNECTION_OK = -1
67
67
  end
68
+ unless method_defined?(:status)
69
+ def status
70
+ CONNECTION_OK
71
+ end
72
+ end
68
73
  end
69
74
  class PGresult
70
75
  alias_method :nfields, :num_fields unless method_defined?(:nfields)
@@ -94,7 +99,7 @@ module Sequel
94
99
  [790, 1700] => lambda{|s| BigDecimal.new(s)}, # numeric
95
100
  [1082] => lambda{|s| @use_iso_date_format ? Date.new(*s.split("-").map{|x| x.to_i}) : Sequel.string_to_date(s)}, # date
96
101
  [1083, 1266] => lambda{|s| Sequel.string_to_time(s)}, # time
97
- [1114, 1184] => lambda{|s| Sequel.string_to_datetime(s)}, # timestamp
102
+ [1114, 1184] => lambda{|s| Sequel.database_to_application_timestamp(s)}, # timestamp
98
103
  }
99
104
  PG_TYPE_PROCS.each do |k,v|
100
105
  k.each{|n| PG_TYPES[n] = v}
@@ -148,6 +148,7 @@ module Sequel
148
148
  SELECT_CLAUSE_ORDER = %w'with limit distinct columns from table_options join where group order having compounds'.freeze
149
149
  TIMESTAMP_FORMAT = "'%Y-%m-%d %H:%M:%S'".freeze
150
150
  WILDCARD = LiteralString.new('*').freeze
151
+ CONSTANT_MAP = {:CURRENT_DATE=>'CAST(CURRENT_TIMESTAMP AS DATE)'.freeze, :CURRENT_TIME=>'CAST(CURRENT_TIMESTAMP AS TIME)'.freeze}
151
152
 
152
153
  # MSSQL uses + for string concatenation
153
154
  def complex_expression_sql(op, args)
@@ -159,6 +160,11 @@ module Sequel
159
160
  end
160
161
  end
161
162
 
163
+ # MSSQL doesn't support the SQL standard CURRENT_DATE or CURRENT_TIME
164
+ def constant_sql(constant)
165
+ CONSTANT_MAP[constant] || super
166
+ end
167
+
162
168
  # When returning all rows, if an offset is used, delete the row_number column
163
169
  # before yielding the row.
164
170
  def each(&block)
@@ -222,6 +228,11 @@ module Sequel
222
228
  false
223
229
  end
224
230
 
231
+ # MSSQL supports timezones in literal timestamps
232
+ def supports_timestamp_timezones?
233
+ true
234
+ end
235
+
225
236
  # MSSQL 2005+ supports window functions
226
237
  def supports_window_functions?
227
238
  true
@@ -198,7 +198,6 @@ module Sequel
198
198
  module DatasetMethods
199
199
  BOOL_TRUE = '1'.freeze
200
200
  BOOL_FALSE = '0'.freeze
201
- TIMESTAMP_FORMAT = "'%Y-%m-%d %H:%M:%S'".freeze
202
201
  COMMA_SEPARATOR = ', '.freeze
203
202
  SELECT_CLAUSE_ORDER = %w'distinct columns from join where group having compounds order limit'.freeze
204
203
 
@@ -360,6 +359,13 @@ module Sequel
360
359
  def supports_intersect_except?
361
360
  false
362
361
  end
362
+
363
+ # MySQL does support fractional timestamps in literal timestamps, but it
364
+ # ignores them. Also, using them seems to cause problems on 1.9. Since
365
+ # they are ignored anyway, not using them is probably best.
366
+ def supports_timestamp_usecs?
367
+ false
368
+ end
363
369
 
364
370
  # MySQL supports ORDER and LIMIT clauses in UPDATE statements.
365
371
  def update_sql(values)
@@ -385,22 +391,12 @@ module Sequel
385
391
  def insert_default_values_sql
386
392
  "#{insert_sql_base}#{source_list(@opts[:from])} () VALUES ()"
387
393
  end
388
-
389
- # Use MySQL Timestamp format
390
- def literal_datetime(v)
391
- v.strftime(TIMESTAMP_FORMAT)
392
- end
393
-
394
+
394
395
  # Use 0 for false on MySQL
395
396
  def literal_false
396
397
  BOOL_FALSE
397
398
  end
398
399
 
399
- # Use MySQL Timestamp format
400
- def literal_time(v)
401
- v.strftime(TIMESTAMP_FORMAT)
402
- end
403
-
404
400
  # Use 1 for true on MySQL
405
401
  def literal_true
406
402
  BOOL_TRUE
@@ -121,6 +121,11 @@ module Sequel
121
121
  false
122
122
  end
123
123
 
124
+ # Oracle supports timezones in literal timestamps.
125
+ def supports_timestamp_timezones?
126
+ true
127
+ end
128
+
124
129
  # Oracle supports window functions
125
130
  def supports_window_functions?
126
131
  true
@@ -143,6 +148,14 @@ module Sequel
143
148
  SELECT_CLAUSE_ORDER
144
149
  end
145
150
 
151
+ # Modify the SQL to add the list of tables to select FROM
152
+ # Oracle doesn't support select without FROM clause
153
+ # so add the dummy DUAL table if the dataset doesn't select
154
+ # from a table.
155
+ def select_from_sql(sql)
156
+ sql << " FROM #{source_list(@opts[:from] || ['DUAL'])}"
157
+ end
158
+
146
159
  # Oracle requires a subselect to do limit and offset
147
160
  def select_limit_sql(sql)
148
161
  if limit = @opts[:limit]
@@ -693,6 +693,11 @@ module Sequel
693
693
  ["#{insert_sql_base}#{source_list(@opts[:from])} (#{identifier_list(columns)}) VALUES #{values}"]
694
694
  end
695
695
 
696
+ # PostgreSQL supports timezones in literal timestamps
697
+ def supports_timestamp_timezones?
698
+ true
699
+ end
700
+
696
701
  # PostgreSQL 8.4+ supports window functions
697
702
  def supports_window_functions?
698
703
  server_version >= 80400
@@ -716,11 +721,6 @@ module Sequel
716
721
  "'#{v.gsub(/[\000-\037\047\134\177-\377]/){|b| "\\#{("%o" % b[0..1].unpack("C")[0]).rjust(3, '0')}"}}'"
717
722
  end
718
723
 
719
- # PostgreSQL supports fractional timestamps
720
- def literal_datetime(v)
721
- "#{v.strftime(PG_TIMESTAMP_FORMAT)}.#{sprintf("%06d", (v.sec_fraction * 86400000000).to_i)}'"
722
- end
723
-
724
724
  # PostgreSQL uses FALSE for false values
725
725
  def literal_false
726
726
  BOOL_FALSE
@@ -736,11 +736,6 @@ module Sequel
736
736
  BOOL_TRUE
737
737
  end
738
738
 
739
- # PostgreSQL supports fractional times
740
- def literal_time(v)
741
- "#{v.strftime(PG_TIMESTAMP_FORMAT)}.#{sprintf("%06d",v.usec)}'"
742
- end
743
-
744
739
  # The order of clauses in the SELECT SQL statement
745
740
  def select_clause_order
746
741
  server_version >= 80400 ? SELECT_CLAUSE_ORDER_84 : SELECT_CLAUSE_ORDER
@@ -10,7 +10,9 @@ module Sequel
10
10
  # Run all alter_table commands in a transaction. This is technically only
11
11
  # needed for drop column.
12
12
  def alter_table(name, generator=nil, &block)
13
- transaction{super}
13
+ remove_cached_schema(name)
14
+ generator ||= Schema::AlterTableGenerator.new(self, &block)
15
+ transaction{generator.operations.each{|op| alter_table_sql_list(name, [op]).flatten.each{|sql| execute_ddl(sql)}}}
14
16
  end
15
17
 
16
18
  # A symbol signifying the value of the auto_vacuum PRAGMA.
@@ -164,6 +166,7 @@ module Sequel
164
166
  # from the existing table into the new table, deleting the existing table
165
167
  # and renaming the new table to the existing table's name.
166
168
  def duplicate_table(table, opts={})
169
+ remove_cached_schema(table)
167
170
  def_columns = defined_columns_for(table)
168
171
  old_columns = def_columns.map{|c| c[:name]}
169
172
  opts[:old_columns_proc].call(old_columns) if opts[:old_columns_proc]
@@ -233,6 +236,7 @@ module Sequel
233
236
  # Instance methods for datasets that connect to an SQLite database
234
237
  module DatasetMethods
235
238
  SELECT_CLAUSE_ORDER = %w'distinct columns from join where group having compounds order limit'.freeze
239
+ CONSTANT_MAP = {:CURRENT_DATE=>"date(CURRENT_TIMESTAMP, 'localtime')".freeze, :CURRENT_TIMESTAMP=>"datetime(CURRENT_TIMESTAMP, 'localtime')".freeze, :CURRENT_TIME=>"time(CURRENT_TIMESTAMP, 'localtime')".freeze}
236
240
 
237
241
  # SQLite does not support pattern matching via regular expressions.
238
242
  # SQLite is case insensitive (depending on pragma), so use LIKE for
@@ -249,6 +253,11 @@ module Sequel
249
253
  end
250
254
  end
251
255
 
256
+ # MSSQL doesn't support the SQL standard CURRENT_DATE or CURRENT_TIME
257
+ def constant_sql(constant)
258
+ CONSTANT_MAP[constant] || super
259
+ end
260
+
252
261
  # SQLite performs a TRUNCATE style DELETE if no filter is specified.
253
262
  # Since we want to always return the count of records, add a condition
254
263
  # that is always true and then delete.
@@ -284,6 +293,12 @@ module Sequel
284
293
  def supports_is_true?
285
294
  false
286
295
  end
296
+
297
+ # SQLite supports timezones in literal timestamps, since it stores them
298
+ # as text.
299
+ def supports_timestamp_timezones?
300
+ true
301
+ end
287
302
 
288
303
  private
289
304
 
@@ -304,6 +319,11 @@ module Sequel
304
319
  def select_clause_order
305
320
  SELECT_CLAUSE_ORDER
306
321
  end
322
+
323
+ # SQLite treats a DELETE with no WHERE clause as a TRUNCATE
324
+ def _truncate_sql(table)
325
+ "DELETE FROM #{table}"
326
+ end
307
327
  end
308
328
  end
309
329
  end