pg_sql_caller 1.1.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 97ef871de183917ba98a3c35bdeb4a184c5aa39ab7b1e6cdc89b11ccb60e8874
4
- data.tar.gz: d18b3aaada2a2a432e892f067233d6d22a45f2bd39045b1015d6b9fb34c6c154
3
+ metadata.gz: dcc72483e5ca6259c0d54d32100d69dc0b95d280a1980326508ef0a925d610fb
4
+ data.tar.gz: 2d59c43a923c2ca41040b5c427a1dead6d025329a9eb67df20ede963b187f55b
5
5
  SHA512:
6
- metadata.gz: 75e47d26186bec99d485e7661343f8096386bea04f380385df624e53ba50e0859e89fec3d6de621efe077710a8919dfe28bcd6000c7b45695588132deddd5f36
7
- data.tar.gz: 5863e6f4406d10316fbfe76f4ee7504ff79e3175786e702315b39ceef2d78ade4249fdd370cbfc0b39210ba339927e04e9ac8d58e8748b86c5619cd808b819ce
6
+ metadata.gz: 5d416d3882391975f85c6145fcac4e05a7a09d97dbbfb4c14f232121c1e4e63054efea8e7901ebc8093ed3c5388bc0791236f494b187ce3cf08e8838081828ee
7
+ data.tar.gz: a7280cd40f97b54f51f3c9deb241dc41d1448850e0f7df092b2c6da54ce0c8bfe4d2bdcf4835d91253ff9ea0aff0564ad9da80d05b0c2faf325633f3211b2eab
data/CHANGELOG.md CHANGED
@@ -5,6 +5,17 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.1.1] - 2026-06-22
9
+
10
+ ### Fixed
11
+
12
+ - `PgSqlCaller::BulkUpdate` no longer truncates `datetime`/`time` values to whole seconds.
13
+ PostgreSQL's default timestamp array encoder formats elements via Ruby's `Time#to_s`,
14
+ dropping sub-seconds — which silently corrupted sub-second writes and, worse, broke any
15
+ `unique_by` match on a sub-second timestamp key (the truncated bind never equalled the
16
+ stored value, so the row was missed and the update became a silent no-op). Temporal
17
+ columns are now encoded at full microsecond precision.
18
+
8
19
  ## [1.1.0] - 2026-06-18
9
20
 
10
21
  ### Added
@@ -216,10 +216,57 @@ module PgSqlCaller
216
216
  def bindings
217
217
  columns.map do |col|
218
218
  values = attrs_list.map { |attrs| attrs[col] }
219
- sql_caller.typecast_array(values, type: model_class.type_for_attribute(col.to_s).type)
219
+ encode_column_array(col, values)
220
220
  end
221
221
  end
222
222
 
223
+ # Encode one column's values as a PostgreSQL array literal for its `?::<sql_type>[]`
224
+ # placeholder. Temporal columns are encoded at full microsecond precision: PostgreSQL's
225
+ # default timestamp/time array encoder formats elements via Ruby's `Time#to_s`, which
226
+ # truncates to whole seconds — silently corrupting writes and, worse, breaking any
227
+ # `unique_by` match on a sub-second key (the truncated bind never equals the stored
228
+ # sub-second value, so the row is missed). Non-temporal columns use the standard
229
+ # typed-array encoder unchanged.
230
+ #
231
+ # @param col [Symbol] the column name
232
+ # @param values [Array] the per-row values for that column
233
+ # @return [String] a PostgreSQL array literal
234
+ def encode_column_array(col, values)
235
+ ar_type = model_class.type_for_attribute(col.to_s)
236
+ case ar_type.type
237
+ when :datetime then format_date_time_array(ar_type, values, include_date: true)
238
+ when :time then format_date_time_array(ar_type, values, include_date: false)
239
+ else sql_caller.typecast_array(values, type: ar_type.type)
240
+ end
241
+ end
242
+
243
+ # Build a `{...}` array literal of microsecond-precision temporal literals, reparsed to
244
+ # the column's real type by the surrounding `?::<sql_type>[]` cast with no precision
245
+ # loss. When `include_date` is set (`datetime` columns) the value is normalized to UTC and
246
+ # suffixed `+00:00` — correct for both `timestamp` (the offset is ignored) and `timestamptz`
247
+ # (the offset is honored); otherwise (`time` columns) only the wall-clock time of day is
248
+ # emitted, with no date or zone. Each element is built from a value already cast to a Time
249
+ # and then `strftime`'d into a fixed numeric format, so the literal can hold only
250
+ # `[-0-9:. +]` and needs no escaping. `nil` becomes SQL `NULL`.
251
+ #
252
+ # @param ar_type [ActiveRecord::Type::Value] the column's cast type, used to coerce each
253
+ # value to a Time
254
+ # @param values [Array] the per-row values for that column
255
+ # @param include_date [Boolean] true for `datetime` (date + time, normalized to UTC),
256
+ # false for `time` (time of day only)
257
+ # @return [String] a PostgreSQL array literal
258
+ def format_date_time_array(ar_type, values, include_date:)
259
+ elements = values.map do |value|
260
+ time = ar_type.cast(value)
261
+ next 'NULL' if time.nil?
262
+
263
+ time = time.utc if include_date
264
+ formatted = include_date ? time.strftime('%Y-%m-%d %H:%M:%S.%6N%:z') : time.strftime('%H:%M:%S.%6N')
265
+ %("#{formatted}")
266
+ end
267
+ "{#{elements.join(',')}}"
268
+ end
269
+
223
270
  # The PostgreSQL type of a column, used to build its array cast.
224
271
  #
225
272
  # @param col [Symbol] a column name
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PgSqlCaller
4
- VERSION = '1.1.0'
4
+ VERSION = '1.1.1'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pg_sql_caller
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Denis Talakevich