prick 0.29.2 → 0.30.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/TODO +2 -0
  3. data/exe/prick +290 -518
  4. data/lib/prick/builder/batch.rb +1 -4
  5. data/lib/prick/builder/builder.rb +15 -9
  6. data/lib/prick/builder/node.rb +3 -2
  7. data/lib/prick/builder/node_pool.rb +3 -1
  8. data/lib/prick/builder/parser.rb +2 -1
  9. data/lib/prick/constants.rb +12 -12
  10. data/lib/prick/diff.rb +2 -2
  11. data/lib/prick/environment.rb +23 -10
  12. data/lib/prick/local/command.rb +8 -2
  13. data/lib/prick/local/fmt.rb +56 -0
  14. data/lib/prick/prick_version.rb +1 -1
  15. data/lib/prick/share/init/{.gitignore → dot.gitignore} +1 -1
  16. data/lib/prick/share/init/prick.environment.yml +16 -0
  17. data/lib/prick/share/init/prick.yml +3 -3
  18. data/lib/prick/share/init/schema/prick/build.yml +11 -2
  19. data/lib/prick/share/init/schema/prick/tables.sql +30 -9
  20. data/lib/prick/share/init/schema/prick/views.sql +6 -0
  21. data/lib/prick/state.rb +297 -95
  22. data/lib/prick/subcommand/prick-build.rb +26 -20
  23. data/lib/prick/subcommand/prick-clean.rb +5 -5
  24. data/lib/prick/subcommand/prick-create.rb +41 -6
  25. data/lib/prick/subcommand/prick-drop.rb +57 -14
  26. data/lib/prick/subcommand/prick-fox.rb +1 -1
  27. data/lib/prick/subcommand/prick-init.rb +25 -18
  28. data/lib/prick/subcommand/prick-list.rb +99 -0
  29. data/lib/prick/subcommand/prick-make.rb +8 -8
  30. data/lib/prick/subcommand/prick-migrate.rb +8 -7
  31. data/lib/prick/subcommand/prick-release.rb +4 -2
  32. data/lib/prick/subcommand/prick-set.rb +52 -0
  33. data/lib/prick/subcommand/prick-setup.rb +3 -13
  34. data/lib/prick/subcommand/prick-teardown.rb +13 -9
  35. data/lib/prick/subcommand/subcommand.rb +12 -0
  36. data/lib/prick/version.rb +1 -1
  37. data/lib/prick.rb +54 -15
  38. metadata +9 -5
  39. data/lib/prick/share/init/schema/prick/data.sql +0 -6
  40. data/prick.environments.yml +0 -14
data/lib/prick/state.rb CHANGED
@@ -2,76 +2,172 @@
2
2
  require 'yaml'
3
3
 
4
4
  module Prick
5
+ # There is only one State object: Prick.state
6
+ #
7
+ # FIXME Not how it is done
8
+ # The prick.state file contains the current database, username, and
9
+ # environment. It is controlled by prick(1) but you can set its values by
10
+ # using 'prick database=asdf
11
+ #
5
12
  class State
6
- # State file
7
- def file() PRICK_PROJECT_PATH end
13
+ # Prick project dir. This is not a constant because prick can change
14
+ # directory through the -C option or the 'init' command
15
+ def prick_dir() @prick_dir ||= Dir.getwd end
8
16
 
9
- # Schema data file
17
+ # Project file. Default 'prick.yml'
18
+ attr_reader :project_file
19
+
20
+ # Environment file. Default 'prick.environment'. Note that the file can be
21
+ # absent if the project doesn't use environments
22
+ attr_reader :environment_file
23
+
24
+ # State file. Default '.prick-state.yml'
25
+ attr_reader :state_file
26
+
27
+ # Schema data file. FIXME What is this?
10
28
  def schema_file() SCHEMA_VERSION_PATH end
11
29
 
30
+ # True if the configuration files has been loaded
31
+ def project_loaded? = @project_loaded
32
+ def state_loaded? = @state_loaded
33
+ def environment_loaded? = @environment_loaded
34
+
12
35
  # Used as an identifier and the default database and username
13
36
  attr_accessor :name
14
37
 
15
38
  # Capitalized name of project
16
39
  attr_accessor :title
17
40
 
18
- # Version in prick.yml. Can be nil
41
+ # Project version in prick.yml. Can be nil FIXME Can it?
19
42
  attr_accessor :version
20
43
 
21
- # Version in schema/prick/data.sql. Retrieved dynamically. TODO
22
- attr_reader :schema_version
23
-
24
- # Version from prick.versions.version. Retrieved dynamically. TODO FIXME: Where is conn?
25
- attr_reader :database_version
26
-
27
- # Version of prick(1)
44
+ # Version of prick in prick.yml. Note that this can be different than the current
45
+ # version of prick
28
46
  attr_accessor :prick_version
29
47
 
30
- # Name of database
48
+ # Database name. nil if state file is absent
31
49
  attr_accessor :database
32
50
 
33
- # Name of database user
51
+ # Database owner name. Typically the same as the database name. nil if
52
+ # database is absent
34
53
  attr_accessor :username
35
54
 
36
- # Run-time environment name
55
+ # Environment name. If not set in the state file, the enviroment is read
56
+ # from the database when the first connection is established by the
57
+ # #connection method. Use 'Environment[environment]' to get the
58
+ # corresponding Environment object
37
59
  def environment() @environment end
38
60
  def environment=(env)
39
- constrain env, String
40
- Environment.environment?(env) or raise "Illegal environment: '#{env}'"
61
+ constrain env, String, nil
62
+ env.nil? || Environment.environment?(env) or raise "Illegal environment: '#{env}'"
41
63
  @environment = env
42
64
  end
43
65
 
44
- # Prick project dir. This is not a constant because exe/prick can change
45
- # directory (FIXME it can?)
46
- def prick_dir
47
- @prick_dir ||= Dir.getwd
66
+ # Project version from PRICK.VERSIONS. Initialized by #connection
67
+ attr_accessor :database_version
68
+
69
+ # Prick version from PRICK.VERSIONS. Initialized by #connection
70
+ attr_accessor :database_prick_version
71
+
72
+ # Environment from PRICK.VERSIONS. Initialized by #connection
73
+ attr_accessor :database_environment
74
+
75
+ # Git branch. Lazy-evaluated
76
+ def branch() @branch ||= Git.branch.current end
77
+
78
+ # Git revision (commit ID). Lazy-evaluated
79
+ def rev(kind: :long)
80
+ case kind
81
+ when :short; @rev_short ||= rev()[0...8]
82
+ when :long; @rev_long ||= Git.id
83
+ end
84
+ end
85
+
86
+ # True if the git repository is clean (not modified). Lazy-evaluated
87
+ def clean?()
88
+ return @clean if defined?(@clean)
89
+ @clean = Git.clean?
90
+ end
91
+
92
+ def initialize(project_file, environment_file, state_file)
93
+ @project_file, @environment_file, @state_file = project_file, environment_file, state_file
94
+ @project_loaded = @state_loaded = @environment_loaded = false
95
+
96
+ if @project_file && File.exist?(@project_file)
97
+ load_project_file
98
+ load_state_file if @state_file && File.exist?(@state_file)
99
+ end
100
+
101
+ # FIXME The environment file should be loaded on-demand but it is hard to
102
+ # do when the environments are accessed through a class-interface
103
+ load_environment_file if @environment_file && File.exist?(@environment_file)
104
+ end
105
+
106
+ # Project user (owner) connection. Memoized to connect only once.
107
+ # TODO Rename. Also rename self.connection
108
+ def connection(database: nil, username: nil, environment: nil, &block)
109
+ database ||= self.database
110
+ username ||= self.username
111
+ environment ||= self.environment
112
+ !database.nil? or Prick.error "Can't connect to Postgres - no database specified"
113
+ # exist_database_environment? or Prick.error "Database '#{database}' is not initialized"
114
+
115
+ if @connection.nil?
116
+ @connection = PgConn.new(database, username)
117
+
118
+ # Set database_version/environment/prick members
119
+ load_database_environment
120
+
121
+ # Set environment if undefined and not overridden by :environment
122
+ self.environment =
123
+ environment ||
124
+ Prick.state.environment ||
125
+ Environment.environment?(database_environment) && database_environment ||
126
+ DEFAULT_ENVIRONMENT
127
+ end
128
+ if block_given?
129
+ yield @connection
130
+ else
131
+ @connection
132
+ end
133
+ end
134
+
135
+ # Superuser connection. This is a connection to Postgres using the current
136
+ # user's credentials. It is assumed that the current user has a postgres
137
+ # superuser account with the same name as the user's. Memoized to connect
138
+ # only once
139
+ def self.connection(&block)
140
+ @@connection ||= PgConn.new("postgres")
141
+ if block_given?
142
+ yield @@connection
143
+ else
144
+ @@connection
145
+ end
48
146
  end
49
147
 
50
- # Prick executable search_path
148
+ # Short-hand for #connection
149
+ alias_method :conn, :connection
150
+
151
+ # Prick executable search_path. This includes the bin and libexec directories
51
152
  def executable_search_path
52
153
  @executable_search_path ||= "#{ENV['PATH']}:#{prick_dir}/#{BIN_DIR}:#{prick_dir}/#{LIBEXEC_DIR}"
53
154
  end
54
155
 
55
156
  # Create a bash(1) environment (Hash). It is used for in-prick expansion of
56
- # variables and also injected into the enviroment of subprocesses
57
- def bash_environment(scope = :global)
58
- hash = {
59
- "DATABASE" => Prick.state.database, # FIXME: Yt
60
- "USERNAME" => Prick.state.username, # FIXME: Yt
61
- "ENVIRONMENT" => Prick.state.environment.to_s, # FIXME: Yt
62
- "PRICK_NAME" => Prick.state.name,
63
- "PRICK_TITLE" => Prick.state.title,
64
- "PRICK_VERSION" => Prick.state.version,
65
- "PRICK_DATABASE" => Prick.state.database,
66
- "PRICK_USERNAME" => Prick.state.username,
67
- "PRICK_ENVIRONMENT" => Prick.state.environment.to_s,
68
- }
69
- case scope
70
- when :local; # nop
71
- hash.merge Environment[Prick.state.environment].bash_env
72
- when :global;
73
- ENV.to_h.merge(hash).merge({
74
- "PATH" => Prick.state.executable_search_path,
157
+ # variables and is also injected into the enviroment of subprocesses
158
+ #
159
+ # FIXME: Problems with BUNDLE_* variables FIXME Still a problem?
160
+ #
161
+ # TODO: Explain handling of PRICK_<STANDARD-DIRECTORY>
162
+ def bash_environment(all: true)
163
+ @bash_environment ||= begin
164
+ hash = {
165
+ "PATH" => Prick.state.executable_search_path
166
+ }
167
+
168
+ if all
169
+ hash.merge!({
170
+ "PRICK_DIR" => Prick.state.prick_dir,
75
171
  "PRICK_SCHEMADIR" => File.join(Prick.state.prick_dir, SCHEMA_DIR),
76
172
  "PRICK_BINDIR" => File.join(Prick.state.prick_dir, BIN_DIR),
77
173
  "PRICK_LIBEXECDIR" => File.join(Prick.state.prick_dir, LIBEXEC_DIR),
@@ -80,52 +176,90 @@ module Prick
80
176
  "PRICK_SPOOLDIR" => File.join(Prick.state.prick_dir, SPOOL_DIR),
81
177
  "PRICK_TMPDIR" => File.join(Prick.state.prick_dir, TMP_DIR),
82
178
  "PRICK_CLONEDIR" => File.join(Prick.state.prick_dir, CLONE_DIR),
83
- "PRICK_SPECDIR" => File.join(Prick.state.prick_dir, BIN_DIR)
84
- }).merge(Environment[Prick.state.environment].bash_env)
179
+ "PRICK_SPECDIR" => File.join(Prick.state.prick_dir, BIN_DIR),
180
+ })
181
+ end
182
+
183
+ hash.merge!({
184
+ "DATABASE" => Prick.state.database, # FIXME: Yt
185
+ "USERNAME" => Prick.state.username, # FIXME: Yt
186
+ "ENVIRONMENT" => Prick.state.environment.to_s, # FIXME: Yt
187
+ "PRICK_NAME" => Prick.state.name,
188
+ "PRICK_TITLE" => Prick.state.title,
189
+ "PRICK_VERSION" => Prick.state.version,
190
+ "PRICK_DATABASE" => Prick.state.database,
191
+ "PRICK_USERNAME" => Prick.state.username,
192
+ "PRICK_ENVIRONMENT" => Prick.state.environment&.to_s, # may be the empty string
193
+ })
194
+
195
+ # PRICK_ENVIRONMENT_* variables
196
+ hash.merge! (Prick.state.environment.nil? ? {} : Environment[Prick.state.environment].bash_env)
197
+ end
198
+ end
199
+
200
+ # @scope can be :global (variables are exported), :local (variables are
201
+ # declared local), or nil (variables are global but not exported)
202
+ def bash_source(vars = nil, scope: nil)
203
+ case scope
204
+ when :global; prefix="export "
205
+ when :local; prefix="local "
206
+ when nil; prefix=""
85
207
  else
86
- raise ArgumentError
208
+ raise ArgumentError, "Illegal value for scope: #{scope.inspect}"
87
209
  end
210
+
211
+ (vars || bash_environment.keys).map { |var|
212
+ val = bash_environment[var]
213
+ "#{prefix}#{var}=\"#{Array(val).join(' ')}\"\n"
214
+ }.join
88
215
  end
89
216
 
90
- def bash_source
91
- bash_environment(:local).map { |var,val| "export #{var}=\"#{Array(val).join(' ')}\"\n" }.join
217
+ # It is an error if the project file exists.
218
+ def save_project
219
+ !File.exists?(project_file) or Prick.error "Won't overwrite '#{project_file}'"
220
+ hash = { name: name, title: title, version: version.to_s, prick: prick_version.to_s }
221
+ save_yaml(project_file, hash)
92
222
  end
93
223
 
94
- def self.load(project_file, context_file)
95
- state = State.new
96
- state.send(:parse_project_file, project_file || PRICK_PROJECT_PATH)
97
- state.send(:parse_context_file, context_file || PRICK_CONTEXT_PATH)
98
- state
224
+ # FIXME: Ugly
225
+ def save_version
226
+ system("sed -i 's/^version:.*/version: #{version.to_s} #{project_file}/'")
99
227
  end
100
228
 
101
- def save
102
- raise NotImplementedError
103
- h = {
104
- "name" => name,
105
- "title" => title,
106
- "version" => version.to_s,
107
- "prick" => prick_version.to_s
108
- }
109
- File.write(PRICK_PROJECT_PATH, h.to_yaml)
229
+ # Used by 'prick setup'
230
+ def save_state(database = nil, username = nil, environment = nil)
231
+ database ||= self.database or raise ArgumentError
232
+ username ||= self.username or raise ArgumentError
233
+ environment ||= self.environment
234
+ save_yaml(state_file, database: database, username: username, environment: environment)
235
+ end
110
236
 
111
- h = {
112
- "environment" => environment.to_s,
113
- "database" => database,
114
- "username" => username
115
- }
116
- File.write(PRICK_CONTEXT_PATH, h.to_yaml)
117
-
118
- if version
119
- File.write(SCHEMA_VERSION_PATH,
120
- <<~EOS
121
- --
122
- -- This file is auto-generated by prick(1). Please don't touch
123
- --
124
- delete from prick.versions;
125
- insert into prick.versions (fork, major, minor, patch, pre, feature, version)
126
- values (null, #{version.major}, #{version.minor}, #{version.patch}, null, null, '#{version}');
127
- EOS
128
- )
237
+ # Save build-start information to PRICK.BUILDS. It is a nop if PRICK.BUILDS
238
+ # doesn't exist, this happens on first build in a completely empty database
239
+ def save_build_begin
240
+ @build_id = nil # Used by save_build_end
241
+ if conn.schema.exist_table?("prick", "builds")
242
+ @build_id = insert_record(
243
+ "prick.builds",
244
+ name: name,
245
+ version: version.to_s, prick: prick_version,
246
+ branch: branch, rev: rev(:short), clean: clean?,
247
+ environment: environment)
248
+ end
249
+ end
250
+
251
+ def save_build_end(success, duration)
252
+ dt = Time.now - TIME
253
+ if @build_id
254
+ update_record("prick.builds", @build_id, success: success, duration: duration, prick_duration: dt)
255
+ else
256
+ insert_record(
257
+ "prick.builds",
258
+ name: name,
259
+ version: version.to_s, prick: prick_version,
260
+ branch: branch, rev: rev(:short), clean: clean?,
261
+ environment: environment,
262
+ success: success, duration: duration, prick_duration: dt)
129
263
  end
130
264
  end
131
265
 
@@ -133,8 +267,8 @@ module Prick
133
267
  puts "State"
134
268
  indent {
135
269
  for method in [
136
- :name, :title, :prick_version, :project_version, :schema_version,
137
- :database_version, :database, :username]
270
+ :name, :title, :prick_version, :project_version,
271
+ :database_version, :database_environment, :database, :username]
138
272
  puts "#{method}: #{self.send method}"
139
273
  end
140
274
  puts "environments:"
@@ -143,36 +277,104 @@ module Prick
143
277
  end
144
278
 
145
279
  private
146
- def load_yaml(file, expected_keys, optional_keys)
280
+ # YAML helper methods
281
+
282
+ def read_yaml(file)
147
283
  begin
148
- hash = YAML.load(File.read file)
284
+ YAML.load(File.read file)
149
285
  rescue Errno::ENOENT
150
- raise Prick::Error, "Can't read #{file}"
286
+ Prick.error "Can't read #{file}"
151
287
  end
152
- for key in expected_keys
153
- !hash[key].to_s.empty? or raise Prick::Error, "Can't find '#{key}' in #{file}"
288
+ end
289
+
290
+ def load_yaml(file, mandatory_keys, optional_keys = [])
291
+ mandatory_keys = mandatory_keys.map(&:to_s)
292
+ optional_keys = optional_keys.map(&:to_s)
293
+ hash = read_yaml(file)
294
+ for key in mandatory_keys
295
+ !hash[key].to_s.empty? or Prick.error "Can't find '#{key}' in #{file}"
154
296
  end
155
- (unknown = (hash.keys - expected_keys - optional_keys).first) and
156
- Prick::Error "Illegal key '#{unknown}' in #{file}"
297
+ (unknown = (hash.keys - mandatory_keys - optional_keys).first) and
298
+ Prick.error "Illegal key '#{unknown}' in #{file}"
157
299
  hash
158
300
  end
159
301
 
160
- def parse_context_file(context_file)
161
- hash = load_yaml(context_file, %w(environment database username), [])
162
- @database = hash["database"]
163
- @username = hash["username"]
164
- @environment = hash["environment"]
302
+ def save_yaml(file, hash)
303
+ self.class.save_yaml(file, hash)
304
+ end
305
+
306
+ def self.save_yaml(file, hash)
307
+ IO.write(file, hash.map { |k,v| [k.to_s, v] }.to_h.to_yaml)
308
+ end
309
+
310
+ # Database helper methods. TODO: Make generally available
311
+
312
+ # FIXME FIXME FIXME Yt. Replace with PgConn methods
313
+ def select_record(table, id, columns = [])
314
+ sql_columns = columns.join(", ")
315
+ sql_id_cond = (id.nil? ? "true" : "id = #{id}")
316
+ connection.tuple "select #{sql_columns} from #{table} where #{sql_id_cond}"
165
317
  end
166
318
 
167
- def parse_project_file(prick_file)
168
- hash = load_yaml(prick_file, %w(name title), %w(version prick environments))
319
+ def select_record?(table, id, columns = [])
320
+ sql_columns = columns.join(", ")
321
+ sql_id_cond = (id.nil? ? "true" : "id = #{id}")
322
+ connection.tuple? "select #{sql_columns} from #{table} where #{sql_id_cond}"
323
+ end
324
+
325
+ def insert_record(table, attrs = {})
326
+ sql_fields = attrs.keys.join(", ")
327
+ sql_values = attrs.values.map { conn.quote_literal(_1) }.join(", ")
328
+ conn.value "insert into #{table} (#{sql_fields}) values (#{sql_values}) returning id"
329
+ end
330
+
331
+ def update_record(table, id, attrs = {})
332
+ sql_assign = attrs.map { |k,v| "#{conn.quote_identifier(k)} = #{conn.quote_literal(v)}" }.join(", ")
333
+ sql_id_cond = (id.nil? ? "true" : "id = #{id}")
334
+ conn.exec "update #{table} set #{sql_assign} where #{sql_id_cond}"
335
+ end
336
+
337
+ # State helper methods
338
+
339
+ def load_project_file
340
+ hash = load_yaml(project_file, %w(name title), %w(version prick))
169
341
  @name = hash["name"]
170
342
  @title = hash["title"]
171
343
  @version = hash["version"] && PrickVersion.new(hash["version"])
172
344
  @prick_version = hash["prick"] && PrickVersion.new(hash["prick"])
173
- Environment.load_environments(hash["environments"] || {})
345
+ @project_loaded = true
346
+ end
347
+
348
+ def load_environment_file
349
+ hash = environment_file ? read_yaml(environment_file) : {}
350
+ Environment.load_environments(hash)
351
+ @environment_loaded = true
352
+ end
353
+
354
+ def load_state_file
355
+ hash = load_yaml(state_file, %w(database username), %w(environment))
356
+ @database = hash["database"]
357
+ @username = hash["username"]
358
+ @environment = hash["environment"]
359
+ @state_loaded = true
360
+ end
361
+
362
+ # Database helper methods
363
+
364
+ # Return true if the database environment exists
365
+ def exist_database_environment?
366
+ connection.schema.exist_relation?("prick", "versions")
367
+ end
368
+
369
+ # Loads database environment. It is an error if the prick schema is absent
370
+ def load_database_environment
371
+ version, environment, prick_version = connection.tuple %(
372
+ select version, environment, prick from prick.versions
373
+ )
374
+ @database_version = PrickVersion.new(version)
375
+ @database_environment = environment
376
+ @database_prick_version = PrickVersion.new(prick_version)
174
377
  end
175
378
  end
176
379
  end
177
380
 
178
-
@@ -5,36 +5,40 @@ require_relative '../builder/builder.rb'
5
5
  module Prick::SubCommand
6
6
  def self.build(
7
7
  database, username, schema,
8
- builddir: "schema", force: false, timer: nil, dump: nil)
8
+ builddir: "schema", force: false,
9
+ timer: nil, dump: nil)
9
10
 
10
11
  Timer.on! if timer
11
- time "Prick::Command#build" do
12
- begin
13
- super_conn = PgConn.new # Used to create new databases (doesn't make a
14
- # difference right now as the mikras user is
15
- # still a superuser)
16
12
 
13
+ Timer.time "Prick::Command#build" do
14
+ begin
15
+ super_conn = State.connection # Used to create new databases (doesn't make a
16
+ # difference right now as the database user is
17
+ # a superuser anyway
17
18
  conn = nil
18
19
  builder = nil
19
- time "Load build object" do
20
- if super_conn.rdbms.exist? database
20
+
21
+ constrain super_conn.rdbms.exist?, true
22
+
23
+ Timer.time "Load build object" do
24
+ if super_conn.rdbms.exist? database # FIXME Why create database? Setup should have done this
21
25
  exist = true
22
26
  else
23
27
  super_conn.rdbms.create database, owner: username
24
28
  exist = false
25
29
  end
26
- conn = PgConn.new(database, username)
30
+ conn = Prick.state.connection
27
31
 
28
32
  builder = Prick::Build::Builder.new(conn, builddir)
29
33
 
30
- if exist
34
+ if exist # Empty (part of) the database
31
35
  if force
32
- # Drop all schemas but re-creates the public schema
33
- super_conn.rdbms.empty!(database, public: false)
34
-
36
+ # Drop all schemas except the prick schema and re-creates the
37
+ # public schema
38
+ build.pool.delete_schema("prick") if super_conn.schema.exist?("prick")
39
+ super_conn.rdbms.empty!(database, public: false, exclude: ["prick"])
35
40
  else
36
- # Find schemas to refresh. This includes all schemas in the
37
- # database
41
+ # Find all schemas
38
42
  refresh_schemas = conn.schema.list
39
43
 
40
44
  # Find existing keep-schemas (but exclude target schema)
@@ -45,15 +49,17 @@ module Prick::SubCommand
45
49
  # Remove keep-schemas from list of schemas
46
50
  refresh_schemas -= keep_schemas
47
51
 
48
- # Eliminate keep schemas from build pool
52
+ # Also remove keep-schemas from the build pool. Why don't we use
53
+ # the pool tracker's list of keep schemas?
49
54
  builder.pool.delete_schema(keep_schemas)
50
55
 
51
56
  # Drop refresh schemes
52
- refresh_schemas.each { |schema| conn.schema.drop(schema, cascade: true) }
57
+ refresh_schemas.each { |s| conn.schema.drop(s, cascade: true) }
53
58
  end
54
59
  end
55
60
 
56
- # Delete schemas after target scheme if present
61
+ # Delete schemas that comes after the target in the build sequence if
62
+ # 'prick build' was given a schema
57
63
  builder.pool.delete_schema(builder.pool.after_schema(schema)) if schema
58
64
  end
59
65
 
@@ -63,10 +69,10 @@ module Prick::SubCommand
63
69
  when :batches; builder.dump
64
70
  when nil;
65
71
  else
66
- raise Prick::Error, "Illegal dump type: #{dump.inspect}"
72
+ Prick.error "Illegal dump type: #{dump.inspect}"
67
73
  end && exit
68
74
 
69
- time "Execute build object" do
75
+ Timer.time "Execute build object" do
70
76
  builder.execute conn
71
77
  end
72
78
 
@@ -3,13 +3,13 @@
3
3
  require_relative '../builder/builder.rb'
4
4
 
5
5
  module Prick::SubCommand
6
- # Drop users and empty database. This has the same as effect as
7
- # teardown+setup but without recreating the user and the database that
8
- # annoyingly terminates all user sessions
6
+ # Drop users and hollow-out database. This has the same as effect as
7
+ # teardown+setup but without recreating the database owner and the database
8
+ # which would otherwise terminate all user sessions
9
9
  def self.clean(database)
10
10
  drop_users(database)
11
- PgConn.new("postgres") { |conn|
12
- conn.rdbms.empty!(database) if conn.rdbms.exist?(database)
11
+ State.connection { |conn|
12
+ conn.rdbms.empty!(database, exclude: ["prick"]) if conn.rdbms.exist?(database)
13
13
  }
14
14
  end
15
15
  end
@@ -1,16 +1,51 @@
1
-
2
1
  module Prick
3
2
  module SubCommand
3
+ PRICK_BUILD_FILES = %w(tables.sql views.sql) # HARDCODED
4
+
5
+ def self.create_database(database, username, environment)
6
+ super_conn = State.connection
7
+
8
+ # Create user if absent
9
+ if !super_conn.role.exist? username
10
+ # FIXME Should not be created as superuser but we can't do that before we
11
+ # have a super: option in build so that superuser-requiring function
12
+ # definitions can be built
13
+ super_conn.role.create username, superuser: true, can_login: true, create_role: true
14
+ end
15
+
16
+ # Create database
17
+ super_conn.rdbms.create database, owner: username
18
+
19
+ # Setup Prick schema. Note that this is hardcoded, the prick build file
20
+ # is ignored
21
+ conn = PgConn.new(database, username) # Can't use Prick.state.connection on a new database
22
+ conn.schema.create("prick")
23
+ PRICK_BUILD_FILES.each { |file| conn.exec(IO.read("#{SCHEMA_PRICK_DIR}/#{file}")) }
24
+
25
+ # Add initial build record
26
+ state = Prick.state # shorthand
27
+ conn.insert "prick.builds",
28
+ name: state.title,
29
+ version: state.version,
30
+ prick: state.prick_version,
31
+ branch: state.branch,
32
+ rev: state.rev(kind: :short),
33
+ clean: state.clean?,
34
+ environment: environment || Prick::DEFAULT_ENVIRONMENT,
35
+ built_at: nil,
36
+ success: nil
37
+ end
38
+
4
39
  def self.create_migration(username, from_version, force: false, file: nil)
5
40
  constrain from_version, PrickVersion
6
41
  Git.clean? or raise "Won't migrate: Repository is dirty"
7
42
  Git.synchronized? or raise "Won't migrate: Repository is not synchronized"
8
43
 
9
- from_version != PrickVersion.zero or raise Prick::Error, "Can't migrate from release 0.0.0"
44
+ from_version != PrickVersion.zero or Prick.error "Can't migrate from release 0.0.0"
10
45
  to_version = Prick.state.version
11
46
  migration_dir = "#{MIGRATION_DIR}/#{from_version}"
12
47
  migration_exist = File.directory? migration_dir
13
- force || file || !migration_exist or raise Prick::Fail, "Migration #{from_version} exists"
48
+ force || file || !migration_exist or Prick.failure "Migration #{from_version} exists"
14
49
 
15
50
  $quiet = true if file
16
51
 
@@ -21,8 +56,8 @@ module Prick
21
56
  end
22
57
  }
23
58
  else
24
- from_version != to_version or raise Prick::Fail, "Can't migrate to same release"
25
- from_version < to_version or raise Prick::Fail, "Can't migrate backwards (why not?)"
59
+ from_version != to_version or Prick.failure "Can't migrate to same release"
60
+ from_version < to_version or Prick.failure "Can't migrate backwards (why not?)"
26
61
 
27
62
  diff = nil
28
63
  if force || !migration_exist
@@ -51,7 +86,7 @@ module Prick
51
86
  mesg " Creating diff"
52
87
  Diff.new(from_db, to_db)
53
88
  }
54
- !diff.same? or raise Prick::Fail, "No changes"
89
+ !diff.same? or Prick.failure "No changes"
55
90
  ensure
56
91
  drop_all(from_db)
57
92
  drop_all(to_db)