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 +4 -4
- data/lib/pg_conn/role_methods.rb +16 -12
- data/lib/pg_conn/session_methods.rb +82 -0
- data/lib/pg_conn/version.rb +1 -1
- data/lib/pg_conn.rb +55 -26
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7708b08a6ef0fcc12d0241ef9a084fe8adcfee342e03933f0f48ec313b9c48b5
|
4
|
+
data.tar.gz: 716d8e6de64dacab36db01303a47480e0d603a31a8fd24025441ca3a5c8adc2e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 12f8f90e3853a0882f93bccde0ea1ce564068612d5d876d26c159625eea4b541bd391b81fe10cbda24b96458129e7caf3c52fe0471d81efc210c151708224887
|
7
|
+
data.tar.gz: 5fd5a73696fbb9daa9dbc873daee9cf580974ab17bca8fc62743967160f2757e5aaafbb8ece7f8d8e32d122e5d0b3e7b6848a834b5bc16ee65099975ff7337dc
|
data/lib/pg_conn/role_methods.rb
CHANGED
@@ -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.
|
61
|
-
#
|
62
|
-
#
|
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
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
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
|
+
|
data/lib/pg_conn/version.rb
CHANGED
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
|
513
|
-
#
|
514
|
-
#
|
515
|
-
#
|
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
|
-
|
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
|
-
|
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,
|
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
|
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
|
-
|
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
|
-
|
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
|
-
#
|
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,
|
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
|
707
|
-
|
708
|
-
|
709
|
-
|
710
|
-
|
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.
|
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-
|
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/
|