share_examples_for 'A DataObjects Adapter' do before :all do raise '+@adapter+ should be defined in before block' unless instance_variable_get('@adapter') @log = StringIO.new @original_logger = DataMapper.logger DataMapper.logger = DataMapper::Logger.new(@log, :debug) # set up the adapter after switching the logger so queries can be captured @adapter = DataMapper.setup(@adapter.name, @adapter.options) @jruby = !!(RUBY_PLATFORM =~ /java/) @postgres = defined?(DataMapper::Adapters::PostgresAdapter) && @adapter.kind_of?(DataMapper::Adapters::PostgresAdapter) @mysql = defined?(DataMapper::Adapters::MysqlAdapter) && @adapter.kind_of?(DataMapper::Adapters::MysqlAdapter) @sql_server = defined?(DataMapper::Adapters::SqlserverAdapter) && @adapter.kind_of?(DataMapper::Adapters::SqlserverAdapter) @oracle = defined?(DataMapper::Adapters::OracleAdapter) && @adapter.kind_of?(DataMapper::Adapters::OracleAdapter) end after :all do DataMapper.logger = @original_logger end def reset_log @log.truncate(0) @log.rewind end def log_output @log.rewind output = @log.read output.chomp! output.gsub!(/\A\s+~ \(\d+[\.,]?\d*\)\s+/, '') output.gsub!(/\Acom\.\w+\.jdbc\.JDBC4PreparedStatement@[^:]+:\s+/, '') if @jruby output.split($/) end def supports_default_values? @adapter.send(:supports_default_values?) end def supports_returning? @adapter.send(:supports_returning?) end describe '#create' do describe 'serial properties' do before :all do class ::Article include DataMapper::Resource property :id, Serial auto_migrate! end reset_log Article.create end it 'should not send NULL values' do statement = if @mysql /\AINSERT INTO `articles` \(\) VALUES \(\)\z/ elsif @oracle /\AINSERT INTO "ARTICLES" \("ID"\) VALUES \(DEFAULT\) RETURNING "ID"/ elsif supports_default_values? && supports_returning? /\AINSERT INTO "articles" DEFAULT VALUES RETURNING \"id\"\z/ elsif supports_default_values? /\AINSERT INTO "articles" DEFAULT VALUES\z/ else /\AINSERT INTO "articles" \(\) VALUES \(\)\z/ end log_output.first.should =~ statement end end describe 'properties without a default' do before :all do class ::Article include DataMapper::Resource property :id, Serial property :title, String auto_migrate! end reset_log Article.create(:id => 1) end it 'should not send NULL values' do regexp = if @mysql /^INSERT INTO `articles` \(`id`\) VALUES \(.{1,2}\)$/i elsif @sql_server /^SET IDENTITY_INSERT \"articles\" ON INSERT INTO "articles" \("id"\) VALUES \(.{1,2}\) SET IDENTITY_INSERT \"articles\" OFF $/i else /^INSERT INTO "articles" \("id"\) VALUES \(('.{1,2}'|.{1,2})\)$/i end log_output.first.should =~ regexp end end end describe '#select' do before :all do class ::Article include DataMapper::Resource property :name, String, :key => true property :author, String, :required => true auto_migrate! end @article_model = Article @article_model.create(:name => 'Learning DataMapper', :author => 'Dan Kubb') end describe 'when one field specified in SELECT statement' do before :all do @return = @adapter.select('SELECT name FROM articles') end it 'should return an Array' do @return.should be_kind_of(Array) end it 'should have a single result' do @return.size.should == 1 end it 'should return an Array of values' do @return.should == [ 'Learning DataMapper' ] end end describe 'when more than one field specified in SELECT statement' do before :all do @return = @adapter.select('SELECT name, author FROM articles') end it 'should return an Array' do @return.should be_kind_of(Array) end it 'should have a single result' do @return.size.should == 1 end it 'should return an Array of Struct objects' do @return.first.should be_kind_of(Struct) end it 'should return expected values' do @return.first.values.should == [ 'Learning DataMapper', 'Dan Kubb' ] end end end describe '#execute' do before :all do class ::Article include DataMapper::Resource property :name, String, :key => true property :author, String, :required => true auto_migrate! end @article_model = Article end before :all do @result = @adapter.execute('INSERT INTO articles (name, author) VALUES(?, ?)', 'Learning DataMapper', 'Dan Kubb') end it 'should return a DataObjects::Result' do @result.should be_kind_of(DataObjects::Result) end it 'should affect 1 row' do @result.affected_rows.should == 1 end it 'should not have an insert_id' do pending_if 'Inconsistent insert_id results', !(@postgres || @mysql || @oracle) do @result.insert_id.should be_nil end end end describe '#read' do before :all do class ::Article include DataMapper::Resource property :name, String, :key => true property :description, String, :required => false belongs_to :parent, self, :required => false has n, :children, self, :inverse => :parent auto_migrate! end class ::Publisher include DataMapper::Resource property :name, String, :key => true auto_migrate! end class ::Author include DataMapper::Resource property :name, String, :key => true belongs_to :article belongs_to :publisher auto_migrate! end @article_model = Article @publisher_model = Publisher @author_model = Author end describe 'with a raw query' do before :all do @article_model.create(:name => 'Test', :description => 'Description').should be_saved @article_model.create(:name => 'NoDescription').should be_saved @query = DataMapper::Query.new(@repository, @article_model, :conditions => [ 'description IS NOT NULL' ]) @return = @adapter.read(@query) end it 'should return an Array of Hashes' do @return.should be_kind_of(Array) @return.all? { |entry| entry.should be_kind_of(Hash) } end it 'should return expected values' do @return.should == [ { @article_model.properties[:name] => 'Test', @article_model.properties[:description] => 'Description', @article_model.properties[:parent_name] => nil } ] end end describe 'with a raw query with a bind value mismatch' do before :all do @article_model.create(:name => 'Test').should be_saved @query = DataMapper::Query.new(@repository, @article_model, :conditions => [ 'name IS NOT NULL', nil ]) end it 'should raise an error' do lambda { @adapter.read(@query) }.should raise_error(ArgumentError, 'Binding mismatch: 1 for 0') end end describe 'with a Collection bind value' do describe 'with an inclusion comparison' do before :all do 5.times do |index| @article_model.create(:name => "Test #{index}", :parent => @article_model.last).should be_saved end @parents = @article_model.all @query = DataMapper::Query.new(@repository, @article_model, :parent => @parents) @expected = @article_model.all[1, 4].map { |article| article.attributes(:property) } end describe 'that is not loaded' do before :all do reset_log @return = @adapter.read(@query) end it 'should return an Array of Hashes' do @return.should be_kind_of(Array) @return.all? { |entry| entry.should be_kind_of(Hash) } end it 'should return expected values' do @return.should == @expected end it 'should execute one subquery' do pending_if @mysql do log_output.size.should == 1 end end end describe 'that is loaded' do before :all do @parents.to_a # lazy load the collection end before :all do reset_log @return = @adapter.read(@query) end it 'should return an Array of Hashes' do @return.should be_kind_of(Array) @return.all? { |entry| entry.should be_kind_of(Hash) } end it 'should return expected values' do @return.should == @expected end it 'should execute one query' do log_output.size.should == 1 end end end describe 'with an negated inclusion comparison' do before :all do 5.times do |index| @article_model.create(:name => "Test #{index}", :parent => @article_model.last).should be_saved end @parents = @article_model.all @query = DataMapper::Query.new(@repository, @article_model, :parent.not => @parents) @expected = [] end describe 'that is not loaded' do before :all do reset_log @return = @adapter.read(@query) end it 'should return an Array of Hashes' do @return.should be_kind_of(Array) @return.all? { |entry| entry.should be_kind_of(Hash) } end it 'should return expected values' do @return.should == @expected end it 'should execute one subquery' do pending_if @mysql do log_output.size.should == 1 end end end describe 'that is loaded' do before :all do @parents.to_a # lazy load the collection end before :all do reset_log @return = @adapter.read(@query) end it 'should return an Array of Hashes' do @return.should be_kind_of(Array) @return.all? { |entry| entry.should be_kind_of(Hash) } end it 'should return expected values' do @return.should == @expected end it 'should execute one query' do log_output.size.should == 1 end end end end describe 'with a Query Path' do subject { @author_model.all(query).to_a } let(:article_name) { 'DataMapper Rocks!' } let(:publisher_name) { 'Unbiased Press' } let(:query) { { 'article.name' => article_name, 'publisher.name' => publisher_name } } before do @author = @author_model.first_or_create( :name => 'Dan Kubb', :article => { :name => article_name }, :publisher => { :name => publisher_name } ) end specify { expect { subject }.to_not raise_error } it { should == [ @author ] } end end end