-- $NetBSD: check.sql,v 1.1 2011/10/12 01:05:00 yamt Exp $

-- Copyright (c)2010,2011 YAMAMOTO Takashi,
-- All rights reserved.
--
-- Redistribution and use in source and binary forms, with or without
-- modification, are permitted provided that the following conditions
-- are met:
-- 1. Redistributions of source code must retain the above copyright
--    notice, this list of conditions and the following disclaimer.
-- 2. Redistributions in binary form must reproduce the above copyright
--    notice, this list of conditions and the following disclaimer in the
--    documentation and/or other materials provided with the distribution.
--
-- THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
-- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-- ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
-- FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
-- OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
-- HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
-- LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
-- OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
-- SUCH DAMAGE.

-- filesystem consistency checks.  ie. something like "fsck -n"

BEGIN;
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SET TRANSACTION READ ONLY;
SET search_path TO pgfs;
SELECT count(*) AS "unreferenced files (dirent)"
       FROM file f LEFT JOIN dirent d
       ON f.fileid = d.child_fileid
       WHERE f.fileid <> 1 AND d.child_fileid IS NULL;
SELECT count(*) AS "unreferenced files (nlink)"
       FROM file f
       WHERE f.nlink = 0;
SELECT count(*) AS "regular files without datafork"
       FROM file f LEFT JOIN datafork df
       ON f.fileid = df.fileid
       WHERE df.fileid IS NULL AND f.type IN ('regular', 'link');
SELECT count(*) AS "broken datafork reference"
       FROM file f INNER JOIN datafork df
       ON f.fileid = df.fileid
       WHERE f.type NOT IN ('regular', 'link');
SELECT count(*) AS "unreferenced dataforks"
       FROM file f RIGHT JOIN datafork df
       ON f.fileid = df.fileid
       WHERE f.fileid IS NULL;
SELECT count(*) AS "dataforks without large object"
       FROM datafork df LEFT JOIN pg_largeobject_metadata lm
       ON df.loid = lm.oid
       WHERE lm.oid IS NULL;
SELECT count(*) AS "unreferenced large objects"
       FROM datafork df RIGHT JOIN pg_largeobject_metadata lm
       ON df.loid = lm.oid
       WHERE df.loid IS NULL;
SELECT count(*) AS "dirent broken parent_fileid references"
       FROM dirent d LEFT JOIN file f
       ON d.parent_fileid = f.fileid
       WHERE f.fileid IS NULL OR f.type <> 'directory';
SELECT count(*) AS "dirent broken child_fileid references"
       FROM dirent d LEFT JOIN file f
       ON d.child_fileid = f.fileid
       WHERE f.fileid IS NULL;
SELECT count(*) AS "dirent loops" FROM file f WHERE EXISTS (
       WITH RECURSIVE r AS
       (
                       SELECT d.* FROM dirent d
                               WHERE d.child_fileid = f.fileid
               UNION ALL
                       SELECT d.* FROM dirent d INNER JOIN r
                               ON d.child_fileid = r.parent_fileid
       )
       SELECT * FROM r WHERE r.parent_fileid = f.fileid);
SELECT count(*) AS "broken nlink"
       FROM
       (
       SELECT coalesce(fp.fileid, fc.fileid) AS fileid,
               coalesce(fp.nlink, 0) + coalesce(fc.nlink, 0) +
               CASE
                       WHEN coalesce(fp.fileid, fc.fileid) = 1 THEN 1
                       ELSE 0
               END
               AS nlink
               FROM
               (
               SELECT child_fileid AS fileid, count(*) AS nlink
                       FROM dirent
                       GROUP BY child_fileid
               ) fp
               FULL JOIN
               (
               SELECT count(*) AS nlink, d.parent_fileid AS fileid
                       FROM dirent d
                       JOIN file f
                       ON d.child_fileid = f.fileid
                       WHERE f.type = 'directory'
                       GROUP BY parent_fileid
               ) fc
               ON fp.fileid = fc.fileid
       ) d
       FULL JOIN file f
       ON d.fileid = f.fileid
       WHERE (d.nlink IS NULL AND (f.fileid <> 1 AND f.nlink <> 0))
           OR f.nlink IS NULL
           OR d.nlink <> f.nlink;
COMMIT;