upsert 2.9.9-universal-java-11

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.
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 +16 -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 +13 -0
  66. data/upsert.gemspec +11 -0
  67. data/upsert.gemspec.common +107 -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.9"
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'])