xmigra 1.0.1 → 1.1.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/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
|