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 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