sequel 2.2.0 → 2.3.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 +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
|