sequel 3.5.0 → 3.6.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +108 -0
- data/README.rdoc +25 -14
- data/Rakefile +20 -1
- data/doc/advanced_associations.rdoc +61 -64
- data/doc/cheat_sheet.rdoc +16 -7
- data/doc/opening_databases.rdoc +3 -3
- data/doc/prepared_statements.rdoc +1 -1
- data/doc/reflection.rdoc +2 -1
- data/doc/release_notes/3.6.0.txt +366 -0
- data/doc/schema.rdoc +19 -14
- data/lib/sequel/adapters/amalgalite.rb +5 -27
- data/lib/sequel/adapters/jdbc.rb +13 -3
- data/lib/sequel/adapters/jdbc/h2.rb +17 -0
- data/lib/sequel/adapters/jdbc/mysql.rb +20 -7
- data/lib/sequel/adapters/mysql.rb +4 -3
- data/lib/sequel/adapters/oracle.rb +1 -1
- data/lib/sequel/adapters/postgres.rb +87 -28
- data/lib/sequel/adapters/shared/mssql.rb +47 -6
- data/lib/sequel/adapters/shared/mysql.rb +12 -31
- data/lib/sequel/adapters/shared/postgres.rb +15 -12
- data/lib/sequel/adapters/shared/sqlite.rb +18 -0
- data/lib/sequel/adapters/sqlite.rb +1 -16
- data/lib/sequel/connection_pool.rb +1 -1
- data/lib/sequel/core.rb +1 -1
- data/lib/sequel/database.rb +1 -1
- data/lib/sequel/database/schema_generator.rb +2 -0
- data/lib/sequel/database/schema_sql.rb +1 -1
- data/lib/sequel/dataset.rb +5 -179
- data/lib/sequel/dataset/actions.rb +123 -0
- data/lib/sequel/dataset/convenience.rb +18 -10
- data/lib/sequel/dataset/features.rb +65 -0
- data/lib/sequel/dataset/prepared_statements.rb +29 -23
- data/lib/sequel/dataset/query.rb +429 -0
- data/lib/sequel/dataset/sql.rb +67 -435
- data/lib/sequel/model/associations.rb +77 -13
- data/lib/sequel/model/base.rb +30 -8
- data/lib/sequel/model/errors.rb +4 -4
- data/lib/sequel/plugins/caching.rb +38 -15
- data/lib/sequel/plugins/force_encoding.rb +18 -7
- data/lib/sequel/plugins/hook_class_methods.rb +4 -0
- data/lib/sequel/plugins/many_through_many.rb +1 -1
- data/lib/sequel/plugins/nested_attributes.rb +40 -11
- data/lib/sequel/plugins/serialization.rb +17 -3
- data/lib/sequel/plugins/validation_helpers.rb +65 -18
- data/lib/sequel/sql.rb +23 -1
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/mssql_spec.rb +96 -10
- data/spec/adapters/mysql_spec.rb +19 -0
- data/spec/adapters/postgres_spec.rb +65 -2
- data/spec/adapters/sqlite_spec.rb +10 -0
- data/spec/core/core_sql_spec.rb +9 -0
- data/spec/core/database_spec.rb +8 -4
- data/spec/core/dataset_spec.rb +122 -29
- data/spec/core/expression_filters_spec.rb +17 -0
- data/spec/extensions/caching_spec.rb +43 -3
- data/spec/extensions/force_encoding_spec.rb +43 -1
- data/spec/extensions/nested_attributes_spec.rb +55 -2
- data/spec/extensions/validation_helpers_spec.rb +71 -0
- data/spec/integration/associations_test.rb +281 -0
- data/spec/integration/dataset_test.rb +383 -9
- data/spec/integration/eager_loader_test.rb +0 -65
- data/spec/integration/model_test.rb +110 -0
- data/spec/integration/plugin_test.rb +306 -0
- data/spec/integration/prepared_statement_test.rb +32 -0
- data/spec/integration/schema_test.rb +8 -3
- data/spec/integration/spec_helper.rb +1 -25
- data/spec/model/association_reflection_spec.rb +38 -0
- data/spec/model/associations_spec.rb +184 -8
- data/spec/model/eager_loading_spec.rb +23 -0
- data/spec/model/model_spec.rb +8 -0
- data/spec/model/record_spec.rb +84 -1
- metadata +9 -2
@@ -34,18 +34,29 @@ module Sequel
|
|
34
34
|
super
|
35
35
|
subclass.forced_encoding = forced_encoding
|
36
36
|
end
|
37
|
-
|
38
|
-
# Force the encoding of all strings for the given row to the model's
|
39
|
-
# forced_encoding.
|
40
|
-
def load(row)
|
41
|
-
row.values.each{|v| v.force_encoding(forced_encoding) if v.is_a?(String)} if forced_encoding
|
42
|
-
super
|
43
|
-
end
|
44
37
|
end
|
45
38
|
|
46
39
|
module InstanceMethods
|
40
|
+
# Allow the force encoding plugin to work with the identity_map
|
41
|
+
# plugin by typecasting new values.
|
42
|
+
def merge_db_update(row)
|
43
|
+
super(force_hash_encoding(row))
|
44
|
+
end
|
45
|
+
|
47
46
|
private
|
48
47
|
|
48
|
+
# Force the encoding for all string values in the given row hash.
|
49
|
+
def force_hash_encoding(row)
|
50
|
+
fe = model.forced_encoding
|
51
|
+
row.values.each{|v| v.force_encoding(fe) if v.is_a?(String)} if fe
|
52
|
+
row
|
53
|
+
end
|
54
|
+
|
55
|
+
# Force the encoding of all string values when setting the instance's values.
|
56
|
+
def set_values(row)
|
57
|
+
super(force_hash_encoding(row))
|
58
|
+
end
|
59
|
+
|
49
60
|
# Force the encoding of all returned strings to the model's forced_encoding.
|
50
61
|
def typecast_value(column, value)
|
51
62
|
s = super
|
@@ -19,6 +19,10 @@ module Sequel
|
|
19
19
|
# return false if super == false
|
20
20
|
# self.created_at = Time.now
|
21
21
|
# end
|
22
|
+
#
|
23
|
+
# Note that returning false in any before hook block will skip further
|
24
|
+
# before hooks and abort the action. So if a before_save hook block returns
|
25
|
+
# false, future before_save hook blocks are not called, and the save is aborted.
|
22
26
|
module HookClassMethods
|
23
27
|
# Set up the hooks instance variable in the model.
|
24
28
|
def self.apply(model)
|
@@ -156,7 +156,7 @@ module Sequel
|
|
156
156
|
uses_lcks = opts[:uses_left_composite_keys] = left_key.is_a?(Array)
|
157
157
|
left_keys = Array(left_key)
|
158
158
|
left_pk = (opts[:left_primary_key] ||= self.primary_key)
|
159
|
-
left_pks = Array(left_pk)
|
159
|
+
left_pks = opts[:left_primary_keys] = Array(left_pk)
|
160
160
|
opts[:dataset] ||= lambda do
|
161
161
|
ds = opts.associated_class
|
162
162
|
opts.reverse_edges.each{|t| ds = ds.join(t[:table], Array(t[:left]).zip(Array(t[:right])), :table_alias=>t[:alias])}
|
@@ -27,6 +27,8 @@ module Sequel
|
|
27
27
|
|
28
28
|
# Allow nested attributes to be set for the given associations. Options:
|
29
29
|
# * :destroy - Allow destruction of nested records.
|
30
|
+
# * :fields - If provided, should be an Array. Restricts the fields allowed to be
|
31
|
+
# modified through the association_attributes= method to the specific fields given.
|
30
32
|
# * :limit - For *_to_many associations, a limit on the number of records
|
31
33
|
# that will be processed, to prevent denial of service attacks.
|
32
34
|
# * :remove - Allow disassociation of nested records (can remove the associated
|
@@ -68,26 +70,32 @@ module Sequel
|
|
68
70
|
end
|
69
71
|
|
70
72
|
module InstanceMethods
|
71
|
-
# Assume if an instance has nested attributes set on it, that it has
|
72
|
-
# been modified even if none of the instance's columns have been modified.
|
73
|
-
def modified?
|
74
|
-
super || @has_nested_attributes
|
75
|
-
end
|
76
|
-
|
77
73
|
private
|
78
74
|
|
75
|
+
# Check that the keys related to the association are not modified inside the block. Does
|
76
|
+
# not use an ensure block, so callers should be careful.
|
77
|
+
def nested_attributes_check_key_modifications(reflection, obj)
|
78
|
+
keys = reflection.associated_object_keys.map{|x| obj.send(x)}
|
79
|
+
yield
|
80
|
+
raise(Error, "Modifying association dependent key(s) when updating associated objects is not allowed") unless keys == reflection.associated_object_keys.map{|x| obj.send(x)}
|
81
|
+
end
|
82
|
+
|
79
83
|
# Create a new associated object with the given attributes, validate
|
80
84
|
# it when the parent is validated, and save it when the object is saved.
|
85
|
+
# Returns the object created.
|
81
86
|
def nested_attributes_create(reflection, attributes)
|
82
|
-
obj = reflection.associated_class.new
|
87
|
+
obj = reflection.associated_class.new
|
88
|
+
nested_attributes_set_attributes(reflection, obj, attributes)
|
83
89
|
after_validation_hook{validate_associated_object(reflection, obj)}
|
84
90
|
if reflection.returns_array?
|
91
|
+
send(reflection[:name]) << obj
|
85
92
|
after_save_hook{send(reflection.add_method, obj)}
|
86
93
|
else
|
87
94
|
# Don't need to validate the object twice if :validate association option is not false
|
88
95
|
# and don't want to validate it at all if it is false.
|
89
96
|
before_save_hook{send(reflection.setter_method, obj.save(:validate=>false))}
|
90
97
|
end
|
98
|
+
obj
|
91
99
|
end
|
92
100
|
|
93
101
|
# Find an associated object with the matching pk. If a matching option
|
@@ -114,6 +122,7 @@ module Sequel
|
|
114
122
|
|
115
123
|
# Remove the matching associated object from the current object.
|
116
124
|
# If the :destroy option is given, destroy the object after disassociating it.
|
125
|
+
# Returns the object removed, if it exists.
|
117
126
|
def nested_attributes_remove(reflection, pk, opts={})
|
118
127
|
if obj = nested_attributes_find(reflection, pk)
|
119
128
|
before_save_hook do
|
@@ -124,9 +133,20 @@ module Sequel
|
|
124
133
|
end
|
125
134
|
end
|
126
135
|
after_save_hook{obj.destroy} if opts[:destroy]
|
136
|
+
obj
|
127
137
|
end
|
128
138
|
end
|
129
139
|
|
140
|
+
# Set the fields in the obj based on the association, only allowing
|
141
|
+
# specific :fields if configured.
|
142
|
+
def nested_attributes_set_attributes(reflection, obj, attributes)
|
143
|
+
if fields = reflection[:nested_attributes][:fields]
|
144
|
+
obj.set_only(attributes, fields)
|
145
|
+
else
|
146
|
+
obj.set(attributes)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
130
150
|
# Modify the associated object based on the contents of the attribtues hash:
|
131
151
|
# * If a block was given to nested_attributes, call it with the attributes and return immediately if the block returns true.
|
132
152
|
# * If no primary key exists in the attributes hash, create a new object.
|
@@ -135,7 +155,7 @@ module Sequel
|
|
135
155
|
# * Otherwise, update the matching associated object with the contents of the hash.
|
136
156
|
def nested_attributes_setter(reflection, attributes)
|
137
157
|
return if (b = reflection[:nested_attributes][:reject_if]) && b.call(attributes)
|
138
|
-
|
158
|
+
modified!
|
139
159
|
klass = reflection.associated_class
|
140
160
|
if pk = attributes.delete(klass.primary_key) || attributes.delete(klass.primary_key.to_s)
|
141
161
|
if klass.db.send(:typecast_value_boolean, attributes[:_delete] || attributes['_delete']) && reflection[:nested_attributes][:destroy]
|
@@ -153,16 +173,25 @@ module Sequel
|
|
153
173
|
# Update the matching associated object with the attributes,
|
154
174
|
# validating it when the parent object is validated and saving it
|
155
175
|
# when the parent is saved.
|
176
|
+
# Returns the object updated, if it exists.
|
156
177
|
def nested_attributes_update(reflection, pk, attributes)
|
157
178
|
if obj = nested_attributes_find(reflection, pk)
|
158
|
-
obj
|
179
|
+
nested_attributes_update_attributes(reflection, obj, attributes)
|
159
180
|
after_validation_hook{validate_associated_object(reflection, obj)}
|
160
181
|
# Don't need to validate the object twice if :validate association option is not false
|
161
182
|
# and don't want to validate it at all if it is false.
|
162
|
-
after_save_hook{obj.
|
183
|
+
after_save_hook{obj.save_changes(:validate=>false)}
|
184
|
+
obj
|
163
185
|
end
|
164
186
|
end
|
165
|
-
|
187
|
+
|
188
|
+
# Update the attributes for the given object related to the current object through the association.
|
189
|
+
def nested_attributes_update_attributes(reflection, obj, attributes)
|
190
|
+
nested_attributes_check_key_modifications(reflection, obj) do
|
191
|
+
nested_attributes_set_attributes(reflection, obj, attributes)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
166
195
|
# Validate the given associated object, adding any validation error messages from the
|
167
196
|
# given object to the parent object.
|
168
197
|
def validate_associated_object(reflection, obj)
|
@@ -3,7 +3,7 @@ module Sequel
|
|
3
3
|
# Sequel's built in Serialization plugin allows you to keep serialized
|
4
4
|
# ruby objects in the database, while giving you deserialized objects
|
5
5
|
# when you call an accessor.
|
6
|
-
#
|
6
|
+
#
|
7
7
|
# This plugin works by keeping the serialized value in the values, and
|
8
8
|
# adding a @deserialized_values hash. The reader method for serialized columns
|
9
9
|
# will check the @deserialized_values for the value, return it if present,
|
@@ -11,13 +11,27 @@ module Sequel
|
|
11
11
|
# set the @deserialized_values entry. This plugin adds a before_save hook
|
12
12
|
# that serializes all @deserialized_values to @values.
|
13
13
|
#
|
14
|
-
# You can use either marshal or
|
15
|
-
# If you use yaml, you should require
|
14
|
+
# You can use either marshal, yaml, or json as the serialization format.
|
15
|
+
# If you use yaml or json, you should require them by yourself.
|
16
16
|
#
|
17
17
|
# Because of how this plugin works, it must be used inside each model class
|
18
18
|
# that needs serialization, after any set_dataset method calls in that class.
|
19
19
|
# Otherwise, it is possible that the default column accessors will take
|
20
20
|
# precedence.
|
21
|
+
#
|
22
|
+
# == Example
|
23
|
+
#
|
24
|
+
# require 'sequel'
|
25
|
+
# require 'json'
|
26
|
+
# class User < Sequel::Model
|
27
|
+
# plugin :serialization, :json, :permissions
|
28
|
+
# # or
|
29
|
+
# plugin :serialization
|
30
|
+
# serialize_attributes :marshal, :permissions, :attributes
|
31
|
+
# end
|
32
|
+
# user = User.create
|
33
|
+
# user.permissions = { :global => 'read-only' }
|
34
|
+
# user.save
|
21
35
|
module Serialization
|
22
36
|
# Set up the column readers to do deserialization and the column writers
|
23
37
|
# to save the value in deserialized_values.
|
@@ -30,49 +30,75 @@ module Sequel
|
|
30
30
|
# Sequel will attempt to insert a NULL value into the database, instead of using the
|
31
31
|
# database's default.
|
32
32
|
# * :allow_nil - Whether to skip the validation if the value is nil.
|
33
|
-
# * :message - The message to use
|
33
|
+
# * :message - The message to use. Can be a string which is used directly, or a
|
34
|
+
# proc which is called. If the validation method takes a argument before the array of attributes,
|
35
|
+
# that argument is passed as an argument to the proc. The exception is the
|
36
|
+
# validates_not_string method, which doesn't take an argument, but passes
|
37
|
+
# the schema type symbol as the argument to the proc.
|
38
|
+
#
|
39
|
+
# The default validation options for all models can be modified by
|
40
|
+
# changing the values of the Sequel::Plugins::ValidationHelpers::DEFAULT_OPTIONS hash. You
|
41
|
+
# change change the default options on a per model basis
|
42
|
+
# by overriding a private instance method default_validation_helpers_options.
|
34
43
|
module ValidationHelpers
|
44
|
+
# Default validation options used by Sequel. Can be modified to change the error
|
45
|
+
# messages for all models (e.g. for internationalization), or to set certain
|
46
|
+
# default options for validations (e.g. :allow_nil=>true for all validates_format).
|
47
|
+
DEFAULT_OPTIONS = {
|
48
|
+
:exact_length=>{:message=>lambda{|exact| "is not #{exact} characters"}},
|
49
|
+
:format=>{:message=>lambda{|with| 'is invalid'}},
|
50
|
+
:includes=>{:message=>lambda{|set| "is not in range or set: #{set.inspect}"}},
|
51
|
+
:integer=>{:message=>lambda{"is not a number"}},
|
52
|
+
:length_range=>{:message=>lambda{|range| "is too short or too long"}},
|
53
|
+
:max_length=>{:message=>lambda{|max| "is longer than #{max} characters"}},
|
54
|
+
:min_length=>{:message=>lambda{|min| "is shorter than #{min} characters"}},
|
55
|
+
:not_string=>{:message=>lambda{|type| type ? "is not a valid #{type}" : "is a string"}},
|
56
|
+
:numeric=>{:message=>lambda{"is not a number"}},
|
57
|
+
:presence=>{:message=>lambda{"is not present"}},
|
58
|
+
:unique=>{:message=>lambda{'is already taken'}}
|
59
|
+
}
|
60
|
+
|
35
61
|
module InstanceMethods
|
36
62
|
# Check that the attribute values are the given exact length.
|
37
63
|
def validates_exact_length(exact, atts, opts={})
|
38
|
-
|
64
|
+
validatable_attributes_for_type(:exact_length, atts, opts){|a,v,m| validation_error_message(m, exact) unless v && v.length == exact}
|
39
65
|
end
|
40
66
|
|
41
67
|
# Check the string representation of the attribute value(s) against the regular expression with.
|
42
68
|
def validates_format(with, atts, opts={})
|
43
|
-
|
69
|
+
validatable_attributes_for_type(:format, atts, opts){|a,v,m| validation_error_message(m, with) unless v.to_s =~ with}
|
44
70
|
end
|
45
71
|
|
46
72
|
# Check attribute value(s) is included in the given set.
|
47
73
|
def validates_includes(set, atts, opts={})
|
48
|
-
|
74
|
+
validatable_attributes_for_type(:includes, atts, opts){|a,v,m| validation_error_message(m, set) unless set.include?(v)}
|
49
75
|
end
|
50
76
|
|
51
77
|
# Check attribute value(s) string representation is a valid integer.
|
52
78
|
def validates_integer(atts, opts={})
|
53
|
-
|
79
|
+
validatable_attributes_for_type(:integer, atts, opts) do |a,v,m|
|
54
80
|
begin
|
55
81
|
Kernel.Integer(v.to_s)
|
56
82
|
nil
|
57
83
|
rescue
|
58
|
-
m
|
84
|
+
validation_error_message(m)
|
59
85
|
end
|
60
86
|
end
|
61
87
|
end
|
62
88
|
|
63
89
|
# Check that the attribute values length is in the specified range.
|
64
90
|
def validates_length_range(range, atts, opts={})
|
65
|
-
|
91
|
+
validatable_attributes_for_type(:length_range, atts, opts){|a,v,m| validation_error_message(m, range) unless v && range.include?(v.length)}
|
66
92
|
end
|
67
93
|
|
68
94
|
# Check that the attribute values are not longer than the given max length.
|
69
95
|
def validates_max_length(max, atts, opts={})
|
70
|
-
|
96
|
+
validatable_attributes_for_type(:max_length, atts, opts){|a,v,m| validation_error_message(m, max) unless v && v.length <= max}
|
71
97
|
end
|
72
98
|
|
73
99
|
# Check that the attribute values are not shorter than the given min length.
|
74
100
|
def validates_min_length(min, atts, opts={})
|
75
|
-
|
101
|
+
validatable_attributes_for_type(:min_length, atts, opts){|a,v,m| validation_error_message(m, min) unless v && v.length >= min}
|
76
102
|
end
|
77
103
|
|
78
104
|
# Check that the attribute value(s) is not a string. This is generally useful
|
@@ -82,28 +108,24 @@ module Sequel
|
|
82
108
|
# be a string in an invalid format, and if typecasting succeeds, the value will
|
83
109
|
# not be a string.
|
84
110
|
def validates_not_string(atts, opts={})
|
85
|
-
|
86
|
-
next unless v.is_a?(String)
|
87
|
-
next m if m
|
88
|
-
(sch = db_schema[a] and typ = sch[:type]) ? "is not a valid #{typ}" : "is a string"
|
89
|
-
end
|
111
|
+
validatable_attributes_for_type(:not_string, atts, opts){|a,v,m| validation_error_message(m, (db_schema[a]||{})[:type]) if v.is_a?(String)}
|
90
112
|
end
|
91
113
|
|
92
114
|
# Check attribute value(s) string representation is a valid float.
|
93
115
|
def validates_numeric(atts, opts={})
|
94
|
-
|
116
|
+
validatable_attributes_for_type(:numeric, atts, opts) do |a,v,m|
|
95
117
|
begin
|
96
118
|
Kernel.Float(v.to_s)
|
97
119
|
nil
|
98
120
|
rescue
|
99
|
-
m
|
121
|
+
validation_error_message(m)
|
100
122
|
end
|
101
123
|
end
|
102
124
|
end
|
103
125
|
|
104
126
|
# Check attribute value(s) is not considered blank by the database, but allow false values.
|
105
127
|
def validates_presence(atts, opts={})
|
106
|
-
|
128
|
+
validatable_attributes_for_type(:presence, atts, opts){|a,v,m| validation_error_message(m) if model.db.send(:blank_object?, v) && v != false}
|
107
129
|
end
|
108
130
|
|
109
131
|
# Checks that there are no duplicate values in the database for the given
|
@@ -132,7 +154,11 @@ module Sequel
|
|
132
154
|
# Possible Options:
|
133
155
|
# * :message - The message to use (default: 'is already taken')
|
134
156
|
def validates_unique(*atts)
|
135
|
-
|
157
|
+
opts = default_validation_helpers_options(:unique)
|
158
|
+
if atts.last.is_a?(Hash)
|
159
|
+
opts = opts.merge(atts.pop)
|
160
|
+
end
|
161
|
+
message = validation_error_message(opts[:message])
|
136
162
|
atts.each do |a|
|
137
163
|
ds = model.filter(Array(a).map{|x| [x, send(x)]})
|
138
164
|
ds = yield(ds) if block_given?
|
@@ -141,6 +167,14 @@ module Sequel
|
|
141
167
|
end
|
142
168
|
|
143
169
|
private
|
170
|
+
|
171
|
+
# The default options hash for the given type of validation. Can
|
172
|
+
# be overridden on a per-model basis for different per model defaults.
|
173
|
+
# The hash return must include a :message option that is either a
|
174
|
+
# proc or string.
|
175
|
+
def default_validation_helpers_options(type)
|
176
|
+
DEFAULT_OPTIONS[type]
|
177
|
+
end
|
144
178
|
|
145
179
|
# Skip validating any attribute that matches one of the allow_* options.
|
146
180
|
# Otherwise, yield the attribute, value, and passed option :message to
|
@@ -158,6 +192,19 @@ module Sequel
|
|
158
192
|
end
|
159
193
|
end
|
160
194
|
end
|
195
|
+
|
196
|
+
# Merge the given options with the default options for the given type
|
197
|
+
# and call validatable_attributes with the merged options.
|
198
|
+
def validatable_attributes_for_type(type, atts, opts, &block)
|
199
|
+
validatable_attributes(atts, default_validation_helpers_options(type).merge(opts), &block)
|
200
|
+
end
|
201
|
+
|
202
|
+
# The validation error message to use, as a string. If message
|
203
|
+
# is a Proc, call it with the args. Otherwise, assume it is a string and
|
204
|
+
# return it.
|
205
|
+
def validation_error_message(message, *args)
|
206
|
+
message.is_a?(Proc) ? message.call(*args) : message
|
207
|
+
end
|
161
208
|
end
|
162
209
|
end
|
163
210
|
end
|
data/lib/sequel/sql.rb
CHANGED
@@ -6,6 +6,19 @@ module Sequel
|
|
6
6
|
class BasicObject
|
7
7
|
(instance_methods - %w"__id__ __send__ instance_eval == equal?").each{|m| undef_method(m)}
|
8
8
|
end
|
9
|
+
else
|
10
|
+
# If on 1.9, create a Sequel::BasicObject class that is just like the
|
11
|
+
# default BasicObject class, except that missing constants are resolved in
|
12
|
+
# Object. This allows the virtual row support to work with classes
|
13
|
+
# without prefixing them with ::, such as:
|
14
|
+
#
|
15
|
+
# DB[:bonds].filter{maturity_date > Time.now}
|
16
|
+
class BasicObject < ::BasicObject
|
17
|
+
# Lookup missing constants in ::Object
|
18
|
+
def self.const_missing(name)
|
19
|
+
::Object.const_get(name)
|
20
|
+
end
|
21
|
+
end
|
9
22
|
end
|
10
23
|
|
11
24
|
class LiteralString < ::String
|
@@ -36,6 +49,11 @@ module Sequel
|
|
36
49
|
def lit
|
37
50
|
self
|
38
51
|
end
|
52
|
+
|
53
|
+
# Alias for to_s
|
54
|
+
def sql_literal(ds)
|
55
|
+
to_s(ds)
|
56
|
+
end
|
39
57
|
end
|
40
58
|
|
41
59
|
# Represents a complex SQL expression, with a given operator and one
|
@@ -652,6 +670,9 @@ module Sequel
|
|
652
670
|
# required for the prepared statement support
|
653
671
|
class PlaceholderLiteralString < Expression
|
654
672
|
# The arguments that will be subsituted into the placeholders.
|
673
|
+
# Either an array of unnamed placeholders (which will be substituted in
|
674
|
+
# order for ? characters), or a hash of named placeholders (which will be
|
675
|
+
# substituted for :key phrases).
|
655
676
|
attr_reader :args
|
656
677
|
|
657
678
|
# The literal string containing placeholders
|
@@ -663,7 +684,7 @@ module Sequel
|
|
663
684
|
# Create an object with the given string, placeholder arguments, and parens flag.
|
664
685
|
def initialize(str, args, parens=false)
|
665
686
|
@str = str
|
666
|
-
@args = args
|
687
|
+
@args = args.is_a?(Array) && args.length == 1 && (v = args.at(0)).is_a?(Hash) ? v : args
|
667
688
|
@parens = parens
|
668
689
|
end
|
669
690
|
|
@@ -783,6 +804,7 @@ module Sequel
|
|
783
804
|
class SQLArray < Expression
|
784
805
|
# The array of objects this SQLArray wraps
|
785
806
|
attr_reader :array
|
807
|
+
alias to_a array
|
786
808
|
|
787
809
|
# Create an object with the given array.
|
788
810
|
def initialize(array)
|
data/lib/sequel/version.rb
CHANGED
data/spec/adapters/mssql_spec.rb
CHANGED
@@ -6,6 +6,32 @@ unless defined?(MSSQL_DB)
|
|
6
6
|
end
|
7
7
|
INTEGRATION_DB = MSSQL_DB unless defined?(INTEGRATION_DB)
|
8
8
|
|
9
|
+
def MSSQL_DB.sqls
|
10
|
+
(@sqls ||= [])
|
11
|
+
end
|
12
|
+
logger = Object.new
|
13
|
+
def logger.method_missing(m, msg)
|
14
|
+
MSSQL_DB.sqls << msg
|
15
|
+
end
|
16
|
+
MSSQL_DB.logger = logger
|
17
|
+
|
18
|
+
MSSQL_DB.create_table! :test do
|
19
|
+
text :name
|
20
|
+
integer :value, :index => true
|
21
|
+
end
|
22
|
+
MSSQL_DB.create_table! :test2 do
|
23
|
+
text :name
|
24
|
+
integer :value
|
25
|
+
end
|
26
|
+
MSSQL_DB.create_table! :test3 do
|
27
|
+
integer :value
|
28
|
+
timestamp :time
|
29
|
+
end
|
30
|
+
MSSQL_DB.create_table! :test4 do
|
31
|
+
varchar :name, :size => 20
|
32
|
+
varbinary :value
|
33
|
+
end
|
34
|
+
|
9
35
|
context "A MSSQL database" do
|
10
36
|
before do
|
11
37
|
@db = MSSQL_DB
|
@@ -27,6 +53,17 @@ context "A MSSQL database" do
|
|
27
53
|
end
|
28
54
|
end
|
29
55
|
|
56
|
+
context "MSSQL Dataset#join_table" do
|
57
|
+
specify "should emulate the USING clause with ON" do
|
58
|
+
MSSQL_DB[:items].join(:categories, [:id]).sql.should ==
|
59
|
+
'SELECT * FROM ITEMS INNER JOIN CATEGORIES ON (CATEGORIES.ID = ITEMS.ID)'
|
60
|
+
MSSQL_DB[:items].join(:categories, [:id1, :id2]).sql.should ==
|
61
|
+
'SELECT * FROM ITEMS INNER JOIN CATEGORIES ON ((CATEGORIES.ID1 = ITEMS.ID1) AND (CATEGORIES.ID2 = ITEMS.ID2))'
|
62
|
+
MSSQL_DB[:items___i].join(:categories___c, [:id]).sql.should ==
|
63
|
+
'SELECT * FROM ITEMS AS I INNER JOIN CATEGORIES AS C ON (C.ID = I.ID)'
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
30
67
|
context "MSSQL Dataset#output" do
|
31
68
|
before do
|
32
69
|
@db = MSSQL_DB
|
@@ -39,21 +76,42 @@ context "MSSQL Dataset#output" do
|
|
39
76
|
@db.drop_table(:out)
|
40
77
|
end
|
41
78
|
|
42
|
-
specify "should format OUTPUT clauses for DELETE statements" do
|
79
|
+
specify "should format OUTPUT clauses without INTO for DELETE statements" do
|
80
|
+
@ds.output(nil, [:deleted__name, :deleted__value]).delete_sql.should =~
|
81
|
+
/DELETE FROM ITEMS OUTPUT DELETED.(NAME|VALUE), DELETED.(NAME|VALUE)/
|
82
|
+
@ds.output(nil, [:deleted.*]).delete_sql.should =~
|
83
|
+
/DELETE FROM ITEMS OUTPUT DELETED.*/
|
84
|
+
end
|
85
|
+
|
86
|
+
specify "should format OUTPUT clauses with INTO for DELETE statements" do
|
43
87
|
@ds.output(:out, [:deleted__name, :deleted__value]).delete_sql.should =~
|
44
88
|
/DELETE FROM ITEMS OUTPUT DELETED.(NAME|VALUE), DELETED.(NAME|VALUE) INTO OUT/
|
45
89
|
@ds.output(:out, {:name => :deleted__name, :value => :deleted__value}).delete_sql.should =~
|
46
90
|
/DELETE FROM ITEMS OUTPUT DELETED.(NAME|VALUE), DELETED.(NAME|VALUE) INTO OUT \((NAME|VALUE), (NAME|VALUE)\)/
|
47
91
|
end
|
48
92
|
|
49
|
-
specify "should format OUTPUT clauses for INSERT statements" do
|
93
|
+
specify "should format OUTPUT clauses without INTO for INSERT statements" do
|
94
|
+
@ds.output(nil, [:inserted__name, :inserted__value]).insert_sql(:name => "name", :value => 1).should =~
|
95
|
+
/INSERT INTO ITEMS \((NAME|VALUE), (NAME|VALUE)\) OUTPUT INSERTED.(NAME|VALUE), INSERTED.(NAME|VALUE) VALUES \((N'name'|1), (N'name'|1)\)/
|
96
|
+
@ds.output(nil, [:inserted.*]).insert_sql(:name => "name", :value => 1).should =~
|
97
|
+
/INSERT INTO ITEMS \((NAME|VALUE), (NAME|VALUE)\) OUTPUT INSERTED.* VALUES \((N'name'|1), (N'name'|1)\)/
|
98
|
+
end
|
99
|
+
|
100
|
+
specify "should format OUTPUT clauses with INTO for INSERT statements" do
|
50
101
|
@ds.output(:out, [:inserted__name, :inserted__value]).insert_sql(:name => "name", :value => 1).should =~
|
51
102
|
/INSERT INTO ITEMS \((NAME|VALUE), (NAME|VALUE)\) OUTPUT INSERTED.(NAME|VALUE), INSERTED.(NAME|VALUE) INTO OUT VALUES \((N'name'|1), (N'name'|1)\)/
|
52
103
|
@ds.output(:out, {:name => :inserted__name, :value => :inserted__value}).insert_sql(:name => "name", :value => 1).should =~
|
53
104
|
/INSERT INTO ITEMS \((NAME|VALUE), (NAME|VALUE)\) OUTPUT INSERTED.(NAME|VALUE), INSERTED.(NAME|VALUE) INTO OUT \((NAME|VALUE), (NAME|VALUE)\) VALUES \((N'name'|1), (N'name'|1)\)/
|
54
105
|
end
|
55
106
|
|
56
|
-
specify "should format OUTPUT clauses for UPDATE statements" do
|
107
|
+
specify "should format OUTPUT clauses without INTO for UPDATE statements" do
|
108
|
+
@ds.output(nil, [:inserted__name, :deleted__value]).update_sql(:value => 2).should =~
|
109
|
+
/UPDATE ITEMS SET VALUE = 2 OUTPUT (INSERTED.NAME|DELETED.VALUE), (INSERTED.NAME|DELETED.VALUE)/
|
110
|
+
@ds.output(nil, [:inserted.*]).update_sql(:value => 2).should =~
|
111
|
+
/UPDATE ITEMS SET VALUE = 2 OUTPUT INSERTED.*/
|
112
|
+
end
|
113
|
+
|
114
|
+
specify "should format OUTPUT clauses with INTO for UPDATE statements" do
|
57
115
|
@ds.output(:out, [:inserted__name, :deleted__value]).update_sql(:value => 2).should =~
|
58
116
|
/UPDATE ITEMS SET VALUE = 2 OUTPUT (INSERTED.NAME|DELETED.VALUE), (INSERTED.NAME|DELETED.VALUE) INTO OUT/
|
59
117
|
@ds.output(:out, {:name => :inserted__name, :value => :deleted__value}).update_sql(:value => 2).should =~
|
@@ -116,26 +174,26 @@ context "MSSQL dataset" do
|
|
116
174
|
before do
|
117
175
|
@db = @db.clone
|
118
176
|
class << @db
|
119
|
-
attr_reader :
|
177
|
+
attr_reader :import_sqls
|
120
178
|
|
121
179
|
def execute(sql, opts={})
|
122
|
-
@
|
123
|
-
@
|
180
|
+
@import_sqls ||= []
|
181
|
+
@import_sqls << sql
|
124
182
|
end
|
125
183
|
alias execute_dui execute
|
126
184
|
|
127
185
|
def transaction(opts={})
|
128
|
-
@
|
129
|
-
@
|
186
|
+
@import_sqls ||= []
|
187
|
+
@import_sqls << 'BEGIN'
|
130
188
|
yield
|
131
|
-
@
|
189
|
+
@import_sqls << 'COMMIT'
|
132
190
|
end
|
133
191
|
end
|
134
192
|
end
|
135
193
|
|
136
194
|
specify "should prepend INSERT statements with WITH clause" do
|
137
195
|
@db[:items].with(:items, @db[:inventory].group(:type)).import([:x, :y], [[1, 2], [3, 4], [5, 6]], :slice => 2)
|
138
|
-
@db.
|
196
|
+
@db.import_sqls.should == [
|
139
197
|
'BEGIN',
|
140
198
|
"WITH ITEMS AS (SELECT * FROM INVENTORY GROUP BY TYPE) INSERT INTO ITEMS (X, Y) SELECT 1, 2 UNION ALL SELECT 3, 4",
|
141
199
|
'COMMIT',
|
@@ -260,3 +318,31 @@ describe "Common Table Expressions" do
|
|
260
318
|
end
|
261
319
|
end
|
262
320
|
|
321
|
+
context "MSSSQL::Dataset#insert" do
|
322
|
+
before do
|
323
|
+
@db = MSSQL_DB
|
324
|
+
@db.create_table!(:test5){primary_key :xid; Integer :value}
|
325
|
+
@db.sqls.clear
|
326
|
+
@ds = @db[:test5]
|
327
|
+
end
|
328
|
+
after do
|
329
|
+
@db.drop_table(:test5) rescue nil
|
330
|
+
end
|
331
|
+
|
332
|
+
specify "should have insert_select return nil if disable_insert_output is used" do
|
333
|
+
@ds.disable_insert_output.insert_select(:value=>10).should == nil
|
334
|
+
end
|
335
|
+
|
336
|
+
specify "should have insert_select insert the record and return the inserted record" do
|
337
|
+
@ds.meta_def(:server_version){80201}
|
338
|
+
h = @ds.insert_select(:value=>10)
|
339
|
+
h[:value].should == 10
|
340
|
+
@ds.first(:xid=>h[:xid])[:value].should == 10
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
344
|
+
context "MSSSQL::Dataset#disable_insert_output" do
|
345
|
+
specify "should play nicely with simple_select_all?" do
|
346
|
+
MSSQL_DB[:test].disable_insert_output.send(:simple_select_all?).should == true
|
347
|
+
end
|
348
|
+
end
|