-- -*-Sql-*- mode (to keep my emacs happy)
--
-- API Package Body for Ontology.
--
-- 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 Ont IS

Ont_cached	SG_ONTOLOGY.OID%TYPE DEFAULT NULL;
cache_key		VARCHAR2(128) DEFAULT NULL;

CURSOR Ont_c (
		Ont_NAME	IN SG_ONTOLOGY.NAME%TYPE)
RETURN SG_ONTOLOGY%ROWTYPE IS
	SELECT t.* FROM SG_ONTOLOGY t
	WHERE
		t.NAME = Ont_NAME
	;

FUNCTION get_oid(
		Ont_OID	IN SG_ONTOLOGY.OID%TYPE DEFAULT NULL,
		Ont_NAME	IN SG_ONTOLOGY.NAME%TYPE,
		Ont_DEFINITION	IN SG_ONTOLOGY.DEFINITION%TYPE DEFAULT NULL,
		do_DML		IN NUMBER DEFAULT BSStd.DML_NO)
RETURN SG_ONTOLOGY.OID%TYPE
IS
	pk	SG_ONTOLOGY.OID%TYPE DEFAULT NULL;
	Ont_row Ont_c%ROWTYPE;
	--;
	key_str	VARCHAR2(128) DEFAULT Ont_NAME;
BEGIN
	-- initialize
	IF (do_DML > BSStd.DML_NO) THEN
		pk := Ont_OID;
	END IF;
	-- look up
	IF (pk IS NULL) THEN
		IF (key_str = cache_key) THEN
			pk := Ont_cached;
		ELSE
			-- reset cache
			cache_key := NULL;
			Ont_cached := NULL;
			-- do the look up
			FOR Ont_row IN Ont_c(Ont_NAME) LOOP
		        	pk := Ont_row.OID;
				-- cache result
			    	cache_key := key_str;
			    	Ont_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
	    	--
	    	-- insert the record and obtain the primary key
	    	pk := do_insert(
		        NAME => Ont_NAME,
			DEFINITION => Ont_DEFINITION);
	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(
			Ont_OID	=> pk,
		        Ont_NAME => Ont_NAME,
			Ont_DEFINITION => Ont_DEFINITION);
	END IF;
	-- return the primary key
	RETURN pk;
END;

FUNCTION do_insert(
		NAME	IN SG_ONTOLOGY.NAME%TYPE,
		DEFINITION	IN SG_ONTOLOGY.DEFINITION%TYPE)
RETURN SG_ONTOLOGY.OID%TYPE 
IS
	pk	SG_ONTOLOGY.OID%TYPE;
BEGIN
	-- pre-generate the primary key value
	SELECT SG_Sequence.nextval INTO pk FROM DUAL;
	-- insert the record
	INSERT INTO SG_ONTOLOGY (
		OID,
		NAME,
		DEFINITION)
	VALUES (pk,
		NAME,
		DEFINITION)
	;
	-- return the new pk value
	RETURN pk;
END;

PROCEDURE do_update(
		Ont_OID	IN SG_ONTOLOGY.OID%TYPE,
		Ont_NAME	IN SG_ONTOLOGY.NAME%TYPE,
		Ont_DEFINITION	IN SG_ONTOLOGY.DEFINITION%TYPE)
IS
BEGIN
	-- update the record (and leave attributes passed as NULL untouched)
	UPDATE SG_ONTOLOGY
	SET
		NAME = NVL(Ont_NAME, NAME),
		DEFINITION = NVL(Ont_DEFINITION, DEFINITION)
	WHERE OID = Ont_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.
-- 
-- All other parameters can be used to aid preparation for the
-- transitive closure computation. The computation relies on each
-- predicate in an ontology being related to itself, which is commonly
-- referred to as identity relationship. Also, if there are valid
-- paths in the ontology with mixed predicates, the algorithm expects
-- a relationship between the two different predicates and a common
-- ancestor (analogous to the superclass of two different classes).
--        
-- Id_* parameters: The predicate for the identity of a predicate
-- 		with itself. If provided, the method will create
--              relationships for all predicates in the ontology,
--		where subject and object are the predicate of the
--		ontology, the predicate is the supplied identity
--		predicate, and the ontology is the ontology referenced
--		by the supplied term.
--
--              If this is not provided, the aforementioned
--              relationships should be present in an ontology in the
--      	database already. Otherwise the transitive closure
--		will be incomplete.
--
--		If the term exists and is specified by OID, the
--  		other attributes need not be specified.
--
-- Basepred* parameters: The common ancestor predicate for all
-- 		predicates in the ontology. If this is specified, the
--		method will create relationships between all predicates
--		in the ontology and the ancestor predicate, where the
--		ancestor predicate is the object, the predicate is
--		the one specified by the Subclass* parameters (or the
--		default term 'subclasses'), and the ontology is the
--              ontology referenced by the ancestor predicate.
--
--              This ancestor predicate is often also referred to as
--		the 'base' class in an object-oriented view.
--
--              If this is not provided, the aforementioned
--		relationships should be present in an ontology in the
--		database already, unless the ontology over which to
-- 		compute the transitive closure has only one predicate,
--		or paths over mixed predicates are void. 
--        
--		If the term exists and is specified by OID, the
--  		other attributes need not be specified.
--
-- Subclass* parameters: The predicate for the relationship between
--              predicate A and predicate B if predicate A can be
--		considered to subclass (or imply) predicate B.
--
--		If the term exists and is specified by OID, the
--  		other attributes need not be specified.
--
PROCEDURE compute_transitive_closure(
		Ont_Oid		IN SG_ONTOLOGY.OID%TYPE DEFAULT NULL,
		Ont_Name	IN SG_ONTOLOGY.NAME%TYPE DEFAULT NULL,
		Id_Trm_Oid	IN SG_TERM.OID%TYPE DEFAULT NULL,
		Id_Trm_Name	IN SG_TERM.NAME%TYPE DEFAULT NULL,
		Id_Trm_Identifier	IN SG_TERM.IDENTIFIER%TYPE DEFAULT NULL,
		Id_Ont_Oid	IN SG_ONTOLOGY.OID%TYPE DEFAULT NULL,
		Id_Ont_Name	IN SG_ONTOLOGY.NAME%TYPE DEFAULT NULL,
		Subclass_Trm_Oid	IN SG_TERM.OID%TYPE DEFAULT NULL,
		Subclass_Trm_Name	IN SG_TERM.NAME%TYPE DEFAULT NULL,
		Subclass_Trm_Identifier	IN SG_TERM.IDENTIFIER%TYPE DEFAULT NULL,
		Subclass_Ont_Oid	IN SG_ONTOLOGY.OID%TYPE DEFAULT NULL,
		Subclass_Ont_Name	IN SG_ONTOLOGY.NAME%TYPE DEFAULT NULL,
		Basepred_Trm_Oid	IN SG_TERM.OID%TYPE DEFAULT NULL,
		Basepred_Trm_Name	IN SG_TERM.NAME%TYPE DEFAULT NULL,
		Basepred_Trm_Identifier	IN SG_TERM.IDENTIFIER%TYPE DEFAULT NULL,
		Basepred_Ont_Oid	IN SG_ONTOLOGY.OID%TYPE DEFAULT NULL,
		Basepred_Ont_Name	IN SG_ONTOLOGY.NAME%TYPE DEFAULT NULL,
		trunc_existing	IN VARCHAR2 DEFAULT 'Y')
IS
	Id_Trm_Oid_	SG_TERM.OID%TYPE DEFAULT Id_Trm_Oid;
	Subclass_Trm_Oid_	SG_TERM.OID%TYPE DEFAULT Subclass_Trm_Oid;
	Basepred_Trm_Oid_	SG_TERM.OID%TYPE DEFAULT Basepred_Trm_Oid;
	Subclass_Trm_Name_	SG_TERM.NAME%TYPE DEFAULT Subclass_Trm_Name;
	Subclass_Ont_Oid_	SG_ONTOLOGY.OID%TYPE DEFAULT Subclass_Ont_Oid;
	Ont_Oid_		SG_ONTOLOGY.OID%TYPE DEFAULT Ont_Oid;
	Trm_row	Trm.Trm_c%ROWTYPE;
	pk	SG_TERM.OID%TYPE;
BEGIN
	-- identity predicate specified?
	IF (Id_Trm_Oid_ IS NULL) AND (Id_Trm_Name IS NOT NULL) THEN
		Id_Trm_Oid_ := Trm.get_oid(
			Trm_Name       => Id_Trm_Name,
			Trm_Identifier => Id_Trm_Identifier,
			Ont_Oid	       => Id_Ont_Oid,
			Ont_Name       => Id_Ont_Name,
			-- insert if it doesn't exist yet
			do_DML	       => BSSTD.DML_I);
	END IF;
	-- Base-class predicate specified?
	IF (Basepred_Trm_Oid_ IS NULL) AND (Basepred_Trm_Name IS NOT NULL) THEN
		Basepred_Trm_Oid_ := Trm.get_oid(
			Trm_Name       => Basepred_Trm_Name,
			Trm_Identifier => Basepred_Trm_Identifier,
			Ont_Oid	       => Basepred_Ont_Oid,
			Ont_Name       => Basepred_Ont_Name,
			-- insert if it doesn't exist yet
			do_DML	       => BSSTD.DML_I);
	END IF;
	-- 'subclasses' predicate specified?
	IF (Subclass_Trm_Oid_ IS NULL) AND 
	   ((Subclass_Trm_Name_ IS NOT NULL) OR 
	    (Basepred_Trm_Oid_ IS NOT NULL)) THEN
		-- assign a default term name and ontology if none provided
		-- but necessary because we are supposed to create rel.ships
		-- to the base class
		IF (Subclass_Trm_Name_ IS NULL) THEN
			Subclass_Trm_Name_ := 'subclasses';
		END IF;
		IF (Subclass_Ont_Name IS NULL) AND 
		   (Subclass_Ont_Oid_ IS NULL) AND
		   -- first try if we have this already somewhere
		   (Trm.get_oid(Trm_Name => Subclass_Trm_Name,
		   		Trm_Identifier => Subclass_Trm_Identifier)
				IS NULL) THEN
			-- otherwise take the ontology from the identity
			-- predicate or base class predicate ontology
			IF (Id_Trm_Oid_ IS NOT NULL) THEN
				Subclass_Ont_Oid_ := Trm.get_ont_oid(Id_Trm_Oid_);
			ELSE
				Subclass_Ont_Oid_ := Trm.get_ont_oid(Basepred_Trm_Oid_);
			END IF;
		END IF;
		Subclass_Trm_Oid_ := Trm.get_oid(
			Trm_Name       => Subclass_Trm_Name_,
			Trm_Identifier => Subclass_Trm_Identifier,
			Ont_Oid	       => Subclass_Ont_Oid_,
			Ont_Name       => Subclass_Ont_Name,
			-- insert if it doesn't exist yet
			do_DML	       => BSSTD.DML_I);
	END IF;
	-- we also need the OID for the ontology on which to compute the
	-- transitive closure
	IF (Ont_Oid_ IS NULL) THEN
		Ont_Oid_ := Ont.get_oid(Ont_Name => Ont_Name);
	END IF;
	-- Establish identity relationships if requested
	IF (Id_Trm_Oid_ IS NOT NULL) THEN
		INSERT INTO SG_Term_Assoc(
				Subj_Trm_Oid,
				Pred_Trm_Oid,
				Obj_Trm_Oid,
				Ont_Oid)
		SELECT DISTINCT TrmA.Pred_Trm_Oid,
		       		IdTrm.Oid,
				TrmA.Pred_Trm_Oid,
				IdTrm.Ont_Oid
		FROM SG_Term_Assoc TrmA, SG_Term IdTrm
		WHERE TrmA.Ont_Oid = Ont_Oid_ 
		AND   IdTrm.Oid = Id_Trm_Oid_
		;
	END IF;
	-- Establish predicate base class if requested
	IF (Basepred_Trm_Oid_ IS NOT NULL) THEN
		INSERT INTO SG_Term_Assoc(
				Subj_Trm_Oid,
				Pred_Trm_Oid,
				Obj_Trm_Oid,
				Ont_Oid)
		SELECT DISTINCT TrmA.Pred_Trm_Oid,
		       		SbclTrm.Oid,
				Basepred_Trm_Oid_,
				SbclTrm.Ont_Oid
		FROM SG_Term_Assoc TrmA, SG_Term SbclTrm
		WHERE TrmA.Ont_Oid = Ont_Oid_ 
		AND   SbclTrm.Oid = Subclass_Trm_Oid_
		;		
	END IF;
	-- call the implementation of the real meat, which is on Term_Path
	TrmP.compute_transitive_closure(Ont_Oid => Ont_Oid_,
					trunc_existing => trunc_existing);
	-- done.
END;

END Ont;
/

