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.
- 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
|