postspec 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +19 -0
- data/.rspec +3 -0
- data/.ruby-version +1 -0
- data/.travis.yml +6 -0
- data/Gemfile +5 -0
- data/README.md +36 -0
- data/Rakefile +6 -0
- data/TODO +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/exe/postspec +40 -0
- data/lib/postspec/config.rb +38 -0
- data/lib/postspec/environment.rb +491 -0
- data/lib/postspec/frame.rb +147 -0
- data/lib/postspec/render.rb +178 -0
- data/lib/postspec/state.rb +112 -0
- data/lib/postspec/version.rb +3 -0
- data/lib/postspec.rb +367 -0
- data/lib/postspec_helper.rb +128 -0
- data/lib/share/postspec_schema.sql +137 -0
- data/performance/performance_spec.rb +114 -0
- data/postspec.gemspec +41 -0
- data/snippets/sequences.sql +19 -0
- data/snippets/serials.sql +13 -0
- data/snippets/triggers.sql +14 -0
- metadata +237 -0
data/lib/postspec.rb
ADDED
@@ -0,0 +1,367 @@
|
|
1
|
+
require "developer_exceptions"
|
2
|
+
|
3
|
+
require "fixture_fox"
|
4
|
+
|
5
|
+
require "postspec/version.rb"
|
6
|
+
require "postspec/frame.rb"
|
7
|
+
require "postspec/render.rb"
|
8
|
+
require "postspec/state.rb"
|
9
|
+
require "postspec/environment.rb"
|
10
|
+
require "postspec/config.rb"
|
11
|
+
|
12
|
+
include DeveloperExceptions
|
13
|
+
|
14
|
+
# before everything
|
15
|
+
# truncate or setup seed
|
16
|
+
#
|
17
|
+
# after everything
|
18
|
+
# drop seed methods
|
19
|
+
|
20
|
+
module Postspec
|
21
|
+
class Postspec
|
22
|
+
class Error < RuntimeError; end
|
23
|
+
|
24
|
+
SHARE_DIR = File.expand_path(File.dirname(__FILE__)) + "/share"
|
25
|
+
|
26
|
+
# Database connection. A PgConn object
|
27
|
+
attr_reader :conn
|
28
|
+
|
29
|
+
# Meta object. A PgMeta object
|
30
|
+
attr_reader :meta
|
31
|
+
|
32
|
+
# Type of the database. A PgGraph::Type object
|
33
|
+
attr_reader :type
|
34
|
+
|
35
|
+
# List of table types in the database except tables from hidden schemas (eg. postspec)
|
36
|
+
attr_reader :tables
|
37
|
+
|
38
|
+
# If true and a test case fails, postspec will commit all changes and
|
39
|
+
# ignore any further tests. If rspec is called with the --fail-fast option
|
40
|
+
# the test run will terminate immediately. Default true
|
41
|
+
attr_reader :fail
|
42
|
+
|
43
|
+
# State
|
44
|
+
attr_reader :state
|
45
|
+
|
46
|
+
# Map from UID to record ID of inserted, updated, and deleted records
|
47
|
+
forward_to :state, :inserted, :updated, :deleted
|
48
|
+
|
49
|
+
# Current mode. Can be one of :seed, :empty, ...
|
50
|
+
forward_to :state, :mode
|
51
|
+
|
52
|
+
# Transaction frame stack
|
53
|
+
attr_reader :frames
|
54
|
+
|
55
|
+
# Current frame
|
56
|
+
def frame() @frames.top end
|
57
|
+
forward_to :frame, :ids, :anchors
|
58
|
+
|
59
|
+
# Prick anchors (FIXME)
|
60
|
+
attr_reader :prick_anchors
|
61
|
+
|
62
|
+
# Render object
|
63
|
+
attr_reader :render
|
64
|
+
|
65
|
+
# TODO: PgMeta object
|
66
|
+
#
|
67
|
+
# +mode+ can be one of :seed, :empty (TODO :reseed, :keep)
|
68
|
+
def initialize(conn, reflector: nil, mode: :empty, anchors: [], fail: true)
|
69
|
+
# puts "Postspec#initialize"
|
70
|
+
constrain conn, PgConn
|
71
|
+
constrain reflector, NilClass, String, PgGraph::Reflector
|
72
|
+
constrain mode, lambda { |m| [:empty, :seed].include?(m) }
|
73
|
+
constrain anchors, [Hash], NilClass
|
74
|
+
constrain fail, TrueClass, FalseClass
|
75
|
+
|
76
|
+
@conn = conn
|
77
|
+
@meta = PgMeta.new(@conn)
|
78
|
+
|
79
|
+
# Make sure the postspec schema is not included in the type model. TODO:
|
80
|
+
# Consolidate this into the :ignore option of PgGraph::Type.new as is
|
81
|
+
# done with the prick schema below
|
82
|
+
has_postspec = @meta.schemas.key?("postspec")
|
83
|
+
!has_postspec or (@meta.schemas["postspec"].hidden = true)
|
84
|
+
|
85
|
+
@type = PgGraph::Type.new(@meta, reflector, ignore: ["prick"])
|
86
|
+
@render = Render.new(self)
|
87
|
+
@tables = type.schemas.map(&:tables).flatten
|
88
|
+
@fail = fail
|
89
|
+
@failed = false
|
90
|
+
@success = true
|
91
|
+
|
92
|
+
# Compile-time state variable with the current search_path. Frames are
|
93
|
+
# initialized with this value when they're declared (#use, #statement,
|
94
|
+
# etc.)
|
95
|
+
@search_path = %w(public)
|
96
|
+
|
97
|
+
# Ensure postgres environment (the postspec schema). TODO: Move into Environment#initialize
|
98
|
+
@environment = Environment.new(self)
|
99
|
+
if @environment.exist?
|
100
|
+
if last_state = State.read(conn)
|
101
|
+
if last_state.ready
|
102
|
+
if !last_state.clean
|
103
|
+
# Last run didn't cleanup after itself
|
104
|
+
@environment.clean
|
105
|
+
# elsif ... HERE
|
106
|
+
end
|
107
|
+
|
108
|
+
if last_state.mode != mode
|
109
|
+
# We have changed mode
|
110
|
+
@environment.teardown(last_state.mode)
|
111
|
+
@environment.setup(mode)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
else
|
115
|
+
# First run after deep-cleaning
|
116
|
+
end
|
117
|
+
else
|
118
|
+
@environment.create
|
119
|
+
@environment.setup(mode)
|
120
|
+
end
|
121
|
+
|
122
|
+
# State of current run. Note that @state needs to be initialized after
|
123
|
+
# the previous state has been read into last_state
|
124
|
+
@state = State.create(conn, mode)
|
125
|
+
|
126
|
+
# Compare seed ids with table_sequence_ids to tell if a table has been
|
127
|
+
# tampered with
|
128
|
+
#
|
129
|
+
# FIXME Implement
|
130
|
+
# %(
|
131
|
+
# select table_uid
|
132
|
+
# record_id
|
133
|
+
# from table_sequence_ids() i
|
134
|
+
# left join postspec.seeds s on s.table_uid = i.table_uid and s.record_id < i.record_id
|
135
|
+
# )
|
136
|
+
|
137
|
+
# Frames. TODO: Move into FrameStack#initialize
|
138
|
+
@frames = FrameStack.new(self)
|
139
|
+
if mode == :seed
|
140
|
+
@frames.push SeedFrame.new(self, @environment.uids, FixtureFox::Anchors.new(@type, anchors))
|
141
|
+
else
|
142
|
+
@frames.push EmptyFrame.new(self)
|
143
|
+
end
|
144
|
+
@conn.execute(render.execution_unit(tables.map(&:uid), frames.top.push_sql))
|
145
|
+
|
146
|
+
@foxes_stack = [] # stack of stack of Fox objects. TODO: Why a stack?
|
147
|
+
@datas_stack = [] # stack of stack of Data objects
|
148
|
+
|
149
|
+
@state.ready = true
|
150
|
+
State.write(conn, @state)
|
151
|
+
end
|
152
|
+
|
153
|
+
def terminate
|
154
|
+
# puts "Postspec#terminate"
|
155
|
+
@state.status = success?
|
156
|
+
@state.clean = !failed?
|
157
|
+
State.write(conn, @state)
|
158
|
+
if frames.top
|
159
|
+
@conn.execute(frames.top.pop_sql)
|
160
|
+
@frames.pop
|
161
|
+
end
|
162
|
+
@conn.terminate
|
163
|
+
end
|
164
|
+
|
165
|
+
# Current fox object. This can be nil (TODO alternatively: Create a dummy fox in the root frame)
|
166
|
+
def fox() @frames.top.is_a?(FixtureFox::Fox) ? @frames.top.fox : nil end
|
167
|
+
|
168
|
+
# True if a transaction is in progress
|
169
|
+
def transaction?() @conn.transaction? end
|
170
|
+
|
171
|
+
# True if this is the primary transaction. A primary transaction is a
|
172
|
+
# Postgres transaction while secondary transactions are savepoints
|
173
|
+
def primary_transaction?() @frames.size == 1 end
|
174
|
+
|
175
|
+
# Transactionn timestamp
|
176
|
+
def timestamp() @conn.timestamp end
|
177
|
+
|
178
|
+
# True if no tests failed. Default true
|
179
|
+
def success?() @success end
|
180
|
+
|
181
|
+
# True if Postspec is in failed state. In failed state no new commands can
|
182
|
+
# be issued. Postspec enters a failed state when it encounters an error and
|
183
|
+
# #fail is true. Note that #failed? can be false while #success? is also
|
184
|
+
# false. That happens when rspec is called without the --fail-fast option
|
185
|
+
def failed?() @failed end
|
186
|
+
|
187
|
+
# Set failed state but only if #fail is true
|
188
|
+
def fail!
|
189
|
+
@success = false
|
190
|
+
@failed = true if @fail
|
191
|
+
end
|
192
|
+
|
193
|
+
def search_path() @search_path end
|
194
|
+
def search_path=(*paths)
|
195
|
+
@search_path = Array(paths).flatten.compact
|
196
|
+
@search_path = %w(public) if @search_path.empty?
|
197
|
+
end
|
198
|
+
|
199
|
+
# Only called from RSpec::Core::ExampleGroup#set_search_path. FIXME: Why not search_path=
|
200
|
+
def set_search_path(rspec, *paths)
|
201
|
+
constrain paths, String, [String]
|
202
|
+
self.search_path = paths
|
203
|
+
|
204
|
+
# Closure variables
|
205
|
+
this = self
|
206
|
+
search_path = Array(paths)
|
207
|
+
|
208
|
+
rspec.before(:all) {
|
209
|
+
frame = this.frames.push NopFrame.new(this.frames.top, search_path)
|
210
|
+
this.conn.execute(frame.push_sql)
|
211
|
+
}
|
212
|
+
|
213
|
+
rspec.after(:all) {
|
214
|
+
frame = this.frames.pop
|
215
|
+
this.conn.execute(frame.pop_sql)
|
216
|
+
}
|
217
|
+
end
|
218
|
+
|
219
|
+
def use(rspec, *files)
|
220
|
+
|
221
|
+
# Closure variables
|
222
|
+
this = self
|
223
|
+
search_path = self.search_path
|
224
|
+
|
225
|
+
rspec.before(:all) {
|
226
|
+
frame = this.frames.push FoxFrame.new(this.frames.top, search_path, this.type, files)
|
227
|
+
|
228
|
+
this.conn.push_transaction if frame.transaction?
|
229
|
+
begin
|
230
|
+
this.conn.execute(frame.push_sql)
|
231
|
+
this.conn.commit if !frame.transaction?
|
232
|
+
rescue PG::Error
|
233
|
+
this.fail!
|
234
|
+
this.conn.cancel_transaction
|
235
|
+
exit(1)
|
236
|
+
end
|
237
|
+
}
|
238
|
+
|
239
|
+
rspec.after(:all) {
|
240
|
+
# fail!(clear: !fail) FIXME ????
|
241
|
+
frame = this.frames.pop
|
242
|
+
this.conn.execute(frame.pop_sql) if !this.failed?
|
243
|
+
this.conn.pop_transaction(commit: this.failed?) if frame.transaction?
|
244
|
+
this.conn.commit if !frame.transaction?
|
245
|
+
}
|
246
|
+
end
|
247
|
+
|
248
|
+
# Can only be used in group context. Encloses subsequent commands in a transaction
|
249
|
+
def statement(rspec, sql, fail: true, &block)
|
250
|
+
this = self
|
251
|
+
search_path = self.search_path
|
252
|
+
|
253
|
+
rspec.before(:all) {
|
254
|
+
frame = this.frames.push NopFrame.new(this.frames.top, search_path)
|
255
|
+
this.conn.push_transaction if frame.transaction?
|
256
|
+
begin
|
257
|
+
this.conn.execute(frame.push_sql)
|
258
|
+
this.conn.execute(sql)
|
259
|
+
this.conn.commit if !frame.transaction?
|
260
|
+
rescue PG::Error
|
261
|
+
this.fail!
|
262
|
+
this.conn.cancel_transaction
|
263
|
+
exit(1)
|
264
|
+
end
|
265
|
+
}
|
266
|
+
|
267
|
+
rspec.after(:all) {
|
268
|
+
frame = this.frames.pop
|
269
|
+
if this.conn.transaction?
|
270
|
+
this.conn.pop_transaction(commit: this.failed?) if frame.transaction?
|
271
|
+
this.conn.commit if !frame.transaction?
|
272
|
+
else
|
273
|
+
this.conn.execute(frame.pop_sql) if !this.failed?
|
274
|
+
end
|
275
|
+
}
|
276
|
+
end
|
277
|
+
|
278
|
+
# Can only be used in example context. Doesn't manipulate transactions
|
279
|
+
def exec(sql)
|
280
|
+
@datas[-1] = @fixture = nil
|
281
|
+
@conn.execute(sql)
|
282
|
+
end
|
283
|
+
|
284
|
+
# Can only be used in example context. Doesn't manipulate transactions
|
285
|
+
def execute(sql)
|
286
|
+
@datas[-1] = @fixture = nil
|
287
|
+
@conn.execute(sql)
|
288
|
+
end
|
289
|
+
|
290
|
+
# Execute procedure. Note that the current transaction is committed before
|
291
|
+
# the procedure is run. The transaction stack is reestablished after the
|
292
|
+
# test is done. TODO: Why can't it be renamed #call? TODO: Implement args
|
293
|
+
def procedure(rspec, stored_procedure, *args)
|
294
|
+
this = self
|
295
|
+
rspec.before(:all) {
|
296
|
+
this.freeze_transaction
|
297
|
+
this.conn.execute("call #{stored_procedure}()")
|
298
|
+
}
|
299
|
+
rspec.around(:each) { |example|
|
300
|
+
example.run
|
301
|
+
}
|
302
|
+
rspec.after(:all) {
|
303
|
+
this.thaw_transaction
|
304
|
+
}
|
305
|
+
end
|
306
|
+
|
307
|
+
# The connection object
|
308
|
+
def db() @conn end
|
309
|
+
|
310
|
+
# The content of the database as a PgGraph::Data object. Note that this
|
311
|
+
# loads the entire database (TODO: Lazy loading)
|
312
|
+
def data() @datas[-1] ||= type.instantiate(db, fox.anchors) end
|
313
|
+
|
314
|
+
# The content of the fixture as a PgGraph::Data object
|
315
|
+
def fixture()
|
316
|
+
@fixture ||= begin
|
317
|
+
if fox.ast
|
318
|
+
fox.data
|
319
|
+
elsif @foxes.size >= 2
|
320
|
+
@foxes[-2].data
|
321
|
+
else
|
322
|
+
type.instantiate
|
323
|
+
end
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
def transaction(&block)
|
328
|
+
push_transaction
|
329
|
+
yield
|
330
|
+
pop_transaction
|
331
|
+
end
|
332
|
+
|
333
|
+
def push_transaction
|
334
|
+
@foxes.push (transaction? ? @foxes.last.dup : FixtureFox::Fox.new(type, schema: search_path.first))
|
335
|
+
@datas.push nil
|
336
|
+
@conn.push_transaction
|
337
|
+
end
|
338
|
+
|
339
|
+
def pop_transaction
|
340
|
+
@foxes.pop
|
341
|
+
@datas.pop
|
342
|
+
fail!(clear: !fail)
|
343
|
+
@conn.pop_transaction(commit: failed?)
|
344
|
+
end
|
345
|
+
|
346
|
+
# Starts a transaction if not already in a transaction. Does nothing otherwise
|
347
|
+
def ensure_transaction
|
348
|
+
push_transaction if !transaction?
|
349
|
+
end
|
350
|
+
|
351
|
+
def freeze_transaction()
|
352
|
+
@foxes_stack.push @foxes
|
353
|
+
@datas_stack.push @datas
|
354
|
+
@foxes.each { @conn.pop_transaction(commit: true) }
|
355
|
+
end
|
356
|
+
|
357
|
+
def thaw_transaction()
|
358
|
+
!@foxes_stack.empty? or raise Error, "Stack underrun"
|
359
|
+
@foxes = @foxes_stack.pop
|
360
|
+
@datas = @datas_stack.pop
|
361
|
+
@foxes.each { |fox|
|
362
|
+
@conn.push_transaction
|
363
|
+
fox.data.write(@conn)
|
364
|
+
}
|
365
|
+
end
|
366
|
+
end
|
367
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
|
2
|
+
def postspec() Postspec.postspec end
|
3
|
+
def use(*args) postspec.use(*args) end
|
4
|
+
def statement(*args, **opts, &block) postspec.statement(*args, **opts, &block) end
|
5
|
+
def exec(*args) postspec.conn.execute(*args) end
|
6
|
+
def execute(*args, **opts, &block) postspec.execute(*args, **opts, &block) end
|
7
|
+
def fox() postspec.fox end
|
8
|
+
def db() postspec.db end
|
9
|
+
def timestamp() postspec.timestamp end
|
10
|
+
def data() postspec.data end
|
11
|
+
def inserted_records() postspec.inserted end
|
12
|
+
def updated_records() postspec.updated end
|
13
|
+
def deleted_records() postspec.deleted end
|
14
|
+
def anchors() postspec.anchors end
|
15
|
+
def fixture() postspec.fixture end
|
16
|
+
def transaction(&block) postspec.transaction(&block) end
|
17
|
+
def push_transaction() postspec.push_transaction end
|
18
|
+
def pop_transaction() postspec.pop_transaction end
|
19
|
+
|
20
|
+
RSpec.configure do |config|
|
21
|
+
config.before(:suite) do
|
22
|
+
# puts "postspec_helper before suite"
|
23
|
+
$postspec_failed = false
|
24
|
+
Postspec.configure { |postspec_config|
|
25
|
+
postspec_config.fail_fast ||= (config.fail_fast == 1)
|
26
|
+
}
|
27
|
+
end
|
28
|
+
|
29
|
+
config.after(:suite) do
|
30
|
+
# puts "postspec_helper after suite"
|
31
|
+
postspec.terminate if !postspec.failed?
|
32
|
+
end
|
33
|
+
|
34
|
+
config.around(:example) do |example|
|
35
|
+
# puts "postspec_helper around example"
|
36
|
+
if postspec.failed?
|
37
|
+
example.skip
|
38
|
+
else
|
39
|
+
postspec.conn.push_transaction
|
40
|
+
begin
|
41
|
+
# puts ">> TRYING"
|
42
|
+
result = example.run
|
43
|
+
# puts ">> RESULT: #{result.inspect} (#{result.class})"
|
44
|
+
# puts ">> EXAMPLE: #{example.exception.inspect} (#{example.exception.class})"
|
45
|
+
# puts ">> EXECUTION_RESULT: #{example.execution_result}"
|
46
|
+
if result.is_a?(PG::Error)
|
47
|
+
postspec.fail!
|
48
|
+
postspec.conn.cancel_transaction
|
49
|
+
elsif result.is_a?(Exception)
|
50
|
+
postspec.fail!
|
51
|
+
postspec.conn.pop_transaction(commit: postspec.failed?)
|
52
|
+
else
|
53
|
+
postspec.conn.pop_transaction(commit: postspec.failed?)
|
54
|
+
end
|
55
|
+
rescue
|
56
|
+
puts ">> OTHER EXCEPTIONS"
|
57
|
+
puts ">> OTHER EXCEPTIONS"
|
58
|
+
puts ">> OTHER EXCEPTIONS"
|
59
|
+
postspec.conn.cancel_transaction
|
60
|
+
postspec.fail!
|
61
|
+
raise
|
62
|
+
end
|
63
|
+
result
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
class RSpec::Core::ExampleGroup
|
69
|
+
def self.set_search_path(*args)
|
70
|
+
postspec.set_search_path(self, *args)
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.use(*args)
|
74
|
+
postspec.use(self, *args)
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.statement(*args)
|
78
|
+
postspec.statement(self, *args)
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.procedure(*args)
|
82
|
+
postspec.procedure(self, *args)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# Monkey patch RSpec to interpret a nil argument to describe/context as "no
|
87
|
+
# output and no indentation". It is used in the implementation of #group below
|
88
|
+
#
|
89
|
+
module RSpec
|
90
|
+
module Core
|
91
|
+
module Formatters
|
92
|
+
# @private
|
93
|
+
class DocumentationFormatter
|
94
|
+
def example_group_started(notification)
|
95
|
+
description = notification.group.description.strip
|
96
|
+
if description != ""
|
97
|
+
output.puts if @group_level == 0
|
98
|
+
output.puts "#{current_indentation}#{notification.group.description.strip}"
|
99
|
+
@group_level += 1
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def example_group_finished(notification)
|
104
|
+
description = notification.group.description.strip
|
105
|
+
if description != ""
|
106
|
+
@group_level -= 1 if @group_level > 0
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# Extend RSpec with a "group" declaration that provides a scope like
|
115
|
+
# describe/context but is silent
|
116
|
+
#
|
117
|
+
module RSpec
|
118
|
+
module Core
|
119
|
+
class ExampleGroup
|
120
|
+
class << self
|
121
|
+
def group(&block)
|
122
|
+
describe(nil, &block)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
@@ -0,0 +1,137 @@
|
|
1
|
+
|
2
|
+
drop schema if exists "postspec" cascade;
|
3
|
+
create schema postspec;
|
4
|
+
|
5
|
+
set search_path to postspec;
|
6
|
+
|
7
|
+
-- Return a map from table UID to last value of sequence. All user tables are
|
8
|
+
-- included but tables without sequences (subtables) have last value set to
|
9
|
+
-- null
|
10
|
+
create or replace function table_sequence_ids() returns table(table_uid text, record_id bigint) as $$
|
11
|
+
declare
|
12
|
+
name varchar(255);
|
13
|
+
tuple record;
|
14
|
+
begin
|
15
|
+
for tuple in
|
16
|
+
select tc.relnamespace::regnamespace::text || '.' || tc.relname as table_uid,
|
17
|
+
s.relnamespace::regnamespace::text || '.' || s.relname as sequence_uid
|
18
|
+
from pg_class tc
|
19
|
+
left join (
|
20
|
+
select d.refobjid,
|
21
|
+
sc.relname,
|
22
|
+
sc.relkind,
|
23
|
+
sc.relnamespace::regnamespace::text as relnamespace
|
24
|
+
from pg_depend d
|
25
|
+
join pg_class sc on sc.oid = d.objid
|
26
|
+
where coalesce(sc.relkind = 'S', true)
|
27
|
+
and coalesce(sc.relnamespace::regnamespace::text != 'postspec', true)
|
28
|
+
) s on s.refobjid = tc.oid
|
29
|
+
where tc.relkind = 'r'
|
30
|
+
and tc.relnamespace::regnamespace::text not like 'pg_%'
|
31
|
+
and tc.relnamespace::regnamespace::text not in ('information_schema', 'postspec', 'prick')
|
32
|
+
loop
|
33
|
+
if tuple.sequence_uid is null then
|
34
|
+
table_uid := tuple.table_uid;
|
35
|
+
record_id := null;
|
36
|
+
return next;
|
37
|
+
else
|
38
|
+
return query execute
|
39
|
+
'select ' || quote_literal(tuple.table_uid) || '::text as table_uid, ' ||
|
40
|
+
'case is_called when true then last_value else last_value - 1 end as last_value ' ||
|
41
|
+
'from ' || tuple.sequence_uid;
|
42
|
+
end if;
|
43
|
+
end loop;
|
44
|
+
return;
|
45
|
+
end
|
46
|
+
$$ language plpgsql;
|
47
|
+
|
48
|
+
create or replace function readonly_failure() returns trigger as $$
|
49
|
+
begin
|
50
|
+
raise 'Postspec: Can''t modify seed data in %', TG_TABLE_NAME::regclass::text;
|
51
|
+
return null;
|
52
|
+
end;
|
53
|
+
$$ language plpgsql immutable leakproof;
|
54
|
+
|
55
|
+
-- :call-seq:
|
56
|
+
-- register_insert(record_id)
|
57
|
+
--
|
58
|
+
create or replace function register_insert() returns trigger as $$
|
59
|
+
begin
|
60
|
+
insert into postspec.inserts (table_uid, record_id) values
|
61
|
+
(TG_TABLE_SCHEMA || '.' || TG_TABLE_NAME, new.id);
|
62
|
+
return new;
|
63
|
+
end;
|
64
|
+
$$ language plpgsql;
|
65
|
+
|
66
|
+
-- :call-seq:
|
67
|
+
-- register_update(record_id)
|
68
|
+
--
|
69
|
+
create or replace function register_update() returns trigger as $$
|
70
|
+
begin
|
71
|
+
insert into postspec.updates (table_uid, record_id) values
|
72
|
+
(TG_TABLE_SCHEMA || '.' || TG_TABLE_NAME, old.id);
|
73
|
+
return new;
|
74
|
+
end;
|
75
|
+
$$ language plpgsql;
|
76
|
+
|
77
|
+
-- :call-seq:
|
78
|
+
-- register_delete(record_id)
|
79
|
+
--
|
80
|
+
create or replace function register_delete() returns trigger as $$
|
81
|
+
begin
|
82
|
+
insert into postspec.deletes (table_uid, record_id) values
|
83
|
+
(TG_TABLE_SCHEMA || '.' || TG_TABLE_NAME, old.id);
|
84
|
+
-- ('postspec.' || TG_TABLE_NAME, old.id);
|
85
|
+
return old;
|
86
|
+
end;
|
87
|
+
$$ language plpgsql;
|
88
|
+
|
89
|
+
create table runs (
|
90
|
+
id integer generated by default as identity primary key,
|
91
|
+
mode text not null,
|
92
|
+
ready boolean not null default false,
|
93
|
+
clean boolean not null default false,
|
94
|
+
status boolean,
|
95
|
+
duration numeric generated always as (
|
96
|
+
round(extract(epoch from updated_at - created_at)::numeric, 3)
|
97
|
+
) stored,
|
98
|
+
created_at timestamp without time zone not null default (now() at time zone 'UTC'),
|
99
|
+
updated_at timestamp without time zone
|
100
|
+
);
|
101
|
+
|
102
|
+
create table seeds (
|
103
|
+
id integer generated by default as identity primary key,
|
104
|
+
table_uid text,
|
105
|
+
record_id integer
|
106
|
+
);
|
107
|
+
|
108
|
+
create table inserts (
|
109
|
+
id integer generated by default as identity primary key,
|
110
|
+
table_uid text,
|
111
|
+
record_id integer
|
112
|
+
);
|
113
|
+
|
114
|
+
create table updates (
|
115
|
+
id integer generated by default as identity primary key,
|
116
|
+
table_uid text,
|
117
|
+
record_id integer
|
118
|
+
);
|
119
|
+
|
120
|
+
create table deletes (
|
121
|
+
id integer generated by default as identity primary key,
|
122
|
+
table_uid text,
|
123
|
+
record_id integer
|
124
|
+
);
|
125
|
+
|
126
|
+
set search_path to public;
|
127
|
+
|
128
|
+
grant all on schema postspec to public;
|
129
|
+
grant all on postspec.runs to public;
|
130
|
+
grant all on postspec.seeds to public;
|
131
|
+
grant all on postspec.inserts to public;
|
132
|
+
grant all on postspec.updates to public;
|
133
|
+
grant all on postspec.deletes to public;
|
134
|
+
|
135
|
+
|
136
|
+
|
137
|
+
|