-- -*-Sql-*- mode (to keep my emacs happy)
--
-- API Package Body for Term_Path.
--
-- Scaffold auto-generated by gen-api.pl. gen-api.pl is
-- Copyright 2002-2003 Genomics Institute of the Novartis Research Foundation
-- Copyright 2002-2008 Hilmar Lapp
-- 
--  This file is part of BioSQL.
--
--  BioSQL is free software: you can redistribute it and/or modify it
--  under the terms of the GNU Lesser General Public License as
--  published by the Free Software Foundation, either version 3 of the
--  License, or (at your option) any later version.
--
--  BioSQL is distributed in the hope that it will be useful,
--  but WITHOUT ANY WARRANTY; without even the implied warranty of
--  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
--  GNU Lesser General Public License for more details.
--
--  You should have received a copy of the GNU Lesser General Public License
--  along with BioSQL. If not, see <http://www.gnu.org/licenses/>.
--

CREATE OR REPLACE
PACKAGE BODY TrmP IS

TrmP_cached	SG_TERM_PATH.OID%TYPE DEFAULT NULL;
cache_key		VARCHAR2(1024) DEFAULT NULL;

CURSOR TrmP_c (
		TrmP_SUBJ_TRM_OID	IN SG_TERM_PATH.SUBJ_TRM_OID%TYPE,
		TrmP_PRED_TRM_OID	IN SG_TERM_PATH.PRED_TRM_OID%TYPE,
		TrmP_OBJ_TRM_OID	IN SG_TERM_PATH.OBJ_TRM_OID%TYPE,
		TrmP_DISTANCE	IN SG_TERM_PATH.DISTANCE%TYPE,
		TrmP_ONT_OID	IN SG_TERM_PATH.ONT_OID%TYPE)
RETURN SG_TERM_PATH%ROWTYPE IS
	SELECT t.* FROM SG_TERM_PATH t
	WHERE
		t.SUBJ_TRM_OID = TrmP_SUBJ_TRM_OID
	AND	t.PRED_TRM_OID = TrmP_PRED_TRM_OID
	AND	t.OBJ_TRM_OID = TrmP_OBJ_TRM_OID
	AND	t.DISTANCE = TrmP_DISTANCE
	AND	t.ONT_OID = TrmP_ONT_OID
	;

FUNCTION get_oid(
		TrmP_OID	IN SG_TERM_PATH.OID%TYPE DEFAULT NULL,
		SUBJ_TRM_OID	IN SG_TERM_PATH.SUBJ_TRM_OID%TYPE,
		PRED_TRM_OID	IN SG_TERM_PATH.PRED_TRM_OID%TYPE,
		OBJ_TRM_OID	IN SG_TERM_PATH.OBJ_TRM_OID%TYPE,
		TrmP_DISTANCE	IN SG_TERM_PATH.DISTANCE%TYPE,
		ONT_OID	IN SG_TERM_PATH.ONT_OID%TYPE,
		Ont_NAME	IN SG_ONTOLOGY.NAME%TYPE DEFAULT NULL,
		Subj_Trm_NAME	IN SG_TERM.NAME%TYPE DEFAULT NULL,
		Subj_ONT_OID	IN SG_TERM.ONT_OID%TYPE DEFAULT NULL,
		Subj_ONT_NAME	IN SG_ONTOLOGY.NAME%TYPE DEFAULT NULL,
		Subj_Trm_IDENTIFIER	IN SG_TERM.IDENTIFIER%TYPE DEFAULT NULL,
		Pred_Trm_NAME	IN SG_TERM.NAME%TYPE DEFAULT NULL,
		Pred_ONT_OID	IN SG_TERM.ONT_OID%TYPE DEFAULT NULL,
		Pred_ONT_NAME	IN SG_ONTOLOGY.NAME%TYPE DEFAULT NULL,
		Pred_Trm_IDENTIFIER	IN SG_TERM.IDENTIFIER%TYPE DEFAULT NULL,
		Obj_Trm_NAME	IN SG_TERM.NAME%TYPE DEFAULT NULL,
		Obj_ONT_OID	IN SG_TERM.ONT_OID%TYPE DEFAULT NULL,
		Obj_ONT_NAME	IN SG_ONTOLOGY.NAME%TYPE DEFAULT NULL,
		Obj_Trm_IDENTIFIER	IN SG_TERM.IDENTIFIER%TYPE DEFAULT NULL,
		do_DML		IN NUMBER DEFAULT BSStd.DML_NO)
RETURN SG_TERM_PATH.OID%TYPE
IS
	pk	SG_TERM_PATH.OID%TYPE DEFAULT NULL;
	TrmP_row TrmP_c%ROWTYPE;
	SUBJ_TRM_OID_	SG_TERM.OID%TYPE DEFAULT SUBJ_TRM_OID;
	OBJ_TRM_OID_	SG_TERM.OID%TYPE DEFAULT OBJ_TRM_OID;
	PRED_TRM_OID_	SG_TERM.OID%TYPE DEFAULT PRED_TRM_OID;
	ONT_OID_	SG_ONTOLOGY.OID%TYPE DEFAULT ONT_OID;
	key_str	VARCHAR2(1024) DEFAULT Subj_Trm_NAME || '|' || Subj_ONT_OID || '|' || Subj_Ont_Name || '|' || Subj_Trm_IDENTIFIER || '|' || Obj_Trm_NAME || '|' || Obj_ONT_OID || '|' || Obj_Ont_Name || '|' || Obj_Trm_IDENTIFIER || '|' || Pred_Trm_NAME || '|' || Pred_ONT_OID || '|' || Pred_Ont_Name || '|' || Pred_Trm_IDENTIFIER || '|' || Ont_NAME || '|' || TrmP_DISTANCE;
BEGIN
	-- initialize
	IF (do_DML > BSStd.DML_NO) THEN
		pk := TrmP_OID;
	END IF;
	-- look up
	IF pk IS NULL THEN
		IF (key_str = cache_key) THEN
			pk := TrmP_cached;
		ELSE
			-- reset cache
			cache_key := NULL;
			TrmP_cached := NULL;
                	-- look up SG_TERM subject
                	IF (SUBJ_TRM_OID_ IS NULL) THEN
                		SUBJ_TRM_OID_ := Trm.get_oid(
                			Trm_NAME => Subj_Trm_NAME,
                			ONT_OID => Subj_ONT_OID,
                			ONT_Name => Subj_ONT_NAME,
                			Trm_IDENTIFIER => Subj_Trm_IDENTIFIER,
                			do_DML => do_DML);
                	END IF;
                	-- look up SG_TERM object
                	IF (OBJ_TRM_OID_ IS NULL) THEN
                		OBJ_TRM_OID_ := Trm.get_oid(
                			Trm_NAME => Obj_Trm_NAME,
                			ONT_OID => Obj_ONT_OID,
                			ONT_Name => Obj_ONT_NAME,
                			Trm_IDENTIFIER => Obj_Trm_IDENTIFIER,
                			do_DML => do_DML);
                	END IF;
                	-- look up SG_TERM predicate
                	IF (PRED_TRM_OID_ IS NULL) THEN
                		PRED_TRM_OID_ := Trm.get_oid(
                			Trm_NAME => Pred_Trm_NAME,
                			ONT_OID => Pred_ONT_OID,
                			ONT_Name => Pred_ONT_NAME,
                			Trm_IDENTIFIER => Pred_Trm_IDENTIFIER,
                			do_DML => do_DML);
                	END IF;
                	-- look up SG_ONTOLOGY
                	IF (ONT_OID_ IS NULL) THEN
                		ONT_OID_ := Ont.get_oid(
                			Ont_NAME => Ont_NAME);
                	END IF;
			-- do the look up
			FOR TrmP_row IN TrmP_c(SUBJ_TRM_OID_, PRED_TRM_OID_, OBJ_TRM_OID_, TrmP_DISTANCE, ONT_OID_) LOOP
		        	pk := TrmP_row.OID;
				-- cache result
			    	cache_key := key_str;
			    	TrmP_cached := pk;
			END LOOP;
		END IF;
	END IF;
	-- insert/update if requested
	IF (pk IS NULL) AND 
	   ((do_DML = BSStd.DML_I) OR (do_DML = BSStd.DML_UI)) THEN
	    	-- look up foreign keys if not provided:
		-- look up SG_TERM subject successful?
		IF (SUBJ_TRM_OID_ IS NULL) THEN
			raise_application_error(-20101,
				'failed to look up Trm <' || Subj_Trm_NAME || '|' || Subj_ONT_OID || '|' || Subj_Ont_Name || '|' || Subj_Trm_IDENTIFIER || '>');
		END IF;
		-- look up SG_TERM object successful?
		IF (OBJ_TRM_OID_ IS NULL) THEN
			raise_application_error(-20101,
				'failed to look up Trm <' || Obj_Trm_NAME || '|' || Obj_ONT_OID || '|' || Obj_Ont_Name || '|' || Obj_Trm_IDENTIFIER || '>');
		END IF;
		-- look up SG_TERM predicate successful?
		IF (PRED_TRM_OID_ IS NULL) THEN
			raise_application_error(-20101,
				'failed to look up Trm <' || Pred_Trm_NAME || '|' || Pred_ONT_OID || '|' || Pred_Ont_Name || '|' || Pred_Trm_IDENTIFIER || '>');
		END IF;
		-- look up SG_ONTOLOGY successful?
		IF (ONT_OID_ IS NULL) THEN
			raise_application_error(-20101,
				'failed to look up Ont <' || Ont_NAME || '>');
		END IF;
	    	-- insert the record and obtain the primary key
	    	pk := do_insert(
		        SUBJ_TRM_OID => SUBJ_TRM_OID_,
			PRED_TRM_OID => PRED_TRM_OID_,
			OBJ_TRM_OID => OBJ_TRM_OID_,
			DISTANCE => TrmP_DISTANCE,
			ONT_OID => ONT_OID_);
	ELSIF (do_DML = BSStd.DML_U) OR (do_DML = BSStd.DML_UI) THEN
	        -- update the record (note that not provided FKs will not
		-- be changed nor looked up)
		do_update(
			TrmP_OID	=> pk,
		        TrmP_SUBJ_TRM_OID => SUBJ_TRM_OID_,
			TrmP_PRED_TRM_OID => PRED_TRM_OID_,
			TrmP_OBJ_TRM_OID => OBJ_TRM_OID_,
			TrmP_DISTANCE => TrmP_DISTANCE,
			TrmP_ONT_OID => ONT_OID_);
	END IF;
	-- return the primary key
	RETURN pk;
END;

FUNCTION do_insert(
		SUBJ_TRM_OID	IN SG_TERM_PATH.SUBJ_TRM_OID%TYPE,
		PRED_TRM_OID	IN SG_TERM_PATH.PRED_TRM_OID%TYPE,
		OBJ_TRM_OID	IN SG_TERM_PATH.OBJ_TRM_OID%TYPE,
		DISTANCE	IN SG_TERM_PATH.DISTANCE%TYPE,
		ONT_OID	IN SG_TERM_PATH.ONT_OID%TYPE)
RETURN SG_TERM_PATH.OID%TYPE 
IS
	pk	SG_TERM_PATH.OID%TYPE;
BEGIN
	-- pre-generate the primary key value
	SELECT SG_Sequence_TrmA.nextval INTO pk FROM DUAL;
	-- insert the record
	INSERT INTO SG_TERM_PATH (
		OID,
		SUBJ_TRM_OID,
		PRED_TRM_OID,
		OBJ_TRM_OID,
		DISTANCE,
		ONT_OID)
	VALUES (pk,
		SUBJ_TRM_OID,
		PRED_TRM_OID,
		OBJ_TRM_OID,
		DISTANCE,
		ONT_OID)
	;
	-- return the new pk value
	RETURN pk;
END;

PROCEDURE do_update(
		TrmP_OID	IN SG_TERM_PATH.OID%TYPE,
		TrmP_SUBJ_TRM_OID	IN SG_TERM_PATH.SUBJ_TRM_OID%TYPE,
		TrmP_PRED_TRM_OID	IN SG_TERM_PATH.PRED_TRM_OID%TYPE,
		TrmP_OBJ_TRM_OID	IN SG_TERM_PATH.OBJ_TRM_OID%TYPE,
		TrmP_DISTANCE	IN SG_TERM_PATH.DISTANCE%TYPE,
		TrmP_ONT_OID	IN SG_TERM_PATH.ONT_OID%TYPE)
IS
BEGIN
	-- update the record (and leave attributes passed as NULL untouched)
	UPDATE SG_TERM_PATH
	SET
		SUBJ_TRM_OID = NVL(TrmP_SUBJ_TRM_OID, SUBJ_TRM_OID),
		PRED_TRM_OID = NVL(TrmP_PRED_TRM_OID, PRED_TRM_OID),
		OBJ_TRM_OID = NVL(TrmP_OBJ_TRM_OID, OBJ_TRM_OID),
		DISTANCE = NVL(TrmP_DISTANCE, DISTANCE),
		ONT_OID = NVL(TrmP_ONT_OID, ONT_OID)
	WHERE OID = TrmP_OID
	;
END;

--
-- Computes the transitive closure table for the given ontology by populating
-- the Term_Path table with it.
--
-- The ontology may be specified by name or by OID. If trunc_existing is
-- set to any non-NULL value (which is the default), the existing transitive
-- closure, if any, will be deleted first.
-- 
PROCEDURE compute_transitive_closure(
		Ont_Oid		IN SG_ONTOLOGY.OID%TYPE DEFAULT NULL,
		Ont_Name	IN SG_ONTOLOGY.NAME%TYPE DEFAULT NULL,
		trunc_existing	IN VARCHAR2 DEFAULT 'Y')
IS
	Ont_Oid_	SG_ONTOLOGY.OID%TYPE DEFAULT ONT_OID;
	dist		INTEGER;
	nrows		INTEGER;
BEGIN
	IF (Ont_Oid_ IS NULL) THEN
                -- look up SG_ONTOLOGY
                IF (Ont_Oid_ IS NULL) THEN
                	Ont_Oid_ := Ont.get_oid(Ont_NAME => Ont_NAME);
                END IF;
		-- look up SG_ONTOLOGY successful?
		IF (Ont_Oid_ IS NULL) THEN
			raise_application_error(-20101,
				'failed to look up Ont <' || Ont_NAME || '>');
		END IF;
	END IF;
    	-- truncate existing path table content (for that ontology)?
	IF (trunc_existing IS NOT NULL) THEN
		DELETE FROM SG_Term_Path WHERE Ont_Oid = Ont_Oid_;
	END IF;
	-- initialize path table with all paths of length 1, which are
	-- equivalent to the relationships in the association table
	INSERT INTO SG_Term_Path (Ont_Oid, 
			Subj_Trm_Oid, Pred_Trm_Oid, Obj_Trm_Oid,
			Distance)
	SELECT Ont_Oid, Subj_Trm_Oid, Pred_Trm_Oid, Obj_Trm_Oid, 1
	FROM   SG_Term_Assoc
	WHERE  Ont_Oid = Ont_Oid_
	;
	-- now build the rest of the transitive closure in a loop
	dist := 1;
	nrows := 1; -- initial dummy value to enter the loop 
	WHILE (nrows > 0) LOOP
		INSERT INTO SG_Term_Path (
			Subj_Trm_Oid, Pred_Trm_Oid, Obj_Trm_Oid, Ont_Oid,
			Distance)
		SELECT DISTINCT 
			tr.Subj_Trm_Oid,
			trp1.Obj_Trm_Oid,
			tp.Obj_Trm_Oid,
			tr.Ont_Oid,
			tp.Distance+1
		FROM SG_Term_Assoc tr, SG_Term_Path tp,
	     	     SG_Term_Assoc trp1, SG_Term_Assoc trp2
		WHERE 
	     	     tp.Ont_Oid        = tr.Ont_Oid
		AND  tr.Obj_Trm_Oid    = tp.Subj_Trm_Oid
		AND  tr.Ont_Oid	       = Ont_Oid_
		AND  tp.Distance       = dist
		AND  trp1.Subj_Trm_Oid = tp.Pred_Trm_Oid
		AND  trp2.Subj_Trm_Oid = tr.Pred_Trm_Oid
		AND  trp1.Obj_Trm_Oid  = trp2.Obj_Trm_Oid
		;
		nrows := SQL%ROWCOUNT;
		dist := dist+1;
	END LOOP;
END;

END TrmP;
/

