upsert 2.9.10-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +19 -0
  3. data/.ruby-version +1 -0
  4. data/.standard.yml +1 -0
  5. data/.travis.yml +63 -0
  6. data/.yardopts +2 -0
  7. data/CHANGELOG +265 -0
  8. data/Gemfile +20 -0
  9. data/LICENSE +24 -0
  10. data/README.md +411 -0
  11. data/Rakefile +54 -0
  12. data/lib/upsert.rb +284 -0
  13. data/lib/upsert/active_record_upsert.rb +12 -0
  14. data/lib/upsert/binary.rb +8 -0
  15. data/lib/upsert/column_definition.rb +79 -0
  16. data/lib/upsert/column_definition/mysql.rb +24 -0
  17. data/lib/upsert/column_definition/postgresql.rb +66 -0
  18. data/lib/upsert/column_definition/sqlite3.rb +34 -0
  19. data/lib/upsert/connection.rb +37 -0
  20. data/lib/upsert/connection/Java_ComMysqlJdbc_JDBC4Connection.rb +31 -0
  21. data/lib/upsert/connection/Java_OrgPostgresqlJdbc_PgConnection.rb +33 -0
  22. data/lib/upsert/connection/Java_OrgSqlite_Conn.rb +17 -0
  23. data/lib/upsert/connection/Mysql2_Client.rb +76 -0
  24. data/lib/upsert/connection/PG_Connection.rb +35 -0
  25. data/lib/upsert/connection/SQLite3_Database.rb +28 -0
  26. data/lib/upsert/connection/jdbc.rb +105 -0
  27. data/lib/upsert/connection/postgresql.rb +24 -0
  28. data/lib/upsert/connection/sqlite3.rb +19 -0
  29. data/lib/upsert/merge_function.rb +73 -0
  30. data/lib/upsert/merge_function/Java_ComMysqlJdbc_JDBC4Connection.rb +42 -0
  31. data/lib/upsert/merge_function/Java_OrgPostgresqlJdbc_PgConnection.rb +27 -0
  32. data/lib/upsert/merge_function/Java_OrgSqlite_Conn.rb +10 -0
  33. data/lib/upsert/merge_function/Mysql2_Client.rb +36 -0
  34. data/lib/upsert/merge_function/PG_Connection.rb +26 -0
  35. data/lib/upsert/merge_function/SQLite3_Database.rb +10 -0
  36. data/lib/upsert/merge_function/mysql.rb +66 -0
  37. data/lib/upsert/merge_function/postgresql.rb +365 -0
  38. data/lib/upsert/merge_function/sqlite3.rb +43 -0
  39. data/lib/upsert/row.rb +59 -0
  40. data/lib/upsert/version.rb +3 -0
  41. data/spec/active_record_upsert_spec.rb +26 -0
  42. data/spec/binary_spec.rb +21 -0
  43. data/spec/correctness_spec.rb +190 -0
  44. data/spec/database_functions_spec.rb +106 -0
  45. data/spec/database_spec.rb +121 -0
  46. data/spec/hstore_spec.rb +249 -0
  47. data/spec/jruby_spec.rb +9 -0
  48. data/spec/logger_spec.rb +52 -0
  49. data/spec/misc/get_postgres_reserved_words.rb +12 -0
  50. data/spec/misc/mysql_reserved.txt +226 -0
  51. data/spec/misc/pg_reserved.txt +742 -0
  52. data/spec/multibyte_spec.rb +27 -0
  53. data/spec/postgresql_spec.rb +94 -0
  54. data/spec/precision_spec.rb +11 -0
  55. data/spec/reserved_words_spec.rb +50 -0
  56. data/spec/sequel_spec.rb +57 -0
  57. data/spec/spec_helper.rb +417 -0
  58. data/spec/speed_spec.rb +44 -0
  59. data/spec/threaded_spec.rb +57 -0
  60. data/spec/timezones_spec.rb +58 -0
  61. data/spec/type_safety_spec.rb +12 -0
  62. data/travis/install_postgres.sh +18 -0
  63. data/travis/run_docker_db.sh +20 -0
  64. data/travis/tune_mysql.sh +7 -0
  65. data/upsert-java.gemspec +14 -0
  66. data/upsert.gemspec +13 -0
  67. data/upsert.gemspec.common +106 -0
  68. metadata +373 -0
@@ -0,0 +1,43 @@
1
+ class Upsert
2
+ class MergeFunction
3
+ # @private
4
+ module Sqlite3
5
+ def self.included(klass)
6
+ klass.extend ClassMethods
7
+ end
8
+
9
+ module ClassMethods
10
+ def clear(*)
11
+ # not necessary
12
+ end
13
+ end
14
+
15
+ attr_reader :quoted_setter_names
16
+ attr_reader :quoted_update_names
17
+ attr_reader :quoted_selector_names
18
+
19
+ def initialize(*)
20
+ super
21
+ @quoted_setter_names = setter_keys.map { |k| connection.quote_ident k }
22
+ @quoted_update_names = setter_keys.select { |k| k !~ CREATED_COL_REGEX }.map { |k| connection.quote_ident k }
23
+ @quoted_selector_names = selector_keys.map { |k| connection.quote_ident k }
24
+ end
25
+
26
+ def create!
27
+ # not necessary
28
+ end
29
+
30
+ def execute(row)
31
+ bind_setter_values = row.setter.values.map { |v| connection.bind_value v }
32
+ bind_selector_values = row.selector.values.map { |v| connection.bind_value v }
33
+ bind_update_values = row.setter.select{ |k,v| k !~ CREATED_COL_REGEX }.map { |k,v| connection.bind_value v }
34
+
35
+ insert_or_ignore_sql = %{INSERT OR IGNORE INTO #{quoted_table_name} (#{quoted_setter_names.join(',')}) VALUES (#{Array.new(bind_setter_values.length, '?').join(',')})}
36
+ connection.execute insert_or_ignore_sql, bind_setter_values
37
+
38
+ update_sql = %{UPDATE #{quoted_table_name} SET #{quoted_update_names.map { |qk| "#{qk}=?" }.join(',')} WHERE #{quoted_selector_names.map { |qk| "#{qk}=?" }.join(' AND ')}}
39
+ connection.execute update_sql, (bind_update_values + bind_selector_values)
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,59 @@
1
+ class Upsert
2
+ # @private
3
+ class Row
4
+ if RUBY_VERSION >= '1.9'
5
+ OrderedHash = ::Hash
6
+ else
7
+ begin
8
+ require 'orderedhash'
9
+ rescue LoadError
10
+ raise LoadError, "[upsert] If you're using upsert on Ruby 1.8, you need to add 'orderedhash' to your Gemfile."
11
+ end
12
+ OrderedHash = ::OrderedHash
13
+ end
14
+
15
+ attr_reader :selector
16
+ attr_reader :setter
17
+ attr_reader :hstore_delete_keys
18
+
19
+ def initialize(raw_selector, raw_setter, options)
20
+ eager_nullify = (options.nil? || options.fetch(:eager_nullify, true))
21
+
22
+ @selector = raw_selector.inject({}) do |memo, (k, v)|
23
+ memo[k.to_s] = v
24
+ memo
25
+ end
26
+
27
+ @hstore_delete_keys = {}
28
+ @setter = raw_setter.inject({}) do |memo, (k, v)|
29
+ k = k.to_s
30
+ if v.is_a?(::Hash) and eager_nullify
31
+ v.each do |kk, vv|
32
+ if vv.nil?
33
+ (@hstore_delete_keys[k] ||= []) << kk
34
+ end
35
+ end
36
+ end
37
+ memo[k] = v
38
+ memo
39
+ end
40
+
41
+ (selector.keys - setter.keys).each do |missing|
42
+ setter[missing] = selector[missing]
43
+ end
44
+
45
+ # there is probably a more clever way to incrementally sort these hashes
46
+ @selector = sort_hash selector
47
+ @setter = sort_hash setter
48
+ end
49
+
50
+ private
51
+
52
+ def sort_hash(original)
53
+ original.keys.sort.inject(OrderedHash.new) do |memo, k|
54
+ memo[k] = original[k]
55
+ memo
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,3 @@
1
+ class Upsert
2
+ VERSION = "2.9.10"
3
+ end
@@ -0,0 +1,26 @@
1
+ require 'spec_helper'
2
+
3
+ require 'upsert/active_record_upsert'
4
+
5
+ describe Upsert do
6
+ describe 'the optional active_record extension' do
7
+ describe :upsert do
8
+ it "is easy to use" do
9
+ assert_creates(Pet,[{:name => 'Jerry', :good => true}]) do
10
+ Pet.upsert({:name => 'Jerry'}, :good => false)
11
+ Pet.upsert({:name => 'Jerry'}, :good => true)
12
+ end
13
+ end
14
+
15
+ it "doesn't fail inside a transaction" do
16
+ Upsert.clear_database_functions(Pet.connection)
17
+ expect {
18
+ Pet.transaction do
19
+ Pet.upsert({name: 'Simba'}, good: true)
20
+ end
21
+ }.to_not raise_error
22
+ expect(Pet.first.name).to eq('Simba')
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,21 @@
1
+ require 'spec_helper'
2
+ describe Upsert do
3
+ describe "supports binary upserts" do
4
+ before do
5
+ @fakes = []
6
+ 10.times do
7
+ @fakes << [Faker::Name.name, Faker::Lorem.paragraphs(10).join("\n\n")]
8
+ end
9
+ end
10
+ it "saves binary one by one" do
11
+ @fakes.each do |name, biography|
12
+ zipped_biography = Zlib::Deflate.deflate biography
13
+ upsert = Upsert.new $conn, :pets
14
+ assert_creates(Pet, [{:name => name, :zipped_biography => zipped_biography}]) do
15
+ upsert.row({:name => name}, {:zipped_biography => Upsert.binary(zipped_biography)})
16
+ end
17
+ Zlib::Inflate.inflate(Pet.find_by_name(name).zipped_biography).should == biography
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,190 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+ describe Upsert do
5
+ describe 'clever correctness' do
6
+ it "doesn't confuse selector and setter" do
7
+ p = Pet.new
8
+ p.name = 'Jerry'
9
+ p.tag_number = 5
10
+ p.save!
11
+
12
+ # won't change anything because selector is wrong
13
+ u = Upsert.new($conn, :pets)
14
+ selector = {:name => 'Jerry', :tag_number => 6}
15
+ u.row(selector)
16
+ p.reload.tag_number.should == 5
17
+ next
18
+
19
+ # won't change anything because selector is wrong
20
+ u = Upsert.new($conn, :pets)
21
+ selector = {:name => 'Jerry', :tag_number => 10}
22
+ setter = { :tag_number => 5 }
23
+ u.row(selector, setter)
24
+ Pet.find_by_name('Jerry').tag_number.should == 5
25
+
26
+ u = Upsert.new($conn, :pets)
27
+ selector = { :name => 'Jerry' }
28
+ setter = { :tag_number => 10 }
29
+ u.row(selector, setter)
30
+ Pet.find_by_name('Jerry').tag_number.should == 10
31
+
32
+ u = Upsert.new($conn, :pets)
33
+ selector = { :name => 'Jerry', :tag_number => 10 }
34
+ setter = { :tag_number => 20 }
35
+ u.row(selector, setter)
36
+ Pet.find_by_name('Jerry').tag_number.should == 20
37
+ end
38
+
39
+ it "really limits its effects to the selector" do
40
+ p = Pet.new
41
+ p.name = 'Jerry'
42
+ p.gender = 'blue'
43
+ p.tag_number = 777
44
+ p.save!
45
+ Pet.find_by_name_and_gender('Jerry', 'blue').tag_number.should == 777
46
+ u = Upsert.new($conn, :pets)
47
+ selector = {:name => 'Jerry', :gender => 'red'} # this shouldn't select anything
48
+ setter = {:tag_number => 888}
49
+ u.row(selector, setter)
50
+ Pet.find_by_name_and_gender('Jerry', 'blue').tag_number.should == 777
51
+ end
52
+
53
+ # https://github.com/seamusabshere/upsert/issues/18
54
+ it "uses nil selectors" do
55
+ Pet.count.should == 0
56
+ now = Date.today
57
+ u = Upsert.new($conn, :pets)
58
+ 5.times do
59
+ u.row(:gender => nil, :birthday => now)
60
+ end
61
+
62
+ Pet.count.should == 1
63
+ end
64
+
65
+ it "uses nil selectors on columns with date type" do
66
+ Pet.count.should == 0
67
+ u = Upsert.new($conn, :pets)
68
+ 5.times do
69
+ u.row(:birthday => nil)
70
+ end
71
+
72
+ Pet.count.should == 1
73
+ end
74
+
75
+ it "uses nil selectors (another way of checking)" do
76
+ u = Upsert.new($conn, :pets)
77
+ now = Date.today
78
+ assert_creates(Pet, [{:name => 'Jerry', :gender => nil, :spiel => 'beagle', :birthday => now}]) do
79
+ u.row(:name => "Jerry", :gender => nil, :spiel => "samoyed")
80
+ u.row({:name => 'Jerry', :gender => nil}, :spiel => 'beagle', :birthday => now)
81
+ end
82
+ end
83
+
84
+ it "works with utf-8 data" do
85
+ u = Upsert.new($conn, :pets)
86
+ records = [
87
+ {:name => '你好', :home_address => '人'},
88
+ {:name => 'Здравствуйте', :home_address => 'человек'},
89
+ {:name => '😀', :home_address => '😂'},
90
+ ]
91
+ assert_creates(Pet, records) do
92
+ records.each { |rec| u.row(rec) }
93
+ end
94
+ end
95
+
96
+ it "tells you if you request a column that doesn't exist" do
97
+ u = Upsert.new($conn, :pets)
98
+ lambda { u.row(:gibberish => 'ba') }.should raise_error(/invalid col/i)
99
+ lambda { u.row(:name => 'Jerry', :gibberish => 'ba') }.should raise_error(/invalid col/i)
100
+ lambda { u.row(:name => 'Jerry', :gibberish => 'ba') }.should raise_error(/invalid col/i)
101
+ lambda { u.row(:name => 'Jerry', :gibberish => 'ba', :gender => 'male') }.should raise_error(/invalid col/i)
102
+ end
103
+
104
+ it "works with a long setter hash" do
105
+ Upsert.batch($conn, :alphabets) do |batch|
106
+ 10_000.times do |time|
107
+ setter = Hash[("a".."z").map { |letter| ["the_letter_#{letter}".to_sym, rand(100)] }]
108
+ selector = Hash[("a".."z").map { |letter| ["the_letter_#{letter}".to_sym, rand(100)] }]
109
+ batch.row(setter, selector)
110
+ end
111
+ end
112
+ end
113
+ end
114
+
115
+ describe "is just as correct as other ways" do
116
+ describe 'compared to native ActiveRecord' do
117
+ it "is as correct as than new/set/save" do
118
+ assert_same_result lotsa_records do |records|
119
+ records.each do |selector, setter|
120
+ if (pet = Pet.where(selector).first)
121
+ pet.update_attributes(setter)
122
+ else
123
+ pet = Pet.new
124
+ selector.each do |k, v|
125
+ pet.send "#{k}=", v
126
+ end
127
+ setter.each do |k, v|
128
+ pet.send "#{k}=", v
129
+ end
130
+ pet.save!
131
+ end
132
+ end
133
+ end
134
+ end
135
+ # it "is as correct as than find_or_create + update_attributes" do
136
+ # assert_same_result lotsa_records do |records|
137
+ # dynamic_method = nil
138
+ # records.each do |selector, setter|
139
+ # dynamic_method ||= "find_or_create_by_#{selector.keys.join('_or_')}"
140
+ # pet = Pet.send(dynamic_method, *selector.values)
141
+ # pet.update_attributes setter, :without_protection => true
142
+ # end
143
+ # end
144
+ # end
145
+ # it "is as correct as than create + rescue/find/update" do
146
+ # assert_same_result lotsa_records do |records|
147
+ # dynamic_method = nil
148
+ # records.each do |selector, setter|
149
+ # dynamic_method ||= "find_or_create_by_#{selector.keys.join('_or_')}"
150
+ # begin
151
+ # Pet.create selector.merge(setter), :without_protection => true
152
+ # rescue
153
+ # pet = Pet.send(dynamic_method, *selector.values)
154
+ # pet.update_attributes setter, :without_protection => true
155
+ # end
156
+ # end
157
+ # end
158
+ # end
159
+ end
160
+
161
+ if ENV['DB'] == 'mysql' || (UNIQUE_CONSTRAINT && ENV["DB"] == "postgresql")
162
+ describe 'compared to activerecord-import' do
163
+ it "is as correct as faking upserts with activerecord-import" do
164
+ assert_same_result lotsa_records do |records|
165
+ columns = nil
166
+ all_values = []
167
+ # Reverse because we want to mimic an 'overwrite' of previous values
168
+ records = records.reverse.uniq { |s, _| s } if ENV['DB'] == "postgresql"
169
+
170
+ records.each do |selector, setter|
171
+ columns ||= (selector.keys + setter.keys).uniq
172
+ all_values << columns.map do |k|
173
+ if setter.has_key?(k)
174
+ # prefer the setter so that you can change rows
175
+ setter[k]
176
+ else
177
+ selector[k]
178
+ end
179
+ end
180
+ end
181
+
182
+ conflict_update = ENV['DB'] == "postgresql" ? {conflict_target: records.first.first.keys, columns: columns} : columns
183
+ Pet.import columns, all_values, :timestamps => false, :on_duplicate_key_update => conflict_update
184
+ end
185
+ end
186
+ end
187
+ end
188
+
189
+ end
190
+ end
@@ -0,0 +1,106 @@
1
+ require 'spec_helper'
2
+ require 'stringio'
3
+ require 'upsert/merge_function/postgresql'
4
+
5
+ describe Upsert do
6
+ describe 'database functions' do
7
+ version = 'postgresql' == ENV['DB'] ? Upsert::MergeFunction::Postgresql.extract_version(
8
+ Pet.connection.select_value("SHOW server_version")
9
+ ) : 0
10
+ before(:each) {
11
+ skip "Not using DB functions" if 'postgresql' == ENV['DB'] && UNIQUE_CONSTRAINT && version >= 90500
12
+ }
13
+ it "does not re-use merge functions across connections" do
14
+ begin
15
+ io = StringIO.new
16
+ old_logger = Upsert.logger
17
+ Upsert.logger = Logger.new io, Logger::INFO
18
+
19
+ # clear, create (#1)
20
+ Upsert.clear_database_functions($conn_factory.new_connection)
21
+ Upsert.new($conn_factory.new_connection, :pets).row :name => 'hello'
22
+
23
+ # clear, create (#2)
24
+ Upsert.clear_database_functions($conn_factory.new_connection)
25
+ Upsert.new($conn_factory.new_connection, :pets).row :name => 'hello'
26
+
27
+ io.rewind
28
+ hits = io.read.split("\n").grep(/Creating or replacing/)
29
+ hits.length.should == 2
30
+ ensure
31
+ Upsert.logger = old_logger
32
+ end
33
+ end
34
+
35
+ it "does not re-use merge functions even when on the same connection" do
36
+ begin
37
+ io = StringIO.new
38
+ old_logger = Upsert.logger
39
+ Upsert.logger = Logger.new io, Logger::INFO
40
+
41
+ connection = $conn_factory.new_connection
42
+
43
+ # clear, create (#1)
44
+ Upsert.clear_database_functions(connection)
45
+ Upsert.new(connection, :pets).row :name => 'hello'
46
+
47
+ # clear, create (#2)
48
+ Upsert.clear_database_functions(connection)
49
+ Upsert.new(connection, :pets).row :name => 'hello'
50
+
51
+ io.rewind
52
+ hits = io.read.split("\n").grep(/Creating or replacing/)
53
+ hits.length.should == 2
54
+ ensure
55
+ Upsert.logger = old_logger
56
+ end
57
+ end
58
+
59
+ it "re-uses merge functions within batch" do
60
+ begin
61
+ io = StringIO.new
62
+ old_logger = Upsert.logger
63
+ Upsert.logger = Logger.new io, Logger::INFO
64
+
65
+ # clear
66
+ Upsert.clear_database_functions($conn_factory.new_connection)
67
+
68
+ # create
69
+ Upsert.batch($conn_factory.new_connection, :pets) do |upsert|
70
+ upsert.row :name => 'hello'
71
+ upsert.row :name => 'world'
72
+ end
73
+
74
+ io.rewind
75
+ hits = io.read.split("\n").grep(/Creating or replacing/)
76
+ hits.length.should == 1
77
+ ensure
78
+ Upsert.logger = old_logger
79
+ end
80
+ end
81
+
82
+ it "assumes function exists if told to" do
83
+ begin
84
+ io = StringIO.new
85
+ old_logger = Upsert.logger
86
+ Upsert.logger = Logger.new io, Logger::INFO
87
+
88
+ # clear
89
+ Upsert.clear_database_functions($conn_factory.new_connection)
90
+
91
+ # tries, "went missing", creates
92
+ Upsert.new($conn_factory.new_connection, :pets, :assume_function_exists => true).row :name => 'hello'
93
+
94
+ # just works
95
+ Upsert.new($conn_factory.new_connection, :pets, :assume_function_exists => true).row :name => 'hello'
96
+
97
+ io.rewind
98
+ lines = io.read.split("\n")
99
+ lines.grep(/went missing/).length.should == 1
100
+ lines.grep(/Creating or replacing/).length.should == 1
101
+ ensure
102
+ Upsert.logger = old_logger
103
+ end
104
+ end
105
+ end
106
+ end if %w{ postgresql mysql }.include?(ENV['DB'])