sequel 3.3.0 → 3.4.0

Sign up to get free protection for your applications and to get access to all the features.
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