viking-sequel 3.10.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 +3134 -0
- data/COPYING +19 -0
- data/README.rdoc +723 -0
- data/Rakefile +193 -0
- data/bin/sequel +196 -0
- data/doc/advanced_associations.rdoc +644 -0
- data/doc/cheat_sheet.rdoc +218 -0
- data/doc/dataset_basics.rdoc +106 -0
- data/doc/dataset_filtering.rdoc +158 -0
- data/doc/opening_databases.rdoc +296 -0
- data/doc/prepared_statements.rdoc +104 -0
- data/doc/reflection.rdoc +84 -0
- data/doc/release_notes/1.0.txt +38 -0
- data/doc/release_notes/1.1.txt +143 -0
- data/doc/release_notes/1.3.txt +101 -0
- data/doc/release_notes/1.4.0.txt +53 -0
- data/doc/release_notes/1.5.0.txt +155 -0
- data/doc/release_notes/2.0.0.txt +298 -0
- data/doc/release_notes/2.1.0.txt +271 -0
- data/doc/release_notes/2.10.0.txt +328 -0
- data/doc/release_notes/2.11.0.txt +215 -0
- data/doc/release_notes/2.12.0.txt +534 -0
- data/doc/release_notes/2.2.0.txt +253 -0
- data/doc/release_notes/2.3.0.txt +88 -0
- data/doc/release_notes/2.4.0.txt +106 -0
- data/doc/release_notes/2.5.0.txt +137 -0
- data/doc/release_notes/2.6.0.txt +157 -0
- data/doc/release_notes/2.7.0.txt +166 -0
- data/doc/release_notes/2.8.0.txt +171 -0
- data/doc/release_notes/2.9.0.txt +97 -0
- data/doc/release_notes/3.0.0.txt +221 -0
- data/doc/release_notes/3.1.0.txt +406 -0
- data/doc/release_notes/3.10.0.txt +286 -0
- data/doc/release_notes/3.2.0.txt +268 -0
- data/doc/release_notes/3.3.0.txt +192 -0
- data/doc/release_notes/3.4.0.txt +325 -0
- data/doc/release_notes/3.5.0.txt +510 -0
- data/doc/release_notes/3.6.0.txt +366 -0
- data/doc/release_notes/3.7.0.txt +179 -0
- data/doc/release_notes/3.8.0.txt +151 -0
- data/doc/release_notes/3.9.0.txt +233 -0
- data/doc/schema.rdoc +36 -0
- data/doc/sharding.rdoc +113 -0
- data/doc/virtual_rows.rdoc +205 -0
- data/lib/sequel.rb +1 -0
- data/lib/sequel/adapters/ado.rb +90 -0
- data/lib/sequel/adapters/ado/mssql.rb +30 -0
- data/lib/sequel/adapters/amalgalite.rb +176 -0
- data/lib/sequel/adapters/db2.rb +139 -0
- data/lib/sequel/adapters/dbi.rb +113 -0
- data/lib/sequel/adapters/do.rb +188 -0
- data/lib/sequel/adapters/do/mysql.rb +49 -0
- data/lib/sequel/adapters/do/postgres.rb +91 -0
- data/lib/sequel/adapters/do/sqlite.rb +40 -0
- data/lib/sequel/adapters/firebird.rb +283 -0
- data/lib/sequel/adapters/informix.rb +77 -0
- data/lib/sequel/adapters/jdbc.rb +587 -0
- data/lib/sequel/adapters/jdbc/as400.rb +58 -0
- data/lib/sequel/adapters/jdbc/h2.rb +133 -0
- data/lib/sequel/adapters/jdbc/mssql.rb +57 -0
- data/lib/sequel/adapters/jdbc/mysql.rb +78 -0
- data/lib/sequel/adapters/jdbc/oracle.rb +50 -0
- data/lib/sequel/adapters/jdbc/postgresql.rb +108 -0
- data/lib/sequel/adapters/jdbc/sqlite.rb +55 -0
- data/lib/sequel/adapters/mysql.rb +421 -0
- data/lib/sequel/adapters/odbc.rb +143 -0
- data/lib/sequel/adapters/odbc/mssql.rb +42 -0
- data/lib/sequel/adapters/openbase.rb +64 -0
- data/lib/sequel/adapters/oracle.rb +131 -0
- data/lib/sequel/adapters/postgres.rb +504 -0
- data/lib/sequel/adapters/shared/mssql.rb +490 -0
- data/lib/sequel/adapters/shared/mysql.rb +498 -0
- data/lib/sequel/adapters/shared/oracle.rb +195 -0
- data/lib/sequel/adapters/shared/postgres.rb +830 -0
- data/lib/sequel/adapters/shared/progress.rb +44 -0
- data/lib/sequel/adapters/shared/sqlite.rb +389 -0
- data/lib/sequel/adapters/sqlite.rb +224 -0
- data/lib/sequel/adapters/utils/stored_procedures.rb +84 -0
- data/lib/sequel/connection_pool.rb +99 -0
- data/lib/sequel/connection_pool/sharded_single.rb +84 -0
- data/lib/sequel/connection_pool/sharded_threaded.rb +211 -0
- data/lib/sequel/connection_pool/single.rb +29 -0
- data/lib/sequel/connection_pool/threaded.rb +150 -0
- data/lib/sequel/core.rb +293 -0
- data/lib/sequel/core_sql.rb +241 -0
- data/lib/sequel/database.rb +1079 -0
- data/lib/sequel/database/schema_generator.rb +327 -0
- data/lib/sequel/database/schema_methods.rb +203 -0
- data/lib/sequel/database/schema_sql.rb +320 -0
- data/lib/sequel/dataset.rb +32 -0
- data/lib/sequel/dataset/actions.rb +441 -0
- data/lib/sequel/dataset/features.rb +86 -0
- data/lib/sequel/dataset/graph.rb +254 -0
- data/lib/sequel/dataset/misc.rb +119 -0
- data/lib/sequel/dataset/mutation.rb +64 -0
- data/lib/sequel/dataset/prepared_statements.rb +227 -0
- data/lib/sequel/dataset/query.rb +709 -0
- data/lib/sequel/dataset/sql.rb +996 -0
- data/lib/sequel/exceptions.rb +51 -0
- data/lib/sequel/extensions/blank.rb +43 -0
- data/lib/sequel/extensions/inflector.rb +242 -0
- data/lib/sequel/extensions/looser_typecasting.rb +21 -0
- data/lib/sequel/extensions/migration.rb +239 -0
- data/lib/sequel/extensions/named_timezones.rb +61 -0
- data/lib/sequel/extensions/pagination.rb +100 -0
- data/lib/sequel/extensions/pretty_table.rb +82 -0
- data/lib/sequel/extensions/query.rb +52 -0
- data/lib/sequel/extensions/schema_dumper.rb +271 -0
- data/lib/sequel/extensions/sql_expr.rb +122 -0
- data/lib/sequel/extensions/string_date_time.rb +46 -0
- data/lib/sequel/extensions/thread_local_timezones.rb +48 -0
- data/lib/sequel/metaprogramming.rb +9 -0
- data/lib/sequel/model.rb +120 -0
- data/lib/sequel/model/associations.rb +1514 -0
- data/lib/sequel/model/base.rb +1069 -0
- data/lib/sequel/model/default_inflections.rb +45 -0
- data/lib/sequel/model/errors.rb +39 -0
- data/lib/sequel/model/exceptions.rb +21 -0
- data/lib/sequel/model/inflections.rb +162 -0
- data/lib/sequel/model/plugins.rb +70 -0
- data/lib/sequel/plugins/active_model.rb +59 -0
- data/lib/sequel/plugins/association_dependencies.rb +103 -0
- data/lib/sequel/plugins/association_proxies.rb +41 -0
- data/lib/sequel/plugins/boolean_readers.rb +53 -0
- data/lib/sequel/plugins/caching.rb +141 -0
- data/lib/sequel/plugins/class_table_inheritance.rb +214 -0
- data/lib/sequel/plugins/composition.rb +138 -0
- data/lib/sequel/plugins/force_encoding.rb +72 -0
- data/lib/sequel/plugins/hook_class_methods.rb +126 -0
- data/lib/sequel/plugins/identity_map.rb +116 -0
- data/lib/sequel/plugins/instance_filters.rb +98 -0
- data/lib/sequel/plugins/instance_hooks.rb +57 -0
- data/lib/sequel/plugins/lazy_attributes.rb +77 -0
- data/lib/sequel/plugins/many_through_many.rb +208 -0
- data/lib/sequel/plugins/nested_attributes.rb +206 -0
- data/lib/sequel/plugins/optimistic_locking.rb +81 -0
- data/lib/sequel/plugins/rcte_tree.rb +281 -0
- data/lib/sequel/plugins/schema.rb +66 -0
- data/lib/sequel/plugins/serialization.rb +166 -0
- data/lib/sequel/plugins/single_table_inheritance.rb +74 -0
- data/lib/sequel/plugins/subclasses.rb +45 -0
- data/lib/sequel/plugins/tactical_eager_loading.rb +61 -0
- data/lib/sequel/plugins/timestamps.rb +87 -0
- data/lib/sequel/plugins/touch.rb +118 -0
- data/lib/sequel/plugins/typecast_on_load.rb +72 -0
- data/lib/sequel/plugins/validation_class_methods.rb +405 -0
- data/lib/sequel/plugins/validation_helpers.rb +223 -0
- data/lib/sequel/sql.rb +1020 -0
- data/lib/sequel/timezones.rb +161 -0
- data/lib/sequel/version.rb +12 -0
- data/lib/sequel_core.rb +1 -0
- data/lib/sequel_model.rb +1 -0
- data/spec/adapters/firebird_spec.rb +407 -0
- data/spec/adapters/informix_spec.rb +97 -0
- data/spec/adapters/mssql_spec.rb +403 -0
- data/spec/adapters/mysql_spec.rb +1019 -0
- data/spec/adapters/oracle_spec.rb +286 -0
- data/spec/adapters/postgres_spec.rb +969 -0
- data/spec/adapters/spec_helper.rb +51 -0
- data/spec/adapters/sqlite_spec.rb +432 -0
- data/spec/core/connection_pool_spec.rb +808 -0
- data/spec/core/core_sql_spec.rb +417 -0
- data/spec/core/database_spec.rb +1662 -0
- data/spec/core/dataset_spec.rb +3827 -0
- data/spec/core/expression_filters_spec.rb +595 -0
- data/spec/core/object_graph_spec.rb +296 -0
- data/spec/core/schema_generator_spec.rb +159 -0
- data/spec/core/schema_spec.rb +830 -0
- data/spec/core/spec_helper.rb +56 -0
- data/spec/core/version_spec.rb +7 -0
- data/spec/extensions/active_model_spec.rb +76 -0
- data/spec/extensions/association_dependencies_spec.rb +127 -0
- data/spec/extensions/association_proxies_spec.rb +50 -0
- data/spec/extensions/blank_spec.rb +67 -0
- data/spec/extensions/boolean_readers_spec.rb +92 -0
- data/spec/extensions/caching_spec.rb +250 -0
- data/spec/extensions/class_table_inheritance_spec.rb +252 -0
- data/spec/extensions/composition_spec.rb +194 -0
- data/spec/extensions/force_encoding_spec.rb +117 -0
- data/spec/extensions/hook_class_methods_spec.rb +470 -0
- data/spec/extensions/identity_map_spec.rb +202 -0
- data/spec/extensions/inflector_spec.rb +181 -0
- data/spec/extensions/instance_filters_spec.rb +55 -0
- data/spec/extensions/instance_hooks_spec.rb +133 -0
- data/spec/extensions/lazy_attributes_spec.rb +153 -0
- data/spec/extensions/looser_typecasting_spec.rb +39 -0
- data/spec/extensions/many_through_many_spec.rb +884 -0
- data/spec/extensions/migration_spec.rb +332 -0
- data/spec/extensions/named_timezones_spec.rb +72 -0
- data/spec/extensions/nested_attributes_spec.rb +396 -0
- data/spec/extensions/optimistic_locking_spec.rb +100 -0
- data/spec/extensions/pagination_spec.rb +99 -0
- data/spec/extensions/pretty_table_spec.rb +91 -0
- data/spec/extensions/query_spec.rb +85 -0
- data/spec/extensions/rcte_tree_spec.rb +205 -0
- data/spec/extensions/schema_dumper_spec.rb +357 -0
- data/spec/extensions/schema_spec.rb +127 -0
- data/spec/extensions/serialization_spec.rb +209 -0
- data/spec/extensions/single_table_inheritance_spec.rb +96 -0
- data/spec/extensions/spec_helper.rb +91 -0
- data/spec/extensions/sql_expr_spec.rb +89 -0
- data/spec/extensions/string_date_time_spec.rb +93 -0
- data/spec/extensions/subclasses_spec.rb +52 -0
- data/spec/extensions/tactical_eager_loading_spec.rb +65 -0
- data/spec/extensions/thread_local_timezones_spec.rb +45 -0
- data/spec/extensions/timestamps_spec.rb +150 -0
- data/spec/extensions/touch_spec.rb +155 -0
- data/spec/extensions/typecast_on_load_spec.rb +69 -0
- data/spec/extensions/validation_class_methods_spec.rb +984 -0
- data/spec/extensions/validation_helpers_spec.rb +438 -0
- data/spec/integration/associations_test.rb +281 -0
- data/spec/integration/database_test.rb +26 -0
- data/spec/integration/dataset_test.rb +963 -0
- data/spec/integration/eager_loader_test.rb +734 -0
- data/spec/integration/model_test.rb +130 -0
- data/spec/integration/plugin_test.rb +814 -0
- data/spec/integration/prepared_statement_test.rb +213 -0
- data/spec/integration/schema_test.rb +361 -0
- data/spec/integration/spec_helper.rb +73 -0
- data/spec/integration/timezone_test.rb +55 -0
- data/spec/integration/transaction_test.rb +122 -0
- data/spec/integration/type_test.rb +96 -0
- data/spec/model/association_reflection_spec.rb +175 -0
- data/spec/model/associations_spec.rb +2633 -0
- data/spec/model/base_spec.rb +418 -0
- data/spec/model/dataset_methods_spec.rb +78 -0
- data/spec/model/eager_loading_spec.rb +1391 -0
- data/spec/model/hooks_spec.rb +240 -0
- data/spec/model/inflector_spec.rb +26 -0
- data/spec/model/model_spec.rb +593 -0
- data/spec/model/plugins_spec.rb +236 -0
- data/spec/model/record_spec.rb +1500 -0
- data/spec/model/spec_helper.rb +97 -0
- data/spec/model/validations_spec.rb +153 -0
- data/spec/rcov.opts +6 -0
- data/spec/spec_config.rb.example +10 -0
- metadata +346 -0
@@ -0,0 +1,286 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'spec_helper.rb')
|
2
|
+
|
3
|
+
unless defined?(ORACLE_DB)
|
4
|
+
ORACLE_DB = Sequel.connect('oracle://hr:hr@localhost/XE')
|
5
|
+
end
|
6
|
+
INTEGRATION_DB = ORACLE_DB unless defined?(INTEGRATION_DB)
|
7
|
+
|
8
|
+
if ORACLE_DB.table_exists?(:items)
|
9
|
+
ORACLE_DB.drop_table :items
|
10
|
+
end
|
11
|
+
ORACLE_DB.create_table :items do
|
12
|
+
varchar2 :name, :size => 50
|
13
|
+
number :value, :size => 38
|
14
|
+
date :date_created
|
15
|
+
index :value
|
16
|
+
end
|
17
|
+
|
18
|
+
if ORACLE_DB.table_exists?(:books)
|
19
|
+
ORACLE_DB.drop_table :books
|
20
|
+
end
|
21
|
+
ORACLE_DB.create_table :books do
|
22
|
+
number :id, :size => 38
|
23
|
+
varchar2 :title, :size => 50
|
24
|
+
number :category_id, :size => 38
|
25
|
+
end
|
26
|
+
|
27
|
+
if ORACLE_DB.table_exists?(:categories)
|
28
|
+
ORACLE_DB.drop_table :categories
|
29
|
+
end
|
30
|
+
ORACLE_DB.create_table :categories do
|
31
|
+
number :id, :size => 38
|
32
|
+
varchar2 :cat_name, :size => 50
|
33
|
+
end
|
34
|
+
|
35
|
+
context "An Oracle database" do
|
36
|
+
specify "should provide disconnect functionality" do
|
37
|
+
ORACLE_DB.execute("select user from dual")
|
38
|
+
ORACLE_DB.pool.size.should == 1
|
39
|
+
ORACLE_DB.disconnect
|
40
|
+
ORACLE_DB.pool.size.should == 0
|
41
|
+
end
|
42
|
+
|
43
|
+
specify "should provide schema information" do
|
44
|
+
books_schema = [
|
45
|
+
[:id, {:char_size=>0, :type=>:number, :allow_null=>true, :type_string=>"NUMBER(38)", :data_size=>22, :precision=>38, :char_used=>false, :scale=>0, :charset_form=>nil, :fsprecision=>38, :lfprecision=>0, :db_type=>"NUMBER(38)"}],
|
46
|
+
[:title, {:char_size=>50, :type=>:varchar2, :allow_null=>true, :type_string=>"VARCHAR2(50)", :data_size=>50, :precision=>0, :char_used=>false, :scale=>0, :charset_form=>:implicit, :fsprecision=>0, :lfprecision=>0, :db_type=>"VARCHAR2(50)"}],
|
47
|
+
[:category_id, {:char_size=>0, :type=>:number, :allow_null=>true, :type_string=>"NUMBER(38)", :data_size=>22, :precision=>38, :char_used=>false, :scale=>0, :charset_form=>nil, :fsprecision=>38, :lfprecision=>0, :db_type=>"NUMBER(38)"}]]
|
48
|
+
categories_schema = [
|
49
|
+
[:id, {:char_size=>0, :type=>:number, :allow_null=>true, :type_string=>"NUMBER(38)", :data_size=>22, :precision=>38, :char_used=>false, :scale=>0, :charset_form=>nil, :fsprecision=>38, :lfprecision=>0, :db_type=>"NUMBER(38)"}],
|
50
|
+
[:cat_name, {:char_size=>50, :type=>:varchar2, :allow_null=>true, :type_string=>"VARCHAR2(50)", :data_size=>50, :precision=>0, :char_used=>false, :scale=>0, :charset_form=>:implicit, :fsprecision=>0, :lfprecision=>0, :db_type=>"VARCHAR2(50)"}]]
|
51
|
+
items_schema = [
|
52
|
+
[:name, {:char_size=>50, :type=>:varchar2, :allow_null=>true, :type_string=>"VARCHAR2(50)", :data_size=>50, :precision=>0, :char_used=>false, :scale=>0, :charset_form=>:implicit, :fsprecision=>0, :lfprecision=>0, :db_type=>"VARCHAR2(50)"}],
|
53
|
+
[:value, {:char_size=>0, :type=>:number, :allow_null=>true, :type_string=>"NUMBER(38)", :data_size=>22, :precision=>38, :char_used=>false, :scale=>0, :charset_form=>nil, :fsprecision=>38, :lfprecision=>0, :db_type=>"NUMBER(38)"}],
|
54
|
+
[:date_created, {:charset_form=>nil, :type=>:date, :type_string=>"DATE", :fsprecision=>0, :data_size=>7, :lfprecision=>0, :precision=>0, :db_type=>"DATE", :char_used=>false, :char_size=>0, :scale=>0, :allow_null=>true}]]
|
55
|
+
|
56
|
+
{:books => books_schema, :categories => categories_schema, :items => items_schema}.each_pair do |table, expected_schema|
|
57
|
+
schema = ORACLE_DB.schema(table)
|
58
|
+
schema.should_not be_nil
|
59
|
+
expected_schema.should == schema
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
specify "should create a temporary table" do
|
64
|
+
ORACLE_DB.create_table :test_tmp, :temporary => true do
|
65
|
+
primary_key :id, :integer, :null => false
|
66
|
+
column :name, :text
|
67
|
+
index :name, :unique => true
|
68
|
+
end
|
69
|
+
|
70
|
+
ORACLE_DB.sqls.should == [
|
71
|
+
'CREATE GLOBAL TEMPORARY TABLE test_tmp (id integer NOT NULL PRIMARY KEY AUTOINCREMENT, name text)',
|
72
|
+
'CREATE UNIQUE INDEX test_tmp_name_index ON test_tmp (name)'
|
73
|
+
]
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
context "An Oracle dataset" do
|
78
|
+
before do
|
79
|
+
@d = ORACLE_DB[:items]
|
80
|
+
@d.delete # remove all records
|
81
|
+
end
|
82
|
+
|
83
|
+
specify "should return the correct record count" do
|
84
|
+
@d.count.should == 0
|
85
|
+
@d << {:name => 'abc', :value => 123}
|
86
|
+
@d << {:name => 'abc', :value => 456}
|
87
|
+
@d << {:name => 'def', :value => 789}
|
88
|
+
@d.count.should == 3
|
89
|
+
end
|
90
|
+
|
91
|
+
specify "should return the correct records" do
|
92
|
+
@d.to_a.should == []
|
93
|
+
@d << {:name => 'abc', :value => 123}
|
94
|
+
@d << {:name => 'abc', :value => 456}
|
95
|
+
@d << {:name => 'def', :value => 789}
|
96
|
+
|
97
|
+
@d.order(:value).to_a.should == [
|
98
|
+
{:date_created=>nil, :name => 'abc', :value => 123},
|
99
|
+
{:date_created=>nil, :name => 'abc', :value => 456},
|
100
|
+
{:date_created=>nil, :name => 'def', :value => 789}
|
101
|
+
]
|
102
|
+
|
103
|
+
@d.select(:name).distinct.order_by(:name).to_a.should == [
|
104
|
+
{:name => 'abc'},
|
105
|
+
{:name => 'def'}
|
106
|
+
]
|
107
|
+
|
108
|
+
@d.order(:value.desc).limit(1).to_a.should == [
|
109
|
+
{:date_created=>nil, :name => 'def', :value => 789}
|
110
|
+
]
|
111
|
+
|
112
|
+
@d.filter(:name => 'abc').to_a.should == [
|
113
|
+
{:date_created=>nil, :name => 'abc', :value => 123},
|
114
|
+
{:date_created=>nil, :name => 'abc', :value => 456}
|
115
|
+
]
|
116
|
+
|
117
|
+
@d.order(:value.desc).filter(:name => 'abc').to_a.should == [
|
118
|
+
{:date_created=>nil, :name => 'abc', :value => 456},
|
119
|
+
{:date_created=>nil, :name => 'abc', :value => 123}
|
120
|
+
]
|
121
|
+
|
122
|
+
@d.filter(:name => 'abc').limit(1).to_a.should == [
|
123
|
+
{:date_created=>nil, :name => 'abc', :value => 123}
|
124
|
+
]
|
125
|
+
|
126
|
+
@d.filter(:name => 'abc').order(:value.desc).limit(1).to_a.should == [
|
127
|
+
{:date_created=>nil, :name => 'abc', :value => 456}
|
128
|
+
]
|
129
|
+
|
130
|
+
@d.filter(:name => 'abc').order(:value).limit(1).to_a.should == [
|
131
|
+
{:date_created=>nil, :name => 'abc', :value => 123}
|
132
|
+
]
|
133
|
+
|
134
|
+
@d.order(:value).limit(1).to_a.should == [
|
135
|
+
{:date_created=>nil, :name => 'abc', :value => 123}
|
136
|
+
]
|
137
|
+
|
138
|
+
@d.order(:value).limit(1, 1).to_a.should == [
|
139
|
+
{:date_created=>nil, :name => 'abc', :value => 456}
|
140
|
+
]
|
141
|
+
|
142
|
+
@d.order(:value).limit(1, 2).to_a.should == [
|
143
|
+
{:date_created=>nil, :name => 'def', :value => 789}
|
144
|
+
]
|
145
|
+
|
146
|
+
@d.avg(:value).to_i.should == (789+123+456)/3
|
147
|
+
|
148
|
+
@d.max(:value).to_i.should == 789
|
149
|
+
|
150
|
+
@d.select(:name, :AVG.sql_function(:value)).filter(:name => 'abc').group(:name).to_a.should == [
|
151
|
+
{:name => 'abc', :"avg(value)" => (456+123)/2.0}
|
152
|
+
]
|
153
|
+
|
154
|
+
@d.select(:AVG.sql_function(:value)).group(:name).order(:name).limit(1).to_a.should == [
|
155
|
+
{:"avg(value)" => (456+123)/2.0}
|
156
|
+
]
|
157
|
+
|
158
|
+
@d.select(:name, :AVG.sql_function(:value)).group(:name).order(:name).to_a.should == [
|
159
|
+
{:name => 'abc', :"avg(value)" => (456+123)/2.0},
|
160
|
+
{:name => 'def', :"avg(value)" => 789*1.0}
|
161
|
+
]
|
162
|
+
|
163
|
+
@d.select(:name, :AVG.sql_function(:value)).group(:name).order(:name).to_a.should == [
|
164
|
+
{:name => 'abc', :"avg(value)" => (456+123)/2.0},
|
165
|
+
{:name => 'def', :"avg(value)" => 789*1.0}
|
166
|
+
]
|
167
|
+
|
168
|
+
@d.select(:name, :AVG.sql_function(:value)).group(:name).having(:name => ['abc', 'def']).order(:name).to_a.should == [
|
169
|
+
{:name => 'abc', :"avg(value)" => (456+123)/2.0},
|
170
|
+
{:name => 'def', :"avg(value)" => 789*1.0}
|
171
|
+
]
|
172
|
+
|
173
|
+
@d.select(:name, :value).filter(:name => 'abc').union(@d.select(:name, :value).filter(:name => 'def')).order(:value).to_a.should == [
|
174
|
+
{:name => 'abc', :value => 123},
|
175
|
+
{:name => 'abc', :value => 456},
|
176
|
+
{:name => 'def', :value => 789}
|
177
|
+
]
|
178
|
+
|
179
|
+
end
|
180
|
+
|
181
|
+
specify "should update records correctly" do
|
182
|
+
@d << {:name => 'abc', :value => 123}
|
183
|
+
@d << {:name => 'abc', :value => 456}
|
184
|
+
@d << {:name => 'def', :value => 789}
|
185
|
+
@d.filter(:name => 'abc').update(:value => 530)
|
186
|
+
|
187
|
+
# the third record should stay the same
|
188
|
+
# floating-point precision bullshit
|
189
|
+
@d[:name => 'def'][:value].should == 789
|
190
|
+
@d.filter(:value => 530).count.should == 2
|
191
|
+
end
|
192
|
+
|
193
|
+
specify "should translate values correctly" do
|
194
|
+
@d << {:name => 'abc', :value => 456}
|
195
|
+
@d << {:name => 'def', :value => 789}
|
196
|
+
@d.filter(:value > 500).update(:date_created => "to_timestamp('2009-09-09', 'YYYY-MM-DD')".lit)
|
197
|
+
|
198
|
+
@d[:name => 'def'][:date_created].should == Time.parse('2009-09-09')
|
199
|
+
end
|
200
|
+
|
201
|
+
specify "should delete records correctly" do
|
202
|
+
@d << {:name => 'abc', :value => 123}
|
203
|
+
@d << {:name => 'abc', :value => 456}
|
204
|
+
@d << {:name => 'def', :value => 789}
|
205
|
+
@d.filter(:name => 'abc').delete
|
206
|
+
|
207
|
+
@d.count.should == 1
|
208
|
+
@d.first[:name].should == 'def'
|
209
|
+
end
|
210
|
+
|
211
|
+
specify "should be able to literalize booleans" do
|
212
|
+
proc {@d.literal(true)}.should_not raise_error
|
213
|
+
proc {@d.literal(false)}.should_not raise_error
|
214
|
+
end
|
215
|
+
|
216
|
+
specify "should support transactions" do
|
217
|
+
ORACLE_DB.transaction do
|
218
|
+
@d << {:name => 'abc', :value => 1}
|
219
|
+
end
|
220
|
+
|
221
|
+
@d.count.should == 1
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
context "Joined Oracle dataset" do
|
226
|
+
before do
|
227
|
+
@d1 = ORACLE_DB[:books]
|
228
|
+
@d1.delete # remove all records
|
229
|
+
@d1 << {:id => 1, :title => 'aaa', :category_id => 100}
|
230
|
+
@d1 << {:id => 2, :title => 'bbb', :category_id => 100}
|
231
|
+
@d1 << {:id => 3, :title => 'ccc', :category_id => 101}
|
232
|
+
@d1 << {:id => 4, :title => 'ddd', :category_id => 102}
|
233
|
+
|
234
|
+
@d2 = ORACLE_DB[:categories]
|
235
|
+
@d2.delete # remove all records
|
236
|
+
@d2 << {:id => 100, :cat_name => 'ruby'}
|
237
|
+
@d2 << {:id => 101, :cat_name => 'rails'}
|
238
|
+
end
|
239
|
+
|
240
|
+
specify "should return correct result" do
|
241
|
+
@d1.join(:categories, :id => :category_id).select(:books__id, :title, :cat_name).order(:books__id).to_a.should == [
|
242
|
+
{:id => 1, :title => 'aaa', :cat_name => 'ruby'},
|
243
|
+
{:id => 2, :title => 'bbb', :cat_name => 'ruby'},
|
244
|
+
{:id => 3, :title => 'ccc', :cat_name => 'rails'}
|
245
|
+
]
|
246
|
+
|
247
|
+
@d1.join(:categories, :id => :category_id).select(:books__id, :title, :cat_name).order(:books__id).limit(2, 1).to_a.should == [
|
248
|
+
{:id => 2, :title => 'bbb', :cat_name => 'ruby'},
|
249
|
+
{:id => 3, :title => 'ccc', :cat_name => 'rails'},
|
250
|
+
]
|
251
|
+
|
252
|
+
@d1.left_outer_join(:categories, :id => :category_id).select(:books__id, :title, :cat_name).order(:books__id).to_a.should == [
|
253
|
+
{:id => 1, :title => 'aaa', :cat_name => 'ruby'},
|
254
|
+
{:id => 2, :title => 'bbb', :cat_name => 'ruby'},
|
255
|
+
{:id => 3, :title => 'ccc', :cat_name => 'rails'},
|
256
|
+
{:id => 4, :title => 'ddd', :cat_name => nil}
|
257
|
+
]
|
258
|
+
|
259
|
+
@d1.left_outer_join(:categories, :id => :category_id).select(:books__id, :title, :cat_name).order(:books__id.desc).limit(2, 0).to_a.should == [
|
260
|
+
{:id => 4, :title => 'ddd', :cat_name => nil},
|
261
|
+
{:id => 3, :title => 'ccc', :cat_name => 'rails'}
|
262
|
+
]
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
context "Oracle aliasing" do
|
267
|
+
before do
|
268
|
+
@d1 = ORACLE_DB[:books]
|
269
|
+
@d1.delete # remove all records
|
270
|
+
@d1 << {:id => 1, :title => 'aaa', :category_id => 100}
|
271
|
+
@d1 << {:id => 2, :title => 'bbb', :category_id => 100}
|
272
|
+
@d1 << {:id => 3, :title => 'bbb', :category_id => 100}
|
273
|
+
end
|
274
|
+
|
275
|
+
specify "should allow columns to be renamed" do
|
276
|
+
@d1.select(:title.as(:name)).order_by(:id).to_a.should == [
|
277
|
+
{ :name => 'aaa' },
|
278
|
+
{ :name => 'bbb' },
|
279
|
+
{ :name => 'bbb' },
|
280
|
+
]
|
281
|
+
end
|
282
|
+
|
283
|
+
specify "nested queries should work" do
|
284
|
+
@d1.select(:title).group_by(:title).count.should == 2
|
285
|
+
end
|
286
|
+
end
|
@@ -0,0 +1,969 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'spec_helper.rb')
|
2
|
+
|
3
|
+
unless defined?(POSTGRES_DB)
|
4
|
+
POSTGRES_URL = 'postgres://postgres:postgres@localhost:5432/reality_spec' unless defined? POSTGRES_URL
|
5
|
+
POSTGRES_DB = Sequel.connect(ENV['SEQUEL_PG_SPEC_DB']||POSTGRES_URL)
|
6
|
+
end
|
7
|
+
INTEGRATION_DB = POSTGRES_DB unless defined?(INTEGRATION_DB)
|
8
|
+
|
9
|
+
def POSTGRES_DB.sqls
|
10
|
+
(@sqls ||= [])
|
11
|
+
end
|
12
|
+
logger = Object.new
|
13
|
+
def logger.method_missing(m, msg)
|
14
|
+
POSTGRES_DB.sqls << msg
|
15
|
+
end
|
16
|
+
POSTGRES_DB.logger = logger
|
17
|
+
|
18
|
+
#POSTGRES_DB.instance_variable_set(:@server_version, 80100)
|
19
|
+
POSTGRES_DB.create_table! :test do
|
20
|
+
text :name
|
21
|
+
integer :value, :index => true
|
22
|
+
end
|
23
|
+
POSTGRES_DB.create_table! :test2 do
|
24
|
+
text :name
|
25
|
+
integer :value
|
26
|
+
end
|
27
|
+
POSTGRES_DB.create_table! :test3 do
|
28
|
+
integer :value
|
29
|
+
timestamp :time
|
30
|
+
end
|
31
|
+
POSTGRES_DB.create_table! :test4 do
|
32
|
+
varchar :name, :size => 20
|
33
|
+
bytea :value
|
34
|
+
end
|
35
|
+
|
36
|
+
context "A PostgreSQL database" do
|
37
|
+
before do
|
38
|
+
@db = POSTGRES_DB
|
39
|
+
end
|
40
|
+
|
41
|
+
specify "should provide the server version" do
|
42
|
+
@db.server_version.should > 70000
|
43
|
+
end
|
44
|
+
|
45
|
+
specify "should correctly parse the schema" do
|
46
|
+
@db.schema(:test3, :reload=>true).should == [
|
47
|
+
[:value, {:type=>:integer, :allow_null=>true, :default=>nil, :ruby_default=>nil, :db_type=>"integer", :primary_key=>false}],
|
48
|
+
[:time, {:type=>:datetime, :allow_null=>true, :default=>nil, :ruby_default=>nil, :db_type=>"timestamp without time zone", :primary_key=>false}]
|
49
|
+
]
|
50
|
+
@db.schema(:test4, :reload=>true).should == [
|
51
|
+
[:name, {:type=>:string, :allow_null=>true, :default=>nil, :ruby_default=>nil, :db_type=>"character varying(20)", :primary_key=>false}],
|
52
|
+
[:value, {:type=>:blob, :allow_null=>true, :default=>nil, :ruby_default=>nil, :db_type=>"bytea", :primary_key=>false}]
|
53
|
+
]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
context "A PostgreSQL dataset" do
|
58
|
+
before do
|
59
|
+
@d = POSTGRES_DB[:test]
|
60
|
+
@d.delete # remove all records
|
61
|
+
end
|
62
|
+
|
63
|
+
specify "should quote columns and tables using double quotes if quoting identifiers" do
|
64
|
+
@d.quote_identifiers = true
|
65
|
+
@d.select(:name).sql.should == \
|
66
|
+
'SELECT "name" FROM "test"'
|
67
|
+
|
68
|
+
@d.select('COUNT(*)'.lit).sql.should == \
|
69
|
+
'SELECT COUNT(*) FROM "test"'
|
70
|
+
|
71
|
+
@d.select(:max.sql_function(:value)).sql.should == \
|
72
|
+
'SELECT max("value") FROM "test"'
|
73
|
+
|
74
|
+
@d.select(:NOW.sql_function).sql.should == \
|
75
|
+
'SELECT NOW() FROM "test"'
|
76
|
+
|
77
|
+
@d.select(:max.sql_function(:items__value)).sql.should == \
|
78
|
+
'SELECT max("items"."value") FROM "test"'
|
79
|
+
|
80
|
+
@d.order(:name.desc).sql.should == \
|
81
|
+
'SELECT * FROM "test" ORDER BY "name" DESC'
|
82
|
+
|
83
|
+
@d.select('test.name AS item_name'.lit).sql.should == \
|
84
|
+
'SELECT test.name AS item_name FROM "test"'
|
85
|
+
|
86
|
+
@d.select('"name"'.lit).sql.should == \
|
87
|
+
'SELECT "name" FROM "test"'
|
88
|
+
|
89
|
+
@d.select('max(test."name") AS "max_name"'.lit).sql.should == \
|
90
|
+
'SELECT max(test."name") AS "max_name" FROM "test"'
|
91
|
+
|
92
|
+
@d.select(:test.sql_function(:abc, 'hello')).sql.should == \
|
93
|
+
"SELECT test(\"abc\", 'hello') FROM \"test\""
|
94
|
+
|
95
|
+
@d.select(:test.sql_function(:abc__def, 'hello')).sql.should == \
|
96
|
+
"SELECT test(\"abc\".\"def\", 'hello') FROM \"test\""
|
97
|
+
|
98
|
+
@d.select(:test.sql_function(:abc__def, 'hello').as(:x2)).sql.should == \
|
99
|
+
"SELECT test(\"abc\".\"def\", 'hello') AS \"x2\" FROM \"test\""
|
100
|
+
|
101
|
+
@d.insert_sql(:value => 333).should =~ \
|
102
|
+
/\AINSERT INTO "test" \("value"\) VALUES \(333\)( RETURNING NULL)?\z/
|
103
|
+
|
104
|
+
@d.insert_sql(:x => :y).should =~ \
|
105
|
+
/\AINSERT INTO "test" \("x"\) VALUES \("y"\)( RETURNING NULL)?\z/
|
106
|
+
|
107
|
+
@d.disable_insert_returning.insert_sql(:value => 333).should =~ \
|
108
|
+
/\AINSERT INTO "test" \("value"\) VALUES \(333\)\z/
|
109
|
+
end
|
110
|
+
|
111
|
+
specify "should quote fields correctly when reversing the order if quoting identifiers" do
|
112
|
+
@d.quote_identifiers = true
|
113
|
+
@d.reverse_order(:name).sql.should == \
|
114
|
+
'SELECT * FROM "test" ORDER BY "name" DESC'
|
115
|
+
|
116
|
+
@d.reverse_order(:name.desc).sql.should == \
|
117
|
+
'SELECT * FROM "test" ORDER BY "name" ASC'
|
118
|
+
|
119
|
+
@d.reverse_order(:name, :test.desc).sql.should == \
|
120
|
+
'SELECT * FROM "test" ORDER BY "name" DESC, "test" ASC'
|
121
|
+
|
122
|
+
@d.reverse_order(:name.desc, :test).sql.should == \
|
123
|
+
'SELECT * FROM "test" ORDER BY "name" ASC, "test" DESC'
|
124
|
+
end
|
125
|
+
|
126
|
+
specify "should support regexps" do
|
127
|
+
@d << {:name => 'abc', :value => 1}
|
128
|
+
@d << {:name => 'bcd', :value => 2}
|
129
|
+
@d.filter(:name => /bc/).count.should == 2
|
130
|
+
@d.filter(:name => /^bc/).count.should == 1
|
131
|
+
end
|
132
|
+
|
133
|
+
specify "#lock should lock tables and yield if a block is given" do
|
134
|
+
@d.lock('EXCLUSIVE'){@d.insert(:name=>'a')}
|
135
|
+
end
|
136
|
+
|
137
|
+
specify "#lock should lock table if inside a transaction" do
|
138
|
+
POSTGRES_DB.transaction{@d.lock('EXCLUSIVE'); @d.insert(:name=>'a')}
|
139
|
+
end
|
140
|
+
|
141
|
+
specify "#lock should return nil" do
|
142
|
+
@d.lock('EXCLUSIVE'){@d.insert(:name=>'a')}.should == nil
|
143
|
+
POSTGRES_DB.transaction{@d.lock('EXCLUSIVE').should == nil; @d.insert(:name=>'a')}
|
144
|
+
end
|
145
|
+
|
146
|
+
specify "should raise an error if attempting to update a joined dataset with a single FROM table" do
|
147
|
+
proc{POSTGRES_DB[:test].join(:test2, [:name]).update(:name=>'a')}.should raise_error(Sequel::Error, 'Need multiple FROM tables if updating/deleting a dataset with JOINs')
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
describe "Dataset#distinct" do
|
152
|
+
before do
|
153
|
+
@db = POSTGRES_DB
|
154
|
+
@db.create_table!(:a) do
|
155
|
+
Integer :a
|
156
|
+
Integer :b
|
157
|
+
end
|
158
|
+
@ds = @db[:a]
|
159
|
+
end
|
160
|
+
after do
|
161
|
+
@db.drop_table(:a)
|
162
|
+
end
|
163
|
+
|
164
|
+
it "#distinct with arguments should return results distinct on those arguments" do
|
165
|
+
@ds.insert(20, 10)
|
166
|
+
@ds.insert(30, 10)
|
167
|
+
@ds.order(:b, :a).distinct.map(:a).should == [20, 30]
|
168
|
+
@ds.order(:b, :a.desc).distinct.map(:a).should == [30, 20]
|
169
|
+
@ds.order(:b, :a).distinct(:b).map(:a).should == [20]
|
170
|
+
@ds.order(:b, :a.desc).distinct(:b).map(:a).should == [30]
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
if POSTGRES_DB.pool.respond_to?(:max_size) and POSTGRES_DB.pool.max_size > 1
|
175
|
+
describe "Dataset#for_update support" do
|
176
|
+
before do
|
177
|
+
@db = POSTGRES_DB.create_table!(:items) do
|
178
|
+
primary_key :id
|
179
|
+
Integer :number
|
180
|
+
String :name
|
181
|
+
end
|
182
|
+
@ds = POSTGRES_DB[:items]
|
183
|
+
clear_sqls
|
184
|
+
end
|
185
|
+
after do
|
186
|
+
POSTGRES_DB.drop_table(:items)
|
187
|
+
POSTGRES_DB.disconnect
|
188
|
+
end
|
189
|
+
|
190
|
+
specify "should handle FOR UPDATE" do
|
191
|
+
@ds.insert(:number=>20)
|
192
|
+
c = nil
|
193
|
+
t = nil
|
194
|
+
POSTGRES_DB.transaction do
|
195
|
+
@ds.for_update.first(:id=>1)
|
196
|
+
t = Thread.new do
|
197
|
+
POSTGRES_DB.transaction do
|
198
|
+
@ds.filter(:id=>1).update(:name=>'Jim')
|
199
|
+
c = @ds.first(:id=>1)
|
200
|
+
end
|
201
|
+
end
|
202
|
+
sleep 0.01
|
203
|
+
@ds.filter(:id=>1).update(:number=>30)
|
204
|
+
end
|
205
|
+
t.join
|
206
|
+
c.should == {:id=>1, :number=>30, :name=>'Jim'}
|
207
|
+
end
|
208
|
+
|
209
|
+
specify "should handle FOR SHARE" do
|
210
|
+
@ds.insert(:number=>20)
|
211
|
+
c = nil
|
212
|
+
t = nil
|
213
|
+
POSTGRES_DB.transaction do
|
214
|
+
@ds.for_share.first(:id=>1)
|
215
|
+
t = Thread.new do
|
216
|
+
POSTGRES_DB.transaction do
|
217
|
+
c = @ds.for_share.filter(:id=>1).first
|
218
|
+
end
|
219
|
+
end
|
220
|
+
sleep 0.1
|
221
|
+
@ds.filter(:id=>1).update(:name=>'Jim')
|
222
|
+
c.should == {:id=>1, :number=>20, :name=>nil}
|
223
|
+
end
|
224
|
+
t.join
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
context "A PostgreSQL dataset with a timestamp field" do
|
230
|
+
before do
|
231
|
+
@d = POSTGRES_DB[:test3]
|
232
|
+
@d.delete
|
233
|
+
end
|
234
|
+
|
235
|
+
cspecify "should store milliseconds in time fields", :do do
|
236
|
+
t = Time.now
|
237
|
+
@d << {:value=>1, :time=>t}
|
238
|
+
@d.literal(@d[:value =>'1'][:time]).should == @d.literal(t)
|
239
|
+
@d[:value=>'1'][:time].usec.should == t.usec
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
context "PostgreSQL's EXPLAIN and ANALYZE" do
|
244
|
+
specify "should not raise errors" do
|
245
|
+
@d = POSTGRES_DB[:test3]
|
246
|
+
proc{@d.explain}.should_not raise_error
|
247
|
+
proc{@d.analyze}.should_not raise_error
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
context "A PostgreSQL database" do
|
252
|
+
before do
|
253
|
+
@db = POSTGRES_DB
|
254
|
+
end
|
255
|
+
|
256
|
+
specify "should support column operations" do
|
257
|
+
@db.create_table!(:test2){text :name; integer :value}
|
258
|
+
@db[:test2] << {}
|
259
|
+
@db[:test2].columns.should == [:name, :value]
|
260
|
+
|
261
|
+
@db.add_column :test2, :xyz, :text, :default => '000'
|
262
|
+
@db[:test2].columns.should == [:name, :value, :xyz]
|
263
|
+
@db[:test2] << {:name => 'mmm', :value => 111}
|
264
|
+
@db[:test2].first[:xyz].should == '000'
|
265
|
+
|
266
|
+
@db[:test2].columns.should == [:name, :value, :xyz]
|
267
|
+
@db.drop_column :test2, :xyz
|
268
|
+
|
269
|
+
@db[:test2].columns.should == [:name, :value]
|
270
|
+
|
271
|
+
@db[:test2].delete
|
272
|
+
@db.add_column :test2, :xyz, :text, :default => '000'
|
273
|
+
@db[:test2] << {:name => 'mmm', :value => 111, :xyz => 'qqqq'}
|
274
|
+
|
275
|
+
@db[:test2].columns.should == [:name, :value, :xyz]
|
276
|
+
@db.rename_column :test2, :xyz, :zyx
|
277
|
+
@db[:test2].columns.should == [:name, :value, :zyx]
|
278
|
+
@db[:test2].first[:zyx].should == 'qqqq'
|
279
|
+
|
280
|
+
@db.add_column :test2, :xyz, :float
|
281
|
+
@db[:test2].delete
|
282
|
+
@db[:test2] << {:name => 'mmm', :value => 111, :xyz => 56.78}
|
283
|
+
@db.set_column_type :test2, :xyz, :integer
|
284
|
+
|
285
|
+
@db[:test2].first[:xyz].should == 57
|
286
|
+
end
|
287
|
+
|
288
|
+
specify "#locks should be a dataset returning database locks " do
|
289
|
+
@db.locks.should be_a_kind_of(Sequel::Dataset)
|
290
|
+
@db.locks.all.should be_a_kind_of(Array)
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
context "A PostgreSQL database" do
|
295
|
+
before do
|
296
|
+
@db = POSTGRES_DB
|
297
|
+
@db.drop_table(:posts) rescue nil
|
298
|
+
@db.sqls.clear
|
299
|
+
end
|
300
|
+
after do
|
301
|
+
@db.drop_table(:posts) rescue nil
|
302
|
+
end
|
303
|
+
|
304
|
+
specify "should support resetting the primary key sequence" do
|
305
|
+
@db.create_table(:posts){primary_key :a}
|
306
|
+
@db[:posts].insert(:a=>20).should == 20
|
307
|
+
@db[:posts].insert.should == 1
|
308
|
+
@db[:posts].insert.should == 2
|
309
|
+
@db[:posts].insert(:a=>10).should == 10
|
310
|
+
@db.reset_primary_key_sequence(:posts).should == 21
|
311
|
+
@db[:posts].insert.should == 21
|
312
|
+
@db[:posts].order(:a).map(:a).should == [1, 2, 10, 20, 21]
|
313
|
+
end
|
314
|
+
|
315
|
+
specify "should support specifying Integer/Bignum/Fixnum types in primary keys and have them be auto incrementing" do
|
316
|
+
@db.create_table(:posts){primary_key :a, :type=>Integer}
|
317
|
+
@db[:posts].insert.should == 1
|
318
|
+
@db[:posts].insert.should == 2
|
319
|
+
@db.create_table!(:posts){primary_key :a, :type=>Fixnum}
|
320
|
+
@db[:posts].insert.should == 1
|
321
|
+
@db[:posts].insert.should == 2
|
322
|
+
@db.create_table!(:posts){primary_key :a, :type=>Bignum}
|
323
|
+
@db[:posts].insert.should == 1
|
324
|
+
@db[:posts].insert.should == 2
|
325
|
+
end
|
326
|
+
|
327
|
+
specify "should not raise an error if attempting to resetting the primary key sequence for a table without a primary key" do
|
328
|
+
@db.create_table(:posts){Integer :a}
|
329
|
+
@db.reset_primary_key_sequence(:posts).should == nil
|
330
|
+
end
|
331
|
+
|
332
|
+
specify "should support opclass specification" do
|
333
|
+
@db.create_table(:posts){text :title; text :body; integer :user_id; index(:user_id, :opclass => :int4_ops, :type => :btree)}
|
334
|
+
@db.sqls.should == [
|
335
|
+
"CREATE TABLE posts (title text, body text, user_id integer)",
|
336
|
+
"CREATE INDEX posts_user_id_index ON posts USING btree (user_id int4_ops)"
|
337
|
+
]
|
338
|
+
end
|
339
|
+
|
340
|
+
specify "should support fulltext indexes and searching" do
|
341
|
+
@db.create_table(:posts){text :title; text :body; full_text_index [:title, :body]; full_text_index :title, :language => 'french'}
|
342
|
+
@db.sqls.should == [
|
343
|
+
"CREATE TABLE posts (title text, body text)",
|
344
|
+
"CREATE INDEX posts_title_body_index ON posts USING gin (to_tsvector('simple', (COALESCE(title, '') || ' ' || COALESCE(body, ''))))",
|
345
|
+
"CREATE INDEX posts_title_index ON posts USING gin (to_tsvector('french', (COALESCE(title, ''))))"
|
346
|
+
]
|
347
|
+
|
348
|
+
@db[:posts].insert(:title=>'ruby rails', :body=>'yowsa')
|
349
|
+
@db[:posts].insert(:title=>'sequel', :body=>'ruby')
|
350
|
+
@db[:posts].insert(:title=>'ruby scooby', :body=>'x')
|
351
|
+
@db.sqls.clear
|
352
|
+
|
353
|
+
@db[:posts].full_text_search(:title, 'rails').all.should == [{:title=>'ruby rails', :body=>'yowsa'}]
|
354
|
+
@db[:posts].full_text_search([:title, :body], ['yowsa', 'rails']).all.should == [:title=>'ruby rails', :body=>'yowsa']
|
355
|
+
@db[:posts].full_text_search(:title, 'scooby', :language => 'french').all.should == [{:title=>'ruby scooby', :body=>'x'}]
|
356
|
+
@db.sqls.should == [
|
357
|
+
"SELECT * FROM posts WHERE (to_tsvector('simple', (COALESCE(title, ''))) @@ to_tsquery('simple', 'rails'))",
|
358
|
+
"SELECT * FROM posts WHERE (to_tsvector('simple', (COALESCE(title, '') || ' ' || COALESCE(body, ''))) @@ to_tsquery('simple', 'yowsa | rails'))",
|
359
|
+
"SELECT * FROM posts WHERE (to_tsvector('french', (COALESCE(title, ''))) @@ to_tsquery('french', 'scooby'))"]
|
360
|
+
end
|
361
|
+
|
362
|
+
specify "should support spatial indexes" do
|
363
|
+
@db.create_table(:posts){box :geom; spatial_index [:geom]}
|
364
|
+
@db.sqls.should == [
|
365
|
+
"CREATE TABLE posts (geom box)",
|
366
|
+
"CREATE INDEX posts_geom_index ON posts USING gist (geom)"
|
367
|
+
]
|
368
|
+
end
|
369
|
+
|
370
|
+
specify "should support indexes with index type" do
|
371
|
+
@db.create_table(:posts){varchar :title, :size => 5; index :title, :type => 'hash'}
|
372
|
+
@db.sqls.should == [
|
373
|
+
"CREATE TABLE posts (title varchar(5))",
|
374
|
+
"CREATE INDEX posts_title_index ON posts USING hash (title)"
|
375
|
+
]
|
376
|
+
end
|
377
|
+
|
378
|
+
specify "should support unique indexes with index type" do
|
379
|
+
@db.create_table(:posts){varchar :title, :size => 5; index :title, :type => 'btree', :unique => true}
|
380
|
+
@db.sqls.should == [
|
381
|
+
"CREATE TABLE posts (title varchar(5))",
|
382
|
+
"CREATE UNIQUE INDEX posts_title_index ON posts USING btree (title)"
|
383
|
+
]
|
384
|
+
end
|
385
|
+
|
386
|
+
specify "should support partial indexes" do
|
387
|
+
@db.create_table(:posts){varchar :title, :size => 5; index :title, :where => {:title => '5'}}
|
388
|
+
@db.sqls.should == [
|
389
|
+
"CREATE TABLE posts (title varchar(5))",
|
390
|
+
"CREATE INDEX posts_title_index ON posts (title) WHERE (title = '5')"
|
391
|
+
]
|
392
|
+
end
|
393
|
+
|
394
|
+
specify "should support identifiers for table names in indicies" do
|
395
|
+
@db.create_table(Sequel::SQL::Identifier.new(:posts)){varchar :title, :size => 5; index :title, :where => {:title => '5'}}
|
396
|
+
@db.sqls.should == [
|
397
|
+
"CREATE TABLE posts (title varchar(5))",
|
398
|
+
"CREATE INDEX posts_title_index ON posts (title) WHERE (title = '5')"
|
399
|
+
]
|
400
|
+
end
|
401
|
+
|
402
|
+
specify "should support renaming tables" do
|
403
|
+
@db.create_table!(:posts1){primary_key :a}
|
404
|
+
@db.rename_table(:posts1, :posts)
|
405
|
+
end
|
406
|
+
end
|
407
|
+
|
408
|
+
context "Postgres::Dataset#import" do
|
409
|
+
before do
|
410
|
+
@db = POSTGRES_DB
|
411
|
+
@db.create_table!(:test){Integer :x; Integer :y}
|
412
|
+
@db.sqls.clear
|
413
|
+
@ds = @db[:test]
|
414
|
+
end
|
415
|
+
after do
|
416
|
+
@db.drop_table(:test) rescue nil
|
417
|
+
end
|
418
|
+
|
419
|
+
specify "#import should return separate insert statements if server_version < 80200" do
|
420
|
+
@ds.meta_def(:server_version){80199}
|
421
|
+
|
422
|
+
@ds.import([:x, :y], [[1, 2], [3, 4]])
|
423
|
+
|
424
|
+
@db.sqls.should == [
|
425
|
+
'BEGIN',
|
426
|
+
'INSERT INTO test (x, y) VALUES (1, 2)',
|
427
|
+
'INSERT INTO test (x, y) VALUES (3, 4)',
|
428
|
+
'COMMIT'
|
429
|
+
]
|
430
|
+
@ds.all.should == [{:x=>1, :y=>2}, {:x=>3, :y=>4}]
|
431
|
+
end
|
432
|
+
|
433
|
+
specify "#import should a single insert statement if server_version >= 80200" do
|
434
|
+
@ds.meta_def(:server_version){80200}
|
435
|
+
|
436
|
+
@ds.import([:x, :y], [[1, 2], [3, 4]])
|
437
|
+
|
438
|
+
@db.sqls.should == [
|
439
|
+
'BEGIN',
|
440
|
+
'INSERT INTO test (x, y) VALUES (1, 2), (3, 4)',
|
441
|
+
'COMMIT'
|
442
|
+
]
|
443
|
+
@ds.all.should == [{:x=>1, :y=>2}, {:x=>3, :y=>4}]
|
444
|
+
end
|
445
|
+
end
|
446
|
+
|
447
|
+
context "Postgres::Dataset#insert" do
|
448
|
+
before do
|
449
|
+
@db = POSTGRES_DB
|
450
|
+
@db.create_table!(:test5){primary_key :xid; Integer :value}
|
451
|
+
@db.sqls.clear
|
452
|
+
@ds = @db[:test5]
|
453
|
+
end
|
454
|
+
after do
|
455
|
+
@db.drop_table(:test5) rescue nil
|
456
|
+
end
|
457
|
+
|
458
|
+
specify "should work with static SQL" do
|
459
|
+
@ds.with_sql('INSERT INTO test5 (value) VALUES (10)').insert.should == nil
|
460
|
+
@db['INSERT INTO test5 (value) VALUES (20)'].insert.should == nil
|
461
|
+
@ds.all.should == [{:xid=>1, :value=>10}, {:xid=>2, :value=>20}]
|
462
|
+
end
|
463
|
+
|
464
|
+
specify "should work regardless of how it is used" do
|
465
|
+
@ds.insert(:value=>10).should == 1
|
466
|
+
@ds.disable_insert_returning.insert(:value=>20).should == 2
|
467
|
+
@ds.meta_def(:server_version){80100}
|
468
|
+
@ds.insert(:value=>13).should == 3
|
469
|
+
|
470
|
+
@db.sqls.reject{|x| x =~ /pg_class/}.should == [
|
471
|
+
'INSERT INTO test5 (value) VALUES (10) RETURNING xid',
|
472
|
+
'INSERT INTO test5 (value) VALUES (20)',
|
473
|
+
"SELECT currval('\"public\".test5_xid_seq')",
|
474
|
+
'INSERT INTO test5 (value) VALUES (13)',
|
475
|
+
"SELECT currval('\"public\".test5_xid_seq')"
|
476
|
+
]
|
477
|
+
@ds.all.should == [{:xid=>1, :value=>10}, {:xid=>2, :value=>20}, {:xid=>3, :value=>13}]
|
478
|
+
end
|
479
|
+
|
480
|
+
specify "should call execute_insert if server_version < 80200" do
|
481
|
+
@ds.meta_def(:server_version){80100}
|
482
|
+
@ds.should_receive(:execute_insert).once.with('INSERT INTO test5 (value) VALUES (10)', :table=>:test5, :values=>{:value=>10})
|
483
|
+
@ds.insert(:value=>10)
|
484
|
+
end
|
485
|
+
|
486
|
+
specify "should call execute_insert if disabling insert returning" do
|
487
|
+
@ds.disable_insert_returning!
|
488
|
+
@ds.should_receive(:execute_insert).once.with('INSERT INTO test5 (value) VALUES (10)', :table=>:test5, :values=>{:value=>10})
|
489
|
+
@ds.insert(:value=>10)
|
490
|
+
end
|
491
|
+
|
492
|
+
specify "should use INSERT RETURNING if server_version >= 80200" do
|
493
|
+
@ds.meta_def(:server_version){80201}
|
494
|
+
@ds.insert(:value=>10)
|
495
|
+
@db.sqls.last.should == 'INSERT INTO test5 (value) VALUES (10) RETURNING xid'
|
496
|
+
end
|
497
|
+
|
498
|
+
specify "should have insert_returning_sql use the RETURNING keyword" do
|
499
|
+
@ds.insert_returning_sql(:xid, :value=>10).should == "INSERT INTO test5 (value) VALUES (10) RETURNING xid"
|
500
|
+
@ds.insert_returning_sql('*'.lit, :value=>10).should == "INSERT INTO test5 (value) VALUES (10) RETURNING *"
|
501
|
+
end
|
502
|
+
|
503
|
+
specify "should have insert_select return nil if server_version < 80200" do
|
504
|
+
@ds.meta_def(:server_version){80100}
|
505
|
+
@ds.insert_select(:value=>10).should == nil
|
506
|
+
end
|
507
|
+
|
508
|
+
specify "should have insert_select return nil if disable_insert_returning is used" do
|
509
|
+
@ds.disable_insert_returning.insert_select(:value=>10).should == nil
|
510
|
+
end
|
511
|
+
|
512
|
+
specify "should have insert_select insert the record and return the inserted record if server_version >= 80200" do
|
513
|
+
@ds.meta_def(:server_version){80201}
|
514
|
+
h = @ds.insert_select(:value=>10)
|
515
|
+
h[:value].should == 10
|
516
|
+
@ds.first(:xid=>h[:xid])[:value].should == 10
|
517
|
+
end
|
518
|
+
|
519
|
+
specify "should correctly return the inserted record's primary key value" do
|
520
|
+
value1 = 10
|
521
|
+
id1 = @ds.insert(:value=>value1)
|
522
|
+
@ds.first(:xid=>id1)[:value].should == value1
|
523
|
+
value2 = 20
|
524
|
+
id2 = @ds.insert(:value=>value2)
|
525
|
+
@ds.first(:xid=>id2)[:value].should == value2
|
526
|
+
end
|
527
|
+
|
528
|
+
specify "should return nil if the table has no primary key" do
|
529
|
+
ds = POSTGRES_DB[:test4]
|
530
|
+
ds.delete
|
531
|
+
ds.insert(:name=>'a').should == nil
|
532
|
+
end
|
533
|
+
end
|
534
|
+
|
535
|
+
context "Postgres::Database schema qualified tables" do
|
536
|
+
before do
|
537
|
+
POSTGRES_DB << "CREATE SCHEMA schema_test"
|
538
|
+
POSTGRES_DB.instance_variable_set(:@primary_keys, {})
|
539
|
+
POSTGRES_DB.instance_variable_set(:@primary_key_sequences, {})
|
540
|
+
end
|
541
|
+
after do
|
542
|
+
POSTGRES_DB.quote_identifiers = false
|
543
|
+
POSTGRES_DB << "DROP SCHEMA schema_test CASCADE"
|
544
|
+
POSTGRES_DB.default_schema = :public
|
545
|
+
end
|
546
|
+
|
547
|
+
specify "should be able to create, drop, select and insert into tables in a given schema" do
|
548
|
+
POSTGRES_DB.create_table(:schema_test__schema_test){primary_key :i}
|
549
|
+
POSTGRES_DB[:schema_test__schema_test].first.should == nil
|
550
|
+
POSTGRES_DB[:schema_test__schema_test].insert(:i=>1).should == 1
|
551
|
+
POSTGRES_DB[:schema_test__schema_test].first.should == {:i=>1}
|
552
|
+
POSTGRES_DB.from('schema_test.schema_test'.lit).first.should == {:i=>1}
|
553
|
+
POSTGRES_DB.drop_table(:schema_test__schema_test)
|
554
|
+
POSTGRES_DB.create_table(:schema_test.qualify(:schema_test)){integer :i}
|
555
|
+
POSTGRES_DB[:schema_test__schema_test].first.should == nil
|
556
|
+
POSTGRES_DB.from('schema_test.schema_test'.lit).first.should == nil
|
557
|
+
POSTGRES_DB.drop_table(:schema_test.qualify(:schema_test))
|
558
|
+
end
|
559
|
+
|
560
|
+
specify "#tables should include only tables in the public schema if no schema is given" do
|
561
|
+
POSTGRES_DB.create_table(:schema_test__schema_test){integer :i}
|
562
|
+
POSTGRES_DB.tables.should_not include(:schema_test)
|
563
|
+
end
|
564
|
+
|
565
|
+
specify "#tables should return tables in the schema provided by the :schema argument" do
|
566
|
+
POSTGRES_DB.create_table(:schema_test__schema_test){integer :i}
|
567
|
+
POSTGRES_DB.tables(:schema=>:schema_test).should == [:schema_test]
|
568
|
+
end
|
569
|
+
|
570
|
+
specify "#table_exists? should assume the public schema if no schema is provided" do
|
571
|
+
POSTGRES_DB.create_table(:schema_test__schema_test){integer :i}
|
572
|
+
POSTGRES_DB.table_exists?(:schema_test).should == false
|
573
|
+
end
|
574
|
+
|
575
|
+
specify "#table_exists? should see if the table is in a given schema" do
|
576
|
+
POSTGRES_DB.create_table(:schema_test__schema_test){integer :i}
|
577
|
+
POSTGRES_DB.table_exists?(:schema_test__schema_test).should == true
|
578
|
+
end
|
579
|
+
|
580
|
+
specify "should be able to get primary keys for tables in a given schema" do
|
581
|
+
POSTGRES_DB.create_table(:schema_test__schema_test){primary_key :i}
|
582
|
+
POSTGRES_DB.primary_key(:schema_test__schema_test).should == 'i'
|
583
|
+
end
|
584
|
+
|
585
|
+
specify "should be able to get serial sequences for tables in a given schema" do
|
586
|
+
POSTGRES_DB.create_table(:schema_test__schema_test){primary_key :i}
|
587
|
+
POSTGRES_DB.primary_key_sequence(:schema_test__schema_test).should == '"schema_test".schema_test_i_seq'
|
588
|
+
end
|
589
|
+
|
590
|
+
specify "should be able to get serial sequences for tables that have spaces in the name in a given schema" do
|
591
|
+
POSTGRES_DB.quote_identifiers = true
|
592
|
+
POSTGRES_DB.create_table(:"schema_test__schema test"){primary_key :i}
|
593
|
+
POSTGRES_DB.primary_key_sequence(:"schema_test__schema test").should == '"schema_test"."schema test_i_seq"'
|
594
|
+
end
|
595
|
+
|
596
|
+
specify "should be able to get custom sequences for tables in a given schema" do
|
597
|
+
POSTGRES_DB << "CREATE SEQUENCE schema_test.kseq"
|
598
|
+
POSTGRES_DB.create_table(:schema_test__schema_test){integer :j; primary_key :k, :type=>:integer, :default=>"nextval('schema_test.kseq'::regclass)".lit}
|
599
|
+
POSTGRES_DB.primary_key_sequence(:schema_test__schema_test).should == '"schema_test".kseq'
|
600
|
+
end
|
601
|
+
|
602
|
+
specify "should be able to get custom sequences for tables that have spaces in the name in a given schema" do
|
603
|
+
POSTGRES_DB.quote_identifiers = true
|
604
|
+
POSTGRES_DB << "CREATE SEQUENCE schema_test.\"ks eq\""
|
605
|
+
POSTGRES_DB.create_table(:"schema_test__schema test"){integer :j; primary_key :k, :type=>:integer, :default=>"nextval('schema_test.\"ks eq\"'::regclass)".lit}
|
606
|
+
POSTGRES_DB.primary_key_sequence(:"schema_test__schema test").should == '"schema_test"."ks eq"'
|
607
|
+
end
|
608
|
+
|
609
|
+
specify "#default_schema= should change the default schema used from public" do
|
610
|
+
POSTGRES_DB.create_table(:schema_test__schema_test){primary_key :i}
|
611
|
+
POSTGRES_DB.default_schema = :schema_test
|
612
|
+
POSTGRES_DB.table_exists?(:schema_test).should == true
|
613
|
+
POSTGRES_DB.tables.should == [:schema_test]
|
614
|
+
POSTGRES_DB.primary_key(:schema_test__schema_test).should == 'i'
|
615
|
+
POSTGRES_DB.primary_key_sequence(:schema_test__schema_test).should == '"schema_test".schema_test_i_seq'
|
616
|
+
end
|
617
|
+
end
|
618
|
+
|
619
|
+
context "Postgres::Database schema qualified tables and eager graphing" do
|
620
|
+
before(:all) do
|
621
|
+
@db = POSTGRES_DB
|
622
|
+
@db.run "DROP SCHEMA s CASCADE" rescue nil
|
623
|
+
@db.run "CREATE SCHEMA s"
|
624
|
+
@db.quote_identifiers = true
|
625
|
+
|
626
|
+
@db.create_table(:s__bands){primary_key :id; String :name}
|
627
|
+
@db.create_table(:s__albums){primary_key :id; String :name; foreign_key :band_id, :s__bands}
|
628
|
+
@db.create_table(:s__tracks){primary_key :id; String :name; foreign_key :album_id, :s__albums}
|
629
|
+
@db.create_table(:s__members){primary_key :id; String :name; foreign_key :band_id, :s__bands}
|
630
|
+
|
631
|
+
@Band = Class.new(Sequel::Model(:s__bands))
|
632
|
+
@Album = Class.new(Sequel::Model(:s__albums))
|
633
|
+
@Track = Class.new(Sequel::Model(:s__tracks))
|
634
|
+
@Member = Class.new(Sequel::Model(:s__members))
|
635
|
+
def @Band.name; :Band; end
|
636
|
+
def @Album.name; :Album; end
|
637
|
+
def @Track.name; :Track; end
|
638
|
+
def @Member.name; :Member; end
|
639
|
+
|
640
|
+
@Band.one_to_many :albums, :class=>@Album, :order=>:name
|
641
|
+
@Band.one_to_many :members, :class=>@Member, :order=>:name
|
642
|
+
@Album.many_to_one :band, :class=>@Band, :order=>:name
|
643
|
+
@Album.one_to_many :tracks, :class=>@Track, :order=>:name
|
644
|
+
@Track.many_to_one :album, :class=>@Album, :order=>:name
|
645
|
+
@Member.many_to_one :band, :class=>@Band, :order=>:name
|
646
|
+
|
647
|
+
@Member.many_to_many :members, :class=>@Member, :join_table=>:s__bands, :right_key=>:id, :left_key=>:id, :left_primary_key=>:band_id, :right_primary_key=>:band_id, :order=>:name
|
648
|
+
@Band.many_to_many :tracks, :class=>@Track, :join_table=>:s__albums, :right_key=>:id, :right_primary_key=>:album_id, :order=>:name
|
649
|
+
|
650
|
+
@b1 = @Band.create(:name=>"BM")
|
651
|
+
@b2 = @Band.create(:name=>"J")
|
652
|
+
@a1 = @Album.create(:name=>"BM1", :band=>@b1)
|
653
|
+
@a2 = @Album.create(:name=>"BM2", :band=>@b1)
|
654
|
+
@a3 = @Album.create(:name=>"GH", :band=>@b2)
|
655
|
+
@a4 = @Album.create(:name=>"GHL", :band=>@b2)
|
656
|
+
@t1 = @Track.create(:name=>"BM1-1", :album=>@a1)
|
657
|
+
@t2 = @Track.create(:name=>"BM1-2", :album=>@a1)
|
658
|
+
@t3 = @Track.create(:name=>"BM2-1", :album=>@a2)
|
659
|
+
@t4 = @Track.create(:name=>"BM2-2", :album=>@a2)
|
660
|
+
@m1 = @Member.create(:name=>"NU", :band=>@b1)
|
661
|
+
@m2 = @Member.create(:name=>"TS", :band=>@b1)
|
662
|
+
@m3 = @Member.create(:name=>"NS", :band=>@b2)
|
663
|
+
@m4 = @Member.create(:name=>"JC", :band=>@b2)
|
664
|
+
end
|
665
|
+
after(:all) do
|
666
|
+
@db.quote_identifiers = false
|
667
|
+
@db.run "DROP SCHEMA s CASCADE"
|
668
|
+
end
|
669
|
+
|
670
|
+
specify "should return all eager graphs correctly" do
|
671
|
+
bands = @Band.order(:bands__name).eager_graph(:albums).all
|
672
|
+
bands.should == [@b1, @b2]
|
673
|
+
bands.map{|x| x.albums}.should == [[@a1, @a2], [@a3, @a4]]
|
674
|
+
|
675
|
+
bands = @Band.order(:bands__name).eager_graph(:albums=>:tracks).all
|
676
|
+
bands.should == [@b1, @b2]
|
677
|
+
bands.map{|x| x.albums}.should == [[@a1, @a2], [@a3, @a4]]
|
678
|
+
bands.map{|x| x.albums.map{|y| y.tracks}}.should == [[[@t1, @t2], [@t3, @t4]], [[], []]]
|
679
|
+
|
680
|
+
bands = @Band.order(:bands__name).eager_graph({:albums=>:tracks}, :members).all
|
681
|
+
bands.should == [@b1, @b2]
|
682
|
+
bands.map{|x| x.albums}.should == [[@a1, @a2], [@a3, @a4]]
|
683
|
+
bands.map{|x| x.albums.map{|y| y.tracks}}.should == [[[@t1, @t2], [@t3, @t4]], [[], []]]
|
684
|
+
bands.map{|x| x.members}.should == [[@m1, @m2], [@m4, @m3]]
|
685
|
+
end
|
686
|
+
|
687
|
+
specify "should have eager graphs work with previous joins" do
|
688
|
+
bands = @Band.order(:bands__name).select(:s__bands.*).join(:s__members, :band_id=>:id).from_self(:alias=>:bands0).eager_graph(:albums=>:tracks).all
|
689
|
+
bands.should == [@b1, @b2]
|
690
|
+
bands.map{|x| x.albums}.should == [[@a1, @a2], [@a3, @a4]]
|
691
|
+
bands.map{|x| x.albums.map{|y| y.tracks}}.should == [[[@t1, @t2], [@t3, @t4]], [[], []]]
|
692
|
+
end
|
693
|
+
|
694
|
+
specify "should have eager graphs work with joins with the same tables" do
|
695
|
+
bands = @Band.order(:bands__name).select(:s__bands.*).join(:s__members, :band_id=>:id).eager_graph({:albums=>:tracks}, :members).all
|
696
|
+
bands.should == [@b1, @b2]
|
697
|
+
bands.map{|x| x.albums}.should == [[@a1, @a2], [@a3, @a4]]
|
698
|
+
bands.map{|x| x.albums.map{|y| y.tracks}}.should == [[[@t1, @t2], [@t3, @t4]], [[], []]]
|
699
|
+
bands.map{|x| x.members}.should == [[@m1, @m2], [@m4, @m3]]
|
700
|
+
end
|
701
|
+
|
702
|
+
specify "should have eager graphs work with self referential associations" do
|
703
|
+
bands = @Band.order(:bands__name).eager_graph(:tracks=>{:album=>:band}).all
|
704
|
+
bands.should == [@b1, @b2]
|
705
|
+
bands.map{|x| x.tracks}.should == [[@t1, @t2, @t3, @t4], []]
|
706
|
+
bands.map{|x| x.tracks.map{|y| y.album}}.should == [[@a1, @a1, @a2, @a2], []]
|
707
|
+
bands.map{|x| x.tracks.map{|y| y.album.band}}.should == [[@b1, @b1, @b1, @b1], []]
|
708
|
+
|
709
|
+
members = @Member.order(:members__name).eager_graph(:members).all
|
710
|
+
members.should == [@m4, @m3, @m1, @m2]
|
711
|
+
members.map{|x| x.members}.should == [[@m4, @m3], [@m4, @m3], [@m1, @m2], [@m1, @m2]]
|
712
|
+
|
713
|
+
members = @Member.order(:members__name).eager_graph(:band, :members=>:band).all
|
714
|
+
members.should == [@m4, @m3, @m1, @m2]
|
715
|
+
members.map{|x| x.band}.should == [@b2, @b2, @b1, @b1]
|
716
|
+
members.map{|x| x.members}.should == [[@m4, @m3], [@m4, @m3], [@m1, @m2], [@m1, @m2]]
|
717
|
+
members.map{|x| x.members.map{|y| y.band}}.should == [[@b2, @b2], [@b2, @b2], [@b1, @b1], [@b1, @b1]]
|
718
|
+
end
|
719
|
+
|
720
|
+
specify "should have eager graphs work with a from_self dataset" do
|
721
|
+
bands = @Band.order(:bands__name).from_self.eager_graph(:tracks=>{:album=>:band}).all
|
722
|
+
bands.should == [@b1, @b2]
|
723
|
+
bands.map{|x| x.tracks}.should == [[@t1, @t2, @t3, @t4], []]
|
724
|
+
bands.map{|x| x.tracks.map{|y| y.album}}.should == [[@a1, @a1, @a2, @a2], []]
|
725
|
+
bands.map{|x| x.tracks.map{|y| y.album.band}}.should == [[@b1, @b1, @b1, @b1], []]
|
726
|
+
end
|
727
|
+
|
728
|
+
specify "should have eager graphs work with different types of aliased from tables" do
|
729
|
+
bands = @Band.order(:tracks__name).from(:s__bands___tracks).eager_graph(:tracks).all
|
730
|
+
bands.should == [@b1, @b2]
|
731
|
+
bands.map{|x| x.tracks}.should == [[@t1, @t2, @t3, @t4], []]
|
732
|
+
|
733
|
+
bands = @Band.order(:tracks__name).from(:s__bands.as(:tracks)).eager_graph(:tracks).all
|
734
|
+
bands.should == [@b1, @b2]
|
735
|
+
bands.map{|x| x.tracks}.should == [[@t1, @t2, @t3, @t4], []]
|
736
|
+
|
737
|
+
bands = @Band.order(:tracks__name).from(:s__bands.as(:tracks.identifier)).eager_graph(:tracks).all
|
738
|
+
bands.should == [@b1, @b2]
|
739
|
+
bands.map{|x| x.tracks}.should == [[@t1, @t2, @t3, @t4], []]
|
740
|
+
|
741
|
+
bands = @Band.order(:tracks__name).from(:s__bands.as('tracks')).eager_graph(:tracks).all
|
742
|
+
bands.should == [@b1, @b2]
|
743
|
+
bands.map{|x| x.tracks}.should == [[@t1, @t2, @t3, @t4], []]
|
744
|
+
end
|
745
|
+
|
746
|
+
specify "should have eager graphs work with join tables with aliases" do
|
747
|
+
bands = @Band.order(:bands__name).eager_graph(:members).join(:s__albums___tracks, :band_id=>:id.qualify(:s__bands)).eager_graph(:albums=>:tracks).all
|
748
|
+
bands.should == [@b1, @b2]
|
749
|
+
bands.map{|x| x.albums}.should == [[@a1, @a2], [@a3, @a4]]
|
750
|
+
bands.map{|x| x.members}.should == [[@m1, @m2], [@m4, @m3]]
|
751
|
+
|
752
|
+
bands = @Band.order(:bands__name).eager_graph(:members).join(:s__albums.as(:tracks), :band_id=>:id.qualify(:s__bands)).eager_graph(:albums=>:tracks).all
|
753
|
+
bands.should == [@b1, @b2]
|
754
|
+
bands.map{|x| x.albums}.should == [[@a1, @a2], [@a3, @a4]]
|
755
|
+
bands.map{|x| x.members}.should == [[@m1, @m2], [@m4, @m3]]
|
756
|
+
|
757
|
+
bands = @Band.order(:bands__name).eager_graph(:members).join(:s__albums.as('tracks'), :band_id=>:id.qualify(:s__bands)).eager_graph(:albums=>:tracks).all
|
758
|
+
bands.should == [@b1, @b2]
|
759
|
+
bands.map{|x| x.albums}.should == [[@a1, @a2], [@a3, @a4]]
|
760
|
+
bands.map{|x| x.members}.should == [[@m1, @m2], [@m4, @m3]]
|
761
|
+
|
762
|
+
bands = @Band.order(:bands__name).eager_graph(:members).join(:s__albums.as(:tracks.identifier), :band_id=>:id.qualify(:s__bands)).eager_graph(:albums=>:tracks).all
|
763
|
+
bands.should == [@b1, @b2]
|
764
|
+
bands.map{|x| x.albums}.should == [[@a1, @a2], [@a3, @a4]]
|
765
|
+
bands.map{|x| x.members}.should == [[@m1, @m2], [@m4, @m3]]
|
766
|
+
|
767
|
+
bands = @Band.order(:bands__name).eager_graph(:members).join(:s__albums, {:band_id=>:id.qualify(:s__bands)}, :table_alias=>:tracks).eager_graph(:albums=>:tracks).all
|
768
|
+
bands.should == [@b1, @b2]
|
769
|
+
bands.map{|x| x.albums}.should == [[@a1, @a2], [@a3, @a4]]
|
770
|
+
bands.map{|x| x.members}.should == [[@m1, @m2], [@m4, @m3]]
|
771
|
+
|
772
|
+
bands = @Band.order(:bands__name).eager_graph(:members).join(:s__albums, {:band_id=>:id.qualify(:s__bands)}, :table_alias=>'tracks').eager_graph(:albums=>:tracks).all
|
773
|
+
bands.should == [@b1, @b2]
|
774
|
+
bands.map{|x| x.albums}.should == [[@a1, @a2], [@a3, @a4]]
|
775
|
+
bands.map{|x| x.members}.should == [[@m1, @m2], [@m4, @m3]]
|
776
|
+
|
777
|
+
bands = @Band.order(:bands__name).eager_graph(:members).join(:s__albums, {:band_id=>:id.qualify(:s__bands)}, :table_alias=>:tracks.identifier).eager_graph(:albums=>:tracks).all
|
778
|
+
bands.should == [@b1, @b2]
|
779
|
+
bands.map{|x| x.albums}.should == [[@a1, @a2], [@a3, @a4]]
|
780
|
+
bands.map{|x| x.members}.should == [[@m1, @m2], [@m4, @m3]]
|
781
|
+
end
|
782
|
+
|
783
|
+
specify "should have eager graphs work with different types of qualified from tables" do
|
784
|
+
bands = @Band.order(:bands__name).from(:bands.qualify(:s)).eager_graph(:tracks).all
|
785
|
+
bands.should == [@b1, @b2]
|
786
|
+
bands.map{|x| x.tracks}.should == [[@t1, @t2, @t3, @t4], []]
|
787
|
+
|
788
|
+
bands = @Band.order(:bands__name).from(:bands.identifier.qualify(:s)).eager_graph(:tracks).all
|
789
|
+
bands.should == [@b1, @b2]
|
790
|
+
bands.map{|x| x.tracks}.should == [[@t1, @t2, @t3, @t4], []]
|
791
|
+
|
792
|
+
bands = @Band.order(:bands__name).from(Sequel::SQL::QualifiedIdentifier.new(:s, 'bands')).eager_graph(:tracks).all
|
793
|
+
bands.should == [@b1, @b2]
|
794
|
+
bands.map{|x| x.tracks}.should == [[@t1, @t2, @t3, @t4], []]
|
795
|
+
end
|
796
|
+
|
797
|
+
end
|
798
|
+
|
799
|
+
if POSTGRES_DB.server_version >= 80300
|
800
|
+
|
801
|
+
POSTGRES_DB.create_table! :test6 do
|
802
|
+
text :title
|
803
|
+
text :body
|
804
|
+
full_text_index [:title, :body]
|
805
|
+
end
|
806
|
+
|
807
|
+
context "PostgreSQL tsearch2" do
|
808
|
+
before do
|
809
|
+
@ds = POSTGRES_DB[:test6]
|
810
|
+
end
|
811
|
+
after do
|
812
|
+
POSTGRES_DB[:test6].delete
|
813
|
+
end
|
814
|
+
|
815
|
+
specify "should search by indexed column" do
|
816
|
+
record = {:title => "oopsla conference", :body => "test"}
|
817
|
+
@ds << record
|
818
|
+
@ds.full_text_search(:title, "oopsla").all.should include(record)
|
819
|
+
end
|
820
|
+
|
821
|
+
specify "should join multiple coumns with spaces to search by last words in row" do
|
822
|
+
record = {:title => "multiple words", :body => "are easy to search"}
|
823
|
+
@ds << record
|
824
|
+
@ds.full_text_search([:title, :body], "words").all.should include(record)
|
825
|
+
end
|
826
|
+
|
827
|
+
specify "should return rows with a NULL in one column if a match in another column" do
|
828
|
+
record = {:title => "multiple words", :body =>nil}
|
829
|
+
@ds << record
|
830
|
+
@ds.full_text_search([:title, :body], "words").all.should include(record)
|
831
|
+
end
|
832
|
+
end
|
833
|
+
end
|
834
|
+
|
835
|
+
if POSTGRES_DB.dataset.supports_window_functions?
|
836
|
+
context "Postgres::Dataset named windows" do
|
837
|
+
before do
|
838
|
+
@db = POSTGRES_DB
|
839
|
+
@db.create_table!(:i1){Integer :id; Integer :group_id; Integer :amount}
|
840
|
+
@ds = @db[:i1].order(:id)
|
841
|
+
@ds.insert(:id=>1, :group_id=>1, :amount=>1)
|
842
|
+
@ds.insert(:id=>2, :group_id=>1, :amount=>10)
|
843
|
+
@ds.insert(:id=>3, :group_id=>1, :amount=>100)
|
844
|
+
@ds.insert(:id=>4, :group_id=>2, :amount=>1000)
|
845
|
+
@ds.insert(:id=>5, :group_id=>2, :amount=>10000)
|
846
|
+
@ds.insert(:id=>6, :group_id=>2, :amount=>100000)
|
847
|
+
end
|
848
|
+
after do
|
849
|
+
@db.drop_table(:i1)
|
850
|
+
end
|
851
|
+
|
852
|
+
specify "should give correct results for window functions" do
|
853
|
+
@ds.window(:win, :partition=>:group_id, :order=>:id).select(:id){sum(:over, :args=>amount, :window=>win){}}.all.should ==
|
854
|
+
[{:sum=>1, :id=>1}, {:sum=>11, :id=>2}, {:sum=>111, :id=>3}, {:sum=>1000, :id=>4}, {:sum=>11000, :id=>5}, {:sum=>111000, :id=>6}]
|
855
|
+
@ds.window(:win, :partition=>:group_id).select(:id){sum(:over, :args=>amount, :window=>win, :order=>id){}}.all.should ==
|
856
|
+
[{:sum=>1, :id=>1}, {:sum=>11, :id=>2}, {:sum=>111, :id=>3}, {:sum=>1000, :id=>4}, {:sum=>11000, :id=>5}, {:sum=>111000, :id=>6}]
|
857
|
+
@ds.window(:win, {}).select(:id){sum(:over, :args=>amount, :window=>:win, :order=>id){}}.all.should ==
|
858
|
+
[{:sum=>1, :id=>1}, {:sum=>11, :id=>2}, {:sum=>111, :id=>3}, {:sum=>1111, :id=>4}, {:sum=>11111, :id=>5}, {:sum=>111111, :id=>6}]
|
859
|
+
@ds.window(:win, :partition=>:group_id).select(:id){sum(:over, :args=>amount, :window=>:win, :order=>id, :frame=>:all){}}.all.should ==
|
860
|
+
[{:sum=>111, :id=>1}, {:sum=>111, :id=>2}, {:sum=>111, :id=>3}, {:sum=>111000, :id=>4}, {:sum=>111000, :id=>5}, {:sum=>111000, :id=>6}]
|
861
|
+
end
|
862
|
+
end
|
863
|
+
end
|
864
|
+
|
865
|
+
context "Postgres::Database functions, languages, and triggers" do
|
866
|
+
before do
|
867
|
+
@d = POSTGRES_DB
|
868
|
+
end
|
869
|
+
after do
|
870
|
+
@d.drop_function('tf', :if_exists=>true, :cascade=>true)
|
871
|
+
@d.drop_function('tf', :if_exists=>true, :cascade=>true, :args=>%w'integer integer')
|
872
|
+
@d.drop_language(:plpgsql, :if_exists=>true, :cascade=>true)
|
873
|
+
@d.drop_table(:test) rescue nil
|
874
|
+
end
|
875
|
+
|
876
|
+
specify "#create_function and #drop_function should create and drop functions" do
|
877
|
+
proc{@d['SELECT tf()'].all}.should raise_error(Sequel::DatabaseError)
|
878
|
+
args = ['tf', 'SELECT 1', {:returns=>:integer}]
|
879
|
+
@d.send(:create_function_sql, *args).should =~ /\A\s*CREATE FUNCTION tf\(\)\s+RETURNS integer\s+LANGUAGE SQL\s+AS 'SELECT 1'\s*\z/
|
880
|
+
@d.create_function(*args)
|
881
|
+
rows = @d['SELECT tf()'].all.should == [{:tf=>1}]
|
882
|
+
@d.send(:drop_function_sql, 'tf').should == 'DROP FUNCTION tf()'
|
883
|
+
@d.drop_function('tf')
|
884
|
+
proc{@d['SELECT tf()'].all}.should raise_error(Sequel::DatabaseError)
|
885
|
+
end
|
886
|
+
|
887
|
+
specify "#create_function and #drop_function should support options" do
|
888
|
+
args = ['tf', 'SELECT $1 + $2', {:args=>[[:integer, :a], :integer], :replace=>true, :returns=>:integer, :language=>'SQL', :behavior=>:immutable, :strict=>true, :security_definer=>true, :cost=>2, :set=>{:search_path => 'public'}}]
|
889
|
+
@d.send(:create_function_sql,*args).should =~ /\A\s*CREATE OR REPLACE FUNCTION tf\(a integer, integer\)\s+RETURNS integer\s+LANGUAGE SQL\s+IMMUTABLE\s+STRICT\s+SECURITY DEFINER\s+COST 2\s+SET search_path = public\s+AS 'SELECT \$1 \+ \$2'\s*\z/
|
890
|
+
@d.create_function(*args)
|
891
|
+
# Make sure replace works
|
892
|
+
@d.create_function(*args)
|
893
|
+
rows = @d['SELECT tf(1, 2)'].all.should == [{:tf=>3}]
|
894
|
+
args = ['tf', {:if_exists=>true, :cascade=>true, :args=>[[:integer, :a], :integer]}]
|
895
|
+
@d.send(:drop_function_sql,*args).should == 'DROP FUNCTION IF EXISTS tf(a integer, integer) CASCADE'
|
896
|
+
@d.drop_function(*args)
|
897
|
+
# Make sure if exists works
|
898
|
+
@d.drop_function(*args)
|
899
|
+
end
|
900
|
+
|
901
|
+
specify "#create_language and #drop_language should create and drop languages" do
|
902
|
+
@d.send(:create_language_sql, :plpgsql).should == 'CREATE LANGUAGE plpgsql'
|
903
|
+
@d.create_language(:plpgsql)
|
904
|
+
proc{@d.create_language(:plpgsql)}.should raise_error(Sequel::DatabaseError)
|
905
|
+
@d.send(:drop_language_sql, :plpgsql).should == 'DROP LANGUAGE plpgsql'
|
906
|
+
@d.drop_language(:plpgsql)
|
907
|
+
proc{@d.drop_language(:plpgsql)}.should raise_error(Sequel::DatabaseError)
|
908
|
+
@d.send(:create_language_sql, :plpgsql, :trusted=>true, :handler=>:a, :validator=>:b).should == 'CREATE TRUSTED LANGUAGE plpgsql HANDLER a VALIDATOR b'
|
909
|
+
@d.send(:drop_language_sql, :plpgsql, :if_exists=>true, :cascade=>true).should == 'DROP LANGUAGE IF EXISTS plpgsql CASCADE'
|
910
|
+
# Make sure if exists works
|
911
|
+
@d.drop_language(:plpgsql, :if_exists=>true, :cascade=>true)
|
912
|
+
end
|
913
|
+
|
914
|
+
specify "#create_trigger and #drop_trigger should create and drop triggers" do
|
915
|
+
@d.create_language(:plpgsql)
|
916
|
+
@d.create_function(:tf, 'BEGIN IF NEW.value IS NULL THEN RAISE EXCEPTION \'Blah\'; END IF; RETURN NEW; END;', :language=>:plpgsql, :returns=>:trigger)
|
917
|
+
@d.send(:create_trigger_sql, :test, :identity, :tf, :each_row=>true).should == 'CREATE TRIGGER identity BEFORE INSERT OR UPDATE OR DELETE ON public.test FOR EACH ROW EXECUTE PROCEDURE tf()'
|
918
|
+
@d.create_table(:test){String :name; Integer :value}
|
919
|
+
@d.create_trigger(:test, :identity, :tf, :each_row=>true)
|
920
|
+
@d[:test].insert(:name=>'a', :value=>1)
|
921
|
+
@d[:test].filter(:name=>'a').all.should == [{:name=>'a', :value=>1}]
|
922
|
+
proc{@d[:test].filter(:name=>'a').update(:value=>nil)}.should raise_error(Sequel::DatabaseError)
|
923
|
+
@d[:test].filter(:name=>'a').all.should == [{:name=>'a', :value=>1}]
|
924
|
+
@d[:test].filter(:name=>'a').update(:value=>3)
|
925
|
+
@d[:test].filter(:name=>'a').all.should == [{:name=>'a', :value=>3}]
|
926
|
+
@d.send(:drop_trigger_sql, :test, :identity).should == 'DROP TRIGGER identity ON public.test'
|
927
|
+
@d.drop_trigger(:test, :identity)
|
928
|
+
@d.send(:create_trigger_sql, :test, :identity, :tf, :after=>true, :events=>:insert, :args=>[1, 'a']).should == 'CREATE TRIGGER identity AFTER INSERT ON public.test EXECUTE PROCEDURE tf(1, \'a\')'
|
929
|
+
@d.send(:drop_trigger_sql, :test, :identity, :if_exists=>true, :cascade=>true).should == 'DROP TRIGGER IF EXISTS identity ON public.test CASCADE'
|
930
|
+
# Make sure if exists works
|
931
|
+
@d.drop_trigger(:test, :identity, :if_exists=>true, :cascade=>true)
|
932
|
+
end
|
933
|
+
end
|
934
|
+
|
935
|
+
if POSTGRES_DB.class.adapter_scheme == :postgres
|
936
|
+
context "Postgres::Dataset #use_cursor" do
|
937
|
+
before(:all) do
|
938
|
+
@db = POSTGRES_DB
|
939
|
+
@db.create_table!(:test_cursor){Integer :x}
|
940
|
+
@db.sqls.clear
|
941
|
+
@ds = @db[:test_cursor]
|
942
|
+
@db.transaction{1001.times{|i| @ds.insert(i)}}
|
943
|
+
end
|
944
|
+
after(:all) do
|
945
|
+
@db.drop_table(:test) rescue nil
|
946
|
+
end
|
947
|
+
|
948
|
+
specify "should return the same results as the non-cursor use" do
|
949
|
+
@ds.all.should == @ds.use_cursor.all
|
950
|
+
end
|
951
|
+
|
952
|
+
specify "should respect the :rows_per_fetch option" do
|
953
|
+
@db.sqls.clear
|
954
|
+
@ds.use_cursor.all
|
955
|
+
@db.sqls.length.should == 6
|
956
|
+
@db.sqls.clear
|
957
|
+
@ds.use_cursor(:rows_per_fetch=>100).all
|
958
|
+
@db.sqls.length.should == 15
|
959
|
+
end
|
960
|
+
|
961
|
+
specify "should handle returning inside block" do
|
962
|
+
def @ds.check_return
|
963
|
+
use_cursor.each{|r| return}
|
964
|
+
end
|
965
|
+
@ds.check_return
|
966
|
+
@ds.all.should == @ds.use_cursor.all
|
967
|
+
end
|
968
|
+
end
|
969
|
+
end
|