upsert 2.2.1 → 2.9.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +5 -5
  2. data/.ruby-version +1 -0
  3. data/.standard.yml +1 -0
  4. data/.travis.yml +54 -31
  5. data/CHANGELOG +9 -0
  6. data/Gemfile +12 -1
  7. data/LICENSE +3 -1
  8. data/README.md +35 -2
  9. data/Rakefile +7 -1
  10. data/lib/upsert.rb +49 -7
  11. data/lib/upsert/column_definition/mysql.rb +2 -2
  12. data/lib/upsert/column_definition/postgresql.rb +9 -8
  13. data/lib/upsert/column_definition/sqlite3.rb +3 -3
  14. data/lib/upsert/connection/Java_ComMysqlJdbc_JDBC4Connection.rb +5 -3
  15. data/lib/upsert/connection/Java_OrgPostgresqlJdbc_PgConnection.rb +33 -0
  16. data/lib/upsert/connection/PG_Connection.rb +5 -0
  17. data/lib/upsert/connection/jdbc.rb +7 -1
  18. data/lib/upsert/connection/postgresql.rb +2 -3
  19. data/lib/upsert/merge_function.rb +3 -2
  20. data/lib/upsert/merge_function/{Java_OrgPostgresqlJdbc4_Jdbc4Connection.rb → Java_OrgPostgresqlJdbc_PgConnection.rb} +2 -2
  21. data/lib/upsert/merge_function/PG_Connection.rb +1 -1
  22. data/lib/upsert/merge_function/postgresql.rb +81 -19
  23. data/lib/upsert/merge_function/sqlite3.rb +10 -0
  24. data/lib/upsert/version.rb +1 -1
  25. data/spec/correctness_spec.rb +20 -5
  26. data/spec/database_functions_spec.rb +6 -2
  27. data/spec/hstore_spec.rb +53 -38
  28. data/spec/logger_spec.rb +1 -1
  29. data/spec/postgresql_spec.rb +81 -3
  30. data/spec/reserved_words_spec.rb +18 -14
  31. data/spec/sequel_spec.rb +16 -7
  32. data/spec/spec_helper.rb +238 -111
  33. data/spec/speed_spec.rb +3 -33
  34. data/spec/threaded_spec.rb +35 -12
  35. data/spec/type_safety_spec.rb +2 -1
  36. data/travis/run_docker_db.sh +20 -0
  37. data/upsert-java.gemspec +12 -0
  38. data/upsert.gemspec +9 -63
  39. data/upsert.gemspec.common +106 -0
  40. metadata +37 -44
  41. data/lib/upsert/connection/Java_OrgPostgresqlJdbc4_Jdbc4Connection.rb +0 -20
@@ -1,3 +1,5 @@
1
+ require_relative "postgresql"
2
+
1
3
  class Upsert
2
4
  class Connection
3
5
  # @private
@@ -7,6 +9,9 @@ class Upsert
7
9
  def execute(sql, params = nil)
8
10
  if params
9
11
  # Upsert.logger.debug { %{[upsert] #{sql} with #{params.inspect}} }
12
+ # The following will blow up if you pass a value that cannot be automatically type-casted,
13
+ # such as passing a string to an integer field. You'll get an error something along the
14
+ # lines of: "invalid input syntax for <type>: <value>"
10
15
  metal.exec sql, convert_binary(params)
11
16
  else
12
17
  Upsert.logger.debug { %{[upsert] #{sql}} }
@@ -4,12 +4,14 @@ class Upsert
4
4
  module Jdbc
5
5
  # /Users/seamusabshere/.rvm/gems/jruby-head/gems/activerecord-jdbc-adapter-1.2.2.1/src/java/arjdbc/jdbc/RubyJdbcConnection.java
6
6
  GETTER = {
7
+ java.sql.Types::CHAR => 'getString',
7
8
  java.sql.Types::VARCHAR => 'getString',
8
9
  java.sql.Types::OTHER => 'getString', # ?! i guess unicode text?
9
10
  java.sql.Types::BINARY => 'getBlob',
10
11
  java.sql.Types::LONGVARCHAR => 'getString',
11
12
  java.sql.Types::BIGINT => 'getLong',
12
13
  java.sql.Types::INTEGER => 'getInt',
14
+ java.sql.Types::REAL => "getLong",
13
15
  java.sql.Types::ARRAY => ->(r, i){ r.getArray(i).array.to_ary }
14
16
  }
15
17
  java.sql.Types.constants.each do |type_name|
@@ -24,6 +26,7 @@ class Upsert
24
26
  'TrueClass' => 'setBoolean',
25
27
  'FalseClass' => 'setBoolean',
26
28
  'Fixnum' => 'setInt',
29
+ 'Integer' => 'setInt'
27
30
  )
28
31
 
29
32
  def binary(v)
@@ -44,13 +47,16 @@ class Upsert
44
47
  case v
45
48
  when Upsert::Binary
46
49
  statement.setBytes i+1, binary(v)
47
- when BigDecimal
50
+ when Float, BigDecimal
48
51
  statement.setBigDecimal i+1, java.math.BigDecimal.new(v.to_s)
49
52
  when NilClass
50
53
  # http://stackoverflow.com/questions/4243513/why-does-preparedstatement-setnull-requires-sqltype
51
54
  statement.setObject i+1, nil
55
+ when java.time.LocalDateTime, java.time.Instant, java.time.LocalDate
56
+ statement.setObject i+1, v
52
57
  else
53
58
  setter = setters[v.class.name]
59
+ Upsert.logger.debug { "Setting [#{v.class}, #{v}] via #{setter}" }
54
60
  statement.send setter, i+1, v
55
61
  end
56
62
  end
@@ -8,9 +8,8 @@ class Upsert
8
8
  # pg array escaping lifted from https://github.com/tlconnor/activerecord-postgres-array/blob/master/lib/activerecord-postgres-array/array.rb
9
9
  '{' + v.map do |vv|
10
10
  vv = vv.to_s.dup
11
- vv.gsub! /\\/, '\&\&'
12
- vv.gsub! /'/, "''"
13
- vv.gsub! /"/, '\"'
11
+ vv.gsub!(/\\/, '\&\&')
12
+ vv.gsub!(/"/, '\"')
14
13
  %{"#{vv}"}
15
14
  end.join(',') + '}'
16
15
  when Hash
@@ -11,7 +11,7 @@ class Upsert
11
11
  def unique_name(table_name, selector_keys, setter_keys)
12
12
  parts = [
13
13
  NAME_PREFIX,
14
- table_name,
14
+ [*table_name].join("_").gsub(/[^\w_]+/, "_"),
15
15
  'SEL',
16
16
  selector_keys.join('_A_').gsub(" ","_"),
17
17
  'SET',
@@ -35,8 +35,9 @@ class Upsert
35
35
  @controller = controller
36
36
  @selector_keys = selector_keys
37
37
  @setter_keys = setter_keys
38
+ @assume_function_exists = assume_function_exists
38
39
  validate!
39
- create! unless assume_function_exists
40
+ create! unless @assume_function_exists
40
41
  end
41
42
 
42
43
  def name
@@ -3,7 +3,7 @@ require 'upsert/merge_function/postgresql'
3
3
  class Upsert
4
4
  class MergeFunction
5
5
  # @private
6
- class Java_OrgPostgresqlJdbc4_Jdbc4Connection < MergeFunction
6
+ class Java_OrgPostgresqlJdbc_PgConnection < MergeFunction
7
7
  ERROR_CLASS = org.postgresql.util.PSQLException
8
8
  include Postgresql
9
9
 
@@ -18,7 +18,7 @@ class Upsert
18
18
 
19
19
  def unique_index_on_selector?
20
20
  return @unique_index_on_selector if defined?(@unique_index_on_selector)
21
- @unique_index_on_selector = schema_query.any? do |row|
21
+ @unique_index_on_selector = unique_index_columns.any? do |row|
22
22
  row["index_columns"].sort == selector_keys.sort
23
23
  end
24
24
  end
@@ -15,7 +15,7 @@ class Upsert
15
15
  return @unique_index_on_selector if defined?(@unique_index_on_selector)
16
16
 
17
17
  type_map = PG::TypeMapByColumn.new([PG::TextDecoder::Array.new])
18
- res = schema_query.tap { |r| r.type_map = type_map }
18
+ res = unique_index_columns.tap { |r| r.type_map = type_map }
19
19
 
20
20
  @unique_index_on_selector = res.values.any? do |row|
21
21
  row.first.sort == selector_keys.sort
@@ -66,7 +66,7 @@ class Upsert
66
66
 
67
67
  first_try = true
68
68
  begin
69
- create! if connection.in_transaction? && !function_exists?
69
+ create! if !@assume_function_exists && (connection.in_transaction? && !function_exists?)
70
70
  execute_parameterized(sql, values.map { |v| connection.bind_value v })
71
71
  rescue self.class::ERROR_CLASS => pg_error
72
72
  if pg_error.message =~ /function #{name}.* does not exist/i
@@ -85,8 +85,7 @@ class Upsert
85
85
  end
86
86
 
87
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
88
+ @function_exists ||= controller.connection.execute("SELECT count(*) AS cnt FROM pg_proc WHERE lower(proname) = lower('#{name}')").first["cnt"].to_i > 0
90
89
  end
91
90
 
92
91
  # strangely ? can't be used as a placeholder
@@ -107,34 +106,93 @@ class Upsert
107
106
  end
108
107
 
109
108
  def use_pg_native?
110
- server_version >= 95 && unique_index_on_selector?
109
+ return @use_pg_native if defined?(@use_pg_native)
110
+
111
+ @use_pg_native = server_version >= 90500 && unique_index_on_selector?
112
+ Upsert.logger.warn "[upsert] WARNING: Not using native PG CONFLICT / UPDATE" unless @use_pg_native
113
+ @use_pg_native
111
114
  end
112
115
 
113
116
  def server_version
114
- @server_version ||=
115
- controller.connection.execute("SHOW server_version").first["server_version"].split('.')[0..1].join('').to_i
117
+ @server_version ||= Upsert::MergeFunction::Postgresql.extract_version(
118
+ controller.connection.execute("SHOW server_version").first["server_version"]
119
+ )
120
+ end
121
+
122
+ # Extracted from https://github.com/dr-itz/activerecord-jdbc-adapter/blob/master/lib/arjdbc/postgresql/adapter.rb
123
+ def self.extract_version(version_string)
124
+ # Use the same versioning format as jdbc-postgresql and libpq
125
+ # https://github.com/dr-itz/activerecord-jdbc-adapter/commit/fd79756374c62fa9d009995dd1914d780e6a3dbf
126
+ # https://github.com/postgres/postgres/blob/master/src/interfaces/libpq/fe-exec.c
127
+ if (match = version_string.match(/([\d\.]*\d).*?/))
128
+ version = match[1].split('.').map(&:to_i)
129
+ # PostgreSQL version representation does not have more than 4 digits
130
+ # From version 10 onwards, PG has changed its versioning policy to
131
+ # limit it to only 2 digits. i.e. in 10.x, 10 being the major
132
+ # version and x representing the patch release
133
+ # Refer to:
134
+ # https://www.postgresql.org/support/versioning/
135
+ # https://www.postgresql.org/docs/10/static/libpq-status.html -> PQserverVersion()
136
+ # for more info
137
+
138
+ if version.size >= 3
139
+ (version[0] * 100 + version[1]) * 100 + version[2]
140
+ elsif version.size == 2
141
+ if version[0] >= 10
142
+ version[0] * 100 * 100 + version[1]
143
+ else
144
+ (version[0] * 100 + version[1]) * 100
145
+ end
146
+ elsif version.size == 1
147
+ version[0] * 100 * 100
148
+ else
149
+ 0
150
+ end
151
+ else
152
+ 0
153
+ end
116
154
  end
117
155
 
118
- def schema_query
156
+ def unique_index_columns
157
+ if table_name.is_a?(Array) && table_name.length > 1
158
+ schema_argument = '$2'
159
+ table_name_arguments = table_name
160
+ else
161
+ schema_argument = 'ANY(current_schemas(true)::text[])'
162
+ table_name_arguments = [*table_name]
163
+ end
164
+
165
+ table_name_arguments.reverse!
166
+
119
167
  execute_parameterized(
120
168
  %{
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
169
+ SELECT
170
+ ARRAY(
171
+ SELECT pg_get_indexdef(pg_index.indexrelid, k + 1, TRUE)
172
+ FROM
173
+ generate_subscripts(pg_index.indkey, 1) AS k
174
+ ORDER BY k
175
+ ) AS index_columns
176
+ FROM pg_index
177
+ JOIN pg_class AS idx ON idx.oid = pg_index.indexrelid
178
+ JOIN pg_class AS tbl ON tbl.oid = pg_index.indrelid
179
+ JOIN pg_namespace ON pg_namespace.oid = idx.relnamespace
180
+ WHERE pg_index.indisunique IS TRUE AND pg_namespace.nspname = #{schema_argument} AND tbl.relname = $1
125
181
  },
126
- [table_name]
182
+ table_name_arguments
127
183
  )
128
184
  end
129
185
 
130
186
  def pg_native(row)
131
187
  bind_setter_values = row.setter.values.map { |v| connection.bind_value v }
188
+ # TODO: Is this needed?
189
+ row_syntax = server_version >= 100 ? "ROW" : ""
132
190
 
133
191
  upsert_sql = %{
134
192
  INSERT INTO #{quoted_table_name} (#{quoted_setter_names.join(',')})
135
193
  VALUES (#{insert_bind_placeholders(row).join(', ')})
136
194
  ON CONFLICT(#{quoted_selector_names.join(', ')})
137
- DO UPDATE SET (#{quoted_setter_names.join(', ')}) = (#{conflict_bind_placeholders(row).join(', ')})
195
+ DO UPDATE SET #{quoted_setter_names.zip(conflict_bind_placeholders(row)).map { |n, v| "#{n} = #{v}" }.join(', ')}
138
196
  }
139
197
 
140
198
  execute_parameterized(upsert_sql, bind_setter_values)
@@ -157,13 +215,17 @@ class Upsert
157
215
  def insert_bind_placeholders(row)
158
216
  if row.hstore_delete_keys.empty?
159
217
  @insert_bind_placeholders ||= setter_column_definitions.each_with_index.map do |column_definition, i|
160
- "$#{i + 1}"
218
+ if column_definition.hstore?
219
+ "CAST($#{i + 1} AS hstore)"
220
+ else
221
+ "$#{i + 1}"
222
+ end
161
223
  end
162
224
  else
163
225
  setter_column_definitions.each_with_index.map do |column_definition, i|
164
226
  idx = i + 1
165
227
  if column_definition.hstore?
166
- hstore_delete_function("$#{idx}", row, column_definition)
228
+ hstore_delete_function("CAST($#{idx} AS hstore)", row, column_definition)
167
229
  else
168
230
  "$#{idx}"
169
231
  end
@@ -176,8 +238,8 @@ class Upsert
176
238
  @conflict_bind_placeholders ||= setter_column_definitions.each_with_index.map do |column_definition, i|
177
239
  idx = i + 1
178
240
  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})" \
241
+ "CASE WHEN #{quoted_table_name}.#{column_definition.quoted_name} IS NULL THEN CAST($#{idx} AS hstore) ELSE" \
242
+ + " (#{quoted_table_name}.#{column_definition.quoted_name} || CAST($#{idx} AS hstore))" \
181
243
  + " END"
182
244
  else
183
245
  "$#{idx}"
@@ -188,9 +250,9 @@ class Upsert
188
250
  idx = i + 1
189
251
  if column_definition.hstore?
190
252
  "CASE WHEN #{quoted_table_name}.#{column_definition.quoted_name} IS NULL THEN " \
191
- + hstore_delete_function("$#{idx}", row, column_definition) \
253
+ + hstore_delete_function("CAST($#{idx} AS hstore)", row, column_definition) \
192
254
  + " ELSE " \
193
- + hstore_delete_function("(#{quoted_table_name}.#{column_definition.quoted_name} || $#{idx})", row, column_definition) \
255
+ + hstore_delete_function("(#{quoted_table_name}.#{column_definition.quoted_name} || CAST($#{idx} AS hstore))", row, column_definition) \
194
256
  + " END"
195
257
  else
196
258
  "$#{idx}"
@@ -2,6 +2,16 @@ class Upsert
2
2
  class MergeFunction
3
3
  # @private
4
4
  module Sqlite3
5
+ def self.included(klass)
6
+ klass.extend ClassMethods
7
+ end
8
+
9
+ module ClassMethods
10
+ def clear(*)
11
+ # not necessary
12
+ end
13
+ end
14
+
5
15
  attr_reader :quoted_setter_names
6
16
  attr_reader :quoted_update_names
7
17
  attr_reader :quoted_selector_names
@@ -1,3 +1,3 @@
1
1
  class Upsert
2
- VERSION = '2.2.1'
2
+ VERSION = "2.9.9"
3
3
  end
@@ -13,7 +13,8 @@ describe Upsert do
13
13
  u = Upsert.new($conn, :pets)
14
14
  selector = {:name => 'Jerry', :tag_number => 6}
15
15
  u.row(selector)
16
- Pet.find_by_name('Jerry').tag_number.should == 5
16
+ p.reload.tag_number.should == 5
17
+ next
17
18
 
18
19
  # won't change anything because selector is wrong
19
20
  u = Upsert.new($conn, :pets)
@@ -100,6 +101,15 @@ describe Upsert do
100
101
  lambda { u.row(:name => 'Jerry', :gibberish => 'ba', :gender => 'male') }.should raise_error(/invalid col/i)
101
102
  end
102
103
 
104
+ it "works with a long setter hash" do
105
+ Upsert.batch($conn, :alphabets) do |batch|
106
+ 10_000.times do |time|
107
+ setter = Hash[("a".."z").map { |letter| ["the_letter_#{letter}".to_sym, rand(100)] }]
108
+ selector = Hash[("a".."z").map { |letter| ["the_letter_#{letter}".to_sym, rand(100)] }]
109
+ batch.row(setter, selector)
110
+ end
111
+ end
112
+ end
103
113
  end
104
114
 
105
115
  describe "is just as correct as other ways" do
@@ -107,8 +117,8 @@ describe Upsert do
107
117
  it "is as correct as than new/set/save" do
108
118
  assert_same_result lotsa_records do |records|
109
119
  records.each do |selector, setter|
110
- if pet = Pet.where(selector).first
111
- pet.update_attributes setter, :without_protection => true
120
+ if (pet = Pet.where(selector).first)
121
+ pet.update_attributes(setter)
112
122
  else
113
123
  pet = Pet.new
114
124
  selector.each do |k, v|
@@ -148,12 +158,15 @@ describe Upsert do
148
158
  # end
149
159
  end
150
160
 
151
- if ENV['DB'] == 'mysql' && RUBY_VERSION >= '1.9'
161
+ if ENV['DB'] == 'mysql' || (UNIQUE_CONSTRAINT && ENV["DB"] == "postgresql")
152
162
  describe 'compared to activerecord-import' do
153
163
  it "is as correct as faking upserts with activerecord-import" do
154
164
  assert_same_result lotsa_records do |records|
155
165
  columns = nil
156
166
  all_values = []
167
+ # Reverse because we want to mimic an 'overwrite' of previous values
168
+ records = records.reverse.uniq { |s, _| s } if ENV['DB'] == "postgresql"
169
+
157
170
  records.each do |selector, setter|
158
171
  columns ||= (selector.keys + setter.keys).uniq
159
172
  all_values << columns.map do |k|
@@ -165,7 +178,9 @@ describe Upsert do
165
178
  end
166
179
  end
167
180
  end
168
- Pet.import columns, all_values, :timestamps => false, :on_duplicate_key_update => columns
181
+
182
+ conflict_update = ENV['DB'] == "postgresql" ? {conflict_target: records.first.first.keys, columns: columns} : columns
183
+ Pet.import columns, all_values, :timestamps => false, :on_duplicate_key_update => conflict_update
169
184
  end
170
185
  end
171
186
  end
@@ -1,10 +1,14 @@
1
1
  require 'spec_helper'
2
2
  require 'stringio'
3
+ require 'upsert/merge_function/postgresql'
4
+
3
5
  describe Upsert do
4
6
  describe 'database functions' do
5
- version = 'postgresql' == ENV['DB'] ? Pet.connection.select_value("SHOW server_version")[0..2].split('.').join('').to_i : 0
7
+ version = 'postgresql' == ENV['DB'] ? Upsert::MergeFunction::Postgresql.extract_version(
8
+ Pet.connection.select_value("SHOW server_version")
9
+ ) : 0
6
10
  before(:each) {
7
- skip "Not using DB functions" if 'postgresql' == ENV['DB'] && UNIQUE_CONSTRAINT && version >= 95
11
+ skip "Not using DB functions" if 'postgresql' == ENV['DB'] && UNIQUE_CONSTRAINT && version >= 90500
8
12
  }
9
13
  it "does not re-use merge functions across connections" do
10
14
  begin
data/spec/hstore_spec.rb CHANGED
@@ -3,6 +3,21 @@ require 'spec_helper'
3
3
  describe Upsert do
4
4
  describe 'hstore on pg' do
5
5
  require 'pg_hstore'
6
+
7
+ let(:deserializer) do
8
+ klass = PgHstore.dup
9
+ if RUBY_PLATFORM == "java"
10
+ # activerecord-jdbc-adapter has native support for hstore
11
+ klass.class_eval do
12
+ def self.parse(obj)
13
+ obj
14
+ end
15
+ end
16
+ end
17
+
18
+ klass
19
+ end
20
+
6
21
  Pet.connection.execute 'CREATE EXTENSION IF NOT EXISTS HSTORE'
7
22
  Pet.connection.execute "ALTER TABLE pets ADD COLUMN crazy HSTORE"
8
23
  Pet.connection.execute "ALTER TABLE pets ADD COLUMN cool HSTORE"
@@ -19,7 +34,7 @@ describe Upsert do
19
34
  EOS
20
35
  upsert.row({:name => 'Uggy'}, :crazy => {:uggy => uggy})
21
36
  row = Pet.connection.select_one(%{SELECT crazy FROM pets WHERE name = 'Uggy'})
22
- crazy = PgHstore.parse row['crazy']
37
+ crazy = deserializer.parse row['crazy']
23
38
  crazy.should == { 'uggy' => uggy }
24
39
  end
25
40
 
@@ -30,7 +45,7 @@ EOS
30
45
 
31
46
  upsert.row({:name => 'Bill'}, :crazy => {:a => 1})
32
47
  row = Pet.connection.select_one(%{SELECT crazy FROM pets WHERE name = 'Bill'})
33
- crazy = PgHstore.parse row['crazy']
48
+ crazy = deserializer.parse row['crazy']
34
49
  crazy.should == { 'a' => '1' }
35
50
 
36
51
  upsert.row({:name => 'Bill'}, :crazy => nil)
@@ -39,29 +54,29 @@ EOS
39
54
 
40
55
  upsert.row({:name => 'Bill'}, :crazy => {:a => 1})
41
56
  row = Pet.connection.select_one(%{SELECT crazy FROM pets WHERE name = 'Bill'})
42
- crazy = PgHstore.parse row['crazy']
57
+ crazy = deserializer.parse row['crazy']
43
58
  crazy.should == { 'a' => '1' }
44
59
 
45
60
  upsert.row({:name => 'Bill'}, :crazy => {:whatdat => 'whodat'})
46
61
  row = Pet.connection.select_one(%{SELECT crazy FROM pets WHERE name = 'Bill'})
47
- crazy = PgHstore.parse row['crazy']
62
+ crazy = deserializer.parse row['crazy']
48
63
  crazy.should == { 'a' => '1', 'whatdat' => 'whodat' }
49
64
 
50
65
  upsert.row({:name => 'Bill'}, :crazy => {:whatdat => "D'ONOFRIO"})
51
66
  row = Pet.connection.select_one(%{SELECT crazy FROM pets WHERE name = 'Bill'})
52
- crazy = PgHstore.parse row['crazy']
67
+ crazy = deserializer.parse row['crazy']
53
68
  crazy.should == { 'a' => '1', 'whatdat' => "D'ONOFRIO" }
54
69
 
55
70
  upsert.row({:name => 'Bill'}, :crazy => {:a => 2})
56
71
  row = Pet.connection.select_one(%{SELECT crazy FROM pets WHERE name = 'Bill'})
57
- crazy = PgHstore.parse row['crazy']
72
+ crazy = deserializer.parse row['crazy']
58
73
  crazy.should == { 'a' => '2', 'whatdat' => "D'ONOFRIO" }
59
74
  end
60
75
 
61
76
  it "can nullify entire hstore" do
62
77
  upsert.row({:name => 'Bill'}, :crazy => {:a => 1})
63
78
  row = Pet.connection.select_one(%{SELECT crazy FROM pets WHERE name = 'Bill'})
64
- crazy = PgHstore.parse row['crazy']
79
+ crazy = deserializer.parse row['crazy']
65
80
  crazy.should == { 'a' => '1' }
66
81
 
67
82
  upsert.row({:name => 'Bill'}, :crazy => nil)
@@ -76,42 +91,42 @@ EOS
76
91
 
77
92
  upsert.row({:name => 'Bill'}, :crazy => {:a => 1})
78
93
  row = Pet.connection.select_one(%{SELECT crazy FROM pets WHERE name = 'Bill'})
79
- crazy = PgHstore.parse row['crazy']
94
+ crazy = deserializer.parse row['crazy']
80
95
  crazy.should == { 'a' => '1' }
81
96
 
82
97
  upsert.row({:name => 'Bill'}, :crazy => {})
83
98
  row = Pet.connection.select_one(%{SELECT crazy FROM pets WHERE name = 'Bill'})
84
- crazy = PgHstore.parse row['crazy']
99
+ crazy = deserializer.parse row['crazy']
85
100
  crazy.should == { 'a' => '1' }
86
101
 
87
102
  upsert.row({:name => 'Bill'}, :crazy => {:a => nil})
88
103
  row = Pet.connection.select_one(%{SELECT crazy FROM pets WHERE name = 'Bill'})
89
- crazy = PgHstore.parse row['crazy']
104
+ crazy = deserializer.parse row['crazy']
90
105
  crazy.should == {}
91
106
 
92
107
  upsert.row({:name => 'Bill'}, :crazy => {:a => 1, :b => 5})
93
108
  row = Pet.connection.select_one(%{SELECT crazy FROM pets WHERE name = 'Bill'})
94
- crazy = PgHstore.parse row['crazy']
109
+ crazy = deserializer.parse row['crazy']
95
110
  crazy.should == { 'a' => '1', 'b' => '5' }
96
111
 
97
112
  upsert.row({:name => 'Bill'}, :crazy => {})
98
113
  row = Pet.connection.select_one(%{SELECT crazy FROM pets WHERE name = 'Bill'})
99
- crazy = PgHstore.parse row['crazy']
114
+ crazy = deserializer.parse row['crazy']
100
115
  crazy.should == { 'a' => '1', 'b' => '5' }
101
116
 
102
117
  upsert.row({:name => 'Bill'}, :crazy => {:a => nil})
103
118
  row = Pet.connection.select_one(%{SELECT crazy FROM pets WHERE name = 'Bill'})
104
- crazy = PgHstore.parse row['crazy']
119
+ crazy = deserializer.parse row['crazy']
105
120
  crazy.should == { 'b' => '5' }
106
121
 
107
122
  upsert.row({:name => 'Bill'}, :crazy => {:a => 1, :b => 5})
108
123
  row = Pet.connection.select_one(%{SELECT crazy FROM pets WHERE name = 'Bill'})
109
- crazy = PgHstore.parse row['crazy']
124
+ crazy = deserializer.parse row['crazy']
110
125
  crazy.should == { 'a' => '1', 'b' => '5' }
111
126
 
112
127
  upsert.row({:name => 'Bill'}, :crazy => {:a => nil, :b => nil, :c => 12})
113
128
  row = Pet.connection.select_one(%{SELECT crazy FROM pets WHERE name = 'Bill'})
114
- crazy = PgHstore.parse row['crazy']
129
+ crazy = deserializer.parse row['crazy']
115
130
  crazy.should == { 'c' => '12' }
116
131
  end
117
132
 
@@ -122,111 +137,111 @@ EOS
122
137
 
123
138
  upsert.row({:name => 'Bill'}, :crazy => {:'foo"bar' => 1})
124
139
  row = Pet.connection.select_one(%{SELECT crazy FROM pets WHERE name = 'Bill'})
125
- crazy = PgHstore.parse row['crazy']
140
+ crazy = deserializer.parse row['crazy']
126
141
  crazy.should == { 'foo"bar' => '1' }
127
142
 
128
143
  upsert.row({:name => 'Bill'}, :crazy => {})
129
144
  row = Pet.connection.select_one(%{SELECT crazy FROM pets WHERE name = 'Bill'})
130
- crazy = PgHstore.parse row['crazy']
145
+ crazy = deserializer.parse row['crazy']
131
146
  crazy.should == { 'foo"bar' => '1' }
132
147
 
133
148
  upsert.row({:name => 'Bill'}, :crazy => {:'foo"bar' => nil})
134
149
  row = Pet.connection.select_one(%{SELECT crazy FROM pets WHERE name = 'Bill'})
135
- crazy = PgHstore.parse row['crazy']
150
+ crazy = deserializer.parse row['crazy']
136
151
  crazy.should == {}
137
152
 
138
153
  upsert.row({:name => 'Bill'}, :crazy => {:'foo"bar' => 1, :b => 5})
139
154
  row = Pet.connection.select_one(%{SELECT crazy FROM pets WHERE name = 'Bill'})
140
- crazy = PgHstore.parse row['crazy']
155
+ crazy = deserializer.parse row['crazy']
141
156
  crazy.should == { 'foo"bar' => '1', 'b' => '5' }
142
157
 
143
158
  upsert.row({:name => 'Bill'}, :crazy => {})
144
159
  row = Pet.connection.select_one(%{SELECT crazy FROM pets WHERE name = 'Bill'})
145
- crazy = PgHstore.parse row['crazy']
160
+ crazy = deserializer.parse row['crazy']
146
161
  crazy.should == { 'foo"bar' => '1', 'b' => '5' }
147
162
 
148
163
  upsert.row({:name => 'Bill'}, :crazy => {:'foo"bar' => nil})
149
164
  row = Pet.connection.select_one(%{SELECT crazy FROM pets WHERE name = 'Bill'})
150
- crazy = PgHstore.parse row['crazy']
165
+ crazy = deserializer.parse row['crazy']
151
166
  crazy.should == { 'b' => '5' }
152
167
 
153
168
  upsert.row({:name => 'Bill'}, :crazy => {:'foo"bar' => 1, :b => 5})
154
169
  row = Pet.connection.select_one(%{SELECT crazy FROM pets WHERE name = 'Bill'})
155
- crazy = PgHstore.parse row['crazy']
170
+ crazy = deserializer.parse row['crazy']
156
171
  crazy.should == { 'foo"bar' => '1', 'b' => '5' }
157
172
 
158
173
  upsert.row({:name => 'Bill'}, :crazy => {:'foo"bar' => nil, :b => nil, :c => 12})
159
174
  row = Pet.connection.select_one(%{SELECT crazy FROM pets WHERE name = 'Bill'})
160
- crazy = PgHstore.parse row['crazy']
175
+ crazy = deserializer.parse row['crazy']
161
176
  crazy.should == { 'c' => '12' }
162
177
  end
163
178
 
164
179
  it "handles multiple hstores" do
165
180
  upsert.row({:name => 'Bill'}, :crazy => {:a => 1, :b => 9}, :cool => {:c => 12, :d => 19})
166
181
  row = Pet.connection.select_one(%{SELECT crazy, cool FROM pets WHERE name = 'Bill'})
167
- crazy = PgHstore.parse row['crazy']
182
+ crazy = deserializer.parse row['crazy']
168
183
  crazy.should == { 'a' => '1', 'b' => '9' }
169
- cool = PgHstore.parse row['cool']
184
+ cool = deserializer.parse row['cool']
170
185
  cool.should == { 'c' => '12', 'd' => '19' }
171
186
  end
172
187
 
173
188
  it "can deletes keys from multiple hstores at once" do
174
189
  upsert.row({:name => 'Bill'}, :crazy => {:a => 1}, :cool => {5 => 9})
175
190
  row = Pet.connection.select_one(%{SELECT crazy, cool FROM pets WHERE name = 'Bill'})
176
- crazy = PgHstore.parse row['crazy']
191
+ crazy = deserializer.parse row['crazy']
177
192
  crazy.should == { 'a' => '1' }
178
- cool = PgHstore.parse row['cool']
193
+ cool = deserializer.parse row['cool']
179
194
  cool.should == { '5' => '9' }
180
195
 
181
196
  # NOOP
182
197
  upsert.row({:name => 'Bill'}, :crazy => {}, :cool => {})
183
198
  row = Pet.connection.select_one(%{SELECT crazy, cool FROM pets WHERE name = 'Bill'})
184
- crazy = PgHstore.parse row['crazy']
199
+ crazy = deserializer.parse row['crazy']
185
200
  crazy.should == { 'a' => '1' }
186
- cool = PgHstore.parse row['cool']
201
+ cool = deserializer.parse row['cool']
187
202
  cool.should == { '5' => '9' }
188
203
 
189
204
  upsert.row({:name => 'Bill'}, :crazy => {:a => nil}, :cool => {13 => 17})
190
205
  row = Pet.connection.select_one(%{SELECT crazy, cool FROM pets WHERE name = 'Bill'})
191
- crazy = PgHstore.parse row['crazy']
206
+ crazy = deserializer.parse row['crazy']
192
207
  crazy.should == {}
193
- cool = PgHstore.parse row['cool']
208
+ cool = deserializer.parse row['cool']
194
209
  cool.should == { '5' => '9', '13' => '17' }
195
210
 
196
211
  upsert.row({:name => 'Bill'}, :crazy => {:a => 1, :b => 5})
197
212
  row = Pet.connection.select_one(%{SELECT crazy, cool FROM pets WHERE name = 'Bill'})
198
- crazy = PgHstore.parse row['crazy']
213
+ crazy = deserializer.parse row['crazy']
199
214
  crazy.should == { 'a' => '1', 'b' => '5' }
200
215
 
201
216
  upsert.row({:name => 'Bill'}, :crazy => {:b => nil}, :cool => {5 => nil})
202
217
  row = Pet.connection.select_one(%{SELECT crazy, cool FROM pets WHERE name = 'Bill'})
203
- crazy = PgHstore.parse row['crazy']
218
+ crazy = deserializer.parse row['crazy']
204
219
  crazy.should == {'a' => '1'}
205
- cool = PgHstore.parse row['cool']
220
+ cool = deserializer.parse row['cool']
206
221
  cool.should == {'13' => '17' }
207
222
  end
208
223
 
209
224
  it "deletes keys whether new or existing record" do
210
225
  upsert.row({:name => 'Bill'}, :crazy => {:z => 1, :x => nil})
211
226
  row = Pet.connection.select_one(%{SELECT crazy FROM pets WHERE name = 'Bill'})
212
- crazy = PgHstore.parse row['crazy']
227
+ crazy = deserializer.parse row['crazy']
213
228
  crazy.should == { 'z' => '1' }
214
229
 
215
230
  upsert.row({:name => 'Bill'}, :crazy => {:a => 1})
216
231
  row = Pet.connection.select_one(%{SELECT crazy FROM pets WHERE name = 'Bill'})
217
- crazy = PgHstore.parse row['crazy']
232
+ crazy = deserializer.parse row['crazy']
218
233
  crazy.should == { 'a' => '1', 'z' => '1' }
219
234
  end
220
235
 
221
236
  it "can turn off eager nullify" do
222
237
  upsert.row({:name => 'Bill'}, {:crazy => {:z => 1, :x => nil}}, :eager_nullify => false)
223
238
  row = Pet.connection.select_one(%{SELECT crazy FROM pets WHERE name = 'Bill'})
224
- crazy = PgHstore.parse row['crazy']
239
+ crazy = deserializer.parse row['crazy']
225
240
  crazy.should == { 'z' => '1', 'x' => nil }
226
241
 
227
242
  upsert.row({:name => 'Bill'}, :crazy => {:a => 1})
228
243
  row = Pet.connection.select_one(%{SELECT crazy FROM pets WHERE name = 'Bill'})
229
- crazy = PgHstore.parse row['crazy']
244
+ crazy = deserializer.parse row['crazy']
230
245
  crazy.should == { 'a' => '1', 'z' => '1', 'x' => nil}
231
246
  end
232
247