upsert 1.1.6 → 1.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +7 -0
 - data/README.md +1 -1
 - data/lib/upsert.rb +11 -3
 - data/lib/upsert/merge_function.rb +3 -3
 - data/lib/upsert/merge_function/postgresql.rb +8 -0
 - data/lib/upsert/version.rb +1 -1
 - data/spec/database_functions_spec.rb +26 -0
 - data/spec/hstore_spec.rb +16 -3
 - data/upsert.gemspec +1 -1
 - metadata +4 -4
 
    
        data/CHANGELOG
    CHANGED
    
    
    
        data/README.md
    CHANGED
    
    | 
         @@ -64,7 +64,7 @@ Pet.upsert({:name => 'Jerry'}, :breed => 'beagle') 
     | 
|
| 
       64 
64 
     | 
    
         
             
            Pull requests for any of these would be greatly appreciated:
         
     | 
| 
       65 
65 
     | 
    
         | 
| 
       66 
66 
     | 
    
         
             
            1. Cache JDBC PreparedStatement objects.
         
     | 
| 
       67 
     | 
    
         
            -
            1.  
     | 
| 
      
 67 
     | 
    
         
            +
            1. Allow "true" upserting like `upsert.row({name: 'Jerry'}, counter: Upsert.sql('counter+1'))`
         
     | 
| 
       68 
68 
     | 
    
         
             
            1. Sanity check my three benchmarks (four if you include activerecord-import on MySQL). Do they accurately represent optimized alternatives?
         
     | 
| 
       69 
69 
     | 
    
         
             
            1. Provide `require 'upsert/debug'` that will make sure you are selecting on columns that have unique indexes
         
     | 
| 
       70 
70 
     | 
    
         
             
            1. Test that `Upsert` instances accept arbitrary columns, even within a batch, which is what people probably expect.
         
     | 
    
        data/lib/upsert.rb
    CHANGED
    
    | 
         @@ -67,8 +67,8 @@ class Upsert 
     | 
|
| 
       67 
67 
     | 
    
         
             
                #     upsert.row({:name => 'Jerry'}, :breed => 'beagle')
         
     | 
| 
       68 
68 
     | 
    
         
             
                #     upsert.row({:name => 'Pierre'}, :breed => 'tabby')
         
     | 
| 
       69 
69 
     | 
    
         
             
                #   end
         
     | 
| 
       70 
     | 
    
         
            -
                def batch(connection, table_name)
         
     | 
| 
       71 
     | 
    
         
            -
                  upsert = new connection, table_name
         
     | 
| 
      
 70 
     | 
    
         
            +
                def batch(connection, table_name, options = {})
         
     | 
| 
      
 71 
     | 
    
         
            +
                  upsert = new connection, table_name, options
         
     | 
| 
       72 
72 
     | 
    
         
             
                  yield upsert
         
     | 
| 
       73 
73 
     | 
    
         
             
                end
         
     | 
| 
       74 
74 
     | 
    
         | 
| 
         @@ -172,9 +172,16 @@ class Upsert 
     | 
|
| 
       172 
172 
     | 
    
         
             
              # @private
         
     | 
| 
       173 
173 
     | 
    
         
             
              attr_reader :adapter
         
     | 
| 
       174 
174 
     | 
    
         | 
| 
      
 175 
     | 
    
         
            +
              # @private
         
     | 
| 
      
 176 
     | 
    
         
            +
              def assume_function_exists?
         
     | 
| 
      
 177 
     | 
    
         
            +
                @assume_function_exists
         
     | 
| 
      
 178 
     | 
    
         
            +
              end
         
     | 
| 
      
 179 
     | 
    
         
            +
             
     | 
| 
       175 
180 
     | 
    
         
             
              # @param [Mysql2::Client,Sqlite3::Database,PG::Connection,#metal] connection A supported database connection.
         
     | 
| 
       176 
181 
     | 
    
         
             
              # @param [String,Symbol] table_name The name of the table into which you will be upserting.
         
     | 
| 
       177 
     | 
    
         
            -
               
     | 
| 
      
 182 
     | 
    
         
            +
              # @param [Hash] options
         
     | 
| 
      
 183 
     | 
    
         
            +
              # @option options [TrueClass,FalseClass] :assume_function_exists (false) Assume the function has already been defined correctly by another process.
         
     | 
| 
      
 184 
     | 
    
         
            +
              def initialize(connection, table_name, options = {})
         
     | 
| 
       178 
185 
     | 
    
         
             
                @table_name = table_name.to_s
         
     | 
| 
       179 
186 
     | 
    
         
             
                metal = Upsert.metal connection
         
     | 
| 
       180 
187 
     | 
    
         
             
                @flavor = Upsert.flavor metal
         
     | 
| 
         @@ -185,6 +192,7 @@ class Upsert 
     | 
|
| 
       185 
192 
     | 
    
         
             
                end
         
     | 
| 
       186 
193 
     | 
    
         
             
                @connection = Connection.const_get(adapter).new self, metal
         
     | 
| 
       187 
194 
     | 
    
         
             
                @merge_function_class = MergeFunction.const_get adapter
         
     | 
| 
      
 195 
     | 
    
         
            +
                @assume_function_exists = options.fetch :assume_function_exists, false
         
     | 
| 
       188 
196 
     | 
    
         
             
              end
         
     | 
| 
       189 
197 
     | 
    
         | 
| 
       190 
198 
     | 
    
         
             
              # Upsert a row given a selector and a setter.
         
     | 
| 
         @@ -34,7 +34,7 @@ class Upsert 
     | 
|
| 
       34 
34 
     | 
    
         
             
                    selector_keys = row.selector.keys
         
     | 
| 
       35 
35 
     | 
    
         
             
                    setter_keys = row.setter.keys
         
     | 
| 
       36 
36 
     | 
    
         
             
                    key = [controller.table_name, selector_keys, setter_keys]
         
     | 
| 
       37 
     | 
    
         
            -
                    @lookup[key] ||= new(controller, selector_keys, setter_keys)
         
     | 
| 
      
 37 
     | 
    
         
            +
                    @lookup[key] ||= new(controller, selector_keys, setter_keys, controller.assume_function_exists?)
         
     | 
| 
       38 
38 
     | 
    
         
             
                  end
         
     | 
| 
       39 
39 
     | 
    
         
             
                end
         
     | 
| 
       40 
40 
     | 
    
         | 
| 
         @@ -42,11 +42,11 @@ class Upsert 
     | 
|
| 
       42 
42 
     | 
    
         
             
                attr_reader :selector_keys
         
     | 
| 
       43 
43 
     | 
    
         
             
                attr_reader :setter_keys
         
     | 
| 
       44 
44 
     | 
    
         | 
| 
       45 
     | 
    
         
            -
                def initialize(controller, selector_keys, setter_keys)
         
     | 
| 
      
 45 
     | 
    
         
            +
                def initialize(controller, selector_keys, setter_keys, assume_function_exists)
         
     | 
| 
       46 
46 
     | 
    
         
             
                  @controller = controller
         
     | 
| 
       47 
47 
     | 
    
         
             
                  @selector_keys = selector_keys
         
     | 
| 
       48 
48 
     | 
    
         
             
                  @setter_keys = setter_keys
         
     | 
| 
       49 
     | 
    
         
            -
                  create!
         
     | 
| 
      
 49 
     | 
    
         
            +
                  create! unless assume_function_exists
         
     | 
| 
       50 
50 
     | 
    
         
             
                end
         
     | 
| 
       51 
51 
     | 
    
         | 
| 
       52 
52 
     | 
    
         
             
                def name
         
     | 
| 
         @@ -53,6 +53,7 @@ class Upsert 
     | 
|
| 
       53 
53 
     | 
    
         
             
                    Upsert.logger.info "[upsert] Creating or replacing database function #{name.inspect} on table #{table_name.inspect} for selector #{selector_keys.map(&:inspect).join(', ')} and setter #{setter_keys.map(&:inspect).join(', ')}"
         
     | 
| 
       54 
54 
     | 
    
         
             
                    selector_column_definitions = column_definitions.select { |cd| selector_keys.include?(cd.name) }
         
     | 
| 
       55 
55 
     | 
    
         
             
                    setter_column_definitions = column_definitions.select { |cd| setter_keys.include?(cd.name) }
         
     | 
| 
      
 56 
     | 
    
         
            +
                    first_try = true
         
     | 
| 
       56 
57 
     | 
    
         
             
                    connection.execute(%{
         
     | 
| 
       57 
58 
     | 
    
         
             
                      CREATE OR REPLACE FUNCTION #{name}(#{(selector_column_definitions.map(&:to_selector_arg) + setter_column_definitions.map(&:to_setter_arg)).join(', ')}) RETURNS VOID AS
         
     | 
| 
       58 
59 
     | 
    
         
             
                      $$
         
     | 
| 
         @@ -86,6 +87,13 @@ class Upsert 
     | 
|
| 
       86 
87 
     | 
    
         
             
                      $$
         
     | 
| 
       87 
88 
     | 
    
         
             
                      LANGUAGE plpgsql;
         
     | 
| 
       88 
89 
     | 
    
         
             
                    })
         
     | 
| 
      
 90 
     | 
    
         
            +
                  rescue
         
     | 
| 
      
 91 
     | 
    
         
            +
                    if first_try and $!.message =~ /tuple concurrently updated/
         
     | 
| 
      
 92 
     | 
    
         
            +
                      first_try = false
         
     | 
| 
      
 93 
     | 
    
         
            +
                      retry
         
     | 
| 
      
 94 
     | 
    
         
            +
                    else
         
     | 
| 
      
 95 
     | 
    
         
            +
                      raise $!
         
     | 
| 
      
 96 
     | 
    
         
            +
                    end
         
     | 
| 
       89 
97 
     | 
    
         
             
                  end
         
     | 
| 
       90 
98 
     | 
    
         
             
                end
         
     | 
| 
       91 
99 
     | 
    
         
             
              end
         
     | 
    
        data/lib/upsert/version.rb
    CHANGED
    
    
| 
         @@ -2,6 +2,7 @@ require 'spec_helper' 
     | 
|
| 
       2 
2 
     | 
    
         
             
            require 'stringio'
         
     | 
| 
       3 
3 
     | 
    
         
             
            describe Upsert do
         
     | 
| 
       4 
4 
     | 
    
         
             
              describe 'database functions' do
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
       5 
6 
     | 
    
         
             
                it "re-uses merge functions across connections" do
         
     | 
| 
       6 
7 
     | 
    
         
             
                  begin
         
     | 
| 
       7 
8 
     | 
    
         
             
                    io = StringIO.new
         
     | 
| 
         @@ -30,5 +31,30 @@ describe Upsert do 
     | 
|
| 
       30 
31 
     | 
    
         
             
                    Upsert.logger = old_logger
         
     | 
| 
       31 
32 
     | 
    
         
             
                  end
         
     | 
| 
       32 
33 
     | 
    
         
             
                end
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
                it "assumes function exists if told to" do
         
     | 
| 
      
 36 
     | 
    
         
            +
                  begin
         
     | 
| 
      
 37 
     | 
    
         
            +
                    io = StringIO.new
         
     | 
| 
      
 38 
     | 
    
         
            +
                    old_logger = Upsert.logger
         
     | 
| 
      
 39 
     | 
    
         
            +
                    Upsert.logger = Logger.new io, Logger::INFO
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                    # clear
         
     | 
| 
      
 42 
     | 
    
         
            +
                    Upsert.clear_database_functions($conn_factory.new_connection)
         
     | 
| 
      
 43 
     | 
    
         
            +
                    
         
     | 
| 
      
 44 
     | 
    
         
            +
                    # tries, "went missing", creates
         
     | 
| 
      
 45 
     | 
    
         
            +
                    Upsert.new($conn_factory.new_connection, :pets, :assume_function_exists => true).row :name => 'hello'
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                    # just works
         
     | 
| 
      
 48 
     | 
    
         
            +
                    Upsert.new($conn_factory.new_connection, :pets, :assume_function_exists => true).row :name => 'hello'
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
                    io.rewind
         
     | 
| 
      
 51 
     | 
    
         
            +
                    lines = io.read.split("\n")
         
     | 
| 
      
 52 
     | 
    
         
            +
                    lines.grep(/went missing/).length.should == 1
         
     | 
| 
      
 53 
     | 
    
         
            +
                    lines.grep(/Creating or replacing/).length.should == 1
         
     | 
| 
      
 54 
     | 
    
         
            +
                  ensure
         
     | 
| 
      
 55 
     | 
    
         
            +
                    Upsert.logger = old_logger
         
     | 
| 
      
 56 
     | 
    
         
            +
                  end
         
     | 
| 
      
 57 
     | 
    
         
            +
                end
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
       33 
59 
     | 
    
         
             
              end
         
     | 
| 
       34 
60 
     | 
    
         
             
            end if %w{ postgresql mysql }.include?(ENV['DB'])
         
     | 
    
        data/spec/hstore_spec.rb
    CHANGED
    
    | 
         @@ -1,10 +1,23 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # -*- encoding: utf-8 -*-
         
     | 
| 
       1 
2 
     | 
    
         
             
            require 'spec_helper'
         
     | 
| 
       2 
3 
     | 
    
         
             
            describe Upsert do
         
     | 
| 
       3 
4 
     | 
    
         
             
              describe 'hstore on pg' do
         
     | 
| 
      
 5 
     | 
    
         
            +
                require 'pg_hstore'
         
     | 
| 
      
 6 
     | 
    
         
            +
                Pet.connection.execute 'CREATE EXTENSION HSTORE'
         
     | 
| 
      
 7 
     | 
    
         
            +
                Pet.connection.execute "ALTER TABLE pets ADD COLUMN crazy HSTORE"
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                it "works for ugly text" do
         
     | 
| 
      
 10 
     | 
    
         
            +
                  upsert = Upsert.new $conn, :pets
         
     | 
| 
      
 11 
     | 
    
         
            +
                  uggy = <<-EOS
         
     | 
| 
      
 12 
     | 
    
         
            +
            {"results":[{"locations":[],"providedLocation":{"location":"3001 STRATTON WAY, MADISON, WI 53719 UNITED STATES"}}],"options":{"ignoreLatLngInput":true,"maxResults":1,"thumbMaps":false},"info":{"copyright":{"text":"© 2012 MapQuest, Inc.","imageUrl":"http://api.mqcdn.com/res/mqlogo.gif","imageAltText":"© 2012 MapQuest, Inc."},"statuscode":0,"messages":[]}}
         
     | 
| 
      
 13 
     | 
    
         
            +
            EOS
         
     | 
| 
      
 14 
     | 
    
         
            +
                  upsert.row({:name => 'Uggy'}, crazy: {uggy: uggy})
         
     | 
| 
      
 15 
     | 
    
         
            +
                  row = Pet.connection.select_one(%{SELECT crazy FROM pets WHERE name = 'Uggy'})
         
     | 
| 
      
 16 
     | 
    
         
            +
                  crazy = PgHstore.parse row['crazy']
         
     | 
| 
      
 17 
     | 
    
         
            +
                  crazy.should == { uggy: uggy }
         
     | 
| 
      
 18 
     | 
    
         
            +
                end
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
       4 
20 
     | 
    
         
             
                it "just works" do
         
     | 
| 
       5 
     | 
    
         
            -
                  require 'pg_hstore'
         
     | 
| 
       6 
     | 
    
         
            -
                  Pet.connection.execute 'CREATE EXTENSION HSTORE'
         
     | 
| 
       7 
     | 
    
         
            -
                  Pet.connection.execute "ALTER TABLE pets ADD COLUMN crazy HSTORE"
         
     | 
| 
       8 
21 
     | 
    
         
             
                  upsert = Upsert.new $conn, :pets
         
     | 
| 
       9 
22 
     | 
    
         | 
| 
       10 
23 
     | 
    
         
             
                  upsert.row({name: 'Bill'}, crazy: nil)
         
     | 
    
        data/upsert.gemspec
    CHANGED
    
    | 
         @@ -28,7 +28,7 @@ Gem::Specification.new do |gem| 
     | 
|
| 
       28 
28 
     | 
    
         
             
              gem.add_development_dependency 'yard'
         
     | 
| 
       29 
29 
     | 
    
         
             
              gem.add_development_dependency 'activerecord-import'
         
     | 
| 
       30 
30 
     | 
    
         
             
              gem.add_development_dependency 'pry'
         
     | 
| 
       31 
     | 
    
         
            -
              gem.add_development_dependency 'pg-hstore', ">=1.1. 
     | 
| 
      
 31 
     | 
    
         
            +
              gem.add_development_dependency 'pg-hstore', ">=1.1.3"
         
     | 
| 
       32 
32 
     | 
    
         | 
| 
       33 
33 
     | 
    
         
             
              unless RUBY_VERSION >= '1.9'
         
     | 
| 
       34 
34 
     | 
    
         
             
                gem.add_development_dependency 'orderedhash'
         
     | 
    
        metadata
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            --- !ruby/object:Gem::Specification
         
     | 
| 
       2 
2 
     | 
    
         
             
            name: upsert
         
     | 
| 
       3 
3 
     | 
    
         
             
            version: !ruby/object:Gem::Version
         
     | 
| 
       4 
     | 
    
         
            -
              version: 1.1. 
     | 
| 
      
 4 
     | 
    
         
            +
              version: 1.1.7
         
     | 
| 
       5 
5 
     | 
    
         
             
              prerelease: 
         
     | 
| 
       6 
6 
     | 
    
         
             
            platform: ruby
         
     | 
| 
       7 
7 
     | 
    
         
             
            authors:
         
     | 
| 
         @@ -9,7 +9,7 @@ authors: 
     | 
|
| 
       9 
9 
     | 
    
         
             
            autorequire: 
         
     | 
| 
       10 
10 
     | 
    
         
             
            bindir: bin
         
     | 
| 
       11 
11 
     | 
    
         
             
            cert_chain: []
         
     | 
| 
       12 
     | 
    
         
            -
            date:  
     | 
| 
      
 12 
     | 
    
         
            +
            date: 2013-01-15 00:00:00.000000000 Z
         
     | 
| 
       13 
13 
     | 
    
         
             
            dependencies:
         
     | 
| 
       14 
14 
     | 
    
         
             
            - !ruby/object:Gem::Dependency
         
     | 
| 
       15 
15 
     | 
    
         
             
              name: rspec-core
         
     | 
| 
         @@ -162,7 +162,7 @@ dependencies: 
     | 
|
| 
       162 
162 
     | 
    
         
             
                requirements:
         
     | 
| 
       163 
163 
     | 
    
         
             
                - - ! '>='
         
     | 
| 
       164 
164 
     | 
    
         
             
                  - !ruby/object:Gem::Version
         
     | 
| 
       165 
     | 
    
         
            -
                    version: 1.1. 
     | 
| 
      
 165 
     | 
    
         
            +
                    version: 1.1.3
         
     | 
| 
       166 
166 
     | 
    
         
             
              type: :development
         
     | 
| 
       167 
167 
     | 
    
         
             
              prerelease: false
         
     | 
| 
       168 
168 
     | 
    
         
             
              version_requirements: !ruby/object:Gem::Requirement
         
     | 
| 
         @@ -170,7 +170,7 @@ dependencies: 
     | 
|
| 
       170 
170 
     | 
    
         
             
                requirements:
         
     | 
| 
       171 
171 
     | 
    
         
             
                - - ! '>='
         
     | 
| 
       172 
172 
     | 
    
         
             
                  - !ruby/object:Gem::Version
         
     | 
| 
       173 
     | 
    
         
            -
                    version: 1.1. 
     | 
| 
      
 173 
     | 
    
         
            +
                    version: 1.1.3
         
     | 
| 
       174 
174 
     | 
    
         
             
            - !ruby/object:Gem::Dependency
         
     | 
| 
       175 
175 
     | 
    
         
             
              name: sqlite3
         
     | 
| 
       176 
176 
     | 
    
         
             
              requirement: !ruby/object:Gem::Requirement
         
     |