sequel 2.5.0 → 2.6.0
Sign up to get free protection for your applications and to get access to all the features.
- 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|
|