sequel 3.31.0 → 3.32.0

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