sequel 3.31.0 → 3.32.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 (56) hide show
  1. data/CHANGELOG +54 -0
  2. data/MIT-LICENSE +1 -1
  3. data/doc/advanced_associations.rdoc +17 -0
  4. data/doc/association_basics.rdoc +74 -30
  5. data/doc/release_notes/3.32.0.txt +202 -0
  6. data/doc/schema_modification.rdoc +1 -1
  7. data/lib/sequel/adapters/jdbc/db2.rb +7 -0
  8. data/lib/sequel/adapters/jdbc/derby.rb +13 -0
  9. data/lib/sequel/adapters/jdbc/h2.rb +10 -1
  10. data/lib/sequel/adapters/jdbc/hsqldb.rb +7 -0
  11. data/lib/sequel/adapters/jdbc/oracle.rb +7 -0
  12. data/lib/sequel/adapters/mock.rb +4 -0
  13. data/lib/sequel/adapters/mysql.rb +3 -0
  14. data/lib/sequel/adapters/oracle.rb +7 -3
  15. data/lib/sequel/adapters/shared/db2.rb +9 -2
  16. data/lib/sequel/adapters/shared/mssql.rb +48 -2
  17. data/lib/sequel/adapters/shared/mysql.rb +24 -4
  18. data/lib/sequel/adapters/shared/oracle.rb +7 -6
  19. data/lib/sequel/adapters/shared/progress.rb +1 -1
  20. data/lib/sequel/adapters/shared/sqlite.rb +16 -10
  21. data/lib/sequel/core.rb +22 -0
  22. data/lib/sequel/database/query.rb +13 -4
  23. data/lib/sequel/dataset/actions.rb +20 -11
  24. data/lib/sequel/dataset/mutation.rb +7 -1
  25. data/lib/sequel/dataset/prepared_statements.rb +11 -0
  26. data/lib/sequel/dataset/sql.rb +21 -24
  27. data/lib/sequel/extensions/query.rb +1 -1
  28. data/lib/sequel/model.rb +5 -2
  29. data/lib/sequel/model/associations.rb +70 -16
  30. data/lib/sequel/model/base.rb +11 -6
  31. data/lib/sequel/plugins/active_model.rb +13 -1
  32. data/lib/sequel/plugins/composition.rb +43 -10
  33. data/lib/sequel/plugins/many_through_many.rb +4 -1
  34. data/lib/sequel/plugins/nested_attributes.rb +65 -10
  35. data/lib/sequel/plugins/serialization.rb +13 -8
  36. data/lib/sequel/plugins/serialization_modification_detection.rb +22 -10
  37. data/lib/sequel/version.rb +1 -1
  38. data/spec/adapters/mssql_spec.rb +33 -10
  39. data/spec/adapters/mysql_spec.rb +111 -91
  40. data/spec/adapters/oracle_spec.rb +18 -0
  41. data/spec/core/database_spec.rb +1 -0
  42. data/spec/core/dataset_spec.rb +110 -15
  43. data/spec/extensions/active_model_spec.rb +13 -0
  44. data/spec/extensions/many_through_many_spec.rb +14 -14
  45. data/spec/extensions/query_spec.rb +6 -0
  46. data/spec/extensions/serialization_modification_detection_spec.rb +36 -1
  47. data/spec/extensions/serialization_spec.rb +9 -0
  48. data/spec/integration/associations_test.rb +278 -154
  49. data/spec/integration/dataset_test.rb +39 -2
  50. data/spec/integration/plugin_test.rb +63 -3
  51. data/spec/integration/prepared_statement_test.rb +10 -3
  52. data/spec/integration/schema_test.rb +61 -14
  53. data/spec/integration/transaction_test.rb +10 -0
  54. data/spec/model/associations_spec.rb +170 -80
  55. data/spec/model/hooks_spec.rb +40 -0
  56. metadata +4 -2
@@ -6,6 +6,8 @@ module Sequel
6
6
  module Derby
7
7
  # Instance methods for Derby Database objects accessed via JDBC.
8
8
  module DatabaseMethods
9
+ PRIMARY_KEY_INDEX_RE = /\Asql\d+\z/i.freeze
10
+
9
11
  include ::Sequel::JDBC::Transactions
10
12
 
11
13
  # Derby doesn't support casting integer to varchar, only integer to char,
@@ -37,6 +39,12 @@ module Sequel
37
39
 
38
40
  private
39
41
 
42
+ # Derby optimizes away Sequel's default check of SELECT NULL FROM table,
43
+ # so use a SELECT * FROM table there.
44
+ def _table_exists?(ds)
45
+ ds.first
46
+ end
47
+
40
48
  # Derby-specific syntax for renaming columns and changing a columns type/nullity.
41
49
  def alter_table_sql(table, op)
42
50
  case op[:op]
@@ -86,6 +94,11 @@ module Sequel
86
94
  "RENAME TABLE #{quote_schema_table(name)} TO #{quote_schema_table(new_name)}"
87
95
  end
88
96
 
97
+ # Primary key indexes appear to be named sqlNNNN on Derby
98
+ def primary_key_index_re
99
+ PRIMARY_KEY_INDEX_RE
100
+ end
101
+
89
102
  # If an :identity option is present in the column, add the necessary IDENTITY SQL.
90
103
  def type_literal(column)
91
104
  if column[:identity]
@@ -75,7 +75,16 @@ module Sequel
75
75
  when :set_column_null
76
76
  "ALTER TABLE #{quote_schema_table(table)} ALTER COLUMN #{quote_identifier(op[:name])} SET#{' NOT' unless op[:null]} NULL"
77
77
  when :set_column_type
78
- "ALTER TABLE #{quote_schema_table(table)} ALTER COLUMN #{quote_identifier(op[:name])} #{type_literal(op)}"
78
+ if sch = schema(table)
79
+ if cs = sch.each{|k, v| break v if k == op[:name]; nil}
80
+ cs = cs.dup
81
+ cs[:default] = cs[:ruby_default]
82
+ op = cs.merge!(op)
83
+ end
84
+ end
85
+ sql = "ALTER TABLE #{quote_schema_table(table)} ALTER COLUMN #{quote_identifier(op[:name])} #{type_literal(op)}"
86
+ column_definition_order.each{|m| send(:"column_definition_#{m}_sql", sql, op)}
87
+ sql
79
88
  else
80
89
  super(table, op)
81
90
  end
@@ -6,6 +6,8 @@ module Sequel
6
6
  module HSQLDB
7
7
  # Instance methods for HSQLDB Database objects accessed via JDBC.
8
8
  module DatabaseMethods
9
+ PRIMARY_KEY_INDEX_RE = /\Asys_idx_sys_pk_/i.freeze
10
+
9
11
  include ::Sequel::JDBC::Transactions
10
12
 
11
13
  # HSQLDB uses the :hsqldb database type.
@@ -54,6 +56,11 @@ module Sequel
54
56
  rs.getInt(1)
55
57
  end
56
58
  end
59
+
60
+ # Primary key indexes appear to start with sys_idx_sys_pk_ on HSQLDB
61
+ def primary_key_index_re
62
+ PRIMARY_KEY_INDEX_RE
63
+ end
57
64
 
58
65
  # If an :identity option is present in the column, add the necessary IDENTITY SQL.
59
66
  # It's possible to use an IDENTITY type, but that defaults the sequence to start
@@ -7,6 +7,8 @@ module Sequel
7
7
  module Oracle
8
8
  # Instance methods for Oracle Database objects accessed via JDBC.
9
9
  module DatabaseMethods
10
+ PRIMARY_KEY_INDEX_RE = /\Asys_/i.freeze
11
+
10
12
  include Sequel::Oracle::DatabaseMethods
11
13
  include Sequel::JDBC::Transactions
12
14
 
@@ -39,6 +41,11 @@ module Sequel
39
41
  end
40
42
  end
41
43
 
44
+ # Primary key indexes appear to start with sys_ on Oracle
45
+ def primary_key_index_re
46
+ PRIMARY_KEY_INDEX_RE
47
+ end
48
+
42
49
  def schema_parse_table(*)
43
50
  sch = super
44
51
  sch.each do |c, s|
@@ -276,6 +276,10 @@ module Sequel
276
276
  def disconnect_connection(c)
277
277
  end
278
278
 
279
+ def log_connection_execute(c, sql)
280
+ c.execute(sql)
281
+ end
282
+
279
283
  def quote_identifiers_default
280
284
  false
281
285
  end
@@ -29,6 +29,9 @@ module Sequel
29
29
 
30
30
  class << self
31
31
  # Whether to convert invalid date time values by default.
32
+ #
33
+ # Only applies to Sequel::Database instances created after this
34
+ # has been set.
32
35
  attr_accessor :convert_invalid_date_time
33
36
  end
34
37
  self.convert_invalid_date_time = false
@@ -236,9 +236,13 @@ module Sequel
236
236
  pks = ds.select_map(:cols__column_name)
237
237
 
238
238
  # Default values
239
- defaults = metadata_dataset.from(:dba_tab_cols).
240
- where(:table_name=>im.call(table)).
241
- to_hash(:column_name, :data_default)
239
+ defaults = begin
240
+ metadata_dataset.from(:dba_tab_cols).
241
+ where(:table_name=>im.call(table)).
242
+ to_hash(:column_name, :data_default)
243
+ rescue DatabaseError
244
+ {}
245
+ end
242
246
 
243
247
  metadata = synchronize(opts[:server]) do |conn|
244
248
  begin
@@ -60,9 +60,16 @@ module Sequel
60
60
 
61
61
  # Use SYSCAT.INDEXES to get the indexes for the table
62
62
  def indexes(table, opts = {})
63
+ m = output_identifier_meth
64
+ indexes = {}
63
65
  metadata_dataset.
64
- with_sql("SELECT INDNAME,UNIQUERULE,MADE_UNIQUE,SYSTEM_REQUIRED FROM SYSCAT.INDEXES WHERE TABNAME = #{literal(input_identifier_meth.call(table))}").
65
- all.map{|h| Hash[ h.map{|k,v| [k.to_sym, v]} ] }
66
+ from(:syscat__indexes).
67
+ select(:indname, :uniquerule, :colnames).
68
+ where(:tabname=>input_identifier_meth.call(table), :system_required=>0).
69
+ each do |r|
70
+ indexes[m.call(r[:indname])] = {:unique=>(r[:uniquerule]=='U'), :columns=>r[:colnames][1..-1].split('+').map{|v| m.call(v)}}
71
+ end
72
+ indexes
66
73
  end
67
74
 
68
75
  private
@@ -28,6 +28,26 @@ module Sequel
28
28
  :mssql
29
29
  end
30
30
 
31
+ # Use the system tables to get index information
32
+ def indexes(table, opts={})
33
+ m = output_identifier_meth
34
+ im = input_identifier_meth
35
+ indexes = {}
36
+ metadata_dataset.from(:sys__tables___t).
37
+ join(:sys__indexes___i, :object_id=>:object_id).
38
+ join(:sys__index_columns___ic, :object_id=>:object_id, :index_id=>:index_id).
39
+ join(:sys__columns___c, :object_id=>:object_id, :column_id=>:column_id).
40
+ select(:i__name, :i__is_unique, :c__name___column).
41
+ where{{t__name=>im.call(table)}}.
42
+ where(:i__is_primary_key=>0, :i__is_disabled=>0).
43
+ order(:i__name, :ic__index_column_id).
44
+ each do |r|
45
+ index = indexes[m.call(r[:name])] ||= {:columns=>[], :unique=>(r[:is_unique] && r[:is_unique]!=0)}
46
+ index[:columns] << m.call(r[:column])
47
+ end
48
+ indexes
49
+ end
50
+
31
51
  # The version of the MSSQL server, as an integer (e.g. 10001600 for
32
52
  # SQL Server 2008 Express).
33
53
  def server_version(server=nil)
@@ -79,7 +99,21 @@ module Sequel
79
99
  when :rename_column
80
100
  "sp_rename #{literal("#{quote_schema_table(table)}.#{quote_identifier(op[:name])}")}, #{literal(op[:new_name].to_s)}, 'COLUMN'"
81
101
  when :set_column_type
82
- "ALTER TABLE #{quote_schema_table(table)} ALTER COLUMN #{quote_identifier(op[:name])} #{type_literal(op)}"
102
+ sqls = []
103
+ if sch = schema(table)
104
+ if cs = sch.each{|k, v| break v if k == op[:name]; nil}
105
+ cs = cs.dup
106
+ if constraint = default_constraint_name(table, op[:name])
107
+ sqls << "ALTER TABLE #{quote_schema_table(table)} DROP CONSTRAINT #{constraint}"
108
+ end
109
+ cs[:default] = cs[:ruby_default]
110
+ op = cs.merge!(op)
111
+ default = op.delete(:default)
112
+ end
113
+ end
114
+ sqls << "ALTER TABLE #{quote_schema_table(table)} ALTER COLUMN #{column_definition_sql(op)}"
115
+ sqls << alter_table_sql(table, op.merge(:op=>:set_column_default, :default=>default)) if default
116
+ sqls
83
117
  when :set_column_null
84
118
  sch = schema(table).find{|k,v| k.to_s == op[:name].to_s}.last
85
119
  type = sch[:db_type]
@@ -122,6 +156,18 @@ module Sequel
122
156
  "CREATE TABLE #{quote_schema_table(options[:temp] ? "##{name}" : name)} (#{column_list_sql(generator)})"
123
157
  end
124
158
 
159
+ # The name of the constraint for setting the default value on the table and column.
160
+ def default_constraint_name(table, column)
161
+ from(:sysobjects___c_obj).
162
+ join(:syscomments___com, :id=>:id).
163
+ join(:sysobjects___t_obj, :id=>:c_obj__parent_obj).
164
+ join(:sysconstraints___con, :constid=>:c_obj__id).
165
+ join(:syscolumns___col, :id=>:t_obj__id, :colid=>:colid).
166
+ where{{c_obj__uid=>user_id{}}}.
167
+ where(:c_obj__xtype=>'D', :t_obj__name=>table.to_s, :col__name=>column.to_s).
168
+ get(:c_obj__name)
169
+ end
170
+
125
171
  # The SQL to drop an index for the table.
126
172
  def drop_index_sql(table, op)
127
173
  "DROP INDEX #{quote_identifier(op[:name] || default_index_name(table, op[:columns]))} ON #{quote_schema_table(table)}"
@@ -238,7 +284,7 @@ module Sequel
238
284
  if index[:type] == :full_text
239
285
  "CREATE FULLTEXT INDEX ON #{quote_schema_table(table_name)} #{literal(index[:columns])} KEY INDEX #{literal(index[:key_index])}"
240
286
  else
241
- "CREATE #{'UNIQUE ' if index[:unique]}#{'CLUSTERED ' if index[:type] == :clustered}INDEX #{quote_identifier(index_name)} ON #{quote_schema_table(table_name)} #{literal(index[:columns])}"
287
+ "CREATE #{'UNIQUE ' if index[:unique]}#{'CLUSTERED ' if index[:type] == :clustered}INDEX #{quote_identifier(index_name)} ON #{quote_schema_table(table_name)} #{literal(index[:columns])}#{" INCLUDE #{literal(index[:include])}" if index[:include]}"
242
288
  end
243
289
  end
244
290
  end
@@ -1,5 +1,6 @@
1
1
  module Sequel
2
2
  Dataset::NON_SQL_OPTIONS << :insert_ignore
3
+ Dataset::NON_SQL_OPTIONS << :update_ignore
3
4
  Dataset::NON_SQL_OPTIONS << :on_duplicate_key_update
4
5
 
5
6
  module MySQL
@@ -311,13 +312,17 @@ module Sequel
311
312
  # MySQL has both datetime and timestamp classes, most people are going
312
313
  # to want datetime
313
314
  def type_literal_generic_datetime(column)
314
- :datetime
315
+ if column[:default] == Sequel::CURRENT_TIMESTAMP
316
+ :timestamp
317
+ else
318
+ :datetime
319
+ end
315
320
  end
316
321
 
317
322
  # MySQL has both datetime and timestamp classes, most people are going
318
323
  # to want datetime
319
324
  def type_literal_generic_time(column)
320
- column[:only_time] ? :time : :datetime
325
+ column[:only_time] ? :time : type_literal_generic_datetime(column)
321
326
  end
322
327
 
323
328
  # MySQL doesn't have a true boolean class, so it uses tinyint(1)
@@ -336,7 +341,7 @@ module Sequel
336
341
  DELETE_CLAUSE_METHODS = Dataset.clause_methods(:delete, %w'delete from where order limit')
337
342
  INSERT_CLAUSE_METHODS = Dataset.clause_methods(:insert, %w'insert ignore into columns values on_duplicate_key_update')
338
343
  SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'select distinct calc_found_rows columns from join where group having compounds order limit lock')
339
- UPDATE_CLAUSE_METHODS = Dataset.clause_methods(:update, %w'update table set where order limit')
344
+ UPDATE_CLAUSE_METHODS = Dataset.clause_methods(:update, %w'update ignore table set where order limit')
340
345
  SPACE = Dataset::SPACE
341
346
  PAREN_OPEN = Dataset::PAREN_OPEN
342
347
  PAREN_CLOSE = Dataset::PAREN_CLOSE
@@ -467,7 +472,7 @@ module Sequel
467
472
  def insert_ignore
468
473
  clone(:insert_ignore=>true)
469
474
  end
470
-
475
+
471
476
  # Sets up the insert methods to use ON DUPLICATE KEY UPDATE
472
477
  # If you pass no arguments, ALL fields will be
473
478
  # updated with the new values. If you pass the fields you
@@ -549,6 +554,16 @@ module Sequel
549
554
  false
550
555
  end
551
556
 
557
+ # Sets up the update methods to use UPDATE IGNORE.
558
+ # Useful if you have a unique key and want to just skip
559
+ # updating rows that violate the unique key restriction.
560
+ #
561
+ # dataset.update_ignore.update({:name => 'a', :value => 1})
562
+ # # UPDATE IGNORE tablename SET name = 'a', value = 1
563
+ def update_ignore
564
+ clone(:update_ignore=>true)
565
+ end
566
+
552
567
  private
553
568
 
554
569
  # MySQL supports the ORDER BY and LIMIT clauses for DELETE statements
@@ -591,6 +606,11 @@ module Sequel
591
606
  sql << IGNORE if opts[:insert_ignore]
592
607
  end
593
608
 
609
+ # MySQL supports UPDATE IGNORE
610
+ def update_ignore_sql(sql)
611
+ sql << IGNORE if opts[:update_ignore]
612
+ end
613
+
594
614
  # If this is an replace instead of an insert, use replace instead
595
615
  def insert_insert_sql(sql)
596
616
  sql << (@opts[:replace] ? REPLACE : INSERT)
@@ -17,7 +17,7 @@ module Sequel
17
17
  end
18
18
 
19
19
  def current_user
20
- @current_user ||= get{sys_context('USERENV', 'CURRENT_USER')}
20
+ @current_user ||= metadata_dataset.get{sys_context('USERENV', 'CURRENT_USER')}
21
21
  end
22
22
 
23
23
  def drop_sequence(name)
@@ -30,17 +30,18 @@ module Sequel
30
30
  end
31
31
 
32
32
  def tables(opts={})
33
- ds = from(:tab).server(opts[:server]).select(:tname).filter(:tabtype => 'TABLE')
34
- ds.map{|r| ds.send(:output_identifier, r[:tname])}
33
+ m = output_identifier_meth
34
+ metadata_dataset.from(:tab).server(opts[:server]).select(:tname).filter(:tabtype => 'TABLE').map{|r| m.call(r[:tname])}
35
35
  end
36
36
 
37
37
  def views(opts={})
38
- ds = from(:tab).server(opts[:server]).select(:tname).filter(:tabtype => 'VIEW')
39
- ds.map{|r| ds.send(:output_identifier, r[:tname])}
38
+ m = output_identifier_meth
39
+ metadata_dataset.from(:tab).server(opts[:server]).select(:tname).filter(:tabtype => 'VIEW').map{|r| m.call(r[:tname])}
40
40
  end
41
41
 
42
42
  def view_exists?(name)
43
- from(:tab).filter(:tname =>dataset.send(:input_identifier, name), :tabtype => 'VIEW').count > 0
43
+ m = input_identifier_meth
44
+ metadata_dataset.from(:tab).filter(:tname =>m.call(name), :tabtype => 'VIEW').count > 0
44
45
  end
45
46
 
46
47
  private
@@ -9,7 +9,7 @@ module Sequel
9
9
  end
10
10
 
11
11
  module DatasetMethods
12
- SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'limit distinct columns from join where group order having compounds')
12
+ SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'select limit distinct columns from join where group order having compounds')
13
13
 
14
14
  # Progress requires SQL standard datetimes
15
15
  def requires_sql_standard_datetimes?
@@ -170,6 +170,8 @@ module Sequel
170
170
  # Run all alter_table commands in a transaction. This is technically only
171
171
  # needed for drop column.
172
172
  def apply_alter_table(table, ops)
173
+ fks = foreign_keys
174
+ self.foreign_keys = false if fks
173
175
  transaction do
174
176
  if ops.length > 1 && ops.all?{|op| op[:op] == :add_constraint}
175
177
  # If you are just doing constraints, apply all of them at the same time,
@@ -181,6 +183,7 @@ module Sequel
181
183
  ops.each{|op| alter_table_sql_list(table, [op]).flatten.each{|sql| execute_ddl(sql)}}
182
184
  end
183
185
  end
186
+ self.foreign_keys = true if fks
184
187
  end
185
188
 
186
189
  # SQLite supports limited table modification. You can add a column
@@ -208,7 +211,7 @@ module Sequel
208
211
  when :set_column_null
209
212
  duplicate_table(table){|columns| columns.each{|s| s[:null] = op[:null] if s[:name].to_s == op[:name].to_s}}
210
213
  when :set_column_type
211
- duplicate_table(table){|columns| columns.each{|s| s[:type] = op[:type] if s[:name].to_s == op[:name].to_s}}
214
+ duplicate_table(table){|columns| columns.each{|s| s.merge!(op) if s[:name].to_s == op[:name].to_s}}
212
215
  when :drop_constraint
213
216
  case op[:type]
214
217
  when :primary_key
@@ -238,6 +241,11 @@ module Sequel
238
241
  end
239
242
  end
240
243
 
244
+ # Surround default with parens to appease SQLite
245
+ def column_definition_default_sql(sql, column)
246
+ sql << " DEFAULT (#{literal(column[:default])})" if column.include?(:default)
247
+ end
248
+
241
249
  # Array of PRAGMA SQL statements based on the Database options that should be applied to
242
250
  # new connections.
243
251
  def connection_pragmas
@@ -268,15 +276,13 @@ module Sequel
268
276
  end
269
277
 
270
278
  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
279
+ metadata_dataset.with_sql("PRAGMA foreign_key_list(?)", input_identifier_meth.call(table)).each do |row|
280
+ c = cols.find {|co| co[:name] == row[:from] } or next
281
+ c[:table] = row[:table]
282
+ c[:key] = row[:to]
283
+ c[:on_update] = on_delete_sql_to_sym(row[:on_update])
284
+ c[:on_delete] = on_delete_sql_to_sym(row[:on_delete])
285
+ # is there any way to get deferrable status?
280
286
  end
281
287
  rescue Sequel::DatabaseError
282
288
  # Doesn't work correctly on some versions of JDBC SQLite,
data/lib/sequel/core.rb CHANGED
@@ -25,6 +25,7 @@
25
25
  module Sequel
26
26
  @convert_two_digit_years = true
27
27
  @datetime_class = Time
28
+ @empty_array_handle_nulls = true
28
29
  @virtual_row_instance_eval = true
29
30
  @require_thread = nil
30
31
 
@@ -54,6 +55,27 @@ module Sequel
54
55
  # days on +DateTime+).
55
56
  attr_accessor :datetime_class
56
57
 
58
+ # Sets whether or not to attempt to handle NULL values correctly when given
59
+ # an empty array. By default:
60
+ #
61
+ # DB[:a].filter(:b=>[])
62
+ # # SELECT * FROM a WHERE (b != b)
63
+ # DB[:a].exclude(:b=>[])
64
+ # # SELECT * FROM a WHERE (b = b)
65
+ #
66
+ # However, some databases (e.g. MySQL) will perform very poorly
67
+ # with this type of query. You can set this to false to get the
68
+ # following behavior:
69
+ #
70
+ # DB[:a].filter(:b=>[])
71
+ # # SELECT * FROM a WHERE 1 = 0
72
+ # DB[:a].exclude(:b=>[])
73
+ # # SELECT * FROM a WHERE 1 = 1
74
+ #
75
+ # This may not handle NULLs correctly, but can be much faster on
76
+ # some databases.
77
+ attr_accessor :empty_array_handle_nulls
78
+
57
79
  # For backwards compatibility, has no effect.
58
80
  attr_accessor :virtual_row_instance_eval
59
81
 
@@ -184,16 +184,19 @@ module Sequel
184
184
  # to the database.
185
185
  #
186
186
  # DB.table_exists?(:foo) # => false
187
- # # SELECT * FROM foo LIMIT 1
187
+ # # SELECT NULL FROM foo LIMIT 1
188
+ #
189
+ # Note that since this does a SELECT from the table, it can give false negatives
190
+ # if you don't have permission to SELECT from the table.
188
191
  def table_exists?(name)
189
192
  sch, table_name = schema_and_table(name)
190
193
  name = SQL::QualifiedIdentifier.new(sch, table_name) if sch
191
- from(name).first
194
+ _table_exists?(from(name))
192
195
  true
193
- rescue
196
+ rescue DatabaseError
194
197
  false
195
198
  end
196
-
199
+
197
200
  # Return all tables in the database as an array of symbols.
198
201
  #
199
202
  # DB.tables # => [:albums, :artists]
@@ -238,6 +241,12 @@ module Sequel
238
241
 
239
242
  private
240
243
 
244
+ # Should raise an error if the table doesn't not exist,
245
+ # and not raise an error if the table does exist.
246
+ def _table_exists?(ds)
247
+ ds.get(Sequel::NULL)
248
+ end
249
+
241
250
  # Internal generic transaction method. Any exception raised by the given
242
251
  # block will cause the transaction to be rolled back. If the exception is
243
252
  # not a Sequel::Rollback, the error will be reraised. If no exception occurs