pg_conn 0.19.0 → 0.21.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/TODO +1 -0
- data/lib/pg_conn/version.rb +1 -1
- data/lib/pg_conn.rb +70 -64
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6f293ad25581975cbd57dc5973d072043d6b5cf13093a21bd354720b39c91c12
|
4
|
+
data.tar.gz: 1ad472d136a9b626717f90058cb220eaaf4c87bced2f8802c8cab68b69bf187a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7720aef7f128e0b8e3c47fdd2acb752b1d44af9625653eae0552a2ac619e3c29ae2cad25789f0f0806fe4576104a64210cf5a5d7e7c7fa66817b8538a556bd35
|
7
|
+
data.tar.gz: 61425eaeabb6b6e0872f6ed49064f8650361ecd8be684b7db2322a6a346265e253489e49be37688c9ef74171733d2c8f19a12d38105a188ba263cf5ea48c9593
|
data/TODO
CHANGED
data/lib/pg_conn/version.rb
CHANGED
data/lib/pg_conn.rb
CHANGED
@@ -64,9 +64,13 @@ module PgConn
|
|
64
64
|
attr_reader :session
|
65
65
|
|
66
66
|
# The transaction timestamp of the most recent SQL statement executed by
|
67
|
-
# #exec or #transaction block
|
67
|
+
# #exec or #transaction block. The timestamp is without time zone
|
68
68
|
attr_reader :timestamp
|
69
69
|
|
70
|
+
# The transaction timestamp of the most recent SQL statement executed by
|
71
|
+
# #exec or #transaction block. The timestamp includes the current time zone
|
72
|
+
attr_reader :timestamptz
|
73
|
+
|
70
74
|
# PG::Error object of the first failed statement in the transaction;
|
71
75
|
# otherwise nil. It is cleared at the beginning of a transaction so be sure
|
72
76
|
# to save it before you run any cleanup code that may initiate new
|
@@ -139,6 +143,7 @@ module PgConn
|
|
139
143
|
if args.last.is_a?(Hash)
|
140
144
|
@field_name_class = args.last.delete(:field_name_class) || Symbol
|
141
145
|
@timestamp = args.last.delete(:timestamp)
|
146
|
+
@timestamptz = args.last.delete(:timestamptz)
|
142
147
|
args.pop if args.last.empty?
|
143
148
|
else
|
144
149
|
@field_name_class = Symbol
|
@@ -185,7 +190,7 @@ module PgConn
|
|
185
190
|
@pg_connection.set_notice_processor { |message| ; } # Intentionally a nop
|
186
191
|
|
187
192
|
# Auto-convert to ruby types
|
188
|
-
type_map = PG::BasicTypeMapForResults.new(@pg_connection)
|
193
|
+
type_map = PG::BasicTypeMapForResults.new(@pg_connection)
|
189
194
|
|
190
195
|
# Use String as default type. Kills 'Warning: no type cast defined for
|
191
196
|
# type "uuid" with oid 2950..' warnings
|
@@ -193,7 +198,7 @@ module PgConn
|
|
193
198
|
|
194
199
|
# Timestamp decoder
|
195
200
|
type_map.add_coder PG::TextDecoder::Timestamp.new( # Timestamp without time zone
|
196
|
-
oid: 1114,
|
201
|
+
oid: 1114,
|
197
202
|
flags: PG::Coder::TIMESTAMP_DB_UTC | PG::Coder::TIMESTAMP_APP_UTC)
|
198
203
|
|
199
204
|
# Decode anonymous records but note that this is only useful to convert the
|
@@ -212,13 +217,12 @@ module PgConn
|
|
212
217
|
@role = RoleMethods.new(self)
|
213
218
|
@rdbms = RdbmsMethods.new(self)
|
214
219
|
@session = SessionMethods.new(self)
|
215
|
-
@timestamp = nil
|
216
220
|
@savepoints = nil # Stack of savepoint names. Nil if no transaction in progress
|
217
221
|
end
|
218
222
|
|
219
223
|
# Close the database connection. TODO: Rename 'close'
|
220
224
|
def terminate()
|
221
|
-
@pg_connection.close if @pg_connection && !@pg_connection.finished?
|
225
|
+
@pg_connection.close if @pg_connection && !@pg_connection.finished?
|
222
226
|
end
|
223
227
|
|
224
228
|
def self.new(*args, **opts, &block)
|
@@ -238,7 +242,7 @@ module PgConn
|
|
238
242
|
# Quote value as an identifier. Value should be a non-nil string or a symbol
|
239
243
|
def quote_identifier(s)
|
240
244
|
s = s.to_s if s.is_a?(Symbol)
|
241
|
-
|
245
|
+
@pg_connection.escape_identifier(s)
|
242
246
|
end
|
243
247
|
|
244
248
|
# Quote the value as a string. Emit 'null' if value is nil
|
@@ -247,7 +251,11 @@ module PgConn
|
|
247
251
|
# before quoting. This works by default for the regular types Integer,
|
248
252
|
# true/false, Time/Date/DateTime, and arrays. Other types may require
|
249
253
|
# special handling
|
250
|
-
|
254
|
+
#
|
255
|
+
# Note that a tuple value (an array) must be quoted using #quote_tuple
|
256
|
+
# because #quote_value would quote the tuple as an array instead of a list
|
257
|
+
# of values
|
258
|
+
def quote_value(value)
|
251
259
|
case value
|
252
260
|
when String; @pg_connection.escape_literal(value)
|
253
261
|
when Integer, Float; value.to_s
|
@@ -255,28 +263,23 @@ module PgConn
|
|
255
263
|
when nil; 'null'
|
256
264
|
when Date, DateTime; "'#{value}'"
|
257
265
|
when Time; "'#{value.strftime("%FT%T%:z")}'"
|
258
|
-
when Array; "array[#{value.map { |elem|
|
266
|
+
when Array; "array[#{value.map { |elem| quote_value(elem) }.join(', ')}]"
|
259
267
|
else
|
260
268
|
@pg_connection.escape_literal(value.to_s)
|
261
269
|
end
|
262
270
|
end
|
263
271
|
|
272
|
+
# Quote an array of values as a tuple. Just an alias for #quote_values
|
273
|
+
def quote_tuple(tuple) = quote_values(tuple)
|
264
274
|
|
265
|
-
# Quote
|
266
|
-
def
|
267
|
-
|
268
|
-
# Quote array as a parenthesis-enclosed sequence of identifiers
|
269
|
-
def quote_identifier_list(identifiers) = "(#{quote_identifier_seq(identifiers)})"
|
270
|
-
|
271
|
-
# Quote array as a comma-separated sequence of values
|
272
|
-
def quote_value_seq(values) = values.map { quote_literal(_1) }.join(', ')
|
275
|
+
# Quote identifiers and concatenate them using ',' as separator
|
276
|
+
def quote_identifiers(idents) = idents.map { |ident| quote_identifier(ident) }.join(", ")
|
273
277
|
|
274
|
-
# Quote
|
275
|
-
def
|
278
|
+
# Quote values and concatenate them using ',' as separator
|
279
|
+
def quote_values(values) = values.map { |value| quote_value(value) }.join(", ")
|
276
280
|
|
277
|
-
#
|
278
|
-
def
|
279
|
-
def quote_literal_list(values) = quote_value_list(values)
|
281
|
+
# Quote an array of tuples
|
282
|
+
def quote_tuples(tuples) = tuples.map { |tuple| "(#{quote_values(tuple)})" }.join(", ")
|
280
283
|
|
281
284
|
# :call-seq:
|
282
285
|
# exist?(query)
|
@@ -287,7 +290,7 @@ module PgConn
|
|
287
290
|
# check if the query returns one or more records
|
288
291
|
def exist?(*args)
|
289
292
|
arg1, arg2 = *args
|
290
|
-
query =
|
293
|
+
query =
|
291
294
|
case arg2
|
292
295
|
when Integer; "select from #{arg1} where id = #{arg2}"
|
293
296
|
when String; "select from #{arg1} where #{arg2}"
|
@@ -301,7 +304,7 @@ module PgConn
|
|
301
304
|
# count(table, where_clause = nil)
|
302
305
|
#
|
303
306
|
# Return true if the table or the result of the query is empty
|
304
|
-
def empty?(arg, where_clause = nil)
|
307
|
+
def empty?(arg, where_clause = nil)
|
305
308
|
if arg =~ /\s/
|
306
309
|
value "select count(*) from (#{arg} limit 1) as inner_query"
|
307
310
|
elsif where_clause
|
@@ -324,6 +327,12 @@ module PgConn
|
|
324
327
|
end
|
325
328
|
end
|
326
329
|
|
330
|
+
# TODO
|
331
|
+
# Query variants
|
332
|
+
# (sql) - simple SQL statement
|
333
|
+
# (schema = nil, table, id-or-where-clause = nil, field-or-fields)
|
334
|
+
#
|
335
|
+
|
327
336
|
# Return a single value. It is an error if the query doesn't return a
|
328
337
|
# single record with a single column. If :transaction is true, the query
|
329
338
|
# will be executed in a transaction and also be committed if :commit is
|
@@ -551,23 +560,12 @@ module PgConn
|
|
551
560
|
# columns (like #tuples)
|
552
561
|
#
|
553
562
|
def call(name, *args, proc: false) # :proc may interfere with hashes
|
554
|
-
|
555
|
-
case arg
|
556
|
-
when NilClass; "null"
|
557
|
-
when String; "'#{arg}'"
|
558
|
-
when Integer; arg
|
559
|
-
when TrueClass, FalseClass; arg
|
560
|
-
when Array; "Array['#{arg.join("', '")}']" # Quick and dirty # FIXME
|
561
|
-
when Hash; raise NotImplementedError
|
562
|
-
else
|
563
|
-
raise ArgumentError, "Unrecognized value: #{arg.inspect}"
|
564
|
-
end
|
565
|
-
}.join(", ")
|
563
|
+
args_seq = quote_values(args)
|
566
564
|
if proc
|
567
|
-
pg_exec "call #{name}(#{
|
565
|
+
pg_exec "call #{name}(#{args_seq})"
|
568
566
|
return nil
|
569
567
|
else
|
570
|
-
r = pg_exec "select * from #{name}(#{
|
568
|
+
r = pg_exec "select * from #{name}(#{args_seq})"
|
571
569
|
if r.ntuples == 0
|
572
570
|
raise Error, "No records returned"
|
573
571
|
elsif r.ntuples == 1
|
@@ -611,15 +609,14 @@ module PgConn
|
|
611
609
|
table = schema ? "#{schema}.#{table}" : table
|
612
610
|
|
613
611
|
# Find method and normalize data
|
614
|
-
if data.is_a?(Array)
|
612
|
+
if data.is_a?(Array) # Array of tuples
|
613
|
+
method = :values
|
615
614
|
if data.empty?
|
616
615
|
return []
|
617
|
-
elsif data.first.is_a?(Array)
|
618
|
-
method = :values
|
616
|
+
elsif data.first.is_a?(Array) # Tuple (array) element. Requires the 'fields' argument
|
619
617
|
fields or raise ArgumentError
|
620
618
|
tuples = data
|
621
|
-
elsif data.first.is_a?(Hash)
|
622
|
-
method = :values
|
619
|
+
elsif data.first.is_a?(Hash) # Hash element
|
623
620
|
fields ||= data.first.keys
|
624
621
|
tuples = data.map { |record| fields.map { |field| record[field] } }
|
625
622
|
else
|
@@ -633,21 +630,23 @@ module PgConn
|
|
633
630
|
raise ArgumentError
|
634
631
|
end
|
635
632
|
|
636
|
-
#
|
637
|
-
|
638
|
-
|
639
|
-
|
633
|
+
# Execute SQL statement using either :value or :values depending on data arity
|
634
|
+
self.send method, %(
|
635
|
+
insert into #{table} (#{quote_identifiers(fields)})
|
636
|
+
values #{quote_tuples(tuples)}
|
637
|
+
returning id
|
638
|
+
)
|
640
639
|
end
|
641
640
|
|
642
641
|
# Update record(s)
|
643
642
|
def update(schema = nil, table, expr, hash)
|
644
643
|
table = [schema, table].compact.join(".")
|
645
|
-
assignments = hash.map { |k,v| "#{k} = #{
|
646
|
-
constraint =
|
644
|
+
assignments = hash.map { |k,v| "#{k} = #{quote_value(v)}" }.join(", ")
|
645
|
+
constraint =
|
647
646
|
case expr
|
648
647
|
when String; expr
|
649
|
-
when Integer; "id = #{
|
650
|
-
when Array; "id in #{
|
648
|
+
when Integer; "id = #{quote_value(expr)}"
|
649
|
+
when Array; "id in (#{quote_values(expr)})"
|
651
650
|
else
|
652
651
|
raise ArgumentError
|
653
652
|
end
|
@@ -657,11 +656,11 @@ module PgConn
|
|
657
656
|
# Delete record(s)
|
658
657
|
def delete(schema = nil, table, expr)
|
659
658
|
table = [schema, table].compact.join(".")
|
660
|
-
constraint =
|
659
|
+
constraint =
|
661
660
|
case expr
|
662
661
|
when String; expr
|
663
|
-
when Integer; "id = #{
|
664
|
-
when Array; "id in #{
|
662
|
+
when Integer; "id = #{quote_value(expr)}"
|
663
|
+
when Array; "id in (#{quote_values(expr)})"
|
665
664
|
else
|
666
665
|
raise ArgumentError
|
667
666
|
end
|
@@ -676,7 +675,7 @@ module PgConn
|
|
676
675
|
# is not.
|
677
676
|
#
|
678
677
|
# #exec pass Postgres exceptions to the caller unless :fail is false in which case
|
679
|
-
# it returns nil.
|
678
|
+
# it returns nil.
|
680
679
|
#
|
681
680
|
# Note that postgres crashes the whole transaction stack if any error is
|
682
681
|
# met so if you're inside a transaction, the transaction will be in an
|
@@ -692,7 +691,7 @@ module PgConn
|
|
692
691
|
# There is not a corresponding #execute? method because any failure rolls
|
693
692
|
# back the whole transaction stack. TODO: Check which exceptions that
|
694
693
|
# should be captured
|
695
|
-
def exec?(sql, commit: true, silent: true)
|
694
|
+
def exec?(sql, commit: true, silent: true)
|
696
695
|
begin
|
697
696
|
exec(sql, commit: commit, fail: true, silent: silent)
|
698
697
|
rescue PG::Error
|
@@ -727,7 +726,7 @@ module PgConn
|
|
727
726
|
# back to the original user
|
728
727
|
#
|
729
728
|
# FIXME: The out-commented transaction block makes postspec fail for some reason
|
730
|
-
# TODO: Rename 'sudo' because it acts just like it.
|
729
|
+
# TODO: Rename 'sudo' because it acts just like it.
|
731
730
|
def su(username, &block)
|
732
731
|
raise Error, "Missing block in call to PgConn::Connection#su" if !block_given?
|
733
732
|
realuser = self.value "select current_user"
|
@@ -776,7 +775,14 @@ module PgConn
|
|
776
775
|
@savepoints = []
|
777
776
|
pg_exec("begin")
|
778
777
|
@error = @err = nil
|
779
|
-
|
778
|
+
# FIXME This special-cases the situation where commands are logged to a
|
779
|
+
# file instead of being executed. Maybe remove logging (or execute always
|
780
|
+
# and log as a side-effect)
|
781
|
+
if @pg_connection
|
782
|
+
@timestamp, @timestamptz = @pg_connection.exec(
|
783
|
+
'select current_timestamp, current_timestamp::timestamp without time zone'
|
784
|
+
).tuple_values(0)
|
785
|
+
end
|
780
786
|
end
|
781
787
|
end
|
782
788
|
|
@@ -913,7 +919,7 @@ module PgConn
|
|
913
919
|
@error = ex
|
914
920
|
@err = nil
|
915
921
|
end
|
916
|
-
if !silent # FIXME Why do we handle this?
|
922
|
+
if !silent # FIXME Why do we handle this?
|
917
923
|
$stderr.puts arg
|
918
924
|
$stderr.puts
|
919
925
|
$stderr.puts ex.message
|
@@ -933,20 +939,20 @@ module PgConn
|
|
933
939
|
end
|
934
940
|
end
|
935
941
|
|
936
|
-
def check_1c(r)
|
942
|
+
def check_1c(r)
|
937
943
|
case r.nfields
|
938
944
|
when 0; raise Error, "No columns returned"
|
939
|
-
when 1;
|
945
|
+
when 1;
|
940
946
|
else
|
941
|
-
raise Error, "More than one column returned"
|
947
|
+
raise Error, "More than one column returned"
|
942
948
|
end
|
943
949
|
end
|
944
950
|
|
945
|
-
def check_1r(r)
|
951
|
+
def check_1r(r)
|
946
952
|
if r.ntuples == 0
|
947
953
|
raise Error, "No records returned"
|
948
|
-
elsif r.ntuples > 1
|
949
|
-
raise Error, "More than one record returned"
|
954
|
+
elsif r.ntuples > 1
|
955
|
+
raise Error, "More than one record returned"
|
950
956
|
end
|
951
957
|
end
|
952
958
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pg_conn
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.21.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Claus Rasmussen
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-08-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: pg
|