upsert 2.1.2 → 2.2.0

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.
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: