@cerema/cadriciel 1.6.2-beta → 1.6.4

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.
@@ -0,0 +1,3604 @@
1
+ --
2
+ -- PostgreSQL database dump
3
+ --
4
+
5
+ SET statement_timeout = 0;
6
+ SET lock_timeout = 0;
7
+ SET idle_in_transaction_session_timeout = 0;
8
+ SET client_encoding = 'UTF8';
9
+ SET standard_conforming_strings = on;
10
+ SELECT pg_catalog.set_config('search_path', '', false);
11
+ SET check_function_bodies = false;
12
+ SET xmloption = content;
13
+ SET client_min_messages = warning;
14
+ SET row_security = off;
15
+ --
16
+ -- Name: public; Type: SCHEMA; Schema: -; Owner: postgres
17
+ --
18
+
19
+ CREATE SCHEMA IF NOT EXISTS public;
20
+ ALTER SCHEMA public OWNER TO postgres;
21
+ CREATE SCHEMA IF NOT EXISTS auth;
22
+ ALTER SCHEMA auth OWNER TO postgres;
23
+ --
24
+ -- TOC entry 3177 (class 2606 OID 17326)
25
+ -- Name: profil profil_utilisateurid_fkey; Type: FK CONSTRAINT; Schema: auth; Owner: postgres
26
+ --
27
+
28
+ --
29
+ -- Name: SCHEMA public; Type: COMMENT; Schema: -; Owner: postgres
30
+ --
31
+
32
+ COMMENT ON SCHEMA public IS 'standard public schema';
33
+ --
34
+ -- Name: cloneparms; Type: TYPE; Schema: public; Owner: postgres
35
+ --
36
+
37
+ CREATE TYPE public.cloneparms AS ENUM (
38
+ 'DATA',
39
+ 'NODATA',
40
+ 'DDLONLY',
41
+ 'NOOWNER',
42
+ 'NOACL',
43
+ 'VERBOSE',
44
+ 'DEBUG',
45
+ 'FILECOPY'
46
+ );
47
+ ALTER TYPE public.cloneparms OWNER TO postgres;
48
+ --
49
+ -- Name: clone_schema(text, text, public.cloneparms[]); Type: FUNCTION; Schema: public; Owner: postgres
50
+ --
51
+
52
+ CREATE FUNCTION public.clone_schema(
53
+ source_schema text,
54
+ dest_schema text,
55
+ VARIADIC arr public.cloneparms [] DEFAULT '{}'::public.cloneparms []
56
+ ) RETURNS void LANGUAGE plpgsql AS $_$ -- This function will clone all sequences, tables, data, views & functions from any existing schema to a new one
57
+ -- SAMPLE CALL:
58
+ -- SELECT clone_schema('sample', 'sample_clone2');
59
+ DECLARE src_oid oid;
60
+ tbl_oid oid;
61
+ func_oid oid;
62
+ object text;
63
+ buffer text;
64
+ buffer2 text;
65
+ buffer3 text;
66
+ srctbl text;
67
+ aname text;
68
+ default_ text;
69
+ column_ text;
70
+ qry text;
71
+ ix_old_name text;
72
+ ix_new_name text;
73
+ relpersist text;
74
+ udt_name text;
75
+ udt_schema text;
76
+ bRelispart bool;
77
+ bChild bool;
78
+ relknd text;
79
+ data_type text;
80
+ ocomment text;
81
+ adef text;
82
+ dest_qry text;
83
+ v_def text;
84
+ part_range text;
85
+ src_path_old text;
86
+ src_path_new text;
87
+ aclstr text;
88
+ -- issue#80 initialize arrays properly
89
+ tblarray text [] := '{}';
90
+ tblarray2 text [] := '{}';
91
+ tblarray3 text [] := '{}';
92
+ tblelement text;
93
+ grantor text;
94
+ grantee text;
95
+ privs text;
96
+ seqval bigint;
97
+ sq_last_value bigint;
98
+ sq_max_value bigint;
99
+ sq_start_value bigint;
100
+ sq_increment_by bigint;
101
+ sq_min_value bigint;
102
+ sq_cache_value bigint;
103
+ sq_is_called boolean := True;
104
+ sq_is_cycled boolean;
105
+ is_prokind boolean;
106
+ abool boolean;
107
+ sq_data_type text;
108
+ sq_cycled char(10);
109
+ sq_owned text;
110
+ sq_version text;
111
+ sq_server_version text;
112
+ sq_server_version_num integer;
113
+ bWindows boolean;
114
+ arec RECORD;
115
+ cnt integer;
116
+ cnt1 integer;
117
+ cnt2 integer;
118
+ cnt3 integer;
119
+ cnt4 integer;
120
+ pos integer;
121
+ tblscopied integer := 0;
122
+ l_child integer;
123
+ action text := 'N/A';
124
+ tblname text;
125
+ v_ret text;
126
+ v_diag1 text;
127
+ v_diag2 text;
128
+ v_diag3 text;
129
+ v_diag4 text;
130
+ v_diag5 text;
131
+ v_diag6 text;
132
+ v_dummy text;
133
+ spath text;
134
+ spath_tmp text;
135
+ -- issue#86 fix
136
+ isGenerated text;
137
+ -- issue#91 fix
138
+ tblowner text;
139
+ func_owner text;
140
+ func_name text;
141
+ func_args text;
142
+ func_argno integer;
143
+ view_owner text;
144
+ -- issue#92
145
+ calleruser text;
146
+ -- issue#94
147
+ bData boolean := False;
148
+ bDDLOnly boolean := False;
149
+ bVerbose boolean := False;
150
+ bDebug boolean := False;
151
+ bNoACL boolean := False;
152
+ bNoOwner boolean := False;
153
+ arglen integer;
154
+ vargs text;
155
+ avarg public.cloneparms;
156
+ -- issue#98
157
+ mvarray text [] := '{}';
158
+ mvscopied integer := 0;
159
+ -- issue#99 tablespaces
160
+ tblspace text;
161
+ -- issue#101
162
+ bFileCopy boolean := False;
163
+ t timestamptz := clock_timestamp();
164
+ r timestamptz;
165
+ s timestamptz;
166
+ lastsql text := '';
167
+ v_version text := '1.19 September 07, 2023';
168
+ BEGIN -- Make sure NOTICE are shown
169
+ SET client_min_messages = 'notice';
170
+ RAISE NOTICE 'clone_schema version %',
171
+ v_version;
172
+ IF 'DEBUG' = ANY ($3) THEN bDebug = True;
173
+ END IF;
174
+ IF 'VERBOSE' = ANY ($3) THEN bVerbose = True;
175
+ END IF;
176
+ -- IF bVerbose THEN RAISE NOTICE 'START: %',clock_timestamp() - t; END IF;
177
+ arglen := array_length($3, 1);
178
+ IF arglen IS NULL THEN -- nothing to do, so defaults are assumed
179
+ NULL;
180
+ ELSE -- loop thru args
181
+ -- IF 'NO_TRIGGERS' = ANY ($3)
182
+ -- select array_to_string($3, ',', '***') INTO vargs;
183
+ IF bDebug THEN RAISE NOTICE 'DEBUG: arguments=%',
184
+ $3;
185
+ END IF;
186
+ FOREACH avarg IN ARRAY $3 LOOP IF bDebug THEN RAISE NOTICE 'DEBUG: arg=%',
187
+ avarg;
188
+ END IF;
189
+ IF avarg = 'DATA' THEN bData = True;
190
+ ELSEIF avarg = 'NODATA' THEN -- already set to that by default
191
+ bData = False;
192
+ ELSEIF avarg = 'DDLONLY' THEN bDDLOnly = True;
193
+ ELSEIF avarg = 'NOACL' THEN bNoACL = True;
194
+ ELSEIF avarg = 'NOOWNER' THEN bNoOwner = True;
195
+ -- issue#101 fix
196
+ ELSEIF avarg = 'FILECOPY' THEN bFileCopy = True;
197
+ END IF;
198
+ END LOOP;
199
+ IF bData
200
+ and bDDLOnly THEN RAISE WARNING 'You can only specify DDLONLY or DATA, but not both.';
201
+ RETURN;
202
+ END IF;
203
+ END IF;
204
+ -- Get server version info to handle certain things differently based on the version.
205
+ SELECT setting INTO sq_server_version
206
+ FROM pg_settings
207
+ WHERE name = 'server_version';
208
+ SELECT version() INTO sq_version;
209
+ IF POSITION('compiled by Visual C++' IN sq_version) > 0 THEN bWindows = True;
210
+ RAISE NOTICE 'Windows: %',
211
+ sq_version;
212
+ ELSE bWindows = False;
213
+ RAISE NOTICE 'Linux: %',
214
+ sq_version;
215
+ END IF;
216
+ SELECT setting INTO sq_server_version_num
217
+ FROM pg_settings
218
+ WHERE name = 'server_version_num';
219
+ IF sq_server_version_num < 100000 THEN IF sq_server_version_num > 90600 THEN RAISE WARNING 'Server Version:% Number:% PG Versions older than v10 are not supported. Will try however for PG 9.6...',
220
+ sq_server_version,
221
+ sq_server_version_num;
222
+ ELSE RAISE WARNING 'Server Version:% Number:% PG Versions older than v10 are not supported. You need to be at minimum version 9.6 to at least try',
223
+ sq_server_version,
224
+ sq_server_version_num;
225
+ RETURN;
226
+ END IF;
227
+ END IF;
228
+ -- Check that source_schema exists
229
+ SELECT oid INTO src_oid
230
+ FROM pg_namespace
231
+ WHERE nspname = quote_ident(source_schema);
232
+ IF NOT FOUND THEN RAISE NOTICE ' source schema % does not exist!',
233
+ source_schema;
234
+ RETURN;
235
+ END IF;
236
+ -- Check for case-sensitive target schemas and reject them for now.
237
+ SELECT lower(dest_schema) = dest_schema INTO abool;
238
+ IF not abool THEN RAISE NOTICE 'Case-sensitive target schemas are not supported at this time.';
239
+ RETURN;
240
+ END IF;
241
+ -- Check that dest_schema does not yet exist
242
+ PERFORM nspname
243
+ FROM pg_namespace
244
+ WHERE nspname = quote_ident(dest_schema);
245
+ IF FOUND THEN RAISE NOTICE ' dest schema % already exists!',
246
+ dest_schema;
247
+ RETURN;
248
+ END IF;
249
+ IF bDDLOnly
250
+ and bData THEN RAISE WARNING 'You cannot specify to clone data and generate ddl at the same time.';
251
+ RETURN;
252
+ END IF;
253
+ -- Issue#92
254
+ SELECT current_user into calleruser;
255
+ -- Set the search_path to source schema. Before exiting set it back to what it was before.
256
+ -- In order to avoid issues with the special schema name "$user" that may be
257
+ -- returned unquoted by some applications, we ensure it remains double quoted.
258
+ -- MJV FIX: #47
259
+ SELECT setting INTO v_dummy
260
+ FROM pg_settings
261
+ WHERE name = 'search_path';
262
+ IF bDebug THEN RAISE NOTICE 'DEBUG: search_path=%',
263
+ v_dummy;
264
+ END IF;
265
+ SELECT REPLACE(
266
+ REPLACE(setting, '"$user"', '$user'),
267
+ '$user',
268
+ '"$user"'
269
+ ) INTO src_path_old
270
+ FROM pg_settings
271
+ WHERE name = 'search_path';
272
+ IF bDebug THEN RAISE NOTICE 'DEBUG: src_path_old=%',
273
+ src_path_old;
274
+ END IF;
275
+ EXECUTE 'SET search_path = ' || quote_ident(source_schema);
276
+ SELECT setting INTO src_path_new
277
+ FROM pg_settings
278
+ WHERE name = 'search_path';
279
+ IF bDebug THEN RAISE NOTICE 'DEBUG: new search_path=%',
280
+ src_path_new;
281
+ END IF;
282
+ -- Validate required types exist. If not, create them.
283
+ SELECT a.objtypecnt,
284
+ b.permtypecnt INTO cnt,
285
+ cnt2
286
+ FROM (
287
+ SELECT count(*) AS objtypecnt
288
+ FROM pg_catalog.pg_type t
289
+ LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace
290
+ WHERE (
291
+ t.typrelid = 0
292
+ OR (
293
+ SELECT c.relkind = 'c'
294
+ FROM pg_catalog.pg_class c
295
+ WHERE c.oid = t.typrelid
296
+ )
297
+ )
298
+ AND NOT EXISTS (
299
+ SELECT 1
300
+ FROM pg_catalog.pg_type el
301
+ WHERE el.oid = t.typelem
302
+ AND el.typarray = t.oid
303
+ )
304
+ AND n.nspname <> 'pg_catalog'
305
+ AND n.nspname <> 'information_schema'
306
+ AND pg_catalog.pg_type_is_visible(t.oid)
307
+ AND pg_catalog.format_type(t.oid, NULL) = 'obj_type'
308
+ ) a,
309
+ (
310
+ SELECT count(*) AS permtypecnt
311
+ FROM pg_catalog.pg_type t
312
+ LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace
313
+ WHERE (
314
+ t.typrelid = 0
315
+ OR (
316
+ SELECT c.relkind = 'c'
317
+ FROM pg_catalog.pg_class c
318
+ WHERE c.oid = t.typrelid
319
+ )
320
+ )
321
+ AND NOT EXISTS (
322
+ SELECT 1
323
+ FROM pg_catalog.pg_type el
324
+ WHERE el.oid = t.typelem
325
+ AND el.typarray = t.oid
326
+ )
327
+ AND n.nspname <> 'pg_catalog'
328
+ AND n.nspname <> 'information_schema'
329
+ AND pg_catalog.pg_type_is_visible(t.oid)
330
+ AND pg_catalog.format_type(t.oid, NULL) = 'perm_type'
331
+ ) b;
332
+ IF cnt = 0 THEN CREATE TYPE obj_type AS ENUM (
333
+ 'TABLE',
334
+ 'VIEW',
335
+ 'COLUMN',
336
+ 'SEQUENCE',
337
+ 'FUNCTION',
338
+ 'SCHEMA',
339
+ 'DATABASE'
340
+ );
341
+ END IF;
342
+ IF cnt2 = 0 THEN CREATE TYPE perm_type AS ENUM (
343
+ 'SELECT',
344
+ 'INSERT',
345
+ 'UPDATE',
346
+ 'DELETE',
347
+ 'TRUNCATE',
348
+ 'REFERENCES',
349
+ 'TRIGGER',
350
+ 'USAGE',
351
+ 'CREATE',
352
+ 'EXECUTE',
353
+ 'CONNECT',
354
+ 'TEMPORARY'
355
+ );
356
+ END IF;
357
+ -- Issue#95
358
+ SELECT pg_catalog.pg_get_userbyid(nspowner) INTO buffer
359
+ FROM pg_namespace
360
+ WHERE nspname = quote_ident(source_schema);
361
+ IF bDDLOnly THEN RAISE NOTICE ' Only generating DDL, not actually creating anything...';
362
+ -- issue#95
363
+ IF bNoOwner THEN RAISE INFO 'CREATE SCHEMA %;',
364
+ quote_ident(dest_schema);
365
+ ELSE RAISE INFO 'CREATE SCHEMA % AUTHORIZATION %;',
366
+ quote_ident(dest_schema),
367
+ buffer;
368
+ END IF;
369
+ RAISE NOTICE 'SET search_path=%;',
370
+ quote_ident(dest_schema);
371
+ ELSE -- issue#95
372
+ IF bNoOwner THEN EXECUTE 'CREATE SCHEMA ' || quote_ident(dest_schema);
373
+ ELSE EXECUTE 'CREATE SCHEMA ' || quote_ident(dest_schema) || ' AUTHORIZATION ' || buffer;
374
+ END IF;
375
+ END IF;
376
+ -- Do system table validations for subsequent system table queries
377
+ -- Issue#65 Fix
378
+ SELECT count(*) into cnt
379
+ FROM pg_attribute
380
+ WHERE attrelid = 'pg_proc'::regclass
381
+ AND attname = 'prokind';
382
+ IF cnt = 0 THEN is_prokind = False;
383
+ ELSE is_prokind = True;
384
+ END IF;
385
+ -- MV: Create Collations
386
+ action := 'Collations';
387
+ cnt := 0;
388
+ -- Issue#96 Handle differently based on PG Versions (PG15 rely on colliculocale, not collcolocate)
389
+ -- perhaps use this logic instead: COALESCE(c.collcollate, c.colliculocale) AS lc_collate, COALESCE(c.collctype, c.colliculocale) AS lc_type
390
+ IF sq_server_version_num > 150000 THEN FOR arec IN
391
+ SELECT n.nspname AS schemaname,
392
+ a.rolname AS ownername,
393
+ c.collname,
394
+ c.collprovider,
395
+ c.collcollate AS locale,
396
+ 'CREATE COLLATION ' || quote_ident(dest_schema) || '."' || c.collname || '" (provider = ' || CASE
397
+ WHEN c.collprovider = 'i' THEN 'icu'
398
+ WHEN c.collprovider = 'c' THEN 'libc'
399
+ ELSE ''
400
+ END || ', locale = ''' || c.colliculocale || ''');' AS COLL_DDL
401
+ FROM pg_collation c
402
+ JOIN pg_namespace n ON (c.collnamespace = n.oid)
403
+ JOIN pg_roles a ON (c.collowner = a.oid)
404
+ WHERE n.nspname = quote_ident(source_schema)
405
+ ORDER BY c.collname LOOP BEGIN cnt := cnt + 1;
406
+ IF bDDLOnly THEN RAISE INFO '%',
407
+ arec.coll_ddl;
408
+ ELSE EXECUTE arec.coll_ddl;
409
+ END IF;
410
+ END;
411
+ END LOOP;
412
+ ELSIF sq_server_version_num > 100000 THEN FOR arec IN
413
+ SELECT n.nspname AS schemaname,
414
+ a.rolname AS ownername,
415
+ c.collname,
416
+ c.collprovider,
417
+ c.collcollate AS locale,
418
+ 'CREATE COLLATION ' || quote_ident(dest_schema) || '."' || c.collname || '" (provider = ' || CASE
419
+ WHEN c.collprovider = 'i' THEN 'icu'
420
+ WHEN c.collprovider = 'c' THEN 'libc'
421
+ ELSE ''
422
+ END || ', locale = ''' || c.collcollate || ''');' AS COLL_DDL
423
+ FROM pg_collation c
424
+ JOIN pg_namespace n ON (c.collnamespace = n.oid)
425
+ JOIN pg_roles a ON (c.collowner = a.oid)
426
+ WHERE n.nspname = quote_ident(source_schema)
427
+ ORDER BY c.collname LOOP BEGIN cnt := cnt + 1;
428
+ IF bDDLOnly THEN RAISE INFO '%',
429
+ arec.coll_ddl;
430
+ ELSE EXECUTE arec.coll_ddl;
431
+ END IF;
432
+ END;
433
+ END LOOP;
434
+ ELSE -- handle 9.6 that is missing some columns in pg_collation
435
+ FOR arec IN
436
+ SELECT n.nspname AS schemaname,
437
+ a.rolname AS ownername,
438
+ c.collname,
439
+ c.collcollate AS locale,
440
+ 'CREATE COLLATION ' || quote_ident(dest_schema) || '."' || c.collname || '" (provider = ' || ', locale = ''' || c.collcollate || ''');' AS COLL_DDL
441
+ FROM pg_collation c
442
+ JOIN pg_namespace n ON (c.collnamespace = n.oid)
443
+ JOIN pg_roles a ON (c.collowner = a.oid)
444
+ WHERE n.nspname = quote_ident(source_schema)
445
+ ORDER BY c.collname LOOP BEGIN cnt := cnt + 1;
446
+ IF bDDLOnly THEN RAISE INFO '%',
447
+ arec.coll_ddl;
448
+ ELSE EXECUTE arec.coll_ddl;
449
+ END IF;
450
+ END;
451
+ END LOOP;
452
+ END IF;
453
+ RAISE NOTICE ' COLLATIONS cloned: %',
454
+ LPAD(cnt::text, 5, ' ');
455
+ -- MV: Create Domains
456
+ action := 'Domains';
457
+ cnt := 0;
458
+ FOR arec IN
459
+ SELECT n.nspname AS "Schema",
460
+ t.typname AS "Name",
461
+ pg_catalog.format_type(t.typbasetype, t.typtypmod) AS "Type",
462
+ (
463
+ SELECT c.collname
464
+ FROM pg_catalog.pg_collation c,
465
+ pg_catalog.pg_type bt
466
+ WHERE c.oid = t.typcollation
467
+ AND bt.oid = t.typbasetype
468
+ AND t.typcollation <> bt.typcollation
469
+ ) AS "Collation",
470
+ CASE
471
+ WHEN t.typnotnull THEN 'not null'
472
+ END AS "Nullable",
473
+ t.typdefault AS "Default",
474
+ pg_catalog.array_to_string(
475
+ ARRAY (
476
+ SELECT pg_catalog.pg_get_constraintdef(r.oid, TRUE)
477
+ FROM pg_catalog.pg_constraint r -- Issue#78 FIX: handle case-sensitive names with quote_ident() on t.typename
478
+ WHERE t.oid = r.contypid
479
+ ),
480
+ ' '
481
+ ) AS "Check",
482
+ 'CREATE DOMAIN ' || quote_ident(dest_schema) || '.' || quote_ident(t.typname) || ' AS ' || pg_catalog.format_type(t.typbasetype, t.typtypmod) || CASE
483
+ WHEN t.typnotnull IS NOT NULL THEN ' NOT NULL '
484
+ ELSE ' '
485
+ END || CASE
486
+ WHEN t.typdefault IS NOT NULL THEN 'DEFAULT ' || t.typdefault || ' '
487
+ ELSE ' '
488
+ END || pg_catalog.array_to_string(
489
+ ARRAY (
490
+ SELECT pg_catalog.pg_get_constraintdef(r.oid, TRUE)
491
+ FROM pg_catalog.pg_constraint r
492
+ WHERE t.oid = r.contypid
493
+ ),
494
+ ' '
495
+ ) || ';' AS DOM_DDL
496
+ FROM pg_catalog.pg_type t
497
+ LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace
498
+ WHERE t.typtype = 'd'
499
+ AND n.nspname = quote_ident(source_schema)
500
+ AND pg_catalog.pg_type_is_visible(t.oid)
501
+ ORDER BY 1,
502
+ 2 LOOP BEGIN cnt := cnt + 1;
503
+ IF bDDLOnly THEN RAISE INFO '%',
504
+ arec.dom_ddl;
505
+ ELSE EXECUTE arec.dom_ddl;
506
+ END IF;
507
+ END;
508
+ END LOOP;
509
+ RAISE NOTICE ' DOMAINS cloned: %',
510
+ LPAD(cnt::text, 5, ' ');
511
+ -- MV: Create types
512
+ action := 'Types';
513
+ cnt := 0;
514
+ lastsql = '';
515
+ FOR arec IN -- Fixed Issue#108:enclose double-quote roles with special characters for setting "OWNER TO"
516
+ -- SELECT c.relkind, n.nspname AS schemaname, t.typname AS typname, t.typcategory, pg_catalog.pg_get_userbyid(t.typowner) AS owner, CASE WHEN t.typcategory = 'C' THEN
517
+ SELECT c.relkind,
518
+ n.nspname AS schemaname,
519
+ t.typname AS typname,
520
+ t.typcategory,
521
+ '"' || pg_catalog.pg_get_userbyid(t.typowner) || '"' AS owner,
522
+ CASE
523
+ WHEN t.typcategory = 'C' THEN 'CREATE TYPE ' || quote_ident(dest_schema) || '.' || t.typname || ' AS (' || array_to_string(
524
+ array_agg(
525
+ a.attname || ' ' || pg_catalog.format_type(a.atttypid, a.atttypmod)
526
+ ORDER BY c.relname,
527
+ a.attnum
528
+ ),
529
+ ', '
530
+ ) || ');'
531
+ WHEN t.typcategory = 'E' THEN 'CREATE TYPE ' || quote_ident(dest_schema) || '.' || t.typname || ' AS ENUM (' || REPLACE(
532
+ quote_literal(
533
+ array_to_string(
534
+ array_agg(
535
+ e.enumlabel
536
+ ORDER BY e.enumsortorder
537
+ ),
538
+ ','
539
+ )
540
+ ),
541
+ ',',
542
+ ''','''
543
+ ) || ');'
544
+ ELSE ''
545
+ END AS type_ddl
546
+ FROM pg_type t
547
+ JOIN pg_namespace n ON (n.oid = t.typnamespace)
548
+ LEFT JOIN pg_enum e ON (t.oid = e.enumtypid)
549
+ LEFT JOIN pg_class c ON (c.reltype = t.oid)
550
+ LEFT JOIN pg_attribute a ON (a.attrelid = c.oid)
551
+ WHERE n.nspname = quote_ident(source_schema)
552
+ AND (
553
+ c.relkind IS NULL
554
+ OR c.relkind = 'c'
555
+ )
556
+ AND t.typcategory IN ('C', 'E')
557
+ GROUP BY 1,
558
+ 2,
559
+ 3,
560
+ 4,
561
+ 5
562
+ ORDER BY n.nspname,
563
+ t.typcategory,
564
+ t.typname LOOP BEGIN cnt := cnt + 1;
565
+ -- Keep composite and enum types in separate branches for fine tuning later if needed.
566
+ IF arec.typcategory = 'E' THEN IF bDDLOnly THEN RAISE INFO '%',
567
+ arec.type_ddl;
568
+ --issue#95
569
+ IF NOT bNoOwner THEN -- Fixed Issue#108: double-quote roles in case they have special characters
570
+ RAISE INFO 'ALTER TYPE % OWNER TO %;',
571
+ quote_ident(dest_schema) || '.' || arec.typname,
572
+ arec.owner;
573
+ END IF;
574
+ ELSE EXECUTE arec.type_ddl;
575
+ --issue#95
576
+ IF NOT bNoOwner THEN -- Fixed Issue#108: double-quote roles in case they have special characters
577
+ EXECUTE 'ALTER TYPE ' || quote_ident(dest_schema) || '.' || arec.typname || ' OWNER TO ' || arec.owner;
578
+ END IF;
579
+ END IF;
580
+ ELSIF arec.typcategory = 'C' THEN IF bDDLOnly THEN RAISE INFO '%',
581
+ arec.type_ddl;
582
+ --issue#95
583
+ IF NOT bNoOwner THEN -- Fixed Issue#108: double-quote roles in case they have special characters
584
+ RAISE INFO 'ALTER TYPE % OWNER TO %;',
585
+ quote_ident(dest_schema) || '.' || arec.typname,
586
+ arec.owner;
587
+ END IF;
588
+ ELSE EXECUTE arec.type_ddl;
589
+ --issue#95
590
+ IF NOT bNoOwner THEN -- Fixed Issue#108: double-quote roles in case they have special characters
591
+ EXECUTE 'ALTER TYPE ' || quote_ident(dest_schema) || '.' || arec.typname || ' OWNER TO ' || arec.owner;
592
+ END IF;
593
+ END IF;
594
+ ELSE RAISE NOTICE ' Unhandled type:%-%',
595
+ arec.typcategory,
596
+ arec.typname;
597
+ END IF;
598
+ END;
599
+ END LOOP;
600
+ RAISE NOTICE ' TYPES cloned: %',
601
+ LPAD(cnt::text, 5, ' ');
602
+ -- Create sequences
603
+ action := 'Sequences';
604
+ cnt := 0;
605
+ -- fix#63 get from pg_sequences not information_schema
606
+ -- fix#63 take 2: get it from information_schema.sequences since we need to treat IDENTITY columns differently.
607
+ -- fix#95 get owner as well by joining to pg_sequences
608
+ -- fix#106 we can get owner info with pg_class, pg_user/pg_group, and information_schema.sequences, so we can avoid the hit to pg_sequences which is not available in 9.6
609
+ FOR object,
610
+ buffer IN -- Fixed Issue#108:
611
+ -- SELECT s1.sequence_name::text, s2.sequenceowner FROM information_schema.sequences s1 JOIN pg_sequences s2 ON (s1.sequence_schema = s2.schemaname AND s1.sequence_name = s2.sequencename) AND s1.sequence_schema = quote_ident(source_schema)
612
+ SELECT s.sequence_name::text,
613
+ '"' || u.usename || '"' as owner
614
+ FROM information_schema.sequences s
615
+ JOIN pg_class c ON (
616
+ s.sequence_name = c.relname
617
+ AND s.sequence_schema = c.relnamespace::regnamespace::text
618
+ )
619
+ JOIN pg_user u ON (c.relowner = u.usesysid)
620
+ WHERE c.relkind = 'S'
621
+ AND s.sequence_schema = quote_ident(source_schema)
622
+ UNION
623
+ SELECT s.sequence_name::text,
624
+ g.groname as owner
625
+ FROM information_schema.sequences s
626
+ JOIN pg_class c ON (
627
+ s.sequence_name = c.relname
628
+ AND s.sequence_schema = c.relnamespace::regnamespace::text
629
+ )
630
+ JOIN pg_group g ON (c.relowner = g.grosysid)
631
+ WHERE c.relkind = 'S'
632
+ AND s.sequence_schema = quote_ident(source_schema) LOOP cnt := cnt + 1;
633
+ IF bDDLOnly THEN -- issue#95
634
+ RAISE INFO '%',
635
+ 'CREATE SEQUENCE ' || quote_ident(dest_schema) || '.' || quote_ident(object) || ';';
636
+ IF NOT bNoOwner THEN -- Fixed Issue#108: double-quote roles in case they have special characters
637
+ RAISE INFO '%',
638
+ 'ALTER SEQUENCE ' || quote_ident(dest_schema) || '.' || quote_ident(object) || ' OWNER TO ' || buffer || ';';
639
+ END IF;
640
+ ELSE EXECUTE 'CREATE SEQUENCE ' || quote_ident(dest_schema) || '.' || quote_ident(object);
641
+ -- issue#95
642
+ IF NOT bNoOwner THEN -- Fixed Issue#108: double-quote roles in case they have special characters
643
+ EXECUTE 'ALTER SEQUENCE ' || quote_ident(dest_schema) || '.' || quote_ident(object) || ' OWNER TO ' || buffer;
644
+ END IF;
645
+ END IF;
646
+ srctbl := quote_ident(source_schema) || '.' || quote_ident(object);
647
+ IF sq_server_version_num < 100000 THEN EXECUTE 'SELECT last_value, is_called FROM ' || quote_ident(source_schema) || '.' || quote_ident(object) || ';' INTO sq_last_value,
648
+ sq_is_called;
649
+ EXECUTE 'SELECT maximum_value, start_value, increment, minimum_value, 1 cache_size, cycle_option, data_type
650
+ FROM information_schema.sequences WHERE sequence_schema=' || quote_literal(source_schema) || ' AND sequence_name=' || quote_literal(object) || ';' INTO sq_max_value,
651
+ sq_start_value,
652
+ sq_increment_by,
653
+ sq_min_value,
654
+ sq_cache_value,
655
+ sq_is_cycled,
656
+ sq_data_type;
657
+ IF sq_is_cycled THEN sq_cycled := 'CYCLE';
658
+ ELSE sq_cycled := 'NO CYCLE';
659
+ END IF;
660
+ qry := 'ALTER SEQUENCE ' || quote_ident(dest_schema) || '.' || quote_ident(object) || ' INCREMENT BY ' || sq_increment_by || ' MINVALUE ' || sq_min_value || ' MAXVALUE ' || sq_max_value -- will update current sequence value after this
661
+ || ' START WITH ' || sq_start_value || ' RESTART ' || sq_min_value || ' CACHE ' || sq_cache_value || ' ' || sq_cycled || ' ;';
662
+ ELSE EXECUTE 'SELECT max_value, start_value, increment_by, min_value, cache_size, cycle, data_type, COALESCE(last_value, 1)
663
+ FROM pg_catalog.pg_sequences WHERE schemaname=' || quote_literal(source_schema) || ' AND sequencename=' || quote_literal(object) || ';' INTO sq_max_value,
664
+ sq_start_value,
665
+ sq_increment_by,
666
+ sq_min_value,
667
+ sq_cache_value,
668
+ sq_is_cycled,
669
+ sq_data_type,
670
+ sq_last_value;
671
+ IF sq_is_cycled THEN sq_cycled := 'CYCLE';
672
+ ELSE sq_cycled := 'NO CYCLE';
673
+ END IF;
674
+ qry := 'ALTER SEQUENCE ' || quote_ident(dest_schema) || '.' || quote_ident(object) || ' AS ' || sq_data_type || ' INCREMENT BY ' || sq_increment_by || ' MINVALUE ' || sq_min_value || ' MAXVALUE ' || sq_max_value -- will update current sequence value after this
675
+ || ' START WITH ' || sq_start_value || ' RESTART ' || sq_min_value || ' CACHE ' || sq_cache_value || ' ' || sq_cycled || ' ;';
676
+ END IF;
677
+ IF bDDLOnly THEN RAISE INFO '%',
678
+ qry;
679
+ ELSE EXECUTE qry;
680
+ END IF;
681
+ buffer := quote_ident(dest_schema) || '.' || quote_ident(object);
682
+ IF bData THEN EXECUTE 'SELECT setval( ''' || buffer || ''', ' || sq_last_value || ', ' || sq_is_called || ');';
683
+ ELSE if bDDLOnly THEN -- fix#63
684
+ -- RAISE INFO '%', 'SELECT setval( ''' || buffer || ''', ' || sq_start_value || ', ' || sq_is_called || ');' ;
685
+ RAISE INFO '%',
686
+ 'SELECT setval( ''' || buffer || ''', ' || sq_last_value || ', ' || sq_is_called || ');';
687
+ ELSE -- fix#63
688
+ -- EXECUTE 'SELECT setval( ''' || buffer || ''', ' || sq_start_value || ', ' || sq_is_called || ');' ;
689
+ EXECUTE 'SELECT setval( ''' || buffer || ''', ' || sq_last_value || ', ' || sq_is_called || ');';
690
+ END IF;
691
+ END IF;
692
+ END LOOP;
693
+ RAISE NOTICE ' SEQUENCES cloned: %',
694
+ LPAD(cnt::text, 5, ' ');
695
+ -- Create tables including partitioned ones (parent/children) and unlogged ones. Order by is critical since child partition range logic is dependent on it.
696
+ action := 'Tables';
697
+ SELECT setting INTO v_dummy
698
+ FROM pg_settings
699
+ WHERE name = 'search_path';
700
+ IF bDebug THEN RAISE NOTICE 'DEBUG: search_path=%',
701
+ v_dummy;
702
+ END IF;
703
+ cnt := 0;
704
+ -- Issue#61 FIX: use set_config for empty string
705
+ -- SET search_path = '';
706
+ SELECT set_config('search_path', '', false) into v_dummy;
707
+ IF bDebug THEN RAISE NOTICE 'DEBUG: setting search_path to empty string:%',
708
+ v_dummy;
709
+ END IF;
710
+ -- Fix#86 add isgenerated to column list
711
+ -- Fix#91 add tblowner for setting the table ownership to that of the source
712
+ -- Fix#99 added join to pg_tablespace
713
+ -- Handle PG versions greater than last major/minor version of PG 9.6.24
714
+ IF sq_server_version_num > 90624 THEN FOR tblname,
715
+ relpersist,
716
+ bRelispart,
717
+ relknd,
718
+ data_type,
719
+ udt_name,
720
+ udt_schema,
721
+ ocomment,
722
+ l_child,
723
+ isGenerated,
724
+ tblowner,
725
+ tblspace IN -- 2021-03-08 MJV #39 fix: change sql to get indicator of user-defined columns to issue warnings
726
+ -- select c.relname, c.relpersistence, c.relispartition, c.relkind
727
+ -- FROM pg_class c, pg_namespace n where n.oid = c.relnamespace and n.nspname = quote_ident(source_schema) and c.relkind in ('r','p') and
728
+ -- order by c.relkind desc, c.relname
729
+ --Fix#65 add another left join to distinguish child tables by inheritance
730
+ -- Fix#86 add is_generated to column select
731
+ -- Fix#91 add tblowner to the select
732
+ -- Fix#105 need a different kinda distint to avoid retrieving a table twice in the case of a table with multiple USER-DEFINED datatypes using DISTINCT ON instead of just DISTINCT
733
+ --SELECT DISTINCT c.relname, c.relpersistence, c.relispartition, c.relkind, co.data_type, co.udt_name, co.udt_schema, obj_description(c.oid), i.inhrelid,
734
+ -- COALESCE(co.is_generated, ''), pg_catalog.pg_get_userbyid(c.relowner) as "Owner", CASE WHEN reltablespace = 0 THEN 'pg_default' ELSE ts.spcname END as tablespace
735
+ -- fixed #108 by enclosing owner in double quotes to avoid errors for bad characters like #.@...
736
+ -- SELECT DISTINCT ON (c.relname, c.relpersistence, c.relispartition, c.relkind, co.data_type) c.relname, c.relpersistence, c.relispartition, c.relkind, co.data_type, co.udt_name, co.udt_schema, obj_description(c.oid), i.inhrelid,
737
+ SELECT DISTINCT ON (
738
+ c.relname,
739
+ c.relpersistence,
740
+ c.relispartition,
741
+ c.relkind,
742
+ co.data_type
743
+ ) c.relname,
744
+ c.relpersistence,
745
+ c.relispartition,
746
+ c.relkind,
747
+ co.data_type,
748
+ co.udt_name,
749
+ co.udt_schema,
750
+ obj_description(c.oid),
751
+ i.inhrelid,
752
+ COALESCE(co.is_generated, ''),
753
+ '"' || pg_catalog.pg_get_userbyid(c.relowner) || '"' as "Owner",
754
+ CASE
755
+ WHEN reltablespace = 0 THEN 'pg_default'
756
+ ELSE ts.spcname
757
+ END as tablespace
758
+ FROM pg_class c
759
+ JOIN pg_namespace n ON (
760
+ n.oid = c.relnamespace
761
+ AND n.nspname = quote_ident(source_schema)
762
+ AND c.relkind IN ('r', 'p')
763
+ )
764
+ LEFT JOIN information_schema.columns co ON (
765
+ co.table_schema = n.nspname
766
+ AND co.table_name = c.relname
767
+ AND (
768
+ co.data_type = 'USER-DEFINED'
769
+ OR co.is_generated = 'ALWAYS'
770
+ )
771
+ )
772
+ LEFT JOIN pg_inherits i ON (c.oid = i.inhrelid) -- issue#99 added join
773
+ LEFT JOIN pg_tablespace ts ON (c.reltablespace = ts.oid)
774
+ ORDER BY c.relkind DESC,
775
+ c.relname LOOP cnt := cnt + 1;
776
+ lastsql = '';
777
+ IF l_child IS NULL THEN bChild := False;
778
+ ELSE bChild := True;
779
+ END IF;
780
+ IF bDebug THEN RAISE NOTICE 'DEBUG: TABLE START --> table=% bRelispart=% relkind=% bChild=%',
781
+ tblname,
782
+ bRelispart,
783
+ relknd,
784
+ bChild;
785
+ END IF;
786
+ IF data_type = 'USER-DEFINED' THEN -- RAISE NOTICE ' Table (%) has column(s) with user-defined types so using get_table_ddl() instead of CREATE TABLE LIKE construct.',tblname;
787
+ cnt := cnt;
788
+ END IF;
789
+ buffer := quote_ident(dest_schema) || '.' || quote_ident(tblname);
790
+ buffer2 := '';
791
+ IF relpersist = 'u' THEN buffer2 := 'UNLOGGED ';
792
+ END IF;
793
+ IF relknd = 'r' THEN IF bDDLOnly THEN IF data_type = 'USER-DEFINED' THEN -- FIXED #65, #67
794
+ -- SELECT * INTO buffer3 FROM public.pg_get_tabledef(quote_ident(source_schema), tblname);
795
+ SELECT * INTO buffer3
796
+ FROM public.get_table_ddl(quote_ident(source_schema), tblname, False);
797
+ buffer3 := REPLACE(
798
+ buffer3,
799
+ quote_ident(source_schema) || '.',
800
+ quote_ident(dest_schema) || '.'
801
+ );
802
+ RAISE INFO '%',
803
+ buffer3;
804
+ -- issue#91 fix
805
+ -- issue#95
806
+ IF NOT bNoOwner THEN -- Fixed Issue#108: double-quote roles in case they have special characters
807
+ RAISE INFO 'ALTER TABLE IF EXISTS % OWNER TO %;',
808
+ quote_ident(dest_schema) || '.' || tblname,
809
+ tblowner;
810
+ END IF;
811
+ ELSE IF NOT bChild THEN RAISE INFO '%',
812
+ 'CREATE ' || buffer2 || 'TABLE ' || buffer || ' (LIKE ' || quote_ident(source_schema) || '.' || quote_ident(tblname) || ' INCLUDING ALL);';
813
+ -- issue#91 fix
814
+ -- issue#95
815
+ IF NOT bNoOwner THEN -- Fixed Issue#108: double-quote roles in case they have special characters
816
+ RAISE INFO 'ALTER TABLE IF EXISTS % OWNER TO %;',
817
+ quote_ident(dest_schema) || '.' || tblname,
818
+ tblowner;
819
+ END IF;
820
+ -- issue#99
821
+ IF tblspace <> 'pg_default' THEN -- replace with user-defined tablespace
822
+ -- ALTER TABLE myschema.mytable SET TABLESPACE usrtblspc;
823
+ RAISE INFO 'ALTER TABLE IF EXISTS % SET TABLESPACE %;',
824
+ quote_ident(dest_schema) || '.' || tblname,
825
+ tblspace;
826
+ END IF;
827
+ ELSE -- FIXED #65, #67
828
+ -- SELECT * INTO buffer3 FROM public.pg_get_tabledef(quote_ident(source_schema), tblname);
829
+ SELECT * INTO buffer3
830
+ FROM public.get_table_ddl(quote_ident(source_schema), tblname, False);
831
+ buffer3 := REPLACE(
832
+ buffer3,
833
+ quote_ident(source_schema) || '.',
834
+ quote_ident(dest_schema) || '.'
835
+ );
836
+ RAISE INFO '%',
837
+ buffer3;
838
+ -- issue#91 fix
839
+ -- issue#95
840
+ IF NOT bNoOwner THEN -- Fixed Issue#108: double-quote roles in case they have special characters
841
+ RAISE INFO 'ALTER TABLE IF EXISTS % OWNER TO %;',
842
+ quote_ident(dest_schema) || '.' || tblname,
843
+ tblowner;
844
+ END IF;
845
+ END IF;
846
+ END IF;
847
+ ELSE IF data_type = 'USER-DEFINED' THEN -- FIXED #65, #67
848
+ -- SELECT * INTO buffer3 FROM public.pg_get_tabledef(quote_ident(source_schema), tblname);
849
+ SELECT * INTO buffer3
850
+ FROM public.get_table_ddl(quote_ident(source_schema), tblname, False);
851
+ buffer3 := REPLACE(
852
+ buffer3,
853
+ quote_ident(source_schema) || '.',
854
+ quote_ident(dest_schema) || '.'
855
+ );
856
+ IF bDebug THEN RAISE NOTICE 'DEBUG: tabledef01:%',
857
+ buffer3;
858
+ END IF;
859
+ -- #82: Table def should be fully qualified with target schema,
860
+ -- so just make search path = public to handle extension types that should reside in public schema
861
+ v_dummy = 'public';
862
+ SELECT set_config('search_path', v_dummy, false) into v_dummy;
863
+ EXECUTE buffer3;
864
+ -- issue#91 fix
865
+ -- issue#95
866
+ IF NOT bNoOwner THEN -- Fixed Issue#108: double-quote roles in case they have special characters
867
+ buffer3 = 'ALTER TABLE IF EXISTS ' || quote_ident(dest_schema) || '.' || tblname || ' OWNER TO ' || tblowner;
868
+ lastsql = buffer3;
869
+ EXECUTE buffer3;
870
+ END IF;
871
+ ELSE IF (
872
+ NOT bChild
873
+ OR bRelispart
874
+ ) THEN buffer3 := 'CREATE ' || buffer2 || 'TABLE ' || buffer || ' (LIKE ' || quote_ident(source_schema) || '.' || quote_ident(tblname) || ' INCLUDING ALL)';
875
+ IF bDebug THEN RAISE NOTICE 'DEBUG: tabledef02:%',
876
+ buffer3;
877
+ END IF;
878
+ EXECUTE buffer3;
879
+ -- issue#91 fix
880
+ -- issue#95
881
+ IF NOT bNoOwner THEN -- Fixed Issue#108: double-quote roles in case they have special characters
882
+ buffer3 = 'ALTER TABLE IF EXISTS ' || quote_ident(dest_schema) || '.' || quote_ident(tblname) || ' OWNER TO ' || tblowner;
883
+ lastsql = buffer3;
884
+ EXECUTE buffer3;
885
+ END IF;
886
+ -- issue#99
887
+ IF tblspace <> 'pg_default' THEN -- replace with user-defined tablespace
888
+ -- ALTER TABLE myschema.mytable SET TABLESPACE usrtblspc;
889
+ buffer3 = 'ALTER TABLE IF EXISTS ' || quote_ident(dest_schema) || '.' || tblname || ' SET TABLESPACE ' || tblspace;
890
+ EXECUTE buffer3;
891
+ END IF;
892
+ ELSE -- FIXED #65, #67
893
+ -- SELECT * INTO buffer3 FROM public.pg_get_tabledef(quote_ident(source_schema), tblname);
894
+ SELECT * INTO buffer3
895
+ FROM public.get_table_ddl(quote_ident(source_schema), tblname, False);
896
+ buffer3 := REPLACE(
897
+ buffer3,
898
+ quote_ident(source_schema) || '.',
899
+ quote_ident(dest_schema) || '.'
900
+ );
901
+ -- set client_min_messages higher to avoid messages like this:
902
+ -- NOTICE: merging column "city_id" with inherited definition
903
+ set client_min_messages = 'WARNING';
904
+ IF bDebug THEN RAISE NOTICE 'DEBUG: tabledef03:%',
905
+ buffer3;
906
+ END IF;
907
+ EXECUTE buffer3;
908
+ -- issue#91 fix
909
+ -- issue#95
910
+ IF NOT bNoOwner THEN -- Fixed Issue#108: double-quote roles in case they have special characters
911
+ buffer3 = 'ALTER TABLE IF EXISTS ' || quote_ident(dest_schema) || '.' || tblname || ' OWNER TO ' || tblowner;
912
+ lastsql = buffer3;
913
+ EXECUTE buffer3;
914
+ END IF;
915
+ -- reset it back, only get these for inheritance-based tables
916
+ set client_min_messages = 'notice';
917
+ END IF;
918
+ END IF;
919
+ -- Add table comment.
920
+ IF ocomment IS NOT NULL THEN EXECUTE 'COMMENT ON TABLE ' || buffer || ' IS ' || quote_literal(ocomment);
921
+ END IF;
922
+ END IF;
923
+ ELSIF relknd = 'p' THEN -- define parent table and assume child tables have already been created based on top level sort order.
924
+ -- Issue #103 Put the complex query into its own function, get_table_ddl_complex()
925
+ SELECT * INTO qry
926
+ FROM public.get_table_ddl_complex(
927
+ source_schema,
928
+ dest_schema,
929
+ tblname,
930
+ sq_server_version_num
931
+ );
932
+ IF bDebug THEN RAISE NOTICE 'DEBUG: tabledef04 - %',
933
+ buffer;
934
+ END IF;
935
+ -- consider replacing complicated query above with this simple call to get_table_ddl()...
936
+ -- SELECT * INTO qry FROM public.get_table_ddl(quote_ident(source_schema), tblname, False);
937
+ -- qry := REPLACE(qry, quote_ident(source_schema) || '.', quote_ident(dest_schema) || '.');
938
+ IF bDDLOnly THEN RAISE INFO '%',
939
+ qry;
940
+ -- issue#95
941
+ IF NOT bNoOwner THEN -- Fixed Issue#108: double-quote roles in case they have special characters
942
+ RAISE INFO 'ALTER TABLE IF EXISTS % OWNER TO %;',
943
+ quote_ident(dest_schema) || '.' || quote_ident(tblname),
944
+ tblowner;
945
+ END IF;
946
+ ELSE -- Issue#103: we need to always set search_path priority to target schema when we execute DDL
947
+ IF bDebug THEN RAISE NOTICE 'DEBUG: tabledef04 context: old search path=% new search path=% current search path=%',
948
+ src_path_old,
949
+ src_path_new,
950
+ v_dummy;
951
+ END IF;
952
+ SELECT setting INTO spath_tmp
953
+ FROM pg_settings
954
+ WHERE name = 'search_path';
955
+ IF spath_tmp <> dest_schema THEN -- change it to target schema and don't forget to change it back after we execute the DDL
956
+ spath = 'SET search_path = "' || dest_schema || '"';
957
+ IF bDebug THEN RAISE NOTICE 'DEBUG: changing search_path --> %',
958
+ spath;
959
+ END IF;
960
+ EXECUTE spath;
961
+ SELECT setting INTO v_dummy
962
+ FROM pg_settings
963
+ WHERE name = 'search_path';
964
+ IF bDebug THEN RAISE NOTICE 'DEBUG: search_path changed to %',
965
+ v_dummy;
966
+ END IF;
967
+ END IF;
968
+ IF bDebug THEN RAISE NOTICE 'DEBUG: tabledef04:%',
969
+ qry;
970
+ END IF;
971
+ EXECUTE qry;
972
+ -- Issue#103
973
+ -- Set search path back to what it was
974
+ spath = 'SET search_path = "' || spath_tmp || '"';
975
+ EXECUTE spath;
976
+ SELECT setting INTO v_dummy
977
+ FROM pg_settings
978
+ WHERE name = 'search_path';
979
+ IF bDebug THEN RAISE NOTICE 'DEBUG: search_path changed back to %',
980
+ v_dummy;
981
+ END IF;
982
+ -- issue#91 fix
983
+ -- issue#95
984
+ IF NOT bNoOwner THEN -- Fixed Issue#108: double-quote roles in case they have special characters
985
+ buffer3 = 'ALTER TABLE IF EXISTS ' || quote_ident(dest_schema) || '.' || quote_ident(tblname) || ' OWNER TO ' || tblowner;
986
+ lastsql = buffer3;
987
+ EXECUTE buffer3;
988
+ END IF;
989
+ END IF;
990
+ -- loop for child tables and alter them to attach to parent for specific partition method.
991
+ -- Issue#103 fix: only loop for the table we are currently processing, tblname!
992
+ FOR aname,
993
+ part_range,
994
+ object IN
995
+ SELECT quote_ident(dest_schema) || '.' || c1.relname as tablename,
996
+ pg_catalog.pg_get_expr(c1.relpartbound, c1.oid) as partrange,
997
+ quote_ident(dest_schema) || '.' || c2.relname as object
998
+ FROM pg_catalog.pg_class c1,
999
+ pg_namespace n,
1000
+ pg_catalog.pg_inherits i,
1001
+ pg_class c2
1002
+ WHERE n.nspname = quote_ident(source_schema)
1003
+ AND c1.relnamespace = n.oid
1004
+ AND c1.relkind = 'r' -- Issue#103: added this condition to only work on current partitioned table. The problem was regression testing previously only worked on one partition table clone case
1005
+ AND c2.relname = tblname
1006
+ AND c1.relispartition
1007
+ AND c1.oid = i.inhrelid
1008
+ AND i.inhparent = c2.oid
1009
+ AND c2.relnamespace = n.oid
1010
+ ORDER BY pg_catalog.pg_get_expr(c1.relpartbound, c1.oid) = 'DEFAULT',
1011
+ c1.oid::pg_catalog.regclass::pg_catalog.text LOOP qry := 'ALTER TABLE ONLY ' || object || ' ATTACH PARTITION ' || aname || ' ' || part_range || ';';
1012
+ IF bDebug THEN RAISE NOTICE 'DEBUG: %',
1013
+ qry;
1014
+ END IF;
1015
+ -- issue#91, not sure if we need to do this for child tables
1016
+ -- issue#95 we dont set ownership here
1017
+ IF bDDLOnly THEN RAISE INFO '%',
1018
+ qry;
1019
+ IF NOT bNoOwner THEN NULL;
1020
+ END IF;
1021
+ ELSE EXECUTE qry;
1022
+ IF NOT bNoOwner THEN NULL;
1023
+ END IF;
1024
+ END IF;
1025
+ END LOOP;
1026
+ END IF;
1027
+ -- INCLUDING ALL creates new index names, we restore them to the old name.
1028
+ -- There should be no conflicts since they live in different schemas
1029
+ FOR ix_old_name,
1030
+ ix_new_name IN
1031
+ SELECT old.indexname,
1032
+ new.indexname
1033
+ FROM pg_indexes old,
1034
+ pg_indexes new
1035
+ WHERE old.schemaname = source_schema
1036
+ AND new.schemaname = dest_schema
1037
+ AND old.tablename = new.tablename
1038
+ AND old.tablename = tblname
1039
+ AND old.indexname <> new.indexname
1040
+ AND regexp_replace(old.indexdef, E'.*USING', '') = regexp_replace(new.indexdef, E'.*USING', '')
1041
+ ORDER BY old.indexdef,
1042
+ new.indexdef LOOP IF bDDLOnly THEN RAISE INFO '%',
1043
+ 'ALTER INDEX ' || quote_ident(dest_schema) || '.' || quote_ident(ix_new_name) || ' RENAME TO ' || quote_ident(ix_old_name) || ';';
1044
+ ELSE -- The SELECT query above may return duplicate names when a column is
1045
+ -- indexed twice the same manner with 2 different names. Therefore, to
1046
+ -- avoid a 'relation "xxx" already exists' we test if the index name
1047
+ -- is in use or free. Skipping existing index will fallback on unused
1048
+ -- ones and every duplicate will be mapped to distinct old names.
1049
+ IF NOT EXISTS (
1050
+ SELECT TRUE
1051
+ FROM pg_indexes
1052
+ WHERE schemaname = dest_schema
1053
+ AND tablename = tblname
1054
+ AND indexname = quote_ident(ix_old_name)
1055
+ )
1056
+ AND EXISTS (
1057
+ SELECT TRUE
1058
+ FROM pg_indexes
1059
+ WHERE schemaname = dest_schema
1060
+ AND tablename = tblname
1061
+ AND indexname = quote_ident(ix_new_name)
1062
+ ) THEN EXECUTE 'ALTER INDEX ' || quote_ident(dest_schema) || '.' || quote_ident(ix_new_name) || ' RENAME TO ' || quote_ident(ix_old_name) || ';';
1063
+ END IF;
1064
+ END IF;
1065
+ END LOOP;
1066
+ lastsql = '';
1067
+ IF bData THEN -- Insert records from source table
1068
+ -- 2021-03-03 MJV FIX
1069
+ buffer := dest_schema || '.' || quote_ident(tblname);
1070
+ -- 2020/06/18 - Issue #31 fix: add "OVERRIDING SYSTEM VALUE" for IDENTITY columns marked as GENERATED ALWAYS.
1071
+ select count(*) into cnt2
1072
+ from pg_class c,
1073
+ pg_attribute a,
1074
+ pg_namespace n
1075
+ where a.attrelid = c.oid
1076
+ and c.relname = quote_ident(tblname)
1077
+ and n.oid = c.relnamespace
1078
+ and n.nspname = quote_ident(source_schema)
1079
+ and a.attidentity = 'a';
1080
+ buffer3 := '';
1081
+ IF cnt2 > 0 THEN buffer3 := ' OVERRIDING SYSTEM VALUE';
1082
+ END IF;
1083
+ -- BUG for inserting rows from tables with user-defined columns
1084
+ -- INSERT INTO sample_clone.address OVERRIDING SYSTEM VALUE SELECT * FROM sample.address;
1085
+ -- ERROR: column "id2" is of type sample_clone.udt_myint but expression is of type udt_myint
1086
+ -- Issue#86 fix:
1087
+ -- IF data_type = 'USER-DEFINED' THEN
1088
+ IF bDebug THEN RAISE NOTICE 'DEBUG: includerecs branch table=% data_type=% isgenerated=% buffer3=%',
1089
+ tblname,
1090
+ data_type,
1091
+ isGenerated,
1092
+ buffer3;
1093
+ END IF;
1094
+ IF data_type = 'USER-DEFINED'
1095
+ OR isGenerated = 'ALWAYS' THEN -- RAISE WARNING 'Bypassing copying rows for table (%) with user-defined data types. You must copy them manually.', tblname;
1096
+ -- wont work --> INSERT INTO clone1.address (id2, id3, addr) SELECT cast(id2 as clone1.udt_myint), cast(id3 as clone1.udt_myint), addr FROM sample.address;
1097
+ -- Issue#101 --> INSERT INTO clone1.address2 (id2, id3, addr) SELECT id2::text::clone1.udt_myint, id3::text::clone1.udt_myint, addr FROM sample.address;
1098
+ -- Issue#79 implementation follows
1099
+ -- COPY sample.statuses(id, s) TO '/tmp/statuses.txt' WITH DELIMITER AS ',';
1100
+ -- COPY sample_clone1.statuses FROM '/tmp/statuses.txt' (DELIMITER ',', NULL '');
1101
+ -- Issue#101 fix: use text cast to get around the problem.
1102
+ IF bFileCopy THEN IF bWindows THEN buffer2 := 'COPY ' || quote_ident(source_schema) || '.' || quote_ident(tblname) || ' TO ''C:\WINDOWS\TEMP\cloneschema.tmp'' WITH DELIMITER AS '','';';
1103
+ tblarray2 := tblarray2 || buffer2;
1104
+ -- Issue #81 reformat COPY command for upload
1105
+ -- buffer2:= 'COPY ' || quote_ident(dest_schema) || '.' || quote_ident(tblname) || ' FROM ''C:\WINDOWS\TEMP\cloneschema.tmp'' (DELIMITER '','', NULL '''');';
1106
+ buffer2 := 'COPY ' || quote_ident(dest_schema) || '.' || quote_ident(tblname) || ' FROM ''C:\WINDOWS\TEMP\cloneschema.tmp'' (DELIMITER '','', NULL ''\N'', FORMAT CSV);';
1107
+ tblarray2 := tblarray2 || buffer2;
1108
+ ELSE buffer2 := 'COPY ' || quote_ident(source_schema) || '.' || quote_ident(tblname) || ' TO ''/tmp/cloneschema.tmp'' WITH DELIMITER AS '','';';
1109
+ tblarray2 := tblarray2 || buffer2;
1110
+ -- Issue #81 reformat COPY command for upload
1111
+ -- buffer2 := 'COPY ' || quote_ident(dest_schema) || '.' || quote_ident(tblname) || ' FROM ''/tmp/cloneschema.tmp'' (DELIMITER '','', NULL '''');';
1112
+ -- works--> COPY sample.timestamptbl2 FROM '/tmp/cloneschema.tmp' WITH (DELIMITER ',', NULL '\N', FORMAT CSV) ;
1113
+ buffer2 := 'COPY ' || quote_ident(dest_schema) || '.' || quote_ident(tblname) || ' FROM ''/tmp/cloneschema.tmp'' (DELIMITER '','', NULL ''\N'', FORMAT CSV);';
1114
+ tblarray2 := tblarray2 || buffer2;
1115
+ END IF;
1116
+ ELSE -- Issue#101: assume direct copy with text cast, add to separate array
1117
+ SELECT * INTO buffer3
1118
+ FROM public.get_insert_stmt_ddl(
1119
+ quote_ident(source_schema),
1120
+ quote_ident(dest_schema),
1121
+ quote_ident(tblname),
1122
+ True
1123
+ );
1124
+ tblarray3 := tblarray3 || buffer3;
1125
+ END IF;
1126
+ ELSE -- bypass child tables since we populate them when we populate the parents
1127
+ IF bDebug THEN RAISE NOTICE 'DEBUG: tblname=% bRelispart=% relknd=% l_child=% bChild=%',
1128
+ tblname,
1129
+ bRelispart,
1130
+ relknd,
1131
+ l_child,
1132
+ bChild;
1133
+ END IF;
1134
+ IF NOT bRelispart
1135
+ AND NOT bChild THEN -- Issue#75: Must defer population of tables until child tables have been added to parents
1136
+ -- Issue#101 Offer alternative of copy to/from file. Although originally intended for tables with UDTs, it is now expanded to handle all cases for performance improvement perhaps for large tables.
1137
+ -- Issue#106 buffer3 shouldnt be in the mix
1138
+ -- revisited: buffer3 should be in play for PG versions that handle IDENTITIES
1139
+ buffer2 := 'INSERT INTO ' || buffer || buffer3 || ' SELECT * FROM ' || quote_ident(source_schema) || '.' || quote_ident(tblname) || ';';
1140
+ -- buffer2 := 'INSERT INTO ' || buffer || ' SELECT * FROM ' || quote_ident(source_schema) || '.' || quote_ident(tblname) || ';';
1141
+ IF bDebug THEN RAISE NOTICE 'DEBUG: buffer2=%',
1142
+ buffer2;
1143
+ END IF;
1144
+ IF bFileCopy THEN tblarray2 := tblarray2 || buffer2;
1145
+ ELSE tblarray := tblarray || buffer2;
1146
+ END IF;
1147
+ END IF;
1148
+ END IF;
1149
+ END IF;
1150
+ -- Issue#61 FIX: use set_config for empty string
1151
+ -- SET search_path = '';
1152
+ SELECT set_config('search_path', '', false) into v_dummy;
1153
+ FOR column_,
1154
+ default_ IN
1155
+ SELECT column_name::text,
1156
+ REPLACE(
1157
+ column_default::text,
1158
+ quote_ident(source_schema) || '.',
1159
+ quote_ident(dest_schema) || '.'
1160
+ )
1161
+ FROM information_schema.COLUMNS
1162
+ WHERE table_schema = source_schema
1163
+ AND TABLE_NAME = tblname
1164
+ AND column_default LIKE 'nextval(%' || quote_ident(source_schema) || '%::regclass)' LOOP -- Issue#78 FIX: handle case-sensitive names with quote_ident() on column name
1165
+ buffer2 = 'ALTER TABLE ' || buffer || ' ALTER COLUMN ' || quote_ident(column_) || ' SET DEFAULT ' || default_ || ';';
1166
+ IF bDDLOnly THEN -- May need to come back and revisit this since previous sql will not return anything since no schema as created!
1167
+ RAISE INFO '%',
1168
+ buffer2;
1169
+ ELSE EXECUTE buffer2;
1170
+ END IF;
1171
+ END LOOP;
1172
+ EXECUTE 'SET search_path = ' || quote_ident(source_schema);
1173
+ END LOOP;
1174
+ ELSE -- Handle 9.6 versions 90600
1175
+ FOR tblname,
1176
+ relpersist,
1177
+ relknd,
1178
+ data_type,
1179
+ udt_name,
1180
+ udt_schema,
1181
+ ocomment,
1182
+ l_child,
1183
+ isGenerated,
1184
+ tblowner,
1185
+ tblspace IN -- 2021-03-08 MJV #39 fix: change sql to get indicator of user-defined columns to issue warnings
1186
+ -- select c.relname, c.relpersistence, c.relispartition, c.relkind
1187
+ -- FROM pg_class c, pg_namespace n where n.oid = c.relnamespace and n.nspname = quote_ident(source_schema) and c.relkind in ('r','p') and
1188
+ -- order by c.relkind desc, c.relname
1189
+ --Fix#65 add another left join to distinguish child tables by inheritance
1190
+ -- Fix#86 add is_generated to column select
1191
+ -- Fix#91 add tblowner to the select
1192
+ -- Fix#105 need a different kinda distint to avoid retrieving a table twice in the case of a table with multiple USER-DEFINED datatypes using DISTINCT ON instead of just DISTINCT
1193
+ -- Fixed Issue#108: double quote roles to avoid problems with special characters in OWNER TO statements
1194
+ --SELECT DISTINCT c.relname, c.relpersistence, c.relispartition, c.relkind, co.data_type, co.udt_name, co.udt_schema, obj_description(c.oid), i.inhrelid,
1195
+ -- COALESCE(co.is_generated, ''), pg_catalog.pg_get_userbyid(c.relowner) as "Owner", CASE WHEN reltablespace = 0 THEN 'pg_default' ELSE ts.spcname END as tablespace
1196
+ -- SELECT DISTINCT ON (c.relname, c.relpersistence, c.relkind, co.data_type) c.relname, c.relpersistence, c.relkind, co.data_type, co.udt_name, co.udt_schema, obj_description(c.oid), i.inhrelid,
1197
+ -- COALESCE(co.is_generated, ''), pg_catalog.pg_get_userbyid(c.relowner) as "Owner", CASE WHEN reltablespace = 0 THEN 'pg_default' ELSE ts.spcname END as tablespace
1198
+ SELECT DISTINCT ON (
1199
+ c.relname,
1200
+ c.relpersistence,
1201
+ c.relkind,
1202
+ co.data_type
1203
+ ) c.relname,
1204
+ c.relpersistence,
1205
+ c.relkind,
1206
+ co.data_type,
1207
+ co.udt_name,
1208
+ co.udt_schema,
1209
+ obj_description(c.oid),
1210
+ i.inhrelid,
1211
+ COALESCE(co.is_generated, ''),
1212
+ '"' || pg_catalog.pg_get_userbyid(c.relowner) || '"' as "Owner",
1213
+ CASE
1214
+ WHEN reltablespace = 0 THEN 'pg_default'
1215
+ ELSE ts.spcname
1216
+ END as tablespace
1217
+ FROM pg_class c
1218
+ JOIN pg_namespace n ON (
1219
+ n.oid = c.relnamespace
1220
+ AND n.nspname = quote_ident(source_schema)
1221
+ AND c.relkind IN ('r', 'p')
1222
+ )
1223
+ LEFT JOIN information_schema.columns co ON (
1224
+ co.table_schema = n.nspname
1225
+ AND co.table_name = c.relname
1226
+ AND (
1227
+ co.data_type = 'USER-DEFINED'
1228
+ OR co.is_generated = 'ALWAYS'
1229
+ )
1230
+ )
1231
+ LEFT JOIN pg_inherits i ON (c.oid = i.inhrelid) -- issue#99 added join
1232
+ LEFT JOIN pg_tablespace ts ON (c.reltablespace = ts.oid)
1233
+ ORDER BY c.relkind DESC,
1234
+ c.relname LOOP cnt := cnt + 1;
1235
+ IF l_child IS NULL THEN bChild := False;
1236
+ ELSE bChild := True;
1237
+ END IF;
1238
+ IF bDebug THEN RAISE NOTICE 'DEBUG: TABLE START --> table=% bRelispart=NA relkind=% bChild=%',
1239
+ tblname,
1240
+ relknd,
1241
+ bChild;
1242
+ END IF;
1243
+ IF data_type = 'USER-DEFINED' THEN -- RAISE NOTICE ' Table (%) has column(s) with user-defined types so using get_table_ddl() instead of CREATE TABLE LIKE construct.',tblname;
1244
+ cnt := cnt;
1245
+ END IF;
1246
+ buffer := quote_ident(dest_schema) || '.' || quote_ident(tblname);
1247
+ buffer2 := '';
1248
+ IF relpersist = 'u' THEN buffer2 := 'UNLOGGED ';
1249
+ END IF;
1250
+ IF relknd = 'r' THEN IF bDDLOnly THEN IF data_type = 'USER-DEFINED' THEN -- FIXED #65, #67
1251
+ -- SELECT * INTO buffer3 FROM public.pg_get_tabledef(quote_ident(source_schema), tblname);
1252
+ SELECT * INTO buffer3
1253
+ FROM public.get_table_ddl(quote_ident(source_schema), tblname, False);
1254
+ buffer3 := REPLACE(
1255
+ buffer3,
1256
+ quote_ident(source_schema) || '.',
1257
+ quote_ident(dest_schema) || '.'
1258
+ );
1259
+ RAISE INFO '%',
1260
+ buffer3;
1261
+ -- issue#91 fix
1262
+ -- issue#95
1263
+ IF NOT bNoOwner THEN -- Fixed Issue#108: double-quote roles in case they have special characters
1264
+ RAISE INFO 'ALTER TABLE IF EXISTS % OWNER TO %;',
1265
+ quote_ident(dest_schema) || '.' || tblname,
1266
+ tblowner;
1267
+ END IF;
1268
+ ELSE IF NOT bChild THEN RAISE INFO '%',
1269
+ 'CREATE ' || buffer2 || 'TABLE ' || buffer || ' (LIKE ' || quote_ident(source_schema) || '.' || quote_ident(tblname) || ' INCLUDING ALL);';
1270
+ -- issue#91 fix
1271
+ -- issue#95
1272
+ IF NOT bNoOwner THEN -- Fixed Issue#108: double-quote roles in case they have special characters
1273
+ RAISE INFO 'ALTER TABLE IF EXISTS % OWNER TO %;',
1274
+ quote_ident(dest_schema) || '.' || tblname,
1275
+ tblowner;
1276
+ END IF;
1277
+ -- issue#99
1278
+ IF tblspace <> 'pg_default' THEN -- replace with user-defined tablespace
1279
+ -- ALTER TABLE myschema.mytable SET TABLESPACE usrtblspc;
1280
+ RAISE INFO 'ALTER TABLE IF EXISTS % SET TABLESPACE %;',
1281
+ quote_ident(dest_schema) || '.' || tblname,
1282
+ tblspace;
1283
+ END IF;
1284
+ ELSE -- FIXED #65, #67
1285
+ -- SELECT * INTO buffer3 FROM public.pg_get_tabledef(quote_ident(source_schema), tblname);
1286
+ SELECT * INTO buffer3
1287
+ FROM public.get_table_ddl(quote_ident(source_schema), tblname, False);
1288
+ buffer3 := REPLACE(
1289
+ buffer3,
1290
+ quote_ident(source_schema) || '.',
1291
+ quote_ident(dest_schema) || '.'
1292
+ );
1293
+ RAISE INFO '%',
1294
+ buffer3;
1295
+ -- issue#91 fix
1296
+ -- issue#95
1297
+ IF NOT bNoOwner THEN -- Fixed Issue#108: double-quote roles in case they have special characters
1298
+ RAISE INFO 'ALTER TABLE IF EXISTS % OWNER TO %;',
1299
+ quote_ident(dest_schema) || '.' || tblname,
1300
+ tblowner;
1301
+ END IF;
1302
+ END IF;
1303
+ END IF;
1304
+ ELSE IF data_type = 'USER-DEFINED' THEN -- FIXED #65, #67
1305
+ -- SELECT * INTO buffer3 FROM public.pg_get_tabledef(quote_ident(source_schema), tblname);
1306
+ SELECT * INTO buffer3
1307
+ FROM public.get_table_ddl(quote_ident(source_schema), tblname, False);
1308
+ buffer3 := REPLACE(
1309
+ buffer3,
1310
+ quote_ident(source_schema) || '.',
1311
+ quote_ident(dest_schema) || '.'
1312
+ );
1313
+ IF bDebug THEN RAISE NOTICE 'DEBUG: tabledef01:%',
1314
+ buffer3;
1315
+ END IF;
1316
+ -- #82: Table def should be fully qualified with target schema,
1317
+ -- so just make search path = public to handle extension types that should reside in public schema
1318
+ v_dummy = 'public';
1319
+ SELECT set_config('search_path', v_dummy, false) into v_dummy;
1320
+ EXECUTE buffer3;
1321
+ -- issue#91 fix
1322
+ -- issue#95
1323
+ IF NOT bNoOwner THEN -- Fixed Issue#108: double-quote roles in case they have special characters
1324
+ buffer3 = 'ALTER TABLE IF EXISTS ' || quote_ident(dest_schema) || '.' || tblname || ' OWNER TO ' || tblowner;
1325
+ lastsql = buffer3;
1326
+ EXECUTE buffer3;
1327
+ END IF;
1328
+ ELSE IF (NOT bChild) THEN buffer3 := 'CREATE ' || buffer2 || 'TABLE ' || buffer || ' (LIKE ' || quote_ident(source_schema) || '.' || quote_ident(tblname) || ' INCLUDING ALL)';
1329
+ IF bDebug THEN RAISE NOTICE 'DEBUG: tabledef02:%',
1330
+ buffer3;
1331
+ END IF;
1332
+ EXECUTE buffer3;
1333
+ -- issue#91 fix
1334
+ -- issue#95
1335
+ IF NOT bNoOwner THEN -- Fixed Issue#108: double-quote roles in case they have special characters
1336
+ buffer3 = 'ALTER TABLE IF EXISTS ' || quote_ident(dest_schema) || '.' || quote_ident(tblname) || ' OWNER TO ' || tblowner;
1337
+ lastsql = buffer3;
1338
+ EXECUTE buffer3;
1339
+ END IF;
1340
+ -- issue#99
1341
+ IF tblspace <> 'pg_default' THEN -- replace with user-defined tablespace
1342
+ -- ALTER TABLE myschema.mytable SET TABLESPACE usrtblspc;
1343
+ buffer3 = 'ALTER TABLE IF EXISTS ' || quote_ident(dest_schema) || '.' || tblname || ' SET TABLESPACE ' || tblspace;
1344
+ EXECUTE buffer3;
1345
+ END IF;
1346
+ ELSE -- FIXED #65, #67
1347
+ -- SELECT * INTO buffer3 FROM public.pg_get_tabledef(quote_ident(source_schema), tblname);
1348
+ SELECT * INTO buffer3
1349
+ FROM public.get_table_ddl(quote_ident(source_schema), tblname, False);
1350
+ buffer3 := REPLACE(
1351
+ buffer3,
1352
+ quote_ident(source_schema) || '.',
1353
+ quote_ident(dest_schema) || '.'
1354
+ );
1355
+ -- set client_min_messages higher to avoid messages like this:
1356
+ -- NOTICE: merging column "city_id" with inherited definition
1357
+ set client_min_messages = 'WARNING';
1358
+ IF bDebug THEN RAISE NOTICE 'DEBUG: tabledef03:%',
1359
+ buffer3;
1360
+ END IF;
1361
+ EXECUTE buffer3;
1362
+ -- issue#91 fix
1363
+ -- issue#95
1364
+ IF NOT bNoOwner THEN -- Fixed Issue#108: double-quote roles in case they have special characters
1365
+ buffer3 = 'ALTER TABLE IF EXISTS ' || quote_ident(dest_schema) || '.' || tblname || ' OWNER TO ' || tblowner;
1366
+ lastsql = buffer3;
1367
+ EXECUTE buffer3;
1368
+ END IF;
1369
+ -- reset it back, only get these for inheritance-based tables
1370
+ set client_min_messages = 'notice';
1371
+ END IF;
1372
+ END IF;
1373
+ -- Add table comment.
1374
+ IF ocomment IS NOT NULL THEN EXECUTE 'COMMENT ON TABLE ' || buffer || ' IS ' || quote_literal(ocomment);
1375
+ END IF;
1376
+ END IF;
1377
+ ELSIF relknd = 'p' THEN -- define parent table and assume child tables have already been created based on top level sort order.
1378
+ -- Issue #103 Put the complex query into its own function, get_table_ddl_complex()
1379
+ SELECT * INTO qry
1380
+ FROM public.get_table_ddl_complex(
1381
+ source_schema,
1382
+ dest_schema,
1383
+ tblname,
1384
+ sq_server_version_num
1385
+ );
1386
+ IF bDebug THEN RAISE NOTICE 'DEBUG: tabledef04 - %',
1387
+ buffer;
1388
+ END IF;
1389
+ -- consider replacing complicated query above with this simple call to get_table_ddl()...
1390
+ -- SELECT * INTO qry FROM public.get_table_ddl(quote_ident(source_schema), tblname, False);
1391
+ -- qry := REPLACE(qry, quote_ident(source_schema) || '.', quote_ident(dest_schema) || '.');
1392
+ IF bDDLOnly THEN RAISE INFO '%',
1393
+ qry;
1394
+ -- issue#95
1395
+ IF NOT bNoOwner THEN -- Fixed Issue#108: double-quote roles in case they have special characters
1396
+ RAISE INFO 'ALTER TABLE IF EXISTS % OWNER TO %;',
1397
+ quote_ident(dest_schema) || '.' || quote_ident(tblname),
1398
+ tblowner;
1399
+ END IF;
1400
+ ELSE -- Issue#103: we need to always set search_path priority to target schema when we execute DDL
1401
+ IF bDebug THEN RAISE NOTICE 'DEBUG: tabledef04 context: old search path=% new search path=% current search path=%',
1402
+ src_path_old,
1403
+ src_path_new,
1404
+ v_dummy;
1405
+ END IF;
1406
+ SELECT setting INTO spath_tmp
1407
+ FROM pg_settings
1408
+ WHERE name = 'search_path';
1409
+ IF spath_tmp <> dest_schema THEN -- change it to target schema and don't forget to change it back after we execute the DDL
1410
+ spath = 'SET search_path = "' || dest_schema || '"';
1411
+ IF bDebug THEN RAISE NOTICE 'DEBUG: changing search_path --> %',
1412
+ spath;
1413
+ END IF;
1414
+ EXECUTE spath;
1415
+ SELECT setting INTO v_dummy
1416
+ FROM pg_settings
1417
+ WHERE name = 'search_path';
1418
+ IF bDebug THEN RAISE NOTICE 'DEBUG: search_path changed to %',
1419
+ v_dummy;
1420
+ END IF;
1421
+ END IF;
1422
+ IF bDebug THEN RAISE NOTICE 'DEBUG: tabledef04:%',
1423
+ qry;
1424
+ END IF;
1425
+ EXECUTE qry;
1426
+ -- Issue#103
1427
+ -- Set search path back to what it was
1428
+ spath = 'SET search_path = "' || spath_tmp || '"';
1429
+ EXECUTE spath;
1430
+ SELECT setting INTO v_dummy
1431
+ FROM pg_settings
1432
+ WHERE name = 'search_path';
1433
+ IF bDebug THEN RAISE NOTICE 'DEBUG: search_path changed back to %',
1434
+ v_dummy;
1435
+ END IF;
1436
+ -- issue#91 fix
1437
+ -- issue#95
1438
+ IF NOT bNoOwner THEN -- Fixed Issue#108: double-quote roles in case they have special characters
1439
+ buffer3 = 'ALTER TABLE IF EXISTS ' || quote_ident(dest_schema) || '.' || quote_ident(tblname) || ' OWNER TO ' || tblowner;
1440
+ EXECUTE buffer3;
1441
+ END IF;
1442
+ END IF;
1443
+ -- loop for child tables and alter them to attach to parent for specific partition method.
1444
+ -- Issue#103 fix: only loop for the table we are currently processing, tblname!
1445
+ FOR aname,
1446
+ part_range,
1447
+ object IN
1448
+ SELECT quote_ident(dest_schema) || '.' || c1.relname as tablename,
1449
+ pg_catalog.pg_get_expr(c1.relpartbound, c1.oid) as partrange,
1450
+ quote_ident(dest_schema) || '.' || c2.relname as object
1451
+ FROM pg_catalog.pg_class c1,
1452
+ pg_namespace n,
1453
+ pg_catalog.pg_inherits i,
1454
+ pg_class c2
1455
+ WHERE n.nspname = quote_ident(source_schema)
1456
+ AND c1.relnamespace = n.oid
1457
+ AND c1.relkind = 'r' -- Issue#103: added this condition to only work on current partitioned table. The problem was regression testing previously only worked on one partition table clone case
1458
+ AND c2.relname = tblname
1459
+ AND c1.relispartition
1460
+ AND c1.oid = i.inhrelid
1461
+ AND i.inhparent = c2.oid
1462
+ AND c2.relnamespace = n.oid
1463
+ ORDER BY pg_catalog.pg_get_expr(c1.relpartbound, c1.oid) = 'DEFAULT',
1464
+ c1.oid::pg_catalog.regclass::pg_catalog.text LOOP qry := 'ALTER TABLE ONLY ' || object || ' ATTACH PARTITION ' || aname || ' ' || part_range || ';';
1465
+ IF bDebug THEN RAISE NOTICE 'DEBUG: %',
1466
+ qry;
1467
+ END IF;
1468
+ -- issue#91, not sure if we need to do this for child tables
1469
+ -- issue#95 we dont set ownership here
1470
+ IF bDDLOnly THEN RAISE INFO '%',
1471
+ qry;
1472
+ IF NOT bNoOwner THEN NULL;
1473
+ END IF;
1474
+ ELSE EXECUTE qry;
1475
+ IF NOT bNoOwner THEN NULL;
1476
+ END IF;
1477
+ END IF;
1478
+ END LOOP;
1479
+ END IF;
1480
+ -- INCLUDING ALL creates new index names, we restore them to the old name.
1481
+ -- There should be no conflicts since they live in different schemas
1482
+ FOR ix_old_name,
1483
+ ix_new_name IN
1484
+ SELECT old.indexname,
1485
+ new.indexname
1486
+ FROM pg_indexes old,
1487
+ pg_indexes new
1488
+ WHERE old.schemaname = source_schema
1489
+ AND new.schemaname = dest_schema
1490
+ AND old.tablename = new.tablename
1491
+ AND old.tablename = tblname
1492
+ AND old.indexname <> new.indexname
1493
+ AND regexp_replace(old.indexdef, E'.*USING', '') = regexp_replace(new.indexdef, E'.*USING', '')
1494
+ ORDER BY old.indexdef,
1495
+ new.indexdef LOOP lastsql = '';
1496
+ IF bDDLOnly THEN RAISE INFO '%',
1497
+ 'ALTER INDEX ' || quote_ident(dest_schema) || '.' || quote_ident(ix_new_name) || ' RENAME TO ' || quote_ident(ix_old_name) || ';';
1498
+ ELSE -- The SELECT query above may return duplicate names when a column is
1499
+ -- indexed twice the same manner with 2 different names. Therefore, to
1500
+ -- avoid a 'relation "xxx" already exists' we test if the index name
1501
+ -- is in use or free. Skipping existing index will fallback on unused
1502
+ -- ones and every duplicate will be mapped to distinct old names.
1503
+ IF NOT EXISTS (
1504
+ SELECT TRUE
1505
+ FROM pg_indexes
1506
+ WHERE schemaname = dest_schema
1507
+ AND tablename = tblname
1508
+ AND indexname = quote_ident(ix_old_name)
1509
+ )
1510
+ AND EXISTS (
1511
+ SELECT TRUE
1512
+ FROM pg_indexes
1513
+ WHERE schemaname = dest_schema
1514
+ AND tablename = tblname
1515
+ AND indexname = quote_ident(ix_new_name)
1516
+ ) THEN EXECUTE 'ALTER INDEX ' || quote_ident(dest_schema) || '.' || quote_ident(ix_new_name) || ' RENAME TO ' || quote_ident(ix_old_name) || ';';
1517
+ END IF;
1518
+ END IF;
1519
+ END LOOP;
1520
+ IF bData THEN -- Insert records from source table
1521
+ -- 2021-03-03 MJV FIX
1522
+ buffer := dest_schema || '.' || quote_ident(tblname);
1523
+ -- Issue#86 fix:
1524
+ -- IF data_type = 'USER-DEFINED' THEN
1525
+ IF bDebug THEN RAISE NOTICE 'DEBUG: includerecs branch table=% data_type=% isgenerated=%',
1526
+ tblname,
1527
+ data_type,
1528
+ isGenerated;
1529
+ END IF;
1530
+ IF data_type = 'USER-DEFINED'
1531
+ OR isGenerated = 'ALWAYS' THEN -- RAISE WARNING 'Bypassing copying rows for table (%) with user-defined data types. You must copy them manually.', tblname;
1532
+ -- wont work --> INSERT INTO clone1.address (id2, id3, addr) SELECT cast(id2 as clone1.udt_myint), cast(id3 as clone1.udt_myint), addr FROM sample.address;
1533
+ -- Issue#101 --> INSERT INTO clone1.address2 (id2, id3, addr) SELECT id2::text::clone1.udt_myint, id3::text::clone1.udt_myint, addr FROM sample.address;
1534
+ -- Issue#79 implementation follows
1535
+ -- COPY sample.statuses(id, s) TO '/tmp/statuses.txt' WITH DELIMITER AS ',';
1536
+ -- COPY sample_clone1.statuses FROM '/tmp/statuses.txt' (DELIMITER ',', NULL '');
1537
+ -- Issue#101 fix: use text cast to get around the problem.
1538
+ IF bFileCopy THEN IF bWindows THEN buffer2 := 'COPY ' || quote_ident(source_schema) || '.' || quote_ident(tblname) || ' TO ''C:\WINDOWS\TEMP\cloneschema.tmp'' WITH DELIMITER AS '','';';
1539
+ tblarray2 := tblarray2 || buffer2;
1540
+ -- Issue #81 reformat COPY command for upload
1541
+ -- buffer2:= 'COPY ' || quote_ident(dest_schema) || '.' || quote_ident(tblname) || ' FROM ''C:\WINDOWS\TEMP\cloneschema.tmp'' (DELIMITER '','', NULL '''');';
1542
+ buffer2 := 'COPY ' || quote_ident(dest_schema) || '.' || quote_ident(tblname) || ' FROM ''C:\WINDOWS\TEMP\cloneschema.tmp'' (DELIMITER '','', NULL ''\N'', FORMAT CSV);';
1543
+ tblarray2 := tblarray2 || buffer2;
1544
+ ELSE buffer2 := 'COPY ' || quote_ident(source_schema) || '.' || quote_ident(tblname) || ' TO ''/tmp/cloneschema.tmp'' WITH DELIMITER AS '','';';
1545
+ tblarray2 := tblarray2 || buffer2;
1546
+ -- Issue #81 reformat COPY command for upload
1547
+ -- buffer2 := 'COPY ' || quote_ident(dest_schema) || '.' || quote_ident(tblname) || ' FROM ''/tmp/cloneschema.tmp'' (DELIMITER '','', NULL '''');';
1548
+ -- works--> COPY sample.timestamptbl2 FROM '/tmp/cloneschema.tmp' WITH (DELIMITER ',', NULL '\N', FORMAT CSV) ;
1549
+ buffer2 := 'COPY ' || quote_ident(dest_schema) || '.' || quote_ident(tblname) || ' FROM ''/tmp/cloneschema.tmp'' (DELIMITER '','', NULL ''\N'', FORMAT CSV);';
1550
+ tblarray2 := tblarray2 || buffer2;
1551
+ END IF;
1552
+ ELSE -- Issue#101: assume direct copy with text cast, add to separate array
1553
+ SELECT * INTO buffer3
1554
+ FROM public.get_insert_stmt_ddl(
1555
+ quote_ident(source_schema),
1556
+ quote_ident(dest_schema),
1557
+ quote_ident(tblname),
1558
+ True
1559
+ );
1560
+ tblarray3 := tblarray3 || buffer3;
1561
+ END IF;
1562
+ ELSE -- bypass child tables since we populate them when we populate the parents
1563
+ IF bDebug THEN RAISE NOTICE 'DEBUG: tblname=% bRelispart=NA relknd=% l_child=% bChild=%',
1564
+ tblname,
1565
+ relknd,
1566
+ l_child,
1567
+ bChild;
1568
+ END IF;
1569
+ IF NOT bChild THEN -- Issue#75: Must defer population of tables until child tables have been added to parents
1570
+ -- Issue#101 Offer alternative of copy to/from file. Although originally intended for tables with UDTs, it is now expanded to handle all cases for performance improvement perhaps for large tables.
1571
+ -- buffer2 := 'INSERT INTO ' || buffer || buffer3 || ' SELECT * FROM ' || quote_ident(source_schema) || '.' || quote_ident(tblname) || ';';
1572
+ buffer2 := 'INSERT INTO ' || buffer || ' SELECT * FROM ' || quote_ident(source_schema) || '.' || quote_ident(tblname) || ';';
1573
+ IF bDebug THEN RAISE NOTICE 'DEBUG: buffer2=%',
1574
+ buffer2;
1575
+ END IF;
1576
+ IF bFileCopy THEN tblarray2 := tblarray2 || buffer2;
1577
+ ELSE tblarray := tblarray || buffer2;
1578
+ END IF;
1579
+ END IF;
1580
+ END IF;
1581
+ END IF;
1582
+ -- Issue#61 FIX: use set_config for empty string
1583
+ -- SET search_path = '';
1584
+ SELECT set_config('search_path', '', false) into v_dummy;
1585
+ FOR column_,
1586
+ default_ IN
1587
+ SELECT column_name::text,
1588
+ REPLACE(
1589
+ column_default::text,
1590
+ quote_ident(source_schema) || '.',
1591
+ quote_ident(dest_schema) || '.'
1592
+ )
1593
+ FROM information_schema.COLUMNS
1594
+ WHERE table_schema = source_schema
1595
+ AND TABLE_NAME = tblname
1596
+ AND column_default LIKE 'nextval(%' || quote_ident(source_schema) || '%::regclass)' LOOP -- Issue#78 FIX: handle case-sensitive names with quote_ident() on column name
1597
+ buffer2 = 'ALTER TABLE ' || buffer || ' ALTER COLUMN ' || quote_ident(column_) || ' SET DEFAULT ' || default_ || ';';
1598
+ IF bDDLOnly THEN -- May need to come back and revisit this since previous sql will not return anything since no schema as created!
1599
+ RAISE INFO '%',
1600
+ buffer2;
1601
+ ELSE EXECUTE buffer2;
1602
+ END IF;
1603
+ END LOOP;
1604
+ EXECUTE 'SET search_path = ' || quote_ident(source_schema);
1605
+ END LOOP;
1606
+ END IF;
1607
+ -- end of 90600 branch
1608
+ RAISE NOTICE ' TABLES cloned: %',
1609
+ LPAD(cnt::text, 5, ' ');
1610
+ SELECT setting INTO v_dummy
1611
+ FROM pg_settings
1612
+ WHERE name = 'search_path';
1613
+ IF bDebug THEN RAISE NOTICE 'DEBUG: search_path=%',
1614
+ v_dummy;
1615
+ END IF;
1616
+ -- Assigning sequences to table columns.
1617
+ action := 'Sequences assigning';
1618
+ cnt := 0;
1619
+ FOR object IN
1620
+ SELECT sequence_name::text
1621
+ FROM information_schema.sequences
1622
+ WHERE sequence_schema = quote_ident(source_schema) LOOP cnt := cnt + 1;
1623
+ srctbl := quote_ident(source_schema) || '.' || quote_ident(object);
1624
+ -- Get owning column, inspired from Sadique Ali post at:
1625
+ -- https://sadique.io/blog/2019/05/07/viewing-sequence-ownership-information-in-postgres/
1626
+ -- Fixed via pull request#109
1627
+ SELECT ' OWNED BY ' || quote_ident(dest_schema) || '.' || quote_ident(dc.relname) || '.' || quote_ident(a.attname) INTO sq_owned
1628
+ FROM pg_class AS c
1629
+ JOIN pg_namespace n ON c.relnamespace = n.oid
1630
+ JOIN pg_depend AS d ON c.relfilenode = d.objid
1631
+ JOIN pg_class AS dc ON (
1632
+ d.refobjid = dc.relfilenode
1633
+ AND dc.relnamespace = n.oid
1634
+ )
1635
+ JOIN pg_attribute AS a ON (
1636
+ a.attnum = d.refobjsubid
1637
+ AND a.attrelid = d.refobjid
1638
+ )
1639
+ WHERE n.nspname = quote_ident(source_schema)
1640
+ AND c.relkind = 'S'
1641
+ AND c.relname = object;
1642
+ IF sq_owned IS NOT NULL THEN qry := 'ALTER SEQUENCE ' || quote_ident(dest_schema) || '.' || quote_ident(object) || sq_owned || ';';
1643
+ IF bDDLOnly THEN RAISE NOTICE 'DEBUG: %',
1644
+ qry;
1645
+ RAISE INFO '%',
1646
+ qry;
1647
+ ELSE EXECUTE qry;
1648
+ END IF;
1649
+ END IF;
1650
+ END LOOP;
1651
+ RAISE NOTICE ' SEQUENCES set: %',
1652
+ LPAD(cnt::text, 5, ' ');
1653
+ -- Update IDENTITY sequences to the last value, bypass 9.6 versions
1654
+ IF sq_server_version_num > 90624 THEN action := 'Identity updating';
1655
+ cnt := 0;
1656
+ FOR object,
1657
+ sq_last_value IN
1658
+ SELECT sequencename::text,
1659
+ COALESCE(last_value, -999)
1660
+ from pg_sequences
1661
+ where schemaname = quote_ident(source_schema)
1662
+ AND NOT EXISTS (
1663
+ select 1
1664
+ from information_schema.sequences
1665
+ where sequence_schema = quote_ident(source_schema)
1666
+ and sequence_name = sequencename
1667
+ ) LOOP IF sq_last_value = -999 THEN continue;
1668
+ END IF;
1669
+ cnt := cnt + 1;
1670
+ buffer := quote_ident(dest_schema) || '.' || quote_ident(object);
1671
+ IF bData THEN EXECUTE 'SELECT setval( ''' || buffer || ''', ' || sq_last_value || ', ' || sq_is_called || ');';
1672
+ ELSE if bDDLOnly THEN -- fix#63
1673
+ RAISE INFO '%',
1674
+ 'SELECT setval( ''' || buffer || ''', ' || sq_last_value || ', ' || sq_is_called || ');';
1675
+ ELSE -- fix#63
1676
+ EXECUTE 'SELECT setval( ''' || buffer || ''', ' || sq_last_value || ', ' || sq_is_called || ');';
1677
+ END IF;
1678
+ END IF;
1679
+ END LOOP;
1680
+ -- Fixed Issue#107: set lpad from 2 to 5
1681
+ RAISE NOTICE ' IDENTITIES set: %',
1682
+ LPAD(cnt::text, 5, ' ');
1683
+ ELSE -- Fixed Issue#107: set lpad from 2 to 5
1684
+ RAISE NOTICE ' IDENTITIES set: %',
1685
+ LPAD('-1'::text, 5, ' ');
1686
+ END IF;
1687
+ -- Issue#78 forces us to defer FKeys until the end since we previously did row copies before FKeys
1688
+ -- add FK constraint
1689
+ -- action := 'FK Constraints';
1690
+ -- Issue#62: Add comments on indexes, and then removed them from here and reworked later below.
1691
+ -- Issue 90: moved functions to here, before views or MVs that might use them
1692
+ -- Create functions
1693
+ action := 'Functions';
1694
+ cnt := 0;
1695
+ -- MJV FIX per issue# 34
1696
+ -- SET search_path = '';
1697
+ EXECUTE 'SET search_path = ' || quote_ident(source_schema);
1698
+ -- Fixed Issue#65
1699
+ -- Fixed Issue#97
1700
+ -- FOR func_oid IN SELECT oid FROM pg_proc WHERE pronamespace = src_oid AND prokind != 'a'
1701
+ IF is_prokind THEN FOR func_oid,
1702
+ func_owner,
1703
+ func_name,
1704
+ func_args,
1705
+ func_argno,
1706
+ buffer3 IN
1707
+ SELECT p.oid,
1708
+ pg_catalog.pg_get_userbyid(p.proowner),
1709
+ p.proname,
1710
+ oidvectortypes(p.proargtypes),
1711
+ p.pronargs,
1712
+ CASE
1713
+ WHEN prokind = 'p' THEN 'PROCEDURE'
1714
+ WHEN prokind = 'f' THEN 'FUNCTION'
1715
+ ELSE ''
1716
+ END
1717
+ FROM pg_proc p
1718
+ WHERE p.pronamespace = src_oid
1719
+ AND p.prokind != 'a' LOOP cnt := cnt + 1;
1720
+ SELECT pg_get_functiondef(func_oid) INTO qry;
1721
+ SELECT replace(
1722
+ qry,
1723
+ quote_ident(source_schema) || '.',
1724
+ quote_ident(dest_schema) || '.'
1725
+ ) INTO dest_qry;
1726
+ IF bDDLOnly THEN RAISE INFO '%;',
1727
+ dest_qry;
1728
+ -- Issue#91 Fix
1729
+ -- issue#95
1730
+ IF NOT bNoOwner THEN IF func_argno = 0 THEN -- Fixed Issue#108: double-quote roles in case they have special characters
1731
+ RAISE INFO 'ALTER % %() OWNER TO %',
1732
+ buffer3,
1733
+ quote_ident(dest_schema) || '.' || quote_ident(func_name),
1734
+ '"' || func_owner || '";';
1735
+ ELSE -- Fixed Issue#108: double-quote roles in case they have special characters
1736
+ RAISE INFO 'ALTER % % OWNER TO %',
1737
+ buffer3,
1738
+ quote_ident(dest_schema) || '.' || quote_ident(func_name) || '(' || func_args || ')',
1739
+ '"' || func_owner || '";';
1740
+ END IF;
1741
+ END IF;
1742
+ ELSE IF bDebug THEN RAISE NOTICE 'DEBUG: %',
1743
+ dest_qry;
1744
+ END IF;
1745
+ EXECUTE dest_qry;
1746
+ -- Issue#91 Fix
1747
+ -- issue#95
1748
+ IF NOT bNoOwner THEN IF func_argno = 0 THEN -- Fixed Issue#108: double-quote roles in case they have special characters
1749
+ dest_qry = 'ALTER ' || buffer3 || ' ' || quote_ident(dest_schema) || '.' || quote_ident(func_name) || '() OWNER TO ' || '"' || func_owner || '";';
1750
+ ELSE -- Fixed Issue#108: double-quote roles in case they have special characters
1751
+ dest_qry = 'ALTER ' || buffer3 || ' ' || quote_ident(dest_schema) || '.' || quote_ident(func_name) || '(' || func_args || ') OWNER TO ' || '"' || func_owner || '";';
1752
+ END IF;
1753
+ END IF;
1754
+ EXECUTE dest_qry;
1755
+ END IF;
1756
+ END LOOP;
1757
+ ELSE FOR func_oid IN
1758
+ SELECT oid
1759
+ FROM pg_proc
1760
+ WHERE pronamespace = src_oid
1761
+ AND not proisagg LOOP cnt := cnt + 1;
1762
+ SELECT pg_get_functiondef(func_oid) INTO qry;
1763
+ SELECT replace(
1764
+ qry,
1765
+ quote_ident(source_schema) || '.',
1766
+ quote_ident(dest_schema) || '.'
1767
+ ) INTO dest_qry;
1768
+ IF bDDLOnly THEN RAISE INFO '%;',
1769
+ dest_qry;
1770
+ ELSE EXECUTE dest_qry;
1771
+ END IF;
1772
+ END LOOP;
1773
+ END IF;
1774
+ -- Create aggregate functions.
1775
+ -- Fixed Issue#65
1776
+ -- FOR func_oid IN SELECT oid FROM pg_proc WHERE pronamespace = src_oid AND prokind = 'a'
1777
+ IF is_prokind THEN FOR func_oid IN
1778
+ SELECT oid
1779
+ FROM pg_proc
1780
+ WHERE pronamespace = src_oid
1781
+ AND prokind = 'a' LOOP cnt := cnt + 1;
1782
+ SELECT 'CREATE AGGREGATE ' || dest_schema || '.' || p.proname || '(' -- || format_type(a.aggtranstype, NULL)
1783
+ -- Issue#65 Fixes for specific datatype mappings
1784
+ || CASE
1785
+ WHEN format_type(a.aggtranstype, NULL) = 'double precision[]' THEN 'float8'
1786
+ WHEN format_type(a.aggtranstype, NULL) = 'anyarray' THEN 'anyelement'
1787
+ ELSE format_type(a.aggtranstype, NULL)
1788
+ END || ') (sfunc = ' || regexp_replace(
1789
+ a.aggtransfn::text,
1790
+ '(^|\W)' || quote_ident(source_schema) || '\.',
1791
+ '\1' || quote_ident(dest_schema) || '.'
1792
+ ) || ', stype = ' -- || format_type(a.aggtranstype, NULL)
1793
+ -- Issue#65 Fixes for specific datatype mappings
1794
+ || CASE
1795
+ WHEN format_type(a.aggtranstype, NULL) = 'double precision[]' THEN 'float8[]'
1796
+ ELSE format_type(a.aggtranstype, NULL)
1797
+ END || CASE
1798
+ WHEN op.oprname IS NULL THEN ''
1799
+ ELSE ', sortop = ' || op.oprname
1800
+ END || CASE
1801
+ WHEN a.agginitval IS NULL THEN ''
1802
+ ELSE ', initcond = ''' || a.agginitval || ''''
1803
+ END || ')' INTO dest_qry
1804
+ FROM pg_proc p
1805
+ JOIN pg_aggregate a ON a.aggfnoid = p.oid
1806
+ LEFT JOIN pg_operator op ON op.oid = a.aggsortop
1807
+ WHERE p.oid = func_oid;
1808
+ IF bDDLOnly THEN RAISE INFO '%;',
1809
+ dest_qry;
1810
+ ELSE EXECUTE dest_qry;
1811
+ END IF;
1812
+ END LOOP;
1813
+ RAISE NOTICE ' FUNCTIONS cloned: %',
1814
+ LPAD(cnt::text, 5, ' ');
1815
+ ELSE FOR func_oid IN
1816
+ SELECT oid
1817
+ FROM pg_proc
1818
+ WHERE pronamespace = src_oid
1819
+ AND proisagg LOOP cnt := cnt + 1;
1820
+ SELECT 'CREATE AGGREGATE ' || dest_schema || '.' || p.proname || '(' -- || format_type(a.aggtranstype, NULL)
1821
+ -- Issue#65 Fixes for specific datatype mappings
1822
+ || CASE
1823
+ WHEN format_type(a.aggtranstype, NULL) = 'double precision[]' THEN 'float8'
1824
+ WHEN format_type(a.aggtranstype, NULL) = 'anyarray' THEN 'anyelement'
1825
+ ELSE format_type(a.aggtranstype, NULL)
1826
+ END || ') (sfunc = ' || regexp_replace(
1827
+ a.aggtransfn::text,
1828
+ '(^|\W)' || quote_ident(source_schema) || '\.',
1829
+ '\1' || quote_ident(dest_schema) || '.'
1830
+ ) || ', stype = ' -- || format_type(a.aggtranstype, NULL)
1831
+ -- Issue#65 Fixes for specific datatype mappings
1832
+ || CASE
1833
+ WHEN format_type(a.aggtranstype, NULL) = 'double precision[]' THEN 'float8[]'
1834
+ ELSE format_type(a.aggtranstype, NULL)
1835
+ END || CASE
1836
+ WHEN op.oprname IS NULL THEN ''
1837
+ ELSE ', sortop = ' || op.oprname
1838
+ END || CASE
1839
+ WHEN a.agginitval IS NULL THEN ''
1840
+ ELSE ', initcond = ''' || a.agginitval || ''''
1841
+ END || ')' INTO dest_qry
1842
+ FROM pg_proc p
1843
+ JOIN pg_aggregate a ON a.aggfnoid = p.oid
1844
+ LEFT JOIN pg_operator op ON op.oid = a.aggsortop
1845
+ WHERE p.oid = func_oid;
1846
+ IF bDDLOnly THEN RAISE INFO '%;',
1847
+ dest_qry;
1848
+ ELSE EXECUTE dest_qry;
1849
+ END IF;
1850
+ END LOOP;
1851
+ RAISE NOTICE ' FUNCTIONS cloned: %',
1852
+ LPAD(cnt::text, 5, ' ');
1853
+ END IF;
1854
+ -- Create views
1855
+ action := 'Views';
1856
+ -- Issue#61 FIX: use set_config for empty string
1857
+ -- MJV FIX #43: also had to reset search_path from source schema to empty.
1858
+ -- SET search_path = '';
1859
+ SELECT set_config('search_path', '', false) INTO v_dummy;
1860
+ cnt := 0;
1861
+ --FOR object IN
1862
+ -- SELECT table_name::text, view_definition
1863
+ -- FROM information_schema.views
1864
+ -- WHERE table_schema = quote_ident(source_schema)
1865
+ -- Issue#73 replace loop query to handle dependencies
1866
+ -- Issue#91 get view_owner
1867
+ FOR srctbl,
1868
+ aname,
1869
+ view_owner,
1870
+ object IN WITH RECURSIVE views AS (
1871
+ SELECT n.nspname as schemaname,
1872
+ v.relname as tablename,
1873
+ v.oid::regclass AS viewname,
1874
+ v.relkind = 'm' AS is_materialized,
1875
+ pg_catalog.pg_get_userbyid(v.relowner) as owner,
1876
+ 1 AS level
1877
+ FROM pg_depend AS d
1878
+ JOIN pg_rewrite AS r ON r.oid = d.objid
1879
+ JOIN pg_class AS v ON v.oid = r.ev_class
1880
+ JOIN pg_namespace n ON n.oid = v.relnamespace -- WHERE v.relkind IN ('v', 'm')
1881
+ WHERE v.relkind IN ('v')
1882
+ AND d.classid = 'pg_rewrite'::regclass
1883
+ AND d.refclassid = 'pg_class'::regclass
1884
+ AND d.deptype = 'n'
1885
+ UNION
1886
+ -- add the views that depend on these
1887
+ SELECT n.nspname as schemaname,
1888
+ v.relname as tablename,
1889
+ v.oid::regclass AS viewname,
1890
+ v.relkind = 'm',
1891
+ pg_catalog.pg_get_userbyid(v.relowner) as owner,
1892
+ views.level + 1
1893
+ FROM views
1894
+ JOIN pg_depend AS d ON d.refobjid = views.viewname
1895
+ JOIN pg_rewrite AS r ON r.oid = d.objid
1896
+ JOIN pg_class AS v ON v.oid = r.ev_class
1897
+ JOIN pg_namespace n ON n.oid = v.relnamespace -- WHERE v.relkind IN ('v', 'm')
1898
+ WHERE v.relkind IN ('v')
1899
+ AND d.classid = 'pg_rewrite'::regclass
1900
+ AND d.refclassid = 'pg_class'::regclass
1901
+ AND d.deptype = 'n'
1902
+ AND v.oid <> views.viewname
1903
+ )
1904
+ SELECT tablename,
1905
+ viewname,
1906
+ owner,
1907
+ format(
1908
+ 'CREATE OR REPLACE%s VIEW %s AS%s',
1909
+ CASE
1910
+ WHEN is_materialized THEN ' MATERIALIZED'
1911
+ ELSE ''
1912
+ END,
1913
+ viewname,
1914
+ pg_get_viewdef(viewname)
1915
+ )
1916
+ FROM views
1917
+ WHERE schemaname = quote_ident(source_schema)
1918
+ GROUP BY schemaname,
1919
+ tablename,
1920
+ viewname,
1921
+ owner,
1922
+ is_materialized
1923
+ ORDER BY max(level),
1924
+ schemaname,
1925
+ tablename LOOP cnt := cnt + 1;
1926
+ -- Issue#73 replace logic based on new loop sql
1927
+ buffer := quote_ident(dest_schema) || '.' || quote_ident(aname);
1928
+ -- MJV FIX: #43
1929
+ -- SELECT view_definition INTO v_def
1930
+ -- SELECT REPLACE(view_definition, quote_ident(source_schema) || '.', quote_ident(dest_schema) || '.') INTO v_def
1931
+ -- FROM information_schema.views
1932
+ -- WHERE table_schema = quote_ident(source_schema)
1933
+ -- AND table_name = quote_ident(object);
1934
+ SELECT REPLACE(
1935
+ object,
1936
+ quote_ident(source_schema) || '.',
1937
+ quote_ident(dest_schema) || '.'
1938
+ ) INTO v_def;
1939
+ -- NOTE: definition already includes the closing statement semicolon
1940
+ SELECT REPLACE(
1941
+ aname,
1942
+ quote_ident(source_schema) || '.',
1943
+ quote_ident(dest_schema) || '.'
1944
+ ) INTO buffer3;
1945
+ IF bDDLOnly THEN RAISE INFO '%',
1946
+ v_def;
1947
+ -- Issue#91 Fix
1948
+ -- issue#95
1949
+ IF NOT bNoOwner THEN -- Fixed Issue#108: double-quote roles in case they have special characters
1950
+ -- RAISE INFO 'ALTER TABLE % OWNER TO %', buffer3, view_owner || ';';
1951
+ RAISE INFO 'ALTER TABLE % OWNER TO %',
1952
+ buffer3,
1953
+ '"' || view_owner || '";';
1954
+ END IF;
1955
+ ELSE -- EXECUTE 'CREATE OR REPLACE VIEW ' || buffer || ' AS ' || v_def;
1956
+ EXECUTE v_def;
1957
+ -- Issue#73: commented out comment logic for views since we do it elsewhere now.
1958
+ -- Issue#91 Fix
1959
+ -- issue#95
1960
+ IF NOT bNoOwner THEN -- Fixed Issue#108: double-quote roles in case they have special characters
1961
+ v_def = 'ALTER TABLE ' || buffer3 || ' OWNER TO ' || '"' || view_owner || '";';
1962
+ EXECUTE v_def;
1963
+ END IF;
1964
+ END IF;
1965
+ END LOOP;
1966
+ RAISE NOTICE ' VIEWS cloned: %',
1967
+ LPAD(cnt::text, 5, ' ');
1968
+ -- Create Materialized views
1969
+ action := 'Mat. Views';
1970
+ cnt := 0;
1971
+ -- Issue#91 get view_owner
1972
+ FOR object,
1973
+ view_owner,
1974
+ v_def IN
1975
+ SELECT matviewname::text,
1976
+ '"' || matviewowner::text || '"',
1977
+ replace(definition, ';', '')
1978
+ FROM pg_catalog.pg_matviews
1979
+ WHERE schemaname = quote_ident(source_schema) LOOP cnt := cnt + 1;
1980
+ -- Issue#78 FIX: handle case-sensitive names with quote_ident() on target schema and object
1981
+ buffer := quote_ident(dest_schema) || '.' || quote_ident(object);
1982
+ -- MJV FIX: #72 remove source schema in MV def
1983
+ SELECT REPLACE(
1984
+ v_def,
1985
+ quote_ident(source_schema) || '.',
1986
+ quote_ident(dest_schema) || '.'
1987
+ ) INTO buffer2;
1988
+ IF bData THEN -- issue#98 defer creation until after regular tables are populated. Also defer the ownership as well.
1989
+ -- EXECUTE 'CREATE MATERIALIZED VIEW ' || buffer || ' AS ' || buffer2 || ' WITH DATA;' ;
1990
+ buffer3 = 'CREATE MATERIALIZED VIEW ' || buffer || ' AS ' || buffer2 || ' WITH DATA;';
1991
+ mvarray := mvarray || buffer3;
1992
+ -- issue#95
1993
+ IF NOT bNoOwner THEN -- buffer3 = 'ALTER MATERIALIZED VIEW ' || buffer || ' OWNER TO ' || view_owner || ';' ;
1994
+ -- EXECUTE buffer3;
1995
+ -- Fixed Issue#108: double-quote roles in case they have special characters
1996
+ buffer3 = 'ALTER MATERIALIZED VIEW ' || buffer || ' OWNER TO ' || view_owner || ';';
1997
+ mvarray := mvarray || buffer3;
1998
+ END IF;
1999
+ ELSE IF bDDLOnly THEN RAISE INFO '%',
2000
+ 'CREATE MATERIALIZED VIEW ' || buffer || ' AS ' || buffer2 || ' WITH NO DATA;';
2001
+ -- Issue#91
2002
+ -- issue#95
2003
+ IF NOT bNoOwner THEN -- Fixed Issue#108: double-quote roles in case they have special characters
2004
+ RAISE INFO '%',
2005
+ 'ALTER MATERIALIZED VIEW ' || buffer || ' OWNER TO ' || view_owner || ';';
2006
+ END IF;
2007
+ ELSE EXECUTE 'CREATE MATERIALIZED VIEW ' || buffer || ' AS ' || buffer2 || ' WITH NO DATA;';
2008
+ -- Issue#91
2009
+ -- issue#95
2010
+ IF NOT bNoOwner THEN -- Fixed Issue#108: double-quote roles in case they have special characters
2011
+ buffer3 = 'ALTER MATERIALIZED VIEW ' || buffer || ' OWNER TO ' || view_owner || ';';
2012
+ EXECUTE buffer3;
2013
+ END IF;
2014
+ END IF;
2015
+ END IF;
2016
+ SELECT coalesce(obj_description(oid), '') into adef
2017
+ from pg_class
2018
+ where relkind = 'm'
2019
+ and relname = object;
2020
+ IF adef <> '' THEN IF bDDLOnly THEN RAISE INFO '%',
2021
+ 'COMMENT ON MATERIALIZED VIEW ' || quote_ident(dest_schema) || '.' || object || ' IS ''' || adef || ''';';
2022
+ ELSE -- Issue#$98: also defer if copy rows is on since we defer MVIEWS in that case
2023
+ IF bData THEN buffer3 = 'COMMENT ON MATERIALIZED VIEW ' || quote_ident(dest_schema) || '.' || object || ' IS ''' || adef || ''';';
2024
+ mvarray = mvarray || buffer3;
2025
+ ELSE EXECUTE 'COMMENT ON MATERIALIZED VIEW ' || quote_ident(dest_schema) || '.' || object || ' IS ''' || adef || ''';';
2026
+ END IF;
2027
+ END IF;
2028
+ END IF;
2029
+ FOR aname,
2030
+ adef IN
2031
+ SELECT indexname,
2032
+ replace(
2033
+ indexdef,
2034
+ quote_ident(source_schema) || '.',
2035
+ quote_ident(dest_schema) || '.'
2036
+ ) as newdef
2037
+ FROM pg_indexes
2038
+ where schemaname = quote_ident(source_schema)
2039
+ and tablename = object
2040
+ order by indexname LOOP IF bDDLOnly THEN RAISE INFO '%',
2041
+ adef || ';';
2042
+ ELSE EXECUTE adef || ';';
2043
+ END IF;
2044
+ END LOOP;
2045
+ END LOOP;
2046
+ RAISE NOTICE ' MAT VIEWS cloned: %',
2047
+ LPAD(cnt::text, 5, ' ');
2048
+ -- Issue 90 Move create functions to before views
2049
+ -- MV: Create Triggers
2050
+ -- MJV FIX: #38
2051
+ -- EXECUTE 'SET search_path = ' || quote_ident(source_schema) ;
2052
+ -- Issue#61 FIX: use set_config for empty string
2053
+ -- SET search_path = '';
2054
+ SELECT set_config('search_path', '', false) into v_dummy;
2055
+ action := 'Triggers';
2056
+ cnt := 0;
2057
+ FOR arec IN -- 2021-03-09 MJV FIX: #40 fixed sql to get the def using pg_get_triggerdef() sql
2058
+ SELECT n.nspname,
2059
+ c.relname,
2060
+ t.tgname,
2061
+ p.proname,
2062
+ REPLACE(
2063
+ pg_get_triggerdef(t.oid),
2064
+ quote_ident(source_schema),
2065
+ quote_ident(dest_schema)
2066
+ ) || ';' AS trig_ddl
2067
+ FROM pg_trigger t,
2068
+ pg_class c,
2069
+ pg_namespace n,
2070
+ pg_proc p
2071
+ WHERE n.nspname = quote_ident(source_schema)
2072
+ AND n.oid = c.relnamespace
2073
+ AND c.relkind in ('r', 'p')
2074
+ AND n.oid = p.pronamespace
2075
+ AND c.oid = t.tgrelid
2076
+ AND p.oid = t.tgfoid
2077
+ ORDER BY c.relname,
2078
+ t.tgname LOOP BEGIN cnt := cnt + 1;
2079
+ IF bDDLOnly THEN RAISE INFO '%',
2080
+ arec.trig_ddl;
2081
+ ELSE EXECUTE arec.trig_ddl;
2082
+ END IF;
2083
+ END;
2084
+ END LOOP;
2085
+ RAISE NOTICE ' TRIGGERS cloned: %',
2086
+ LPAD(cnt::text, 5, ' ');
2087
+ -- MV: Create Rules
2088
+ -- Fixes Issue#59 Implement Rules
2089
+ action := 'Rules';
2090
+ cnt := 0;
2091
+ FOR arec IN
2092
+ SELECT regexp_replace(definition, E'[\\n\\r]+', ' ', 'g') as definition
2093
+ FROM pg_rules
2094
+ WHERE schemaname = quote_ident(source_schema) LOOP cnt := cnt + 1;
2095
+ buffer := REPLACE(
2096
+ arec.definition,
2097
+ quote_ident(source_schema) || '.',
2098
+ quote_ident(dest_schema) || '.'
2099
+ );
2100
+ IF bDDLOnly THEN RAISE INFO '%',
2101
+ buffer;
2102
+ ELSE EXECUTE buffer;
2103
+ END IF;
2104
+ END LOOP;
2105
+ RAISE NOTICE ' RULES cloned: %',
2106
+ LPAD(cnt::text, 5, ' ');
2107
+ -- MV: Create Policies
2108
+ -- Fixes Issue#66 Implement Security policies for RLS
2109
+ action := 'Policies';
2110
+ cnt := 0;
2111
+ -- #106 Handle 9.6 which doesn't have "permissive"
2112
+ IF sq_server_version_num > 90624 THEN FOR arec IN -- Issue#78 FIX: handle case-sensitive names with quote_ident() on policy, tablename
2113
+ SELECT schemaname as schemaname,
2114
+ tablename as tablename,
2115
+ 'CREATE POLICY ' || policyname || ' ON ' || quote_ident(dest_schema) || '.' || quote_ident(tablename) || ' AS ' || permissive || ' FOR ' || cmd || ' TO ' || array_to_string(roles, ',', '*') || ' USING (' || regexp_replace(qual, E'[\\n\\r]+', ' ', 'g') || ')' || CASE
2116
+ WHEN with_check IS NOT NULL THEN ' WITH CHECK ('
2117
+ ELSE ''
2118
+ END || coalesce(with_check, '') || CASE
2119
+ WHEN with_check IS NOT NULL THEN ');'
2120
+ ELSE ';'
2121
+ END as definition
2122
+ FROM pg_policies
2123
+ WHERE schemaname = quote_ident(source_schema)
2124
+ ORDER BY policyname LOOP cnt := cnt + 1;
2125
+ IF bDDLOnly THEN RAISE INFO '%',
2126
+ arec.definition;
2127
+ ELSE EXECUTE arec.definition;
2128
+ END IF;
2129
+ -- Issue#76: Enable row security if indicated
2130
+ SELECT c.relrowsecurity INTO abool
2131
+ FROM pg_class c,
2132
+ pg_namespace n
2133
+ where n.nspname = quote_ident(arec.schemaname)
2134
+ AND n.oid = c.relnamespace
2135
+ AND c.relname = quote_ident(arec.tablename)
2136
+ and c.relkind = 'r';
2137
+ IF abool THEN buffer = 'ALTER TABLE ' || dest_schema || '.' || arec.tablename || ' ENABLE ROW LEVEL SECURITY;';
2138
+ IF bDDLOnly THEN RAISE INFO '%',
2139
+ buffer;
2140
+ ELSE EXECUTE buffer;
2141
+ END IF;
2142
+ END IF;
2143
+ END LOOP;
2144
+ ELSE -- handle 9.6 versions
2145
+ FOR arec IN -- Issue#78 FIX: handle case-sensitive names with quote_ident() on policy, tablename
2146
+ SELECT schemaname as schemaname,
2147
+ tablename as tablename,
2148
+ 'CREATE POLICY ' || policyname || ' ON ' || quote_ident(dest_schema) || '.' || quote_ident(tablename) || ' FOR ' || cmd || ' TO ' || array_to_string(roles, ',', '*') || ' USING (' || regexp_replace(qual, E'[\\n\\r]+', ' ', 'g') || ')' || CASE
2149
+ WHEN with_check IS NOT NULL THEN ' WITH CHECK ('
2150
+ ELSE ''
2151
+ END || coalesce(with_check, '') || CASE
2152
+ WHEN with_check IS NOT NULL THEN ');'
2153
+ ELSE ';'
2154
+ END as definition
2155
+ FROM pg_policies
2156
+ WHERE schemaname = quote_ident(source_schema)
2157
+ ORDER BY policyname LOOP cnt := cnt + 1;
2158
+ IF bDDLOnly THEN RAISE INFO '%',
2159
+ arec.definition;
2160
+ ELSE EXECUTE arec.definition;
2161
+ END IF;
2162
+ -- Issue#76: Enable row security if indicated
2163
+ SELECT c.relrowsecurity INTO abool
2164
+ FROM pg_class c,
2165
+ pg_namespace n
2166
+ where n.nspname = quote_ident(arec.schemaname)
2167
+ AND n.oid = c.relnamespace
2168
+ AND c.relname = quote_ident(arec.tablename)
2169
+ and c.relkind = 'r';
2170
+ IF abool THEN buffer = 'ALTER TABLE ' || dest_schema || '.' || arec.tablename || ' ENABLE ROW LEVEL SECURITY;';
2171
+ IF bDDLOnly THEN RAISE INFO '%',
2172
+ buffer;
2173
+ ELSE EXECUTE buffer;
2174
+ END IF;
2175
+ END IF;
2176
+ END LOOP;
2177
+ END IF;
2178
+ RAISE NOTICE ' POLICIES cloned: %',
2179
+ LPAD(cnt::text, 5, ' ');
2180
+ -- MJV Fixed #62 for comments (PASS 1)
2181
+ action := 'Comments1';
2182
+ cnt := 0;
2183
+ FOR qry IN -- Issue#74 Fix: Change schema from source to target. Also, do not include comments on foreign tables since we do not clone foreign tables at this time.
2184
+ SELECT 'COMMENT ON ' || CASE
2185
+ WHEN c.relkind in ('r', 'p')
2186
+ AND a.attname IS NULL THEN 'TABLE '
2187
+ WHEN c.relkind in ('r', 'p')
2188
+ AND a.attname IS NOT NULL THEN 'COLUMN '
2189
+ WHEN c.relkind = 'f' THEN 'FOREIGN TABLE '
2190
+ WHEN c.relkind = 'm' THEN 'MATERIALIZED VIEW '
2191
+ WHEN c.relkind = 'v' THEN 'VIEW '
2192
+ WHEN c.relkind = 'i' THEN 'INDEX '
2193
+ WHEN c.relkind = 'S' THEN 'SEQUENCE '
2194
+ ELSE 'XX'
2195
+ END || quote_ident(dest_schema) || '.' || CASE
2196
+ WHEN c.relkind in ('r', 'p')
2197
+ AND -- Issue#78: handle case-sensitive names with quote_ident()
2198
+ a.attname IS NOT NULL THEN quote_ident(c.relname) || '.' || a.attname
2199
+ ELSE quote_ident(c.relname)
2200
+ END || -- Issue#74 Fix
2201
+ -- ' IS ''' || d.description || ''';' as ddl
2202
+ ' IS ' || quote_literal(d.description) || ';' as ddl
2203
+ FROM pg_class c
2204
+ JOIN pg_namespace n ON (n.oid = c.relnamespace)
2205
+ LEFT JOIN pg_description d ON (c.oid = d.objoid)
2206
+ LEFT JOIN pg_attribute a ON (
2207
+ c.oid = a.attrelid
2208
+ AND a.attnum > 0
2209
+ and a.attnum = d.objsubid
2210
+ )
2211
+ WHERE c.relkind <> 'f'
2212
+ AND d.description IS NOT NULL
2213
+ AND n.nspname = quote_ident(source_schema)
2214
+ ORDER BY ddl LOOP cnt := cnt + 1;
2215
+ -- BAD : "COMMENT ON SEQUENCE sample_clone2.CaseSensitive_ID_seq IS 'just a comment on CaseSensitive sequence';"
2216
+ -- GOOD: "COMMENT ON SEQUENCE "CaseSensitive_ID_seq" IS 'just a comment on CaseSensitive sequence';"
2217
+ -- Issue#98 For MVs we create comments when we create the MVs
2218
+ IF substring(qry, 1, 28) = 'COMMENT ON MATERIALIZED VIEW' THEN IF bDebug THEN RAISE NOTICE 'DEBUG: deferring comments on MVs';
2219
+ END IF;
2220
+ cnt = cnt - 1;
2221
+ continue;
2222
+ END IF;
2223
+ IF bDDLOnly THEN RAISE INFO '%',
2224
+ qry;
2225
+ ELSE EXECUTE qry;
2226
+ END IF;
2227
+ END LOOP;
2228
+ RAISE NOTICE ' COMMENTS(1) cloned: %',
2229
+ LPAD(cnt::text, 5, ' ');
2230
+ -- MJV Fixed #62 for comments (PASS 2)
2231
+ action := 'Comments2';
2232
+ cnt2 := 0;
2233
+ IF is_prokind THEN FOR qry IN -- Issue#74 Fix: Change schema from source to target.
2234
+ SELECT 'COMMENT ON SCHEMA ' || dest_schema || -- Issue#74 Fix
2235
+ -- ' IS ''' || d.description || ''';' as ddl
2236
+ ' IS ' || quote_literal(d.description) || ';' as ddl
2237
+ from pg_namespace n,
2238
+ pg_description d
2239
+ where d.objoid = n.oid
2240
+ and n.nspname = quote_ident(source_schema)
2241
+ UNION
2242
+ -- Issue#74 Fix: need to replace source schema inline
2243
+ -- SELECT 'COMMENT ON TYPE ' || pg_catalog.format_type(t.oid, NULL) || ' IS ''' || pg_catalog.obj_description(t.oid, 'pg_type') || ''';' as ddl
2244
+ SELECT 'COMMENT ON TYPE ' || REPLACE(
2245
+ pg_catalog.format_type(t.oid, NULL),
2246
+ quote_ident(source_schema),
2247
+ quote_ident(dest_schema)
2248
+ ) || ' IS ''' || pg_catalog.obj_description(t.oid, 'pg_type') || ''';' as ddl
2249
+ FROM pg_catalog.pg_type t
2250
+ JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace
2251
+ WHERE (
2252
+ t.typrelid = 0
2253
+ OR (
2254
+ SELECT c.relkind = 'c'
2255
+ FROM pg_catalog.pg_class c
2256
+ WHERE c.oid = t.typrelid
2257
+ )
2258
+ )
2259
+ AND NOT EXISTS(
2260
+ SELECT 1
2261
+ FROM pg_catalog.pg_type el
2262
+ WHERE el.oid = t.typelem
2263
+ AND el.typarray = t.oid
2264
+ )
2265
+ AND n.nspname = quote_ident(source_schema) COLLATE pg_catalog.default
2266
+ AND pg_catalog.obj_description(t.oid, 'pg_type') IS NOT NULL
2267
+ and t.typtype = 'c'
2268
+ UNION
2269
+ -- Issue#78: handle case-sensitive names with quote_ident()
2270
+ SELECT 'COMMENT ON COLLATION ' || quote_ident(dest_schema) || '.' || quote_ident(c.collname) || ' IS ''' || pg_catalog.obj_description(c.oid, 'pg_collation') || ''';' as ddl
2271
+ FROM pg_catalog.pg_collation c,
2272
+ pg_catalog.pg_namespace n
2273
+ WHERE n.oid = c.collnamespace
2274
+ AND c.collencoding IN (
2275
+ -1,
2276
+ pg_catalog.pg_char_to_encoding(pg_catalog.getdatabaseencoding())
2277
+ )
2278
+ AND n.nspname = quote_ident(source_schema) COLLATE pg_catalog.default
2279
+ AND pg_catalog.obj_description(c.oid, 'pg_collation') IS NOT NULL
2280
+ UNION
2281
+ SELECT 'COMMENT ON ' || CASE
2282
+ WHEN p.prokind = 'f' THEN 'FUNCTION '
2283
+ WHEN p.prokind = 'p' THEN 'PROCEDURE '
2284
+ WHEN p.prokind = 'a' THEN 'AGGREGATE '
2285
+ END || dest_schema || '.' || p.proname || ' (' || oidvectortypes(p.proargtypes) || ')' -- Issue#74 Fix
2286
+ -- ' IS ''' || d.description || ''';' as ddl
2287
+ ' IS ' || quote_literal(d.description) || ';' as ddl
2288
+ FROM pg_catalog.pg_namespace n
2289
+ JOIN pg_catalog.pg_proc p ON p.pronamespace = n.oid
2290
+ JOIN pg_description d ON (d.objoid = p.oid)
2291
+ WHERE n.nspname = quote_ident(source_schema)
2292
+ UNION
2293
+ SELECT 'COMMENT ON POLICY ' || p1.policyname || ' ON ' || dest_schema || '.' || p1.tablename || -- Issue#74 Fix
2294
+ -- ' IS ''' || d.description || ''';' as ddl
2295
+ ' IS ' || quote_literal(d.description) || ';' as ddl
2296
+ FROM pg_policies p1,
2297
+ pg_policy p2,
2298
+ pg_class c,
2299
+ pg_namespace n,
2300
+ pg_description d
2301
+ WHERE p1.schemaname = n.nspname
2302
+ AND p1.tablename = c.relname
2303
+ AND n.oid = c.relnamespace
2304
+ AND c.relkind in ('r', 'p')
2305
+ AND p1.policyname = p2.polname
2306
+ AND d.objoid = p2.oid
2307
+ AND p1.schemaname = quote_ident(source_schema)
2308
+ UNION
2309
+ SELECT 'COMMENT ON DOMAIN ' || dest_schema || '.' || t.typname || -- Issue#74 Fix
2310
+ -- ' IS ''' || d.description || ''';' as ddl
2311
+ ' IS ' || quote_literal(d.description) || ';' as ddl
2312
+ FROM pg_catalog.pg_type t
2313
+ LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace
2314
+ JOIN pg_catalog.pg_description d ON d.classoid = t.tableoid
2315
+ AND d.objoid = t.oid
2316
+ AND d.objsubid = 0
2317
+ WHERE t.typtype = 'd'
2318
+ AND n.nspname = quote_ident(source_schema) COLLATE pg_catalog.default
2319
+ ORDER BY 1 LOOP cnt2 := cnt2 + 1;
2320
+ IF bDDLOnly THEN RAISE INFO '%',
2321
+ qry;
2322
+ ELSE EXECUTE qry;
2323
+ END IF;
2324
+ END LOOP;
2325
+ ELSE -- must be v 10 or less
2326
+ FOR qry IN -- Issue#74 Fix: Change schema from source to target.
2327
+ SELECT 'COMMENT ON SCHEMA ' || dest_schema || -- Issue#74 Fix
2328
+ -- ' IS ''' || d.description || ''';' as ddl
2329
+ ' IS ' || quote_literal(d.description) || ';' as ddl
2330
+ from pg_namespace n,
2331
+ pg_description d
2332
+ where d.objoid = n.oid
2333
+ and n.nspname = quote_ident(source_schema)
2334
+ UNION
2335
+ -- Issue#74 Fix: need to replace source schema inline
2336
+ -- SELECT 'COMMENT ON TYPE ' || pg_catalog.format_type(t.oid, NULL) || ' IS ''' || pg_catalog.obj_description(t.oid, 'pg_type') || ''';' as ddl
2337
+ SELECT 'COMMENT ON TYPE ' || REPLACE(
2338
+ pg_catalog.format_type(t.oid, NULL),
2339
+ quote_ident(source_schema),
2340
+ quote_ident(dest_schema)
2341
+ ) || ' IS ''' || pg_catalog.obj_description(t.oid, 'pg_type') || ''';' as ddl
2342
+ FROM pg_catalog.pg_type t
2343
+ JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace
2344
+ WHERE (
2345
+ t.typrelid = 0
2346
+ OR (
2347
+ SELECT c.relkind = 'c'
2348
+ FROM pg_catalog.pg_class c
2349
+ WHERE c.oid = t.typrelid
2350
+ )
2351
+ )
2352
+ AND NOT EXISTS(
2353
+ SELECT 1
2354
+ FROM pg_catalog.pg_type el
2355
+ WHERE el.oid = t.typelem
2356
+ AND el.typarray = t.oid
2357
+ )
2358
+ AND n.nspname = quote_ident(source_schema) COLLATE pg_catalog.default
2359
+ AND pg_catalog.obj_description(t.oid, 'pg_type') IS NOT NULL
2360
+ and t.typtype = 'c'
2361
+ UNION
2362
+ -- FIX Isse#87 by adding double quotes around collation name
2363
+ SELECT 'COMMENT ON COLLATION ' || dest_schema || '."' || c.collname || '" IS ''' || pg_catalog.obj_description(c.oid, 'pg_collation') || ''';' as ddl
2364
+ FROM pg_catalog.pg_collation c,
2365
+ pg_catalog.pg_namespace n
2366
+ WHERE n.oid = c.collnamespace
2367
+ AND c.collencoding IN (
2368
+ -1,
2369
+ pg_catalog.pg_char_to_encoding(pg_catalog.getdatabaseencoding())
2370
+ )
2371
+ AND n.nspname = quote_ident(source_schema) COLLATE pg_catalog.default
2372
+ AND pg_catalog.obj_description(c.oid, 'pg_collation') IS NOT NULL
2373
+ UNION
2374
+ SELECT 'COMMENT ON ' || CASE
2375
+ WHEN proisagg THEN 'AGGREGATE '
2376
+ ELSE 'FUNCTION '
2377
+ END || dest_schema || '.' || p.proname || ' (' || oidvectortypes(p.proargtypes) || ')' -- Issue#74 Fix
2378
+ -- ' IS ''' || d.description || ''';' as ddl
2379
+ ' IS ' || quote_literal(d.description) || ';' as ddl
2380
+ FROM pg_catalog.pg_namespace n
2381
+ JOIN pg_catalog.pg_proc p ON p.pronamespace = n.oid
2382
+ JOIN pg_description d ON (d.objoid = p.oid)
2383
+ WHERE n.nspname = quote_ident(source_schema)
2384
+ UNION
2385
+ SELECT 'COMMENT ON POLICY ' || p1.policyname || ' ON ' || dest_schema || '.' || p1.tablename || -- Issue#74 Fix
2386
+ -- ' IS ''' || d.description || ''';' as ddl
2387
+ ' IS ' || quote_literal(d.description) || ';' as ddl
2388
+ FROM pg_policies p1,
2389
+ pg_policy p2,
2390
+ pg_class c,
2391
+ pg_namespace n,
2392
+ pg_description d
2393
+ WHERE p1.schemaname = n.nspname
2394
+ AND p1.tablename = c.relname
2395
+ AND n.oid = c.relnamespace
2396
+ AND c.relkind in ('r', 'p')
2397
+ AND p1.policyname = p2.polname
2398
+ AND d.objoid = p2.oid
2399
+ AND p1.schemaname = quote_ident(source_schema)
2400
+ UNION
2401
+ SELECT 'COMMENT ON DOMAIN ' || dest_schema || '.' || t.typname || -- Issue#74 Fix
2402
+ -- ' IS ''' || d.description || ''';' as ddl
2403
+ ' IS ' || quote_literal(d.description) || ';' as ddl
2404
+ FROM pg_catalog.pg_type t
2405
+ LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace
2406
+ JOIN pg_catalog.pg_description d ON d.classoid = t.tableoid
2407
+ AND d.objoid = t.oid
2408
+ AND d.objsubid = 0
2409
+ WHERE t.typtype = 'd'
2410
+ AND n.nspname = quote_ident(source_schema) COLLATE pg_catalog.default
2411
+ ORDER BY 1 LOOP cnt2 := cnt2 + 1;
2412
+ IF bDDLOnly THEN RAISE INFO '%',
2413
+ qry;
2414
+ ELSE EXECUTE qry;
2415
+ END IF;
2416
+ END LOOP;
2417
+ END IF;
2418
+ RAISE NOTICE ' COMMENTS(2) cloned: %',
2419
+ LPAD(cnt2::text, 5, ' ');
2420
+ -- Issue#95 bypass if No ACL specified.
2421
+ IF NOT bNoACL THEN -- ---------------------
2422
+ -- MV: Permissions: Defaults
2423
+ -- ---------------------
2424
+ EXECUTE 'SET search_path = ' || quote_ident(source_schema);
2425
+ action := 'PRIVS: Defaults';
2426
+ cnt := 0;
2427
+ FOR arec IN
2428
+ SELECT pg_catalog.pg_get_userbyid(d.defaclrole) AS "owner",
2429
+ n.nspname AS schema,
2430
+ CASE
2431
+ d.defaclobjtype
2432
+ WHEN 'r' THEN 'table'
2433
+ WHEN 'S' THEN 'sequence'
2434
+ WHEN 'f' THEN 'function'
2435
+ WHEN 'T' THEN 'type'
2436
+ WHEN 'n' THEN 'schema'
2437
+ END AS atype,
2438
+ d.defaclacl as defaclacl,
2439
+ pg_catalog.array_to_string(d.defaclacl, ',') as defaclstr
2440
+ FROM pg_catalog.pg_default_acl d
2441
+ LEFT JOIN pg_catalog.pg_namespace n ON (n.oid = d.defaclnamespace)
2442
+ WHERE n.nspname IS NOT NULL
2443
+ AND n.nspname = quote_ident(source_schema)
2444
+ ORDER BY 3,
2445
+ 2,
2446
+ 1 LOOP BEGIN -- RAISE NOTICE ' owner=% type=% defaclacl=% defaclstr=%', arec.owner, arec.atype, arec.defaclacl, arec.defaclstr;
2447
+ FOREACH aclstr IN ARRAY arec.defaclacl LOOP cnt := cnt + 1;
2448
+ -- RAISE NOTICE ' aclstr=%', aclstr;
2449
+ -- break up into grantor, grantee, and privs, mydb_update=rwU/mydb_owner
2450
+ SELECT split_part(aclstr, '=', 1) INTO grantee;
2451
+ SELECT split_part(aclstr, '=', 2) INTO grantor;
2452
+ SELECT split_part(grantor, '/', 1) INTO privs;
2453
+ SELECT split_part(grantor, '/', 2) INTO grantor;
2454
+ -- RAISE NOTICE ' grantor=% grantee=% privs=%', grantor, grantee, privs;
2455
+ IF arec.atype = 'function' THEN -- Just having execute is enough to grant all apparently.
2456
+ buffer := 'ALTER DEFAULT PRIVILEGES FOR ROLE ' || grantor || ' IN SCHEMA ' || quote_ident(dest_schema) || ' GRANT ALL ON FUNCTIONS TO "' || grantee || '";';
2457
+ -- Issue#92 Fix
2458
+ -- set role = cm_stage_ro_grp;
2459
+ -- ALTER DEFAULT PRIVILEGES FOR ROLE cm_stage_ro_grp IN SCHEMA cm_stage GRANT REFERENCES, TRIGGER ON TABLES TO cm_stage_ro_grp;
2460
+ IF grantor = grantee THEN -- append set role to statement
2461
+ buffer = 'SET ROLE = ' || grantor || '; ' || buffer;
2462
+ END IF;
2463
+ IF bDDLOnly THEN RAISE INFO '%',
2464
+ buffer;
2465
+ ELSE EXECUTE buffer;
2466
+ END IF;
2467
+ -- Issue#92 Fix:
2468
+ EXECUTE 'SET ROLE = ' || calleruser;
2469
+ ELSIF arec.atype = 'sequence' THEN IF POSITION('r' IN privs) > 0
2470
+ AND POSITION('w' IN privs) > 0
2471
+ AND POSITION('U' IN privs) > 0 THEN -- arU is enough for all privs
2472
+ buffer := 'ALTER DEFAULT PRIVILEGES FOR ROLE ' || grantor || ' IN SCHEMA ' || quote_ident(dest_schema) || ' GRANT ALL ON SEQUENCES TO "' || grantee || '";';
2473
+ -- Issue#92 Fix
2474
+ IF grantor = grantee THEN -- append set role to statement
2475
+ buffer = 'SET ROLE = ' || grantor || '; ' || buffer;
2476
+ END IF;
2477
+ IF bDDLOnly THEN RAISE INFO '%',
2478
+ buffer;
2479
+ ELSE EXECUTE buffer;
2480
+ END IF;
2481
+ -- Issue#92 Fix:
2482
+ EXECUTE 'SET ROLE = ' || calleruser;
2483
+ ELSE -- have to specify each priv individually
2484
+ buffer2 := '';
2485
+ IF POSITION('r' IN privs) > 0 THEN buffer2 := 'SELECT';
2486
+ END IF;
2487
+ IF POSITION('w' IN privs) > 0 THEN IF buffer2 = '' THEN buffer2 := 'UPDATE';
2488
+ ELSE buffer2 := buffer2 || ', UPDATE';
2489
+ END IF;
2490
+ END IF;
2491
+ IF POSITION('U' IN privs) > 0 THEN IF buffer2 = '' THEN buffer2 := 'USAGE';
2492
+ ELSE buffer2 := buffer2 || ', USAGE';
2493
+ END IF;
2494
+ END IF;
2495
+ buffer := 'ALTER DEFAULT PRIVILEGES FOR ROLE ' || grantor || ' IN SCHEMA ' || quote_ident(dest_schema) || ' GRANT ' || buffer2 || ' ON SEQUENCES TO "' || grantee || '";';
2496
+ -- Issue#92 Fix
2497
+ IF grantor = grantee THEN -- append set role to statement
2498
+ buffer = 'SET ROLE = ' || grantor || '; ' || buffer;
2499
+ END IF;
2500
+ IF bDDLOnly THEN RAISE INFO '%',
2501
+ buffer;
2502
+ ELSE EXECUTE buffer;
2503
+ END IF;
2504
+ select current_user into buffer;
2505
+ -- Issue#92 Fix:
2506
+ EXECUTE 'SET ROLE = ' || calleruser;
2507
+ END IF;
2508
+ ELSIF arec.atype = 'table' THEN -- do each priv individually, jeeeesh!
2509
+ buffer2 := '';
2510
+ IF POSITION('a' IN privs) > 0 THEN buffer2 := 'INSERT';
2511
+ END IF;
2512
+ IF POSITION('r' IN privs) > 0 THEN IF buffer2 = '' THEN buffer2 := 'SELECT';
2513
+ ELSE buffer2 := buffer2 || ', SELECT';
2514
+ END IF;
2515
+ END IF;
2516
+ IF POSITION('w' IN privs) > 0 THEN IF buffer2 = '' THEN buffer2 := 'UPDATE';
2517
+ ELSE buffer2 := buffer2 || ', UPDATE';
2518
+ END IF;
2519
+ END IF;
2520
+ IF POSITION('d' IN privs) > 0 THEN IF buffer2 = '' THEN buffer2 := 'DELETE';
2521
+ ELSE buffer2 := buffer2 || ', DELETE';
2522
+ END IF;
2523
+ END IF;
2524
+ IF POSITION('t' IN privs) > 0 THEN IF buffer2 = '' THEN buffer2 := 'TRIGGER';
2525
+ ELSE buffer2 := buffer2 || ', TRIGGER';
2526
+ END IF;
2527
+ END IF;
2528
+ IF POSITION('T' IN privs) > 0 THEN IF buffer2 = '' THEN buffer2 := 'TRUNCATE';
2529
+ ELSE buffer2 := buffer2 || ', TRUNCATE';
2530
+ END IF;
2531
+ END IF;
2532
+ buffer := 'ALTER DEFAULT PRIVILEGES FOR ROLE ' || grantor || ' IN SCHEMA ' || quote_ident(dest_schema) || ' GRANT ' || buffer2 || ' ON TABLES TO "' || grantee || '";';
2533
+ -- Issue#92 Fix
2534
+ IF grantor = grantee THEN -- append set role to statement
2535
+ buffer = 'SET ROLE = ' || grantor || '; ' || buffer;
2536
+ END IF;
2537
+ IF bDDLOnly THEN RAISE INFO '%',
2538
+ buffer;
2539
+ ELSE EXECUTE buffer;
2540
+ END IF;
2541
+ select current_user into buffer;
2542
+ -- Issue#92 Fix:
2543
+ EXECUTE 'SET ROLE = ' || calleruser;
2544
+ ELSIF arec.atype = 'type' THEN IF POSITION('r' IN privs) > 0
2545
+ AND POSITION('w' IN privs) > 0
2546
+ AND POSITION('U' IN privs) > 0 THEN -- arU is enough for all privs
2547
+ buffer := 'ALTER DEFAULT PRIVILEGES FOR ROLE ' || grantor || ' IN SCHEMA ' || quote_ident(dest_schema) || ' GRANT ALL ON TYPES TO "' || grantee || '";';
2548
+ -- Issue#92 Fix
2549
+ IF grantor = grantee THEN -- append set role to statement
2550
+ buffer = 'SET ROLE = ' || grantor || '; ' || buffer;
2551
+ END IF;
2552
+ IF bDDLOnly THEN RAISE INFO '%',
2553
+ buffer;
2554
+ ELSE EXECUTE buffer;
2555
+ END IF;
2556
+ -- Issue#92 Fix:
2557
+ EXECUTE 'SET ROLE = ' || calleruser;
2558
+ ELSIF POSITION('U' IN privs) THEN buffer := 'ALTER DEFAULT PRIVILEGES FOR ROLE ' || grantor || ' IN SCHEMA ' || quote_ident(dest_schema) || ' GRANT USAGE ON TYPES TO "' || grantee || '";';
2559
+ -- Issue#92 Fix
2560
+ IF grantor = grantee THEN -- append set role to statement
2561
+ buffer = 'SET ROLE = ' || grantor || '; ' || buffer;
2562
+ END IF;
2563
+ IF bDDLOnly THEN RAISE INFO '%',
2564
+ buffer;
2565
+ ELSE EXECUTE buffer;
2566
+ END IF;
2567
+ -- Issue#92 Fix:
2568
+ EXECUTE 'SET ROLE = ' || calleruser;
2569
+ ELSE RAISE WARNING 'Unhandled TYPE Privs:: type=% privs=% owner=% defaclacl=% defaclstr=% grantor=% grantee=% ',
2570
+ arec.atype,
2571
+ privs,
2572
+ arec.owner,
2573
+ arec.defaclacl,
2574
+ arec.defaclstr,
2575
+ grantor,
2576
+ grantee;
2577
+ END IF;
2578
+ ELSE RAISE WARNING 'Unhandled Privs:: type=% privs=% owner=% defaclacl=% defaclstr=% grantor=% grantee=% ',
2579
+ arec.atype,
2580
+ privs,
2581
+ arec.owner,
2582
+ arec.defaclacl,
2583
+ arec.defaclstr,
2584
+ grantor,
2585
+ grantee;
2586
+ END IF;
2587
+ END LOOP;
2588
+ END;
2589
+ END LOOP;
2590
+ RAISE NOTICE ' DFLT PRIVS cloned: %',
2591
+ LPAD(cnt::text, 5, ' ');
2592
+ END IF;
2593
+ -- NO ACL BRANCH
2594
+ -- Issue#95 bypass if No ACL specified
2595
+ IF NOT bNoACL THEN -- MV: PRIVS: schema
2596
+ -- crunchy data extension, check_access
2597
+ -- SELECT role_path, base_role, as_role, objtype, schemaname, objname, array_to_string(array_agg(privname),',') as privs FROM all_access()
2598
+ -- WHERE base_role != CURRENT_USER and objtype = 'schema' and schemaname = 'public' group by 1,2,3,4,5,6;
2599
+ action := 'PRIVS: Schema';
2600
+ cnt := 0;
2601
+ FOR arec IN
2602
+ SELECT 'GRANT ' || p.perm::perm_type || ' ON SCHEMA ' || quote_ident(dest_schema) || ' TO "' || r.rolname || '";' as schema_ddl
2603
+ FROM pg_catalog.pg_namespace AS n
2604
+ CROSS JOIN pg_catalog.pg_roles AS r
2605
+ CROSS JOIN (
2606
+ VALUES ('USAGE'),
2607
+ ('CREATE')
2608
+ ) AS p(perm)
2609
+ WHERE n.nspname = quote_ident(source_schema)
2610
+ AND NOT r.rolsuper
2611
+ AND has_schema_privilege(r.oid, n.oid, p.perm)
2612
+ ORDER BY r.rolname,
2613
+ p.perm::perm_type LOOP BEGIN cnt := cnt + 1;
2614
+ IF bDDLOnly THEN RAISE INFO '%',
2615
+ arec.schema_ddl;
2616
+ ELSE EXECUTE arec.schema_ddl;
2617
+ END IF;
2618
+ END;
2619
+ END LOOP;
2620
+ RAISE NOTICE 'SCHEMA PRIVS cloned: %',
2621
+ LPAD(cnt::text, 5, ' ');
2622
+ END IF;
2623
+ -- NO ACL BRANCH
2624
+ -- Issue#95 bypass if No ACL specified
2625
+ IF NOT bNoACL THEN -- MV: PRIVS: sequences
2626
+ action := 'PRIVS: Sequences';
2627
+ cnt := 0;
2628
+ FOR arec IN -- Issue#78 FIX: handle case-sensitive names with quote_ident() on t.relname
2629
+ SELECT 'GRANT ' || p.perm::perm_type || ' ON ' || quote_ident(dest_schema) || '.' || quote_ident(t.relname::text) || ' TO "' || r.rolname || '";' as seq_ddl
2630
+ FROM pg_catalog.pg_class AS t
2631
+ CROSS JOIN pg_catalog.pg_roles AS r
2632
+ CROSS JOIN (
2633
+ VALUES ('SELECT'),
2634
+ ('USAGE'),
2635
+ ('UPDATE')
2636
+ ) AS p(perm)
2637
+ WHERE t.relnamespace::regnamespace::name = quote_ident(source_schema)
2638
+ AND t.relkind = 'S'
2639
+ AND NOT r.rolsuper
2640
+ AND has_sequence_privilege(r.oid, t.oid, p.perm) LOOP BEGIN cnt := cnt + 1;
2641
+ -- IF bDebug THEN RAISE NOTICE 'DEBUG: ddl=%', arec.seq_ddl; END IF;
2642
+ IF bDDLOnly THEN RAISE INFO '%',
2643
+ arec.seq_ddl;
2644
+ ELSE EXECUTE arec.seq_ddl;
2645
+ END IF;
2646
+ END;
2647
+ END LOOP;
2648
+ RAISE NOTICE ' SEQ. PRIVS cloned: %',
2649
+ LPAD(cnt::text, 5, ' ');
2650
+ END IF;
2651
+ -- NO ACL BRANCH
2652
+ -- Issue#95 bypass if No ACL specified
2653
+ IF NOT bNoACL THEN -- MV: PRIVS: functions
2654
+ action := 'PRIVS: Functions/Procedures';
2655
+ cnt := 0;
2656
+ -- Issue#61 FIX: use set_config for empty string
2657
+ -- SET search_path = '';
2658
+ SELECT set_config('search_path', '', false) into v_dummy;
2659
+ -- RAISE NOTICE ' source_schema=% dest_schema=%',source_schema, dest_schema;
2660
+ FOR arec IN -- 2021-03-05 MJV FIX: issue#35: caused exception in some functions with parameters and gave privileges to other users that should not have gotten them.
2661
+ -- SELECT 'GRANT EXECUTE ON FUNCTION ' || quote_ident(dest_schema) || '.' || replace(regexp_replace(f.oid::regprocedure::text, '^((("[^"]*")|([^"][^.]*))\.)?', ''), source_schema, dest_schema) || ' TO "' || r.rolname || '";' as func_ddl
2662
+ -- FROM pg_catalog.pg_proc f CROSS JOIN pg_catalog.pg_roles AS r WHERE f.pronamespace::regnamespace::name = quote_ident(source_schema) AND NOT r.rolsuper AND has_function_privilege(r.oid, f.oid, 'EXECUTE')
2663
+ -- order by regexp_replace(f.oid::regprocedure::text, '^((("[^"]*")|([^"][^.]*))\.)?', '')
2664
+ -- 2021-03-05 MJV FIX: issue#37: defaults cause problems, use system function that returns args WITHOUT DEFAULTS
2665
+ -- COALESCE(r.routine_type, 'FUNCTION'): for aggregate functions, information_schema.routines contains NULL as routine_type value.
2666
+ -- Issue#78 FIX: handle case-sensitive names with quote_ident() on rp.routine_name
2667
+ SELECT 'GRANT ' || rp.privilege_type || ' ON ' || COALESCE(r.routine_type, 'FUNCTION') || ' ' || quote_ident(dest_schema) || '.' || quote_ident(rp.routine_name) || ' (' || pg_get_function_identity_arguments(p.oid) || ') TO ' || string_agg(distinct rp.grantee, ',') || ';' as func_dcl
2668
+ FROM information_schema.routine_privileges rp,
2669
+ information_schema.routines r,
2670
+ pg_proc p,
2671
+ pg_namespace n
2672
+ WHERE rp.routine_schema = quote_ident(source_schema)
2673
+ AND rp.is_grantable = 'YES'
2674
+ AND rp.routine_schema = r.routine_schema
2675
+ AND rp.routine_name = r.routine_name
2676
+ AND rp.routine_schema = n.nspname
2677
+ AND n.oid = p.pronamespace
2678
+ AND p.proname = r.routine_name
2679
+ GROUP BY rp.privilege_type,
2680
+ r.routine_type,
2681
+ rp.routine_name,
2682
+ pg_get_function_identity_arguments(p.oid) LOOP BEGIN cnt := cnt + 1;
2683
+ IF bDDLOnly THEN RAISE INFO '%',
2684
+ arec.func_dcl;
2685
+ ELSE EXECUTE arec.func_dcl;
2686
+ END IF;
2687
+ END;
2688
+ END LOOP;
2689
+ EXECUTE 'SET search_path = ' || quote_ident(source_schema);
2690
+ RAISE NOTICE ' FUNC PRIVS cloned: %',
2691
+ LPAD(cnt::text, 5, ' ');
2692
+ END IF;
2693
+ -- NO ACL BRANCH
2694
+ -- Issue#95 bypass if No ACL specified
2695
+ IF NOT bNoACL THEN -- MV: PRIVS: tables
2696
+ action := 'PRIVS: Tables';
2697
+ -- regular, partitioned, and foreign tables plus view and materialized view permissions. Ignored for now: implement foreign table defs.
2698
+ cnt := 0;
2699
+ FOR arec IN -- SELECT 'GRANT ' || p.perm::perm_type || CASE WHEN t.relkind in ('r', 'p', 'f') THEN ' ON TABLE ' WHEN t.relkind in ('v', 'm') THEN ' ON ' END || quote_ident(dest_schema) || '.' || t.relname::text || ' TO "' || r.rolname || '";' as tbl_ddl,
2700
+ -- has_table_privilege(r.oid, t.oid, p.perm) AS granted, t.relkind
2701
+ -- FROM pg_catalog.pg_class AS t CROSS JOIN pg_catalog.pg_roles AS r CROSS JOIN (VALUES (TEXT 'SELECT'), ('INSERT'), ('UPDATE'), ('DELETE'), ('TRUNCATE'), ('REFERENCES'), ('TRIGGER')) AS p(perm)
2702
+ -- WHERE t.relnamespace::regnamespace::name = quote_ident(source_schema) AND t.relkind in ('r', 'p', 'f', 'v', 'm') AND NOT r.rolsuper AND has_table_privilege(r.oid, t.oid, p.perm) order by t.relname::text, t.relkind
2703
+ -- 2021-03-05 MJV FIX: Fixed Issue#36 for tables
2704
+ SELECT c.relkind,
2705
+ 'GRANT ' || tb.privilege_type || CASE
2706
+ WHEN c.relkind in ('r', 'p') THEN ' ON TABLE '
2707
+ WHEN c.relkind in ('v', 'm') THEN ' ON '
2708
+ END || -- Issue#78 FIX: handle case-sensitive names with quote_ident() on t.relname
2709
+ -- Issue#108 FIX: enclose double-quote grantees with special characters
2710
+ -- quote_ident(dest_schema) || '.' || quote_ident(tb.table_name) || ' TO ' || string_agg(tb.grantee, ',') || ';' as tbl_dcl
2711
+ quote_ident(dest_schema) || '.' || quote_ident(tb.table_name) || ' TO ' || string_agg('"' || tb.grantee || '"', ',') || ';' as tbl_dcl
2712
+ FROM information_schema.table_privileges tb,
2713
+ pg_class c,
2714
+ pg_namespace n
2715
+ WHERE tb.table_schema = quote_ident(source_schema)
2716
+ AND tb.table_name = c.relname
2717
+ AND c.relkind in ('r', 'p', 'v', 'm')
2718
+ AND c.relnamespace = n.oid
2719
+ AND n.nspname = quote_ident(source_schema)
2720
+ GROUP BY c.relkind,
2721
+ tb.privilege_type,
2722
+ tb.table_schema,
2723
+ tb.table_name LOOP BEGIN cnt := cnt + 1;
2724
+ -- IF bDebug THEN RAISE NOTICE 'DEBUG: ddl=%', arec.tbl_dcl; END IF;
2725
+ -- Issue#46. Fixed reference to invalid record name (tbl_ddl --> tbl_dcl).
2726
+ IF arec.relkind = 'f' THEN RAISE WARNING 'Foreign tables are not currently implemented, so skipping privs for them. ddl=%',
2727
+ arec.tbl_dcl;
2728
+ ELSE IF bDDLOnly THEN RAISE INFO '%',
2729
+ arec.tbl_dcl;
2730
+ ELSE EXECUTE arec.tbl_dcl;
2731
+ END IF;
2732
+ END IF;
2733
+ END;
2734
+ END LOOP;
2735
+ RAISE NOTICE ' TABLE PRIVS cloned: %',
2736
+ LPAD(cnt::text, 5, ' ');
2737
+ END IF;
2738
+ -- NO ACL BRANCH
2739
+ -- LOOP for regular tables and populate them if specified
2740
+ -- Issue#75 moved from big table loop above to here.
2741
+ IF bData THEN r = clock_timestamp();
2742
+ -- IF bVerbose THEN RAISE NOTICE 'START: copy rows %',clock_timestamp() - t; END IF;
2743
+ IF bVerbose THEN RAISE NOTICE 'Copying rows...';
2744
+ END IF;
2745
+ EXECUTE 'SET search_path = ' || quote_ident(dest_schema);
2746
+ action := 'Copy Rows';
2747
+ FOREACH tblelement IN ARRAY tblarray LOOP s = clock_timestamp();
2748
+ IF bDebug THEN RAISE NOTICE 'DEBUG1: no UDTs %',
2749
+ tblelement;
2750
+ END IF;
2751
+ EXECUTE tblelement;
2752
+ GET DIAGNOSTICS cnt = ROW_COUNT;
2753
+ buffer = substring(tblelement, 13);
2754
+ SELECT POSITION(' OVERRIDING SYSTEM VALUE SELECT ' IN buffer) INTO cnt2;
2755
+ IF cnt2 = 0 THEN
2756
+ SELECT POSITION(' SELECT ' IN buffer) INTO cnt2;
2757
+ buffer = substring(buffer, 1, cnt2);
2758
+ ELSE buffer = substring(buffer, 1, cnt2);
2759
+ END IF;
2760
+ SELECT RPAD(buffer, 35, ' ') INTO buffer;
2761
+ cnt2 := cast(
2762
+ extract(
2763
+ epoch
2764
+ from (clock_timestamp() - s)
2765
+ ) as numeric(18, 3)
2766
+ );
2767
+ IF bVerbose THEN RAISE NOTICE 'Populated cloned table, % Rows Copied: % seconds: %',
2768
+ buffer,
2769
+ LPAD(cnt::text, 10, ' '),
2770
+ LPAD(cnt2::text, 5, ' ');
2771
+ END IF;
2772
+ tblscopied := tblscopied + 1;
2773
+ END LOOP;
2774
+ -- Issue#79 implementation
2775
+ -- Do same for tables with user-defined elements using copy to file method
2776
+ FOREACH tblelement IN ARRAY tblarray2 LOOP s = clock_timestamp();
2777
+ IF bDebug THEN RAISE NOTICE 'DEBUG2: UDTs %',
2778
+ tblelement;
2779
+ END IF;
2780
+ EXECUTE tblelement;
2781
+ GET DIAGNOSTICS cnt = ROW_COUNT;
2782
+ -- STATEMENT LOOKS LIKE THIS:
2783
+ -- INSERT INTO sample11.warehouses SELECT * FROM sample.warehouses;
2784
+ -- INSERT INTO sample11.person OVERRIDING SYSTEM VALUE SELECT * FROM sample.person;
2785
+ -- COPY sample.address TO '/tmp/cloneschema.tmp' WITH DELIMITER AS ',';\
2786
+ buffer = TRIM(tblelement::text);
2787
+ -- RAISE NOTICE 'element=%', buffer;
2788
+ cnt1 = POSITION('INSERT INTO' IN buffer);
2789
+ cnt2 = POSITION('COPY ' IN buffer);
2790
+ IF cnt1 > 0 THEN buffer = substring(buffer, 12);
2791
+ ELSIF cnt2 > 0 THEN buffer = substring(buffer, 5);
2792
+ ELSE RAISE EXCEPTION 'Programming Error for parsing tblarray2.';
2793
+ END IF;
2794
+ -- RAISE NOTICE 'buffer1=%', buffer;
2795
+ cnt1 = POSITION(' OVERRIDING ' IN buffer);
2796
+ cnt2 = POSITION('SELECT * FROM ' IN buffer);
2797
+ cnt3 = POSITION(' FROM ' IN buffer);
2798
+ cnt4 = POSITION(' TO ' IN buffer);
2799
+ IF cnt1 > 0 THEN buffer = substring(buffer, 1, cnt1 -2);
2800
+ ELSIF cnt2 > 0 THEN buffer = substring(buffer, 1, cnt2 -2);
2801
+ ELSIF cnt3 > 0 THEN buffer = substring(buffer, 1, cnt3 -1);
2802
+ ELSIF cnt4 > 0 THEN -- skip the COPY TO statements
2803
+ continue;
2804
+ ELSE RAISE EXCEPTION 'Programming Error for parsing tblarray2.';
2805
+ END IF;
2806
+ -- RAISE NOTICE 'buffer2=%', buffer;
2807
+ SELECT RPAD(buffer, 35, ' ') INTO buffer;
2808
+ -- RAISE NOTICE 'buffer3=%', buffer;
2809
+ cnt2 := cast(
2810
+ extract(
2811
+ epoch
2812
+ from (clock_timestamp() - s)
2813
+ ) as numeric(18, 3)
2814
+ );
2815
+ IF bVerbose THEN RAISE NOTICE 'Populated cloned table, % Rows Copied: % seconds: %',
2816
+ buffer,
2817
+ LPAD(cnt::text, 10, ' '),
2818
+ LPAD(cnt2::text, 5, ' ');
2819
+ END IF;
2820
+ tblscopied := tblscopied + 1;
2821
+ END LOOP;
2822
+ -- Issue#101
2823
+ -- Do same for tables with user-defined elements using direct method with text cast
2824
+ FOREACH tblelement IN ARRAY tblarray3 LOOP s = clock_timestamp();
2825
+ IF bDebug THEN RAISE NOTICE 'DEBUG3: UDTs %',
2826
+ tblelement;
2827
+ END IF;
2828
+ EXECUTE tblelement;
2829
+ GET DIAGNOSTICS cnt = ROW_COUNT;
2830
+ cnt2 = POSITION(' (' IN tblelement::text);
2831
+ IF cnt2 > 0 THEN buffer = substring(tblelement, 1, cnt2);
2832
+ buffer = substring(buffer, 6);
2833
+ SELECT RPAD(buffer, 35, ' ') INTO buffer;
2834
+ cnt2 := cast(
2835
+ extract(
2836
+ epoch
2837
+ from (clock_timestamp() - s)
2838
+ ) as numeric(18, 3)
2839
+ );
2840
+ IF bVerbose THEN RAISE NOTICE 'Populated cloned table, % Rows Copied: % seconds: %',
2841
+ buffer,
2842
+ LPAD(cnt::text, 10, ' '),
2843
+ LPAD(cnt2::text, 5, ' ');
2844
+ END IF;
2845
+ tblscopied := tblscopied + 1;
2846
+ END IF;
2847
+ END LOOP;
2848
+ -- Issue#98 MVs deferred until now
2849
+ FOREACH tblelement IN ARRAY mvarray LOOP s = clock_timestamp();
2850
+ EXECUTE tblelement;
2851
+ -- get diagnostics for MV creates or refreshes does not work, always returns 1
2852
+ GET DIAGNOSTICS cnt = ROW_COUNT;
2853
+ buffer = substring(tblelement, 25);
2854
+ cnt2 = POSITION(' AS ' IN buffer);
2855
+ IF cnt2 > 0 THEN buffer = substring(buffer, 1, cnt2);
2856
+ SELECT RPAD(buffer, 36, ' ') INTO buffer;
2857
+ cnt2 := cast(
2858
+ extract(
2859
+ epoch
2860
+ from (clock_timestamp() - s)
2861
+ ) as numeric(18, 3)
2862
+ );
2863
+ IF bVerbose THEN RAISE NOTICE 'Populated Mat. View, % Rows Inserted: ? seconds: %',
2864
+ buffer,
2865
+ LPAD(cnt2::text, 5, ' ');
2866
+ END IF;
2867
+ mvscopied := mvscopied + 1;
2868
+ END IF;
2869
+ END LOOP;
2870
+ cnt := cast(
2871
+ extract(
2872
+ epoch
2873
+ from (clock_timestamp() - r)
2874
+ ) as numeric(18, 3)
2875
+ );
2876
+ IF bVerbose THEN RAISE NOTICE 'Copy rows duration: % seconds',
2877
+ cnt;
2878
+ END IF;
2879
+ END IF;
2880
+ RAISE NOTICE ' TABLES copied: %',
2881
+ LPAD(tblscopied::text, 5, ' ');
2882
+ RAISE NOTICE ' MATVIEWS refreshed: %',
2883
+ LPAD(mvscopied::text, 5, ' ');
2884
+ -- Issue#78 forces us to defer FKeys until the end since we previously did row copies before FKeys
2885
+ -- add FK constraint
2886
+ action := 'FK Constraints';
2887
+ cnt := 0;
2888
+ -- Issue#61 FIX: use set_config for empty string
2889
+ -- SET search_path = '';
2890
+ SELECT set_config('search_path', '', false) into v_dummy;
2891
+ FOR qry IN
2892
+ SELECT 'ALTER TABLE ' || quote_ident(dest_schema) || '.' || quote_ident(rn.relname) || ' ADD CONSTRAINT ' || quote_ident(ct.conname) || ' ' || REPLACE(
2893
+ pg_get_constraintdef(ct.oid),
2894
+ 'REFERENCES ' || quote_ident(source_schema) || '.',
2895
+ 'REFERENCES ' || quote_ident(dest_schema) || '.'
2896
+ ) || ';'
2897
+ FROM pg_constraint ct
2898
+ JOIN pg_class rn ON rn.oid = ct.conrelid -- Issue#103 needed to addd this left join
2899
+ LEFT JOIN pg_inherits i ON (rn.oid = i.inhrelid)
2900
+ WHERE connamespace = src_oid
2901
+ AND rn.relkind = 'r'
2902
+ AND ct.contype = 'f' -- Issue#103 fix: needed to also add this null check
2903
+ AND i.inhrelid is null LOOP cnt := cnt + 1;
2904
+ IF bDDLOnly THEN RAISE INFO '%',
2905
+ qry;
2906
+ ELSE IF bDebug THEN RAISE NOTICE 'DEBUG: adding FKEY constraint: %',
2907
+ qry;
2908
+ END IF;
2909
+ EXECUTE qry;
2910
+ END IF;
2911
+ END LOOP;
2912
+ EXECUTE 'SET search_path = ' || quote_ident(source_schema);
2913
+ RAISE NOTICE ' FKEYS cloned: %',
2914
+ LPAD(cnt::text, 5, ' ');
2915
+ IF src_path_old = ''
2916
+ OR src_path_old = '""' THEN -- RAISE NOTICE 'Restoring old search_path to empty string';
2917
+ SELECT set_config('search_path', '', false) into v_dummy;
2918
+ ELSE -- RAISE NOTICE 'Restoring old search_path to:%', src_path_old;
2919
+ EXECUTE 'SET search_path = ' || src_path_old;
2920
+ END IF;
2921
+ SELECT setting INTO v_dummy
2922
+ FROM pg_settings
2923
+ WHERE name = 'search_path';
2924
+ IF bDebug THEN RAISE NOTICE 'DEBUG: setting search_path back to what it was: %',
2925
+ v_dummy;
2926
+ END IF;
2927
+ cnt := cast(
2928
+ extract(
2929
+ epoch
2930
+ from (clock_timestamp() - t)
2931
+ ) as numeric(18, 3)
2932
+ );
2933
+ IF bVerbose THEN RAISE NOTICE 'clone_schema duration: % seconds',
2934
+ cnt;
2935
+ END IF;
2936
+ EXCEPTION
2937
+ WHEN others THEN BEGIN GET STACKED DIAGNOSTICS v_diag1 = MESSAGE_TEXT,
2938
+ v_diag2 = PG_EXCEPTION_DETAIL,
2939
+ v_diag3 = PG_EXCEPTION_HINT,
2940
+ v_diag4 = RETURNED_SQLSTATE,
2941
+ v_diag5 = PG_CONTEXT,
2942
+ v_diag6 = PG_EXCEPTION_CONTEXT;
2943
+ v_ret := 'line=' || v_diag6 || '. ' || v_diag4 || '. ' || v_diag1;
2944
+ -- Issue#101: added version to exception output
2945
+ -- RAISE NOTICE 'v_diag1=% v_diag2=% v_diag3=% v_diag4=% v_diag5=% v_diag6=%', v_diag1, v_diag2, v_diag3, v_diag4, v_diag5, v_diag6;
2946
+ buffer2 = '';
2947
+ IF action = 'Copy Rows'
2948
+ AND v_diag4 = '42704' THEN -- Issue#105 Help user to fix the problem.
2949
+ buffer2 = 'It appears you have a USER-DEFINED column type mismatch. Try running clone_schema with the FILECOPY option. ';
2950
+ END IF;
2951
+ IF lastsql <> '' THEN buffer = v_ret || E'\n' || buffer2 || E'\n' || lastsql;
2952
+ ELSE buffer = v_ret || E'\n' || buffer2;
2953
+ END IF;
2954
+ RAISE EXCEPTION 'Version: % Action: % Diagnostics: %',
2955
+ v_version,
2956
+ action,
2957
+ buffer;
2958
+ IF src_path_old = '' THEN -- RAISE NOTICE 'setting old search_path to empty string';
2959
+ SELECT set_config('search_path', '', false);
2960
+ ELSE -- RAISE NOTICE 'setting old search_path to:%', src_path_old;
2961
+ EXECUTE 'SET search_path = ' || src_path_old;
2962
+ END IF;
2963
+ RETURN;
2964
+ END;
2965
+ RETURN;
2966
+ END;
2967
+ $_$;
2968
+ ALTER FUNCTION public.clone_schema(
2969
+ source_schema text,
2970
+ dest_schema text,
2971
+ VARIADIC arr public.cloneparms []
2972
+ ) OWNER TO postgres;
2973
+ --
2974
+ -- Name: get_insert_stmt_ddl(text, text, text, boolean); Type: FUNCTION; Schema: public; Owner: postgres
2975
+ --
2976
+
2977
+ CREATE FUNCTION public.get_insert_stmt_ddl(
2978
+ source_schema text,
2979
+ target_schema text,
2980
+ atable text,
2981
+ btextcast boolean DEFAULT false
2982
+ ) RETURNS text LANGUAGE plpgsql AS $$
2983
+ DECLARE -- the ddl we're building
2984
+ v_insert_ddl text := '';
2985
+ v_cols text := '';
2986
+ v_cols_sel text := '';
2987
+ v_cnt int := 0;
2988
+ v_colrec record;
2989
+ v_schema text;
2990
+ BEGIN FOR v_colrec IN
2991
+ SELECT c.column_name,
2992
+ c.data_type,
2993
+ c.udt_name,
2994
+ c.udt_schema,
2995
+ c.character_maximum_length,
2996
+ c.is_nullable,
2997
+ c.column_default,
2998
+ c.numeric_precision,
2999
+ c.numeric_scale,
3000
+ c.is_identity,
3001
+ c.identity_generation,
3002
+ c.is_generated
3003
+ FROM information_schema.columns c
3004
+ WHERE (table_schema, table_name) = (source_schema, atable)
3005
+ ORDER BY ordinal_position LOOP IF v_colrec.udt_schema = 'public' THEN v_schema = 'public';
3006
+ ELSE v_schema = target_schema;
3007
+ END IF;
3008
+ v_cnt = v_cnt + 1;
3009
+ IF v_colrec.is_identity = 'YES'
3010
+ OR v_colrec.is_generated = 'ALWAYS' THEN -- skip
3011
+ continue;
3012
+ END IF;
3013
+ IF v_colrec.data_type = 'USER-DEFINED' THEN IF v_cols = '' THEN v_cols = v_colrec.column_name;
3014
+ IF bTextCast THEN -- v_cols_sel = v_colrec.column_name || '::text::' || v_schema || '.' || v_colrec.udt_name;
3015
+ IF v_schema = 'public' THEN v_cols_sel = v_colrec.column_name || '::' || v_schema || '.' || v_colrec.udt_name;
3016
+ ELSE v_cols_sel = v_colrec.column_name || '::text::' || v_colrec.udt_name;
3017
+ END IF;
3018
+ ELSE v_cols_sel = v_colrec.column_name || '::' || v_schema || '.' || v_colrec.udt_name;
3019
+ END IF;
3020
+ ELSE v_cols = v_cols || ', ' || v_colrec.column_name;
3021
+ IF bTextCast THEN -- v_cols_sel = v_cols_sel || ', ' || v_colrec.column_name || '::text::' || v_schema || '.' || v_colrec.udt_name;
3022
+ IF v_schema = 'public' THEN v_cols_sel = v_cols_sel || ', ' || v_colrec.column_name || '::' || v_schema || '.' || v_colrec.udt_name;
3023
+ ELSE v_cols_sel = v_cols_sel || ', ' || v_colrec.column_name || '::text::' || v_colrec.udt_name;
3024
+ END IF;
3025
+ ELSE v_cols_sel = v_cols_sel || ', ' || v_colrec.column_name || '::' || v_schema || '.' || v_colrec.udt_name;
3026
+ END IF;
3027
+ END IF;
3028
+ ELSE IF v_cols = '' THEN v_cols = v_colrec.column_name;
3029
+ v_cols_sel = v_colrec.column_name;
3030
+ ELSE v_cols = v_cols || ', ' || v_colrec.column_name;
3031
+ v_cols_sel = v_cols_sel || ', ' || v_colrec.column_name;
3032
+ END IF;
3033
+ END IF;
3034
+ END LOOP;
3035
+ -- put it all together and return the insert statement
3036
+ -- INSERT INTO clone1.address2 (id2, id3, addr) SELECT id2::text::clone1.udt_myint, id3::text::clone1.udt_myint, addr FROM sample.address;
3037
+ v_insert_ddl = 'INSERT INTO ' || target_schema || '.' || atable || ' (' || v_cols || ') ' || 'SELECT ' || v_cols_sel || ' FROM ' || source_schema || '.' || atable || ';';
3038
+ RETURN v_insert_ddl;
3039
+ END;
3040
+ $$;
3041
+ ALTER FUNCTION public.get_insert_stmt_ddl(
3042
+ source_schema text,
3043
+ target_schema text,
3044
+ atable text,
3045
+ btextcast boolean
3046
+ ) OWNER TO postgres;
3047
+ --
3048
+ -- Name: get_table_ddl(character varying, character varying, boolean); Type: FUNCTION; Schema: public; Owner: postgres
3049
+ --
3050
+
3051
+ CREATE FUNCTION public.get_table_ddl(
3052
+ in_schema character varying,
3053
+ in_table character varying,
3054
+ bfkeys boolean
3055
+ ) RETURNS text LANGUAGE plpgsql AS $_$
3056
+ DECLARE -- the ddl we're building
3057
+ v_table_ddl text;
3058
+ -- data about the target table
3059
+ v_table_oid int;
3060
+ -- records for looping
3061
+ v_colrec record;
3062
+ v_constraintrec record;
3063
+ v_indexrec record;
3064
+ v_primary boolean := False;
3065
+ v_constraint_name text;
3066
+ v_src_path_old text := '';
3067
+ v_src_path_new text := '';
3068
+ v_dummy text;
3069
+ v_partbound text;
3070
+ v_pgversion int;
3071
+ v_parent text := '';
3072
+ v_relopts text := '';
3073
+ v_tablespace text;
3074
+ v_partition_key text := '';
3075
+ v_temp text;
3076
+ bPartitioned bool := False;
3077
+ bInheritance bool := False;
3078
+ bRelispartition bool;
3079
+ constraintarr text [] := '{}';
3080
+ constraintelement text;
3081
+ bSkip boolean;
3082
+ BEGIN
3083
+ SELECT c.oid,
3084
+ (
3085
+ SELECT setting
3086
+ FROM pg_settings
3087
+ WHERE name = 'server_version_num'
3088
+ ) INTO v_table_oid,
3089
+ v_pgversion
3090
+ FROM pg_catalog.pg_class c
3091
+ LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
3092
+ WHERE c.relkind IN ('r', 'p')
3093
+ AND c.relname = in_table
3094
+ AND n.nspname = in_schema;
3095
+ IF (v_table_oid IS NULL) THEN RAISE EXCEPTION 'table does not exist';
3096
+ END IF;
3097
+ -- get user-defined tablespaces if applicable
3098
+ SELECT TABLESPACE INTO v_temp
3099
+ FROM pg_tables
3100
+ WHERE schemaname = in_schema
3101
+ AND tablename = in_table
3102
+ AND TABLESPACE IS NOT NULL;
3103
+ -- Issue#99 Fix: simple coding error!
3104
+ -- IF v_tablespace IS NULL THEN
3105
+ IF v_temp IS NULL THEN v_tablespace := 'TABLESPACE pg_default';
3106
+ ELSE v_tablespace := 'TABLESPACE ' || v_temp;
3107
+ END IF;
3108
+ -- also see if there are any SET commands for this table, ie, autovacuum_enabled=off, fillfactor=70
3109
+ WITH relopts AS (
3110
+ SELECT unnest(c.reloptions) relopts
3111
+ FROM pg_class c,
3112
+ pg_namespace n
3113
+ WHERE n.nspname = in_schema
3114
+ AND n.oid = c.relnamespace
3115
+ AND c.relname = in_table
3116
+ )
3117
+ SELECT string_agg(r.relopts, ', ') AS relopts INTO v_temp
3118
+ FROM relopts r;
3119
+ IF v_temp IS NULL THEN v_relopts := '';
3120
+ ELSE v_relopts := ' WITH (' || v_temp || ')';
3121
+ END IF;
3122
+ -- Issue#61 FIX: set search_path = public before we do anything to force explicit schema qualification but dont forget to set it back before exiting...
3123
+ SELECT setting INTO v_src_path_old
3124
+ FROM pg_settings
3125
+ WHERE name = 'search_path';
3126
+ SELECT REPLACE(
3127
+ REPLACE(setting, '"$user"', '$user'),
3128
+ '$user',
3129
+ '"$user"'
3130
+ ) INTO v_src_path_old
3131
+ FROM pg_settings
3132
+ WHERE name = 'search_path';
3133
+ -- RAISE INFO 'DEBUG tableddl: saving old search_path: ***%***', v_src_path_old;
3134
+ EXECUTE 'SET search_path = "public"';
3135
+ SELECT setting INTO v_src_path_new
3136
+ FROM pg_settings
3137
+ WHERE name = 'search_path';
3138
+ -- grab the oid of the table; https://www.postgresql.org/docs/8.3/catalog-pg-class.html
3139
+ SELECT c.oid INTO v_table_oid
3140
+ FROM pg_catalog.pg_class c
3141
+ LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
3142
+ WHERE 1 = 1
3143
+ AND c.relkind = 'r'
3144
+ AND c.relname = in_table
3145
+ AND n.nspname = in_schema;
3146
+ IF (v_table_oid IS NULL) THEN -- Dont give up yet. It might be a partitioned table
3147
+ SELECT c.oid INTO v_table_oid
3148
+ FROM pg_catalog.pg_class c
3149
+ LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
3150
+ WHERE 1 = 1
3151
+ AND c.relkind = 'p'
3152
+ AND c.relname = in_table
3153
+ AND n.nspname = in_schema;
3154
+ IF (v_table_oid IS NULL) THEN RAISE EXCEPTION 'table does not exist';
3155
+ END IF;
3156
+ bPartitioned := True;
3157
+ END IF;
3158
+ IF v_pgversion < 100000 THEN
3159
+ SELECT c2.relname parent INTO v_parent
3160
+ FROM pg_class c1,
3161
+ pg_namespace n,
3162
+ pg_inherits i,
3163
+ pg_class c2
3164
+ WHERE n.nspname = in_schema
3165
+ AND n.oid = c1.relnamespace
3166
+ AND c1.relname = in_table
3167
+ AND c1.oid = i.inhrelid
3168
+ AND i.inhparent = c2.oid
3169
+ AND c1.relkind = 'r';
3170
+ IF (v_parent IS NOT NULL) THEN bPartitioned := True;
3171
+ bInheritance := True;
3172
+ END IF;
3173
+ ELSE
3174
+ SELECT c2.relname parent,
3175
+ c1.relispartition,
3176
+ pg_get_expr(c1.relpartbound, c1.oid, TRUE) INTO v_parent,
3177
+ bRelispartition,
3178
+ v_partbound
3179
+ FROM pg_class c1,
3180
+ pg_namespace n,
3181
+ pg_inherits i,
3182
+ pg_class c2
3183
+ WHERE n.nspname = in_schema
3184
+ AND n.oid = c1.relnamespace
3185
+ AND c1.relname = in_table
3186
+ AND c1.oid = i.inhrelid
3187
+ AND i.inhparent = c2.oid
3188
+ AND c1.relkind = 'r';
3189
+ IF (v_parent IS NOT NULL) THEN bPartitioned := True;
3190
+ IF bRelispartition THEN bInheritance := False;
3191
+ ELSE bInheritance := True;
3192
+ END IF;
3193
+ END IF;
3194
+ END IF;
3195
+ -- RAISE NOTICE 'version=% schema=% parent=% relopts=% tablespace=% partitioned=% inherited=% relispartition=%',v_pgversion, in_schema, v_parent, v_relopts, v_tablespace, bPartitioned, bInheritance, bRelispartition;
3196
+ -- start the create definition
3197
+ v_table_ddl := 'CREATE TABLE ' || in_schema || '.' || in_table || ' (' || E'\n';
3198
+ -- define all of the columns in the table; https://stackoverflow.com/a/8153081/3068233
3199
+ FOR v_colrec IN
3200
+ SELECT c.column_name,
3201
+ c.data_type,
3202
+ c.udt_name,
3203
+ c.udt_schema,
3204
+ c.character_maximum_length,
3205
+ c.is_nullable,
3206
+ c.column_default,
3207
+ c.numeric_precision,
3208
+ c.numeric_scale,
3209
+ c.is_identity,
3210
+ c.identity_generation
3211
+ FROM information_schema.columns c
3212
+ WHERE (table_schema, table_name) = (in_schema, in_table)
3213
+ ORDER BY ordinal_position LOOP v_table_ddl := v_table_ddl || ' ' -- note: two char spacer to start, to indent the column
3214
+ || v_colrec.column_name || ' ' -- FIX #82, FIX #100 as well by adding 'citext' to the list
3215
+ -- FIX #105 by overriding the previous fixes (#82, #100), which presumed "public" was always the schema for extensions. It could be a custom schema.
3216
+ -- so assume udt_schema for all USER-DEFINED datatypes
3217
+ -- || CASE WHEN v_colrec.udt_name in ('geometry', 'box2d', 'box2df', 'box3d', 'geography', 'geometry_dump', 'gidx', 'spheroid', 'valid_detail','citext')
3218
+ -- THEN v_colrec.udt_name
3219
+ || CASE
3220
+ WHEN v_colrec.data_type = 'USER-DEFINED' -- THEN in_schema || '.' || v_colrec.udt_name ELSE v_colrec.data_type END
3221
+ THEN v_colrec.udt_schema || '.' || v_colrec.udt_name
3222
+ ELSE v_colrec.data_type
3223
+ END || CASE
3224
+ WHEN v_colrec.is_identity = 'YES' THEN CASE
3225
+ WHEN v_colrec.identity_generation = 'ALWAYS' THEN ' GENERATED ALWAYS AS IDENTITY'
3226
+ ELSE ' GENERATED BY DEFAULT AS IDENTITY'
3227
+ END
3228
+ ELSE ''
3229
+ END || CASE
3230
+ WHEN v_colrec.character_maximum_length IS NOT NULL THEN ('(' || v_colrec.character_maximum_length || ')')
3231
+ WHEN v_colrec.numeric_precision > 0
3232
+ AND v_colrec.numeric_scale > 0 THEN '(' || v_colrec.numeric_precision || ',' || v_colrec.numeric_scale || ')'
3233
+ ELSE ''
3234
+ END || ' ' || CASE
3235
+ WHEN v_colrec.is_nullable = 'NO' THEN 'NOT NULL'
3236
+ ELSE 'NULL'
3237
+ END || CASE
3238
+ WHEN v_colrec.column_default IS NOT null THEN (' DEFAULT ' || v_colrec.column_default)
3239
+ ELSE ''
3240
+ END || ',' || E'\n';
3241
+ END LOOP;
3242
+ -- define all the constraints in the; https://www.postgresql.org/docs/9.1/catalog-pg-constraint.html && https://dba.stackexchange.com/a/214877/75296
3243
+ -- Issue#103: do not get foreign keys for partitions since they are defined on the parent and this will cause an "already exists" error otherwise
3244
+ -- Also conparentid is not in V10, so bypass since we do not have FKEYS in partitioned tables in V10
3245
+ IF v_pgversion < 110000 THEN FOR v_constraintrec IN
3246
+ SELECT con.conname as constraint_name,
3247
+ con.contype as constraint_type,
3248
+ CASE
3249
+ WHEN con.contype = 'p' THEN 1 -- primary key constraint
3250
+ WHEN con.contype = 'u' THEN 2 -- unique constraint
3251
+ WHEN con.contype = 'f' THEN 3 -- foreign key constraint
3252
+ WHEN con.contype = 'c' THEN 4
3253
+ ELSE 5
3254
+ END as type_rank,
3255
+ pg_get_constraintdef(con.oid) as constraint_definition
3256
+ FROM pg_catalog.pg_constraint con
3257
+ JOIN pg_catalog.pg_class rel ON rel.oid = con.conrelid
3258
+ JOIN pg_catalog.pg_namespace nsp ON nsp.oid = connamespace
3259
+ WHERE nsp.nspname = in_schema
3260
+ AND rel.relname = in_table
3261
+ ORDER BY type_rank LOOP -- Issue#85 fix
3262
+ -- constraintarr := constraintarr || v_constraintrec.constraint_name;
3263
+ constraintarr := constraintarr || v_constraintrec.constraint_name::text;
3264
+ IF v_constraintrec.type_rank = 1 THEN v_primary := True;
3265
+ v_constraint_name := v_constraintrec.constraint_name;
3266
+ END IF;
3267
+ IF NOT bfkeys
3268
+ AND v_constraintrec.constraint_type = 'f' THEN continue;
3269
+ END IF;
3270
+ v_table_ddl := v_table_ddl || ' ' -- note: two char spacer to start, to indent the column
3271
+ || 'CONSTRAINT' || ' ' || v_constraintrec.constraint_name || ' ' || v_constraintrec.constraint_definition || ',' || E'\n';
3272
+ END LOOP;
3273
+ ELSE FOR v_constraintrec IN
3274
+ SELECT con.conname as constraint_name,
3275
+ con.contype as constraint_type,
3276
+ CASE
3277
+ WHEN con.contype = 'p' THEN 1 -- primary key constraint
3278
+ WHEN con.contype = 'u' THEN 2 -- unique constraint
3279
+ WHEN con.contype = 'f' THEN 3 -- foreign key constraint
3280
+ WHEN con.contype = 'c' THEN 4
3281
+ ELSE 5
3282
+ END as type_rank,
3283
+ pg_get_constraintdef(con.oid) as constraint_definition
3284
+ FROM pg_catalog.pg_constraint con
3285
+ JOIN pg_catalog.pg_class rel ON rel.oid = con.conrelid
3286
+ JOIN pg_catalog.pg_namespace nsp ON nsp.oid = connamespace
3287
+ WHERE nsp.nspname = in_schema
3288
+ AND rel.relname = in_table -- Issue#103: do not get partitioned tables
3289
+ AND con.conparentid = 0
3290
+ ORDER BY type_rank LOOP -- Issue#85 fix
3291
+ -- constraintarr := constraintarr || v_constraintrec.constraint_name;
3292
+ constraintarr := constraintarr || v_constraintrec.constraint_name::text;
3293
+ IF v_constraintrec.type_rank = 1 THEN v_primary := True;
3294
+ v_constraint_name := v_constraintrec.constraint_name;
3295
+ END IF;
3296
+ IF NOT bfkeys
3297
+ AND v_constraintrec.constraint_type = 'f' THEN continue;
3298
+ END IF;
3299
+ v_table_ddl := v_table_ddl || ' ' -- note: two char spacer to start, to indent the column
3300
+ || 'CONSTRAINT' || ' ' || v_constraintrec.constraint_name || ' ' || v_constraintrec.constraint_definition || ',' || E'\n';
3301
+ END LOOP;
3302
+ END IF;
3303
+ -- drop the last comma before ending the create statement
3304
+ v_table_ddl = substr(v_table_ddl, 0, length(v_table_ddl) - 1) || E'\n';
3305
+ -- end the create table def but add inherits clause if valid
3306
+ IF bPartitioned
3307
+ and bInheritance THEN v_table_ddl := v_table_ddl || ') INHERITS (' || in_schema || '.' || v_parent || ') ' || v_relopts || ' ' || v_tablespace || ';' || E'\n';
3308
+ ELSIF v_pgversion >= 100000
3309
+ AND bPartitioned
3310
+ and NOT bInheritance THEN -- See if this is a partitioned table (pg_class.relkind = 'p') and add the partitioned key
3311
+ SELECT pg_get_partkeydef (c1.oid) AS partition_key INTO v_partition_key
3312
+ FROM pg_class c1
3313
+ JOIN pg_namespace n ON (n.oid = c1.relnamespace)
3314
+ LEFT JOIN pg_partitioned_table p ON (c1.oid = p.partrelid)
3315
+ WHERE n.nspname = in_schema
3316
+ AND n.oid = c1.relnamespace
3317
+ AND c1.relname = in_table
3318
+ AND c1.relkind = 'p';
3319
+ END IF;
3320
+ IF v_partition_key IS NOT NULL
3321
+ AND v_partition_key <> '' THEN -- add partition clause
3322
+ -- NOTE: cannot specify default tablespace for partitioned relations
3323
+ v_table_ddl := v_table_ddl || ') PARTITION BY ' || v_partition_key || ';' || E'\n';
3324
+ ELSIF bPartitioned
3325
+ AND not bInheritance THEN IF v_relopts <> '' THEN v_table_ddl := 'CREATE TABLE ' || in_schema || '.' || in_table || ' PARTITION OF ' || in_schema || '.' || v_parent || ' ' || v_partbound || v_relopts || ' ' || v_tablespace || '; ' || E'\n';
3326
+ ELSE v_table_ddl := 'CREATE TABLE ' || in_schema || '.' || in_table || ' PARTITION OF ' || in_schema || '.' || v_parent || ' ' || v_partbound || ' ' || v_tablespace || '; ' || E'\n';
3327
+ END IF;
3328
+ ELSIF bPartitioned
3329
+ and bInheritance THEN -- we already did this above
3330
+ v_table_ddl := v_table_ddl;
3331
+ ELSIF v_relopts <> '' THEN v_table_ddl := v_table_ddl || ') ' || v_relopts || ' ' || v_tablespace || ';' || E'\n';
3332
+ ELSE v_table_ddl := v_table_ddl || ') ' || v_tablespace || ';' || E'\n';
3333
+ END IF;
3334
+ -- suffix create statement with all of the indexes on the table
3335
+ FOR v_indexrec IN
3336
+ SELECT indexdef,
3337
+ indexname
3338
+ FROM pg_indexes
3339
+ WHERE (schemaname, tablename) = (in_schema, in_table) LOOP -- Issue#83 fix: loop through constraints and skip ones already defined
3340
+ bSkip = False;
3341
+ FOREACH constraintelement IN ARRAY constraintarr LOOP IF constraintelement = v_indexrec.indexname THEN bSkip = True;
3342
+ EXIT;
3343
+ END IF;
3344
+ END LOOP;
3345
+ if bSkip THEN CONTINUE;
3346
+ END IF;
3347
+ v_table_ddl := v_table_ddl || v_indexrec.indexdef || ';' || E'\n';
3348
+ END LOOP;
3349
+ -- reset search_path back to what it was
3350
+ IF v_src_path_old = '' THEN
3351
+ SELECT set_config('search_path', '', false) into v_dummy;
3352
+ ELSE EXECUTE 'SET search_path = ' || v_src_path_old;
3353
+ END IF;
3354
+ -- RAISE NOTICE 'DEBUG tableddl: reset search_path back to ***%***', v_src_path_old;
3355
+ -- return the ddl
3356
+ RETURN v_table_ddl;
3357
+ END;
3358
+ $_$;
3359
+ ALTER FUNCTION public.get_table_ddl(
3360
+ in_schema character varying,
3361
+ in_table character varying,
3362
+ bfkeys boolean
3363
+ ) OWNER TO postgres;
3364
+ --
3365
+ -- Name: get_table_ddl_complex(text, text, text, integer); Type: FUNCTION; Schema: public; Owner: postgres
3366
+ --
3367
+
3368
+ CREATE FUNCTION public.get_table_ddl_complex(
3369
+ src_schema text,
3370
+ dst_schema text,
3371
+ in_table text,
3372
+ sq_server_version_num integer
3373
+ ) RETURNS text LANGUAGE plpgsql AS $$
3374
+ DECLARE v_table_ddl text;
3375
+ v_buffer1 text;
3376
+ v_buffer2 text;
3377
+ BEGIN IF sq_server_version_num < 110000 THEN
3378
+ SELECT 'CREATE TABLE ' || quote_ident(dst_schema) || '.' || pc.relname || E'(\n' || string_agg(
3379
+ pa.attname || ' ' || pg_catalog.format_type(pa.atttypid, pa.atttypmod) || coalesce(
3380
+ ' DEFAULT ' || (
3381
+ SELECT pg_catalog.pg_get_expr(d.adbin, d.adrelid)
3382
+ FROM pg_catalog.pg_attrdef d
3383
+ WHERE d.adrelid = pa.attrelid
3384
+ AND d.adnum = pa.attnum
3385
+ AND pa.atthasdef
3386
+ ),
3387
+ ''
3388
+ ) || ' ' || CASE
3389
+ pa.attnotnull
3390
+ WHEN TRUE THEN 'NOT NULL'
3391
+ ELSE 'NULL'
3392
+ END,
3393
+ E',\n'
3394
+ ) || coalesce(
3395
+ (
3396
+ SELECT E',\n' || string_agg(
3397
+ 'CONSTRAINT ' || pc1.conname || ' ' || pg_get_constraintdef(pc1.oid),
3398
+ E',\n'
3399
+ ORDER BY pc1.conindid
3400
+ )
3401
+ FROM pg_constraint pc1 --Issue#103: do not return FKEYS for partitions since we assume it is implied by the one done on the parent table, otherwise error for trying to define it again.
3402
+ WHERE pc1.conrelid = pa.attrelid
3403
+ ),
3404
+ ''
3405
+ ) INTO v_buffer1
3406
+ FROM pg_catalog.pg_attribute pa
3407
+ JOIN pg_catalog.pg_class pc ON pc.oid = pa.attrelid
3408
+ AND pc.relname = quote_ident(in_table)
3409
+ JOIN pg_catalog.pg_namespace pn ON pn.oid = pc.relnamespace
3410
+ AND pn.nspname = quote_ident(src_schema)
3411
+ WHERE pa.attnum > 0
3412
+ AND NOT pa.attisdropped
3413
+ GROUP BY pn.nspname,
3414
+ pc.relname,
3415
+ pa.attrelid;
3416
+ ELSE
3417
+ SELECT 'CREATE TABLE ' || quote_ident(dst_schema) || '.' || pc.relname || E'(\n' || string_agg(
3418
+ pa.attname || ' ' || pg_catalog.format_type(pa.atttypid, pa.atttypmod) || coalesce(
3419
+ ' DEFAULT ' || (
3420
+ SELECT pg_catalog.pg_get_expr(d.adbin, d.adrelid)
3421
+ FROM pg_catalog.pg_attrdef d
3422
+ WHERE d.adrelid = pa.attrelid
3423
+ AND d.adnum = pa.attnum
3424
+ AND pa.atthasdef
3425
+ ),
3426
+ ''
3427
+ ) || ' ' || CASE
3428
+ pa.attnotnull
3429
+ WHEN TRUE THEN 'NOT NULL'
3430
+ ELSE 'NULL'
3431
+ END,
3432
+ E',\n'
3433
+ ) || coalesce(
3434
+ (
3435
+ SELECT E',\n' || string_agg(
3436
+ 'CONSTRAINT ' || pc1.conname || ' ' || pg_get_constraintdef(pc1.oid),
3437
+ E',\n'
3438
+ ORDER BY pc1.conindid
3439
+ )
3440
+ FROM pg_constraint pc1 --Issue#103: do not return FKEYS for partitions since we assume it is implied by the one done on the parent table, otherwise error for trying to define it again.
3441
+ WHERE pc1.conrelid = pa.attrelid
3442
+ AND pc1.conparentid = 0
3443
+ ),
3444
+ ''
3445
+ ) INTO v_buffer1
3446
+ FROM pg_catalog.pg_attribute pa
3447
+ JOIN pg_catalog.pg_class pc ON pc.oid = pa.attrelid
3448
+ AND pc.relname = quote_ident(in_table)
3449
+ JOIN pg_catalog.pg_namespace pn ON pn.oid = pc.relnamespace
3450
+ AND pn.nspname = quote_ident(src_schema)
3451
+ WHERE pa.attnum > 0
3452
+ AND NOT pa.attisdropped
3453
+ GROUP BY pn.nspname,
3454
+ pc.relname,
3455
+ pa.attrelid;
3456
+ END IF;
3457
+ -- append partition keyword to it
3458
+ SELECT pg_catalog.pg_get_partkeydef(c.oid::pg_catalog.oid) into v_buffer2
3459
+ FROM pg_catalog.pg_class c
3460
+ LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
3461
+ WHERE c.relname = quote_ident(in_table) COLLATE pg_catalog.default
3462
+ AND n.nspname = quote_ident(src_schema) COLLATE pg_catalog.default;
3463
+ v_table_ddl := v_buffer1 || ') PARTITION BY ' || v_buffer2 || ';';
3464
+ RETURN v_table_ddl;
3465
+ END;
3466
+ $$;
3467
+ ALTER FUNCTION public.get_table_ddl_complex(
3468
+ src_schema text,
3469
+ dst_schema text,
3470
+ in_table text,
3471
+ sq_server_version_num integer
3472
+ ) OWNER TO postgres;
3473
+ SET default_tablespace = '';
3474
+ SET default_table_access_method = heap;
3475
+ --
3476
+ -- Name: migrations; Type: TABLE; Schema: public; Owner: postgres
3477
+ --
3478
+
3479
+ CREATE TABLE public.migrations (
3480
+ id bigint NOT NULL,
3481
+ name character varying,
3482
+ description text,
3483
+ snapshot jsonb
3484
+ );
3485
+ ALTER TABLE public.migrations OWNER TO postgres;
3486
+ --
3487
+ -- Name: migrations_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres
3488
+ --
3489
+
3490
+ CREATE SEQUENCE public.migrations_id_seq START WITH 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1;
3491
+ ALTER TABLE public.migrations_id_seq OWNER TO postgres;
3492
+ --
3493
+ -- Name: migrations_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: postgres
3494
+ --
3495
+
3496
+ ALTER SEQUENCE public.migrations_id_seq OWNED BY public.migrations.id;
3497
+ --
3498
+ -- Name: migrations id; Type: DEFAULT; Schema: public; Owner: postgres
3499
+ --
3500
+
3501
+ ALTER TABLE ONLY public.migrations
3502
+ ALTER COLUMN id
3503
+ SET DEFAULT nextval('public.migrations_id_seq'::regclass);
3504
+ SELECT pg_catalog.setval('public.migrations_id_seq', 1, false);
3505
+ -- 1. Créer la fonction d'event trigger corrigée
3506
+ CREATE OR REPLACE FUNCTION public.notify_schema_change() RETURNS event_trigger AS $$ BEGIN PERFORM pg_notify('schema_changes', 'Test notification');
3507
+ END;
3508
+ $$ LANGUAGE plpgsql;
3509
+ -- 2. Créer l'event trigger
3510
+ CREATE EVENT TRIGGER schema_change_trigger ON ddl_command_end
3511
+ WHEN TAG IN (
3512
+ 'CREATE TABLE',
3513
+ 'ALTER TABLE',
3514
+ 'DROP TABLE',
3515
+ 'CREATE VIEW',
3516
+ 'ALTER VIEW',
3517
+ 'DROP VIEW',
3518
+ 'CREATE INDEX',
3519
+ 'DROP INDEX',
3520
+ 'CREATE FUNCTION',
3521
+ 'ALTER FUNCTION',
3522
+ 'DROP FUNCTION'
3523
+ ) EXECUTE FUNCTION public.notify_schema_change();
3524
+
3525
+ --
3526
+ -- AUTH Schema
3527
+ --
3528
+
3529
+ CREATE TABLE auth.profil (
3530
+ id bigint NOT NULL,
3531
+ utilisateur_id bigint NOT NULL,
3532
+ role_id integer
3533
+ );
3534
+ CREATE SEQUENCE auth.profil_id_seq
3535
+ START WITH 1
3536
+ INCREMENT BY 1
3537
+ NO MINVALUE
3538
+ NO MAXVALUE
3539
+ CACHE 1;
3540
+ CREATE TABLE auth.roles (
3541
+ id bigint NOT NULL,
3542
+ name character varying(50) NOT NULL
3543
+ );
3544
+ CREATE SEQUENCE auth.roles_id_seq
3545
+ START WITH 1
3546
+ INCREMENT BY 1
3547
+ NO MINVALUE
3548
+ NO MAXVALUE
3549
+ CACHE 1;
3550
+ CREATE TABLE auth.users (
3551
+ id bigint NOT NULL,
3552
+ email character varying(255) NOT NULL,
3553
+ orion_id UUID,
3554
+ info JSONB
3555
+ );
3556
+ CREATE SEQUENCE auth.users_id_seq
3557
+ START WITH 1
3558
+ INCREMENT BY 1
3559
+ NO MINVALUE
3560
+ NO MAXVALUE
3561
+ CACHE 1;
3562
+
3563
+ ALTER TABLE ONLY auth.profil ALTER COLUMN id SET DEFAULT nextval('auth.profil_id_seq'::regclass);
3564
+ ALTER TABLE ONLY auth.roles ALTER COLUMN id SET DEFAULT nextval('auth.roles_id_seq'::regclass);
3565
+ ALTER TABLE ONLY auth.users ALTER COLUMN id SET DEFAULT nextval('auth.users_id_seq'::regclass);
3566
+
3567
+ ALTER TABLE ONLY auth.profil
3568
+ ADD CONSTRAINT profil_pkey PRIMARY KEY (id);
3569
+ ALTER TABLE ONLY auth.roles
3570
+ ADD CONSTRAINT roles_pkey PRIMARY KEY (id);
3571
+ ALTER TABLE ONLY auth.users
3572
+ ADD CONSTRAINT users_pkey PRIMARY KEY (id);
3573
+ ALTER TABLE ONLY auth.profil
3574
+ ADD CONSTRAINT profil_role_id_fkey FOREIGN KEY (role_id) REFERENCES auth.roles(id);
3575
+ ALTER TABLE ONLY auth.profil
3576
+ ADD CONSTRAINT profil_utilisateurid_fkey FOREIGN KEY (utilisateur_id) REFERENCES auth.users(id);
3577
+
3578
+ ALTER TABLE auth.users
3579
+ ADD CONSTRAINT users_orion_id_unique UNIQUE (orion_id);
3580
+
3581
+ CREATE FUNCTION auth.uid() RETURNS uuid AS $$
3582
+ BEGIN
3583
+ RETURN current_setting('app.current_user_id', true)::uuid;
3584
+ EXCEPTION
3585
+ WHEN OTHERS THEN
3586
+ RETURN NULL;
3587
+ END;
3588
+ $$ LANGUAGE plpgsql;
3589
+
3590
+
3591
+
3592
+ SET statement_timeout = 0;
3593
+ SET lock_timeout = 0;
3594
+ SET idle_in_transaction_session_timeout = 0;
3595
+ SET client_encoding = 'UTF8';
3596
+ SET standard_conforming_strings = on;
3597
+ SELECT pg_catalog.set_config('search_path', '', false);
3598
+ SET check_function_bodies = false;
3599
+ SET xmloption = content;
3600
+ SET client_min_messages = warning;
3601
+ SET row_security = off;
3602
+ --
3603
+ -- PostgreSQL database dump complete
3604
+ --