upsert 2.1.2 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6522186c1e008a4b2386d957052e532149fc2ea3
4
- data.tar.gz: f80674428af81360e0d91c24626e86abde86ccd7
3
+ metadata.gz: dfb6bea0fbe024f56a107ddb4db99ef247b5ebdb
4
+ data.tar.gz: 6a3f979f4b136debf07b327073b2e774fdd60322
5
5
  SHA512:
6
- metadata.gz: cb6e925f2c76a2bab2800a609f4ccbec4b596b1b854a13293143dda2a67be30fe82f8138ab778a958033fe9acef8fedacded0819ea367daa9b850765eaf35aef
7
- data.tar.gz: eb93f41e68345199be249465046e7dcf66ea1481c33d9ed14384b8155b3e33a9dea84d86fb50bd0b56fa8205cc2f5d433450799fa2f5c45637854a596890be33
6
+ metadata.gz: 850b04b05fab5c9ec30b9c27253fe8b751beef9dbbc7718abdd6cfbe93d6054796361cde49543018dd3afaf1c59ffe0c8baca1f2874b58053a94726cbf444112
7
+ data.tar.gz: 49db75e385e5d9cb3960e9f8d8927e8fa0657932e8422fe5ee7f77bcaf77a8dfc5b844b28c8a061953a285590b0c6ed9a1e8f87afb6c7c3ab1ba0d8dbdb8e312
@@ -1,18 +1,40 @@
1
+ sudo: required
2
+ dist: trusty
1
3
  language: ruby
2
4
  global:
3
5
  - USERNAME=travis
4
- PASSWORD=
6
+ - PASSWORD=
7
+ addons:
8
+ apt:
9
+ packages:
10
+ # https://github.com/travis-ci/docs-travis-ci-com/pull/743
11
+ - haveged
12
+ - mysql-server-5.6
13
+ - mysql-client-core-5.6
14
+ - mysql-client-5.6
5
15
  rvm:
6
- - 2.3.0
7
- - 2.2.0
8
- - 2.1.0
16
+ - 2.3
17
+ - 2.2
18
+ - 2.1
9
19
  - 1.9.3
10
- - rbx-2
20
+ - rbx
21
+ - jruby-1.7
22
+ - jruby-9
23
+ matrix:
24
+ allow_failures:
25
+ - rvm: rbx
11
26
  env:
12
- - DB=postgresql
13
- - DB=mysql
27
+ - DB=postgresql PGVERSION=9.4
28
+ - DB=postgresql PGVERSION=9.5
29
+ - DB=postgresql PGVERSION=9.4 UNIQUE_CONSTRAINT=true
30
+ - DB=postgresql PGVERSION=9.5 UNIQUE_CONSTRAINT=true
31
+ - DB=mysql DB_USER=root
14
32
  before_install:
33
+ - if [ "$DB" = 'mysql' ]; then sudo ./travis/tune_mysql.sh; fi
34
+ # Right now the build-script is properly installing Postgres version. We will need this to test PG 9.6 and up, though
35
+ # - if [ "$DB" = 'postgresql' ]; then sudo ./travis/install_postgres.sh; fi
36
+ - gem update --system
15
37
  - gem update bundler
16
38
  - bundle --version
17
- - gem update --system 2.1.11
18
39
  - gem --version
40
+ script: bundle exec rake spec
data/CHANGELOG CHANGED
@@ -1,3 +1,10 @@
1
+ 2.2.0 / 2017-04-14
2
+
3
+ * Enhancements
4
+
5
+ * Use native "upsert" on Postgres 9.5+! (thanks to @pnomolos https://github.com/seamusabshere/upsert/pull/79)
6
+ * More modern CI tests
7
+
1
8
  2.1.2 / 2016-02-25
2
9
 
3
10
  * Enhancements
data/README.md CHANGED
@@ -57,6 +57,12 @@ end
57
57
 
58
58
  Batch mode is tested to be about 80% faster on PostgreSQL, MySQL, and SQLite3 than other ways to emulate upsert (see the tests, which fail if they are not faster).
59
59
 
60
+ ### Native Postgres upsert
61
+
62
+ `INSERT ... ON CONFLICT DO UPDATE` is used when Postgres 9.5+ is detected and *unique indexes are in place.*
63
+
64
+ If you don't have unique indexes, it will fall back to the classic Upsert gem user-defined function, which does not require indexes.
65
+
60
66
  ### ActiveRecord helper method
61
67
 
62
68
  ```ruby
@@ -15,7 +15,7 @@ class Upsert
15
15
  # @return [#info,#warn,#debug]
16
16
  attr_writer :logger
17
17
  MUTEX_FOR_PERFORM = Mutex.new
18
-
18
+
19
19
  # The current logger
20
20
  # @return [#info,#warn,#debug]
21
21
  def logger
@@ -224,7 +224,7 @@ class Upsert
224
224
  def clear_database_functions
225
225
  merge_function_class.clear connection
226
226
  end
227
-
227
+
228
228
  def merge_function(row)
229
229
  cache_key = [row.selector.keys, row.setter.keys]
230
230
  @merge_function_cache[cache_key] ||= merge_function_class.new(self, row.selector.keys, row.setter.keys, assume_function_exists?)
@@ -6,9 +6,13 @@ class Upsert
6
6
  class Java_ComMysqlJdbc_JDBC4Connection < Connection
7
7
  include Jdbc
8
8
 
9
- # ? backtick?
10
9
  def quote_ident(k)
11
- DOUBLE_QUOTE + k.to_s.gsub(DOUBLE_QUOTE, '""') + DOUBLE_QUOTE
10
+ if metal.useAnsiQuotedIdentifiers
11
+ DOUBLE_QUOTE + k.to_s.gsub(DOUBLE_QUOTE, '""') + DOUBLE_QUOTE
12
+ else
13
+ # Escape backticks by doubling them. Ref http://dev.mysql.com/doc/refman/5.7/en/identifiers.html
14
+ BACKTICK + k.to_s.gsub(BACKTICK, BACKTICK + BACKTICK) + BACKTICK
15
+ end
12
16
  end
13
17
 
14
18
  def bind_value(v)
@@ -10,6 +10,11 @@ class Upsert
10
10
  def quote_ident(k)
11
11
  DOUBLE_QUOTE + k.to_s.gsub(DOUBLE_QUOTE, '""') + DOUBLE_QUOTE
12
12
  end
13
+
14
+ def in_transaction?
15
+ # https://github.com/kares/activerecord-jdbc-adapter/commit/4d6e0e0c52d12b0166810dffc9f898141a23bee6
16
+ ![0, 4].include?(metal.get_transaction_state)
17
+ end
13
18
  end
14
19
  end
15
20
  end
@@ -3,7 +3,7 @@ class Upsert
3
3
  # @private
4
4
  class PG_Connection < Connection
5
5
  include Postgresql
6
-
6
+
7
7
  def execute(sql, params = nil)
8
8
  if params
9
9
  # Upsert.logger.debug { %{[upsert] #{sql} with #{params.inspect}} }
@@ -21,6 +21,10 @@ class Upsert
21
21
  def binary(v)
22
22
  { :value => v.value, :format => 1 }
23
23
  end
24
+
25
+ def in_transaction?
26
+ ![PG::PQTRANS_IDLE, PG::PQTRANS_UNKNOWN].include?(metal.transaction_status)
27
+ end
24
28
  end
25
29
  end
26
30
  end
@@ -8,7 +8,9 @@ class Upsert
8
8
  java.sql.Types::OTHER => 'getString', # ?! i guess unicode text?
9
9
  java.sql.Types::BINARY => 'getBlob',
10
10
  java.sql.Types::LONGVARCHAR => 'getString',
11
+ java.sql.Types::BIGINT => 'getLong',
11
12
  java.sql.Types::INTEGER => 'getInt',
13
+ java.sql.Types::ARRAY => ->(r, i){ r.getArray(i).array.to_ary }
12
14
  }
13
15
  java.sql.Types.constants.each do |type_name|
14
16
  i = java.sql.Types.const_get type_name
@@ -34,6 +36,11 @@ class Upsert
34
36
  setters = self.class.const_get(:SETTER)
35
37
  statement = metal.prepareStatement sql
36
38
  params.each_with_index do |v, i|
39
+ if v.is_a?(Fixnum) && v > 2_147_483_647
40
+ statement.setLong i+1, v
41
+ next
42
+ end
43
+
37
44
  case v
38
45
  when Upsert::Binary
39
46
  statement.setBytes i+1, binary(v)
@@ -72,6 +79,8 @@ class Upsert
72
79
  column_name, getter = cg
73
80
  if getter == 'getNull'
74
81
  row[column_name] = nil
82
+ elsif getter.respond_to?(:call)
83
+ row[column_name] = getter.call(raw_result, i)
75
84
  else
76
85
  row[column_name] = raw_result.send(getter, i)
77
86
  end
@@ -81,6 +90,10 @@ class Upsert
81
90
  statement.close
82
91
  result
83
92
  end
93
+
94
+ def in_transaction?
95
+ raise "Not implemented"
96
+ end
84
97
  end
85
98
  end
86
99
  end
@@ -4,36 +4,24 @@ class Upsert
4
4
  class MergeFunction
5
5
  # @private
6
6
  class Java_OrgPostgresqlJdbc4_Jdbc4Connection < MergeFunction
7
+ ERROR_CLASS = org.postgresql.util.PSQLException
7
8
  include Postgresql
8
9
 
9
- def execute(row)
10
- first_try = true
11
- values = []
12
- values += row.selector.values
13
- values += row.setter.values
14
- hstore_delete_handlers.each do |hstore_delete_handler|
15
- values << row.hstore_delete_keys.fetch(hstore_delete_handler.name, [])
16
- end
17
- begin
18
- connection.execute sql, values.map { |v| connection.bind_value v }
19
- rescue org.postgresql.util.PSQLException => pg_error
20
- if pg_error.message =~ /function #{name}.* does not exist/i
21
- if first_try
22
- Upsert.logger.info %{[upsert] Function #{name.inspect} went missing, trying to recreate}
23
- first_try = false
24
- create!
25
- retry
26
- else
27
- Upsert.logger.info %{[upsert] Failed to create function #{name.inspect} for some reason}
28
- raise pg_error
29
- end
30
- else
31
- raise pg_error
32
- end
10
+ def execute_parameterized(query, args = [])
11
+ query_args = []
12
+ query = query.gsub(/\$(\d+)/) do |str|
13
+ query_args << args[Regexp.last_match[1].to_i - 1]
14
+ "?"
33
15
  end
16
+ controller.connection.execute(query, query_args)
34
17
  end
35
18
 
36
-
19
+ def unique_index_on_selector?
20
+ return @unique_index_on_selector if defined?(@unique_index_on_selector)
21
+ @unique_index_on_selector = schema_query.any? do |row|
22
+ row["index_columns"].sort == selector_keys.sort
23
+ end
24
+ end
37
25
  end
38
26
  end
39
27
  end
@@ -4,52 +4,21 @@ class Upsert
4
4
  class MergeFunction
5
5
  # @private
6
6
  class PG_Connection < MergeFunction
7
+ ERROR_CLASS = PG::Error
7
8
  include Postgresql
8
9
 
9
- def execute(row)
10
- first_try = true
11
- values = []
12
- values += row.selector.values
13
- values += row.setter.values
14
- hstore_delete_handlers.each do |hstore_delete_handler|
15
- values << row.hstore_delete_keys.fetch(hstore_delete_handler.name, [])
16
- end
17
- Upsert.logger.debug do
18
- %{[upsert]\n\tSelector: #{row.selector.inspect}\n\tSetter: #{row.setter.inspect}}
19
- end
20
- begin
21
- connection.execute sql, values.map { |v| connection.bind_value v }
22
- rescue PG::Error => pg_error
23
- if pg_error.message =~ /function #{name}.* does not exist/i
24
- if first_try
25
- Upsert.logger.info %{[upsert] Function #{name.inspect} went missing, trying to recreate}
26
- first_try = false
27
- create!
28
- retry
29
- else
30
- Upsert.logger.info %{[upsert] Failed to create function #{name.inspect} for some reason}
31
- raise pg_error
32
- end
33
- else
34
- raise pg_error
35
- end
36
- end
10
+ def execute_parameterized(query, args = [])
11
+ controller.connection.execute(query, args)
37
12
  end
38
13
 
39
- # strangely ? can't be used as a placeholder
40
- def sql
41
- @sql ||= begin
42
- bind_params = []
43
- i = 1
44
- (selector_keys.length + setter_keys.length).times do
45
- bind_params << "$#{i}"
46
- i += 1
47
- end
48
- hstore_delete_handlers.length.times do
49
- bind_params << "$#{i}::text[]"
50
- i += 1
51
- end
52
- %{SELECT #{name}(#{bind_params.join(', ')})}
14
+ def unique_index_on_selector?
15
+ return @unique_index_on_selector if defined?(@unique_index_on_selector)
16
+
17
+ type_map = PG::TypeMapByColumn.new([PG::TextDecoder::Array.new])
18
+ schema_query.type_map = type_map
19
+
20
+ @unique_index_on_selector = schema_query.values.any? do |row|
21
+ row.first.sort == selector_keys.sort
53
22
  end
54
23
  end
55
24
  end
@@ -40,13 +40,165 @@ class Upsert
40
40
  end
41
41
  end
42
42
 
43
+ attr_reader :quoted_setter_names
44
+ attr_reader :quoted_selector_names
45
+
46
+ def initialize(controller, *args)
47
+ super
48
+ @quoted_setter_names = setter_keys.map { |k| connection.quote_ident k }
49
+ @quoted_selector_names = selector_keys.map { |k| connection.quote_ident k }
50
+ end
51
+
52
+ def execute(row)
53
+ use_pg_native? ? pg_native(row) : pg_function(row)
54
+ end
55
+
56
+ def pg_function(row)
57
+ values = []
58
+ values += row.selector.values
59
+ values += row.setter.values
60
+ hstore_delete_handlers.each do |hstore_delete_handler|
61
+ values << row.hstore_delete_keys.fetch(hstore_delete_handler.name, [])
62
+ end
63
+ Upsert.logger.debug do
64
+ %{[upsert]\n\tSelector: #{row.selector.inspect}\n\tSetter: #{row.setter.inspect}}
65
+ end
66
+
67
+ first_try = true
68
+ begin
69
+ create! if connection.in_transaction? && !function_exists?
70
+ execute_parameterized(sql, values.map { |v| connection.bind_value v })
71
+ rescue self.class::ERROR_CLASS => pg_error
72
+ if pg_error.message =~ /function #{name}.* does not exist/i
73
+ if first_try
74
+ Upsert.logger.info %{[upsert] Function #{name.inspect} went missing, trying to recreate}
75
+ first_try = false
76
+ create!
77
+ retry
78
+ end
79
+ Upsert.logger.info %{[upsert] Failed to create function #{name.inspect} for some reason}
80
+ raise pg_error
81
+ else
82
+ raise pg_error
83
+ end
84
+ end
85
+ end
86
+
87
+ def function_exists?
88
+ # The ::int is a hack until jruby+jdbc is happy with bigints being returned
89
+ @function_exists ||= controller.connection.execute("SELECT count(*)::int AS cnt FROM pg_proc WHERE lower(proname) = lower('#{name}')").first["cnt"].to_i > 0
90
+ end
91
+
92
+ # strangely ? can't be used as a placeholder
43
93
  def sql
44
94
  @sql ||= begin
45
- bind_params = Array.new(selector_keys.length + setter_keys.length, '?') + Array.new(hstore_delete_handlers.length, '?::text[]')
95
+ bind_params = []
96
+ i = 1
97
+ (selector_keys.length + setter_keys.length).times do
98
+ bind_params << "$#{i}"
99
+ i += 1
100
+ end
101
+ hstore_delete_handlers.length.times do
102
+ bind_params << "$#{i}::text[]"
103
+ i += 1
104
+ end
46
105
  %{SELECT #{name}(#{bind_params.join(', ')})}
47
106
  end
48
107
  end
49
108
 
109
+ def use_pg_native?
110
+ server_version >= 95 && unique_index_on_selector?
111
+ end
112
+
113
+ def server_version
114
+ @server_version ||=
115
+ controller.connection.execute("SHOW server_version").first["server_version"].split('.')[0..1].join('').to_i
116
+ end
117
+
118
+ def schema_query
119
+ execute_parameterized(
120
+ %{
121
+ SELECT array_agg(column_name::text) AS index_columns FROM information_schema.constraint_column_usage
122
+ JOIN pg_catalog.pg_constraint ON constraint_name::text = conname::text
123
+ WHERE table_name = $1 AND conrelid = $1::regclass::oid AND contype = 'u'
124
+ GROUP BY table_catalog, table_name, constraint_name
125
+ },
126
+ [table_name]
127
+ )
128
+ end
129
+
130
+ def pg_native(row)
131
+ bind_setter_values = row.setter.values.map { |v| connection.bind_value v }
132
+
133
+ upsert_sql = %{
134
+ INSERT INTO #{quoted_table_name} (#{quoted_setter_names.join(',')})
135
+ VALUES (#{insert_bind_placeholders(row).join(', ')})
136
+ ON CONFLICT(#{quoted_selector_names.join(', ')})
137
+ DO UPDATE SET (#{quoted_setter_names.join(', ')}) = (#{conflict_bind_placeholders(row).join(', ')})
138
+ }
139
+
140
+ execute_parameterized(upsert_sql, bind_setter_values)
141
+ end
142
+
143
+ def hstore_delete_function(sql, row, column_definition)
144
+ parts = []
145
+ if row.hstore_delete_keys.key?(column_definition.name)
146
+ parts << "DELETE("
147
+ end
148
+ parts << sql
149
+ if row.hstore_delete_keys.key?(column_definition.name)
150
+ keys = row.hstore_delete_keys[column_definition.name].map { |k| "'#{k.to_s.gsub("'", "\\'")}'" }
151
+ parts << ", ARRAY[#{keys.join(', ')}])"
152
+ end
153
+
154
+ parts.join(" ")
155
+ end
156
+
157
+ def insert_bind_placeholders(row)
158
+ if row.hstore_delete_keys.empty?
159
+ @insert_bind_placeholders ||= setter_column_definitions.each_with_index.map do |column_definition, i|
160
+ "$#{i + 1}"
161
+ end
162
+ else
163
+ setter_column_definitions.each_with_index.map do |column_definition, i|
164
+ idx = i + 1
165
+ if column_definition.hstore?
166
+ hstore_delete_function("$#{idx}", row, column_definition)
167
+ else
168
+ "$#{idx}"
169
+ end
170
+ end
171
+ end
172
+ end
173
+
174
+ def conflict_bind_placeholders(row)
175
+ if row.hstore_delete_keys.empty?
176
+ @conflict_bind_placeholders ||= setter_column_definitions.each_with_index.map do |column_definition, i|
177
+ idx = i + 1
178
+ if column_definition.hstore?
179
+ "CASE WHEN #{quoted_table_name}.#{column_definition.quoted_name} IS NULL THEN $#{idx} ELSE" \
180
+ + " (#{quoted_table_name}.#{column_definition.quoted_name} || $#{idx})" \
181
+ + " END"
182
+ else
183
+ "$#{idx}"
184
+ end
185
+ end
186
+ else
187
+ setter_column_definitions.each_with_index.map do |column_definition, i|
188
+ idx = i + 1
189
+ if column_definition.hstore?
190
+ "CASE WHEN #{quoted_table_name}.#{column_definition.quoted_name} IS NULL THEN " \
191
+ + hstore_delete_function("$#{idx}", row, column_definition) \
192
+ + " ELSE " \
193
+ + hstore_delete_function("(#{quoted_table_name}.#{column_definition.quoted_name} || $#{idx})", row, column_definition) \
194
+ + " END"
195
+ else
196
+ "$#{idx}"
197
+ end
198
+ end
199
+ end
200
+ end
201
+
50
202
  class HstoreDeleteHandler
51
203
  attr_reader :merge_function
52
204
  attr_reader :column_definition
@@ -1,3 +1,3 @@
1
1
  class Upsert
2
- VERSION = '2.1.2'
2
+ VERSION = '2.2.0'
3
3
  end
@@ -11,6 +11,16 @@ describe Upsert do
11
11
  Pet.upsert({:name => 'Jerry'}, :good => true)
12
12
  end
13
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
14
24
  end
15
25
  end
16
26
  end
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  require 'spec_helper'
2
4
  describe Upsert do
3
5
  describe 'clever correctness' do
@@ -78,6 +80,18 @@ describe Upsert do
78
80
  end
79
81
  end
80
82
 
83
+ it "works with utf-8 data" do
84
+ u = Upsert.new($conn, :pets)
85
+ records = [
86
+ {:name => '你好', :home_address => '人'},
87
+ {:name => 'Здравствуйте', :home_address => 'человек'},
88
+ {:name => '😀', :home_address => '😂'},
89
+ ]
90
+ assert_creates(Pet, records) do
91
+ records.each { |rec| u.row(rec) }
92
+ end
93
+ end
94
+
81
95
  it "tells you if you request a column that doesn't exist" do
82
96
  u = Upsert.new($conn, :pets)
83
97
  lambda { u.row(:gibberish => 'ba') }.should raise_error(/invalid col/i)
@@ -2,6 +2,10 @@ require 'spec_helper'
2
2
  require 'stringio'
3
3
  describe Upsert do
4
4
  describe 'database functions' do
5
+ version = 'postgresql' == ENV['DB'] ? Pet.connection.select_value("SHOW server_version")[0..2].split('.').join('').to_i : 0
6
+ before(:each) {
7
+ skip "Not using DB functions" if 'postgresql' == ENV['DB'] && UNIQUE_CONSTRAINT && version >= 95
8
+ }
5
9
  it "does not re-use merge functions across connections" do
6
10
  begin
7
11
  io = StringIO.new
@@ -15,7 +19,7 @@ describe Upsert do
15
19
  # clear, create (#2)
16
20
  Upsert.clear_database_functions($conn_factory.new_connection)
17
21
  Upsert.new($conn_factory.new_connection, :pets).row :name => 'hello'
18
-
22
+
19
23
  io.rewind
20
24
  hits = io.read.split("\n").grep(/Creating or replacing/)
21
25
  hits.length.should == 2
@@ -23,13 +27,13 @@ describe Upsert do
23
27
  Upsert.logger = old_logger
24
28
  end
25
29
  end
26
-
30
+
27
31
  it "does not re-use merge functions even when on the same connection" do
28
32
  begin
29
33
  io = StringIO.new
30
34
  old_logger = Upsert.logger
31
35
  Upsert.logger = Logger.new io, Logger::INFO
32
-
36
+
33
37
  connection = $conn_factory.new_connection
34
38
 
35
39
  # clear, create (#1)
@@ -39,7 +43,7 @@ describe Upsert do
39
43
  # clear, create (#2)
40
44
  Upsert.clear_database_functions(connection)
41
45
  Upsert.new(connection, :pets).row :name => 'hello'
42
-
46
+
43
47
  io.rewind
44
48
  hits = io.read.split("\n").grep(/Creating or replacing/)
45
49
  hits.length.should == 2
@@ -47,7 +51,7 @@ describe Upsert do
47
51
  Upsert.logger = old_logger
48
52
  end
49
53
  end
50
-
54
+
51
55
  it "re-uses merge functions within batch" do
52
56
  begin
53
57
  io = StringIO.new
@@ -56,13 +60,13 @@ describe Upsert do
56
60
 
57
61
  # clear
58
62
  Upsert.clear_database_functions($conn_factory.new_connection)
59
-
63
+
60
64
  # create
61
65
  Upsert.batch($conn_factory.new_connection, :pets) do |upsert|
62
66
  upsert.row :name => 'hello'
63
67
  upsert.row :name => 'world'
64
68
  end
65
-
69
+
66
70
  io.rewind
67
71
  hits = io.read.split("\n").grep(/Creating or replacing/)
68
72
  hits.length.should == 1
@@ -79,7 +83,7 @@ describe Upsert do
79
83
 
80
84
  # clear
81
85
  Upsert.clear_database_functions($conn_factory.new_connection)
82
-
86
+
83
87
  # tries, "went missing", creates
84
88
  Upsert.new($conn_factory.new_connection, :pets, :assume_function_exists => true).row :name => 'hello'
85
89
 
@@ -94,6 +98,5 @@ describe Upsert do
94
98
  Upsert.logger = old_logger
95
99
  end
96
100
  end
97
-
98
101
  end
99
102
  end if %w{ postgresql mysql }.include?(ENV['DB'])
@@ -3,7 +3,7 @@ require 'spec_helper'
3
3
  describe Upsert do
4
4
  describe 'hstore on pg' do
5
5
  require 'pg_hstore'
6
- Pet.connection.execute 'CREATE EXTENSION HSTORE'
6
+ Pet.connection.execute 'CREATE EXTENSION IF NOT EXISTS HSTORE'
7
7
  Pet.connection.execute "ALTER TABLE pets ADD COLUMN crazy HSTORE"
8
8
  Pet.connection.execute "ALTER TABLE pets ADD COLUMN cool HSTORE"
9
9
 
@@ -11,8 +11,9 @@ describe Upsert do
11
11
  Pet.delete_all
12
12
  end
13
13
 
14
+ let(:upsert) { Upsert.new $conn, :pets }
15
+
14
16
  it "works for ugly text" do
15
- upsert = Upsert.new $conn, :pets
16
17
  uggy = <<-EOS
17
18
  {"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":[]}}
18
19
  EOS
@@ -23,8 +24,6 @@ EOS
23
24
  end
24
25
 
25
26
  it "just works" do
26
- upsert = Upsert.new $conn, :pets
27
-
28
27
  upsert.row({:name => 'Bill'}, :crazy => nil)
29
28
  row = Pet.connection.select_one(%{SELECT crazy FROM pets WHERE name = 'Bill'})
30
29
  row['crazy'].should == nil
@@ -60,8 +59,6 @@ EOS
60
59
  end
61
60
 
62
61
  it "can nullify entire hstore" do
63
- upsert = Upsert.new $conn, :pets
64
-
65
62
  upsert.row({:name => 'Bill'}, :crazy => {:a => 1})
66
63
  row = Pet.connection.select_one(%{SELECT crazy FROM pets WHERE name = 'Bill'})
67
64
  crazy = PgHstore.parse row['crazy']
@@ -73,8 +70,6 @@ EOS
73
70
  end
74
71
 
75
72
  it "deletes keys that are nil" do
76
- upsert = Upsert.new $conn, :pets
77
-
78
73
  upsert.row({:name => 'Bill'}, :crazy => nil)
79
74
  row = Pet.connection.select_one(%{SELECT crazy FROM pets WHERE name = 'Bill'})
80
75
  row['crazy'].should == nil
@@ -121,8 +116,6 @@ EOS
121
116
  end
122
117
 
123
118
  it "takes dangerous keys" do
124
- upsert = Upsert.new $conn, :pets
125
-
126
119
  upsert.row({:name => 'Bill'}, :crazy => nil)
127
120
  row = Pet.connection.select_one(%{SELECT crazy FROM pets WHERE name = 'Bill'})
128
121
  row['crazy'].should == nil
@@ -169,7 +162,6 @@ EOS
169
162
  end
170
163
 
171
164
  it "handles multiple hstores" do
172
- upsert = Upsert.new $conn, :pets
173
165
  upsert.row({:name => 'Bill'}, :crazy => {:a => 1, :b => 9}, :cool => {:c => 12, :d => 19})
174
166
  row = Pet.connection.select_one(%{SELECT crazy, cool FROM pets WHERE name = 'Bill'})
175
167
  crazy = PgHstore.parse row['crazy']
@@ -179,8 +171,6 @@ EOS
179
171
  end
180
172
 
181
173
  it "can deletes keys from multiple hstores at once" do
182
- upsert = Upsert.new $conn, :pets
183
-
184
174
  upsert.row({:name => 'Bill'}, :crazy => {:a => 1}, :cool => {5 => 9})
185
175
  row = Pet.connection.select_one(%{SELECT crazy, cool FROM pets WHERE name = 'Bill'})
186
176
  crazy = PgHstore.parse row['crazy']
@@ -217,8 +207,6 @@ EOS
217
207
  end
218
208
 
219
209
  it "deletes keys whether new or existing record" do
220
- upsert = Upsert.new $conn, :pets
221
-
222
210
  upsert.row({:name => 'Bill'}, :crazy => {:z => 1, :x => nil})
223
211
  row = Pet.connection.select_one(%{SELECT crazy FROM pets WHERE name = 'Bill'})
224
212
  crazy = PgHstore.parse row['crazy']
@@ -231,8 +219,6 @@ EOS
231
219
  end
232
220
 
233
221
  it "can turn off eager nullify" do
234
- upsert = Upsert.new $conn, :pets
235
-
236
222
  upsert.row({:name => 'Bill'}, {:crazy => {:z => 1, :x => nil}}, :eager_nullify => false)
237
223
  row = Pet.connection.select_one(%{SELECT crazy FROM pets WHERE name = 'Bill'})
238
224
  crazy = PgHstore.parse row['crazy']
@@ -0,0 +1,9 @@
1
+ require 'spec_helper'
2
+ describe Upsert do
3
+ it "works correct with large ints" do
4
+ u = Upsert.new($conn, :pets)
5
+ Pet.create(:name => "Jerry", :big_tag_number => 2)
6
+ u.row({ :name => 'Jerry' }, :big_tag_number => 3599657714)
7
+ Pet.find_by_name('Jerry').big_tag_number.should == 3599657714
8
+ end
9
+ end if RUBY_PLATFORM == 'java'
@@ -20,12 +20,12 @@ describe Upsert do
20
20
  end
21
21
 
22
22
  it "logs queries" do
23
+ old_logger = Upsert.logger
23
24
  begin
24
- old_logger = Upsert.logger
25
25
  io = StringIO.new
26
26
  MUTEX_FOR_PERFORM.synchronize do
27
27
  Upsert.logger = Logger.new(io)
28
-
28
+
29
29
  u = Upsert.new($conn, :pets)
30
30
  u.row(:name => 'Jerry')
31
31
 
@@ -38,7 +38,8 @@ describe Upsert do
38
38
  log.should =~ /call #{Upsert::MergeFunction::NAME_PREFIX}_pets_SEL_name/i
39
39
  when /p.*g/i
40
40
  # [54ae2eea857] Possibly much more useful debug output
41
- log.should =~ /selector:/i
41
+ # TODO: Should check for both upsert and non-upsert log output
42
+ log.should =~ /selector:|SHOW server_version/i
42
43
  else
43
44
  raise "not sure"
44
45
  end
@@ -0,0 +1,16 @@
1
+ require 'spec_helper'
2
+ describe Upsert do
3
+ version = Pet.connection.select_value("SHOW server_version")[0..2].split('.').join('').to_i
4
+
5
+ let(:upsert) do
6
+ Upsert.new($conn, :pets)
7
+ end
8
+
9
+ it "uses the native method if available (#{(UNIQUE_CONSTRAINT && version >= 95).inspect})" do
10
+ p = Pet.create(:name => 'Jerry', :tag_number => 5)
11
+ upsert.row({ :name => 'Jerry'}, :tag_number => 6 )
12
+ expect(upsert.instance_variable_get(:@merge_function_cache).values.first.use_pg_native?).to(
13
+ UNIQUE_CONSTRAINT && version >= 95 ? be_truthy : be_falsey
14
+ )
15
+ end
16
+ end if ENV['DB'] == 'postgresql'
@@ -7,7 +7,7 @@ describe Upsert do
7
7
  end.map do |path|
8
8
  IO.readlines(path)
9
9
  end.flatten.map(&:chomp).select(&:present?).uniq
10
-
10
+
11
11
  # make lots of AR models, each of which has 10 columns named after these words
12
12
  nasties = []
13
13
  reserved_words.each_slice(10) do |words|
@@ -18,9 +18,9 @@ describe Upsert do
18
18
  nasty = Object.const_get("Nasty#{nasties.length}")
19
19
  nasty.class_eval do
20
20
  self.primary_key = 'fake_primary_key'
21
- col :fake_primary_key
21
+ col :fake_primary_key, limit: 191
22
22
  words.each do |word|
23
- col word
23
+ col word, limit: 191
24
24
  end
25
25
  end
26
26
  nasties << [ nasty, words ]
@@ -28,7 +28,7 @@ describe Upsert do
28
28
  nasties.each do |nasty, _|
29
29
  nasty.auto_upgrade!
30
30
  end
31
-
31
+
32
32
  describe "reserved words" do
33
33
  nasties.each do |nasty, words|
34
34
  it "doesn't die on reserved words #{words.join(',')}" do
@@ -43,4 +43,4 @@ describe Upsert do
43
43
  end
44
44
  end
45
45
  end
46
- end
46
+ end
@@ -4,17 +4,27 @@ require 'sequel'
4
4
  describe Upsert do
5
5
  describe "Plays nice with Sequel" do
6
6
  config = ActiveRecord::Base.connection.instance_variable_get(:@config)
7
- case
8
- when 'postgresql' == config[:adapter]; config[:adapter] = 'postgres'
9
- when 'sqlite3' == config[:adapter]; config[:adapter] = 'sqlite'
7
+ config[:adapter] = case config[:adapter]
8
+ when 'postgresql' then 'postgres'
9
+ when 'sqlie3' then 'sqlite'
10
+ else config[:adapter]
11
+ end
12
+
13
+ let(:db) do
14
+ params = if RUBY_PLATFORM == 'java'
15
+ RawConnectionFactory::CONFIG
16
+ else
17
+ config.slice(:adapter, :host, :database, :username, :password).merge(:user => config[:username])
18
+ end
19
+ Sequel.connect(params)
10
20
  end
11
21
 
12
22
  it "Doesn't explode on connection" do
13
- expect { DB = Sequel.connect config }.to_not raise_error
23
+ expect { db }.to_not raise_error
14
24
  end
15
25
 
16
26
  it "Doesn't explode when using DB.pool.hold" do
17
- DB.pool.hold do |conn|
27
+ db.pool.hold do |conn|
18
28
  expect {
19
29
  upsert = Upsert.new(conn, :pets)
20
30
  assert_creates(Pet, [{:name => 'Jerry', :gender => 'male'}]) do
@@ -25,7 +35,7 @@ describe Upsert do
25
35
  end
26
36
 
27
37
  it "Doesn't explode when using DB.synchronize" do
28
- DB.synchronize do |conn|
38
+ db.synchronize do |conn|
29
39
  expect {
30
40
  upsert = Upsert.new(conn, :pets)
31
41
  assert_creates(Pet, [{:name => 'Jerry', :gender => 'male'}]) do
@@ -35,4 +45,4 @@ describe Upsert do
35
45
  end
36
46
  end
37
47
  end
38
- end
48
+ end
@@ -1,7 +1,8 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
  require 'bundler/setup'
3
3
 
4
- require 'pry'
4
+ # require 'pry'
5
+ require 'shellwords'
5
6
 
6
7
  require 'active_record'
7
8
  ActiveRecord::Base.default_timezone = :utc
@@ -11,11 +12,14 @@ require 'active_record_inline_schema'
11
12
  require 'activerecord-import' if RUBY_VERSION >= '1.9'
12
13
 
13
14
  ENV['DB'] ||= 'mysql'
15
+ ENV['DB'] = 'postgresql' if ENV['DB'].to_s =~ /postgresql/
16
+ UNIQUE_CONSTRAINT = ENV['UNIQUE_CONSTRAINT'] == 'true'
17
+
14
18
 
15
19
  class RawConnectionFactory
16
20
  DATABASE = 'upsert_test'
17
- CURRENT_USER = `whoami`.chomp
18
- PASSWORD = ''
21
+ CURRENT_USER = (ENV['DB_USER'] || `whoami`.chomp)
22
+ PASSWORD = ENV['DB_PASSWORD']
19
23
 
20
24
  case ENV['DB']
21
25
 
@@ -41,9 +45,9 @@ class RawConnectionFactory
41
45
  ActiveRecord::Base.establish_connection :adapter => 'postgresql', :database => DATABASE, :username => CURRENT_USER
42
46
 
43
47
  when 'mysql'
44
- password_argument = (PASSWORD.empty?) ? "" : "-p#{PASSWORD}"
45
- Kernel.system %{ mysql -u #{CURRENT_USER} #{password_argument} -e "DROP DATABASE IF EXISTS #{DATABASE}" }
46
- Kernel.system %{ mysql -u #{CURRENT_USER} #{password_argument} -e "CREATE DATABASE #{DATABASE} CHARSET utf8" }
48
+ password_argument = (PASSWORD.nil?) ? "" : "--password=#{Shellwords.escape(PASSWORD)}"
49
+ Kernel.system %{ mysql -h 127.0.0.1 -u #{CURRENT_USER} #{password_argument} -e "DROP DATABASE IF EXISTS #{DATABASE}" }
50
+ Kernel.system %{ mysql -h 127.0.0.1 -u #{CURRENT_USER} #{password_argument} -e "CREATE DATABASE #{DATABASE} CHARSET utf8mb4 COLLATE utf8mb4_general_ci" }
47
51
  if RUBY_PLATFORM == 'java'
48
52
  CONFIG = "jdbc:mysql://127.0.0.1/#{DATABASE}?user=#{CURRENT_USER}&password=#{PASSWORD}"
49
53
  require 'jdbc/mysql'
@@ -55,12 +59,20 @@ class RawConnectionFactory
55
59
  else
56
60
  require 'mysql2'
57
61
  def new_connection
58
- config = { :username => CURRENT_USER, :database => DATABASE, :host => "127.0.0.1" }
59
- config.merge!(:password => PASSWORD) unless PASSWORD.empty?
62
+ config = { :username => CURRENT_USER, :database => DATABASE, :host => "127.0.0.1", :encoding => 'utf8mb4' }
63
+ config.merge!(:password => PASSWORD) unless PASSWORD.nil?
60
64
  Mysql2::Client.new config
61
65
  end
62
66
  end
63
- ActiveRecord::Base.establish_connection "#{RUBY_PLATFORM == 'java' ? 'mysql' : 'mysql2'}://#{CURRENT_USER}:#{PASSWORD}@127.0.0.1/#{DATABASE}"
67
+ ActiveRecord::Base.establish_connection(
68
+ :adapter => RUBY_PLATFORM == 'java' ? 'mysql' : 'mysql2',
69
+ :user => CURRENT_USER,
70
+ :password => PASSWORD,
71
+ :host => '127.0.0.1',
72
+ :database => DATABASE,
73
+ :encoding => 'utf8mb4'
74
+ )
75
+ ActiveRecord::Base.connection.execute "SET NAMES utf8mb4 COLLATE utf8mb4_general_ci"
64
76
 
65
77
  when 'sqlite3'
66
78
  CONFIG = { :adapter => 'sqlite3', :database => 'file::memory:?cache=shared' }
@@ -102,7 +114,7 @@ else
102
114
  end
103
115
 
104
116
  class Pet < ActiveRecord::Base
105
- col :name
117
+ col :name, limit: 191 # utf8mb4 in mysql requirement
106
118
  col :gender
107
119
  col :spiel
108
120
  col :good, :type => :boolean
@@ -110,6 +122,7 @@ class Pet < ActiveRecord::Base
110
122
  col :morning_walk_time, :type => :datetime
111
123
  col :zipped_biography, :type => :binary
112
124
  col :tag_number, :type => :integer
125
+ col :big_tag_number, :type => :bigint
113
126
  col :birthday, :type => :date
114
127
  col :home_address, :type => :text
115
128
  if ENV['DB'] == 'postgresql'
@@ -117,8 +130,19 @@ class Pet < ActiveRecord::Base
117
130
  end
118
131
  add_index :name, :unique => true
119
132
  end
133
+ if ENV['DB'] == 'postgresql' && UNIQUE_CONSTRAINT
134
+ begin
135
+ Pet.connection.execute("ALTER TABLE pets DROP CONSTRAINT IF EXISTS unique_name")
136
+ rescue => e
137
+ puts e.inspect
138
+ end
139
+ end
140
+
120
141
  Pet.auto_upgrade!
121
142
 
143
+ if ENV['DB'] == 'postgresql' && UNIQUE_CONSTRAINT
144
+ Pet.connection.execute("ALTER TABLE pets ADD CONSTRAINT unique_name UNIQUE (name)")
145
+ end
122
146
 
123
147
  class Task < ActiveRecord::Base
124
148
  col :name
@@ -183,7 +207,7 @@ module SpecHelper
183
207
  def assert_same_result(records, &blk)
184
208
  blk.call(records)
185
209
  ref1 = Pet.order(:name).all.map { |pet| pet.attributes.except('id') }
186
-
210
+
187
211
  Pet.delete_all
188
212
 
189
213
  Upsert.batch($conn, :pets) do |upsert|
@@ -204,6 +228,7 @@ module SpecHelper
204
228
  expected_records.each do |selector, setter|
205
229
  setter ||= {}
206
230
  found = model.where(selector).map { |record| record.attributes.except('id') }
231
+ expect(found).to_not be_empty, { :selector => selector, :setter => setter }.inspect
207
232
  expected = [ selector.stringify_keys.merge(setter.stringify_keys) ]
208
233
  compare_attribute_sets expected, found
209
234
  end
@@ -237,7 +262,7 @@ module SpecHelper
237
262
  Pet.delete_all
238
263
  sleep 1
239
264
  # --
240
-
265
+
241
266
  ar_time = Benchmark.realtime { blk.call(records) }
242
267
 
243
268
  Pet.delete_all
@@ -46,7 +46,7 @@ describe Upsert do
46
46
  end
47
47
  end
48
48
 
49
- # FIXME apparently no longer faster?
49
+ # FIXME apparently no longer faster?
50
50
  # if ENV['DB'] == 'mysql' && RUBY_VERSION >= '1.9'
51
51
  # describe 'compared to activerecord-import' do
52
52
  # it "is faster than faking upserts with activerecord-import" do
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env bash
2
+ # Ref https://github.com/clkao/plv8x/blob/master/.travis.yml
3
+ echo "Install postgres version $PGVERSION"
4
+ psql --version
5
+ sudo /etc/init.d/postgresql stop
6
+ sudo apt-get -y --purge remove postgresql libpq-dev libpq5 postgresql-client-common postgresql-common
7
+ sudo rm -rf /var/lib/postgresql
8
+ wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -
9
+ sudo sh -c "echo deb http://apt.postgresql.org/pub/repos/apt/ precise-pgdg main $PGVERSION >> /etc/apt/sources.list.d/postgresql.list"
10
+ sudo sh -c "echo deb http://apt.postgresql.org/pub/repos/apt/ precise-pgdg-testing main $PGVERSION >> /etc/apt/sources.list.d/postgresql.list"
11
+ sudo apt-get update -qq
12
+ sudo apt-get -y -o Dpkg::Options::=--force-confdef -o Dpkg::Options::="--force-confnew" install postgresql-$PGVERSION postgresql-server-dev-$PGVERSION postgresql-$PGVERSION-plv8
13
+ sudo chmod 777 /etc/postgresql/$PGVERSION/main/pg_hba.conf
14
+ sudo echo "local all postgres trust" > /etc/postgresql/$PGVERSION/main/pg_hba.conf
15
+ sudo echo "local all all trust" >> /etc/postgresql/$PGVERSION/main/pg_hba.conf
16
+ sudo echo "host all all 127.0.0.1/32 trust" >> /etc/postgresql/$PGVERSION/main/pg_hba.conf
17
+ sudo echo "host all all ::1/128 trust" >> /etc/postgresql/$PGVERSION/main/pg_hba.conf
18
+ sudo /etc/init.d/postgresql restart
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env bash
2
+ # Ref https://github.com/travis-ci/travis-ci/issues/2250
3
+ # Ref https://github.com/jruby/activerecord-jdbc-adapter/issues/481
4
+ # JDBC adapters determine the encoding automatically via the server's charset and collation
5
+ echo "Tuning MySQL"
6
+ echo -e "[server]\ncharacter-set-server=utf8mb4\ncollation-server=utf8mb4_unicode_ci\n" | sudo tee -a /etc/mysql/my.cnf
7
+ sudo service mysql restart
@@ -2,7 +2,7 @@
2
2
  require File.expand_path('../lib/upsert/version', __FILE__)
3
3
 
4
4
  Gem::Specification.new do |gem|
5
- gem.authors = ["Seamus Abshere"]
5
+ gem.authors = ["Seamus Abshere", "Phil Schalm"]
6
6
  gem.email = ["seamus@abshere.net"]
7
7
  t = %{Make it easy to upsert on MySQL, PostgreSQL, and SQLite3. Transparently creates merge functions for MySQL and PostgreSQL; on SQLite3, uses INSERT OR IGNORE.}
8
8
  gem.description = t
@@ -32,7 +32,7 @@ Gem::Specification.new do |gem|
32
32
  gem.add_development_dependency 'rake', '~>10.1.1'
33
33
 
34
34
  if RUBY_VERSION >= '1.9'
35
- gem.add_development_dependency 'activerecord-import'
35
+ gem.add_development_dependency 'activerecord-import', '0.11.0' # 0.12 and up were failing
36
36
  else
37
37
  gem.add_development_dependency 'orderedhash'
38
38
  end
@@ -46,11 +46,10 @@ Gem::Specification.new do |gem|
46
46
  gem.add_development_dependency 'activerecord-jdbcmysql-adapter'
47
47
  gem.add_development_dependency 'activerecord-jdbcpostgresql-adapter'
48
48
  else
49
- gem.add_development_dependency 'activerecord-mysql2-adapter'
50
49
  gem.add_development_dependency 'activerecord-postgresql-adapter'
51
50
  gem.add_development_dependency 'sqlite3'
52
- gem.add_development_dependency 'mysql2'
53
- gem.add_development_dependency 'pg'
51
+ gem.add_development_dependency 'mysql2', '~> 0.3.10'
52
+ gem.add_development_dependency 'pg', '~> 0.18.0'
54
53
  # github-flavored markdown
55
54
  if RUBY_VERSION >= '1.9'
56
55
  gem.add_development_dependency 'redcarpet'
metadata CHANGED
@@ -1,14 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: upsert
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.2
4
+ version: 2.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Seamus Abshere
8
+ - Phil Schalm
8
9
  autorequire:
9
10
  bindir: bin
10
11
  cert_chain: []
11
- date: 2016-02-25 00:00:00.000000000 Z
12
+ date: 2017-04-14 00:00:00.000000000 Z
12
13
  dependencies:
13
14
  - !ruby/object:Gem::Dependency
14
15
  name: rspec-core
@@ -168,30 +169,16 @@ dependencies:
168
169
  name: activerecord-import
169
170
  requirement: !ruby/object:Gem::Requirement
170
171
  requirements:
171
- - - ">="
172
+ - - '='
172
173
  - !ruby/object:Gem::Version
173
- version: '0'
174
+ version: 0.11.0
174
175
  type: :development
175
176
  prerelease: false
176
177
  version_requirements: !ruby/object:Gem::Requirement
177
178
  requirements:
178
- - - ">="
179
+ - - '='
179
180
  - !ruby/object:Gem::Version
180
- version: '0'
181
- - !ruby/object:Gem::Dependency
182
- name: activerecord-mysql2-adapter
183
- requirement: !ruby/object:Gem::Requirement
184
- requirements:
185
- - - ">="
186
- - !ruby/object:Gem::Version
187
- version: '0'
188
- type: :development
189
- prerelease: false
190
- version_requirements: !ruby/object:Gem::Requirement
191
- requirements:
192
- - - ">="
193
- - !ruby/object:Gem::Version
194
- version: '0'
181
+ version: 0.11.0
195
182
  - !ruby/object:Gem::Dependency
196
183
  name: activerecord-postgresql-adapter
197
184
  requirement: !ruby/object:Gem::Requirement
@@ -224,30 +211,30 @@ dependencies:
224
211
  name: mysql2
225
212
  requirement: !ruby/object:Gem::Requirement
226
213
  requirements:
227
- - - ">="
214
+ - - "~>"
228
215
  - !ruby/object:Gem::Version
229
- version: '0'
216
+ version: 0.3.10
230
217
  type: :development
231
218
  prerelease: false
232
219
  version_requirements: !ruby/object:Gem::Requirement
233
220
  requirements:
234
- - - ">="
221
+ - - "~>"
235
222
  - !ruby/object:Gem::Version
236
- version: '0'
223
+ version: 0.3.10
237
224
  - !ruby/object:Gem::Dependency
238
225
  name: pg
239
226
  requirement: !ruby/object:Gem::Requirement
240
227
  requirements:
241
- - - ">="
228
+ - - "~>"
242
229
  - !ruby/object:Gem::Version
243
- version: '0'
230
+ version: 0.18.0
244
231
  type: :development
245
232
  prerelease: false
246
233
  version_requirements: !ruby/object:Gem::Requirement
247
234
  requirements:
248
- - - ">="
235
+ - - "~>"
249
236
  - !ruby/object:Gem::Version
250
- version: '0'
237
+ version: 0.18.0
251
238
  - !ruby/object:Gem::Dependency
252
239
  name: redcarpet
253
240
  requirement: !ruby/object:Gem::Requirement
@@ -313,11 +300,13 @@ files:
313
300
  - spec/database_functions_spec.rb
314
301
  - spec/database_spec.rb
315
302
  - spec/hstore_spec.rb
303
+ - spec/jruby_spec.rb
316
304
  - spec/logger_spec.rb
317
305
  - spec/misc/get_postgres_reserved_words.rb
318
306
  - spec/misc/mysql_reserved.txt
319
307
  - spec/misc/pg_reserved.txt
320
308
  - spec/multibyte_spec.rb
309
+ - spec/postgresql_spec.rb
321
310
  - spec/precision_spec.rb
322
311
  - spec/reserved_words_spec.rb
323
312
  - spec/sequel_spec.rb
@@ -326,6 +315,8 @@ files:
326
315
  - spec/threaded_spec.rb
327
316
  - spec/timezones_spec.rb
328
317
  - spec/type_safety_spec.rb
318
+ - travis/install_postgres.sh
319
+ - travis/tune_mysql.sh
329
320
  - upsert.gemspec
330
321
  homepage: https://github.com/seamusabshere/upsert
331
322
  licenses: []
@@ -346,7 +337,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
346
337
  version: '0'
347
338
  requirements: []
348
339
  rubyforge_project:
349
- rubygems_version: 2.2.2
340
+ rubygems_version: 2.6.8
350
341
  signing_key:
351
342
  specification_version: 4
352
343
  summary: Make it easy to upsert on MySQL, PostgreSQL, and SQLite3. Transparently creates
@@ -358,11 +349,13 @@ test_files:
358
349
  - spec/database_functions_spec.rb
359
350
  - spec/database_spec.rb
360
351
  - spec/hstore_spec.rb
352
+ - spec/jruby_spec.rb
361
353
  - spec/logger_spec.rb
362
354
  - spec/misc/get_postgres_reserved_words.rb
363
355
  - spec/misc/mysql_reserved.txt
364
356
  - spec/misc/pg_reserved.txt
365
357
  - spec/multibyte_spec.rb
358
+ - spec/postgresql_spec.rb
366
359
  - spec/precision_spec.rb
367
360
  - spec/reserved_words_spec.rb
368
361
  - spec/sequel_spec.rb
@@ -371,4 +364,3 @@ test_files:
371
364
  - spec/threaded_spec.rb
372
365
  - spec/timezones_spec.rb
373
366
  - spec/type_safety_spec.rb
374
- has_rdoc: