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 +4 -4
- data/.travis.yml +30 -8
- data/CHANGELOG +7 -0
- data/README.md +6 -0
- data/lib/upsert.rb +2 -2
- data/lib/upsert/connection/Java_ComMysqlJdbc_JDBC4Connection.rb +6 -2
- data/lib/upsert/connection/Java_OrgPostgresqlJdbc4_Jdbc4Connection.rb +5 -0
- data/lib/upsert/connection/PG_Connection.rb +5 -1
- data/lib/upsert/connection/jdbc.rb +13 -0
- data/lib/upsert/merge_function/Java_OrgPostgresqlJdbc4_Jdbc4Connection.rb +13 -25
- data/lib/upsert/merge_function/PG_Connection.rb +11 -42
- data/lib/upsert/merge_function/postgresql.rb +153 -1
- data/lib/upsert/version.rb +1 -1
- data/spec/active_record_upsert_spec.rb +10 -0
- data/spec/correctness_spec.rb +14 -0
- data/spec/database_functions_spec.rb +12 -9
- data/spec/hstore_spec.rb +3 -17
- data/spec/jruby_spec.rb +9 -0
- data/spec/logger_spec.rb +4 -3
- data/spec/postgresql_spec.rb +16 -0
- data/spec/reserved_words_spec.rb +5 -5
- data/spec/sequel_spec.rb +17 -7
- data/spec/spec_helper.rb +37 -12
- data/spec/speed_spec.rb +1 -1
- data/travis/install_postgres.sh +18 -0
- data/travis/tune_mysql.sh +7 -0
- data/upsert.gemspec +4 -5
- metadata +22 -30
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dfb6bea0fbe024f56a107ddb4db99ef247b5ebdb
|
4
|
+
data.tar.gz: 6a3f979f4b136debf07b327073b2e774fdd60322
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 850b04b05fab5c9ec30b9c27253fe8b751beef9dbbc7718abdd6cfbe93d6054796361cde49543018dd3afaf1c59ffe0c8baca1f2874b58053a94726cbf444112
|
7
|
+
data.tar.gz: 49db75e385e5d9cb3960e9f8d8927e8fa0657932e8422fe5ee7f77bcaf77a8dfc5b844b28c8a061953a285590b0c6ed9a1e8f87afb6c7c3ab1ba0d8dbdb8e312
|
data/.travis.yml
CHANGED
@@ -1,18 +1,40 @@
|
|
1
|
+
sudo: required
|
2
|
+
dist: trusty
|
1
3
|
language: ruby
|
2
4
|
global:
|
3
5
|
- USERNAME=travis
|
4
|
-
|
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
|
7
|
-
- 2.2
|
8
|
-
- 2.1
|
16
|
+
- 2.3
|
17
|
+
- 2.2
|
18
|
+
- 2.1
|
9
19
|
- 1.9.3
|
10
|
-
- rbx
|
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=
|
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
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
|
data/lib/upsert.rb
CHANGED
@@ -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
|
-
|
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
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
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
|
10
|
-
|
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
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
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 =
|
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
|
data/lib/upsert/version.rb
CHANGED
@@ -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
|
data/spec/correctness_spec.rb
CHANGED
@@ -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'])
|
data/spec/hstore_spec.rb
CHANGED
@@ -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']
|
data/spec/jruby_spec.rb
ADDED
@@ -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'
|
data/spec/logger_spec.rb
CHANGED
@@ -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
|
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'
|
data/spec/reserved_words_spec.rb
CHANGED
@@ -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
|
data/spec/sequel_spec.rb
CHANGED
@@ -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
|
-
|
9
|
-
|
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 {
|
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
|
-
|
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
|
-
|
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
|
data/spec/spec_helper.rb
CHANGED
@@ -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.
|
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
|
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.
|
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
|
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
|
data/spec/speed_spec.rb
CHANGED
@@ -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
|
data/upsert.gemspec
CHANGED
@@ -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.
|
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:
|
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:
|
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:
|
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:
|
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:
|
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:
|
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:
|
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.
|
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:
|