sequel 3.31.0 → 3.32.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
data/lib/sequel/model/base.rb
CHANGED
@@ -86,6 +86,11 @@ module Sequel
|
|
86
86
|
# database to typecast the value correctly.
|
87
87
|
attr_accessor :typecast_on_assignment
|
88
88
|
|
89
|
+
# Whether to enable the after_commit and after_rollback hooks when saving/destroying
|
90
|
+
# instances. On by default, can be turned off for performance reasons or when using
|
91
|
+
# prepared transactions (which aren't compatible with after commit/rollback).
|
92
|
+
attr_accessor :use_after_commit_rollback
|
93
|
+
|
89
94
|
# Whether to use a transaction by default when saving/deleting records (default: true).
|
90
95
|
# If you are sending database queries in before_* or after_* hooks, you shouldn't change
|
91
96
|
# the default setting without a good reason.
|
@@ -793,7 +798,7 @@ module Sequel
|
|
793
798
|
#
|
794
799
|
# * All of the methods in +HOOKS+ and +AROUND_HOOKS+ create instance methods that are called
|
795
800
|
# by Sequel when the appropriate action occurs. For example, when destroying
|
796
|
-
# a model object, Sequel will call +
|
801
|
+
# a model object, Sequel will call +around_destroy+, which will call +before_destroy+, do
|
797
802
|
# the destroy, and then call +after_destroy+.
|
798
803
|
# * The following instance_methods all call the class method of the same
|
799
804
|
# name: columns, db, primary_key, db_schema.
|
@@ -1080,7 +1085,7 @@ module Sequel
|
|
1080
1085
|
# a = Artist[1]
|
1081
1086
|
# a.modified? # => false
|
1082
1087
|
# a.set(:name=>'Jim')
|
1083
|
-
# a.modified # => true
|
1088
|
+
# a.modified? # => true
|
1084
1089
|
def modified?
|
1085
1090
|
@modified || !changed_columns.empty?
|
1086
1091
|
end
|
@@ -1360,7 +1365,7 @@ module Sequel
|
|
1360
1365
|
# allow running inside a transaction
|
1361
1366
|
def _destroy(opts)
|
1362
1367
|
sh = {:server=>this_server}
|
1363
|
-
db.after_rollback(sh){after_destroy_rollback}
|
1368
|
+
db.after_rollback(sh){after_destroy_rollback} if use_after_commit_rollback
|
1364
1369
|
called = false
|
1365
1370
|
around_destroy do
|
1366
1371
|
called = true
|
@@ -1370,7 +1375,7 @@ module Sequel
|
|
1370
1375
|
true
|
1371
1376
|
end
|
1372
1377
|
raise_hook_failure(:destroy) unless called
|
1373
|
-
db.after_commit(sh){after_destroy_commit}
|
1378
|
+
db.after_commit(sh){after_destroy_commit} if use_after_commit_rollback
|
1374
1379
|
self
|
1375
1380
|
end
|
1376
1381
|
|
@@ -1432,7 +1437,7 @@ module Sequel
|
|
1432
1437
|
# it's own transaction.
|
1433
1438
|
def _save(columns, opts)
|
1434
1439
|
sh = {:server=>this_server}
|
1435
|
-
db.after_rollback(sh){after_rollback}
|
1440
|
+
db.after_rollback(sh){after_rollback} if use_after_commit_rollback
|
1436
1441
|
was_new = false
|
1437
1442
|
pk = nil
|
1438
1443
|
called_save = false
|
@@ -1486,7 +1491,7 @@ module Sequel
|
|
1486
1491
|
@columns_updated = nil
|
1487
1492
|
end
|
1488
1493
|
@modified = false
|
1489
|
-
db.after_commit(sh){after_commit}
|
1494
|
+
db.after_commit(sh){after_commit} if use_after_commit_rollback
|
1490
1495
|
self
|
1491
1496
|
end
|
1492
1497
|
|
@@ -16,7 +16,14 @@ module Sequel
|
|
16
16
|
# # Make the Album class active_model compliant
|
17
17
|
# Album.plugin :active_model
|
18
18
|
module ActiveModel
|
19
|
-
ClassMethods
|
19
|
+
module ClassMethods
|
20
|
+
include ::ActiveModel::Naming
|
21
|
+
|
22
|
+
# Class level cache for to_partial_path.
|
23
|
+
def _to_partial_path
|
24
|
+
@_to_partial_path ||= "#{underscore(pluralize(to_s))}/#{underscore(demodulize(to_s))}".freeze
|
25
|
+
end
|
26
|
+
end
|
20
27
|
|
21
28
|
module InstanceMethods
|
22
29
|
# The default string to join composite primary keys with in to_param.
|
@@ -56,6 +63,11 @@ module Sequel
|
|
56
63
|
k.join(to_param_joiner)
|
57
64
|
end
|
58
65
|
end
|
66
|
+
|
67
|
+
# Returns a string identifying the path associated with the object.
|
68
|
+
def to_partial_path
|
69
|
+
model._to_partial_path
|
70
|
+
end
|
59
71
|
|
60
72
|
private
|
61
73
|
|
@@ -1,20 +1,53 @@
|
|
1
1
|
module Sequel
|
2
2
|
module Plugins
|
3
|
-
# The composition plugin allows you to easily define
|
4
|
-
#
|
5
|
-
# is composed of other getters and decomposed to other setters.
|
3
|
+
# The composition plugin allows you to easily define a virtual
|
4
|
+
# attribute where the backing data is composed of other columns.
|
6
5
|
#
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
# with
|
6
|
+
# There are two ways to use the plugin. One way is with the
|
7
|
+
# :mapping option. A simple example of this is when you have a
|
8
|
+
# database table with separate columns for year, month, and day,
|
9
|
+
# but where you want to deal with Date objects in your ruby code.
|
10
|
+
# This can be handled with:
|
11
11
|
#
|
12
12
|
# Album.plugin :composition
|
13
13
|
# Album.composition :date, :mapping=>[:year, :month, :day]
|
14
14
|
#
|
15
|
-
#
|
16
|
-
#
|
17
|
-
# :
|
15
|
+
# With the :mapping option, you can provide a :class option
|
16
|
+
# that gives the class to use, but if that is not provided, it
|
17
|
+
# is inferred from the name of the composition (e.g. :date -> Date).
|
18
|
+
# When the <tt>date</tt> method is called, it will return a
|
19
|
+
# Date object by calling:
|
20
|
+
#
|
21
|
+
# Date.new(year, month, day)
|
22
|
+
#
|
23
|
+
# When saving the object, if the date composition has been used
|
24
|
+
# (by calling either the getter or setter method), it will
|
25
|
+
# populate the related columns of the object before saving:
|
26
|
+
#
|
27
|
+
# self.year = date.year
|
28
|
+
# self.month = date.month
|
29
|
+
# self.day = date.day
|
30
|
+
#
|
31
|
+
# The :mapping option is just a shortcut that works in particular
|
32
|
+
# cases. To handle any case, you can define a custom :composer
|
33
|
+
# and :decomposer procs. The :composer proc will be instance_evaled
|
34
|
+
# the first time the getter is called, and the :decomposer proc
|
35
|
+
# will be instance_evaled before saving. The above example could
|
36
|
+
# also be implemented as:
|
37
|
+
#
|
38
|
+
# Album.composition, :date,
|
39
|
+
# :composer=>proc{Date.new(year, month, day) if year || month || day},
|
40
|
+
# :decomposer=>(proc do
|
41
|
+
# if d = compositions[:date]
|
42
|
+
# self.year = d.year
|
43
|
+
# self.month = d.month
|
44
|
+
# self.day = d.day
|
45
|
+
# else
|
46
|
+
# self.year = nil
|
47
|
+
# self.month = nil
|
48
|
+
# self.day = nil
|
49
|
+
# end
|
50
|
+
# end)
|
18
51
|
#
|
19
52
|
# Note that when using the composition object, you should not
|
20
53
|
# modify the underlying columns if you are also instantiating
|
@@ -255,6 +255,7 @@ module Sequel
|
|
255
255
|
def many_through_many_association_filter_expression(op, ref, obj)
|
256
256
|
lpks = ref[:left_primary_keys]
|
257
257
|
lpks = lpks.first if lpks.length == 1
|
258
|
+
lpks = ref.qualify(model.table_name, lpks)
|
258
259
|
edges = ref.edges
|
259
260
|
first, rest = edges.first, edges[1..-1]
|
260
261
|
last = edges.last
|
@@ -266,7 +267,9 @@ module Sequel
|
|
266
267
|
last_join = ds.opts[:join].last
|
267
268
|
last_join.table_alias || last_join.table
|
268
269
|
end
|
269
|
-
|
270
|
+
meths = ref.right_primary_keys
|
271
|
+
meths = ref.qualify(obj.model.table_name, meths) if obj.is_a?(Sequel::Dataset)
|
272
|
+
exp = association_filter_key_expression(ref.qualify(last_alias, Array(ref[:final_edge][:left])), meths, obj)
|
270
273
|
if exp == SQL::Constants::FALSE
|
271
274
|
association_filter_handle_inversion(op, exp, Array(lpks))
|
272
275
|
else
|
@@ -1,20 +1,75 @@
|
|
1
1
|
module Sequel
|
2
2
|
module Plugins
|
3
|
-
# The nested_attributes plugin allows you to update
|
4
|
-
# objects directly
|
5
|
-
# Nested
|
6
|
-
#
|
7
|
-
# Nested attributes are created using the nested_attributes method:
|
3
|
+
# The nested_attributes plugin allows you to create, update, and delete
|
4
|
+
# associated objects directly by calling a method on the current object.
|
5
|
+
# Nested attributes are defined using the nested_attributes class method:
|
8
6
|
#
|
9
7
|
# Artist.one_to_many :albums
|
10
8
|
# Artist.plugin :nested_attributes
|
11
9
|
# Artist.nested_attributes :albums
|
12
|
-
# a = Artist.new(:name=>'YJM',
|
13
|
-
# :albums_attributes=>[{:name=>'RF'}, {:name=>'MO'}])
|
14
|
-
# # No database activity yet
|
15
10
|
#
|
16
|
-
#
|
17
|
-
#
|
11
|
+
# The nested_attributes call defines a single method, <tt><i>association</i>_attributes=</tt>,
|
12
|
+
# (e.g. <tt>albums_attributes=</tt>). So if you have an Artist instance:
|
13
|
+
#
|
14
|
+
# a = Artist.new(:name=>'YJM')
|
15
|
+
#
|
16
|
+
# You can create new album instances related to this artist:
|
17
|
+
#
|
18
|
+
# a.albums_attributes = [{:name=>'RF'}, {:name=>'MO'}]
|
19
|
+
#
|
20
|
+
# Note that this doesn't send any queries to the database yet. That doesn't happen till
|
21
|
+
# you save the object:
|
22
|
+
#
|
23
|
+
# a.save
|
24
|
+
#
|
25
|
+
# That will save the artist first, and then save both albums. If either the artist
|
26
|
+
# is invalid or one of the albums is invalid, none of the objects will be saved to the
|
27
|
+
# database, and all related validation errors will be available in the artist's validation
|
28
|
+
# errors.
|
29
|
+
#
|
30
|
+
# In addition to creating new associated objects, you can also update existing associated
|
31
|
+
# objects. You just need to make sure that the primary key field is filled in for the
|
32
|
+
# associated object:
|
33
|
+
#
|
34
|
+
# a.update(:albums_attributes => [{:id=>1, :name=>'T'}])
|
35
|
+
#
|
36
|
+
# Since the primary key field is filled in, the plugin will update the album with id 1 instead
|
37
|
+
# of creating a new album.
|
38
|
+
#
|
39
|
+
# If you would like to delete the associated object instead of updating it, you add a _delete
|
40
|
+
# entry to the hash:
|
41
|
+
#
|
42
|
+
# a.update(:albums_attributes => [{:id=>1, :_delete=>true}])
|
43
|
+
#
|
44
|
+
# This will delete the related associated object from the database. If you want to leave the
|
45
|
+
# associated object in the database, but just remove it from the association, add a _remove
|
46
|
+
# entry in the hash:
|
47
|
+
#
|
48
|
+
# a.update(:albums_attributes => [{:id=>1, :_remove=>true}])
|
49
|
+
#
|
50
|
+
# The above example was for a one_to_many association, but the plugin also works similarly
|
51
|
+
# for other association types. For one_to_one and many_to_one associations, you need to
|
52
|
+
# pass a single hash instead of an array of hashes.
|
53
|
+
#
|
54
|
+
# This plugin is mainly designed to make it easy to use on html forms, where a single form
|
55
|
+
# submission can contained nested attributes (and even nested attributes of those attributes).
|
56
|
+
# You just need to name your form inputs correctly:
|
57
|
+
#
|
58
|
+
# artist[name]
|
59
|
+
# artist[albums_attributes][0][:name]
|
60
|
+
# artist[albums_attributes][1][:id]
|
61
|
+
# artist[albums_attributes][1][:name]
|
62
|
+
#
|
63
|
+
# Your web stack will probably parse that into a nested hash similar to:
|
64
|
+
#
|
65
|
+
# {:artist=>{:name=>?, :albums_attributes=>{0=>{:name=>?}, 1=>{:id=>?, :name=>?}}}}
|
66
|
+
#
|
67
|
+
# Then you can do:
|
68
|
+
#
|
69
|
+
# artist.update(params[:artist])
|
70
|
+
#
|
71
|
+
# To save changes to the artist, create the first album and associate it to the artist,
|
72
|
+
# and update the other existing associated album.
|
18
73
|
module NestedAttributes
|
19
74
|
# Depend on the instance_hooks plugin.
|
20
75
|
def self.apply(model)
|
@@ -165,18 +165,12 @@ module Sequel
|
|
165
165
|
super
|
166
166
|
end
|
167
167
|
|
168
|
-
# Serialize
|
168
|
+
# Serialize deserialized values before saving
|
169
169
|
def before_save
|
170
|
-
|
170
|
+
serialize_deserialized_values
|
171
171
|
super
|
172
172
|
end
|
173
173
|
|
174
|
-
# Empty the deserialized values when refreshing.
|
175
|
-
def refresh
|
176
|
-
@deserialized_values = {}
|
177
|
-
super
|
178
|
-
end
|
179
|
-
|
180
174
|
# Initialization the deserialized values for objects retrieved from the database.
|
181
175
|
def set_values(*)
|
182
176
|
@deserialized_values ||= {}
|
@@ -185,6 +179,12 @@ module Sequel
|
|
185
179
|
|
186
180
|
private
|
187
181
|
|
182
|
+
# Empty the deserialized values when refreshing.
|
183
|
+
def _refresh(*)
|
184
|
+
@deserialized_values = {}
|
185
|
+
super
|
186
|
+
end
|
187
|
+
|
188
188
|
# Deserialize the column value. Called when the model column accessor is called to
|
189
189
|
# return a deserialized value.
|
190
190
|
def deserialize_value(column, v)
|
@@ -194,6 +194,11 @@ module Sequel
|
|
194
194
|
end
|
195
195
|
end
|
196
196
|
|
197
|
+
# Serialize all deserialized values
|
198
|
+
def serialize_deserialized_values
|
199
|
+
deserialized_values.each{|k,v| @values[k] = serialize_value(k, v)}
|
200
|
+
end
|
201
|
+
|
197
202
|
# Serialize the column value. Called before saving to ensure the serialized value
|
198
203
|
# is saved in the database.
|
199
204
|
def serialize_value(column, v)
|
@@ -1,11 +1,16 @@
|
|
1
1
|
module Sequel
|
2
2
|
module Plugins
|
3
|
-
#
|
4
|
-
#
|
5
|
-
#
|
6
|
-
#
|
7
|
-
#
|
8
|
-
#
|
3
|
+
# This plugin extends the serialization plugin and enables it to detect
|
4
|
+
# changes in serialized values by checking whether the current
|
5
|
+
# deserialized value is the same as the original deserialized value.
|
6
|
+
# The serialization plugin does not do such checks by default, as they
|
7
|
+
# often aren't needed and can hurt performance.
|
8
|
+
#
|
9
|
+
# Note that for this plugin to work correctly, the values you are
|
10
|
+
# serializing must roundtrip correctly (i.e. deserialize(serialize(value))
|
11
|
+
# should equal value). This is true in most cases, but not in all. For
|
12
|
+
# example, ruby symbols round trip through yaml, but not json (as they get
|
13
|
+
# turned into strings in json).
|
9
14
|
#
|
10
15
|
# == Example
|
11
16
|
#
|
@@ -25,6 +30,13 @@ module Sequel
|
|
25
30
|
end
|
26
31
|
|
27
32
|
module InstanceMethods
|
33
|
+
# Clear the cache of original deserialized values after saving so that it doesn't
|
34
|
+
# show the column is modified after saving.
|
35
|
+
def after_save
|
36
|
+
super
|
37
|
+
@original_deserialized_values = {}
|
38
|
+
end
|
39
|
+
|
28
40
|
# Detect which serialized columns have changed.
|
29
41
|
def changed_columns
|
30
42
|
cc = super
|
@@ -34,11 +46,11 @@ module Sequel
|
|
34
46
|
|
35
47
|
private
|
36
48
|
|
37
|
-
#
|
38
|
-
#
|
39
|
-
def
|
49
|
+
# For new objects, serialize any existing deserialized values so that changes can
|
50
|
+
# be detected.
|
51
|
+
def initialize_set(values)
|
40
52
|
super
|
41
|
-
|
53
|
+
serialize_deserialized_values
|
42
54
|
end
|
43
55
|
|
44
56
|
# Return the original deserialized value of the column, caching it to improve performance.
|
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 = 32
|
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
@@ -75,16 +75,16 @@ describe "MSSQL full_text_search" do
|
|
75
75
|
|
76
76
|
specify "should support fulltext indexes and full_text_search" do
|
77
77
|
log do
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
78
|
+
@db.create_table(:posts){Integer :id, :null=>false; String :title; String :body; index :id, :name=>:fts_id_idx, :unique=>true; full_text_index :title, :key_index=>:fts_id_idx; full_text_index [:title, :body], :key_index=>:fts_id_idx}
|
79
|
+
@db[:posts].insert(:title=>'ruby rails', :body=>'y')
|
80
|
+
@db[:posts].insert(:title=>'sequel', :body=>'ruby')
|
81
|
+
@db[:posts].insert(:title=>'ruby scooby', :body=>'x')
|
82
82
|
|
83
|
-
|
84
|
-
|
83
|
+
@db[:posts].full_text_search(:title, 'rails').all.should == [{:title=>'ruby rails', :body=>'y'}]
|
84
|
+
@db[:posts].full_text_search([:title, :body], ['sequel', 'ruby']).all.should == [{:title=>'sequel', :body=>'ruby'}]
|
85
85
|
|
86
|
-
|
87
|
-
|
86
|
+
@db[:posts].full_text_search(:title, :$n).call(:select, :n=>'rails').should == [{:title=>'ruby rails', :body=>'y'}]
|
87
|
+
@db[:posts].full_text_search(:title, :$n).prepare(:select, :fts_select).call(:n=>'rails').should == [{:title=>'ruby rails', :body=>'y'}]
|
88
88
|
end
|
89
89
|
end
|
90
90
|
end if false
|
@@ -94,8 +94,8 @@ describe "MSSQL Dataset#join_table" do
|
|
94
94
|
MSSQL_DB[:items].join(:categories, [:id]).sql.should ==
|
95
95
|
'SELECT * FROM [ITEMS] INNER JOIN [CATEGORIES] ON ([CATEGORIES].[ID] = [ITEMS].[ID])'
|
96
96
|
['SELECT * FROM [ITEMS] INNER JOIN [CATEGORIES] ON (([CATEGORIES].[ID1] = [ITEMS].[ID1]) AND ([CATEGORIES].[ID2] = [ITEMS].[ID2]))',
|
97
|
-
|
98
|
-
|
97
|
+
'SELECT * FROM [ITEMS] INNER JOIN [CATEGORIES] ON (([CATEGORIES].[ID2] = [ITEMS].[ID2]) AND ([CATEGORIES].[ID1] = [ITEMS].[ID1]))'].
|
98
|
+
should include(MSSQL_DB[:items].join(:categories, [:id1, :id2]).sql)
|
99
99
|
MSSQL_DB[:items___i].join(:categories___c, [:id]).sql.should ==
|
100
100
|
'SELECT * FROM [ITEMS] AS [I] INNER JOIN [CATEGORIES] AS [C] ON ([C].[ID] = [I].[ID])'
|
101
101
|
end
|
@@ -522,3 +522,26 @@ describe "MSSQL::Database#mssql_unicode_strings = false" do
|
|
522
522
|
ds.select_map(:name).should == ['foo']
|
523
523
|
end
|
524
524
|
end
|
525
|
+
|
526
|
+
describe "A MSSQL database adds index with include" do
|
527
|
+
before :all do
|
528
|
+
@table_name = :test_index_include
|
529
|
+
@db = MSSQL_DB
|
530
|
+
@db.create_table! @table_name do
|
531
|
+
integer :col1
|
532
|
+
integer :col2
|
533
|
+
integer :col3
|
534
|
+
end
|
535
|
+
end
|
536
|
+
|
537
|
+
after :all do
|
538
|
+
@db.drop_table @table_name
|
539
|
+
end
|
540
|
+
|
541
|
+
cspecify "should be able add index with include" do
|
542
|
+
@db.alter_table @table_name do
|
543
|
+
add_index [:col1], :include => [:col2,:col3]
|
544
|
+
end
|
545
|
+
@db.indexes(@table_name).should have_key("#{@table_name}_col1_index".to_sym)
|
546
|
+
end
|
547
|
+
end
|
data/spec/adapters/mysql_spec.rb
CHANGED
@@ -42,27 +42,27 @@ describe "MySQL", '#create_table' do
|
|
42
42
|
after do
|
43
43
|
@db.drop_table(:dolls) rescue nil
|
44
44
|
end
|
45
|
-
|
45
|
+
|
46
46
|
specify "should allow to specify options for MySQL" do
|
47
47
|
@db.create_table(:dolls, :engine => 'MyISAM', :charset => 'latin2'){text :name}
|
48
48
|
@db.sqls.should == ["CREATE TABLE `dolls` (`name` text) ENGINE=MyISAM DEFAULT CHARSET=latin2"]
|
49
49
|
end
|
50
|
-
|
50
|
+
|
51
51
|
specify "should create a temporary table" do
|
52
52
|
@db.create_table(:tmp_dolls, :temp => true, :engine => 'MyISAM', :charset => 'latin2'){text :name}
|
53
53
|
@db.sqls.should == ["CREATE TEMPORARY TABLE `tmp_dolls` (`name` text) ENGINE=MyISAM DEFAULT CHARSET=latin2"]
|
54
54
|
end
|
55
|
-
|
55
|
+
|
56
56
|
specify "should not use a default for a String :text=>true type" do
|
57
57
|
@db.create_table(:dolls){String :name, :text=>true, :default=>'blah'}
|
58
58
|
@db.sqls.should == ["CREATE TABLE `dolls` (`name` text)"]
|
59
59
|
end
|
60
|
-
|
60
|
+
|
61
61
|
specify "should not use a default for a File type" do
|
62
62
|
@db.create_table(:dolls){File :name, :default=>'blah'}
|
63
63
|
@db.sqls.should == ["CREATE TABLE `dolls` (`name` blob)"]
|
64
64
|
end
|
65
|
-
|
65
|
+
|
66
66
|
specify "should respect the size option for File type" do
|
67
67
|
@db.create_table(:dolls) do
|
68
68
|
File :n1
|
@@ -94,11 +94,11 @@ describe "A MySQL database" do
|
|
94
94
|
specify "should provide the server version" do
|
95
95
|
MYSQL_DB.server_version.should >= 40000
|
96
96
|
end
|
97
|
-
|
97
|
+
|
98
98
|
specify "should handle the creation and dropping of an InnoDB table with foreign keys" do
|
99
99
|
proc{MYSQL_DB.create_table!(:test_innodb, :engine=>:InnoDB){primary_key :id; foreign_key :fk, :test_innodb, :key=>:id}}.should_not raise_error
|
100
100
|
end
|
101
|
-
|
101
|
+
|
102
102
|
specify "should support for_share" do
|
103
103
|
MYSQL_DB.transaction{MYSQL_DB[:test2].for_share.all.should == []}
|
104
104
|
end
|
@@ -115,13 +115,13 @@ if MYSQL_DB.adapter_scheme == :mysql
|
|
115
115
|
@db.convert_tinyint_to_bool = true
|
116
116
|
@db.drop_table(:booltest)
|
117
117
|
end
|
118
|
-
|
118
|
+
|
119
119
|
specify "should consider tinyint(1) datatypes as boolean if set, but not larger tinyints" do
|
120
120
|
@db.schema(:booltest, :reload=>true).should == [[:b, {:type=>:boolean, :allow_null=>true, :primary_key=>false, :default=>nil, :ruby_default=>nil, :db_type=>"tinyint(1)"}, ], [:i, {:type=>:integer, :allow_null=>true, :primary_key=>false, :default=>nil, :ruby_default=>nil, :db_type=>"tinyint(4)"}, ]]
|
121
121
|
@db.convert_tinyint_to_bool = false
|
122
122
|
@db.schema(:booltest, :reload=>true).should == [[:b, {:type=>:integer, :allow_null=>true, :primary_key=>false, :default=>nil, :ruby_default=>nil, :db_type=>"tinyint(1)"}, ], [:i, {:type=>:integer, :allow_null=>true, :primary_key=>false, :default=>nil, :ruby_default=>nil, :db_type=>"tinyint(4)"}, ]]
|
123
123
|
end
|
124
|
-
|
124
|
+
|
125
125
|
specify "should return tinyint(1)s as bools and tinyint(4)s as integers when set" do
|
126
126
|
@db.convert_tinyint_to_bool = true
|
127
127
|
@ds.delete
|
@@ -143,7 +143,7 @@ if MYSQL_DB.adapter_scheme == :mysql
|
|
143
143
|
@ds.delete
|
144
144
|
@ds << {:b=>false, :i=>0}
|
145
145
|
@ds.all.should == [{:b=>0, :i=>0}]
|
146
|
-
|
146
|
+
|
147
147
|
@ds.delete
|
148
148
|
@ds << {:b=>1, :i=>10}
|
149
149
|
@ds.all.should == [{:b=>1, :i=>10}]
|
@@ -163,18 +163,18 @@ describe "A MySQL dataset" do
|
|
163
163
|
after do
|
164
164
|
MYSQL_DB.drop_table(:items)
|
165
165
|
end
|
166
|
-
|
166
|
+
|
167
167
|
specify "should quote columns and tables using back-ticks if quoting identifiers" do
|
168
168
|
@d.quote_identifiers = true
|
169
169
|
@d.select(:name).sql.should == \
|
170
170
|
'SELECT `name` FROM `items`'
|
171
|
-
|
171
|
+
|
172
172
|
@d.select('COUNT(*)'.lit).sql.should == \
|
173
173
|
'SELECT COUNT(*) FROM `items`'
|
174
174
|
|
175
175
|
@d.select(:max.sql_function(:value)).sql.should == \
|
176
176
|
'SELECT max(`value`) FROM `items`'
|
177
|
-
|
177
|
+
|
178
178
|
@d.select(:NOW.sql_function).sql.should == \
|
179
179
|
'SELECT NOW() FROM `items`'
|
180
180
|
|
@@ -186,13 +186,13 @@ describe "A MySQL dataset" do
|
|
186
186
|
|
187
187
|
@d.select('items.name AS item_name'.lit).sql.should == \
|
188
188
|
'SELECT items.name AS item_name FROM `items`'
|
189
|
-
|
189
|
+
|
190
190
|
@d.select('`name`'.lit).sql.should == \
|
191
191
|
'SELECT `name` FROM `items`'
|
192
192
|
|
193
193
|
@d.select('max(items.`name`) AS `max_name`'.lit).sql.should == \
|
194
194
|
'SELECT max(items.`name`) AS `max_name` FROM `items`'
|
195
|
-
|
195
|
+
|
196
196
|
@d.select(:test.sql_function(:abc, 'hello')).sql.should == \
|
197
197
|
"SELECT test(`abc`, 'hello') FROM `items`"
|
198
198
|
|
@@ -208,7 +208,7 @@ describe "A MySQL dataset" do
|
|
208
208
|
@d.insert_sql(:x => :y).should == \
|
209
209
|
'INSERT INTO `items` (`x`) VALUES (`y`)'
|
210
210
|
end
|
211
|
-
|
211
|
+
|
212
212
|
specify "should quote fields correctly when reversing the order" do
|
213
213
|
@d.quote_identifiers = true
|
214
214
|
@d.reverse_order(:name).sql.should == \
|
@@ -223,31 +223,31 @@ describe "A MySQL dataset" do
|
|
223
223
|
@d.reverse_order(:name.desc, :test).sql.should == \
|
224
224
|
'SELECT * FROM `items` ORDER BY `name` ASC, `test` DESC'
|
225
225
|
end
|
226
|
-
|
226
|
+
|
227
227
|
specify "should support ORDER clause in UPDATE statements" do
|
228
228
|
@d.order(:name).update_sql(:value => 1).should == \
|
229
229
|
'UPDATE `items` SET `value` = 1 ORDER BY `name`'
|
230
230
|
end
|
231
|
-
|
231
|
+
|
232
232
|
specify "should support LIMIT clause in UPDATE statements" do
|
233
233
|
@d.limit(10).update_sql(:value => 1).should == \
|
234
234
|
'UPDATE `items` SET `value` = 1 LIMIT 10'
|
235
235
|
end
|
236
|
-
|
236
|
+
|
237
237
|
specify "should support regexps" do
|
238
238
|
@d << {:name => 'abc', :value => 1}
|
239
239
|
@d << {:name => 'bcd', :value => 2}
|
240
240
|
@d.filter(:name => /bc/).count.should == 2
|
241
241
|
@d.filter(:name => /^bc/).count.should == 1
|
242
242
|
end
|
243
|
-
|
243
|
+
|
244
244
|
specify "should correctly literalize strings with comment backslashes in them" do
|
245
245
|
@d.delete
|
246
246
|
proc {@d << {:name => ':\\'}}.should_not raise_error
|
247
|
-
|
247
|
+
|
248
248
|
@d.first[:name].should == ':\\'
|
249
249
|
end
|
250
|
-
|
250
|
+
|
251
251
|
specify "should handle prepared statements with on_duplicate_key_update" do
|
252
252
|
@d.db.add_index :items, :value, :unique=>true
|
253
253
|
ds = @d.on_duplicate_key_update
|
@@ -263,7 +263,7 @@ describe "MySQL datasets" do
|
|
263
263
|
before do
|
264
264
|
@d = MYSQL_DB[:orders]
|
265
265
|
end
|
266
|
-
|
266
|
+
|
267
267
|
specify "should correctly quote column references" do
|
268
268
|
@d.quote_identifiers = true
|
269
269
|
market = 'ICE'
|
@@ -287,7 +287,7 @@ describe "Dataset#distinct" do
|
|
287
287
|
after do
|
288
288
|
@db.drop_table(:a)
|
289
289
|
end
|
290
|
-
|
290
|
+
|
291
291
|
it "#distinct with arguments should return results distinct on those arguments" do
|
292
292
|
@ds.insert(20, 10)
|
293
293
|
@ds.insert(30, 10)
|
@@ -353,20 +353,20 @@ describe "Joined MySQL dataset" do
|
|
353
353
|
before do
|
354
354
|
@ds = MYSQL_DB[:nodes]
|
355
355
|
end
|
356
|
-
|
356
|
+
|
357
357
|
specify "should quote fields correctly" do
|
358
358
|
@ds.quote_identifiers = true
|
359
359
|
@ds.join(:attributes, :node_id => :id).sql.should == \
|
360
360
|
"SELECT * FROM `nodes` INNER JOIN `attributes` ON (`attributes`.`node_id` = `nodes`.`id`)"
|
361
361
|
end
|
362
|
-
|
362
|
+
|
363
363
|
specify "should allow a having clause on ungrouped datasets" do
|
364
364
|
proc {@ds.having('blah')}.should_not raise_error
|
365
365
|
|
366
366
|
@ds.having('blah').sql.should == \
|
367
367
|
"SELECT * FROM `nodes` HAVING (blah)"
|
368
368
|
end
|
369
|
-
|
369
|
+
|
370
370
|
specify "should put a having clause before an order by clause" do
|
371
371
|
@ds.order(:aaa).having(:bbb => :ccc).sql.should == \
|
372
372
|
"SELECT * FROM `nodes` HAVING (`bbb` = `ccc`) ORDER BY `aaa`"
|
@@ -380,19 +380,19 @@ describe "A MySQL database" do
|
|
380
380
|
|
381
381
|
specify "should support add_column operations" do
|
382
382
|
@db.add_column :test2, :xyz, :text
|
383
|
-
|
383
|
+
|
384
384
|
@db[:test2].columns.should == [:name, :value, :xyz]
|
385
385
|
@db[:test2] << {:name => 'mmm', :value => 111, :xyz => '000'}
|
386
386
|
@db[:test2].first[:xyz].should == '000'
|
387
387
|
end
|
388
|
-
|
388
|
+
|
389
389
|
specify "should support drop_column operations" do
|
390
390
|
@db[:test2].columns.should == [:name, :value, :xyz]
|
391
391
|
@db.drop_column :test2, :xyz
|
392
|
-
|
392
|
+
|
393
393
|
@db[:test2].columns.should == [:name, :value]
|
394
394
|
end
|
395
|
-
|
395
|
+
|
396
396
|
specify "should support rename_column operations" do
|
397
397
|
@db[:test2].delete
|
398
398
|
@db.add_column :test2, :xyz, :text
|
@@ -403,7 +403,7 @@ describe "A MySQL database" do
|
|
403
403
|
@db[:test2].columns.should == [:name, :value, :zyx]
|
404
404
|
@db[:test2].first[:zyx].should == 'qqqq'
|
405
405
|
end
|
406
|
-
|
406
|
+
|
407
407
|
specify "should support rename_column operations with types like varchar(255)" do
|
408
408
|
@db[:test2].delete
|
409
409
|
@db.add_column :test2, :tre, :text
|
@@ -414,24 +414,24 @@ describe "A MySQL database" do
|
|
414
414
|
@db[:test2].columns.should == [:name, :value, :zyx, :ert]
|
415
415
|
@db[:test2].first[:ert].should == 'qqqq'
|
416
416
|
end
|
417
|
-
|
417
|
+
|
418
418
|
specify "should support set_column_type operations" do
|
419
419
|
@db.add_column :test2, :xyz, :float
|
420
420
|
@db[:test2].delete
|
421
421
|
@db[:test2] << {:name => 'mmm', :value => 111, :xyz => 56.78}
|
422
422
|
@db.set_column_type :test2, :xyz, :integer
|
423
|
-
|
423
|
+
|
424
424
|
@db[:test2].first[:xyz].should == 57
|
425
425
|
end
|
426
|
-
|
426
|
+
|
427
427
|
specify "should support add_index" do
|
428
428
|
@db.add_index :test2, :value
|
429
429
|
end
|
430
|
-
|
430
|
+
|
431
431
|
specify "should support drop_index" do
|
432
432
|
@db.drop_index :test2, :value
|
433
433
|
end
|
434
|
-
|
434
|
+
|
435
435
|
specify "should support add_foreign_key" do
|
436
436
|
@db.alter_table :test2 do
|
437
437
|
add_foreign_key :value2, :test2, :key=>:value
|
@@ -443,14 +443,14 @@ end
|
|
443
443
|
describe "A MySQL database with table options" do
|
444
444
|
before do
|
445
445
|
@options = {:engine=>'MyISAM', :charset=>'latin1', :collate => 'latin1_swedish_ci'}
|
446
|
-
|
446
|
+
|
447
447
|
Sequel::MySQL.default_engine = 'InnoDB'
|
448
448
|
Sequel::MySQL.default_charset = 'utf8'
|
449
449
|
Sequel::MySQL.default_collate = 'utf8_general_ci'
|
450
|
-
|
450
|
+
|
451
451
|
@db = MYSQL_DB
|
452
452
|
@db.drop_table(:items) rescue nil
|
453
|
-
|
453
|
+
|
454
454
|
MYSQL_DB.sqls.clear
|
455
455
|
end
|
456
456
|
after do
|
@@ -460,17 +460,17 @@ describe "A MySQL database with table options" do
|
|
460
460
|
Sequel::MySQL.default_charset = nil
|
461
461
|
Sequel::MySQL.default_collate = nil
|
462
462
|
end
|
463
|
-
|
463
|
+
|
464
464
|
specify "should allow to pass custom options (engine, charset, collate) for table creation" do
|
465
465
|
@db.create_table(:items, @options){Integer :size; text :name}
|
466
466
|
@db.sqls.should == ["CREATE TABLE `items` (`size` integer, `name` text) ENGINE=MyISAM DEFAULT CHARSET=latin1 DEFAULT COLLATE=latin1_swedish_ci"]
|
467
467
|
end
|
468
|
-
|
468
|
+
|
469
469
|
specify "should use default options if specified (engine, charset, collate) for table creation" do
|
470
470
|
@db.create_table(:items){Integer :size; text :name}
|
471
471
|
@db.sqls.should == ["CREATE TABLE `items` (`size` integer, `name` text) ENGINE=InnoDB DEFAULT CHARSET=utf8 DEFAULT COLLATE=utf8_general_ci"]
|
472
472
|
end
|
473
|
-
|
473
|
+
|
474
474
|
specify "should not use default if option has a nil value" do
|
475
475
|
@db.create_table(:items, :engine=>nil, :charset=>nil, :collate=>nil){Integer :size; text :name}
|
476
476
|
@db.sqls.should == ["CREATE TABLE `items` (`size` integer, `name` text)"]
|
@@ -486,23 +486,23 @@ describe "A MySQL database" do
|
|
486
486
|
after do
|
487
487
|
@db.drop_table(:items) rescue nil
|
488
488
|
end
|
489
|
-
|
489
|
+
|
490
490
|
specify "should support defaults for boolean columns" do
|
491
491
|
@db.create_table(:items){TrueClass :active1, :default=>true; FalseClass :active2, :default => false}
|
492
492
|
@db.sqls.should == ["CREATE TABLE `items` (`active1` tinyint(1) DEFAULT 1, `active2` tinyint(1) DEFAULT 0)"]
|
493
493
|
end
|
494
|
-
|
494
|
+
|
495
495
|
specify "should correctly format CREATE TABLE statements with foreign keys" do
|
496
496
|
@db.create_table(:items){Integer :id; foreign_key :p_id, :items, :key => :id, :null => false, :on_delete => :cascade}
|
497
497
|
@db.sqls.should == ["CREATE TABLE `items` (`id` integer, `p_id` integer NOT NULL, FOREIGN KEY (`p_id`) REFERENCES `items`(`id`) ON DELETE CASCADE)"]
|
498
498
|
end
|
499
|
-
|
499
|
+
|
500
500
|
specify "should correctly format ALTER TABLE statements with foreign keys" do
|
501
501
|
@db.create_table(:items){Integer :id}
|
502
502
|
@db.alter_table(:items){add_foreign_key :p_id, :users, :key => :id, :null => false, :on_delete => :cascade}
|
503
503
|
@db.sqls.should == ["CREATE TABLE `items` (`id` integer)", "ALTER TABLE `items` ADD COLUMN `p_id` integer NOT NULL", "ALTER TABLE `items` ADD FOREIGN KEY (`p_id`) REFERENCES `users`(`id`) ON DELETE CASCADE"]
|
504
504
|
end
|
505
|
-
|
505
|
+
|
506
506
|
specify "should have rename_column support keep existing options" do
|
507
507
|
@db.create_table(:items){String :id, :null=>false, :default=>'blah'}
|
508
508
|
@db.alter_table(:items){rename_column :id, :nid}
|
@@ -511,7 +511,7 @@ describe "A MySQL database" do
|
|
511
511
|
@db[:items].all.should == [{:nid=>'blah'}]
|
512
512
|
proc{@db[:items].insert(:nid=>nil)}.should raise_error(Sequel::DatabaseError)
|
513
513
|
end
|
514
|
-
|
514
|
+
|
515
515
|
specify "should have set_column_type support keep existing options" do
|
516
516
|
@db.create_table(:items){Integer :id, :null=>false, :default=>5}
|
517
517
|
@db.alter_table(:items){set_column_type :id, Bignum}
|
@@ -529,7 +529,7 @@ describe "A MySQL database" do
|
|
529
529
|
@db.alter_table(:items){set_column_type :id, :int, :unsigned=>true, :size=>8; set_column_type :list, :enum, :elements=>%w[two]}
|
530
530
|
@db.sqls.should == ["CREATE TABLE `items` (`id` integer, `list` enum('one'))", "DESCRIBE `items`", "ALTER TABLE `items` CHANGE COLUMN `id` `id` int(8) UNSIGNED NULL", "ALTER TABLE `items` CHANGE COLUMN `list` `list` enum('two') NULL"]
|
531
531
|
end
|
532
|
-
|
532
|
+
|
533
533
|
specify "should have set_column_default support keep existing options" do
|
534
534
|
@db.create_table(:items){Integer :id, :null=>false, :default=>5}
|
535
535
|
@db.alter_table(:items){set_column_default :id, 6}
|
@@ -538,7 +538,7 @@ describe "A MySQL database" do
|
|
538
538
|
@db[:items].all.should == [{:id=>6}]
|
539
539
|
proc{@db[:items].insert(:id=>nil)}.should raise_error(Sequel::DatabaseError)
|
540
540
|
end
|
541
|
-
|
541
|
+
|
542
542
|
specify "should have set_column_allow_null support keep existing options" do
|
543
543
|
@db.create_table(:items){Integer :id, :null=>false, :default=>5}
|
544
544
|
@db.alter_table(:items){set_column_allow_null :id, true}
|
@@ -547,15 +547,15 @@ describe "A MySQL database" do
|
|
547
547
|
@db[:items].all.should == [{:id=>5}]
|
548
548
|
proc{@db[:items].insert(:id=>nil)}.should_not
|
549
549
|
end
|
550
|
-
|
550
|
+
|
551
551
|
specify "should accept repeated raw sql statements using Database#<<" do
|
552
552
|
@db.create_table(:items){String :name; Integer :value}
|
553
553
|
@db << 'DELETE FROM items'
|
554
554
|
@db[:items].count.should == 0
|
555
|
-
|
555
|
+
|
556
556
|
@db << "INSERT INTO items (name, value) VALUES ('tutu', 1234)"
|
557
557
|
@db[:items].first.should == {:name => 'tutu', :value => 1234}
|
558
|
-
|
558
|
+
|
559
559
|
@db << 'DELETE FROM items'
|
560
560
|
@db[:items].first.should == nil
|
561
561
|
end
|
@@ -568,12 +568,12 @@ if %w'localhost 127.0.0.1 ::1'.include?(MYSQL_URI.host) and MYSQL_DB.adapter_sch
|
|
568
568
|
db = Sequel.mysql(MYSQL_DB.opts[:database], :host => 'localhost', :user => MYSQL_DB.opts[:user], :password => MYSQL_DB.opts[:password], :socket => MYSQL_SOCKET_FILE)
|
569
569
|
proc {db.test_connection}.should_not raise_error
|
570
570
|
end
|
571
|
-
|
571
|
+
|
572
572
|
specify "should accept a socket option without host option" do
|
573
573
|
db = Sequel.mysql(MYSQL_DB.opts[:database], :user => MYSQL_DB.opts[:user], :password => MYSQL_DB.opts[:password], :socket => MYSQL_SOCKET_FILE)
|
574
574
|
proc {db.test_connection}.should_not raise_error
|
575
575
|
end
|
576
|
-
|
576
|
+
|
577
577
|
specify "should fail to connect with invalid socket" do
|
578
578
|
db = Sequel.mysql(MYSQL_DB.opts[:database], :user => MYSQL_DB.opts[:user], :password => MYSQL_DB.opts[:password], :socket =>'blah')
|
579
579
|
proc {db.test_connection}.should raise_error
|
@@ -603,12 +603,12 @@ describe "A grouped MySQL dataset" do
|
|
603
603
|
MYSQL_DB[:test2] << {:name => '12', :value => 20}
|
604
604
|
MYSQL_DB[:test2] << {:name => '13', :value => 10}
|
605
605
|
end
|
606
|
-
|
606
|
+
|
607
607
|
specify "should return the correct count for raw sql query" do
|
608
608
|
ds = MYSQL_DB["select name FROM test2 WHERE name = '11' GROUP BY name"]
|
609
609
|
ds.count.should == 1
|
610
610
|
end
|
611
|
-
|
611
|
+
|
612
612
|
specify "should return the correct count for a normal dataset" do
|
613
613
|
ds = MYSQL_DB[:test2].select(:name).where(:name => '11').group(:name)
|
614
614
|
ds.count.should == 1
|
@@ -624,7 +624,7 @@ describe "A MySQL database" do
|
|
624
624
|
after do
|
625
625
|
@db.drop_table(:posts) rescue nil
|
626
626
|
end
|
627
|
-
|
627
|
+
|
628
628
|
specify "should support fulltext indexes and full_text_search" do
|
629
629
|
@db.create_table(:posts){text :title; text :body; full_text_index :title; full_text_index [:title, :body]}
|
630
630
|
@db.sqls.should == [
|
@@ -632,7 +632,7 @@ describe "A MySQL database" do
|
|
632
632
|
"CREATE FULLTEXT INDEX `posts_title_index` ON `posts` (`title`)",
|
633
633
|
"CREATE FULLTEXT INDEX `posts_title_body_index` ON `posts` (`title`, `body`)"
|
634
634
|
]
|
635
|
-
|
635
|
+
|
636
636
|
@db[:posts].insert(:title=>'ruby rails', :body=>'y')
|
637
637
|
@db[:posts].insert(:title=>'sequel', :body=>'ruby')
|
638
638
|
@db[:posts].insert(:title=>'ruby scooby', :body=>'x')
|
@@ -732,7 +732,7 @@ describe "MySQL::Dataset#insert and related methods" do
|
|
732
732
|
|
733
733
|
specify "#multi_insert should insert multiple records in a single statement" do
|
734
734
|
@d.multi_insert([{:name => 'abc'}, {:name => 'def'}])
|
735
|
-
|
735
|
+
|
736
736
|
MYSQL_DB.sqls.should == [
|
737
737
|
SQL_BEGIN,
|
738
738
|
"INSERT INTO `items` (`name`) VALUES ('abc'), ('def')",
|
@@ -756,7 +756,7 @@ describe "MySQL::Dataset#insert and related methods" do
|
|
756
756
|
"INSERT INTO `items` (`value`) VALUES (3), (4)",
|
757
757
|
SQL_COMMIT
|
758
758
|
]
|
759
|
-
|
759
|
+
|
760
760
|
@d.all.should == [
|
761
761
|
{:name => nil, :value => 1},
|
762
762
|
{:name => nil, :value => 2},
|
@@ -777,7 +777,7 @@ describe "MySQL::Dataset#insert and related methods" do
|
|
777
777
|
"INSERT INTO `items` (`value`) VALUES (3), (4)",
|
778
778
|
SQL_COMMIT
|
779
779
|
]
|
780
|
-
|
780
|
+
|
781
781
|
@d.all.should == [
|
782
782
|
{:name => nil, :value => 1},
|
783
783
|
{:name => nil, :value => 2},
|
@@ -785,7 +785,7 @@ describe "MySQL::Dataset#insert and related methods" do
|
|
785
785
|
{:name => nil, :value => 4}
|
786
786
|
]
|
787
787
|
end
|
788
|
-
|
788
|
+
|
789
789
|
specify "#import should support inserting using columns and values arrays" do
|
790
790
|
@d.import([:name, :value], [['abc', 1], ['def', 2]])
|
791
791
|
|
@@ -794,16 +794,16 @@ describe "MySQL::Dataset#insert and related methods" do
|
|
794
794
|
"INSERT INTO `items` (`name`, `value`) VALUES ('abc', 1), ('def', 2)",
|
795
795
|
SQL_COMMIT
|
796
796
|
]
|
797
|
-
|
797
|
+
|
798
798
|
@d.all.should == [
|
799
799
|
{:name => 'abc', :value => 1},
|
800
800
|
{:name => 'def', :value => 2}
|
801
801
|
]
|
802
802
|
end
|
803
|
-
|
803
|
+
|
804
804
|
specify "#insert_ignore should add the IGNORE keyword when inserting" do
|
805
805
|
@d.insert_ignore.multi_insert([{:name => 'abc'}, {:name => 'def'}])
|
806
|
-
|
806
|
+
|
807
807
|
MYSQL_DB.sqls.should == [
|
808
808
|
SQL_BEGIN,
|
809
809
|
"INSERT IGNORE INTO `items` (`name`) VALUES ('abc'), ('def')",
|
@@ -814,16 +814,16 @@ describe "MySQL::Dataset#insert and related methods" do
|
|
814
814
|
{:name => 'abc', :value => nil}, {:name => 'def', :value => nil}
|
815
815
|
]
|
816
816
|
end
|
817
|
-
|
817
|
+
|
818
818
|
specify "#insert_ignore should add the IGNORE keyword for single inserts" do
|
819
819
|
@d.insert_ignore.insert(:name => 'ghi')
|
820
820
|
MYSQL_DB.sqls.should == ["INSERT IGNORE INTO `items` (`name`) VALUES ('ghi')"]
|
821
821
|
@d.all.should == [{:name => 'ghi', :value => nil}]
|
822
822
|
end
|
823
|
-
|
823
|
+
|
824
824
|
specify "#on_duplicate_key_update should add the ON DUPLICATE KEY UPDATE and ALL columns when no args given" do
|
825
825
|
@d.on_duplicate_key_update.import([:name,:value], [['abc', 1], ['def',2]])
|
826
|
-
|
826
|
+
|
827
827
|
MYSQL_DB.sqls.should == [
|
828
828
|
"SELECT * FROM `items` LIMIT 1",
|
829
829
|
SQL_BEGIN,
|
@@ -835,12 +835,12 @@ describe "MySQL::Dataset#insert and related methods" do
|
|
835
835
|
{:name => 'abc', :value => 1}, {:name => 'def', :value => 2}
|
836
836
|
]
|
837
837
|
end
|
838
|
-
|
838
|
+
|
839
839
|
specify "#on_duplicate_key_update should add the ON DUPLICATE KEY UPDATE and columns specified when args are given" do
|
840
840
|
@d.on_duplicate_key_update(:value).import([:name,:value],
|
841
841
|
[['abc', 1], ['def',2]]
|
842
842
|
)
|
843
|
-
|
843
|
+
|
844
844
|
MYSQL_DB.sqls.should == [
|
845
845
|
SQL_BEGIN,
|
846
846
|
"INSERT INTO `items` (`name`, `value`) VALUES ('abc', 1), ('def', 2) ON DUPLICATE KEY UPDATE `value`=VALUES(`value`)",
|
@@ -851,7 +851,27 @@ describe "MySQL::Dataset#insert and related methods" do
|
|
851
851
|
{:name => 'abc', :value => 1}, {:name => 'def', :value => 2}
|
852
852
|
]
|
853
853
|
end
|
854
|
-
|
854
|
+
|
855
|
+
end
|
856
|
+
|
857
|
+
describe "MySQL::Dataset#update and related methods" do
|
858
|
+
before do
|
859
|
+
MYSQL_DB.create_table(:items){String :name; Integer :value; index :name, :unique=>true}
|
860
|
+
@d = MYSQL_DB[:items]
|
861
|
+
end
|
862
|
+
after do
|
863
|
+
MYSQL_DB.drop_table(:items)
|
864
|
+
end
|
865
|
+
|
866
|
+
specify "#update_ignore should not raise error where normal update would fail" do
|
867
|
+
@d.insert(:name => 'cow', :value => 0)
|
868
|
+
@d.insert(:name => 'cat', :value => 1)
|
869
|
+
proc{@d.where(:value => 1).update(:name => 'cow')}.should raise_error(Sequel::DatabaseError)
|
870
|
+
MYSQL_DB.sqls.clear
|
871
|
+
@d.update_ignore.where(:value => 1).update(:name => 'cow')
|
872
|
+
MYSQL_DB.sqls.should == ["UPDATE IGNORE `items` SET `name` = 'cow' WHERE (`value` = 1)"]
|
873
|
+
@d.order(:name).all.should == [{:name => 'cat', :value => 1}, {:name => 'cow', :value => 0}]
|
874
|
+
end
|
855
875
|
end
|
856
876
|
|
857
877
|
describe "MySQL::Dataset#replace" do
|
@@ -863,7 +883,7 @@ describe "MySQL::Dataset#replace" do
|
|
863
883
|
after do
|
864
884
|
MYSQL_DB.drop_table(:items)
|
865
885
|
end
|
866
|
-
|
886
|
+
|
867
887
|
specify "should use default values if they exist" do
|
868
888
|
MYSQL_DB.alter_table(:items){set_column_default :id, 1; set_column_default :value, 2}
|
869
889
|
@d.replace
|
@@ -873,7 +893,7 @@ describe "MySQL::Dataset#replace" do
|
|
873
893
|
@d.replace({})
|
874
894
|
@d.all.should == [{:id=>1, :value=>2}]
|
875
895
|
end
|
876
|
-
|
896
|
+
|
877
897
|
specify "should use support arrays, datasets, and multiple values" do
|
878
898
|
@d.replace([1, 2])
|
879
899
|
@d.all.should == [{:id=>1, :value=>2}]
|
@@ -882,12 +902,12 @@ describe "MySQL::Dataset#replace" do
|
|
882
902
|
@d.replace(@d)
|
883
903
|
@d.all.should == [{:id=>1, :value=>2}]
|
884
904
|
end
|
885
|
-
|
905
|
+
|
886
906
|
specify "should create a record if the condition is not met" do
|
887
907
|
@d.replace(:id => 111, :value => 333)
|
888
908
|
@d.all.should == [{:id => 111, :value => 333}]
|
889
909
|
end
|
890
|
-
|
910
|
+
|
891
911
|
specify "should update a record if the condition is met" do
|
892
912
|
@d << {:id => 111}
|
893
913
|
@d.all.should == [{:id => 111, :value => nil}]
|
@@ -962,7 +982,7 @@ if MYSQL_DB.adapter_scheme == :mysql or MYSQL_DB.adapter_scheme == :jdbc or MYSQ
|
|
962
982
|
MYSQL_DB.drop_table(:items)
|
963
983
|
MYSQL_DB.execute('DROP PROCEDURE test_sproc')
|
964
984
|
end
|
965
|
-
|
985
|
+
|
966
986
|
specify "should be callable on the database object" do
|
967
987
|
MYSQL_DB.execute_ddl('CREATE PROCEDURE test_sproc() BEGIN DELETE FROM items; END')
|
968
988
|
MYSQL_DB[:items].delete
|
@@ -971,7 +991,7 @@ if MYSQL_DB.adapter_scheme == :mysql or MYSQL_DB.adapter_scheme == :jdbc or MYSQ
|
|
971
991
|
MYSQL_DB.call_sproc(:test_sproc)
|
972
992
|
MYSQL_DB[:items].count.should == 0
|
973
993
|
end
|
974
|
-
|
994
|
+
|
975
995
|
# Mysql2 doesn't support stored procedures that return result sets, probably because
|
976
996
|
# CLIENT_MULTI_RESULTS is not set.
|
977
997
|
unless MYSQL_DB.adapter_scheme == :mysql2
|
@@ -985,7 +1005,7 @@ if MYSQL_DB.adapter_scheme == :mysql or MYSQL_DB.adapter_scheme == :jdbc or MYSQ
|
|
985
1005
|
@d.row_proc = proc{|r| r.keys.each{|k| r[k] *= 2 if r[k].is_a?(Integer)}; r}
|
986
1006
|
@d.call_sproc(:select, :test_sproc, 3).should == [{:id=>nil, :value=>2, :b=>6}]
|
987
1007
|
end
|
988
|
-
|
1008
|
+
|
989
1009
|
specify "should be callable on the dataset object with multiple arguments" do
|
990
1010
|
MYSQL_DB.execute_ddl('CREATE PROCEDURE test_sproc(a INTEGER, c INTEGER) BEGIN SELECT *, a AS b, c AS d FROM items; END')
|
991
1011
|
MYSQL_DB[:items].delete
|
@@ -1012,14 +1032,14 @@ if MYSQL_DB.adapter_scheme == :mysql
|
|
1012
1032
|
after do
|
1013
1033
|
MYSQL_DB.convert_invalid_date_time = false
|
1014
1034
|
end
|
1015
|
-
|
1035
|
+
|
1016
1036
|
specify "should raise an exception when a bad date/time is used and convert_invalid_date_time is false" do
|
1017
1037
|
MYSQL_DB.convert_invalid_date_time = false
|
1018
1038
|
proc{MYSQL_DB["SELECT CAST('0000-00-00' AS date)"].single_value}.should raise_error(Sequel::InvalidValue)
|
1019
1039
|
proc{MYSQL_DB["SELECT CAST('0000-00-00 00:00:00' AS datetime)"].single_value}.should raise_error(Sequel::InvalidValue)
|
1020
1040
|
proc{MYSQL_DB["SELECT CAST('25:00:00' AS time)"].single_value}.should raise_error(Sequel::InvalidValue)
|
1021
1041
|
end
|
1022
|
-
|
1042
|
+
|
1023
1043
|
specify "should not use a nil value bad date/time is used and convert_invalid_date_time is nil or :nil" do
|
1024
1044
|
MYSQL_DB.convert_invalid_date_time = nil
|
1025
1045
|
MYSQL_DB["SELECT CAST('0000-00-00' AS date)"].single_value.should == nil
|
@@ -1030,7 +1050,7 @@ if MYSQL_DB.adapter_scheme == :mysql
|
|
1030
1050
|
MYSQL_DB["SELECT CAST('0000-00-00 00:00:00' AS datetime)"].single_value.should == nil
|
1031
1051
|
MYSQL_DB["SELECT CAST('25:00:00' AS time)"].single_value.should == nil
|
1032
1052
|
end
|
1033
|
-
|
1053
|
+
|
1034
1054
|
specify "should not use a nil value bad date/time is used and convert_invalid_date_time is :string" do
|
1035
1055
|
MYSQL_DB.convert_invalid_date_time = :string
|
1036
1056
|
MYSQL_DB["SELECT CAST('0000-00-00' AS date)"].single_value.should == '0000-00-00'
|
@@ -1038,7 +1058,7 @@ if MYSQL_DB.adapter_scheme == :mysql
|
|
1038
1058
|
MYSQL_DB["SELECT CAST('25:00:00' AS time)"].single_value.should == '25:00:00'
|
1039
1059
|
end
|
1040
1060
|
end
|
1041
|
-
|
1061
|
+
|
1042
1062
|
describe "MySQL multiple result sets" do
|
1043
1063
|
before do
|
1044
1064
|
MYSQL_DB.create_table!(:a){Integer :a}
|
@@ -1052,39 +1072,39 @@ if MYSQL_DB.adapter_scheme == :mysql
|
|
1052
1072
|
after do
|
1053
1073
|
MYSQL_DB.drop_table(:a, :b)
|
1054
1074
|
end
|
1055
|
-
|
1075
|
+
|
1056
1076
|
specify "should combine all results by default" do
|
1057
1077
|
@ds.all.should == [{:a=>10}, {:a=>15}, {:b=>20}, {:b=>25}]
|
1058
1078
|
end
|
1059
|
-
|
1079
|
+
|
1060
1080
|
specify "should work with Database#run" do
|
1061
1081
|
proc{MYSQL_DB.run('SELECT * FROM a; SELECT * FROM b')}.should_not raise_error
|
1062
1082
|
proc{MYSQL_DB.run('SELECT * FROM a; SELECT * FROM b')}.should_not raise_error
|
1063
1083
|
end
|
1064
|
-
|
1084
|
+
|
1065
1085
|
specify "should work with Database#run and other statements" do
|
1066
1086
|
proc{MYSQL_DB.run('UPDATE a SET a = 1; SELECT * FROM a; DELETE FROM b')}.should_not raise_error
|
1067
1087
|
MYSQL_DB[:a].select_order_map(:a).should == [1, 1]
|
1068
1088
|
MYSQL_DB[:b].all.should == []
|
1069
1089
|
end
|
1070
|
-
|
1090
|
+
|
1071
1091
|
specify "should split results returned into arrays if split_multiple_result_sets is used" do
|
1072
1092
|
@ds.split_multiple_result_sets.all.should == [[{:a=>10}, {:a=>15}], [{:b=>20}, {:b=>25}]]
|
1073
1093
|
end
|
1074
|
-
|
1094
|
+
|
1075
1095
|
specify "should have regular row_procs work when splitting multiple result sets" do
|
1076
1096
|
@ds.row_proc = proc{|x| x[x.keys.first] *= 2; x}
|
1077
1097
|
@ds.split_multiple_result_sets.all.should == [[{:a=>20}, {:a=>30}], [{:b=>40}, {:b=>50}]]
|
1078
1098
|
end
|
1079
|
-
|
1099
|
+
|
1080
1100
|
specify "should use the columns from the first result set when splitting result sets" do
|
1081
1101
|
@ds.split_multiple_result_sets.columns.should == [:a]
|
1082
1102
|
end
|
1083
|
-
|
1103
|
+
|
1084
1104
|
specify "should not allow graphing a dataset that splits multiple statements" do
|
1085
1105
|
proc{@ds.split_multiple_result_sets.graph(:b, :b=>:a)}.should raise_error(Sequel::Error)
|
1086
1106
|
end
|
1087
|
-
|
1107
|
+
|
1088
1108
|
specify "should not allow splitting a graphed dataset" do
|
1089
1109
|
proc{MYSQL_DB[:a].graph(:b, :b=>:a).split_multiple_result_sets}.should raise_error(Sequel::Error)
|
1090
1110
|
end
|