xmigra 1.0.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/xmigra/access_artifact.rb +44 -0
- data/lib/xmigra/access_artifact_collection.rb +50 -0
- data/lib/xmigra/branch_upgrade.rb +62 -0
- data/lib/xmigra/db_support/mssql.rb +1070 -0
- data/lib/xmigra/function.rb +17 -0
- data/lib/xmigra/index.rb +21 -0
- data/lib/xmigra/index_collection.rb +38 -0
- data/lib/xmigra/migration.rb +27 -0
- data/lib/xmigra/migration_chain.rb +63 -0
- data/lib/xmigra/migration_conflict.rb +68 -0
- data/lib/xmigra/new_file.rb +4 -0
- data/lib/xmigra/new_migration_adder.rb +74 -0
- data/lib/xmigra/permission_script_writer.rb +67 -0
- data/lib/xmigra/program.rb +927 -0
- data/lib/xmigra/schema_manipulator.rb +39 -0
- data/lib/xmigra/schema_updater.rb +183 -0
- data/lib/xmigra/stored_procedure.rb +20 -0
- data/lib/xmigra/vcs_support/git.rb +275 -0
- data/lib/xmigra/vcs_support/svn.rb +213 -0
- data/lib/xmigra/version.rb +1 -1
- data/lib/xmigra/view.rb +17 -0
- data/lib/xmigra.rb +47 -3222
- data/test/runner.rb +53 -4
- metadata +22 -2
@@ -0,0 +1,927 @@
|
|
1
|
+
require 'xmigra'
|
2
|
+
|
3
|
+
module XMigra
|
4
|
+
class Program
|
5
|
+
ILLEGAL_PATH_CHARS = "\"<>:|"
|
6
|
+
ILLEGAL_FILENAME_CHARS = ILLEGAL_PATH_CHARS + "/\\"
|
7
|
+
|
8
|
+
class TerminatingOption < Exception; end
|
9
|
+
class ArgumentError < XMigra::Error; end
|
10
|
+
module QuietError; end
|
11
|
+
|
12
|
+
class << self
|
13
|
+
def subcommand(name, description, &block)
|
14
|
+
(@subcommands ||= {})[name] = block
|
15
|
+
(@subcommand_descriptions ||= {})[name] = description
|
16
|
+
end
|
17
|
+
|
18
|
+
# Run the given command line.
|
19
|
+
#
|
20
|
+
# An array of command line arguments may be given as the only argument
|
21
|
+
# or arguments may be given as call parameters. Returns nil if the
|
22
|
+
# command completed or a TerminatingOption object if a terminating
|
23
|
+
# option (typically "--help") was passed.
|
24
|
+
def run(*argv)
|
25
|
+
options = (Hash === argv.last) ? argv.pop : {}
|
26
|
+
argv = argv[0] if argv.length == 1 && Array === argv[0]
|
27
|
+
prev_subcommand = @active_subcommand
|
28
|
+
begin
|
29
|
+
@active_subcommand = subcmd = argv.shift
|
30
|
+
|
31
|
+
begin
|
32
|
+
if subcmd == "help" || subcmd.nil?
|
33
|
+
help(argv)
|
34
|
+
return
|
35
|
+
end
|
36
|
+
|
37
|
+
begin
|
38
|
+
(@subcommands[subcmd] || method(:show_subcommands_as_help)).call(argv)
|
39
|
+
rescue StandardError => error
|
40
|
+
raise unless options[:error]
|
41
|
+
options[:error].call(error)
|
42
|
+
end
|
43
|
+
rescue TerminatingOption => stop
|
44
|
+
return stop
|
45
|
+
end
|
46
|
+
ensure
|
47
|
+
@active_subcommand = prev_subcommand
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def help(argv)
|
52
|
+
if (argv.length != 1) || (argv[0] == '--help')
|
53
|
+
show_subcommands
|
54
|
+
return
|
55
|
+
end
|
56
|
+
|
57
|
+
argv << "--help"
|
58
|
+
run(argv)
|
59
|
+
end
|
60
|
+
|
61
|
+
def show_subcommands(_1=nil)
|
62
|
+
puts
|
63
|
+
puts "Use '#{File.basename($0)} help <subcommand>' for help on one of these subcommands:"
|
64
|
+
puts
|
65
|
+
|
66
|
+
descs = @subcommand_descriptions
|
67
|
+
cmd_width = descs.enum_for(:each_key).max_by {|i| i.length}.length + 2
|
68
|
+
descs.each_pair do |cmd, description|
|
69
|
+
printf("%*s - ", cmd_width, cmd)
|
70
|
+
description.lines.each_with_index do |line, i|
|
71
|
+
indent = if (i > 0)..(i == description.lines.count - 1)
|
72
|
+
cmd_width + 3
|
73
|
+
else
|
74
|
+
0
|
75
|
+
end
|
76
|
+
puts(" " * indent + line.chomp)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def show_subcommands_as_help(_1=nil)
|
82
|
+
show_subcommands
|
83
|
+
raise ArgumentError.new("Invalid subcommand").extend(QuietError)
|
84
|
+
end
|
85
|
+
|
86
|
+
def command_line(argv, use, cmdopts = {})
|
87
|
+
options = OpenStruct.new
|
88
|
+
argument_desc = cmdopts[:argument_desc]
|
89
|
+
|
90
|
+
optparser = OptionParser.new do |flags|
|
91
|
+
subcmd = @active_subcommand || "<subcmd>"
|
92
|
+
flags.banner = [
|
93
|
+
"Usage: #{File.basename($0)} #{subcmd} [<options>]",
|
94
|
+
argument_desc
|
95
|
+
].compact.join(' ')
|
96
|
+
flags.banner << "\n\n" + cmdopts[:help].chomp if cmdopts[:help]
|
97
|
+
|
98
|
+
flags.separator ''
|
99
|
+
flags.separator 'Subcommand options:'
|
100
|
+
|
101
|
+
if use[:target_type]
|
102
|
+
options.target_type = :unspecified
|
103
|
+
allowed = [:exact, :substring, :regexp]
|
104
|
+
flags.on(
|
105
|
+
"--by=TYPE", allowed,
|
106
|
+
"Specify how TARGETs are matched",
|
107
|
+
"against subject strings",
|
108
|
+
"(#{allowed.collect {|i| i.to_s}.join(', ')})"
|
109
|
+
) do |type|
|
110
|
+
options.target_type = type
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
if use[:dev_branch]
|
115
|
+
options.dev_branch = false
|
116
|
+
flags.on("--dev-branch", "Favor development branch usage assumption") do
|
117
|
+
options.dev_branch = true
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
unless use[:edit].nil?
|
122
|
+
options.edit = use[:edit] ? true : false
|
123
|
+
flags.banner << "\n\n" << (<<END_OF_HELP).chomp
|
124
|
+
When opening an editor, the program specified by the environment variable
|
125
|
+
VISUAL is preferred, then the one specified by EDITOR. If neither of these
|
126
|
+
environment variables is set no editor will be opened.
|
127
|
+
END_OF_HELP
|
128
|
+
flags.on("--[no-]edit", "Open the resulting file in an editor",
|
129
|
+
"(defaults to #{options.edit})") do |v|
|
130
|
+
options.edit = %w{EDITOR VISUAL}.any? {|k| ENV.has_key?(k)} && v
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
if use[:search_type]
|
135
|
+
options.search_type = :changes
|
136
|
+
allowed = [:changes, :sql]
|
137
|
+
flags.on(
|
138
|
+
"--match=SUBJECT", allowed,
|
139
|
+
"Specify the type of subject against",
|
140
|
+
"which TARGETs match",
|
141
|
+
"(#{allowed.collect {|i| i.to_s}.join(', ')})"
|
142
|
+
) do |type|
|
143
|
+
options.search_type = type
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
if use[:outfile]
|
148
|
+
options.outfile = nil
|
149
|
+
flags.on("-o", "--outfile=FILE", "Output to FILE") do |fpath|
|
150
|
+
options.outfile = File.expand_path(fpath)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
if use[:production]
|
155
|
+
options.production = false
|
156
|
+
flags.on("-p", "--production", "Generate script for production databases") do
|
157
|
+
options.production = true
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
options.source_dir = Dir.pwd
|
162
|
+
flags.on("--source=DIR", "Work from/on the schema in DIR") do |dir|
|
163
|
+
options.source_dir = File.expand_path(dir)
|
164
|
+
end
|
165
|
+
|
166
|
+
flags.on_tail("-h", "--help", "Show this message") do
|
167
|
+
puts
|
168
|
+
puts flags
|
169
|
+
raise TerminatingOption.new('--help')
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
argv = optparser.parse(argv)
|
174
|
+
|
175
|
+
if use[:target_type] && options.target_type == :unspecified
|
176
|
+
options.target_type = case options.search_type
|
177
|
+
when :changes then :strict
|
178
|
+
else :substring
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
return argv, options
|
183
|
+
end
|
184
|
+
|
185
|
+
def output_to(fpath_or_nil)
|
186
|
+
if fpath_or_nil.nil?
|
187
|
+
yield(STDOUT)
|
188
|
+
else
|
189
|
+
File.open(fpath_or_nil, "w") do |stream|
|
190
|
+
yield(stream)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
def argument_error_unless(test, message)
|
196
|
+
return if test
|
197
|
+
raise ArgumentError, XMigra.program_message(message, :cmd=>@active_subcommand)
|
198
|
+
end
|
199
|
+
|
200
|
+
def edit(fpath)
|
201
|
+
case
|
202
|
+
when (editor = ENV['VISUAL']) && PLATFORM == :mswin
|
203
|
+
system(%Q{start #{editor} "#{fpath}"})
|
204
|
+
when editor = ENV['VISUAL']
|
205
|
+
system(%Q{#{editor} "#{fpath}" &})
|
206
|
+
when editor = ENV['EDITOR']
|
207
|
+
system(%Q{#{editor} "#{fpath}"})
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
subcommand 'overview', "Explain usage of this tool" do |argv|
|
213
|
+
argument_error_unless([[], ["-h"], ["--help"]].include?(argv),
|
214
|
+
"'%prog %cmd' does not accept arguments.")
|
215
|
+
|
216
|
+
formalizations = {
|
217
|
+
/xmigra/i=>'XMigra',
|
218
|
+
}
|
219
|
+
|
220
|
+
section = proc do |name, content|
|
221
|
+
puts
|
222
|
+
puts name
|
223
|
+
puts "=" * name.length
|
224
|
+
puts XMigra.program_message(
|
225
|
+
content,
|
226
|
+
:prog=>/%program_cmd\b/
|
227
|
+
)
|
228
|
+
end
|
229
|
+
|
230
|
+
puts XMigra.program_message(<<END_HEADER) # Overview
|
231
|
+
|
232
|
+
===========================================================================
|
233
|
+
# Usage of %program_name
|
234
|
+
===========================================================================
|
235
|
+
END_HEADER
|
236
|
+
|
237
|
+
begin; section['Introduction', <<END_SECTION]
|
238
|
+
|
239
|
+
%program_name is a tool designed to assist development of software using
|
240
|
+
relational databases for persistent storage. During the development cycle, this
|
241
|
+
tool helps manage:
|
242
|
+
|
243
|
+
- Migration of production databases to newer versions, including migration
|
244
|
+
between parallel, released versions.
|
245
|
+
|
246
|
+
- Using transactional scripts, so that unexpected database conditions do not
|
247
|
+
lead to corrupt production databases.
|
248
|
+
|
249
|
+
- Protection of production databases from changes still under development.
|
250
|
+
|
251
|
+
- Parallel development of features requiring database changes.
|
252
|
+
|
253
|
+
- Assignment of permissions to database objects.
|
254
|
+
|
255
|
+
To accomplish this, the database schema to be created is decomposed into
|
256
|
+
several parts and formatted in text files according to certain rules. The
|
257
|
+
%program_name tool is then used to manipulate, query, or generate scripts from
|
258
|
+
the set of files.
|
259
|
+
END_SECTION
|
260
|
+
end
|
261
|
+
begin; section['Schema Files and Folders', <<END_SECTION]
|
262
|
+
|
263
|
+
SCHEMA (root folder/directory of decomposed schema)
|
264
|
+
+-- database.yaml
|
265
|
+
+-- permissions.yaml (optional)
|
266
|
+
+-- structure
|
267
|
+
| +-- head.yaml
|
268
|
+
| +-- <migration files>
|
269
|
+
| ...
|
270
|
+
+-- access
|
271
|
+
| +-- <stored procedure definition files>
|
272
|
+
| +-- <view definition files>
|
273
|
+
| +-- <user defined function definition files>
|
274
|
+
| ...
|
275
|
+
+-- indexes
|
276
|
+
+-- <index definition files>
|
277
|
+
...
|
278
|
+
|
279
|
+
--------------------------------------------------------------------------
|
280
|
+
NOTE: In case-sensitive filesystems, all file and directory names dictated
|
281
|
+
by %program_name are lowercase.
|
282
|
+
--------------------------------------------------------------------------
|
283
|
+
|
284
|
+
All data files used by %program_name conform to the YAML 1.0 data format
|
285
|
+
specification. Please refer to that specification for information
|
286
|
+
on the specifics of encoding particular values. This documentation, at many
|
287
|
+
points, makes reference to "sections" of a .yaml file; such a section is,
|
288
|
+
technically, an entry in the mapping at the top level of the .yaml file with
|
289
|
+
the given key. The simplest understanding of this is that the section name
|
290
|
+
followed immediately (no whitespace) by a colon (':') and at least one space
|
291
|
+
character appears in the left-most column, and the section contents appear
|
292
|
+
either on the line after the colon-space or in an indented block starting on
|
293
|
+
the next line (often used with a scalar block indicator ('|' or '>') following
|
294
|
+
the colon-space).
|
295
|
+
|
296
|
+
The decomposed database schema lives within a filesystem subtree rooted at a
|
297
|
+
single folder (i.e. directory). For examples in this documentation, that
|
298
|
+
folder will be called SCHEMA. Two important files are stored directly in the
|
299
|
+
SCHEMA directory: database.yaml and permissions.yaml. The "database.yaml" file
|
300
|
+
provides general information about the database for which scripts are to be
|
301
|
+
generated. Please see the section below detailing this file's contents for
|
302
|
+
more information. The "permissions.yaml" file specifies permissions to be
|
303
|
+
granted when generating a permission-granting script (run
|
304
|
+
'%program_cmd help permissions' for more information).
|
305
|
+
|
306
|
+
Within the SCHEMA folder, %program_name expects three other folders: structure,
|
307
|
+
access, and indexes.
|
308
|
+
END_SECTION
|
309
|
+
end
|
310
|
+
begin; section['The "SCHEMA/structure" Folder', <<END_SECTION]
|
311
|
+
|
312
|
+
Every relational database has structures in which it stores the persistent
|
313
|
+
data of the related application(s). These database objects are special in
|
314
|
+
relation to other parts of the database because they contain information that
|
315
|
+
cannot be reproduced just from the schema definition. Yet bug fixes and
|
316
|
+
feature additions will need to update this structure and good programming
|
317
|
+
practice dictates that such changes, and the functionalities relying on them,
|
318
|
+
need to be tested. Testability, in turn, dictates a repeatable sequence of
|
319
|
+
actions to be executed on the database starting from a known state.
|
320
|
+
|
321
|
+
%program_name models the evolution of the persistent data storage structures
|
322
|
+
of a database as a chain of "migrations," each of which makes changes to the
|
323
|
+
database storage structure from a previous, known state of the database. The
|
324
|
+
first migration starts at an empty database and each subsequent migration
|
325
|
+
starts where the previous migration left off. Each migration is stored in
|
326
|
+
a file within the SCHEMA/structure folder. The names of migration files start
|
327
|
+
with a date (YYYY-MM-DD) and include a short description of the change. As
|
328
|
+
with other files used by the %program_name tool, migration files are in the
|
329
|
+
YAML format, using the ".yaml" extension. Because some set of migrations
|
330
|
+
will determine the state of production databases, migrations themselves (as
|
331
|
+
used to produce production upgrade scripts) must be "set in stone" -- once
|
332
|
+
committed to version control (on a production branch) they must never change
|
333
|
+
their content.
|
334
|
+
|
335
|
+
Migration files are usually generated by running the '%program_cmd new'
|
336
|
+
command (see '%program_cmd help new' for more information) and then editing
|
337
|
+
the resulting file. The migration file has several sections: "starting from",
|
338
|
+
"sql", "changes", and "description". The "starting from" section indicates the
|
339
|
+
previous migration in the chain (or "empty database" for the first migration).
|
340
|
+
SQL code that effects the migration on the database is the content of the "sql"
|
341
|
+
section (usually given as a YAML literal block). The "changes" section
|
342
|
+
supports '%program_cmd history', allowing a more user-friendly look at the
|
343
|
+
evolution of a subset of the database structure over the migration chain.
|
344
|
+
Finally, the "description" section is intended for a prose description of the
|
345
|
+
migration, and is included in the upgrade metadata stored in the database. Use
|
346
|
+
of the '%program_cmd new' command is recommended; it handles several tiresome
|
347
|
+
and error-prone tasks: creating a new migration file with a conformant name,
|
348
|
+
setting the "starting from" section to the correct value, and updating
|
349
|
+
SCHEMA/structure/head.yaml to reference the newly generated file.
|
350
|
+
|
351
|
+
The SCHEMA/structure/head.yaml file deserves special note: it contains a
|
352
|
+
reference to the last migration to be applied. Because of this, parallel
|
353
|
+
development of database changes will cause conflicts in the contents of this
|
354
|
+
file. This is by design, and '%program_cmd unbranch' will assist in resolving
|
355
|
+
these conflicts.
|
356
|
+
|
357
|
+
Care must be taken when committing migration files to version control; because
|
358
|
+
the structure of production databases will be determined by the chain of
|
359
|
+
migrations (starting at an empty database, going up to some certain point),
|
360
|
+
it is imperative that migrations used to build these production upgrade scripts
|
361
|
+
not be modified once committed to the version control system. When building
|
362
|
+
a production upgrade script, %program_name verifies that this constraint is
|
363
|
+
followed. Therefore, if the need arises to commit a migration file that may
|
364
|
+
require amendment, the best practice is to commit it to a development branch.
|
365
|
+
|
366
|
+
Migrating a database from one released version (which may receive bug fixes
|
367
|
+
or critical feature updates) to another released version which developed along
|
368
|
+
a parallel track is generally a tricky endeavor. Please see the section on
|
369
|
+
"branch upgrades" below for information on how %program_name supports this
|
370
|
+
use case.
|
371
|
+
END_SECTION
|
372
|
+
end
|
373
|
+
begin; section['The "SCHEMA/access" Folder', <<END_SECTION]
|
374
|
+
|
375
|
+
In addition to the structures that store persistent data, many relational
|
376
|
+
databases also support persistent constructs for providing consistent access
|
377
|
+
(creation, retrieval, update, and deletion) to the persistent data even as the
|
378
|
+
actual storage structure changes, allowing for a degree of backward
|
379
|
+
compatibility with applications. These constructs do not, of themselves,
|
380
|
+
contain persistent data, but rather specify procedures for accessing the
|
381
|
+
persistent data.
|
382
|
+
|
383
|
+
In %program_name, such constructs are defined in the SCHEMA/access folder, with
|
384
|
+
each construct (usually) having its own file. The name of the file must be
|
385
|
+
a valid SQL name for the construct defined. The filename may be accessed
|
386
|
+
within the definition by the filename metavariable, by default "[{filename}]"
|
387
|
+
(without quotation marks); this assists renaming such constructs, making the
|
388
|
+
operation of renaming the construct a simple rename of the containing file
|
389
|
+
within the filesystem (and version control repository). Use of files in this
|
390
|
+
way creates a history of each "access object's" definition in the version
|
391
|
+
control system organized by the name of the object.
|
392
|
+
|
393
|
+
The definition of the access object is given in the "sql" section of the
|
394
|
+
definition file, usually with a YAML literal block. This SQL MUST define the
|
395
|
+
object for which the containing file is named; failure to do so will result in
|
396
|
+
failure of the script when it is run against the database. After deleting
|
397
|
+
all access objects previously created by %program_name, the generated script
|
398
|
+
first checks that the access object does not exist, then runs the definition
|
399
|
+
SQL, and finally checks that the object now exists.
|
400
|
+
|
401
|
+
In addition to the SQL definition, %program_name needs to know what kind of
|
402
|
+
object is to be created by this definition. This information is presented in
|
403
|
+
the "define" section, and is currently limited to "function",
|
404
|
+
"stored procedure", and "view".
|
405
|
+
|
406
|
+
Some database management systems enforce a rule that statements defining access
|
407
|
+
objects (or at least, some kinds of access objects) may not reference access
|
408
|
+
objects that do not yet exist. (A good example is Microsoft SQL Server's rule
|
409
|
+
about user defined functions that means a definition for the function A may
|
410
|
+
only call the user defined function B if B exists when A is defined.) To
|
411
|
+
accommodate this situation, %program_name provides an optional "referencing"
|
412
|
+
section in the access object definition file. The content of this section
|
413
|
+
must be a YAML sequence of scalars, each of which is the name of an access
|
414
|
+
object file (the name must be given the same way the filename is written, not
|
415
|
+
just a way that is equivalent in the target SQL language). The scalar values
|
416
|
+
must be appropriately escaped as necessary (e.g. Microsoft SQL Server uses
|
417
|
+
square brackets as a quotation mechanism, and square brackets have special
|
418
|
+
meaning in YAML, so it is necessary use quoted strings or a scalar block to
|
419
|
+
contain them). Any access objects listed in this way will be created before
|
420
|
+
the referencing object.
|
421
|
+
END_SECTION
|
422
|
+
end
|
423
|
+
begin; section['The "SCHEMA/indexes" Folder', <<END_SECTION]
|
424
|
+
|
425
|
+
Database indexes vary from the other kinds of definitions supported by
|
426
|
+
%program_name: while SCHEMA/structure elements only hold data and
|
427
|
+
SCHEMA/access elements are defined entirely by their code in the schema and
|
428
|
+
can thus be efficiently re-created, indexes have their whole definition in
|
429
|
+
the schema, but store data gleaned from the persistent data. Re-creation of
|
430
|
+
an index is an expensive operation that should be avoided when unnecessary.
|
431
|
+
|
432
|
+
To accomplish this end, %program_name looks in the SCHEMA/indexes folder for
|
433
|
+
index definitions. The generated scripts will drop and (re-)create only
|
434
|
+
indexes whose definitions are changed. %program_name uses a very literal
|
435
|
+
comparison of the SQL text used to create the index to determine "change;"
|
436
|
+
even so much as a single whitespace added or removed, even if insignificant to
|
437
|
+
the database management system, will be enough to cause the index to be dropped
|
438
|
+
and re-created.
|
439
|
+
|
440
|
+
Index definition files use only the "sql" section to provide the SQL definition
|
441
|
+
of the index. Index definitions do not support use of the filename
|
442
|
+
metavariable because renaming an index would cause it to be dropped and
|
443
|
+
re-created.
|
444
|
+
END_SECTION
|
445
|
+
end
|
446
|
+
begin; section['The "SCHEMA/database.yaml" File', <<END_SECTION]
|
447
|
+
|
448
|
+
The SCHEMA/database.yaml file consists of several sections that provide general
|
449
|
+
information about the database schema. The following subsection detail some
|
450
|
+
contents that may be included in this file.
|
451
|
+
|
452
|
+
system
|
453
|
+
------
|
454
|
+
|
455
|
+
The "system" section specifies for %program_name which database management
|
456
|
+
system shall be targeted for the generation of scripts. Currently the
|
457
|
+
supported values are:
|
458
|
+
|
459
|
+
- Microsoft SQL Server
|
460
|
+
|
461
|
+
Each system can also have sub-settings that modify the generated scripts.
|
462
|
+
|
463
|
+
Microsoft SQL Server:
|
464
|
+
The "MSSQL 2005 compatible" setting in SCEMA/database.yaml, if set to
|
465
|
+
"true", causes INSERT statements to be generated in a more verbose and
|
466
|
+
SQL Server 2005 compatible manner.
|
467
|
+
|
468
|
+
Also, each system may modify in other ways the behavior of the generator or
|
469
|
+
the interpretation of the definition files:
|
470
|
+
|
471
|
+
Microsoft SQL Server:
|
472
|
+
The SQL in the definition files may use the "GO" metacommand also found in
|
473
|
+
Microsoft SQL Server Management Studio and sqlcmd.exe. This metacommand
|
474
|
+
must be on a line by itself where used. It should produce the same results
|
475
|
+
as it would in MSSMS or sqlcmd.exe, except that the overall script is
|
476
|
+
transacted.
|
477
|
+
|
478
|
+
script comment
|
479
|
+
--------------
|
480
|
+
|
481
|
+
The "script comment" section defines a body of SQL to be inserted at the top
|
482
|
+
of all generated scripts. This is useful for including copyright information
|
483
|
+
in the resulting SQL.
|
484
|
+
|
485
|
+
filename metavariable
|
486
|
+
---------------------
|
487
|
+
|
488
|
+
The "filename metavariable" section allows the schema to override the filename
|
489
|
+
metavariable that is used for access object definitions. The default value
|
490
|
+
is "[{filename}]" (excluding the quotation marks). If that string is required
|
491
|
+
in one or more access object definitions, this section allows the schema to
|
492
|
+
dictate another value.
|
493
|
+
END_SECTION
|
494
|
+
end
|
495
|
+
begin; section['Script Generation Modes', <<END_SECTION]
|
496
|
+
|
497
|
+
%program_name supports two modes of upgrade script creation: development and
|
498
|
+
production. (Permission script generation is not constrained to these modes.)
|
499
|
+
Upgrade script generation defaults to development mode, which does less
|
500
|
+
work to generate a script and skips tests intended to ensure that the script
|
501
|
+
could be generated again at some future point from the contents of the version
|
502
|
+
control repository. The resulting script can only be run on an empty database
|
503
|
+
or a database that was set up with a development mode script earlier on the
|
504
|
+
same migration chain; running a development mode script on a database created
|
505
|
+
with a production script fails by design (preventing a production database from
|
506
|
+
undergoing a migration that has not been duly recorded in the version control
|
507
|
+
system). Development scripts have a big warning comment at the beginning of
|
508
|
+
the script as a reminder that they are not suitable to use on a production
|
509
|
+
system.
|
510
|
+
|
511
|
+
Use of the '--production' flag with the '%program_cmd upgrade' command
|
512
|
+
enables production mode, which carries out a significant number of
|
513
|
+
additional checks. These checks serve two purposes: making sure that all
|
514
|
+
migrations to be applied to a production database are recorded in the version
|
515
|
+
control system and that all of the definition files in the whole schema
|
516
|
+
represent a single, coherent point in the version history (i.e. all files are
|
517
|
+
from the same revision). Where a case arises that a script needs to be
|
518
|
+
generated that cannot meet these two criteria, it is almost certainly a case
|
519
|
+
that calls for a development script. There is always the option of creating
|
520
|
+
a new production branch and committing the %program_name schema files to that
|
521
|
+
branch if a production script is needed, thus meeting the criteria of the test.
|
522
|
+
|
523
|
+
Note that "development mode" and "production mode" are not about the quality
|
524
|
+
of the scripts generated or the "build mode" of the application that may access
|
525
|
+
the resulting database, but rather about the designation of the database
|
526
|
+
to which the generated scripts may be applied. "Production" scripts certainly
|
527
|
+
should be tested in a non-production environment before they are applied to
|
528
|
+
a production environment with irreplaceable data. But "development" scripts,
|
529
|
+
by design, can never be run on production systems (so that the production
|
530
|
+
systems only move from one well-documented state to another).
|
531
|
+
END_SECTION
|
532
|
+
end
|
533
|
+
begin; section['Branch Upgrades', <<END_SECTION]
|
534
|
+
|
535
|
+
Maintaining a single, canonical chain of database schema migrations released to
|
536
|
+
customers dramatically reduces the amount of complexity inherent in
|
537
|
+
same-version product upgrades. But expecting development and releases to stay
|
538
|
+
on a single migration chain is overly optimistic; there are many circumstances
|
539
|
+
that lead to divergent migration chains across instances of the database. A
|
540
|
+
bug fix on an already released version or some emergency feature addition for
|
541
|
+
an enormous profit opportunity are entirely possible, and any database
|
542
|
+
evolution system that breaks under those requirements will be an intolerable
|
543
|
+
hinderance.
|
544
|
+
|
545
|
+
%program_name supports these situations through the mechanism of "branch
|
546
|
+
upgrades." A branch upgrade is a migration containing SQL commands to effect
|
547
|
+
the conversion of the head of the current branch's migration chain (i.e. the
|
548
|
+
state of the database after the most recent migration in the current branch)
|
549
|
+
into some state along another branch's migration chain. These commands are not
|
550
|
+
applied when the upgrade script for this branch is run. Rather, they are saved
|
551
|
+
in the database and run only if, and then prior to, an upgrade script for the
|
552
|
+
targeted branch is run against the same database.
|
553
|
+
|
554
|
+
Unlike regular migrations, changes to the branch upgrade migration MAY be
|
555
|
+
committed to the version control repository.
|
556
|
+
|
557
|
+
::::::::::::::::::::::::::::::::::: EXAMPLE :::::::::::::::::::::::::::::::::::
|
558
|
+
:: ::
|
559
|
+
|
560
|
+
Product X is about to release version 2.0. Version 1.4 of Product X has
|
561
|
+
been in customers' hands for 18 months and seven bug fixes involving database
|
562
|
+
structure changes have been implemented in that time. Our example company has
|
563
|
+
worked out the necessary SQL commands to convert the current head of the 1.4
|
564
|
+
migration chain to the same resulting structure as the head of the 2.0
|
565
|
+
migration chain. Those SQL commands, and the appropriate metadata about the
|
566
|
+
target branch (version 2.0), the completed migration (the one named as the head
|
567
|
+
of the 2.0 branch), and the head of the 1.4 branch are all put into the
|
568
|
+
SCHEMA/structure/branch-upgrade.yaml file as detailed below. %program_name
|
569
|
+
can then script the storage of these commands into the database for execution
|
570
|
+
by a Product X version 2.0 upgrade script.
|
571
|
+
|
572
|
+
Once the branch-upgrade.yaml file is created and committed to version control
|
573
|
+
in the version 1.4 branch, two upgrade scripts need to be generated: a
|
574
|
+
version 1.4 upgrade script and a version 2.0 upgrade script, each from its
|
575
|
+
respective branch in version control. For each version 1.4 installation, the
|
576
|
+
1.4 script will first be run, bringing the installation up to the head of
|
577
|
+
version 1.4 and installing the instructions for upgrading to a version 2.0
|
578
|
+
database. Then the version 2.0 script will be run, which will execute the
|
579
|
+
stored instructions for bringing the database from version 1.4 to version 2.0.
|
580
|
+
|
581
|
+
Had the branch upgrade not brought the version 1.4 database all the way up to
|
582
|
+
the head of version 2.0 (i.e. if the YAML file indicates a completed migration
|
583
|
+
prior to the version 2.0 head), the version 2.0 script would then apply any
|
584
|
+
following migrations in order to bring the database up to the version 2.0 head.
|
585
|
+
|
586
|
+
:: ::
|
587
|
+
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
588
|
+
|
589
|
+
In addition to the "sql" section, which has the same function as the "sql"
|
590
|
+
section of a regular migration, the SCHEMA/structure/branch-upgrade.yaml file
|
591
|
+
has three other required sections.
|
592
|
+
|
593
|
+
starting from
|
594
|
+
-------------
|
595
|
+
|
596
|
+
As with regular migrations, the branch-upgrade.yaml specifies a "starting from"
|
597
|
+
point, which should always be the head migration of the current branch (see
|
598
|
+
SCHEMA/structure/head.yaml). If this section does not match with the latest
|
599
|
+
migration in the migration chain, no branch upgrade information will be
|
600
|
+
included in the resulting upgrade script and a warning will be issued during
|
601
|
+
script generation. This precaution prevents out-of-date branch upgrade
|
602
|
+
commands from being run.
|
603
|
+
|
604
|
+
resulting branch
|
605
|
+
----------------
|
606
|
+
|
607
|
+
The "resulting branch" section must give the identifier of the branch
|
608
|
+
containing the migration chain into which the included SQL commands will
|
609
|
+
migrate the current chain head. This identifier can be obtained with the
|
610
|
+
'%program_cmd branchid' command run against a working copy of the
|
611
|
+
target branch.
|
612
|
+
|
613
|
+
completes migration to
|
614
|
+
----------------------
|
615
|
+
|
616
|
+
Because the migration chain of the target branch is likely to be extended over
|
617
|
+
time, it is necessary to pin down the intended result state of the branch
|
618
|
+
upgrade to one particular migration in the target chain. The migration name
|
619
|
+
listed in the "completes migration to" section should be the name of the
|
620
|
+
migration (the file basename) which brings the target branch database to the
|
621
|
+
same state as the head state of the current branch after applying the
|
622
|
+
branch upgrade commands.
|
623
|
+
END_SECTION
|
624
|
+
end
|
625
|
+
puts
|
626
|
+
end
|
627
|
+
|
628
|
+
subcommand 'new', "Create a new migration file" do |argv|
|
629
|
+
args, options = command_line(argv, {:edit=>true},
|
630
|
+
:argument_desc=>"MIGRATION_SUMMARY",
|
631
|
+
:help=> <<END_OF_HELP)
|
632
|
+
This command generates a new migration file and ties it into the current
|
633
|
+
migration chain. The name of the new file is generated from today's date and
|
634
|
+
the given MIGRATION_SUMMARY. The resulting new file may be opened in an
|
635
|
+
editor (see the --[no-]edit option).
|
636
|
+
END_OF_HELP
|
637
|
+
|
638
|
+
argument_error_unless(args.length == 1,
|
639
|
+
"'%prog %cmd' takes one argument.")
|
640
|
+
migration_summary = args[0]
|
641
|
+
argument_error_unless(
|
642
|
+
migration_summary.chars.all? {|c| !ILLEGAL_FILENAME_CHARS.include?(c)},
|
643
|
+
"Migration summary may not contain any of: " + ILLEGAL_FILENAME_CHARS
|
644
|
+
)
|
645
|
+
|
646
|
+
tool = NewMigrationAdder.new(options.source_dir).extend(WarnToStderr)
|
647
|
+
new_fpath = tool.add_migration(migration_summary)
|
648
|
+
|
649
|
+
edit(new_fpath) if options.edit
|
650
|
+
end
|
651
|
+
|
652
|
+
subcommand 'upgrade', "Generate an upgrade script" do |argv|
|
653
|
+
args, options = command_line(argv, {:production=>true, :outfile=>true},
|
654
|
+
:help=> <<END_OF_HELP)
|
655
|
+
Running this command will generate an update script from the source schema.
|
656
|
+
Generation of a production script involves more checks on the status of the
|
657
|
+
schema source files but produces a script that may be run on a development,
|
658
|
+
production, or empty database. If the generated script is not specified for
|
659
|
+
production it may only be run on a development or empty database; it will not
|
660
|
+
run on production databases.
|
661
|
+
END_OF_HELP
|
662
|
+
|
663
|
+
argument_error_unless(args.length == 0,
|
664
|
+
"'%prog %cmd' does not take any arguments.")
|
665
|
+
|
666
|
+
sql_gen = SchemaUpdater.new(options.source_dir).extend(WarnToStderr)
|
667
|
+
sql_gen.production = options.production
|
668
|
+
|
669
|
+
output_to(options.outfile) do |out_stream|
|
670
|
+
out_stream.print(sql_gen.update_sql)
|
671
|
+
end
|
672
|
+
end
|
673
|
+
|
674
|
+
subcommand 'render', "Generate SQL script for an access object" do |argv|
|
675
|
+
args, options = command_line(argv, {:outfile=>true},
|
676
|
+
:argument_desc=>"ACCESS_OBJECT_FILE",
|
677
|
+
:help=> <<END_OF_HELP)
|
678
|
+
This command outputs the creation script for the access object defined in the
|
679
|
+
file at path ACCESS_OBJECT_FILE, making any substitutions in the same way as
|
680
|
+
the update script generator.
|
681
|
+
END_OF_HELP
|
682
|
+
|
683
|
+
argument_error_unless(args.length == 1,
|
684
|
+
"'%prog %cmd' takes one argument.")
|
685
|
+
argument_error_unless(File.exist?(args[0]),
|
686
|
+
"'%prog %cmd' must target an existing access object definition.")
|
687
|
+
|
688
|
+
sql_gen = SchemaUpdater.new(options.source_dir).extend(WarnToStderr)
|
689
|
+
|
690
|
+
artifact = sql_gen.access_artifacts[args[0]] || sql_gen.access_artifacts.at_path(args[0])
|
691
|
+
output_to(options.outfile) do |out_stream|
|
692
|
+
out_stream.print(artifact.creation_sql)
|
693
|
+
end
|
694
|
+
end
|
695
|
+
|
696
|
+
subcommand 'unbranch', "Fix a branched migration chain" do |argv|
|
697
|
+
args, options = command_line(argv, {:dev_branch=>true},
|
698
|
+
:help=> <<END_OF_HELP)
|
699
|
+
Use this command to fix a branched migration chain. The need for this command
|
700
|
+
usually arises when code is pulled from the repository into the working copy.
|
701
|
+
|
702
|
+
Because this program checks that migration files are unaltered when building
|
703
|
+
a production upgrade script it is important to use this command only:
|
704
|
+
|
705
|
+
A) after updating in any branch, or
|
706
|
+
|
707
|
+
B) after merging into (including synching) a development branch
|
708
|
+
|
709
|
+
If used in case B, the resulting changes may make the migration chain in the
|
710
|
+
current branch ineligible for generating production upgrade scripts. When the
|
711
|
+
development branch is (cleanly) merged back to the production branch it will
|
712
|
+
still be possible to generate a production upgrade script from the production
|
713
|
+
branch. In case B the resulting script (generated in development mode) should
|
714
|
+
be thoroughly tested.
|
715
|
+
|
716
|
+
Because of the potential danger to a production branch, this command checks
|
717
|
+
the branch usage before executing. Inherent branch usage can be set through
|
718
|
+
the 'productionpattern' command. If the target working copy has not been
|
719
|
+
marked with a production-branch pattern, the branch usage is ambiguous and
|
720
|
+
this command makes the fail-safe assumption that the branch is used for
|
721
|
+
production. This assumption can be overriden with the --dev-branch option.
|
722
|
+
Note that the --dev-branch option will NOT override the production-branch
|
723
|
+
pattern if one exists.
|
724
|
+
END_OF_HELP
|
725
|
+
|
726
|
+
argument_error_unless(args.length == 0,
|
727
|
+
"'%prog %cmd' does not take any arguments.")
|
728
|
+
|
729
|
+
tool = SchemaManipulator.new(options.source_dir).extend(WarnToStderr)
|
730
|
+
conflict = tool.get_conflict_info
|
731
|
+
|
732
|
+
unless conflict
|
733
|
+
STDERR.puts("No conflict!")
|
734
|
+
return
|
735
|
+
end
|
736
|
+
|
737
|
+
if conflict.scope == :repository
|
738
|
+
if conflict.branch_use == :production
|
739
|
+
STDERR.puts(<<END_OF_MESSAGE)
|
740
|
+
|
741
|
+
The target working copy is on a production branch. Because fixing the branched
|
742
|
+
migration chain would require modifying a committed migration, this operation
|
743
|
+
would result in a migration chain incapable of producing a production upgrade
|
744
|
+
script, leaving this branch unable to fulfill its purpose.
|
745
|
+
|
746
|
+
END_OF_MESSAGE
|
747
|
+
raise(XMigra::Error, "Branch use conflict")
|
748
|
+
end
|
749
|
+
|
750
|
+
dev_branch = (conflict.branch_use == :development) || options.dev_branch
|
751
|
+
|
752
|
+
unless dev_branch
|
753
|
+
STDERR.puts(<<END_OF_MESSAGE)
|
754
|
+
|
755
|
+
The target working copy is neither marked for production branch recognition
|
756
|
+
nor was the --dev-branch option given on the command line. Because fixing the
|
757
|
+
branched migration chain would require modifying a committed migration, this
|
758
|
+
operation would result in a migration chain incapable of producing a production
|
759
|
+
upgrage which, because the usage of the working copy's branch is ambiguous,
|
760
|
+
might leave this branch unable to fulfill its purpose.
|
761
|
+
|
762
|
+
END_OF_MESSAGE
|
763
|
+
raise(XMigra::Error, "Potential branch use conflict")
|
764
|
+
end
|
765
|
+
|
766
|
+
if conflict.branch_use == :undefined
|
767
|
+
STDERR.puts(<<END_OF_MESSAGE)
|
768
|
+
|
769
|
+
The branch of the target working copy is not marked with a production branch
|
770
|
+
recognition pattern. The --dev-branch option was given to override the
|
771
|
+
ambiguity, but it is much safer to use the 'productionpattern' command to
|
772
|
+
permanently mark the schema so that production branches can be automatically
|
773
|
+
recognized.
|
774
|
+
|
775
|
+
END_OF_MESSAGE
|
776
|
+
# Warning, not error
|
777
|
+
end
|
778
|
+
end
|
779
|
+
|
780
|
+
conflict.fix_conflict!
|
781
|
+
end
|
782
|
+
|
783
|
+
subcommand 'productionpattern', "Set the recognition pattern for production branches" do |argv|
|
784
|
+
args, options = command_line(argv, {},
|
785
|
+
:argument_desc=>"PATTERN",
|
786
|
+
:help=> <<END_OF_HELP)
|
787
|
+
This command sets the production branch recognition pattern for the schema.
|
788
|
+
The pattern given will determine whether this program treats the current
|
789
|
+
working copy as a production or development branch. The PATTERN given
|
790
|
+
is a Ruby Regexp that is used to evaluate the branch identifier of the working
|
791
|
+
copy. Each supported version control system has its own type of branch
|
792
|
+
identifier:
|
793
|
+
|
794
|
+
Subversion: The path within the repository, starting with a slash (e.g.
|
795
|
+
"/trunk", "/branches/my-branch", "/foo/bar%20baz")
|
796
|
+
|
797
|
+
If PATTERN matches the branch identifier, the branch is considered to be a
|
798
|
+
production branch. If PATTERN does not match, then the branch is a development
|
799
|
+
branch. Some operations (e.g. 'unbranch') are prevented on production branches
|
800
|
+
to avoid making the branch ineligible for generating production upgrade
|
801
|
+
scripts.
|
802
|
+
|
803
|
+
In specifying PATTERN, it is not necessary to escape Ruby special characters
|
804
|
+
(especially including the slash character), but special characters for the
|
805
|
+
shell or command interpreter need their usual escaping. The matching algorithm
|
806
|
+
used for PATTERN does not require the match to start at the beginning of the
|
807
|
+
branch identifier; specify the anchor as part of PATTERN if desired.
|
808
|
+
END_OF_HELP
|
809
|
+
|
810
|
+
argument_error_unless(args.length == 1,
|
811
|
+
"'%prog %cmd' takes one argument.")
|
812
|
+
Regexp.compile(args[0])
|
813
|
+
|
814
|
+
tool = SchemaManipulator.new(options.source_dir).extend(WarnToStderr)
|
815
|
+
|
816
|
+
tool.production_pattern = args[0]
|
817
|
+
end
|
818
|
+
|
819
|
+
subcommand 'branchid', "Print the branch identifier string" do |argv|
|
820
|
+
args, options = command_line(argv, {},
|
821
|
+
:help=> <<END_OF_HELP)
|
822
|
+
This command prints the branch identifier string to standard out (followed by
|
823
|
+
a newline).
|
824
|
+
END_OF_HELP
|
825
|
+
|
826
|
+
argument_error_unless(args.length == 0,
|
827
|
+
"'%prog %cmd' does not take any arguments.")
|
828
|
+
|
829
|
+
tool = SchemaManipulator.new(options.source_dir).extend(WarnToStderr)
|
830
|
+
|
831
|
+
puts tool.branch_identifier
|
832
|
+
end
|
833
|
+
|
834
|
+
subcommand 'history', "Show all SQL from migrations changing the target" do |argv|
|
835
|
+
args, options = command_line(argv, {:outfile=>true, :target_type=>true, :search_type=>true},
|
836
|
+
:argument_desc=>"TARGET [TARGET [...]]",
|
837
|
+
:help=> <<END_OF_HELP)
|
838
|
+
Use this command to get the SQL run by the upgrade script that modifies any of
|
839
|
+
the specified TARGETs. By default this command uses a full item match against
|
840
|
+
the contents of each item in each migration's "changes" key (i.e.
|
841
|
+
--by=exact --match=changes). Migration SQL is printed in order of application
|
842
|
+
to the database.
|
843
|
+
END_OF_HELP
|
844
|
+
|
845
|
+
argument_error_unless(args.length >= 1,
|
846
|
+
"'%prog %cmd' requires at least one argument.")
|
847
|
+
|
848
|
+
target_matches = case options.target_type
|
849
|
+
when :substring
|
850
|
+
proc {|subject| args.any? {|a| subject.include?(a)}}
|
851
|
+
when :regexp
|
852
|
+
patterns = args.map {|a| Regexp.compile(a)}
|
853
|
+
proc {|subject| patterns.any? {|pat| pat.match(subject)}}
|
854
|
+
else
|
855
|
+
targets = Set.new(args)
|
856
|
+
proc {|subject| targets.include?(subject)}
|
857
|
+
end
|
858
|
+
|
859
|
+
criteria_met = case options.search_type
|
860
|
+
when :sql
|
861
|
+
proc {|migration| target_matches.call(migration.sql)}
|
862
|
+
else
|
863
|
+
proc {|migration| migration.changes.any? {|subject| target_matches.call(subject)}}
|
864
|
+
end
|
865
|
+
|
866
|
+
tool = SchemaUpdater.new(options.source_dir).extend(WarnToStderr)
|
867
|
+
|
868
|
+
output_to(options.outfile) do |out_stream|
|
869
|
+
tool.migrations.each do |migration|
|
870
|
+
next unless criteria_met.call(migration)
|
871
|
+
|
872
|
+
out_stream << tool.sql_comment_block(File.basename(migration.file_path))
|
873
|
+
out_stream << migration.sql
|
874
|
+
out_stream << "\n" << tool.batch_separator if tool.respond_to? :batch_separator
|
875
|
+
out_stream << "\n"
|
876
|
+
end
|
877
|
+
end
|
878
|
+
end
|
879
|
+
|
880
|
+
subcommand 'permissions', "Generate a permission assignment script" do |argv|
|
881
|
+
args, options = command_line(argv, {:outfile=>true},
|
882
|
+
:help=> <<END_OF_HELP)
|
883
|
+
This command generates and outputs a script that assigns permissions within
|
884
|
+
a database instance. The permission information is read from the
|
885
|
+
permissions.yaml file in the schema root directory (the same directory in which
|
886
|
+
database.yaml resides) and has the format:
|
887
|
+
|
888
|
+
dbo.MyTable:
|
889
|
+
Alice: SELECT
|
890
|
+
Bob:
|
891
|
+
- SELECT
|
892
|
+
- INSERT
|
893
|
+
|
894
|
+
(More specifically: The top-level object is a mapping whose scalar keys are
|
895
|
+
the names of the objects to be modified and whose values are mappings from
|
896
|
+
security principals to either a single permission or a sequence of
|
897
|
+
permissions.) The file is in YAML format; use quoted strings if necessary
|
898
|
+
(e.g. for Microsoft SQL Server "square bracket escaping", enclose the name in
|
899
|
+
single or double quotes within the permissions.yaml file to avoid
|
900
|
+
interpretation of the square brackets as delimiting a sequence).
|
901
|
+
|
902
|
+
Before establishing the permissions listed in permissions.yaml, the generated
|
903
|
+
script first removes any permissions previously granted through use of an
|
904
|
+
XMigra permissions script. To accomplish this, the script establishes a table
|
905
|
+
if it does not yet exist. The code for this precedes the code to remove
|
906
|
+
previous permissions. Thus, the resulting script has the sequence:
|
907
|
+
|
908
|
+
- Establish permission tracking table (if not present)
|
909
|
+
- Revoke all previously granted permissions (only those granted
|
910
|
+
by a previous XMigra script)
|
911
|
+
- Grant permissions indicated in permissions.yaml
|
912
|
+
|
913
|
+
To facilitate review of the script, the term "GRANT" is avoided except for
|
914
|
+
the statements granting the permissions laid out in the source file.
|
915
|
+
END_OF_HELP
|
916
|
+
|
917
|
+
argument_error_unless(args.length == 0,
|
918
|
+
"'%prog %cmd' does not take any arguments.")
|
919
|
+
|
920
|
+
sql_gen = PermissionScriptWriter.new(options.source_dir).extend(WarnToStderr)
|
921
|
+
|
922
|
+
output_to(options.outfile) do |out_stream|
|
923
|
+
out_stream.print(sql_gen.permissions_sql)
|
924
|
+
end
|
925
|
+
end
|
926
|
+
end
|
927
|
+
end
|