sequel 3.28.0 → 3.29.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (148) hide show
  1. data/CHANGELOG +119 -3
  2. data/Rakefile +5 -3
  3. data/bin/sequel +1 -5
  4. data/doc/model_hooks.rdoc +9 -1
  5. data/doc/opening_databases.rdoc +49 -40
  6. data/doc/prepared_statements.rdoc +27 -6
  7. data/doc/release_notes/3.28.0.txt +2 -2
  8. data/doc/release_notes/3.29.0.txt +459 -0
  9. data/doc/sharding.rdoc +7 -1
  10. data/doc/testing.rdoc +18 -9
  11. data/doc/transactions.rdoc +41 -1
  12. data/lib/sequel/adapters/ado.rb +28 -17
  13. data/lib/sequel/adapters/ado/mssql.rb +18 -6
  14. data/lib/sequel/adapters/amalgalite.rb +11 -7
  15. data/lib/sequel/adapters/db2.rb +122 -70
  16. data/lib/sequel/adapters/dbi.rb +15 -15
  17. data/lib/sequel/adapters/do.rb +5 -36
  18. data/lib/sequel/adapters/do/mysql.rb +0 -5
  19. data/lib/sequel/adapters/do/postgres.rb +0 -5
  20. data/lib/sequel/adapters/do/sqlite.rb +0 -5
  21. data/lib/sequel/adapters/firebird.rb +3 -6
  22. data/lib/sequel/adapters/ibmdb.rb +24 -16
  23. data/lib/sequel/adapters/informix.rb +2 -4
  24. data/lib/sequel/adapters/jdbc.rb +47 -11
  25. data/lib/sequel/adapters/jdbc/as400.rb +5 -24
  26. data/lib/sequel/adapters/jdbc/db2.rb +0 -5
  27. data/lib/sequel/adapters/jdbc/derby.rb +217 -0
  28. data/lib/sequel/adapters/jdbc/firebird.rb +0 -5
  29. data/lib/sequel/adapters/jdbc/h2.rb +10 -12
  30. data/lib/sequel/adapters/jdbc/hsqldb.rb +166 -0
  31. data/lib/sequel/adapters/jdbc/informix.rb +0 -5
  32. data/lib/sequel/adapters/jdbc/jtds.rb +0 -5
  33. data/lib/sequel/adapters/jdbc/mysql.rb +0 -10
  34. data/lib/sequel/adapters/jdbc/oracle.rb +70 -3
  35. data/lib/sequel/adapters/jdbc/postgresql.rb +0 -11
  36. data/lib/sequel/adapters/jdbc/sqlite.rb +0 -5
  37. data/lib/sequel/adapters/jdbc/sqlserver.rb +0 -5
  38. data/lib/sequel/adapters/jdbc/transactions.rb +56 -7
  39. data/lib/sequel/adapters/mock.rb +315 -0
  40. data/lib/sequel/adapters/mysql.rb +64 -51
  41. data/lib/sequel/adapters/mysql2.rb +15 -9
  42. data/lib/sequel/adapters/odbc.rb +13 -6
  43. data/lib/sequel/adapters/odbc/db2.rb +0 -4
  44. data/lib/sequel/adapters/odbc/mssql.rb +0 -5
  45. data/lib/sequel/adapters/openbase.rb +2 -4
  46. data/lib/sequel/adapters/oracle.rb +333 -51
  47. data/lib/sequel/adapters/postgres.rb +80 -27
  48. data/lib/sequel/adapters/shared/access.rb +0 -6
  49. data/lib/sequel/adapters/shared/db2.rb +13 -15
  50. data/lib/sequel/adapters/shared/firebird.rb +6 -6
  51. data/lib/sequel/adapters/shared/mssql.rb +23 -18
  52. data/lib/sequel/adapters/shared/mysql.rb +6 -6
  53. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +6 -0
  54. data/lib/sequel/adapters/shared/oracle.rb +185 -30
  55. data/lib/sequel/adapters/shared/postgres.rb +35 -18
  56. data/lib/sequel/adapters/shared/progress.rb +0 -6
  57. data/lib/sequel/adapters/shared/sqlite.rb +116 -37
  58. data/lib/sequel/adapters/sqlite.rb +16 -8
  59. data/lib/sequel/adapters/swift.rb +5 -5
  60. data/lib/sequel/adapters/swift/mysql.rb +0 -5
  61. data/lib/sequel/adapters/swift/postgres.rb +0 -5
  62. data/lib/sequel/adapters/swift/sqlite.rb +6 -4
  63. data/lib/sequel/adapters/tinytds.rb +13 -10
  64. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +8 -0
  65. data/lib/sequel/core.rb +40 -0
  66. data/lib/sequel/database/connecting.rb +1 -2
  67. data/lib/sequel/database/dataset.rb +3 -3
  68. data/lib/sequel/database/dataset_defaults.rb +58 -0
  69. data/lib/sequel/database/misc.rb +62 -2
  70. data/lib/sequel/database/query.rb +113 -49
  71. data/lib/sequel/database/schema_methods.rb +7 -2
  72. data/lib/sequel/dataset/actions.rb +37 -19
  73. data/lib/sequel/dataset/features.rb +24 -0
  74. data/lib/sequel/dataset/graph.rb +7 -6
  75. data/lib/sequel/dataset/misc.rb +11 -3
  76. data/lib/sequel/dataset/mutation.rb +2 -3
  77. data/lib/sequel/dataset/prepared_statements.rb +6 -4
  78. data/lib/sequel/dataset/query.rb +46 -15
  79. data/lib/sequel/dataset/sql.rb +28 -4
  80. data/lib/sequel/extensions/named_timezones.rb +5 -0
  81. data/lib/sequel/extensions/thread_local_timezones.rb +1 -1
  82. data/lib/sequel/model.rb +2 -1
  83. data/lib/sequel/model/associations.rb +115 -33
  84. data/lib/sequel/model/base.rb +91 -31
  85. data/lib/sequel/plugins/class_table_inheritance.rb +4 -4
  86. data/lib/sequel/plugins/dataset_associations.rb +100 -0
  87. data/lib/sequel/plugins/force_encoding.rb +6 -6
  88. data/lib/sequel/plugins/identity_map.rb +1 -1
  89. data/lib/sequel/plugins/many_through_many.rb +6 -10
  90. data/lib/sequel/plugins/prepared_statements.rb +12 -1
  91. data/lib/sequel/plugins/prepared_statements_associations.rb +1 -1
  92. data/lib/sequel/plugins/rcte_tree.rb +29 -15
  93. data/lib/sequel/plugins/serialization.rb +6 -1
  94. data/lib/sequel/plugins/sharding.rb +0 -5
  95. data/lib/sequel/plugins/single_table_inheritance.rb +1 -1
  96. data/lib/sequel/plugins/typecast_on_load.rb +9 -12
  97. data/lib/sequel/plugins/update_primary_key.rb +1 -1
  98. data/lib/sequel/timezones.rb +42 -42
  99. data/lib/sequel/version.rb +1 -1
  100. data/spec/adapters/mssql_spec.rb +29 -29
  101. data/spec/adapters/mysql_spec.rb +86 -104
  102. data/spec/adapters/oracle_spec.rb +48 -76
  103. data/spec/adapters/postgres_spec.rb +98 -33
  104. data/spec/adapters/spec_helper.rb +0 -5
  105. data/spec/adapters/sqlite_spec.rb +24 -21
  106. data/spec/core/connection_pool_spec.rb +9 -15
  107. data/spec/core/core_sql_spec.rb +20 -31
  108. data/spec/core/database_spec.rb +491 -227
  109. data/spec/core/dataset_spec.rb +638 -1051
  110. data/spec/core/expression_filters_spec.rb +0 -1
  111. data/spec/core/mock_adapter_spec.rb +378 -0
  112. data/spec/core/object_graph_spec.rb +48 -114
  113. data/spec/core/schema_generator_spec.rb +3 -3
  114. data/spec/core/schema_spec.rb +51 -114
  115. data/spec/core/spec_helper.rb +3 -90
  116. data/spec/extensions/class_table_inheritance_spec.rb +1 -1
  117. data/spec/extensions/dataset_associations_spec.rb +199 -0
  118. data/spec/extensions/instance_hooks_spec.rb +71 -0
  119. data/spec/extensions/named_timezones_spec.rb +22 -2
  120. data/spec/extensions/nested_attributes_spec.rb +3 -0
  121. data/spec/extensions/schema_spec.rb +1 -1
  122. data/spec/extensions/serialization_modification_detection_spec.rb +1 -0
  123. data/spec/extensions/serialization_spec.rb +5 -8
  124. data/spec/extensions/spec_helper.rb +4 -0
  125. data/spec/extensions/thread_local_timezones_spec.rb +22 -2
  126. data/spec/extensions/typecast_on_load_spec.rb +1 -6
  127. data/spec/integration/associations_test.rb +123 -12
  128. data/spec/integration/dataset_test.rb +140 -47
  129. data/spec/integration/eager_loader_test.rb +19 -21
  130. data/spec/integration/model_test.rb +80 -1
  131. data/spec/integration/plugin_test.rb +179 -128
  132. data/spec/integration/prepared_statement_test.rb +92 -91
  133. data/spec/integration/schema_test.rb +42 -23
  134. data/spec/integration/spec_helper.rb +25 -31
  135. data/spec/integration/timezone_test.rb +38 -12
  136. data/spec/integration/transaction_test.rb +161 -34
  137. data/spec/integration/type_test.rb +3 -3
  138. data/spec/model/association_reflection_spec.rb +83 -7
  139. data/spec/model/associations_spec.rb +393 -676
  140. data/spec/model/base_spec.rb +186 -116
  141. data/spec/model/dataset_methods_spec.rb +7 -27
  142. data/spec/model/eager_loading_spec.rb +343 -867
  143. data/spec/model/hooks_spec.rb +160 -79
  144. data/spec/model/model_spec.rb +118 -165
  145. data/spec/model/plugins_spec.rb +7 -13
  146. data/spec/model/record_spec.rb +138 -207
  147. data/spec/model/spec_helper.rb +10 -73
  148. metadata +14 -8
@@ -241,16 +241,6 @@ module Sequel
241
241
  self << drop_language_sql(name, opts)
242
242
  end
243
243
 
244
- # Remove the cached entries for primary keys and sequences when dropping a table.
245
- def drop_table(*names)
246
- names.each do |name|
247
- name = quote_schema_table(name)
248
- @primary_keys.delete(name)
249
- @primary_key_sequences.delete(name)
250
- end
251
- super
252
- end
253
-
254
244
  # Drops a trigger from the database. Arguments:
255
245
  # * table : table from which to drop the trigger
256
246
  # * name : name of the trigger to drop
@@ -294,6 +284,16 @@ module Sequel
294
284
  dataset.from(:pg_class).join(:pg_locks, :relation=>:relfilenode).select(:pg_class__relname, Sequel::SQL::ColumnAll.new(:pg_locks))
295
285
  end
296
286
 
287
+ # Notifies the given channel. See the PostgreSQL NOTIFY documentation. Options:
288
+ #
289
+ # :payload :: The payload string to use for the NOTIFY statement. Only supported
290
+ # in PostgreSQL 9.0+.
291
+ # :server :: The server to which to send the NOTIFY statement, if the sharding support
292
+ # is being used.
293
+ def notify(channel, opts={})
294
+ execute_ddl("NOTIFY #{channel}#{", #{literal(opts[:payload].to_s)}" if opts[:payload]}", opts)
295
+ end
296
+
297
297
  # Return primary key for the given table.
298
298
  def primary_key(table, opts={})
299
299
  quoted_table = quote_schema_table(table)
@@ -398,8 +398,8 @@ module Sequel
398
398
  # If the :prepare option is given and we aren't in a savepoint,
399
399
  # prepare the transaction for a two-phase commit.
400
400
  def commit_transaction(conn, opts={})
401
- if opts[:prepare] && Thread.current[:sequel_transaction_depth] <= 1
402
- log_connection_execute(conn, "PREPARE TRANSACTION #{literal(opts[:prepare])}")
401
+ if (s = opts[:prepare]) && @transactions[conn][:savepoint_level] <= 1
402
+ log_connection_execute(conn, "PREPARE TRANSACTION #{literal(s)}")
403
403
  else
404
404
  super
405
405
  end
@@ -548,6 +548,15 @@ module Sequel
548
548
  PREPARED_ARG_PLACEHOLDER
549
549
  end
550
550
 
551
+ # Remove the cached entries for primary keys and sequences when a table is
552
+ # changed.
553
+ def remove_cached_schema(table)
554
+ tab = quote_schema_table(table)
555
+ @primary_keys.delete(tab)
556
+ @primary_key_sequences.delete(tab)
557
+ super
558
+ end
559
+
551
560
  # SQL DDL statement for renaming a table. PostgreSQL doesn't allow you to change a table's schema in
552
561
  # a rename table operation, so speciying a new schema in new_name will not have an effect.
553
562
  def rename_table_sql(name, new_name)
@@ -562,8 +571,8 @@ module Sequel
562
571
 
563
572
  # The dataset used for parsing table schemas, using the pg_* system catalogs.
564
573
  def schema_parse_table(table_name, opts)
565
- m = output_identifier_meth
566
- m2 = input_identifier_meth
574
+ m = output_identifier_meth(opts[:dataset])
575
+ m2 = input_identifier_meth(opts[:dataset])
567
576
  ds = metadata_dataset.select(:pg_attribute__attname___name,
568
577
  SQL::Function.new(:format_type, :pg_type__oid, :pg_attribute__atttypmod).as(:db_type),
569
578
  SQL::Function.new(:pg_get_expr, :pg_attrdef__adbin, :pg_class__oid).as(:default),
@@ -692,7 +701,7 @@ module Sequel
692
701
  def complex_expression_sql(op, args)
693
702
  case op
694
703
  when :^
695
- "(#{literal(args.at(0))} # #{literal(args.at(1))})"
704
+ "(#{args.collect{|a| literal(a)}.join(" # ")})"
696
705
  else
697
706
  super
698
707
  end
@@ -722,12 +731,14 @@ module Sequel
722
731
 
723
732
  # Insert given values into the database.
724
733
  def insert(*values, &block)
725
- if @opts[:sql] || @opts[:returning]
734
+ if @opts[:returning]
726
735
  super
727
- elsif supports_insert_select?
736
+ elsif !@opts[:sql] && supports_insert_select?
728
737
  returning(insert_pk).insert(*values){|r| return r.values.first}
738
+ elsif (f = opts[:from]) && !f.empty?
739
+ execute_insert(insert_sql(*values), :table=>f.first, :values=>values.size == 1 ? values.first : values)
729
740
  else
730
- execute_insert(insert_sql(*values), :table=>opts[:from].first, :values=>values.size == 1 ? values.first : values)
741
+ super
731
742
  end
732
743
  end
733
744
 
@@ -868,6 +879,12 @@ module Sequel
868
879
  server_version >= 80400 ? SELECT_CLAUSE_METHODS_84 : SELECT_CLAUSE_METHODS
869
880
  end
870
881
 
882
+ # PostgreSQL requires parentheses around compound datasets if they use
883
+ # CTEs, and using them in other places doesn't hurt.
884
+ def compound_dataset_sql(ds)
885
+ "(#{super})"
886
+ end
887
+
871
888
  # Support FOR SHARE locking when using the :share lock style.
872
889
  def select_lock_sql(sql)
873
890
  @opts[:lock] == :share ? (sql << FOR_SHARE) : super
@@ -6,12 +6,6 @@ module Sequel
6
6
  def database_type
7
7
  :progress
8
8
  end
9
-
10
- def dataset(opts = nil)
11
- ds = super
12
- ds.extend(DatasetMethods)
13
- ds
14
- end
15
9
  end
16
10
 
17
11
  module DatasetMethods
@@ -11,14 +11,6 @@ module Sequel
11
11
  TEMP_STORE = [:default, :file, :memory].freeze
12
12
  VIEWS_FILTER = "type = 'view'".freeze
13
13
 
14
- # Run all alter_table commands in a transaction. This is technically only
15
- # needed for drop column.
16
- def alter_table(name, generator=nil, &block)
17
- remove_cached_schema(name)
18
- generator ||= Schema::AlterTableGenerator.new(self, &block)
19
- transaction{generator.operations.each{|op| alter_table_sql_list(name, [op]).flatten.each{|sql| execute_ddl(sql)}}}
20
- end
21
-
22
14
  # A symbol signifying the value of the auto_vacuum PRAGMA.
23
15
  def auto_vacuum
24
16
  AUTO_VACUUM[pragma_get(:auto_vacuum).to_i]
@@ -31,7 +23,20 @@ module Sequel
31
23
  value = AUTO_VACUUM.index(value) || (raise Error, "Invalid value for auto_vacuum option. Please specify one of :none, :full, :incremental.")
32
24
  pragma_set(:auto_vacuum, value)
33
25
  end
26
+
27
+ # Boolean signifying the value of the case_sensitive_likePRAGMA, or nil
28
+ # if not using SQLite 3.2.3+.
29
+ def case_sensitive_like
30
+ pragma_get(:case_sensitive_like).to_i == 1 if sqlite_version >= 30203
31
+ end
34
32
 
33
+ # Set the case_sensitive_like PRAGMA using the given boolean value, if using
34
+ # SQLite 3.2.3+. If not using 3.2.3+, no error is raised. See pragma_set.
35
+ # Consider using the :case_sensitive_like Database option instead.
36
+ def case_sensitive_like=(value)
37
+ pragma_set(:case_sensitive_like, !!value ? 'on' : 'off') if sqlite_version >= 30203
38
+ end
39
+
35
40
  # SQLite uses the :sqlite database type.
36
41
  def database_type
37
42
  :sqlite
@@ -162,6 +167,22 @@ module Sequel
162
167
 
163
168
  private
164
169
 
170
+ # Run all alter_table commands in a transaction. This is technically only
171
+ # needed for drop column.
172
+ def apply_alter_table(table, ops)
173
+ transaction do
174
+ if ops.length > 1 && ops.all?{|op| op[:op] == :add_constraint}
175
+ # If you are just doing constraints, apply all of them at the same time,
176
+ # as otherwise all but the last one get lost.
177
+ alter_table_sql_list(table, [{:op=>:add_constraints, :ops=>ops}]).flatten.each{|sql| execute_ddl(sql)}
178
+ else
179
+ # Run each operation separately, as later operations may depend on the
180
+ # results of earlier operations.
181
+ ops.each{|op| alter_table_sql_list(table, [op]).flatten.each{|sql| execute_ddl(sql)}}
182
+ end
183
+ end
184
+ end
185
+
165
186
  # SQLite supports limited table modification. You can add a column
166
187
  # or an index. Dropping columns is supported by copying the table into
167
188
  # a temporary table, dropping the table, and creating a new table without
@@ -188,11 +209,26 @@ module Sequel
188
209
  duplicate_table(table){|columns| columns.each{|s| s[:null] = op[:null] if s[:name].to_s == op[:name].to_s}}
189
210
  when :set_column_type
190
211
  duplicate_table(table){|columns| columns.each{|s| s[:type] = op[:type] if s[:name].to_s == op[:name].to_s}}
212
+ when :drop_constraint
213
+ case op[:type]
214
+ when :primary_key
215
+ duplicate_table(table){|columns| columns.each{|s| s[:primary_key] = nil}}
216
+ when :foreign_key
217
+ duplicate_table(table){|columns| columns.each{|s| s[:table] = nil}}
218
+ when :unique
219
+ duplicate_table(table)
220
+ else
221
+ raise Error, "Unsupported :type option for drop_constraint: #{op[:type].inspect}"
222
+ end
223
+ when :add_constraint
224
+ duplicate_table(table, :constraints=>[op])
225
+ when :add_constraints
226
+ duplicate_table(table, :constraints=>op[:ops])
191
227
  else
192
- raise Error, "Unsupported ALTER TABLE operation"
228
+ raise Error, "Unsupported ALTER TABLE operation: #{op[:op].inspect}"
193
229
  end
194
230
  end
195
-
231
+
196
232
  # The array of column symbols in the table, except for ones given in opts[:except]
197
233
  def backup_table_name(table, opts={})
198
234
  table = table.gsub('`', '')
@@ -202,17 +238,14 @@ module Sequel
202
238
  end
203
239
  end
204
240
 
205
- # Allow use without a generator, needed for the alter table hackery that Sequel allows.
206
- def column_list_sql(generator)
207
- generator.is_a?(Schema::Generator) ? super : generator.map{|c| column_definition_sql(c)}.join(', ')
208
- end
209
-
210
241
  # Array of PRAGMA SQL statements based on the Database options that should be applied to
211
242
  # new connections.
212
243
  def connection_pragmas
213
244
  ps = []
214
245
  v = typecast_value_boolean(opts.fetch(:foreign_keys, 1))
215
246
  ps << "PRAGMA foreign_keys = #{v ? 1 : 0}"
247
+ v = typecast_value_boolean(opts.fetch(:case_sensitive_like, 1))
248
+ ps << "PRAGMA case_sensitive_like = #{v ? 1 : 0}"
216
249
  [[:auto_vacuum, AUTO_VACUUM], [:synchronous, SYNCHRONOUS], [:temp_store, TEMP_STORE]].each do |prag, con|
217
250
  if v = opts[prag]
218
251
  raise(Error, "Value for PRAGMA #{prag} not supported, should be one of #{con.join(', ')}") unless v = con.index(v.to_sym)
@@ -234,15 +267,20 @@ module Sequel
234
267
  cols.reject!{|c| nono.include? c[:name] }
235
268
  end
236
269
 
237
- if foreign_keys
238
- metadata_dataset.with_sql("PRAGMA foreign_key_list(?)", input_identifier_meth.call(table)).each do |row|
239
- c = cols.find {|co| co[:name] == row[:from] } or next
240
- c[:table] = row[:table]
241
- c[:key] = row[:to]
242
- c[:on_update] = on_delete_sql_to_sym(row[:on_update])
243
- c[:on_delete] = on_delete_sql_to_sym(row[:on_delete])
244
- # is there any way to get deferrable status?
270
+ begin
271
+ if foreign_keys
272
+ metadata_dataset.with_sql("PRAGMA foreign_key_list(?)", input_identifier_meth.call(table)).each do |row|
273
+ c = cols.find {|co| co[:name] == row[:from] } or next
274
+ c[:table] = row[:table]
275
+ c[:key] = row[:to]
276
+ c[:on_update] = on_delete_sql_to_sym(row[:on_update])
277
+ c[:on_delete] = on_delete_sql_to_sym(row[:on_delete])
278
+ # is there any way to get deferrable status?
279
+ end
245
280
  end
281
+ rescue Sequel::DatabaseError
282
+ # Doesn't work correctly on some versions of JDBC SQLite,
283
+ # giving a "query does not return ResultSet" error.
246
284
  end
247
285
  cols
248
286
  end
@@ -257,17 +295,26 @@ module Sequel
257
295
  opts[:old_columns_proc].call(old_columns) if opts[:old_columns_proc]
258
296
 
259
297
  yield def_columns if block_given?
260
- def_columns_str = column_list_sql(def_columns)
298
+
299
+ constraints = (opts[:constraints] || []).dup
300
+ pks = []
301
+ def_columns.each{|c| pks << c[:name] if c[:primary_key]}
302
+ if pks.length > 1
303
+ constraints << {:type=>:primary_key, :columns=>pks}
304
+ def_columns.each{|c| c[:primary_key] = false if c[:primary_key]}
305
+ end
306
+
307
+ def_columns_str = (def_columns.map{|c| column_definition_sql(c)} + constraints.map{|c| constraint_definition_sql(c)}).join(', ')
261
308
  new_columns = old_columns.dup
262
309
  opts[:new_columns_proc].call(new_columns) if opts[:new_columns_proc]
263
310
 
264
311
  qt = quote_schema_table(table)
265
312
  bt = quote_identifier(backup_table_name(qt))
266
313
  a = [
267
- "CREATE TABLE #{bt}(#{def_columns_str})",
268
- "INSERT INTO #{bt}(#{dataset.send(:identifier_list, new_columns)}) SELECT #{dataset.send(:identifier_list, old_columns)} FROM #{qt}",
269
- "DROP TABLE #{qt}",
270
- "ALTER TABLE #{bt} RENAME TO #{qt}"
314
+ "ALTER TABLE #{qt} RENAME TO #{bt}",
315
+ "CREATE TABLE #{qt}(#{def_columns_str})",
316
+ "INSERT INTO #{qt}(#{dataset.send(:identifier_list, new_columns)}) SELECT #{dataset.send(:identifier_list, old_columns)} FROM #{bt}",
317
+ "DROP TABLE #{bt}"
271
318
  ]
272
319
  indexes(table).each do |name, h|
273
320
  if (h[:columns].map{|x| x.to_s} - new_columns).empty?
@@ -306,7 +353,7 @@ module Sequel
306
353
 
307
354
  # Parse the output of the table_info pragma
308
355
  def parse_pragma(table_name, opts)
309
- metadata_dataset.with_sql("PRAGMA table_info(?)", input_identifier_meth.call(table_name)).map do |row|
356
+ metadata_dataset.with_sql("PRAGMA table_info(?)", input_identifier_meth(opts[:dataset]).call(table_name)).map do |row|
310
357
  row.delete(:cid)
311
358
  row[:allow_null] = row.delete(:notnull).to_i == 0
312
359
  row[:default] = row.delete(:dflt_value)
@@ -326,7 +373,7 @@ module Sequel
326
373
  # SQLite supports schema parsing using the table_info PRAGMA, so
327
374
  # parse the output of that into the format Sequel expects.
328
375
  def schema_parse_table(table_name, opts)
329
- m = output_identifier_meth
376
+ m = output_identifier_meth(opts[:dataset])
330
377
  parse_pragma(table_name, opts).map do |row|
331
378
  [m.call(row.delete(:name)), row]
332
379
  end
@@ -361,13 +408,16 @@ module Sequel
361
408
  case op
362
409
  when :~, :'!~', :'~*', :'!~*'
363
410
  raise Error, "SQLite does not support pattern matching via regular expressions"
364
- when :LIKE, :'NOT LIKE', :ILIKE, :'NOT ILIKE'
365
- # SQLite is case insensitive for ASCII, and non case sensitive for other character sets
366
- "#{'NOT ' if [:'NOT LIKE', :'NOT ILIKE'].include?(op)}(#{literal(args.at(0))} LIKE #{literal(args.at(1))})"
411
+ when :ILIKE
412
+ super(:LIKE, args.map{|a| SQL::Function.new(:upper, a)})
413
+ when :"NOT LIKE", :"NOT ILIKE"
414
+ "NOT #{complex_expression_sql((op == :"NOT ILIKE" ? :ILIKE : :LIKE), args)}"
367
415
  when :^
368
- a = literal(args.at(0))
369
- b = literal(args.at(1))
370
- "((~(#{a} & #{b})) & (#{a} | #{b}))"
416
+ complex_expression_arg_pairs(args) do |a, b|
417
+ a = literal(a)
418
+ b = literal(b)
419
+ "((~(#{a} & #{b})) & (#{a} | #{b}))"
420
+ end
371
421
  when :extract
372
422
  part = args.at(0)
373
423
  raise(Sequel::Error, "unsupported extract argument: #{part.inspect}") unless format = EXTRACT_MAP[part]
@@ -408,6 +458,18 @@ module Sequel
408
458
  "`#{c}`"
409
459
  end
410
460
 
461
+ # When a qualified column is selected on SQLite and the qualifier
462
+ # is a subselect, the column name used is the full qualified name
463
+ # (including the qualifier) instead of just the column name. To
464
+ # get correct column names, you must use an alias.
465
+ def select(*cols)
466
+ if ((f = @opts[:from]) && f.any?{|t| t.is_a?(Dataset) || (t.is_a?(SQL::AliasedExpression) && t.expression.is_a?(Dataset))}) || ((j = @opts[:join]) && j.any?{|t| t.table.is_a?(Dataset)})
467
+ super(*cols.map{|c| alias_qualified_column(c)})
468
+ else
469
+ super
470
+ end
471
+ end
472
+
411
473
  # SQLite does not support INTERSECT ALL or EXCEPT ALL
412
474
  def supports_intersect_except_all?
413
475
  false
@@ -442,6 +504,23 @@ module Sequel
442
504
  aliaz = aliaz.value if aliaz.is_a?(SQL::Identifier)
443
505
  "#{expression} AS #{literal(aliaz.to_s)}"
444
506
  end
507
+
508
+ # If col is a qualified column, alias it to the same as the column name
509
+ def alias_qualified_column(col)
510
+ case col
511
+ when Symbol
512
+ t, c, a = split_symbol(col)
513
+ if t && !a
514
+ alias_qualified_column(SQL::QualifiedIdentifier.new(t, c))
515
+ else
516
+ col
517
+ end
518
+ when SQL::QualifiedIdentifier
519
+ SQL::AliasedExpression.new(col, col.column)
520
+ else
521
+ col
522
+ end
523
+ end
445
524
 
446
525
  # SQL fragment specifying a list of identifiers
447
526
  def identifier_list(columns)
@@ -464,7 +543,7 @@ module Sequel
464
543
  def select_lock_sql(sql)
465
544
  super unless @opts[:lock] == :update
466
545
  end
467
-
546
+
468
547
  # SQLite treats a DELETE with no WHERE clause as a TRUNCATE
469
548
  def _truncate_sql(table)
470
549
  "DELETE FROM #{table}"
@@ -21,7 +21,6 @@ module Sequel
21
21
  # Hash with string keys and callable values for converting SQLite types.
22
22
  SQLITE_TYPES = {}
23
23
  {
24
- %w'timestamp datetime' => ::Sequel.method(:database_to_application_timestamp),
25
24
  %w'date' => ::Sequel.method(:string_to_date),
26
25
  %w'time' => ::Sequel.method(:string_to_time),
27
26
  %w'bit bool boolean' => tt.method(:boolean),
@@ -47,6 +46,16 @@ module Sequel
47
46
  end
48
47
 
49
48
  private_class_method :uri_to_options
49
+
50
+ # The conversion procs to use for this database
51
+ attr_reader :conversion_procs
52
+
53
+ def initialize(opts={})
54
+ super
55
+ @conversion_procs = SQLITE_TYPES.dup
56
+ @conversion_procs['timestamp'] = method(:to_application_timestamp)
57
+ @conversion_procs['datetime'] = method(:to_application_timestamp)
58
+ end
50
59
 
51
60
  # Connect to the database. Since SQLite is a file based database,
52
61
  # the only options available are :database (to specify the database
@@ -68,11 +77,6 @@ module Sequel
68
77
  db
69
78
  end
70
79
 
71
- # Return instance of Sequel::SQLite::Dataset with the given options.
72
- def dataset(opts = nil)
73
- SQLite::Dataset.new(self, opts)
74
- end
75
-
76
80
  # Run the given SQL with the given arguments and yield each row.
77
81
  def execute(sql, opts={}, &block)
78
82
  _execute(:select, sql, opts, &block)
@@ -113,7 +117,8 @@ module Sequel
113
117
  synchronize(opts[:server]) do |conn|
114
118
  return execute_prepared_statement(conn, type, sql, opts, &block) if sql.is_a?(Symbol)
115
119
  log_args = opts[:arguments]
116
- args = opts.fetch(:arguments, [])
120
+ args = {}
121
+ opts.fetch(:arguments, {}).each{|k, v| args[k] = prepared_statement_argument(v)}
117
122
  case type
118
123
  when :select
119
124
  log_yield(sql, log_args){conn.query(sql, args, &block)}
@@ -200,6 +205,8 @@ module Sequel
200
205
  # Dataset class for SQLite datasets that use the ruby-sqlite3 driver.
201
206
  class Dataset < Sequel::Dataset
202
207
  include ::Sequel::SQLite::DatasetMethods
208
+
209
+ Database::DatasetClass = self
203
210
 
204
211
  PREPARED_ARG_PLACEHOLDER = ':'.freeze
205
212
 
@@ -288,7 +295,8 @@ module Sequel
288
295
  def fetch_rows(sql)
289
296
  execute(sql) do |result|
290
297
  i = -1
291
- type_procs = result.types.map{|t| SQLITE_TYPES[base_type_name(t)]}
298
+ cps = db.conversion_procs
299
+ type_procs = result.types.map{|t| cps[base_type_name(t)]}
292
300
  cols = result.columns.map{|c| i+=1; [output_identifier(c), i, type_procs[i]]}
293
301
  @columns = cols.map{|c| c.first}
294
302
  result.each do |values|
@@ -15,16 +15,19 @@ module Sequel
15
15
  DATABASE_SETUP = {:postgres=>proc do |db|
16
16
  Sequel.ts_require 'adapters/swift/postgres'
17
17
  db.extend(Sequel::Swift::Postgres::DatabaseMethods)
18
+ db.dataset_class = Sequel::Swift::Postgres::Dataset
18
19
  db.swift_class = ::Swift::DB::Postgres
19
20
  end,
20
21
  :mysql=>proc do |db|
21
22
  Sequel.ts_require 'adapters/swift/mysql'
22
23
  db.extend(Sequel::Swift::MySQL::DatabaseMethods)
24
+ db.dataset_class = Sequel::Swift::MySQL::Dataset
23
25
  db.swift_class = ::Swift::DB::Mysql
24
26
  end,
25
27
  :sqlite=>proc do |db|
26
28
  Sequel.ts_require 'adapters/swift/sqlite'
27
29
  db.extend(Sequel::Swift::SQLite::DatabaseMethods)
30
+ db.dataset_class = Sequel::Swift::SQLite::Dataset
28
31
  db.swift_class = ::Swift::DB::Sqlite3
29
32
  end,
30
33
  }
@@ -59,11 +62,6 @@ module Sequel
59
62
  setup_connection(swift_class.new(server_opts(server)))
60
63
  end
61
64
 
62
- # Return a Sequel::Swift::Dataset object for this database.
63
- def dataset(opts = nil)
64
- Swift::Dataset.new(self, opts)
65
- end
66
-
67
65
  # Execute the given SQL, yielding a Swift::Result if a block is given.
68
66
  def execute(sql, opts={})
69
67
  synchronize(opts[:server]) do |conn|
@@ -134,6 +132,8 @@ module Sequel
134
132
  end
135
133
 
136
134
  class Dataset < Sequel::Dataset
135
+ Database::DatasetClass = self
136
+
137
137
  # Set the columns and yield the hashes to the block.
138
138
  def fetch_rows(sql, &block)
139
139
  execute(sql) do |res|