pg_conn 0.21.0 → 0.23.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6f293ad25581975cbd57dc5973d072043d6b5cf13093a21bd354720b39c91c12
4
- data.tar.gz: 1ad472d136a9b626717f90058cb220eaaf4c87bced2f8802c8cab68b69bf187a
3
+ metadata.gz: d7de75668042e3abdc5b983e0b9a3f6c909ff69aeed2f22c632d1c693f3baac0
4
+ data.tar.gz: 90cecbdd79e6478d7f6bd1bc3ae042917bc2683771c89de61aab6c05fa2e5279
5
5
  SHA512:
6
- metadata.gz: 7720aef7f128e0b8e3c47fdd2acb752b1d44af9625653eae0552a2ac619e3c29ae2cad25789f0f0806fe4576104a64210cf5a5d7e7c7fa66817b8538a556bd35
7
- data.tar.gz: 61425eaeabb6b6e0872f6ed49064f8650361ecd8be684b7db2322a6a346265e253489e49be37688c9ef74171733d2c8f19a12d38105a188ba263cf5ea48c9593
6
+ metadata.gz: 76b775138260668bf75e5bc31e220561f665dffc901349a9ec875e7ec594dee2750e0d979bde80e9045e38e44a3894fe55c2be37faefbe7f35173812cea2a22a
7
+ data.tar.gz: f0e2e9169cd734814cce4e8b73f7ec017cf3fd9a2622290c4acbf06620ddb3b943957ab5dab527ee0e29a9d71ad4efb511f4f219543ca86301d9fd5c4f5838ad
data/TODO CHANGED
@@ -1,5 +1,6 @@
1
1
  TODO
2
- o "use 'drop ... cascade' everywhere
2
+ o Use :elem_type everywhere
3
+ o Use 'drop ... cascade' everywhere
3
4
  o db.context(schema: app_portal, transaction: true) { ... }
4
5
  db.context(set: app_portal, transaction: true) { ... }
5
6
  db.context(add: app_portal, transaction: true) { ... }
@@ -21,7 +21,7 @@ module PgConn
21
21
  end
22
22
 
23
23
  # Create a new database
24
- def create(database, owner: ENV['USER'], template: "template1")
24
+ def create(database, owner: ENV['USER'], template: "template1")
25
25
  owner_clause = owner ? "owner = \"#{owner}\"" : nil
26
26
  template_clause = template ? "template = \"#{template}\"" : nil
27
27
  stmt = ["create database \"#{database}\"", owner_clause, template_clause].compact.join(" ")
@@ -80,7 +80,7 @@ module PgConn
80
80
  local = !database.nil?
81
81
  begin
82
82
  conn = local ? PgConn.new(database) : self.conn
83
- schemas =
83
+ schemas =
84
84
  conn
85
85
  .values("select nspname from pg_namespace where nspowner != 10 or nspname = 'public'")
86
86
  .select { |schema| !exclude.include?(schema) }
@@ -90,7 +90,7 @@ module PgConn
90
90
  # FIXME FIXME FIXME SECURITY Why grant 'create' to public?
91
91
  conn.exec %(
92
92
  create schema public authorization postgres;
93
- grant usage, create on schema public to public
93
+ grant usage, create on schema public to public
94
94
  ) if public
95
95
  ensure
96
96
  conn&.terminate if local
@@ -18,7 +18,7 @@ module PgConn
18
18
  else
19
19
  nil
20
20
  end
21
- can_login_clause =
21
+ can_login_clause =
22
22
  case can_login
23
23
  when true; "rolcanlogin"
24
24
  when false; "not rolcanlogin"
@@ -62,7 +62,7 @@ module PgConn
62
62
  # counldn't be deleted
63
63
  #
64
64
  # Note that cascade only works if connected to the database where the
65
- # privileges exist.
65
+ # privileges exist.
66
66
  #
67
67
  # TODO The :silent option is used in tests - fix it somehow!
68
68
  def drop(*rolenames, cascade: false, fail: true, silent: false)
@@ -86,7 +86,7 @@ module PgConn
86
86
  superuser_clause = superuser.nil? ? nil : "rolsuper = #{superuser}"
87
87
  can_login_clause = can_login.nil? ? nil : "rolcanlogin = #{can_login}"
88
88
  query = [
89
- "select rolname from pg_roles where true",
89
+ "select rolname from pg_roles where true",
90
90
  database_clause, superuser_clause, can_login_clause
91
91
  ].compact.join(" and ")
92
92
  conn.values(query)
@@ -49,8 +49,8 @@ module PgConn
49
49
  # Empty all tables in the given schema
50
50
  def clean!(schema, exclude: [])
51
51
  conn.session.triggers(false) {
52
- self.list_tables(schema, exclude: exclude).each { |table|
53
- conn.exec "delete from #{schema}.#{table}"
52
+ self.list_tables(schema, exclude: exclude).each { |table|
53
+ conn.exec "delete from #{schema}.#{table}"
54
54
  }
55
55
  }
56
56
  end
@@ -125,8 +125,8 @@ module PgConn
125
125
  end
126
126
 
127
127
  # Return name of the table's sequence (if any)
128
- def sequence(schema, table)
129
- conn.value "select pg_get_serial_sequence('#{schema}.#{table}', 'id')"
128
+ def sequence(schema, table)
129
+ conn.value "select pg_get_serial_sequence('#{schema}.#{table}', 'id')"
130
130
  end
131
131
 
132
132
  # Get the current serial value for the table. Returns nil if the serial has
@@ -168,7 +168,7 @@ module PgConn
168
168
  kind_sql_list = "'" + (kind.nil? ? %w(r f v m) : Array(kind).flatten).join("', '") + "'"
169
169
  %(
170
170
  select 1
171
- from pg_class
171
+ from pg_class
172
172
  where relnamespace::regnamespace::text = '#{schema}'
173
173
  and relname = '#{relation}'
174
174
  and relkind in (#{kind_sql_list})
@@ -183,7 +183,7 @@ module PgConn
183
183
  exclude_expr = exclude.empty? ? "true = true" : "not relname in (#{exclude_list})"
184
184
  %(
185
185
  select relname
186
- from pg_class
186
+ from pg_class
187
187
  where relnamespace::regnamespace::text = '#{schema}'
188
188
  and #{kind_expr}
189
189
  and #{exclude_expr}
@@ -211,7 +211,7 @@ module PgConn
211
211
  join pg_attribute a on a.attrelid = c.oid
212
212
  where relnamespace::regnamespace::text = '#{schema}'
213
213
  and a.attnum > 0
214
- ),
214
+ ),
215
215
  relation_clause
216
216
  ].compact.join(" and ")
217
217
  end
@@ -1,3 +1,3 @@
1
1
  module PgConn
2
- VERSION = "0.21.0"
2
+ VERSION = "0.23.0"
3
3
  end
data/lib/pg_conn.rb CHANGED
@@ -239,12 +239,16 @@ module PgConn
239
239
  end
240
240
  end
241
241
 
242
- # Quote value as an identifier. Value should be a non-nil string or a symbol
242
+ # Quote argument as an identifier. The argument should be a non-nil string
243
+ # or a symbol
243
244
  def quote_identifier(s)
244
245
  s = s.to_s if s.is_a?(Symbol)
245
246
  @pg_connection.escape_identifier(s)
246
247
  end
247
248
 
249
+ # Quote identifiers and concatenate them using ',' as separator
250
+ def quote_identifiers(idents) = idents.map { |ident| quote_identifier(ident) }.join(", ")
251
+
248
252
  # Quote the value as a string. Emit 'null' if value is nil
249
253
  #
250
254
  # The value can be of any type but is converted to a string using #to_s
@@ -255,7 +259,13 @@ module PgConn
255
259
  # Note that a tuple value (an array) must be quoted using #quote_tuple
256
260
  # because #quote_value would quote the tuple as an array instead of a list
257
261
  # of values
258
- def quote_value(value)
262
+ #
263
+ # The :elem_type option can be a postgres type name (String or Symbol) or
264
+ # an array of type names. They are used as the required explicit element
265
+ # type when the argument is an empty array. The element types shoud be in
266
+ # the same order as the array arguments. Nested arrays is not supported
267
+ #
268
+ def quote_value(value, elem_type: nil)
259
269
  case value
260
270
  when String; @pg_connection.escape_literal(value)
261
271
  when Integer, Float; value.to_s
@@ -263,23 +273,34 @@ module PgConn
263
273
  when nil; 'null'
264
274
  when Date, DateTime; "'#{value}'"
265
275
  when Time; "'#{value.strftime("%FT%T%:z")}'"
266
- when Array; "array[#{value.map { |elem| quote_value(elem) }.join(', ')}]"
276
+ when Array
277
+ if value.empty?
278
+ elem_type or raise Error, "Empty array without elem_type"
279
+ "array[]::#{elem_type}[]"
280
+ else
281
+ "array[#{value.map { |elem| quote_value(elem) }.join(', ')}]"
282
+ end
267
283
  else
268
284
  @pg_connection.escape_literal(value.to_s)
269
285
  end
270
286
  end
271
287
 
272
- # Quote an array of values as a tuple. Just an alias for #quote_values
273
- def quote_tuple(tuple) = quote_values(tuple)
274
-
275
- # Quote identifiers and concatenate them using ',' as separator
276
- def quote_identifiers(idents) = idents.map { |ident| quote_identifier(ident) }.join(", ")
277
-
278
288
  # Quote values and concatenate them using ',' as separator
279
- def quote_values(values) = values.map { |value| quote_value(value) }.join(", ")
289
+ def quote_values(values, elem_type: nil)
290
+ elem_types = Array(elem_type)
291
+ values.map { |value|
292
+ elem_type = value.is_a?(Array) ? elem_types&.shift : nil
293
+ quote_value(value, elem_type: elem_type)
294
+ }.join(", ")
295
+ end
296
+
297
+ # Quote an array of values as a tuple. Just an alias for #quote_values
298
+ def quote_tuple(tuple, elem_type: nil) = quote_values(tuple, elem_type: elem_type)
280
299
 
281
300
  # Quote an array of tuples
282
- def quote_tuples(tuples) = tuples.map { |tuple| "(#{quote_values(tuple)})" }.join(", ")
301
+ def quote_tuples(tuples, elem_type: nil)
302
+ tuples.map { |tuple| "(#{quote_values(tuple, elem_type: elem_type)})" }.join(", ")
303
+ end
283
304
 
284
305
  # :call-seq:
285
306
  # exist?(query)
@@ -515,8 +536,9 @@ module PgConn
515
536
 
516
537
  # Returns a hash from the first field to a tuple of the remaining fields.
517
538
  # If there is only one remaining field then that value is used instead of a
518
- # tuple of that value. The optional +key+ argument sets the mapping field
519
- def map(query, key = nil) # TODO Swap arguments
539
+ # tuple. The optional +key+ argument sets the mapping field and the
540
+ # +symbol+ option convert key to Symbol objects when true
541
+ def map(query, key = nil, symbol: false) # TODO Swap arguments
520
542
  r = pg_exec(query)
521
543
  begin
522
544
  key = (key || r.fname(0)).to_s
@@ -528,13 +550,18 @@ module PgConn
528
550
  h = {}
529
551
  r.each_row { |row|
530
552
  key_value = row.delete_at(key_index)
553
+ key_value = key_value.to_sym if symbol
531
554
  !h.key?(key_value) or raise Error, "Duplicate key: #{key_value}"
532
555
  h[key_value] = (one ? row.first : row)
533
556
  }
534
557
  h
535
558
  end
536
559
 
537
- def multimap(query, key = nil)
560
+ # Like #map but values of duplicate keys are concatenated. It acts as a
561
+ # group-by on the key and array_agg on the remaining values. The value is
562
+ # an array of tuples if the query has more than one value field and an
563
+ # array of values if there is only one value field
564
+ def multimap(query, key = nil, symbol: false)
538
565
  r = pg_exec(query)
539
566
  begin
540
567
  key = (key || r.fname(0)).to_s
@@ -546,6 +573,7 @@ module PgConn
546
573
  h = {}
547
574
  r.each_row { |row|
548
575
  key_value = row.delete_at(key_index)
576
+ key_value = key_value.to_sym if symbol
549
577
  (h[key_value] ||= []) << (one ? row.first : row)
550
578
  }
551
579
  h
@@ -557,10 +585,11 @@ module PgConn
557
585
  # values if the result contained only one column (like #value or #values),
558
586
  # a tuple if the record has multiple columns (like #tuple), and an array of
559
587
  # of tuples if the result contained more than one record with multiple
560
- # columns (like #tuples)
588
+ # columns (like #tuples). If the :proc option is true the "function" is
589
+ # assumed to be a procedure
561
590
  #
562
- def call(name, *args, proc: false) # :proc may interfere with hashes
563
- args_seq = quote_values(args)
591
+ def call(name, *args, elem_type: nil, proc: false) # :proc may interfere with hashes
592
+ args_seq = quote_values(args, elem_type: elem_type)
564
593
  if proc
565
594
  pg_exec "call #{name}(#{args_seq})"
566
595
  return nil
@@ -582,7 +611,7 @@ module PgConn
582
611
  end
583
612
  end
584
613
 
585
- # :call-seq*
614
+ # :call-seq:
586
615
  # insert(table, record|records)
587
616
  # insert(table, fields, record|records|tuples)
588
617
  # insert(schema, table, record|records)
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.21.0
4
+ version: 0.23.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-08-07 00:00:00.000000000 Z
11
+ date: 2024-08-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pg