OSDN Git Service

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