upsert 2.1.0 → 2.9.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. checksums.yaml +5 -5
  2. data/.ruby-version +1 -0
  3. data/.standard.yml +1 -0
  4. data/.travis.yml +60 -12
  5. data/CHANGELOG +39 -0
  6. data/Gemfile +12 -1
  7. data/LICENSE +3 -1
  8. data/README.md +47 -6
  9. data/Rakefile +7 -1
  10. data/lib/upsert.rb +54 -11
  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 +11 -5
  15. data/lib/upsert/connection/Java_OrgPostgresqlJdbc_PgConnection.rb +33 -0
  16. data/lib/upsert/connection/PG_Connection.rb +10 -1
  17. data/lib/upsert/connection/jdbc.rb +20 -1
  18. data/lib/upsert/connection/postgresql.rb +2 -3
  19. data/lib/upsert/merge_function.rb +5 -4
  20. data/lib/upsert/merge_function/Java_OrgPostgresqlJdbc_PgConnection.rb +27 -0
  21. data/lib/upsert/merge_function/PG_Connection.rb +11 -42
  22. data/lib/upsert/merge_function/postgresql.rb +215 -1
  23. data/lib/upsert/merge_function/sqlite3.rb +10 -0
  24. data/lib/upsert/version.rb +1 -1
  25. data/spec/active_record_upsert_spec.rb +10 -0
  26. data/spec/correctness_spec.rb +34 -5
  27. data/spec/database_functions_spec.rb +16 -9
  28. data/spec/database_spec.rb +7 -0
  29. data/spec/hstore_spec.rb +56 -55
  30. data/spec/jruby_spec.rb +9 -0
  31. data/spec/logger_spec.rb +8 -6
  32. data/spec/postgresql_spec.rb +94 -0
  33. data/spec/reserved_words_spec.rb +21 -17
  34. data/spec/sequel_spec.rb +26 -7
  35. data/spec/spec_helper.rb +251 -92
  36. data/spec/speed_spec.rb +3 -32
  37. data/spec/threaded_spec.rb +35 -12
  38. data/spec/type_safety_spec.rb +2 -1
  39. data/travis/install_postgres.sh +18 -0
  40. data/travis/run_docker_db.sh +20 -0
  41. data/travis/tune_mysql.sh +7 -0
  42. data/upsert-java.gemspec +13 -0
  43. data/upsert.gemspec +9 -57
  44. data/upsert.gemspec.common +107 -0
  45. metadata +53 -40
  46. data/lib/upsert/connection/Java_OrgPostgresqlJdbc4_Jdbc4Connection.rb +0 -15
  47. data/lib/upsert/merge_function/Java_OrgPostgresqlJdbc4_Jdbc4Connection.rb +0 -39
@@ -3,9 +3,9 @@ class Upsert
3
3
  # @private
4
4
  class Sqlite3 < ColumnDefinition
5
5
  class << self
6
- def all(connection, table_name)
6
+ def all(connection, quoted_table_name)
7
7
  # activerecord-3.2.13/lib/active_record/connection_adapters/sqlite_adapter.rb
8
- connection.execute("PRAGMA table_info(#{connection.quote_ident(table_name)})").map do |row|#, 'SCHEMA').to_hash
8
+ connection.execute("PRAGMA table_info(#{quoted_table_name})").map do |row|#, 'SCHEMA').to_hash
9
9
  if connection.metal.respond_to?(:results_as_hash) and not connection.metal.results_as_hash
10
10
  row = {'name' => row[1], 'type' => row[2], 'dflt_value' => row[4]}
11
11
  end
@@ -25,7 +25,7 @@ class Upsert
25
25
  end
26
26
  end
27
27
  end
28
-
28
+
29
29
  def equality(left, right)
30
30
  "(#{left} IS #{right} OR (#{left} IS NULL AND #{right} IS NULL))"
31
31
  end
@@ -6,16 +6,22 @@ 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)
15
19
  case v
16
- when Time, DateTime
17
- # mysql doesn't like it when you send timezone to a datetime
18
- Upsert.utc_iso8601 v, false
20
+ when DateTime, Time
21
+ date = v.utc
22
+ java.time.LocalDateTime.of(date.year, date.month, date.day, date.hour, date.min, date.sec, date.nsec)
23
+ when Date
24
+ java.time.LocalDate.of(v.year, v.month, v.day)
19
25
  else
20
26
  super
21
27
  end
@@ -0,0 +1,33 @@
1
+ require_relative "jdbc"
2
+ require_relative "postgresql"
3
+
4
+ class Upsert
5
+ class Connection
6
+ # @private
7
+ class Java_OrgPostgresqlJdbc_PgConnection < Connection
8
+ include Jdbc
9
+ include Postgresql
10
+
11
+ def quote_ident(k)
12
+ DOUBLE_QUOTE + k.to_s.gsub(DOUBLE_QUOTE, '""') + DOUBLE_QUOTE
13
+ end
14
+
15
+ def in_transaction?
16
+ # https://github.com/kares/activerecord-jdbc-adapter/commit/4d6e0e0c52d12b0166810dffc9f898141a23bee6
17
+ ![0, 4].include?(metal.get_transaction_state)
18
+ end
19
+
20
+ def bind_value(v)
21
+ case v
22
+ when DateTime, Time
23
+ date = v.utc
24
+ java.time.LocalDateTime.of(date.year, date.month, date.day, date.hour, date.min, date.sec, date.nsec)
25
+ when Date
26
+ java.time.LocalDate.of(v.year, v.month, v.day)
27
+ else
28
+ super
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -1,12 +1,17 @@
1
+ require_relative "postgresql"
2
+
1
3
  class Upsert
2
4
  class Connection
3
5
  # @private
4
6
  class PG_Connection < Connection
5
7
  include Postgresql
6
-
8
+
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}} }
@@ -21,6 +26,10 @@ class Upsert
21
26
  def binary(v)
22
27
  { :value => v.value, :format => 1 }
23
28
  end
29
+
30
+ def in_transaction?
31
+ ![PG::PQTRANS_IDLE, PG::PQTRANS_UNKNOWN].include?(metal.transaction_status)
32
+ end
24
33
  end
25
34
  end
26
35
  end
@@ -4,11 +4,15 @@ 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',
12
+ java.sql.Types::BIGINT => 'getLong',
11
13
  java.sql.Types::INTEGER => 'getInt',
14
+ java.sql.Types::REAL => "getLong",
15
+ java.sql.Types::ARRAY => ->(r, i){ r.getArray(i).array.to_ary }
12
16
  }
13
17
  java.sql.Types.constants.each do |type_name|
14
18
  i = java.sql.Types.const_get type_name
@@ -22,6 +26,7 @@ class Upsert
22
26
  'TrueClass' => 'setBoolean',
23
27
  'FalseClass' => 'setBoolean',
24
28
  'Fixnum' => 'setInt',
29
+ 'Integer' => 'setInt'
25
30
  )
26
31
 
27
32
  def binary(v)
@@ -34,16 +39,24 @@ class Upsert
34
39
  setters = self.class.const_get(:SETTER)
35
40
  statement = metal.prepareStatement sql
36
41
  params.each_with_index do |v, i|
42
+ if v.is_a?(Fixnum) && v > 2_147_483_647
43
+ statement.setLong i+1, v
44
+ next
45
+ end
46
+
37
47
  case v
38
48
  when Upsert::Binary
39
49
  statement.setBytes i+1, binary(v)
40
- when BigDecimal
50
+ when Float, BigDecimal
41
51
  statement.setBigDecimal i+1, java.math.BigDecimal.new(v.to_s)
42
52
  when NilClass
43
53
  # http://stackoverflow.com/questions/4243513/why-does-preparedstatement-setnull-requires-sqltype
44
54
  statement.setObject i+1, nil
55
+ when java.time.LocalDateTime, java.time.Instant, java.time.LocalDate
56
+ statement.setObject i+1, v
45
57
  else
46
58
  setter = setters[v.class.name]
59
+ Upsert.logger.debug { "Setting [#{v.class}, #{v}] via #{setter}" }
47
60
  statement.send setter, i+1, v
48
61
  end
49
62
  end
@@ -72,6 +85,8 @@ class Upsert
72
85
  column_name, getter = cg
73
86
  if getter == 'getNull'
74
87
  row[column_name] = nil
88
+ elsif getter.respond_to?(:call)
89
+ row[column_name] = getter.call(raw_result, i)
75
90
  else
76
91
  row[column_name] = raw_result.send(getter, i)
77
92
  end
@@ -81,6 +96,10 @@ class Upsert
81
96
  statement.close
82
97
  result
83
98
  end
99
+
100
+ def in_transaction?
101
+ raise "Not implemented"
102
+ end
84
103
  end
85
104
  end
86
105
  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,11 +11,11 @@ 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
- selector_keys.join('_A_'),
16
+ selector_keys.join('_A_').gsub(" ","_"),
17
17
  'SET',
18
- setter_keys.join('_A_')
18
+ setter_keys.join('_A_').gsub(" ","_")
19
19
  ].join('_')
20
20
  if parts.length > MAX_NAME_LENGTH
21
21
  # maybe i should md5 instead
@@ -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
@@ -0,0 +1,27 @@
1
+ require 'upsert/merge_function/postgresql'
2
+
3
+ class Upsert
4
+ class MergeFunction
5
+ # @private
6
+ class Java_OrgPostgresqlJdbc_PgConnection < MergeFunction
7
+ ERROR_CLASS = org.postgresql.util.PSQLException
8
+ include Postgresql
9
+
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
+ "?"
15
+ end
16
+ controller.connection.execute(query, query_args)
17
+ end
18
+
19
+ def unique_index_on_selector?
20
+ return @unique_index_on_selector if defined?(@unique_index_on_selector)
21
+ @unique_index_on_selector = unique_index_columns.any? do |row|
22
+ row["index_columns"].sort == selector_keys.sort
23
+ end
24
+ end
25
+ end
26
+ end
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
+ res = unique_index_columns.tap { |r| r.type_map = type_map }
19
+
20
+ @unique_index_on_selector = res.values.any? do |row|
21
+ row.first.sort == selector_keys.sort
53
22
  end
54
23
  end
55
24
  end
@@ -40,13 +40,227 @@ 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 !@assume_function_exists && (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
+ @function_exists ||= controller.connection.execute("SELECT count(*) AS cnt FROM pg_proc WHERE lower(proname) = lower('#{name}')").first["cnt"].to_i > 0
89
+ end
90
+
91
+ # strangely ? can't be used as a placeholder
43
92
  def sql
44
93
  @sql ||= begin
45
- bind_params = Array.new(selector_keys.length + setter_keys.length, '?') + Array.new(hstore_delete_handlers.length, '?::text[]')
94
+ bind_params = []
95
+ i = 1
96
+ (selector_keys.length + setter_keys.length).times do
97
+ bind_params << "$#{i}"
98
+ i += 1
99
+ end
100
+ hstore_delete_handlers.length.times do
101
+ bind_params << "$#{i}::text[]"
102
+ i += 1
103
+ end
46
104
  %{SELECT #{name}(#{bind_params.join(', ')})}
47
105
  end
48
106
  end
49
107
 
108
+ def use_pg_native?
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
114
+ end
115
+
116
+ def server_version
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
154
+ end
155
+
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
+
167
+ execute_parameterized(
168
+ %{
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
181
+ },
182
+ table_name_arguments
183
+ )
184
+ end
185
+
186
+ def pg_native(row)
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" : ""
190
+
191
+ upsert_sql = %{
192
+ INSERT INTO #{quoted_table_name} (#{quoted_setter_names.join(',')})
193
+ VALUES (#{insert_bind_placeholders(row).join(', ')})
194
+ ON CONFLICT(#{quoted_selector_names.join(', ')})
195
+ DO UPDATE SET #{quoted_setter_names.zip(conflict_bind_placeholders(row)).map { |n, v| "#{n} = #{v}" }.join(', ')}
196
+ }
197
+
198
+ execute_parameterized(upsert_sql, bind_setter_values)
199
+ end
200
+
201
+ def hstore_delete_function(sql, row, column_definition)
202
+ parts = []
203
+ if row.hstore_delete_keys.key?(column_definition.name)
204
+ parts << "DELETE("
205
+ end
206
+ parts << sql
207
+ if row.hstore_delete_keys.key?(column_definition.name)
208
+ keys = row.hstore_delete_keys[column_definition.name].map { |k| "'#{k.to_s.gsub("'", "\\'")}'" }
209
+ parts << ", ARRAY[#{keys.join(', ')}])"
210
+ end
211
+
212
+ parts.join(" ")
213
+ end
214
+
215
+ def insert_bind_placeholders(row)
216
+ if row.hstore_delete_keys.empty?
217
+ @insert_bind_placeholders ||= setter_column_definitions.each_with_index.map do |column_definition, i|
218
+ if column_definition.hstore?
219
+ "CAST($#{i + 1} AS hstore)"
220
+ else
221
+ "$#{i + 1}"
222
+ end
223
+ end
224
+ else
225
+ setter_column_definitions.each_with_index.map do |column_definition, i|
226
+ idx = i + 1
227
+ if column_definition.hstore?
228
+ hstore_delete_function("CAST($#{idx} AS hstore)", row, column_definition)
229
+ else
230
+ "$#{idx}"
231
+ end
232
+ end
233
+ end
234
+ end
235
+
236
+ def conflict_bind_placeholders(row)
237
+ if row.hstore_delete_keys.empty?
238
+ @conflict_bind_placeholders ||= setter_column_definitions.each_with_index.map do |column_definition, i|
239
+ idx = i + 1
240
+ if column_definition.hstore?
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))" \
243
+ + " END"
244
+ else
245
+ "$#{idx}"
246
+ end
247
+ end
248
+ else
249
+ setter_column_definitions.each_with_index.map do |column_definition, i|
250
+ idx = i + 1
251
+ if column_definition.hstore?
252
+ "CASE WHEN #{quoted_table_name}.#{column_definition.quoted_name} IS NULL THEN " \
253
+ + hstore_delete_function("CAST($#{idx} AS hstore)", row, column_definition) \
254
+ + " ELSE " \
255
+ + hstore_delete_function("(#{quoted_table_name}.#{column_definition.quoted_name} || CAST($#{idx} AS hstore))", row, column_definition) \
256
+ + " END"
257
+ else
258
+ "$#{idx}"
259
+ end
260
+ end
261
+ end
262
+ end
263
+
50
264
  class HstoreDeleteHandler
51
265
  attr_reader :merge_function
52
266
  attr_reader :column_definition