xmigra 1.5.1 → 1.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/xmigra/db_support/mssql.rb +43 -0
- data/lib/xmigra/db_support/psql.rb +45 -0
- data/lib/xmigra/declarative_migration.rb +160 -0
- data/lib/xmigra/declarative_support/table.rb +590 -0
- data/lib/xmigra/declarative_support.rb +158 -0
- data/lib/xmigra/impdecl_migration_adder.rb +249 -0
- data/lib/xmigra/migration.rb +10 -3
- data/lib/xmigra/migration_chain.rb +22 -8
- data/lib/xmigra/migration_conflict.rb +27 -0
- data/lib/xmigra/new_access_artifact_adder.rb +44 -0
- data/lib/xmigra/new_index_adder.rb +33 -0
- data/lib/xmigra/new_migration_adder.rb +10 -6
- data/lib/xmigra/permission_script_writer.rb +11 -5
- data/lib/xmigra/program.rb +231 -23
- data/lib/xmigra/schema_updater.rb +28 -5
- data/lib/xmigra/vcs_support/git.rb +189 -8
- data/lib/xmigra/vcs_support/svn.rb +107 -1
- data/lib/xmigra/version.rb +1 -1
- data/lib/xmigra.rb +47 -2
- data/test/git_vcs.rb +64 -4
- data/test/new_files.rb +14 -0
- data/test/runner.rb +49 -4
- data/test/structure_declarative.rb +811 -0
- data/test/utils.rb +17 -2
- metadata +10 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6bd9e09df1d03a0bb23ee61fd21644669c459315
|
4
|
+
data.tar.gz: 2467c3c1de5236f6e27f87706b7d1b091c774dbd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4cdf662d14d79137b156324376dcb3725183712354ecbe0a2e3ecd453f3a6b331f322f46204d9e5a5290f356062e9f6125a2571754d3ee75d439ca22c9cb21f6
|
7
|
+
data.tar.gz: 75c686b17ad25eb2a117a25d30ced42e501944facef9cb8446461ff9715556a7b99f7049bfea154a0cf83e12ebe9caea5922b4fc239c9c3fdf2e34da5b1d8941
|
@@ -1153,6 +1153,49 @@ INSERT INTO [xmigra].[branch_upgrade] ([Current]) VALUES (#{branch_id_literal});
|
|
1153
1153
|
return parts.join("\n")
|
1154
1154
|
end
|
1155
1155
|
|
1156
|
+
def index_template_sql
|
1157
|
+
<<-"END_OF_SQL"
|
1158
|
+
CREATE INDEX [{filename}]
|
1159
|
+
ON <<<table>>> (<<<columns>>>);
|
1160
|
+
END_OF_SQL
|
1161
|
+
end
|
1162
|
+
|
1163
|
+
def view_definition_template_sql
|
1164
|
+
<<-"END_OF_SQL"
|
1165
|
+
CREATE VIEW [{filename}]
|
1166
|
+
AS SELECT <<<query>>>;
|
1167
|
+
END_OF_SQL
|
1168
|
+
end
|
1169
|
+
|
1170
|
+
def procedure_definition_template_sql
|
1171
|
+
<<-"END_OF_SQL"
|
1172
|
+
CREATE PROCEDURE [{filename}]
|
1173
|
+
<<<parameters>>>
|
1174
|
+
AS BEGIN
|
1175
|
+
<<<statements>>>
|
1176
|
+
END
|
1177
|
+
END_OF_SQL
|
1178
|
+
end
|
1179
|
+
|
1180
|
+
def function_definition_template_sql
|
1181
|
+
<<-"END_OF_SQL"
|
1182
|
+
CREATE FUNCTION [{filename}] (
|
1183
|
+
<<<parameters>>>
|
1184
|
+
) RETURNS <<<return-type>>>
|
1185
|
+
AS BEGIN
|
1186
|
+
<<<statements>>>
|
1187
|
+
RETURN <<<return-value-expression>>>;
|
1188
|
+
END
|
1189
|
+
END_OF_SQL
|
1190
|
+
end
|
1191
|
+
|
1192
|
+
def alter_table_columns_sql_statements(col_pairs)
|
1193
|
+
col_pairs.map do |_, col|
|
1194
|
+
nullability = (col.nullable? ? "" : "NOT ") + "NULL"
|
1195
|
+
"ALTER TABLE #{name} ALTER COLUMN #{col.name} #{col.type} #{nullability};"
|
1196
|
+
end
|
1197
|
+
end
|
1198
|
+
|
1156
1199
|
class << self
|
1157
1200
|
def strip_identifier_quoting(s)
|
1158
1201
|
case
|
@@ -704,6 +704,51 @@ module XMigra
|
|
704
704
|
return parts.join("\n")
|
705
705
|
end
|
706
706
|
|
707
|
+
def index_template_sql
|
708
|
+
XMigra.dedent(%Q{
|
709
|
+
CREATE INDEX [{filename}]
|
710
|
+
ON <<<table>>> (<<<columns>>>);
|
711
|
+
})
|
712
|
+
end
|
713
|
+
|
714
|
+
def view_definition_template_sql
|
715
|
+
XMigra.dedent(%Q{
|
716
|
+
CREATE VIEW [{filename}]
|
717
|
+
AS SELECT <<<query>>>;
|
718
|
+
})
|
719
|
+
end
|
720
|
+
|
721
|
+
def procedure_definition_template_sql
|
722
|
+
raise XMigra::NewAccessArtifactAdder::UnsupportedArtifactType.new(:procedure, SYSTEM_NAME)
|
723
|
+
end
|
724
|
+
|
725
|
+
def function_definition_template_sql
|
726
|
+
XMigra.dedent(%Q{
|
727
|
+
CREATE FUNCTION [{filename}] (
|
728
|
+
<<<parameters>>>
|
729
|
+
) RETURNS <<<return-type>>>
|
730
|
+
AS $$
|
731
|
+
<<<function-body>>>
|
732
|
+
$$ LANGUAGE plpgsql;
|
733
|
+
})
|
734
|
+
end
|
735
|
+
|
736
|
+
def alter_table_columns_sql_statements(col_pairs)
|
737
|
+
col_pairs.flat_map do |old_col, col|
|
738
|
+
[].tap do |parts|
|
739
|
+
if !old_col.nullable? && col.nullable?
|
740
|
+
parts << "ALTER TABLE #{name} ALTER COLUMN #{col.name} DROP NOT NULL;"
|
741
|
+
end
|
742
|
+
if old_col.type != col.type
|
743
|
+
parts << "ALTER TABLE #{name} ALTER COLUMN #{col.name} TYPE #{col.type};"
|
744
|
+
end
|
745
|
+
if old_col.nullable? && !col.nullable?
|
746
|
+
parts << "ALTER TABLE #{name} ALTER COLUMN #{col.name} SET NOT NULL;"
|
747
|
+
end
|
748
|
+
end
|
749
|
+
end
|
750
|
+
end
|
751
|
+
|
707
752
|
class <<self
|
708
753
|
def in_plpgsql(*args)
|
709
754
|
variables = args[0].kind_of?(Hash) ? args.shift : {}
|
@@ -0,0 +1,160 @@
|
|
1
|
+
|
2
|
+
module XMigra
|
3
|
+
module DeclarativeMigration
|
4
|
+
VALID_GOALS = %w{creation adoption revision renunciation destruction}
|
5
|
+
GOAL_KEY = 'does'
|
6
|
+
TARGET_KEY = 'of object'
|
7
|
+
DECLARATION_VERSION_KEY = 'to realize'
|
8
|
+
QUALIFICATION_KEY = 'implementation qualification'
|
9
|
+
SUBDIR = 'declarative'
|
10
|
+
|
11
|
+
class MissingImplementationError < Error
|
12
|
+
COMMAND_LINE_HELP = "The '%prog impdecl' command may help resolve this error."
|
13
|
+
end
|
14
|
+
|
15
|
+
class QuestionableImplementationError < Error; end
|
16
|
+
|
17
|
+
Missing = Class.new do
|
18
|
+
def goal
|
19
|
+
:newly_managed_object
|
20
|
+
end
|
21
|
+
|
22
|
+
def declarative_status
|
23
|
+
:unimplemented
|
24
|
+
end
|
25
|
+
|
26
|
+
def delta(file_path)
|
27
|
+
Pathname(file_path).readlines.map {|l| '+' + l}.join('')
|
28
|
+
end
|
29
|
+
end.new
|
30
|
+
|
31
|
+
module ChainSupport
|
32
|
+
def latest_declarative_implementations
|
33
|
+
@latest_declarative_implementations ||= Hash.new do |h, file_path|
|
34
|
+
ext_path = Pathname(file_path).expand_path
|
35
|
+
if h.has_key? ext_path
|
36
|
+
next h[ext_path]
|
37
|
+
end
|
38
|
+
raise Error, (
|
39
|
+
"Unexpected file path '#{file_path}', known file paths:" +
|
40
|
+
h.keys.collect {|kp| " #{kp}\n"}.join('')
|
41
|
+
)
|
42
|
+
end.tap do |files|
|
43
|
+
each do |migration|
|
44
|
+
# Skip non-declarative migrations
|
45
|
+
next unless migration.is_a? DeclarativeMigration
|
46
|
+
if (
|
47
|
+
[:renunciation, :destruction].include?(migration.goal) &&
|
48
|
+
migration.declarative_status == :missing
|
49
|
+
)
|
50
|
+
files.delete(migration.declarative_file_path)
|
51
|
+
else
|
52
|
+
files[migration.declarative_file_path] = migration
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
Dir.glob(Pathname(path).join(SUBDIR, '*.yaml').to_s) do |decl_file|
|
57
|
+
decl_file_path = Pathname(decl_file).expand_path
|
58
|
+
next if files.has_key?(decl_file_path)
|
59
|
+
files[decl_file_path] = DeclarativeMigration::Missing
|
60
|
+
end
|
61
|
+
|
62
|
+
files.freeze
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def unimplemented_declaratives
|
67
|
+
@unimplemented_declaratives ||= latest_declarative_implementations.reject do |file_path, migration|
|
68
|
+
[:equal, :older].include? migration.declarative_status
|
69
|
+
end.keys
|
70
|
+
end
|
71
|
+
|
72
|
+
def check_declaratives_current!
|
73
|
+
unless unimplemented_declaratives.empty?
|
74
|
+
raise(
|
75
|
+
MissingImplementationError,
|
76
|
+
"Declaratives without migration implementing current state:\n" +
|
77
|
+
unimplemented_declaratives.collect {|df| " #{df.basename('.yaml')}\n"}.join("")
|
78
|
+
)
|
79
|
+
end
|
80
|
+
|
81
|
+
questionable_migrations = latest_declarative_implementations.values.select {|m| m.questionable?}
|
82
|
+
unless questionable_migrations.empty?
|
83
|
+
raise(
|
84
|
+
QuestionableImplementationError,
|
85
|
+
"Implementing migrations with questionable SQL:\n" +
|
86
|
+
questionable_migrations.collect {|m| " #{m.file_path}\n"}.join("")
|
87
|
+
)
|
88
|
+
end
|
89
|
+
|
90
|
+
questionable_migrations = latest_declarative_implementations.values.each do |m|
|
91
|
+
next unless m.management_migration?
|
92
|
+
raise(
|
93
|
+
QuestionableImplementationError,
|
94
|
+
"#{m.file_path} cannot execute SQL for a declarative #{m.goal}"
|
95
|
+
) unless m.sql.nil? || m.sql.empty?
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def goal
|
101
|
+
@declarative_goal ||= @all_info[GOAL_KEY].tap do |val|
|
102
|
+
raise(ArgumentError, "'#{GOAL_KEY}' must be one of: #{VALID_GOALS.join(', ')}") unless VALID_GOALS.include?(val)
|
103
|
+
end.to_sym
|
104
|
+
end
|
105
|
+
|
106
|
+
def affected_object
|
107
|
+
@declarative_target ||= @all_info[TARGET_KEY].dup.freeze
|
108
|
+
end
|
109
|
+
|
110
|
+
# Override the way the base class handles changes -- this integrates with
|
111
|
+
# the "history" command
|
112
|
+
def changes
|
113
|
+
if management_migration?
|
114
|
+
[]
|
115
|
+
else
|
116
|
+
[affected_object]
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def sql
|
121
|
+
if management_migration?
|
122
|
+
''
|
123
|
+
else
|
124
|
+
super()
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def management_migration?
|
129
|
+
[:adoption, :renunciation].include? goal
|
130
|
+
end
|
131
|
+
|
132
|
+
# This method is only used when the declarative file has uncommitted
|
133
|
+
# modifications and the migration file is uncommitted
|
134
|
+
def implemented_version
|
135
|
+
@declarative_implemented_ver ||= (@all_info[DECLARATION_VERSION_KEY].dup.freeze if vcs_uncommitted?)
|
136
|
+
end
|
137
|
+
|
138
|
+
def implementation_of?(file_path)
|
139
|
+
XMigra.secure_digest(file_path.read) == implemented_version
|
140
|
+
end
|
141
|
+
|
142
|
+
def declarative_file_path
|
143
|
+
@declarative_file_path ||= Pathname(file_path).dirname.join(SUBDIR, affected_object + '.yaml')
|
144
|
+
end
|
145
|
+
|
146
|
+
def declarative_status
|
147
|
+
@declarative_status ||= begin
|
148
|
+
vcs_comparator(:expected_content_method=>:implementation_of?).relative_version(declarative_file_path)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def delta(file_path)
|
153
|
+
vcs_changes_from(vcs_latest_revision, file_path)
|
154
|
+
end
|
155
|
+
|
156
|
+
def questionable?
|
157
|
+
@all_info.has_key? QUALIFICATION_KEY
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|