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 +4 -4
- data/CHANGELOG.md +11 -0
- data/lib/pg_sql_caller/bulk_update.rb +48 -1
- data/lib/pg_sql_caller/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: dcc72483e5ca6259c0d54d32100d69dc0b95d280a1980326508ef0a925d610fb
|
|
4
|
+
data.tar.gz: 2d59c43a923c2ca41040b5c427a1dead6d025329a9eb67df20ede963b187f55b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
|
|
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
|