epugh-sequel 0.0.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 (134) hide show
  1. data/README.rdoc +652 -0
  2. data/VERSION.yml +4 -0
  3. data/bin/sequel +104 -0
  4. data/lib/sequel.rb +1 -0
  5. data/lib/sequel/adapters/ado.rb +85 -0
  6. data/lib/sequel/adapters/db2.rb +132 -0
  7. data/lib/sequel/adapters/dbi.rb +101 -0
  8. data/lib/sequel/adapters/do.rb +197 -0
  9. data/lib/sequel/adapters/do/mysql.rb +38 -0
  10. data/lib/sequel/adapters/do/postgres.rb +92 -0
  11. data/lib/sequel/adapters/do/sqlite.rb +31 -0
  12. data/lib/sequel/adapters/firebird.rb +307 -0
  13. data/lib/sequel/adapters/informix.rb +75 -0
  14. data/lib/sequel/adapters/jdbc.rb +485 -0
  15. data/lib/sequel/adapters/jdbc/h2.rb +62 -0
  16. data/lib/sequel/adapters/jdbc/mysql.rb +56 -0
  17. data/lib/sequel/adapters/jdbc/oracle.rb +23 -0
  18. data/lib/sequel/adapters/jdbc/postgresql.rb +101 -0
  19. data/lib/sequel/adapters/jdbc/sqlite.rb +43 -0
  20. data/lib/sequel/adapters/mysql.rb +370 -0
  21. data/lib/sequel/adapters/odbc.rb +184 -0
  22. data/lib/sequel/adapters/openbase.rb +57 -0
  23. data/lib/sequel/adapters/oracle.rb +140 -0
  24. data/lib/sequel/adapters/postgres.rb +453 -0
  25. data/lib/sequel/adapters/shared/mssql.rb +93 -0
  26. data/lib/sequel/adapters/shared/mysql.rb +341 -0
  27. data/lib/sequel/adapters/shared/oracle.rb +62 -0
  28. data/lib/sequel/adapters/shared/postgres.rb +743 -0
  29. data/lib/sequel/adapters/shared/progress.rb +34 -0
  30. data/lib/sequel/adapters/shared/sqlite.rb +263 -0
  31. data/lib/sequel/adapters/sqlite.rb +243 -0
  32. data/lib/sequel/adapters/utils/date_format.rb +21 -0
  33. data/lib/sequel/adapters/utils/stored_procedures.rb +75 -0
  34. data/lib/sequel/adapters/utils/unsupported.rb +62 -0
  35. data/lib/sequel/connection_pool.rb +258 -0
  36. data/lib/sequel/core.rb +204 -0
  37. data/lib/sequel/core_sql.rb +185 -0
  38. data/lib/sequel/database.rb +687 -0
  39. data/lib/sequel/database/schema_generator.rb +324 -0
  40. data/lib/sequel/database/schema_methods.rb +164 -0
  41. data/lib/sequel/database/schema_sql.rb +324 -0
  42. data/lib/sequel/dataset.rb +422 -0
  43. data/lib/sequel/dataset/convenience.rb +237 -0
  44. data/lib/sequel/dataset/prepared_statements.rb +220 -0
  45. data/lib/sequel/dataset/sql.rb +1105 -0
  46. data/lib/sequel/deprecated.rb +529 -0
  47. data/lib/sequel/exceptions.rb +44 -0
  48. data/lib/sequel/extensions/blank.rb +42 -0
  49. data/lib/sequel/extensions/inflector.rb +288 -0
  50. data/lib/sequel/extensions/pagination.rb +96 -0
  51. data/lib/sequel/extensions/pretty_table.rb +78 -0
  52. data/lib/sequel/extensions/query.rb +48 -0
  53. data/lib/sequel/extensions/string_date_time.rb +47 -0
  54. data/lib/sequel/metaprogramming.rb +44 -0
  55. data/lib/sequel/migration.rb +212 -0
  56. data/lib/sequel/model.rb +142 -0
  57. data/lib/sequel/model/association_reflection.rb +263 -0
  58. data/lib/sequel/model/associations.rb +1024 -0
  59. data/lib/sequel/model/base.rb +911 -0
  60. data/lib/sequel/model/deprecated.rb +188 -0
  61. data/lib/sequel/model/deprecated_hooks.rb +103 -0
  62. data/lib/sequel/model/deprecated_inflector.rb +335 -0
  63. data/lib/sequel/model/deprecated_validations.rb +384 -0
  64. data/lib/sequel/model/errors.rb +37 -0
  65. data/lib/sequel/model/exceptions.rb +7 -0
  66. data/lib/sequel/model/inflections.rb +230 -0
  67. data/lib/sequel/model/plugins.rb +74 -0
  68. data/lib/sequel/object_graph.rb +230 -0
  69. data/lib/sequel/plugins/caching.rb +122 -0
  70. data/lib/sequel/plugins/hook_class_methods.rb +122 -0
  71. data/lib/sequel/plugins/schema.rb +53 -0
  72. data/lib/sequel/plugins/single_table_inheritance.rb +63 -0
  73. data/lib/sequel/plugins/validation_class_methods.rb +373 -0
  74. data/lib/sequel/sql.rb +854 -0
  75. data/lib/sequel/version.rb +11 -0
  76. data/lib/sequel_core.rb +1 -0
  77. data/lib/sequel_model.rb +1 -0
  78. data/spec/adapters/ado_spec.rb +46 -0
  79. data/spec/adapters/firebird_spec.rb +376 -0
  80. data/spec/adapters/informix_spec.rb +96 -0
  81. data/spec/adapters/mysql_spec.rb +875 -0
  82. data/spec/adapters/oracle_spec.rb +272 -0
  83. data/spec/adapters/postgres_spec.rb +692 -0
  84. data/spec/adapters/spec_helper.rb +10 -0
  85. data/spec/adapters/sqlite_spec.rb +550 -0
  86. data/spec/core/connection_pool_spec.rb +526 -0
  87. data/spec/core/core_ext_spec.rb +156 -0
  88. data/spec/core/core_sql_spec.rb +528 -0
  89. data/spec/core/database_spec.rb +1214 -0
  90. data/spec/core/dataset_spec.rb +3513 -0
  91. data/spec/core/expression_filters_spec.rb +363 -0
  92. data/spec/core/migration_spec.rb +261 -0
  93. data/spec/core/object_graph_spec.rb +280 -0
  94. data/spec/core/pretty_table_spec.rb +58 -0
  95. data/spec/core/schema_generator_spec.rb +167 -0
  96. data/spec/core/schema_spec.rb +778 -0
  97. data/spec/core/spec_helper.rb +82 -0
  98. data/spec/core/version_spec.rb +7 -0
  99. data/spec/extensions/blank_spec.rb +67 -0
  100. data/spec/extensions/caching_spec.rb +201 -0
  101. data/spec/extensions/hook_class_methods_spec.rb +470 -0
  102. data/spec/extensions/inflector_spec.rb +122 -0
  103. data/spec/extensions/pagination_spec.rb +99 -0
  104. data/spec/extensions/pretty_table_spec.rb +91 -0
  105. data/spec/extensions/query_spec.rb +85 -0
  106. data/spec/extensions/schema_spec.rb +111 -0
  107. data/spec/extensions/single_table_inheritance_spec.rb +53 -0
  108. data/spec/extensions/spec_helper.rb +90 -0
  109. data/spec/extensions/string_date_time_spec.rb +93 -0
  110. data/spec/extensions/validation_class_methods_spec.rb +1054 -0
  111. data/spec/integration/dataset_test.rb +160 -0
  112. data/spec/integration/eager_loader_test.rb +683 -0
  113. data/spec/integration/prepared_statement_test.rb +130 -0
  114. data/spec/integration/schema_test.rb +183 -0
  115. data/spec/integration/spec_helper.rb +75 -0
  116. data/spec/integration/type_test.rb +96 -0
  117. data/spec/model/association_reflection_spec.rb +93 -0
  118. data/spec/model/associations_spec.rb +1780 -0
  119. data/spec/model/base_spec.rb +494 -0
  120. data/spec/model/caching_spec.rb +217 -0
  121. data/spec/model/dataset_methods_spec.rb +78 -0
  122. data/spec/model/eager_loading_spec.rb +1165 -0
  123. data/spec/model/hooks_spec.rb +472 -0
  124. data/spec/model/inflector_spec.rb +126 -0
  125. data/spec/model/model_spec.rb +588 -0
  126. data/spec/model/plugins_spec.rb +142 -0
  127. data/spec/model/record_spec.rb +1243 -0
  128. data/spec/model/schema_spec.rb +92 -0
  129. data/spec/model/spec_helper.rb +124 -0
  130. data/spec/model/validations_spec.rb +1080 -0
  131. data/spec/rcov.opts +6 -0
  132. data/spec/spec.opts +0 -0
  133. data/spec/spec_config.rb.example +10 -0
  134. metadata +202 -0
@@ -0,0 +1,272 @@
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
+
7
+ if ORACLE_DB.table_exists?(:items)
8
+ ORACLE_DB.drop_table :items
9
+ end
10
+ ORACLE_DB.create_table :items do
11
+ varchar2 :name, :size => 50
12
+ number :value, :size => 38
13
+ date :date_created
14
+ index :value
15
+ end
16
+
17
+ if ORACLE_DB.table_exists?(:books)
18
+ ORACLE_DB.drop_table :books
19
+ end
20
+ ORACLE_DB.create_table :books do
21
+ number :id, :size => 38
22
+ varchar2 :title, :size => 50
23
+ number :category_id, :size => 38
24
+ end
25
+
26
+ if ORACLE_DB.table_exists?(:categories)
27
+ ORACLE_DB.drop_table :categories
28
+ end
29
+ ORACLE_DB.create_table :categories do
30
+ number :id, :size => 38
31
+ varchar2 :cat_name, :size => 50
32
+ end
33
+
34
+ context "An Oracle database" do
35
+ specify "should provide disconnect functionality" do
36
+ ORACLE_DB.execute("select user from dual")
37
+ ORACLE_DB.pool.size.should == 1
38
+ ORACLE_DB.disconnect
39
+ ORACLE_DB.pool.size.should == 0
40
+ end
41
+
42
+ specify "should provide schema information" do
43
+ books_schema = [
44
+ [: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)"}],
45
+ [: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)"}],
46
+ [: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)"}]]
47
+ categories_schema = [
48
+ [: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)"}],
49
+ [: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)"}]]
50
+ items_schema = [
51
+ [: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)"}],
52
+ [: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)"}],
53
+ [: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}]]
54
+
55
+ {:books => books_schema, :categories => categories_schema, :items => items_schema}.each_pair do |table, expected_schema|
56
+ schema = ORACLE_DB.schema(table)
57
+ schema.should_not be_nil
58
+ expected_schema.should == schema
59
+ end
60
+ end
61
+ end
62
+
63
+ context "An Oracle dataset" do
64
+ setup do
65
+ @d = ORACLE_DB[:items]
66
+ @d.delete # remove all records
67
+ end
68
+
69
+ specify "should return the correct record count" do
70
+ @d.count.should == 0
71
+ @d << {:name => 'abc', :value => 123}
72
+ @d << {:name => 'abc', :value => 456}
73
+ @d << {:name => 'def', :value => 789}
74
+ @d.count.should == 3
75
+ end
76
+
77
+ specify "should return the correct records" do
78
+ @d.to_a.should == []
79
+ @d << {:name => 'abc', :value => 123}
80
+ @d << {:name => 'abc', :value => 456}
81
+ @d << {:name => 'def', :value => 789}
82
+
83
+ @d.order(:value).to_a.should == [
84
+ {:date_created=>nil, :name => 'abc', :value => 123},
85
+ {:date_created=>nil, :name => 'abc', :value => 456},
86
+ {:date_created=>nil, :name => 'def', :value => 789}
87
+ ]
88
+
89
+ @d.select(:name).uniq.order_by(:name).to_a.should == [
90
+ {:name => 'abc'},
91
+ {:name => 'def'}
92
+ ]
93
+
94
+ @d.order(:value.desc).limit(1).to_a.should == [
95
+ {:date_created=>nil, :name => 'def', :value => 789}
96
+ ]
97
+
98
+ @d.filter(:name => 'abc').to_a.should == [
99
+ {:date_created=>nil, :name => 'abc', :value => 123},
100
+ {:date_created=>nil, :name => 'abc', :value => 456}
101
+ ]
102
+
103
+ @d.order(:value.desc).filter(:name => 'abc').to_a.should == [
104
+ {:date_created=>nil, :name => 'abc', :value => 456},
105
+ {:date_created=>nil, :name => 'abc', :value => 123}
106
+ ]
107
+
108
+ @d.filter(:name => 'abc').limit(1).to_a.should == [
109
+ {:date_created=>nil, :name => 'abc', :value => 123}
110
+ ]
111
+
112
+ @d.filter(:name => 'abc').order(:value.desc).limit(1).to_a.should == [
113
+ {:date_created=>nil, :name => 'abc', :value => 456}
114
+ ]
115
+
116
+ @d.filter(:name => 'abc').order(:value).limit(1).to_a.should == [
117
+ {:date_created=>nil, :name => 'abc', :value => 123}
118
+ ]
119
+
120
+ @d.order(:value).limit(1).to_a.should == [
121
+ {:date_created=>nil, :name => 'abc', :value => 123}
122
+ ]
123
+
124
+ @d.order(:value).limit(1, 1).to_a.should == [
125
+ {:date_created=>nil, :name => 'abc', :value => 456}
126
+ ]
127
+
128
+ @d.order(:value).limit(1, 2).to_a.should == [
129
+ {:date_created=>nil, :name => 'def', :value => 789}
130
+ ]
131
+
132
+ @d.avg(:value).to_i.should == (789+123+456)/3
133
+
134
+ @d.max(:value).to_i.should == 789
135
+
136
+ @d.select(:name, :AVG.sql_function(:value)).filter(:name => 'abc').group(:name).to_a.should == [
137
+ {:name => 'abc', :"avg(value)" => (456+123)/2.0}
138
+ ]
139
+
140
+ @d.select(:AVG.sql_function(:value)).group(:name).order(:name).limit(1).to_a.should == [
141
+ {:"avg(value)" => (456+123)/2.0}
142
+ ]
143
+
144
+ @d.select(:name, :AVG.sql_function(:value)).group(:name).order(:name).to_a.should == [
145
+ {:name => 'abc', :"avg(value)" => (456+123)/2.0},
146
+ {:name => 'def', :"avg(value)" => 789*1.0}
147
+ ]
148
+
149
+ @d.select(:name, :AVG.sql_function(:value)).group(:name).order(:name).to_a.should == [
150
+ {:name => 'abc', :"avg(value)" => (456+123)/2.0},
151
+ {:name => 'def', :"avg(value)" => 789*1.0}
152
+ ]
153
+
154
+ @d.select(:name, :AVG.sql_function(:value)).group(:name).having(:name => ['abc', 'def']).order(:name).to_a.should == [
155
+ {:name => 'abc', :"avg(value)" => (456+123)/2.0},
156
+ {:name => 'def', :"avg(value)" => 789*1.0}
157
+ ]
158
+
159
+ @d.select(:name, :value).filter(:name => 'abc').union(@d.select(:name, :value).filter(:name => 'def')).order(:value).to_a.should == [
160
+ {:name => 'abc', :value => 123},
161
+ {:name => 'abc', :value => 456},
162
+ {:name => 'def', :value => 789}
163
+ ]
164
+
165
+ end
166
+
167
+ specify "should update records correctly" do
168
+ @d << {:name => 'abc', :value => 123}
169
+ @d << {:name => 'abc', :value => 456}
170
+ @d << {:name => 'def', :value => 789}
171
+ @d.filter(:name => 'abc').update(:value => 530)
172
+
173
+ # the third record should stay the same
174
+ # floating-point precision bullshit
175
+ @d[:name => 'def'][:value].should == 789
176
+ @d.filter(:value => 530).count.should == 2
177
+ end
178
+
179
+ specify "should translate values correctly" do
180
+ @d << {:name => 'abc', :value => 456}
181
+ @d << {:name => 'def', :value => 789}
182
+ @d.filter(:value > 500).update(:date_created => "to_timestamp('2009-09-09', 'YYYY-MM-DD')".lit)
183
+
184
+ @d[:name => 'def'][:date_created].should == Time.parse('2009-09-09')
185
+ end
186
+
187
+ specify "should delete records correctly" do
188
+ @d << {:name => 'abc', :value => 123}
189
+ @d << {:name => 'abc', :value => 456}
190
+ @d << {:name => 'def', :value => 789}
191
+ @d.filter(:name => 'abc').delete
192
+
193
+ @d.count.should == 1
194
+ @d.first[:name].should == 'def'
195
+ end
196
+
197
+ specify "should be able to literalize booleans" do
198
+ proc {@d.literal(true)}.should_not raise_error
199
+ proc {@d.literal(false)}.should_not raise_error
200
+ end
201
+
202
+ specify "should support transactions" do
203
+ ORACLE_DB.transaction do
204
+ @d << {:name => 'abc', :value => 1}
205
+ end
206
+
207
+ @d.count.should == 1
208
+ end
209
+ end
210
+
211
+ context "Joined Oracle dataset" do
212
+ setup do
213
+ @d1 = ORACLE_DB[:books]
214
+ @d1.delete # remove all records
215
+ @d1 << {:id => 1, :title => 'aaa', :category_id => 100}
216
+ @d1 << {:id => 2, :title => 'bbb', :category_id => 100}
217
+ @d1 << {:id => 3, :title => 'ccc', :category_id => 101}
218
+ @d1 << {:id => 4, :title => 'ddd', :category_id => 102}
219
+
220
+ @d2 = ORACLE_DB[:categories]
221
+ @d2.delete # remove all records
222
+ @d2 << {:id => 100, :cat_name => 'ruby'}
223
+ @d2 << {:id => 101, :cat_name => 'rails'}
224
+ end
225
+
226
+ specify "should return correct result" do
227
+ @d1.join(:categories, :id => :category_id).select(:books__id, :title, :cat_name).order(:books__id).to_a.should == [
228
+ {:id => 1, :title => 'aaa', :cat_name => 'ruby'},
229
+ {:id => 2, :title => 'bbb', :cat_name => 'ruby'},
230
+ {:id => 3, :title => 'ccc', :cat_name => 'rails'}
231
+ ]
232
+
233
+ @d1.join(:categories, :id => :category_id).select(:books__id, :title, :cat_name).order(:books__id).limit(2, 1).to_a.should == [
234
+ {:id => 2, :title => 'bbb', :cat_name => 'ruby'},
235
+ {:id => 3, :title => 'ccc', :cat_name => 'rails'},
236
+ ]
237
+
238
+ @d1.left_outer_join(:categories, :id => :category_id).select(:books__id, :title, :cat_name).order(:books__id).to_a.should == [
239
+ {:id => 1, :title => 'aaa', :cat_name => 'ruby'},
240
+ {:id => 2, :title => 'bbb', :cat_name => 'ruby'},
241
+ {:id => 3, :title => 'ccc', :cat_name => 'rails'},
242
+ {:id => 4, :title => 'ddd', :cat_name => nil}
243
+ ]
244
+
245
+ @d1.left_outer_join(:categories, :id => :category_id).select(:books__id, :title, :cat_name).order(:books__id.desc).limit(2, 0).to_a.should == [
246
+ {:id => 4, :title => 'ddd', :cat_name => nil},
247
+ {:id => 3, :title => 'ccc', :cat_name => 'rails'}
248
+ ]
249
+ end
250
+ end
251
+
252
+ context "Oracle aliasing" do
253
+ setup do
254
+ @d1 = ORACLE_DB[:books]
255
+ @d1.delete # remove all records
256
+ @d1 << {:id => 1, :title => 'aaa', :category_id => 100}
257
+ @d1 << {:id => 2, :title => 'bbb', :category_id => 100}
258
+ @d1 << {:id => 3, :title => 'bbb', :category_id => 100}
259
+ end
260
+
261
+ specify "should allow columns to be renamed" do
262
+ @d1.select(:title.as(:name)).order_by(:id).to_a.should == [
263
+ { :name => 'aaa' },
264
+ { :name => 'bbb' },
265
+ { :name => 'bbb' },
266
+ ]
267
+ end
268
+
269
+ specify "nested queries should work" do
270
+ @d1.select(:title).group_by(:title).count.should == 2
271
+ end
272
+ end
@@ -0,0 +1,692 @@
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
+ #POSTGRES_DB.instance_variable_set(:@server_version, 80100)
8
+ POSTGRES_DB.create_table! :test do
9
+ text :name
10
+ integer :value, :index => true
11
+ end
12
+ POSTGRES_DB.create_table! :test2 do
13
+ text :name
14
+ integer :value
15
+ end
16
+ POSTGRES_DB.create_table! :test3 do
17
+ integer :value
18
+ timestamp :time
19
+ end
20
+ POSTGRES_DB.create_table! :test4 do
21
+ varchar :name, :size => 20
22
+ bytea :value
23
+ end
24
+ POSTGRES_DB.create_table! :test5 do
25
+ primary_key :xid
26
+ integer :value
27
+ end
28
+
29
+ context "A PostgreSQL database" do
30
+ setup do
31
+ @db = POSTGRES_DB
32
+ end
33
+
34
+ specify "should provide disconnect functionality" do
35
+ @db.tables
36
+ @db.pool.size.should == 1
37
+ @db.disconnect
38
+ @db.pool.size.should == 0
39
+ end
40
+
41
+ specify "should provide the server version" do
42
+ @db.server_version.should > 70000
43
+ end
44
+
45
+ specify "should raise Sequel::Error on error" do
46
+ proc{@db << "SELECT 1 + 'a'"}.should raise_error(Sequel::Error)
47
+ end
48
+
49
+ specify "should correctly parse the schema" do
50
+ require 'logger'
51
+ @db.schema(:test3, :reload=>true).should == [
52
+ [:value, {:type=>:integer, :allow_null=>true, :default=>nil, :db_type=>"integer", :primary_key=>false}],
53
+ [:time, {:type=>:datetime, :allow_null=>true, :default=>nil, :db_type=>"timestamp without time zone", :primary_key=>false}]
54
+ ]
55
+ @db.schema(:test4, :reload=>true).should == [
56
+ [:name, {:type=>:string, :allow_null=>true, :default=>nil, :db_type=>"character varying(20)", :primary_key=>false}],
57
+ [:value, {:type=>:blob, :allow_null=>true, :default=>nil, :db_type=>"bytea", :primary_key=>false}]
58
+ ]
59
+ end
60
+ end
61
+
62
+ context "A PostgreSQL dataset" do
63
+ setup do
64
+ @d = POSTGRES_DB[:test]
65
+ @d.delete # remove all records
66
+ end
67
+
68
+ specify "should return the correct record count" do
69
+ @d.count.should == 0
70
+ @d << {:name => 'abc', :value => 123}
71
+ @d << {:name => 'abc', :value => 456}
72
+ @d << {:name => 'def', :value => 789}
73
+ @d.count.should == 3
74
+ end
75
+
76
+ specify "should return the correct records" do
77
+ @d.to_a.should == []
78
+ @d << {:name => 'abc', :value => 123}
79
+ @d << {:name => 'abc', :value => 456}
80
+ @d << {:name => 'def', :value => 789}
81
+
82
+ @d.order(:value).to_a.should == [
83
+ {:name => 'abc', :value => 123},
84
+ {:name => 'abc', :value => 456},
85
+ {:name => 'def', :value => 789}
86
+ ]
87
+ end
88
+
89
+ specify "should update records correctly" do
90
+ @d << {:name => 'abc', :value => 123}
91
+ @d << {:name => 'abc', :value => 456}
92
+ @d << {:name => 'def', :value => 789}
93
+ @d.filter(:name => 'abc').update(:value => 530)
94
+
95
+ # the third record should stay the same
96
+ # floating-point precision bullshit
97
+ @d[:name => 'def'][:value].should == 789
98
+ @d.filter(:value => 530).count.should == 2
99
+ end
100
+
101
+ specify "should delete records correctly" do
102
+ @d << {:name => 'abc', :value => 123}
103
+ @d << {:name => 'abc', :value => 456}
104
+ @d << {:name => 'def', :value => 789}
105
+ @d.filter(:name => 'abc').delete
106
+
107
+ @d.count.should == 1
108
+ @d.first[:name].should == 'def'
109
+ end
110
+
111
+ specify "should be able to literalize booleans" do
112
+ proc {@d.literal(true)}.should_not raise_error
113
+ proc {@d.literal(false)}.should_not raise_error
114
+ end
115
+
116
+ specify "should quote columns and tables using double quotes if quoting identifiers" do
117
+ @d.quote_identifiers = true
118
+ @d.select(:name).sql.should == \
119
+ 'SELECT "name" FROM "test"'
120
+
121
+ @d.select('COUNT(*)'.lit).sql.should == \
122
+ 'SELECT COUNT(*) FROM "test"'
123
+
124
+ @d.select(:max.sql_function(:value)).sql.should == \
125
+ 'SELECT max("value") FROM "test"'
126
+
127
+ @d.select(:NOW.sql_function).sql.should == \
128
+ 'SELECT NOW() FROM "test"'
129
+
130
+ @d.select(:max.sql_function(:items__value)).sql.should == \
131
+ 'SELECT max("items"."value") FROM "test"'
132
+
133
+ @d.order(:name.desc).sql.should == \
134
+ 'SELECT * FROM "test" ORDER BY "name" DESC'
135
+
136
+ @d.select('test.name AS item_name'.lit).sql.should == \
137
+ 'SELECT test.name AS item_name FROM "test"'
138
+
139
+ @d.select('"name"'.lit).sql.should == \
140
+ 'SELECT "name" FROM "test"'
141
+
142
+ @d.select('max(test."name") AS "max_name"'.lit).sql.should == \
143
+ 'SELECT max(test."name") AS "max_name" FROM "test"'
144
+
145
+ @d.select(:test.sql_function(:abc, 'hello')).sql.should == \
146
+ "SELECT test(\"abc\", 'hello') FROM \"test\""
147
+
148
+ @d.select(:test.sql_function(:abc__def, 'hello')).sql.should == \
149
+ "SELECT test(\"abc\".\"def\", 'hello') FROM \"test\""
150
+
151
+ @d.select(:test.sql_function(:abc__def, 'hello').as(:x2)).sql.should == \
152
+ "SELECT test(\"abc\".\"def\", 'hello') AS \"x2\" FROM \"test\""
153
+
154
+ @d.insert_sql(:value => 333).should =~ \
155
+ /\AINSERT INTO "test" \("value"\) VALUES \(333\)( RETURNING NULL)?\z/
156
+
157
+ @d.insert_sql(:x => :y).should =~ \
158
+ /\AINSERT INTO "test" \("x"\) VALUES \("y"\)( RETURNING NULL)?\z/
159
+
160
+ @d.disable_insert_returning.insert_sql(:value => 333).should =~ \
161
+ /\AINSERT INTO "test" \("value"\) VALUES \(333\)\z/
162
+ end
163
+
164
+ specify "should quote fields correctly when reversing the order if quoting identifiers" do
165
+ @d.quote_identifiers = true
166
+ @d.reverse_order(:name).sql.should == \
167
+ 'SELECT * FROM "test" ORDER BY "name" DESC'
168
+
169
+ @d.reverse_order(:name.desc).sql.should == \
170
+ 'SELECT * FROM "test" ORDER BY "name" ASC'
171
+
172
+ @d.reverse_order(:name, :test.desc).sql.should == \
173
+ 'SELECT * FROM "test" ORDER BY "name" DESC, "test" ASC'
174
+
175
+ @d.reverse_order(:name.desc, :test).sql.should == \
176
+ 'SELECT * FROM "test" ORDER BY "name" ASC, "test" DESC'
177
+ end
178
+
179
+ specify "should support transactions" do
180
+ POSTGRES_DB.transaction do
181
+ @d << {:name => 'abc', :value => 1}
182
+ end
183
+
184
+ @d.count.should == 1
185
+ end
186
+
187
+ specify "should have #transaction yield the connection" do
188
+ POSTGRES_DB.transaction do |conn|
189
+ conn.should_not == nil
190
+ end
191
+ end
192
+
193
+ specify "should correctly rollback transactions" do
194
+ proc do
195
+ POSTGRES_DB.transaction do
196
+ @d << {:name => 'abc', :value => 1}
197
+ raise Interrupt, 'asdf'
198
+ end
199
+ end.should raise_error(Interrupt)
200
+
201
+ @d.count.should == 0
202
+ end
203
+
204
+ specify "should handle returning inside of the block by committing" do
205
+ def POSTGRES_DB.ret_commit
206
+ transaction do
207
+ self[:test] << {:name => 'abc'}
208
+ return
209
+ self[:test] << {:name => 'd'}
210
+ end
211
+ end
212
+ @d.count.should == 0
213
+ POSTGRES_DB.ret_commit
214
+ @d.count.should == 1
215
+ POSTGRES_DB.ret_commit
216
+ @d.count.should == 2
217
+ proc do
218
+ POSTGRES_DB.transaction do
219
+ raise Interrupt, 'asdf'
220
+ end
221
+ end.should raise_error(Interrupt)
222
+
223
+ @d.count.should == 2
224
+ end
225
+
226
+ specify "should support nested transactions through savepoints using the savepoint option" do
227
+ POSTGRES_DB.transaction do
228
+ @d << {:name => '1'}
229
+ POSTGRES_DB.transaction(:savepoint=>true) do
230
+ @d << {:name => '2'}
231
+ POSTGRES_DB.transaction do
232
+ @d << {:name => '3'}
233
+ raise Sequel::Error::Rollback
234
+ end
235
+ end
236
+ @d << {:name => '4'}
237
+ POSTGRES_DB.transaction do
238
+ @d << {:name => '6'}
239
+ POSTGRES_DB.transaction(:savepoint=>true) do
240
+ @d << {:name => '7'}
241
+ raise Sequel::Error::Rollback
242
+ end
243
+ end
244
+ @d << {:name => '5'}
245
+ end
246
+
247
+ @d.order(:name).map(:name).should == %w{1 4 5 6}
248
+ end
249
+
250
+ specify "should support regexps" do
251
+ @d << {:name => 'abc', :value => 1}
252
+ @d << {:name => 'bcd', :value => 2}
253
+ @d.filter(:name => /bc/).count.should == 2
254
+ @d.filter(:name => /^bc/).count.should == 1
255
+ end
256
+
257
+ specify "should correctly escape strings" do
258
+ POSTGRES_DB['SELECT ? AS a', "\\dingo"].get(:a) == "\\dingo"
259
+ end
260
+
261
+ specify "should correctly escape strings with quotes" do
262
+ POSTGRES_DB['SELECT ? AS a', "\\'dingo"].get(:a) == "\\'dingo"
263
+ end
264
+
265
+ specify "should properly escape binary data" do
266
+ POSTGRES_DB['SELECT ? AS a', "\1\2\3".to_sequel_blob].get(:a) == "\1\2\3"
267
+ end
268
+
269
+ specify "should retrieve binary data as Blob object" do
270
+ d = POSTGRES_DB[:test4]
271
+ d << {:name => '123', :value => "\1\2\3".to_sequel_blob}
272
+ retrieved_binary_value = d[:name => '123'][:value]
273
+ retrieved_binary_value.should be_a_kind_of(::Sequel::SQL::Blob)
274
+ retrieved_binary_value.should == "\1\2\3"
275
+ retrieved_binary_value = d[:value => "\1\2\3".to_sequel_blob][:value]
276
+ retrieved_binary_value.should be_a_kind_of(::Sequel::SQL::Blob)
277
+ retrieved_binary_value.should == "\1\2\3"
278
+ end
279
+
280
+ specify "should properly receive binary data" do
281
+ POSTGRES_DB['SELECT ?::bytea AS a', "a"].get(:a) == "a"
282
+ end
283
+ end
284
+
285
+ context "A PostgreSQL dataset with a timestamp field" do
286
+ setup do
287
+ @d = POSTGRES_DB[:test3]
288
+ @d.delete
289
+ end
290
+
291
+ specify "should store milliseconds in time fields" do
292
+ t = Time.now
293
+ @d << {:value=>1, :time=>t}
294
+ @d.literal(@d[:value =>'1'][:time]).should == @d.literal(t)
295
+ @d[:value=>'1'][:time].usec.should == t.usec
296
+ end
297
+ end
298
+
299
+ context "A PostgreSQL database" do
300
+ setup do
301
+ @db = POSTGRES_DB
302
+ end
303
+
304
+ specify "should support column operations" do
305
+ @db.create_table!(:test2){text :name; integer :value}
306
+ @db[:test2] << {}
307
+ @db[:test2].columns.should == [:name, :value]
308
+
309
+ @db.add_column :test2, :xyz, :text, :default => '000'
310
+ @db[:test2].columns.should == [:name, :value, :xyz]
311
+ @db[:test2] << {:name => 'mmm', :value => 111}
312
+ @db[:test2].first[:xyz].should == '000'
313
+
314
+ @db[:test2].columns.should == [:name, :value, :xyz]
315
+ @db.drop_column :test2, :xyz
316
+
317
+ @db[:test2].columns.should == [:name, :value]
318
+
319
+ @db[:test2].delete
320
+ @db.add_column :test2, :xyz, :text, :default => '000'
321
+ @db[:test2] << {:name => 'mmm', :value => 111, :xyz => 'qqqq'}
322
+
323
+ @db[:test2].columns.should == [:name, :value, :xyz]
324
+ @db.rename_column :test2, :xyz, :zyx
325
+ @db[:test2].columns.should == [:name, :value, :zyx]
326
+ @db[:test2].first[:zyx].should == 'qqqq'
327
+
328
+ @db.add_column :test2, :xyz, :float
329
+ @db[:test2].delete
330
+ @db[:test2] << {:name => 'mmm', :value => 111, :xyz => 56.78}
331
+ @db.set_column_type :test2, :xyz, :integer
332
+
333
+ @db[:test2].first[:xyz].should == 57
334
+ end
335
+ end
336
+
337
+ context "A PostgreSQL database" do
338
+ setup do
339
+ end
340
+
341
+ specify "should support fulltext indexes" do
342
+ g = Sequel::Schema::Generator.new(POSTGRES_DB) do
343
+ text :title
344
+ text :body
345
+ full_text_index [:title, :body]
346
+ end
347
+ POSTGRES_DB.create_table_sql_list(:posts, *g.create_info).should == [
348
+ "CREATE TABLE posts (title text, body text)",
349
+ "CREATE INDEX posts_title_body_index ON posts USING gin (to_tsvector('simple', (COALESCE(title, '') || ' ' || COALESCE(body, ''))))"
350
+ ]
351
+ end
352
+
353
+ specify "should support fulltext indexes with a specific language" do
354
+ g = Sequel::Schema::Generator.new(POSTGRES_DB) do
355
+ text :title
356
+ text :body
357
+ full_text_index [:title, :body], :language => 'french'
358
+ end
359
+ POSTGRES_DB.create_table_sql_list(:posts, *g.create_info).should == [
360
+ "CREATE TABLE posts (title text, body text)",
361
+ "CREATE INDEX posts_title_body_index ON posts USING gin (to_tsvector('french', (COALESCE(title, '') || ' ' || COALESCE(body, ''))))"
362
+ ]
363
+ end
364
+
365
+ specify "should support full_text_search" do
366
+ POSTGRES_DB[:posts].full_text_search(:title, 'ruby').sql.should ==
367
+ "SELECT * FROM posts WHERE (to_tsvector('simple', (COALESCE(title, ''))) @@ to_tsquery('simple', 'ruby'))"
368
+
369
+ POSTGRES_DB[:posts].full_text_search([:title, :body], ['ruby', 'sequel']).sql.should ==
370
+ "SELECT * FROM posts WHERE (to_tsvector('simple', (COALESCE(title, '') || ' ' || COALESCE(body, ''))) @@ to_tsquery('simple', 'ruby | sequel'))"
371
+
372
+ POSTGRES_DB[:posts].full_text_search(:title, 'ruby', :language => 'french').sql.should ==
373
+ "SELECT * FROM posts WHERE (to_tsvector('french', (COALESCE(title, ''))) @@ to_tsquery('french', 'ruby'))"
374
+ end
375
+
376
+ specify "should support spatial indexes" do
377
+ g = Sequel::Schema::Generator.new(POSTGRES_DB) do
378
+ geometry :geom
379
+ spatial_index [:geom]
380
+ end
381
+ POSTGRES_DB.create_table_sql_list(:posts, *g.create_info).should == [
382
+ "CREATE TABLE posts (geom geometry)",
383
+ "CREATE INDEX posts_geom_index ON posts USING gist (geom)"
384
+ ]
385
+ end
386
+
387
+ specify "should support indexes with index type" do
388
+ g = Sequel::Schema::Generator.new(POSTGRES_DB) do
389
+ varchar :title, :size => 5
390
+ index :title, :type => 'hash'
391
+ end
392
+ POSTGRES_DB.create_table_sql_list(:posts, *g.create_info).should == [
393
+ "CREATE TABLE posts (title varchar(5))",
394
+ "CREATE INDEX posts_title_index ON posts USING hash (title)"
395
+ ]
396
+ end
397
+
398
+ specify "should support unique indexes with index type" do
399
+ g = Sequel::Schema::Generator.new(POSTGRES_DB) do
400
+ varchar :title, :size => 5
401
+ index :title, :type => 'hash', :unique => true
402
+ end
403
+ POSTGRES_DB.create_table_sql_list(:posts, *g.create_info).should == [
404
+ "CREATE TABLE posts (title varchar(5))",
405
+ "CREATE UNIQUE INDEX posts_title_index ON posts USING hash (title)"
406
+ ]
407
+ end
408
+
409
+ specify "should support partial indexes" do
410
+ g = Sequel::Schema::Generator.new(POSTGRES_DB) do
411
+ varchar :title, :size => 5
412
+ index :title, :where => {:something => 5}
413
+ end
414
+ POSTGRES_DB.create_table_sql_list(:posts, *g.create_info).should == [
415
+ "CREATE TABLE posts (title varchar(5))",
416
+ "CREATE INDEX posts_title_index ON posts (title) WHERE (something = 5)"
417
+ ]
418
+ end
419
+
420
+ specify "should support identifiers for table names in indicies" do
421
+ g = Sequel::Schema::Generator.new(POSTGRES_DB) do
422
+ varchar :title, :size => 5
423
+ index :title, :where => {:something => 5}
424
+ end
425
+ POSTGRES_DB.create_table_sql_list(Sequel::SQL::Identifier.new(:posts__test), *g.create_info).should == [
426
+ "CREATE TABLE posts__test (title varchar(5))",
427
+ "CREATE INDEX posts__test_title_index ON posts__test (title) WHERE (something = 5)"
428
+ ]
429
+ end
430
+ end
431
+
432
+ context "Postgres::Dataset#multi_insert_sql" do
433
+ setup do
434
+ @ds = POSTGRES_DB[:test]
435
+ end
436
+
437
+ specify "should return separate insert statements if server_version < 80200" do
438
+ @ds.meta_def(:server_version){80199}
439
+
440
+ @ds.multi_insert_sql([:x, :y], [[1, 2], [3, 4]]).should == [
441
+ 'INSERT INTO test (x, y) VALUES (1, 2)',
442
+ 'INSERT INTO test (x, y) VALUES (3, 4)'
443
+ ]
444
+ end
445
+
446
+ specify "should a single insert statement if server_version >= 80200" do
447
+ @ds.meta_def(:server_version){80200}
448
+
449
+ @ds.multi_insert_sql([:x, :y], [[1, 2], [3, 4]]).should == [
450
+ 'INSERT INTO test (x, y) VALUES (1, 2), (3, 4)'
451
+ ]
452
+
453
+ @ds.meta_def(:server_version){80201}
454
+
455
+ @ds.multi_insert_sql([:x, :y], [[1, 2], [3, 4]]).should == [
456
+ 'INSERT INTO test (x, y) VALUES (1, 2), (3, 4)'
457
+ ]
458
+ end
459
+ end
460
+
461
+ context "Postgres::Dataset#insert" do
462
+ setup do
463
+ @ds = POSTGRES_DB[:test5]
464
+ @ds.delete
465
+ end
466
+
467
+ specify "should call insert_sql if server_version < 80200" do
468
+ @ds.meta_def(:server_version){80100}
469
+ @ds.should_receive(:execute_insert).once.with('INSERT INTO test5 (value) VALUES (10)', :table=>:test5, :values=>{:value=>10})
470
+ @ds.insert(:value=>10)
471
+ end
472
+
473
+ specify "should call insert_sql if disabling insert returning" do
474
+ @ds.disable_insert_returning!
475
+ @ds.should_receive(:execute_insert).once.with('INSERT INTO test5 (value) VALUES (10)', :table=>:test5, :values=>{:value=>10})
476
+ @ds.insert(:value=>10)
477
+ end
478
+
479
+ specify "should use INSERT RETURNING if server_version >= 80200" do
480
+ @ds.meta_def(:server_version){80201}
481
+ @ds.should_receive(:clone).once.with(:server=>:default, :sql=>'INSERT INTO test5 (value) VALUES (10) RETURNING xid').and_return(@ds)
482
+ @ds.should_receive(:single_value).once
483
+ @ds.insert(:value=>10)
484
+ end
485
+
486
+ specify "should have insert_returning_sql use the RETURNING keyword" do
487
+ @ds.insert_returning_sql(:xid, :value=>10).should == "INSERT INTO test5 (value) VALUES (10) RETURNING xid"
488
+ @ds.insert_returning_sql('*'.lit, :value=>10).should == "INSERT INTO test5 (value) VALUES (10) RETURNING *"
489
+ end
490
+
491
+ specify "should have insert_select return nil if server_version < 80200" do
492
+ @ds.meta_def(:server_version){80100}
493
+ @ds.insert_select(:value=>10).should == nil
494
+ end
495
+
496
+ specify "should have insert_select insert the record and return the inserted record if server_version < 80200" do
497
+ @ds.meta_def(:server_version){80201}
498
+ h = @ds.insert_select(:value=>10)
499
+ h[:value].should == 10
500
+ @ds.first(:xid=>h[:xid])[:value].should == 10
501
+ end
502
+
503
+ specify "should correctly return the inserted record's primary key value" do
504
+ value1 = 10
505
+ id1 = @ds.insert(:value=>value1)
506
+ @ds.first(:xid=>id1)[:value].should == value1
507
+ value2 = 20
508
+ id2 = @ds.insert(:value=>value2)
509
+ @ds.first(:xid=>id2)[:value].should == value2
510
+ end
511
+
512
+ specify "should return nil if the table has no primary key" do
513
+ ds = POSTGRES_DB[:test4]
514
+ ds.delete
515
+ ds.insert(:name=>'a').should == nil
516
+ end
517
+ end
518
+
519
+ context "Postgres::Database schema qualified tables" do
520
+ setup do
521
+ POSTGRES_DB << "CREATE SCHEMA schema_test"
522
+ POSTGRES_DB.instance_variable_set(:@primary_keys, {})
523
+ POSTGRES_DB.instance_variable_set(:@primary_key_sequences, {})
524
+ end
525
+ teardown do
526
+ POSTGRES_DB << "DROP SCHEMA schema_test CASCADE"
527
+ POSTGRES_DB.default_schema = :public
528
+ end
529
+
530
+ specify "should be able to create, drop, select and insert into tables in a given schema" do
531
+ POSTGRES_DB.create_table(:schema_test__schema_test){primary_key :i}
532
+ POSTGRES_DB[:schema_test__schema_test].first.should == nil
533
+ POSTGRES_DB[:schema_test__schema_test].insert(:i=>1).should == 1
534
+ POSTGRES_DB[:schema_test__schema_test].first.should == {:i=>1}
535
+ POSTGRES_DB.from('schema_test.schema_test'.lit).first.should == {:i=>1}
536
+ POSTGRES_DB.drop_table(:schema_test__schema_test)
537
+ POSTGRES_DB.create_table(:schema_test.qualify(:schema_test)){integer :i}
538
+ POSTGRES_DB[:schema_test__schema_test].first.should == nil
539
+ POSTGRES_DB.from('schema_test.schema_test'.lit).first.should == nil
540
+ POSTGRES_DB.drop_table(:schema_test.qualify(:schema_test))
541
+ end
542
+
543
+ specify "#tables should include only tables in the public schema if no schema is given" do
544
+ POSTGRES_DB.create_table(:schema_test__schema_test){integer :i}
545
+ POSTGRES_DB.tables.should_not include(:schema_test)
546
+ end
547
+
548
+ specify "#tables should return tables in the schema provided by the :schema argument" do
549
+ POSTGRES_DB.create_table(:schema_test__schema_test){integer :i}
550
+ POSTGRES_DB.tables(:schema=>:schema_test).should == [:schema_test]
551
+ end
552
+
553
+ specify "#table_exists? should assume the public schema if no schema is provided" do
554
+ POSTGRES_DB.create_table(:schema_test__schema_test){integer :i}
555
+ POSTGRES_DB.table_exists?(:schema_test).should == false
556
+ end
557
+
558
+ specify "#table_exists? should see if the table is in a given schema" do
559
+ POSTGRES_DB.create_table(:schema_test__schema_test){integer :i}
560
+ POSTGRES_DB.table_exists?(:schema_test__schema_test).should == true
561
+ end
562
+
563
+ specify "should be able to get primary keys for tables in a given schema" do
564
+ POSTGRES_DB.create_table(:schema_test__schema_test){primary_key :i}
565
+ POSTGRES_DB.primary_key(:schema_test__schema_test).should == 'i'
566
+ end
567
+
568
+ specify "should be able to get serial sequences for tables in a given schema" do
569
+ POSTGRES_DB.create_table(:schema_test__schema_test){primary_key :i}
570
+ POSTGRES_DB.primary_key_sequence(:schema_test__schema_test).should == '"schema_test"."schema_test_i_seq"'
571
+ end
572
+
573
+ specify "should be able to get custom sequences for tables in a given schema" do
574
+ POSTGRES_DB << "CREATE SEQUENCE schema_test.kseq"
575
+ POSTGRES_DB.create_table(:schema_test__schema_test){integer :j; primary_key :k, :type=>:integer, :default=>"nextval('schema_test.kseq'::regclass)".lit}
576
+ POSTGRES_DB.primary_key_sequence(:schema_test__schema_test).should == '"schema_test"."kseq"'
577
+ end
578
+
579
+ specify "#default_schema= should change the default schema used from public" do
580
+ POSTGRES_DB.create_table(:schema_test__schema_test){primary_key :i}
581
+ POSTGRES_DB.default_schema = :schema_test
582
+ POSTGRES_DB.table_exists?(:schema_test).should == true
583
+ POSTGRES_DB.tables.should == [:schema_test]
584
+ POSTGRES_DB.primary_key(:schema_test__schema_test).should == 'i'
585
+ POSTGRES_DB.primary_key_sequence(:schema_test__schema_test).should == '"schema_test"."schema_test_i_seq"'
586
+ end
587
+ end
588
+
589
+ if POSTGRES_DB.server_version >= 80300
590
+
591
+ POSTGRES_DB.create_table! :test6 do
592
+ text :title
593
+ text :body
594
+ full_text_index [:title, :body]
595
+ end
596
+
597
+ context "PostgreSQL tsearch2" do
598
+ before do
599
+ @ds = POSTGRES_DB[:test6]
600
+ end
601
+ after do
602
+ POSTGRES_DB[:test6].delete
603
+ end
604
+
605
+ specify "should search by indexed column" do
606
+ record = {:title => "oopsla conference", :body => "test"}
607
+ @ds << record
608
+ @ds.full_text_search(:title, "oopsla").all.should include(record)
609
+ end
610
+
611
+ specify "should join multiple coumns with spaces to search by last words in row" do
612
+ record = {:title => "multiple words", :body => "are easy to search"}
613
+ @ds << record
614
+ @ds.full_text_search([:title, :body], "words").all.should include(record)
615
+ end
616
+
617
+ specify "should return rows with a NULL in one column if a match in another column" do
618
+ record = {:title => "multiple words", :body =>nil}
619
+ @ds << record
620
+ @ds.full_text_search([:title, :body], "words").all.should include(record)
621
+ end
622
+ end
623
+ end
624
+
625
+ context "Postgres::Database functions, languages, and triggers" do
626
+ setup do
627
+ @d = POSTGRES_DB
628
+ end
629
+ teardown do
630
+ @d.drop_function('tf', :if_exists=>true, :cascade=>true)
631
+ @d.drop_function('tf', :if_exists=>true, :cascade=>true, :args=>%w'integer integer')
632
+ @d.drop_language(:plpgsql, :if_exists=>true, :cascade=>true)
633
+ @d.drop_trigger(:test5, :identity, :if_exists=>true, :cascade=>true)
634
+ end
635
+
636
+ specify "#create_function and #drop_function should create and drop functions" do
637
+ proc{@d['SELECT tf()'].all}.should raise_error(Sequel::DatabaseError)
638
+ args = ['tf', 'SELECT 1', {:returns=>:integer}]
639
+ @d.create_function_sql(*args).should =~ /\A\s*CREATE FUNCTION tf\(\)\s+RETURNS integer\s+LANGUAGE SQL\s+AS 'SELECT 1'\s*\z/
640
+ @d.create_function(*args)
641
+ rows = @d['SELECT tf()'].all.should == [{:tf=>1}]
642
+ @d.drop_function_sql('tf').should == 'DROP FUNCTION tf()'
643
+ @d.drop_function('tf')
644
+ proc{@d['SELECT tf()'].all}.should raise_error(Sequel::DatabaseError)
645
+ end
646
+
647
+ specify "#create_function and #drop_function should support options" do
648
+ 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'}}]
649
+ @d.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/
650
+ @d.create_function(*args)
651
+ # Make sure replace works
652
+ @d.create_function(*args)
653
+ rows = @d['SELECT tf(1, 2)'].all.should == [{:tf=>3}]
654
+ args = ['tf', {:if_exists=>true, :cascade=>true, :args=>[[:integer, :a], :integer]}]
655
+ @d.drop_function_sql(*args).should == 'DROP FUNCTION IF EXISTS tf(a integer, integer) CASCADE'
656
+ @d.drop_function(*args)
657
+ # Make sure if exists works
658
+ @d.drop_function(*args)
659
+ end
660
+
661
+ specify "#create_language and #drop_language should create and drop languages" do
662
+ @d.create_language_sql(:plpgsql).should == 'CREATE LANGUAGE plpgsql'
663
+ @d.create_language(:plpgsql)
664
+ proc{@d.create_language(:plpgsql)}.should raise_error(Sequel::DatabaseError)
665
+ @d.drop_language_sql(:plpgsql).should == 'DROP LANGUAGE plpgsql'
666
+ @d.drop_language(:plpgsql)
667
+ proc{@d.drop_language(:plpgsql)}.should raise_error(Sequel::DatabaseError)
668
+ @d.create_language_sql(:plpgsql, :trusted=>true, :handler=>:a, :validator=>:b).should == 'CREATE TRUSTED LANGUAGE plpgsql HANDLER a VALIDATOR b'
669
+ @d.drop_language_sql(:plpgsql, :if_exists=>true, :cascade=>true).should == 'DROP LANGUAGE IF EXISTS plpgsql CASCADE'
670
+ # Make sure if exists works
671
+ @d.drop_language(:plpgsql, :if_exists=>true, :cascade=>true)
672
+ end
673
+
674
+ specify "#create_trigger and #drop_trigger should create and drop triggers" do
675
+ @d.create_language(:plpgsql)
676
+ @d.create_function(:tf, 'BEGIN IF NEW.value IS NULL THEN RAISE EXCEPTION \'Blah\'; END IF; RETURN NEW; END;', :language=>:plpgsql, :returns=>:trigger)
677
+ @d.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()'
678
+ @d.create_trigger(:test, :identity, :tf, :each_row=>true)
679
+ @d[:test].insert(:name=>'a', :value=>1)
680
+ @d[:test].filter(:name=>'a').all.should == [{:name=>'a', :value=>1}]
681
+ proc{@d[:test].filter(:name=>'a').update(:value=>nil)}.should raise_error(Sequel::DatabaseError)
682
+ @d[:test].filter(:name=>'a').all.should == [{:name=>'a', :value=>1}]
683
+ @d[:test].filter(:name=>'a').update(:value=>3)
684
+ @d[:test].filter(:name=>'a').all.should == [{:name=>'a', :value=>3}]
685
+ @d.drop_trigger_sql(:test, :identity).should == 'DROP TRIGGER identity ON public.test'
686
+ @d.drop_trigger(:test, :identity)
687
+ @d.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\')'
688
+ @d.drop_trigger_sql(:test, :identity, :if_exists=>true, :cascade=>true).should == 'DROP TRIGGER IF EXISTS identity ON public.test CASCADE'
689
+ # Make sure if exists works
690
+ @d.drop_trigger(:test, :identity, :if_exists=>true, :cascade=>true)
691
+ end
692
+ end