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.
- data/CHANGELOG +54 -0
- data/MIT-LICENSE +1 -1
- data/doc/advanced_associations.rdoc +17 -0
- data/doc/association_basics.rdoc +74 -30
- data/doc/release_notes/3.32.0.txt +202 -0
- data/doc/schema_modification.rdoc +1 -1
- data/lib/sequel/adapters/jdbc/db2.rb +7 -0
- data/lib/sequel/adapters/jdbc/derby.rb +13 -0
- data/lib/sequel/adapters/jdbc/h2.rb +10 -1
- data/lib/sequel/adapters/jdbc/hsqldb.rb +7 -0
- data/lib/sequel/adapters/jdbc/oracle.rb +7 -0
- data/lib/sequel/adapters/mock.rb +4 -0
- data/lib/sequel/adapters/mysql.rb +3 -0
- data/lib/sequel/adapters/oracle.rb +7 -3
- data/lib/sequel/adapters/shared/db2.rb +9 -2
- data/lib/sequel/adapters/shared/mssql.rb +48 -2
- data/lib/sequel/adapters/shared/mysql.rb +24 -4
- data/lib/sequel/adapters/shared/oracle.rb +7 -6
- data/lib/sequel/adapters/shared/progress.rb +1 -1
- data/lib/sequel/adapters/shared/sqlite.rb +16 -10
- data/lib/sequel/core.rb +22 -0
- data/lib/sequel/database/query.rb +13 -4
- data/lib/sequel/dataset/actions.rb +20 -11
- data/lib/sequel/dataset/mutation.rb +7 -1
- data/lib/sequel/dataset/prepared_statements.rb +11 -0
- data/lib/sequel/dataset/sql.rb +21 -24
- data/lib/sequel/extensions/query.rb +1 -1
- data/lib/sequel/model.rb +5 -2
- data/lib/sequel/model/associations.rb +70 -16
- data/lib/sequel/model/base.rb +11 -6
- data/lib/sequel/plugins/active_model.rb +13 -1
- data/lib/sequel/plugins/composition.rb +43 -10
- data/lib/sequel/plugins/many_through_many.rb +4 -1
- data/lib/sequel/plugins/nested_attributes.rb +65 -10
- data/lib/sequel/plugins/serialization.rb +13 -8
- data/lib/sequel/plugins/serialization_modification_detection.rb +22 -10
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/mssql_spec.rb +33 -10
- data/spec/adapters/mysql_spec.rb +111 -91
- data/spec/adapters/oracle_spec.rb +18 -0
- data/spec/core/database_spec.rb +1 -0
- data/spec/core/dataset_spec.rb +110 -15
- data/spec/extensions/active_model_spec.rb +13 -0
- data/spec/extensions/many_through_many_spec.rb +14 -14
- data/spec/extensions/query_spec.rb +6 -0
- data/spec/extensions/serialization_modification_detection_spec.rb +36 -1
- data/spec/extensions/serialization_spec.rb +9 -0
- data/spec/integration/associations_test.rb +278 -154
- data/spec/integration/dataset_test.rb +39 -2
- data/spec/integration/plugin_test.rb +63 -3
- data/spec/integration/prepared_statement_test.rb +10 -3
- data/spec/integration/schema_test.rb +61 -14
- data/spec/integration/transaction_test.rb +10 -0
- data/spec/model/associations_spec.rb +170 -80
- data/spec/model/hooks_spec.rb +40 -0
- 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
|
-
|
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|
|
data/lib/sequel/adapters/mock.rb
CHANGED
@@ -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 =
|
240
|
-
|
241
|
-
|
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
|
-
|
65
|
-
|
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
|
-
|
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
|
-
:
|
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 :
|
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
|
-
|
34
|
-
|
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
|
-
|
39
|
-
|
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
|
-
|
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
|
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
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
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
|
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)
|
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
|