xmigra 1.5.1 → 1.6.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.
@@ -79,6 +79,7 @@ module XMigra
79
79
  puts(" " * indent + line.chomp)
80
80
  end
81
81
  end
82
+ puts
82
83
  end
83
84
 
84
85
  def show_subcommands_as_help(_1=nil)
@@ -101,14 +102,30 @@ module XMigra
101
102
  flags.separator ''
102
103
  flags.separator 'Subcommand options:'
103
104
 
105
+ if use[:source_file_type]
106
+ options.source_file_type = nil
107
+ [
108
+ '--migration migration',
109
+ '--view view',
110
+ '--procedure stored procedure',
111
+ '--function user defined function',
112
+ '--index index',
113
+ ].each do |opt|
114
+ flag, desc = opt.split(' ', 2)
115
+ flags.on(flag, "Select #{desc} as the source file type") do
116
+ set_source_type(options, flag[2..-1].to_sym)
117
+ end
118
+ end
119
+ end
120
+
104
121
  if use[:target_type]
105
122
  options.target_type = :unspecified
106
123
  allowed = [:exact, :substring, :regexp]
107
124
  flags.on(
108
125
  "--by=TYPE", allowed,
109
126
  "Specify how TARGETs are matched",
110
- "against subject strings",
111
- "(#{allowed.collect {|i| i.to_s}.join(', ')})"
127
+ " against subject strings",
128
+ " (#{allowed.collect {|i| i.to_s}.join(', ')})"
112
129
  ) do |type|
113
130
  options.target_type = type
114
131
  end
@@ -129,7 +146,7 @@ VISUAL is preferred, then the one specified by EDITOR. If neither of these
129
146
  environment variables is set no editor will be opened.
130
147
  END_OF_HELP
131
148
  flags.on("--[no-]edit", "Open the resulting file in an editor",
132
- "(defaults to #{options.edit})") do |v|
149
+ " (defaults to #{options.edit})") do |v|
133
150
  options.edit = %w{EDITOR VISUAL}.any? {|k| ENV.has_key?(k)} && v
134
151
  end
135
152
  end
@@ -140,13 +157,22 @@ END_OF_HELP
140
157
  flags.on(
141
158
  "--match=SUBJECT", allowed,
142
159
  "Specify the type of subject against",
143
- "which TARGETs match",
144
- "(#{allowed.collect {|i| i.to_s}.join(', ')})"
160
+ " which TARGETs match",
161
+ " (#{allowed.collect {|i| i.to_s}.join(', ')})"
145
162
  ) do |type|
146
163
  options.search_type = type
147
164
  end
148
165
  end
149
166
 
167
+ if use[:permissions_too]
168
+ options.include_permissions = nil
169
+ flags.on("--[no-]grants-included",
170
+ "Include (or exclude) permission grants",
171
+ " (overrides setting in #{SchemaManipulator::DBINFO_FILE})") do |v|
172
+ options.include_permissions = v
173
+ end
174
+ end
175
+
150
176
  if use[:outfile]
151
177
  options.outfile = nil
152
178
  flags.on("-o", "--outfile=FILE", "Output to FILE") do |fpath|
@@ -163,12 +189,23 @@ END_OF_HELP
163
189
 
164
190
  if use[:dry_run]
165
191
  options.dry_run = false
166
- flags.on("--dry-run", "Generated script will test upgrade",
167
- " commands without committing changes") do
192
+ flags.on("--dry-run", "Generated script will test commands",
193
+ " without committing changes") do
168
194
  options.dry_run = true
169
195
  end
170
196
  end
171
197
 
198
+ if use[:impdecl_mode]
199
+ options.impdecl_special = {}
200
+ flags.on("--adopt", "New declarative adopts existing object") do
201
+ options.impdecl_special[:adopt] = true
202
+ end
203
+ flags.on("--renounce", "Deleted declarative releases object",
204
+ " from declarative management") do
205
+ options.impdecl_special[:renounce] = true
206
+ end
207
+ end
208
+
172
209
  options.source_dir = Dir.pwd
173
210
  flags.on("--source=DIR", "Work from/on the schema in DIR") do |dir|
174
211
  options.source_dir = File.expand_path(dir)
@@ -218,6 +255,14 @@ END_OF_HELP
218
255
  system(%Q{#{editor} "#{fpath}"})
219
256
  end
220
257
  end
258
+
259
+ def set_source_type(options, type)
260
+ argument_error_unless(
261
+ [nil, type].include?(options.source_file_type),
262
+ "Source type cannot be #{options.source_file_type} and #{type}"
263
+ )
264
+ options.source_file_type = type
265
+ end
221
266
  end
222
267
 
223
268
  subcommand 'overview', "Explain usage of this tool" do |argv|
@@ -275,6 +320,8 @@ END_SECTION
275
320
  +-- database.yaml
276
321
  +-- permissions.yaml (optional)
277
322
  +-- structure
323
+ | +-- declarative (optional)
324
+ | | +-- <declarative files>
278
325
  | +-- head.yaml
279
326
  | +-- <migration files>
280
327
  | ...
@@ -361,11 +408,24 @@ SCHEMA/structure/head.yaml to reference the newly generated file. It can also
361
408
  print a warning or run a command when the source condition indicates a database
362
409
  backup would be a good idea.
363
410
 
411
+ A small variation on this standard for migration files exists in files ending
412
+ with '.decl.yaml'. These files are "declarative change implementation"
413
+ migrations and have a few differences: they do not have a "changes" section,
414
+ but do have "does", "of object", and "to realize" sections. Each one of these
415
+ files is tied to some change (including creation or deletion) of a '.yaml'
416
+ file in the SCHEMA/structure/declarative subfolder.
417
+
364
418
  The SCHEMA/structure/head.yaml file deserves special note: it contains a
365
419
  reference to the last migration to be applied. Because of this, parallel
366
420
  development of database changes will cause conflicts in the contents of this
367
421
  file. This is by design, and '%program_cmd unbranch' will assist in resolving
368
- these conflicts.
422
+ these conflicts. Due to the relationship between declarative change
423
+ implementation migration files and their corresponding declarative file, if a
424
+ merge of two version control branches results in a three-way merge of the
425
+ declarative file, '%program_cmd unbranch' will modify the implementing
426
+ migration file; a modification to that migration file during a
427
+ '%program_cmd unbranch' was already a possibility, since the first migration
428
+ local to the branch will have its "starting from" section adjusted.
369
429
 
370
430
  Care must be taken when committing migration files to version control; because
371
431
  the structure of production databases will be determined by the chain of
@@ -381,6 +441,34 @@ or critical feature updates) to another released version which developed along
381
441
  a parallel track is generally a tricky endeavor. Please see the section on
382
442
  "branch upgrades" below for information on how %program_name supports this
383
443
  use case.
444
+ END_SECTION
445
+ end
446
+ begin; section['The "SCHEMA/structure/declarative" Folder', <<END_SECTION]
447
+
448
+ %program_name's canonical description of the database storage structures is encoded
449
+ in the migration files in SCHEMA/structure. The procedural nature of those
450
+ files, however, makes them less useful than is ideal for programmers wanting
451
+ information on the current storage structure of the database. %program_name's
452
+ structure/declarative system (with '.yaml' files stored in the
453
+ SCHEMA/structure/declarative folder) allows programmers to place a declaration
454
+ of the expected current state of a given database object in a file and get
455
+ assistance from XMigra in building the migration(s) that implement the
456
+ creation, revision, and destruction of that object.
457
+
458
+ When a '.yaml' file is added to, modified in, or removed from the
459
+ SCHEMA/structure/declarative folder, %program_name will not generate an upgrade script
460
+ until a matching migration is added to implement that change in the migration
461
+ chain. If this error happens, %program_name will print out a list of all files
462
+ with changes blocking the script generation, and '%program_cmd impdecl' can be
463
+ used to create the implementing migration (see '%program_cmd help impdecl' for
464
+ more information).
465
+
466
+ In some cases it may be desirable to bring a database object that was not
467
+ previously under declarative control into the declarative system without
468
+ effecting any change to the database; similarly, there may be conditions under
469
+ which it is preferable to take an object that is currently managed via the
470
+ declarative system out from that management without destroying it. %program_name
471
+ provides the '--adopt' and '--renounce' flags to support these use cases.
384
472
  END_SECTION
385
473
  end
386
474
  begin; section['The "SCHEMA/access" Folder', <<END_SECTION]
@@ -431,6 +519,11 @@ square brackets as a quotation mechanism, and square brackets have special
431
519
  meaning in YAML, so it is necessary use quoted strings or a scalar block to
432
520
  contain them). Any access objects listed in this way will be created before
433
521
  the referencing object.
522
+
523
+ Through use of the "--function", "--procedure", or "--view" flags, the
524
+ '%program_cmd new' command can be directed to generate a skeleton file for
525
+ this directory, with the option of opening the resulting file in an editor.
526
+ Please see '%program_cmd help new' for details.
434
527
  END_SECTION
435
528
  end
436
529
  begin; section['The "SCHEMA/indexes" Folder', <<END_SECTION]
@@ -454,6 +547,10 @@ Index definition files use only the "sql" section to provide the SQL definition
454
547
  of the index. Index definitions do not support use of the filename
455
548
  metavariable because renaming an index would cause it to be dropped and
456
549
  re-created.
550
+
551
+ As is the case for files in the SCHEMA/access folder, the skeleton for a new
552
+ index can be generated with the '%program_cmd new --index' command. Details
553
+ are available with '%program_cmd help new'.
457
554
  END_SECTION
458
555
  end
459
556
  begin; section['The "SCHEMA/database.yaml" File', <<END_SECTION]
@@ -475,7 +572,7 @@ supported values are:
475
572
  Each system can also have sub-settings that modify the generated scripts.
476
573
 
477
574
  Microsoft SQL Server:
478
- The "MSSQL 2005 compatible" setting in SCEMA/database.yaml, if set to
575
+ The "MSSQL 2005 compatible" setting in SCHEMA/database.yaml, if set to
479
576
  "true", causes INSERT statements to be generated in a more verbose and
480
577
  SQL Server 2005 compatible manner.
481
578
 
@@ -523,6 +620,13 @@ the plugin. While the resulting script will still (if possible) be transacted,
523
620
  the incompatibility may not be discovered until the script is run against a
524
621
  production database, requiring cancellation of deployment. Use this feature
525
622
  with extreme caution.
623
+
624
+ grants in upgrade
625
+ -----------------
626
+
627
+ This section optionally (defaulting to "false") specifies whether permission
628
+ management is included in generated upgrade scripts. The behavior specified by
629
+ this section may be overridden on the command line with an option.
526
630
  END_SECTION
527
631
  end
528
632
  begin; section['Script Generation Modes', <<END_SECTION]
@@ -674,14 +778,29 @@ END_OF_HELP
674
778
  end
675
779
  end
676
780
 
677
- subcommand 'new', "Create a new migration file" do |argv|
678
- args, options = command_line(argv, {:edit=>true},
679
- :argument_desc=>"MIGRATION_SUMMARY",
781
+ subcommand 'new', "Create a new source file" do |argv|
782
+ args, options = command_line(argv, {:edit=>true, :source_file_type=>true},
783
+ :argument_desc=>"MIGRATION_SUMMARY_OR_NAME",
680
784
  :help=> <<END_OF_HELP)
681
- This command generates a new migration file and ties it into the current
682
- migration chain. The name of the new file is generated from today's date and
683
- the given MIGRATION_SUMMARY. The resulting new file may be opened in an
684
- editor (see the --[no-]edit option).
785
+ This command generates a new XMigra source file. It can create a migration
786
+ file or a view, stored procedure, user defined function, or index definition
787
+ file, with the the default being a migration file.
788
+
789
+ When generating a migration file, MIGRATION_SUMMARY_OR_NAME is taken as a
790
+ brief summary of the migration and used, along with today's date, to generate
791
+ a name for the migration file.
792
+
793
+ When index definition file generation is selected, MIGRATION_SUMMARY_OR_NAME
794
+ is taken as the name of the index to create, and an index file with this
795
+ name and skeleton content will be created.
796
+
797
+ Otherwise, MIGRATION_SUMMARY_OR_NAME will be taken as the name of the access
798
+ artifact to create, and the file will be named for it and appropriate skeleton
799
+ content will be generated. This command may fail with an error for source
800
+ file types not supported by the database system declared in database.yaml.
801
+
802
+ In all cases, the resulting new file may be opened in an editor (see the
803
+ --[no-]edit option).
685
804
 
686
805
  If this command extends the production migration chain, it will attempt to
687
806
  locate a handler function ("on-prod-chain-extended-local", a VCS-specified
@@ -696,20 +815,108 @@ END_OF_HELP
696
815
 
697
816
  argument_error_unless(args.length == 1,
698
817
  "'%prog %cmd' takes one argument.")
699
- migration_summary = args[0]
818
+ file_type = options.source_file_type || :migration
819
+ if file_type == :migration
820
+ tool_spec = [NewMigrationAdder, :add_migration]
821
+ arg_desc = "Migration summary"
822
+ elsif file_type == :index
823
+ tool_spec = [NewIndexAdder, :add_index]
824
+ arg_desc = "Index name"
825
+ else
826
+ tool_spec = [NewAccessArtifactAdder, :add_artifact, true]
827
+ arg_desc = "Access artifact name"
828
+ end
829
+
700
830
  argument_error_unless(
701
- migration_summary.chars.all? {|c| !ILLEGAL_FILENAME_CHARS.include?(c)},
702
- "Migration summary may not contain any of: " + ILLEGAL_FILENAME_CHARS
831
+ args[0].chars.all? {|c| !ILLEGAL_FILENAME_CHARS.include?(c)},
832
+ arg_desc + " may not contain any of: " + ILLEGAL_FILENAME_CHARS
703
833
  )
704
834
 
705
- tool = NewMigrationAdder.new(options.source_dir).extend(WarnToStderr)
706
- new_fpath = tool.add_migration(migration_summary)
835
+ tool = tool_spec[0].new(options.source_dir).extend(WarnToStderr)
836
+ create_proc = tool.method(tool_spec[1])
837
+ new_fpath = begin
838
+ if tool_spec[2]
839
+ create_proc.call(file_type, args[0])
840
+ else
841
+ create_proc.call(args[0])
842
+ end
843
+ end
844
+ edit(new_fpath) if options.edit
845
+ end
846
+
847
+ subcommand 'decldoc', "Get help on supported structure/declarative syntax" do |argv|
848
+ args, options = command_line(argv, {},
849
+ :argument_desc=>"[TAG]",
850
+ :help=> <<END_OF_HELP)
851
+ Use this command to list supported top level tags in structure/declarative YAML
852
+ files or, by giving a TAG, to get the supported data schema for one of the
853
+ tags.
854
+ END_OF_HELP
855
+ argument_error_unless((0..1).include?(args.length),
856
+ "'%prog $cmd' takes zero arguments or one argument.")
857
+ tag = args[0]
858
+ if tag.nil?
859
+ # Print a list of available tags
860
+ puts
861
+ puts "Supported structure/declarative tags:"
862
+ puts
863
+ ImpdeclMigrationAdder.each_support_type do |tag, klass|
864
+ next unless klass.respond_to?(:decldoc)
865
+ puts " #{tag}"
866
+ end
867
+ puts
868
+ else
869
+ # Try to get the class associated with the tag and see if it provides
870
+ # documentation
871
+ impl_class = ImpdeclMigrationAdder.support_type(tag)
872
+ argument_error_unless(
873
+ !impl_class.nil?,
874
+ "#{tag} is not a supported tag for structure/declarative"
875
+ )
876
+ docmethod = begin
877
+ impl_class.method :decldoc
878
+ rescue
879
+ raise ArgumentError, "#{tag} is supported but undocumented"
880
+ end
881
+ puts
882
+ puts docmethod.call
883
+ puts
884
+ end
885
+ end
886
+
887
+ subcommand 'impdecl', "Implement an outstanding structure/declarative change" do |argv|
888
+ args, options = command_line(argv, {:edit=>true, :impdecl_mode=>true},
889
+ :argument_desc=>"DECLARATIVE_PATH",
890
+ :help=> <<END_OF_HELP)
891
+ When a new declarative file has been added, a declarative file has been
892
+ removed, or a declarative file has been modified, running this command and
893
+ specifying the path to that declarative file will add a new migration file
894
+ at the end of the migration chain with a special setup that links it to the
895
+ declarative file and the declarative file processing subsystem.
896
+
897
+ The skeleton file generated by this command may have an
898
+ "#{DeclarativeMigration::QUALIFICATION_KEY}" key, which will prevent this
899
+ tool from generating an upgrade script. Remove this key when the SQL for
900
+ implementing this change is added or verified.
901
+
902
+ The resulting new file may be opened in an editor (see the --[no-]edit
903
+ option).
904
+
905
+ As this command creates a new migration file in much the way the
906
+ 'new --migration' command would, the explanation of production-chain-extension
907
+ hooks given in 'new --help' also applies to this command.
908
+ END_OF_HELP
707
909
 
910
+ argument_error_unless(args.length == 1,
911
+ "'%prog $cmd' takes one argument.")
912
+ file_path = Pathname(args[0])
913
+ tool = ImpdeclMigrationAdder.new(options.source_dir)
914
+ new_fpath = tool.add_migration_implementing_changes(file_path, options.impdecl_special)
708
915
  edit(new_fpath) if options.edit
709
916
  end
710
917
 
711
918
  subcommand 'upgrade', "Generate an upgrade script" do |argv|
712
- args, options = command_line(argv, {:production=>true, :outfile=>true, :dry_run=>true},
919
+ args, options = command_line(argv, {:production=>true, :outfile=>true, :dry_run=>true, :permissions_too=>true},
713
920
  :help=> <<END_OF_HELP)
714
921
  Running this command will generate an update script from the source schema.
715
922
  Generation of a production script involves more checks on the status of the
@@ -726,6 +933,7 @@ END_OF_HELP
726
933
  sql_gen.load_plugin!
727
934
  sql_gen.production = options.production
728
935
  sql_gen.dry_run = options.dry_run
936
+ sql_gen.include_grants = options.include_permissions
729
937
 
730
938
  output_to(options.outfile) do |out_stream|
731
939
  out_stream.print(sql_gen.update_sql)
@@ -849,7 +1057,7 @@ The target working copy is neither marked for production branch recognition
849
1057
  nor was the --dev-branch option given on the command line. Because fixing the
850
1058
  branched migration chain would require modifying a committed migration, this
851
1059
  operation would result in a migration chain incapable of producing a production
852
- upgrage which, because the usage of the working copy's branch is ambiguous,
1060
+ upgrade which, because the usage of the working copy's branch is ambiguous,
853
1061
  might leave this branch unable to fulfill its purpose.
854
1062
 
855
1063
  END_OF_MESSAGE
@@ -38,7 +38,8 @@ RUNNING THIS SCRIPT ON A PRODUCTION DATABASE WILL FAIL.
38
38
  ))
39
39
  @file_based_groups << (@migrations = MigrationChain.new(
40
40
  @path.join(STRUCTURE_SUBDIR),
41
- :db_specifics=>@db_specifics
41
+ :db_specifics=>@db_specifics,
42
+ :vcs_specifics=>@vcs_specifics,
42
43
  ))
43
44
 
44
45
  @branch_upgrade = BranchUpgrade.new(branch_upgrade_file)
@@ -51,9 +52,10 @@ RUNNING THIS SCRIPT ON A PRODUCTION DATABASE WILL FAIL.
51
52
 
52
53
  @production = false
53
54
  @dry_run = false
55
+ @include_grants = nil
54
56
  end
55
57
 
56
- attr_accessor :production, :dry_run
58
+ attr_accessor :production, :dry_run, :include_grants
57
59
  attr_reader :migrations, :access_artifacts, :indexes, :branch_upgrade
58
60
 
59
61
  def inspect
@@ -81,6 +83,7 @@ RUNNING THIS SCRIPT ON A PRODUCTION DATABASE WILL FAIL.
81
83
  end
82
84
 
83
85
  check_working_copy!
86
+ migrations.check_declaratives_current!
84
87
 
85
88
  intro_comment = @db_info.fetch('script comment', '')
86
89
  if Plugin.active
@@ -143,11 +146,15 @@ RUNNING THIS SCRIPT ON A PRODUCTION DATABASE WILL FAIL.
143
146
 
144
147
  # Create any desired indexes that don't yet exist
145
148
  :create_new_indexes_sql,
146
-
147
- # Any cleanup needed
148
- :upgrade_cleanup_sql,
149
149
  ]
150
150
 
151
+ if include_grants_in_upgrade?
152
+ script_parts << :grant_access_sql
153
+ end
154
+
155
+ # Any cleanup needed
156
+ script_parts << :upgrade_cleanup_sql
157
+
151
158
  amend_script_parts(script_parts)
152
159
 
153
160
  script_parts.map {|mn| self.send(mn)}.flatten.compact.join(ddl_block_separator).tap do |result|
@@ -183,6 +190,17 @@ RUNNING THIS SCRIPT ON A PRODUCTION DATABASE WILL FAIL.
183
190
  def branch_upgrade_sql
184
191
  end
185
192
 
193
+ def grant_access_sql
194
+ sql_gen = PermissionScriptWriter.new(path)
195
+ if respond_to?(:warning)
196
+ updater = self
197
+ sql_gen.define_singleton_method(:warning) do |message|
198
+ updater.warning(message)
199
+ end
200
+ end
201
+ return sql_gen.permissions_sql(:transactional => false)
202
+ end
203
+
186
204
  def upgrade_cleanup_sql
187
205
  end
188
206
 
@@ -194,5 +212,10 @@ RUNNING THIS SCRIPT ON A PRODUCTION DATABASE WILL FAIL.
194
212
  group.each {|item| yield item.file_path}
195
213
  end
196
214
  end
215
+
216
+ def include_grants_in_upgrade?
217
+ return @include_grants unless @include_grants.nil?
218
+ return @db_info.fetch('grants in upgrade', false)
219
+ end
197
220
  end
198
221
  end