pg_conn 0.19.0 → 0.21.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|