PageRenderTime 72ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 1ms

/spec/adapters/postgres_spec.rb

https://github.com/lee-dohm/sequel
Ruby | 2568 lines | 2282 code | 269 blank | 17 comment | 633 complexity | 2041a41019f488fdc497dd93532a1451 MD5 | raw file
  1. require File.join(File.dirname(File.expand_path(__FILE__)), 'spec_helper.rb')
  2. unless defined?(POSTGRES_DB)
  3. POSTGRES_URL = 'postgres://postgres:postgres@localhost:5432/reality_spec' unless defined? POSTGRES_URL
  4. POSTGRES_DB = Sequel.connect(ENV['SEQUEL_PG_SPEC_DB']||POSTGRES_URL)
  5. end
  6. INTEGRATION_DB = POSTGRES_DB unless defined?(INTEGRATION_DB)
  7. # Automatic parameterization changes the SQL used, so don't check
  8. # for expected SQL if it is being used.
  9. if defined?(Sequel::Postgres::AutoParameterize)
  10. check_sqls = false
  11. else
  12. check_sqls = true
  13. end
  14. def POSTGRES_DB.sqls
  15. (@sqls ||= [])
  16. end
  17. logger = Object.new
  18. def logger.method_missing(m, msg)
  19. POSTGRES_DB.sqls << msg
  20. end
  21. POSTGRES_DB.loggers << logger
  22. #POSTGRES_DB.instance_variable_set(:@server_version, 80200)
  23. POSTGRES_DB.create_table! :test do
  24. text :name
  25. integer :value, :index => true
  26. end
  27. POSTGRES_DB.create_table! :test2 do
  28. text :name
  29. integer :value
  30. end
  31. POSTGRES_DB.create_table! :test3 do
  32. integer :value
  33. timestamp :time
  34. end
  35. POSTGRES_DB.create_table! :test4 do
  36. varchar :name, :size => 20
  37. bytea :value
  38. end
  39. describe "A PostgreSQL database" do
  40. before do
  41. @db = POSTGRES_DB
  42. end
  43. specify "should provide the server version" do
  44. @db.server_version.should > 70000
  45. end
  46. specify "should correctly parse the schema" do
  47. @db.schema(:test3, :reload=>true).should == [
  48. [:value, {:oid=>23, :type=>:integer, :allow_null=>true, :default=>nil, :ruby_default=>nil, :db_type=>"integer", :primary_key=>false}],
  49. [:time, {:oid=>1114, :type=>:datetime, :allow_null=>true, :default=>nil, :ruby_default=>nil, :db_type=>"timestamp without time zone", :primary_key=>false}]
  50. ]
  51. @db.schema(:test4, :reload=>true).should == [
  52. [:name, {:oid=>1043, :type=>:string, :allow_null=>true, :default=>nil, :ruby_default=>nil, :db_type=>"character varying(20)", :primary_key=>false}],
  53. [:value, {:oid=>17, :type=>:blob, :allow_null=>true, :default=>nil, :ruby_default=>nil, :db_type=>"bytea", :primary_key=>false}]
  54. ]
  55. end
  56. specify "should parse foreign keys for tables in a schema" do
  57. begin
  58. @db.create_table!(:public__testfk){primary_key :id; foreign_key :i, :public__testfk}
  59. @db.foreign_key_list(:public__testfk).should == [{:on_delete=>:no_action, :on_update=>:no_action, :columns=>[:i], :key=>[:id], :deferrable=>false, :table=>Sequel.qualify(:public, :testfk), :name=>:testfk_i_fkey}]
  60. ensure
  61. @db.drop_table(:public__testfk)
  62. end
  63. end
  64. specify "should return uuid fields as strings" do
  65. @db.get(Sequel.cast('550e8400-e29b-41d4-a716-446655440000', :uuid)).should == '550e8400-e29b-41d4-a716-446655440000'
  66. end
  67. end
  68. describe "A PostgreSQL dataset" do
  69. before do
  70. @d = POSTGRES_DB[:test]
  71. @d.delete
  72. POSTGRES_DB.sqls.clear
  73. end
  74. specify "should quote columns and tables using double quotes if quoting identifiers" do
  75. @d.select(:name).sql.should == 'SELECT "name" FROM "test"'
  76. @d.select(Sequel.lit('COUNT(*)')).sql.should == 'SELECT COUNT(*) FROM "test"'
  77. @d.select(Sequel.function(:max, :value)).sql.should == 'SELECT max("value") FROM "test"'
  78. @d.select(Sequel.function(:NOW)).sql.should == 'SELECT NOW() FROM "test"'
  79. @d.select(Sequel.function(:max, :items__value)).sql.should == 'SELECT max("items"."value") FROM "test"'
  80. @d.order(Sequel.desc(:name)).sql.should == 'SELECT * FROM "test" ORDER BY "name" DESC'
  81. @d.select(Sequel.lit('test.name AS item_name')).sql.should == 'SELECT test.name AS item_name FROM "test"'
  82. @d.select(Sequel.lit('"name"')).sql.should == 'SELECT "name" FROM "test"'
  83. @d.select(Sequel.lit('max(test."name") AS "max_name"')).sql.should == 'SELECT max(test."name") AS "max_name" FROM "test"'
  84. @d.insert_sql(:x => :y).should =~ /\AINSERT INTO "test" \("x"\) VALUES \("y"\)( RETURNING NULL)?\z/
  85. if check_sqls
  86. @d.select(Sequel.function(:test, :abc, 'hello')).sql.should == "SELECT test(\"abc\", 'hello') FROM \"test\""
  87. @d.select(Sequel.function(:test, :abc__def, 'hello')).sql.should == "SELECT test(\"abc\".\"def\", 'hello') FROM \"test\""
  88. @d.select(Sequel.function(:test, :abc__def, 'hello').as(:x2)).sql.should == "SELECT test(\"abc\".\"def\", 'hello') AS \"x2\" FROM \"test\""
  89. @d.insert_sql(:value => 333).should =~ /\AINSERT INTO "test" \("value"\) VALUES \(333\)( RETURNING NULL)?\z/
  90. end
  91. end
  92. specify "should quote fields correctly when reversing the order if quoting identifiers" do
  93. @d.reverse_order(:name).sql.should == 'SELECT * FROM "test" ORDER BY "name" DESC'
  94. @d.reverse_order(Sequel.desc(:name)).sql.should == 'SELECT * FROM "test" ORDER BY "name" ASC'
  95. @d.reverse_order(:name, Sequel.desc(:test)).sql.should == 'SELECT * FROM "test" ORDER BY "name" DESC, "test" ASC'
  96. @d.reverse_order(Sequel.desc(:name), :test).sql.should == 'SELECT * FROM "test" ORDER BY "name" ASC, "test" DESC'
  97. end
  98. specify "should support regexps" do
  99. @d << {:name => 'abc', :value => 1}
  100. @d << {:name => 'bcd', :value => 2}
  101. @d.filter(:name => /bc/).count.should == 2
  102. @d.filter(:name => /^bc/).count.should == 1
  103. end
  104. specify "should support NULLS FIRST and NULLS LAST" do
  105. @d << {:name => 'abc'}
  106. @d << {:name => 'bcd'}
  107. @d << {:name => 'bcd', :value => 2}
  108. @d.order(Sequel.asc(:value, :nulls=>:first), :name).select_map(:name).should == %w[abc bcd bcd]
  109. @d.order(Sequel.asc(:value, :nulls=>:last), :name).select_map(:name).should == %w[bcd abc bcd]
  110. @d.order(Sequel.asc(:value, :nulls=>:first), :name).reverse.select_map(:name).should == %w[bcd bcd abc]
  111. end
  112. specify "#lock should lock tables and yield if a block is given" do
  113. @d.lock('EXCLUSIVE'){@d.insert(:name=>'a')}
  114. end
  115. specify "should support exclusion constraints when creating or altering tables" do
  116. begin
  117. @db = POSTGRES_DB
  118. @db.create_table!(:atest){Integer :t; exclude [[Sequel.desc(:t, :nulls=>:last), '=']], :using=>:btree, :where=>proc{t > 0}}
  119. @db[:atest].insert(1)
  120. @db[:atest].insert(2)
  121. proc{@db[:atest].insert(2)}.should raise_error(Sequel::DatabaseError)
  122. @db.create_table!(:atest){Integer :t}
  123. @db.alter_table(:atest){add_exclusion_constraint [[:t, '=']], :using=>:btree, :name=>'atest_ex'}
  124. @db[:atest].insert(1)
  125. @db[:atest].insert(2)
  126. proc{@db[:atest].insert(2)}.should raise_error(Sequel::DatabaseError)
  127. @db.alter_table(:atest){drop_constraint 'atest_ex'}
  128. ensure
  129. @db.drop_table?(:atest)
  130. end
  131. end if POSTGRES_DB.server_version >= 90000
  132. specify "should support adding foreign key constarints that are not yet valid, and validating them later" do
  133. begin
  134. @db = POSTGRES_DB
  135. @db.create_table!(:atest){primary_key :id; Integer :fk}
  136. @db[:atest].insert(1, 5)
  137. @db.alter_table(:atest){add_foreign_key [:fk], :atest, :not_valid=>true, :name=>:atest_fk}
  138. @db[:atest].insert(2, 1)
  139. proc{@db[:atest].insert(3, 4)}.should raise_error(Sequel::DatabaseError)
  140. proc{@db.alter_table(:atest){validate_constraint :atest_fk}}.should raise_error(Sequel::DatabaseError)
  141. @db[:atest].where(:id=>1).update(:fk=>2)
  142. @db.alter_table(:atest){validate_constraint :atest_fk}
  143. proc{@db.alter_table(:atest){validate_constraint :atest_fk}}.should_not raise_error
  144. ensure
  145. @db.drop_table?(:atest)
  146. end
  147. end if POSTGRES_DB.server_version >= 90200
  148. specify "should support :using when altering a column's type" do
  149. begin
  150. @db = POSTGRES_DB
  151. @db.create_table!(:atest){Integer :t}
  152. @db[:atest].insert(1262304000)
  153. @db.alter_table(:atest){set_column_type :t, Time, :using=>Sequel.cast('epoch', Time) + Sequel.cast('1 second', :interval) * :t}
  154. @db[:atest].get(Sequel.extract(:year, :t)).should == 2010
  155. ensure
  156. @db.drop_table?(:atest)
  157. end
  158. end
  159. specify "should support :using with a string when altering a column's type" do
  160. begin
  161. @db = POSTGRES_DB
  162. @db.create_table!(:atest){Integer :t}
  163. @db[:atest].insert(1262304000)
  164. @db.alter_table(:atest){set_column_type :t, Time, :using=>"'epoch'::timestamp + '1 second'::interval * t"}
  165. @db[:atest].get(Sequel.extract(:year, :t)).should == 2010
  166. ensure
  167. @db.drop_table?(:atest)
  168. end
  169. end
  170. specify "should have #transaction support various types of synchronous options" do
  171. @db = POSTGRES_DB
  172. @db.transaction(:synchronous=>:on){}
  173. @db.transaction(:synchronous=>true){}
  174. @db.transaction(:synchronous=>:off){}
  175. @db.transaction(:synchronous=>false){}
  176. @db.sqls.grep(/synchronous/).should == ["SET LOCAL synchronous_commit = on", "SET LOCAL synchronous_commit = on", "SET LOCAL synchronous_commit = off", "SET LOCAL synchronous_commit = off"]
  177. @db.sqls.clear
  178. @db.transaction(:synchronous=>nil){}
  179. @db.sqls.should == ['BEGIN', 'COMMIT']
  180. if @db.server_version >= 90100
  181. @db.sqls.clear
  182. @db.transaction(:synchronous=>:local){}
  183. @db.sqls.grep(/synchronous/).should == ["SET LOCAL synchronous_commit = local"]
  184. if @db.server_version >= 90200
  185. @db.sqls.clear
  186. @db.transaction(:synchronous=>:remote_write){}
  187. @db.sqls.grep(/synchronous/).should == ["SET LOCAL synchronous_commit = remote_write"]
  188. end
  189. end
  190. end
  191. specify "should have #transaction support read only transactions" do
  192. @db = POSTGRES_DB
  193. @db.transaction(:read_only=>true){}
  194. @db.transaction(:read_only=>false){}
  195. @db.transaction(:isolation=>:serializable, :read_only=>true){}
  196. @db.transaction(:isolation=>:serializable, :read_only=>false){}
  197. @db.sqls.grep(/READ/).should == ["SET TRANSACTION READ ONLY", "SET TRANSACTION READ WRITE", "SET TRANSACTION ISOLATION LEVEL SERIALIZABLE READ ONLY", "SET TRANSACTION ISOLATION LEVEL SERIALIZABLE READ WRITE"]
  198. end
  199. specify "should have #transaction support deferrable transactions" do
  200. @db = POSTGRES_DB
  201. @db.transaction(:deferrable=>true){}
  202. @db.transaction(:deferrable=>false){}
  203. @db.transaction(:deferrable=>true, :read_only=>true){}
  204. @db.transaction(:deferrable=>false, :read_only=>false){}
  205. @db.transaction(:isolation=>:serializable, :deferrable=>true, :read_only=>true){}
  206. @db.transaction(:isolation=>:serializable, :deferrable=>false, :read_only=>false){}
  207. @db.sqls.grep(/DEF/).should == ["SET TRANSACTION DEFERRABLE", "SET TRANSACTION NOT DEFERRABLE", "SET TRANSACTION READ ONLY DEFERRABLE", "SET TRANSACTION READ WRITE NOT DEFERRABLE", "SET TRANSACTION ISOLATION LEVEL SERIALIZABLE READ ONLY DEFERRABLE", "SET TRANSACTION ISOLATION LEVEL SERIALIZABLE READ WRITE NOT DEFERRABLE"]
  208. end if POSTGRES_DB.server_version >= 90100
  209. specify "should support creating indexes concurrently" do
  210. POSTGRES_DB.sqls.clear
  211. POSTGRES_DB.add_index :test, [:name, :value], :concurrently=>true
  212. POSTGRES_DB.sqls.should == ['CREATE INDEX CONCURRENTLY "test_name_value_index" ON "test" ("name", "value")'] if check_sqls
  213. end
  214. specify "should support dropping indexes only if they already exist" do
  215. POSTGRES_DB.add_index :test, [:name, :value], :name=>'tnv1'
  216. POSTGRES_DB.sqls.clear
  217. POSTGRES_DB.drop_index :test, [:name, :value], :if_exists=>true, :name=>'tnv1'
  218. POSTGRES_DB.sqls.should == ['DROP INDEX IF EXISTS "tnv1"']
  219. end
  220. specify "should support CASCADE when dropping indexes" do
  221. POSTGRES_DB.add_index :test, [:name, :value], :name=>'tnv2'
  222. POSTGRES_DB.sqls.clear
  223. POSTGRES_DB.drop_index :test, [:name, :value], :cascade=>true, :name=>'tnv2'
  224. POSTGRES_DB.sqls.should == ['DROP INDEX "tnv2" CASCADE']
  225. end
  226. specify "should support dropping indexes concurrently" do
  227. POSTGRES_DB.add_index :test, [:name, :value], :name=>'tnv2'
  228. POSTGRES_DB.sqls.clear
  229. POSTGRES_DB.drop_index :test, [:name, :value], :concurrently=>true, :name=>'tnv2'
  230. POSTGRES_DB.sqls.should == ['DROP INDEX CONCURRENTLY "tnv2"']
  231. end if POSTGRES_DB.server_version >= 90200
  232. specify "#lock should lock table if inside a transaction" do
  233. POSTGRES_DB.transaction{@d.lock('EXCLUSIVE'); @d.insert(:name=>'a')}
  234. end
  235. specify "#lock should return nil" do
  236. @d.lock('EXCLUSIVE'){@d.insert(:name=>'a')}.should == nil
  237. POSTGRES_DB.transaction{@d.lock('EXCLUSIVE').should == nil; @d.insert(:name=>'a')}
  238. end
  239. specify "should raise an error if attempting to update a joined dataset with a single FROM table" do
  240. 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')
  241. end
  242. specify "should truncate with options" do
  243. @d << { :name => 'abc', :value => 1}
  244. @d.count.should == 1
  245. @d.truncate(:cascade => true)
  246. @d.count.should == 0
  247. if @d.db.server_version > 80400
  248. @d << { :name => 'abc', :value => 1}
  249. @d.truncate(:cascade => true, :only=>true, :restart=>true)
  250. @d.count.should == 0
  251. end
  252. end
  253. specify "should truncate multiple tables at once" do
  254. tables = [:test, :test2, :test3, :test4]
  255. tables.each{|t| @d.from(t).insert}
  256. @d.from(:test, :test2, :test3, :test4).truncate
  257. tables.each{|t| @d.from(t).count.should == 0}
  258. end
  259. end
  260. describe "Dataset#distinct" do
  261. before do
  262. @db = POSTGRES_DB
  263. @db.create_table!(:a) do
  264. Integer :a
  265. Integer :b
  266. end
  267. @ds = @db[:a]
  268. end
  269. after do
  270. @db.drop_table?(:a)
  271. end
  272. it "#distinct with arguments should return results distinct on those arguments" do
  273. @ds.insert(20, 10)
  274. @ds.insert(30, 10)
  275. @ds.order(:b, :a).distinct.map(:a).should == [20, 30]
  276. @ds.order(:b, Sequel.desc(:a)).distinct.map(:a).should == [30, 20]
  277. @ds.order(:b, :a).distinct(:b).map(:a).should == [20]
  278. @ds.order(:b, Sequel.desc(:a)).distinct(:b).map(:a).should == [30]
  279. end
  280. end
  281. if POSTGRES_DB.pool.respond_to?(:max_size) and POSTGRES_DB.pool.max_size > 1
  282. describe "Dataset#for_update support" do
  283. before do
  284. @db = POSTGRES_DB.create_table!(:items) do
  285. primary_key :id
  286. Integer :number
  287. String :name
  288. end
  289. @ds = POSTGRES_DB[:items]
  290. end
  291. after do
  292. POSTGRES_DB.drop_table?(:items)
  293. POSTGRES_DB.disconnect
  294. end
  295. specify "should handle FOR UPDATE" do
  296. @ds.insert(:number=>20)
  297. c, t = nil, nil
  298. q = Queue.new
  299. POSTGRES_DB.transaction do
  300. @ds.for_update.first(:id=>1)
  301. t = Thread.new do
  302. POSTGRES_DB.transaction do
  303. q.push nil
  304. @ds.filter(:id=>1).update(:name=>'Jim')
  305. c = @ds.first(:id=>1)
  306. q.push nil
  307. end
  308. end
  309. q.pop
  310. @ds.filter(:id=>1).update(:number=>30)
  311. end
  312. q.pop
  313. t.join
  314. c.should == {:id=>1, :number=>30, :name=>'Jim'}
  315. end
  316. specify "should handle FOR SHARE" do
  317. @ds.insert(:number=>20)
  318. c, t = nil
  319. q = Queue.new
  320. POSTGRES_DB.transaction do
  321. @ds.for_share.first(:id=>1)
  322. t = Thread.new do
  323. POSTGRES_DB.transaction do
  324. c = @ds.for_share.filter(:id=>1).first
  325. q.push nil
  326. end
  327. end
  328. q.pop
  329. @ds.filter(:id=>1).update(:name=>'Jim')
  330. c.should == {:id=>1, :number=>20, :name=>nil}
  331. end
  332. t.join
  333. end
  334. end
  335. end
  336. describe "A PostgreSQL dataset with a timestamp field" do
  337. before do
  338. @db = POSTGRES_DB
  339. @d = @db[:test3]
  340. @d.delete
  341. end
  342. after do
  343. @db.convert_infinite_timestamps = false if @db.adapter_scheme == :postgres
  344. end
  345. cspecify "should store milliseconds in time fields for Time objects", :do, :swift do
  346. t = Time.now
  347. @d << {:value=>1, :time=>t}
  348. t2 = @d[:value =>1][:time]
  349. @d.literal(t2).should == @d.literal(t)
  350. t2.strftime('%Y-%m-%d %H:%M:%S').should == t.strftime('%Y-%m-%d %H:%M:%S')
  351. (t2.is_a?(Time) ? t2.usec : t2.strftime('%N').to_i/1000).should == t.usec
  352. end
  353. cspecify "should store milliseconds in time fields for DateTime objects", :do, :swift do
  354. t = DateTime.now
  355. @d << {:value=>1, :time=>t}
  356. t2 = @d[:value =>1][:time]
  357. @d.literal(t2).should == @d.literal(t)
  358. t2.strftime('%Y-%m-%d %H:%M:%S').should == t.strftime('%Y-%m-%d %H:%M:%S')
  359. (t2.is_a?(Time) ? t2.usec : t2.strftime('%N').to_i/1000).should == t.strftime('%N').to_i/1000
  360. end
  361. if POSTGRES_DB.adapter_scheme == :postgres
  362. specify "should handle infinite timestamps if convert_infinite_timestamps is set" do
  363. @d << {:time=>Sequel.cast('infinity', :timestamp)}
  364. @db.convert_infinite_timestamps = :nil
  365. @db[:test3].get(:time).should == nil
  366. @db.convert_infinite_timestamps = :string
  367. @db[:test3].get(:time).should == 'infinity'
  368. @db.convert_infinite_timestamps = :float
  369. @db[:test3].get(:time).should == 1.0/0.0
  370. @d.update(:time=>Sequel.cast('-infinity', :timestamp))
  371. @db.convert_infinite_timestamps = :nil
  372. @db[:test3].get(:time).should == nil
  373. @db.convert_infinite_timestamps = :string
  374. @db[:test3].get(:time).should == '-infinity'
  375. @db.convert_infinite_timestamps = :float
  376. @db[:test3].get(:time).should == -1.0/0.0
  377. end
  378. specify "should handle conversions from infinite strings/floats in models" do
  379. c = Class.new(Sequel::Model(:test3))
  380. @db.convert_infinite_timestamps = :float
  381. c.new(:time=>'infinity').time.should == 'infinity'
  382. c.new(:time=>'-infinity').time.should == '-infinity'
  383. c.new(:time=>1.0/0.0).time.should == 1.0/0.0
  384. c.new(:time=>-1.0/0.0).time.should == -1.0/0.0
  385. end
  386. end
  387. end
  388. describe "PostgreSQL's EXPLAIN and ANALYZE" do
  389. specify "should not raise errors" do
  390. @d = POSTGRES_DB[:test3]
  391. proc{@d.explain}.should_not raise_error
  392. proc{@d.analyze}.should_not raise_error
  393. end
  394. end
  395. describe "A PostgreSQL database" do
  396. before do
  397. @db = POSTGRES_DB
  398. end
  399. specify "should support column operations" do
  400. @db.create_table!(:test2){text :name; integer :value}
  401. @db[:test2] << {}
  402. @db[:test2].columns.should == [:name, :value]
  403. @db.add_column :test2, :xyz, :text, :default => '000'
  404. @db[:test2].columns.should == [:name, :value, :xyz]
  405. @db[:test2] << {:name => 'mmm', :value => 111}
  406. @db[:test2].first[:xyz].should == '000'
  407. @db[:test2].columns.should == [:name, :value, :xyz]
  408. @db.drop_column :test2, :xyz
  409. @db[:test2].columns.should == [:name, :value]
  410. @db[:test2].delete
  411. @db.add_column :test2, :xyz, :text, :default => '000'
  412. @db[:test2] << {:name => 'mmm', :value => 111, :xyz => 'qqqq'}
  413. @db[:test2].columns.should == [:name, :value, :xyz]
  414. @db.rename_column :test2, :xyz, :zyx
  415. @db[:test2].columns.should == [:name, :value, :zyx]
  416. @db[:test2].first[:zyx].should == 'qqqq'
  417. @db.add_column :test2, :xyz, :float
  418. @db[:test2].delete
  419. @db[:test2] << {:name => 'mmm', :value => 111, :xyz => 56.78}
  420. @db.set_column_type :test2, :xyz, :integer
  421. @db[:test2].first[:xyz].should == 57
  422. end
  423. specify "#locks should be a dataset returning database locks " do
  424. @db.locks.should be_a_kind_of(Sequel::Dataset)
  425. @db.locks.all.should be_a_kind_of(Array)
  426. end
  427. end
  428. describe "A PostgreSQL database" do
  429. before do
  430. @db = POSTGRES_DB
  431. @db.drop_table?(:posts)
  432. @db.sqls.clear
  433. end
  434. after do
  435. @db.drop_table?(:posts)
  436. end
  437. specify "should support resetting the primary key sequence" do
  438. @db.create_table(:posts){primary_key :a}
  439. @db[:posts].insert(:a=>20).should == 20
  440. @db[:posts].insert.should == 1
  441. @db[:posts].insert.should == 2
  442. @db[:posts].insert(:a=>10).should == 10
  443. @db.reset_primary_key_sequence(:posts).should == 21
  444. @db[:posts].insert.should == 21
  445. @db[:posts].order(:a).map(:a).should == [1, 2, 10, 20, 21]
  446. end
  447. specify "should support specifying Integer/Bignum/Fixnum types in primary keys and have them be auto incrementing" do
  448. @db.create_table(:posts){primary_key :a, :type=>Integer}
  449. @db[:posts].insert.should == 1
  450. @db[:posts].insert.should == 2
  451. @db.create_table!(:posts){primary_key :a, :type=>Fixnum}
  452. @db[:posts].insert.should == 1
  453. @db[:posts].insert.should == 2
  454. @db.create_table!(:posts){primary_key :a, :type=>Bignum}
  455. @db[:posts].insert.should == 1
  456. @db[:posts].insert.should == 2
  457. end
  458. specify "should not raise an error if attempting to resetting the primary key sequence for a table without a primary key" do
  459. @db.create_table(:posts){Integer :a}
  460. @db.reset_primary_key_sequence(:posts).should == nil
  461. end
  462. specify "should support opclass specification" do
  463. @db.create_table(:posts){text :title; text :body; integer :user_id; index(:user_id, :opclass => :int4_ops, :type => :btree)}
  464. @db.sqls.should == [
  465. 'CREATE TABLE "posts" ("title" text, "body" text, "user_id" integer)',
  466. 'CREATE INDEX "posts_user_id_index" ON "posts" USING btree ("user_id" int4_ops)'
  467. ]
  468. end
  469. specify "should support fulltext indexes and searching" do
  470. @db.create_table(:posts){text :title; text :body; full_text_index [:title, :body]; full_text_index :title, :language => 'french'}
  471. @db.sqls.should == [
  472. %{CREATE TABLE "posts" ("title" text, "body" text)},
  473. %{CREATE INDEX "posts_title_body_index" ON "posts" USING gin (to_tsvector('simple'::regconfig, (COALESCE("title", '') || ' ' || COALESCE("body", ''))))},
  474. %{CREATE INDEX "posts_title_index" ON "posts" USING gin (to_tsvector('french'::regconfig, (COALESCE("title", ''))))}
  475. ] if check_sqls
  476. @db[:posts].insert(:title=>'ruby rails', :body=>'yowsa')
  477. @db[:posts].insert(:title=>'sequel', :body=>'ruby')
  478. @db[:posts].insert(:title=>'ruby scooby', :body=>'x')
  479. @db.sqls.clear
  480. @db[:posts].full_text_search(:title, 'rails').all.should == [{:title=>'ruby rails', :body=>'yowsa'}]
  481. @db[:posts].full_text_search([:title, :body], ['yowsa', 'rails']).all.should == [:title=>'ruby rails', :body=>'yowsa']
  482. @db[:posts].full_text_search(:title, 'scooby', :language => 'french').all.should == [{:title=>'ruby scooby', :body=>'x'}]
  483. @db.sqls.should == [
  484. %{SELECT * FROM "posts" WHERE (to_tsvector('simple'::regconfig, (COALESCE("title", ''))) @@ to_tsquery('simple'::regconfig, 'rails'))},
  485. %{SELECT * FROM "posts" WHERE (to_tsvector('simple'::regconfig, (COALESCE("title", '') || ' ' || COALESCE("body", ''))) @@ to_tsquery('simple'::regconfig, 'yowsa | rails'))},
  486. %{SELECT * FROM "posts" WHERE (to_tsvector('french'::regconfig, (COALESCE("title", ''))) @@ to_tsquery('french'::regconfig, 'scooby'))}] if check_sqls
  487. @db[:posts].full_text_search(:title, :$n).call(:select, :n=>'rails').should == [{:title=>'ruby rails', :body=>'yowsa'}]
  488. @db[:posts].full_text_search(:title, :$n).prepare(:select, :fts_select).call(:n=>'rails').should == [{:title=>'ruby rails', :body=>'yowsa'}]
  489. end
  490. specify "should support spatial indexes" do
  491. @db.create_table(:posts){box :geom; spatial_index [:geom]}
  492. @db.sqls.should == [
  493. 'CREATE TABLE "posts" ("geom" box)',
  494. 'CREATE INDEX "posts_geom_index" ON "posts" USING gist ("geom")'
  495. ]
  496. end
  497. specify "should support indexes with index type" do
  498. @db.create_table(:posts){varchar :title, :size => 5; index :title, :type => 'hash'}
  499. @db.sqls.should == [
  500. 'CREATE TABLE "posts" ("title" varchar(5))',
  501. 'CREATE INDEX "posts_title_index" ON "posts" USING hash ("title")'
  502. ]
  503. end
  504. specify "should support unique indexes with index type" do
  505. @db.create_table(:posts){varchar :title, :size => 5; index :title, :type => 'btree', :unique => true}
  506. @db.sqls.should == [
  507. 'CREATE TABLE "posts" ("title" varchar(5))',
  508. 'CREATE UNIQUE INDEX "posts_title_index" ON "posts" USING btree ("title")'
  509. ]
  510. end
  511. specify "should support partial indexes" do
  512. @db.create_table(:posts){varchar :title, :size => 5; index :title, :where => {:title => '5'}}
  513. @db.sqls.should == [
  514. 'CREATE TABLE "posts" ("title" varchar(5))',
  515. 'CREATE INDEX "posts_title_index" ON "posts" ("title") WHERE ("title" = \'5\')'
  516. ]
  517. end
  518. specify "should support identifiers for table names in indicies" do
  519. @db.create_table(Sequel::SQL::Identifier.new(:posts)){varchar :title, :size => 5; index :title, :where => {:title => '5'}}
  520. @db.sqls.should == [
  521. 'CREATE TABLE "posts" ("title" varchar(5))',
  522. 'CREATE INDEX "posts_title_index" ON "posts" ("title") WHERE ("title" = \'5\')'
  523. ]
  524. end
  525. specify "should support renaming tables" do
  526. @db.create_table!(:posts1){primary_key :a}
  527. @db.rename_table(:posts1, :posts)
  528. end
  529. end
  530. describe "Postgres::Dataset#import" do
  531. before do
  532. @db = POSTGRES_DB
  533. @db.create_table!(:test){primary_key :x; Integer :y}
  534. @db.sqls.clear
  535. @ds = @db[:test]
  536. end
  537. after do
  538. @db.drop_table?(:test)
  539. end
  540. specify "#import should a single insert statement" do
  541. @ds.import([:x, :y], [[1, 2], [3, 4]])
  542. @db.sqls.should == ['BEGIN', 'INSERT INTO "test" ("x", "y") VALUES (1, 2), (3, 4)', 'COMMIT']
  543. @ds.all.should == [{:x=>1, :y=>2}, {:x=>3, :y=>4}]
  544. end
  545. specify "#import should work correctly when returning primary keys" do
  546. @ds.import([:x, :y], [[1, 2], [3, 4]], :return=>:primary_key).should == [1, 3]
  547. @ds.all.should == [{:x=>1, :y=>2}, {:x=>3, :y=>4}]
  548. end
  549. specify "#import should work correctly when returning primary keys with :slice option" do
  550. @ds.import([:x, :y], [[1, 2], [3, 4]], :return=>:primary_key, :slice=>1).should == [1, 3]
  551. @ds.all.should == [{:x=>1, :y=>2}, {:x=>3, :y=>4}]
  552. end
  553. specify "#import should work correctly with an arbitrary returning value" do
  554. @ds.returning(:y, :x).import([:x, :y], [[1, 2], [3, 4]]).should == [{:y=>2, :x=>1}, {:y=>4, :x=>3}]
  555. @ds.all.should == [{:x=>1, :y=>2}, {:x=>3, :y=>4}]
  556. end
  557. end
  558. describe "Postgres::Dataset#insert" do
  559. before do
  560. @db = POSTGRES_DB
  561. @db.create_table!(:test5){primary_key :xid; Integer :value}
  562. @db.sqls.clear
  563. @ds = @db[:test5]
  564. end
  565. after do
  566. @db.drop_table?(:test5)
  567. end
  568. specify "should work with static SQL" do
  569. @ds.with_sql('INSERT INTO test5 (value) VALUES (10)').insert.should == nil
  570. @db['INSERT INTO test5 (value) VALUES (20)'].insert.should == nil
  571. @ds.all.should == [{:xid=>1, :value=>10}, {:xid=>2, :value=>20}]
  572. end
  573. specify "should insert correctly if using a column array and a value array" do
  574. @ds.insert([:value], [10]).should == 1
  575. @ds.all.should == [{:xid=>1, :value=>10}]
  576. end
  577. specify "should use INSERT RETURNING" do
  578. @ds.insert(:value=>10).should == 1
  579. @db.sqls.last.should == 'INSERT INTO "test5" ("value") VALUES (10) RETURNING "xid"' if check_sqls
  580. end
  581. specify "should have insert_select insert the record and return the inserted record" do
  582. h = @ds.insert_select(:value=>10)
  583. h[:value].should == 10
  584. @ds.first(:xid=>h[:xid])[:value].should == 10
  585. end
  586. specify "should correctly return the inserted record's primary key value" do
  587. value1 = 10
  588. id1 = @ds.insert(:value=>value1)
  589. @ds.first(:xid=>id1)[:value].should == value1
  590. value2 = 20
  591. id2 = @ds.insert(:value=>value2)
  592. @ds.first(:xid=>id2)[:value].should == value2
  593. end
  594. specify "should return nil if the table has no primary key" do
  595. ds = POSTGRES_DB[:test4]
  596. ds.delete
  597. ds.insert(:name=>'a').should == nil
  598. end
  599. end
  600. describe "Postgres::Database schema qualified tables" do
  601. before do
  602. POSTGRES_DB << "CREATE SCHEMA schema_test"
  603. POSTGRES_DB.instance_variable_set(:@primary_keys, {})
  604. POSTGRES_DB.instance_variable_set(:@primary_key_sequences, {})
  605. end
  606. after do
  607. POSTGRES_DB << "DROP SCHEMA schema_test CASCADE"
  608. POSTGRES_DB.default_schema = nil
  609. end
  610. specify "should be able to create, drop, select and insert into tables in a given schema" do
  611. POSTGRES_DB.create_table(:schema_test__schema_test){primary_key :i}
  612. POSTGRES_DB[:schema_test__schema_test].first.should == nil
  613. POSTGRES_DB[:schema_test__schema_test].insert(:i=>1).should == 1
  614. POSTGRES_DB[:schema_test__schema_test].first.should == {:i=>1}
  615. POSTGRES_DB.from(Sequel.lit('schema_test.schema_test')).first.should == {:i=>1}
  616. POSTGRES_DB.drop_table(:schema_test__schema_test)
  617. POSTGRES_DB.create_table(Sequel.qualify(:schema_test, :schema_test)){integer :i}
  618. POSTGRES_DB[:schema_test__schema_test].first.should == nil
  619. POSTGRES_DB.from(Sequel.lit('schema_test.schema_test')).first.should == nil
  620. POSTGRES_DB.drop_table(Sequel.qualify(:schema_test, :schema_test))
  621. end
  622. specify "#tables should not include tables in a default non-public schema" do
  623. POSTGRES_DB.create_table(:schema_test__schema_test){integer :i}
  624. POSTGRES_DB.tables.should include(:schema_test)
  625. POSTGRES_DB.tables.should_not include(:pg_am)
  626. POSTGRES_DB.tables.should_not include(:domain_udt_usage)
  627. end
  628. specify "#tables should return tables in the schema provided by the :schema argument" do
  629. POSTGRES_DB.create_table(:schema_test__schema_test){integer :i}
  630. POSTGRES_DB.tables(:schema=>:schema_test).should == [:schema_test]
  631. end
  632. specify "#schema should not include columns from tables in a default non-public schema" do
  633. POSTGRES_DB.create_table(:schema_test__domains){integer :i}
  634. sch = POSTGRES_DB.schema(:domains)
  635. cs = sch.map{|x| x.first}
  636. cs.should include(:i)
  637. cs.should_not include(:data_type)
  638. end
  639. specify "#schema should only include columns from the table in the given :schema argument" do
  640. POSTGRES_DB.create_table!(:domains){integer :d}
  641. POSTGRES_DB.create_table(:schema_test__domains){integer :i}
  642. sch = POSTGRES_DB.schema(:domains, :schema=>:schema_test)
  643. cs = sch.map{|x| x.first}
  644. cs.should include(:i)
  645. cs.should_not include(:d)
  646. POSTGRES_DB.drop_table(:domains)
  647. end
  648. specify "#schema should raise an exception if columns from tables in two separate schema are returned" do
  649. POSTGRES_DB.create_table!(:public__domains){integer :d}
  650. POSTGRES_DB.create_table(:schema_test__domains){integer :i}
  651. begin
  652. proc{POSTGRES_DB.schema(:domains)}.should raise_error(Sequel::Error)
  653. POSTGRES_DB.schema(:public__domains).map{|x| x.first}.should == [:d]
  654. POSTGRES_DB.schema(:schema_test__domains).map{|x| x.first}.should == [:i]
  655. ensure
  656. POSTGRES_DB.drop_table?(:public__domains)
  657. end
  658. end
  659. specify "#table_exists? should see if the table is in a given schema" do
  660. POSTGRES_DB.create_table(:schema_test__schema_test){integer :i}
  661. POSTGRES_DB.table_exists?(:schema_test__schema_test).should == true
  662. end
  663. specify "should be able to get primary keys for tables in a given schema" do
  664. POSTGRES_DB.create_table(:schema_test__schema_test){primary_key :i}
  665. POSTGRES_DB.primary_key(:schema_test__schema_test).should == 'i'
  666. end
  667. specify "should be able to get serial sequences for tables in a given schema" do
  668. POSTGRES_DB.create_table(:schema_test__schema_test){primary_key :i}
  669. POSTGRES_DB.primary_key_sequence(:schema_test__schema_test).should == '"schema_test".schema_test_i_seq'
  670. end
  671. specify "should be able to get serial sequences for tables that have spaces in the name in a given schema" do
  672. POSTGRES_DB.create_table(:"schema_test__schema test"){primary_key :i}
  673. POSTGRES_DB.primary_key_sequence(:"schema_test__schema test").should == '"schema_test"."schema test_i_seq"'
  674. end
  675. specify "should be able to get custom sequences for tables in a given schema" do
  676. POSTGRES_DB << "CREATE SEQUENCE schema_test.kseq"
  677. POSTGRES_DB.create_table(:schema_test__schema_test){integer :j; primary_key :k, :type=>:integer, :default=>Sequel.lit("nextval('schema_test.kseq'::regclass)")}
  678. POSTGRES_DB.primary_key_sequence(:schema_test__schema_test).should == '"schema_test".kseq'
  679. end
  680. specify "should be able to get custom sequences for tables that have spaces in the name in a given schema" do
  681. POSTGRES_DB << "CREATE SEQUENCE schema_test.\"ks eq\""
  682. POSTGRES_DB.create_table(:"schema_test__schema test"){integer :j; primary_key :k, :type=>:integer, :default=>Sequel.lit("nextval('schema_test.\"ks eq\"'::regclass)")}
  683. POSTGRES_DB.primary_key_sequence(:"schema_test__schema test").should == '"schema_test"."ks eq"'
  684. end
  685. specify "#default_schema= should change the default schema used from public" do
  686. POSTGRES_DB.create_table(:schema_test__schema_test){primary_key :i}
  687. POSTGRES_DB.default_schema = :schema_test
  688. POSTGRES_DB.table_exists?(:schema_test).should == true
  689. POSTGRES_DB.tables.should == [:schema_test]
  690. POSTGRES_DB.primary_key(:schema_test__schema_test).should == 'i'
  691. POSTGRES_DB.primary_key_sequence(:schema_test__schema_test).should == '"schema_test".schema_test_i_seq'
  692. end
  693. end
  694. describe "Postgres::Database schema qualified tables and eager graphing" do
  695. before(:all) do
  696. @db = POSTGRES_DB
  697. @db.run "DROP SCHEMA s CASCADE" rescue nil
  698. @db.run "CREATE SCHEMA s"
  699. @db.create_table(:s__bands){primary_key :id; String :name}
  700. @db.create_table(:s__albums){primary_key :id; String :name; foreign_key :band_id, :s__bands}
  701. @db.create_table(:s__tracks){primary_key :id; String :name; foreign_key :album_id, :s__albums}
  702. @db.create_table(:s__members){primary_key :id; String :name; foreign_key :band_id, :s__bands}
  703. @Band = Class.new(Sequel::Model(:s__bands))
  704. @Album = Class.new(Sequel::Model(:s__albums))
  705. @Track = Class.new(Sequel::Model(:s__tracks))
  706. @Member = Class.new(Sequel::Model(:s__members))
  707. def @Band.name; :Band; end
  708. def @Album.name; :Album; end
  709. def @Track.name; :Track; end
  710. def @Member.name; :Member; end
  711. @Band.one_to_many :albums, :class=>@Album, :order=>:name
  712. @Band.one_to_many :members, :class=>@Member, :order=>:name
  713. @Album.many_to_one :band, :class=>@Band, :order=>:name
  714. @Album.one_to_many :tracks, :class=>@Track, :order=>:name
  715. @Track.many_to_one :album, :class=>@Album, :order=>:name
  716. @Member.many_to_one :band, :class=>@Band, :order=>:name
  717. @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
  718. @Band.many_to_many :tracks, :class=>@Track, :join_table=>:s__albums, :right_key=>:id, :right_primary_key=>:album_id, :order=>:name
  719. @b1 = @Band.create(:name=>"BM")
  720. @b2 = @Band.create(:name=>"J")
  721. @a1 = @Album.create(:name=>"BM1", :band=>@b1)
  722. @a2 = @Album.create(:name=>"BM2", :band=>@b1)
  723. @a3 = @Album.create(:name=>"GH", :band=>@b2)
  724. @a4 = @Album.create(:name=>"GHL", :band=>@b2)
  725. @t1 = @Track.create(:name=>"BM1-1", :album=>@a1)
  726. @t2 = @Track.create(:name=>"BM1-2", :album=>@a1)
  727. @t3 = @Track.create(:name=>"BM2-1", :album=>@a2)
  728. @t4 = @Track.create(:name=>"BM2-2", :album=>@a2)
  729. @m1 = @Member.create(:name=>"NU", :band=>@b1)
  730. @m2 = @Member.create(:name=>"TS", :band=>@b1)
  731. @m3 = @Member.create(:name=>"NS", :band=>@b2)
  732. @m4 = @Member.create(:name=>"JC", :band=>@b2)
  733. end
  734. after(:all) do
  735. @db.run "DROP SCHEMA s CASCADE"
  736. end
  737. specify "should return all eager graphs correctly" do
  738. bands = @Band.order(:bands__name).eager_graph(:albums).all
  739. bands.should == [@b1, @b2]
  740. bands.map{|x| x.albums}.should == [[@a1, @a2], [@a3, @a4]]
  741. bands = @Band.order(:bands__name).eager_graph(:albums=>:tracks).all
  742. bands.should == [@b1, @b2]
  743. bands.map{|x| x.albums}.should == [[@a1, @a2], [@a3, @a4]]
  744. bands.map{|x| x.albums.map{|y| y.tracks}}.should == [[[@t1, @t2], [@t3, @t4]], [[], []]]
  745. bands = @Band.order(:bands__name).eager_graph({:albums=>:tracks}, :members).all
  746. bands.should == [@b1, @b2]
  747. bands.map{|x| x.albums}.should == [[@a1, @a2], [@a3, @a4]]
  748. bands.map{|x| x.albums.map{|y| y.tracks}}.should == [[[@t1, @t2], [@t3, @t4]], [[], []]]
  749. bands.map{|x| x.members}.should == [[@m1, @m2], [@m4, @m3]]
  750. end
  751. specify "should have eager graphs work with previous joins" do
  752. bands = @Band.order(:bands__name).select_all(:s__bands).join(:s__members, :band_id=>:id).from_self(:alias=>:bands0).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.albums.map{|y| y.tracks}}.should == [[[@t1, @t2], [@t3, @t4]], [[], []]]
  756. end
  757. specify "should have eager graphs work with joins with the same tables" do
  758. bands = @Band.order(:bands__name).select_all(:s__bands).join(:s__members, :band_id=>:id).eager_graph({:albums=>:tracks}, :members).all
  759. bands.should == [@b1, @b2]
  760. bands.map{|x| x.albums}.should == [[@a1, @a2], [@a3, @a4]]
  761. bands.map{|x| x.albums.map{|y| y.tracks}}.should == [[[@t1, @t2], [@t3, @t4]], [[], []]]
  762. bands.map{|x| x.members}.should == [[@m1, @m2], [@m4, @m3]]
  763. end
  764. specify "should have eager graphs work with self referential associations" do
  765. bands = @Band.order(:bands__name).eager_graph(:tracks=>{:album=>:band}).all
  766. bands.should == [@b1, @b2]
  767. bands.map{|x| x.tracks}.should == [[@t1, @t2, @t3, @t4], []]
  768. bands.map{|x| x.tracks.map{|y| y.album}}.should == [[@a1, @a1, @a2, @a2], []]
  769. bands.map{|x| x.tracks.map{|y| y.album.band}}.should == [[@b1, @b1, @b1, @b1], []]
  770. members = @Member.order(:members__name).eager_graph(:members).all
  771. members.should == [@m4, @m3, @m1, @m2]
  772. members.map{|x| x.members}.should == [[@m4, @m3], [@m4, @m3], [@m1, @m2], [@m1, @m2]]
  773. members = @Member.order(:members__name).eager_graph(:band, :members=>:band).all
  774. members.should == [@m4, @m3, @m1, @m2]
  775. members.map{|x| x.band}.should == [@b2, @b2, @b1, @b1]
  776. members.map{|x| x.members}.should == [[@m4, @m3], [@m4, @m3], [@m1, @m2], [@m1, @m2]]
  777. members.map{|x| x.members.map{|y| y.band}}.should == [[@b2, @b2], [@b2, @b2], [@b1, @b1], [@b1, @b1]]
  778. end
  779. specify "should have eager graphs work with a from_self dataset" do
  780. bands = @Band.order(:bands__name).from_self.eager_graph(:tracks=>{:album=>:band}).all
  781. bands.should == [@b1, @b2]
  782. bands.map{|x| x.tracks}.should == [[@t1, @t2, @t3, @t4], []]
  783. bands.map{|x| x.tracks.map{|y| y.album}}.should == [[@a1, @a1, @a2, @a2], []]
  784. bands.map{|x| x.tracks.map{|y| y.album.band}}.should == [[@b1, @b1, @b1, @b1], []]
  785. end
  786. specify "should have eager graphs work with different types of aliased from tables" do
  787. bands = @Band.order(:tracks__name).from(:s__bands___tracks).eager_graph(:tracks).all
  788. bands.should == [@b1, @b2]
  789. bands.map{|x| x.tracks}.should == [[@t1, @t2, @t3, @t4], []]
  790. bands = @Band.order(:tracks__name).from(Sequel.expr(:s__bands).as(:tracks)).eager_graph(:tracks).all
  791. bands.should == [@b1, @b2]
  792. bands.map{|x| x.tracks}.should == [[@t1, @t2, @t3, @t4], []]
  793. bands = @Band.order(:tracks__name).from(Sequel.expr(:s__bands).as(Sequel.identifier(:tracks))).eager_graph(:tracks).all
  794. bands.should == [@b1, @b2]
  795. bands.map{|x| x.tracks}.should == [[@t1, @t2, @t3, @t4], []]
  796. bands = @Band.order(:tracks__name).from(Sequel.expr(:s__bands).as('tracks')).eager_graph(:tracks).all
  797. bands.should == [@b1, @b2]
  798. bands.map{|x| x.tracks}.should == [[@t1, @t2, @t3, @t4], []]
  799. end
  800. specify "should have eager graphs work with join tables with aliases" do
  801. bands = @Band.order(:bands__name).eager_graph(:members).join(:s__albums___tracks, :band_id=>Sequel.qualify(:s__bands, :id)).eager_graph(:albums=>:tracks).all
  802. bands.should == [@b1, @b2]
  803. bands.map{|x| x.albums}.should == [[@a1, @a2], [@a3, @a4]]
  804. bands.map{|x| x.members}.should == [[@m1, @m2], [@m4, @m3]]
  805. bands = @Band.order(:bands__name).eager_graph(:members).join(Sequel.as(:s__albums, :tracks), :band_id=>Sequel.qualify(:s__bands, :id)).eager_graph(:albums=>:tracks).all
  806. bands.should == [@b1, @b2]
  807. bands.map{|x| x.albums}.should == [[@a1, @a2], [@a3, @a4]]
  808. bands.map{|x| x.members}.should == [[@m1, @m2], [@m4, @m3]]
  809. bands = @Band.order(:bands__name).eager_graph(:members).join(Sequel.as(:s__albums, 'tracks'), :band_id=>Sequel.qualify(:s__bands, :id)).eager_graph(:albums=>:tracks).all
  810. bands.should == [@b1, @b2]
  811. bands.map{|x| x.albums}.should == [[@a1, @a2], [@a3, @a4]]
  812. bands.map{|x| x.members}.should == [[@m1, @m2], [@m4, @m3]]
  813. bands = @Band.order(:bands__name).eager_graph(:members).join(Sequel.as(:s__albums, Sequel.identifier(:tracks)), :band_id=>Sequel.qualify(:s__bands, :id)).eager_graph(:albums=>:tracks).all
  814. bands.should == [@b1, @b2]
  815. bands.map{|x| x.albums}.should == [[@a1, @a2], [@a3, @a4]]
  816. bands.map{|x| x.members}.should == [[@m1, @m2], [@m4, @m3]]
  817. bands = @Band.order(:bands__name).eager_graph(:members).join(:s__albums, {:band_id=>Sequel.qualify(:s__bands, :id)}, :table_alias=>:tracks).eager_graph(:albums=>:tracks).all
  818. bands.should == [@b1, @b2]
  819. bands.map{|x| x.albums}.should == [[@a1, @a2], [@a3, @a4]]
  820. bands.map{|x| x.members}.should == [[@m1, @m2], [@m4, @m3]]
  821. bands = @Band.order(:bands__name).eager_graph(:members).join(:s__albums, {:band_id=>Sequel.qualify(:s__bands, :id)}, :table_alias=>'tracks').eager_graph(:albums=>:tracks).all
  822. bands.should == [@b1, @b2]
  823. bands.map{|x| x.albums}.should == [[@a1, @a2], [@a3, @a4]]
  824. bands.map{|x| x.members}.should == [[@m1, @m2], [@m4, @m3]]
  825. bands = @Band.order(:bands__name).eager_graph(:members).join(:s__albums, {:band_id=>Sequel.qualify(:s__bands, :id)}, :table_alias=>Sequel.identifier(:tracks)).eager_graph(:albums=>:tracks).all
  826. bands.should == [@b1, @b2]
  827. bands.map{|x| x.albums}.should == [[@a1, @a2], [@a3, @a4]]
  828. bands.map{|x| x.members}.should == [[@m1, @m2], [@m4, @m3]]
  829. end
  830. specify "should have eager graphs work with different types of qualified from tables" do
  831. bands = @Band.order(:bands__name).from(Sequel.qualify(:s, :bands)).eager_graph(:tracks).all
  832. bands.should == [@b1, @b2]
  833. bands.map{|x| x.tracks}.should == [[@t1, @t2, @t3, @t4], []]
  834. bands = @Band.order(:bands__name).from(Sequel.identifier(:bands).qualify(:s)).eager_graph(:tracks).all
  835. bands.should == [@b1, @b2]
  836. bands.map{|x| x.tracks}.should == [[@t1, @t2, @t3, @t4], []]
  837. bands = @Band.order(:bands__name).from(Sequel::SQL::QualifiedIdentifier.new(:s, 'bands')).eager_graph(:tracks).all
  838. bands.should == [@b1, @b2]
  839. bands.map{|x| x.tracks}.should == [[@t1, @t2, @t3, @t4], []]
  840. end
  841. end
  842. if POSTGRES_DB.server_version >= 80300
  843. POSTGRES_DB.create_table! :test6 do
  844. text :title
  845. text :body
  846. full_text_index [:title, :body]
  847. end
  848. describe "PostgreSQL tsearch2" do
  849. before do
  850. @ds = POSTGRES_DB[:test6]
  851. end
  852. after do
  853. POSTGRES_DB[:test6].delete
  854. end
  855. specify "should search by indexed column" do
  856. record = {:title => "oopsla conference", :body => "test"}
  857. @ds << record
  858. @ds.full_text_search(:title, "oopsla").all.should include(record)
  859. end
  860. specify "should join multiple coumns with spaces to search by last words in row" do
  861. record = {:title => "multiple words", :body => "are easy to search"}
  862. @ds << record
  863. @ds.full_text_search([:title, :body], "words").all.should include(record)
  864. end
  865. specify "should return rows with a NULL in one column if a match in another column" do
  866. record = {:title => "multiple words", :body =>nil}
  867. @ds << record
  868. @ds.full_text_search([:title, :body], "words").all.should include(record)
  869. end
  870. end
  871. end
  872. if POSTGRES_DB.dataset.supports_window_functions?
  873. describe "Postgres::Dataset named windows" do
  874. before do
  875. @db = POSTGRES_DB
  876. @db.create_table!(:i1){Integer :id; Integer :group_id; Integer :amount}
  877. @ds = @db[:i1].order(:id)
  878. @ds.insert(:id=>1, :group_id=>1, :amount=>1)
  879. @ds.insert(:id=>2, :group_id=>1, :amount=>10)
  880. @ds.insert(:id=>3, :group_id=>1, :amount=>100)
  881. @ds.insert(:id=>4, :group_id=>2, :amount=>1000)
  882. @ds.insert(:id=>5, :group_id=>2, :amount=>10000)
  883. @ds.insert(:id=>6, :group_id=>2, :amount=>100000)
  884. end
  885. after do
  886. @db.drop_table?(:i1)
  887. end
  888. specify "should give correct results for window functions" do
  889. @ds.window(:win, :partition=>:group_id, :order=>:id).select(:id){sum(:over, :args=>amount, :window=>win){}}.all.should ==
  890. [{:sum=>1, :id=>1}, {:sum=>11, :id=>2}, {:sum=>111, :id=>3}, {:sum=>1000, :id=>4}, {:sum=>11000, :id=>5}, {:sum=>111000, :id=>6}]
  891. @ds.window(:win, :partition=>:group_id).select(:id){sum(:over, :args=>amount, :window=>win, :order=>id){}}.all.should ==
  892. [{:sum=>1, :id=>1}, {:sum=>11, :id=>2}, {:sum=>111, :id=>3}, {:sum=>1000, :id=>4}, {:sum=>11000, :id=>5}, {:sum=>111000, :id=>6}]
  893. @ds.window(:win, {}).select(:id){sum(:over, :args=>amount, :window=>:win, :order=>id){}}.all.should ==
  894. [{:sum=>1, :id=>1}, {:sum=>11, :id=>2}, {:sum=>111, :id=>3}, {:sum=>1111, :id=>4}, {:sum=>11111, :id=>5}, {:sum=>111111, :id=>6}]
  895. @ds.window(:win, :partition=>:group_id).select(:id){sum(:over, :args=>amount, :window=>:win, :order=>id, :frame=>:all){}}.all.should ==
  896. [{:sum=>111, :id=>1}, {:sum=>111, :id=>2}, {:sum=>111, :id=>3}, {:sum=>111000, :id=>4}, {:sum=>111000, :id=>5}, {:sum=>111000, :id=>6}]
  897. end
  898. end
  899. end
  900. describe "Postgres::Database functions, languages, schemas, and triggers" do
  901. before do
  902. @d = POSTGRES_DB
  903. end
  904. after do
  905. @d.drop_function('tf', :if_exists=>true, :cascade=>true)
  906. @d.drop_function('tf', :if_exists=>true, :cascade=>true, :args=>%w'integer integer')
  907. @d.drop_language(:plpgsql, :if_exists=>true, :cascade=>true) if @d.server_version < 90000
  908. @d.drop_schema(:sequel, :if_exists=>true, :cascade=>true)
  909. @d.drop_table?(:test)
  910. end
  911. specify "#create_function and #drop_function should create and drop functions" do
  912. proc{@d['SELECT tf()'].all}.should raise_error(Sequel::DatabaseError)
  913. args = ['tf', 'SELECT 1', {:returns=>:integer}]
  914. @d.send(:create_function_sql, *args).should =~ /\A\s*CREATE FUNCTION tf\(\)\s+RETURNS integer\s+LANGUAGE SQL\s+AS 'SELECT 1'\s*\z/
  915. @d.create_function(*args)
  916. rows = @d['SELECT tf()'].all.should == [{:tf=>1}]
  917. @d.send(:drop_function_sql, 'tf').should == 'DROP FUNCTION tf()'
  918. @d.drop_function('tf')
  919. proc{@d['SELECT tf()'].all}.should raise_error(Sequel::DatabaseError)
  920. end
  921. specify "#create_function and #drop_function should support options" do
  922. 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'}}]
  923. @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/
  924. @d.create_function(*args)
  925. # Make sure replace works
  926. @d.create_function(*args)
  927. rows = @d['SELECT tf(1, 2)'].all.should == [{:tf=>3}]
  928. args = ['tf', {:if_exists=>true, :cascade=>true, :args=>[[:integer, :a], :integer]}]
  929. @d.send(:drop_function_sql,*args).should == 'DROP FUNCTION IF EXISTS tf(a integer, integer) CASCADE'
  930. @d.drop_function(*args)
  931. # Make sure if exists works
  932. @d.drop_function(*args)
  933. end
  934. specify "#create_language and #drop_language should create and drop languages" do
  935. @d.send(:create_language_sql, :plpgsql).should == 'CREATE LANGUAGE plpgsql'
  936. @d.create_language(:plpgsql, :replace=>true) if @d.server_version < 90000
  937. proc{@d.create_language(:plpgsql)}.should raise_error(Sequel::DatabaseError)
  938. @d.send(:drop_language_sql, :plpgsql).should == 'DROP LANGUAGE plpgsql'
  939. @d.drop_language(:plpgsql) if @d.server_version < 90000
  940. proc{@d.drop_language(:plpgsql)}.should raise_error(Sequel::DatabaseError) if @d.server_version < 90000
  941. @d.send(:create_language_sql, :plpgsql, :replace=>true, :trusted=>true, :handler=>:a, :validator=>:b).should == (@d.server_version >= 90000 ? 'CREATE OR REPLACE TRUSTED LANGUAGE plpgsql HANDLER a VALIDATOR b' : 'CREATE TRUSTED LANGUAGE plpgsql HANDLER a VALIDATOR b')
  942. @d.send(:drop_language_sql, :plpgsql, :if_exists=>true, :cascade=>true).should == 'DROP LANGUAGE IF EXISTS plpgsql CASCADE'
  943. # Make sure if exists works
  944. @d.drop_language(:plpgsql, :if_exists=>true, :cascade=>true) if @d.server_version < 90000
  945. end
  946. specify "#create_schema and #drop_schema should create and drop schemas" do
  947. @d.send(:create_schema_sql, :sequel).should == 'CREATE SCHEMA "sequel"'
  948. @d.send(:drop_schema_sql, :sequel).should == 'DROP SCHEMA "sequel"'
  949. @d.send(:drop_schema_sql, :sequel, :if_exists=>true, :cascade=>true).should == 'DROP SCHEMA IF EXISTS "sequel" CASCADE'
  950. @d.create_schema(:sequel)
  951. @d.create_table(:sequel__test){Integer :a}
  952. @d.drop_schema(:sequel, :if_exists=>true, :cascade=>true)
  953. end
  954. specify "#create_trigger and #drop_trigger should create and drop triggers" do
  955. @d.create_language(:plpgsql) if @d.server_version < 90000
  956. @d.create_function(:tf, 'BEGIN IF NEW.value IS NULL THEN RAISE EXCEPTION \'Blah\'; END IF; RETURN NEW; END;', :language=>:plpgsql, :returns=>:trigger)
  957. @d.send(:create_trigger_sql, :test, :identity, :tf, :each_row=>true).should == 'CREATE TRIGGER identity BEFORE INSERT OR UPDATE OR DELETE ON "test" FOR EACH ROW EXECUTE PROCEDURE tf()'
  958. @d.create_table(:test){String :name; Integer :value}
  959. @d.create_trigger(:test, :identity, :tf, :each_row=>true)
  960. @d[:test].insert(:name=>'a', :value=>1)
  961. @d[:test].filter(:name=>'a').all.should == [{:name=>'a', :value=>1}]
  962. proc{@d[:test].filter(:name=>'a').update(:value=>nil)}.should raise_error(Sequel::DatabaseError)
  963. @d[:test].filter(:name=>'a').all.should == [{:name=>'a', :value=>1}]
  964. @d[:test].filter(:name=>'a').update(:value=>3)
  965. @d[:test].filter(:name=>'a').all.should == [{:name=>'a', :value=>3}]
  966. @d.send(:drop_trigger_sql, :test, :identity).should == 'DROP TRIGGER identity ON "test"'
  967. @d.drop_trigger(:test, :identity)
  968. @d.send(:create_trigger_sql, :test, :identity, :tf, :after=>true, :events=>:insert, :args=>[1, 'a']).should == 'CREATE TRIGGER identity AFTER INSERT ON "test" EXECUTE PROCEDURE tf(1, \'a\')'
  969. @d.send(:drop_trigger_sql, :test, :identity, :if_exists=>true, :cascade=>true).should == 'DROP TRIGGER IF EXISTS identity ON "test" CASCADE'
  970. # Make sure if exists works
  971. @d.drop_trigger(:test, :identity, :if_exists=>true, :cascade=>true)
  972. end
  973. end
  974. if POSTGRES_DB.adapter_scheme == :postgres
  975. describe "Postgres::Dataset #use_cursor" do
  976. before(:all) do
  977. @db = POSTGRES_DB
  978. @db.create_table!(:test_cursor){Integer :x}
  979. @db.sqls.clear
  980. @ds = @db[:test_cursor]
  981. @db.transaction{1001.times{|i| @ds.insert(i)}}
  982. end
  983. after(:all) do
  984. @db.drop_table?(:test_cursor)
  985. end
  986. specify "should return the same results as the non-cursor use" do
  987. @ds.all.should == @ds.use_cursor.all
  988. end
  989. specify "should respect the :rows_per_fetch option" do
  990. @db.sqls.clear
  991. @ds.use_cursor.all
  992. @db.sqls.length.should == 6
  993. @db.sqls.clear
  994. @ds.use_cursor(:rows_per_fetch=>100).all
  995. @db.sqls.length.should == 15
  996. end
  997. specify "should handle returning inside block" do
  998. def @ds.check_return
  999. use_cursor.each{|r| return}
  1000. end
  1001. @ds.check_return
  1002. @ds.all.should == @ds.use_cursor.all
  1003. end
  1004. end
  1005. describe "Postgres::PG_NAMED_TYPES" do
  1006. before do
  1007. @db = POSTGRES_DB
  1008. Sequel::Postgres::PG_NAMED_TYPES[:interval] = lambda{|v| v.reverse}
  1009. @db.reset_conversion_procs
  1010. end
  1011. after do
  1012. Sequel::Postgres::PG_NAMED_TYPES.delete(:interval)
  1013. @db.reset_conversion_procs
  1014. @db.drop_table?(:foo)
  1015. end
  1016. specify "should look up conversion procs by name" do
  1017. @db.create_table!(:foo){interval :bar}
  1018. @db[:foo].insert(Sequel.cast('21 days', :interval))
  1019. @db[:foo].get(:bar).should == 'syad 12'
  1020. end
  1021. end
  1022. end
  1023. if POSTGRES_DB.adapter_scheme == :postgres && SEQUEL_POSTGRES_USES_PG && POSTGRES_DB.server_version >= 90000
  1024. describe "Postgres::Database#copy_table" do
  1025. before(:all) do
  1026. @db = POSTGRES_DB
  1027. @db.create_table!(:test_copy){Integer :x; Integer :y}
  1028. ds = @db[:test_copy]
  1029. ds.insert(1, 2)
  1030. ds.insert(3, 4)
  1031. end
  1032. after(:all) do
  1033. @db.drop_table?(:test_copy)
  1034. end
  1035. specify "without a block or options should return a text version of the table as a single string" do
  1036. @db.copy_table(:test_copy).should == "1\t2\n3\t4\n"
  1037. end
  1038. specify "without a block and with :format=>:csv should return a csv version of the table as a single string" do
  1039. @db.copy_table(:test_copy, :format=>:csv).should == "1,2\n3,4\n"
  1040. end
  1041. specify "should treat string as SQL code" do
  1042. @db.copy_table('COPY "test_copy" TO STDOUT').should == "1\t2\n3\t4\n"
  1043. end
  1044. specify "should respect given :options options" do
  1045. @db.copy_table(:test_copy, :options=>"FORMAT csv, HEADER TRUE").should == "x,y\n1,2\n3,4\n"
  1046. end
  1047. specify "should respect given :options options when :format is used" do
  1048. @db.copy_table(:test_copy, :format=>:csv, :options=>"QUOTE '''', FORCE_QUOTE *").should == "'1','2'\n'3','4'\n"
  1049. end
  1050. specify "should accept dataset as first argument" do
  1051. @db.copy_table(@db[:test_copy].cross_join(:test_copy___tc).order(:test_copy__x, :test_copy__y, :tc__x, :tc__y)).should == "1\t2\t1\t2\n1\t2\t3\t4\n3\t4\t1\t2\n3\t4\t3\t4\n"
  1052. end
  1053. specify "with a block and no options should yield each row as a string in text format" do
  1054. buf = []
  1055. @db.copy_table(:test_copy){|b| buf << b}
  1056. buf.should == ["1\t2\n", "3\t4\n"]
  1057. end
  1058. specify "with a block and :format=>:csv should yield each row as a string in csv format" do
  1059. buf = []
  1060. @db.copy_table(:test_copy, :format=>:csv){|b| buf << b}
  1061. buf.should == ["1,2\n", "3,4\n"]
  1062. end
  1063. specify "should work fine when using a block that is terminated early with a following copy_table" do
  1064. buf = []
  1065. proc{@db.copy_table(:test_copy, :format=>:csv){|b| buf << b; break}}.should raise_error(Sequel::DatabaseDisconnectError)
  1066. buf.should == ["1,2\n"]
  1067. buf.clear
  1068. proc{@db.copy_table(:test_copy, :format=>:csv){|b| buf << b; raise ArgumentError}}.should raise_error(Sequel::DatabaseDisconnectError)
  1069. buf.should == ["1,2\n"]
  1070. buf.clear
  1071. @db.copy_table(:test_copy){|b| buf << b}
  1072. buf.should == ["1\t2\n", "3\t4\n"]
  1073. end
  1074. specify "should work fine when using a block that is terminated early with a following regular query" do
  1075. buf = []
  1076. proc{@db.copy_table(:test_copy, :format=>:csv){|b| buf << b; break}}.should raise_error(Sequel::DatabaseDisconnectError)
  1077. buf.should == ["1,2\n"]
  1078. buf.clear
  1079. proc{@db.copy_table(:test_copy, :format=>:csv){|b| buf << b; raise ArgumentError}}.should raise_error(Sequel::DatabaseDisconnectError)
  1080. buf.should == ["1,2\n"]
  1081. @db[:test_copy].select_order_map(:x).should == [1, 3]
  1082. end
  1083. end
  1084. describe "Postgres::Database LISTEN/NOTIFY" do
  1085. before(:all) do
  1086. @db = POSTGRES_DB
  1087. end
  1088. specify "should support listen and notify" do
  1089. notify_pid = @db.synchronize{|conn| conn.backend_pid}
  1090. called = false
  1091. @db.listen('foo', :after_listen=>proc{@db.notify('foo')}) do |ev, pid, payload|
  1092. ev.should == 'foo'
  1093. pid.should == notify_pid
  1094. ['', nil].should include(payload)
  1095. called = true
  1096. end.should == 'foo'
  1097. called.should be_true
  1098. called = false
  1099. @db.listen('foo', :after_listen=>proc{@db.notify('foo', :payload=>'bar')}) do |ev, pid, payload|
  1100. ev.should == 'foo'
  1101. pid.should == notify_pid
  1102. payload.should == 'bar'
  1103. called = true
  1104. end.should == 'foo'
  1105. called.should be_true
  1106. @db.listen('foo', :after_listen=>proc{@db.notify('foo')}).should == 'foo'
  1107. called = false
  1108. called2 = false
  1109. i = 0
  1110. @db.listen(['foo', 'bar'], :after_listen=>proc{@db.notify('foo', :payload=>'bar'); @db.notify('bar', :payload=>'foo')}, :loop=>proc{i+=1}) do |ev, pid, payload|
  1111. if !called
  1112. ev.should == 'foo'
  1113. pid.should == notify_pid
  1114. payload.should == 'bar'
  1115. called = true
  1116. else
  1117. ev.should == 'bar'
  1118. pid.should == notify_pid
  1119. payload.should == 'foo'
  1120. called2 = true
  1121. break
  1122. end
  1123. end.should be_nil
  1124. called.should be_true
  1125. called2.should be_true
  1126. i.should == 1
  1127. end
  1128. specify "should accept a :timeout option in listen" do
  1129. @db.listen('foo2', :timeout=>0.001).should == nil
  1130. called = false
  1131. @db.listen('foo2', :timeout=>0.001){|ev, pid, payload| called = true}.should == nil
  1132. called.should be_false
  1133. i = 0
  1134. @db.listen('foo2', :timeout=>0.001, :loop=>proc{i+=1; throw :stop if i > 3}){|ev, pid, payload| called = true}.should == nil
  1135. i.should == 4
  1136. end unless RUBY_PLATFORM =~ /mingw/ # Ruby freezes on this spec on this platform/version
  1137. end
  1138. end
  1139. describe 'PostgreSQL special float handling' do
  1140. before do
  1141. @db = POSTGRES_DB
  1142. @db.create_table!(:test5){Float :value}
  1143. @db.sqls.clear
  1144. @ds = @db[:test5]
  1145. end
  1146. after do
  1147. @db.drop_table?(:test5)
  1148. end
  1149. if check_sqls
  1150. specify 'should quote NaN' do
  1151. nan = 0.0/0.0
  1152. @ds.insert_sql(:value => nan).should == %q{INSERT INTO "test5" ("value") VALUES ('NaN')}
  1153. end
  1154. specify 'should quote +Infinity' do
  1155. inf = 1.0/0.0
  1156. @ds.insert_sql(:value => inf).should == %q{INSERT INTO "test5" ("value") VALUES ('Infinity')}
  1157. end
  1158. specify 'should quote -Infinity' do
  1159. inf = -1.0/0.0
  1160. @ds.insert_sql(:value => inf).should == %q{INSERT INTO "test5" ("value") VALUES ('-Infinity')}
  1161. end
  1162. end
  1163. if POSTGRES_DB.adapter_scheme == :postgres
  1164. specify 'inserts NaN' do
  1165. nan = 0.0/0.0
  1166. @ds.insert(:value=>nan)
  1167. @ds.all[0][:value].nan?.should be_true
  1168. end
  1169. specify 'inserts +Infinity' do
  1170. inf = 1.0/0.0
  1171. @ds.insert(:value=>inf)
  1172. @ds.all[0][:value].infinite?.should > 0
  1173. end
  1174. specify 'inserts -Infinity' do
  1175. inf = -1.0/0.0
  1176. @ds.insert(:value=>inf)
  1177. @ds.all[0][:value].infinite?.should < 0
  1178. end
  1179. end
  1180. end
  1181. describe 'PostgreSQL array handling' do
  1182. before(:all) do
  1183. @db = POSTGRES_DB
  1184. @db.extension :pg_array
  1185. @ds = @db[:items]
  1186. @native = POSTGRES_DB.adapter_scheme == :postgres
  1187. @jdbc = POSTGRES_DB.adapter_scheme == :jdbc
  1188. @tp = lambda{@db.schema(:items).map{|a| a.last[:type]}}
  1189. end
  1190. after do
  1191. @db.drop_table?(:items)
  1192. end
  1193. specify 'insert and retrieve integer and float arrays of various sizes' do
  1194. @db.create_table!(:items) do
  1195. column :i2, 'int2[]'
  1196. column :i4, 'int4[]'
  1197. column :i8, 'int8[]'
  1198. column :r, 'real[]'
  1199. column :dp, 'double precision[]'
  1200. end
  1201. @tp.call.should == [:integer_array, :integer_array, :bigint_array, :float_array, :float_array]
  1202. @ds.insert(Sequel.pg_array([1], :int2), Sequel.pg_array([nil, 2], :int4), Sequel.pg_array([3, nil], :int8), Sequel.pg_array([4, nil, 4.5], :real), Sequel.pg_array([5, nil, 5.5], "double precision"))
  1203. @ds.count.should == 1
  1204. rs = @ds.all
  1205. if @jdbc || @native
  1206. rs.should == [{:i2=>[1], :i4=>[nil, 2], :i8=>[3, nil], :r=>[4.0, nil, 4.5], :dp=>[5.0, nil, 5.5]}]
  1207. end
  1208. if @native
  1209. rs.first.values.each{|v| v.should_not be_a_kind_of(Array)}
  1210. rs.first.values.each{|v| v.to_a.should be_a_kind_of(Array)}
  1211. @ds.delete
  1212. @ds.insert(rs.first)
  1213. @ds.all.should == rs
  1214. end
  1215. @ds.delete
  1216. @ds.insert(Sequel.pg_array([[1], [2]], :int2), Sequel.pg_array([[nil, 2], [3, 4]], :int4), Sequel.pg_array([[3, nil], [nil, nil]], :int8), Sequel.pg_array([[4, nil], [nil, 4.5]], :real), Sequel.pg_array([[5, nil], [nil, 5.5]], "double precision"))
  1217. rs = @ds.all
  1218. if @jdbc || @native
  1219. rs.should == [{:i2=>[[1], [2]], :i4=>[[nil, 2], [3, 4]], :i8=>[[3, nil], [nil, nil]], :r=>[[4, nil], [nil, 4.5]], :dp=>[[5, nil], [nil, 5.5]]}]
  1220. end
  1221. if @native
  1222. rs.first.values.each{|v| v.should_not be_a_kind_of(Array)}
  1223. rs.first.values.each{|v| v.to_a.should be_a_kind_of(Array)}
  1224. @ds.delete
  1225. @ds.insert(rs.first)
  1226. @ds.all.should == rs
  1227. end
  1228. end
  1229. specify 'insert and retrieve decimal arrays' do
  1230. @db.create_table!(:items) do
  1231. column :n, 'numeric[]'
  1232. end
  1233. @tp.call.should == [:decimal_array]
  1234. @ds.insert(Sequel.pg_array([BigDecimal.new('1.000000000000000000001'), nil, BigDecimal.new('1')], :numeric))
  1235. @ds.count.should == 1
  1236. rs = @ds.all
  1237. if @jdbc || @native
  1238. rs.should == [{:n=>[BigDecimal.new('1.000000000000000000001'), nil, BigDecimal.new('1')]}]
  1239. end
  1240. if @native
  1241. rs.first.values.each{|v| v.should_not be_a_kind_of(Array)}
  1242. rs.first.values.each{|v| v.to_a.should be_a_kind_of(Array)}
  1243. @ds.delete
  1244. @ds.insert(rs.first)
  1245. @ds.all.should == rs
  1246. end
  1247. @ds.delete
  1248. @ds.insert(Sequel.pg_array([[BigDecimal.new('1.0000000000000000000000000000001'), nil], [nil, BigDecimal.new('1')]], :numeric))
  1249. rs = @ds.all
  1250. if @jdbc || @native
  1251. rs.should == [{:n=>[[BigDecimal.new('1.0000000000000000000000000000001'), nil], [nil, BigDecimal.new('1')]]}]
  1252. end
  1253. if @native
  1254. rs.first.values.each{|v| v.should_not be_a_kind_of(Array)}
  1255. rs.first.values.each{|v| v.to_a.should be_a_kind_of(Array)}
  1256. @ds.delete
  1257. @ds.insert(rs.first)
  1258. @ds.all.should == rs
  1259. end
  1260. end
  1261. specify 'insert and retrieve string arrays' do
  1262. @db.create_table!(:items) do
  1263. column :c, 'char(4)[]'
  1264. column :vc, 'varchar[]'
  1265. column :t, 'text[]'
  1266. end
  1267. @tp.call.should == [:string_array, :string_array, :string_array]
  1268. @ds.insert(Sequel.pg_array(['a', nil, 'NULL', 'b"\'c'], 'char(4)'), Sequel.pg_array(['a', nil, 'NULL', 'b"\'c'], :varchar), Sequel.pg_array(['a', nil, 'NULL', 'b"\'c'], :text))
  1269. @ds.count.should == 1
  1270. rs = @ds.all
  1271. if @jdbc || @native
  1272. rs.should == [{:c=>['a ', nil, 'NULL', 'b"\'c'], :vc=>['a', nil, 'NULL', 'b"\'c'], :t=>['a', nil, 'NULL', 'b"\'c']}]
  1273. end
  1274. if @native
  1275. rs.first.values.each{|v| v.should_not be_a_kind_of(Array)}
  1276. rs.first.values.each{|v| v.to_a.should be_a_kind_of(Array)}
  1277. @ds.delete
  1278. @ds.insert(rs.first)
  1279. @ds.all.should == rs
  1280. end
  1281. @ds.delete
  1282. @ds.insert(Sequel.pg_array([[['a'], [nil]], [['NULL'], ['b"\'c']]], 'char(4)'), Sequel.pg_array([[['a'], ['']], [['NULL'], ['b"\'c']]], :varchar), Sequel.pg_array([[['a'], [nil]], [['NULL'], ['b"\'c']]], :text))
  1283. rs = @ds.all
  1284. if @jdbc || @native
  1285. rs.should == [{:c=>[[['a '], [nil]], [['NULL'], ['b"\'c']]], :vc=>[[['a'], ['']], [['NULL'], ['b"\'c']]], :t=>[[['a'], [nil]], [['NULL'], ['b"\'c']]]}]
  1286. end
  1287. if @native
  1288. rs.first.values.each{|v| v.should_not be_a_kind_of(Array)}
  1289. rs.first.values.each{|v| v.to_a.should be_a_kind_of(Array)}
  1290. @ds.delete
  1291. @ds.insert(rs.first)
  1292. @ds.all.should == rs
  1293. end
  1294. end
  1295. specify 'insert and retrieve arrays of other types' do
  1296. @db.create_table!(:items) do
  1297. column :b, 'bool[]'
  1298. column :d, 'date[]'
  1299. column :t, 'time[]'
  1300. column :ts, 'timestamp[]'
  1301. column :tstz, 'timestamptz[]'
  1302. end
  1303. @tp.call.should == [:boolean_array, :date_array, :time_array, :datetime_array, :datetime_timezone_array]
  1304. d = Date.today
  1305. t = Sequel::SQLTime.create(10, 20, 30)
  1306. ts = Time.local(2011, 1, 2, 3, 4, 5)
  1307. @ds.insert(Sequel.pg_array([true, false], :bool), Sequel.pg_array([d, nil], :date), Sequel.pg_array([t, nil], :time), Sequel.pg_array([ts, nil], :timestamp), Sequel.pg_array([ts, nil], :timestamptz))
  1308. @ds.count.should == 1
  1309. rs = @ds.all
  1310. if @jdbc || @native
  1311. rs.should == [{:b=>[true, false], :d=>[d, nil], :t=>[t, nil], :ts=>[ts, nil], :tstz=>[ts, nil]}]
  1312. end
  1313. if @native
  1314. rs.first.values.each{|v| v.should_not be_a_kind_of(Array)}
  1315. rs.first.values.each{|v| v.to_a.should be_a_kind_of(Array)}
  1316. @ds.delete
  1317. @ds.insert(rs.first)
  1318. @ds.all.should == rs
  1319. end
  1320. @db.create_table!(:items) do
  1321. column :ba, 'bytea[]'
  1322. column :tz, 'timetz[]'
  1323. column :o, 'oid[]'
  1324. end
  1325. @tp.call.should == [:blob_array, :time_timezone_array, :integer_array]
  1326. @ds.insert(Sequel.pg_array([Sequel.blob("a\0"), nil], :bytea), Sequel.pg_array([t, nil], :timetz), Sequel.pg_array([1, 2, 3], :oid))
  1327. @ds.count.should == 1
  1328. if @native
  1329. rs = @ds.all
  1330. rs.should == [{:ba=>[Sequel.blob("a\0"), nil], :tz=>[t, nil], :o=>[1, 2, 3]}]
  1331. rs.first.values.each{|v| v.should_not be_a_kind_of(Array)}
  1332. rs.first.values.each{|v| v.to_a.should be_a_kind_of(Array)}
  1333. @ds.delete
  1334. @ds.insert(rs.first)
  1335. @ds.all.should == rs
  1336. end
  1337. end
  1338. specify 'use arrays in bound variables' do
  1339. @db.create_table!(:items) do
  1340. column :i, 'int4[]'
  1341. end
  1342. @ds.call(:insert, {:i=>[1,2]}, {:i=>:$i})
  1343. @ds.get(:i).should == [1, 2]
  1344. @ds.filter(:i=>:$i).call(:first, :i=>[1,2]).should == {:i=>[1,2]}
  1345. @ds.filter(:i=>:$i).call(:first, :i=>[1,3]).should == nil
  1346. @db.create_table!(:items) do
  1347. column :i, 'text[]'
  1348. end
  1349. a = ["\"\\\\\"{}\n\t\r \v\b123afP", 'NULL', nil, '']
  1350. @ds.call(:insert, {:i=>:$i}, :i=>Sequel.pg_array(a))
  1351. @ds.get(:i).should == a
  1352. @ds.filter(:i=>:$i).call(:first, :i=>a).should == {:i=>a}
  1353. @ds.filter(:i=>:$i).call(:first, :i=>['', nil, nil, 'a']).should == nil
  1354. @db.create_table!(:items) do
  1355. column :i, 'date[]'
  1356. end
  1357. a = [Date.today]
  1358. @ds.call(:insert, {:i=>:$i}, :i=>Sequel.pg_array(a, 'date'))
  1359. @ds.get(:i).should == a
  1360. @ds.filter(:i=>:$i).call(:first, :i=>a).should == {:i=>a}
  1361. @ds.filter(:i=>:$i).call(:first, :i=>Sequel.pg_array([Date.today-1], 'date')).should == nil
  1362. @db.create_table!(:items) do
  1363. column :i, 'timestamp[]'
  1364. end
  1365. a = [Time.local(2011, 1, 2, 3, 4, 5)]
  1366. @ds.call(:insert, {:i=>:$i}, :i=>Sequel.pg_array(a, 'timestamp'))
  1367. @ds.get(:i).should == a
  1368. @ds.filter(:i=>:$i).call(:first, :i=>a).should == {:i=>a}
  1369. @ds.filter(:i=>:$i).call(:first, :i=>Sequel.pg_array([a.first-1], 'timestamp')).should == nil
  1370. @db.create_table!(:items) do
  1371. column :i, 'boolean[]'
  1372. end
  1373. a = [true, false]
  1374. @ds.call(:insert, {:i=>:$i}, :i=>Sequel.pg_array(a, 'boolean'))
  1375. @ds.get(:i).should == a
  1376. @ds.filter(:i=>:$i).call(:first, :i=>a).should == {:i=>a}
  1377. @ds.filter(:i=>:$i).call(:first, :i=>Sequel.pg_array([false, true], 'boolean')).should == nil
  1378. @db.create_table!(:items) do
  1379. column :i, 'bytea[]'
  1380. end
  1381. a = [Sequel.blob("a\0'\"")]
  1382. @ds.call(:insert, {:i=>:$i}, :i=>Sequel.pg_array(a, 'bytea'))
  1383. @ds.get(:i).should == a
  1384. @ds.filter(:i=>:$i).call(:first, :i=>a).should == {:i=>a}
  1385. @ds.filter(:i=>:$i).call(:first, :i=>Sequel.pg_array([Sequel.blob("b\0")], 'bytea')).should == nil
  1386. end if POSTGRES_DB.adapter_scheme == :postgres && SEQUEL_POSTGRES_USES_PG
  1387. specify 'with models' do
  1388. @db.create_table!(:items) do
  1389. primary_key :id
  1390. column :i, 'integer[]'
  1391. column :f, 'double precision[]'
  1392. column :d, 'numeric[]'
  1393. column :t, 'text[]'
  1394. end
  1395. c = Class.new(Sequel::Model(@db[:items]))
  1396. c.plugin :pg_typecast_on_load, :i, :f, :d, :t unless @native
  1397. o = c.create(:i=>[1,2, nil], :f=>[[1, 2.5], [3, 4.5]], :d=>[1, BigDecimal.new('1.000000000000000000001')], :t=>[%w'a b c', ['NULL', nil, '1']])
  1398. o.i.should == [1, 2, nil]
  1399. o.f.should == [[1, 2.5], [3, 4.5]]
  1400. o.d.should == [BigDecimal.new('1'), BigDecimal.new('1.000000000000000000001')]
  1401. o.t.should == [%w'a b c', ['NULL', nil, '1']]
  1402. end
  1403. specify 'operations/functions with pg_array_ops' do
  1404. Sequel.extension :pg_array_ops
  1405. @db.create_table!(:items){column :i, 'integer[]'; column :i2, 'integer[]'; column :i3, 'integer[]'; column :i4, 'integer[]'; column :i5, 'integer[]'}
  1406. @ds.insert(Sequel.pg_array([1, 2, 3]), Sequel.pg_array([2, 1]), Sequel.pg_array([4, 4]), Sequel.pg_array([[5, 5], [4, 3]]), Sequel.pg_array([1, nil, 5]))
  1407. @ds.get(Sequel.pg_array(:i) > :i3).should be_false
  1408. @ds.get(Sequel.pg_array(:i3) > :i).should be_true
  1409. @ds.get(Sequel.pg_array(:i) >= :i3).should be_false
  1410. @ds.get(Sequel.pg_array(:i) >= :i).should be_true
  1411. @ds.get(Sequel.pg_array(:i3) < :i).should be_false
  1412. @ds.get(Sequel.pg_array(:i) < :i3).should be_true
  1413. @ds.get(Sequel.pg_array(:i3) <= :i).should be_false
  1414. @ds.get(Sequel.pg_array(:i) <= :i).should be_true
  1415. @ds.get(Sequel.expr(5=>Sequel.pg_array(:i).any)).should be_false
  1416. @ds.get(Sequel.expr(1=>Sequel.pg_array(:i).any)).should be_true
  1417. @ds.get(Sequel.expr(1=>Sequel.pg_array(:i3).all)).should be_false
  1418. @ds.get(Sequel.expr(4=>Sequel.pg_array(:i3).all)).should be_true
  1419. @ds.get(Sequel.pg_array(:i2)[1]).should == 2
  1420. @ds.get(Sequel.pg_array(:i2)[2]).should == 1
  1421. @ds.get(Sequel.pg_array(:i4)[2][1]).should == 4
  1422. @ds.get(Sequel.pg_array(:i4)[2][2]).should == 3
  1423. @ds.get(Sequel.pg_array(:i).contains(:i2)).should be_true
  1424. @ds.get(Sequel.pg_array(:i).contains(:i3)).should be_false
  1425. @ds.get(Sequel.pg_array(:i2).contained_by(:i)).should be_true
  1426. @ds.get(Sequel.pg_array(:i).contained_by(:i2)).should be_false
  1427. @ds.get(Sequel.pg_array(:i).overlaps(:i2)).should be_true
  1428. @ds.get(Sequel.pg_array(:i2).overlaps(:i3)).should be_false
  1429. @ds.get(Sequel.pg_array(:i).dims).should == '[1:3]'
  1430. @ds.get(Sequel.pg_array(:i).length).should == 3
  1431. @ds.get(Sequel.pg_array(:i).lower).should == 1
  1432. if @db.server_version >= 90000
  1433. @ds.get(Sequel.pg_array(:i5).join).should == '15'
  1434. @ds.get(Sequel.pg_array(:i5).join(':')).should == '1:5'
  1435. @ds.get(Sequel.pg_array(:i5).join(':', '*')).should == '1:*:5'
  1436. end
  1437. @ds.select(Sequel.pg_array(:i).unnest).from_self.count.should == 3 if @db.server_version >= 80400
  1438. if @native
  1439. @ds.get(Sequel.pg_array(:i).push(4)).should == [1, 2, 3, 4]
  1440. @ds.get(Sequel.pg_array(:i).unshift(4)).should == [4, 1, 2, 3]
  1441. @ds.get(Sequel.pg_array(:i).concat(:i2)).should == [1, 2, 3, 2, 1]
  1442. end
  1443. end
  1444. end
  1445. describe 'PostgreSQL hstore handling' do
  1446. before(:all) do
  1447. @db = POSTGRES_DB
  1448. @db.extension :pg_hstore
  1449. @ds = @db[:items]
  1450. @h = {'a'=>'b', 'c'=>nil, 'd'=>'NULL', 'e'=>'\\\\" \\\' ,=>'}
  1451. @native = POSTGRES_DB.adapter_scheme == :postgres
  1452. end
  1453. after do
  1454. @db.drop_table?(:items)
  1455. end
  1456. specify 'insert and retrieve hstore values' do
  1457. @db.create_table!(:items) do
  1458. column :h, :hstore
  1459. end
  1460. @ds.insert(Sequel.hstore(@h))
  1461. @ds.count.should == 1
  1462. if @native
  1463. rs = @ds.all
  1464. v = rs.first[:h]
  1465. v.should_not be_a_kind_of(Hash)
  1466. v.to_hash.should be_a_kind_of(Hash)
  1467. v.to_hash.should == @h
  1468. @ds.delete
  1469. @ds.insert(rs.first)
  1470. @ds.all.should == rs
  1471. end
  1472. end
  1473. specify 'use hstore in bound variables' do
  1474. @db.create_table!(:items) do
  1475. column :i, :hstore
  1476. end
  1477. @ds.call(:insert, {:i=>Sequel.hstore(@h)}, {:i=>:$i})
  1478. @ds.get(:i).should == @h
  1479. @ds.filter(:i=>:$i).call(:first, :i=>Sequel.hstore(@h)).should == {:i=>@h}
  1480. @ds.filter(:i=>:$i).call(:first, :i=>Sequel.hstore({})).should == nil
  1481. @ds.delete
  1482. @ds.call(:insert, {:i=>@h}, {:i=>:$i})
  1483. @ds.get(:i).should == @h
  1484. @ds.filter(:i=>:$i).call(:first, :i=>@h).should == {:i=>@h}
  1485. @ds.filter(:i=>:$i).call(:first, :i=>{}).should == nil
  1486. end if POSTGRES_DB.adapter_scheme == :postgres && SEQUEL_POSTGRES_USES_PG
  1487. specify 'with models and associations' do
  1488. @db.create_table!(:items) do
  1489. primary_key :id
  1490. column :h, :hstore
  1491. end
  1492. c = Class.new(Sequel::Model(@db[:items])) do
  1493. def self.name
  1494. 'Item'
  1495. end
  1496. unrestrict_primary_key
  1497. def item_id
  1498. h['item_id'].to_i if h
  1499. end
  1500. def left_item_id
  1501. h['left_item_id'].to_i if h
  1502. end
  1503. end
  1504. Sequel.extension :pg_hstore_ops
  1505. c.plugin :many_through_many
  1506. c.plugin :pg_typecast_on_load, :h unless @native
  1507. h = {'item_id'=>"2", 'left_item_id'=>"1"}
  1508. o2 = c.create(:id=>2)
  1509. o = c.create(:id=>1, :h=>h)
  1510. o.h.should == h
  1511. c.many_to_one :item, :class=>c, :key_column=>Sequel.cast(Sequel.hstore(:h)['item_id'], Integer)
  1512. c.one_to_many :items, :class=>c, :key=>Sequel.cast(Sequel.hstore(:h)['item_id'], Integer), :key_method=>:item_id
  1513. c.many_to_many :related_items, :class=>c, :join_table=>:items___i, :left_key=>Sequel.cast(Sequel.hstore(:h)['left_item_id'], Integer), :right_key=>Sequel.cast(Sequel.hstore(:h)['item_id'], Integer)
  1514. c.many_to_one :other_item, :class=>c, :key=>:id, :primary_key_method=>:item_id, :primary_key=>Sequel.cast(Sequel.hstore(:h)['item_id'], Integer)
  1515. c.one_to_many :other_items, :class=>c, :primary_key=>:item_id, :key=>:id, :primary_key_column=>Sequel.cast(Sequel.hstore(:h)['item_id'], Integer)
  1516. c.many_to_many :other_related_items, :class=>c, :join_table=>:items___i, :left_key=>:id, :right_key=>:id,
  1517. :left_primary_key_column=>Sequel.cast(Sequel.hstore(:h)['left_item_id'], Integer),
  1518. :left_primary_key=>:left_item_id,
  1519. :right_primary_key=>Sequel.cast(Sequel.hstore(:h)['left_item_id'], Integer),
  1520. :right_primary_key_method=>:left_item_id
  1521. c.many_through_many :mtm_items, [
  1522. [:items, Sequel.cast(Sequel.hstore(:h)['item_id'], Integer), Sequel.cast(Sequel.hstore(:h)['left_item_id'], Integer)],
  1523. [:items, Sequel.cast(Sequel.hstore(:h)['left_item_id'], Integer), Sequel.cast(Sequel.hstore(:h)['left_item_id'], Integer)]
  1524. ],
  1525. :class=>c,
  1526. :left_primary_key_column=>Sequel.cast(Sequel.hstore(:h)['item_id'], Integer),
  1527. :left_primary_key=>:item_id,
  1528. :right_primary_key=>Sequel.cast(Sequel.hstore(:h)['left_item_id'], Integer),
  1529. :right_primary_key_method=>:left_item_id
  1530. # Lazily Loading
  1531. o.item.should == o2
  1532. o2.items.should == [o]
  1533. o.related_items.should == [o2]
  1534. o2.other_item.should == o
  1535. o.other_items.should == [o2]
  1536. o.other_related_items.should == [o]
  1537. o.mtm_items.should == [o]
  1538. # Eager Loading via eager
  1539. os = c.eager(:item, :related_items, :other_items, :other_related_items, :mtm_items).where(:id=>1).all.first
  1540. os.item.should == o2
  1541. os.related_items.should == [o2]
  1542. os.other_items.should == [o2]
  1543. os.other_related_items.should == [o]
  1544. os.mtm_items.should == [o]
  1545. os = c.eager(:items, :other_item).where(:id=>2).all.first
  1546. os.items.should == [o]
  1547. os.other_item.should == o
  1548. # Eager Loading via eager_graph
  1549. c.eager_graph(:item).where(:items__id=>1).all.first.item.should == o2
  1550. c.eager_graph(:items).where(:items__id=>2).all.first.items.should == [o]
  1551. c.eager_graph(:related_items).where(:items__id=>1).all.first.related_items.should == [o2]
  1552. c.eager_graph(:other_item).where(:items__id=>2).all.first.other_item.should == o
  1553. c.eager_graph(:other_items).where(:items__id=>1).all.first.other_items.should == [o2]
  1554. c.eager_graph(:other_related_items).where(:items__id=>1).all.first.other_related_items.should == [o]
  1555. c.eager_graph(:mtm_items).where(:items__id=>1).all.first.mtm_items.should == [o]
  1556. # Filter By Associations - Model Instances
  1557. c.filter(:item=>o2).all.should == [o]
  1558. c.filter(:items=>o).all.should == [o2]
  1559. c.filter(:related_items=>o2).all.should == [o]
  1560. c.filter(:other_item=>o).all.should == [o2]
  1561. c.filter(:other_items=>o2).all.should == [o]
  1562. c.filter(:other_related_items=>o).all.should == [o]
  1563. c.filter(:mtm_items=>o).all.should == [o]
  1564. # Filter By Associations - Model Datasets
  1565. c.filter(:item=>c.filter(:id=>o2.id)).all.should == [o]
  1566. c.filter(:items=>c.filter(:id=>o.id)).all.should == [o2]
  1567. c.filter(:related_items=>c.filter(:id=>o2.id)).all.should == [o]
  1568. c.filter(:other_item=>c.filter(:id=>o.id)).all.should == [o2]
  1569. c.filter(:other_items=>c.filter(:id=>o2.id)).all.should == [o]
  1570. c.filter(:other_related_items=>c.filter(:id=>o.id)).all.should == [o]
  1571. c.filter(:mtm_items=>c.filter(:id=>o.id)).all.should == [o]
  1572. end
  1573. specify 'operations/functions with pg_hstore_ops' do
  1574. Sequel.extension :pg_hstore_ops, :pg_array, :pg_array_ops
  1575. @db.create_table!(:items){hstore :h1; hstore :h2; hstore :h3; String :t}
  1576. @ds.insert(Sequel.hstore('a'=>'b', 'c'=>nil), Sequel.hstore('a'=>'b'), Sequel.hstore('d'=>'e'))
  1577. h1 = Sequel.hstore(:h1)
  1578. h2 = Sequel.hstore(:h2)
  1579. h3 = Sequel.hstore(:h3)
  1580. @ds.get(h1['a']).should == 'b'
  1581. @ds.get(h1['d']).should == nil
  1582. @ds.get(h2.concat(h3).keys.pg_array.length).should == 2
  1583. @ds.get(h1.concat(h3).keys.pg_array.length).should == 3
  1584. @ds.get(h2.merge(h3).keys.pg_array.length).should == 2
  1585. @ds.get(h1.merge(h3).keys.pg_array.length).should == 3
  1586. unless @db.adapter_scheme == :do
  1587. # Broken DataObjects thinks operators with ? represent placeholders
  1588. @ds.get(h1.contain_all(Sequel.pg_array(%w'a c'))).should == true
  1589. @ds.get(h1.contain_all(Sequel.pg_array(%w'a d'))).should == false
  1590. @ds.get(h1.contain_any(Sequel.pg_array(%w'a d'))).should == true
  1591. @ds.get(h1.contain_any(Sequel.pg_array(%w'e d'))).should == false
  1592. end
  1593. @ds.get(h1.contains(h2)).should == true
  1594. @ds.get(h1.contains(h3)).should == false
  1595. @ds.get(h2.contained_by(h1)).should == true
  1596. @ds.get(h2.contained_by(h3)).should == false
  1597. @ds.get(h1.defined('a')).should == true
  1598. @ds.get(h1.defined('c')).should == false
  1599. @ds.get(h1.defined('d')).should == false
  1600. @ds.get(h1.delete('a')['c']).should == nil
  1601. @ds.get(h1.delete(Sequel.pg_array(%w'a d'))['c']).should == nil
  1602. @ds.get(h1.delete(h2)['c']).should == nil
  1603. @ds.from(Sequel.hstore('a'=>'b', 'c'=>nil).op.each).order(:key).all.should == [{:key=>'a', :value=>'b'}, {:key=>'c', :value=>nil}]
  1604. unless @db.adapter_scheme == :do
  1605. @ds.get(h1.has_key?('c')).should == true
  1606. @ds.get(h1.include?('c')).should == true
  1607. @ds.get(h1.key?('c')).should == true
  1608. @ds.get(h1.member?('c')).should == true
  1609. @ds.get(h1.exist?('c')).should == true
  1610. @ds.get(h1.has_key?('d')).should == false
  1611. @ds.get(h1.include?('d')).should == false
  1612. @ds.get(h1.key?('d')).should == false
  1613. @ds.get(h1.member?('d')).should == false
  1614. @ds.get(h1.exist?('d')).should == false
  1615. end
  1616. @ds.get(h1.hstore.hstore.hstore.keys.pg_array.length).should == 2
  1617. @ds.get(h1.keys.pg_array.length).should == 2
  1618. @ds.get(h2.keys.pg_array.length).should == 1
  1619. @ds.get(h1.akeys.pg_array.length).should == 2
  1620. @ds.get(h2.akeys.pg_array.length).should == 1
  1621. @ds.from(Sequel.hstore('t'=>'s').op.populate(Sequel::SQL::Cast.new(nil, :items))).select_map(:t).should == ['s']
  1622. @ds.from(:items___i).select(Sequel.hstore('t'=>'s').op.record_set(:i).as(:r)).from_self(:alias=>:s).select(Sequel.lit('(r).*')).from_self.select_map(:t).should == ['s']
  1623. @ds.from(Sequel.hstore('t'=>'s', 'a'=>'b').op.skeys.as(:s)).select_order_map(:s).should == %w'a t'
  1624. @ds.get(h1.slice(Sequel.pg_array(%w'a c')).keys.pg_array.length).should == 2
  1625. @ds.get(h1.slice(Sequel.pg_array(%w'd c')).keys.pg_array.length).should == 1
  1626. @ds.get(h1.slice(Sequel.pg_array(%w'd e')).keys.pg_array.length).should == nil
  1627. @ds.from(Sequel.hstore('t'=>'s', 'a'=>'b').op.svals.as(:s)).select_order_map(:s).should == %w'b s'
  1628. @ds.get(h1.to_array.pg_array.length).should == 4
  1629. @ds.get(h2.to_array.pg_array.length).should == 2
  1630. @ds.get(h1.to_matrix.pg_array.length).should == 2
  1631. @ds.get(h2.to_matrix.pg_array.length).should == 1
  1632. @ds.get(h1.values.pg_array.length).should == 2
  1633. @ds.get(h2.values.pg_array.length).should == 1
  1634. @ds.get(h1.avals.pg_array.length).should == 2
  1635. @ds.get(h2.avals.pg_array.length).should == 1
  1636. end
  1637. end if POSTGRES_DB.type_supported?(:hstore)
  1638. describe 'PostgreSQL json type' do
  1639. before(:all) do
  1640. @db = POSTGRES_DB
  1641. @db.extension :pg_array, :pg_json
  1642. @ds = @db[:items]
  1643. @a = [1, 2, {'a'=>'b'}, 3.0]
  1644. @h = {'a'=>'b', '1'=>[3, 4, 5]}
  1645. @native = POSTGRES_DB.adapter_scheme == :postgres
  1646. end
  1647. after do
  1648. @db.drop_table?(:items)
  1649. end
  1650. specify 'insert and retrieve json values' do
  1651. @db.create_table!(:items){json :j}
  1652. @ds.insert(Sequel.pg_json(@h))
  1653. @ds.count.should == 1
  1654. if @native
  1655. rs = @ds.all
  1656. v = rs.first[:j]
  1657. v.should_not be_a_kind_of(Hash)
  1658. v.to_hash.should be_a_kind_of(Hash)
  1659. v.should == @h
  1660. v.to_hash.should == @h
  1661. @ds.delete
  1662. @ds.insert(rs.first)
  1663. @ds.all.should == rs
  1664. end
  1665. @ds.delete
  1666. @ds.insert(Sequel.pg_json(@a))
  1667. @ds.count.should == 1
  1668. if @native
  1669. rs = @ds.all
  1670. v = rs.first[:j]
  1671. v.should_not be_a_kind_of(Array)
  1672. v.to_a.should be_a_kind_of(Array)
  1673. v.should == @a
  1674. v.to_a.should == @a
  1675. @ds.delete
  1676. @ds.insert(rs.first)
  1677. @ds.all.should == rs
  1678. end
  1679. end
  1680. specify 'insert and retrieve json[] values' do
  1681. @db.create_table!(:items){column :j, 'json[]'}
  1682. j = Sequel.pg_array([Sequel.pg_json('a'=>1), Sequel.pg_json(['b', 2])])
  1683. @ds.insert(j)
  1684. @ds.count.should == 1
  1685. if @native
  1686. rs = @ds.all
  1687. v = rs.first[:j]
  1688. v.should_not be_a_kind_of(Array)
  1689. v.to_a.should be_a_kind_of(Array)
  1690. v.should == j
  1691. v.to_a.should == j
  1692. @ds.delete
  1693. @ds.insert(rs.first)
  1694. @ds.all.should == rs
  1695. end
  1696. end
  1697. specify 'use json in bound variables' do
  1698. @db.create_table!(:items){json :i}
  1699. @ds.call(:insert, {:i=>Sequel.pg_json(@h)}, {:i=>:$i})
  1700. @ds.get(:i).should == @h
  1701. @ds.filter(Sequel.cast(:i, String)=>:$i).call(:first, :i=>Sequel.pg_json(@h)).should == {:i=>@h}
  1702. @ds.filter(Sequel.cast(:i, String)=>:$i).call(:first, :i=>Sequel.pg_json({})).should == nil
  1703. @ds.filter(Sequel.cast(:i, String)=>:$i).call(:delete, :i=>Sequel.pg_json(@h)).should == 1
  1704. @ds.call(:insert, {:i=>Sequel.pg_json(@a)}, {:i=>:$i})
  1705. @ds.get(:i).should == @a
  1706. @ds.filter(Sequel.cast(:i, String)=>:$i).call(:first, :i=>Sequel.pg_json(@a)).should == {:i=>@a}
  1707. @ds.filter(Sequel.cast(:i, String)=>:$i).call(:first, :i=>Sequel.pg_json([])).should == nil
  1708. @db.create_table!(:items){column :i, 'json[]'}
  1709. j = Sequel.pg_array([Sequel.pg_json('a'=>1), Sequel.pg_json(['b', 2])], :text)
  1710. @ds.call(:insert, {:i=>j}, {:i=>:$i})
  1711. @ds.get(:i).should == j
  1712. @ds.filter(Sequel.cast(:i, 'text[]')=>:$i).call(:first, :i=>j).should == {:i=>j}
  1713. @ds.filter(Sequel.cast(:i, 'text[]')=>:$i).call(:first, :i=>Sequel.pg_array([])).should == nil
  1714. end if POSTGRES_DB.adapter_scheme == :postgres && SEQUEL_POSTGRES_USES_PG
  1715. specify 'with models' do
  1716. @db.create_table!(:items) do
  1717. primary_key :id
  1718. json :h
  1719. end
  1720. c = Class.new(Sequel::Model(@db[:items]))
  1721. c.plugin :pg_typecast_on_load, :h unless @native
  1722. c.create(:h=>Sequel.pg_json(@h)).h.should == @h
  1723. c.create(:h=>Sequel.pg_json(@a)).h.should == @a
  1724. end
  1725. end if POSTGRES_DB.server_version >= 90200
  1726. describe 'PostgreSQL inet/cidr types' do
  1727. ipv6_broken = (IPAddr.new('::1'); false) rescue true
  1728. before(:all) do
  1729. @db = POSTGRES_DB
  1730. @db.extension :pg_array, :pg_inet
  1731. @ds = @db[:items]
  1732. @v4 = '127.0.0.1'
  1733. @v4nm = '127.0.0.0/8'
  1734. @v6 = '2001:4f8:3:ba:2e0:81ff:fe22:d1f1'
  1735. @v6nm = '2001:4f8:3:ba::/64'
  1736. @ipv4 = IPAddr.new(@v4)
  1737. @ipv4nm = IPAddr.new(@v4nm)
  1738. unless ipv6_broken
  1739. @ipv6 = IPAddr.new(@v6)
  1740. @ipv6nm = IPAddr.new(@v6nm)
  1741. end
  1742. @native = POSTGRES_DB.adapter_scheme == :postgres
  1743. end
  1744. after do
  1745. @db.drop_table?(:items)
  1746. end
  1747. specify 'insert and retrieve inet/cidr values' do
  1748. @db.create_table!(:items){inet :i; cidr :c}
  1749. @ds.insert(@ipv4, @ipv4nm)
  1750. @ds.count.should == 1
  1751. if @native
  1752. rs = @ds.all
  1753. rs.first[:i].should == @ipv4
  1754. rs.first[:c].should == @ipv4nm
  1755. rs.first[:i].should be_a_kind_of(IPAddr)
  1756. rs.first[:c].should be_a_kind_of(IPAddr)
  1757. @ds.delete
  1758. @ds.insert(rs.first)
  1759. @ds.all.should == rs
  1760. end
  1761. unless ipv6_broken
  1762. @ds.delete
  1763. @ds.insert(@ipv6, @ipv6nm)
  1764. @ds.count.should == 1
  1765. if @native
  1766. rs = @ds.all
  1767. v = rs.first[:j]
  1768. rs.first[:i].should == @ipv6
  1769. rs.first[:c].should == @ipv6nm
  1770. rs.first[:i].should be_a_kind_of(IPAddr)
  1771. rs.first[:c].should be_a_kind_of(IPAddr)
  1772. @ds.delete
  1773. @ds.insert(rs.first)
  1774. @ds.all.should == rs
  1775. end
  1776. end
  1777. end
  1778. specify 'insert and retrieve inet/cidr/macaddr array values' do
  1779. @db.create_table!(:items){column :i, 'inet[]'; column :c, 'cidr[]'; column :m, 'macaddr[]'}
  1780. @ds.insert(Sequel.pg_array([@ipv4], 'inet'), Sequel.pg_array([@ipv4nm], 'cidr'), Sequel.pg_array(['12:34:56:78:90:ab'], 'macaddr'))
  1781. @ds.count.should == 1
  1782. if @native
  1783. rs = @ds.all
  1784. rs.first.values.all?{|c| c.is_a?(Sequel::Postgres::PGArray)}.should be_true
  1785. rs.first[:i].first.should == @ipv4
  1786. rs.first[:c].first.should == @ipv4nm
  1787. rs.first[:m].first.should == '12:34:56:78:90:ab'
  1788. rs.first[:i].first.should be_a_kind_of(IPAddr)
  1789. rs.first[:c].first.should be_a_kind_of(IPAddr)
  1790. @ds.delete
  1791. @ds.insert(rs.first)
  1792. @ds.all.should == rs
  1793. end
  1794. end
  1795. specify 'use ipaddr in bound variables' do
  1796. @db.create_table!(:items){inet :i; cidr :c}
  1797. @ds.call(:insert, {:i=>@ipv4, :c=>@ipv4nm}, {:i=>:$i, :c=>:$c})
  1798. @ds.get(:i).should == @ipv4
  1799. @ds.get(:c).should == @ipv4nm
  1800. @ds.filter(:i=>:$i, :c=>:$c).call(:first, :i=>@ipv4, :c=>@ipv4nm).should == {:i=>@ipv4, :c=>@ipv4nm}
  1801. @ds.filter(:i=>:$i, :c=>:$c).call(:first, :i=>@ipv6, :c=>@ipv6nm).should == nil
  1802. @ds.filter(:i=>:$i, :c=>:$c).call(:delete, :i=>@ipv4, :c=>@ipv4nm).should == 1
  1803. unless ipv6_broken
  1804. @ds.call(:insert, {:i=>@ipv6, :c=>@ipv6nm}, {:i=>:$i, :c=>:$c})
  1805. @ds.get(:i).should == @ipv6
  1806. @ds.get(:c).should == @ipv6nm
  1807. @ds.filter(:i=>:$i, :c=>:$c).call(:first, :i=>@ipv6, :c=>@ipv6nm).should == {:i=>@ipv6, :c=>@ipv6nm}
  1808. @ds.filter(:i=>:$i, :c=>:$c).call(:first, :i=>@ipv4, :c=>@ipv4nm).should == nil
  1809. @ds.filter(:i=>:$i, :c=>:$c).call(:delete, :i=>@ipv6, :c=>@ipv6nm).should == 1
  1810. end
  1811. @db.create_table!(:items){column :i, 'inet[]'; column :c, 'cidr[]'; column :m, 'macaddr[]'}
  1812. @ds.call(:insert, {:i=>[@ipv4], :c=>[@ipv4nm], :m=>['12:34:56:78:90:ab']}, {:i=>:$i, :c=>:$c, :m=>:$m})
  1813. @ds.filter(:i=>:$i, :c=>:$c, :m=>:$m).call(:first, :i=>[@ipv4], :c=>[@ipv4nm], :m=>['12:34:56:78:90:ab']).should == {:i=>[@ipv4], :c=>[@ipv4nm], :m=>['12:34:56:78:90:ab']}
  1814. @ds.filter(:i=>:$i, :c=>:$c, :m=>:$m).call(:first, :i=>[], :c=>[], :m=>[]).should == nil
  1815. @ds.filter(:i=>:$i, :c=>:$c, :m=>:$m).call(:delete, :i=>[@ipv4], :c=>[@ipv4nm], :m=>['12:34:56:78:90:ab']).should == 1
  1816. end if POSTGRES_DB.adapter_scheme == :postgres && SEQUEL_POSTGRES_USES_PG
  1817. specify 'with models' do
  1818. @db.create_table!(:items) do
  1819. primary_key :id
  1820. inet :i
  1821. cidr :c
  1822. end
  1823. c = Class.new(Sequel::Model(@db[:items]))
  1824. c.plugin :pg_typecast_on_load, :i, :c unless @native
  1825. c.create(:i=>@v4, :c=>@v4nm).values.values_at(:i, :c).should == [@ipv4, @ipv4nm]
  1826. unless ipv6_broken
  1827. c.create(:i=>@ipv6, :c=>@ipv6nm).values.values_at(:i, :c).should == [@ipv6, @ipv6nm]
  1828. end
  1829. end
  1830. end
  1831. describe 'PostgreSQL range types' do
  1832. before(:all) do
  1833. @db = POSTGRES_DB
  1834. @db.extension :pg_array, :pg_range
  1835. @ds = @db[:items]
  1836. @map = {:i4=>'int4range', :i8=>'int8range', :n=>'numrange', :d=>'daterange', :t=>'tsrange', :tz=>'tstzrange'}
  1837. @r = {:i4=>1...2, :i8=>2...3, :n=>BigDecimal.new('1.0')..BigDecimal.new('2.0'), :d=>Date.today...(Date.today+1), :t=>Time.local(2011, 1)..Time.local(2011, 2), :tz=>Time.local(2011, 1)..Time.local(2011, 2)}
  1838. @ra = {}
  1839. @pgr = {}
  1840. @pgra = {}
  1841. @r.each{|k, v| @ra[k] = Sequel.pg_array([v], @map[k])}
  1842. @r.each{|k, v| @pgr[k] = Sequel.pg_range(v)}
  1843. @r.each{|k, v| @pgra[k] = Sequel.pg_array([Sequel.pg_range(v)], @map[k])}
  1844. @native = POSTGRES_DB.adapter_scheme == :postgres
  1845. end
  1846. after do
  1847. @db.drop_table?(:items)
  1848. end
  1849. specify 'insert and retrieve range type values' do
  1850. @db.create_table!(:items){int4range :i4; int8range :i8; numrange :n; daterange :d; tsrange :t; tstzrange :tz}
  1851. [@r, @pgr].each do |input|
  1852. h = {}
  1853. input.each{|k, v| h[k] = Sequel.cast(v, @map[k])}
  1854. @ds.insert(h)
  1855. @ds.count.should == 1
  1856. if @native
  1857. rs = @ds.all
  1858. rs.first.each do |k, v|
  1859. v.should_not be_a_kind_of(Range)
  1860. v.to_range.should be_a_kind_of(Range)
  1861. v.should == @r[k]
  1862. v.to_range.should == @r[k]
  1863. end
  1864. @ds.delete
  1865. @ds.insert(rs.first)
  1866. @ds.all.should == rs
  1867. end
  1868. @ds.delete
  1869. end
  1870. end
  1871. specify 'insert and retrieve arrays of range type values' do
  1872. @db.create_table!(:items){column :i4, 'int4range[]'; column :i8, 'int8range[]'; column :n, 'numrange[]'; column :d, 'daterange[]'; column :t, 'tsrange[]'; column :tz, 'tstzrange[]'}
  1873. [@ra, @pgra].each do |input|
  1874. @ds.insert(input)
  1875. @ds.count.should == 1
  1876. if @native
  1877. rs = @ds.all
  1878. rs.first.each do |k, v|
  1879. v.should_not be_a_kind_of(Array)
  1880. v.to_a.should be_a_kind_of(Array)
  1881. v.first.should_not be_a_kind_of(Range)
  1882. v.first.to_range.should be_a_kind_of(Range)
  1883. v.should == @ra[k].to_a
  1884. v.first.should == @r[k]
  1885. end
  1886. @ds.delete
  1887. @ds.insert(rs.first)
  1888. @ds.all.should == rs
  1889. end
  1890. @ds.delete
  1891. end
  1892. end
  1893. specify 'use range types in bound variables' do
  1894. @db.create_table!(:items){int4range :i4; int8range :i8; numrange :n; daterange :d; tsrange :t; tstzrange :tz}
  1895. h = {}
  1896. @r.keys.each{|k| h[k] = :"$#{k}"}
  1897. r2 = {}
  1898. @r.each{|k, v| r2[k] = Range.new(v.begin, v.end+2)}
  1899. @ds.call(:insert, @r, h)
  1900. @ds.first.should == @r
  1901. @ds.filter(h).call(:first, @r).should == @r
  1902. @ds.filter(h).call(:first, @pgr).should == @r
  1903. @ds.filter(h).call(:first, r2).should == nil
  1904. @ds.filter(h).call(:delete, @r).should == 1
  1905. @db.create_table!(:items){column :i4, 'int4range[]'; column :i8, 'int8range[]'; column :n, 'numrange[]'; column :d, 'daterange[]'; column :t, 'tsrange[]'; column :tz, 'tstzrange[]'}
  1906. @r.each{|k, v| r2[k] = [Range.new(v.begin, v.end+2)]}
  1907. @ds.call(:insert, @ra, h)
  1908. @ds.filter(h).call(:first, @ra).each{|k, v| v.should == @ra[k].to_a}
  1909. @ds.filter(h).call(:first, @pgra).each{|k, v| v.should == @ra[k].to_a}
  1910. @ds.filter(h).call(:first, r2).should == nil
  1911. @ds.filter(h).call(:delete, @ra).should == 1
  1912. end if POSTGRES_DB.adapter_scheme == :postgres && SEQUEL_POSTGRES_USES_PG
  1913. specify 'with models' do
  1914. @db.create_table!(:items){primary_key :id; int4range :i4; int8range :i8; numrange :n; daterange :d; tsrange :t; tstzrange :tz}
  1915. c = Class.new(Sequel::Model(@db[:items]))
  1916. c.plugin :pg_typecast_on_load, :i4, :i8, :n, :d, :t, :tz unless @native
  1917. v = c.create(@r).values
  1918. v.delete(:id)
  1919. v.should == @r
  1920. unless @db.adapter_scheme == :jdbc
  1921. @db.create_table!(:items){primary_key :id; column :i4, 'int4range[]'; column :i8, 'int8range[]'; column :n, 'numrange[]'; column :d, 'daterange[]'; column :t, 'tsrange[]'; column :tz, 'tstzrange[]'}
  1922. c = Class.new(Sequel::Model(@db[:items]))
  1923. c.plugin :pg_typecast_on_load, :i4, :i8, :n, :d, :t, :tz unless @native
  1924. v = c.create(@ra).values
  1925. v.delete(:id)
  1926. v.each{|k,v| v.should == @ra[k].to_a}
  1927. end
  1928. end
  1929. specify 'operations/functions with pg_range_ops' do
  1930. Sequel.extension :pg_range_ops
  1931. @db.get(Sequel.pg_range(1..5, :int4range).op.contains(2..4)).should be_true
  1932. @db.get(Sequel.pg_range(1..5, :int4range).op.contains(3..6)).should be_false
  1933. @db.get(Sequel.pg_range(1..5, :int4range).op.contains(0..6)).should be_false
  1934. @db.get(Sequel.pg_range(1..5, :int4range).op.contained_by(0..6)).should be_true
  1935. @db.get(Sequel.pg_range(1..5, :int4range).op.contained_by(3..6)).should be_false
  1936. @db.get(Sequel.pg_range(1..5, :int4range).op.contained_by(2..4)).should be_false
  1937. @db.get(Sequel.pg_range(1..5, :int4range).op.overlaps(5..6)).should be_true
  1938. @db.get(Sequel.pg_range(1...5, :int4range).op.overlaps(5..6)).should be_false
  1939. @db.get(Sequel.pg_range(1..5, :int4range).op.left_of(6..10)).should be_true
  1940. @db.get(Sequel.pg_range(1..5, :int4range).op.left_of(5..10)).should be_false
  1941. @db.get(Sequel.pg_range(1..5, :int4range).op.left_of(-1..0)).should be_false
  1942. @db.get(Sequel.pg_range(1..5, :int4range).op.left_of(-1..3)).should be_false
  1943. @db.get(Sequel.pg_range(1..5, :int4range).op.right_of(6..10)).should be_false
  1944. @db.get(Sequel.pg_range(1..5, :int4range).op.right_of(5..10)).should be_false
  1945. @db.get(Sequel.pg_range(1..5, :int4range).op.right_of(-1..0)).should be_true
  1946. @db.get(Sequel.pg_range(1..5, :int4range).op.right_of(-1..3)).should be_false
  1947. @db.get(Sequel.pg_range(1..5, :int4range).op.starts_before(6..10)).should be_true
  1948. @db.get(Sequel.pg_range(1..5, :int4range).op.starts_before(5..10)).should be_true
  1949. @db.get(Sequel.pg_range(1..5, :int4range).op.starts_before(-1..0)).should be_false
  1950. @db.get(Sequel.pg_range(1..5, :int4range).op.starts_before(-1..3)).should be_false
  1951. @db.get(Sequel.pg_range(1..5, :int4range).op.ends_after(6..10)).should be_false
  1952. @db.get(Sequel.pg_range(1..5, :int4range).op.ends_after(5..10)).should be_false
  1953. @db.get(Sequel.pg_range(1..5, :int4range).op.ends_after(-1..0)).should be_true
  1954. @db.get(Sequel.pg_range(1..5, :int4range).op.ends_after(-1..3)).should be_true
  1955. @db.get(Sequel.pg_range(1..5, :int4range).op.adjacent_to(6..10)).should be_true
  1956. @db.get(Sequel.pg_range(1...5, :int4range).op.adjacent_to(6..10)).should be_false
  1957. @db.get((Sequel.pg_range(1..5, :int4range).op + (6..10)).adjacent_to(6..10)).should be_false
  1958. @db.get((Sequel.pg_range(1..5, :int4range).op + (6..10)).adjacent_to(11..20)).should be_true
  1959. @db.get((Sequel.pg_range(1..5, :int4range).op * (2..6)).adjacent_to(6..10)).should be_true
  1960. @db.get((Sequel.pg_range(1..4, :int4range).op * (2..6)).adjacent_to(6..10)).should be_false
  1961. @db.get((Sequel.pg_range(1..5, :int4range).op - (2..6)).adjacent_to(2..10)).should be_true
  1962. @db.get((Sequel.pg_range(0..4, :int4range).op - (3..6)).adjacent_to(4..10)).should be_false
  1963. @db.get(Sequel.pg_range(0..4, :int4range).op.lower).should == 0
  1964. @db.get(Sequel.pg_range(0..4, :int4range).op.upper).should == 5
  1965. @db.get(Sequel.pg_range(0..4, :int4range).op.isempty).should be_false
  1966. @db.get(Sequel::Postgres::PGRange.empty(:int4range).op.isempty).should be_true
  1967. @db.get(Sequel.pg_range(1..5, :numrange).op.lower_inc).should be_true
  1968. @db.get(Sequel::Postgres::PGRange.new(1, 5, :exclude_begin=>true, :db_type=>:numrange).op.lower_inc).should be_false
  1969. @db.get(Sequel.pg_range(1..5, :numrange).op.upper_inc).should be_true
  1970. @db.get(Sequel.pg_range(1...5, :numrange).op.upper_inc).should be_false
  1971. @db.get(Sequel::Postgres::PGRange.new(1, 5, :db_type=>:int4range).op.lower_inf).should be_false
  1972. @db.get(Sequel::Postgres::PGRange.new(nil, 5, :db_type=>:int4range).op.lower_inf).should be_true
  1973. @db.get(Sequel::Postgres::PGRange.new(1, 5, :db_type=>:int4range).op.upper_inf).should be_false
  1974. @db.get(Sequel::Postgres::PGRange.new(1, nil, :db_type=>:int4range).op.upper_inf).should be_true
  1975. end
  1976. end if POSTGRES_DB.server_version >= 90200
  1977. describe 'PostgreSQL interval types' do
  1978. before(:all) do
  1979. @db = POSTGRES_DB
  1980. @db.extension :pg_array, :pg_interval
  1981. @ds = @db[:items]
  1982. @native = POSTGRES_DB.adapter_scheme == :postgres
  1983. end
  1984. after(:all) do
  1985. Sequel::Postgres::PG_TYPES.delete(1186)
  1986. end
  1987. after do
  1988. @db.drop_table?(:items)
  1989. end
  1990. specify 'insert and retrieve interval values' do
  1991. @db.create_table!(:items){interval :i}
  1992. [
  1993. ['0', '00:00:00', 0, [[:seconds, 0]]],
  1994. ['1 microsecond', '00:00:00.000001', 0.000001, [[:seconds, 0.000001]]],
  1995. ['1 millisecond', '00:00:00.001', 0.001, [[:seconds, 0.001]]],
  1996. ['1 second', '00:00:01', 1, [[:seconds, 1]]],
  1997. ['1 minute', '00:01:00', 60, [[:seconds, 60]]],
  1998. ['1 hour', '01:00:00', 3600, [[:seconds, 3600]]],
  1999. ['1 day', '1 day', 86400, [[:days, 1]]],
  2000. ['1 week', '7 days', 86400*7, [[:days, 7]]],
  2001. ['1 month', '1 mon', 86400*30, [[:months, 1]]],
  2002. ['1 year', '1 year', 31557600, [[:years, 1]]],
  2003. ['1 decade', '10 years', 31557600*10, [[:years, 10]]],
  2004. ['1 century', '100 years', 31557600*100, [[:years, 100]]],
  2005. ['1 millennium', '1000 years', 31557600*1000, [[:years, 1000]]],
  2006. ['1 year 2 months 3 weeks 4 days 5 hours 6 minutes 7 seconds', '1 year 2 mons 25 days 05:06:07', 31557600 + 2*86400*30 + 3*86400*7 + 4*86400 + 5*3600 + 6*60 + 7, [[:years, 1], [:months, 2], [:days, 25], [:seconds, 18367]]],
  2007. ['-1 year +2 months -3 weeks +4 days -5 hours +6 minutes -7 seconds', '-10 mons -17 days -04:54:07', -10*86400*30 - 3*86400*7 + 4*86400 - 5*3600 + 6*60 - 7, [[:months, -10], [:days, -17], [:seconds, -17647]]],
  2008. ['+2 years -1 months +3 weeks -4 days +5 hours -6 minutes +7 seconds', '1 year 11 mons 17 days 04:54:07', 31557600 + 11*86400*30 + 3*86400*7 - 4*86400 + 5*3600 - 6*60 + 7, [[:years, 1], [:months, 11], [:days, 17], [:seconds, 17647]]],
  2009. ].each do |instr, outstr, value, parts|
  2010. @ds.insert(instr)
  2011. @ds.count.should == 1
  2012. if @native
  2013. @ds.get(Sequel.cast(:i, String)).should == outstr
  2014. rs = @ds.all
  2015. rs.first[:i].is_a?(ActiveSupport::Duration).should be_true
  2016. rs.first[:i].should == ActiveSupport::Duration.new(value, parts)
  2017. rs.first[:i].parts.sort_by{|k,v| k.to_s}.should == parts.sort_by{|k,v| k.to_s}
  2018. @ds.delete
  2019. @ds.insert(rs.first)
  2020. @ds.all.should == rs
  2021. end
  2022. @ds.delete
  2023. end
  2024. end
  2025. specify 'insert and retrieve interval array values' do
  2026. @db.create_table!(:items){column :i, 'interval[]'}
  2027. @ds.insert(Sequel.pg_array(['1 year 2 months 3 weeks 4 days 5 hours 6 minutes 7 seconds'], 'interval'))
  2028. @ds.count.should == 1
  2029. if @native
  2030. rs = @ds.all
  2031. rs.first[:i].is_a?(Sequel::Postgres::PGArray).should be_true
  2032. rs.first[:i].first.is_a?(ActiveSupport::Duration).should be_true
  2033. rs.first[:i].first.should == ActiveSupport::Duration.new(31557600 + 2*86400*30 + 3*86400*7 + 4*86400 + 5*3600 + 6*60 + 7, [[:years, 1], [:months, 2], [:days, 25], [:seconds, 18367]])
  2034. rs.first[:i].first.parts.sort_by{|k,v| k.to_s}.should == [[:years, 1], [:months, 2], [:days, 25], [:seconds, 18367]].sort_by{|k,v| k.to_s}
  2035. @ds.delete
  2036. @ds.insert(rs.first)
  2037. @ds.all.should == rs
  2038. end
  2039. end
  2040. specify 'use intervals in bound variables' do
  2041. @db.create_table!(:items){interval :i}
  2042. @ds.insert('1 year 2 months 3 weeks 4 days 5 hours 6 minutes 7 seconds')
  2043. d = @ds.get(:i)
  2044. @ds.delete
  2045. @ds.call(:insert, {:i=>d}, {:i=>:$i})
  2046. @ds.get(:i).should == d
  2047. @ds.filter(:i=>:$i).call(:first, :i=>d).should == {:i=>d}
  2048. @ds.filter(:i=>:$i).call(:first, :i=>'0').should == nil
  2049. @ds.filter(:i=>:$i).call(:delete, :i=>d).should == 1
  2050. @db.create_table!(:items){column :i, 'interval[]'}
  2051. @ds.call(:insert, {:i=>[d]}, {:i=>:$i})
  2052. @ds.filter(:i=>:$i).call(:first, :i=>[d]).should == {:i=>[d]}
  2053. @ds.filter(:i=>:$i).call(:first, :i=>[]).should == nil
  2054. @ds.filter(:i=>:$i).call(:delete, :i=>[d]).should == 1
  2055. end if POSTGRES_DB.adapter_scheme == :postgres && SEQUEL_POSTGRES_USES_PG
  2056. specify 'with models' do
  2057. @db.create_table!(:items) do
  2058. primary_key :id
  2059. interval :i
  2060. end
  2061. c = Class.new(Sequel::Model(@db[:items]))
  2062. c.plugin :pg_typecast_on_load, :i, :c unless @native
  2063. v = c.create(:i=>'1 year 2 mons 25 days 05:06:07').i
  2064. v.is_a?(ActiveSupport::Duration).should be_true
  2065. v.should == ActiveSupport::Duration.new(31557600 + 2*86400*30 + 3*86400*7 + 4*86400 + 5*3600 + 6*60 + 7, [[:years, 1], [:months, 2], [:days, 25], [:seconds, 18367]])
  2066. v.parts.sort_by{|k,v| k.to_s}.should == [[:years, 1], [:months, 2], [:days, 25], [:seconds, 18367]].sort_by{|k,v| k.to_s}
  2067. end
  2068. end if ((require 'active_support/duration'; require 'active_support/inflector'; require 'active_support/core_ext/string/inflections'; true) rescue false)
  2069. describe 'PostgreSQL row-valued/composite types' do
  2070. before(:all) do
  2071. @db = POSTGRES_DB
  2072. @db.extension :pg_array, :pg_row
  2073. @ds = @db[:person]
  2074. @db.create_table!(:address) do
  2075. String :street
  2076. String :city
  2077. String :zip
  2078. end
  2079. @db.create_table!(:person) do
  2080. Integer :id
  2081. address :address
  2082. end
  2083. @db.create_table!(:company) do
  2084. Integer :id
  2085. column :employees, 'person[]'
  2086. end
  2087. @db.register_row_type(:address)
  2088. @db.register_row_type(:person)
  2089. @db.register_row_type(:company)
  2090. @native = POSTGRES_DB.adapter_scheme == :postgres
  2091. end
  2092. after(:all) do
  2093. @db.drop_table?(:company, :person, :address)
  2094. @db.row_types.clear
  2095. @db.reset_conversion_procs if @native
  2096. end
  2097. after do
  2098. [:company, :person, :address].each{|t| @db[t].delete}
  2099. end
  2100. specify 'insert and retrieve row types' do
  2101. @ds.insert(:id=>1, :address=>Sequel.pg_row(['123 Sesame St', 'Somewhere', '12345']))
  2102. @ds.count.should == 1
  2103. if @native
  2104. # Single row valued type
  2105. rs = @ds.all
  2106. v = rs.first[:address]
  2107. v.should_not be_a_kind_of(Hash)
  2108. v.to_hash.should be_a_kind_of(Hash)
  2109. v.to_hash.should == {:street=>'123 Sesame St', :city=>'Somewhere', :zip=>'12345'}
  2110. @ds.delete
  2111. @ds.insert(rs.first)
  2112. @ds.all.should == rs
  2113. # Nested row value type
  2114. p = @ds.get(:person)
  2115. p[:id].should == 1
  2116. p[:address].should == v
  2117. end
  2118. end
  2119. specify 'insert and retrieve arrays of row types' do
  2120. @ds = @db[:company]
  2121. @ds.insert(:id=>1, :employees=>Sequel.pg_array([@db.row_type(:person, [1, Sequel.pg_row(['123 Sesame St', 'Somewhere', '12345'])])]))
  2122. @ds.count.should == 1
  2123. if @native
  2124. v = @ds.get(:company)
  2125. v.should_not be_a_kind_of(Hash)
  2126. v.to_hash.should be_a_kind_of(Hash)
  2127. v[:id].should == 1
  2128. employees = v[:employees]
  2129. employees.should_not be_a_kind_of(Array)
  2130. employees.to_a.should be_a_kind_of(Array)
  2131. employees.should == [{:id=>1, :address=>{:street=>'123 Sesame St', :city=>'Somewhere', :zip=>'12345'}}]
  2132. @ds.delete
  2133. @ds.insert(v[:id], v[:employees])
  2134. @ds.get(:company).should == v
  2135. end
  2136. end
  2137. specify 'use row types in bound variables' do
  2138. @ds.call(:insert, {:address=>Sequel.pg_row(['123 Sesame St', 'Somewhere', '12345'])}, {:address=>:$address, :id=>1})
  2139. @ds.get(:address).should == {:street=>'123 Sesame St', :city=>'Somewhere', :zip=>'12345'}
  2140. @ds.filter(:address=>Sequel.cast(:$address, :address)).call(:first, :address=>Sequel.pg_row(['123 Sesame St', 'Somewhere', '12345']))[:id].should == 1
  2141. @ds.filter(:address=>Sequel.cast(:$address, :address)).call(:first, :address=>Sequel.pg_row(['123 Sesame St', 'Somewhere', '12356'])).should == nil
  2142. end if POSTGRES_DB.adapter_scheme == :postgres && SEQUEL_POSTGRES_USES_PG
  2143. specify 'use arrays of row types in bound variables' do
  2144. @ds = @db[:company]
  2145. @ds.call(:insert, {:employees=>Sequel.pg_array([@db.row_type(:person, [1, Sequel.pg_row(['123 Sesame St', 'Somewhere', '12345'])])])}, {:employees=>:$employees, :id=>1})
  2146. @ds.get(:company).should == {:id=>1, :employees=>[{:id=>1, :address=>{:street=>'123 Sesame St', :city=>'Somewhere', :zip=>'12345'}}]}
  2147. @ds.filter(:employees=>Sequel.cast(:$employees, 'person[]')).call(:first, :employees=>Sequel.pg_array([@db.row_type(:person, [1, Sequel.pg_row(['123 Sesame St', 'Somewhere', '12345'])])]))[:id].should == 1
  2148. @ds.filter(:employees=>Sequel.cast(:$employees, 'person[]')).call(:first, :employees=>Sequel.pg_array([@db.row_type(:person, [1, Sequel.pg_row(['123 Sesame St', 'Somewhere', '12356'])])])).should == nil
  2149. end if POSTGRES_DB.adapter_scheme == :postgres && SEQUEL_POSTGRES_USES_PG
  2150. specify 'operations/functions with pg_row_ops' do
  2151. Sequel.extension :pg_row_ops, :pg_array_ops
  2152. @ds.insert(:id=>1, :address=>Sequel.pg_row(['123 Sesame St', 'Somewhere', '12345']))
  2153. @ds.get(Sequel.pg_row(:address)[:street]).should == '123 Sesame St'
  2154. @ds.get(Sequel.pg_row(:address)[:city]).should == 'Somewhere'
  2155. @ds.get(Sequel.pg_row(:address)[:zip]).should == '12345'
  2156. @ds = @db[:company]
  2157. @ds.insert(:id=>1, :employees=>Sequel.pg_array([@db.row_type(:person, [1, Sequel.pg_row(['123 Sesame St', 'Somewhere', '12345'])])]))
  2158. @ds.get(Sequel.pg_row(:company)[:id]).should == 1
  2159. if @native
  2160. @ds.get(Sequel.pg_row(:company)[:employees]).should == [{:id=>1, :address=>{:street=>'123 Sesame St', :city=>'Somewhere', :zip=>'12345'}}]
  2161. @ds.get(Sequel.pg_row(:company)[:employees][1]).should == {:id=>1, :address=>{:street=>'123 Sesame St', :city=>'Somewhere', :zip=>'12345'}}
  2162. @ds.get(Sequel.pg_row(:company)[:employees][1][:address]).should == {:street=>'123 Sesame St', :city=>'Somewhere', :zip=>'12345'}
  2163. end
  2164. @ds.get(Sequel.pg_row(:company)[:employees][1][:id]).should == 1
  2165. @ds.get(Sequel.pg_row(:company)[:employees][1][:address][:street]).should == '123 Sesame St'
  2166. @ds.get(Sequel.pg_row(:company)[:employees][1][:address][:city]).should == 'Somewhere'
  2167. @ds.get(Sequel.pg_row(:company)[:employees][1][:address][:zip]).should == '12345'
  2168. end
  2169. context "with models" do
  2170. before(:all) do
  2171. class Address < Sequel::Model(:address)
  2172. plugin :pg_row
  2173. end
  2174. class Person < Sequel::Model(:person)
  2175. plugin :pg_row
  2176. end
  2177. class Company < Sequel::Model(:company)
  2178. plugin :pg_row
  2179. end
  2180. @a = Address.new(:street=>'123 Sesame St', :city=>'Somewhere', :zip=>'12345')
  2181. @es = Sequel.pg_array([Person.new(:id=>1, :address=>@a)])
  2182. end
  2183. after(:all) do
  2184. Object.send(:remove_const, :Address) rescue nil
  2185. Object.send(:remove_const, :Person) rescue nil
  2186. Object.send(:remove_const, :Company) rescue nil
  2187. end
  2188. specify 'insert and retrieve row types as model objects' do
  2189. @ds.insert(:id=>1, :address=>@a)
  2190. @ds.count.should == 1
  2191. if @native
  2192. # Single row valued type
  2193. rs = @ds.all
  2194. v = rs.first[:address]
  2195. v.should be_a_kind_of(Address)
  2196. v.should == @a
  2197. @ds.delete
  2198. @ds.insert(rs.first)
  2199. @ds.all.should == rs
  2200. # Nested row value type
  2201. p = @ds.get(:person)
  2202. p.should be_a_kind_of(Person)
  2203. p.id.should == 1
  2204. p.address.should be_a_kind_of(Address)
  2205. p.address.should == @a
  2206. end
  2207. end
  2208. specify 'insert and retrieve arrays of row types as model objects' do
  2209. @ds = @db[:company]
  2210. @ds.insert(:id=>1, :employees=>@es)
  2211. @ds.count.should == 1
  2212. if @native
  2213. v = @ds.get(:company)
  2214. v.should be_a_kind_of(Company)
  2215. v.id.should == 1
  2216. employees = v[:employees]
  2217. employees.should_not be_a_kind_of(Array)
  2218. employees.to_a.should be_a_kind_of(Array)
  2219. employees.should == @es
  2220. @ds.delete
  2221. @ds.insert(v.id, v.employees)
  2222. @ds.get(:company).should == v
  2223. end
  2224. end
  2225. specify 'use model objects in bound variables' do
  2226. @ds.call(:insert, {:address=>@a}, {:address=>:$address, :id=>1})
  2227. @ds.get(:address).should == @a
  2228. @ds.filter(:address=>Sequel.cast(:$address, :address)).call(:first, :address=>@a)[:id].should == 1
  2229. @ds.filter(:address=>Sequel.cast(:$address, :address)).call(:first, :address=>Address.new(:street=>'123 Sesame St', :city=>'Somewhere', :zip=>'12356')).should == nil
  2230. end if POSTGRES_DB.adapter_scheme == :postgres && SEQUEL_POSTGRES_USES_PG
  2231. specify 'use arrays of model objects in bound variables' do
  2232. @ds = @db[:company]
  2233. @ds.call(:insert, {:employees=>@es}, {:employees=>:$employees, :id=>1})
  2234. @ds.get(:company).should == Company.new(:id=>1, :employees=>@es)
  2235. @ds.filter(:employees=>Sequel.cast(:$employees, 'person[]')).call(:first, :employees=>@es)[:id].should == 1
  2236. @ds.filter(:employees=>Sequel.cast(:$employees, 'person[]')).call(:first, :employees=>Sequel.pg_array([@db.row_type(:person, [1, Sequel.pg_row(['123 Sesame St', 'Somewhere', '12356'])])])).should == nil
  2237. end if POSTGRES_DB.adapter_scheme == :postgres && SEQUEL_POSTGRES_USES_PG
  2238. specify 'model typecasting' do
  2239. Person.plugin :pg_typecast_on_load, :address unless @native
  2240. a = Address.new(:street=>'123 Sesame St', :city=>'Somewhere', :zip=>'12345')
  2241. o = Person.create(:id=>1, :address=>['123 Sesame St', 'Somewhere', '12345'])
  2242. o.address.should == a
  2243. o = Person.create(:id=>1, :address=>{:street=>'123 Sesame St', :city=>'Somewhere', :zip=>'12345'})
  2244. o.address.should == a
  2245. o = Person.create(:id=>1, :address=>a)
  2246. o.address.should == a
  2247. Company.plugin :pg_typecast_on_load, :employees unless @native
  2248. e = Person.new(:id=>1, :address=>a)
  2249. unless @db.adapter_scheme == :jdbc
  2250. o = Company.create(:id=>1, :employees=>[{:id=>1, :address=>{:street=>'123 Sesame St', :city=>'Somewhere', :zip=>'12345'}}])
  2251. o.employees.should == [e]
  2252. o = Company.create(:id=>1, :employees=>[e])
  2253. o.employees.should == [e]
  2254. end
  2255. end
  2256. end
  2257. end