fixture_fox 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +20 -0
  3. data/.rspec +3 -0
  4. data/.ruby-version +1 -0
  5. data/.travis.yml +6 -0
  6. data/Gemfile +7 -0
  7. data/README.md +36 -0
  8. data/Rakefile +6 -0
  9. data/TODO +79 -0
  10. data/bin/console +14 -0
  11. data/bin/setup +8 -0
  12. data/doc/diagram.drawio +1 -0
  13. data/examples/1-1-subrecord.fox +15 -0
  14. data/examples/1-1-subrecord.sql +22 -0
  15. data/examples/N-M.fox +25 -0
  16. data/examples/N-M.sql +34 -0
  17. data/examples/anchors.sql +13 -0
  18. data/examples/anchors.yml +4 -0
  19. data/examples/anchors1.fox +4 -0
  20. data/examples/anchors2.fox +6 -0
  21. data/examples/array.fox +84 -0
  22. data/examples/array.sql +53 -0
  23. data/examples/base.fox +81 -0
  24. data/examples/base.sql +52 -0
  25. data/examples/empty.fox +8 -0
  26. data/examples/empty.sql +33 -0
  27. data/examples/include/schema-included.fox +23 -0
  28. data/examples/inherit.fox +26 -0
  29. data/examples/inherit.sql +28 -0
  30. data/examples/kind.fox +17 -0
  31. data/examples/kind.sql +31 -0
  32. data/examples/link.fox +35 -0
  33. data/examples/link.sql +32 -0
  34. data/examples/root.fox +22 -0
  35. data/examples/schema-fragment-1.fox +9 -0
  36. data/examples/schema-fragment-2.fox +21 -0
  37. data/examples/schema-include.fox +10 -0
  38. data/examples/schema-indent.fox +29 -0
  39. data/examples/schema.fox +29 -0
  40. data/examples/schema.sql +33 -0
  41. data/examples/types.fox +8 -0
  42. data/examples/types.sql +17 -0
  43. data/examples/views.fox +15 -0
  44. data/examples/views.sql +38 -0
  45. data/exe/fox +178 -0
  46. data/fixture_fox.gemspec +37 -0
  47. data/lib/fixture_fox/analyzer.rb +371 -0
  48. data/lib/fixture_fox/anchor.rb +93 -0
  49. data/lib/fixture_fox/ast.rb +176 -0
  50. data/lib/fixture_fox/error.rb +23 -0
  51. data/lib/fixture_fox/hash_parser.rb +111 -0
  52. data/lib/fixture_fox/idr.rb +62 -0
  53. data/lib/fixture_fox/line.rb +217 -0
  54. data/lib/fixture_fox/parser.rb +153 -0
  55. data/lib/fixture_fox/token.rb +173 -0
  56. data/lib/fixture_fox/tokenizer.rb +78 -0
  57. data/lib/fixture_fox/version.rb +3 -0
  58. data/lib/fixture_fox.rb +216 -0
  59. metadata +227 -0
@@ -0,0 +1,26 @@
1
+
2
+ accounts
3
+ - &alice
4
+ i: 1
5
+ user:
6
+ name: 'alice'
7
+ - &bob
8
+ i: 2
9
+ user:
10
+ name: 'bob'
11
+ - user:
12
+ name: 'cloe'
13
+
14
+ jobs
15
+ - name: 'bobjob' # her
16
+ - name: 'alicejob'
17
+ account:
18
+ - i: 3
19
+ user:
20
+ name: 'dan'
21
+
22
+ infos
23
+ - &bla_info
24
+ info: "Bla bla"
25
+ job: # Her
26
+ name: 'info job'
@@ -0,0 +1,28 @@
1
+
2
+ \connect postgres
3
+
4
+ drop database if exists fox;
5
+ create database fox;
6
+
7
+ \connect fox
8
+
9
+ create unlogged table accounts (
10
+ id integer generated by default as identity primary key,
11
+ i integer
12
+ );
13
+
14
+ create unlogged table users (
15
+ id integer primary key references accounts(id),
16
+ name text not null
17
+ );
18
+
19
+ create unlogged table jobs (
20
+ id integer primary key references accounts(id),
21
+ name text not null
22
+ );
23
+
24
+ create unlogged table infos (
25
+ id integer generated by default as identity primary key,
26
+ job_id integer references jobs(id),
27
+ info text not null
28
+ );
data/examples/kind.fox ADDED
@@ -0,0 +1,17 @@
1
+
2
+ user_groups
3
+ - name: all
4
+ - name: intern
5
+
6
+ user_roles
7
+ - name: sysadm
8
+ - name: user
9
+
10
+ users
11
+ - username: Alice
12
+ emails:
13
+ - email: "alice@restaurant.com"
14
+ user_group: all
15
+ user_role: user
16
+
17
+
data/examples/kind.sql ADDED
@@ -0,0 +1,31 @@
1
+
2
+ \connect postgres
3
+
4
+ drop database if exists fox;
5
+ create database fox;
6
+
7
+ \connect fox
8
+
9
+ create table user_groups (
10
+ id integer not null generated by default as identity primary key,
11
+ name varchar not null unique
12
+ );
13
+
14
+ create table user_roles (
15
+ id integer not null generated by default as identity primary key,
16
+ name varchar not null unique
17
+ );
18
+
19
+ create table users (
20
+ id integer not null generated by default as identity primary key,
21
+ username varchar not null,
22
+ user_role_kind varchar not null references user_roles(name),
23
+ user_group varchar not null references user_groups(name)
24
+ );
25
+
26
+ create table emails (
27
+ id integer not null generated by default as identity primary key,
28
+ user_id integer not null references users(id),
29
+ email varchar not null
30
+ );
31
+
data/examples/link.fox ADDED
@@ -0,0 +1,35 @@
1
+
2
+ #alice: Users
3
+ # name: Alice
4
+ #__END__
5
+
6
+ alice: Users
7
+ name: "Alice"
8
+ roles:
9
+ - &sysadm
10
+ name: Sysadm
11
+ parent_role: *user
12
+ picture:
13
+ file: "pretty.jpg"
14
+
15
+ roles
16
+ - &user
17
+ name: User
18
+ users:
19
+ - *bob
20
+ - &clara
21
+ name: Clara
22
+
23
+ bob: Users
24
+ name: Bob
25
+ picture:
26
+ file: "bob.jpg"
27
+
28
+ pictures
29
+ - user:
30
+ name: Dan
31
+ file: "dan.jpg"
32
+
33
+
34
+
35
+
data/examples/link.sql ADDED
@@ -0,0 +1,32 @@
1
+
2
+ \connect postgres
3
+
4
+ drop database if exists fox;
5
+ create database fox;
6
+
7
+ \connect fox
8
+
9
+ create table users (
10
+ id integer generated by default as identity primary key,
11
+ name varchar not null
12
+ );
13
+
14
+ create table pictures (
15
+ id integer generated by default as identity primary key,
16
+ user_id integer not null references users(id) unique,
17
+ file text not null
18
+ );
19
+
20
+ create table roles (
21
+ id integer generated by default as identity primary key,
22
+ parent_role_id integer references roles(id),
23
+ name varchar not null
24
+ );
25
+
26
+ create table user_roles (
27
+ id integer generated by default as identity primary key,
28
+ user_id integer not null references users(id),
29
+ role_id integer not null references roles(id)
30
+ );
31
+
32
+
data/examples/root.fox ADDED
@@ -0,0 +1,22 @@
1
+
2
+ # Not implemented
3
+
4
+ *alice.age: 42
5
+
6
+ *alice.roles:
7
+ - *sysadm
8
+ - *user
9
+ - &test
10
+ name: test
11
+
12
+ alice: Users
13
+ name: Alice
14
+ age: 20
15
+ roles:
16
+ - &admin
17
+ name: Administrator
18
+ - &sysadm
19
+ name: System administrator
20
+ - &user
21
+ name: User
22
+ - *test
@@ -0,0 +1,9 @@
1
+
2
+ @schema user_schema
3
+
4
+ role_schema.roles
5
+ - &sysadm_role
6
+ name: Sysadm
7
+ - &admin_role
8
+ name: Admin
9
+
@@ -0,0 +1,21 @@
1
+
2
+ posts
3
+ - &alices_first_post
4
+ title: "FIRST"
5
+
6
+ @schema user_schema
7
+
8
+ users
9
+ - name: Alice
10
+ roles:
11
+ - *sysadm_role
12
+ - &user_role
13
+ name: User
14
+ posts:
15
+ - *alices_first_post
16
+ - title: "SECOND"
17
+ - name: Bob
18
+ roles:
19
+ - *admin_role
20
+ - *user_role
21
+
@@ -0,0 +1,10 @@
1
+
2
+ @schema user_schema
3
+
4
+ role_schema.roles
5
+ - &sysadm_role
6
+ name: Sysadm
7
+ - &admin_role
8
+ name: Admin
9
+
10
+ @include include/schema-included.fox
@@ -0,0 +1,29 @@
1
+
2
+ @schema user_schema
3
+
4
+ role_schema.roles
5
+ - &sysadm_role
6
+ name: Sysadm
7
+ - &admin_role
8
+ name: Admin
9
+
10
+ public.posts
11
+ - &alices_first_post
12
+ title: "FIRST"
13
+
14
+ @schema user_schema
15
+
16
+ users
17
+ - name: Alice
18
+ roles:
19
+ - *sysadm_role
20
+ - &user_role
21
+ name: User
22
+ posts:
23
+ - *alices_first_post
24
+ - title: "SECOND"
25
+ - name: Bob
26
+ roles:
27
+ - *admin_role
28
+ - *user_role
29
+
@@ -0,0 +1,29 @@
1
+
2
+ @schema user_schema
3
+
4
+ role_schema.roles
5
+ - &sysadm_role
6
+ name: Sysadm
7
+ - &admin_role
8
+ name: Admin
9
+
10
+ public.posts
11
+ - &alices_first_post
12
+ title: "FIRST"
13
+
14
+ @schema user_schema
15
+
16
+ users
17
+ - name: Alice
18
+ roles:
19
+ - *sysadm_role
20
+ - &user_role
21
+ name: User
22
+ posts:
23
+ - *alices_first_post
24
+ - title: "SECOND"
25
+ - name: Bob
26
+ roles:
27
+ - *admin_role
28
+ - *user_role
29
+
@@ -0,0 +1,33 @@
1
+
2
+ \connect postgres
3
+
4
+ drop database if exists fox;
5
+ create database fox;
6
+
7
+ \connect fox
8
+
9
+ create schema user_schema;
10
+ create schema role_schema;
11
+
12
+ create table role_schema.roles (
13
+ id integer generated by default as identity primary key,
14
+ name text not null
15
+ );
16
+
17
+ create table user_schema.users (
18
+ id integer generated by default as identity primary key,
19
+ name text not null
20
+ );
21
+
22
+ create table public.user_roles (
23
+ id integer generated by default as identity primary key,
24
+ role_id integer not null references role_schema.roles(id),
25
+ user_id integer not null references user_schema.users(id)
26
+ );
27
+
28
+ create table public.posts (
29
+ id integer generated by default as identity primary key,
30
+ user_id integer not null references user_schema.users(id),
31
+ title text not null
32
+ );
33
+
@@ -0,0 +1,8 @@
1
+
2
+ Users
3
+ - &record
4
+ f_text: "string"
5
+ f_integer: 42
6
+ f_float: 4.2
7
+ f_boolean: true
8
+ f_timestamp: '2020-01-01'
@@ -0,0 +1,17 @@
1
+
2
+ \connect postgres
3
+
4
+ drop database if exists fox;
5
+ create database fox;
6
+
7
+ \connect fox
8
+
9
+ create table users (
10
+ id integer generated by default as identity primary key,
11
+ f_text varchar,
12
+ f_integer integer,
13
+ f_float float,
14
+ f_boolean boolean,
15
+ f_timestamp timestamp without time zone default (now() at time zone 'UTC')
16
+ );
17
+
@@ -0,0 +1,15 @@
1
+
2
+ Users
3
+ - &alice
4
+ name: Alice
5
+ orders:
6
+ - item: Bread
7
+ amount: 10
8
+ - item: Coffee
9
+ amount: 5
10
+ - &bob
11
+ name: Bob
12
+ orders:
13
+ - item: Tomato
14
+ amount: 2
15
+
@@ -0,0 +1,38 @@
1
+
2
+ \connect postgres
3
+
4
+ drop database if exists fox;
5
+ create database fox;
6
+
7
+ \connect fox
8
+
9
+ create table users (
10
+ id integer generated by default as identity primary key,
11
+ name text not null
12
+ );
13
+
14
+ create table orders (
15
+ id integer generated by default as identity primary key,
16
+ item text not null,
17
+ amount integer not null default 0
18
+ );
19
+
20
+ create table user_orders (
21
+ id integer generated by default as identity primary key,
22
+ user_id integer not null references users(id),
23
+ order_id integer not null references orders(id)
24
+ );
25
+
26
+ create materialized view order_sums as
27
+ select u.id,
28
+ u.name,
29
+ sum(o.amount)
30
+ from users u
31
+ join user_orders uo on uo.user_id = u.id
32
+ join orders o on o.id = uo.order_id
33
+ group by
34
+ u.id,
35
+ u.name
36
+ ;
37
+
38
+
data/exe/fox ADDED
@@ -0,0 +1,178 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $t0 = Time.now
4
+
5
+ require 'fileutils'
6
+
7
+ require 'fixture_fox.rb'
8
+ require 'shellopts'
9
+ require "pg_graph/timer.rb"
10
+
11
+ include ShellOpts
12
+
13
+ SPEC = %(
14
+ @ Generate test data for Postgres databases
15
+
16
+ -r,reflections=EFILE
17
+ Reflections YML file
18
+
19
+ -s,state=EFILE?
20
+ State file. fox(1) reads ids and anchors from this file. Default is
21
+ fox.yml
22
+
23
+ -w,write=FILE? @ Write new state file
24
+ Write the new state to FILE or back to the input state file if FILE is
25
+ not given
26
+
27
+ -d,delete=KIND:touched,recursive,all,none
28
+ Controls the delete statements. 'touched' deletes only from tables that
29
+ are specified in the fox file, 'recursive' deletes from touched tables
30
+ and their dependent tables, and 'all' (the default) deletes from all
31
+ tables in the database. If 'none' is given, no delete statements are
32
+ emitted. Records included in the state file are never deleted
33
+
34
+ -f,format=FORMAT:sql,exec,psql,yaml
35
+ Controls the format of the 'data' output type. FORMAT can be one of
36
+ 'sql', 'exec', 'psql' (the default), or 'yaml'. The format option can
37
+ also be used together with the --dump option for some of the dump types
38
+
39
+ -x,exec
40
+ Execute the generated SQL instead of printing it on standard output
41
+
42
+ -t,time
43
+ Emit timings for sub-processes
44
+
45
+ --dump=KIND:tokens,meta,type,ast,idr,state,anchors,ids
46
+ Dump the given data on standard output. KIND can be one of 'tokens',
47
+ 'meta', 'type', 'ast', 'idr', 'state', 'anchors', or 'ids'
48
+
49
+ -- DATABASE [FOX-FILE...]
50
+ )
51
+
52
+ DEFAULT_STATE_FILE = "fox-state.fox"
53
+
54
+ class Break < RuntimeError; end
55
+
56
+ begin
57
+ opts, args = ShellOpts.process(SPEC, ARGV, exception: true)
58
+
59
+ timing = opts.time?
60
+ timer = Timer:: Timer.new
61
+
62
+ opts.format ||= 'psql'
63
+
64
+ if opts.state?
65
+ opts.state = File.expand_path(opts.state || DEFAULT_STATE_FILE)
66
+ FileUtils.touch(opts.state) if !File.exist?(opts.state)
67
+ end
68
+
69
+ db = args.extract(1)
70
+ files = args.empty? ? %w(/dev/stdin) : args
71
+ files.each { |path| File.readable?(path) or raise ShellOpts::Error.new(nil), "Can't read '#{path}'" }
72
+
73
+ tg = timer.group("initialization")
74
+
75
+ # Connect to postgres
76
+ begin
77
+ conn = tg.time("connect") { PgConn.new(db) }
78
+ rescue PG::ConnectionBad
79
+ raise ShellOpts::Error.new(nil), "Can't connect to database '#{db}'"
80
+ end
81
+
82
+ # Load meta object
83
+ meta = tg.time("meta") { PgMeta.new(conn) }
84
+ if opts.dump == "meta"
85
+ if opts.format == "yaml"
86
+ puts meta.to_yaml
87
+ else
88
+ meta.dump
89
+ end
90
+ exit
91
+ end
92
+
93
+ # Load reflections
94
+ if opts.reflections?
95
+ reflector = tg.time("reflections") { PgGraph::Reflector.load_yaml(YAML.load(File.read(opts.reflections))) }
96
+ else
97
+ reflector = tg.time("reflections") { PgGraph::Reflector.new }
98
+ end
99
+
100
+ # Dump types
101
+ type = tg.time("type") { PgGraph::Type.new(meta, reflector) }
102
+ if opts.dump == "type"
103
+ type.dump
104
+ exit
105
+ end
106
+
107
+ # Load state
108
+ tg = timer.group("compiling")
109
+ ids, anchors, fox = nil, nil, nil # Enter into namespace
110
+ tg.time("state") {
111
+ ids, anchors = opts.state? ? FixtureFox::Fox.read_state(type, opts.state) : [nil, nil]
112
+ }
113
+
114
+ # Create fox object
115
+ tg.time("initialize") {
116
+ fox = FixtureFox::Fox.new(type, ids: ids, anchors: anchors)
117
+ }
118
+ for file in files
119
+ source, lines = tg.time("tokenize") { fox.tokenize(file) }
120
+ tg.time("parse") { fox.parse(source, lines) }
121
+ end
122
+
123
+ if opts.dump == "tokens"
124
+ fox.lines.each { |l| puts l.to_s(long: true) }
125
+ exit
126
+ end
127
+
128
+ # Analyze
129
+ tga = tg.group("analyze")
130
+ tga.time("assign types") { fox.assign_types }
131
+ tga.time("check types") { fox.check_types }
132
+ tg.time("generate") { fox.generate }
133
+
134
+ if opts.dump?
135
+ case opts.dump || "data"
136
+ # when "tokens"; # Handled above
137
+ # when "meta"; # Handled above
138
+ # when "type"; # Handled above
139
+ when "ast"; fox.ast.dump
140
+ when "state"; FixtureFox::Fox.write_state(ids, anchors, "/dev/stdout")
141
+ when "idr"; fox.idr.dump
142
+ when "anchors"; FixtureFox::Fox.write_anchors(fox.anchors, "/dev/stdout")
143
+ when "ids"; FixtureFox::Fox.write_ids(fox.ids, "/dev/stdout")
144
+ end
145
+ exit
146
+ end
147
+
148
+ if opts.exec?
149
+ tg.time("write") { fox.data.write(conn, ids: ids) }
150
+ else
151
+ tg.time("emit") {
152
+ # puts "Format: #{opts.format.inspect}"
153
+ # exit
154
+ if opts.format == "yaml"
155
+ puts fox.to_yaml
156
+ else
157
+ puts fox.to_sql(format: opts.format&.to_sym || :psql, ids: ids, delete: opts.delete&.to_sym || :all)
158
+ end
159
+ }
160
+ end
161
+
162
+ # Save state
163
+ if opts.write?
164
+ FixtureFox::Fox.write_state(fox.ids, fox.anchors, opts.write || opts.state)
165
+ end
166
+
167
+ rescue FixtureFox::Error => ex
168
+ $stderr.puts ex.backtrace.join("\n")
169
+ $stderr.puts
170
+ $stderr.puts ex.error_message
171
+ $stderr.puts
172
+ $error = true
173
+ exit 1
174
+
175
+ ensure
176
+ timer.dump($stderr) if timing && !$error
177
+ end
178
+
@@ -0,0 +1,37 @@
1
+ require_relative 'lib/fixture_fox/version'
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = "fixture_fox"
5
+ spec.version = FixtureFox::VERSION
6
+ spec.authors = ["Claus Rasmussen"]
7
+ spec.email = ["claus.l.rasmussen@gmail.com"]
8
+
9
+ spec.summary = %q{fixture_fox gem}
10
+ spec.description = %q{fixture_fox gem}
11
+ spec.homepage = "http://www.nowhere.com/"
12
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
13
+
14
+
15
+ spec.metadata["homepage_uri"] = spec.homepage
16
+
17
+ # Specify which files should be added to the gem when it is released.
18
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
19
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
20
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
21
+ end
22
+ spec.bindir = "exe"
23
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
24
+ spec.require_paths = ["lib"]
25
+
26
+ spec.add_dependency "pg"
27
+ spec.add_dependency "dry-inflector"
28
+ spec.add_dependency "indented_io"
29
+ spec.add_dependency "boolean"
30
+ spec.add_dependency "developer_exceptions"
31
+ spec.add_dependency "constrain"
32
+
33
+ spec.add_dependency "shellopts", "2.0.6"
34
+ spec.add_dependency "pg_graph", "0.1.0"
35
+
36
+ spec.add_development_dependency "simplecov"
37
+ end