pg_conn 0.33.1 → 0.35.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.
Files changed (4) hide show
  1. checksums.yaml +4 -4
  2. data/lib/pg_conn/version.rb +1 -1
  3. data/lib/pg_conn.rb +163 -32
  4. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '0819b55757ac186639c7615df4a1ec09fb449e85216a19cad30c7a6f8feb139f'
4
- data.tar.gz: fdf28a9434d88abf0fb0f89b3459aeaf2602b1644524a931ab1b84832f9067a3
3
+ metadata.gz: a4c942ce055688931946e0d5a71ddc89a722b2dd98ea6d9d0edc150b39421dd9
4
+ data.tar.gz: 3fea9a99e361c86b3b413845d5f319562051347afe025a27006c80548f822ff4
5
5
  SHA512:
6
- metadata.gz: a053216dde0bb4e3d060ff3dcd58527f2f36b1e560057020a8610c54fa32e00d0390c15841d44e3733039f10fa8a14cf98cb99660bd2d05daa1be9e2941dc139
7
- data.tar.gz: 7c88b089bd4dbd3892ae20622a6edc0e2caf89f837833f78cf99fcb20bca91c92aad885029f5e381946970937414782513c3f7d4053d41512d391af0d6015eff
6
+ metadata.gz: aa391cc15005c51ca01503e33096dacecec5743fa3bf6f28c22d7fd2ad7e14d2e23f48dc2cc0e3e9cade6d77bc359d3b56ebb871015e2db9d08cffcedcf8988f
7
+ data.tar.gz: 6fd5537fe417a8dac42ad2ac3af16c75a329bcafc93118ae2a375a3ec3368d0dc088163f8dbd87d5b56a8cb357b3bc5c76c6f9ec49618d4d4c6099bee472b423
@@ -1,3 +1,3 @@
1
1
  module PgConn
2
- VERSION = "0.33.1"
2
+ VERSION = "0.35.0"
3
3
  end
data/lib/pg_conn.rb CHANGED
@@ -147,13 +147,40 @@ module PgConn
147
147
  attr_reader :session
148
148
 
149
149
  # The transaction timestamp of the most recent SQL statement executed by
150
- # #exec or #transaction block. The timestamp is without time zone
150
+ # #exec or #transaction block. The timestamp is without time zone and WRONG
151
+ # in most cases
151
152
  attr_reader :timestamp
152
153
 
153
154
  # The transaction timestamp of the most recent SQL statement executed by
154
155
  # #exec or #transaction block. The timestamp includes the current time zone
155
156
  attr_reader :timestamptz
156
157
 
158
+ # Controls error messages. It can be assigned true, false, nil. True causes
159
+ # the error message to be printed to standard error, false ignores it, and
160
+ # nil resets the state to the default given when the connection was
161
+ # initialized or false if absent. Note that #silent only controls the error
162
+ # message, the exception is not affected
163
+ def silent() @options[:silent] end
164
+ def silent=(value) set_option(:silent, value) end
165
+
166
+ # Controls notices. It can be assigned true, false, nil, or a Proc object
167
+ # that recieves the message. True causes the message to be printed to
168
+ # standard output, false ignores it, and nil resets the state to the
169
+ # default given when the connection was initialized or false if absent
170
+ def notice() @options[:notice] end
171
+ def notice=(value) set_option(:notice, value) end
172
+
173
+ # Controls warnings. It can be assigned true, false, nil, or a Proc object
174
+ # that recieves the message. True causes the message to be printed to
175
+ # standard error, false ignores it, and nil resets the state to the default
176
+ # given when the connection was initialized or false if absent
177
+ def warning() @options[:warning] end
178
+ def warning=(value) set_option(:warning, value) end
179
+
180
+ # TODO: Move error message handling into the same framework as notice and
181
+ # warning but we have a name collision just below that would need to be
182
+ # resolved somehow
183
+
157
184
  # PG::Error object of the first failed statement in the transaction;
158
185
  # otherwise nil. It is cleared at the beginning of a transaction so be sure
159
186
  # to save it before you run any cleanup code that may initiate new
@@ -163,7 +190,7 @@ module PgConn
163
190
  # True if the transaction is in a error state
164
191
  def error?() !@error.nil? end
165
192
 
166
- # Tuple of error message, lineno, and charno of the error object where each
193
+ # Tuple of error message, lineno, and charno of the error object. Each
167
194
  # element defaults to nil if not found
168
195
  def err
169
196
  @err ||=
@@ -187,13 +214,17 @@ module PgConn
187
214
  # if absent in the Postgres error message
188
215
  def errchar = err[2]
189
216
 
217
+ DEFAULT_OPTIONS = { silent: false, notice: false, warning: false }
218
+
190
219
  # :call-seq:
191
- # initialize(dbname = nil, user = nil, field_name_class: Symbol)
192
- # initialize(connection_hash, field_name_class: Symbol)
193
- # initialize(connection_string, field_name_class: Symbol)
194
- # initialize(host, port, dbname, user, password, field_name_class: Symbol)
195
- # initialize(array, field_name_class: Symbol)
196
- # initialize(pg_connection_object)
220
+ # initialize(dbname = nil, user = nil, **options)
221
+ # initialize(connection_hash, **options)
222
+ # initialize(connection_string, **options)
223
+ # initialize(host, port, dbname, user, password, **options)
224
+ # initialize(array, **options)
225
+ # initialize(pg_connection_object, **options)
226
+ #
227
+ # options can be :notice, :warning, :field_name_class, :timestamp, :timestamptz
197
228
  #
198
229
  # Initialize a connection object and connect to the database
199
230
  #
@@ -216,6 +247,9 @@ module PgConn
216
247
  # Symbol (the default) or String. The :timestamp option is used
217
248
  # internally to set the timestamp for transactions
218
249
  #
250
+ # The :notice and :warning options sets the default output handling this
251
+ # connection (FIXME fails on copied connections)
252
+ #
219
253
  # Note that the connection hash and the connection string may support more
220
254
  # parameters than documented here. Consult
221
255
  # https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING
@@ -223,13 +257,22 @@ module PgConn
223
257
  #
224
258
  # TODO: Change to 'initialize(*args, **opts)'
225
259
  def initialize(*args)
260
+ # Extract connection level options and leave other options (that should
261
+ # be the postgres authentication hash - postgres will emit an error if
262
+ # not)
226
263
  if args.last.is_a?(Hash)
227
- @field_name_class = args.last.delete(:field_name_class) || Symbol
228
- @timestamp = args.last.delete(:timestamp)
229
- @timestamptz = args.last.delete(:timestamptz)
230
- args.pop if args.last.empty?
264
+ opts = args.last
265
+ @field_name_class = opts.delete(:field_name_class) || Symbol
266
+ options = DEFAULT_OPTIONS.transform_values! { |k,v| opts.key?(k) ? opts.delete(k) : v }
267
+
268
+ # FIXME: Is this used?
269
+ @timestamp = opts.delete(:timestamp)
270
+ @timestamptz = opts.delete(:timestamptz)
271
+
272
+ args.pop if opts.empty?
231
273
  else
232
274
  @field_name_class = Symbol
275
+ options = DEFAULT_OPTIONS
233
276
  end
234
277
 
235
278
  # else # We assume that the current user is a postgres superuser
@@ -265,12 +308,12 @@ module PgConn
265
308
  elsif args.size == 5
266
309
  make_connection args[0], args[1], nil, nil, args[2], args[3], args[4]
267
310
  else
268
- raise Error, "Illegal number of parameters: #{args.size}"
311
+ raise Error, "Illegal number of arguments: #{args.size}"
269
312
  end
270
313
 
271
- if @pg_connection && !using_existing_connection
272
- # Set a dummy notice processor to avoid warnings on stderr
273
- @pg_connection.set_notice_processor { |message| ; } # Intentionally a nop
314
+ if @pg_connection #&& !using_existing_connection
315
+ # Set a notice processor that separates notices and warnings
316
+ @pg_connection.set_notice_processor { |message| message_processor(message) }
274
317
 
275
318
  # Auto-convert to ruby types
276
319
  type_map = PG::BasicTypeMapForResults.new(@pg_connection)
@@ -279,23 +322,29 @@ module PgConn
279
322
  # type "uuid" with oid 2950..' warnings
280
323
  type_map.default_type_map = PG::TypeMapAllStrings.new
281
324
 
282
- # Timestamp decoder
325
+ # Timestamp decoder. FIXME What is this? Why only Timestamp and not
326
+ # Timestamptz?
283
327
  type_map.add_coder PG::TextDecoder::Timestamp.new( # Timestamp without time zone
284
328
  oid: 1114,
285
329
  flags: PG::Coder::TIMESTAMP_DB_UTC | PG::Coder::TIMESTAMP_APP_UTC)
286
330
 
287
- # Decode anonymous records but note that this is only useful to convert the
288
- # outermost structure into an array, the elements are not decoded and are
289
- # returned as strings. It is best to avoid anonymous records if possible
331
+ # Decode anonymous records but note that this is only useful to convert
332
+ # the outermost structure into an array, the elements are not decoded
333
+ # and are returned as strings. It is best to avoid anonymous records if
334
+ # possible
290
335
  type_map.add_coder PG::TextDecoder::Record.new(
291
336
  oid: 2249
292
337
  )
293
338
 
294
339
  @pg_connection.type_map_for_results = type_map
295
340
  @pg_connection.field_name_type = @field_name_class.to_s.downcase.to_sym # Use symbol field names
296
- @pg_connection.exec "set client_min_messages to warning;" # Silence warnings
297
341
  end
298
342
 
343
+ # Set options even if there is no connection to avoid special casing
344
+ # absent options
345
+ @options, @default_options = {}, DEFAULT_OPTIONS.merge(options)
346
+ set_options(options) if @pg_connection
347
+
299
348
  @schema = SchemaMethods.new(self)
300
349
  @role = RoleMethods.new(self)
301
350
  @rdbms = RdbmsMethods.new(self)
@@ -307,6 +356,8 @@ module PgConn
307
356
  # per-session settings in #initialize? Are they cleared by #reset too?)
308
357
  def reset
309
358
  @pg_connection.reset
359
+ self.warning = false
360
+ self.notice = false
310
361
  @pg_connection.exec "set client_min_messages to warning;" # Silence warnings
311
362
  end
312
363
 
@@ -644,7 +695,7 @@ module PgConn
644
695
  # of the function. If the :proc option is true the "function" is assumed
645
696
  # to be a procedure
646
697
  #
647
- def call(name, *args, elem_type: nil, silent: false, proc: false) # :proc may interfere with hashes
698
+ def call(name, *args, elem_type: nil, silent: self.silent, proc: false) # :proc may interfere with hashes
648
699
  args_seq = quote_values(args, elem_type: elem_type)
649
700
  if proc
650
701
  pg_exec "call #{name}(#{args_seq})", silent: silent
@@ -668,7 +719,7 @@ module PgConn
668
719
  end
669
720
 
670
721
  # Like #call with :proc set to true
671
- def proc(name, *args, silent: false)
722
+ def proc(name, *args, silent: self.silent)
672
723
  call(name, *args, silent: silent, proc: true)
673
724
  end
674
725
 
@@ -776,6 +827,21 @@ module PgConn
776
827
  exec %(delete from #{table} where #{constraint})
777
828
  end
778
829
 
830
+ # Execute block with global options and resets afterwards. Currently only
831
+ # :silent, :notice and :warning is supported. Very useful in RSpec tests
832
+ #
833
+ # TODO: :error, :fail, :symbol, :schema, :search_path
834
+ #
835
+ def with(**options, &block)
836
+ begin
837
+ saved_options = @options.dup
838
+ set_options(options)
839
+ yield
840
+ ensure
841
+ set_options(saved_options)
842
+ end
843
+ end
844
+
779
845
  # Execute SQL statement(s) in a transaction and return the number of
780
846
  # affected records (if any). Also sets #timestamp unless a transaction is
781
847
  # already in progress. The +sql+ argument can be a command (String) or an
@@ -783,8 +849,8 @@ module PgConn
783
849
  # that span multiple lines. The empty array is a NOP but the empty string
784
850
  # is not.
785
851
  #
786
- # #exec pass Postgres exceptions to the caller unless :fail is false in which case
787
- # it returns nil.
852
+ # #exec pass Postgres exceptions to the caller unless :fail is false in
853
+ # which case it returns nil if an error occurred
788
854
  #
789
855
  # Note that postgres crashes the whole transaction stack if any error is
790
856
  # met so if you're inside a transaction, the transaction will be in an
@@ -792,14 +858,14 @@ module PgConn
792
858
  # transaction stack has collapsed
793
859
  #
794
860
  # TODO: Make sure the transaction stack is emptied on postgres errors
795
- def exec(sql, commit: true, fail: true, silent: false)
861
+ def exec(sql, commit: true, fail: true, silent: self.silent)
796
862
  transaction(commit: commit) { execute(sql, fail: fail, silent: silent) }
797
863
  end
798
864
 
799
- # Like #exec but returns true/false depending on if the command succeeded.
800
- # There is not a corresponding #execute? method because any failure rolls
801
- # back the whole transaction stack. TODO: Check which exceptions that
802
- # should be captured
865
+ # Like #exec but returns true/false depending on if the command succeeded,
866
+ # error messages are suppressed by default. There is no corresponding
867
+ # #execute? method because any failure rolls back the whole transaction
868
+ # stack. TODO: Check which exceptions that should be captured
803
869
  def exec?(sql, commit: true, silent: true)
804
870
  begin
805
871
  exec(sql, commit: commit, fail: true, silent: silent)
@@ -817,7 +883,7 @@ module PgConn
817
883
  # unless :fail is false in which case it returns nil
818
884
  #
819
885
  # TODO: Handle postgres exceptions wrt transaction state and stack
820
- def execute(sql, fail: true, silent: false)
886
+ def execute(sql, fail: true, silent: self.silent)
821
887
  if @pg_connection
822
888
  begin
823
889
  pg_exec(sql, silent: silent)&.cmd_tuples
@@ -938,6 +1004,9 @@ module PgConn
938
1004
  # PgConn::Rollback exception in which case #transaction returns nil. Note
939
1005
  # that the transaction timestamp is set to the start of the first
940
1006
  # transaction even if transactions are nested
1007
+ #
1008
+ # FIXME: There is some strange problem in rspec where an #insert handles
1009
+ # an exception correctly while #exec, #execute, and #transaction does not
941
1010
  def transaction(commit: true, &block)
942
1011
  if block_given?
943
1012
  result = nil
@@ -1102,6 +1171,68 @@ module PgConn
1102
1171
  "select #{field_list} from #{table} where #{where_clause}"
1103
1172
  end
1104
1173
 
1174
+ STDOUT_PRODUCER = lambda { |msg| $stdout.puts msg }
1175
+ STDERR_PRODUCER = lambda { |msg| $stderr.puts msg }
1176
+
1177
+ # FIXME Fails if connection is copied - requires a common options object
1178
+ # that is shared between all connection copies
1179
+ def set_option(option, value)
1180
+ case option
1181
+ when :silent
1182
+ @options[:silent] =
1183
+ case value
1184
+ when true, false; value
1185
+ when nil; @default_options[:silent]
1186
+ else
1187
+ raise ArgumentError, "Illegal value #{value.inspect}"
1188
+ end
1189
+ when :notice, :warning
1190
+ @options[option] =
1191
+ case value
1192
+ when true; option == :notice ? STDOUT_PRODUCER : STDERR_PRODUCER
1193
+ when false; nil
1194
+ when nil; @default_options[option]
1195
+ when Proc; value
1196
+ else
1197
+ raise ArgumentError, "Illegal value #{value.inspect}"
1198
+ end
1199
+ enabled = !@options[option].nil?
1200
+ if option == :notice
1201
+ if enabled
1202
+ @pg_connection.exec "set client_min_messages to notice"
1203
+ elsif warning
1204
+ @pg_connection.exec "set client_min_messages to warning"
1205
+ else
1206
+ @pg_connection.exec "set client_min_messages to error"
1207
+ end
1208
+ elsif !notice
1209
+ if enabled
1210
+ @pg_connection.exec "set client_min_messages to notice"
1211
+ else
1212
+ @pg_connection.exec "set client_min_messages to error"
1213
+ end
1214
+ end
1215
+ else
1216
+ raise ArgumentError, "Illegal option: #{option.inspect}"
1217
+ end
1218
+ end
1219
+
1220
+ def set_options(opts)
1221
+ opts.each { |k,v| set_option(k,v) if !@options.key?(k) || @options[k] != v }
1222
+ end
1223
+
1224
+ def get_options() @options.dup end
1225
+
1226
+ # Called from postgres. Installed in #initialize
1227
+ def message_processor(message)
1228
+ case message
1229
+ when /^NOTICE:\s*(.*)$/; @options[:notice]&.call($1)
1230
+ when /^WARNING:\s*(.*)$/; @options[:warning]&.call($1)
1231
+ else
1232
+ raise "Oops"
1233
+ end
1234
+ end
1235
+
1105
1236
  # :call-seq:
1106
1237
  # pg_exec(string)
1107
1238
  # pg_exec(array)
@@ -1121,7 +1252,7 @@ module PgConn
1121
1252
  # though
1122
1253
  #
1123
1254
  # TODO: Fix silent by not handling exceptions
1124
- def pg_exec(arg, silent: false)
1255
+ def pg_exec(arg, silent: self.silent)
1125
1256
  if @pg_connection
1126
1257
  begin
1127
1258
  last_stmt = nil # To make the current SQL statement visible to the rescue clause. FIXME Not used?
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.33.1
4
+ version: 0.35.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: 2025-03-06 00:00:00.000000000 Z
11
+ date: 2025-03-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pg