OSDN Git Service

626dcab8f7c153afccf831b46f39227a24c9ee37
[pgdbmsstats/pg_dbms_stats.git] / pg_dbms_stats--1.4.0.sql
1 /* pg_dbms_stats/pg_dbms_stats--1.3.11.sql */
2
3 -- complain if script is sourced in psql, rather than via CREATE EXTENSION
4 \echo Use "CREATE EXTENSION pg_dbms_stats" to load this file. \quit
5
6 -- define alias of anyarray type because parser does not allow to use
7 -- anyarray in type definitions.
8 --
9 CREATE FUNCTION dbms_stats.anyarray_in(cstring) RETURNS dbms_stats.anyarray
10     AS 'anyarray_in' LANGUAGE internal STRICT IMMUTABLE;
11 CREATE FUNCTION dbms_stats.anyarray_out(dbms_stats.anyarray) RETURNS cstring
12     AS 'anyarray_out' LANGUAGE internal STRICT IMMUTABLE;
13 CREATE FUNCTION dbms_stats.anyarray_recv(internal) RETURNS dbms_stats.anyarray
14     AS 'MODULE_PATHNAME', 'dbms_stats_array_recv' LANGUAGE C STRICT IMMUTABLE;
15 CREATE FUNCTION dbms_stats.anyarray_send(dbms_stats.anyarray) RETURNS bytea
16     AS 'anyarray_send' LANGUAGE internal STRICT IMMUTABLE;
17 CREATE TYPE dbms_stats.anyarray (
18     INPUT = dbms_stats.anyarray_in,
19     OUTPUT = dbms_stats.anyarray_out,
20     RECEIVE = dbms_stats.anyarray_recv,
21     SEND = dbms_stats.anyarray_send,
22     INTERNALLENGTH = VARIABLE,
23     ALIGNMENT = double,
24     STORAGE = extended,
25     CATEGORY = 'P'
26 );
27
28 --
29 -- User defined stats tables
30 --
31
32 CREATE TABLE dbms_stats.relation_stats_locked (
33     relid            oid    NOT NULL,
34     relname          text   NOT NULL,
35     relpages         int4,
36     reltuples        float4,
37     relallvisible    int4,
38     curpages         int4,
39     last_analyze     timestamp with time zone,
40     last_autoanalyze timestamp with time zone,
41     PRIMARY KEY (relid)
42 );
43
44 CREATE TABLE dbms_stats.column_stats_locked (
45     starelid    oid    NOT NULL,
46     staattnum   int2   NOT NULL,
47     stainherit  bool   NOT NULL,
48     stanullfrac float4,
49     stawidth    int4,
50     stadistinct float4,
51     stakind1    int2,
52     stakind2    int2,
53     stakind3    int2,
54     stakind4    int2,
55     stakind5    int2,
56     staop1      oid,
57     staop2      oid,
58     staop3      oid,
59     staop4      oid,
60     staop5      oid,
61     stacoll1    oid,
62     stacoll2    oid,
63     stacoll3    oid,
64     stacoll4    oid,
65     stacoll5    oid,
66     stanumbers1 float4[],
67     stanumbers2 float4[],
68     stanumbers3 float4[],
69     stanumbers4 float4[],
70     stanumbers5 float4[],
71     stavalues1  dbms_stats.anyarray,
72     stavalues2  dbms_stats.anyarray,
73     stavalues3  dbms_stats.anyarray,
74     stavalues4  dbms_stats.anyarray,
75     stavalues5  dbms_stats.anyarray,
76     PRIMARY KEY (starelid, staattnum, stainherit),
77     FOREIGN KEY (starelid) REFERENCES dbms_stats.relation_stats_locked (relid) ON DELETE CASCADE
78 );
79
80 --
81 -- Statistics backup tables
82 --
83
84 CREATE TABLE dbms_stats.backup_history (
85     id      serial8 PRIMARY KEY,
86     time    timestamp with time zone NOT NULL,
87     unit    char(1) NOT NULL,
88     comment text
89 );
90
91 CREATE TABLE dbms_stats.relation_stats_backup (
92     id               int8   NOT NULL,
93     relid            oid    NOT NULL,
94     relname          text   NOT NULL,
95     relpages         int4   NOT NULL,
96     reltuples        float4 NOT NULL,
97     relallvisible    int4   NOT NULL,
98     curpages         int4   NOT NULL,
99     last_analyze     timestamp with time zone,
100     last_autoanalyze timestamp with time zone,
101     PRIMARY KEY (id, relid),
102     FOREIGN KEY (id) REFERENCES dbms_stats.backup_history (id) ON DELETE CASCADE
103 );
104
105 CREATE TABLE dbms_stats.column_stats_backup (
106     id          int8   NOT NULL,
107     statypid    oid    NOT NULL,
108     starelid    oid    NOT NULL,
109     staattnum   int2   NOT NULL,
110     stainherit  bool   NOT NULL,
111     stanullfrac float4 NOT NULL,
112     stawidth    int4   NOT NULL,
113     stadistinct float4 NOT NULL,
114     stakind1    int2   NOT NULL,
115     stakind2    int2   NOT NULL,
116     stakind3    int2   NOT NULL,
117     stakind4    int2   NOT NULL,
118     stakind5    int2   NOT NULL,
119     staop1      oid    NOT NULL,
120     staop2      oid    NOT NULL,
121     staop3      oid    NOT NULL,
122     staop4      oid    NOT NULL,
123     staop5      oid    NOT NULL,
124     stacoll1    oid    NOT NULL,
125     stacoll2    oid    NOT NULL,
126     stacoll3    oid    NOT NULL,
127     stacoll4    oid    NOT NULL,
128     stacoll5    oid    NOT NULL,
129     stanumbers1 float4[],
130     stanumbers2 float4[],
131     stanumbers3 float4[],
132     stanumbers4 float4[],
133     stanumbers5 float4[],
134     stavalues1  dbms_stats.anyarray,
135     stavalues2  dbms_stats.anyarray,
136     stavalues3  dbms_stats.anyarray,
137     stavalues4  dbms_stats.anyarray,
138     stavalues5  dbms_stats.anyarray,
139     PRIMARY KEY (id, starelid, staattnum, stainherit),
140     FOREIGN KEY (id) REFERENCES dbms_stats.backup_history (id) ON DELETE CASCADE,
141     FOREIGN KEY (id, starelid) REFERENCES dbms_stats.relation_stats_backup (id, relid) ON DELETE CASCADE
142 );
143
144 --
145 -- Functions
146 --
147
148 CREATE FUNCTION dbms_stats.relname(nspname text, relname text)
149 RETURNS text AS
150 $$SELECT quote_ident($1) || '.' || quote_ident($2)$$
151 LANGUAGE sql STABLE STRICT;
152
153 CREATE FUNCTION dbms_stats.is_system_schema(schemaname text)
154 RETURNS boolean AS
155 'MODULE_PATHNAME', 'dbms_stats_is_system_schema'
156 LANGUAGE C IMMUTABLE STRICT;
157
158 CREATE FUNCTION dbms_stats.is_system_catalog(relid regclass)
159 RETURNS boolean AS
160 'MODULE_PATHNAME', 'dbms_stats_is_system_catalog'
161 LANGUAGE C STABLE;
162
163 CREATE FUNCTION dbms_stats.is_target_relkind(relkind "char")
164 RETURNS boolean AS
165 $$SELECT $1 IN ('r', 'i', 'f', 'm')$$
166 LANGUAGE sql STABLE;
167
168 CREATE FUNCTION dbms_stats.merge(
169     lhs dbms_stats.column_stats_locked,
170     rhs pg_catalog.pg_statistic
171 ) RETURNS dbms_stats.column_stats_locked AS
172 'MODULE_PATHNAME', 'dbms_stats_merge'
173 LANGUAGE C STABLE;
174
175 CREATE VIEW dbms_stats.relation_stats_effective AS
176     SELECT
177         c.oid AS relid,
178         dbms_stats.relname(nspname, c.relname) AS relname,
179         COALESCE(v.relpages, c.relpages) AS relpages,
180         COALESCE(v.reltuples, c.reltuples) AS reltuples,
181         COALESCE(v.relallvisible, c.relallvisible) AS relallvisible,
182         COALESCE(v.curpages,
183             (pg_relation_size(c.oid) / current_setting('block_size')::int4)::int4)
184             AS curpages,
185         COALESCE(v.last_analyze,
186             pg_catalog.pg_stat_get_last_analyze_time(c.oid))
187             AS last_analyze,
188         COALESCE(v.last_autoanalyze,
189             pg_catalog.pg_stat_get_last_autoanalyze_time(c.oid))
190             AS last_autoanalyze
191       FROM pg_catalog.pg_class c
192       JOIN pg_catalog.pg_namespace n
193         ON c.relnamespace = n.oid
194       LEFT JOIN dbms_stats.relation_stats_locked v
195         ON v.relid = c.oid
196      WHERE dbms_stats.is_target_relkind(c.relkind)
197        AND NOT dbms_stats.is_system_schema(nspname);
198
199 CREATE VIEW dbms_stats.column_stats_effective AS
200     SELECT * FROM (
201         SELECT (dbms_stats.merge(v, s)).*
202           FROM pg_catalog.pg_statistic s
203           FULL JOIN dbms_stats.column_stats_locked v
204          USING (starelid, staattnum, stainherit)
205          WHERE NOT dbms_stats.is_system_catalog(starelid)
206                    AND EXISTS (
207                         SELECT NULL
208                           FROM pg_attribute a
209                          WHERE a.attrelid = starelid
210                            AND a.attnum = staattnum
211                            AND a.attisdropped  = false
212                         )
213         ) m
214      WHERE starelid IS NOT NULL;
215
216 --
217 -- Note: This view is copied from pg_stats in
218 -- src/backend/catalog/system_views.sql in core source tree of version
219 -- 9.5, and customized for pg_dbms_stats.  Changes from orignal one are:
220 --   - rename from pg_stats to dbms_stats.stats by a view name.
221 --   - changed the table name from pg_statistic to dbms_stats.column_stats_effective.
222 --
223 CREATE VIEW dbms_stats.stats with (security_barrier) AS
224     SELECT
225         nspname AS schemaname,
226         relname AS tablename,
227         attname AS attname,
228         stainherit AS inherited,
229         stanullfrac AS null_frac,
230         stawidth AS avg_width,
231         stadistinct AS n_distinct,
232         CASE
233             WHEN stakind1 = 1 THEN stavalues1
234             WHEN stakind2 = 1 THEN stavalues2
235             WHEN stakind3 = 1 THEN stavalues3
236             WHEN stakind4 = 1 THEN stavalues4
237             WHEN stakind5 = 1 THEN stavalues5
238         END AS most_common_vals,
239         CASE
240             WHEN stakind1 = 1 THEN stanumbers1
241             WHEN stakind2 = 1 THEN stanumbers2
242             WHEN stakind3 = 1 THEN stanumbers3
243             WHEN stakind4 = 1 THEN stanumbers4
244             WHEN stakind5 = 1 THEN stanumbers5
245         END AS most_common_freqs,
246         CASE
247             WHEN stakind1 = 2 THEN stavalues1
248             WHEN stakind2 = 2 THEN stavalues2
249             WHEN stakind3 = 2 THEN stavalues3
250             WHEN stakind4 = 2 THEN stavalues4
251             WHEN stakind5 = 2 THEN stavalues5
252         END AS histogram_bounds,
253         CASE
254             WHEN stakind1 = 3 THEN stanumbers1[1]
255             WHEN stakind2 = 3 THEN stanumbers2[1]
256             WHEN stakind3 = 3 THEN stanumbers3[1]
257             WHEN stakind4 = 3 THEN stanumbers4[1]
258             WHEN stakind5 = 3 THEN stanumbers5[1]
259         END AS correlation,
260         CASE
261             WHEN stakind1 = 4 THEN stavalues1
262             WHEN stakind2 = 4 THEN stavalues2
263             WHEN stakind3 = 4 THEN stavalues3
264             WHEN stakind4 = 4 THEN stavalues4
265             WHEN stakind5 = 4 THEN stavalues5
266         END AS most_common_elems,
267         CASE
268             WHEN stakind1 = 4 THEN stanumbers1
269             WHEN stakind2 = 4 THEN stanumbers2
270             WHEN stakind3 = 4 THEN stanumbers3
271             WHEN stakind4 = 4 THEN stanumbers4
272             WHEN stakind5 = 4 THEN stanumbers5
273         END AS most_common_elem_freqs,
274         CASE
275             WHEN stakind1 = 5 THEN stanumbers1
276             WHEN stakind2 = 5 THEN stanumbers2
277             WHEN stakind3 = 5 THEN stanumbers3
278             WHEN stakind4 = 5 THEN stanumbers4
279             WHEN stakind5 = 5 THEN stanumbers5
280         END AS elem_count_histogram
281     FROM dbms_stats.column_stats_effective s JOIN pg_class c ON (c.oid = s.starelid)
282          JOIN pg_attribute a ON (c.oid = attrelid AND attnum = s.staattnum)
283          LEFT JOIN pg_namespace n ON (n.oid = c.relnamespace)
284     WHERE NOT attisdropped
285         AND has_column_privilege(c.oid, a.attnum, 'select')
286         AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
287
288 --
289 -- Utility functions
290 --
291
292 CREATE FUNCTION dbms_stats.invalidate_relation_cache()
293     RETURNS trigger AS
294     'MODULE_PATHNAME', 'dbms_stats_invalidate_relation_cache'
295     LANGUAGE C;
296
297 -- Invalidate cached plans when dbms_stats.relation_stats_locked is modified.
298 CREATE TRIGGER invalidate_relation_cache
299     BEFORE INSERT OR DELETE OR UPDATE
300     ON dbms_stats.relation_stats_locked
301    FOR EACH ROW EXECUTE PROCEDURE dbms_stats.invalidate_relation_cache();
302
303 CREATE FUNCTION dbms_stats.invalidate_column_cache()
304     RETURNS trigger AS
305     'MODULE_PATHNAME', 'dbms_stats_invalidate_column_cache'
306     LANGUAGE C;
307
308 -- Invalidate cached plans when dbms_stats.column_stats_locked is modified.
309 CREATE TRIGGER invalidate_column_cache
310     BEFORE INSERT OR DELETE OR UPDATE
311     ON dbms_stats.column_stats_locked
312     FOR EACH ROW EXECUTE PROCEDURE dbms_stats.invalidate_column_cache();
313
314 --
315 -- BACKUP_STATS: Statistics backup functions
316 --
317
318 CREATE FUNCTION dbms_stats.backup(
319     backup_id int8,
320     relid regclass,
321     attnum int2
322 ) RETURNS int8 AS
323 $$
324 /* Lock the backup id */
325 SELECT * from dbms_stats.backup_history
326     WHERE  id = $1 FOR UPDATE;
327
328 INSERT INTO dbms_stats.relation_stats_backup
329     SELECT $1, v.relid, v.relname, v.relpages, v.reltuples, v.relallvisible,
330            v.curpages, v.last_analyze, v.last_autoanalyze
331       FROM pg_catalog.pg_class c,
332            dbms_stats.relation_stats_effective v
333      WHERE c.oid = v.relid
334        AND dbms_stats.is_target_relkind(relkind)
335        AND NOT dbms_stats.is_system_catalog(v.relid)
336        AND (v.relid = $2 OR $2 IS NULL);
337
338 INSERT INTO dbms_stats.column_stats_backup
339     SELECT $1, atttypid, s.*
340       FROM pg_catalog.pg_class c,
341            dbms_stats.column_stats_effective s,
342            pg_catalog.pg_attribute a
343      WHERE c.oid = starelid
344        AND starelid = attrelid
345        AND staattnum = attnum
346        AND dbms_stats.is_target_relkind(relkind)
347        AND NOT dbms_stats.is_system_catalog(c.oid)
348        AND ($2 IS NULL OR starelid = $2)
349        AND ($3 IS NULL OR staattnum = $3);
350
351 SELECT $1;
352 $$
353 LANGUAGE sql;
354
355 CREATE FUNCTION dbms_stats.backup(
356     relid regclass DEFAULT NULL,
357     attname text DEFAULT NULL,
358     comment text DEFAULT NULL
359 ) RETURNS int8 AS
360 $$
361 DECLARE
362     backup_id       int8;
363     backup_relkind  "char";
364     set_attnum      int2;
365     unit_type       char;
366 BEGIN
367     IF $1 IS NULL AND $2 IS NOT NULL THEN
368         RAISE EXCEPTION 'relation required';
369     END IF;
370     IF $1 IS NOT NULL THEN
371         SELECT relkind INTO backup_relkind
372           FROM pg_catalog.pg_class WHERE oid = $1 FOR SHARE;
373         IF NOT FOUND THEN
374             RAISE EXCEPTION 'relation "%" not found', $1;
375         END IF;
376         IF NOT dbms_stats.is_target_relkind(backup_relkind) THEN
377             RAISE EXCEPTION 'relation of relkind "%" cannot have statistics to backup: "%"',
378                                 backup_relkind, $1
379                                 USING HINT = 'Only tables(r), materialized views(m), foreign tables(f) and indexes(i) are allowed.';
380         END IF;
381         IF dbms_stats.is_system_catalog($1) THEN
382             RAISE EXCEPTION 'backing up statistics is inhibited for system catalogs: "%"', $1;
383         END IF;
384         IF $2 IS NOT NULL THEN
385             SELECT a.attnum INTO set_attnum FROM pg_catalog.pg_attribute a
386              WHERE a.attrelid = $1 AND a.attname = $2 FOR SHARE;
387             IF set_attnum IS NULL THEN
388                 RAISE EXCEPTION 'column "%" not found in relation "%"', $2, $1;
389             END IF;
390             IF NOT EXISTS(SELECT * FROM dbms_stats.column_stats_effective WHERE starelid = $1 AND staattnum = set_attnum) THEN
391                 RAISE EXCEPTION 'no statistics available for column "%" of relation "%"', $2, $1;
392             END IF;
393             unit_type = 'c';
394         ELSE
395             unit_type = 't';
396         END IF;
397     ELSE
398         unit_type = 'd';
399     END IF;
400
401     INSERT INTO dbms_stats.backup_history(time, unit, comment)
402         VALUES (current_timestamp, unit_type, $3)
403         RETURNING dbms_stats.backup(id, $1, set_attnum) INTO backup_id;
404     RETURN backup_id;
405 END;
406 $$
407 LANGUAGE plpgsql;
408
409 CREATE FUNCTION dbms_stats.backup_database_stats(
410     comment text
411 ) RETURNS int8 AS
412 $$
413 SELECT dbms_stats.backup(NULL, NULL, $1)
414 $$
415 LANGUAGE sql;
416
417 CREATE FUNCTION dbms_stats.backup_schema_stats(
418     schemaname text,
419     comment text
420 ) RETURNS int8 AS
421 $$
422 DECLARE
423     backup_id       int8;
424 BEGIN
425     IF NOT EXISTS(SELECT * FROM pg_namespace WHERE nspname = $1 FOR SHARE)
426     THEN
427         RAISE EXCEPTION 'schema "%" not found', $1;
428     END IF;
429     IF dbms_stats.is_system_schema($1) THEN
430         RAISE EXCEPTION 'backing up statistics is inhibited for system schemas: "%"', $1;
431     END IF;
432
433     INSERT INTO dbms_stats.backup_history(time, unit, comment)
434         VALUES (current_timestamp, 's', comment)
435         RETURNING id INTO backup_id;
436
437     PERFORM dbms_stats.backup(backup_id, cn.oid, NULL)
438       FROM (SELECT c.oid
439               FROM pg_catalog.pg_class c,
440                    pg_catalog.pg_namespace n
441              WHERE n.nspname = schemaname
442                AND c.relnamespace = n.oid
443                AND dbms_stats.is_target_relkind(c.relkind)
444              ORDER BY c.oid
445            ) cn;
446
447     RETURN backup_id;
448 END;
449 $$
450 LANGUAGE plpgsql;
451
452 CREATE FUNCTION dbms_stats.backup_table_stats(
453     relid regclass,
454     comment text
455 ) RETURNS int8 AS
456 $$
457 SELECT dbms_stats.backup($1, NULL, $2)
458 $$
459 LANGUAGE sql;
460
461 CREATE FUNCTION dbms_stats.backup_table_stats(
462     schemaname text,
463     tablename text,
464     comment text
465 ) RETURNS int8 AS
466 $$
467 SELECT dbms_stats.backup(dbms_stats.relname($1, $2)::regclass, NULL, $3)
468 $$
469 LANGUAGE sql;
470
471 CREATE FUNCTION dbms_stats.backup_column_stats(
472     relid regclass,
473     attname text,
474     comment text
475 ) RETURNS int8 AS
476 $$
477 SELECT dbms_stats.backup($1, $2, $3)
478 $$
479 LANGUAGE sql;
480
481 CREATE FUNCTION dbms_stats.backup_column_stats(
482     schemaname text,
483     tablename text,
484     attname text,
485     comment text
486 ) RETURNS int8 AS
487 $$
488 SELECT dbms_stats.backup(dbms_stats.relname($1, $2)::regclass, $3, $4)
489 $$
490 LANGUAGE sql;
491
492 --
493 -- RESTORE_STATS: Statistics restore functions
494 --
495 CREATE FUNCTION dbms_stats.restore(
496     backup_id int8,
497     relid regclass DEFAULT NULL,
498     attname text DEFAULT NULL
499 ) RETURNS SETOF regclass AS
500 $$
501 DECLARE
502     restore_id      int8;
503     restore_relid   regclass;
504     restore_attnum  int2;
505     set_attnum      int2;
506     restore_attname text;
507     restore_type    regtype;
508     cur_type        regtype;
509 BEGIN
510     IF $1 IS NULL THEN
511         RAISE EXCEPTION 'backup id required';
512     END IF;
513     IF $2 IS NULL AND $3 IS NOT NULL THEN
514         RAISE EXCEPTION 'relation required';
515     END IF;
516     IF NOT EXISTS(SELECT * FROM dbms_stats.backup_history
517                            WHERE id <= $1 FOR SHARE) THEN
518         RAISE EXCEPTION 'backup id % not found', $1;
519     END IF;
520     IF $2 IS NOT NULL THEN
521         IF NOT EXISTS(SELECT * FROM pg_catalog.pg_class
522                                WHERE oid = $2 FOR SHARE) THEN
523             RAISE EXCEPTION 'relation "%" not found', $2;
524         END IF;
525                 -- Grabbing all backups for the relation which is not used in restore.
526         IF NOT EXISTS(SELECT * FROM dbms_stats.relation_stats_backup b
527                        WHERE b.id <= $1 AND b.relid = $2 FOR SHARE) THEN
528             RAISE EXCEPTION 'statistics of relation "%" not found in any backups before backup id = %', $2, $1;
529         END IF;
530         IF $3 IS NOT NULL THEN
531             SELECT a.attnum INTO set_attnum FROM pg_catalog.pg_attribute a
532              WHERE a.attrelid = $2 AND a.attname = $3;
533             IF set_attnum IS NULL THEN
534                                 RAISE EXCEPTION 'column "%" not found in relation %', $3, $2;
535             END IF;
536             IF NOT EXISTS(SELECT * FROM dbms_stats.column_stats_backup WHERE id <= $1 AND starelid = $2 AND staattnum = set_attnum) THEN
537                 RAISE EXCEPTION 'statistics of column "%" of relation "%" are not found in any backups before backup id = %',$3, $2, $1;
538             END IF;
539         END IF;
540                 PERFORM * FROM dbms_stats.relation_stats_locked r
541                   WHERE r.relid = $2 FOR UPDATE;
542     ELSE
543                 /* Lock the whole relation stats if relation is not specified.*/
544             LOCK dbms_stats.relation_stats_locked IN EXCLUSIVE MODE;
545     END IF;
546
547     FOR restore_id, restore_relid IN
548           SELECT max(id), coid FROM
549         (SELECT b.id as id, c.oid as coid
550            FROM pg_class c, dbms_stats.relation_stats_backup b
551           WHERE (c.oid = $2 OR $2 IS NULL)
552             AND c.oid = b.relid
553             AND dbms_stats.is_target_relkind(c.relkind)
554             AND NOT dbms_stats.is_system_catalog(c.oid)
555             AND b.id <= $1
556          FOR SHARE) t
557       GROUP BY coid
558       ORDER BY coid::regclass::text
559     LOOP
560         UPDATE dbms_stats.relation_stats_locked r
561            SET relid = b.relid,
562                relname = b.relname,
563                relpages = b.relpages,
564                reltuples = b.reltuples,
565                relallvisible = b.relallvisible,
566                curpages = b.curpages,
567                last_analyze = b.last_analyze,
568                last_autoanalyze = b.last_autoanalyze
569           FROM dbms_stats.relation_stats_backup b
570          WHERE r.relid = restore_relid
571            AND b.id = restore_id
572            AND b.relid = restore_relid;
573         IF NOT FOUND THEN
574             INSERT INTO dbms_stats.relation_stats_locked
575             SELECT b.relid,
576                    b.relname,
577                    b.relpages,
578                    b.reltuples,
579                    b.relallvisible,
580                    b.curpages,
581                    b.last_analyze,
582                    b.last_autoanalyze
583               FROM dbms_stats.relation_stats_backup b
584              WHERE b.id = restore_id
585                AND b.relid = restore_relid;
586         END IF;
587         RETURN NEXT restore_relid;
588     END LOOP;
589
590     FOR restore_id, restore_relid, restore_attnum, restore_type, cur_type IN
591         SELECT t.id, t.oid, t.attnum, b.statypid, a.atttypid
592           FROM pg_attribute a,
593                dbms_stats.column_stats_backup b,
594                (SELECT max(b.id) AS id, c.oid, a.attnum
595                   FROM pg_class c, pg_attribute a, dbms_stats.column_stats_backup b
596                  WHERE (c.oid = $2 OR $2 IS NULL)
597                    AND c.oid = a.attrelid
598                    AND c.oid = b.starelid
599                    AND (a.attnum = set_attnum OR set_attnum IS NULL)
600                    AND a.attnum = b.staattnum
601                    AND NOT a.attisdropped
602                    AND dbms_stats.is_target_relkind(c.relkind)
603                    AND b.id <= $1
604                  GROUP BY c.oid, a.attnum) t
605          WHERE a.attrelid = t.oid
606            AND a.attnum = t.attnum
607            AND b.id = t.id
608            AND b.starelid = t.oid
609            AND b.staattnum = t.attnum
610     LOOP
611         IF restore_type <> cur_type THEN
612             SELECT a.attname INTO restore_attname
613               FROM pg_catalog.pg_attribute a
614              WHERE a.attrelid = restore_relid
615                AND a.attnum = restore_attnum;
616             RAISE WARNING 'data type of column "%.%" is inconsistent between database(%) and backup (%). Skip.',
617                 restore_relid, restore_attname, cur_type, restore_type;
618         ELSE
619             DELETE FROM dbms_stats.column_stats_locked
620              WHERE starelid = restore_relid
621                AND staattnum = restore_attnum;
622             INSERT INTO dbms_stats.column_stats_locked
623                 SELECT starelid, staattnum, stainherit,
624                        stanullfrac, stawidth, stadistinct,
625                        stakind1, stakind2, stakind3, stakind4, stakind5,
626                        staop1, staop2, staop3, staop4, staop5,
627                        stacoll1, stacoll2, stacoll3, stacoll4, stacoll5,
628                        stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5,
629                        stavalues1, stavalues2, stavalues3, stavalues4, stavalues5
630                   FROM dbms_stats.column_stats_backup
631                  WHERE id = restore_id
632                    AND starelid = restore_relid
633                    AND staattnum = restore_attnum;
634         END IF;
635     END LOOP;
636 EXCEPTION
637   WHEN unique_violation THEN
638     RAISE EXCEPTION 'This operation is canceled by simultaneous lock or restore operation on the same relation.';
639 END;
640 $$
641 LANGUAGE plpgsql;
642
643 CREATE FUNCTION dbms_stats.restore_database_stats(
644     as_of_timestamp timestamp with time zone
645 ) RETURNS SETOF regclass AS
646 $$
647 SELECT dbms_stats.restore(m.id, m.relid)
648   FROM (SELECT max(id) AS id, relid
649         FROM (SELECT r.id, r.relid
650               FROM pg_class c, dbms_stats.relation_stats_backup r,
651                    dbms_stats.backup_history b
652               WHERE c.oid = r.relid
653                 AND r.id = b.id
654                 AND b.time <= $1
655               FOR SHARE) t1
656         GROUP BY t1.relid
657         ORDER BY t1.relid) m;
658 $$
659 LANGUAGE sql STRICT;
660
661 CREATE FUNCTION dbms_stats.restore_schema_stats(
662     schemaname text,
663     as_of_timestamp timestamp with time zone
664 ) RETURNS SETOF regclass AS
665 $$
666 BEGIN
667     IF NOT EXISTS(SELECT * FROM pg_namespace WHERE nspname = $1) THEN
668         RAISE EXCEPTION 'schema "%" not found', $1;
669     END IF;
670     IF dbms_stats.is_system_schema($1) THEN
671         RAISE EXCEPTION 'restoring statistics is inhibited for system schemas: "%"', $1;
672     END IF;
673
674     RETURN QUERY
675         SELECT dbms_stats.restore(m.id, m.relid)
676           FROM (SELECT max(id) AS id, relid
677                 FROM (SELECT r.id, r.relid
678                       FROM pg_class c, pg_namespace n,
679                            dbms_stats.relation_stats_backup r,
680                            dbms_stats.backup_history b
681                       WHERE c.oid = r.relid
682                         AND c.relnamespace = n.oid
683                         AND n.nspname = $1
684                         AND r.id = b.id
685                         AND b.time <= $2
686                                         FOR SHARE) t1
687                 GROUP BY t1.relid
688                 ORDER BY t1.relid) m;
689 END;
690 $$
691 LANGUAGE plpgsql STRICT;
692
693 CREATE FUNCTION dbms_stats.restore_table_stats(
694     relid regclass,
695     as_of_timestamp timestamp with time zone
696 ) RETURNS SETOF regclass AS
697 $$
698 SELECT dbms_stats.restore(max(id), $1, NULL)
699   FROM dbms_stats.backup_history WHERE time <= $2
700 $$
701 LANGUAGE sql STRICT;
702
703 CREATE FUNCTION dbms_stats.restore_table_stats(
704     schemaname text,
705     tablename text,
706     as_of_timestamp timestamp with time zone
707 ) RETURNS SETOF regclass AS
708 $$
709 SELECT dbms_stats.restore_table_stats(dbms_stats.relname($1, $2)::regclass, $3)
710 $$
711 LANGUAGE sql STRICT;
712
713 CREATE FUNCTION dbms_stats.restore_column_stats(
714     relid regclass,
715     attname text,
716     as_of_timestamp timestamp with time zone
717 ) RETURNS SETOF regclass AS
718 $$
719 SELECT dbms_stats.restore(max(id), $1, $2)
720   FROM dbms_stats.backup_history WHERE time <= $3
721 $$
722 LANGUAGE sql STRICT;
723
724 CREATE FUNCTION dbms_stats.restore_column_stats(
725     schemaname text,
726     tablename text,
727     attname text,
728     as_of_timestamp timestamp with time zone
729 ) RETURNS SETOF regclass AS
730 $$
731 SELECT dbms_stats.restore(max(id), dbms_stats.relname($1, $2)::regclass, $3)
732   FROM dbms_stats.backup_history WHERE time <= $4
733 $$
734 LANGUAGE sql STRICT;
735
736 CREATE FUNCTION dbms_stats.restore_stats(
737     backup_id int8
738 ) RETURNS SETOF regclass AS
739 $$
740 DECLARE
741     restore_relid   regclass;
742     restore_attnum  int2;
743     restore_attname text;
744     restore_type    regtype;
745     cur_type        regtype;
746 BEGIN
747     IF NOT EXISTS(SELECT * FROM dbms_stats.backup_history WHERE id = $1) THEN
748         RAISE EXCEPTION 'backup id % not found', $1;
749     END IF;
750
751     /* Lock the backup */
752     PERFORM * from dbms_stats.relation_stats_backup b
753         WHERE  id = $1 FOR SHARE;
754
755         /* Locking only relation_stats_locked is sufficient */
756     LOCK dbms_stats.relation_stats_locked IN EXCLUSIVE MODE;
757
758     FOR restore_relid IN
759         SELECT b.relid
760           FROM pg_class c
761           JOIN dbms_stats.relation_stats_backup b ON (c.oid = b.relid)
762          WHERE b.id = $1
763          ORDER BY c.oid::regclass::text
764     LOOP
765         UPDATE dbms_stats.relation_stats_locked r
766            SET relid = b.relid,
767                relname = b.relname,
768                relpages = b.relpages,
769                reltuples = b.reltuples,
770                relallvisible = b.relallvisible,
771                curpages = b.curpages,
772                last_analyze = b.last_analyze,
773                last_autoanalyze = b.last_autoanalyze
774           FROM dbms_stats.relation_stats_backup b
775          WHERE r.relid = restore_relid
776            AND b.id = $1
777            AND b.relid = restore_relid;
778         IF NOT FOUND THEN
779             INSERT INTO dbms_stats.relation_stats_locked
780             SELECT b.relid,
781                    b.relname,
782                    b.relpages,
783                    b.reltuples,
784                    b.relallvisible,
785                    b.curpages,
786                    b.last_analyze,
787                    b.last_autoanalyze
788               FROM dbms_stats.relation_stats_backup b
789              WHERE b.id = $1
790                AND b.relid = restore_relid;
791         END IF;
792         RETURN NEXT restore_relid;
793     END LOOP;
794
795     FOR restore_relid, restore_attnum, restore_type, cur_type  IN
796         SELECT c.oid, a.attnum, b.statypid, a.atttypid
797           FROM pg_class c
798           JOIN dbms_stats.column_stats_backup b ON (c.oid = b.starelid)
799           JOIN pg_attribute a ON (b.starelid = attrelid
800                               AND b.staattnum = a.attnum)
801          WHERE b.id = $1
802     LOOP
803         IF restore_type <> cur_type THEN
804             SELECT attname INTO restore_attname
805               FROM pg_catalog.pg_attribute
806              WHERE attrelid = restore_relid
807                AND attnum = restore_attnum;
808             RAISE WARNING 'data type of column "%.%" is inconsistent between database(%) and backup (%). Skip.',
809                 restore_relid, restore_attname, cur_type, restore_type;
810         ELSE
811             DELETE FROM dbms_stats.column_stats_locked
812              WHERE starelid = restore_relid
813                AND staattnum = restore_attnum;
814             INSERT INTO dbms_stats.column_stats_locked
815                 SELECT starelid, staattnum, stainherit,
816                        stanullfrac, stawidth, stadistinct,
817                        stakind1, stakind2, stakind3, stakind4, stakind5,
818                        staop1, staop2, staop3, staop4, staop5,
819                        stacoll1, stacoll2, stacoll3, stacoll4, stacoll5,
820                        stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5,
821                        stavalues1, stavalues2, stavalues3, stavalues4, stavalues5
822                   FROM dbms_stats.column_stats_backup
823                  WHERE id = $1
824                    AND starelid = restore_relid
825                    AND staattnum = restore_attnum;
826         END IF;
827     END LOOP;
828
829 END;
830 $$
831 LANGUAGE plpgsql STRICT;
832
833 --
834 -- LOCK_STATS: Statistics lock functions
835 --
836
837 CREATE FUNCTION dbms_stats.lock(
838     relid regclass,
839     attname text
840 ) RETURNS regclass AS
841 $$
842 DECLARE
843     lock_relkind "char";
844     set_attnum   int2;
845     r            record;
846 BEGIN
847     IF $1 IS NULL THEN
848         RAISE EXCEPTION 'relation required';
849     END IF;
850     IF $2 IS NULL THEN
851         RETURN dbms_stats.lock($1);
852     END IF;
853     SELECT relkind INTO lock_relkind FROM pg_catalog.pg_class WHERE oid = $1;
854     IF NOT FOUND THEN
855         RAISE EXCEPTION 'relation "%" not found', $1;
856     END IF;
857     IF NOT dbms_stats.is_target_relkind(lock_relkind) THEN
858         RAISE EXCEPTION '"%" must be a table or an index', $1;
859     END IF;
860     IF EXISTS(SELECT * FROM pg_catalog.pg_index WHERE lock_relkind = 'i' AND indexrelid = $1 AND indexprs IS NULL) THEN
861         RAISE EXCEPTION '"%" must be an expression index', $1;
862     END IF;
863     IF dbms_stats.is_system_catalog($1) THEN
864                 RAISE EXCEPTION 'locking statistics is inhibited for system catalogs: "%"', $1;
865     END IF;
866     SELECT a.attnum INTO set_attnum FROM pg_catalog.pg_attribute a
867      WHERE a.attrelid = $1 AND a.attname = $2;
868     IF set_attnum IS NULL THEN
869         RAISE EXCEPTION 'column "%" not found in relation "%"', $2, $1;
870     END IF;
871
872         /*
873          * If we don't have per-table statistics, create new one which has NULL for
874          * every statistic value for column_stats_effective.
875          */
876     IF NOT EXISTS(SELECT * FROM dbms_stats.relation_stats_locked ru
877                    WHERE ru.relid = $1 FOR SHARE) THEN
878         INSERT INTO dbms_stats.relation_stats_locked
879             SELECT $1, dbms_stats.relname(nspname, relname),
880                    NULL, NULL, NULL, NULL, NULL
881               FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n
882              WHERE c.relnamespace = n.oid
883                AND c.oid = $1;
884     END IF;
885
886         /*
887          * Process for per-column statistics
888          */
889     FOR r IN
890         SELECT stainherit, stanullfrac, stawidth, stadistinct,
891                stakind1, stakind2, stakind3, stakind4, stakind5,
892                staop1, staop2, staop3, staop4, staop5,
893                stacoll1, stacoll2, stacoll3, stacoll4, stacoll5,
894                stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5,
895                stavalues1, stavalues2, stavalues3, stavalues4, stavalues5
896           FROM dbms_stats.column_stats_effective
897          WHERE starelid = $1
898            AND staattnum = set_attnum
899     LOOP
900         UPDATE dbms_stats.column_stats_locked c
901            SET stanullfrac = r.stanullfrac,
902                stawidth = r.stawidth,
903                stadistinct = r.stadistinct,
904                stakind1 = r.stakind1,
905                stakind2 = r.stakind2,
906                stakind3 = r.stakind3,
907                stakind4 = r.stakind4,
908                stakind5 = r.stakind5,
909                staop1 = r.staop1,
910                staop2 = r.staop2,
911                staop3 = r.staop3,
912                staop4 = r.staop4,
913                staop5 = r.staop5,
914                stacoll1 = r.stacoll1,
915                stacoll2 = r.stacoll2,
916                stacoll3 = r.stacoll3,
917                stacoll4 = r.stacoll4,
918                stacoll5 = r.stacoll5,
919                stanumbers1 = r.stanumbers1,
920                stanumbers2 = r.stanumbers2,
921                stanumbers3 = r.stanumbers3,
922                stanumbers4 = r.stanumbers4,
923                stanumbers5 = r.stanumbers5,
924                stavalues1 = r.stavalues1,
925                stavalues2 = r.stavalues2,
926                stavalues3 = r.stavalues3,
927                stavalues4 = r.stavalues4,
928                stavalues5 = r.stavalues5
929          WHERE c.starelid = $1
930            AND c.staattnum = set_attnum
931            AND c.stainherit = r.stainherit;
932
933         IF NOT FOUND THEN
934             INSERT INTO dbms_stats.column_stats_locked
935                  VALUES ($1,
936                          set_attnum,
937                          r.stainherit,
938                          r.stanullfrac,
939                          r.stawidth,
940                          r.stadistinct,
941                          r.stakind1,
942                          r.stakind2,
943                          r.stakind3,
944                          r.stakind4,
945                          r.stakind5,
946                          r.staop1,
947                          r.staop2,
948                          r.staop3,
949                          r.staop4,
950                          r.staop5,
951                          r.stacoll1,
952                          r.stacoll2,
953                          r.stacoll3,
954                          r.stacoll4,
955                          r.stacoll5,
956                          r.stanumbers1,
957                          r.stanumbers2,
958                          r.stanumbers3,
959                          r.stanumbers4,
960                          r.stanumbers5,
961                          r.stavalues1,
962                          r.stavalues2,
963                          r.stavalues3,
964                          r.stavalues4,
965                          r.stavalues5);
966         END IF;
967         END LOOP;
968
969                 /* If we don't have statistics at all, raise error. */
970         IF NOT FOUND THEN
971                         RAISE EXCEPTION 'no statistics available for column "%" of relation "%"', $2, $1::regclass;
972                 END IF;
973
974     RETURN $1;
975 EXCEPTION
976   WHEN unique_violation THEN
977     RAISE EXCEPTION 'This operation is canceled by simultaneous lock or restore operation on the same relation.';
978 END;
979 $$
980 LANGUAGE plpgsql;
981
982 CREATE FUNCTION dbms_stats.lock(relid regclass)
983     RETURNS regclass AS
984 $$
985 DECLARE
986     lock_relkind "char";
987     i            record;
988 BEGIN
989     IF $1 IS NULL THEN
990         RAISE EXCEPTION 'relation required';
991     END IF;
992     SELECT relkind INTO lock_relkind FROM pg_catalog.pg_class WHERE oid = $1;
993     IF NOT FOUND THEN
994         RAISE EXCEPTION 'relation "%" not found', $1;
995     END IF;
996     IF NOT dbms_stats.is_target_relkind(lock_relkind) THEN
997         RAISE EXCEPTION 'locking statistics is not allowed for relations with relkind "%": "%"', lock_relkind, $1
998                         USING HINT = 'Only tables(r, m, f) and indexes(i) are lockable.';
999     END IF;
1000     IF dbms_stats.is_system_catalog($1) THEN
1001                 RAISE EXCEPTION 'locking statistics is not allowed for system catalogs: "%"', $1;
1002     END IF;
1003
1004     UPDATE dbms_stats.relation_stats_locked r
1005        SET relname = dbms_stats.relname(nspname, c.relname),
1006            relpages = v.relpages,
1007            reltuples = v.reltuples,
1008            relallvisible = v.relallvisible,
1009            curpages = v.curpages,
1010            last_analyze = v.last_analyze,
1011            last_autoanalyze = v.last_autoanalyze
1012       FROM pg_catalog.pg_class c,
1013            pg_catalog.pg_namespace n,
1014            dbms_stats.relation_stats_effective v
1015      WHERE r.relid = $1
1016        AND c.oid = $1
1017        AND c.relnamespace = n.oid
1018        AND v.relid = $1;
1019     IF NOT FOUND THEN
1020         INSERT INTO dbms_stats.relation_stats_locked
1021         SELECT $1, dbms_stats.relname(nspname, c.relname),
1022                v.relpages, v.reltuples, v.relallvisible, v.curpages,
1023                v.last_analyze, v.last_autoanalyze
1024           FROM pg_catalog.pg_class c,
1025                pg_catalog.pg_namespace n,
1026                dbms_stats.relation_stats_effective v
1027          WHERE c.oid = $1
1028            AND c.relnamespace = n.oid
1029            AND v.relid = $1;
1030     END IF;
1031
1032     IF EXISTS(SELECT *
1033                 FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_index ind
1034                   ON c.oid = ind.indexrelid
1035                WHERE c.oid = $1
1036                  AND c.relkind = 'i'
1037                  AND ind.indexprs IS NULL) THEN
1038         RETURN $1;
1039     END IF;
1040
1041     FOR i IN
1042         SELECT staattnum, stainherit, stanullfrac,
1043                stawidth, stadistinct,
1044                stakind1, stakind2, stakind3, stakind4, stakind5,
1045                staop1, staop2, staop3, staop4, staop5,
1046                stacoll1, stacoll2, stacoll3, stacoll4, stacoll5,
1047                stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5,
1048                stavalues1, stavalues2, stavalues3, stavalues4, stavalues5
1049           FROM dbms_stats.column_stats_effective
1050          WHERE starelid = $1
1051     LOOP
1052         UPDATE dbms_stats.column_stats_locked c
1053            SET stanullfrac = i.stanullfrac,
1054                stawidth = i.stawidth,
1055                stadistinct = i.stadistinct,
1056                stakind1 = i.stakind1,
1057                stakind2 = i.stakind2,
1058                stakind3 = i.stakind3,
1059                stakind4 = i.stakind4,
1060                stakind5 = i.stakind5,
1061                staop1 = i.staop1,
1062                staop2 = i.staop2,
1063                staop3 = i.staop3,
1064                staop4 = i.staop4,
1065                staop5 = i.staop5,
1066                stacoll1 = i.stacoll1,
1067                stacoll2 = i.stacoll2,
1068                stacoll3 = i.stacoll3,
1069                stacoll4 = i.stacoll4,
1070                stacoll5 = i.stacoll5,
1071                stanumbers1 = i.stanumbers1,
1072                stanumbers2 = i.stanumbers2,
1073                stanumbers3 = i.stanumbers3,
1074                stanumbers4 = i.stanumbers4,
1075                stanumbers5 = i.stanumbers5,
1076                stavalues1 = i.stavalues1,
1077                stavalues2 = i.stavalues2,
1078                stavalues3 = i.stavalues3,
1079                stavalues4 = i.stavalues4,
1080                stavalues5 = i.stavalues5
1081          WHERE c.starelid = $1
1082            AND c.staattnum = i.staattnum
1083            AND c.stainherit = i.stainherit;
1084
1085         IF NOT FOUND THEN
1086             INSERT INTO dbms_stats.column_stats_locked
1087                  VALUES ($1,
1088                          i.staattnum,
1089                          i.stainherit,
1090                          i.stanullfrac,
1091                          i.stawidth,
1092                          i.stadistinct,
1093                          i.stakind1,
1094                          i.stakind2,
1095                          i.stakind3,
1096                          i.stakind4,
1097                          i.stakind5,
1098                          i.staop1,
1099                          i.staop2,
1100                          i.staop3,
1101                          i.staop4,
1102                          i.staop5,
1103                          i.stacoll1,
1104                          i.stacoll2,
1105                          i.stacoll3,
1106                          i.stacoll4,
1107                          i.stacoll5,
1108                          i.stanumbers1,
1109                          i.stanumbers2,
1110                          i.stanumbers3,
1111                          i.stanumbers4,
1112                          i.stanumbers5,
1113                          i.stavalues1,
1114                          i.stavalues2,
1115                          i.stavalues3,
1116                          i.stavalues4,
1117                          i.stavalues5);
1118             END IF;
1119         END LOOP;
1120
1121     RETURN $1;
1122 EXCEPTION
1123   WHEN unique_violation THEN
1124     RAISE EXCEPTION 'This operation is canceled by simultaneous lock operation on the same relation.';
1125 END;
1126 $$
1127 LANGUAGE plpgsql;
1128
1129 CREATE FUNCTION dbms_stats.lock_database_stats()
1130   RETURNS SETOF regclass AS
1131 $$
1132 SELECT dbms_stats.lock(c.oid)
1133   FROM (SELECT oid
1134           FROM pg_catalog.pg_class
1135          WHERE NOT dbms_stats.is_system_catalog(oid)
1136            AND dbms_stats.is_target_relkind(relkind)
1137          ORDER BY pg_class.oid
1138        ) c;
1139 $$
1140 LANGUAGE sql;
1141
1142 CREATE FUNCTION dbms_stats.lock_schema_stats(
1143     schemaname text
1144 ) RETURNS SETOF regclass AS
1145 $$
1146 BEGIN
1147     IF NOT EXISTS(SELECT * FROM pg_namespace WHERE nspname = $1) THEN
1148         RAISE EXCEPTION 'schema "%" not found', $1;
1149     END IF;
1150     IF dbms_stats.is_system_schema($1) THEN
1151         RAISE EXCEPTION 'locking statistics is not allowed for system schemas: "%"', $1;
1152     END IF;
1153
1154     RETURN QUERY
1155         SELECT dbms_stats.lock(cn.oid)
1156           FROM (SELECT c.oid
1157                   FROM pg_class c, pg_namespace n
1158                  WHERE c.relnamespace = n.oid
1159                    AND dbms_stats.is_target_relkind(c.relkind)
1160                    AND n.nspname = $1
1161                  ORDER BY c.oid
1162                ) cn;
1163 END;
1164 $$
1165 LANGUAGE plpgsql STRICT;
1166
1167 CREATE FUNCTION dbms_stats.lock_table_stats(relid regclass)
1168   RETURNS regclass AS
1169 $$
1170 SELECT dbms_stats.lock($1)
1171 $$
1172 LANGUAGE sql STRICT;
1173
1174 CREATE FUNCTION dbms_stats.lock_table_stats(
1175     schemaname text,
1176     tablename text
1177 ) RETURNS regclass AS
1178 $$
1179 SELECT dbms_stats.lock(dbms_stats.relname($1, $2)::regclass)
1180 $$
1181 LANGUAGE sql STRICT;
1182
1183 CREATE FUNCTION dbms_stats.lock_column_stats(
1184     relid regclass,
1185     attname text
1186 ) RETURNS regclass AS
1187 $$
1188 SELECT dbms_stats.lock($1, $2)
1189 $$
1190 LANGUAGE sql STRICT;
1191
1192 CREATE FUNCTION dbms_stats.lock_column_stats(
1193     schemaname text,
1194     tablename text,
1195     attname text
1196 ) RETURNS regclass AS
1197 $$
1198 SELECT dbms_stats.lock(dbms_stats.relname($1, $2)::regclass, $3)
1199 $$
1200 LANGUAGE sql STRICT;
1201
1202 --
1203 -- UNLOCK_STATS: Statistics unlock functions
1204 --
1205
1206 CREATE FUNCTION dbms_stats.unlock(
1207     relid regclass DEFAULT NULL,
1208     attname text DEFAULT NULL
1209 ) RETURNS SETOF regclass AS
1210 $$
1211 DECLARE
1212     set_attnum int2;
1213     unlock_id  int8;
1214 BEGIN
1215     IF $1 IS NULL AND $2 IS NOT NULL THEN
1216         RAISE EXCEPTION 'relation required';
1217     END IF;
1218
1219         /*
1220          * Lock the target relation to prevent conflicting with stats lock/restore
1221      */
1222         PERFORM * FROM dbms_stats.relation_stats_locked ru
1223          WHERE (ru.relid = $1 OR $1 IS NULL) FOR UPDATE;
1224
1225     SELECT a.attnum INTO set_attnum FROM pg_catalog.pg_attribute a
1226      WHERE a.attrelid = $1 AND a.attname = $2;
1227     IF $2 IS NOT NULL AND set_attnum IS NULL THEN
1228         RAISE EXCEPTION 'column "%" not found in relation "%"', $2, $1;
1229     END IF;
1230
1231     DELETE FROM dbms_stats.column_stats_locked
1232      WHERE (starelid = $1 OR $1 IS NULL)
1233        AND (staattnum = set_attnum OR $2 IS NULL);
1234
1235     IF $1 IS NOT NULL AND $2 IS NOT NULL THEN
1236         RETURN QUERY
1237             SELECT $1;
1238     END IF;
1239     FOR unlock_id IN
1240         SELECT ru.relid
1241           FROM dbms_stats.relation_stats_locked ru
1242          WHERE (ru.relid = $1 OR $1 IS NULL) AND ($2 IS NULL)
1243          ORDER BY ru.relid
1244     LOOP
1245         DELETE FROM dbms_stats.relation_stats_locked ru
1246          WHERE ru.relid = unlock_id;
1247         RETURN NEXT unlock_id;
1248     END LOOP;
1249 END;
1250 $$
1251 LANGUAGE plpgsql;
1252
1253 CREATE FUNCTION dbms_stats.unlock_database_stats()
1254   RETURNS SETOF regclass AS
1255 $$
1256 DECLARE
1257     unlock_id int8;
1258 BEGIN
1259     LOCK dbms_stats.relation_stats_locked IN EXCLUSIVE MODE;
1260
1261     FOR unlock_id IN
1262         SELECT relid
1263           FROM dbms_stats.relation_stats_locked
1264          ORDER BY relid
1265     LOOP
1266         DELETE FROM dbms_stats.relation_stats_locked
1267          WHERE relid = unlock_id;
1268         RETURN NEXT unlock_id;
1269     END LOOP;
1270 END;
1271 $$
1272 LANGUAGE plpgsql STRICT;
1273
1274 CREATE FUNCTION dbms_stats.unlock_schema_stats(
1275     schemaname text
1276 ) RETURNS SETOF regclass AS
1277 $$
1278 DECLARE
1279     unlock_id int8;
1280 BEGIN
1281     IF NOT EXISTS(SELECT * FROM pg_namespace WHERE nspname = $1) THEN
1282         RAISE EXCEPTION 'schema "%" not found', $1;
1283     END IF;
1284     IF dbms_stats.is_system_schema($1) THEN
1285         RAISE EXCEPTION 'unlocking statistics is not allowed for system schemas: "%"', $1;
1286     END IF;
1287
1288     FOR unlock_id IN
1289         SELECT r.relid
1290           FROM dbms_stats.relation_stats_locked r, pg_class c, pg_namespace n
1291          WHERE relid = c.oid
1292            AND c.relnamespace = n.oid
1293            AND n.nspname = $1
1294          ORDER BY relid
1295          FOR UPDATE
1296     LOOP
1297         DELETE FROM dbms_stats.relation_stats_locked
1298          WHERE relid = unlock_id;
1299         RETURN NEXT unlock_id;
1300     END LOOP;
1301 END;
1302 $$
1303 LANGUAGE plpgsql STRICT;
1304
1305 CREATE FUNCTION dbms_stats.unlock_table_stats(relid regclass)
1306   RETURNS SETOF regclass AS
1307 $$
1308 DELETE FROM dbms_stats.relation_stats_locked
1309  WHERE relid = $1
1310  RETURNING relid::regclass
1311 $$
1312 LANGUAGE sql STRICT;
1313
1314 CREATE FUNCTION dbms_stats.unlock_table_stats(
1315     schemaname text,
1316     tablename text
1317 ) RETURNS SETOF regclass AS
1318 $$
1319 DELETE FROM dbms_stats.relation_stats_locked
1320  WHERE relid = dbms_stats.relname($1, $2)::regclass
1321  RETURNING relid::regclass
1322 $$
1323 LANGUAGE sql STRICT;
1324
1325 CREATE FUNCTION dbms_stats.unlock_column_stats(
1326     relid regclass,
1327     attname text
1328 ) RETURNS SETOF regclass AS
1329 $$
1330 DECLARE
1331     set_attnum int2;
1332 BEGIN
1333     SELECT a.attnum INTO set_attnum FROM pg_catalog.pg_attribute a
1334      WHERE a.attrelid = $1 AND a.attname = $2;
1335     IF $2 IS NOT NULL AND set_attnum IS NULL THEN
1336         RAISE EXCEPTION 'column "%" not found in relation "%"', $2, $1;
1337     END IF;
1338
1339         /* Lock the locked table stats */
1340     PERFORM * from dbms_stats.relation_stats_locked r
1341         WHERE r.relid = $1 FOR SHARE;
1342
1343     DELETE FROM dbms_stats.column_stats_locked
1344       WHERE starelid = $1
1345         AND staattnum = set_attnum;
1346
1347     RETURN QUERY
1348         SELECT $1;
1349 END;
1350 $$
1351 LANGUAGE plpgsql STRICT;
1352
1353 CREATE FUNCTION dbms_stats.unlock_column_stats(
1354     schemaname text,
1355     tablename text,
1356     attname text
1357 ) RETURNS SETOF regclass AS
1358 $$
1359 DECLARE
1360     set_attnum int2;
1361 BEGIN
1362     SELECT a.attnum INTO set_attnum FROM pg_catalog.pg_attribute a
1363      WHERE a.attrelid = dbms_stats.relname($1, $2)::regclass
1364        AND a.attname = $3;
1365     IF $3 IS NOT NULL AND set_attnum IS NULL THEN
1366                 RAISE EXCEPTION 'column "%" not found in relation "%.%"', $3, $1, $2;
1367     END IF;
1368
1369         /* Lock the locked table stats */
1370         PERFORM * from dbms_stats.relation_stats_locked r
1371         WHERE  relid = dbms_stats.relname($1, $2)::regclass FOR SHARE;
1372
1373     DELETE FROM dbms_stats.column_stats_locked
1374       WHERE starelid = dbms_stats.relname($1, $2)::regclass
1375         AND staattnum = set_attnum;
1376
1377     RETURN QUERY
1378         SELECT dbms_stats.relname($1, $2)::regclass;
1379 END;
1380 $$
1381 LANGUAGE plpgsql STRICT;
1382
1383 --
1384 -- IMPORT_STATS: Statistics import functions
1385 --
1386
1387 CREATE FUNCTION dbms_stats.import(
1388     nspname text DEFAULT NULL,
1389     relid regclass DEFAULT NULL,
1390     attname text DEFAULT NULL,
1391     src text DEFAULT NULL
1392 ) RETURNS void AS
1393 'MODULE_PATHNAME', 'dbms_stats_import'
1394 LANGUAGE C;
1395
1396 CREATE FUNCTION dbms_stats.import_database_stats(src text)
1397   RETURNS void AS
1398 $$
1399 SELECT dbms_stats.import(NULL, NULL, NULL, $1)
1400 $$
1401 LANGUAGE sql;
1402
1403 CREATE FUNCTION dbms_stats.import_schema_stats(
1404     schemaname text,
1405     src text
1406 ) RETURNS void AS
1407 $$
1408 SELECT dbms_stats.import($1, NULL, NULL, $2)
1409 $$
1410 LANGUAGE sql;
1411
1412 CREATE FUNCTION dbms_stats.import_table_stats(
1413     relid regclass,
1414     src text
1415 ) RETURNS void AS
1416 $$
1417 SELECT dbms_stats.import(NULL, $1, NULL, $2)
1418 $$
1419 LANGUAGE sql;
1420
1421 CREATE FUNCTION dbms_stats.import_table_stats(
1422     schemaname text,
1423     tablename text,
1424     src text
1425 ) RETURNS void AS
1426 $$
1427 SELECT dbms_stats.import(NULL, dbms_stats.relname($1, $2)::regclass, NULL, $3)
1428 $$
1429 LANGUAGE sql;
1430
1431 CREATE FUNCTION dbms_stats.import_column_stats(
1432     relid regclass,
1433     attname text,
1434     src text
1435 ) RETURNS void AS
1436 $$
1437 SELECT dbms_stats.import(NULL, $1, $2, $3)
1438 $$
1439 LANGUAGE sql;
1440
1441 CREATE FUNCTION dbms_stats.import_column_stats(
1442     schemaname text,
1443     tablename text,
1444     attname text,
1445     src text
1446 ) RETURNS void AS
1447 $$
1448 SELECT dbms_stats.import(NULL, dbms_stats.relname($1, $2)::regclass, $3, $4)
1449 $$
1450 LANGUAGE sql;
1451
1452 --
1453 -- PURGE_STATS: Statistics purge function
1454 --
1455 CREATE FUNCTION dbms_stats.purge_stats(
1456     backup_id int8,
1457     force bool DEFAULT false
1458 ) RETURNS SETOF dbms_stats.backup_history AS
1459 $$
1460 DECLARE
1461     delete_id int8;
1462     todelete   dbms_stats.backup_history;
1463 BEGIN
1464     IF $1 IS NULL THEN
1465         RAISE EXCEPTION 'backup id required';
1466     END IF;
1467     IF $2 IS NULL THEN
1468         RAISE EXCEPTION 'NULL is not allowed as the second parameter';
1469     END IF;
1470
1471     IF NOT EXISTS(SELECT * FROM dbms_stats.backup_history
1472                   WHERE id = $1 FOR UPDATE) THEN
1473         RAISE EXCEPTION 'backup id % not found', $1;
1474     END IF;
1475     IF NOT $2 AND NOT EXISTS(SELECT *
1476                                FROM dbms_stats.backup_history
1477                               WHERE unit = 'd'
1478                                 AND id > $1) THEN
1479         RAISE WARNING 'no database-wide backup will remain after purge'
1480                         USING HINT = 'Give true for second parameter to purge forcibly.';
1481         RETURN;
1482     END IF;
1483
1484     FOR todelete IN
1485         SELECT * FROM dbms_stats.backup_history
1486          WHERE id <= $1
1487          ORDER BY id FOR UPDATE
1488     LOOP
1489         DELETE FROM dbms_stats.backup_history
1490          WHERE id = todelete.id;
1491         RETURN NEXT todelete;
1492     END LOOP;
1493 END;
1494 $$
1495 LANGUAGE plpgsql;
1496
1497 --
1498 -- CLEAN_STATS: Clean orphan dummy statistics
1499 --
1500 CREATE FUNCTION dbms_stats.clean_up_stats() RETURNS SETOF text AS
1501 $$
1502 DECLARE
1503         clean_relid             Oid;
1504         clean_attnum    int2;
1505         clean_inherit   bool;
1506         clean_rel_col   text;
1507 BEGIN
1508         -- We don't have to check that table-level dummy statistics of the table
1509         -- exists here, because the foreign key constraints defined on column-level
1510         -- dummy static table ensures that.
1511         FOR clean_rel_col, clean_relid, clean_attnum, clean_inherit IN
1512                 SELECT r.relname || ', ' || v.staattnum::text,
1513                            v.starelid, v.staattnum, v.stainherit
1514                   FROM dbms_stats.column_stats_locked v
1515                   JOIN dbms_stats.relation_stats_locked r ON (v.starelid = r.relid)
1516                  WHERE NOT EXISTS (
1517                         SELECT NULL
1518                           FROM pg_attribute a
1519                          WHERE a.attrelid = v.starelid
1520                            AND a.attnum = v.staattnum
1521                            AND a.attisdropped  = false
1522          FOR UPDATE
1523                 )
1524         LOOP
1525                 DELETE FROM dbms_stats.column_stats_locked
1526                  WHERE starelid = clean_relid
1527                    AND staattnum = clean_attnum
1528                    AND stainherit = clean_inherit;
1529                 RETURN NEXT clean_rel_col;
1530         END LOOP;
1531
1532         RETURN QUERY
1533                 DELETE FROM dbms_stats.relation_stats_locked r
1534                  WHERE NOT EXISTS (
1535                         SELECT NULL
1536                           FROM pg_class c
1537                          WHERE c.oid = r.relid)
1538                  RETURNING relname || ',';
1539         RETURN;
1540 END
1541 $$
1542 LANGUAGE plpgsql;
1543 --
1544 /*
1545  * Stuff for manipulating statistics
1546  */
1547
1548 /* Primitive functions for tweaking statistics */
1549 CREATE FUNCTION dbms_stats.anyarray_basetype(dbms_stats.anyarray)
1550         RETURNS name
1551         AS 'MODULE_PATHNAME', 'dbms_stats_anyarray_basetype'
1552         LANGUAGE C STABLE;
1553
1554 CREATE FUNCTION dbms_stats.type_is_analyzable(oid) returns bool
1555         AS 'MODULE_PATHNAME', 'dbms_stats_type_is_analyzable'
1556         LANGUAGE C STRICT STABLE;
1557
1558 /*
1559  * Create and drop a cast necessary to set column values of dbms_stats.anyarray
1560  * type.
1561  */
1562 CREATE OR REPLACE FUNCTION dbms_stats.prepare_statstweak(regtype)
1563 RETURNS text AS $$
1564 DECLARE
1565   srctypname varchar;
1566   funcname varchar;
1567   funcdef varchar;
1568   castdef varchar;
1569 BEGIN
1570   srctypname := $1 || '[]';
1571   funcname := 'dbms_stats._' || replace($1::text, ' ', '_') || '_ary_anyarray';
1572   funcdef := funcname || '(' || srctypname || ')';
1573   castdef := '(' || srctypname || ' AS dbms_stats.anyarray)';
1574
1575   IF (NOT dbms_stats.type_is_analyzable($1::regtype)) THEN
1576     RAISE 'the type can not have statistics';
1577   END IF;
1578
1579   EXECUTE 'CREATE FUNCTION ' || funcdef || 
1580           ' RETURNS dbms_stats.anyarray ' ||
1581           ' AS ''pg_dbms_stats'', ''dbms_stats_anyary_anyary'''||
1582           ' LANGUAGE C STRICT IMMUTABLE';
1583   EXECUTE 'CREATE CAST '|| castdef ||
1584               ' WITH FUNCTION ' || funcdef ||
1585               ' AS ASSIGNMENT';
1586   RETURN '(func ' || funcdef || ', cast ' || castdef || ')';
1587 EXCEPTION
1588   WHEN duplicate_function THEN
1589     RAISE 'run dbms_stats.drop_statstweak() for the type before this';
1590 END;
1591 $$ LANGUAGE plpgsql;
1592
1593 CREATE OR REPLACE FUNCTION dbms_stats.drop_statstweak(regtype)
1594 RETURNS text AS $$
1595 DECLARE
1596   srctypname varchar;
1597   funcname varchar;
1598   funcdef varchar;
1599   castdef varchar;
1600 BEGIN
1601   srctypname := $1 || '[]';
1602   funcname := 'dbms_stats._' || replace($1::text, ' ', '_') || '_ary_anyarray';
1603   funcdef := funcname || '(' || srctypname || ')';
1604   castdef := '(' || srctypname || ' AS dbms_stats.anyarray)';
1605
1606   EXECUTE 'DROP CAST ' || castdef;
1607   EXECUTE 'DROP FUNCTION ' || funcdef;
1608   RETURN '(func ' || funcdef || ', cast ' || castdef || ')';
1609 EXCEPTION
1610   WHEN undefined_function OR undefined_object THEN
1611     RAISE 'function % or cast % does not exist', funcdef, castdef;
1612 END;
1613 $$ LANGUAGE plpgsql;