pg_conn 0.35.0 → 0.36.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 +209 -130
  4. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a4c942ce055688931946e0d5a71ddc89a722b2dd98ea6d9d0edc150b39421dd9
4
- data.tar.gz: 3fea9a99e361c86b3b413845d5f319562051347afe025a27006c80548f822ff4
3
+ metadata.gz: 3d6940809a3f62bfad5cd2a94566a76bcd1944ad5b6ded6df2be159a010b2169
4
+ data.tar.gz: 6f213cb288ba5a85205fd5054fb498e6200b0319eee8c2bbc8c60515705410f7
5
5
  SHA512:
6
- metadata.gz: aa391cc15005c51ca01503e33096dacecec5743fa3bf6f28c22d7fd2ad7e14d2e23f48dc2cc0e3e9cade6d77bc359d3b56ebb871015e2db9d08cffcedcf8988f
7
- data.tar.gz: 6fd5537fe417a8dac42ad2ac3af16c75a329bcafc93118ae2a375a3ec3368d0dc088163f8dbd87d5b56a8cb357b3bc5c76c6f9ec49618d4d4c6099bee472b423
6
+ metadata.gz: a90dab6c75eb2cfd7a99b39676a4aa0dc167462e37e9229d96ed30517b4ff0c691f6b4389ceb6d26c3f2562d7ce4e7dd2be17d054f1e61369989fb9f8c3db8e1
7
+ data.tar.gz: f91893227ef9697eb236051728d509727b2d20b5d9efd7c6f848cf2e887b1abc0e5c8a48da291914372049ab25aee6580b9b74fd422d269c3e7813928032dd23
@@ -1,3 +1,3 @@
1
1
  module PgConn
2
- VERSION = "0.35.0"
2
+ VERSION = "0.36.0"
3
3
  end
data/lib/pg_conn.rb CHANGED
@@ -40,22 +40,22 @@ module PgConn
40
40
  #
41
41
  def self.quote_identifier(s)
42
42
  s = s.to_s if s.is_a?(Symbol)
43
- escape_identifier(s).gsub(/\./, '"."').sub(/"\*"/, "*")
43
+ Literal.new escape_identifier(s).gsub(/\./, '"."').sub(/"\*"/, "*")
44
44
  end
45
45
 
46
46
  # Quote identifiers and concatenate them using ',' as separator
47
- def self.quote_identifiers(idents) = idents.map { |ident| quote_identifier(ident) }.join(", ")
47
+ def self.quote_identifiers(idents) = Literal.new idents.map { |ident| quote_identifier(ident) }.join(", ")
48
48
 
49
- # Quote the value as a string. Emit 'null' if value is nil
49
+ # Quote the value as a string. Returns a Literal object
50
50
  #
51
51
  # The value can be of any type but is converted to a string using #to_s
52
52
  # before quoting. This works by default for the regular types Integer,
53
53
  # true/false, Time/Date/DateTime, and arrays. Other types may require
54
54
  # special handling
55
55
  #
56
- # Hashes are quoted as a literal JSON expression. The result is a string
57
- # and it is the application's responsibility to cast them to either 'json'
58
- # or 'jsonb'
56
+ # Hashes are quoted as a literal JSON expression converted into the given
57
+ # :json_type. If :json_type is nil, it is the application's responsibility to
58
+ # cast the value to either 'json' or 'jsonb'
59
59
  #
60
60
  # Note that a tuple value (an array) must be quoted using #quote_tuple
61
61
  # because #quote_value would quote the tuple as an array value instead of a
@@ -66,48 +66,51 @@ module PgConn
66
66
  # type when the argument is an empty array. It is not needed if the array
67
67
  # is guaranteed to be non-empty. Nested arrays are not supported
68
68
  #
69
- def self.quote_value(value, elem_type: nil)
70
- case value
71
- when Literal; value
72
- when String; escape_literal(value)
73
- when Integer, Float; value.to_s
74
- when true, false; value.to_s
75
- when nil; 'null'
76
- when Date, DateTime; "'#{value}'"
77
- when Time; "'#{value.strftime("%FT%T%:z")}'"
78
- when Array
79
- if value.empty?
80
- elem_type or raise Error, "Empty array without elem_type"
81
- "array[]::#{elem_type}[]"
69
+ def self.quote_value(value, elem_type: nil, json_type: nil)
70
+ Literal.new \
71
+ case value
72
+ when Literal; value
73
+ when String; escape_literal(value)
74
+ when Integer, Float; value.to_s
75
+ when true, false; value.to_s
76
+ when nil; 'null'
77
+ when Date, DateTime; "'#{value}'"
78
+ when Time; "'#{value.strftime("%FT%T%:z")}'"
79
+ when Array
80
+ if value.empty?
81
+ elem_type or raise Error, "Empty array without elem_type"
82
+ "array[]::#{elem_type}[]"
83
+ else
84
+ "array[#{value.map { |elem| quote_value(elem) }.join(', ')}]"
85
+ end
86
+ when Hash; ["'#{value.to_json}'", json_type].compact.join('::')
82
87
  else
83
- "array[#{value.map { |elem| quote_value(elem) }.join(', ')}]"
88
+ escape_literal(value.to_s)
84
89
  end
85
- when Hash; "'#{value.to_json}'"
86
- else
87
- escape_literal(value.to_s)
88
- end
89
90
  end
90
91
 
91
92
  # Quote values and concatenate them using ',' as separator
92
- def self.quote_values(values, elem_type: nil)
93
- values.map { |value| quote_value(value, elem_type: elem_type) }.join(", ")
93
+ def self.quote_values(values, **opts)
94
+ Literal.new values.map { |value| quote_value(value, **opts) }.join(", ")
94
95
  end
95
96
 
96
97
  # Quote an array of values as a tuple. The element types should be in the
97
98
  # same order as the array arguments. #quote_tuples is same as #quote_values
98
99
  # except the values may have different types (this makes no difference
99
100
  # except in the case when the tuple may contain empty array(s))
100
- def self.quote_tuple(tuple, elem_types: nil)
101
+ #
102
+ # Note that it is :elem_types (plural) and not :elem_type
103
+ def self.quote_tuple(tuple, elem_types: nil, **opts)
101
104
  elem_types = Array(elem_types)
102
- tuple.map { |value|
105
+ Literal.new tuple.map { |value|
103
106
  elem_type = value.is_a?(Array) ? elem_types&.shift : nil
104
- quote_value(value, elem_type: elem_type)
107
+ quote_value(value, **opts, elem_type: elem_type)
105
108
  }.join(", ")
106
109
  end
107
110
 
108
111
  # Quote an array of tuples
109
- def self.quote_tuples(tuples, elem_types: nil)
110
- tuples.map { |tuple| "(#{quote_tuple(tuple, elem_types: elem_types)})" }.join(", ")
112
+ def self.quote_tuples(tuples, **opts)
113
+ Literal.new tuples.map { |tuple| "(#{quote_tuple(tuple, **opts)})" }.join(", ")
111
114
  end
112
115
 
113
116
  # Used to mark strings as literals that should not be quoted. This is the
@@ -125,6 +128,9 @@ module PgConn
125
128
  # The class of column names (Symbol or String). Default is Symbol
126
129
  attr_reader :field_name_class
127
130
 
131
+ # Default postgres JSON type (either 'json' or 'jsonb'). Default is 'jsonb'
132
+ attr_reader :default_json_type
133
+
128
134
  # Name of user
129
135
  def user() @pg_connection.user end
130
136
  alias_method :username, :user # Obsolete FIXME Is it?
@@ -155,28 +161,56 @@ module PgConn
155
161
  # #exec or #transaction block. The timestamp includes the current time zone
156
162
  attr_reader :timestamptz
157
163
 
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
164
+ # Controls error messages. It can be assigned true, false, nil, or a Proc
165
+ # object that recieves the message. True causes the message to be printed
166
+ # to standard error, false ignores it, and nil resets the state to the
167
+ # default given when the connection was initialized. #silent? returns true
168
+ # if #silent is false or a Proc object and should be used instead #silent
169
+ # to check the state because #silent returns truish when output is
170
+ # redirected to a Proc
171
+ #
172
+ # Note that #silent=, #notice=, and warning= only controls the error
173
+ # message, the exception is passed through unaltered
174
+ #
163
175
  def silent() @options[:silent] end
176
+ def silent?() @producers[:silent].nil? end # silent == false/Proc is true
164
177
  def silent=(value) set_option(:silent, value) end
165
178
 
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
179
  # Controls warnings. It can be assigned true, false, nil, or a Proc object
174
180
  # that recieves the message. True causes the message to be printed to
175
181
  # standard error, false ignores it, and nil resets the state to the default
176
- # given when the connection was initialized or false if absent
182
+ # given when the connection was initialized
177
183
  def warning() @options[:warning] end
184
+ def warning?() !warning.nil? end
178
185
  def warning=(value) set_option(:warning, value) end
179
186
 
187
+ # Controls notice messages. It can be assigned true, false, nil, or a Proc
188
+ # object that recieves the message. True causes the message to be printed
189
+ # to standard error, false ignores it, and nil resets the state to the
190
+ # default given when the connection was initialized. Default false
191
+ def notice() @options[:notice] end
192
+ def notice?() !notice.nil? end
193
+ def notice=(value) set_option(:notice, value) end
194
+
195
+ # Controls info messages. It can be assigned true, false, nil, or a Proc
196
+ # object that recieves the message. True causes the message to be printed
197
+ # to standard output, false ignores it, and nil resets the state to the
198
+ # default given when the connection was initialized. Default false. Note
199
+ # that #info is the only level that outputs to standard output
200
+ def info() @options[:info] end
201
+ def info?() !info.nil? end
202
+ def info=(value) set_option(:info, value) end
203
+
204
+ # Controls debug messages. It can be assigned true, false, nil, or a Proc
205
+ # object that recieves the message. True causes the message to be printed
206
+ # to standard error, false ignores it, and nil resets the state to the
207
+ # default given when the connection was initialized. Default false
208
+ def debug() @options[:debug] end
209
+ def debug?() !debug.nil? end
210
+ def debug=(value) set_option(:debug, value) end
211
+
212
+ DEFAULT_OPTIONS = { silent: false, warning: true, notice: false, info: false, debug: false }
213
+
180
214
  # TODO: Move error message handling into the same framework as notice and
181
215
  # warning but we have a name collision just below that would need to be
182
216
  # resolved somehow
@@ -214,8 +248,6 @@ module PgConn
214
248
  # if absent in the Postgres error message
215
249
  def errchar = err[2]
216
250
 
217
- DEFAULT_OPTIONS = { silent: false, notice: false, warning: false }
218
-
219
251
  # :call-seq:
220
252
  # initialize(dbname = nil, user = nil, **options)
221
253
  # initialize(connection_hash, **options)
@@ -247,8 +279,10 @@ module PgConn
247
279
  # Symbol (the default) or String. The :timestamp option is used
248
280
  # internally to set the timestamp for transactions
249
281
  #
250
- # The :notice and :warning options sets the default output handling this
251
- # connection (FIXME fails on copied connections)
282
+ # The :notice and :warning options sets the default output handling for this
283
+ # connection (FIXME fails on copied connections). Default is to suppress
284
+ # notices and lower - this is diffent from postgres that by default include
285
+ # notices
252
286
  #
253
287
  # Note that the connection hash and the connection string may support more
254
288
  # parameters than documented here. Consult
@@ -263,7 +297,17 @@ module PgConn
263
297
  if args.last.is_a?(Hash)
264
298
  opts = args.last
265
299
  @field_name_class = opts.delete(:field_name_class) || Symbol
266
- options = DEFAULT_OPTIONS.transform_values! { |k,v| opts.key?(k) ? opts.delete(k) : v }
300
+
301
+ # Extract options from arguments
302
+ options = DEFAULT_OPTIONS.to_h { |k,v|
303
+ r = if opts.key?(k)
304
+ value = opts.delete(k)
305
+ value.nil? ? v : value
306
+ else
307
+ v
308
+ end
309
+ [k, r]
310
+ }
267
311
 
268
312
  # FIXME: Is this used?
269
313
  @timestamp = opts.delete(:timestamp)
@@ -272,7 +316,7 @@ module PgConn
272
316
  args.pop if opts.empty?
273
317
  else
274
318
  @field_name_class = Symbol
275
- options = DEFAULT_OPTIONS
319
+ options = DEFAULT_OPTIONS.dup
276
320
  end
277
321
 
278
322
  # else # We assume that the current user is a postgres superuser
@@ -340,11 +384,17 @@ module PgConn
340
384
  @pg_connection.field_name_type = @field_name_class.to_s.downcase.to_sym # Use symbol field names
341
385
  end
342
386
 
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
-
387
+ # Set options. The initial options also serves as default values and are
388
+ # themselves initialized using DEFAULT_VALUES
389
+ #
390
+ # Note that options is initialized even if there is no connection to
391
+ # avoid special casing
392
+ @default_options = options
393
+ @options = {}
394
+ @producers = {} # Map from message level to Proc or nil
395
+ set_options(@default_options) if @pg_connection
396
+
397
+ @default_json_type = :jsonb
348
398
  @schema = SchemaMethods.new(self)
349
399
  @role = RoleMethods.new(self)
350
400
  @rdbms = RdbmsMethods.new(self)
@@ -380,17 +430,17 @@ module PgConn
380
430
  end
381
431
  end
382
432
 
383
- # Mark argument as already being quoted. TODO: Make this the default in all
384
- # quote methods
433
+ # Mark string argument as already being quoted
385
434
  def literal(arg) Literal.new(arg) end
386
435
 
387
436
  # Connection member method variations of the PgConn quote class methods
437
+ # with at least a default value for :json_type
388
438
  def quote_identifier(s) = PgConn.quote_identifier(s)
389
439
  def quote_identifiers(idents) = PgConn.quote_identifiers(idents)
390
- def quote_value(value, **opts) = PgConn.quote_value(value, **opts)
391
- def quote_values(values, **opts) = PgConn.quote_values(values, **opts)
392
- def quote_tuple(tuple, **opts) = PgConn.quote_tuple(tuple, **opts)
393
- def quote_tuples(tuples, **opts) = PgConn.quote_tuples(tuples, **opts)
440
+ def quote_value(value, **opts) = PgConn.quote_value(value, json_type: self.default_json_type, **opts)
441
+ def quote_values(values, **opts) = PgConn.quote_values(values, json_type: self.default_json_type, **opts)
442
+ def quote_tuple(tuple, **opts) = PgConn.quote_tuple(tuple, json_type: self.default_json_type, **opts)
443
+ def quote_tuples(tuples, **opts) = PgConn.quote_tuples(tuples, json_type: self.default_json_type, **opts)
394
444
 
395
445
  # Quote a record and cast it into the given type, the type can also be a
396
446
  # table or view. 'data' is an array, hash, or struct representation of the
@@ -403,14 +453,14 @@ module PgConn
403
453
  #
404
454
  # Also note that there is not class-method variant of this method because
405
455
  # it requires a connection
406
- def quote_record(data, schema_name = nil, type, elem_types: nil)
407
- quote_record_impl(data, schema_name, type, elem_types: elem_types, array: false)
456
+ def quote_record(data, schema_name = nil, type, **opts)
457
+ quote_record_impl(data, schema_name, type, array: false, **opts)
408
458
  end
409
459
 
410
460
  # Quote an array of records. The type is the record type, not the type of
411
461
  # the enclosing array
412
- def quote_records(data, schema_name = nil, type, elem_types: nil)
413
- quote_record_impl(data, schema_name, type, elem_types: elem_types, array: true)
462
+ def quote_records(data, schema_name = nil, type, **opts)
463
+ quote_record_impl(data, schema_name, type, array: true, **opts)
414
464
  end
415
465
 
416
466
  # :call-seq:
@@ -692,11 +742,11 @@ module PgConn
692
742
  # columns (like #tuples).
693
743
  #
694
744
  # The name argument can be a String or a Symbol that may contain the schema
695
- # of the function. If the :proc option is true the "function" is assumed
745
+ # of the function. If the :proc option is true the "function" is assumed
696
746
  # to be a procedure
697
747
  #
698
- def call(name, *args, elem_type: nil, silent: self.silent, proc: false) # :proc may interfere with hashes
699
- args_seq = quote_values(args, elem_type: elem_type)
748
+ def call(name, *args, silent: self.silent, proc: false, **opts) # :proc may interfere with hashes
749
+ args_seq = quote_values(args, **opts)
700
750
  if proc
701
751
  pg_exec "call #{name}(#{args_seq})", silent: silent
702
752
  return nil
@@ -719,8 +769,8 @@ module PgConn
719
769
  end
720
770
 
721
771
  # Like #call with :proc set to true
722
- def proc(name, *args, silent: self.silent)
723
- call(name, *args, silent: silent, proc: true)
772
+ def proc(name, *args, json_type: self.default_json_type, silent: self.silent)
773
+ call(name, *args, silent: silent, proc: true, json_type: json_type)
724
774
  end
725
775
 
726
776
  # :call-seq:
@@ -859,7 +909,14 @@ module PgConn
859
909
  #
860
910
  # TODO: Make sure the transaction stack is emptied on postgres errors
861
911
  def exec(sql, commit: true, fail: true, silent: self.silent)
862
- transaction(commit: commit) { execute(sql, fail: fail, silent: silent) }
912
+ transaction(commit: commit) {
913
+ begin
914
+ execute(sql, fail: fail, silent: silent)
915
+ rescue PG::Error
916
+ cancel_transaction
917
+ raise
918
+ end
919
+ }
863
920
  end
864
921
 
865
922
  # Like #exec but returns true/false depending on if the command succeeded,
@@ -888,7 +945,6 @@ module PgConn
888
945
  begin
889
946
  pg_exec(sql, silent: silent)&.cmd_tuples
890
947
  rescue PG::Error
891
- cancel_transaction
892
948
  raise if fail
893
949
  return nil
894
950
  end
@@ -954,9 +1010,9 @@ module PgConn
954
1010
  # file instead of being executed. Maybe remove logging (or execute always
955
1011
  # and log as a side-effect)
956
1012
  if @pg_connection
957
- @timestamp, @timestamptz = @pg_connection.exec(
958
- 'select current_timestamp::timestamp without time zone, current_timestamp'
959
- ).tuple_values(0)
1013
+ @timestamp, @timestamptz = @pg_connection.exec(
1014
+ 'select current_timestamp::timestamp without time zone, current_timestamp'
1015
+ ).tuple_values(0)
960
1016
  end
961
1017
  end
962
1018
  end
@@ -987,9 +1043,16 @@ module PgConn
987
1043
  # progress, the method always succeeds
988
1044
  def cancel_transaction
989
1045
  begin
990
- pg_exec("rollback")
1046
+ # The transaction may be invalid to we can't use #set_option to silence
1047
+ # warnings when the transaction is rolled back. Instead we manipulate the
1048
+ # procudure method
1049
+ saved_producer = @producers[:warning]
1050
+ @producers[:warning] = nil
1051
+ pg_exec("rollback", silent: true)
991
1052
  rescue PG::Error
992
1053
  ;
1054
+ ensure
1055
+ @producers[:warning] = saved_producer
993
1056
  end
994
1057
  @savepoints = nil
995
1058
  true
@@ -1018,7 +1081,6 @@ module PgConn
1018
1081
  return nil
1019
1082
  rescue PG::Error
1020
1083
  cancel_transaction
1021
- @savepoints = nil
1022
1084
  raise
1023
1085
  end
1024
1086
  pop_transaction(commit: commit, fail: false)
@@ -1093,16 +1155,18 @@ module PgConn
1093
1155
  end
1094
1156
 
1095
1157
  # Common implementation for #quote_record and #quote_records that avoids
1096
- # querying the database multiple times or duplication the code. The :array
1097
- # flag is true when called via #quote_records
1158
+ # querying the database multiple times or duplication the code
1098
1159
  #
1099
1160
  # @data can be a Hash, Array, or OpenStruct. Hash keys must be symbols or
1100
1161
  # strings, they belong to the same namespace so :k and "k" refer to the
1101
- # same value
1162
+ # same value. The :array flag is true when called via #quote_records
1102
1163
  #
1103
1164
  # Note that #quote_record_impl queries the database for information about
1104
1165
  # the type. TODO Cache this information?
1105
- def quote_record_impl(datas, schema_name = nil, type, elem_types: nil, array: nil)
1166
+ #
1167
+ def quote_record_impl(datas, schema_name = nil, type, elem_types: nil, array: nil, **opts)
1168
+ datas = [datas] if !array
1169
+
1106
1170
  pg_type = [schema_name, type].compact.join('.')
1107
1171
  fields = self.values(%(
1108
1172
  select attname
@@ -1114,8 +1178,6 @@ module PgConn
1114
1178
  order by attnum
1115
1179
  )).map(&:to_sym)
1116
1180
 
1117
- datas = [datas] if !array
1118
-
1119
1181
  literals = datas.map { |data|
1120
1182
  values =
1121
1183
  case data
@@ -1125,20 +1187,19 @@ module PgConn
1125
1187
  else
1126
1188
  raise Error, "Illegal value #{data.inspect}"
1127
1189
  end
1128
- "(#{quote_tuple(values, elem_types: elem_types)})::#{pg_type}"
1190
+ "(#{quote_tuple(values, elem_types: elem_types, **opts)})::#{pg_type}"
1129
1191
  }
1130
1192
 
1131
1193
  if array
1132
- "array[#{literals.join(', ')}]::#{pg_type}[]"
1194
+ Literal.new "array[#{literals.join(', ')}]::#{pg_type}[]"
1133
1195
  else
1134
- literals.first
1196
+ Literal.new literals.first
1135
1197
  end
1136
1198
  end
1137
1199
 
1138
1200
  # :call-seq
1139
1201
  # parse_query(query)
1140
1202
  # parse_query(table, id, fields...)
1141
- # parse_query(table, where_clause, fields...)
1142
1203
  # parse_query(table, hash, fields...)
1143
1204
  # parse_query(table, fields...)
1144
1205
  #
@@ -1172,45 +1233,58 @@ module PgConn
1172
1233
  end
1173
1234
 
1174
1235
  STDOUT_PRODUCER = lambda { |msg| $stdout.puts msg }
1175
- STDERR_PRODUCER = lambda { |msg| $stderr.puts msg }
1236
+ STDERR_PRODUCER = lambda { |msg| $stderr.puts msg; $stderr.flush }
1237
+ ERROR_PRODUCER = lambda { |msg, stmt| $stderr.puts stmt, nil, msg; $stderr.flush }
1238
+
1239
+ # Map from message level to default producer. Note that we rely on the key
1240
+ # order to determine the minimum message level in #set_option below
1241
+ DEFAULT_PRODUCER = {
1242
+ debug: STDERR_PRODUCER,
1243
+ info: STDOUT_PRODUCER,
1244
+ notice: STDERR_PRODUCER,
1245
+ warning: STDERR_PRODUCER,
1246
+ error: STDERR_PRODUCER
1247
+ }
1176
1248
 
1177
1249
  # FIXME Fails if connection is copied - requires a common options object
1178
1250
  # that is shared between all connection copies
1179
1251
  def set_option(option, value)
1252
+ # Assign default
1253
+ value = @default_options[option] if value.nil?
1254
+
1255
+ # Find current message level
1256
+ old_level = DEFAULT_PRODUCER.keys.find { |level| @producers[level] } || :error
1257
+
1258
+ # Set new value. Can be true, false, or a Proc object
1259
+ @options[option] = value
1260
+
1261
+ # Set producer
1180
1262
  case option
1181
1263
  when :silent
1182
- @options[:silent] =
1264
+ @producers[:silent] =
1183
1265
  case value
1184
- when true, false; value
1185
- when nil; @default_options[:silent]
1266
+ when true; nil
1267
+ when false; ERROR_PRODUCER
1268
+ when Proc; value
1186
1269
  else
1187
- raise ArgumentError, "Illegal value #{value.inspect}"
1270
+ raise ArgumentError, "Illegal value: #{value.inspect}"
1188
1271
  end
1189
- when :notice, :warning
1190
- @options[option] =
1272
+ when :debug, :info, :notice, :warning
1273
+ @producers[option] =
1191
1274
  case value
1192
- when true; option == :notice ? STDOUT_PRODUCER : STDERR_PRODUCER
1275
+ when true; DEFAULT_PRODUCER[option]
1193
1276
  when false; nil
1194
- when nil; @default_options[option]
1195
1277
  when Proc; value
1196
1278
  else
1197
1279
  raise ArgumentError, "Illegal value #{value.inspect}"
1198
1280
  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
1281
+
1282
+ # Find new message level
1283
+ new_level = DEFAULT_PRODUCER.keys.find { |level| @producers[level] } || :error
1284
+
1285
+ # Adjust postgres message level if changed
1286
+ if old_level != new_level
1287
+ @pg_connection.exec "set client_min_messages to #{new_level}"
1214
1288
  end
1215
1289
  else
1216
1290
  raise ArgumentError, "Illegal option: #{option.inspect}"
@@ -1224,10 +1298,13 @@ module PgConn
1224
1298
  def get_options() @options.dup end
1225
1299
 
1226
1300
  # Called from postgres. Installed in #initialize
1227
- def message_processor(message)
1301
+ def message_processor(message, stmt = nil)
1228
1302
  case message
1229
- when /^NOTICE:\s*(.*)$/; @options[:notice]&.call($1)
1230
- when /^WARNING:\s*(.*)$/; @options[:warning]&.call($1)
1303
+ when /^DEBUG:\s*(.*)$/; @producers[:debug]&.call($1)
1304
+ when /^INFO:\s*(.*)$/; @producers[:info]&.call($1)
1305
+ when /^NOTICE:\s*(.*)$/; @producers[:notice]&.call($1)
1306
+ when /^WARNING:\s*(.*)$/; @producers[:warning]&.call($1)
1307
+ when /^ERROR:\s*(.*)$/m; @producers[:silent]&.call($1, stmt)
1231
1308
  else
1232
1309
  raise "Oops"
1233
1310
  end
@@ -1255,6 +1332,10 @@ module PgConn
1255
1332
  def pg_exec(arg, silent: self.silent)
1256
1333
  if @pg_connection
1257
1334
  begin
1335
+ # Set silent state
1336
+ saved_silent = self.silent
1337
+ self.silent = silent
1338
+
1258
1339
  last_stmt = nil # To make the current SQL statement visible to the rescue clause. FIXME Not used?
1259
1340
  if arg.is_a?(String)
1260
1341
  return nil if arg == ""
@@ -1272,12 +1353,11 @@ module PgConn
1272
1353
  @error = ex
1273
1354
  @err = nil
1274
1355
  end
1275
- if !silent # FIXME Why do we handle this?
1276
- $stderr.puts arg
1277
- $stderr.puts
1278
- $stderr.puts ex.message
1279
- $stderr.flush
1280
- end
1356
+
1357
+ # Process message before resetting silent state
1358
+ message_processor(@error.message, arg)
1359
+ self.silent = saved_silent
1360
+
1281
1361
  raise
1282
1362
  end
1283
1363
 
@@ -1313,16 +1393,15 @@ module PgConn
1313
1393
  def self.sql_values(values) "'" + values.join("', '") + "'" end
1314
1394
  def self.sql_idents(values) '"' + values.join('", "') + '"' end
1315
1395
 
1316
- # Same as postgres PG#escape_literal but do not need a connection
1317
- def self.escape_literal(s) = s.nil? ? 'NULL' : "'#{s.gsub("'", "''")}'"
1318
-
1319
- # Same as postgres PG#escape_identifier but do not need a connection
1320
- def self.escape_identifier(s)
1321
- !s.nil? or raise TypeError, "Identifier can't be nil"
1322
- s.is_a?(String) or raise TypeError, "Identifier can't be a #{s.class}"
1323
- s !~ /\A\s*\z/ or raise TypeError, "Identifier be blank"
1324
- %("#{s.gsub('"', '""')}")
1325
- end
1396
+ # Same as postgres PG#escape_literal but do not need a connection
1397
+ def self.escape_literal(s) = s.nil? ? 'NULL' : "'#{s.gsub("'", "''")}'"
1326
1398
 
1399
+ # Same as postgres PG#escape_identifier but do not need a connection
1400
+ def self.escape_identifier(s)
1401
+ !s.nil? or raise TypeError, "Identifier can't be nil"
1402
+ s.is_a?(String) or raise TypeError, "Identifier can't be a #{s.class}"
1403
+ s !~ /\A\s*\z/ or raise TypeError, "Identifier be blank"
1404
+ %("#{s.gsub('"', '""')}")
1405
+ end
1327
1406
  end
1328
1407
 
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.35.0
4
+ version: 0.36.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-11 00:00:00.000000000 Z
11
+ date: 2025-03-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pg