pg_conn 0.15.1 → 0.17.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 +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
|