pg_conn 0.8.0 → 0.10.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: 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/