postspec 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 46c5d89777324cedb5b8468b0c33806b45dd908b6dd7ce839bd9d3290ad9d4a0
4
+ data.tar.gz: 927fb7974ae6a9f7063e0b9a90e94dab313860c2e367d1b20f73e6afef645193
5
+ SHA512:
6
+ metadata.gz: 7fb4a38a74f0af6c527605789d127243b3624cf7f7073981a69bef007087809093c0bb273f4859f80b95aa3228da061b23674ca0d012920607b1bcf7b9e8159f
7
+ data.tar.gz: 23df58f3ba1eb8a48af8bed88ae3fedf8e151c5cf453fa5b4c24f211faaec732480c66c5560ec3e95832baeef379a7c4621bec6636033dc93091152b70e7bcf2
data/.gitignore ADDED
@@ -0,0 +1,19 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
12
+
13
+ # Ignore auto-generated main file
14
+ /main
15
+
16
+ # Ignore Gemfile.lock. See https://stackoverflow.com/questions/4151495/should-gemfile-lock-be-included-in-gitignore
17
+ /Gemfile.lock
18
+
19
+ # Put your personal ignore files in /home/clr/.config/git/ignore
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ ruby-2.7.1
data/.travis.yml ADDED
@@ -0,0 +1,6 @@
1
+ ---
2
+ language: ruby
3
+ cache: bundler
4
+ rvm:
5
+ - 2.7.1
6
+ before_install: gem install bundler -v 2.1.4
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in postspec.gemspec
4
+ gemspec
5
+
data/README.md ADDED
@@ -0,0 +1,36 @@
1
+ # Postspec
2
+
3
+ Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/postspec`. To experiment with that code, run `bin/console` for an interactive prompt.
4
+
5
+ TODO: Delete this and the text above, and describe your gem
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'postspec'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle install
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install postspec
22
+
23
+ ## Usage
24
+
25
+ TODO: Write usage instructions here
26
+
27
+ ## Development
28
+
29
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
30
+
31
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
32
+
33
+ ## Contributing
34
+
35
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/postspec.
36
+
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/TODO ADDED
@@ -0,0 +1,6 @@
1
+ o Also use prick anchors
2
+ o Modify all table to be unlogged for better performance: alter table table_name set unlogged
3
+ o Doesn't detect changes to the database by the developer between runs of
4
+ postspec). Can be fixed by inspecting postspec.[change-tables]
5
+ o Doesn't remove change-triggers after runs (this may be a feature)
6
+ o Needs a clean-postspec option that removes the postspec schema and triggers
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "postspec"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/exe/postspec ADDED
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'pg_graph'
4
+ require 'shellopts'
5
+
6
+ SPEC = "
7
+ @ Demo
8
+
9
+ Demonstration of reading and writing a database. Has actually nothing to do
10
+ with postspec
11
+
12
+ -r,reflections=EFILE @ Reflections file
13
+
14
+ -- DATABASE"
15
+
16
+ def timeit(label = nil, &block)
17
+ t0 = Time.now
18
+ r = yield
19
+ dt = Time.now - t0
20
+ if dt < 1
21
+ puts "#{label}: #{(1000.0 * dt).round(1)}ms"
22
+ else
23
+ puts "#{label}: #{dt.round(1)}s"
24
+ end
25
+ r
26
+ end
27
+
28
+ opts, args = ShellOpts.process(SPEC, ARGV)
29
+
30
+ database = args.expect(1)
31
+
32
+ puts "Reading and writing the given database"
33
+ indent {
34
+ conn = PgConn.new(database)
35
+ meta = timeit("Meta") { PgMeta.new(conn) }
36
+ type = timeit("Type") { PgGraph::Type.new(meta, opts.reflections) }
37
+ data = timeit("Read data") { PgGraph::Data.new(type, conn) }
38
+ load_data = timeit("Write data") { data.write(conn) }
39
+ }
40
+
@@ -0,0 +1,38 @@
1
+ # Used to initialize postspec from spec/spec_helper.rb or spec/postspec_helper.rb
2
+ #
3
+ module Postspec
4
+ Config = Struct.new(:database, :mode, :reflections, :anchors, :fail_fast)
5
+
6
+ @database = nil
7
+ @mode = nil
8
+ @reflections = nil
9
+ @anchors = nil
10
+ @fail_fast = nil
11
+
12
+ DEFAULT_MODE = :empty
13
+ DEFAULT_FAIL_FAST = true
14
+
15
+ def self.database() @database end
16
+ def self.mode() @mode end
17
+ def self.reflections() @reflections end
18
+ def self.anchors() @anchors end
19
+ def self.fail_fast() @fail_fast end
20
+
21
+ def self.configure(&block)
22
+ config = Config.new(@database, DEFAULT_MODE, nil, nil, DEFAULT_FAIL_FAST)
23
+ yield(config)
24
+ @database = config.database
25
+ @mode = config.mode || DEFAULT_MODE
26
+ @reflections = config.reflections
27
+ @anchors = config.anchors
28
+ @fail_fast = config.fail_fast || DEFAULT_FAIL_FAST
29
+ self
30
+ end
31
+
32
+ def self.postspec()
33
+ !@database.nil? or raise Error, "Database unspecified"
34
+ @postspec ||= Postspec.new(
35
+ PgConn.new(@database), mode: mode, reflector: @reflections, anchors: @anchors, fail: @fail_fast)
36
+ end
37
+ end
38
+
@@ -0,0 +1,491 @@
1
+
2
+ module Postspec
3
+ class Environment
4
+ attr_reader :postspec
5
+ forward_to :postspec, :conn, :render
6
+
7
+ def uids() table_sequence_ids end # FIXME: What is this? And why not using @uids?
8
+
9
+ def initialize(postspec)
10
+ @postspec = postspec
11
+ end
12
+
13
+ def table_sequence_ids(all: false)
14
+ conn.map \
15
+ "select table_uid, record_id from postspec.table_sequence_ids()" +
16
+ (all ? "" : " where record_id is not null")
17
+ end
18
+
19
+ def table_seed_ids
20
+ conn.map "select table_uid, record_id from postspec.seeds"
21
+ end
22
+
23
+ def table_max_ids
24
+ result = table_sequence_ids(all: true)
25
+ postspec.type.tables.select(&:subtable?).each { |table|
26
+ result[table.uid] = result[table.supertable.uid] or raise "Oops"
27
+ }
28
+ result
29
+ end
30
+
31
+ # Returns map from table UID to record ID of the last record to be kept so
32
+ # that 'delete from table_uid where id > record_id' does the right thing.
33
+ # Note: This assumes that the change triggers has been installed beforehand
34
+ def dirty_tables
35
+ seed_ids = table_seed_ids
36
+ table_max_ids.map { |table_uid, record_id|
37
+ (seed_id = seed_ids[table_uid]) ? [table_uid, seed_id] : [table_uid, record_id]
38
+ }.compact.to_h
39
+ end
40
+
41
+ def create()
42
+ # puts "Environment#create"
43
+ conn.execute IO.read(Postspec::SHARE_DIR + "/postspec_schema.sql")
44
+ conn.execute render.change_triggers(:create)
45
+ end
46
+
47
+ def drop()
48
+ # puts "Environment#drop"
49
+ conn.execute "drop schema if exists postspec cascade"
50
+ end
51
+
52
+ def exist?()
53
+ conn.exist? "select 1 from pg_namespace where nspname = 'postspec'"
54
+ end
55
+
56
+ # Removes excess data and meta data, and resets sequences. It doens't change any triggers
57
+ def clean
58
+ # puts "Environment#clean"
59
+ @uids = {}
60
+ user_tables = dirty_tables
61
+ postspec_tables = CHANGE_TABLE_UIDS
62
+ sql = render.delete_tables(user_tables) +
63
+ render.delete_tables(postspec_tables)
64
+ conn.execute render.execution_unit(user_tables.keys, sql)
65
+ end
66
+
67
+ def setup(mode)
68
+ # puts "Environment#setup"
69
+ sql =
70
+ case mode
71
+ when :seed
72
+ @uids = table_max_ids
73
+ render.seed_triggers(:create, @uids)
74
+ when :empty
75
+ @uids = {}
76
+ []
77
+ else
78
+ raise ArgumentError
79
+ end
80
+ conn.execute sql
81
+ end
82
+
83
+ def teardown(mode)
84
+ # puts "Environment#teardown"
85
+ @uids = nil
86
+ tables, reset_sql = reset_data
87
+ sql =
88
+ case mode
89
+ when :seed; render.seed_triggers(:drop) + render.delete_tables(SEED_TABLE_UIDS)
90
+ when :empty; []
91
+ else
92
+ raise ArgumentError
93
+ end + reset_sql
94
+ conn.execute render.execution_unit(tables, sql)
95
+ end
96
+
97
+ def reset()
98
+ # puts "Environment#reset"
99
+ conn.execute render.execution_unit(*reset_data)
100
+ end
101
+
102
+ private
103
+ # Generate SQL to delete data using the postspec.inserts table
104
+ def reset_data
105
+ uids = conn.map "select table_uid, min(record_id) from postspec.inserts group by table_uid"
106
+ sql = render.delete_tables(uids) + render.delete_tables(CHANGE_TABLE_UIDS)
107
+ [uids.keys, sql]
108
+ end
109
+
110
+ CHANGE_TABLE_UIDS = %w(postspec.inserts postspec.updates postspec.deletes)
111
+ SEED_TABLE_UIDS = %w(postspec.seeds)
112
+ RUN_TABLE_UIDS = %w(postspec.runs)
113
+ TABLE_UIDS = CHANGE_TABLE_UIDS + SEED_TABLE_UIDS + RUN_TABLE_UIDS
114
+ end
115
+ end
116
+
117
+ __END__
118
+
119
+
120
+
121
+
122
+ class Environment
123
+ attr_reader :postspec
124
+ forward_to :postspec, :conn, :render
125
+
126
+ attr_reader :uids # UID to ID map. Only used by the seed environment
127
+
128
+ def user_tables() @tables ||= postspec.tables.map(&:uid) end
129
+
130
+ CHANGE_TABLE_UIDS = %w(postspec.inserts postspec.updates postspec.deletes)
131
+ SEED_TABLE_UIDS = %w(postspec.seeds)
132
+ RUN_TABLE_UIDS = %w(postspec.runs)
133
+ TABLE_UIDS = CHANGE_TABLE_UIDS + SEED_TABLE_UIDS + RUN_TABLE_UIDS
134
+
135
+ def initialize(postspec)
136
+ @postspec = postspec
137
+ @uids = nil
138
+ setup_schema if !exist_schema?
139
+ end
140
+
141
+
142
+
143
+ # setup(mode); teardown(mode) is a NOP
144
+
145
+ def setup(mode) self.send :"setup_#{mode}_environment" end
146
+ def teardown(mode) self.send :"teardown_#{mode}_environment" end
147
+ def reset(mode, *type) self.send :"reset_#{mode}_environment", *type.compact end
148
+
149
+ def setup_schema
150
+ conn.execute(IO.read(SHARE_DIR + "/postspec_schema.sql"))
151
+ end
152
+
153
+ def teardown_schema
154
+ conn.execute "drop schema if exists postspec cascade"
155
+ end
156
+
157
+ def reset_schema
158
+ teardown_seed_environment
159
+ teardown_empty_environment
160
+ end
161
+
162
+ def exist_schema?
163
+ conn.exist? "select 1 from pg_namespace where nspname = 'postspec'"
164
+ end
165
+
166
+ def setup_change_environment
167
+ conn.exec render.change_triggers(:create)
168
+ end
169
+
170
+ def teardown_change_environment
171
+ sql =
172
+ render.change_triggers(:drop) +
173
+ render.delete_tables(CHANGE_TABLE_UIDS)
174
+ conn.exec render.execution_unit(CHANGE_TABLE_UIDS, sql)
175
+ end
176
+
177
+ # Not used - should be delegated to seed or empty environment
178
+ # def reset_change_environment
179
+ # end
180
+
181
+
182
+
183
+ def setup_seed_environment()
184
+ @uids = conn.map("select name, id from postspec.sequence_ids()")
185
+ sql = render.seed_triggers(:create, uids)
186
+ conn.exec sql
187
+ end
188
+
189
+ def teardown_seed_environment()
190
+ @uids = nil
191
+ uids = conn.map "select table_uid, min(record_id) from postspec.inserts"
192
+ sql =
193
+ render.seed_triggers(:drop) +
194
+ render.delete_tables(uids) +
195
+ render.delete_tables(CHANGE_TABLE_UIDS) +
196
+ render.delete_tables(SEED_TABLE_UIDS)
197
+ conn.exec render.execution_unit(uids.keys, sql)
198
+ end
199
+
200
+ def reset_seed_environment
201
+ uids = conn.map "select table_uid, min(record_id) from postspec.inserts"
202
+ sql =
203
+ render.delete_tables(uids) +
204
+ render.delete_tables(CHANGE_TABLE_UIDS)
205
+ conn.exec render.execution_unit(uids.keys, sql)
206
+ end
207
+
208
+
209
+ def setup_empty_environment
210
+ @uids = {}
211
+ sql = render.delete_tables(user_tables + CHANGE_TABLE_UIDS)
212
+ conn.exec render.execution_unit(tables, sql)
213
+ end
214
+
215
+ def teardown_empty_environment
216
+ @uids = nil
217
+ end
218
+
219
+ def reset_empty_environment
220
+ # Because we know the initial environment was empty we only need to
221
+ # inspect the postspect.inserts tables to find records to delete
222
+ changed_tables = conn.values "select distinct table_uid from inserts"
223
+ sql = render.delete_tables(changed_tables + CHANGE_TABLE_UIDS)
224
+ conn.exec render.execution_unit(tables, sql)
225
+ end
226
+ end
227
+ end
228
+
229
+ __END__
230
+
231
+ end
232
+
233
+ def drop
234
+
235
+ class PostspecEnvironment
236
+ attr_reader :postspec
237
+ forward_to :postspec, :conn, :render
238
+
239
+ def initialize(postspec)
240
+ @postspec = postspec
241
+ self.ensure
242
+ end
243
+
244
+ def self.instance(postspec)
245
+ @instance ||= PostspecEnvironment.new(postspec)
246
+ end
247
+
248
+ def create
249
+ end
250
+
251
+ def drop
252
+ end
253
+
254
+ def reset
255
+ State.reset(postspec)
256
+ end
257
+
258
+ def ensure
259
+ if !postspec.meta.schemas.key?("postspec")
260
+ create
261
+ else
262
+ # meta knows about the postspec schema so we hide it
263
+ postspec.meta.schemas["postspec"].hidden = true
264
+ reset
265
+ end
266
+ end
267
+
268
+ private
269
+ @instance = nil
270
+ end
271
+
272
+ class Environment
273
+ attr_reader :postspec_environment
274
+ attr_reader :state
275
+
276
+ forward_to :postspec_environment, :postspec, :conn, :render
277
+
278
+ # :call-seq:
279
+ # ::new(postspec, state)
280
+ # ::new(postspec, mode)
281
+ def self.new(postspec, arg)
282
+ klass =
283
+ case mode
284
+ when :seed; SeedEnvironment
285
+ when :empty; EmptyEnvironment
286
+ else
287
+ KeepEnvironment
288
+ end
289
+ object = klass.allocate
290
+ state = arg.is_state? ? arg : State.create(postspec, arg)
291
+ object.send(:initialize, PostspecEnvironment.instance(postspec), state)
292
+ object
293
+ end
294
+
295
+ def terminate
296
+ state.write
297
+ end
298
+
299
+ def create() raise NotThis end
300
+ def drop() raise NotThis end
301
+ def reset() postspec_environment.reset end
302
+
303
+ protected
304
+ def initialize(postspec_environment, state)
305
+ @postspec_environment = postspec_environment
306
+ @state = state
307
+ end
308
+ end
309
+
310
+ class SeedEnvironment < Environment
311
+ def create
312
+ uids = @conn.map("select name, id from postspec.sequence_ids()", :name)
313
+ conn.exec render.seed_triggers(:create, uids)
314
+ conn.exec "delete from postspec.seeds"
315
+ end
316
+
317
+ def drop
318
+ conn.exec render.seed_triggers(:drop)
319
+ conn.exec "delete from postspec.seeds"
320
+ end
321
+
322
+ def reset # TODO: Merge with PgGraph::Data::SqlRender
323
+ end
324
+ end
325
+
326
+ class EmptyEnvironment < Environment
327
+ def create() end
328
+ def drop() end
329
+
330
+ def reset
331
+ super
332
+ conn.exec state.inserts.keys.map { |uid| "delete from #{uid}" }
333
+ end
334
+ end
335
+
336
+ class KeepEnvironment < Environment
337
+ end
338
+ end
339
+
340
+ __END__
341
+
342
+ class Environment
343
+ attr_reader :postspec
344
+ attr_reader :id
345
+ attr_reader :mode
346
+ attr_accessor :status
347
+ def duration() @duraction ||= Time.now - created_at end
348
+ attr_reader :created_at
349
+
350
+ forward_to :postspec, :conn
351
+
352
+ def initialize(postspec, id, mode, status, duration, created_at)
353
+ @postspec = postspec
354
+ @id = id
355
+ @mode = mode
356
+ @status = status
357
+ @duration = duration
358
+ @created_at = created_at
359
+ end
360
+
361
+ def update
362
+ sql = %(
363
+ update postspec.runs
364
+ set status = #{status},
365
+ duration = #{duration}
366
+ where id = #{id}
367
+ )
368
+ conn.exec(sql)
369
+ end
370
+
371
+ def self.create(conn, mode)
372
+ sql = %(
373
+ insert into postspec.runs (mode) values ('#{mode}') returning id, created_at
374
+ )
375
+ id, created_at = @conn.tuple(sql)
376
+ State.new(conn, id, mode, nil, nil, created_at)
377
+ end
378
+
379
+ def self.read_last(conn)
380
+ sql = %(
381
+ select id, mode, status, duration, created_at
382
+ from postspec.runs
383
+ order by desc id
384
+ limit 1
385
+ )
386
+ tuple = @conn.tuples(sql).first
387
+ tuple && State.new(conn, *tuple)
388
+ end
389
+ end
390
+
391
+ class SeedEnvironment < Environment
392
+ def initialize(postspec, id, mode, status, duration, created_at)
393
+ end
394
+ def create
395
+ super
396
+ conn.execute(render.readonly_triggers(:create, ids)
397
+ end
398
+
399
+ def update
400
+ end
401
+
402
+ def drop
403
+ conn.execute(render.readonly_triggers(:drop, ids)
404
+ end
405
+ end
406
+
407
+ class EmptyEnvironment < Environment
408
+ def create
409
+ super
410
+ conn.execute(render.delete_tables(postspec.tables.map(&:uid))
411
+ end
412
+
413
+ def update
414
+ end
415
+
416
+ def drop
417
+ end
418
+ end
419
+
420
+ class KeepEnvironment < Environment
421
+ end
422
+
423
+
424
+
425
+
426
+
427
+ class Environment
428
+ attr_reader :postspec
429
+ attr_reader :state
430
+ forward_to :postspec, :conn, :meta, :render
431
+
432
+ def initialize(postspec, mode)
433
+ @postspec = postspec
434
+ @state = State.create(postspec.conn, mode)
435
+
436
+ last_state
437
+
438
+ end
439
+
440
+ def create()
441
+ if meta.schemas.key?("postspec")
442
+ # meta knows about the postspec schema so we hide it
443
+ meta.schemas["postspec"].hidden = true
444
+ # @conn.execute render.delete_postspec_tables
445
+ else
446
+ # meta doesn't know about the postspec schema
447
+ conn.execute(IO.read(SHARE_DIR + "/postspec_schema.sql"))
448
+ end
449
+ conn.execute(render.change_triggers(:create))
450
+ end
451
+
452
+ def reset()
453
+ conn.execute(render.reset_postspec_tables)
454
+ end
455
+
456
+ def drop()
457
+ conn.execute(render.change_triggers(:drop))
458
+ conn.execute("drop schema postspec cascade")
459
+ end
460
+ end
461
+
462
+ class SeedEnvironment < Environment
463
+ def create
464
+ super
465
+ conn.execute(render.readonly_triggers(:create, ids)
466
+ end
467
+
468
+ def update
469
+ end
470
+
471
+ def drop
472
+ conn.execute(render.readonly_triggers(:drop, ids)
473
+ end
474
+ end
475
+
476
+ class EmptyEnvironment < Environment
477
+ def create
478
+ super
479
+ conn.execute(render.delete_tables(postspec.tables.map(&:uid))
480
+ end
481
+
482
+ def update
483
+ end
484
+
485
+ def drop
486
+ end
487
+ end
488
+
489
+ class KeepEnvironment < Environment
490
+ end
491
+ end