sequel 2.2.0 → 2.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +1551 -4
- data/README +306 -19
- data/Rakefile +84 -56
- data/bin/sequel +106 -0
- data/doc/cheat_sheet.rdoc +225 -0
- data/doc/dataset_filtering.rdoc +182 -0
- data/lib/sequel_core.rb +136 -0
- data/lib/sequel_core/adapters/adapter_skeleton.rb +54 -0
- data/lib/sequel_core/adapters/ado.rb +80 -0
- data/lib/sequel_core/adapters/db2.rb +148 -0
- data/lib/sequel_core/adapters/dbi.rb +117 -0
- data/lib/sequel_core/adapters/informix.rb +78 -0
- data/lib/sequel_core/adapters/jdbc.rb +186 -0
- data/lib/sequel_core/adapters/jdbc/mysql.rb +55 -0
- data/lib/sequel_core/adapters/jdbc/postgresql.rb +66 -0
- data/lib/sequel_core/adapters/jdbc/sqlite.rb +47 -0
- data/lib/sequel_core/adapters/mysql.rb +231 -0
- data/lib/sequel_core/adapters/odbc.rb +155 -0
- data/lib/sequel_core/adapters/odbc_mssql.rb +106 -0
- data/lib/sequel_core/adapters/openbase.rb +64 -0
- data/lib/sequel_core/adapters/oracle.rb +170 -0
- data/lib/sequel_core/adapters/postgres.rb +199 -0
- data/lib/sequel_core/adapters/shared/mysql.rb +275 -0
- data/lib/sequel_core/adapters/shared/postgres.rb +351 -0
- data/lib/sequel_core/adapters/shared/sqlite.rb +146 -0
- data/lib/sequel_core/adapters/sqlite.rb +138 -0
- data/lib/sequel_core/connection_pool.rb +194 -0
- data/lib/sequel_core/core_ext.rb +203 -0
- data/lib/sequel_core/core_sql.rb +184 -0
- data/lib/sequel_core/database.rb +471 -0
- data/lib/sequel_core/database/schema.rb +156 -0
- data/lib/sequel_core/dataset.rb +457 -0
- data/lib/sequel_core/dataset/callback.rb +13 -0
- data/lib/sequel_core/dataset/convenience.rb +245 -0
- data/lib/sequel_core/dataset/pagination.rb +96 -0
- data/lib/sequel_core/dataset/query.rb +41 -0
- data/lib/sequel_core/dataset/schema.rb +15 -0
- data/lib/sequel_core/dataset/sql.rb +889 -0
- data/lib/sequel_core/deprecated.rb +26 -0
- data/lib/sequel_core/exceptions.rb +42 -0
- data/lib/sequel_core/migration.rb +187 -0
- data/lib/sequel_core/object_graph.rb +216 -0
- data/lib/sequel_core/pretty_table.rb +71 -0
- data/lib/sequel_core/schema.rb +2 -0
- data/lib/sequel_core/schema/generator.rb +239 -0
- data/lib/sequel_core/schema/sql.rb +325 -0
- data/lib/sequel_core/sql.rb +812 -0
- data/lib/sequel_model.rb +5 -1
- data/lib/sequel_model/association_reflection.rb +3 -8
- data/lib/sequel_model/base.rb +15 -10
- data/lib/sequel_model/inflector.rb +3 -5
- data/lib/sequel_model/plugins.rb +1 -1
- data/lib/sequel_model/record.rb +11 -3
- data/lib/sequel_model/schema.rb +4 -4
- data/lib/sequel_model/validations.rb +6 -1
- data/spec/adapters/ado_spec.rb +17 -0
- data/spec/adapters/informix_spec.rb +96 -0
- data/spec/adapters/mysql_spec.rb +764 -0
- data/spec/adapters/oracle_spec.rb +222 -0
- data/spec/adapters/postgres_spec.rb +441 -0
- data/spec/adapters/spec_helper.rb +7 -0
- data/spec/adapters/sqlite_spec.rb +400 -0
- data/spec/integration/dataset_test.rb +51 -0
- data/spec/integration/eager_loader_test.rb +702 -0
- data/spec/integration/schema_test.rb +102 -0
- data/spec/integration/spec_helper.rb +44 -0
- data/spec/integration/type_test.rb +43 -0
- data/spec/rcov.opts +2 -0
- data/spec/sequel_core/connection_pool_spec.rb +363 -0
- data/spec/sequel_core/core_ext_spec.rb +156 -0
- data/spec/sequel_core/core_sql_spec.rb +427 -0
- data/spec/sequel_core/database_spec.rb +964 -0
- data/spec/sequel_core/dataset_spec.rb +2977 -0
- data/spec/sequel_core/expression_filters_spec.rb +346 -0
- data/spec/sequel_core/migration_spec.rb +261 -0
- data/spec/sequel_core/object_graph_spec.rb +234 -0
- data/spec/sequel_core/pretty_table_spec.rb +58 -0
- data/spec/sequel_core/schema_generator_spec.rb +122 -0
- data/spec/sequel_core/schema_spec.rb +497 -0
- data/spec/sequel_core/spec_helper.rb +51 -0
- data/spec/{association_reflection_spec.rb → sequel_model/association_reflection_spec.rb} +6 -6
- data/spec/{associations_spec.rb → sequel_model/associations_spec.rb} +47 -18
- data/spec/{base_spec.rb → sequel_model/base_spec.rb} +2 -1
- data/spec/{caching_spec.rb → sequel_model/caching_spec.rb} +0 -0
- data/spec/{dataset_methods_spec.rb → sequel_model/dataset_methods_spec.rb} +13 -1
- data/spec/{eager_loading_spec.rb → sequel_model/eager_loading_spec.rb} +75 -14
- data/spec/{hooks_spec.rb → sequel_model/hooks_spec.rb} +4 -4
- data/spec/sequel_model/inflector_spec.rb +119 -0
- data/spec/{model_spec.rb → sequel_model/model_spec.rb} +30 -11
- data/spec/{plugins_spec.rb → sequel_model/plugins_spec.rb} +0 -0
- data/spec/{record_spec.rb → sequel_model/record_spec.rb} +47 -6
- data/spec/{schema_spec.rb → sequel_model/schema_spec.rb} +18 -4
- data/spec/{spec_helper.rb → sequel_model/spec_helper.rb} +3 -2
- data/spec/{validations_spec.rb → sequel_model/validations_spec.rb} +37 -17
- data/spec/spec_config.rb +9 -0
- data/spec/spec_config.rb.example +10 -0
- metadata +110 -37
- data/spec/inflector_spec.rb +0 -34
data/lib/sequel_model.rb
CHANGED
@@ -54,7 +54,7 @@ module Sequel
|
|
54
54
|
# sti_dataset, and sti_key. You should not usually need to
|
55
55
|
# access these directly.
|
56
56
|
# * The following class level attr_accessors are created: raise_on_save_failure,
|
57
|
-
# strict_param_setting, and typecast_on_assignment:
|
57
|
+
# strict_param_setting, typecast_empty_string_to_nil, and typecast_on_assignment:
|
58
58
|
#
|
59
59
|
# # Don't raise an error if a validation attempt fails in
|
60
60
|
# # save/create/save_changes/etc.
|
@@ -71,6 +71,10 @@ module Sequel
|
|
71
71
|
# m = Model.new
|
72
72
|
# m.number = '10'
|
73
73
|
# m.number # => '10' instead of 10
|
74
|
+
# # Don't typecast empty string to nil for non-string, non-blob columns.
|
75
|
+
# Model.typecast_empty_string_to_nil = false
|
76
|
+
# m.number = ''
|
77
|
+
# m.number # => '' instead of nil
|
74
78
|
#
|
75
79
|
# * The following class level method aliases are defined:
|
76
80
|
# * Model.dataset= => set_dataset
|
@@ -70,14 +70,14 @@ module Sequel
|
|
70
70
|
|
71
71
|
# Name symbol for default join table
|
72
72
|
def default_join_table
|
73
|
-
([self[:class_name].demodulize, self[:model].name.demodulize]. \
|
73
|
+
([self[:class_name].demodulize, self[:model].name.to_s.demodulize]. \
|
74
74
|
map{|i| i.pluralize.underscore}.sort.join('_')).to_sym
|
75
75
|
end
|
76
76
|
|
77
77
|
# Default foreign key name symbol for key in associated table that points to
|
78
78
|
# current table's primary key.
|
79
79
|
def default_left_key
|
80
|
-
:"#{self[:model].name.demodulize.underscore}_id"
|
80
|
+
:"#{self[:model].name.to_s.demodulize.underscore}_id"
|
81
81
|
end
|
82
82
|
|
83
83
|
# Default foreign key name symbol for foreign key in current model's table that points to
|
@@ -86,14 +86,9 @@ module Sequel
|
|
86
86
|
:"#{self[:type] == :many_to_one ? self[:name] : self[:name].to_s.singularize}_id"
|
87
87
|
end
|
88
88
|
|
89
|
-
# Name symbol for _dataset association method
|
90
|
-
def eager_dataset_method
|
91
|
-
:"#{self[:name]}_eager_dataset"
|
92
|
-
end
|
93
|
-
|
94
89
|
# Whether to eagerly graph a lazy dataset
|
95
90
|
def eager_graph_lazy_dataset?
|
96
|
-
self[:type] != :many_to_one or
|
91
|
+
self[:type] != :many_to_one or self[:key].nil?
|
97
92
|
end
|
98
93
|
|
99
94
|
# Whether the associated object needs a primary key to be added/removed
|
data/lib/sequel_model/base.rb
CHANGED
@@ -14,6 +14,7 @@ module Sequel
|
|
14
14
|
@sti_dataset = nil
|
15
15
|
@sti_key = nil
|
16
16
|
@strict_param_setting = true
|
17
|
+
@typecast_empty_string_to_nil = true
|
17
18
|
@typecast_on_assignment = true
|
18
19
|
|
19
20
|
# Which columns should be the only columns allowed in a call to set
|
@@ -47,12 +48,16 @@ module Sequel
|
|
47
48
|
# access is restricted to it).
|
48
49
|
metaattr_accessor :strict_param_setting
|
49
50
|
|
51
|
+
# Whether to typecast the empty string ('') to nil for columns that
|
52
|
+
# are not string or blob.
|
53
|
+
metaattr_accessor :typecast_empty_string_to_nil
|
54
|
+
|
50
55
|
# Whether to typecast attribute values on assignment (default: true)
|
51
56
|
metaattr_accessor :typecast_on_assignment
|
52
57
|
|
53
58
|
# Dataset methods to proxy via metaprogramming
|
54
59
|
DATASET_METHODS = %w'<< all avg count delete distinct eager eager_graph each each_page
|
55
|
-
empty? except exclude filter first from_self full_outer_join get graph
|
60
|
+
empty? except exclude filter first from from_self full_outer_join get graph
|
56
61
|
group group_and_count group_by having import inner_join insert
|
57
62
|
insert_multiple intersect interval invert_order join join_table last
|
58
63
|
left_outer_join limit map multi_insert naked order order_by order_more
|
@@ -65,7 +70,7 @@ module Sequel
|
|
65
70
|
:@cache_ttl=>nil, :@dataset_methods=>:dup, :@primary_key=>nil,
|
66
71
|
:@raise_on_save_failure=>nil, :@restricted_columns=>:dup, :@restrict_primary_key=>nil,
|
67
72
|
:@sti_dataset=>nil, :@sti_key=>nil, :@strict_param_setting=>nil,
|
68
|
-
:@typecast_on_assignment=>nil}
|
73
|
+
:@typecast_empty_string_to_nil=>nil, :@typecast_on_assignment=>nil}
|
69
74
|
|
70
75
|
# Returns the first record from the database matching the conditions.
|
71
76
|
# If a hash is given, it is used as the conditions. If another
|
@@ -190,7 +195,7 @@ module Sequel
|
|
190
195
|
# from the parent class.
|
191
196
|
def self.inherited(subclass)
|
192
197
|
sup_class = subclass.superclass
|
193
|
-
ivs = subclass.instance_variables
|
198
|
+
ivs = subclass.instance_variables.collect{|x| x.to_s}
|
194
199
|
INHERITED_INSTANCE_VARIABLES.each do |iv, dup|
|
195
200
|
next if ivs.include?(iv.to_s)
|
196
201
|
sup_class_value = sup_class.instance_variable_get(iv)
|
@@ -200,11 +205,12 @@ module Sequel
|
|
200
205
|
unless ivs.include?("@dataset")
|
201
206
|
begin
|
202
207
|
if sup_class == Model
|
203
|
-
subclass.set_dataset(Model.db[subclass.implicit_table_name]) unless subclass.name.
|
208
|
+
subclass.set_dataset(Model.db[subclass.implicit_table_name]) unless subclass.name.blank?
|
204
209
|
elsif ds = sup_class.instance_variable_get(:@dataset)
|
205
|
-
subclass.set_dataset(sup_class.sti_key ? sup_class.sti_dataset.filter(sup_class.sti_key=>subclass.name) : ds.clone)
|
210
|
+
subclass.set_dataset(sup_class.sti_key ? sup_class.sti_dataset.filter(sup_class.sti_key=>subclass.name.to_s) : ds.clone)
|
206
211
|
end
|
207
212
|
rescue
|
213
|
+
nil
|
208
214
|
end
|
209
215
|
end
|
210
216
|
end
|
@@ -318,9 +324,8 @@ module Sequel
|
|
318
324
|
@dataset.extend(Associations::EagerLoading)
|
319
325
|
@dataset.transform(@transform) if @transform
|
320
326
|
@dataset_methods.each{|meth, block| @dataset.meta_def(meth, &block)} if @dataset_methods
|
321
|
-
|
322
|
-
(@db_schema = get_db_schema)
|
323
|
-
rescue
|
327
|
+
unless @@lazy_load_schema
|
328
|
+
(@db_schema = get_db_schema) rescue nil
|
324
329
|
end
|
325
330
|
self
|
326
331
|
end
|
@@ -375,7 +380,7 @@ module Sequel
|
|
375
380
|
@sti_key = key
|
376
381
|
@sti_dataset = dataset
|
377
382
|
dataset.set_model(key, Hash.new{|h,k| h[k] = (k.constantize rescue m)})
|
378
|
-
before_create(:set_sti_key){send("#{key}=", model.name)}
|
383
|
+
before_create(:set_sti_key){send("#{key}=", model.name.to_s)}
|
379
384
|
end
|
380
385
|
|
381
386
|
# Returns the columns as a list of frozen strings instead
|
@@ -422,7 +427,7 @@ module Sequel
|
|
422
427
|
# Create the column accessors
|
423
428
|
def self.def_column_accessor(*columns) # :nodoc:
|
424
429
|
columns.each do |column|
|
425
|
-
im = instance_methods
|
430
|
+
im = instance_methods.collect{|x| x.to_s}
|
426
431
|
meth = "#{column}="
|
427
432
|
define_method(column){self[column]} unless im.include?(column.to_s)
|
428
433
|
unless im.include?(meth)
|
@@ -148,11 +148,9 @@ class String
|
|
148
148
|
# "active_record/errors".camelize #=> "ActiveRecord::Errors"
|
149
149
|
# "active_record/errors".camelize(:lower) #=> "activeRecord::Errors"
|
150
150
|
def camelize(first_letter_in_uppercase = :upper)
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
"#{first}#{camelize[1..-1]}"
|
155
|
-
end
|
151
|
+
s = gsub(/\/(.?)/){|x| "::#{x[-1..-1].upcase unless x == '/'}"}.gsub(/(^|_)(.)/){|x| x[-1..-1].upcase}
|
152
|
+
s[0...1] = s[0...1].downcase unless first_letter_in_uppercase == :upper
|
153
|
+
s
|
156
154
|
end
|
157
155
|
alias_method :camelcase, :camelize
|
158
156
|
|
data/lib/sequel_model/plugins.rb
CHANGED
@@ -34,7 +34,7 @@ module Sequel
|
|
34
34
|
end
|
35
35
|
if m.const_defined?("DatasetMethods")
|
36
36
|
dataset.meta_def(:"#{plugin}_opts") {args.first}
|
37
|
-
dataset.
|
37
|
+
dataset.extend(m::DatasetMethods)
|
38
38
|
def_dataset_method(*m::DatasetMethods.instance_methods)
|
39
39
|
end
|
40
40
|
end
|
data/lib/sequel_model/record.rb
CHANGED
@@ -2,7 +2,7 @@ module Sequel
|
|
2
2
|
class Model
|
3
3
|
# The setter methods (methods ending with =) that are never allowed
|
4
4
|
# to be called automatically via set.
|
5
|
-
RESTRICTED_SETTER_METHODS = %w"== === []= taguri= typecast_on_assignment= strict_param_setting= raise_on_save_failure="
|
5
|
+
RESTRICTED_SETTER_METHODS = %w"== === []= taguri= typecast_empty_string_to_nil= typecast_on_assignment= strict_param_setting= raise_on_save_failure="
|
6
6
|
|
7
7
|
# The current cached associations. A hash with the keys being the
|
8
8
|
# association name symbols and the values being the associated object
|
@@ -22,6 +22,10 @@ module Sequel
|
|
22
22
|
# doesn't exist or access to it is denied.
|
23
23
|
attr_writer :strict_param_setting
|
24
24
|
|
25
|
+
# Whether this model instance should typecast the empty string ('') to
|
26
|
+
# nil for columns that are non string or blob.
|
27
|
+
attr_writer :typecast_empty_string_to_nil
|
28
|
+
|
25
29
|
# Whether this model instance should typecast on attribute assignment
|
26
30
|
attr_writer :typecast_on_assignment
|
27
31
|
|
@@ -49,6 +53,7 @@ module Sequel
|
|
49
53
|
@raise_on_save_failure = model.raise_on_save_failure
|
50
54
|
@strict_param_setting = model.strict_param_setting
|
51
55
|
@typecast_on_assignment = model.typecast_on_assignment
|
56
|
+
@typecast_empty_string_to_nil = model.typecast_empty_string_to_nil
|
52
57
|
if from_db
|
53
58
|
@new = false
|
54
59
|
@values = values
|
@@ -247,7 +252,8 @@ module Sequel
|
|
247
252
|
end
|
248
253
|
|
249
254
|
# Updates the instance with the supplied values with support for virtual
|
250
|
-
# attributes,
|
255
|
+
# attributes, raising an exception if a value is used that doesn't have
|
256
|
+
# a setter method (or ignoring it if strict_param_setting = false).
|
251
257
|
# Does not save the record.
|
252
258
|
#
|
253
259
|
# If no columns have been set for this model (very unlikely), assume symbol
|
@@ -479,6 +485,7 @@ module Sequel
|
|
479
485
|
raise Error, "method #{m} doesn't exist or access is restricted to it"
|
480
486
|
end
|
481
487
|
end
|
488
|
+
self
|
482
489
|
end
|
483
490
|
|
484
491
|
# Returns all methods that can be used for attribute
|
@@ -503,7 +510,7 @@ module Sequel
|
|
503
510
|
if only
|
504
511
|
only.map{|x| "#{x}="}
|
505
512
|
else
|
506
|
-
meths = methods.grep(/=\z/) - RESTRICTED_SETTER_METHODS
|
513
|
+
meths = methods.collect{|x| x.to_s}.grep(/=\z/) - RESTRICTED_SETTER_METHODS
|
507
514
|
meths -= Array(primary_key).map{|x| "#{x}="} if primary_key && model.restrict_primary_key?
|
508
515
|
meths -= except.map{|x| "#{x}="} if except
|
509
516
|
meths
|
@@ -515,6 +522,7 @@ module Sequel
|
|
515
522
|
# for database specific column types.
|
516
523
|
def typecast_value(column, value)
|
517
524
|
return value unless @typecast_on_assignment && @db_schema && (col_schema = @db_schema[column])
|
525
|
+
value = nil if value == '' and @typecast_empty_string_to_nil and col_schema[:type] and ![:string, :blob].include?(col_schema[:type])
|
518
526
|
raise(Error, "nil/NULL is not allowed for the #{column} column") if value.nil? && (col_schema[:allow_null] == false)
|
519
527
|
model.db.typecast_value(col_schema[:type], value)
|
520
528
|
end
|
data/lib/sequel_model/schema.rb
CHANGED
@@ -2,27 +2,27 @@ module Sequel
|
|
2
2
|
class Model
|
3
3
|
# Creates table.
|
4
4
|
def self.create_table
|
5
|
-
db.
|
5
|
+
db.create_table(table_name, @schema)
|
6
6
|
@db_schema = get_db_schema(true) unless @@lazy_load_schema
|
7
7
|
columns
|
8
8
|
end
|
9
9
|
|
10
10
|
# Drops the table if it exists and then runs create_table.
|
11
11
|
def self.create_table!
|
12
|
-
drop_table
|
12
|
+
drop_table rescue nil
|
13
13
|
create_table
|
14
14
|
end
|
15
15
|
|
16
16
|
# Drops table.
|
17
17
|
def self.drop_table
|
18
|
-
db.
|
18
|
+
db.drop_table(table_name)
|
19
19
|
end
|
20
20
|
|
21
21
|
# Returns table schema created with set_schema for direct descendant of Model.
|
22
22
|
# Does not retreive schema information from the database, see db_schema if you
|
23
23
|
# want that.
|
24
24
|
def self.schema
|
25
|
-
@schema || (
|
25
|
+
@schema || (superclass.schema unless superclass == Model)
|
26
26
|
end
|
27
27
|
|
28
28
|
# Defines a table schema (see Schema::Generator for more information).
|
@@ -223,7 +223,12 @@ module Sequel
|
|
223
223
|
:wrong_length => 'is the wrong length'
|
224
224
|
}.merge!(atts.extract_options!)
|
225
225
|
|
226
|
-
|
226
|
+
tag = if opts[:tag]
|
227
|
+
opts[:tag]
|
228
|
+
else
|
229
|
+
([:length] + [:maximum, :minimum, :is, :within].reject{|x| !opts.include?(x)}).join('-').to_sym
|
230
|
+
end
|
231
|
+
atts << {:if=>opts[:if], :tag=>tag}
|
227
232
|
validates_each(*atts) do |o, a, v|
|
228
233
|
next if (v.nil? && opts[:allow_nil]) || (v.blank? && opts[:allow_blank])
|
229
234
|
if m = opts[:maximum]
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'spec_helper.rb')
|
2
|
+
|
3
|
+
unless defined?(ADO_DB)
|
4
|
+
ADO_DB = Sequel.connect(:adapter => 'ado', :driver => "{Microsoft Access Driver (*.mdb)}; DBQ=c:\\Nwind.mdb")
|
5
|
+
end
|
6
|
+
|
7
|
+
context "An ADO dataset" do
|
8
|
+
setup do
|
9
|
+
ADO_DB.create_table!(:items) { text :name }
|
10
|
+
end
|
11
|
+
|
12
|
+
specify "should not raise exceptions when working with empty datasets" do
|
13
|
+
lambda {
|
14
|
+
ADO_DB[:items].all
|
15
|
+
}.should_not raise_error
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'spec_helper.rb')
|
2
|
+
|
3
|
+
unless defined?(INFORMIX_DB)
|
4
|
+
INFORMIX_DB = Sequel.connect('informix://localhost/mydb')
|
5
|
+
end
|
6
|
+
|
7
|
+
if INFORMIX_DB.table_exists?(:test)
|
8
|
+
INFORMIX_DB.drop_table :test
|
9
|
+
end
|
10
|
+
INFORMIX_DB.create_table :test do
|
11
|
+
text :name
|
12
|
+
integer :value
|
13
|
+
|
14
|
+
index :value
|
15
|
+
end
|
16
|
+
|
17
|
+
context "A Informix database" do
|
18
|
+
specify "should provide disconnect functionality" do
|
19
|
+
INFORMIX_DB.execute("select user from dual")
|
20
|
+
INFORMIX_DB.pool.size.should == 1
|
21
|
+
INFORMIX_DB.disconnect
|
22
|
+
INFORMIX_DB.pool.size.should == 0
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context "A Informix dataset" do
|
27
|
+
setup do
|
28
|
+
@d = INFORMIX_DB[:test]
|
29
|
+
@d.delete # remove all records
|
30
|
+
end
|
31
|
+
|
32
|
+
specify "should return the correct record count" do
|
33
|
+
@d.count.should == 0
|
34
|
+
@d << {:name => 'abc', :value => 123}
|
35
|
+
@d << {:name => 'abc', :value => 456}
|
36
|
+
@d << {:name => 'def', :value => 789}
|
37
|
+
@d.count.should == 3
|
38
|
+
end
|
39
|
+
|
40
|
+
specify "should return the correct records" do
|
41
|
+
@d.to_a.should == []
|
42
|
+
@d << {:name => 'abc', :value => 123}
|
43
|
+
@d << {:name => 'abc', :value => 456}
|
44
|
+
@d << {:name => 'def', :value => 789}
|
45
|
+
|
46
|
+
@d.order(:value).to_a.should == [
|
47
|
+
{:name => 'abc', :value => 123},
|
48
|
+
{:name => 'abc', :value => 456},
|
49
|
+
{:name => 'def', :value => 789}
|
50
|
+
]
|
51
|
+
end
|
52
|
+
|
53
|
+
specify "should update records correctly" do
|
54
|
+
@d << {:name => 'abc', :value => 123}
|
55
|
+
@d << {:name => 'abc', :value => 456}
|
56
|
+
@d << {:name => 'def', :value => 789}
|
57
|
+
@d.filter(:name => 'abc').update(:value => 530)
|
58
|
+
|
59
|
+
# the third record should stay the same
|
60
|
+
# floating-point precision bullshit
|
61
|
+
@d[:name => 'def'][:value].should == 789
|
62
|
+
@d.filter(:value => 530).count.should == 2
|
63
|
+
end
|
64
|
+
|
65
|
+
specify "should delete records correctly" do
|
66
|
+
@d << {:name => 'abc', :value => 123}
|
67
|
+
@d << {:name => 'abc', :value => 456}
|
68
|
+
@d << {:name => 'def', :value => 789}
|
69
|
+
@d.filter(:name => 'abc').delete
|
70
|
+
|
71
|
+
@d.count.should == 1
|
72
|
+
@d.first[:name].should == 'def'
|
73
|
+
end
|
74
|
+
|
75
|
+
specify "should be able to literalize booleans" do
|
76
|
+
proc {@d.literal(true)}.should_not raise_error
|
77
|
+
proc {@d.literal(false)}.should_not raise_error
|
78
|
+
end
|
79
|
+
|
80
|
+
specify "should support transactions" do
|
81
|
+
INFORMIX_DB.transaction do
|
82
|
+
@d << {:name => 'abc', :value => 1}
|
83
|
+
end
|
84
|
+
|
85
|
+
@d.count.should == 1
|
86
|
+
end
|
87
|
+
|
88
|
+
specify "should support #first and #last" do
|
89
|
+
@d << {:name => 'abc', :value => 123}
|
90
|
+
@d << {:name => 'abc', :value => 456}
|
91
|
+
@d << {:name => 'def', :value => 789}
|
92
|
+
|
93
|
+
@d.order(:value).first.should == {:name => 'abc', :value => 123}
|
94
|
+
@d.order(:value).last.should == {:name => 'def', :value => 789}
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,764 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'spec_helper.rb')
|
2
|
+
|
3
|
+
unless defined?(MYSQL_USER)
|
4
|
+
MYSQL_USER = 'root'
|
5
|
+
end
|
6
|
+
unless defined?(MYSQL_DB)
|
7
|
+
MYSQL_URL = (ENV['SEQUEL_MY_SPEC_DB']||"mysql://#{MYSQL_USER}@localhost/sandbox") unless defined? MYSQL_URL
|
8
|
+
MYSQL_DB = Sequel.connect(MYSQL_URL)
|
9
|
+
end
|
10
|
+
unless defined?(MYSQL_SOCKET_FILE)
|
11
|
+
MYSQL_SOCKET_FILE = '/tmp/mysql.sock'
|
12
|
+
end
|
13
|
+
|
14
|
+
MYSQL_URI = URI.parse(MYSQL_DB.uri)
|
15
|
+
MYSQL_DB_NAME = (m = /\/(.*)/.match(MYSQL_URI.path)) && m[1]
|
16
|
+
|
17
|
+
MYSQL_DB.create_table! :items do
|
18
|
+
text :name
|
19
|
+
integer :value, :index => true
|
20
|
+
end
|
21
|
+
MYSQL_DB.create_table! :test2 do
|
22
|
+
text :name
|
23
|
+
integer :value
|
24
|
+
end
|
25
|
+
MYSQL_DB.create_table! :booltest do
|
26
|
+
tinyint :value
|
27
|
+
end
|
28
|
+
def MYSQL_DB.sqls
|
29
|
+
(@sqls ||= [])
|
30
|
+
end
|
31
|
+
logger = Object.new
|
32
|
+
def logger.method_missing(m, msg)
|
33
|
+
MYSQL_DB.sqls << msg
|
34
|
+
end
|
35
|
+
MYSQL_DB.logger = logger
|
36
|
+
|
37
|
+
context "A MySQL database" do
|
38
|
+
setup do
|
39
|
+
@db = MYSQL_DB
|
40
|
+
end
|
41
|
+
teardown do
|
42
|
+
Sequel.convert_tinyint_to_bool = true
|
43
|
+
end
|
44
|
+
|
45
|
+
specify "should provide disconnect functionality" do
|
46
|
+
@db.tables
|
47
|
+
@db.pool.size.should == 1
|
48
|
+
@db.disconnect
|
49
|
+
@db.pool.size.should == 0
|
50
|
+
end
|
51
|
+
|
52
|
+
specify "should provide the server version" do
|
53
|
+
@db.server_version.should >= 40000
|
54
|
+
end
|
55
|
+
|
56
|
+
specify "should support sequential primary keys" do
|
57
|
+
@db.create_table!(:with_pk) {primary_key :id; text :name}
|
58
|
+
@db[:with_pk] << {:name => 'abc'}
|
59
|
+
@db[:with_pk] << {:name => 'def'}
|
60
|
+
@db[:with_pk] << {:name => 'ghi'}
|
61
|
+
@db[:with_pk].order(:name).all.should == [
|
62
|
+
{:id => 1, :name => 'abc'},
|
63
|
+
{:id => 2, :name => 'def'},
|
64
|
+
{:id => 3, :name => 'ghi'}
|
65
|
+
]
|
66
|
+
end
|
67
|
+
|
68
|
+
specify "should convert Mysql::Errors to Sequel::Errors" do
|
69
|
+
proc{@db << "SELECT 1 + blah;"}.should raise_error(Sequel::Error)
|
70
|
+
end
|
71
|
+
|
72
|
+
specify "should correctly parse the schema" do
|
73
|
+
@db.schema(:booltest, :reload=>true).should == [[:value, {:type=>:boolean, :allow_null=>true, :max_chars=>nil, :default=>nil, :db_type=>"tinyint", :numeric_precision=>3}]]
|
74
|
+
|
75
|
+
Sequel.convert_tinyint_to_bool = false
|
76
|
+
@db.schema(:booltest, :reload=>true).should == [[:value, {:type=>:integer, :allow_null=>true, :max_chars=>nil, :default=>nil, :db_type=>"tinyint", :numeric_precision=>3}]]
|
77
|
+
end
|
78
|
+
|
79
|
+
specify "should get the schema all database tables if no table name is used" do
|
80
|
+
@db.schema(:booltest, :reload=>true).should == @db.schema(nil, :reload=>true)[:booltest]
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
context "A MySQL dataset" do
|
85
|
+
setup do
|
86
|
+
@d = MYSQL_DB[:items]
|
87
|
+
@d.delete # remove all records
|
88
|
+
MYSQL_DB.sqls.clear
|
89
|
+
end
|
90
|
+
|
91
|
+
specify "should return the correct record count" do
|
92
|
+
@d.count.should == 0
|
93
|
+
@d << {:name => 'abc', :value => 123}
|
94
|
+
@d << {:name => 'abc', :value => 456}
|
95
|
+
@d << {:name => 'def', :value => 789}
|
96
|
+
@d.count.should == 3
|
97
|
+
end
|
98
|
+
|
99
|
+
specify "should return the correct records" do
|
100
|
+
@d.to_a.should == []
|
101
|
+
@d << {:name => 'abc', :value => 123}
|
102
|
+
@d << {:name => 'abc', :value => 456}
|
103
|
+
@d << {:name => 'def', :value => 789}
|
104
|
+
|
105
|
+
@d.order(:value).to_a.should == [
|
106
|
+
{:name => 'abc', :value => 123},
|
107
|
+
{:name => 'abc', :value => 456},
|
108
|
+
{:name => 'def', :value => 789}
|
109
|
+
]
|
110
|
+
end
|
111
|
+
|
112
|
+
specify "should update records correctly" do
|
113
|
+
@d << {:name => 'abc', :value => 123}
|
114
|
+
@d << {:name => 'abc', :value => 456}
|
115
|
+
@d << {:name => 'def', :value => 789}
|
116
|
+
@d.filter(:name => 'abc').update(:value => 530)
|
117
|
+
|
118
|
+
# the third record should stay the same
|
119
|
+
# floating-point precision bullshit
|
120
|
+
@d[:name => 'def'][:value].should == 789
|
121
|
+
@d.filter(:value => 530).count.should == 2
|
122
|
+
end
|
123
|
+
|
124
|
+
specify "should delete records correctly" do
|
125
|
+
@d << {:name => 'abc', :value => 123}
|
126
|
+
@d << {:name => 'abc', :value => 456}
|
127
|
+
@d << {:name => 'def', :value => 789}
|
128
|
+
@d.filter(:name => 'abc').delete
|
129
|
+
|
130
|
+
@d.count.should == 1
|
131
|
+
@d.first[:name].should == 'def'
|
132
|
+
end
|
133
|
+
|
134
|
+
specify "should be able to literalize booleans" do
|
135
|
+
proc {@d.literal(true)}.should_not raise_error
|
136
|
+
proc {@d.literal(false)}.should_not raise_error
|
137
|
+
end
|
138
|
+
|
139
|
+
specify "should quote columns and tables using back-ticks if quoting identifiers" do
|
140
|
+
@d.quote_identifiers = true
|
141
|
+
@d.select(:name).sql.should == \
|
142
|
+
'SELECT `name` FROM `items`'
|
143
|
+
|
144
|
+
@d.select('COUNT(*)'.lit).sql.should == \
|
145
|
+
'SELECT COUNT(*) FROM `items`'
|
146
|
+
|
147
|
+
@d.select(:max[:value]).sql.should == \
|
148
|
+
'SELECT max(`value`) FROM `items`'
|
149
|
+
|
150
|
+
@d.select(:NOW[]).sql.should == \
|
151
|
+
'SELECT NOW() FROM `items`'
|
152
|
+
|
153
|
+
@d.select(:max[:items__value]).sql.should == \
|
154
|
+
'SELECT max(`items`.`value`) FROM `items`'
|
155
|
+
|
156
|
+
@d.order(:name.desc).sql.should == \
|
157
|
+
'SELECT * FROM `items` ORDER BY `name` DESC'
|
158
|
+
|
159
|
+
@d.select('items.name AS item_name'.lit).sql.should == \
|
160
|
+
'SELECT items.name AS item_name FROM `items`'
|
161
|
+
|
162
|
+
@d.select('`name`'.lit).sql.should == \
|
163
|
+
'SELECT `name` FROM `items`'
|
164
|
+
|
165
|
+
@d.select('max(items.`name`) AS `max_name`'.lit).sql.should == \
|
166
|
+
'SELECT max(items.`name`) AS `max_name` FROM `items`'
|
167
|
+
|
168
|
+
@d.select(:test[:abc, 'hello']).sql.should == \
|
169
|
+
"SELECT test(`abc`, 'hello') FROM `items`"
|
170
|
+
|
171
|
+
@d.select(:test[:abc__def, 'hello']).sql.should == \
|
172
|
+
"SELECT test(`abc`.`def`, 'hello') FROM `items`"
|
173
|
+
|
174
|
+
@d.select(:test[:abc__def, 'hello'].as(:x2)).sql.should == \
|
175
|
+
"SELECT test(`abc`.`def`, 'hello') AS `x2` FROM `items`"
|
176
|
+
|
177
|
+
@d.insert_sql(:value => 333).should == \
|
178
|
+
'INSERT INTO `items` (`value`) VALUES (333)'
|
179
|
+
|
180
|
+
@d.insert_sql(:x => :y).should == \
|
181
|
+
'INSERT INTO `items` (`x`) VALUES (`y`)'
|
182
|
+
end
|
183
|
+
|
184
|
+
specify "should quote fields correctly when reversing the order" do
|
185
|
+
@d.quote_identifiers = true
|
186
|
+
@d.reverse_order(:name).sql.should == \
|
187
|
+
'SELECT * FROM `items` ORDER BY `name` DESC'
|
188
|
+
|
189
|
+
@d.reverse_order(:name.desc).sql.should == \
|
190
|
+
'SELECT * FROM `items` ORDER BY `name` ASC'
|
191
|
+
|
192
|
+
@d.reverse_order(:name, :test.desc).sql.should == \
|
193
|
+
'SELECT * FROM `items` ORDER BY `name` DESC, `test` ASC'
|
194
|
+
|
195
|
+
@d.reverse_order(:name.desc, :test).sql.should == \
|
196
|
+
'SELECT * FROM `items` ORDER BY `name` ASC, `test` DESC'
|
197
|
+
end
|
198
|
+
|
199
|
+
specify "should support ORDER clause in UPDATE statements" do
|
200
|
+
@d.order(:name).update_sql(:value => 1).should == \
|
201
|
+
'UPDATE items SET value = 1 ORDER BY name'
|
202
|
+
end
|
203
|
+
|
204
|
+
specify "should support LIMIT clause in UPDATE statements" do
|
205
|
+
@d.limit(10).update_sql(:value => 1).should == \
|
206
|
+
'UPDATE items SET value = 1 LIMIT 10'
|
207
|
+
end
|
208
|
+
|
209
|
+
specify "should support transactions" do
|
210
|
+
MYSQL_DB.transaction do
|
211
|
+
@d << {:name => 'abc', :value => 1}
|
212
|
+
end
|
213
|
+
|
214
|
+
@d.count.should == 1
|
215
|
+
end
|
216
|
+
|
217
|
+
specify "should correctly rollback transactions" do
|
218
|
+
proc do
|
219
|
+
MYSQL_DB.transaction do
|
220
|
+
@d << {:name => 'abc'}
|
221
|
+
raise Interrupt, 'asdf'
|
222
|
+
end
|
223
|
+
end.should raise_error(Interrupt)
|
224
|
+
|
225
|
+
MYSQL_DB.sqls.should == ['BEGIN', "INSERT INTO items (name) VALUES ('abc')", 'ROLLBACK']
|
226
|
+
end
|
227
|
+
|
228
|
+
specify "should handle returning inside of the block by committing" do
|
229
|
+
def MYSQL_DB.ret_commit
|
230
|
+
transaction do
|
231
|
+
self[:items] << {:name => 'abc'}
|
232
|
+
return
|
233
|
+
self[:items] << {:name => 'd'}
|
234
|
+
end
|
235
|
+
end
|
236
|
+
MYSQL_DB.ret_commit
|
237
|
+
MYSQL_DB.sqls.should == ['BEGIN', "INSERT INTO items (name) VALUES ('abc')", 'COMMIT']
|
238
|
+
end
|
239
|
+
|
240
|
+
specify "should support regexps" do
|
241
|
+
@d << {:name => 'abc', :value => 1}
|
242
|
+
@d << {:name => 'bcd', :value => 2}
|
243
|
+
@d.filter(:name => /bc/).count.should == 2
|
244
|
+
@d.filter(:name => /^bc/).count.should == 1
|
245
|
+
end
|
246
|
+
|
247
|
+
specify "should correctly literalize strings with comment backslashes in them" do
|
248
|
+
@d.delete
|
249
|
+
proc {@d << {:name => ':\\'}}.should_not raise_error
|
250
|
+
|
251
|
+
@d.first[:name].should == ':\\'
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
context "MySQL datasets" do
|
256
|
+
setup do
|
257
|
+
@d = MYSQL_DB[:orders]
|
258
|
+
end
|
259
|
+
teardown do
|
260
|
+
Sequel.convert_tinyint_to_bool = true
|
261
|
+
end
|
262
|
+
|
263
|
+
specify "should correctly quote column references" do
|
264
|
+
@d.quote_identifiers = true
|
265
|
+
market = 'ICE'
|
266
|
+
ack_stamp = Time.now - 15 * 60 # 15 minutes ago
|
267
|
+
@d.query do
|
268
|
+
select :market, :minute[:from_unixtime[:ack]].as(:minute)
|
269
|
+
where {(:ack > ack_stamp) & {:market => market}}
|
270
|
+
group_by :minute[:from_unixtime[:ack]]
|
271
|
+
end.sql.should == \
|
272
|
+
"SELECT `market`, minute(from_unixtime(`ack`)) AS `minute` FROM `orders` WHERE ((`ack` > #{@d.literal(ack_stamp)}) AND (`market` = 'ICE')) GROUP BY minute(from_unixtime(`ack`))"
|
273
|
+
end
|
274
|
+
|
275
|
+
specify "should accept and return tinyints as bools or integers when configured to do so" do
|
276
|
+
MYSQL_DB[:booltest].delete
|
277
|
+
MYSQL_DB[:booltest] << {:value=>true}
|
278
|
+
MYSQL_DB[:booltest].all.should == [{:value=>true}]
|
279
|
+
MYSQL_DB[:booltest].delete
|
280
|
+
MYSQL_DB[:booltest] << {:value=>false}
|
281
|
+
MYSQL_DB[:booltest].all.should == [{:value=>false}]
|
282
|
+
|
283
|
+
Sequel.convert_tinyint_to_bool = false
|
284
|
+
MYSQL_DB[:booltest].delete
|
285
|
+
MYSQL_DB[:booltest] << {:value=>true}
|
286
|
+
MYSQL_DB[:booltest].all.should == [{:value=>1}]
|
287
|
+
MYSQL_DB[:booltest].delete
|
288
|
+
MYSQL_DB[:booltest] << {:value=>false}
|
289
|
+
MYSQL_DB[:booltest].all.should == [{:value=>0}]
|
290
|
+
|
291
|
+
MYSQL_DB[:booltest].delete
|
292
|
+
MYSQL_DB[:booltest] << {:value=>1}
|
293
|
+
MYSQL_DB[:booltest].all.should == [{:value=>1}]
|
294
|
+
MYSQL_DB[:booltest].delete
|
295
|
+
MYSQL_DB[:booltest] << {:value=>0}
|
296
|
+
MYSQL_DB[:booltest].all.should == [{:value=>0}]
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
# # Commented out because it was causing subsequent examples to fail for some reason
|
301
|
+
# context "Simple stored procedure test" do
|
302
|
+
# setup do
|
303
|
+
# # Create a simple stored procedure but drop it first if there
|
304
|
+
# MYSQL_DB.execute("DROP PROCEDURE IF EXISTS sp_get_server_id;")
|
305
|
+
# MYSQL_DB.execute("CREATE PROCEDURE sp_get_server_id() SQL SECURITY DEFINER SELECT @@SERVER_ID as server_id;")
|
306
|
+
# end
|
307
|
+
#
|
308
|
+
# specify "should return the server-id via a stored procedure call" do
|
309
|
+
# @server_id = MYSQL_DB["SELECT @@SERVER_ID as server_id;"].first[:server_id] # grab the server_id via a simple query
|
310
|
+
# @server_id_by_sp = MYSQL_DB["CALL sp_get_server_id();"].first[:server_id]
|
311
|
+
# @server_id_by_sp.should == @server_id # compare it to output from stored procedure
|
312
|
+
# end
|
313
|
+
# end
|
314
|
+
#
|
315
|
+
context "MySQL join expressions" do
|
316
|
+
setup do
|
317
|
+
@ds = MYSQL_DB[:nodes]
|
318
|
+
@ds.db.meta_def(:server_version) {50014}
|
319
|
+
end
|
320
|
+
|
321
|
+
specify "should raise error for :full_outer join requests." do
|
322
|
+
lambda{@ds.join_table(:full_outer, :nodes)}.should raise_error(Sequel::Error::InvalidJoinType)
|
323
|
+
end
|
324
|
+
specify "should support natural left joins" do
|
325
|
+
@ds.join_table(:natural_left, :nodes).sql.should == \
|
326
|
+
'SELECT * FROM nodes NATURAL LEFT JOIN nodes'
|
327
|
+
end
|
328
|
+
specify "should support natural right joins" do
|
329
|
+
@ds.join_table(:natural_right, :nodes).sql.should == \
|
330
|
+
'SELECT * FROM nodes NATURAL RIGHT JOIN nodes'
|
331
|
+
end
|
332
|
+
specify "should support natural left outer joins" do
|
333
|
+
@ds.join_table(:natural_left_outer, :nodes).sql.should == \
|
334
|
+
'SELECT * FROM nodes NATURAL LEFT OUTER JOIN nodes'
|
335
|
+
end
|
336
|
+
specify "should support natural right outer joins" do
|
337
|
+
@ds.join_table(:natural_right_outer, :nodes).sql.should == \
|
338
|
+
'SELECT * FROM nodes NATURAL RIGHT OUTER JOIN nodes'
|
339
|
+
end
|
340
|
+
specify "should support natural inner joins" do
|
341
|
+
@ds.join_table(:natural_inner, :nodes).sql.should == \
|
342
|
+
'SELECT * FROM nodes NATURAL LEFT JOIN nodes'
|
343
|
+
end
|
344
|
+
specify "should support cross joins" do
|
345
|
+
@ds.join_table(:cross, :nodes).sql.should == \
|
346
|
+
'SELECT * FROM nodes CROSS JOIN nodes'
|
347
|
+
end
|
348
|
+
specify "should support cross joins as inner joins if conditions are used" do
|
349
|
+
@ds.join_table(:cross, :nodes, :id=>:id).sql.should == \
|
350
|
+
'SELECT * FROM nodes INNER JOIN nodes ON (nodes.id = nodes.id)'
|
351
|
+
end
|
352
|
+
specify "should support straight joins (force left table to be read before right)" do
|
353
|
+
@ds.join_table(:straight, :nodes).sql.should == \
|
354
|
+
'SELECT * FROM nodes STRAIGHT_JOIN nodes'
|
355
|
+
end
|
356
|
+
specify "should support natural joins on multiple tables." do
|
357
|
+
@ds.join_table(:natural_left_outer, [:nodes, :branches]).sql.should == \
|
358
|
+
'SELECT * FROM nodes NATURAL LEFT OUTER JOIN (nodes, branches)'
|
359
|
+
end
|
360
|
+
specify "should support straight joins on multiple tables." do
|
361
|
+
@ds.join_table(:straight, [:nodes,:branches]).sql.should == \
|
362
|
+
'SELECT * FROM nodes STRAIGHT_JOIN (nodes, branches)'
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
366
|
+
context "Joined MySQL dataset" do
|
367
|
+
setup do
|
368
|
+
@ds = MYSQL_DB[:nodes]
|
369
|
+
end
|
370
|
+
|
371
|
+
specify "should quote fields correctly" do
|
372
|
+
@ds.quote_identifiers = true
|
373
|
+
@ds.join(:attributes, :node_id => :id).sql.should == \
|
374
|
+
"SELECT * FROM `nodes` INNER JOIN `attributes` ON (`attributes`.`node_id` = `nodes`.`id`)"
|
375
|
+
end
|
376
|
+
|
377
|
+
specify "should allow a having clause on ungrouped datasets" do
|
378
|
+
proc {@ds.having('blah')}.should_not raise_error
|
379
|
+
|
380
|
+
@ds.having('blah').sql.should == \
|
381
|
+
"SELECT * FROM nodes HAVING (blah)"
|
382
|
+
end
|
383
|
+
|
384
|
+
specify "should put a having clause before an order by clause" do
|
385
|
+
@ds.order(:aaa).having(:bbb => :ccc).sql.should == \
|
386
|
+
"SELECT * FROM nodes HAVING (bbb = ccc) ORDER BY aaa"
|
387
|
+
end
|
388
|
+
end
|
389
|
+
|
390
|
+
context "A MySQL database" do
|
391
|
+
setup do
|
392
|
+
@db = MYSQL_DB
|
393
|
+
end
|
394
|
+
|
395
|
+
specify "should support add_column operations" do
|
396
|
+
@db.add_column :test2, :xyz, :text
|
397
|
+
|
398
|
+
@db[:test2].columns.should == [:name, :value, :xyz]
|
399
|
+
@db[:test2] << {:name => 'mmm', :value => 111, :xyz => '000'}
|
400
|
+
@db[:test2].first[:xyz].should == '000'
|
401
|
+
end
|
402
|
+
|
403
|
+
specify "should support drop_column operations" do
|
404
|
+
@db[:test2].columns.should == [:name, :value, :xyz]
|
405
|
+
@db.drop_column :test2, :xyz
|
406
|
+
|
407
|
+
@db[:test2].columns.should == [:name, :value]
|
408
|
+
end
|
409
|
+
|
410
|
+
specify "should support rename_column operations" do
|
411
|
+
@db[:test2].delete
|
412
|
+
@db.add_column :test2, :xyz, :text
|
413
|
+
@db[:test2] << {:name => 'mmm', :value => 111, :xyz => 'qqqq'}
|
414
|
+
|
415
|
+
@db[:test2].columns.should == [:name, :value, :xyz]
|
416
|
+
@db.rename_column :test2, :xyz, :zyx, :type => :text
|
417
|
+
@db[:test2].columns.should == [:name, :value, :zyx]
|
418
|
+
@db[:test2].first[:zyx].should == 'qqqq'
|
419
|
+
end
|
420
|
+
|
421
|
+
specify "should support rename_column operations with types like varchar(255)" do
|
422
|
+
@db[:test2].delete
|
423
|
+
@db.add_column :test2, :tre, :text
|
424
|
+
@db[:test2] << {:name => 'mmm', :value => 111, :tre => 'qqqq'}
|
425
|
+
|
426
|
+
@db[:test2].columns.should == [:name, :value, :zyx, :tre]
|
427
|
+
@db.rename_column :test2, :tre, :ert, :type => :varchar[255]
|
428
|
+
@db[:test2].columns.should == [:name, :value, :zyx, :ert]
|
429
|
+
@db[:test2].first[:ert].should == 'qqqq'
|
430
|
+
end
|
431
|
+
|
432
|
+
specify "should support set_column_type operations" do
|
433
|
+
@db.add_column :test2, :xyz, :float
|
434
|
+
@db[:test2].delete
|
435
|
+
@db[:test2] << {:name => 'mmm', :value => 111, :xyz => 56.78}
|
436
|
+
@db.set_column_type :test2, :xyz, :integer
|
437
|
+
|
438
|
+
@db[:test2].first[:xyz].should == 57
|
439
|
+
end
|
440
|
+
|
441
|
+
specify "should support add_index" do
|
442
|
+
@db.add_index :test2, :value
|
443
|
+
end
|
444
|
+
|
445
|
+
specify "should support drop_index" do
|
446
|
+
@db.drop_index :test2, :value
|
447
|
+
end
|
448
|
+
end
|
449
|
+
|
450
|
+
context "A MySQL database" do
|
451
|
+
setup do
|
452
|
+
@db = MYSQL_DB
|
453
|
+
end
|
454
|
+
|
455
|
+
specify "should support defaults for boolean columns" do
|
456
|
+
g = Sequel::Schema::Generator.new(@db) do
|
457
|
+
boolean :active1, :default => true
|
458
|
+
boolean :active2, :default => false
|
459
|
+
end
|
460
|
+
statements = @db.create_table_sql_list(:items, *g.create_info)
|
461
|
+
statements.should == [
|
462
|
+
"CREATE TABLE items (active1 boolean DEFAULT 1, active2 boolean DEFAULT 0)"
|
463
|
+
]
|
464
|
+
end
|
465
|
+
|
466
|
+
specify "should correctly format CREATE TABLE statements with foreign keys" do
|
467
|
+
g = Sequel::Schema::Generator.new(@db) do
|
468
|
+
foreign_key :p_id, :table => :users, :key => :id,
|
469
|
+
:null => false, :on_delete => :cascade
|
470
|
+
end
|
471
|
+
@db.create_table_sql_list(:items, *g.create_info).should == [
|
472
|
+
"CREATE TABLE items (p_id integer NOT NULL, FOREIGN KEY (p_id) REFERENCES users(id) ON DELETE CASCADE)"
|
473
|
+
]
|
474
|
+
end
|
475
|
+
|
476
|
+
specify "should accept repeated raw sql statements using Database#<<" do
|
477
|
+
@db << 'DELETE FROM items'
|
478
|
+
@db[:items].count.should == 0
|
479
|
+
|
480
|
+
@db << "INSERT INTO items (name, value) VALUES ('tutu', 1234)"
|
481
|
+
@db[:items].first.should == {:name => 'tutu', :value => 1234}
|
482
|
+
|
483
|
+
@db << 'DELETE FROM items'
|
484
|
+
@db[:items].first.should == nil
|
485
|
+
end
|
486
|
+
end
|
487
|
+
|
488
|
+
# Socket tests should only be run if the MySQL server is on localhost
|
489
|
+
if %w'localhost 127.0.0.1 ::1'.include? MYSQL_URI.host
|
490
|
+
context "A MySQL database" do
|
491
|
+
specify "should accept a socket option" do
|
492
|
+
db = Sequel.mysql(MYSQL_DB_NAME, :host => 'localhost', :user => MYSQL_USER, :socket => MYSQL_SOCKET_FILE)
|
493
|
+
proc {db.test_connection}.should_not raise_error
|
494
|
+
end
|
495
|
+
|
496
|
+
specify "should accept a socket option without host option" do
|
497
|
+
db = Sequel.mysql(MYSQL_DB_NAME, :user => MYSQL_USER, :socket => MYSQL_SOCKET_FILE)
|
498
|
+
proc {db.test_connection}.should_not raise_error
|
499
|
+
end
|
500
|
+
|
501
|
+
specify "should fail to connect with invalid socket" do
|
502
|
+
db = Sequel.mysql(MYSQL_DB_NAME, :host => 'localhost', :user => MYSQL_USER, :socket => 'blah')
|
503
|
+
proc {db.test_connection}.should raise_error
|
504
|
+
end
|
505
|
+
end
|
506
|
+
end
|
507
|
+
|
508
|
+
context "A grouped MySQL dataset" do
|
509
|
+
setup do
|
510
|
+
MYSQL_DB[:test2].delete
|
511
|
+
MYSQL_DB[:test2] << {:name => '11', :value => 10}
|
512
|
+
MYSQL_DB[:test2] << {:name => '11', :value => 20}
|
513
|
+
MYSQL_DB[:test2] << {:name => '11', :value => 30}
|
514
|
+
MYSQL_DB[:test2] << {:name => '12', :value => 10}
|
515
|
+
MYSQL_DB[:test2] << {:name => '12', :value => 20}
|
516
|
+
MYSQL_DB[:test2] << {:name => '13', :value => 10}
|
517
|
+
end
|
518
|
+
|
519
|
+
specify "should return the correct count for raw sql query" do
|
520
|
+
ds = MYSQL_DB["select name FROM test2 WHERE name = '11' GROUP BY name"]
|
521
|
+
ds.count.should == 1
|
522
|
+
end
|
523
|
+
|
524
|
+
specify "should return the correct count for a normal dataset" do
|
525
|
+
ds = MYSQL_DB[:test2].select(:name).where(:name => '11').group(:name)
|
526
|
+
ds.count.should == 1
|
527
|
+
end
|
528
|
+
end
|
529
|
+
|
530
|
+
context "A MySQL database" do
|
531
|
+
setup do
|
532
|
+
end
|
533
|
+
|
534
|
+
specify "should support fulltext indexes" do
|
535
|
+
g = Sequel::Schema::Generator.new(MYSQL_DB) do
|
536
|
+
text :title
|
537
|
+
text :body
|
538
|
+
full_text_index [:title, :body]
|
539
|
+
end
|
540
|
+
MYSQL_DB.create_table_sql_list(:posts, *g.create_info).should == [
|
541
|
+
"CREATE TABLE posts (title text, body text)",
|
542
|
+
"CREATE FULLTEXT INDEX posts_title_body_index ON posts (title, body)"
|
543
|
+
]
|
544
|
+
end
|
545
|
+
|
546
|
+
specify "should support full_text_search" do
|
547
|
+
MYSQL_DB[:posts].full_text_search(:title, 'ruby').sql.should ==
|
548
|
+
"SELECT * FROM posts WHERE (MATCH (title) AGAINST ('ruby'))"
|
549
|
+
|
550
|
+
MYSQL_DB[:posts].full_text_search([:title, :body], ['ruby', 'sequel']).sql.should ==
|
551
|
+
"SELECT * FROM posts WHERE (MATCH (title, body) AGAINST ('ruby', 'sequel'))"
|
552
|
+
|
553
|
+
MYSQL_DB[:posts].full_text_search(:title, '+ruby -rails', :boolean => true).sql.should ==
|
554
|
+
"SELECT * FROM posts WHERE (MATCH (title) AGAINST ('+ruby -rails' IN BOOLEAN MODE))"
|
555
|
+
end
|
556
|
+
|
557
|
+
specify "should support spatial indexes" do
|
558
|
+
g = Sequel::Schema::Generator.new(MYSQL_DB) do
|
559
|
+
point :geom
|
560
|
+
spatial_index [:geom]
|
561
|
+
end
|
562
|
+
MYSQL_DB.create_table_sql_list(:posts, *g.create_info).should == [
|
563
|
+
"CREATE TABLE posts (geom point)",
|
564
|
+
"CREATE SPATIAL INDEX posts_geom_index ON posts (geom)"
|
565
|
+
]
|
566
|
+
end
|
567
|
+
|
568
|
+
specify "should support indexes with index type" do
|
569
|
+
g = Sequel::Schema::Generator.new(MYSQL_DB) do
|
570
|
+
text :title
|
571
|
+
index :title, :type => :hash
|
572
|
+
end
|
573
|
+
MYSQL_DB.create_table_sql_list(:posts, *g.create_info).should == [
|
574
|
+
"CREATE TABLE posts (title text)",
|
575
|
+
"CREATE INDEX posts_title_index ON posts (title) USING hash"
|
576
|
+
]
|
577
|
+
end
|
578
|
+
|
579
|
+
specify "should support unique indexes with index type" do
|
580
|
+
g = Sequel::Schema::Generator.new(MYSQL_DB) do
|
581
|
+
text :title
|
582
|
+
index :title, :type => :hash, :unique => true
|
583
|
+
end
|
584
|
+
MYSQL_DB.create_table_sql_list(:posts, *g.create_info).should == [
|
585
|
+
"CREATE TABLE posts (title text)",
|
586
|
+
"CREATE UNIQUE INDEX posts_title_index ON posts (title) USING hash"
|
587
|
+
]
|
588
|
+
end
|
589
|
+
end
|
590
|
+
|
591
|
+
context "MySQL::Dataset#insert" do
|
592
|
+
setup do
|
593
|
+
@d = MYSQL_DB[:items]
|
594
|
+
@d.delete # remove all records
|
595
|
+
MYSQL_DB.sqls.clear
|
596
|
+
end
|
597
|
+
|
598
|
+
specify "should insert record with default values when no arguments given" do
|
599
|
+
@d.insert
|
600
|
+
|
601
|
+
MYSQL_DB.sqls.should == [
|
602
|
+
"INSERT INTO items () VALUES ()"
|
603
|
+
]
|
604
|
+
|
605
|
+
@d.all.should == [
|
606
|
+
{:name => nil, :value => nil}
|
607
|
+
]
|
608
|
+
end
|
609
|
+
|
610
|
+
specify "should insert record with default values when empty hash given" do
|
611
|
+
@d.insert({})
|
612
|
+
|
613
|
+
MYSQL_DB.sqls.should == [
|
614
|
+
"INSERT INTO items () VALUES ()"
|
615
|
+
]
|
616
|
+
|
617
|
+
@d.all.should == [
|
618
|
+
{:name => nil, :value => nil}
|
619
|
+
]
|
620
|
+
end
|
621
|
+
|
622
|
+
specify "should insert record with default values when empty array given" do
|
623
|
+
@d.insert []
|
624
|
+
|
625
|
+
MYSQL_DB.sqls.should == [
|
626
|
+
"INSERT INTO items () VALUES ()"
|
627
|
+
]
|
628
|
+
|
629
|
+
@d.all.should == [
|
630
|
+
{:name => nil, :value => nil}
|
631
|
+
]
|
632
|
+
end
|
633
|
+
end
|
634
|
+
|
635
|
+
context "MySQL::Dataset#multi_insert" do
|
636
|
+
setup do
|
637
|
+
@d = MYSQL_DB[:items]
|
638
|
+
@d.delete # remove all records
|
639
|
+
MYSQL_DB.sqls.clear
|
640
|
+
end
|
641
|
+
|
642
|
+
specify "should insert multiple records in a single statement" do
|
643
|
+
@d.multi_insert([{:name => 'abc'}, {:name => 'def'}])
|
644
|
+
|
645
|
+
MYSQL_DB.sqls.should == [
|
646
|
+
'BEGIN',
|
647
|
+
"INSERT INTO items (name) VALUES ('abc'), ('def')",
|
648
|
+
'COMMIT'
|
649
|
+
]
|
650
|
+
|
651
|
+
@d.all.should == [
|
652
|
+
{:name => 'abc', :value => nil}, {:name => 'def', :value => nil}
|
653
|
+
]
|
654
|
+
end
|
655
|
+
|
656
|
+
specify "should split the list of records into batches if :commit_every option is given" do
|
657
|
+
@d.multi_insert([{:value => 1}, {:value => 2}, {:value => 3}, {:value => 4}],
|
658
|
+
:commit_every => 2)
|
659
|
+
|
660
|
+
MYSQL_DB.sqls.should == [
|
661
|
+
'BEGIN',
|
662
|
+
"INSERT INTO items (value) VALUES (1), (2)",
|
663
|
+
'COMMIT',
|
664
|
+
'BEGIN',
|
665
|
+
"INSERT INTO items (value) VALUES (3), (4)",
|
666
|
+
'COMMIT'
|
667
|
+
]
|
668
|
+
|
669
|
+
@d.all.should == [
|
670
|
+
{:name => nil, :value => 1},
|
671
|
+
{:name => nil, :value => 2},
|
672
|
+
{:name => nil, :value => 3},
|
673
|
+
{:name => nil, :value => 4}
|
674
|
+
]
|
675
|
+
end
|
676
|
+
|
677
|
+
specify "should split the list of records into batches if :slice option is given" do
|
678
|
+
@d.multi_insert([{:value => 1}, {:value => 2}, {:value => 3}, {:value => 4}],
|
679
|
+
:slice => 2)
|
680
|
+
|
681
|
+
MYSQL_DB.sqls.should == [
|
682
|
+
'BEGIN',
|
683
|
+
"INSERT INTO items (value) VALUES (1), (2)",
|
684
|
+
'COMMIT',
|
685
|
+
'BEGIN',
|
686
|
+
"INSERT INTO items (value) VALUES (3), (4)",
|
687
|
+
'COMMIT'
|
688
|
+
]
|
689
|
+
|
690
|
+
@d.all.should == [
|
691
|
+
{:name => nil, :value => 1},
|
692
|
+
{:name => nil, :value => 2},
|
693
|
+
{:name => nil, :value => 3},
|
694
|
+
{:name => nil, :value => 4}
|
695
|
+
]
|
696
|
+
end
|
697
|
+
|
698
|
+
specify "should support inserting using columns and values arrays" do
|
699
|
+
@d.multi_insert([:name, :value], [['abc', 1], ['def', 2]])
|
700
|
+
|
701
|
+
MYSQL_DB.sqls.should == [
|
702
|
+
'BEGIN',
|
703
|
+
"INSERT INTO items (name, value) VALUES ('abc', 1), ('def', 2)",
|
704
|
+
'COMMIT'
|
705
|
+
]
|
706
|
+
|
707
|
+
@d.all.should == [
|
708
|
+
{:name => 'abc', :value => 1},
|
709
|
+
{:name => 'def', :value => 2}
|
710
|
+
]
|
711
|
+
end
|
712
|
+
end
|
713
|
+
|
714
|
+
context "MySQL::Dataset#replace" do
|
715
|
+
setup do
|
716
|
+
MYSQL_DB.drop_table(:items) if MYSQL_DB.table_exists?(:items)
|
717
|
+
MYSQL_DB.create_table :items do
|
718
|
+
integer :id, :unique => true
|
719
|
+
integer :value, :index => true
|
720
|
+
end
|
721
|
+
@d = MYSQL_DB[:items]
|
722
|
+
MYSQL_DB.sqls.clear
|
723
|
+
end
|
724
|
+
|
725
|
+
specify "should create a record if the condition is not met" do
|
726
|
+
@d.replace(:id => 111, :value => 333)
|
727
|
+
@d.all.should == [{:id => 111, :value => 333}]
|
728
|
+
end
|
729
|
+
|
730
|
+
specify "should update a record if the condition is met" do
|
731
|
+
@d << {:id => 111}
|
732
|
+
@d.all.should == [{:id => 111, :value => nil}]
|
733
|
+
@d.replace(:id => 111, :value => 333)
|
734
|
+
@d.all.should == [{:id => 111, :value => 333}]
|
735
|
+
end
|
736
|
+
end
|
737
|
+
|
738
|
+
context "MySQL::Dataset#complex_expression_sql" do
|
739
|
+
setup do
|
740
|
+
@d = MYSQL_DB.dataset
|
741
|
+
end
|
742
|
+
|
743
|
+
specify "should handle pattern matches correctly" do
|
744
|
+
@d.literal(:x.like('a')).should == "(x LIKE BINARY 'a')"
|
745
|
+
@d.literal(~:x.like('a')).should == "(x NOT LIKE BINARY 'a')"
|
746
|
+
@d.literal(:x.ilike('a')).should == "(x LIKE 'a')"
|
747
|
+
@d.literal(~:x.ilike('a')).should == "(x NOT LIKE 'a')"
|
748
|
+
@d.literal(:x.like(/a/)).should == "(x REGEXP BINARY 'a')"
|
749
|
+
@d.literal(~:x.like(/a/)).should == "(x NOT REGEXP BINARY 'a')"
|
750
|
+
@d.literal(:x.like(/a/i)).should == "(x REGEXP 'a')"
|
751
|
+
@d.literal(~:x.like(/a/i)).should == "(x NOT REGEXP 'a')"
|
752
|
+
end
|
753
|
+
|
754
|
+
specify "should handle string concatenation with CONCAT if more than one record" do
|
755
|
+
@d.literal([:x, :y].sql_string_join).should == "CONCAT(x, y)"
|
756
|
+
@d.literal([:x, :y].sql_string_join(' ')).should == "CONCAT(x, ' ', y)"
|
757
|
+
@d.literal([:x[:y], 1, 'z'.lit].sql_string_join(:y|1)).should == "CONCAT(x(y), y[1], '1', y[1], z)"
|
758
|
+
end
|
759
|
+
|
760
|
+
specify "should handle string concatenation as simple string if just one record" do
|
761
|
+
@d.literal([:x].sql_string_join).should == "x"
|
762
|
+
@d.literal([:x].sql_string_join(' ')).should == "x"
|
763
|
+
end
|
764
|
+
end
|