-- $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;
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;
