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.
Files changed (237) hide show
  1. data/CHANGELOG +3134 -0
  2. data/COPYING +19 -0
  3. data/README.rdoc +723 -0
  4. data/Rakefile +193 -0
  5. data/bin/sequel +196 -0
  6. data/doc/advanced_associations.rdoc +644 -0
  7. data/doc/cheat_sheet.rdoc +218 -0
  8. data/doc/dataset_basics.rdoc +106 -0
  9. data/doc/dataset_filtering.rdoc +158 -0
  10. data/doc/opening_databases.rdoc +296 -0
  11. data/doc/prepared_statements.rdoc +104 -0
  12. data/doc/reflection.rdoc +84 -0
  13. data/doc/release_notes/1.0.txt +38 -0
  14. data/doc/release_notes/1.1.txt +143 -0
  15. data/doc/release_notes/1.3.txt +101 -0
  16. data/doc/release_notes/1.4.0.txt +53 -0
  17. data/doc/release_notes/1.5.0.txt +155 -0
  18. data/doc/release_notes/2.0.0.txt +298 -0
  19. data/doc/release_notes/2.1.0.txt +271 -0
  20. data/doc/release_notes/2.10.0.txt +328 -0
  21. data/doc/release_notes/2.11.0.txt +215 -0
  22. data/doc/release_notes/2.12.0.txt +534 -0
  23. data/doc/release_notes/2.2.0.txt +253 -0
  24. data/doc/release_notes/2.3.0.txt +88 -0
  25. data/doc/release_notes/2.4.0.txt +106 -0
  26. data/doc/release_notes/2.5.0.txt +137 -0
  27. data/doc/release_notes/2.6.0.txt +157 -0
  28. data/doc/release_notes/2.7.0.txt +166 -0
  29. data/doc/release_notes/2.8.0.txt +171 -0
  30. data/doc/release_notes/2.9.0.txt +97 -0
  31. data/doc/release_notes/3.0.0.txt +221 -0
  32. data/doc/release_notes/3.1.0.txt +406 -0
  33. data/doc/release_notes/3.10.0.txt +286 -0
  34. data/doc/release_notes/3.2.0.txt +268 -0
  35. data/doc/release_notes/3.3.0.txt +192 -0
  36. data/doc/release_notes/3.4.0.txt +325 -0
  37. data/doc/release_notes/3.5.0.txt +510 -0
  38. data/doc/release_notes/3.6.0.txt +366 -0
  39. data/doc/release_notes/3.7.0.txt +179 -0
  40. data/doc/release_notes/3.8.0.txt +151 -0
  41. data/doc/release_notes/3.9.0.txt +233 -0
  42. data/doc/schema.rdoc +36 -0
  43. data/doc/sharding.rdoc +113 -0
  44. data/doc/virtual_rows.rdoc +205 -0
  45. data/lib/sequel.rb +1 -0
  46. data/lib/sequel/adapters/ado.rb +90 -0
  47. data/lib/sequel/adapters/ado/mssql.rb +30 -0
  48. data/lib/sequel/adapters/amalgalite.rb +176 -0
  49. data/lib/sequel/adapters/db2.rb +139 -0
  50. data/lib/sequel/adapters/dbi.rb +113 -0
  51. data/lib/sequel/adapters/do.rb +188 -0
  52. data/lib/sequel/adapters/do/mysql.rb +49 -0
  53. data/lib/sequel/adapters/do/postgres.rb +91 -0
  54. data/lib/sequel/adapters/do/sqlite.rb +40 -0
  55. data/lib/sequel/adapters/firebird.rb +283 -0
  56. data/lib/sequel/adapters/informix.rb +77 -0
  57. data/lib/sequel/adapters/jdbc.rb +587 -0
  58. data/lib/sequel/adapters/jdbc/as400.rb +58 -0
  59. data/lib/sequel/adapters/jdbc/h2.rb +133 -0
  60. data/lib/sequel/adapters/jdbc/mssql.rb +57 -0
  61. data/lib/sequel/adapters/jdbc/mysql.rb +78 -0
  62. data/lib/sequel/adapters/jdbc/oracle.rb +50 -0
  63. data/lib/sequel/adapters/jdbc/postgresql.rb +108 -0
  64. data/lib/sequel/adapters/jdbc/sqlite.rb +55 -0
  65. data/lib/sequel/adapters/mysql.rb +421 -0
  66. data/lib/sequel/adapters/odbc.rb +143 -0
  67. data/lib/sequel/adapters/odbc/mssql.rb +42 -0
  68. data/lib/sequel/adapters/openbase.rb +64 -0
  69. data/lib/sequel/adapters/oracle.rb +131 -0
  70. data/lib/sequel/adapters/postgres.rb +504 -0
  71. data/lib/sequel/adapters/shared/mssql.rb +490 -0
  72. data/lib/sequel/adapters/shared/mysql.rb +498 -0
  73. data/lib/sequel/adapters/shared/oracle.rb +195 -0
  74. data/lib/sequel/adapters/shared/postgres.rb +830 -0
  75. data/lib/sequel/adapters/shared/progress.rb +44 -0
  76. data/lib/sequel/adapters/shared/sqlite.rb +389 -0
  77. data/lib/sequel/adapters/sqlite.rb +224 -0
  78. data/lib/sequel/adapters/utils/stored_procedures.rb +84 -0
  79. data/lib/sequel/connection_pool.rb +99 -0
  80. data/lib/sequel/connection_pool/sharded_single.rb +84 -0
  81. data/lib/sequel/connection_pool/sharded_threaded.rb +211 -0
  82. data/lib/sequel/connection_pool/single.rb +29 -0
  83. data/lib/sequel/connection_pool/threaded.rb +150 -0
  84. data/lib/sequel/core.rb +293 -0
  85. data/lib/sequel/core_sql.rb +241 -0
  86. data/lib/sequel/database.rb +1079 -0
  87. data/lib/sequel/database/schema_generator.rb +327 -0
  88. data/lib/sequel/database/schema_methods.rb +203 -0
  89. data/lib/sequel/database/schema_sql.rb +320 -0
  90. data/lib/sequel/dataset.rb +32 -0
  91. data/lib/sequel/dataset/actions.rb +441 -0
  92. data/lib/sequel/dataset/features.rb +86 -0
  93. data/lib/sequel/dataset/graph.rb +254 -0
  94. data/lib/sequel/dataset/misc.rb +119 -0
  95. data/lib/sequel/dataset/mutation.rb +64 -0
  96. data/lib/sequel/dataset/prepared_statements.rb +227 -0
  97. data/lib/sequel/dataset/query.rb +709 -0
  98. data/lib/sequel/dataset/sql.rb +996 -0
  99. data/lib/sequel/exceptions.rb +51 -0
  100. data/lib/sequel/extensions/blank.rb +43 -0
  101. data/lib/sequel/extensions/inflector.rb +242 -0
  102. data/lib/sequel/extensions/looser_typecasting.rb +21 -0
  103. data/lib/sequel/extensions/migration.rb +239 -0
  104. data/lib/sequel/extensions/named_timezones.rb +61 -0
  105. data/lib/sequel/extensions/pagination.rb +100 -0
  106. data/lib/sequel/extensions/pretty_table.rb +82 -0
  107. data/lib/sequel/extensions/query.rb +52 -0
  108. data/lib/sequel/extensions/schema_dumper.rb +271 -0
  109. data/lib/sequel/extensions/sql_expr.rb +122 -0
  110. data/lib/sequel/extensions/string_date_time.rb +46 -0
  111. data/lib/sequel/extensions/thread_local_timezones.rb +48 -0
  112. data/lib/sequel/metaprogramming.rb +9 -0
  113. data/lib/sequel/model.rb +120 -0
  114. data/lib/sequel/model/associations.rb +1514 -0
  115. data/lib/sequel/model/base.rb +1069 -0
  116. data/lib/sequel/model/default_inflections.rb +45 -0
  117. data/lib/sequel/model/errors.rb +39 -0
  118. data/lib/sequel/model/exceptions.rb +21 -0
  119. data/lib/sequel/model/inflections.rb +162 -0
  120. data/lib/sequel/model/plugins.rb +70 -0
  121. data/lib/sequel/plugins/active_model.rb +59 -0
  122. data/lib/sequel/plugins/association_dependencies.rb +103 -0
  123. data/lib/sequel/plugins/association_proxies.rb +41 -0
  124. data/lib/sequel/plugins/boolean_readers.rb +53 -0
  125. data/lib/sequel/plugins/caching.rb +141 -0
  126. data/lib/sequel/plugins/class_table_inheritance.rb +214 -0
  127. data/lib/sequel/plugins/composition.rb +138 -0
  128. data/lib/sequel/plugins/force_encoding.rb +72 -0
  129. data/lib/sequel/plugins/hook_class_methods.rb +126 -0
  130. data/lib/sequel/plugins/identity_map.rb +116 -0
  131. data/lib/sequel/plugins/instance_filters.rb +98 -0
  132. data/lib/sequel/plugins/instance_hooks.rb +57 -0
  133. data/lib/sequel/plugins/lazy_attributes.rb +77 -0
  134. data/lib/sequel/plugins/many_through_many.rb +208 -0
  135. data/lib/sequel/plugins/nested_attributes.rb +206 -0
  136. data/lib/sequel/plugins/optimistic_locking.rb +81 -0
  137. data/lib/sequel/plugins/rcte_tree.rb +281 -0
  138. data/lib/sequel/plugins/schema.rb +66 -0
  139. data/lib/sequel/plugins/serialization.rb +166 -0
  140. data/lib/sequel/plugins/single_table_inheritance.rb +74 -0
  141. data/lib/sequel/plugins/subclasses.rb +45 -0
  142. data/lib/sequel/plugins/tactical_eager_loading.rb +61 -0
  143. data/lib/sequel/plugins/timestamps.rb +87 -0
  144. data/lib/sequel/plugins/touch.rb +118 -0
  145. data/lib/sequel/plugins/typecast_on_load.rb +72 -0
  146. data/lib/sequel/plugins/validation_class_methods.rb +405 -0
  147. data/lib/sequel/plugins/validation_helpers.rb +223 -0
  148. data/lib/sequel/sql.rb +1020 -0
  149. data/lib/sequel/timezones.rb +161 -0
  150. data/lib/sequel/version.rb +12 -0
  151. data/lib/sequel_core.rb +1 -0
  152. data/lib/sequel_model.rb +1 -0
  153. data/spec/adapters/firebird_spec.rb +407 -0
  154. data/spec/adapters/informix_spec.rb +97 -0
  155. data/spec/adapters/mssql_spec.rb +403 -0
  156. data/spec/adapters/mysql_spec.rb +1019 -0
  157. data/spec/adapters/oracle_spec.rb +286 -0
  158. data/spec/adapters/postgres_spec.rb +969 -0
  159. data/spec/adapters/spec_helper.rb +51 -0
  160. data/spec/adapters/sqlite_spec.rb +432 -0
  161. data/spec/core/connection_pool_spec.rb +808 -0
  162. data/spec/core/core_sql_spec.rb +417 -0
  163. data/spec/core/database_spec.rb +1662 -0
  164. data/spec/core/dataset_spec.rb +3827 -0
  165. data/spec/core/expression_filters_spec.rb +595 -0
  166. data/spec/core/object_graph_spec.rb +296 -0
  167. data/spec/core/schema_generator_spec.rb +159 -0
  168. data/spec/core/schema_spec.rb +830 -0
  169. data/spec/core/spec_helper.rb +56 -0
  170. data/spec/core/version_spec.rb +7 -0
  171. data/spec/extensions/active_model_spec.rb +76 -0
  172. data/spec/extensions/association_dependencies_spec.rb +127 -0
  173. data/spec/extensions/association_proxies_spec.rb +50 -0
  174. data/spec/extensions/blank_spec.rb +67 -0
  175. data/spec/extensions/boolean_readers_spec.rb +92 -0
  176. data/spec/extensions/caching_spec.rb +250 -0
  177. data/spec/extensions/class_table_inheritance_spec.rb +252 -0
  178. data/spec/extensions/composition_spec.rb +194 -0
  179. data/spec/extensions/force_encoding_spec.rb +117 -0
  180. data/spec/extensions/hook_class_methods_spec.rb +470 -0
  181. data/spec/extensions/identity_map_spec.rb +202 -0
  182. data/spec/extensions/inflector_spec.rb +181 -0
  183. data/spec/extensions/instance_filters_spec.rb +55 -0
  184. data/spec/extensions/instance_hooks_spec.rb +133 -0
  185. data/spec/extensions/lazy_attributes_spec.rb +153 -0
  186. data/spec/extensions/looser_typecasting_spec.rb +39 -0
  187. data/spec/extensions/many_through_many_spec.rb +884 -0
  188. data/spec/extensions/migration_spec.rb +332 -0
  189. data/spec/extensions/named_timezones_spec.rb +72 -0
  190. data/spec/extensions/nested_attributes_spec.rb +396 -0
  191. data/spec/extensions/optimistic_locking_spec.rb +100 -0
  192. data/spec/extensions/pagination_spec.rb +99 -0
  193. data/spec/extensions/pretty_table_spec.rb +91 -0
  194. data/spec/extensions/query_spec.rb +85 -0
  195. data/spec/extensions/rcte_tree_spec.rb +205 -0
  196. data/spec/extensions/schema_dumper_spec.rb +357 -0
  197. data/spec/extensions/schema_spec.rb +127 -0
  198. data/spec/extensions/serialization_spec.rb +209 -0
  199. data/spec/extensions/single_table_inheritance_spec.rb +96 -0
  200. data/spec/extensions/spec_helper.rb +91 -0
  201. data/spec/extensions/sql_expr_spec.rb +89 -0
  202. data/spec/extensions/string_date_time_spec.rb +93 -0
  203. data/spec/extensions/subclasses_spec.rb +52 -0
  204. data/spec/extensions/tactical_eager_loading_spec.rb +65 -0
  205. data/spec/extensions/thread_local_timezones_spec.rb +45 -0
  206. data/spec/extensions/timestamps_spec.rb +150 -0
  207. data/spec/extensions/touch_spec.rb +155 -0
  208. data/spec/extensions/typecast_on_load_spec.rb +69 -0
  209. data/spec/extensions/validation_class_methods_spec.rb +984 -0
  210. data/spec/extensions/validation_helpers_spec.rb +438 -0
  211. data/spec/integration/associations_test.rb +281 -0
  212. data/spec/integration/database_test.rb +26 -0
  213. data/spec/integration/dataset_test.rb +963 -0
  214. data/spec/integration/eager_loader_test.rb +734 -0
  215. data/spec/integration/model_test.rb +130 -0
  216. data/spec/integration/plugin_test.rb +814 -0
  217. data/spec/integration/prepared_statement_test.rb +213 -0
  218. data/spec/integration/schema_test.rb +361 -0
  219. data/spec/integration/spec_helper.rb +73 -0
  220. data/spec/integration/timezone_test.rb +55 -0
  221. data/spec/integration/transaction_test.rb +122 -0
  222. data/spec/integration/type_test.rb +96 -0
  223. data/spec/model/association_reflection_spec.rb +175 -0
  224. data/spec/model/associations_spec.rb +2633 -0
  225. data/spec/model/base_spec.rb +418 -0
  226. data/spec/model/dataset_methods_spec.rb +78 -0
  227. data/spec/model/eager_loading_spec.rb +1391 -0
  228. data/spec/model/hooks_spec.rb +240 -0
  229. data/spec/model/inflector_spec.rb +26 -0
  230. data/spec/model/model_spec.rb +593 -0
  231. data/spec/model/plugins_spec.rb +236 -0
  232. data/spec/model/record_spec.rb +1500 -0
  233. data/spec/model/spec_helper.rb +97 -0
  234. data/spec/model/validations_spec.rb +153 -0
  235. data/spec/rcov.opts +6 -0
  236. data/spec/spec_config.rb.example +10 -0
  237. 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