xmigra 1.0.1 → 1.1.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/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,1070 @@
|
|
1
|
+
|
2
|
+
module XMigra
|
3
|
+
module MSSQLSpecifics
|
4
|
+
DatabaseSupportModules << self
|
5
|
+
|
6
|
+
SYSTEM_NAME = 'Microsoft SQL Server'
|
7
|
+
|
8
|
+
IDENTIFIER_SUBPATTERN = '[a-z_@#][a-z0-9@$#_]*|"[^\[\]"]+"|\[[^\[\]]+\]'
|
9
|
+
DBNAME_PATTERN = /^
|
10
|
+
(?:(#{IDENTIFIER_SUBPATTERN})\.)?
|
11
|
+
(#{IDENTIFIER_SUBPATTERN})
|
12
|
+
$/ix
|
13
|
+
STATISTICS_FILE = 'statistics-objects.yaml'
|
14
|
+
|
15
|
+
class StatisticsObject
|
16
|
+
def initialize(name, params)
|
17
|
+
(@name = name.dup).freeze
|
18
|
+
(@target = params[0].dup).freeze
|
19
|
+
(@columns = params[1].dup).freeze
|
20
|
+
@options = params[2] || {}
|
21
|
+
@options.freeze
|
22
|
+
@options.each_value {|v| v.freeze}
|
23
|
+
end
|
24
|
+
|
25
|
+
attr_reader :name, :target, :columns, :options
|
26
|
+
|
27
|
+
def creation_sql
|
28
|
+
result = "CREATE STATISTICS #{name} ON #{target} (#{columns})"
|
29
|
+
|
30
|
+
result += " WHERE " + @options['where'] if @options['where']
|
31
|
+
result += " WITH " + @options['with'] if @options['with']
|
32
|
+
|
33
|
+
result += ";"
|
34
|
+
return result
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def ddl_block_separator; "\nGO\n"; end
|
39
|
+
def filename_metavariable; "[{filename}]"; end
|
40
|
+
|
41
|
+
def stats_objs
|
42
|
+
return @stats_objs if @stats_objs
|
43
|
+
|
44
|
+
begin
|
45
|
+
stats_data = YAML::load_file(path.join(MSSQLSpecifics::STATISTICS_FILE))
|
46
|
+
rescue Errno::ENOENT
|
47
|
+
return @stats_objs = [].freeze
|
48
|
+
end
|
49
|
+
|
50
|
+
@stats_objs = stats_data.collect {|item| StatisticsObject.new(*item)}
|
51
|
+
@stats_objs.each {|o| o.freeze}
|
52
|
+
@stats_objs.freeze
|
53
|
+
|
54
|
+
return @stats_objs
|
55
|
+
end
|
56
|
+
|
57
|
+
def in_ddl_transaction
|
58
|
+
parts = []
|
59
|
+
parts << <<-"END_OF_SQL"
|
60
|
+
SET ANSI_NULLS ON
|
61
|
+
GO
|
62
|
+
|
63
|
+
SET QUOTED_IDENTIFIER ON
|
64
|
+
GO
|
65
|
+
|
66
|
+
SET ANSI_PADDING ON
|
67
|
+
GO
|
68
|
+
|
69
|
+
SET NOCOUNT ON
|
70
|
+
GO
|
71
|
+
|
72
|
+
BEGIN TRY
|
73
|
+
BEGIN TRAN;
|
74
|
+
END_OF_SQL
|
75
|
+
|
76
|
+
each_batch(yield) do |batch|
|
77
|
+
batch_literal = MSSQLSpecifics.string_literal("\n" + batch)
|
78
|
+
parts << "EXEC sp_executesql @statement = #{batch_literal};"
|
79
|
+
end
|
80
|
+
|
81
|
+
parts << <<-"END_OF_SQL"
|
82
|
+
COMMIT TRAN;
|
83
|
+
END TRY
|
84
|
+
BEGIN CATCH
|
85
|
+
ROLLBACK TRAN;
|
86
|
+
|
87
|
+
DECLARE @ErrorMessage NVARCHAR(4000);
|
88
|
+
DECLARE @ErrorSeverity INT;
|
89
|
+
DECLARE @ErrorState INT;
|
90
|
+
|
91
|
+
PRINT N'Update failed: ' + ERROR_MESSAGE();
|
92
|
+
PRINT N' State: ' + CAST(ERROR_STATE() AS NVARCHAR);
|
93
|
+
PRINT N' Line: ' + CAST(ERROR_LINE() AS NVARCHAR);
|
94
|
+
|
95
|
+
SELECT
|
96
|
+
@ErrorMessage = N'Update failed: ' + ERROR_MESSAGE(),
|
97
|
+
@ErrorSeverity = ERROR_SEVERITY(),
|
98
|
+
@ErrorState = ERROR_STATE();
|
99
|
+
|
100
|
+
-- Use RAISERROR inside the CATCH block to return error
|
101
|
+
-- information about the original error that caused
|
102
|
+
-- execution to jump to the CATCH block.
|
103
|
+
RAISERROR (@ErrorMessage, -- Message text.
|
104
|
+
@ErrorSeverity, -- Severity.
|
105
|
+
@ErrorState -- State.
|
106
|
+
);
|
107
|
+
END CATCH;
|
108
|
+
END_OF_SQL
|
109
|
+
|
110
|
+
return parts.join("\n")
|
111
|
+
end
|
112
|
+
|
113
|
+
def amend_script_parts(parts)
|
114
|
+
parts.insert_after(
|
115
|
+
:create_and_fill_indexes_table_sql,
|
116
|
+
:create_and_fill_statistics_table_sql
|
117
|
+
)
|
118
|
+
parts.insert_after(
|
119
|
+
:remove_undesired_indexes_sql,
|
120
|
+
:remove_undesired_statistics_sql
|
121
|
+
)
|
122
|
+
parts.insert_after(:create_new_indexes_sql, :create_new_statistics_sql)
|
123
|
+
end
|
124
|
+
|
125
|
+
def check_execution_environment_sql
|
126
|
+
<<-"END_OF_SQL"
|
127
|
+
PRINT N'Checking execution environment:';
|
128
|
+
IF DB_NAME() IN ('master', 'tempdb', 'model', 'msdb')
|
129
|
+
BEGIN
|
130
|
+
RAISERROR(N'Please select an appropriate target database for the update script.', 11, 1);
|
131
|
+
END;
|
132
|
+
END_OF_SQL
|
133
|
+
end
|
134
|
+
|
135
|
+
def ensure_version_tables_sql
|
136
|
+
<<-"END_OF_SQL"
|
137
|
+
PRINT N'Ensuring version tables:';
|
138
|
+
IF NOT EXISTS (
|
139
|
+
SELECT * FROM sys.schemas
|
140
|
+
WHERE name = N'xmigra'
|
141
|
+
)
|
142
|
+
BEGIN
|
143
|
+
EXEC sp_executesql N'
|
144
|
+
CREATE SCHEMA [xmigra] AUTHORIZATION [dbo];
|
145
|
+
';
|
146
|
+
END;
|
147
|
+
GO
|
148
|
+
|
149
|
+
IF NOT EXISTS (
|
150
|
+
SELECT * FROM sys.objects
|
151
|
+
WHERE object_id = OBJECT_ID(N'[xmigra].[applied]')
|
152
|
+
AND type IN (N'U')
|
153
|
+
)
|
154
|
+
BEGIN
|
155
|
+
CREATE TABLE [xmigra].[applied] (
|
156
|
+
[MigrationID] nvarchar(80) NOT NULL,
|
157
|
+
[ApplicationOrder] int IDENTITY(1,1) NOT NULL,
|
158
|
+
[VersionBridgeMark] bit NOT NULL,
|
159
|
+
[Description] nvarchar(max) NOT NULL,
|
160
|
+
|
161
|
+
CONSTRAINT [PK_version] PRIMARY KEY CLUSTERED (
|
162
|
+
[MigrationID] ASC
|
163
|
+
) WITH (
|
164
|
+
PAD_INDEX = OFF,
|
165
|
+
STATISTICS_NORECOMPUTE = OFF,
|
166
|
+
IGNORE_DUP_KEY = OFF,
|
167
|
+
ALLOW_ROW_LOCKS = ON,
|
168
|
+
ALLOW_PAGE_LOCKS = ON
|
169
|
+
) ON [PRIMARY]
|
170
|
+
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY];
|
171
|
+
END;
|
172
|
+
GO
|
173
|
+
|
174
|
+
IF NOT EXISTS (
|
175
|
+
SELECT * FROM sys.objects
|
176
|
+
WHERE object_id = OBJECT_ID(N'[xmigra].[DF_version_VersionBridgeMark]')
|
177
|
+
AND type IN (N'D')
|
178
|
+
)
|
179
|
+
BEGIN
|
180
|
+
ALTER TABLE [xmigra].[applied] ADD CONSTRAINT [DF_version_VersionBridgeMark]
|
181
|
+
DEFAULT (0) FOR [VersionBridgeMark];
|
182
|
+
END;
|
183
|
+
GO
|
184
|
+
|
185
|
+
IF NOT EXISTS (
|
186
|
+
SELECT * FROM sys.objects
|
187
|
+
WHERE object_id = OBJECT_ID(N'[xmigra].[access_objects]')
|
188
|
+
AND type IN (N'U')
|
189
|
+
)
|
190
|
+
BEGIN
|
191
|
+
CREATE TABLE [xmigra].[access_objects] (
|
192
|
+
[type] nvarchar(40) NOT NULL,
|
193
|
+
[name] nvarchar(256) NOT NULL,
|
194
|
+
[order] int identity(1,1) NOT NULL,
|
195
|
+
|
196
|
+
CONSTRAINT [PK_access_objects] PRIMARY KEY CLUSTERED (
|
197
|
+
[name] ASC
|
198
|
+
) WITH (
|
199
|
+
PAD_INDEX = OFF,
|
200
|
+
STATISTICS_NORECOMPUTE = OFF,
|
201
|
+
IGNORE_DUP_KEY = OFF,
|
202
|
+
ALLOW_ROW_LOCKS = ON,
|
203
|
+
ALLOW_PAGE_LOCKS = ON
|
204
|
+
) ON [PRIMARY]
|
205
|
+
) ON [PRIMARY];
|
206
|
+
END;
|
207
|
+
GO
|
208
|
+
|
209
|
+
IF NOT EXISTS (
|
210
|
+
SELECT * FROM sys.objects
|
211
|
+
WHERE object_id = OBJECT_ID(N'[xmigra].[indexes]')
|
212
|
+
AND type in (N'U')
|
213
|
+
)
|
214
|
+
BEGIN
|
215
|
+
CREATE TABLE [xmigra].[indexes] (
|
216
|
+
[IndexID] nvarchar(80) NOT NULL PRIMARY KEY,
|
217
|
+
[name] nvarchar(256) NOT NULL
|
218
|
+
) ON [PRIMARY];
|
219
|
+
END;
|
220
|
+
|
221
|
+
IF NOT EXISTS (
|
222
|
+
SELECT * FROM sys.objects
|
223
|
+
WHERE object_id = OBJECT_ID(N'[xmigra].[statistics]')
|
224
|
+
AND type in (N'U')
|
225
|
+
)
|
226
|
+
BEGIN
|
227
|
+
CREATE TABLE [xmigra].[statistics] (
|
228
|
+
[Name] nvarchar(100) NOT NULL PRIMARY KEY,
|
229
|
+
[Columns] nvarchar(256) NOT NULL
|
230
|
+
) ON [PRIMARY];
|
231
|
+
END;
|
232
|
+
|
233
|
+
IF NOT EXISTS (
|
234
|
+
SELECT * FROM sys.objects
|
235
|
+
WHERE object_id = OBJECT_ID(N'[xmigra].[branch_upgrade]')
|
236
|
+
AND type in (N'U')
|
237
|
+
)
|
238
|
+
BEGIN
|
239
|
+
CREATE TABLE [xmigra].[branch_upgrade] (
|
240
|
+
[ApplicationOrder] int identity(1,1) NOT NULL,
|
241
|
+
[Current] nvarchar(80) NOT NULL PRIMARY KEY,
|
242
|
+
[Next] nvarchar(80) NULL,
|
243
|
+
[UpgradeSql] nvarchar(max) NULL,
|
244
|
+
[CompletesMigration] nvarchar(80) NULL
|
245
|
+
) ON [PRIMARY];
|
246
|
+
END;
|
247
|
+
END_OF_SQL
|
248
|
+
end
|
249
|
+
|
250
|
+
def create_and_fill_migration_table_sql
|
251
|
+
intro = <<-"END_OF_SQL"
|
252
|
+
IF EXISTS (
|
253
|
+
SELECT * FROM sys.objects
|
254
|
+
WHERE object_id = OBJECT_ID(N'[xmigra].[migrations]')
|
255
|
+
AND type IN (N'U')
|
256
|
+
)
|
257
|
+
BEGIN
|
258
|
+
DROP TABLE [xmigra].[migrations];
|
259
|
+
END;
|
260
|
+
GO
|
261
|
+
|
262
|
+
CREATE TABLE [xmigra].[migrations] (
|
263
|
+
[MigrationID] nvarchar(80) NOT NULL,
|
264
|
+
[ApplicationOrder] int NOT NULL,
|
265
|
+
[Description] ntext NOT NULL,
|
266
|
+
[Install] bit NOT NULL DEFAULT(0)
|
267
|
+
);
|
268
|
+
GO
|
269
|
+
|
270
|
+
END_OF_SQL
|
271
|
+
|
272
|
+
mig_insert = <<-"END_OF_SQL"
|
273
|
+
INSERT INTO [xmigra].[migrations] (
|
274
|
+
[MigrationID],
|
275
|
+
[ApplicationOrder],
|
276
|
+
[Description]
|
277
|
+
) VALUES
|
278
|
+
END_OF_SQL
|
279
|
+
|
280
|
+
if (@db_info || {}).fetch('MSSQL 2005 compatible', false).eql?(true)
|
281
|
+
parts = [intro]
|
282
|
+
(0...migrations.length).each do |i|
|
283
|
+
m = migrations[i]
|
284
|
+
description_literal = MSSQLSpecifics.string_literal(m.description.strip)
|
285
|
+
parts << mig_insert + "(N'#{m.id}', #{i + 1}, #{description_literal});\n"
|
286
|
+
end
|
287
|
+
return parts.join('')
|
288
|
+
else
|
289
|
+
return intro + mig_insert + (0...migrations.length).collect do |i|
|
290
|
+
m = migrations[i]
|
291
|
+
description_literal = MSSQLSpecifics.string_literal(m.description.strip)
|
292
|
+
"(N'#{m.id}', #{i + 1}, #{description_literal})"
|
293
|
+
end.join(",\n") + ";\n"
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
def create_and_fill_indexes_table_sql
|
298
|
+
intro = <<-"END_OF_SQL"
|
299
|
+
PRINT N'Creating and filling index manipulation table:';
|
300
|
+
IF EXISTS (
|
301
|
+
SELECT * FROM sys.objects
|
302
|
+
WHERE object_id = OBJECT_ID(N'[xmigra].[updated_indexes]')
|
303
|
+
AND type IN (N'U')
|
304
|
+
)
|
305
|
+
BEGIN
|
306
|
+
DROP TABLE [xmigra].[updated_indexes];
|
307
|
+
END;
|
308
|
+
GO
|
309
|
+
|
310
|
+
CREATE TABLE [xmigra].[updated_indexes] (
|
311
|
+
[IndexID] NVARCHAR(80) NOT NULL PRIMARY KEY
|
312
|
+
);
|
313
|
+
GO
|
314
|
+
|
315
|
+
END_OF_SQL
|
316
|
+
|
317
|
+
insertion = <<-"END_OF_SQL"
|
318
|
+
INSERT INTO [xmigra].[updated_indexes] ([IndexID]) VALUES
|
319
|
+
END_OF_SQL
|
320
|
+
|
321
|
+
strlit = MSSQLSpecifics.method :string_literal
|
322
|
+
return intro + (insertion + indexes.collect do |index|
|
323
|
+
"(#{strlit[index.id]})"
|
324
|
+
end.join(",\n") + ";\n" unless indexes.empty?).to_s
|
325
|
+
|
326
|
+
return intro
|
327
|
+
end
|
328
|
+
|
329
|
+
def create_and_fill_statistics_table_sql
|
330
|
+
intro = <<-"END_OF_SQL"
|
331
|
+
PRINT N'Creating and filling statistics object manipulation table:';
|
332
|
+
IF EXISTS (
|
333
|
+
SELECT * FROM sys.objects
|
334
|
+
WHERE object_id = OBJECT_ID(N'[xmigra].[updated_statistics]')
|
335
|
+
AND type in (N'U')
|
336
|
+
)
|
337
|
+
BEGIN
|
338
|
+
DROP TABLE [xmigra].[updated_statistics];
|
339
|
+
END;
|
340
|
+
GO
|
341
|
+
|
342
|
+
CREATE TABLE [xmigra].[updated_statistics] (
|
343
|
+
[Name] nvarchar(100) NOT NULL PRIMARY KEY,
|
344
|
+
[Columns] nvarchar(256) NOT NULL
|
345
|
+
);
|
346
|
+
GO
|
347
|
+
|
348
|
+
END_OF_SQL
|
349
|
+
|
350
|
+
insertion = <<-"END_OF_SQL"
|
351
|
+
INSERT INTO [xmigra].[updated_statistics] ([Name], [Columns]) VALUES
|
352
|
+
END_OF_SQL
|
353
|
+
|
354
|
+
strlit = MSSQLSpecifics.method :string_literal
|
355
|
+
return intro + (insertion + stats_objs.collect do |stats_obj|
|
356
|
+
"(#{strlit[stats_obj.name]}, #{strlit[stats_obj.columns]})"
|
357
|
+
end.join(",\n") + ";\n" unless stats_objs.empty?).to_s
|
358
|
+
end
|
359
|
+
|
360
|
+
def check_preceding_migrations_sql
|
361
|
+
parts = []
|
362
|
+
|
363
|
+
parts << (<<-"END_OF_SQL") if production
|
364
|
+
IF EXISTS (
|
365
|
+
SELECT TOP(1) * FROM [xmigra].[branch_upgrade]
|
366
|
+
) AND NOT EXISTS (
|
367
|
+
SELECT TOP(1) * FROM [xmigra].[branch_upgrade]
|
368
|
+
WHERE #{branch_id_literal} IN ([Current], [Next])
|
369
|
+
)
|
370
|
+
RAISERROR (N'Existing database is from a different (and non-upgradable) branch.', 11, 1);
|
371
|
+
|
372
|
+
END_OF_SQL
|
373
|
+
|
374
|
+
parts << (<<-"END_OF_SQL")
|
375
|
+
IF NOT #{upgrading_to_new_branch_test_sql}
|
376
|
+
BEGIN
|
377
|
+
PRINT N'Checking preceding migrations:';
|
378
|
+
-- Get the ApplicationOrder of the most recent version bridge migration
|
379
|
+
DECLARE @VersionBridge INT;
|
380
|
+
SET @VersionBridge = (
|
381
|
+
SELECT COALESCE(MAX([ApplicationOrder]), 0)
|
382
|
+
FROM [xmigra].[applied]
|
383
|
+
WHERE [VersionBridgeMark] <> 0
|
384
|
+
);
|
385
|
+
|
386
|
+
-- Check for existence of applied migrations after the latest version
|
387
|
+
-- bridge that are not in [xmigra].[migrations]
|
388
|
+
IF EXISTS (
|
389
|
+
SELECT * FROM [xmigra].[applied] a
|
390
|
+
WHERE a.[ApplicationOrder] > @VersionBridge
|
391
|
+
AND a.[MigrationID] NOT IN (
|
392
|
+
SELECT m.[MigrationID] FROM [xmigra].[migrations] m
|
393
|
+
)
|
394
|
+
)
|
395
|
+
RAISERROR (N'Unknown in-version migrations have been applied.', 11, 1);
|
396
|
+
END;
|
397
|
+
END_OF_SQL
|
398
|
+
|
399
|
+
return parts.join('')
|
400
|
+
end
|
401
|
+
|
402
|
+
def check_chain_continuity_sql
|
403
|
+
<<-"END_OF_SQL"
|
404
|
+
IF NOT #{upgrading_to_new_branch_test_sql}
|
405
|
+
BEGIN
|
406
|
+
PRINT N'Checking migration chain continuity:';
|
407
|
+
-- Get the [xmigra].[migrations] ApplicationOrder of the most recent version bridge migration
|
408
|
+
DECLARE @BridgePoint INT;
|
409
|
+
SET @BridgePoint = (
|
410
|
+
SELECT COALESCE(MAX(m.[ApplicationOrder]), 0)
|
411
|
+
FROM [xmigra].[applied] a
|
412
|
+
INNER JOIN [xmigra].[migrations] m
|
413
|
+
ON a.[MigrationID] = m.[MigrationID]
|
414
|
+
WHERE a.[VersionBridgeMark] <> 0
|
415
|
+
);
|
416
|
+
|
417
|
+
-- Test for previously applied migrations that break the continuity of the
|
418
|
+
-- migration chain in this script:
|
419
|
+
IF EXISTS (
|
420
|
+
SELECT *
|
421
|
+
FROM [xmigra].[applied] a
|
422
|
+
INNER JOIN [xmigra].[migrations] m
|
423
|
+
ON a.[MigrationID] = m.[MigrationID]
|
424
|
+
INNER JOIN [xmigra].[migrations] p
|
425
|
+
ON m.[ApplicationOrder] - 1 = p.[ApplicationOrder]
|
426
|
+
WHERE p.[ApplicationOrder] > @BridgePoint
|
427
|
+
AND p.[MigrationID] NOT IN (
|
428
|
+
SELECT a2.[MigrationID] FROM [xmigra].[applied] a2
|
429
|
+
)
|
430
|
+
)
|
431
|
+
BEGIN
|
432
|
+
RAISERROR(
|
433
|
+
N'Previously applied migrations interrupt the continuity of the migration chain',
|
434
|
+
11,
|
435
|
+
1
|
436
|
+
);
|
437
|
+
END;
|
438
|
+
END;
|
439
|
+
END_OF_SQL
|
440
|
+
end
|
441
|
+
|
442
|
+
def select_for_install_sql
|
443
|
+
<<-"END_OF_SQL"
|
444
|
+
PRINT N'Selecting migrations to apply:';
|
445
|
+
DECLARE @BridgePoint INT;
|
446
|
+
IF #{upgrading_to_new_branch_test_sql}
|
447
|
+
BEGIN
|
448
|
+
-- Get the [xmigra].[migrations] ApplicationOrder of the record corresponding to the branch transition
|
449
|
+
SET @BridgePoint = (
|
450
|
+
SELECT MAX(m.[ApplicationOrder])
|
451
|
+
FROM [xmigra].[migrations] m
|
452
|
+
INNER JOIN [xmigra].[branch_upgrade] bu
|
453
|
+
ON m.[MigrationID] = bu.[CompletesMigration]
|
454
|
+
);
|
455
|
+
|
456
|
+
UPDATE [xmigra].[migrations]
|
457
|
+
SET [Install] = 1
|
458
|
+
WHERE [ApplicationOrder] > @BridgePoint;
|
459
|
+
END
|
460
|
+
ELSE BEGIN
|
461
|
+
-- Get the [xmigra].[migrations] ApplicationOrder of the most recent version bridge migration
|
462
|
+
SET @BridgePoint = (
|
463
|
+
SELECT COALESCE(MAX(m.[ApplicationOrder]), 0)
|
464
|
+
FROM [xmigra].[applied] a
|
465
|
+
INNER JOIN [xmigra].[migrations] m
|
466
|
+
ON a.[MigrationID] = m.[MigrationID]
|
467
|
+
WHERE a.[VersionBridgeMark] <> 0
|
468
|
+
);
|
469
|
+
|
470
|
+
UPDATE [xmigra].[migrations]
|
471
|
+
SET [Install] = 1
|
472
|
+
WHERE [MigrationID] NOT IN (
|
473
|
+
SELECT a.[MigrationID] FROM [xmigra].[applied] a
|
474
|
+
)
|
475
|
+
AND [ApplicationOrder] > @BridgePoint;
|
476
|
+
END;
|
477
|
+
END_OF_SQL
|
478
|
+
end
|
479
|
+
|
480
|
+
def production_config_check_sql
|
481
|
+
unless production
|
482
|
+
id_literal = MSSQLSpecifics.string_literal(@migrations[0].id)
|
483
|
+
<<-"END_OF_SQL"
|
484
|
+
PRINT N'Checking for production status:';
|
485
|
+
IF EXISTS (
|
486
|
+
SELECT * FROM [xmigra].[migrations]
|
487
|
+
WHERE [MigrationID] = #{id_literal}
|
488
|
+
AND [Install] <> 0
|
489
|
+
)
|
490
|
+
BEGIN
|
491
|
+
CREATE TABLE [xmigra].[development] (
|
492
|
+
[info] nvarchar(200) NOT NULL PRIMARY KEY
|
493
|
+
);
|
494
|
+
END;
|
495
|
+
GO
|
496
|
+
|
497
|
+
IF NOT EXISTS (
|
498
|
+
SELECT * FROM [sys].[objects]
|
499
|
+
WHERE object_id = OBJECT_ID(N'[xmigra].[development]')
|
500
|
+
AND type = N'U'
|
501
|
+
)
|
502
|
+
RAISERROR(N'Development script cannot be applied to a production database.', 11, 1);
|
503
|
+
END_OF_SQL
|
504
|
+
end
|
505
|
+
end
|
506
|
+
|
507
|
+
def remove_access_artifacts_sql
|
508
|
+
# Iterate the [xmigra].[access_objects] table and drop all access
|
509
|
+
# objects previously created by xmigra
|
510
|
+
return <<-"END_OF_SQL"
|
511
|
+
PRINT N'Removing data access artifacts:';
|
512
|
+
DECLARE @sqlcmd NVARCHAR(1000); -- Built SQL command
|
513
|
+
DECLARE @obj_name NVARCHAR(256); -- Name of object to drop
|
514
|
+
DECLARE @obj_type NVARCHAR(40); -- Type of object to drop
|
515
|
+
|
516
|
+
DECLARE AccObjs_cursor CURSOR LOCAL FOR
|
517
|
+
SELECT [name], [type]
|
518
|
+
FROM [xmigra].[access_objects]
|
519
|
+
ORDER BY [order] DESC;
|
520
|
+
|
521
|
+
OPEN AccObjs_cursor;
|
522
|
+
|
523
|
+
FETCH NEXT FROM AccObjs_cursor INTO @obj_name, @obj_type;
|
524
|
+
|
525
|
+
WHILE @@FETCH_STATUS = 0 BEGIN
|
526
|
+
SET @sqlcmd = N'DROP ' + @obj_type + N' ' + @obj_name + N';';
|
527
|
+
EXEC sp_executesql @sqlcmd;
|
528
|
+
|
529
|
+
FETCH NEXT FROM AccObjs_cursor INTO @obj_name, @obj_type;
|
530
|
+
END;
|
531
|
+
|
532
|
+
CLOSE AccObjs_cursor;
|
533
|
+
DEALLOCATE AccObjs_cursor;
|
534
|
+
|
535
|
+
DELETE FROM [xmigra].[access_objects];
|
536
|
+
|
537
|
+
END_OF_SQL
|
538
|
+
end
|
539
|
+
|
540
|
+
def remove_undesired_indexes_sql
|
541
|
+
<<-"END_OF_SQL"
|
542
|
+
PRINT N'Removing undesired indexes:';
|
543
|
+
-- Iterate over indexes in [xmigra].[indexes] that don't have an entry in
|
544
|
+
-- [xmigra].[updated_indexes].
|
545
|
+
DECLARE @sqlcmd NVARCHAR(1000); -- Built SQL command
|
546
|
+
DECLARE @index_name NVARCHAR(256); -- Name of index to drop
|
547
|
+
DECLARE @table_name SYSNAME; -- Name of table owning index
|
548
|
+
DECLARE @match_count INT; -- Number of matching index names
|
549
|
+
|
550
|
+
DECLARE Index_cursor CURSOR LOCAL FOR
|
551
|
+
SELECT
|
552
|
+
xi.[name],
|
553
|
+
MAX(QUOTENAME(OBJECT_SCHEMA_NAME(si.object_id)) + N'.' + QUOTENAME(OBJECT_NAME(si.object_id))),
|
554
|
+
COUNT(*)
|
555
|
+
FROM [xmigra].[indexes] xi
|
556
|
+
INNER JOIN sys.indexes si ON si.[name] = xi.[name]
|
557
|
+
WHERE xi.[IndexID] NOT IN (
|
558
|
+
SELECT [IndexID]
|
559
|
+
FROM [xmigra].[updated_indexes]
|
560
|
+
)
|
561
|
+
GROUP BY xi.[name];
|
562
|
+
|
563
|
+
OPEN Index_cursor;
|
564
|
+
|
565
|
+
FETCH NEXT FROM Index_cursor INTO @index_name, @table_name, @match_count;
|
566
|
+
|
567
|
+
WHILE @@FETCH_STATUS = 0 BEGIN
|
568
|
+
IF @match_count > 1
|
569
|
+
BEGIN
|
570
|
+
RAISERROR(N'Multiple indexes are named %s', 11, 1, @index_name);
|
571
|
+
END;
|
572
|
+
|
573
|
+
SET @sqlcmd = N'DROP INDEX ' + @index_name + N' ON ' + @table_name + N';';
|
574
|
+
EXEC sp_executesql @sqlcmd;
|
575
|
+
PRINT N' Removed ' + @index_name + N'.';
|
576
|
+
|
577
|
+
FETCH NEXT FROM Index_cursor INTO @index_name, @table_name, @match_count;
|
578
|
+
END;
|
579
|
+
|
580
|
+
CLOSE Index_cursor;
|
581
|
+
DEALLOCATE Index_cursor;
|
582
|
+
|
583
|
+
DELETE FROM [xmigra].[indexes]
|
584
|
+
WHERE [IndexID] NOT IN (
|
585
|
+
SELECT ui.[IndexID]
|
586
|
+
FROM [xmigra].[updated_indexes] ui
|
587
|
+
);
|
588
|
+
END_OF_SQL
|
589
|
+
end
|
590
|
+
|
591
|
+
def remove_undesired_statistics_sql
|
592
|
+
<<-"END_OF_SQL"
|
593
|
+
PRINT N'Removing undesired statistics objects:';
|
594
|
+
-- Iterate over statistics in [xmigra].[statistics] that don't have an entry in
|
595
|
+
-- [xmigra].[updated_statistics].
|
596
|
+
DECLARE @sqlcmd NVARCHAR(1000); -- Built SQL command
|
597
|
+
DECLARE @statsobj_name NVARCHAR(256); -- Name of statistics object to drop
|
598
|
+
DECLARE @table_name SYSNAME; -- Name of table owning the statistics object
|
599
|
+
DECLARE @match_count INT; -- Number of matching statistics object names
|
600
|
+
|
601
|
+
DECLARE Stats_cursor CURSOR LOCAL FOR
|
602
|
+
SELECT
|
603
|
+
QUOTENAME(xs.[Name]),
|
604
|
+
MAX(QUOTENAME(OBJECT_SCHEMA_NAME(ss.object_id)) + N'.' + QUOTENAME(OBJECT_NAME(ss.object_id))),
|
605
|
+
COUNT(ss.object_id)
|
606
|
+
FROM [xmigra].[statistics] xs
|
607
|
+
INNER JOIN sys.stats ss ON ss.[name] = xs.[Name]
|
608
|
+
WHERE xs.[Columns] NOT IN (
|
609
|
+
SELECT us.[Columns]
|
610
|
+
FROM [xmigra].[updated_statistics] us
|
611
|
+
WHERE us.[Name] = xs.[Name]
|
612
|
+
)
|
613
|
+
GROUP BY xs.[Name];
|
614
|
+
|
615
|
+
OPEN Stats_cursor;
|
616
|
+
|
617
|
+
FETCH NEXT FROM Stats_cursor INTO @statsobj_name, @table_name, @match_count;
|
618
|
+
|
619
|
+
WHILE @@FETCH_STATUS = 0 BEGIN
|
620
|
+
IF @match_count > 1
|
621
|
+
BEGIN
|
622
|
+
RAISERROR(N'Multiple indexes are named %s', 11, 1, @statsobj_name);
|
623
|
+
END;
|
624
|
+
|
625
|
+
SET @sqlcmd = N'DROP STATISTICS ' + @table_name + N'.' + @statsobj_name + N';';
|
626
|
+
EXEC sp_executesql @sqlcmd;
|
627
|
+
PRINT N' Removed statistics object ' + @statsobj_name + N'.'
|
628
|
+
|
629
|
+
FETCH NEXT FROM Stats_cursor INTO @statsobj_name, @table_name, @match_count;
|
630
|
+
END;
|
631
|
+
|
632
|
+
CLOSE Stats_cursor;
|
633
|
+
DEALLOCATE Stats_cursor;
|
634
|
+
|
635
|
+
DELETE FROM [xmigra].[statistics]
|
636
|
+
WHERE [Columns] NOT IN (
|
637
|
+
SELECT us.[Columns]
|
638
|
+
FROM [xmigra].[updated_statistics] us
|
639
|
+
WHERE us.[Name] = [Name]
|
640
|
+
);
|
641
|
+
END_OF_SQL
|
642
|
+
end
|
643
|
+
|
644
|
+
def create_new_indexes_sql
|
645
|
+
indexes.collect do |index|
|
646
|
+
index_id_literal = MSSQLSpecifics.string_literal(index.id)
|
647
|
+
index_name_literal = MSSQLSpecifics.string_literal(index.name)
|
648
|
+
<<-"END_OF_SQL"
|
649
|
+
PRINT N'Index ' + #{index_id_literal} + ':';
|
650
|
+
IF EXISTS(
|
651
|
+
SELECT * FROM [xmigra].[updated_indexes] ui
|
652
|
+
WHERE ui.[IndexID] = #{index_id_literal}
|
653
|
+
AND ui.[IndexID] NOT IN (
|
654
|
+
SELECT i.[IndexID] FROM [xmigra].[indexes] i
|
655
|
+
)
|
656
|
+
)
|
657
|
+
BEGIN
|
658
|
+
IF EXISTS (
|
659
|
+
SELECT * FROM sys.indexes
|
660
|
+
WHERE [name] = #{index_name_literal}
|
661
|
+
)
|
662
|
+
BEGIN
|
663
|
+
RAISERROR(N'An index already exists named %s', 11, 1, #{index_name_literal});
|
664
|
+
END;
|
665
|
+
|
666
|
+
PRINT N' Creating...';
|
667
|
+
#{index.definition_sql};
|
668
|
+
|
669
|
+
IF (SELECT COUNT(*) FROM sys.indexes WHERE [name] = #{index_name_literal}) <> 1
|
670
|
+
BEGIN
|
671
|
+
RAISERROR(N'Index %s was not created by its definition.', 11, 1,
|
672
|
+
#{index_name_literal});
|
673
|
+
END;
|
674
|
+
|
675
|
+
INSERT INTO [xmigra].[indexes] ([IndexID], [name])
|
676
|
+
VALUES (#{index_id_literal}, #{index_name_literal});
|
677
|
+
END
|
678
|
+
ELSE
|
679
|
+
BEGIN
|
680
|
+
PRINT N' Already exists.';
|
681
|
+
END;
|
682
|
+
END_OF_SQL
|
683
|
+
end.join(ddl_block_separator)
|
684
|
+
end
|
685
|
+
|
686
|
+
def create_new_statistics_sql
|
687
|
+
stats_objs.collect do |stats_obj|
|
688
|
+
stats_name = MSSQLSpecifics.string_literal(stats_obj.name)
|
689
|
+
strlit = lambda {|s| MSSQLSpecifics.string_literal(s)}
|
690
|
+
|
691
|
+
stats_obj.creation_sql
|
692
|
+
<<-"END_OF_SQL"
|
693
|
+
PRINT N'Statistics object #{stats_obj.name}:';
|
694
|
+
IF EXISTS (
|
695
|
+
SELECT * FROM [xmigra].[updated_statistics] us
|
696
|
+
WHERE us.[Name] = #{stats_name}
|
697
|
+
AND us.[Columns] NOT IN (
|
698
|
+
SELECT s.[Columns]
|
699
|
+
FROM [xmigra].[statistics] s
|
700
|
+
WHERE s.[Name] = us.[Name]
|
701
|
+
)
|
702
|
+
)
|
703
|
+
BEGIN
|
704
|
+
IF EXISTS (
|
705
|
+
SELECT * FROM sys.stats
|
706
|
+
WHERE [name] = #{stats_name}
|
707
|
+
)
|
708
|
+
BEGIN
|
709
|
+
RAISERROR(N'A statistics object named %s already exists.', 11, 1, #{stats_name})
|
710
|
+
END;
|
711
|
+
|
712
|
+
PRINT N' Creating...';
|
713
|
+
#{stats_obj.creation_sql}
|
714
|
+
|
715
|
+
INSERT INTO [xmigra].[statistics] ([Name], [Columns])
|
716
|
+
VALUES (#{stats_name}, #{strlit[stats_obj.columns]})
|
717
|
+
END
|
718
|
+
ELSE
|
719
|
+
BEGIN
|
720
|
+
PRINT N' Already exists.';
|
721
|
+
END;
|
722
|
+
END_OF_SQL
|
723
|
+
end.join(ddl_block_separator)
|
724
|
+
end
|
725
|
+
|
726
|
+
def upgrade_cleanup_sql
|
727
|
+
<<-"END_OF_SQL"
|
728
|
+
PRINT N'Cleaning up from the upgrade:';
|
729
|
+
DROP TABLE [xmigra].[migrations];
|
730
|
+
DROP TABLE [xmigra].[updated_indexes];
|
731
|
+
DROP TABLE [xmigra].[updated_statistics];
|
732
|
+
END_OF_SQL
|
733
|
+
end
|
734
|
+
|
735
|
+
def ensure_permissions_table_sql
|
736
|
+
strlit = MSSQLSpecifics.method(:string_literal)
|
737
|
+
<<-"END_OF_SQL"
|
738
|
+
-- ------------ SET UP XMIGRA PERMISSION TRACKING OBJECTS ------------ --
|
739
|
+
|
740
|
+
PRINT N'Setting up XMigra permission tracking:';
|
741
|
+
IF NOT EXISTS (
|
742
|
+
SELECT * FROM sys.schemas
|
743
|
+
WHERE name = N'xmigra'
|
744
|
+
)
|
745
|
+
BEGIN
|
746
|
+
EXEC sp_executesql N'
|
747
|
+
CREATE SCHEMA [xmigra] AUTHORIZATION [dbo];
|
748
|
+
';
|
749
|
+
END;
|
750
|
+
GO
|
751
|
+
|
752
|
+
IF NOT EXISTS(
|
753
|
+
SELECT * FROM sys.objects
|
754
|
+
WHERE object_id = OBJECT_ID(N'[xmigra].[revokable_permissions]')
|
755
|
+
AND type IN (N'U')
|
756
|
+
)
|
757
|
+
BEGIN
|
758
|
+
CREATE TABLE [xmigra].[revokable_permissions] (
|
759
|
+
[permissions] nvarchar(200) NOT NULL,
|
760
|
+
[object] nvarchar(260) NOT NULL,
|
761
|
+
[principal_id] int NOT NULL
|
762
|
+
) ON [PRIMARY];
|
763
|
+
END;
|
764
|
+
GO
|
765
|
+
|
766
|
+
IF EXISTS(
|
767
|
+
SELECT * FROM sys.objects
|
768
|
+
WHERE object_id = OBJECT_ID(N'[xmigra].[ip_prepare_revoke]')
|
769
|
+
AND type IN (N'P', N'PC')
|
770
|
+
)
|
771
|
+
BEGIN
|
772
|
+
DROP PROCEDURE [xmigra].[ip_prepare_revoke];
|
773
|
+
END;
|
774
|
+
GO
|
775
|
+
|
776
|
+
CREATE PROCEDURE [xmigra].[ip_prepare_revoke]
|
777
|
+
(
|
778
|
+
@permissions nvarchar(200),
|
779
|
+
@object nvarchar(260),
|
780
|
+
@principal sysname
|
781
|
+
)
|
782
|
+
AS
|
783
|
+
BEGIN
|
784
|
+
INSERT INTO [xmigra].[revokable_permissions] ([permissions], [object], [principal_id])
|
785
|
+
VALUES (@permissions, @object, DATABASE_PRINCIPAL_ID(@principal));
|
786
|
+
END;
|
787
|
+
END_OF_SQL
|
788
|
+
end
|
789
|
+
|
790
|
+
def revoke_previous_permissions_sql
|
791
|
+
<<-"END_OF_SQL"
|
792
|
+
|
793
|
+
-- ------------- REVOKING PREVIOUSLY GRANTED PERMISSIONS ------------- --
|
794
|
+
|
795
|
+
PRINT N'Revoking previously granted permissions:';
|
796
|
+
-- Iterate over permissions listed in [xmigra].[revokable_permissions]
|
797
|
+
DECLARE @sqlcmd NVARCHAR(1000); -- Built SQL command
|
798
|
+
DECLARE @permissions NVARCHAR(200);
|
799
|
+
DECLARE @object NVARCHAR(260);
|
800
|
+
DECLARE @principal NVARCHAR(150);
|
801
|
+
|
802
|
+
DECLARE Permission_cursor CURSOR LOCAL FOR
|
803
|
+
SELECT
|
804
|
+
xp.[permissions],
|
805
|
+
xp.[object],
|
806
|
+
QUOTENAME(sdp.name)
|
807
|
+
FROM [xmigra].[revokable_permissions] xp
|
808
|
+
INNER JOIN sys.database_principals sdp ON xp.principal_id = sdp.principal_id;
|
809
|
+
|
810
|
+
OPEN Permission_cursor;
|
811
|
+
|
812
|
+
FETCH NEXT FROM Permission_cursor INTO @permissions, @object, @principal;
|
813
|
+
|
814
|
+
WHILE @@FETCH_STATUS = 0 BEGIN
|
815
|
+
SET @sqlcmd = N'REVOKE ' + @permissions + N' ON ' + @object + ' FROM ' + @principal + N';';
|
816
|
+
BEGIN TRY
|
817
|
+
EXEC sp_executesql @sqlcmd;
|
818
|
+
END TRY
|
819
|
+
BEGIN CATCH
|
820
|
+
END CATCH
|
821
|
+
|
822
|
+
FETCH NEXT FROM Permission_cursor INTO @permissions, @object, @principal;
|
823
|
+
END;
|
824
|
+
|
825
|
+
CLOSE Permission_cursor;
|
826
|
+
DEALLOCATE Permission_cursor;
|
827
|
+
|
828
|
+
DELETE FROM [xmigra].[revokable_permissions];
|
829
|
+
END_OF_SQL
|
830
|
+
end
|
831
|
+
|
832
|
+
def granting_permissions_comment_sql
|
833
|
+
<<-"END_OF_SQL"
|
834
|
+
|
835
|
+
-- ---------------------- GRANTING PERMISSIONS ----------------------- --
|
836
|
+
|
837
|
+
END_OF_SQL
|
838
|
+
end
|
839
|
+
|
840
|
+
def grant_permissions_sql(permissions, object, principal)
|
841
|
+
strlit = MSSQLSpecifics.method(:string_literal)
|
842
|
+
permissions_string = permissions.to_a.join(', ')
|
843
|
+
|
844
|
+
<<-"END_OF_SQL"
|
845
|
+
PRINT N'Granting #{permissions_string} on #{object} to #{principal}:';
|
846
|
+
GRANT #{permissions_string} ON #{object} TO #{principal};
|
847
|
+
EXEC [xmigra].[ip_prepare_revoke] #{strlit[permissions_string]}, #{strlit[object]}, #{strlit[principal]};
|
848
|
+
END_OF_SQL
|
849
|
+
end
|
850
|
+
|
851
|
+
def insert_access_creation_record_sql
|
852
|
+
name_literal = MSSQLSpecifics.string_literal(quoted_name)
|
853
|
+
|
854
|
+
<<-"END_OF_SQL"
|
855
|
+
INSERT INTO [xmigra].[access_objects] ([type], [name])
|
856
|
+
VALUES (N'#{self.class::OBJECT_TYPE}', #{name_literal});
|
857
|
+
END_OF_SQL
|
858
|
+
end
|
859
|
+
|
860
|
+
# Call on an extended Migration object to get the SQL to execute.
|
861
|
+
def migration_application_sql
|
862
|
+
id_literal = MSSQLSpecifics.string_literal(id)
|
863
|
+
template = <<-"END_OF_SQL"
|
864
|
+
IF EXISTS (
|
865
|
+
SELECT * FROM [xmigra].[migrations]
|
866
|
+
WHERE [MigrationID] = #{id_literal}
|
867
|
+
AND [Install] <> 0
|
868
|
+
)
|
869
|
+
BEGIN
|
870
|
+
PRINT #{MSSQLSpecifics.string_literal('Applying "' + File.basename(file_path) + '":')};
|
871
|
+
|
872
|
+
%s
|
873
|
+
|
874
|
+
INSERT INTO [xmigra].[applied] ([MigrationID], [Description])
|
875
|
+
VALUES (#{id_literal}, #{MSSQLSpecifics.string_literal(description)});
|
876
|
+
END;
|
877
|
+
END_OF_SQL
|
878
|
+
|
879
|
+
parts = []
|
880
|
+
|
881
|
+
each_batch(sql) do |batch|
|
882
|
+
parts << batch
|
883
|
+
end
|
884
|
+
|
885
|
+
return (template % parts.collect do |batch|
|
886
|
+
"EXEC sp_executesql @statement = " + MSSQLSpecifics.string_literal(batch) + ";"
|
887
|
+
end.join("\n"))
|
888
|
+
end
|
889
|
+
|
890
|
+
def each_batch(sql)
|
891
|
+
current_batch_lines = []
|
892
|
+
sql.each_line do |line|
|
893
|
+
if line.strip.upcase == 'GO'
|
894
|
+
batch = current_batch_lines.join('')
|
895
|
+
yield batch unless batch.strip.empty?
|
896
|
+
current_batch_lines.clear
|
897
|
+
else
|
898
|
+
current_batch_lines << line
|
899
|
+
end
|
900
|
+
end
|
901
|
+
unless current_batch_lines.empty?
|
902
|
+
batch = current_batch_lines.join('')
|
903
|
+
yield batch unless batch.strip.empty?
|
904
|
+
end
|
905
|
+
end
|
906
|
+
|
907
|
+
def batch_separator
|
908
|
+
"GO\n"
|
909
|
+
end
|
910
|
+
|
911
|
+
def check_existence_sql(for_existence, error_message)
|
912
|
+
error_message = sprintf(error_message, quoted_name)
|
913
|
+
|
914
|
+
return <<-"END_OF_SQL"
|
915
|
+
|
916
|
+
IF #{"NOT" if for_existence} #{existence_test_sql}
|
917
|
+
RAISERROR(N'#{error_message}', 11, 1);
|
918
|
+
END_OF_SQL
|
919
|
+
end
|
920
|
+
|
921
|
+
def creation_notice
|
922
|
+
return "PRINT " + MSSQLSpecifics.string_literal("Creating #{printable_type} #{quoted_name}:") + ";"
|
923
|
+
end
|
924
|
+
|
925
|
+
def name_parts
|
926
|
+
if m = DBNAME_PATTERN.match(name)
|
927
|
+
[m[1], m[2]].compact.collect do |p|
|
928
|
+
MSSQLSpecifics.strip_identifier_quoting(p)
|
929
|
+
end
|
930
|
+
else
|
931
|
+
raise XMigra::Error, "Invalid database object name"
|
932
|
+
end
|
933
|
+
end
|
934
|
+
|
935
|
+
def quoted_name
|
936
|
+
name_parts.collect do |p|
|
937
|
+
"[]".insert(1, p)
|
938
|
+
end.join('.')
|
939
|
+
end
|
940
|
+
|
941
|
+
def object_type_codes
|
942
|
+
MSSQLSpecifics.object_type_codes(self)
|
943
|
+
end
|
944
|
+
|
945
|
+
def existence_test_sql
|
946
|
+
object_type_list = object_type_codes.collect {|t| "N'#{t}'"}.join(', ')
|
947
|
+
|
948
|
+
return <<-"END_OF_SQL"
|
949
|
+
EXISTS (
|
950
|
+
SELECT * FROM sys.objects
|
951
|
+
WHERE object_id = OBJECT_ID(N'#{quoted_name}')
|
952
|
+
AND type IN (#{object_type_list})
|
953
|
+
)
|
954
|
+
END_OF_SQL
|
955
|
+
end
|
956
|
+
|
957
|
+
def branch_id_literal
|
958
|
+
@mssql_branch_id_literal ||= MSSQLSpecifics.string_literal(XMigra.secure_digest(branch_identifier))
|
959
|
+
end
|
960
|
+
|
961
|
+
def upgrading_to_new_branch_test_sql
|
962
|
+
return "(0 = 1)" unless respond_to? :branch_identifier
|
963
|
+
|
964
|
+
(<<-"END_OF_SQL").chomp
|
965
|
+
(EXISTS (
|
966
|
+
SELECT TOP(1) * FROM [xmigra].[branch_upgrade]
|
967
|
+
WHERE [Next] = #{branch_id_literal}
|
968
|
+
))
|
969
|
+
END_OF_SQL
|
970
|
+
end
|
971
|
+
|
972
|
+
def branch_upgrade_sql
|
973
|
+
return unless respond_to? :branch_identifier
|
974
|
+
|
975
|
+
parts = [<<-"END_OF_SQL"]
|
976
|
+
IF #{upgrading_to_new_branch_test_sql}
|
977
|
+
BEGIN
|
978
|
+
PRINT N'Migrating from previous schema branch:';
|
979
|
+
|
980
|
+
DECLARE @sqlcmd NVARCHAR(MAX);
|
981
|
+
|
982
|
+
DECLARE CmdCursor CURSOR LOCAL FOR
|
983
|
+
SELECT bu.[UpgradeSql]
|
984
|
+
FROM [xmigra].[branch_upgrade] bu
|
985
|
+
WHERE bu.[Next] = #{branch_id_literal}
|
986
|
+
ORDER BY bu.[ApplicationOrder] ASC;
|
987
|
+
|
988
|
+
OPEN CmdCursor;
|
989
|
+
|
990
|
+
FETCH NEXT FROM CmdCursor INTO @sqlcmd;
|
991
|
+
|
992
|
+
WHILE @@FETCH_STATUS = 0 BEGIN
|
993
|
+
EXECUTE sp_executesql @sqlcmd;
|
994
|
+
|
995
|
+
FETCH NEXT FROM CmdCursor INTO @sqlcmd;
|
996
|
+
END;
|
997
|
+
|
998
|
+
CLOSE CmdCursor;
|
999
|
+
DEALLOCATE CmdCursor;
|
1000
|
+
|
1001
|
+
DECLARE @applied NVARCHAR(80);
|
1002
|
+
DECLARE @old_branch NVARCHAR(80);
|
1003
|
+
|
1004
|
+
SELECT TOP(1) @applied = [CompletesMigration], @old_branch = [Current]
|
1005
|
+
FROM [xmigra].[branch_upgrade]
|
1006
|
+
WHERE [Next] = #{branch_id_literal};
|
1007
|
+
|
1008
|
+
-- Delete the "applied" record for the migration if there was one, so that
|
1009
|
+
-- a new record with this ID can be inserted.
|
1010
|
+
DELETE FROM [xmigra].[applied] WHERE [MigrationID] = @applied;
|
1011
|
+
|
1012
|
+
-- Create a "version bridge" record in the "applied" table for the branch upgrade
|
1013
|
+
INSERT INTO [xmigra].[applied] ([MigrationID], [VersionBridgeMark], [Description])
|
1014
|
+
VALUES (@applied, 1, N'Branch upgrade from branch ' + @old_branch);
|
1015
|
+
END;
|
1016
|
+
|
1017
|
+
DELETE FROM [xmigra].[branch_upgrade];
|
1018
|
+
|
1019
|
+
END_OF_SQL
|
1020
|
+
|
1021
|
+
if branch_upgrade.applicable? migrations
|
1022
|
+
batch_template = <<-"END_OF_SQL"
|
1023
|
+
INSERT INTO [xmigra].[branch_upgrade]
|
1024
|
+
([Current], [Next], [CompletesMigration], [UpgradeSql])
|
1025
|
+
VALUES (
|
1026
|
+
#{branch_id_literal},
|
1027
|
+
#{MSSQLSpecifics.string_literal(branch_upgrade.target_branch)},
|
1028
|
+
#{MSSQLSpecifics.string_literal(branch_upgrade.migration_completed_id)},
|
1029
|
+
%s
|
1030
|
+
);
|
1031
|
+
END_OF_SQL
|
1032
|
+
|
1033
|
+
each_batch(branch_upgrade.sql) do |batch|
|
1034
|
+
# Insert the batch into the [xmigra].[branch_upgrade] table
|
1035
|
+
parts << (batch_template % MSSQLSpecifics.string_literal(batch))
|
1036
|
+
end
|
1037
|
+
else
|
1038
|
+
# Insert a placeholder that only declares the current branch of the schema
|
1039
|
+
parts << <<-"END_OF_SQL"
|
1040
|
+
INSERT INTO [xmigra].[branch_upgrade] ([Current]) VALUES (#{branch_id_literal});
|
1041
|
+
END_OF_SQL
|
1042
|
+
end
|
1043
|
+
|
1044
|
+
return parts.join("\n")
|
1045
|
+
end
|
1046
|
+
|
1047
|
+
class << self
|
1048
|
+
def strip_identifier_quoting(s)
|
1049
|
+
case
|
1050
|
+
when s.empty? then return s
|
1051
|
+
when s[0,1] == "[" && s[-1,1] == "]" then return s[1..-2]
|
1052
|
+
when s[0,1] == '"' && s[-1,1] == '"' then return s[1..-2]
|
1053
|
+
else return s
|
1054
|
+
end
|
1055
|
+
end
|
1056
|
+
|
1057
|
+
def object_type_codes(type)
|
1058
|
+
case type
|
1059
|
+
when StoredProcedure then %w{P PC}
|
1060
|
+
when View then ['V']
|
1061
|
+
when Function then %w{AF FN FS FT IF TF}
|
1062
|
+
end
|
1063
|
+
end
|
1064
|
+
|
1065
|
+
def string_literal(s)
|
1066
|
+
"N'#{s.gsub("'","''")}'"
|
1067
|
+
end
|
1068
|
+
end
|
1069
|
+
end
|
1070
|
+
end
|