sequel 3.13.0 → 3.14.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 +36 -0
- data/doc/release_notes/3.14.0.txt +118 -0
- data/lib/sequel/adapters/oracle.rb +7 -2
- data/lib/sequel/adapters/shared/mssql.rb +9 -3
- data/lib/sequel/connection_pool/sharded_threaded.rb +1 -1
- data/lib/sequel/connection_pool/threaded.rb +3 -3
- data/lib/sequel/database/connecting.rb +47 -11
- data/lib/sequel/database/dataset.rb +17 -6
- data/lib/sequel/database/dataset_defaults.rb +15 -3
- data/lib/sequel/database/logging.rb +4 -3
- data/lib/sequel/database/misc.rb +33 -21
- data/lib/sequel/database/query.rb +61 -22
- data/lib/sequel/database/schema_generator.rb +108 -45
- data/lib/sequel/database/schema_methods.rb +8 -5
- data/lib/sequel/dataset/actions.rb +194 -45
- data/lib/sequel/dataset/features.rb +1 -1
- data/lib/sequel/dataset/graph.rb +51 -43
- data/lib/sequel/dataset/misc.rb +29 -5
- data/lib/sequel/dataset/mutation.rb +0 -1
- data/lib/sequel/dataset/prepared_statements.rb +14 -2
- data/lib/sequel/dataset/query.rb +268 -125
- data/lib/sequel/dataset/sql.rb +33 -44
- data/lib/sequel/extensions/migration.rb +3 -2
- data/lib/sequel/extensions/pagination.rb +1 -1
- data/lib/sequel/model/associations.rb +89 -87
- data/lib/sequel/model/base.rb +386 -109
- data/lib/sequel/model/errors.rb +15 -1
- data/lib/sequel/model/exceptions.rb +3 -3
- data/lib/sequel/model/inflections.rb +2 -2
- data/lib/sequel/model/plugins.rb +9 -5
- data/lib/sequel/plugins/rcte_tree.rb +43 -15
- data/lib/sequel/plugins/schema.rb +6 -5
- data/lib/sequel/plugins/serialization.rb +1 -1
- data/lib/sequel/plugins/single_table_inheritance.rb +1 -1
- data/lib/sequel/plugins/tree.rb +33 -1
- data/lib/sequel/timezones.rb +16 -10
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/mssql_spec.rb +36 -2
- data/spec/adapters/mysql_spec.rb +4 -4
- data/spec/adapters/postgres_spec.rb +1 -1
- data/spec/adapters/spec_helper.rb +2 -2
- data/spec/core/database_spec.rb +8 -1
- data/spec/core/dataset_spec.rb +36 -1
- data/spec/extensions/pagination_spec.rb +1 -1
- data/spec/extensions/rcte_tree_spec.rb +40 -8
- data/spec/extensions/schema_spec.rb +5 -0
- data/spec/extensions/serialization_spec.rb +4 -4
- data/spec/extensions/single_table_inheritance_spec.rb +7 -0
- data/spec/extensions/tree_spec.rb +36 -0
- data/spec/integration/dataset_test.rb +19 -0
- data/spec/integration/prepared_statement_test.rb +2 -2
- data/spec/integration/schema_test.rb +1 -1
- data/spec/integration/spec_helper.rb +4 -4
- data/spec/integration/timezone_test.rb +27 -21
- data/spec/model/associations_spec.rb +5 -5
- data/spec/model/dataset_methods_spec.rb +13 -0
- data/spec/model/hooks_spec.rb +31 -0
- data/spec/model/record_spec.rb +24 -7
- data/spec/model/validations_spec.rb +9 -4
- metadata +6 -4
data/lib/sequel/model/errors.rb
CHANGED
@@ -5,17 +5,24 @@ module Sequel
|
|
5
5
|
class Errors < ::Hash
|
6
6
|
ATTRIBUTE_JOINER = ' and '.freeze
|
7
7
|
|
8
|
-
# Assign an array of messages for each attribute on access
|
8
|
+
# Assign an array of messages for each attribute on access.
|
9
|
+
# Using this message is discouraged in new code, use +add+
|
10
|
+
# to add new error messages, and +on+ to check existing
|
11
|
+
# error messages.
|
9
12
|
def [](k)
|
10
13
|
has_key?(k) ? super : (self[k] = [])
|
11
14
|
end
|
12
15
|
|
13
16
|
# Adds an error for the given attribute.
|
17
|
+
#
|
18
|
+
# errors.add(:name, 'is not valid') if name == 'invalid'
|
14
19
|
def add(att, msg)
|
15
20
|
self[att] << msg
|
16
21
|
end
|
17
22
|
|
18
23
|
# Return the total number of error messages.
|
24
|
+
#
|
25
|
+
# errors.count # => 3
|
19
26
|
def count
|
20
27
|
values.inject(0){|m, v| m + v.length}
|
21
28
|
end
|
@@ -26,6 +33,10 @@ module Sequel
|
|
26
33
|
end
|
27
34
|
|
28
35
|
# Returns an array of fully-formatted error messages.
|
36
|
+
#
|
37
|
+
# errors.full_messages
|
38
|
+
# # => ['name is not valid',
|
39
|
+
# # 'hometown is not at least 2 letters']
|
29
40
|
def full_messages
|
30
41
|
inject([]) do |m, kv|
|
31
42
|
att, errors = *kv
|
@@ -36,6 +47,9 @@ module Sequel
|
|
36
47
|
|
37
48
|
# Returns the array of errors for the given attribute, or nil
|
38
49
|
# if there are no errors for the attribute.
|
50
|
+
#
|
51
|
+
# errors.on(:name) # => ['name is not valid']
|
52
|
+
# errors.on(:id) # => nil
|
39
53
|
def on(att)
|
40
54
|
if v = fetch(att, nil) and !v.empty?
|
41
55
|
v
|
@@ -1,12 +1,12 @@
|
|
1
1
|
module Sequel
|
2
|
-
# Exception class raised when raise_on_save_failure is set and a before hook returns false
|
2
|
+
# Exception class raised when +raise_on_save_failure+ is set and a before hook returns false
|
3
3
|
class BeforeHookFailed < Error; end
|
4
4
|
|
5
|
-
# Exception class raised when require_modification is set and an UPDATE or DELETE statement to modify the dataset doesn't
|
5
|
+
# Exception class raised when +require_modification+ is set and an UPDATE or DELETE statement to modify the dataset doesn't
|
6
6
|
# modify a single row.
|
7
7
|
class NoExistingObject < Error; end
|
8
8
|
|
9
|
-
# Exception class raised when raise_on_save_failure is set and validation fails
|
9
|
+
# Exception class raised when +raise_on_save_failure+ is set and validation fails
|
10
10
|
class ValidationFailed < Error
|
11
11
|
def initialize(errors)
|
12
12
|
if errors.respond_to?(:full_messages)
|
@@ -39,10 +39,10 @@ module Sequel
|
|
39
39
|
@plurals, @singulars, @uncountables = [], [], []
|
40
40
|
|
41
41
|
class << self
|
42
|
-
# Array of
|
42
|
+
# Array of two element arrays, first containing a regex, and the second containing a substitution pattern, used for plurization.
|
43
43
|
attr_reader :plurals
|
44
44
|
|
45
|
-
# Array of
|
45
|
+
# Array of two element arrays, first containing a regex, and the second containing a substitution pattern, used for singularization.
|
46
46
|
attr_reader :singulars
|
47
47
|
|
48
48
|
# Array of strings for words were the singular form is the same as the plural form
|
data/lib/sequel/model/plugins.rb
CHANGED
@@ -6,7 +6,8 @@ module Sequel
|
|
6
6
|
# * A singleton method named apply, which takes a model,
|
7
7
|
# additional arguments, and an optional block. This is called
|
8
8
|
# the first time the plugin is loaded for this model (unless it was
|
9
|
-
# already loaded by an ancestor class),
|
9
|
+
# already loaded by an ancestor class), before including/extending
|
10
|
+
# any modules, with the arguments
|
10
11
|
# and block provided to the call to Model.plugin.
|
11
12
|
# * A module inside the plugin module named InstanceMethods,
|
12
13
|
# which will be included in the model class.
|
@@ -16,7 +17,8 @@ module Sequel
|
|
16
17
|
# which will extend the model's dataset.
|
17
18
|
# * A singleton method named configure, which takes a model,
|
18
19
|
# additional arguments, and an optional block. This is called
|
19
|
-
# every time the Model.plugin method is called
|
20
|
+
# every time the Model.plugin method is called, after including/extending
|
21
|
+
# any modules.
|
20
22
|
module Plugins
|
21
23
|
end
|
22
24
|
|
@@ -27,7 +29,6 @@ module Sequel
|
|
27
29
|
# sequel_#{plugin}, and then attempt to load the module using a
|
28
30
|
# the camelized plugin name under Sequel::Plugins.
|
29
31
|
def self.plugin(plugin, *args, &blk)
|
30
|
-
arg = args.first
|
31
32
|
m = plugin.is_a?(Module) ? plugin : plugin_module(plugin)
|
32
33
|
unless @plugins.include?(m)
|
33
34
|
@plugins << m
|
@@ -45,13 +46,16 @@ module Sequel
|
|
45
46
|
end
|
46
47
|
|
47
48
|
module ClassMethods
|
48
|
-
# Array of
|
49
|
+
# Array of plugin modules loaded by this class
|
50
|
+
#
|
51
|
+
# Sequel::Model.plugins
|
52
|
+
# # => [Sequel::Model, Sequel::Model::Associations]
|
49
53
|
attr_reader :plugins
|
50
54
|
|
51
55
|
private
|
52
56
|
|
53
57
|
# Returns the module for the specified plugin. If the module is not
|
54
|
-
# defined, the corresponding plugin
|
58
|
+
# defined, the corresponding plugin required.
|
55
59
|
def plugin_module(plugin)
|
56
60
|
module_name = plugin.to_s.gsub(/(^|_)(.)/){|x| x[-1..-1].upcase}
|
57
61
|
if !Sequel::Plugins.const_defined?(module_name) ||
|
@@ -32,9 +32,7 @@ module Sequel
|
|
32
32
|
#
|
33
33
|
# = Usage
|
34
34
|
#
|
35
|
-
# The rcte_tree plugin
|
36
|
-
# instance, or dataset modules. It only has a single apply method, which
|
37
|
-
# adds four associations to the model: parent, children, ancestors, and
|
35
|
+
# The rcte_tree plugin adds four associations to the model: parent, children, ancestors, and
|
38
36
|
# descendants. Both the parent and children are fairly standard many_to_one
|
39
37
|
# and one_to_many associations, respectively. However, the ancestors and
|
40
38
|
# descendants associations are special. Both the ancestors and descendants
|
@@ -118,9 +116,17 @@ module Sequel
|
|
118
116
|
a[:read_only] = true unless a.has_key?(:read_only)
|
119
117
|
a[:eager_loader_key] = key
|
120
118
|
a[:dataset] ||= proc do
|
121
|
-
model.
|
122
|
-
|
123
|
-
|
119
|
+
base_ds = model.filter(prkey=>send(key))
|
120
|
+
recursive_ds = model.join(t, key=>prkey)
|
121
|
+
if c = a[:conditions]
|
122
|
+
(base_ds, recursive_ds) = [base_ds, recursive_ds].collect do |ds|
|
123
|
+
(c.is_a?(Array) && !Sequel.condition_specifier?(c)) ? ds.filter(*c) : ds.filter(c)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
table_alias = model.dataset.schema_and_table(model.table_name)[1].to_sym
|
127
|
+
model.from(t => table_alias).
|
128
|
+
with_recursive(t, base_ds,
|
129
|
+
recursive_ds.
|
124
130
|
select(c_all))
|
125
131
|
end
|
126
132
|
aal = Array(a[:after_load])
|
@@ -156,12 +162,20 @@ module Sequel
|
|
156
162
|
obj.associations[parent] = nil
|
157
163
|
end
|
158
164
|
r = model.association_reflection(ancestors)
|
165
|
+
base_case = model.filter(prkey=>id_map.keys).
|
166
|
+
select(SQL::AliasedExpression.new(prkey, ka), c_all)
|
167
|
+
recursive_case = model.join(t, key=>prkey).
|
168
|
+
select(SQL::QualifiedIdentifier.new(t, ka), c_all)
|
169
|
+
if c = r[:conditions]
|
170
|
+
(base_case, recursive_case) = [base_case, recursive_case].collect do |ds|
|
171
|
+
(c.is_a?(Array) && !Sequel.condition_specifier?(c)) ? ds.filter(*c) : ds.filter(c)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
table_alias = model.dataset.schema_and_table(model.table_name)[1].to_sym
|
159
175
|
model.eager_loading_dataset(r,
|
160
|
-
model.from(t).
|
161
|
-
with_recursive(t,
|
162
|
-
|
163
|
-
model.join(t, key=>prkey).
|
164
|
-
select(SQL::QualifiedIdentifier.new(t, ka), c_all)),
|
176
|
+
model.from(t => table_alias).
|
177
|
+
with_recursive(t, base_case,
|
178
|
+
recursive_case),
|
165
179
|
r.select,
|
166
180
|
eo[:associations], eo).all do |obj|
|
167
181
|
opk = obj[prkey]
|
@@ -197,9 +211,17 @@ module Sequel
|
|
197
211
|
d[:read_only] = true unless d.has_key?(:read_only)
|
198
212
|
la = d[:level_alias] ||= :x_level_x
|
199
213
|
d[:dataset] ||= proc do
|
200
|
-
model.
|
201
|
-
|
202
|
-
|
214
|
+
base_ds = model.filter(key=>send(prkey))
|
215
|
+
recursive_ds = model.join(t, prkey=>key)
|
216
|
+
if c = d[:conditions]
|
217
|
+
(base_ds, recursive_ds) = [base_ds, recursive_ds].collect do |ds|
|
218
|
+
(c.is_a?(Array) && !Sequel.condition_specifier?(c)) ? ds.filter(*c) : ds.filter(c)
|
219
|
+
end
|
220
|
+
end
|
221
|
+
table_alias = model.dataset.schema_and_table(model.table_name)[1].to_sym
|
222
|
+
model.from(t => table_alias).
|
223
|
+
with_recursive(t, base_ds,
|
224
|
+
recursive_ds.
|
203
225
|
select(SQL::ColumnAll.new(model.table_name)))
|
204
226
|
end
|
205
227
|
dal = Array(d[:after_load])
|
@@ -238,6 +260,11 @@ module Sequel
|
|
238
260
|
select(SQL::AliasedExpression.new(key, ka), c_all)
|
239
261
|
recursive_case = model.join(t, prkey=>key).
|
240
262
|
select(SQL::QualifiedIdentifier.new(t, ka), c_all)
|
263
|
+
if c = r[:conditions]
|
264
|
+
(base_case, recursive_case) = [base_case, recursive_case].collect do |ds|
|
265
|
+
(c.is_a?(Array) && !Sequel.condition_specifier?(c)) ? ds.filter(*c) : ds.filter(c)
|
266
|
+
end
|
267
|
+
end
|
241
268
|
if associations.is_a?(Integer)
|
242
269
|
level = associations
|
243
270
|
no_cache_level = level - 1
|
@@ -245,8 +272,9 @@ module Sequel
|
|
245
272
|
base_case = base_case.select_more(SQL::AliasedExpression.new(0, la))
|
246
273
|
recursive_case = recursive_case.select_more(SQL::AliasedExpression.new(SQL::QualifiedIdentifier.new(t, la) + 1, la)).filter(SQL::QualifiedIdentifier.new(t, la) < level - 1)
|
247
274
|
end
|
275
|
+
table_alias = model.dataset.schema_and_table(model.table_name)[1].to_sym
|
248
276
|
model.eager_loading_dataset(r,
|
249
|
-
model.from(t).with_recursive(t, base_case, recursive_case),
|
277
|
+
model.from(t => table_alias).with_recursive(t, base_case, recursive_case),
|
250
278
|
r.select,
|
251
279
|
associations, eo).all do |obj|
|
252
280
|
if level
|
@@ -19,7 +19,8 @@ module Sequel
|
|
19
19
|
module Schema
|
20
20
|
module ClassMethods
|
21
21
|
# Creates table, using the column information from set_schema.
|
22
|
-
def create_table
|
22
|
+
def create_table(*args, &block)
|
23
|
+
set_schema(*args, &block) if block_given?
|
23
24
|
db.create_table(table_name, :generator=>@schema)
|
24
25
|
@db_schema = get_db_schema(true)
|
25
26
|
columns
|
@@ -27,14 +28,14 @@ module Sequel
|
|
27
28
|
|
28
29
|
# Drops the table if it exists and then runs create_table. Should probably
|
29
30
|
# not be used except in testing.
|
30
|
-
def create_table!
|
31
|
+
def create_table!(*args, &block)
|
31
32
|
drop_table rescue nil
|
32
|
-
create_table
|
33
|
+
create_table(*args, &block)
|
33
34
|
end
|
34
35
|
|
35
36
|
# Creates the table unless the table already exists
|
36
|
-
def create_table?
|
37
|
-
create_table unless table_exists?
|
37
|
+
def create_table?(*args, &block)
|
38
|
+
create_table(*args, &block) unless table_exists?
|
38
39
|
end
|
39
40
|
|
40
41
|
# Drops table.
|
@@ -157,7 +157,7 @@ module Sequel
|
|
157
157
|
module InstanceMethods
|
158
158
|
# Set the sti_key column based on the sti_key_map.
|
159
159
|
def before_create
|
160
|
-
send("#{model.sti_key}=", model.sti_key_map[model]) unless
|
160
|
+
send("#{model.sti_key}=", model.sti_key_map[model]) unless self[model.sti_key]
|
161
161
|
super
|
162
162
|
end
|
163
163
|
end
|
data/lib/sequel/plugins/tree.rb
CHANGED
@@ -8,6 +8,9 @@ module Sequel
|
|
8
8
|
#
|
9
9
|
# Optionally, a column to control order of nodes returned can be specified
|
10
10
|
# by passing column name via :order.
|
11
|
+
#
|
12
|
+
# If you pass true for the :single_root option, the class will ensure there is
|
13
|
+
# only ever one root in the tree.
|
11
14
|
#
|
12
15
|
# Examples:
|
13
16
|
#
|
@@ -40,6 +43,8 @@ module Sequel
|
|
40
43
|
chi = opts.merge(opts.fetch(:children, {}))
|
41
44
|
children = chi.fetch(:name, :children)
|
42
45
|
model.one_to_many children, chi
|
46
|
+
|
47
|
+
model.plugin SingleRoot if opts[:single_root]
|
43
48
|
end
|
44
49
|
|
45
50
|
module ClassMethods
|
@@ -88,7 +93,7 @@ module Sequel
|
|
88
93
|
#
|
89
94
|
# subchild1.ancestors # => [child1, root]
|
90
95
|
def descendants
|
91
|
-
nodes =
|
96
|
+
nodes = children.dup
|
92
97
|
nodes.each{|child| nodes.concat(child.descendants)}
|
93
98
|
nodes
|
94
99
|
end
|
@@ -99,6 +104,11 @@ module Sequel
|
|
99
104
|
ancestors.last || self
|
100
105
|
end
|
101
106
|
|
107
|
+
# Returns true if this is a root node, false otherwise.
|
108
|
+
def root?
|
109
|
+
!new? && self[model.parent_column].nil?
|
110
|
+
end
|
111
|
+
|
102
112
|
# Returns all siblings and a reference to the current node.
|
103
113
|
#
|
104
114
|
# subchild1.self_and_siblings # => [subchild1, subchild2]
|
@@ -113,6 +123,28 @@ module Sequel
|
|
113
123
|
self_and_siblings - [self]
|
114
124
|
end
|
115
125
|
end
|
126
|
+
|
127
|
+
# Plugin included when :single_root option is passed
|
128
|
+
module SingleRoot
|
129
|
+
module ClassMethods
|
130
|
+
# Returns the single root node.
|
131
|
+
def root
|
132
|
+
roots_dataset.first
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
module InstanceMethods
|
137
|
+
# Hook that prevents a second root from being created.
|
138
|
+
def before_save
|
139
|
+
if self[model.parent_column].nil? && (root = model.root) && pk != root.pk
|
140
|
+
raise TreeMultipleRootError, "there is already a root #{model.name} defined"
|
141
|
+
end
|
142
|
+
super
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
class TreeMultipleRootError < Error; end
|
116
148
|
end
|
117
149
|
end
|
118
150
|
end
|
data/lib/sequel/timezones.rb
CHANGED
@@ -1,10 +1,4 @@
|
|
1
1
|
module Sequel
|
2
|
-
# The offset of the current time zone from UTC, in seconds.
|
3
|
-
LOCAL_DATETIME_OFFSET_SECS = Time.now.utc_offset
|
4
|
-
|
5
|
-
# The offset of the current time zone from UTC, as a fraction of a day.
|
6
|
-
LOCAL_DATETIME_OFFSET = respond_to?(:Rational, true) ? Rational(LOCAL_DATETIME_OFFSET_SECS, 60*60*24) : LOCAL_DATETIME_OFFSET_SECS/60/60/24.0
|
7
|
-
|
8
2
|
@application_timezone = nil
|
9
3
|
@database_timezone = nil
|
10
4
|
@typecast_timezone = nil
|
@@ -75,7 +69,8 @@ module Sequel
|
|
75
69
|
when :utc, nil
|
76
70
|
v # DateTime assumes UTC if no offset is given
|
77
71
|
when :local
|
78
|
-
v
|
72
|
+
offset = local_offset_for_datetime(v)
|
73
|
+
v.new_offset(offset) - offset
|
79
74
|
else
|
80
75
|
convert_input_datetime_other(v, input_timezone)
|
81
76
|
end
|
@@ -103,7 +98,7 @@ module Sequel
|
|
103
98
|
v2 = convert_input_datetime_no_offset(v2, input_timezone)
|
104
99
|
else
|
105
100
|
# Time assumes local time if no offset is given
|
106
|
-
v2 = v2.getutc +
|
101
|
+
v2 = v2.getutc + v2.utc_offset if input_timezone == :utc
|
107
102
|
end
|
108
103
|
v2
|
109
104
|
end
|
@@ -132,7 +127,7 @@ module Sequel
|
|
132
127
|
raise InvalidValue, "Invalid convert_input_timestamp type: #{v.inspect}"
|
133
128
|
end
|
134
129
|
end
|
135
|
-
|
130
|
+
|
136
131
|
# Convert the given +DateTime+ to the given output_timezone that is not supported
|
137
132
|
# by default (i.e. one other than +nil+, <tt>:local</tt>, or <tt>:utc</tt>). Raises an +InvalidValue+ by default.
|
138
133
|
# Can be overridden in extensions.
|
@@ -148,7 +143,7 @@ module Sequel
|
|
148
143
|
when :utc
|
149
144
|
v.new_offset(0)
|
150
145
|
when :local
|
151
|
-
v.new_offset(
|
146
|
+
v.new_offset(local_offset_for_datetime(v))
|
152
147
|
else
|
153
148
|
convert_output_datetime_other(v, output_timezone)
|
154
149
|
end
|
@@ -178,6 +173,17 @@ module Sequel
|
|
178
173
|
def convert_timezone_setter_arg(tz)
|
179
174
|
tz
|
180
175
|
end
|
176
|
+
|
177
|
+
# Takes a DateTime dt, and returns the correct local offset for that dt, daylight savings included.
|
178
|
+
def local_offset_for_datetime(dt)
|
179
|
+
time_offset_to_datetime_offset Time.local(dt.year, dt.month, dt.day, dt.hour, dt.min, dt.sec).utc_offset
|
180
|
+
end
|
181
|
+
|
182
|
+
# Caches offset conversions to avoid excess Rational math.
|
183
|
+
def time_offset_to_datetime_offset(offset_secs)
|
184
|
+
@local_offsets ||= {}
|
185
|
+
@local_offsets[offset_secs] ||= respond_to?(:Rational, true) ? Rational(offset_secs, 60*60*24) : offset_secs/60/60/24.0
|
186
|
+
end
|
181
187
|
end
|
182
188
|
|
183
189
|
extend Timezones
|
data/lib/sequel/version.rb
CHANGED
@@ -3,7 +3,7 @@ module Sequel
|
|
3
3
|
MAJOR = 3
|
4
4
|
# The minor version of Sequel. Bumped for every non-patch level
|
5
5
|
# release, generally around once a month.
|
6
|
-
MINOR =
|
6
|
+
MINOR = 14
|
7
7
|
# The tiny version of Sequel. Usually 0, only bumped for bugfix
|
8
8
|
# releases that fix regressions from previous versions.
|
9
9
|
TINY = 0
|
data/spec/adapters/mssql_spec.rb
CHANGED
@@ -63,8 +63,9 @@ context "MSSQL Dataset#join_table" do
|
|
63
63
|
specify "should emulate the USING clause with ON" do
|
64
64
|
MSSQL_DB[:items].join(:categories, [:id]).sql.should ==
|
65
65
|
'SELECT * FROM ITEMS INNER JOIN CATEGORIES ON (CATEGORIES.ID = ITEMS.ID)'
|
66
|
-
|
67
|
-
|
66
|
+
['SELECT * FROM ITEMS INNER JOIN CATEGORIES ON ((CATEGORIES.ID1 = ITEMS.ID1) AND (CATEGORIES.ID2 = ITEMS.ID2))',
|
67
|
+
'SELECT * FROM ITEMS INNER JOIN CATEGORIES ON ((CATEGORIES.ID2 = ITEMS.ID2) AND (CATEGORIES.ID1 = ITEMS.ID1))'].
|
68
|
+
should include(MSSQL_DB[:items].join(:categories, [:id1, :id2]).sql)
|
68
69
|
MSSQL_DB[:items___i].join(:categories___c, [:id]).sql.should ==
|
69
70
|
'SELECT * FROM ITEMS AS I INNER JOIN CATEGORIES AS C ON (C.ID = I.ID)'
|
70
71
|
end
|
@@ -401,3 +402,36 @@ context "A MSSQL database" do
|
|
401
402
|
(s[:max_chars] || s[:column_size]).should == 2
|
402
403
|
end
|
403
404
|
end
|
405
|
+
|
406
|
+
context "MSSQL::Database#rename_table" do
|
407
|
+
specify "should work on non-schema bound tables which need escaping" do
|
408
|
+
MSSQL_DB.quote_identifiers = true
|
409
|
+
MSSQL_DB.create_table! :'foo bar' do
|
410
|
+
text :name
|
411
|
+
end
|
412
|
+
MSSQL_DB.drop_table :baz rescue nil
|
413
|
+
proc { MSSQL_DB.rename_table 'foo bar', 'baz' }.should_not raise_error
|
414
|
+
end
|
415
|
+
|
416
|
+
specify "should workd on schema bound tables" do
|
417
|
+
MSSQL_DB.execute(<<-SQL)
|
418
|
+
IF NOT EXISTS (SELECT * FROM sys.schemas WHERE name = 'MY')
|
419
|
+
EXECUTE sp_executesql N'create schema MY'
|
420
|
+
SQL
|
421
|
+
MSSQL_DB.create_table! :MY__foo do
|
422
|
+
text :name
|
423
|
+
end
|
424
|
+
proc { MSSQL_DB.rename_table :MY__foo, :MY__bar }.should_not raise_error
|
425
|
+
proc { MSSQL_DB.rename_table :MY__bar, :foo }.should_not raise_error
|
426
|
+
end
|
427
|
+
end
|
428
|
+
|
429
|
+
context "MSSQL::Dataset#count" do
|
430
|
+
specify "should work with a distinct query with an order clause" do
|
431
|
+
MSSQL_DB.create_table!(:items){String :name; Integer :value}
|
432
|
+
MSSQL_DB[:items].insert(:name => "name", :value => 1)
|
433
|
+
MSSQL_DB[:items].insert(:name => "name", :value => 1)
|
434
|
+
MSSQL_DB[:items].select(:name, :value).distinct.order(:name).count.should == 1
|
435
|
+
MSSQL_DB[:items].select(:name, :value).group(:name, :value).order(:name).count.should == 1
|
436
|
+
end
|
437
|
+
end
|