sequel 3.31.0 → 3.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +54 -0
- data/MIT-LICENSE +1 -1
- data/doc/advanced_associations.rdoc +17 -0
- data/doc/association_basics.rdoc +74 -30
- data/doc/release_notes/3.32.0.txt +202 -0
- data/doc/schema_modification.rdoc +1 -1
- data/lib/sequel/adapters/jdbc/db2.rb +7 -0
- data/lib/sequel/adapters/jdbc/derby.rb +13 -0
- data/lib/sequel/adapters/jdbc/h2.rb +10 -1
- data/lib/sequel/adapters/jdbc/hsqldb.rb +7 -0
- data/lib/sequel/adapters/jdbc/oracle.rb +7 -0
- data/lib/sequel/adapters/mock.rb +4 -0
- data/lib/sequel/adapters/mysql.rb +3 -0
- data/lib/sequel/adapters/oracle.rb +7 -3
- data/lib/sequel/adapters/shared/db2.rb +9 -2
- data/lib/sequel/adapters/shared/mssql.rb +48 -2
- data/lib/sequel/adapters/shared/mysql.rb +24 -4
- data/lib/sequel/adapters/shared/oracle.rb +7 -6
- data/lib/sequel/adapters/shared/progress.rb +1 -1
- data/lib/sequel/adapters/shared/sqlite.rb +16 -10
- data/lib/sequel/core.rb +22 -0
- data/lib/sequel/database/query.rb +13 -4
- data/lib/sequel/dataset/actions.rb +20 -11
- data/lib/sequel/dataset/mutation.rb +7 -1
- data/lib/sequel/dataset/prepared_statements.rb +11 -0
- data/lib/sequel/dataset/sql.rb +21 -24
- data/lib/sequel/extensions/query.rb +1 -1
- data/lib/sequel/model.rb +5 -2
- data/lib/sequel/model/associations.rb +70 -16
- data/lib/sequel/model/base.rb +11 -6
- data/lib/sequel/plugins/active_model.rb +13 -1
- data/lib/sequel/plugins/composition.rb +43 -10
- data/lib/sequel/plugins/many_through_many.rb +4 -1
- data/lib/sequel/plugins/nested_attributes.rb +65 -10
- data/lib/sequel/plugins/serialization.rb +13 -8
- data/lib/sequel/plugins/serialization_modification_detection.rb +22 -10
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/mssql_spec.rb +33 -10
- data/spec/adapters/mysql_spec.rb +111 -91
- data/spec/adapters/oracle_spec.rb +18 -0
- data/spec/core/database_spec.rb +1 -0
- data/spec/core/dataset_spec.rb +110 -15
- data/spec/extensions/active_model_spec.rb +13 -0
- data/spec/extensions/many_through_many_spec.rb +14 -14
- data/spec/extensions/query_spec.rb +6 -0
- data/spec/extensions/serialization_modification_detection_spec.rb +36 -1
- data/spec/extensions/serialization_spec.rb +9 -0
- data/spec/integration/associations_test.rb +278 -154
- data/spec/integration/dataset_test.rb +39 -2
- data/spec/integration/plugin_test.rb +63 -3
- data/spec/integration/prepared_statement_test.rb +10 -3
- data/spec/integration/schema_test.rb +61 -14
- data/spec/integration/transaction_test.rb +10 -0
- data/spec/model/associations_spec.rb +170 -80
- data/spec/model/hooks_spec.rb +40 -0
- metadata +4 -2
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
|