pg_conn 0.15.0 → 0.16.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/rdbms_methods.rb +4 -4
- data/lib/pg_conn/schema_methods.rb +38 -10
- data/lib/pg_conn/session_methods.rb +73 -22
- data/lib/pg_conn/version.rb +1 -1
- data/lib/pg_conn.rb +32 -20
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 25c23025d55818536e6d89bc5fa40b12ac296f0e61657db387f8a8732bdf3504
|
4
|
+
data.tar.gz: 7592d3a2b26d7f75f0ea88ad9b8f951422ced0cc913a387eb3f774b31bb1c4eb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0701ef07551bc1db06b654aa12e957ca4ad2286f1050c122af95ce78670ea426e6bf8f566831070200588e2c844eaf70ddab0cc5147941ccdcf6f4dc232c4102
|
7
|
+
data.tar.gz: 5310090682a9c93a648823b477081eebe31c214554ebe06a44d2f6702ec18c3fd5eb43e16bad0c15178d48a6194e109dbf15adb8bb3f75e645b90581ef3bc948
|
@@ -69,8 +69,8 @@ module PgConn
|
|
69
69
|
end
|
70
70
|
|
71
71
|
# Hollow-out a database by removing all schemas in the database. The public
|
72
|
-
# schema is recreated afterwards unless
|
73
|
-
#
|
72
|
+
# schema is recreated afterwards unless :public is false. Uses the current
|
73
|
+
# database if @database is nil
|
74
74
|
#
|
75
75
|
# Note that the database can have active users logged in while the database
|
76
76
|
# is emptied. TODO Explain what happens if the users have active
|
@@ -103,7 +103,7 @@ module PgConn
|
|
103
103
|
create(to_database, owner: owner, template: from_database)
|
104
104
|
end
|
105
105
|
|
106
|
-
# TODO: This code is replicated across many
|
106
|
+
# TODO: This code is replicated across many projects. Should be moved to PgConn
|
107
107
|
def load(database, file, role: ENV['USER'], gzip: nil)
|
108
108
|
command_opt = role ? "-c \"set role #{role}\";\n" : nil
|
109
109
|
if gzip
|
@@ -118,7 +118,7 @@ module PgConn
|
|
118
118
|
status == 0 or raise PsqlError.new(stderr)
|
119
119
|
end
|
120
120
|
|
121
|
-
# TODO: This code is replicated across many
|
121
|
+
# TODO: This code is replicated across many projects. Should be moved to PgConn
|
122
122
|
def save(database, file, data: true, schema: true, gzip: nil)
|
123
123
|
data_opt = data ? nil : "--schema-only"
|
124
124
|
schema_opt = schema ? nil : "--data-only"
|
@@ -35,6 +35,26 @@ module PgConn
|
|
35
35
|
true
|
36
36
|
end
|
37
37
|
|
38
|
+
# Hollow out a schema by dropping all tables and views (but still not
|
39
|
+
# functions and procedures TODO)
|
40
|
+
def empty!(schema, exclude: [])
|
41
|
+
self.list_tables(schema, exclude: exclude).each { |table|
|
42
|
+
conn.exec "drop table if exists #{schema}.#{table} cascade"
|
43
|
+
}
|
44
|
+
self.list_views(schema, exclude: exclude).each { |view|
|
45
|
+
conn.exec "drop view if exists #{schema}.#{view} cascade"
|
46
|
+
}
|
47
|
+
end
|
48
|
+
|
49
|
+
# Empty all tables in the given schema
|
50
|
+
def clean!(schema, exclude: [])
|
51
|
+
conn.session.triggers(false) {
|
52
|
+
self.list_tables(schema, exclude: exclude).each { |table|
|
53
|
+
conn.exec "delete from #{schema}.#{table}"
|
54
|
+
}
|
55
|
+
}
|
56
|
+
end
|
57
|
+
|
38
58
|
# List schemas. Built-in schemas are not listed unless the :all option is
|
39
59
|
# true. The :exclude option can be used to exclude named schemas
|
40
60
|
def list(all: false, exclude: [])
|
@@ -53,7 +73,7 @@ module PgConn
|
|
53
73
|
|
54
74
|
# Return true if table exists
|
55
75
|
def exist_table?(schema, table)
|
56
|
-
conn.exist?
|
76
|
+
conn.exist? relation_exist_query(schema, table, kind: %w(r f))
|
57
77
|
end
|
58
78
|
|
59
79
|
# Return true if view exists
|
@@ -66,19 +86,22 @@ module PgConn
|
|
66
86
|
conn.exist? column_exist_query(schema, relation, column)
|
67
87
|
end
|
68
88
|
|
89
|
+
# TODO
|
90
|
+
# def exist_index?(schema, relation, FIXME)
|
91
|
+
|
69
92
|
# Return list of relations in the schema
|
70
|
-
def list_relations(schema)
|
71
|
-
conn.values relation_list_query(schema)
|
93
|
+
def list_relations(schema, exclude: [])
|
94
|
+
conn.values relation_list_query(schema, exclude: exclude)
|
72
95
|
end
|
73
96
|
|
74
97
|
# Return list of tables in the schema
|
75
|
-
def list_tables(schema)
|
76
|
-
conn.values relation_list_query(schema, kind: %w(r f))
|
98
|
+
def list_tables(schema, exclude: [])
|
99
|
+
conn.values relation_list_query(schema, exclude: exclude, kind: %w(r f))
|
77
100
|
end
|
78
101
|
|
79
102
|
# Return list of view in the schema
|
80
|
-
def list_views(schema)
|
81
|
-
conn.values relation_list_query(schema, kind: %w(v m))
|
103
|
+
def list_views(schema, exclude: [])
|
104
|
+
conn.values relation_list_query(schema, exclude: exclude, kind: %w(v m))
|
82
105
|
end
|
83
106
|
|
84
107
|
# Return a list of columns. If +relation+ is defined, only columns from that
|
@@ -113,13 +136,18 @@ module PgConn
|
|
113
136
|
)
|
114
137
|
end
|
115
138
|
|
116
|
-
def relation_list_query(schema, kind: nil)
|
117
|
-
|
139
|
+
def relation_list_query(schema, exclude: nil, kind: nil)
|
140
|
+
kind_list = "'" + (kind.nil? ? %w(r f v m) : Array(kind).flatten).join("', '") + "'"
|
141
|
+
kind_expr = "relkind in (#{kind_list})"
|
142
|
+
exclude = Array(exclude || []).flatten
|
143
|
+
exclude_list = "'#{exclude.flatten.join("', '")}'" if !exclude.empty?
|
144
|
+
exclude_expr = exclude.empty? ? "true = true" : "not relname in (#{exclude_list})"
|
118
145
|
%(
|
119
146
|
select relname
|
120
147
|
from pg_class
|
121
148
|
where relnamespace::regnamespace::text = '#{schema}'
|
122
|
-
and
|
149
|
+
and #{kind_expr}
|
150
|
+
and #{exclude_expr}
|
123
151
|
)
|
124
152
|
end
|
125
153
|
|
@@ -8,7 +8,8 @@ module PgConn
|
|
8
8
|
end
|
9
9
|
|
10
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
|
11
|
+
# nil, it returns a list of database/username tuples for all connected
|
12
|
+
# users
|
12
13
|
def list(database)
|
13
14
|
if database
|
14
15
|
conn.values "select usename from pg_stat_activity where datname = '#{database}'"
|
@@ -21,34 +22,52 @@ module PgConn
|
|
21
22
|
end
|
22
23
|
end
|
23
24
|
|
24
|
-
#
|
25
|
-
|
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)
|
25
|
+
# Return true if the given database accepts connections
|
26
|
+
def enabled?(database)
|
31
27
|
!database.nil? or raise ArgumentError
|
32
|
-
|
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)"
|
28
|
+
conn.value "select datallowconn from pg_catalog.pg_database where datname = '#{database}'"
|
42
29
|
end
|
43
30
|
|
31
|
+
# Ensure connections to the given database are enabled
|
32
|
+
def enable(database)
|
33
|
+
!database.nil? or raise ArgumentError
|
34
|
+
conn.execute "alter database #{database} allow_connections = true"
|
35
|
+
end
|
36
|
+
|
37
|
+
# Ensure connections to the given database are disabled
|
44
38
|
def disable(database)
|
45
39
|
!database.nil? or raise ArgumentError
|
46
40
|
conn.execute "alter database #{database} allow_connections = false"
|
47
41
|
end
|
48
42
|
|
49
|
-
|
43
|
+
# TODO: Why not let a nil database argument have the current database as default?
|
44
|
+
|
45
|
+
# Terminate sessions in the database of the given users or of all users if
|
46
|
+
# nil. Note that 'terminate(database)' is a nop because the absent users
|
47
|
+
# argument defaults to an empty list
|
48
|
+
#
|
49
|
+
# TODO: Make is possible to terminate a single session of a user with
|
50
|
+
# multiple sessions (is this ever relevant?)
|
51
|
+
#
|
52
|
+
def terminate(database, *users)
|
50
53
|
!database.nil? or raise ArgumentError
|
51
|
-
|
54
|
+
enabled = self.enabled?(database)
|
55
|
+
|
56
|
+
case users
|
57
|
+
when [];
|
58
|
+
return
|
59
|
+
when [nil]
|
60
|
+
self.disable(database) if enabled
|
61
|
+
users = self.list(database)
|
62
|
+
else
|
63
|
+
users = Array(users).flatten
|
64
|
+
end
|
65
|
+
pids = self.pids(database, users)
|
66
|
+
if !pids.empty?
|
67
|
+
pids_sql = pids.map { |pid| "(#{pid})" }.join(", ")
|
68
|
+
conn.execute "select pg_terminate_backend(pid) from ( values #{pids_sql} ) as x(pid)"
|
69
|
+
end
|
70
|
+
self.enable(database) if self.enabled?(database) != enabled
|
52
71
|
end
|
53
72
|
|
54
73
|
# Run block without any connected users. Existing sessions are terminated
|
@@ -56,14 +75,46 @@ module PgConn
|
|
56
75
|
!database.nil? or raise ArgumentError
|
57
76
|
begin
|
58
77
|
disable(database)
|
59
|
-
|
60
|
-
terminate(database, users)
|
78
|
+
terminate(database, nil)
|
61
79
|
yield
|
62
80
|
ensure
|
63
81
|
enable(database)
|
64
82
|
end
|
65
83
|
end
|
66
84
|
|
85
|
+
# Return true if session triggers are enabled. Triggers are enabled by
|
86
|
+
# default by Postgres
|
87
|
+
def triggers?() conn.value "select current_setting('session_replication_role') = 'replica'" end
|
88
|
+
|
89
|
+
# Enable session triggers
|
90
|
+
def enable_triggers()
|
91
|
+
conn.execute "set session session_replication_role = replica"
|
92
|
+
end
|
93
|
+
|
94
|
+
# Disable session triggers
|
95
|
+
def disable_triggers()
|
96
|
+
conn.execute "set session session_replication_role = DEFAULT"
|
97
|
+
end
|
98
|
+
|
99
|
+
# Execute block with session triggers on or off
|
100
|
+
def triggers(on_off, &block)
|
101
|
+
begin
|
102
|
+
active = triggers?
|
103
|
+
if on_off && !active
|
104
|
+
enable_triggers
|
105
|
+
elsif !on_off && active
|
106
|
+
disable_triggers
|
107
|
+
end
|
108
|
+
yield
|
109
|
+
ensure
|
110
|
+
if on_off && !active
|
111
|
+
disable_triggers
|
112
|
+
elsif !on_off && active
|
113
|
+
enable_triggers
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
67
118
|
private
|
68
119
|
# Like #list but returns the PIDs of the users
|
69
120
|
def pids(database, users)
|
data/lib/pg_conn/version.rb
CHANGED
data/lib/pg_conn.rb
CHANGED
@@ -67,9 +67,15 @@ module PgConn
|
|
67
67
|
# #exec or #transaction block
|
68
68
|
attr_reader :timestamp
|
69
69
|
|
70
|
-
# PG::Error object
|
70
|
+
# PG::Error object of the first failed statement in the transaction;
|
71
|
+
# otherwise nil. It is cleared at the beginning of a transaction so be sure
|
72
|
+
# to save it before you run any cleanup code that may initiate new
|
73
|
+
# transactions
|
71
74
|
attr_reader :error
|
72
75
|
|
76
|
+
# True if the transaction is in a error state
|
77
|
+
def error?() !@error.nil? end
|
78
|
+
|
73
79
|
# Tuple of error message, lineno, and charno of the error object where each
|
74
80
|
# element defaults to nil if not found
|
75
81
|
def err
|
@@ -607,24 +613,17 @@ module PgConn
|
|
607
613
|
#
|
608
614
|
# TODO: Make sure the transaction stack is emptied on postgres errors
|
609
615
|
def exec(sql, commit: true, fail: true, silent: false)
|
610
|
-
|
611
|
-
transaction(commit: commit) { execute(sql, fail: fail, silent: silent) }
|
612
|
-
rescue PG::Error
|
613
|
-
raise if fail
|
614
|
-
cancel_transaction
|
615
|
-
return nil
|
616
|
-
end
|
616
|
+
transaction(commit: commit) { execute(sql, fail: fail, silent: silent) }
|
617
617
|
end
|
618
618
|
|
619
619
|
# Like #exec but returns true/false depending on if the command succeeded.
|
620
|
-
# There is not corresponding #
|
620
|
+
# There is not a corresponding #execute? method because any failure rolls
|
621
621
|
# back the whole transaction stack. TODO: Check which exceptions that
|
622
622
|
# should be captured
|
623
623
|
def exec?(sql, commit: true, silent: true)
|
624
624
|
begin
|
625
625
|
exec(sql, commit: commit, fail: true, silent: silent)
|
626
626
|
rescue PG::Error
|
627
|
-
cancel_transaction
|
628
627
|
return false
|
629
628
|
end
|
630
629
|
return true
|
@@ -643,6 +642,7 @@ module PgConn
|
|
643
642
|
begin
|
644
643
|
pg_exec(sql, silent: silent)&.cmd_tuples
|
645
644
|
rescue PG::Error
|
645
|
+
cancel_transaction
|
646
646
|
raise if fail
|
647
647
|
return nil
|
648
648
|
end
|
@@ -672,7 +672,7 @@ module PgConn
|
|
672
672
|
|
673
673
|
def commit()
|
674
674
|
if transaction?
|
675
|
-
pop_transaction
|
675
|
+
pop_transaction(fail: false)
|
676
676
|
else
|
677
677
|
pg_exec("commit")
|
678
678
|
end
|
@@ -680,11 +680,18 @@ module PgConn
|
|
680
680
|
|
681
681
|
def rollback() raise Rollback end
|
682
682
|
|
683
|
-
# True if a transaction is in progress
|
684
|
-
#
|
685
|
-
#
|
683
|
+
# True if a transaction is in progress
|
684
|
+
#
|
685
|
+
# Note that this requires all transactions to be started using PgConn's
|
686
|
+
# transaction methods; transactions started using raw SQL are not
|
687
|
+
# registered
|
686
688
|
def transaction?() !@savepoints.nil? end
|
687
689
|
|
690
|
+
# True if a database transaction is in progress
|
691
|
+
def database_transaction?
|
692
|
+
pg_exec("select transaction_timestamp() != statement_timestamp()", fail: false)
|
693
|
+
end
|
694
|
+
|
688
695
|
# Returns number of transaction or savepoint levels
|
689
696
|
def transactions() @savepoints ? 1 + @savepoints.size : 0 end
|
690
697
|
|
@@ -696,11 +703,12 @@ module PgConn
|
|
696
703
|
else
|
697
704
|
@savepoints = []
|
698
705
|
pg_exec("begin")
|
706
|
+
@error = @err = nil
|
699
707
|
@timestamp = pg_exec("select current_timestamp").values[0][0] if @pg_connection
|
700
708
|
end
|
701
709
|
end
|
702
710
|
|
703
|
-
def pop_transaction(commit: true, fail: true)
|
711
|
+
def pop_transaction(commit: true, fail: true, exception: true)
|
704
712
|
if transaction?
|
705
713
|
if savepoint = @savepoints.pop
|
706
714
|
if !commit
|
@@ -728,8 +736,10 @@ module PgConn
|
|
728
736
|
begin
|
729
737
|
pg_exec("rollback")
|
730
738
|
rescue PG::Error
|
739
|
+
;
|
731
740
|
end
|
732
741
|
@savepoints = nil
|
742
|
+
true
|
733
743
|
end
|
734
744
|
|
735
745
|
# Start a transaction. If called with a block, the block is executed within
|
@@ -748,13 +758,14 @@ module PgConn
|
|
748
758
|
push_transaction
|
749
759
|
result = yield
|
750
760
|
rescue PgConn::Rollback
|
751
|
-
pop_transaction(commit: false)
|
761
|
+
pop_transaction(commit: false, fail: false)
|
752
762
|
return nil
|
753
763
|
rescue PG::Error
|
764
|
+
cancel_transaction
|
754
765
|
@savepoints = nil
|
755
766
|
raise
|
756
767
|
end
|
757
|
-
pop_transaction(commit: commit)
|
768
|
+
pop_transaction(commit: commit, fail: false)
|
758
769
|
result
|
759
770
|
else
|
760
771
|
push_transaction
|
@@ -812,7 +823,6 @@ module PgConn
|
|
812
823
|
# TODO: Fix silent by not handling exceptions
|
813
824
|
def pg_exec(arg, silent: false)
|
814
825
|
if @pg_connection
|
815
|
-
@error = @err = nil
|
816
826
|
begin
|
817
827
|
last_stmt = nil # To make the current SQL statement visible to the rescue clause. FIXME Not used?
|
818
828
|
if arg.is_a?(String)
|
@@ -826,9 +836,11 @@ module PgConn
|
|
826
836
|
last_stmt = stmts.last
|
827
837
|
@pg_connection.exec(stmts.join(";\n"))
|
828
838
|
end
|
829
|
-
|
830
839
|
rescue PG::Error => ex
|
831
|
-
@error
|
840
|
+
if @error.nil?
|
841
|
+
@error = ex
|
842
|
+
@err = nil
|
843
|
+
end
|
832
844
|
if !silent # FIXME Why do we handle this?
|
833
845
|
$stderr.puts arg
|
834
846
|
$stderr.puts
|
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.16.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-04
|
11
|
+
date: 2024-05-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: pg
|