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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 841d32e7e4f11988348f7284060272f295f18e4d
4
- data.tar.gz: 22dc8eeb6703aa74dc1587f13a7830e61216cec5
3
+ metadata.gz: 3e69aae7f431f58d88125bfc0a31adb4a0f3b76c
4
+ data.tar.gz: 65f872a4d23fd7515bf6231c636c0f10725effc7
5
5
  SHA512:
6
- metadata.gz: d1ce7617249b3ec15d7bbad0ad673b15a7620df1f4d7966ce06e67fd15674e16e60b74539c56c89be88b2f559e9cb0c22d0fd812b0d573b91e3fdf2c71e1650d
7
- data.tar.gz: e90c716f60ad4bd81c56e7af85c17f594aa6028a895ca91ad8c6dd65b53c044f342bfe12852f6ef555d8eb48e4c2c04f32b64853cac1f46482fd25c30bd82223
6
+ metadata.gz: 299d09f45384c3dc16473c59e6d2494583c8486d776a3edef6ab4123e2245374e4cbe13472b73c013f9c82081f44fdef0a242b6048f694d666f2b5102b6a988d
7
+ data.tar.gz: 5016ef96078d289add80ec678ca6a5e989fcaac0a9dc4fd30ce186c0fc7e766ab22c00c752f5f62c0c11ccdf6621b808d920709792b2dae8d0df774e3e61c158
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # XMigra
1
+ # XMigra [![Gem Version](https://badge.fury.io/rb/xmigra.svg)](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 (coming soon)
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-version migrations have been applied.', 11, 1);
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
  }
@@ -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 = `#{cmd_str}`
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?
@@ -1,3 +1,3 @@
1
1
  module XMigra
2
- VERSION = "1.1.0"
2
+ VERSION = "1.2.0"
3
3
  end
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.1.0
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-11-28 00:00:00.000000000 Z
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