xmigra 1.1.0 → 1.2.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/README.md +2 -2
- data/lib/xmigra/db_support/mssql.rb +1 -3
- data/lib/xmigra/db_support/psql.rb +673 -0
- data/lib/xmigra/new_migration_adder.rb +1 -1
- data/lib/xmigra/utils.rb +20 -0
- data/lib/xmigra/vcs_support/git.rb +6 -1
- data/lib/xmigra/version.rb +1 -1
- data/lib/xmigra.rb +13 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3e69aae7f431f58d88125bfc0a31adb4a0f3b76c
|
4
|
+
data.tar.gz: 65f872a4d23fd7515bf6231c636c0f10725effc7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 299d09f45384c3dc16473c59e6d2494583c8486d776a3edef6ab4123e2245374e4cbe13472b73c013f9c82081f44fdef0a242b6048f694d666f2b5102b6a988d
|
7
|
+
data.tar.gz: 5016ef96078d289add80ec678ca6a5e989fcaac0a9dc4fd30ce186c0fc7e766ab22c00c752f5f62c0c11ccdf6621b808d920709792b2dae8d0df774e3e61c158
|
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# XMigra
|
1
|
+
# XMigra [](http://badge.fury.io/rb/xmigra)
|
2
2
|
|
3
3
|
The world of persistent data is a wild and wooly place. Let XMigra make it
|
4
4
|
easier!
|
@@ -40,7 +40,7 @@ easier!
|
|
40
40
|
|
41
41
|
**Databases**
|
42
42
|
* Microsoft SQL Server (2005 and later)
|
43
|
-
* PostgreSQL (
|
43
|
+
* PostgreSQL (tested on 9.3, should work for 8.4 or later)
|
44
44
|
|
45
45
|
## Tell Me More!
|
46
46
|
|
@@ -322,8 +322,6 @@ INSERT INTO [xmigra].[updated_indexes] ([IndexID]) VALUES
|
|
322
322
|
return intro + (insertion + indexes.collect do |index|
|
323
323
|
"(#{strlit[index.id]})"
|
324
324
|
end.join(",\n") + ";\n" unless indexes.empty?).to_s
|
325
|
-
|
326
|
-
return intro
|
327
325
|
end
|
328
326
|
|
329
327
|
def create_and_fill_statistics_table_sql
|
@@ -392,7 +390,7 @@ BEGIN
|
|
392
390
|
SELECT m.[MigrationID] FROM [xmigra].[migrations] m
|
393
391
|
)
|
394
392
|
)
|
395
|
-
RAISERROR (N'Unknown in-
|
393
|
+
RAISERROR (N'Unknown in-branch migrations have been applied.', 11, 1);
|
396
394
|
END;
|
397
395
|
END_OF_SQL
|
398
396
|
|
@@ -0,0 +1,673 @@
|
|
1
|
+
require 'digest/md5'
|
2
|
+
require 'xmigra/utils'
|
3
|
+
|
4
|
+
module XMigra
|
5
|
+
module PgSQLSpecifics
|
6
|
+
DatabaseSupportModules << self
|
7
|
+
|
8
|
+
SYSTEM_NAME = 'PostgreSQL'
|
9
|
+
|
10
|
+
IDENTIFIER_SUBPATTERN = '[[:alpha:]_][[:alnum:]_$]*|"(?:[^"]|"")+"'
|
11
|
+
DBNAME_PATTERN = /^
|
12
|
+
(?:(#{IDENTIFIER_SUBPATTERN})\.)?
|
13
|
+
(#{IDENTIFIER_SUBPATTERN})
|
14
|
+
(\(
|
15
|
+
(?:
|
16
|
+
(?:#{IDENTIFIER_SUBPATTERN}\.)?#{IDENTIFIER_SUBPATTERN}
|
17
|
+
(?:,\s*
|
18
|
+
(?:#{IDENTIFIER_SUBPATTERN}\.)?#{IDENTIFIER_SUBPATTERN}
|
19
|
+
)*
|
20
|
+
)?
|
21
|
+
\))?
|
22
|
+
$/ix
|
23
|
+
|
24
|
+
def filename_metavariable; "[{filename}]"; end
|
25
|
+
|
26
|
+
def in_ddl_transaction
|
27
|
+
["BEGIN;", yield, "COMMIT;"].join("\n")
|
28
|
+
end
|
29
|
+
|
30
|
+
def check_execution_environment_sql
|
31
|
+
XMigra.dedent %Q{
|
32
|
+
CREATE OR REPLACE FUNCTION enable_plpgsql() RETURNS VOID AS $$
|
33
|
+
CREATE LANGUAGE plpgsql;
|
34
|
+
$$ LANGUAGE SQL;
|
35
|
+
|
36
|
+
SELECT
|
37
|
+
CASE
|
38
|
+
WHEN EXISTS(
|
39
|
+
SELECT 1
|
40
|
+
FROM pg_catalog.pg_language
|
41
|
+
WHERE lanname = 'plpgsql'
|
42
|
+
)
|
43
|
+
THEN NULL
|
44
|
+
ELSE enable_plpgsql()
|
45
|
+
END;
|
46
|
+
|
47
|
+
DROP FUNCTION enable_plpgsql();
|
48
|
+
|
49
|
+
CREATE OR REPLACE FUNCTION f_raise(text)
|
50
|
+
RETURNS VOID
|
51
|
+
LANGUAGE plpgsql AS
|
52
|
+
$$
|
53
|
+
BEGIN
|
54
|
+
RAISE EXCEPTION '%', $1;
|
55
|
+
END;
|
56
|
+
$$;
|
57
|
+
|
58
|
+
CREATE OR REPLACE FUNCTION f_alert(text)
|
59
|
+
RETURNS VOID
|
60
|
+
LANGUAGE plpgsql AS
|
61
|
+
$$
|
62
|
+
BEGIN
|
63
|
+
RAISE NOTICE '%', $1;
|
64
|
+
END;
|
65
|
+
$$;
|
66
|
+
|
67
|
+
CREATE OR REPLACE FUNCTION f_resolvename(varchar(100), varchar(100))
|
68
|
+
RETURNS OID
|
69
|
+
LANGUAGE plpgsql AS
|
70
|
+
$$
|
71
|
+
DECLARE
|
72
|
+
Statement TEXT;
|
73
|
+
Result OID;
|
74
|
+
BEGIN
|
75
|
+
Statement := 'SELECT ' || quote_literal($1) || '::' || $2 || '::oid;';
|
76
|
+
EXECUTE Statement INTO Result;
|
77
|
+
RETURN Result;
|
78
|
+
EXCEPTION
|
79
|
+
WHEN OTHERS THEN RETURN NULL;
|
80
|
+
END;
|
81
|
+
$$;
|
82
|
+
|
83
|
+
SELECT
|
84
|
+
CASE
|
85
|
+
WHEN current_database() IN ('postgres', 'template0', 'template1')
|
86
|
+
THEN f_raise('Invalid target database.')
|
87
|
+
END;
|
88
|
+
}
|
89
|
+
end
|
90
|
+
|
91
|
+
def ensure_version_tables_sql
|
92
|
+
PgSQLSpecifics.in_plpgsql %Q{
|
93
|
+
RAISE NOTICE 'Ensuring version tables:';
|
94
|
+
|
95
|
+
IF NOT EXISTS(
|
96
|
+
SELECT * FROM information_schema.schemata
|
97
|
+
WHERE schema_name = 'xmigra'
|
98
|
+
) THEN
|
99
|
+
CREATE SCHEMA xmigra;
|
100
|
+
END IF;
|
101
|
+
|
102
|
+
IF NOT EXISTS(
|
103
|
+
SELECT * FROM information_schema.tables
|
104
|
+
WHERE table_schema = 'xmigra' AND table_name = 'applied'
|
105
|
+
) THEN
|
106
|
+
CREATE TABLE xmigra.applied (
|
107
|
+
"MigrationID" varchar(80) PRIMARY KEY,
|
108
|
+
"ApplicationOrder" SERIAL,
|
109
|
+
"VersionBridgeMark" boolean DEFAULT FALSE NOT NULL,
|
110
|
+
"Description" text NOT NULL
|
111
|
+
);
|
112
|
+
END IF;
|
113
|
+
|
114
|
+
IF NOT EXISTS(
|
115
|
+
SELECT * FROM information_schema.tables
|
116
|
+
WHERE table_schema = 'xmigra' AND table_name = 'access_objects'
|
117
|
+
) THEN
|
118
|
+
CREATE TABLE xmigra.access_objects (
|
119
|
+
"type" varchar(40) NOT NULL,
|
120
|
+
"name" varchar(150) PRIMARY KEY,
|
121
|
+
"order" SERIAL
|
122
|
+
);
|
123
|
+
END IF;
|
124
|
+
|
125
|
+
IF NOT EXISTS(
|
126
|
+
SELECT * FROM information_schema.tables
|
127
|
+
WHERE table_schema = 'xmigra' and table_name = 'indexes'
|
128
|
+
) THEN
|
129
|
+
CREATE TABLE xmigra.indexes (
|
130
|
+
"IndexID" varchar(80) PRIMARY KEY,
|
131
|
+
"name" varchar(150) NOT NULL
|
132
|
+
);
|
133
|
+
END IF;
|
134
|
+
|
135
|
+
IF NOT EXISTS(
|
136
|
+
SELECT * FROM information_schema.tables
|
137
|
+
WHERE table_schema = 'xmigra' and table_name = 'branch_upgrade'
|
138
|
+
) THEN
|
139
|
+
CREATE TABLE xmigra.branch_upgrade (
|
140
|
+
"ApplicationOrder" SERIAL,
|
141
|
+
"Current" varchar(80) PRIMARY KEY,
|
142
|
+
"Next" varchar(80) NULL,
|
143
|
+
"UpgradeSql" text NULL,
|
144
|
+
"CompletesMigration" varchar(80) NULL
|
145
|
+
);
|
146
|
+
END IF;
|
147
|
+
|
148
|
+
RAISE NOTICE ' done';
|
149
|
+
}
|
150
|
+
end
|
151
|
+
|
152
|
+
def create_and_fill_migration_table_sql
|
153
|
+
intro = XMigra.dedent %Q{
|
154
|
+
CREATE TEMP TABLE temp$xmigra_migrations (
|
155
|
+
"MigrationID" varchar(80) NOT NULL,
|
156
|
+
"ApplicationOrder" int NOT NULL,
|
157
|
+
"Description" text NOT NULL,
|
158
|
+
"Install" boolean DEFAULT FALSE NOT NULL
|
159
|
+
) ON COMMIT DROP;
|
160
|
+
}
|
161
|
+
|
162
|
+
mig_insert = XMigra.dedent %Q{
|
163
|
+
INSERT INTO temp$xmigra_migrations ("MigrationID", "ApplicationOrder", "Description")
|
164
|
+
VALUES (%s);
|
165
|
+
}
|
166
|
+
|
167
|
+
parts = [intro]
|
168
|
+
migrations.each_with_index do |m, i|
|
169
|
+
description_literal = PgSQLSpecifics.string_literal(m.description.strip)
|
170
|
+
parts << (mig_insert % ["'#{m.id}', #{i + 1}, #{description_literal}"])
|
171
|
+
end
|
172
|
+
return parts.join('')
|
173
|
+
end
|
174
|
+
|
175
|
+
def create_and_fill_indexes_table_sql
|
176
|
+
intro = XMigra.dedent %Q{
|
177
|
+
CREATE TEMP TABLE temp$xmigra_updated_indexes (
|
178
|
+
"IndexID" varchar(80) PRIMARY KEY
|
179
|
+
) ON COMMIT DROP;
|
180
|
+
}
|
181
|
+
|
182
|
+
insertion = XMigra.dedent %Q{
|
183
|
+
INSERT INTO temp$xmigra_updated_indexes ("IndexID") VALUES (%s);
|
184
|
+
}
|
185
|
+
|
186
|
+
return intro + indexes.collect do |index|
|
187
|
+
insertion % [PgSQLSpecifics.string_literal(index.id)]
|
188
|
+
end.join("\n")
|
189
|
+
end
|
190
|
+
|
191
|
+
def check_preceding_migrations_sql
|
192
|
+
branch_check = production ? XMigra.dedent(%Q{
|
193
|
+
IF EXISTS(
|
194
|
+
SELECT * FROM xmigra.branch_upgrade LIMIT 1
|
195
|
+
) AND NOT EXISTS(
|
196
|
+
SELECT * FROM xmigra.branch_upgrade LIMIT 1
|
197
|
+
WHERE #{branch_id_literal} IN ("Current", "Next")
|
198
|
+
) THEN
|
199
|
+
RAISE EXCEPTION 'Existing database is from a different (and non-upgradable) branch.';
|
200
|
+
END IF;
|
201
|
+
|
202
|
+
}, ' ') : ''
|
203
|
+
|
204
|
+
PgSQLSpecifics.in_plpgsql({:VersionBridge => "INT"}, %Q{
|
205
|
+
#{branch_check[2..-1]}
|
206
|
+
|
207
|
+
RAISE NOTICE 'Checking preceding migrations:';
|
208
|
+
|
209
|
+
IF NOT #{upgrading_to_new_branch_test_sql} THEN
|
210
|
+
VersionBridge := (
|
211
|
+
SELECT COALESCE(MAX("ApplicationOrder"), 0)
|
212
|
+
FROM xmigra.applied
|
213
|
+
WHERE "VersionBridgeMark"
|
214
|
+
);
|
215
|
+
|
216
|
+
IF EXISTS (
|
217
|
+
SELECT * FROM xmigra.applied a
|
218
|
+
WHERE a."ApplicationOrder" > VersionBridge
|
219
|
+
AND a."MigrationID" NOT IN (
|
220
|
+
SELECT m."MigrationID" FROM temp$xmigra_migrations m
|
221
|
+
)
|
222
|
+
) THEN
|
223
|
+
RAISE EXCEPTION 'Unknown in-branch migrations have been applied.';
|
224
|
+
END IF;
|
225
|
+
END IF;
|
226
|
+
|
227
|
+
RAISE NOTICE ' done';
|
228
|
+
})
|
229
|
+
end
|
230
|
+
|
231
|
+
def check_chain_continuity_sql
|
232
|
+
PgSQLSpecifics.in_plpgsql({:VersionBridge => "INT"}, %Q{
|
233
|
+
IF NOT #{upgrading_to_new_branch_test_sql} THEN
|
234
|
+
RAISE NOTICE 'Checking migration chain continuity:';
|
235
|
+
|
236
|
+
VersionBridge := (
|
237
|
+
SELECT COALESCE(MAX(m."ApplicationOrder"), 0)
|
238
|
+
FROM xmigra.applied a
|
239
|
+
INNER JOIN temp$xmigra_migrations m
|
240
|
+
ON a."MigrationID" = m."MigrationID"
|
241
|
+
WHERE a."VersionBridgeMark"
|
242
|
+
);
|
243
|
+
|
244
|
+
IF EXISTS(
|
245
|
+
SELECT *
|
246
|
+
FROM xmigra.applied a
|
247
|
+
INNER JOIN temp$xmigra_migrations m
|
248
|
+
ON a."MigrationID" = m."MigrationID"
|
249
|
+
INNER JOIN temp$xmigra_migrations p
|
250
|
+
ON m."ApplicationOrder" - 1 = p."ApplicationOrder"
|
251
|
+
WHERE p."ApplicationOrder" > VersionBridge
|
252
|
+
AND p."MigrationID" NOT IN (
|
253
|
+
SELECT a2."MigrationID" FROM xmigra.applied a2
|
254
|
+
)
|
255
|
+
) THEN
|
256
|
+
RAISE EXCEPTION 'Previously applied migrations interrupt the continuity of the migration chain.';
|
257
|
+
END IF;
|
258
|
+
|
259
|
+
RAISE NOTICE ' done';
|
260
|
+
END IF;
|
261
|
+
})
|
262
|
+
end
|
263
|
+
|
264
|
+
def select_for_install_sql
|
265
|
+
PgSQLSpecifics.in_plpgsql({:VersionBridge => "INT"}, %Q{
|
266
|
+
RAISE NOTICE 'Selecting migrations to apply:';
|
267
|
+
|
268
|
+
IF #{upgrading_to_new_branch_test_sql} THEN
|
269
|
+
VersionBridge := (
|
270
|
+
SELECT MAX(m."ApplicationOrder")
|
271
|
+
FROM temp$xmigra_migrations m
|
272
|
+
INNER JOIN xmigra.branch_upgrade bu
|
273
|
+
ON m."MigrationID" = bu."CompletesMigration"
|
274
|
+
);
|
275
|
+
|
276
|
+
UPDATE temp$xmigra_migrations
|
277
|
+
SET "Install" = TRUE
|
278
|
+
WHERE "ApplicationOrder" > VersionBridge;
|
279
|
+
ELSE
|
280
|
+
VersionBridge := (
|
281
|
+
SELECT COALESCE(MAX(m."ApplicationOrder"), 0)
|
282
|
+
FROM xmigra.applied a
|
283
|
+
INNER JOIN temp$xmigra_migrations m
|
284
|
+
ON a."MigrationID" = m."MigrationID"
|
285
|
+
WHERE a."VersionBridgeMark"
|
286
|
+
);
|
287
|
+
|
288
|
+
UPDATE temp$xmigra_migrations
|
289
|
+
SET "Install" = TRUE
|
290
|
+
WHERE "MigrationID" NOT IN (
|
291
|
+
SELECT a."MigrationID" FROM xmigra.applied a
|
292
|
+
);
|
293
|
+
END IF;
|
294
|
+
|
295
|
+
RAISE NOTICE ' done';
|
296
|
+
})
|
297
|
+
end
|
298
|
+
|
299
|
+
def production_config_check_sql
|
300
|
+
return if production
|
301
|
+
|
302
|
+
PgSQLSpecifics.in_plpgsql %Q{
|
303
|
+
RAISE NOTICE 'Checking for production status:';
|
304
|
+
|
305
|
+
IF EXISTS(
|
306
|
+
SELECT * FROM temp$xmigra_migrations
|
307
|
+
WHERE "MigrationID" = '#{@migrations[0].id}'
|
308
|
+
AND "Install"
|
309
|
+
) THEN
|
310
|
+
CREATE TABLE xmigra.development (
|
311
|
+
info varchar(200) PRIMARY KEY
|
312
|
+
);
|
313
|
+
END IF;
|
314
|
+
|
315
|
+
IF NOT EXISTS(
|
316
|
+
SELECT * FROM information_schema.tables
|
317
|
+
WHERE table_schema = 'xmigra' AND table_name = 'development'
|
318
|
+
) THEN
|
319
|
+
RAISE EXCEPTION 'Development script cannot be applied to a production database.';
|
320
|
+
END IF;
|
321
|
+
|
322
|
+
RAISE NOTICE ' done';
|
323
|
+
}
|
324
|
+
end
|
325
|
+
|
326
|
+
def remove_access_artifacts_sql
|
327
|
+
PgSQLSpecifics.in_plpgsql({:AccessObject => 'RECORD'}, %Q{
|
328
|
+
RAISE NOTICE 'Removing data access artifacts:';
|
329
|
+
|
330
|
+
FOR AccessObject IN
|
331
|
+
SELECT "name", "type"
|
332
|
+
FROM xmigra.access_objects
|
333
|
+
ORDER BY "order" DESC
|
334
|
+
LOOP
|
335
|
+
EXECUTE 'DROP ' || AccessObject."type" || ' ' || AccessObject."name" || ';';
|
336
|
+
END LOOP;
|
337
|
+
|
338
|
+
DELETE FROM xmigra.access_objects;
|
339
|
+
|
340
|
+
RAISE NOTICE ' done';
|
341
|
+
})
|
342
|
+
end
|
343
|
+
|
344
|
+
def remove_undesired_indexes_sql
|
345
|
+
PgSQLSpecifics.in_plpgsql({:IndexName => 'varchar(150)'}, %Q{
|
346
|
+
RAISE NOTICE 'Removing undesired indexes:';
|
347
|
+
|
348
|
+
FOR IndexName IN
|
349
|
+
SELECT xi."name"
|
350
|
+
FROM xmigra.indexes xi
|
351
|
+
WHERE xi."IndexID" NOT IN (
|
352
|
+
SELECT "IndexID"
|
353
|
+
FROM temp$xmigra_updated_indexes
|
354
|
+
)
|
355
|
+
LOOP
|
356
|
+
EXECUTE 'DROP INDEX ' || IndexName || ';';
|
357
|
+
END LOOP;
|
358
|
+
|
359
|
+
DELETE FROM xmigra.indexes
|
360
|
+
WHERE "IndexID" NOT IN (
|
361
|
+
SELECT ui."IndexID"
|
362
|
+
FROM temp$xmigra_updated_indexes ui
|
363
|
+
);
|
364
|
+
|
365
|
+
RAISE NOTICE ' done';
|
366
|
+
})
|
367
|
+
end
|
368
|
+
|
369
|
+
def create_new_indexes_sql
|
370
|
+
return nil if indexes.empty?
|
371
|
+
PgSQLSpecifics.in_plpgsql(indexes.collect do |index|
|
372
|
+
XMigra.dedent %Q{
|
373
|
+
RAISE NOTICE 'Index #{index.id}:';
|
374
|
+
|
375
|
+
IF EXISTS(
|
376
|
+
SELECT * FROM temp$xmigra_updated_indexes ui
|
377
|
+
WHERE ui."IndexID" = '#{index.id}'
|
378
|
+
AND ui."IndexID" NOT IN (
|
379
|
+
SELECT i."IndexID" FROM xmigra.indexes i
|
380
|
+
)
|
381
|
+
) THEN
|
382
|
+
RAISE NOTICE ' creating...';
|
383
|
+
|
384
|
+
EXECUTE #{PgSQLSpecifics.string_literal index.definition_sql};
|
385
|
+
|
386
|
+
INSERT INTO xmigra.indexes ("IndexID", "name")
|
387
|
+
VALUES ('#{index.id}', #{PgSQLSpecifics.string_literal index.quoted_name});
|
388
|
+
|
389
|
+
RAISE NOTICE ' done';
|
390
|
+
ELSE
|
391
|
+
RAISE NOTICE ' already exists';
|
392
|
+
END IF;
|
393
|
+
}
|
394
|
+
|
395
|
+
end.join("\n"))
|
396
|
+
end
|
397
|
+
|
398
|
+
def ensure_permissions_table_sql
|
399
|
+
"-- ------------ SET UP XMIGRA PERMISSION TRACKING OBJECTS ------------ --\n\n" +
|
400
|
+
PgSQLSpecifics.in_plpgsql(%Q{
|
401
|
+
RAISE NOTICE 'Setting up XMigra permission tracking:';
|
402
|
+
|
403
|
+
IF NOT EXISTS (
|
404
|
+
SELECT * FROM information_schema.schemata
|
405
|
+
WHERE schema_name = 'xmigra'
|
406
|
+
) THEN
|
407
|
+
CREATE SCHEMA xmigra;
|
408
|
+
END IF;
|
409
|
+
|
410
|
+
IF NOT EXISTS (
|
411
|
+
SELECT * FROM information_schema.tables
|
412
|
+
WHERE table_schema = 'xmigra' AND table_name = 'revokable_permissions'
|
413
|
+
) THEN
|
414
|
+
CREATE TABLE xmigra.revokable_permissions (
|
415
|
+
permissions varchar(200) NOT NULL,
|
416
|
+
"object" varchar(150) NOT NULL,
|
417
|
+
"role" oid NOT NULL
|
418
|
+
);
|
419
|
+
END IF;
|
420
|
+
|
421
|
+
}) + XMigra.dedent(%Q{
|
422
|
+
CREATE OR REPLACE FUNCTION xmigra.ip_prepare_revoke(varchar(200), varchar(150), varchar(80)) RETURNS VOID AS $$
|
423
|
+
BEGIN
|
424
|
+
INSERT INTO xmigra.revokable_permissions (permissions, "object", "role")
|
425
|
+
SELECT $1, $2, r.oid
|
426
|
+
FROM pg_catalog.pg_roles r
|
427
|
+
WHERE r.rolname = $3;
|
428
|
+
END;
|
429
|
+
$$ LANGUAGE plpgsql;
|
430
|
+
})
|
431
|
+
end
|
432
|
+
|
433
|
+
def revoke_previous_permissions_sql
|
434
|
+
"-- ------------- REVOKING PREVIOUSLY GRANTED PERMISSIONS ------------- --\n\n" +
|
435
|
+
PgSQLSpecifics.in_plpgsql({:PermissionGrant => 'RECORD'}, %Q{
|
436
|
+
RAISE NOTICE 'Revoking previously granted permissions:';
|
437
|
+
|
438
|
+
FOR PermissionGrant IN
|
439
|
+
SELECT p.permissions, p."object", r.rolname AS "role"
|
440
|
+
FROM xmigra.revokable_permissions p
|
441
|
+
INNER JOIN pg_catalog.pg_roles r
|
442
|
+
ON p."role" = r.oid
|
443
|
+
LOOP
|
444
|
+
EXECUTE 'REVOKE ' || PermissionGrant.permissions || ' ON ' || PermissionGrant."object" || ' FROM ' || PermissionGrant."role";
|
445
|
+
END LOOP;
|
446
|
+
|
447
|
+
RAISE NOTICE ' done';
|
448
|
+
})
|
449
|
+
end
|
450
|
+
|
451
|
+
def granting_permissions_comment_sql
|
452
|
+
"\n-- ---------------------- GRANTING PERMISSIONS ----------------------- --\n\n"
|
453
|
+
end
|
454
|
+
|
455
|
+
def grant_permissions_sql(permissions, object, principal)
|
456
|
+
strlit = PgSQLSpecifics.method(:string_literal)
|
457
|
+
permissions_string = permissions.to_a.join(', ')
|
458
|
+
|
459
|
+
PgSQLSpecifics.in_plpgsql %Q{
|
460
|
+
RAISE NOTICE 'Granting #{permissions_string} on #{object} to #{principal}:';
|
461
|
+
GRANT #{permissions_string} ON #{object} TO #{principal};
|
462
|
+
PERFORM xmigra.ip_prepare_revoke(#{strlit[permissions_string]}, #{strlit[object]}, #{strlit[principal]});
|
463
|
+
RAISE NOTICE ' done';
|
464
|
+
}
|
465
|
+
end
|
466
|
+
|
467
|
+
def insert_access_creation_record_sql
|
468
|
+
XMigra.dedent %Q{
|
469
|
+
INSERT INTO xmigra.access_objects ("type", "name")
|
470
|
+
VALUES ('#{self.class::OBJECT_TYPE}', #{PgSQLSpecifics.string_literal quoted_name});
|
471
|
+
}
|
472
|
+
end
|
473
|
+
|
474
|
+
def migration_application_sql
|
475
|
+
PgSQLSpecifics.in_plpgsql %Q{
|
476
|
+
IF EXISTS (
|
477
|
+
SELECT * FROM temp$xmigra_migrations
|
478
|
+
WHERE "MigrationID" = '#{id}'
|
479
|
+
AND "Install"
|
480
|
+
) THEN
|
481
|
+
RAISE NOTICE #{PgSQLSpecifics.string_literal %Q{Applying "#{File.basename(file_path)}":}};
|
482
|
+
|
483
|
+
EXECUTE #{PgSQLSpecifics.string_literal sql};
|
484
|
+
|
485
|
+
INSERT INTO xmigra.applied ("MigrationID", "Description")
|
486
|
+
VALUES ('#{id}', #{PgSQLSpecifics.string_literal description});
|
487
|
+
|
488
|
+
RAISE NOTICE ' done';
|
489
|
+
END IF;
|
490
|
+
}
|
491
|
+
end
|
492
|
+
|
493
|
+
def check_existence_sql(for_existence, error_message)
|
494
|
+
error_message_literal = PgSQLSpecifics.string_literal sprintf(error_message, quoted_name)
|
495
|
+
|
496
|
+
XMigra.dedent %Q{
|
497
|
+
SELECT CASE
|
498
|
+
WHEN #{existence_test_sql(!for_existence)}
|
499
|
+
THEN f_raise(#{error_message_literal})
|
500
|
+
END;
|
501
|
+
}
|
502
|
+
end
|
503
|
+
|
504
|
+
def creation_notice
|
505
|
+
"SELECT f_alert(#{PgSQLSpecifics.string_literal "Creating #{printable_type} #{quoted_name}:"});"
|
506
|
+
end
|
507
|
+
|
508
|
+
def name_parts
|
509
|
+
if m = DBNAME_PATTERN.match(name)
|
510
|
+
[m[1], m[2]].compact.collect do |p|
|
511
|
+
PgSQLSpecifics.strip_identifier_quoting(p)
|
512
|
+
end.tap do |result|
|
513
|
+
result << [].tap {|types| m[3][1..-2].scan(DBNAME_PATTERN) {|m| types << $&}} if m[3]
|
514
|
+
end
|
515
|
+
else
|
516
|
+
raise XMigra::Error, "Invalid database object name"
|
517
|
+
end
|
518
|
+
end
|
519
|
+
|
520
|
+
def quoted_name
|
521
|
+
formatted_name {|p| '""'.insert(1, p.gsub('"', '""'))}
|
522
|
+
end
|
523
|
+
|
524
|
+
def unquoted_name
|
525
|
+
formatted_name {|p| p}
|
526
|
+
end
|
527
|
+
|
528
|
+
def formatted_name
|
529
|
+
''.tap do |result|
|
530
|
+
name_parts.each do |p|
|
531
|
+
if p.kind_of? Array
|
532
|
+
result << '()'.insert(1, p.join(', '))
|
533
|
+
else
|
534
|
+
result << '.' unless result.empty?
|
535
|
+
result << (yield p)
|
536
|
+
end
|
537
|
+
end
|
538
|
+
end
|
539
|
+
end
|
540
|
+
|
541
|
+
def existence_test_sql(for_existence=true)
|
542
|
+
name_literal = PgSQLSpecifics.string_literal(quoted_name)
|
543
|
+
oid_type_strlit = PgSQLSpecifics.string_literal(PgSQLSpecifics.oid_type(self))
|
544
|
+
"f_resolvename(#{name_literal}, #{oid_type_strlit}) IS #{"NOT " if for_existence}NULL"
|
545
|
+
end
|
546
|
+
|
547
|
+
def branch_id_literal
|
548
|
+
@pgsql_branch_id_literal ||= PgSQLSpecifics.string_literal(
|
549
|
+
XMigra.secure_digest(branch_identifier)
|
550
|
+
)
|
551
|
+
end
|
552
|
+
|
553
|
+
def upgrading_to_new_branch_test_sql
|
554
|
+
return "FALSE" unless respond_to? :branch_identifier
|
555
|
+
|
556
|
+
XMigra.dedent %Q{
|
557
|
+
(EXISTS (
|
558
|
+
SELECT * FROM xmigra.branch_upgrade
|
559
|
+
WHERE "Next" = #{branch_id_literal}
|
560
|
+
LIMIT 1
|
561
|
+
))
|
562
|
+
}
|
563
|
+
end
|
564
|
+
|
565
|
+
def branch_upgrade_sql
|
566
|
+
return unless respond_to? :branch_identifier
|
567
|
+
|
568
|
+
parts = []
|
569
|
+
|
570
|
+
parts << PgSQLSpecifics.in_plpgsql({
|
571
|
+
:UpgradeCommands => 'text',
|
572
|
+
:CompletedMigration => 'RECORD'
|
573
|
+
}, %Q{
|
574
|
+
IF #{upgrading_to_new_branch_test_sql} THEN
|
575
|
+
RAISE NOTICE 'Migrating from previous schema branch:';
|
576
|
+
|
577
|
+
FOR UpgradeCommands IN
|
578
|
+
SELECT bu."UpgradeSql"
|
579
|
+
FROM xmigra.branch_upgrade bu
|
580
|
+
WHERE bu."Next" = #{branch_id_literal}
|
581
|
+
ORDER BY bu."ApplicationOrder" ASC
|
582
|
+
LOOP
|
583
|
+
EXECUTE UpgradeCommads;
|
584
|
+
END LOOP;
|
585
|
+
|
586
|
+
SELECT "CompletesMigration" AS applied, "Current" AS old_branch
|
587
|
+
INTO CompletedMigration
|
588
|
+
FROM xmigra.branch_upgrade
|
589
|
+
WHERE "Next" = #{branch_id_literal};
|
590
|
+
|
591
|
+
DELETE FROM xmigra.applied WHERE "MigrationID" = CompletedMigration.applied;
|
592
|
+
|
593
|
+
INSERT INTO xmigra.applied ("MigrationID", "VersionBridgeMark", "Description")
|
594
|
+
VALUE (CompletedMigration.applied, TRUE, 'Branch upgrade from branch ' || CompletedMigration.old_branch || '.');
|
595
|
+
|
596
|
+
RAISE NOTICE ' done';
|
597
|
+
END IF;
|
598
|
+
|
599
|
+
DELETE FROM xmigra.branch_upgrade;
|
600
|
+
|
601
|
+
})
|
602
|
+
|
603
|
+
if branch_upgrade.applicable? migrations
|
604
|
+
parts << XMigra.dedent(%Q{
|
605
|
+
INSERT INTO xmigra.branch_upgrade
|
606
|
+
("Current", "Next", "CompletesMigration", "UpgradeSql")
|
607
|
+
VALUES (
|
608
|
+
#{branch_id_literal},
|
609
|
+
#{PgSQLSpecifics.string_literal branch_upgrade.target_branch},
|
610
|
+
#{PgSQLSpecifics.string_literal branch_upgrade.migration_completed_id},
|
611
|
+
#{PgSQLSpecifics.string_literal branch_upgrade.sql}
|
612
|
+
);
|
613
|
+
})
|
614
|
+
else
|
615
|
+
parts << %Q{INSERT INTO xmigra.branch_upgrade ("Current") VALUES (#{branch_id_literal});\n}
|
616
|
+
end
|
617
|
+
|
618
|
+
return parts.join("\n")
|
619
|
+
end
|
620
|
+
|
621
|
+
class <<self
|
622
|
+
def in_plpgsql(*args)
|
623
|
+
variables = args[0].kind_of?(Hash) ? args.shift : {}
|
624
|
+
s = args.shift
|
625
|
+
name = "xmigra_" + Digest::MD5.hexdigest(s)
|
626
|
+
|
627
|
+
decl_block = (if variables.length > 0
|
628
|
+
["DECLARE\n"].tap do |lines|
|
629
|
+
variables.each_pair do |n, d|
|
630
|
+
lines << " #{n} #{d};\n"
|
631
|
+
end
|
632
|
+
end.join('')
|
633
|
+
else
|
634
|
+
''
|
635
|
+
end)
|
636
|
+
|
637
|
+
s = s[0..-2] if s.end_with? "\n"
|
638
|
+
XMigra.dedent(%Q{
|
639
|
+
CREATE OR REPLACE FUNCTION #{name}() RETURNS VOID AS $$
|
640
|
+
#{decl_block}BEGIN
|
641
|
+
%s
|
642
|
+
END;
|
643
|
+
$$ LANGUAGE plpgsql;
|
644
|
+
|
645
|
+
SELECT #{name}();
|
646
|
+
DROP FUNCTION #{name}();
|
647
|
+
}) % [XMigra.dedent(s)]
|
648
|
+
end
|
649
|
+
|
650
|
+
def string_literal(s)
|
651
|
+
"'%s'" % [s.gsub("'", "''")]
|
652
|
+
end
|
653
|
+
|
654
|
+
def strip_identifier_quoting(s)
|
655
|
+
case
|
656
|
+
when s[0,1] == '"' && s[-1,1] == '"' then return s[1..-2].gsub('""', '"')
|
657
|
+
else return s
|
658
|
+
end
|
659
|
+
end
|
660
|
+
|
661
|
+
def oid_type(type)
|
662
|
+
case type
|
663
|
+
when View then 'regclass'
|
664
|
+
when Function then 'regprocedure'
|
665
|
+
when Class
|
666
|
+
raise XMigra::Error, "Invalid access object type '#{type.name}'"
|
667
|
+
else
|
668
|
+
raise XMigra::Error, "Invalid access object type '#{type.class.name}'"
|
669
|
+
end
|
670
|
+
end
|
671
|
+
end
|
672
|
+
end
|
673
|
+
end
|
@@ -30,7 +30,7 @@ module XMigra
|
|
30
30
|
|
31
31
|
new_data = {
|
32
32
|
Migration::FOLLOWS=>head_info.fetch(MigrationChain::LATEST_CHANGE, Migration::EMPTY_DB),
|
33
|
-
'sql'=>options.fetch(:sql, "<<<<< INSERT SQL HERE >>>>>\n"),
|
33
|
+
'sql'=>options.fetch(:sql, "<<<<< INSERT SQL HERE >>>>>\n").dup.extend(LiteralYamlStyle),
|
34
34
|
'description'=>options.fetch(:description, "<<<<< DESCRIPTION OF MIGRATION >>>>>").dup.extend(FoldedYamlStyle),
|
35
35
|
Migration::CHANGES=>options.fetch(:changes, ["<<<<< WHAT THIS MIGRATION CHANGES >>>>>"]),
|
36
36
|
}
|
data/lib/xmigra/utils.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
module XMigra
|
2
|
+
def self.dedent(s, prefix='')
|
3
|
+
margin = nil
|
4
|
+
s.lines.map do |l|
|
5
|
+
case
|
6
|
+
when margin.nil? && l =~ /^ *$/
|
7
|
+
l
|
8
|
+
when margin.nil?
|
9
|
+
margin = /^ */.match(l)[0].length
|
10
|
+
l[margin..-1]
|
11
|
+
else
|
12
|
+
/^(?: {0,#{margin}})(.*)/m.match(l)[1]
|
13
|
+
end
|
14
|
+
end.tap do |lines|
|
15
|
+
lines.shift if lines.first == "\n"
|
16
|
+
end.map do |l|
|
17
|
+
prefix + l
|
18
|
+
end.join('')
|
19
|
+
end
|
20
|
+
end
|
@@ -27,7 +27,12 @@ module XMigra
|
|
27
27
|
|
28
28
|
cmd_str = cmd_parts.join(' ')
|
29
29
|
|
30
|
-
output =
|
30
|
+
output = begin
|
31
|
+
`#{cmd_str}`
|
32
|
+
rescue
|
33
|
+
return false if check_exit
|
34
|
+
raise
|
35
|
+
end
|
31
36
|
return ($?.success? ? output : nil) if options[:get_result] == :on_success
|
32
37
|
return $?.success? if check_exit
|
33
38
|
raise(VersionControlError, "Git command failed with exit code #{$?.exitstatus}") unless $?.success?
|
data/lib/xmigra/version.rb
CHANGED
data/lib/xmigra.rb
CHANGED
@@ -283,6 +283,18 @@ module XMigra
|
|
283
283
|
|
284
284
|
end
|
285
285
|
|
286
|
+
module LiteralYamlStyle
|
287
|
+
def to_yaml_style
|
288
|
+
:literal
|
289
|
+
end
|
290
|
+
|
291
|
+
if defined? Psych
|
292
|
+
def yaml_style
|
293
|
+
Psych::Nodes::Scalar::LITERAL
|
294
|
+
end
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
286
298
|
def self.command_line_program
|
287
299
|
XMigra::Program.run(
|
288
300
|
ARGV,
|
@@ -300,6 +312,7 @@ require 'xmigra/vcs_support/svn'
|
|
300
312
|
require 'xmigra/vcs_support/git'
|
301
313
|
|
302
314
|
require 'xmigra/db_support/mssql'
|
315
|
+
require 'xmigra/db_support/psql'
|
303
316
|
|
304
317
|
require 'xmigra/access_artifact'
|
305
318
|
require 'xmigra/stored_procedure'
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: xmigra
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Next IT Corporation
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2014-
|
12
|
+
date: 2014-12-08 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|
@@ -62,6 +62,7 @@ files:
|
|
62
62
|
- lib/xmigra/access_artifact_collection.rb
|
63
63
|
- lib/xmigra/branch_upgrade.rb
|
64
64
|
- lib/xmigra/db_support/mssql.rb
|
65
|
+
- lib/xmigra/db_support/psql.rb
|
65
66
|
- lib/xmigra/function.rb
|
66
67
|
- lib/xmigra/index.rb
|
67
68
|
- lib/xmigra/index_collection.rb
|
@@ -75,6 +76,7 @@ files:
|
|
75
76
|
- lib/xmigra/schema_manipulator.rb
|
76
77
|
- lib/xmigra/schema_updater.rb
|
77
78
|
- lib/xmigra/stored_procedure.rb
|
79
|
+
- lib/xmigra/utils.rb
|
78
80
|
- lib/xmigra/vcs_support/git.rb
|
79
81
|
- lib/xmigra/vcs_support/svn.rb
|
80
82
|
- lib/xmigra/version.rb
|