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