PageRenderTime 76ms CodeModel.GetById 27ms RepoModel.GetById 1ms app.codeStats 0ms

/spec/adapters/postgres_spec.rb

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