xmigra 1.3.1 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ed23f4357ce987546bcfd0cd391281647c71af9f
4
- data.tar.gz: 681a6faf3bae62f19913d3554eec2c1800d31667
3
+ metadata.gz: 87e678fcb7c1e98cff100798bd30948628becbcb
4
+ data.tar.gz: d89d555130e4e53e28f449f1cfdcd5ded31bf942
5
5
  SHA512:
6
- metadata.gz: a296f86a8e0cd4c0f41ad2d0bec9f354186d90b9504b85d83f0f0221ee506a0b41eba47a8fa48b2eb1c6414eea092c66551416b3ec67f55995eaa3e3dfe9346f
7
- data.tar.gz: 546d429aa2b0acfb39b75416e42701f2c8355145d7dd22daa2abf511f0c3778f546feb331fcba24e3a0a13d17d68924df93a3fe9e0a835f3b3710b40f2e2200f
6
+ metadata.gz: e35d004c20dc96dfd27418281ce19ded3b1c639d8aa7cb3591ba713c29b3c1246a4389ef63801a270b6409cfef22968f6d0eaa97f71ca2db574b35eedf99b933
7
+ data.tar.gz: ade9c348aacb41f7a81aa669c60511076fbf20b5ea0464f129473e2a859b0dfd87a558b979910eb88104550d0b32e9f8c3cc9ff683f11055bf282ed8006674f8
@@ -1,3 +1,4 @@
1
+ require 'xmigra/plugin'
1
2
 
2
3
  module XMigra
3
4
  class AccessArtifact
@@ -26,10 +27,20 @@ module XMigra
26
27
  end
27
28
 
28
29
  def creation_sql
29
- if metavar = filename_metavariable
30
- @definition.gsub(metavar) {|m| self.name}
30
+ raw = begin
31
+ if metavar = filename_metavariable
32
+ @definition.gsub(metavar) {|m| self.name}
33
+ else
34
+ @definition
35
+ end
36
+ end
37
+
38
+ if Plugin.active
39
+ raw.dup.tap do |sql|
40
+ Plugin.active.amend_source_sql(sql)
41
+ end
31
42
  else
32
- @definition
43
+ raw
33
44
  end
34
45
  end
35
46
 
@@ -1,5 +1,6 @@
1
1
 
2
2
  require "tsort"
3
+ require 'xmigra/plugin'
3
4
 
4
5
  module XMigra
5
6
  class AccessArtifactCollection
@@ -10,9 +11,21 @@ module XMigra
10
11
  filename_metavariable = filename_metavariable.dup.freeze if filename_metavariable
11
12
 
12
13
  XMigra.each_access_artifact(path) do |artifact|
13
- @items[artifact.name] = artifact
14
14
  artifact.extend(db_specifics) if db_specifics
15
15
  artifact.filename_metavariable = filename_metavariable
16
+
17
+ if Plugin.active
18
+ next unless Plugin.active.include_access_artifact?(artifact)
19
+ Plugin.active.amend_access_artifact(artifact)
20
+ end
21
+
22
+ @items[artifact.name] = artifact
23
+ end
24
+
25
+ if Plugin.active
26
+ Plugin.active.each_additional_access_artifact(db_specifics) do |artifact|
27
+ @items[artifact.name] = artifact
28
+ end
16
29
  end
17
30
  end
18
31
 
@@ -1,5 +1,6 @@
1
1
 
2
2
  require 'xmigra/migration'
3
+ require 'xmigra/plugin'
3
4
 
4
5
  module XMigra
5
6
  class BranchUpgrade
@@ -27,7 +28,7 @@ module XMigra
27
28
  @sql = verinc_info['sql']
28
29
  end
29
30
 
30
- attr_reader :file_path, :base_migration, :target_branch, :migration_completed, :sql
31
+ attr_reader :file_path, :base_migration, :target_branch, :migration_completed
31
32
 
32
33
  def found?
33
34
  @found
@@ -48,6 +49,16 @@ module XMigra
48
49
  @warnings.dup
49
50
  end
50
51
 
52
+ def sql
53
+ if Plugin.active
54
+ @sql.dup.tap do |result|
55
+ Plugin.active.amend_source_sql(result)
56
+ end
57
+ else
58
+ @sql
59
+ end
60
+ end
61
+
51
62
  def migration_completed_id
52
63
  Migration.id_from_filename(XMigra.yaml_path(migration_completed))
53
64
  end
@@ -0,0 +1,115 @@
1
+ module XMigra
2
+ module Console
3
+ class Menu
4
+ def initialize(title, options, prompt, opts={})
5
+ @title = title
6
+ @prompt = prompt
7
+ @title_width = opts[:title_width] || 40
8
+ @title_rule = opts[:title_rule] || '='
9
+ @trailing_newlines = opts[:trailing_newlines] || 3
10
+ get_name = opts[:get_name] || lambda {|o| o.to_s}
11
+ @name_map = {}
12
+ @menu_map = {}
13
+ options.each_with_index do |opt, i|
14
+ opt_name = get_name[opt]
15
+ @name_map[opt_name] = opt
16
+ @menu_map[i + 1] = opt_name
17
+ end
18
+ end
19
+
20
+ attr :title, :prompt, :title_width, :title_rule, :trailing_newlines
21
+
22
+ def show_once
23
+ Console.output_section(
24
+ title,
25
+ :trailing_newlines => @trailing_newlines
26
+ ) do
27
+ @menu_map.each_pair do |item_num, name|
28
+ puts "#{item_num.to_s.rjust(4)}. #{name}"
29
+ end
30
+ puts
31
+ print prompt + ': '
32
+
33
+ user_choice = $stdin.gets.strip
34
+ @menu_map[user_choice.to_i].tap do |mapped|
35
+ return mapped unless mapped.nil?
36
+ end
37
+ return user_choice if @menu_map.values.include? user_choice
38
+ by_prefix = @menu_map.values.select {|e| e.start_with? user_choice}
39
+ return by_prefix[0] if by_prefix.length == 1
40
+ end
41
+ return nil
42
+ end
43
+
44
+ def get_selection
45
+ loop do
46
+ selection = show_once
47
+ break unless selection.nil?
48
+ puts "That input did not uniquely identify one of the available options."
49
+ puts
50
+ end
51
+ @trailing_newlines.times {puts}
52
+ return @name_map[selection]
53
+ end
54
+ end
55
+
56
+ class InvalidInput < Exception
57
+ def initialize(msg=nil)
58
+ super(msg)
59
+ @explicit_message = !msg.nil?
60
+ end
61
+
62
+ def explicit_message?
63
+ @explicit_message
64
+ end
65
+ end
66
+
67
+ class <<self
68
+ def output_section(title=nil, opts={})
69
+ trailing_newlines = opts[:trailing_newlines] || 3
70
+
71
+ if title
72
+ puts " #{title} ".center(40, '=')
73
+ puts
74
+ end
75
+
76
+ (yield).tap do
77
+ trailing_newlines.times {puts}
78
+ end
79
+ end
80
+
81
+ def validated_input(prompt)
82
+ loop do
83
+ print prompt + ": "
84
+ input_value = $stdin.gets.strip
85
+
86
+ result = begin
87
+ yield input_value
88
+ rescue InvalidInput => e
89
+ puts e.message if e.explicit_message?
90
+ next
91
+ end
92
+
93
+ return result unless result.nil?
94
+ end
95
+ end
96
+
97
+ def yes_no(prompt, default_value)
98
+ input_options = ""
99
+ input_options << (default_value == :yes ? "Y" : "y")
100
+ input_options << (default_value == :no ? "N" : "n")
101
+
102
+ validated_input("#{prompt} [#{input_options}]") do |input_value|
103
+ case input_value
104
+ when /^y(es)?$/io
105
+ true
106
+ when /^n(o)?$/io
107
+ false
108
+ when ''
109
+ {:yes => true, :no => false}[default_value]
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
@@ -1,3 +1,4 @@
1
+ require 'xmigra/console'
1
2
 
2
3
  module XMigra
3
4
  module MSSQLSpecifics
@@ -1166,6 +1167,15 @@ INSERT INTO [xmigra].[branch_upgrade] ([Current]) VALUES (#{branch_id_literal});
1166
1167
  def string_literal(s)
1167
1168
  "N'#{s.gsub("'","''")}'"
1168
1169
  end
1170
+
1171
+ def init_schema(schema_config)
1172
+ Console.output_section "Microsoft SQL Server Specifics" do
1173
+ if Console.yes_no("Use more verbose syntax compatible with SQL Server 2005", :no)
1174
+ schema_config.dbinfo["MSSQL 2005 compatible"] = true
1175
+ puts "Configured for SQL Server 2005 compatibility mode."
1176
+ end
1177
+ end
1178
+ end
1169
1179
  end
1170
1180
  end
1171
1181
  end
data/lib/xmigra/index.rb CHANGED
@@ -1,3 +1,4 @@
1
+ require 'xmigra/plugin'
1
2
 
2
3
  module XMigra
3
4
  class Index
@@ -15,7 +16,13 @@ module XMigra
15
16
  end
16
17
 
17
18
  def definition_sql
18
- @definition
19
+ if Plugin.active
20
+ @definition.dup.tap do |sql|
21
+ Plugin.active.amend_source_sql(sql)
22
+ end
23
+ else
24
+ @definition
25
+ end
19
26
  end
20
27
  end
21
28
  end
@@ -1,5 +1,6 @@
1
1
 
2
2
  require 'xmigra/index'
3
+ require 'xmigra/plugin'
3
4
 
4
5
  module XMigra
5
6
  class IndexCollection
@@ -12,8 +13,20 @@ module XMigra
12
13
  index = Index.new(info)
13
14
  index.extend(db_specifics) if db_specifics
14
15
  index.file_path = File.expand_path(fpath)
16
+
17
+ if Plugin.active
18
+ next unless Plugin.active.include_index?(index)
19
+ Plugin.active.amend_index(index)
20
+ end
21
+
15
22
  @items[index.name] = index
16
23
  end
24
+
25
+ if Plugin.active
26
+ Plugin.active.each_additional_index(db_specifics) do |index|
27
+ @items[index.name] = index
28
+ end
29
+ end
17
30
  end
18
31
 
19
32
  def [](name)
@@ -1,4 +1,5 @@
1
1
  require 'pathname'
2
+ require 'xmigra/plugin'
2
3
  require 'xmigra/revert_file'
3
4
 
4
5
  module XMigra
@@ -17,13 +18,23 @@ module XMigra
17
18
  @changes.each {|c| c.freeze}
18
19
  end
19
20
 
20
- attr_reader :id, :follows, :sql, :description, :changes
21
+ attr_reader :id, :follows, :description, :changes
21
22
  attr_accessor :file_path
22
23
 
23
24
  def schema_dir
24
25
  Pathname(file_path).dirname.join('..')
25
26
  end
26
27
 
28
+ def sql
29
+ if Plugin.active
30
+ @sql.dup.tap do |result|
31
+ Plugin.active.amend_source_sql(result)
32
+ end
33
+ else
34
+ @sql
35
+ end
36
+ end
37
+
27
38
  def reversion
28
39
  result = RevertFile.new(self)
29
40
  return result if result.exist?
@@ -1,4 +1,5 @@
1
1
 
2
+ require 'xmigra/plugin'
2
3
  require 'xmigra/schema_manipulator'
3
4
 
4
5
  module XMigra
@@ -35,7 +36,9 @@ module XMigra
35
36
  # Grant the permissions indicated in the source file
36
37
  grant_specified_permissions_sql,
37
38
 
38
- ].flatten.compact.join(ddl_block_separator)
39
+ ].flatten.compact.join(ddl_block_separator).tap do |result|
40
+ Plugin.active.amend_composed_sql(result) if Plugin.active
41
+ end
39
42
  end
40
43
  end
41
44
 
@@ -0,0 +1,148 @@
1
+ module XMigra
2
+
3
+ # Base class for XMigra plugins.
4
+ #
5
+ # Derive a class from this class, then call XMigra::Plugin.activate! with
6
+ # a block that instantiates your class.
7
+ #
8
+ # require "xmigra/plugin"
9
+ #
10
+ # class YearTemplatePlugin < XMigra::Plugin
11
+ # def amend_composed_sql(sql)
12
+ # sql.gsub! '[{year}]', Date.today.year.to_s
13
+ # end
14
+ # end
15
+ #
16
+ # XMigra::Plugin.activate! {YearTemplatePlugin.new}
17
+ #
18
+ # The last call to XMigra::Plugin.activate! will determine which block will
19
+ # be executed to return the active plugin, so make sure to +require+ any
20
+ # plugins to be aggregated before activating your own.
21
+
22
+ class Plugin
23
+ class LoadingError < ::LoadError; end
24
+
25
+ class <<self
26
+ attr_reader :active
27
+
28
+ def loading?
29
+ !!@load_depth
30
+ end
31
+
32
+ def load!(name)
33
+ previous_depth, @load_depth = @load_depth, (@load_depth || 0) + 1
34
+ @activation = nil if previous_depth.nil?
35
+ begin
36
+ require name
37
+ rescue ::LoadError => error
38
+ if previous_depth.nil? && error.path == name
39
+ raise LoadingError, "The XMigra plugin #{name.inspect} is not installed (Kernel#require failed)."
40
+ else
41
+ raise
42
+ end
43
+ ensure
44
+ @load_depth = previous_depth
45
+ end
46
+
47
+ if previous_depth.nil? && @activation
48
+ @active = @activation.call
49
+ end
50
+ end
51
+
52
+ def activate!(&blk)
53
+ @activation = blk
54
+ end
55
+ end
56
+
57
+
58
+ # Amend SQL coming from source documents. The String object passed to
59
+ # this method will be included in the commands to run, and any
60
+ # modifications made to this object will be reflected in the script.
61
+ # XMigra only calls this method to amend SQL read in from source files.
62
+ #
63
+ # The default implementation does nothing.
64
+
65
+ def amend_source_sql(sql)
66
+ end
67
+
68
+
69
+ # Amend SQL for script after all parts are combined. This method will
70
+ # have an opportunity to amend all of the SQL in the output script,
71
+ # including SQL generated by XMigra.
72
+ #
73
+ # XMigra only calls this method one time for the entire output script
74
+ # _prior_ to any transformation necessary for script transactionality
75
+ # (e.g. splitting the script into batches and encoding as string
76
+ # literals). The one exception to this is for any branch upgrade SQL,
77
+ # which will already be encoded in one or more string literals.
78
+ #
79
+ # The default implementation does nothing.
80
+
81
+ def amend_composed_sql(sql)
82
+ end
83
+
84
+
85
+ # Indicate if the access artifact (stored procedure, user defined function,
86
+ # or view) should be included in the logical schema.
87
+ #
88
+ # The default implementation always returns +true+.
89
+
90
+ def include_access_artifact?(artifact)
91
+ true
92
+ end
93
+
94
+
95
+ # Amend each included access artifact.
96
+ #
97
+ # _artifact_ - an XMigra::StoredProcedure, XMigra::Function, or XMigra::View
98
+ #
99
+ # The default implementation does nothing.
100
+
101
+ def amend_access_artifact(artifact)
102
+ end
103
+
104
+
105
+ # Yields additional access artifacts to include in the logical schema.
106
+ #
107
+ # _db_specifics_ - A module providing methods useful for building SQL
108
+ # specific to the target RDBMS.
109
+ #
110
+ # The yielded artifact objects must respond to +name+, +depends_on+ and
111
+ # +definition_sql+.
112
+ #
113
+ # The default implementation does not yield any objects.
114
+
115
+ def each_additional_access_artifact(db_specifics=nil) # :yields: artifact
116
+ end
117
+
118
+
119
+ # Indicate if the index should be included in the logical schema.
120
+ #
121
+ # The default implementation always return +true+.
122
+
123
+ def include_index?(index)
124
+ true
125
+ end
126
+
127
+
128
+ # Amend each included index.
129
+ #
130
+ # The default implementation does nothing.
131
+
132
+ def amend_index(index)
133
+ end
134
+
135
+
136
+ # Yields additional indexes to include in the logical schema.
137
+ #
138
+ # _db_specifics_ - A module providing methods useful for building SQL
139
+ # specific to the target RDBMS.
140
+ #
141
+ # The yielded index objects must respond to +name+ and +definition_sql+.
142
+ #
143
+ # The default implementation does not yield any objects.
144
+
145
+ def each_additional_index(db_specifics=nil) # :yields: index
146
+ end
147
+ end
148
+ end
@@ -42,6 +42,9 @@ module XMigra
42
42
  end
43
43
  rescue TerminatingOption => stop
44
44
  return stop
45
+ rescue Plugin::LoadingError => error
46
+ $stderr.puts error.message
47
+ exit 1
45
48
  end
46
49
  ensure
47
50
  @active_subcommand = prev_subcommand
@@ -446,7 +449,7 @@ END_SECTION
446
449
  begin; section['The "SCHEMA/database.yaml" File', <<END_SECTION]
447
450
 
448
451
  The SCHEMA/database.yaml file consists of several sections that provide general
449
- information about the database schema. The following subsection detail some
452
+ information about the database schema. The following subsections detail some
450
453
  contents that may be included in this file.
451
454
 
452
455
  system
@@ -491,6 +494,25 @@ metavariable that is used for access object definitions. The default value
491
494
  is "[{filename}]" (excluding the quotation marks). If that string is required
492
495
  in one or more access object definitions, this section allows the schema to
493
496
  dictate another value.
497
+
498
+ XMigra plugin
499
+ -------------
500
+
501
+ If given, this section/entry provides a name to require into the XMigra
502
+ program (see documentation on Ruby's Kernel#require), with the intention that
503
+ the required file will define and activate an instance of a subclass of
504
+ XMigra::Plugin (see the documentation for XMigra::Plugin or
505
+ lib/xmigra/plugin.rb). Only one plugin may be specified, though that one
506
+ plugin may aggregate the functionality of other plugins.
507
+
508
+ Plugins are an advanced feature that can defeat many of the measures %program_name
509
+ takes to guarantee that a database generated from scratch will go through the
510
+ same sequence of changes as the production database(s) has/have. This can
511
+ happen even unintentionally, for instance by upgrading the gem that provides
512
+ the plugin. While the resulting script will still (if possible) be transacted,
513
+ the incompatibility may not be discovered until the script is run against a
514
+ production database, requiring cancellation of deployment. Use this feature
515
+ with extreme caution.
494
516
  END_SECTION
495
517
  end
496
518
  begin; section['Script Generation Modes', <<END_SECTION]
@@ -626,6 +648,22 @@ END_SECTION
626
648
  puts
627
649
  end
628
650
 
651
+ subcommand 'init', "Interactively set up a source filesystem subtree" do |argv|
652
+ args, options = command_line(argv, {:edit=>true},
653
+ :help=> <<END_OF_HELP)
654
+ This command interactively asks for and records the information needed to set
655
+ up a filesystem subtree as a source for generating scripts.
656
+ END_OF_HELP
657
+
658
+ tool = SourceTreeInitializer.new(options.source_dir).extend(WarnToStderr)
659
+
660
+ file_paths = tool.create_files!
661
+
662
+ if options.edit
663
+ file_paths.each {|fpath| edit(fpath)}
664
+ end
665
+ end
666
+
629
667
  subcommand 'new', "Create a new migration file" do |argv|
630
668
  args, options = command_line(argv, {:edit=>true},
631
669
  :argument_desc=>"MIGRATION_SUMMARY",
@@ -665,6 +703,7 @@ END_OF_HELP
665
703
  "'%prog %cmd' does not take any arguments.")
666
704
 
667
705
  sql_gen = SchemaUpdater.new(options.source_dir).extend(WarnToStderr)
706
+ sql_gen.load_plugin!
668
707
  sql_gen.production = options.production
669
708
 
670
709
  output_to(options.outfile) do |out_stream|
@@ -696,6 +735,7 @@ END_OF_HELP
696
735
  "'%prog %cmd' does not take any arguments.")
697
736
 
698
737
  sql_gen = SchemaUpdater.new(options.source_dir).extend(WarnToStderr)
738
+ sql_gen.load_plugin!
699
739
 
700
740
  output_to(options.outfile) do |out_stream|
701
741
  out_stream.print(sql_gen.reversion_script)
@@ -717,6 +757,7 @@ END_OF_HELP
717
757
  "'%prog %cmd' must target an existing access object definition.")
718
758
 
719
759
  sql_gen = SchemaUpdater.new(options.source_dir).extend(WarnToStderr)
760
+ sql_gen.load_plugin!
720
761
 
721
762
  artifact = sql_gen.access_artifacts[args[0]] || sql_gen.access_artifacts.at_path(args[0])
722
763
  output_to(options.outfile) do |out_stream|
@@ -895,6 +936,7 @@ END_OF_HELP
895
936
  end
896
937
 
897
938
  tool = SchemaUpdater.new(options.source_dir).extend(WarnToStderr)
939
+ tool.load_plugin!
898
940
 
899
941
  output_to(options.outfile) do |out_stream|
900
942
  tool.migrations.each do |migration|
@@ -949,6 +991,7 @@ END_OF_HELP
949
991
  "'%prog %cmd' does not take any arguments.")
950
992
 
951
993
  sql_gen = PermissionScriptWriter.new(options.source_dir).extend(WarnToStderr)
994
+ sql_gen.load_plugin!
952
995
 
953
996
  output_to(options.outfile) do |out_stream|
954
997
  out_stream.print(sql_gen.permissions_sql)
@@ -1,4 +1,6 @@
1
1
 
2
+ require 'xmigra/plugin'
3
+
2
4
  module XMigra
3
5
  module ReversionScriptBuilding
4
6
  # This module is intended to be included into XMigra::SchemaUpdater
@@ -33,8 +35,12 @@ module XMigra
33
35
  "database.\n",
34
36
  ].collect {|l| '-- ' + l + "\n"}.join('')
35
37
 
36
- return usage_note + "========================================\n" + \
37
- reversions.join("-- ================================== --\n")
38
+ "".tap do |result|
39
+ result << usage_note + "========================================\n"
40
+ result << reversions.join("-- ================================== --\n")
41
+
42
+ Plugin.active.amend_composed_sql(result) if Plugin.active
43
+ end
38
44
  end
39
45
  end
40
46
  end
@@ -8,6 +8,8 @@ module XMigra
8
8
  STRUCTURE_SUBDIR = 'structure'
9
9
  VERINC_FILE = 'branch-upgrade.yaml'
10
10
 
11
+ PLUGIN_KEY = 'XMigra plugin'
12
+
11
13
  def initialize(path)
12
14
  @path = Pathname.new(path)
13
15
  @db_info = YAML.load_file(@path + DBINFO_FILE)
@@ -28,12 +30,20 @@ module XMigra
28
30
  m.manages(path)
29
31
  } || NoSpecifics
30
32
  )
33
+
34
+ if @db_info.has_key? PLUGIN_KEY
35
+ @plugin = @db_info[PLUGIN_KEY]
36
+ end
31
37
  end
32
38
 
33
- attr_reader :path
39
+ attr_reader :path, :plugin
34
40
 
35
41
  def branch_upgrade_file
36
42
  @path.join(STRUCTURE_SUBDIR, VERINC_FILE)
37
43
  end
44
+
45
+ def load_plugin!
46
+ Plugin.load! plugin if plugin
47
+ end
38
48
  end
39
49
  end
@@ -1,4 +1,5 @@
1
1
 
2
+ require 'xmigra/plugin'
2
3
  require 'xmigra/schema_manipulator'
3
4
  require 'xmigra/reversion_script_building'
4
5
 
@@ -77,6 +78,10 @@ RUNNING THIS SCRIPT ON A PRODUCTION DATABASE WILL FAIL.
77
78
  check_working_copy!
78
79
 
79
80
  intro_comment = @db_info.fetch('script comment', '')
81
+ if Plugin.active
82
+ intro_comment = intro_comment.dup
83
+ Plugin.active.amend_source_sql(intro_comment)
84
+ end
80
85
  intro_comment << if production
81
86
  sql_comment_block(vcs_information || "")
82
87
  else
@@ -140,7 +145,9 @@ RUNNING THIS SCRIPT ON A PRODUCTION DATABASE WILL FAIL.
140
145
 
141
146
  amend_script_parts(script_parts)
142
147
 
143
- script_parts.map {|mn| self.send(mn)}.flatten.compact.join(ddl_block_separator)
148
+ script_parts.map {|mn| self.send(mn)}.flatten.compact.join(ddl_block_separator).tap do |result|
149
+ Plugin.active.amend_composed_sql(result) if Plugin.active
150
+ end
144
151
  end
145
152
  end
146
153
 
@@ -0,0 +1,106 @@
1
+ require 'pathname'
2
+ require 'xmigra/console'
3
+ require 'xmigra/schema_manipulator'
4
+
5
+ module XMigra
6
+ class SourceTreeInitializer
7
+ class ConfigInfo
8
+ def initialize(root_path)
9
+ @root_path = Pathname(root_path)
10
+ @dbinfo = {}
11
+ @steps_after_dbinfo_creation = []
12
+ @created_files = []
13
+ end
14
+
15
+ attr_reader :root_path, :dbinfo
16
+
17
+ def created_files
18
+ @created_files.dup
19
+ end
20
+
21
+ def created_file!(fpath)
22
+ @created_files << Pathname(fpath)
23
+ end
24
+
25
+ def after_dbinfo_creation(&blk)
26
+ @steps_after_dbinfo_creation << blk
27
+ end
28
+
29
+ def run_steps_after_dbinfo_creation!
30
+ @steps_after_dbinfo_creation.each do |step|
31
+ step.call
32
+ end
33
+ end
34
+ end
35
+
36
+ def initialize(root_path)
37
+ @root_path = Pathname.new(root_path)
38
+ end
39
+
40
+ def dbinfo_path
41
+ @root_path + SchemaManipulator::DBINFO_FILE
42
+ end
43
+
44
+ def get_user_input_block(input_type)
45
+ puts "Input ends on a line containing nothing but a single '.'."
46
+ "".tap do |result|
47
+ while (line = $stdin.gets).strip != '.'
48
+ result << line
49
+ end
50
+ end
51
+ end
52
+
53
+ def vcs_system
54
+ @vcs_system ||= VersionControlSupportModules.find do |m|
55
+ m.manages(@root_path)
56
+ end
57
+ end
58
+
59
+ def create_files!
60
+ schema_config = ConfigInfo.new(@root_path)
61
+
62
+ if vcs_system.nil?
63
+ puts "The indicated folder is not under version control. Some features"
64
+ puts "of this system require version control for full functionality. If"
65
+ puts "you later decide to use version control, you will need to configure"
66
+ puts "it without assistance from this script."
67
+ puts
68
+ unless Console.yes_no("Continue configuring schema management", :no)
69
+ return
70
+ end
71
+ end
72
+
73
+ db_system = Console::Menu.new(
74
+ "Supported Database Systems",
75
+ DatabaseSupportModules,
76
+ "Target system",
77
+ :get_name => lambda {|m| m::SYSTEM_NAME}
78
+ ).get_selection
79
+
80
+ schema_config.dbinfo['system'] = db_system::SYSTEM_NAME
81
+ if db_system.respond_to? :init_schema
82
+ db_system.init_schema(schema_config)
83
+ end
84
+
85
+ if vcs_system.respond_to? :init_schema
86
+ vcs_system.init_schema(schema_config)
87
+ end
88
+
89
+ puts "Enter a script comment. This comment will be prepended to each"
90
+ puts "generated script exactly as given here."
91
+ script_comment = get_user_input_block('script comment').extend(LiteralYamlStyle)
92
+ schema_config.dbinfo['script comment'] = script_comment if script_comment != ''
93
+
94
+ schema_config.root_path.mkpath
95
+
96
+ dbinfo_path.open('w') do |dbinfo_io|
97
+ $xmigra_yamler.dump(schema_config.dbinfo, dbinfo_io)
98
+ end
99
+ schema_config.created_file! dbinfo_path
100
+
101
+ schema_config.run_steps_after_dbinfo_creation!
102
+
103
+ return schema_config.created_files
104
+ end
105
+ end
106
+ end
@@ -1,3 +1,4 @@
1
+ require 'xmigra/console'
1
2
 
2
3
  module XMigra
3
4
  module GitSpecifics
@@ -6,6 +7,57 @@ module XMigra
6
7
  MASTER_HEAD_ATTRIBUTE = 'xmigra-master'
7
8
  MASTER_BRANCH_SUBDIR = 'xmigra-master'
8
9
 
10
+ class AttributesFile
11
+ def initialize(effect_root, access=:shared)
12
+ @effect_root = Pathname(effect_root)
13
+ @access = access
14
+ end
15
+
16
+ attr_reader :effect_root, :access
17
+
18
+ def file_relative_path
19
+ case @access
20
+ when :local
21
+ Pathname('.git/info/attributes')
22
+ else
23
+ Pathname('.gitattributes')
24
+ end
25
+ end
26
+
27
+ def file_path
28
+ @effect_root + file_relative_path
29
+ end
30
+
31
+ def path_from(path)
32
+ file_path.relative_path_from(Pathname(path))
33
+ end
34
+
35
+ def description
36
+ "".tap do |result|
37
+ result << "#{path_from(Pathname.pwd)}"
38
+
39
+ chars = []
40
+
41
+ if file_path.exist?
42
+ chars << "exists"
43
+ end
44
+
45
+ case access
46
+ when :local
47
+ chars << "local"
48
+ end
49
+
50
+ unless chars.empty?
51
+ result << " (#{chars.join(', ')})"
52
+ end
53
+ end
54
+ end
55
+
56
+ def open(*args, &blk)
57
+ file_path.open(*args, &blk)
58
+ end
59
+ end
60
+
9
61
  class << self
10
62
  def manages(path)
11
63
  run_git(:status, :check_exit=>true, :quiet=>true)
@@ -50,6 +102,61 @@ module XMigra
50
102
  end
51
103
  return value_list[0]
52
104
  end
105
+
106
+ def attributes_file_paths(path)
107
+ wdroot = Dir.chdir path do
108
+ Pathname(run_git('rev-parse', '--show-toplevel').strip).realpath
109
+ end
110
+ pwd = Pathname.pwd
111
+
112
+ [].tap do |result|
113
+ path.realpath.ascend do |dirpath|
114
+ result << AttributesFile.new(dirpath)
115
+ break if (wdroot <=> dirpath) >= 0
116
+ end
117
+
118
+ result << AttributesFile.new(wdroot, :local)
119
+ end
120
+ end
121
+
122
+ def get_master_url
123
+ print "Master repository URL (empty for none): "
124
+ master_repo = $stdin.gets.strip
125
+ return nil if master_repo.empty?
126
+
127
+ Console.validated_input "Master branch name" do |master_branch|
128
+ if master_branch.empty?
129
+ raise Console::InvalidInput.new(
130
+ "Master branch name required to set 'xmigra-master' attribute --"
131
+ )
132
+ end
133
+ "#{master_repo}##{master_branch}"
134
+ end
135
+ end
136
+
137
+ def init_schema(schema_config)
138
+ Console.output_section "Git Integration" do
139
+ if master_url = get_master_url
140
+ # Select locations for .gitattributes or .git/info/attributes
141
+ attribs_file = Console::Menu.new(
142
+ "Git Attributes Files",
143
+ attributes_file_paths(schema_config.root_path),
144
+ "File for storing 'xmigra-master' attribute",
145
+ :get_name => lambda {|af| af.description}
146
+ ).get_selection
147
+
148
+ dbinfo_path = schema_config.root_path + SchemaManipulator::DBINFO_FILE
149
+ attribute_pattern = "/#{dbinfo_path.relative_path_from(attribs_file.effect_root)}"
150
+
151
+ schema_config.after_dbinfo_creation do
152
+ attribs_file.open('a') do |attribs_io|
153
+ attribs_io.puts "#{attribute_pattern} xmigra-master=#{master_url}"
154
+ end
155
+ schema_config.created_file! attribs_file.file_path
156
+ end
157
+ end
158
+ end
159
+ end
53
160
  end
54
161
 
55
162
  def git(*args)
@@ -1,3 +1,4 @@
1
+ require 'xmigra/console'
1
2
 
2
3
  module XMigra
3
4
  module SubversionSpecifics
@@ -26,7 +27,7 @@ module XMigra
26
27
  cmd_parts = ["svn", subcmd.to_s]
27
28
  cmd_parts << "--xml" unless no_result || raw_result
28
29
  cmd_parts.concat(
29
- args.collect {|a| '""'.insert(1, a)}
30
+ args.collect {|a| '""'.insert(1, a.to_s)}
30
31
  )
31
32
  cmd_str = cmd_parts.join(' ')
32
33
 
@@ -35,6 +36,31 @@ module XMigra
35
36
  return output if raw_result && !no_result
36
37
  return REXML::Document.new(output) unless no_result
37
38
  end
39
+
40
+ def init_schema(schema_config)
41
+ Console.output_section "Subversion Integration" do
42
+ puts "Establishing a \"production pattern,\" a regular expression for"
43
+ puts "recognizing branch identifiers of branches used for production"
44
+ puts "script generation, simplifies the process of resolving conflicts"
45
+ puts "in the migration chain, should any arise."
46
+ puts
47
+ puts "No escaping (either shell or Ruby) of the regular expression is"
48
+ puts "necessary when entered here."
49
+ puts
50
+ puts "Common choices are:"
51
+ puts " ^trunk/"
52
+ puts " ^version/"
53
+ puts
54
+ print "Production pattern (empty to skip): "
55
+
56
+ production_pattern = $stdin.gets.chomp
57
+ return if production_pattern.empty?
58
+ schema_config.after_dbinfo_creation do
59
+ tool = SchemaManipulator.new(schema_config.root_path).extend(WarnToStderr)
60
+ tool.production_pattern = production_pattern
61
+ end
62
+ end
63
+ end
38
64
  end
39
65
 
40
66
  def subversion(*args)
@@ -1,3 +1,3 @@
1
1
  module XMigra
2
- VERSION = "1.3.1"
2
+ VERSION = "1.4.0"
3
3
  end
data/lib/xmigra.rb CHANGED
@@ -319,6 +319,8 @@ require 'xmigra/stored_procedure'
319
319
  require 'xmigra/view'
320
320
  require 'xmigra/function'
321
321
 
322
+ require 'xmigra/plugin'
323
+
322
324
  require 'xmigra/access_artifact_collection'
323
325
  require 'xmigra/index'
324
326
  require 'xmigra/index_collection'
@@ -330,6 +332,7 @@ require 'xmigra/schema_manipulator'
330
332
  require 'xmigra/schema_updater'
331
333
  require 'xmigra/new_migration_adder'
332
334
  require 'xmigra/permission_script_writer'
335
+ require 'xmigra/source_tree_initializer'
333
336
 
334
337
  require 'xmigra/program'
335
338
 
data/test/reversions.rb CHANGED
@@ -1,33 +1,4 @@
1
1
 
2
- def in_xmigra_schema
3
- 1.temp_dirs do |schema|
4
- Dir.chdir(schema) do
5
- initialize_xmigra_schema
6
- yield
7
- end
8
- end
9
- end
10
-
11
- def add_migration(migration_name, reversion_sql=nil)
12
- tool = XMigra::NewMigrationAdder.new('.')
13
- mig_path = tool.add_migration migration_name
14
- mig_chain = XMigra::MigrationChain.new('structure')
15
- migration = mig_chain[-1]
16
- unless reversion_sql.nil?
17
- class <<migration
18
- def reversion_tracking_sql
19
- '-- TRACK REVERSION OF MIGRATION --'
20
- end
21
- end
22
- rev_file = XMigra::RevertFile.new(migration)
23
- rev_file.path.dirname.mkpath
24
- rev_file.path.open('w') do |rev_stream|
25
- rev_stream.puts reversion_sql
26
- end
27
- end
28
- return migration
29
- end
30
-
31
2
  def add_migration_reversion_pair(migration_name, reversion_sql)
32
3
  return [add_migration(migration_name, reversion_sql), reversion_sql]
33
4
  end
@@ -78,7 +49,7 @@ run_test "Generated revisions script for one migration removes application recor
78
49
  assert("Reversions script does not remove migration application record") {
79
50
  XMigra::Program.run(['reversions'])
80
51
  script = test_output
81
- script =~ /DELETE\s+FROM\s+.?xmigra.?\..?applied.?\s+WHERE\s+.?MigrationID.?\s*=\s*'#{migration.id}'\s*;/
52
+ script =~ /DELETE\s+FROM\s+.?xmigra.?\..?applied.?\s+WHERE\s+.?MigrationID.?\s*=\s*'#{Regexp.escape migration.id}'\s*;/
82
53
  }
83
54
  end
84
55
  end
data/test/runner.rb CHANGED
@@ -13,6 +13,7 @@ TESTS = %w[
13
13
  $:.unshift Pathname(__FILE__).expand_path.dirname.dirname + 'lib'
14
14
  $:.unshift Pathname(__FILE__).expand_path.dirname.dirname
15
15
  require 'xmigra'
16
+ require 'test/utils'
16
17
 
17
18
  $test_count = 0
18
19
  $test_successes = 0
@@ -68,40 +69,6 @@ def run_test(name, &block)
68
69
  end
69
70
  end
70
71
 
71
- class Integer
72
- def temp_dirs(prefix='')
73
- tmpdirs = []
74
- begin
75
- (1..self).each do |i|
76
- tmpdirs << Pathname(Dir.mktmpdir([prefix, ".#{i}"]))
77
- end
78
-
79
- yield(*tmpdirs)
80
- ensure
81
- tmpdirs.each do |dp|
82
- begin
83
- FileUtils.remove_entry dp
84
- rescue
85
- # Skip failure
86
- end
87
- end
88
- end
89
- end
90
- end
91
-
92
- def do_or_die(command, message=nil, exc_type=Exception)
93
- output = `#{command}`
94
- $?.success? || raise(exc_type, message || ("Unable to " + command + "\n" + output))
95
- end
96
-
97
- def initialize_xmigra_schema(path='.', options={})
98
- (Pathname(path) + XMigra::SchemaManipulator::DBINFO_FILE).open('w') do |f|
99
- YAML.dump({
100
- 'system' => $xmigra_test_system,
101
- }.merge(options[:db_info] || {}), f)
102
- end
103
- end
104
-
105
72
  def test_output
106
73
  return nil unless $stdout.kind_of? StringIO
107
74
  return $stdout.string
data/test/utils.rb ADDED
@@ -0,0 +1,78 @@
1
+ require 'fileutils'
2
+ require 'ostruct'
3
+ require 'pathname'
4
+ require 'stringio'
5
+ require 'tmpdir'
6
+
7
+ class Integer
8
+ def temp_dirs(prefix='')
9
+ tmpdirs = []
10
+ begin
11
+ (1..self).each do |i|
12
+ tmpdirs << Pathname(Dir.mktmpdir([prefix, ".#{i}"]))
13
+ end
14
+
15
+ yield(*tmpdirs)
16
+ ensure
17
+ tmpdirs.each do |dp|
18
+ begin
19
+ FileUtils.remove_entry dp
20
+ rescue
21
+ # Skip failure
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+
28
+ def do_or_die(command, message=nil, exc_type=Exception)
29
+ output = `#{command}`
30
+ $?.success? || raise(exc_type, message || ("Unable to " + command + "\n" + output))
31
+ end
32
+
33
+ def initialize_xmigra_schema(path='.', options={})
34
+ (Pathname(path) + XMigra::SchemaManipulator::DBINFO_FILE).open('w') do |f|
35
+ YAML.dump({
36
+ 'system' => $xmigra_test_system,
37
+ }.merge(options[:db_info] || {}), f)
38
+ end
39
+ end
40
+
41
+ def in_xmigra_schema
42
+ 1.temp_dirs do |schema|
43
+ Dir.chdir(schema) do
44
+ initialize_xmigra_schema
45
+ yield
46
+ end
47
+ end
48
+ end
49
+
50
+ def add_migration(migration_name, reversion_sql=nil)
51
+ tool = XMigra::NewMigrationAdder.new('.')
52
+ mig_path = tool.add_migration migration_name
53
+ mig_chain = XMigra::MigrationChain.new('structure')
54
+ migration = mig_chain[-1]
55
+ unless reversion_sql.nil?
56
+ class <<migration
57
+ def reversion_tracking_sql
58
+ '-- TRACK REVERSION OF MIGRATION --'
59
+ end
60
+ end
61
+ rev_file = XMigra::RevertFile.new(migration)
62
+ rev_file.path.dirname.mkpath
63
+ rev_file.path.open('w') do |rev_stream|
64
+ rev_stream.puts reversion_sql
65
+ end
66
+ end
67
+ return migration
68
+ end
69
+
70
+ def capture_stdout
71
+ old_stdout, $stdout = $stdout, StringIO.new
72
+ begin
73
+ yield
74
+ return $stdout.string
75
+ ensure
76
+ $stdout = old_stdout
77
+ end
78
+ end
data/xmigra.gemspec CHANGED
@@ -13,7 +13,7 @@ Gem::Specification.new do |spec|
13
13
  XMigra is a suite of tools for managing database schema evolution with
14
14
  version controlled files. All database manipulations are written in
15
15
  SQL (specific to the target database). Works with Git or Subversion.
16
- Currently supports Microsoft SQL Server.
16
+ Currently supports Microsoft SQL Server and PostgreSQL.
17
17
  END
18
18
  spec.homepage = "https://github.com/rtweeks/xmigra"
19
19
  spec.license = "CC-BY-SA 4.0 Itnl."
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: xmigra
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.1
4
+ version: 1.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Next IT Corporation
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2015-04-23 00:00:00.000000000 Z
12
+ date: 2015-05-25 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -43,7 +43,7 @@ description: |2
43
43
  XMigra is a suite of tools for managing database schema evolution with
44
44
  version controlled files. All database manipulations are written in
45
45
  SQL (specific to the target database). Works with Git or Subversion.
46
- Currently supports Microsoft SQL Server.
46
+ Currently supports Microsoft SQL Server and PostgreSQL.
47
47
  email:
48
48
  - rtweeks21@gmail.com
49
49
  executables:
@@ -61,6 +61,7 @@ files:
61
61
  - lib/xmigra/access_artifact.rb
62
62
  - lib/xmigra/access_artifact_collection.rb
63
63
  - lib/xmigra/branch_upgrade.rb
64
+ - lib/xmigra/console.rb
64
65
  - lib/xmigra/db_support/mssql.rb
65
66
  - lib/xmigra/db_support/psql.rb
66
67
  - lib/xmigra/function.rb
@@ -72,11 +73,13 @@ files:
72
73
  - lib/xmigra/new_file.rb
73
74
  - lib/xmigra/new_migration_adder.rb
74
75
  - lib/xmigra/permission_script_writer.rb
76
+ - lib/xmigra/plugin.rb
75
77
  - lib/xmigra/program.rb
76
78
  - lib/xmigra/reversion_script_building.rb
77
79
  - lib/xmigra/revert_file.rb
78
80
  - lib/xmigra/schema_manipulator.rb
79
81
  - lib/xmigra/schema_updater.rb
82
+ - lib/xmigra/source_tree_initializer.rb
80
83
  - lib/xmigra/stored_procedure.rb
81
84
  - lib/xmigra/utils.rb
82
85
  - lib/xmigra/vcs_support/git.rb
@@ -86,6 +89,7 @@ files:
86
89
  - test/git_vcs.rb
87
90
  - test/reversions.rb
88
91
  - test/runner.rb
92
+ - test/utils.rb
89
93
  - xmigra.gemspec
90
94
  homepage: https://github.com/rtweeks/xmigra
91
95
  licenses: