pg_conn 0.8.0 → 0.10.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: 599ccb65823ae32d0a3baf96ba0d636104c7994f2473d71eafa1927228f47302
4
- data.tar.gz: b08bb15804c363fb25c03e102f35de6f64694220b9865e5a9d0525516cc6db24
3
+ metadata.gz: 7708b08a6ef0fcc12d0241ef9a084fe8adcfee342e03933f0f48ec313b9c48b5
4
+ data.tar.gz: 716d8e6de64dacab36db01303a47480e0d603a31a8fd24025441ca3a5c8adc2e
5
5
  SHA512:
6
- metadata.gz: 1dba080845fa70f52f4b0614392be42169822809a6a436da49f3c8a2453614c5b9586927ee5abfa5546933d43370d691458fb0677b350a0c40d1257acd42b79b
7
- data.tar.gz: 8cd9e0b2481ba760dd250a7e68bda460ddf38564843929143ca3622904484866818b75bad0bc9c8a9fbbacd8bd9980eaa53b8670f4e68d9f0f04886fe24f573c
6
+ metadata.gz: 12f8f90e3853a0882f93bccde0ea1ce564068612d5d876d26c159625eea4b541bd391b81fe10cbda24b96458129e7caf3c52fe0471d81efc210c151708224887
7
+ data.tar.gz: 5fd5a73696fbb9daa9dbc873daee9cf580974ab17bca8fc62743967160f2757e5aaafbb8ece7f8d8e32d122e5d0b3e7b6848a834b5bc16ee65099975ff7337dc
@@ -57,22 +57,26 @@ module PgConn
57
57
  end
58
58
 
59
59
  # Drop existing users. Return true if any role was dropped. Drop depending
60
- # privileges and objects too if :cascade is true. Note that cascade only
61
- # works if connected to the database where the privileges exist. The
62
- # :silent option is used in tests - fix it somehow!
60
+ # privileges and objects too if :cascade is true. Returns true if the
61
+ # user(s) was deleted and false if :fail is true and one or more user
62
+ # counldn't be deleted
63
+ #
64
+ # Note that cascade only works if connected to the database where the
65
+ # privileges exist.
66
+ #
67
+ # TODO The :silent option is used in tests - fix it somehow!
63
68
  def drop(*rolenames, cascade: false, fail: true, silent: false)
64
69
  rolenames = Array(rolenames).flatten.compact.select { |role| exist?(role) }
65
70
  return false if rolenames.empty?
66
71
  rolenames_sql = PgConn.sql_idents(rolenames)
67
- begin
68
- conn.exec("drop owned by #{rolenames_sql} cascade", fail: false, silent: silent) if cascade
69
- conn.exec("drop role #{rolenames_sql}", fail: fail, silent: silent)
70
- rescue PG::Error
71
- raise if fail
72
- conn.cancel_transaction
73
- return false
74
- end
75
- true
72
+ # begin
73
+ conn.exec("drop owned by #{rolenames_sql} cascade", fail: false, silent: silent) if cascade
74
+ conn.exec("drop role #{rolenames_sql}", fail: fail, silent: silent) && true
75
+ # rescue PG::Error
76
+ # raise if fail
77
+ # conn.cancel_transaction
78
+ # return false
79
+ # end
76
80
  end
77
81
 
78
82
  # List users. TODO Use RE instead of database argument. Also doc this shit
@@ -0,0 +1,82 @@
1
+ module PgConn
2
+ # Schema methods
3
+ class SessionMethods
4
+ attr_reader :conn
5
+
6
+ def initialize(conn)
7
+ @conn = conn
8
+ end
9
+
10
+ # Returns a list of users connected to the given database. If database is
11
+ # nil, it returns a list of database/username tuples for all connected users
12
+ def list(database)
13
+ if database
14
+ conn.values "select usename from pg_stat_activity where datname = '#{database}'"
15
+ else
16
+ conn.tuples %(
17
+ select datname, usename
18
+ from pg_stat_activity
19
+ where datname is not null and usename is not null
20
+ )
21
+ end
22
+ end
23
+
24
+ # Terminate sessions in the database of the given users or of all users if
25
+ # the users is nil. Note that 'terminate(database)' is a nop because the
26
+ # absent users argument defaults to an empty list
27
+ #
28
+ # TODO: Make is possible to terminate a single session of a user with
29
+ # multiple sessions (is this ever relevant?)
30
+ def terminate(database, *users)
31
+ !database.nil? or raise ArgumentError
32
+ users = Array(users).flatten
33
+ case users
34
+ when []; return
35
+ when [nil]; users = list(database)
36
+ else users = Array(users).flatten
37
+ end
38
+ pids = self.pids(database, users)
39
+ return if pids.empty?
40
+ pids_sql = pids.map { |pid| "(#{pid})" }.join(", ")
41
+ conn.execute "select pg_terminate_backend(pid) from ( values #{pids_sql} ) as x(pid)"
42
+ end
43
+
44
+ def disable(database)
45
+ !database.nil? or raise ArgumentError
46
+ conn.execute "alter database #{database} allow_connections = false"
47
+ end
48
+
49
+ def enable(database)
50
+ !database.nil? or raise ArgumentError
51
+ conn.execute "alter database #{database} allow_connections = true"
52
+ end
53
+
54
+ # Run block without any connected users. Existing sessions are terminated
55
+ def exclusive(database, &block)
56
+ !database.nil? or raise ArgumentError
57
+ begin
58
+ disable(database)
59
+ users = list(database)
60
+ terminate(database, users)
61
+ yield
62
+ ensure
63
+ enable(database)
64
+ end
65
+ end
66
+
67
+ private
68
+ # Like #list but returns the PIDs of the users
69
+ def pids(database, users)
70
+ users ||= list(database)
71
+ if !users.empty?
72
+ users_sql = "(" + users.map { |user| "'#{user}'" }.join(", ") + ")"
73
+ conn.values "select pid from pg_stat_activity where datname = '#{database}' and usename in #{users_sql}"
74
+ else
75
+ conn.values "select pid from pg_stat_activity where datname = '#{database}'"
76
+ end
77
+ end
78
+ end
79
+ end
80
+
81
+
82
+
@@ -1,3 +1,3 @@
1
1
  module PgConn
2
- VERSION = "0.8.0"
2
+ VERSION = "0.10.0"
3
3
  end
data/lib/pg_conn.rb CHANGED
@@ -5,6 +5,7 @@ require "pg_conn/version"
5
5
  require "pg_conn/role_methods"
6
6
  require "pg_conn/schema_methods"
7
7
  require "pg_conn/rdbms_methods"
8
+ require "pg_conn/session_methods"
8
9
 
9
10
  module PgConn
10
11
  class Error < StandardError; end
@@ -59,6 +60,9 @@ module PgConn
59
60
  # #exist?/#list for relations/tables/views/columns
60
61
  attr_reader :schema
61
62
 
63
+ # Session manipulation methods: #list, #terminate, #disable, #enable
64
+ attr_reader :session
65
+
62
66
  # The transaction timestamp of the most recent SQL statement executed by
63
67
  # #exec or #transaction block
64
68
  attr_reader :timestamp
@@ -198,11 +202,12 @@ module PgConn
198
202
  @schema = SchemaMethods.new(self)
199
203
  @role = RoleMethods.new(self)
200
204
  @rdbms = RdbmsMethods.new(self)
205
+ @session = SessionMethods.new(self)
201
206
  @timestamp = nil
202
207
  @savepoints = nil # Stack of savepoint names. Nil if no transaction in progress
203
208
  end
204
209
 
205
- # Close the database connection
210
+ # Close the database connection. TODO: Rename 'close'
206
211
  def terminate()
207
212
  @pg_connection.close if @pg_connection && !@pg_connection.finished?
208
213
  end
@@ -416,6 +421,9 @@ module PgConn
416
421
  h
417
422
  end
418
423
 
424
+ # TODO: An #array method that returns a map from id to tuple. Hmm... almost
425
+ # the same as #map
426
+
419
427
  # Return a hash from the record id column to an OpenStruct representation
420
428
  # of the record. If the :key_column option is defined it will be used
421
429
  # instead of id as the key. It is an error if the id field value is not
@@ -509,15 +517,23 @@ module PgConn
509
517
  # that span multiple lines. The empty array is a NOP but the empty string
510
518
  # is not.
511
519
  #
512
- # #exec pass Postgres exceptions to the caller unless :fail is false. If
513
- # fail is false #exec instead return nil but note that postgres doesn't
514
- # ignore it so that if you're inside a transaction, the transaction will be
515
- # in an error state and if you're also using subtransactions the whole
520
+ # #exec pass Postgres exceptions to the caller unless :fail is false in which case
521
+ # it returns nil.
522
+ #
523
+ # Note that postgres crashes the whole transaction stack if any error is
524
+ # met so if you're inside a transaction, the transaction will be in an
525
+ # error state and if you're also using subtransactions the whole
516
526
  # transaction stack has collapsed
517
527
  #
518
528
  # TODO: Make sure the transaction stack is emptied on postgres errors
519
529
  def exec(sql, commit: true, fail: true, silent: false)
520
- transaction(commit: commit) { execute(sql, fail: fail, silent: silent) }
530
+ begin
531
+ transaction(commit: commit) { execute(sql, fail: fail, silent: silent) }
532
+ rescue PG::Error
533
+ raise if fail
534
+ cancel_transaction
535
+ return nil
536
+ end
521
537
  end
522
538
 
523
539
  # Like #exec but returns true/false depending on if the command succeeded.
@@ -528,6 +544,7 @@ module PgConn
528
544
  begin
529
545
  exec(sql, commit: commit, fail: true, silent: silent)
530
546
  rescue PG::Error
547
+ cancel_transaction
531
548
  return false
532
549
  end
533
550
  return true
@@ -543,9 +560,14 @@ module PgConn
543
560
  # TODO: Handle postgres exceptions wrt transaction state and stack
544
561
  def execute(sql, fail: true, silent: false)
545
562
  if @pg_connection
546
- pg_exec(sql, fail: fail, silent: silent)&.cmd_tuples
563
+ begin
564
+ pg_exec(sql, silent: silent)&.cmd_tuples
565
+ rescue PG::Error
566
+ raise if fail
567
+ return nil
568
+ end
547
569
  else
548
- pg_exec(sql, fail: fail, silent: silent)
570
+ pg_exec(sql, silent: silent)
549
571
  end
550
572
  end
551
573
 
@@ -553,6 +575,7 @@ module PgConn
553
575
  # back to the original user
554
576
  #
555
577
  # FIXME: The out-commented transaction block makes postspec fail for some reason
578
+ # TODO: Rename 'sudo' because it acts just like it.
556
579
  def su(username, &block)
557
580
  raise Error, "Missing block in call to PgConn::Connection#su" if !block_given?
558
581
  realuser = self.value "select current_user"
@@ -611,10 +634,16 @@ module PgConn
611
634
  end
612
635
 
613
636
  # Does a rollback and empties the stack. This should be called in response
614
- # to PG::Error exceptions because then the whole transaction stack is
615
- # invalid
637
+ # to PG::Error exceptions because the whole transaction stack is
638
+ # invalid and the server is in an invalid state
639
+ #
640
+ # It is not an error to call #cancel_transaction when no transaction is in
641
+ # progress
616
642
  def cancel_transaction
617
- pg_exec("rollback")
643
+ begin
644
+ pg_exec("rollback")
645
+ rescue PG::Error
646
+ end
618
647
  @savepoints = nil
619
648
  end
620
649
 
@@ -631,7 +660,9 @@ module PgConn
631
660
  rescue PgConn::Rollback
632
661
  pop_transaction(commit: false)
633
662
  return nil
634
- # FIXME: Rescue other postgres errors and wipe-out stack
663
+ rescue PG::Error
664
+ @savepoints = nil
665
+ raise
635
666
  end
636
667
  pop_transaction(commit: commit)
637
668
  result
@@ -640,7 +671,6 @@ module PgConn
640
671
  private
641
672
  # Wrapper around PG::Connection.new that switches to the postgres user
642
673
  # before connecting if the current user is the root user
643
- #
644
674
  def make_connection(*args, **opts)
645
675
  if Process.euid == 0
646
676
  begin
@@ -673,8 +703,10 @@ module PgConn
673
703
  #
674
704
  # Execute statement(s) on the server. If the argument is an array of
675
705
  # commands, the commands are concatenated with ';' before being sent to the
676
- # server. #pg_exec returns a PG::Result object or nil if +arg+ was empty.
677
- # #exec pass Postgres exceptions to the caller unless :fail is false
706
+ # server. #pg_exec returns a PG::Result object or nil if +arg+ was empty
707
+ #
708
+ # Postgres errors are passed through and #error and #err set to the last
709
+ # statement's SQL errors or nil if it succeeded
678
710
  #
679
711
  # FIXME: Error message prints the last statement but what if another
680
712
  # statement failed?
@@ -684,7 +716,7 @@ module PgConn
684
716
  # though
685
717
  #
686
718
  # TODO: Fix silent by not handling exceptions
687
- def pg_exec(arg, fail: true, silent: false)
719
+ def pg_exec(arg, silent: false)
688
720
  if @pg_connection
689
721
  @error = @err = nil
690
722
  begin
@@ -703,19 +735,16 @@ module PgConn
703
735
 
704
736
  rescue PG::Error => ex
705
737
  @error = ex
706
- if fail
707
- if !silent # FIXME Why do we handle this?
708
- $stderr.puts arg
709
- $stderr.puts
710
- $stderr.puts ex.message
711
- $stderr.flush
712
- end
713
- raise
714
- else
715
- return nil
738
+ if !silent # FIXME Why do we handle this?
739
+ $stderr.puts arg
740
+ $stderr.puts
741
+ $stderr.puts ex.message
742
+ $stderr.flush
716
743
  end
744
+ raise
717
745
  end
718
746
 
747
+ # For dump of SQL statements
719
748
  else # @pg_commands is defined
720
749
  if arg.is_a?(String)
721
750
  @pg_commands << arg if arg != ""
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.8.0
4
+ version: 0.10.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-02-06 00:00:00.000000000 Z
11
+ date: 2024-02-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pg
@@ -85,6 +85,7 @@ files:
85
85
  - lib/pg_conn/rdbms_methods.rb
86
86
  - lib/pg_conn/role_methods.rb
87
87
  - lib/pg_conn/schema_methods.rb
88
+ - lib/pg_conn/session_methods.rb
88
89
  - lib/pg_conn/version.rb
89
90
  - pg_conn.gemspec
90
91
  homepage: http://www.nowhere.com/