sequel 2.5.0 → 2.6.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 +48 -0
- data/Rakefile +16 -6
- data/bin/sequel +0 -0
- data/doc/cheat_sheet.rdoc +4 -4
- data/doc/schema.rdoc +9 -0
- data/lib/sequel_core/adapters/jdbc.rb +7 -7
- data/lib/sequel_core/adapters/mysql.rb +6 -11
- data/lib/sequel_core/adapters/shared/mssql.rb +21 -1
- data/lib/sequel_core/adapters/shared/mysql.rb +19 -27
- data/lib/sequel_core/adapters/shared/postgres.rb +67 -11
- data/lib/sequel_core/adapters/shared/sqlite.rb +11 -22
- data/lib/sequel_core/adapters/sqlite.rb +8 -4
- data/lib/sequel_core/core_sql.rb +4 -4
- data/lib/sequel_core/database.rb +56 -31
- data/lib/sequel_core/database/schema.rb +13 -5
- data/lib/sequel_core/dataset/convenience.rb +1 -1
- data/lib/sequel_core/dataset/sql.rb +30 -15
- data/lib/sequel_core/migration.rb +7 -0
- data/lib/sequel_core/schema/generator.rb +13 -2
- data/lib/sequel_core/schema/sql.rb +27 -81
- data/lib/sequel_core/sql.rb +5 -2
- data/lib/sequel_model.rb +8 -2
- data/lib/sequel_model/associations.rb +7 -4
- data/lib/sequel_model/base.rb +10 -1
- data/lib/sequel_model/eager_loading.rb +29 -10
- data/lib/sequel_model/record.rb +19 -5
- data/spec/adapters/mysql_spec.rb +2 -2
- data/spec/adapters/postgres_spec.rb +26 -5
- data/spec/adapters/sqlite_spec.rb +42 -8
- data/spec/integration/eager_loader_test.rb +51 -58
- data/spec/integration/schema_test.rb +28 -4
- data/spec/sequel_core/core_sql_spec.rb +3 -0
- data/spec/sequel_core/database_spec.rb +24 -0
- data/spec/sequel_core/dataset_spec.rb +25 -17
- data/spec/sequel_core/schema_spec.rb +58 -26
- data/spec/sequel_model/eager_loading_spec.rb +65 -0
- data/spec/sequel_model/hooks_spec.rb +1 -1
- data/spec/sequel_model/model_spec.rb +1 -0
- data/spec/sequel_model/record_spec.rb +74 -1
- data/spec/sequel_model/spec_helper.rb +8 -0
- metadata +5 -3
data/lib/sequel_core/sql.rb
CHANGED
@@ -481,11 +481,14 @@ module Sequel
|
|
481
481
|
# The default value if no conditions are true
|
482
482
|
attr_reader :default
|
483
483
|
|
484
|
+
# The expression to test the conditions against
|
485
|
+
attr_reader :expression
|
486
|
+
|
484
487
|
# Create an object with the given conditions and
|
485
488
|
# default value.
|
486
|
-
def initialize(conditions, default)
|
489
|
+
def initialize(conditions, default, expression = nil)
|
487
490
|
raise(Sequel::Error, 'CaseExpression conditions must be an array with all_two_pairs') unless Array === conditions and conditions.all_two_pairs?
|
488
|
-
@conditions, @default = conditions, default
|
491
|
+
@conditions, @default, @expression = conditions, default, expression
|
489
492
|
end
|
490
493
|
|
491
494
|
# Delegate the creation of the resulting SQL to the given dataset,
|
data/lib/sequel_model.rb
CHANGED
@@ -50,8 +50,9 @@ module Sequel
|
|
50
50
|
# cache_store, cache_ttl, dataset_methods, primary_key, restricted_columns,
|
51
51
|
# sti_dataset, and sti_key. You should not usually need to
|
52
52
|
# access these directly.
|
53
|
-
# * The following class level attr_accessors are created:
|
54
|
-
# strict_param_setting, typecast_empty_string_to_nil,
|
53
|
+
# * The following class level attr_accessors are created: raise_on_typecast_failure,
|
54
|
+
# raise_on_save_failure, strict_param_setting, typecast_empty_string_to_nil,
|
55
|
+
# and typecast_on_assignment:
|
55
56
|
#
|
56
57
|
# # Don't raise an error if a validation attempt fails in
|
57
58
|
# # save/create/save_changes/etc.
|
@@ -72,6 +73,11 @@ module Sequel
|
|
72
73
|
# Model.typecast_empty_string_to_nil = false
|
73
74
|
# m.number = ''
|
74
75
|
# m.number # => '' instead of nil
|
76
|
+
# # Don't raise if unable to typecast data for a column
|
77
|
+
# Model.typecast_empty_string_to_nil = true
|
78
|
+
# Model.raise_on_typecast_failure = false
|
79
|
+
# m.not_null_column = '' # => nil
|
80
|
+
# m.number = 'A' # => 'A'
|
75
81
|
#
|
76
82
|
# * The following class level method aliases are defined:
|
77
83
|
# * Model.dataset= => set_dataset
|
@@ -135,6 +135,8 @@ module Sequel::Model::Associations
|
|
135
135
|
# an array with two arguments for the value to specify a limit and offset.
|
136
136
|
# - :order - the column(s) by which to order the association dataset. Can be a
|
137
137
|
# singular column or an array.
|
138
|
+
# - :order_eager_graph - Whether to add the order to the dataset's order when graphing
|
139
|
+
# via eager graph. Defaults to true, so set to false to disable.
|
138
140
|
# - :read_only - Do not add a setter method (for many_to_one or one_to_many with :one_to_one),
|
139
141
|
# or add_/remove_/remove_all_ methods (for one_to_many, many_to_many)
|
140
142
|
# - :reciprocal - the symbol name of the reciprocal association,
|
@@ -192,6 +194,7 @@ module Sequel::Model::Associations
|
|
192
194
|
opts = AssociationReflection.new.merge!(opts)
|
193
195
|
opts[:eager_block] = block unless opts.include?(:eager_block)
|
194
196
|
opts[:graph_join_type] ||= :left_outer
|
197
|
+
opts[:order_eager_graph] = true unless opts.include?(:order_eager_graph)
|
195
198
|
opts[:graph_conditions] = opts[:graph_conditions] ? opts[:graph_conditions].to_a : []
|
196
199
|
opts[:graph_select] = Array(opts[:graph_select]) if opts[:graph_select]
|
197
200
|
[:before_add, :before_remove, :after_add, :after_remove, :after_load, :extend].each do |cb_type|
|
@@ -325,12 +328,12 @@ module Sequel::Model::Associations
|
|
325
328
|
opts[:eager_loader] ||= proc do |key_hash, records, associations|
|
326
329
|
h = key_hash[key]
|
327
330
|
keys = h.keys
|
331
|
+
# Default the cached association to nil, so any object that doesn't have it
|
332
|
+
# populated will have cached the negative lookup.
|
333
|
+
records.each{|object| object.associations[name] = nil}
|
328
334
|
# Skip eager loading if no objects have a foreign key for this association
|
329
335
|
unless keys.empty?
|
330
|
-
|
331
|
-
# populated will have cached the negative lookup.
|
332
|
-
records.each{|object| object.associations[name] = nil}
|
333
|
-
model.eager_loading_dataset(opts, opts.associated_class.filter(opts.associated_primary_key.qualify(opts.associated_class.table_name)=>keys), opts.select, associations).all do |assoc_record|
|
336
|
+
model.eager_loading_dataset(opts, opts.associated_class.filter(opts.associated_primary_key.qualify(opts.associated_class.table_name)=>keys), opts.select, associations).all do |assoc_record|
|
334
337
|
next unless objects = h[assoc_record.pk]
|
335
338
|
objects.each{|object| object.associations[name] = assoc_record}
|
336
339
|
end
|
data/lib/sequel_model/base.rb
CHANGED
@@ -9,6 +9,7 @@ module Sequel
|
|
9
9
|
@dataset_methods = {}
|
10
10
|
@primary_key = :id
|
11
11
|
@raise_on_save_failure = true
|
12
|
+
@raise_on_typecast_failure = true
|
12
13
|
@restrict_primary_key = true
|
13
14
|
@restricted_columns = nil
|
14
15
|
@sti_dataset = nil
|
@@ -32,6 +33,10 @@ module Sequel
|
|
32
33
|
# to save/create/save_changes/etc.
|
33
34
|
metaattr_accessor :raise_on_save_failure
|
34
35
|
|
36
|
+
# Whether to raise an error when unable to typecast data for a column
|
37
|
+
# (default: true)
|
38
|
+
metaattr_accessor :raise_on_typecast_failure
|
39
|
+
|
35
40
|
# Which columns should not be update in a call to set
|
36
41
|
# (default: no columns).
|
37
42
|
metaattr_reader :restricted_columns
|
@@ -70,7 +75,8 @@ module Sequel
|
|
70
75
|
:@cache_ttl=>nil, :@dataset_methods=>:dup, :@primary_key=>nil,
|
71
76
|
:@raise_on_save_failure=>nil, :@restricted_columns=>:dup, :@restrict_primary_key=>nil,
|
72
77
|
:@sti_dataset=>nil, :@sti_key=>nil, :@strict_param_setting=>nil,
|
73
|
-
:@typecast_empty_string_to_nil=>nil, :@typecast_on_assignment=>nil
|
78
|
+
:@typecast_empty_string_to_nil=>nil, :@typecast_on_assignment=>nil,
|
79
|
+
:@raise_on_typecast_failure=>nil}
|
74
80
|
|
75
81
|
# Returns the first record from the database matching the conditions.
|
76
82
|
# If a hash is given, it is used as the conditions. If another
|
@@ -470,6 +476,9 @@ module Sequel
|
|
470
476
|
# returned by the schema.
|
471
477
|
cols = schema_array.collect{|k,v| k}
|
472
478
|
set_columns(cols)
|
479
|
+
# Set the primary key(s) based on the schema information
|
480
|
+
pks = schema_array.collect{|k,v| k if v[:primary_key]}.compact
|
481
|
+
pks.length > 0 ? set_primary_key(*pks) : no_primary_key
|
473
482
|
# Also set the columns for the dataset, so the dataset
|
474
483
|
# doesn't have to do a query to get them.
|
475
484
|
dataset.instance_variable_set(:@columns, cols)
|
@@ -87,10 +87,8 @@ module Sequel::Model::Associations::EagerLoading
|
|
87
87
|
# create large cartesian products. If you must graph multiple *_to_many associations,
|
88
88
|
# make sure your filters are specific if you have a large database.
|
89
89
|
#
|
90
|
-
#
|
91
|
-
#
|
92
|
-
#
|
93
|
-
# #eager_graph probably won't work correctly on a limited dataset, unless you are
|
90
|
+
# Each association's order, if definied, is respected. #eager_graph probably
|
91
|
+
# won't work correctly on a limited dataset, unless you are
|
94
92
|
# only graphing many_to_one associations.
|
95
93
|
#
|
96
94
|
# Does not use the block defined for the association, since it does a single query for
|
@@ -149,6 +147,8 @@ module Sequel::Model::Associations::EagerLoading
|
|
149
147
|
ds = ds.graph(r[:join_table], use_jt_only_conditions ? r[:graph_join_table_only_conditions] : [[r[:left_key], model.primary_key.qualify(ta)]] + r[:graph_join_table_conditions], :select=>false, :table_alias=>ds.eager_unique_table_alias(ds, r[:join_table]), :join_type=>r[:graph_join_table_join_type], &r[:graph_join_table_block])
|
150
148
|
ds.graph(klass, use_only_conditions ? only_conditions : [[klass.primary_key, r[:right_key]]] + conditions, :select=>select, :table_alias=>assoc_table_alias, :join_type=>join_type, &graph_block)
|
151
149
|
end
|
150
|
+
|
151
|
+
ds = ds.order_more(*Array(r[:order]).map{|c| eager_graph_qualify_order(assoc_table_alias, c)}) if r[:order] and r[:order_eager_graph]
|
152
152
|
eager_graph = ds.opts[:eager_graph]
|
153
153
|
eager_graph[:requirements][assoc_table_alias] = requirements.dup
|
154
154
|
eager_graph[:alias_association_name_map][assoc_table_alias] = assoc_name
|
@@ -250,16 +250,18 @@ module Sequel::Model::Associations::EagerLoading
|
|
250
250
|
# Will either be the table_alias itself or table_alias_N for some integer
|
251
251
|
# N (starting at 0 and increasing until an unused one is found).
|
252
252
|
def eager_unique_table_alias(ds, table_alias)
|
253
|
-
|
253
|
+
used_aliases = ds.opts[:from]
|
254
|
+
graph = ds.opts[:graph]
|
255
|
+
used_aliases += graph[:table_aliases].keys if graph
|
256
|
+
if used_aliases.include?(table_alias)
|
254
257
|
i = 0
|
255
258
|
loop do
|
256
259
|
ta = :"#{table_alias}_#{i}"
|
257
|
-
return ta unless
|
260
|
+
return ta unless used_aliases.include?(ta)
|
258
261
|
i += 1
|
259
262
|
end
|
260
|
-
else
|
261
|
-
table_alias
|
262
263
|
end
|
264
|
+
table_alias
|
263
265
|
end
|
264
266
|
|
265
267
|
private
|
@@ -323,13 +325,30 @@ module Sequel::Model::Associations::EagerLoading
|
|
323
325
|
else
|
324
326
|
list = record.send(alias_map[ta])
|
325
327
|
list.uniq!
|
326
|
-
# Recurse into dependencies
|
327
|
-
list.each{|rec| eager_graph_make_associations_unique(rec, deps, alias_map, type_map)}
|
328
328
|
end
|
329
|
+
# Recurse into dependencies
|
330
|
+
eager_graph_make_associations_unique(list, deps, alias_map, type_map) if list
|
329
331
|
end
|
330
332
|
end
|
331
333
|
end
|
332
334
|
|
335
|
+
# Qualify the given expression if necessary. The only expressions which are qualified are
|
336
|
+
# unqualified symbols and identifiers, either of which may by sorted.
|
337
|
+
def eager_graph_qualify_order(table_alias, expression)
|
338
|
+
case expression
|
339
|
+
when Symbol
|
340
|
+
table, column, aliaz = split_symbol(expression)
|
341
|
+
raise(Sequel::Error, "Can't use an aliased expression in the :order option") if aliaz
|
342
|
+
table ? expression : Sequel::SQL::QualifiedIdentifier.new(table_alias, expression)
|
343
|
+
when Sequel::SQL::Identifier
|
344
|
+
Sequel::SQL::QualifiedIdentifier.new(table_alias, expression)
|
345
|
+
when Sequel::SQL::OrderedExpression
|
346
|
+
Sequel::SQL::OrderedExpression.new(eager_graph_qualify_order(table_alias, expression.expression), expression.descending)
|
347
|
+
else
|
348
|
+
expression
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
333
352
|
# Eagerly load all specified associations
|
334
353
|
def eager_load(a)
|
335
354
|
return if a.empty?
|
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_empty_string_to_nil= 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= raise_on_typecast_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
|
@@ -17,6 +17,10 @@ module Sequel
|
|
17
17
|
# returning nil on a failure to save/save_changes/etc.
|
18
18
|
attr_writer :raise_on_save_failure
|
19
19
|
|
20
|
+
# Whether this model instance should raise an error when it cannot typecast
|
21
|
+
# data for a column correctly.
|
22
|
+
attr_writer :raise_on_typecast_failure
|
23
|
+
|
20
24
|
# Whether this model instance should raise an error if attempting
|
21
25
|
# to call a method through set/update and their variants that either
|
22
26
|
# doesn't exist or access to it is denied.
|
@@ -54,6 +58,7 @@ module Sequel
|
|
54
58
|
@strict_param_setting = model.strict_param_setting
|
55
59
|
@typecast_on_assignment = model.typecast_on_assignment
|
56
60
|
@typecast_empty_string_to_nil = model.typecast_empty_string_to_nil
|
61
|
+
@raise_on_typecast_failure = model.raise_on_typecast_failure
|
57
62
|
if from_db
|
58
63
|
@new = false
|
59
64
|
@values = values
|
@@ -199,7 +204,8 @@ module Sequel
|
|
199
204
|
# Creates or updates the record, after making sure the record
|
200
205
|
# is valid. If the record is not valid, or before_save,
|
201
206
|
# before_create (if new?), or before_update (if !new?) return
|
202
|
-
# false, returns nil unless raise_on_save_failure is true
|
207
|
+
# false, returns nil unless raise_on_save_failure is true (if it
|
208
|
+
# is true, it raises an error).
|
203
209
|
# Otherwise, returns self. You can provide an optional list of
|
204
210
|
# columns to update, in which case it only updates those columns.
|
205
211
|
def save(*columns)
|
@@ -212,7 +218,7 @@ module Sequel
|
|
212
218
|
# in which case it only updates those columns.
|
213
219
|
# If before_save, before_create (if new?), or before_update
|
214
220
|
# (if !new?) return false, returns nil unless raise_on_save_failure
|
215
|
-
# is true. Otherwise, returns self.
|
221
|
+
# is true (if it is true, it raises an error). Otherwise, returns self.
|
216
222
|
def save!(*columns)
|
217
223
|
opts = columns.extract_options!
|
218
224
|
return save_failure(:save) if before_save == false
|
@@ -531,8 +537,16 @@ module Sequel
|
|
531
537
|
def typecast_value(column, value)
|
532
538
|
return value unless @typecast_on_assignment && @db_schema && (col_schema = @db_schema[column])
|
533
539
|
value = nil if value == '' and @typecast_empty_string_to_nil and col_schema[:type] and ![:string, :blob].include?(col_schema[:type])
|
534
|
-
raise(Error, "nil/NULL is not allowed for the #{column} column") if value.nil? && (col_schema[:allow_null] == false)
|
535
|
-
|
540
|
+
raise(Error::InvalidValue, "nil/NULL is not allowed for the #{column} column") if @raise_on_typecast_failure && value.nil? && (col_schema[:allow_null] == false)
|
541
|
+
begin
|
542
|
+
model.db.typecast_value(col_schema[:type], value)
|
543
|
+
rescue Error::InvalidValue
|
544
|
+
if @raise_on_typecast_failure
|
545
|
+
raise
|
546
|
+
else
|
547
|
+
value
|
548
|
+
end
|
549
|
+
end
|
536
550
|
end
|
537
551
|
|
538
552
|
# Set the columns, filtered by the only and except arrays.
|
data/spec/adapters/mysql_spec.rb
CHANGED
@@ -69,10 +69,10 @@ context "A MySQL database" do
|
|
69
69
|
end
|
70
70
|
|
71
71
|
specify "should correctly parse the schema" do
|
72
|
-
@db.schema(:booltest, :reload=>true).should == [[:value, {:type=>:boolean, :allow_null=>true, :
|
72
|
+
@db.schema(:booltest, :reload=>true).should == [[:value, {:type=>:boolean, :allow_null=>true, :primary_key=>false, :default=>nil, :db_type=>"tinyint(4)"}]]
|
73
73
|
|
74
74
|
Sequel.convert_tinyint_to_bool = false
|
75
|
-
@db.schema(:booltest, :reload=>true).should == [[:value, {:type=>:integer, :allow_null=>true, :
|
75
|
+
@db.schema(:booltest, :reload=>true).should == [[:value, {:type=>:integer, :allow_null=>true, :primary_key=>false, :default=>nil, :db_type=>"tinyint(4)"}]]
|
76
76
|
end
|
77
77
|
|
78
78
|
specify "should get the schema all database tables if no table name is used" do
|
@@ -47,13 +47,14 @@ context "A PostgreSQL database" do
|
|
47
47
|
end
|
48
48
|
|
49
49
|
specify "should correctly parse the schema" do
|
50
|
+
require 'logger'
|
50
51
|
@db.schema(:test3, :reload=>true).should == [
|
51
|
-
[:value, {:type=>:integer, :allow_null=>true, :
|
52
|
-
[:time, {:type=>:datetime, :allow_null=>true, :
|
52
|
+
[:value, {:type=>:integer, :allow_null=>true, :default=>nil, :db_type=>"integer", :primary_key=>false}],
|
53
|
+
[:time, {:type=>:datetime, :allow_null=>true, :default=>nil, :db_type=>"timestamp without time zone", :primary_key=>false}]
|
53
54
|
]
|
54
55
|
@db.schema(:test4, :reload=>true).should == [
|
55
|
-
[:name, {:type=>:string, :allow_null=>true, :
|
56
|
-
[:value, {:type=>:blob, :allow_null=>true, :
|
56
|
+
[:name, {:type=>:string, :allow_null=>true, :default=>nil, :db_type=>"character varying(20)", :primary_key=>false}],
|
57
|
+
[:value, {:type=>:blob, :allow_null=>true, :default=>nil, :db_type=>"bytea", :primary_key=>false}]
|
57
58
|
]
|
58
59
|
end
|
59
60
|
|
@@ -343,7 +344,7 @@ context "A PostgreSQL database" do
|
|
343
344
|
end
|
344
345
|
POSTGRES_DB.create_table_sql_list(:posts, *g.create_info).should == [
|
345
346
|
"CREATE TABLE posts (title text, body text)",
|
346
|
-
"CREATE INDEX posts_title_body_index ON posts USING gin (to_tsvector(title || body))"
|
347
|
+
"CREATE INDEX posts_title_body_index ON posts USING gin (to_tsvector('simple', title || body))"
|
347
348
|
]
|
348
349
|
end
|
349
350
|
|
@@ -494,3 +495,23 @@ context "Postgres::Dataset#insert" do
|
|
494
495
|
ds.insert(:name=>'a').should == nil
|
495
496
|
end
|
496
497
|
end
|
498
|
+
|
499
|
+
if POSTGRES_DB.server_version >= 80300
|
500
|
+
|
501
|
+
POSTGRES_DB.create_table! :test6 do
|
502
|
+
text :title
|
503
|
+
full_text_index [:title]
|
504
|
+
end
|
505
|
+
|
506
|
+
context "PostgreSQL tsearch2" do
|
507
|
+
|
508
|
+
specify "should search by indexed column" do
|
509
|
+
# tsearch is by default included from PostgreSQL 8.3
|
510
|
+
ds = POSTGRES_DB[:test6]
|
511
|
+
record = {:title => "oopsla conference"}
|
512
|
+
ds << record
|
513
|
+
actual = ds.full_text_search(:title, "oopsla")
|
514
|
+
actual.should include(record)
|
515
|
+
end
|
516
|
+
end
|
517
|
+
end
|
@@ -9,6 +9,9 @@ context "An SQLite database" do
|
|
9
9
|
before do
|
10
10
|
@db = SQLITE_DB
|
11
11
|
end
|
12
|
+
after do
|
13
|
+
Sequel.datetime_class = Time
|
14
|
+
end
|
12
15
|
|
13
16
|
if SQLITE_DB.auto_vacuum == :none
|
14
17
|
specify "should support getting pragma values" do
|
@@ -128,12 +131,17 @@ context "An SQLite database" do
|
|
128
131
|
@db[:items2].count.should == 2
|
129
132
|
end
|
130
133
|
|
131
|
-
specify "should support timestamps" do
|
132
|
-
|
133
|
-
|
134
|
-
@db[:time] << {:t => t1}
|
135
|
-
|
136
|
-
[
|
134
|
+
specify "should support timestamps and datetimes and respect datetime_class" do
|
135
|
+
@db.create_table!(:time){timestamp :t; datetime :d}
|
136
|
+
t1 = Time.at(1)
|
137
|
+
@db[:time] << {:t => t1, :d => t1.to_i}
|
138
|
+
@db[:time] << {:t => t1.to_i, :d => t1}
|
139
|
+
@db[:time].map(:t).should == [t1, t1]
|
140
|
+
@db[:time].map(:d).should == [t1, t1]
|
141
|
+
t2 = t1.iso8601.to_datetime
|
142
|
+
Sequel.datetime_class = DateTime
|
143
|
+
@db[:time].map(:t).should == [t2, t2]
|
144
|
+
@db[:time].map(:d).should == [t2, t2]
|
137
145
|
end
|
138
146
|
|
139
147
|
specify "should support sequential primary keys" do
|
@@ -159,12 +167,12 @@ context "An SQLite database" do
|
|
159
167
|
|
160
168
|
specify "should correctly parse the schema" do
|
161
169
|
@db.create_table!(:time2) {timestamp :t}
|
162
|
-
@db.schema(:time2, :reload=>true).should == [[:t, {:type=>:datetime, :allow_null=>true, :
|
170
|
+
@db.schema(:time2, :reload=>true).should == [[:t, {:type=>:datetime, :allow_null=>true, :default=>nil, :db_type=>"timestamp", :primary_key=>false}]]
|
163
171
|
end
|
164
172
|
|
165
173
|
specify "should get the schema all database tables if no table name is used" do
|
166
174
|
@db.create_table!(:time2) {timestamp :t}
|
167
|
-
@db.schema(:time2, :reload=>true).should == @db.schema(nil, :reload=>true)[:
|
175
|
+
@db.schema(:time2, :reload=>true).should == @db.schema(nil, :reload=>true)[:time2]
|
168
176
|
end
|
169
177
|
end
|
170
178
|
|
@@ -240,6 +248,32 @@ context "An SQLite dataset" do
|
|
240
248
|
end
|
241
249
|
end
|
242
250
|
|
251
|
+
context "An SQLite dataset AS clause" do
|
252
|
+
specify "should use a string literal for :col___alias" do
|
253
|
+
SQLITE_DB.literal(:c___a).should == "c AS 'a'"
|
254
|
+
end
|
255
|
+
|
256
|
+
specify "should use a string literal for :table__col___alias" do
|
257
|
+
SQLITE_DB.literal(:t__c___a).should == "t.c AS 'a'"
|
258
|
+
end
|
259
|
+
|
260
|
+
specify "should use a string literal for :column.as(:alias)" do
|
261
|
+
SQLITE_DB.literal(:c.as(:a)).should == "c AS 'a'"
|
262
|
+
end
|
263
|
+
|
264
|
+
specify "should use a string literal in the SELECT clause" do
|
265
|
+
SQLITE_DB[:t].select(:c___a).sql.should == "SELECT c AS 'a' FROM t"
|
266
|
+
end
|
267
|
+
|
268
|
+
specify "should use a string literal in the FROM clause" do
|
269
|
+
SQLITE_DB[:t___a].sql.should == "SELECT * FROM t AS 'a'"
|
270
|
+
end
|
271
|
+
|
272
|
+
specify "should use a string literal in the JOIN clause" do
|
273
|
+
SQLITE_DB[:t].join_table(:natural, :j, nil, :a).sql.should == "SELECT * FROM t NATURAL JOIN j AS 'a'"
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
243
277
|
context "An SQLite dataset" do
|
244
278
|
setup do
|
245
279
|
SQLITE_DB.create_table! :items do
|
@@ -2,13 +2,12 @@ require File.join(File.dirname(__FILE__), 'spec_helper.rb')
|
|
2
2
|
|
3
3
|
describe "Eagerly loading a tree structure" do
|
4
4
|
before do
|
5
|
+
INTEGRATION_DB.instance_variable_set(:@schemas, nil)
|
6
|
+
INTEGRATION_DB.create_table!(:nodes) do
|
7
|
+
primary_key :id
|
8
|
+
foreign_key :parent_id, :nodes
|
9
|
+
end
|
5
10
|
class ::Node < Sequel::Model
|
6
|
-
set_schema do
|
7
|
-
primary_key :id
|
8
|
-
foreign_key :parent_id, :nodes
|
9
|
-
end
|
10
|
-
create_table!
|
11
|
-
|
12
11
|
many_to_one :parent
|
13
12
|
one_to_many :children, :key=>:parent_id
|
14
13
|
|
@@ -146,11 +145,11 @@ describe "Association Extensions" do
|
|
146
145
|
@opts[:models][nil].create(vals.merge(:author_id=>author_id))
|
147
146
|
end
|
148
147
|
end
|
148
|
+
INTEGRATION_DB.instance_variable_set(:@schemas, nil)
|
149
|
+
INTEGRATION_DB.create_table!(:authors) do
|
150
|
+
primary_key :id
|
151
|
+
end
|
149
152
|
class ::Author < Sequel::Model
|
150
|
-
set_schema do
|
151
|
-
primary_key :id
|
152
|
-
end
|
153
|
-
create_table!
|
154
153
|
one_to_many :authorships, :extend=>FindOrCreate, :dataset=>(proc do
|
155
154
|
key = pk
|
156
155
|
ds = Authorship.filter(:author_id=>key)
|
@@ -160,13 +159,12 @@ describe "Association Extensions" do
|
|
160
159
|
ds
|
161
160
|
end)
|
162
161
|
end
|
162
|
+
INTEGRATION_DB.create_table!(:authorships) do
|
163
|
+
primary_key :id
|
164
|
+
foreign_key :author_id, :authors
|
165
|
+
text :name
|
166
|
+
end
|
163
167
|
class ::Authorship < Sequel::Model
|
164
|
-
set_schema do
|
165
|
-
primary_key :id
|
166
|
-
foreign_key :author_id, :authors
|
167
|
-
text :name
|
168
|
-
end
|
169
|
-
create_table!
|
170
168
|
many_to_one :author
|
171
169
|
end
|
172
170
|
@author = Author.create
|
@@ -212,11 +210,11 @@ end
|
|
212
210
|
|
213
211
|
describe "has_many :through has_many and has_one :through belongs_to" do
|
214
212
|
before do
|
213
|
+
INTEGRATION_DB.instance_variable_set(:@schemas, nil)
|
214
|
+
INTEGRATION_DB.create_table!(:firms) do
|
215
|
+
primary_key :id
|
216
|
+
end
|
215
217
|
class ::Firm < Sequel::Model
|
216
|
-
set_schema do
|
217
|
-
primary_key :id
|
218
|
-
end
|
219
|
-
create_table!
|
220
218
|
one_to_many :clients
|
221
219
|
one_to_many :invoices, :read_only=>true, \
|
222
220
|
:dataset=>proc{Invoice.eager_graph(:client).filter(:client__firm_id=>pk)}, \
|
@@ -237,22 +235,20 @@ describe "has_many :through has_many and has_one :through belongs_to" do
|
|
237
235
|
end)
|
238
236
|
end
|
239
237
|
|
238
|
+
INTEGRATION_DB.create_table!(:clients) do
|
239
|
+
primary_key :id
|
240
|
+
foreign_key :firm_id, :firms
|
241
|
+
end
|
240
242
|
class ::Client < Sequel::Model
|
241
|
-
set_schema do
|
242
|
-
primary_key :id
|
243
|
-
foreign_key :firm_id, :firms
|
244
|
-
end
|
245
|
-
create_table!
|
246
243
|
many_to_one :firm
|
247
244
|
one_to_many :invoices
|
248
245
|
end
|
249
246
|
|
247
|
+
INTEGRATION_DB.create_table!(:invoices) do
|
248
|
+
primary_key :id
|
249
|
+
foreign_key :client_id, :clients
|
250
|
+
end
|
250
251
|
class ::Invoice < Sequel::Model
|
251
|
-
set_schema do
|
252
|
-
primary_key :id
|
253
|
-
foreign_key :client_id, :clients
|
254
|
-
end
|
255
|
-
create_table!
|
256
252
|
many_to_one :client
|
257
253
|
many_to_one :firm, :key=>nil, :read_only=>true, \
|
258
254
|
:dataset=>proc{Firm.eager_graph(:clients).filter(:clients__id=>client_id)}, \
|
@@ -304,7 +300,7 @@ describe "has_many :through has_many and has_one :through belongs_to" do
|
|
304
300
|
|
305
301
|
it "should return has_many :through has_many records for a single object" do
|
306
302
|
invs = @firm1.invoices.sort_by{|x| x.pk}
|
307
|
-
sqls_should_be(
|
303
|
+
sqls_should_be("SELECT invoices.id, invoices.client_id, client.id AS 'client_id_0', client.firm_id FROM invoices LEFT OUTER JOIN clients AS 'client' ON (client.id = invoices.client_id) WHERE (client.firm_id = 1)")
|
308
304
|
invs.should == [@invoice1, @invoice2, @invoice3]
|
309
305
|
invs[0].client.should == @client1
|
310
306
|
invs[1].client.should == @client1
|
@@ -316,7 +312,7 @@ describe "has_many :through has_many and has_one :through belongs_to" do
|
|
316
312
|
|
317
313
|
it "should eagerly load has_many :through has_many records for multiple objects" do
|
318
314
|
firms = Firm.order(:id).eager(:invoices).all
|
319
|
-
sqls_should_be("SELECT * FROM firms ORDER BY id", "SELECT invoices.id, invoices.client_id, client.id AS client_id_0, client.firm_id FROM invoices LEFT OUTER JOIN clients AS client ON (client.id = invoices.client_id) WHERE (client.firm_id IN (1, 2))")
|
315
|
+
sqls_should_be("SELECT * FROM firms ORDER BY id", "SELECT invoices.id, invoices.client_id, client.id AS 'client_id_0', client.firm_id FROM invoices LEFT OUTER JOIN clients AS 'client' ON (client.id = invoices.client_id) WHERE (client.firm_id IN (1, 2))")
|
320
316
|
firms.should == [@firm1, @firm2]
|
321
317
|
firm1, firm2 = firms
|
322
318
|
invs1 = firm1.invoices.sort_by{|x| x.pk}
|
@@ -337,7 +333,7 @@ describe "has_many :through has_many and has_one :through belongs_to" do
|
|
337
333
|
|
338
334
|
it "should return has_one :through belongs_to records for a single object" do
|
339
335
|
firm = @invoice1.firm
|
340
|
-
sqls_should_be(
|
336
|
+
sqls_should_be("SELECT firms.id, clients.id AS 'clients_id', clients.firm_id FROM firms LEFT OUTER JOIN clients ON (clients.firm_id = firms.id) WHERE (clients.id = 1)")
|
341
337
|
firm.should == @firm1
|
342
338
|
@invoice1.client.should == @client1
|
343
339
|
@invoice1.client.firm.should == @firm1
|
@@ -347,7 +343,7 @@ describe "has_many :through has_many and has_one :through belongs_to" do
|
|
347
343
|
|
348
344
|
it "should eagerly load has_one :through belongs_to records for multiple objects" do
|
349
345
|
invs = Invoice.order(:id).eager(:firm).all
|
350
|
-
sqls_should_be("SELECT * FROM invoices ORDER BY id", "SELECT firms.id, clients.id AS clients_id, clients.firm_id FROM firms LEFT OUTER JOIN clients ON (clients.firm_id = firms.id) WHERE (clients.id IN (1, 2, 3))")
|
346
|
+
sqls_should_be("SELECT * FROM invoices ORDER BY id", "SELECT firms.id, clients.id AS 'clients_id', clients.firm_id FROM firms LEFT OUTER JOIN clients ON (clients.firm_id = firms.id) WHERE (clients.id IN (1, 2, 3))")
|
351
347
|
invs.should == [@invoice1, @invoice2, @invoice3, @invoice4, @invoice5]
|
352
348
|
invs[0].firm.should == @firm1
|
353
349
|
invs[0].client.should == @client1
|
@@ -375,13 +371,13 @@ end
|
|
375
371
|
|
376
372
|
describe "Polymorphic Associations" do
|
377
373
|
before do
|
374
|
+
INTEGRATION_DB.instance_variable_set(:@schemas, nil)
|
375
|
+
INTEGRATION_DB.create_table!(:assets) do
|
376
|
+
primary_key :id
|
377
|
+
integer :attachable_id
|
378
|
+
text :attachable_type
|
379
|
+
end
|
378
380
|
class ::Asset < Sequel::Model
|
379
|
-
set_schema do
|
380
|
-
primary_key :id
|
381
|
-
integer :attachable_id
|
382
|
-
text :attachable_type
|
383
|
-
end
|
384
|
-
create_table!
|
385
381
|
many_to_one :attachable, :reciprocal=>:assets, \
|
386
382
|
:dataset=>(proc do
|
387
383
|
klass = attachable_type.constantize
|
@@ -411,11 +407,10 @@ describe "Polymorphic Associations" do
|
|
411
407
|
end
|
412
408
|
end
|
413
409
|
|
410
|
+
INTEGRATION_DB.create_table!(:posts) do
|
411
|
+
primary_key :id
|
412
|
+
end
|
414
413
|
class ::Post < Sequel::Model
|
415
|
-
set_schema do
|
416
|
-
primary_key :id
|
417
|
-
end
|
418
|
-
create_table!
|
419
414
|
one_to_many :assets, :key=>:attachable_id do |ds|
|
420
415
|
ds.filter(:attachable_type=>'Post')
|
421
416
|
end
|
@@ -438,11 +433,10 @@ describe "Polymorphic Associations" do
|
|
438
433
|
end
|
439
434
|
end
|
440
435
|
|
436
|
+
INTEGRATION_DB.create_table!(:notes) do
|
437
|
+
primary_key :id
|
438
|
+
end
|
441
439
|
class ::Note < Sequel::Model
|
442
|
-
set_schema do
|
443
|
-
primary_key :id
|
444
|
-
end
|
445
|
-
create_table!
|
446
440
|
one_to_many :assets, :key=>:attachable_id do |ds|
|
447
441
|
ds.filter(:attachable_type=>'Note')
|
448
442
|
end
|
@@ -550,12 +544,12 @@ end
|
|
550
544
|
|
551
545
|
describe "many_to_one/one_to_many not referencing primary key" do
|
552
546
|
before do
|
547
|
+
INTEGRATION_DB.instance_variable_set(:@schemas, nil)
|
548
|
+
INTEGRATION_DB.create_table!(:clients) do
|
549
|
+
primary_key :id
|
550
|
+
text :name
|
551
|
+
end
|
553
552
|
class ::Client < Sequel::Model
|
554
|
-
set_schema do
|
555
|
-
primary_key :id
|
556
|
-
text :name
|
557
|
-
end
|
558
|
-
create_table!
|
559
553
|
one_to_many :invoices, :reciprocal=>:client, \
|
560
554
|
:dataset=>proc{Invoice.filter(:client_name=>name)}, \
|
561
555
|
:eager_loader=>(proc do |key_hash, clients, associations|
|
@@ -585,12 +579,11 @@ describe "many_to_one/one_to_many not referencing primary key" do
|
|
585
579
|
end
|
586
580
|
end
|
587
581
|
|
582
|
+
INTEGRATION_DB.create_table!(:invoices) do
|
583
|
+
primary_key :id
|
584
|
+
text :client_name
|
585
|
+
end
|
588
586
|
class ::Invoice < Sequel::Model
|
589
|
-
set_schema do
|
590
|
-
primary_key :id
|
591
|
-
text :client_name
|
592
|
-
end
|
593
|
-
create_table!
|
594
587
|
many_to_one :client, :key=>:client_name, \
|
595
588
|
:dataset=>proc{Client.filter(:name=>client_name)}, \
|
596
589
|
:eager_loader=>(proc do |key_hash, invoices, associations|
|