/spec/adapters/postgres_spec.rb
Ruby | 2838 lines | 2568 code | 252 blank | 18 comment | 594 complexity | d8889a806d50625ee040a142319975ce MD5 | raw file
Large files files are truncated, but you can click here to view the full file
- require File.join(File.dirname(File.expand_path(__FILE__)), 'spec_helper.rb')
- unless defined?(POSTGRES_DB)
- POSTGRES_URL = 'postgres://postgres:postgres@localhost:5432/reality_spec' unless defined? POSTGRES_URL
- POSTGRES_DB = Sequel.connect(ENV['SEQUEL_PG_SPEC_DB']||POSTGRES_URL)
- end
- INTEGRATION_DB = POSTGRES_DB unless defined?(INTEGRATION_DB)
- def POSTGRES_DB.sqls
- (@sqls ||= [])
- end
- logger = Object.new
- def logger.method_missing(m, msg)
- POSTGRES_DB.sqls << msg
- end
- POSTGRES_DB.loggers << logger
- #POSTGRES_DB.instance_variable_set(:@server_version, 80200)
- describe "PostgreSQL", '#create_table' do
- before do
- @db = POSTGRES_DB
- POSTGRES_DB.sqls.clear
- end
- after do
- @db.drop_table?(:tmp_dolls)
- @db.drop_table?(:unlogged_dolls)
- end
- specify "should create a temporary table" do
- @db.create_table(:tmp_dolls, :temp => true){text :name}
- check_sqls do
- @db.sqls.should == ['CREATE TEMPORARY TABLE "tmp_dolls" ("name" text)']
- end
- end
- specify "should create an unlogged table" do
- @db.create_table(:unlogged_dolls, :unlogged => true){text :name}
- check_sqls do
- @db.sqls.should == ['CREATE UNLOGGED TABLE "unlogged_dolls" ("name" text)']
- end
- end
- specify "should not allow to pass both :temp and :unlogged" do
- proc do
- @db.create_table(:temp_unlogged_dolls, :temp => true, :unlogged => true){text :name}
- end.should raise_error(Sequel::Error, "can't provide both :temp and :unlogged to create_table")
- end
- end
- describe "PostgreSQL temporary views" do
- before do
- @db = POSTGRES_DB
- @db.drop_view(:items_view) rescue nil
- @db.create_table!(:items){Integer :number}
- @db[:items].insert(10)
- @db[:items].insert(20)
- end
- after do
- @db.drop_table?(:items)
- end
- specify "should be supported" do
- @db.create_view(:items_view, @db[:items].where(:number=>10), :temp=>true)
- @db[:items_view].map(:number).should == [10]
- @db.create_or_replace_view(:items_view, @db[:items].where(:number=>20), :temp=>true)
- @db[:items_view].map(:number).should == [20]
- @db.disconnect
- lambda{@db[:items_view].map(:number)}.should raise_error(Sequel::DatabaseError)
- end
- end unless POSTGRES_DB.adapter_scheme == :do # Causes freezing later
-
- describe "A PostgreSQL database" do
- before(:all) do
- @db = POSTGRES_DB
- @db.create_table!(:public__testfk){primary_key :id; foreign_key :i, :public__testfk}
- end
- after(:all) do
- @db.drop_table?(:public__testfk)
- end
- specify "should provide the server version" do
- @db.server_version.should > 70000
- end
- specify "should not typecast the int2vector type incorrectly" do
- @db.get(Sequel.cast('10 20', :int2vector)).should_not == 10
- end
- cspecify "should not typecast the money type incorrectly", :do do
- @db.get(Sequel.cast('10.01', :money)).should_not == 0
- end
- specify "should correctly parse the schema" do
- @db.schema(:public__testfk, :reload=>true).should == [
- [:id, {:type=>:integer, :ruby_default=>nil, :db_type=>"integer", :default=>"nextval('testfk_id_seq'::regclass)", :oid=>23, :primary_key=>true, :allow_null=>false}],
- [:i, {:type=>:integer, :ruby_default=>nil, :db_type=>"integer", :default=>nil, :oid=>23, :primary_key=>false, :allow_null=>true}]]
- end
- specify "should parse foreign keys for tables in a schema" do
- @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}]
- end
- specify "should return uuid fields as strings" do
- @db.get(Sequel.cast('550e8400-e29b-41d4-a716-446655440000', :uuid)).should == '550e8400-e29b-41d4-a716-446655440000'
- end
- end
- describe "A PostgreSQL dataset" do
- before(:all) do
- @db = POSTGRES_DB
- @d = @db[:test]
- @db.create_table! :test do
- text :name
- integer :value, :index => true
- end
- end
- before do
- @d.delete
- @db.sqls.clear
- end
- after do
- @db.drop_table?(:atest)
- end
- after(:all) do
- @db.drop_table?(:test)
- end
- specify "should quote columns and tables using double quotes if quoting identifiers" do
- check_sqls do
- @d.select(:name).sql.should == 'SELECT "name" FROM "test"'
- @d.select(Sequel.lit('COUNT(*)')).sql.should == 'SELECT COUNT(*) FROM "test"'
- @d.select(Sequel.function(:max, :value)).sql.should == 'SELECT max("value") FROM "test"'
- @d.select(Sequel.function(:NOW)).sql.should == 'SELECT NOW() FROM "test"'
- @d.select(Sequel.function(:max, :items__value)).sql.should == 'SELECT max("items"."value") FROM "test"'
- @d.order(Sequel.desc(:name)).sql.should == 'SELECT * FROM "test" ORDER BY "name" DESC'
- @d.select(Sequel.lit('test.name AS item_name')).sql.should == 'SELECT test.name AS item_name FROM "test"'
- @d.select(Sequel.lit('"name"')).sql.should == 'SELECT "name" FROM "test"'
- @d.select(Sequel.lit('max(test."name") AS "max_name"')).sql.should == 'SELECT max(test."name") AS "max_name" FROM "test"'
- @d.insert_sql(:x => :y).should =~ /\AINSERT INTO "test" \("x"\) VALUES \("y"\)( RETURNING NULL)?\z/
- @d.select(Sequel.function(:test, :abc, 'hello')).sql.should == "SELECT test(\"abc\", 'hello') FROM \"test\""
- @d.select(Sequel.function(:test, :abc__def, 'hello')).sql.should == "SELECT test(\"abc\".\"def\", 'hello') FROM \"test\""
- @d.select(Sequel.function(:test, :abc__def, 'hello').as(:x2)).sql.should == "SELECT test(\"abc\".\"def\", 'hello') AS \"x2\" FROM \"test\""
- @d.insert_sql(:value => 333).should =~ /\AINSERT INTO "test" \("value"\) VALUES \(333\)( RETURNING NULL)?\z/
- end
- end
- specify "should quote fields correctly when reversing the order if quoting identifiers" do
- check_sqls do
- @d.reverse_order(:name).sql.should == 'SELECT * FROM "test" ORDER BY "name" DESC'
- @d.reverse_order(Sequel.desc(:name)).sql.should == 'SELECT * FROM "test" ORDER BY "name" ASC'
- @d.reverse_order(:name, Sequel.desc(:test)).sql.should == 'SELECT * FROM "test" ORDER BY "name" DESC, "test" ASC'
- @d.reverse_order(Sequel.desc(:name), :test).sql.should == 'SELECT * FROM "test" ORDER BY "name" ASC, "test" DESC'
- end
- end
- specify "should support regexps" do
- @d << {:name => 'abc', :value => 1}
- @d << {:name => 'bcd', :value => 2}
- @d.filter(:name => /bc/).count.should == 2
- @d.filter(:name => /^bc/).count.should == 1
- end
- specify "should support NULLS FIRST and NULLS LAST" do
- @d << {:name => 'abc'}
- @d << {:name => 'bcd'}
- @d << {:name => 'bcd', :value => 2}
- @d.order(Sequel.asc(:value, :nulls=>:first), :name).select_map(:name).should == %w[abc bcd bcd]
- @d.order(Sequel.asc(:value, :nulls=>:last), :name).select_map(:name).should == %w[bcd abc bcd]
- @d.order(Sequel.asc(:value, :nulls=>:first), :name).reverse.select_map(:name).should == %w[bcd bcd abc]
- end
- specify "#lock should lock tables and yield if a block is given" do
- @d.lock('EXCLUSIVE'){@d.insert(:name=>'a')}
- end
- specify "should support exclusion constraints when creating or altering tables" do
- @db.create_table!(:atest){Integer :t; exclude [[Sequel.desc(:t, :nulls=>:last), '=']], :using=>:btree, :where=>proc{t > 0}}
- @db[:atest].insert(1)
- @db[:atest].insert(2)
- proc{@db[:atest].insert(2)}.should raise_error(Sequel::Postgres::ExclusionConstraintViolation)
- @db.create_table!(:atest){Integer :t}
- @db.alter_table(:atest){add_exclusion_constraint [[:t, '=']], :using=>:btree, :name=>'atest_ex'}
- @db[:atest].insert(1)
- @db[:atest].insert(2)
- proc{@db[:atest].insert(2)}.should raise_error(Sequel::Postgres::ExclusionConstraintViolation)
- @db.alter_table(:atest){drop_constraint 'atest_ex'}
- end if POSTGRES_DB.server_version >= 90000
- specify "should support Database#do for executing anonymous code blocks" do
- @db.drop_table?(:btest)
- @db.do "BEGIN EXECUTE 'CREATE TABLE btest (a INTEGER)'; EXECUTE 'INSERT INTO btest VALUES (1)'; END"
- @db[:btest].select_map(:a).should == [1]
- @db.do "BEGIN EXECUTE 'DROP TABLE btest; CREATE TABLE atest (a INTEGER)'; EXECUTE 'INSERT INTO atest VALUES (1)'; END", :language=>:plpgsql
- @db[:atest].select_map(:a).should == [1]
- end if POSTGRES_DB.server_version >= 90000
- specify "should support adding foreign key constarints that are not yet valid, and validating them later" do
- @db.create_table!(:atest){primary_key :id; Integer :fk}
- @db[:atest].insert(1, 5)
- @db.alter_table(:atest){add_foreign_key [:fk], :atest, :not_valid=>true, :name=>:atest_fk}
- @db[:atest].insert(2, 1)
- proc{@db[:atest].insert(3, 4)}.should raise_error(Sequel::DatabaseError)
- proc{@db.alter_table(:atest){validate_constraint :atest_fk}}.should raise_error(Sequel::DatabaseError)
- @db[:atest].where(:id=>1).update(:fk=>2)
- @db.alter_table(:atest){validate_constraint :atest_fk}
- proc{@db.alter_table(:atest){validate_constraint :atest_fk}}.should_not raise_error
- end if POSTGRES_DB.server_version >= 90200
- specify "should support :using when altering a column's type" do
- @db.create_table!(:atest){Integer :t}
- @db[:atest].insert(1262304000)
- @db.alter_table(:atest){set_column_type :t, Time, :using=>Sequel.cast('epoch', Time) + Sequel.cast('1 second', :interval) * :t}
- @db[:atest].get(Sequel.extract(:year, :t)).should == 2010
- end
- specify "should support :using with a string when altering a column's type" do
- @db.create_table!(:atest){Integer :t}
- @db[:atest].insert(1262304000)
- @db.alter_table(:atest){set_column_type :t, Time, :using=>"'epoch'::timestamp + '1 second'::interval * t"}
- @db[:atest].get(Sequel.extract(:year, :t)).should == 2010
- end
- specify "should be able to parse the default value for an interval type" do
- @db.create_table!(:atest){interval :t, :default=>'1 week'}
- @db.schema(:atest).first.last[:ruby_default].should == '7 days'
- end
- specify "should have #transaction support various types of synchronous options" do
- @db.transaction(:synchronous=>:on){}
- @db.transaction(:synchronous=>true){}
- @db.transaction(:synchronous=>:off){}
- @db.transaction(:synchronous=>false){}
- @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"]
- @db.sqls.clear
- @db.transaction(:synchronous=>nil){}
- check_sqls do
- @db.sqls.should == ['BEGIN', 'COMMIT']
- end
- if @db.server_version >= 90100
- @db.sqls.clear
- @db.transaction(:synchronous=>:local){}
- check_sqls do
- @db.sqls.grep(/synchronous/).should == ["SET LOCAL synchronous_commit = local"]
- end
- if @db.server_version >= 90200
- @db.sqls.clear
- @db.transaction(:synchronous=>:remote_write){}
- check_sqls do
- @db.sqls.grep(/synchronous/).should == ["SET LOCAL synchronous_commit = remote_write"]
- end
- end
- end
- end
- specify "should have #transaction support read only transactions" do
- @db.transaction(:read_only=>true){}
- @db.transaction(:read_only=>false){}
- @db.transaction(:isolation=>:serializable, :read_only=>true){}
- @db.transaction(:isolation=>:serializable, :read_only=>false){}
- @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"]
- end
- specify "should have #transaction support deferrable transactions" do
- @db.transaction(:deferrable=>true){}
- @db.transaction(:deferrable=>false){}
- @db.transaction(:deferrable=>true, :read_only=>true){}
- @db.transaction(:deferrable=>false, :read_only=>false){}
- @db.transaction(:isolation=>:serializable, :deferrable=>true, :read_only=>true){}
- @db.transaction(:isolation=>:serializable, :deferrable=>false, :read_only=>false){}
- @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"]
- end if POSTGRES_DB.server_version >= 90100
- specify "should support creating indexes concurrently" do
- @db.add_index :test, [:name, :value], :concurrently=>true
- check_sqls do
- @db.sqls.should == ['CREATE INDEX CONCURRENTLY "test_name_value_index" ON "test" ("name", "value")']
- end
- end
- specify "should support dropping indexes only if they already exist" do
- @db.add_index :test, [:name, :value], :name=>'tnv1'
- @db.sqls.clear
- @db.drop_index :test, [:name, :value], :if_exists=>true, :name=>'tnv1'
- check_sqls do
- @db.sqls.should == ['DROP INDEX IF EXISTS "tnv1"']
- end
- end
- specify "should support CASCADE when dropping indexes" do
- @db.add_index :test, [:name, :value], :name=>'tnv2'
- @db.sqls.clear
- @db.drop_index :test, [:name, :value], :cascade=>true, :name=>'tnv2'
- check_sqls do
- @db.sqls.should == ['DROP INDEX "tnv2" CASCADE']
- end
- end
- specify "should support dropping indexes concurrently" do
- @db.add_index :test, [:name, :value], :name=>'tnv2'
- @db.sqls.clear
- @db.drop_index :test, [:name, :value], :concurrently=>true, :name=>'tnv2'
- check_sqls do
- @db.sqls.should == ['DROP INDEX CONCURRENTLY "tnv2"']
- end
- end if POSTGRES_DB.server_version >= 90200
- specify "#lock should lock table if inside a transaction" do
- @db.transaction{@d.lock('EXCLUSIVE'); @d.insert(:name=>'a')}
- end
- specify "#lock should return nil" do
- @d.lock('EXCLUSIVE'){@d.insert(:name=>'a')}.should == nil
- @db.transaction{@d.lock('EXCLUSIVE').should == nil; @d.insert(:name=>'a')}
- end
- specify "should raise an error if attempting to update a joined dataset with a single FROM table" do
- 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')
- end
- specify "should truncate with options" do
- @d << { :name => 'abc', :value => 1}
- @d.count.should == 1
- @d.truncate(:cascade => true)
- @d.count.should == 0
- if @d.db.server_version > 80400
- @d << { :name => 'abc', :value => 1}
- @d.truncate(:cascade => true, :only=>true, :restart=>true)
- @d.count.should == 0
- end
- end
- specify "should truncate multiple tables at once" do
- tables = [:test, :test]
- tables.each{|t| @d.from(t).insert}
- @d.from(:test, :test).truncate
- tables.each{|t| @d.from(t).count.should == 0}
- end
- end
- describe "Dataset#distinct" do
- before do
- @db = POSTGRES_DB
- @db.create_table!(:a) do
- Integer :a
- Integer :b
- end
- @ds = @db[:a]
- end
- after do
- @db.drop_table?(:a)
- end
- it "#distinct with arguments should return results distinct on those arguments" do
- @ds.insert(20, 10)
- @ds.insert(30, 10)
- @ds.order(:b, :a).distinct.map(:a).should == [20, 30]
- @ds.order(:b, Sequel.desc(:a)).distinct.map(:a).should == [30, 20]
- @ds.order(:b, :a).distinct(:b).map(:a).should == [20]
- @ds.order(:b, Sequel.desc(:a)).distinct(:b).map(:a).should == [30]
- end
- end
- if POSTGRES_DB.pool.respond_to?(:max_size) and POSTGRES_DB.pool.max_size > 1
- describe "Dataset#for_update support" do
- before do
- @db = POSTGRES_DB.create_table!(:items) do
- primary_key :id
- Integer :number
- String :name
- end
- @ds = POSTGRES_DB[:items]
- end
- after do
- POSTGRES_DB.drop_table?(:items)
- POSTGRES_DB.disconnect
- end
- specify "should handle FOR UPDATE" do
- @ds.insert(:number=>20)
- c, t = nil, nil
- q = Queue.new
- POSTGRES_DB.transaction do
- @ds.for_update.first(:id=>1)
- t = Thread.new do
- POSTGRES_DB.transaction do
- q.push nil
- @ds.filter(:id=>1).update(:name=>'Jim')
- c = @ds.first(:id=>1)
- q.push nil
- end
- end
- q.pop
- @ds.filter(:id=>1).update(:number=>30)
- end
- q.pop
- t.join
- c.should == {:id=>1, :number=>30, :name=>'Jim'}
- end
- specify "should handle FOR SHARE" do
- @ds.insert(:number=>20)
- c, t = nil
- q = Queue.new
- POSTGRES_DB.transaction do
- @ds.for_share.first(:id=>1)
- t = Thread.new do
- POSTGRES_DB.transaction do
- c = @ds.for_share.filter(:id=>1).first
- q.push nil
- end
- end
- q.pop
- @ds.filter(:id=>1).update(:name=>'Jim')
- c.should == {:id=>1, :number=>20, :name=>nil}
- end
- t.join
- end
- end
- end
- describe "A PostgreSQL dataset with a timestamp field" do
- before(:all) do
- @db = POSTGRES_DB
- @db.create_table! :test3 do
- integer :value
- timestamp :time
- end
- @d = @db[:test3]
- end
- before do
- @d.delete
- end
- after do
- @db.convert_infinite_timestamps = false if @db.adapter_scheme == :postgres
- end
- after(:all) do
- @db.drop_table?(:test3)
- end
- cspecify "should store milliseconds in time fields for Time objects", :do, :swift do
- t = Time.now
- @d << {:value=>1, :time=>t}
- t2 = @d[:value =>1][:time]
- @d.literal(t2).should == @d.literal(t)
- t2.strftime('%Y-%m-%d %H:%M:%S').should == t.strftime('%Y-%m-%d %H:%M:%S')
- (t2.is_a?(Time) ? t2.usec : t2.strftime('%N').to_i/1000).should == t.usec
- end
- cspecify "should store milliseconds in time fields for DateTime objects", :do, :swift do
- t = DateTime.now
- @d << {:value=>1, :time=>t}
- t2 = @d[:value =>1][:time]
- @d.literal(t2).should == @d.literal(t)
- t2.strftime('%Y-%m-%d %H:%M:%S').should == t.strftime('%Y-%m-%d %H:%M:%S')
- (t2.is_a?(Time) ? t2.usec : t2.strftime('%N').to_i/1000).should == t.strftime('%N').to_i/1000
- end
- if POSTGRES_DB.adapter_scheme == :postgres
- specify "should handle infinite timestamps if convert_infinite_timestamps is set" do
- @d << {:time=>Sequel.cast('infinity', :timestamp)}
- @db.convert_infinite_timestamps = :nil
- @db[:test3].get(:time).should == nil
- @db.convert_infinite_timestamps = :string
- @db[:test3].get(:time).should == 'infinity'
- @db.convert_infinite_timestamps = :float
- @db[:test3].get(:time).should == 1.0/0.0
- @d.update(:time=>Sequel.cast('-infinity', :timestamp))
- @db.convert_infinite_timestamps = :nil
- @db[:test3].get(:time).should == nil
- @db.convert_infinite_timestamps = :string
- @db[:test3].get(:time).should == '-infinity'
- @db.convert_infinite_timestamps = :float
- @db[:test3].get(:time).should == -1.0/0.0
- end
- specify "should handle conversions from infinite strings/floats in models" do
- c = Class.new(Sequel::Model(:test3))
- @db.convert_infinite_timestamps = :float
- c.new(:time=>'infinity').time.should == 'infinity'
- c.new(:time=>'-infinity').time.should == '-infinity'
- c.new(:time=>1.0/0.0).time.should == 1.0/0.0
- c.new(:time=>-1.0/0.0).time.should == -1.0/0.0
- end
- end
- specify "explain and analyze should not raise errors" do
- @d = POSTGRES_DB[:test3]
- proc{@d.explain}.should_not raise_error
- proc{@d.analyze}.should_not raise_error
- end
- specify "#locks should be a dataset returning database locks " do
- @db.locks.should be_a_kind_of(Sequel::Dataset)
- @db.locks.all.should be_a_kind_of(Array)
- end
- end
- describe "A PostgreSQL database" do
- before do
- @db = POSTGRES_DB
- @db.create_table! :test2 do
- text :name
- integer :value
- end
- end
- after do
- @db.drop_table?(:test2)
- end
- specify "should support column operations" do
- @db.create_table!(:test2){text :name; integer :value}
- @db[:test2] << {}
- @db[:test2].columns.should == [:name, :value]
- @db.add_column :test2, :xyz, :text, :default => '000'
- @db[:test2].columns.should == [:name, :value, :xyz]
- @db[:test2] << {:name => 'mmm', :value => 111}
- @db[:test2].first[:xyz].should == '000'
- @db[:test2].columns.should == [:name, :value, :xyz]
- @db.drop_column :test2, :xyz
- @db[:test2].columns.should == [:name, :value]
- @db[:test2].delete
- @db.add_column :test2, :xyz, :text, :default => '000'
- @db[:test2] << {:name => 'mmm', :value => 111, :xyz => 'qqqq'}
- @db[:test2].columns.should == [:name, :value, :xyz]
- @db.rename_column :test2, :xyz, :zyx
- @db[:test2].columns.should == [:name, :value, :zyx]
- @db[:test2].first[:zyx].should == 'qqqq'
- @db.add_column :test2, :xyz, :float
- @db[:test2].delete
- @db[:test2] << {:name => 'mmm', :value => 111, :xyz => 56.78}
- @db.set_column_type :test2, :xyz, :integer
- @db[:test2].first[:xyz].should == 57
- end
- end
- describe "A PostgreSQL database" do
- before do
- @db = POSTGRES_DB
- @db.drop_table?(:posts)
- @db.sqls.clear
- end
- after do
- @db.drop_table?(:posts)
- end
- specify "should support resetting the primary key sequence" do
- @db.create_table(:posts){primary_key :a}
- @db[:posts].insert(:a=>20).should == 20
- @db[:posts].insert.should == 1
- @db[:posts].insert.should == 2
- @db[:posts].insert(:a=>10).should == 10
- @db.reset_primary_key_sequence(:posts).should == 21
- @db[:posts].insert.should == 21
- @db[:posts].order(:a).map(:a).should == [1, 2, 10, 20, 21]
- end
-
- specify "should support resetting the primary key sequence with default_schema" do
- begin
- @db.run("DROP SCHEMA p") rescue nil
- @db.run("CREATE SCHEMA p")
- @db.default_schema = :p
- @db.create_table(:posts){primary_key :a}
- @db[:p__posts].insert(:a=>20).should == 20
- @db[:p__posts].insert.should == 1
- @db[:p__posts].insert.should == 2
- @db[:p__posts].insert(:a=>10).should == 10
- @db.reset_primary_key_sequence(:posts).should == 21
- @db[:p__posts].insert.should == 21
- @db[:p__posts].order(:a).map(:a).should == [1, 2, 10, 20, 21]
- ensure
- @db.default_schema = nil
- @db.run("DROP SCHEMA p CASCADE")
- end
- end
- specify "should support specifying Integer/Bignum/Fixnum types in primary keys and have them be auto incrementing" do
- @db.create_table(:posts){primary_key :a, :type=>Integer}
- @db[:posts].insert.should == 1
- @db[:posts].insert.should == 2
- @db.create_table!(:posts){primary_key :a, :type=>Fixnum}
- @db[:posts].insert.should == 1
- @db[:posts].insert.should == 2
- @db.create_table!(:posts){primary_key :a, :type=>Bignum}
- @db[:posts].insert.should == 1
- @db[:posts].insert.should == 2
- end
- specify "should not raise an error if attempting to resetting the primary key sequence for a table without a primary key" do
- @db.create_table(:posts){Integer :a}
- @db.reset_primary_key_sequence(:posts).should == nil
- end
- specify "should support opclass specification" do
- @db.create_table(:posts){text :title; text :body; integer :user_id; index(:user_id, :opclass => :int4_ops, :type => :btree)}
- check_sqls do
- @db.sqls.should == [
- 'CREATE TABLE "posts" ("title" text, "body" text, "user_id" integer)',
- 'CREATE INDEX "posts_user_id_index" ON "posts" USING btree ("user_id" int4_ops)'
- ]
- end
- end
- specify "should support fulltext indexes and searching" do
- @db.create_table(:posts){text :title; text :body; full_text_index [:title, :body]; full_text_index :title, :language => 'french'}
- check_sqls do
- @db.sqls.should == [
- %{CREATE TABLE "posts" ("title" text, "body" text)},
- %{CREATE INDEX "posts_title_body_index" ON "posts" USING gin (to_tsvector('simple'::regconfig, (COALESCE("title", '') || ' ' || COALESCE("body", ''))))},
- %{CREATE INDEX "posts_title_index" ON "posts" USING gin (to_tsvector('french'::regconfig, (COALESCE("title", ''))))}
- ]
- end
- @db[:posts].insert(:title=>'ruby rails', :body=>'yowsa')
- @db[:posts].insert(:title=>'sequel', :body=>'ruby')
- @db[:posts].insert(:title=>'ruby scooby', :body=>'x')
- @db.sqls.clear
- @db[:posts].full_text_search(:title, 'rails').all.should == [{:title=>'ruby rails', :body=>'yowsa'}]
- @db[:posts].full_text_search([:title, :body], ['yowsa', 'rails']).all.should == [:title=>'ruby rails', :body=>'yowsa']
- @db[:posts].full_text_search(:title, 'scooby', :language => 'french').all.should == [{:title=>'ruby scooby', :body=>'x'}]
- check_sqls do
- @db.sqls.should == [
- %{SELECT * FROM "posts" WHERE (to_tsvector('simple'::regconfig, (COALESCE("title", ''))) @@ to_tsquery('simple'::regconfig, 'rails'))},
- %{SELECT * FROM "posts" WHERE (to_tsvector('simple'::regconfig, (COALESCE("title", '') || ' ' || COALESCE("body", ''))) @@ to_tsquery('simple'::regconfig, 'yowsa | rails'))},
- %{SELECT * FROM "posts" WHERE (to_tsvector('french'::regconfig, (COALESCE("title", ''))) @@ to_tsquery('french'::regconfig, 'scooby'))}]
- end
- @db[:posts].full_text_search(:title, :$n).call(:select, :n=>'rails').should == [{:title=>'ruby rails', :body=>'yowsa'}]
- @db[:posts].full_text_search(:title, :$n).prepare(:select, :fts_select).call(:n=>'rails').should == [{:title=>'ruby rails', :body=>'yowsa'}]
- end
- specify "should support spatial indexes" do
- @db.create_table(:posts){box :geom; spatial_index [:geom]}
- check_sqls do
- @db.sqls.should == [
- 'CREATE TABLE "posts" ("geom" box)',
- 'CREATE INDEX "posts_geom_index" ON "posts" USING gist ("geom")'
- ]
- end
- end
- specify "should support indexes with index type" do
- @db.create_table(:posts){varchar :title, :size => 5; index :title, :type => 'hash'}
- check_sqls do
- @db.sqls.should == [
- 'CREATE TABLE "posts" ("title" varchar(5))',
- 'CREATE INDEX "posts_title_index" ON "posts" USING hash ("title")'
- ]
- end
- end
- specify "should support unique indexes with index type" do
- @db.create_table(:posts){varchar :title, :size => 5; index :title, :type => 'btree', :unique => true}
- check_sqls do
- @db.sqls.should == [
- 'CREATE TABLE "posts" ("title" varchar(5))',
- 'CREATE UNIQUE INDEX "posts_title_index" ON "posts" USING btree ("title")'
- ]
- end
- end
- specify "should support partial indexes" do
- @db.create_table(:posts){varchar :title, :size => 5; index :title, :where => {:title => '5'}}
- check_sqls do
- @db.sqls.should == [
- 'CREATE TABLE "posts" ("title" varchar(5))',
- 'CREATE INDEX "posts_title_index" ON "posts" ("title") WHERE ("title" = \'5\')'
- ]
- end
- end
- specify "should support identifiers for table names in indicies" do
- @db.create_table(Sequel::SQL::Identifier.new(:posts)){varchar :title, :size => 5; index :title, :where => {:title => '5'}}
- check_sqls do
- @db.sqls.should == [
- 'CREATE TABLE "posts" ("title" varchar(5))',
- 'CREATE INDEX "posts_title_index" ON "posts" ("title") WHERE ("title" = \'5\')'
- ]
- end
- end
- specify "should support renaming tables" do
- @db.create_table!(:posts1){primary_key :a}
- @db.rename_table(:posts1, :posts)
- end
- end
- describe "Postgres::Dataset#import" do
- before do
- @db = POSTGRES_DB
- @db.create_table!(:test){primary_key :x; Integer :y}
- @db.sqls.clear
- @ds = @db[:test]
- end
- after do
- @db.drop_table?(:test)
- end
- specify "#import should a single insert statement" do
- @ds.import([:x, :y], [[1, 2], [3, 4]])
- check_sqls do
- @db.sqls.should == ['BEGIN', 'INSERT INTO "test" ("x", "y") VALUES (1, 2), (3, 4)', 'COMMIT']
- end
- @ds.all.should == [{:x=>1, :y=>2}, {:x=>3, :y=>4}]
- end
- specify "#import should work correctly when returning primary keys" do
- @ds.import([:x, :y], [[1, 2], [3, 4]], :return=>:primary_key).should == [1, 3]
- @ds.all.should == [{:x=>1, :y=>2}, {:x=>3, :y=>4}]
- end
- specify "#import should work correctly when returning primary keys with :slice option" do
- @ds.import([:x, :y], [[1, 2], [3, 4]], :return=>:primary_key, :slice=>1).should == [1, 3]
- @ds.all.should == [{:x=>1, :y=>2}, {:x=>3, :y=>4}]
- end
- specify "#import should work correctly with an arbitrary returning value" do
- @ds.returning(:y, :x).import([:x, :y], [[1, 2], [3, 4]]).should == [{:y=>2, :x=>1}, {:y=>4, :x=>3}]
- @ds.all.should == [{:x=>1, :y=>2}, {:x=>3, :y=>4}]
- end
- end
- describe "Postgres::Dataset#insert" do
- before do
- @db = POSTGRES_DB
- @db.create_table!(:test5){primary_key :xid; Integer :value}
- @db.sqls.clear
- @ds = @db[:test5]
- end
- after do
- @db.drop_table?(:test5)
- end
- specify "should work with static SQL" do
- @ds.with_sql('INSERT INTO test5 (value) VALUES (10)').insert.should == nil
- @db['INSERT INTO test5 (value) VALUES (20)'].insert.should == nil
- @ds.all.should == [{:xid=>1, :value=>10}, {:xid=>2, :value=>20}]
- end
- specify "should insert correctly if using a column array and a value array" do
- @ds.insert([:value], [10]).should == 1
- @ds.all.should == [{:xid=>1, :value=>10}]
- end
- specify "should use INSERT RETURNING" do
- @ds.insert(:value=>10).should == 1
- check_sqls do
- @db.sqls.last.should == 'INSERT INTO "test5" ("value") VALUES (10) RETURNING "xid"'
- end
- end
- specify "should have insert_select insert the record and return the inserted record" do
- h = @ds.insert_select(:value=>10)
- h[:value].should == 10
- @ds.first(:xid=>h[:xid])[:value].should == 10
- end
- specify "should correctly return the inserted record's primary key value" do
- value1 = 10
- id1 = @ds.insert(:value=>value1)
- @ds.first(:xid=>id1)[:value].should == value1
- value2 = 20
- id2 = @ds.insert(:value=>value2)
- @ds.first(:xid=>id2)[:value].should == value2
- end
- specify "should return nil if the table has no primary key" do
- @db.create_table!(:test5){String :name; Integer :value}
- @ds.delete
- @ds.insert(:name=>'a').should == nil
- end
- end
- describe "Postgres::Database schema qualified tables" do
- before do
- POSTGRES_DB << "CREATE SCHEMA schema_test"
- POSTGRES_DB.instance_variable_set(:@primary_keys, {})
- POSTGRES_DB.instance_variable_set(:@primary_key_sequences, {})
- end
- after do
- POSTGRES_DB << "DROP SCHEMA schema_test CASCADE"
- POSTGRES_DB.default_schema = nil
- end
- specify "should be able to create, drop, select and insert into tables in a given schema" do
- POSTGRES_DB.create_table(:schema_test__schema_test){primary_key :i}
- POSTGRES_DB[:schema_test__schema_test].first.should == nil
- POSTGRES_DB[:schema_test__schema_test].insert(:i=>1).should == 1
- POSTGRES_DB[:schema_test__schema_test].first.should == {:i=>1}
- POSTGRES_DB.from(Sequel.lit('schema_test.schema_test')).first.should == {:i=>1}
- POSTGRES_DB.drop_table(:schema_test__schema_test)
- POSTGRES_DB.create_table(Sequel.qualify(:schema_test, :schema_test)){integer :i}
- POSTGRES_DB[:schema_test__schema_test].first.should == nil
- POSTGRES_DB.from(Sequel.lit('schema_test.schema_test')).first.should == nil
- POSTGRES_DB.drop_table(Sequel.qualify(:schema_test, :schema_test))
- end
- specify "#tables should not include tables in a default non-public schema" do
- POSTGRES_DB.create_table(:schema_test__schema_test){integer :i}
- POSTGRES_DB.tables.should include(:schema_test)
- POSTGRES_DB.tables.should_not include(:pg_am)
- POSTGRES_DB.tables.should_not include(:domain_udt_usage)
- end
- specify "#tables should return tables in the schema provided by the :schema argument" do
- POSTGRES_DB.create_table(:schema_test__schema_test){integer :i}
- POSTGRES_DB.tables(:schema=>:schema_test).should == [:schema_test]
- end
- specify "#schema should not include columns from tables in a default non-public schema" do
- POSTGRES_DB.create_table(:schema_test__domains){integer :i}
- sch = POSTGRES_DB.schema(:domains)
- cs = sch.map{|x| x.first}
- cs.should include(:i)
- cs.should_not include(:data_type)
- end
- specify "#schema should only include columns from the table in the given :schema argument" do
- POSTGRES_DB.create_table!(:domains){integer :d}
- POSTGRES_DB.create_table(:schema_test__domains){integer :i}
- sch = POSTGRES_DB.schema(:domains, :schema=>:schema_test)
- cs = sch.map{|x| x.first}
- cs.should include(:i)
- cs.should_not include(:d)
- POSTGRES_DB.drop_table(:domains)
- end
- specify "#schema should raise an exception if columns from tables in two separate schema are returned" do
- POSTGRES_DB.create_table!(:public__domains){integer :d}
- POSTGRES_DB.create_table(:schema_test__domains){integer :i}
- begin
- proc{POSTGRES_DB.schema(:domains)}.should raise_error(Sequel::Error)
- POSTGRES_DB.schema(:public__domains).map{|x| x.first}.should == [:d]
- POSTGRES_DB.schema(:schema_test__domains).map{|x| x.first}.should == [:i]
- ensure
- POSTGRES_DB.drop_table?(:public__domains)
- end
- end
- specify "#table_exists? should see if the table is in a given schema" do
- POSTGRES_DB.create_table(:schema_test__schema_test){integer :i}
- POSTGRES_DB.table_exists?(:schema_test__schema_test).should == true
- end
- specify "should be able to get primary keys for tables in a given schema" do
- POSTGRES_DB.create_table(:schema_test__schema_test){primary_key :i}
- POSTGRES_DB.primary_key(:schema_test__schema_test).should == 'i'
- end
- specify "should be able to get serial sequences for tables in a given schema" do
- POSTGRES_DB.create_table(:schema_test__schema_test){primary_key :i}
- POSTGRES_DB.primary_key_sequence(:schema_test__schema_test).should == '"schema_test"."schema_test_i_seq"'
- end
- specify "should be able to get serial sequences for tables that have spaces in the name in a given schema" do
- POSTGRES_DB.create_table(:"schema_test__schema test"){primary_key :i}
- POSTGRES_DB.primary_key_sequence(:"schema_test__schema test").should == '"schema_test"."schema test_i_seq"'
- end
- specify "should be able to get custom sequences for tables in a given schema" do
- POSTGRES_DB << "CREATE SEQUENCE schema_test.kseq"
- POSTGRES_DB.create_table(:schema_test__schema_test){integer :j; primary_key :k, :type=>:integer, :default=>Sequel.lit("nextval('schema_test.kseq'::regclass)")}
- POSTGRES_DB.primary_key_sequence(:schema_test__schema_test).should == '"schema_test".kseq'
- end
- specify "should be able to get custom sequences for tables that have spaces in the name in a given schema" do
- POSTGRES_DB << "CREATE SEQUENCE schema_test.\"ks eq\""
- POSTGRES_DB.create_table(:"schema_test__schema test"){integer :j; primary_key :k, :type=>:integer, :default=>Sequel.lit("nextval('schema_test.\"ks eq\"'::regclass)")}
- POSTGRES_DB.primary_key_sequence(:"schema_test__schema test").should == '"schema_test"."ks eq"'
- end
- specify "#default_schema= should change the default schema used from public" do
- POSTGRES_DB.create_table(:schema_test__schema_test){primary_key :i}
- POSTGRES_DB.default_schema = :schema_test
- POSTGRES_DB.table_exists?(:schema_test).should == true
- POSTGRES_DB.tables.should == [:schema_test]
- POSTGRES_DB.primary_key(:schema_test__schema_test).should == 'i'
- POSTGRES_DB.primary_key_sequence(:schema_test__schema_test).should == '"schema_test"."schema_test_i_seq"'
- end
- end
- describe "Postgres::Database schema qualified tables and eager graphing" do
- before(:all) do
- @db = POSTGRES_DB
- @db.run "DROP SCHEMA s CASCADE" rescue nil
- @db.run "CREATE SCHEMA s"
- @db.create_table(:s__bands){primary_key :id; String :name}
- @db.create_table(:s__albums){primary_key :id; String :name; foreign_key :band_id, :s__bands}
- @db.create_table(:s__tracks){primary_key :id; String :name; foreign_key :album_id, :s__albums}
- @db.create_table(:s__members){primary_key :id; String :name; foreign_key :band_id, :s__bands}
- @Band = Class.new(Sequel::Model(:s__bands))
- @Album = Class.new(Sequel::Model(:s__albums))
- @Track = Class.new(Sequel::Model(:s__tracks))
- @Member = Class.new(Sequel::Model(:s__members))
- def @Band.name; :Band; end
- def @Album.name; :Album; end
- def @Track.name; :Track; end
- def @Member.name; :Member; end
- @Band.one_to_many :albums, :class=>@Album, :order=>:name
- @Band.one_to_many :members, :class=>@Member, :order=>:name
- @Album.many_to_one :band, :class=>@Band, :order=>:name
- @Album.one_to_many :tracks, :class=>@Track, :order=>:name
- @Track.many_to_one :album, :class=>@Album, :order=>:name
- @Member.many_to_one :band, :class=>@Band, :order=>:name
- @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
- @Band.many_to_many :tracks, :class=>@Track, :join_table=>:s__albums, :right_key=>:id, :right_primary_key=>:album_id, :order=>:name
- @b1 = @Band.create(:name=>"BM")
- @b2 = @Band.create(:name=>"J")
- @a1 = @Album.create(:name=>"BM1", :band=>@b1)
- @a2 = @Album.create(:name=>"BM2", :band=>@b1)
- @a3 = @Album.create(:name=>"GH", :band=>@b2)
- @a4 = @Album.create(:name=>"GHL", :band=>@b2)
- @t1 = @Track.create(:name=>"BM1-1", :album=>@a1)
- @t2 = @Track.create(:name=>"BM1-2", :album=>@a1)
- @t3 = @Track.create(:name=>"BM2-1", :album=>@a2)
- @t4 = @Track.create(:name=>"BM2-2", :album=>@a2)
- @m1 = @Member.create(:name=>"NU", :band=>@b1)
- @m2 = @Member.create(:name=>"TS", :band=>@b1)
- @m3 = @Member.create(:name=>"NS", :band=>@b2)
- @m4 = @Member.create(:name=>"JC", :band=>@b2)
- end
- after(:all) do
- @db.run "DROP SCHEMA s CASCADE"
- end
- specify "should return all eager graphs correctly" do
- bands = @Band.order(:bands__name).eager_graph(:albums).all
- bands.should == [@b1, @b2]
- bands.map{|x| x.albums}.should == [[@a1, @a2], [@a3, @a4]]
- bands = @Band.order(:bands__name).eager_graph(:albums=>:tracks).all
- bands.should == [@b1, @b2]
- bands.map{|x| x.albums}.should == [[@a1, @a2], [@a3, @a4]]
- bands.map{|x| x.albums.map{|y| y.tracks}}.should == [[[@t1, @t2], [@t3, @t4]], [[], []]]
- bands = @Band.order(:bands__name).eager_graph({:albums=>:tracks}, :members).all
- bands.should == [@b1, @b2]
- bands.map{|x| x.albums}.should == [[@a1, @a2], [@a3, @a4]]
- bands.map{|x| x.albums.map{|y| y.tracks}}.should == [[[@t1, @t2], [@t3, @t4]], [[], []]]
- bands.map{|x| x.members}.should == [[@m1, @m2], [@m4, @m3]]
- end
- specify "should have eager graphs work with previous joins" do
- bands = @Band.order(:bands__name).select_all(:s__bands).join(:s__members, :band_id=>:id).from_self(:alias=>:bands0).eager_graph(:albums=>:tracks).all
- bands.should == [@b1, @b2]
- bands.map{|x| x.albums}.should == [[@a1, @a2], [@a3, @a4]]
- bands.map{|x| x.albums.map{|y| y.tracks}}.should == [[[@t1, @t2], [@t3, @t4]], [[], []]]
- end
- specify "should have eager graphs work with joins with the same tables" do
- bands = @Band.order(:bands__name).select_all(:s__bands).join(:s__members, :band_id=>:id).eager_graph({:albums=>:tracks}, :members).all
- bands.should == [@b1, @b2]
- bands.map{|x| x.albums}.should == [[@a1, @a2], [@a3, @a4]]
- bands.map{|x| x.albums.map{|y| y.tracks}}.should == [[[@t1, @t2], [@t3, @t4]], [[], []]]
- bands.map{|x| x.members}.should == [[@m1, @m2], [@m4, @m3]]
- end
- specify "should have eager graphs work with self referential associations" do
- bands = @Band.order(:bands__name).eager_graph(:tracks=>{:album=>:band}).all
- bands.should == [@b1, @b2]
- bands.map{|x| x.tracks}.should == [[@t1, @t2, @t3, @t4], []]
- bands.map{|x| x.tracks.map{|y| y.album}}.should == [[@a1, @a1, @a2, @a2], []]
- bands.map{|x| x.tracks.map{|y| y.album.band}}.should == [[@b1, @b1, @b1, @b1], []]
- members = @Member.order(:members__name).eager_graph(:members).all
- members.should == [@m4, @m3, @m1, @m2]
- members.map{|x| x.members}.should == [[@m4, @m3], [@m4, @m3], [@m1, @m2], [@m1, @m2]]
- members = @Member.order(:members__name).eager_graph(:band, :members=>:band).all
- members.should == [@m4, @m3, @m1, @m2]
- members.map{|x| x.band}.should == [@b2, @b2, @b1, @b1]
- members.map{|x| x.members}.should == [[@m4, @m3], [@m4, @m3], [@m1, @m2], [@m1, @m2]]
- members.map{|x| x.members.map{|y| y.band}}.should == [[@b2, @b2], [@b2, @b2], [@b1, @b1], [@b1, @b1]]
- end
- specify "should have eager graphs work with a from_self dataset" do
- bands = @Band.order(:bands__name).from_self.eager_graph(:tracks=>{:album=>:band}).all
- bands.should == [@b1, @b2]
- bands.map{|x| x.tracks}.should == [[@t1, @t2, @t3, @t4], []]
- bands.map{|x| x.tracks.map{|y| y.album}}.should == [[@a1, @a1, @a2, @a2], []]
- bands.map{|x| x.tracks.map{|y| y.album.band}}.should == [[@b1, @b1, @b1, @b1], []]
- end
- specify "should have eager graphs work with different types of aliased from tables" do
- bands = @Band.order(:tracks__name).from(:s__bands___tracks).eager_graph(:tracks).all
- bands.should == [@b1, @b2]
- bands.map{|x| x.tracks}.should == [[@t1, @t2, @t3, @t4], []]
- bands = @Band.order(:tracks__name).from(Sequel.expr(:s__bands).as(:tracks)).eager_graph(:tracks).all
- bands.should == [@b1, @b2]
- bands.map{|x| x.tracks}.should == [[@t1, @t2, @t3, @t4], []]
- bands = @Band.order(:tracks__name).from(Sequel.expr(:s__bands).as(Sequel.identifier(:tracks))).eager_graph(:tracks).all
- bands.should == [@b1, @b2]
- bands.map{|x| x.tracks}.should == [[@t1, @t2, @t3, @t4], []]
- bands = @Band.order(:tracks__name).from(Sequel.expr(:s__bands).as('tracks')).eager_graph(:tracks).all
- bands.should == [@b1, @b2]
- bands.map{|x| x.tracks}.should == [[@t1, @t2, @t3, @t4], []]
- end
- specify "should have eager graphs work with join tables with aliases" do
- bands = @Band.order(:bands__name).eager_graph(:members).join(:s__albums___tracks, :band_id=>Sequel.qualify(:s__bands, :id)).eager_graph(:albums=>:tracks).all
- bands.should == [@b1, @b2]
- bands.map{|x| x.albums}.should == [[@a1, @a2], [@a3, @a4]]
- bands.map{|x| x.members}.should == [[@m1, @m2], [@m4, @m3]]
- 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
- bands.should == [@b1, @b2]
- bands.map{|x| x.albums}.should == [[@a1, @a2], [@a3, @a4]]
- bands.map{|x| x.members}.should == [[@m1, @m2], [@m4, @m3]]
- 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
- bands.should == [@b1, @b2]
- bands.map{|x| x.albums}.should == [[@a1, @a2], [@a3, @a4]]
- bands.map{|x| x.members}.should == [[@m1, @m2], [@m4, @m3]]
- 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
- bands.should == [@b1, @b2]
- bands.map{|x| x.albums}.should == [[@a1, @a2], [@a3, @a4]]
- bands.map{|x| x.members}.should == [[@m1, @m2], [@m4, @m3]]
- 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
- bands.should == [@b1, @b2]
- bands.map{|x| x.albums}.should == [[@a1, @a2], [@a3, @a4]]
- bands.map{|x| x.members}.should == [[@m1, @m2], [@m4, @m3]]
- 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
- bands.should == [@b1, @b2]
- bands.map{|x| x.albums}.should == [[@a1, @a2], [@a3, @a4]]
- bands.map{|x| x.members}.should == [[@m1, @m2], [@m4, @m3]]
- 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
- bands.should == [@b1, @b2]
- bands.map{|x| x.albums}.should == [[@a1, @a2], [@a3, @a4]]
- bands.map{|x| x.members}.should == [[@m1, @m2], [@m4, @m3]]
- end
- specify "should have eager graphs work with different types of qualified from tables" do
- bands = @Band.order(:bands__name).from(Sequel.qualify(:s, :bands)).eager_graph(:tracks).all
- bands.should == [@b1, @b2]
- bands.map{|x| x.tracks}.should == [[@t1, @t2, @t3, @t4], []]
- bands = @Band.order(:bands__name).from(Sequel.identifier(:bands).qualify(:s)).eager_graph(:tracks).all
- bands.should == [@b1, @b2]
- bands.map{|x| x.tracks}.should == [[@t1, @t2, @t3, @t4], []]
- bands = @Band.order(:bands__name).from(Sequel::SQL::QualifiedIdentifier.new(:s, 'bands')).eager_graph(:tracks).all
- bands.should == [@b1, @b2]
- bands.map{|x| x.tracks}.should == [[@t1, @t2, @t3, @t4], []]
- end
- end
- if POSTGRES_DB.server_version >= 80300
- describe "PostgreSQL tsearch2" do
- before(:all) do
- POSTGRES_DB.create_table! :test6 do
- text :title
- text :body
- full_text_index [:title, :body]
- end
- @ds = POSTGRES_DB[:test6]
- end
- after do
- POSTGRES_DB[:test6].delete
- end
- after(:all) do
- POSTGRES_DB.drop_table?(:test6)
- end
- specify "should search by indexed column" do
- record = {:title => "oopsla conference", :body => "test"}
- @ds << record
- @ds.full_text_search(:title, "oopsla").all.should include(record)
- end
- specify "should join multiple coumns with spaces to search by last words in row" do
- record = {:title => "multiple words", :body => "are easy to search"}
- @ds << record
- @ds.full_text_search([:title, :body], "words").all.should include(record)
- end
- specify "should return rows with a NULL in one column if a match in another column" do
- record = {:title => "multiple words", :body =>nil}
- @ds << record
- @ds.full_text_search([:title, :body], "words").all.should include(record)
- end
- end
- end
- if POSTGRES_DB.dataset.supports_window_functions?
- describe "Postgres::Dataset named windows" do
- before do
- @db = POSTGRES_DB
- @db.create_table!(:i1){Integer :id; Integer :group_id; Integer :amount}
- @ds = @db[:i1].order(:id)
- @ds.insert(:id=>1, :group_id=>1, :amount=>1)
- @ds.insert(:id=>2, :group_id=>1, :amount=>10)
- @ds.insert(:id=>3, :group_id=>1, :amount=>100)
- @ds.insert(:id=>4, :group_id=>2, :amount=>1000)
- @ds.insert(:id=>5, :group_id=>2, :amount=>10000)
- @ds.insert(:id=>6, :group_id=>2, :amount=>100000)
- end
- after do
- @db.drop_table?(:i1)
- end
- specify "should give correct results for window functions" do
- @ds.window(:win, :partition=>:group_id, :order=>:id).select(:id){sum(:over, :args=>amount, :window=>win){}}.all.should ==
- [{:sum=>1, :id=>1}, {:sum=>11, :id=>2}, {:sum=>111, :id=>3}, {:sum=>1000, :id=>4}, {:sum=>11000, :id=>5}, {:sum=>111000, :id=>6}]
- @ds.window(:win, :partition=>:group_id).select(:id){sum(:over, :args=>amount, :window=>win, :order=>id){}}.all.should ==
- [{:sum=>1, :id=>1}, {:sum=>11, :id=>2}, {:sum=>111, :id=>3}, {:sum=>1000, :id=>4}, {:sum=>11000, :id=>5}, {:sum=>111000, :id=>6}]
- @ds.window(:win, {}).select(:id){sum(:over, :args=>amount, :window=>:win, :order=>id){}}.all.should ==
- [{:sum=>1, :id=>1}, {:sum=>11, :id=>2}, {:sum=>111, :id=>3}, {:sum=>1111, :id=>4}, {:sum=>11111, :id=>5}, {:sum=>111111, :id=>6}]
- @ds.window(:win, :partition=>:group_id).select(:id){sum(:over, :args=>amount, :window=>:win, :order=>id, :frame=>:all){}}.all.should ==
- [{:sum=>111, :id=>1}, {:sum=>111, :id=>2}, {:sum=>111, :id=>3}, {:sum=>111000, :id=>4}, {:sum=>111000, :id=>5}, {:sum=>111000, :id=>6}]
- end
- end
- end
- describe "Postgres::Database functions, languages, schemas, and triggers" do
- before do
- @d = POSTGRES_DB
- end
- after do
- @d.drop_function('tf', :if_exists=>true, :cascade=>true)
- @d.drop_function('tf', :if_exists=>true, :cascade=>true, :args=>%w'integer integer')
- @d.drop_language(:plpgsql, :if_exists=>true, :cascade=>true) if @d.server_version < 90000
- @d.drop_schema(:sequel, :if_exists=>true, :cascade=>true)
- @d.drop_table?(:test)
- end
- specify "#create_function and #drop_function should create and drop functions" do
- proc{@d['SELECT tf()'].all}.should raise_error(Sequel::DatabaseError)
- args = ['tf', 'SELECT 1', {:returns=>:integer}]
- @d.send(:create_function_sql, *args).should =~ /\A\s*CREATE FUNCTION tf\(\)\s+RETURNS integer\s+LANGUAGE SQL\s+AS 'SELECT 1'\s*\z/
- @d.create_function(*args)
- rows = @d['SELECT tf()'].all.should == [{:tf=>1}]
- @d.send(:drop_function_sql, 'tf').should == 'DROP FUNCTION tf()'
- @d.drop_function('tf')
- proc{@d['SELECT tf()'].all}.should raise_error(Sequel::DatabaseError)
- end
- specify "#create_function and #drop_function should support options" do
- 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'}}]
- @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/
- @d.create_function(*args)
- # Make sure replace works
- @d.create_function(*args)
- rows = @d['SELECT tf(1, 2)'].all.should == [{:tf=>3}]
- args = ['tf', {:if_exists=>true, :cascade=>true, :args=>[[:integer, :a], :integer]}]
- @d.send(:drop_function_sql,*args).should == 'DROP FUNCTION IF EXISTS tf(a integer, integer) CASCADE'
- @d.drop_function(*args)
- # Make sure if exists works
- @d.drop_function(*args)
- end
- specify "#create_language and #drop_language should create and drop languages" do
- @d.send(:create_language_sql, :plpgsql).should == 'CREATE LANGUAGE plpgsql'
- @d.create_language(:plp…
Large files files are truncated, but you can click here to view the full file