pg_conn 0.21.0 → 0.23.0

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