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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5ebb0df0000ecb59d313c906b608bdd267aaef84edcb4be633650601c453768b
4
- data.tar.gz: 96d5e1db6d3644fef73dbb69f50acd7deec6dc79404641615d034586d1fddcf0
3
+ metadata.gz: b6f8840c223cb2dd9a6a5f8b2211e43184eb308114ffdd8bdc76d92f6ea03e16
4
+ data.tar.gz: fbfad4033c2d21abb91b726c183001d4edbaf348d61cb425b4b003dd89a37f35
5
5
  SHA512:
6
- metadata.gz: 1cba9680627ce4ba4c5b8aa3df5505ab407e6096c26560799680a340640cef3b6a107319c58c1230ee130f1811d569367f8819d81f1be957069703b03d0bbb8b
7
- data.tar.gz: 4c578bd594f740f9b07d288a1ae37de9d54cee7009da5f4d493a58ed324821cd4258ebce916ddfd68527e773c4396cd866da8054d4558d31c95962c6fd9ce9d1
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 if :public is false. Uses the
73
- # current database if @database is nil
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 project. Should be moved to PgConn
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 project. Should be moved to PgConn
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?(relation_exist_query(schema, table, kind: %w(r f)))
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
- kind_sql_list = "'" + (kind.nil? ? %w(r f v m) : Array(kind).flatten).join("', '") + "'"
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 relkind in (#{kind_sql_list})
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 users
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
- # 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)
25
+ # Return true if the given database accepts connections
26
+ def enabled?(database)
31
27
  !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)"
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
- def enable(database)
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
- conn.execute "alter database #{database} allow_connections = true"
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
- users = list(database)
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)
@@ -1,3 +1,3 @@
1
1
  module PgConn
2
- VERSION = "0.15.1"
2
+ VERSION = "0.17.0"
3
3
  end
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.15.1
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-13 00:00:00.000000000 Z
11
+ date: 2024-05-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pg