pg_conn 0.15.1 → 0.17.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 +77 -10
- data/lib/pg_conn/session_methods.rb +73 -22
- data/lib/pg_conn/version.rb +1 -1
- data/lib/pg_conn.rb +3 -0
- 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: b6f8840c223cb2dd9a6a5f8b2211e43184eb308114ffdd8bdc76d92f6ea03e16
|
4
|
+
data.tar.gz: fbfad4033c2d21abb91b726c183001d4edbaf348d61cb425b4b003dd89a37f35
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fe6647045b3bad635ed480e201967a20f0b11b5fd71bcbdec6766b6a2f056e3b0c8de045e87f56af90fb429130b2a9f105c46f5de5de31219b8384d18b27b3ca
|
7
|
+
data.tar.gz: 5e114bd53908faf4a881799035276151ace147c63156cc006359e6483c182d0522d2a724f4e7b4fd551914739b23240c06a55319b46d4aa68c6bf65f0f8da095
|
@@ -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
|
@@ -101,6 +124,45 @@ module PgConn
|
|
101
124
|
raise NotImplementedError
|
102
125
|
end
|
103
126
|
|
127
|
+
# Return name of the table's sequence (if any)
|
128
|
+
def sequence(schema, table)
|
129
|
+
conn.value "select pg_get_serial_sequence('#{schema}.#{table}', 'id')"
|
130
|
+
end
|
131
|
+
|
132
|
+
# Get the current serial value for the table. Returns nil if the serial has
|
133
|
+
# not been used. If :next is true, the next value will be returned
|
134
|
+
def get_serial(schema, table, next: false)
|
135
|
+
uid = "#{schema}.#{table}"
|
136
|
+
next_option = binding.local_variable_get(:next) # because 'next' is a keyword
|
137
|
+
|
138
|
+
seq = sequence(schema, table) or raise ArgumentError, "Table #{uid} does not have a sequence"
|
139
|
+
value = conn.value %(
|
140
|
+
select
|
141
|
+
case is_called
|
142
|
+
when true then last_value
|
143
|
+
else null
|
144
|
+
end as "value"
|
145
|
+
from
|
146
|
+
#{seq}
|
147
|
+
)
|
148
|
+
if next_option
|
149
|
+
value&.+(1) || 1
|
150
|
+
else
|
151
|
+
value
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
# Set the serial value for the table
|
156
|
+
def set_serial(schema, table, value)
|
157
|
+
uid = "#{schema}.#{table}"
|
158
|
+
seq = sequence(schema, table) or raise ArgumentError, "Table #{uid} does not have a sequence"
|
159
|
+
if value
|
160
|
+
conn.exec "select setval('#{seq}', #{value})"
|
161
|
+
else
|
162
|
+
conn.exec "select setval('#{seq}', 1, false)"
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
104
166
|
private
|
105
167
|
def relation_exist_query(schema, relation, kind: nil)
|
106
168
|
kind_sql_list = "'" + (kind.nil? ? %w(r f v m) : Array(kind).flatten).join("', '") + "'"
|
@@ -113,13 +175,18 @@ module PgConn
|
|
113
175
|
)
|
114
176
|
end
|
115
177
|
|
116
|
-
def relation_list_query(schema, kind: nil)
|
117
|
-
|
178
|
+
def relation_list_query(schema, exclude: nil, kind: nil)
|
179
|
+
kind_list = "'" + (kind.nil? ? %w(r f v m) : Array(kind).flatten).join("', '") + "'"
|
180
|
+
kind_expr = "relkind in (#{kind_list})"
|
181
|
+
exclude = Array(exclude || []).flatten
|
182
|
+
exclude_list = "'#{exclude.flatten.join("', '")}'" if !exclude.empty?
|
183
|
+
exclude_expr = exclude.empty? ? "true = true" : "not relname in (#{exclude_list})"
|
118
184
|
%(
|
119
185
|
select relname
|
120
186
|
from pg_class
|
121
187
|
where relnamespace::regnamespace::text = '#{schema}'
|
122
|
-
and
|
188
|
+
and #{kind_expr}
|
189
|
+
and #{exclude_expr}
|
123
190
|
)
|
124
191
|
end
|
125
192
|
|
@@ -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
@@ -73,6 +73,9 @@ module PgConn
|
|
73
73
|
# transactions
|
74
74
|
attr_reader :error
|
75
75
|
|
76
|
+
# True if the transaction is in a error state
|
77
|
+
def error?() !@error.nil? end
|
78
|
+
|
76
79
|
# Tuple of error message, lineno, and charno of the error object where each
|
77
80
|
# element defaults to nil if not found
|
78
81
|
def err
|
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.17.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
|