-- sql/api-create.sql
--
-- Portal PL/SQL API.
--
-- Ian Baker <ibaker@arsdigita.com>
-- $Id: api-create.sql,v 1.1.1.1 2002/07/09 17:35:10 rmello Exp $
--
--
-- The Oracle Relational Database Management System was designed
-- by a horde of gnomes whose alignment is chaotic good, and
-- implemented by a second horde who were all lawful evil.
-- Their marketing staff, on the other hand...

-- portal is an acs_object.
create or replace package portal
as
	function new (
		portal_id		in portals.portal_id%TYPE default null,
		template_id		in portals.template_id%TYPE default null,
		name			in portals.name%TYPE default null,
		package_id		in apm_packages.package_id%TYPE default 0,
		target_object_id	in portals.target_object_id%TYPE default 0,
		owner_id		in persons.person_id%TYPE default 0,
		parent_portal_id	in portals.parent_portal_id%TYPE default null,
		copy_portal_id		in portals.portal_id%TYPE default null,
		object_type		in acs_object_types.object_type%TYPE default 'portal',
		creation_date		in acs_objects.creation_date%TYPE 
					default sysdate,
		creation_user		in acs_objects.creation_user%TYPE 
					default null,
		creation_ip		in acs_objects.creation_ip%TYPE default null, 
		context_id		in acs_objects.context_id%TYPE default null 
	) return portals.portal_id%TYPE;

	procedure delete (
		portal_id	in portals.portal_id%TYPE
	);

	function id_for_user (
		package_id	in portals.package_id%TYPE,
		user_id		in portals.owner_id%TYPE
	) return portals.portal_id%TYPE;

	function parent (
		portal_id	in portals.portal_id%TYPE
	) return portals.portal_id%TYPE;

	function root_p (
		portal_id	in portals.portal_id%TYPE
	) return char;

	function url (

	-- **/ Returns the URL at which this portal is mounted. If
	--  *  this portal is not mounted, returns null. If this portal
	--  *  is mounted in more than one place, returns the first url
	--  *  encountered.
	--  *  
	--  *  @author Michael Bryzek (mbryzek@arsdigita.com)
	--  *  @creation-date 2001-02-09
	-- **/
		portal_id	in portals.portal_id%TYPE
	) return varchar;

end portal;
/
show errors

create or replace package body portal
as
	function new (
		portal_id		in portals.portal_id%TYPE default null,
		template_id		in portals.template_id%TYPE default null,
		name			in portals.name%TYPE default null,
		package_id		in apm_packages.package_id%TYPE default 0,
		target_object_id	in portals.target_object_id%TYPE default 0,
		owner_id		in persons.person_id%TYPE default 0,
		parent_portal_id	in portals.parent_portal_id%TYPE default null,
		copy_portal_id		in portals.portal_id%TYPE default null,
		object_type		in acs_object_types.object_type%TYPE default 'portal',
		creation_date		in acs_objects.creation_date%TYPE 
					default sysdate,
		creation_user		in acs_objects.creation_user%TYPE 
					default null,
		creation_ip		in acs_objects.creation_ip%TYPE default null, 
		context_id		in acs_objects.context_id%TYPE default null 
	) return portals.portal_id%TYPE
	is
		v_portal_id portals.portal_id%TYPE;
		v_child_p char(1);
		v_target_object portals.target_object_id%TYPE;
	begin
		v_portal_id := acs_object.new (
			object_id	=> portal_id,
			object_type	=> object_type,
			creation_date	=> creation_date,
			creation_user	=> creation_user,
			creation_ip	=> creation_ip,
			context_id	=> context_id
		);

		if new.target_object_id = 0
		then
			v_target_object := new.package_id;
		else
			v_target_object := new.target_object_id;
		end if;

		if parent_portal_id is not null
		then
			-- create the child portal
			-- the passed-in values take precedence.
			--
			-- package_id and owner_id default to zero, since null is a legal
			-- value for them.
			insert into portals (
				portal_id, name, template_id, package_id, target_object_id, owner_id, parent_portal_id
			)
			select
				new.v_portal_id,
				nvl(new.name, p.name),
				nvl(new.template_id, p.template_id),
				decode(new.package_id, 0, p.package_id, new.package_id),
				decode(new.v_target_object, 0, p.package_id, new.v_target_object),
				decode(new.owner_id, 0, p.owner_id, new.owner_id),
				new.parent_portal_id
			from portals p
			where portal_id = new.parent_portal_id;

			-- copy the portal_element_map entries
			insert into portal_element_map
				(portal_id, element_id, region, sort_key)
			select new.v_portal_id, m.element_id, m.region, m.sort_key
			from portal_element_map m
			where portal_id = new.parent_portal_id;

		elsif copy_portal_id is not null
		then
			-- copy the portal, using the source's parent_portal_id.
			insert into portals (
				portal_id, name, template_id, package_id, target_object_id, owner_id, parent_portal_id
			)
			select
				new.v_portal_id,
				nvl(new.name, p.name),
				nvl(new.template_id, p.template_id),
				decode(new.package_id, 0, p.package_id, new.package_id),
				decode(new.v_target_object, 0, p.package_id, new.v_target_object),
				decode(new.owner_id, 0, p.owner_id, new.owner_id),
				p.parent_portal_id
			from portals p
			where portal_id = new.copy_portal_id;


			-- copy the portal_element_map entries
			insert into portal_element_map
				(portal_id, element_id, region, sort_key)
			select new.v_portal_id, m.element_id, m.region, m.sort_key
			from portal_element_map m
			where portal_id = new.copy_portal_id;

			-- copy the available template and available element map entries, if necessary.
			select decode(p.parent_portal_id, NULL, 'f', 't')
			into v_child_p
			from portals p
			where portal_id = new.v_portal_id;

			if new.v_child_p = 'f'
			then
				insert into portal_available_element_map
					(portal_id, element_id)
				select new.v_portal_id, m.element_id
				from portal_available_element_map m
				where portal_id = new.copy_portal_id;

				insert into portal_available_template_map
					(portal_id, template_id)
				select new.v_portal_id, m.template_id
				from portal_available_template_map m
				where portal_id = new.copy_portal_id;
			end if;

		else
			-- it's all new.  just insert the thing.
			insert into portals (
				portal_id, template_id, name, package_id, target_object_id, owner_id, parent_portal_id
			) values (
				v_portal_id,
				template_id,
				name,
				decode(package_id, 0, NULL, package_id),
				v_target_object,
				decode(owner_id, 0, NULL, owner_id),
				parent_portal_id
			);
		end if;

		return v_portal_id;
	end new;

	procedure delete (
		portal_id	in portals.portal_id%TYPE
	)
	is
	begin
		acs_object.delete(portal_id);
	end delete;

	-- determine what the appropriate portal to present is within an
	-- instance of portal for a particular user.
	function id_for_user (
		package_id	in portals.package_id%TYPE,
		user_id		in portals.owner_id%TYPE
	) return portals.portal_id%TYPE
	is
		v_portal_id portals.portal_id%TYPE;
	begin
		-- I have a way to do this in a single query, but for some
		-- reason, PL/SQL's parser wouldn't accept it.  <shrug>
		select portal_id
		into v_portal_id
		from portals
		where
		  package_id = id_for_user.package_id and
		  owner_id = id_for_user.user_id;

		return v_portal_id;
	exception
		when no_data_found then
			select portal_id
			into v_portal_id
			from portals
			where
			  package_id = id_for_user.package_id and
			  owner_id is null;

			return v_portal_id;
	end id_for_user;

	-- find a portal's ultimate parent.
	-- only iterate a few times, since circular refs are a posibility.
	function parent (
		portal_id	in portals.portal_id%TYPE
	) return portals.portal_id%TYPE
	is
		i integer(5);
		parent_portal_id portals.portal_id%TYPE;
		current_portal_id portals.portal_id%TYPE;
		too_many_refs exception;
	begin
		parent.current_portal_id := parent.portal_id;

		for i in 1..5 loop
			select p.parent_portal_id
			into parent.parent_portal_id
			from portals p
			where portal_id = parent.current_portal_id;

			if parent.parent_portal_id is null
			then
				return parent.current_portal_id;
			end if;

			parent.current_portal_id := parent.parent_portal_id;
		end loop;

		raise too_many_refs;

	exception
		when too_many_refs
		then
			-- this error number was picked at random.  do we have
			-- a way to coordinate these?  will there be conflicts?
			raise_application_error(-20304, 'Found more than 5 parent portal references (circular ref?)');
	end parent;

	function root_p (
		portal_id	in portals.portal_id%TYPE
	) return char
	is
		is_root_p char(1);
	begin
		select decode(p.parent_portal_id, NULL, 't', 'f')
		into root_p.is_root_p
		from portals p
		where p.portal_id = root_p.portal_id;

		return is_root_p;
	end root_p;

	function url (
		portal_id	in portals.portal_id%TYPE
	) return varchar
	is
	   v_url varchar(350);
	begin

         begin
	   select site_node.url(n.node_id) into v_url
	     from site_nodes n, portals, apm_packages p
	    where n.object_id = p.package_id
	      and p.package_id = portals.package_id
	      and portals.portal_id = url.portal_id
	      and rownum = 1;
           exception when others then return null;
         end;

         return v_url;

        end url;


end portal;
/
show errors


-- Templates...
create or replace package portal_template
as
	function new (
		template_id		in portal_templates.template_id%TYPE default null,
		name			in portal_templates.name%TYPE,
		description		in portal_templates.description%TYPE default null,
		type			in portal_templates.type%TYPE default null,
		filename		in portal_templates.filename%TYPE,
		resource_dir		in portal_templates.resource_dir%TYPE,
		object_type		in acs_object_types.object_type%TYPE default 'portal_template',
		creation_date		in acs_objects.creation_date%TYPE 
					default sysdate,
		creation_user		in acs_objects.creation_user%TYPE 
					default null,
		creation_ip		in acs_objects.creation_ip%TYPE default null, 
		context_id		in acs_objects.context_id%TYPE default null 
	) return portal_templates.template_id%TYPE;

	procedure delete (
		template_id	in portal_templates.template_id%TYPE
	);

	procedure add_type (
		template_id		in portal_templates.template_id%TYPE,
		mime_type		in portal_mime_types.name%TYPE
	);

	procedure add_region (
		template_id		in portal_supported_regions.template_id%TYPE,
		region			in portal_supported_regions.region%TYPE,
		immutable_p		in portal_supported_regions.immutable_p%TYPE default 'f'
	);

end portal_template;
/
show errors

create or replace package body portal_template
as
	function new (
		template_id		in portal_templates.template_id%TYPE default null,
		name			in portal_templates.name%TYPE,
		description		in portal_templates.description%TYPE default null,
		type			in portal_templates.type%TYPE default null,
		filename		in portal_templates.filename%TYPE,
		resource_dir		in portal_templates.resource_dir%TYPE,
		object_type		in acs_object_types.object_type%TYPE default 'portal_template',
		creation_date		in acs_objects.creation_date%TYPE 
					default sysdate,
		creation_user		in acs_objects.creation_user%TYPE 
					default null,
		creation_ip		in acs_objects.creation_ip%TYPE default null, 
		context_id		in acs_objects.context_id%TYPE default null 
	) return portal_templates.template_id%TYPE
	is
		v_template_id portal_templates.template_id%TYPE;
	begin
		v_template_id := acs_object.new (
			object_id	=> template_id,
			object_type	=> object_type,
			creation_date	=> creation_date,
			creation_user	=> creation_user,
			creation_ip	=> creation_ip,
			context_id	=> context_id
		);

		insert into portal_templates
			(template_id, name, description, type, filename, resource_dir)
		values
			(v_template_id, name, description, type, filename, resource_dir);

		return v_template_id;
	end new;

	procedure delete (
		template_id	in portal_templates.template_id%TYPE
	)
	is
	begin
		acs_object.delete(template_id);
	end delete;

	procedure add_type (
		template_id		in portal_templates.template_id%TYPE,
		mime_type		in portal_mime_types.name%TYPE
	)
	is
	begin
		insert into portal_available_mime_type_map (template_id, mime_type) values (template_id, mime_type);
	end add_type;

	procedure add_region (
		template_id		in portal_supported_regions.template_id%TYPE,
		region			in portal_supported_regions.region%TYPE,
		immutable_p		in portal_supported_regions.immutable_p%TYPE default 'f'
	)
	is
	begin
		insert into portal_supported_regions (template_id, region, immutable_p)
		values (template_id, region, immutable_p);
	end add_region;

end portal_template;
/
show errors


-- element configs...
create or replace package portal_element_config
as
	function new (
		config_id	in portal_element_configs.config_id%TYPE default null,
		portal_id	in acs_objects.object_id%TYPE default null,
		object_type	in acs_object_types.object_type%TYPE default 'portal_element_config',
		creation_date	in acs_objects.creation_date%TYPE 
				default sysdate,
		creation_user	in acs_objects.creation_user%TYPE 
				default null,
		creation_ip	in acs_objects.creation_ip%TYPE default null, 
		context_id	in acs_objects.context_id%TYPE default null 
	) return portal_element_configs.config_id%TYPE;

	procedure delete (
		config_id	in portal_element_configs.config_id%TYPE
	);

	procedure assign (
		config_id	in portal_element_configs.config_id%TYPE default null,
		to_element	in portal_elements.element_id%TYPE default null,
		from_element	in portal_elements.element_id%TYPE default null,
		to_datasource	in portal_datasources.datasource_id%TYPE default null,
		from_datasource	in portal_datasources.datasource_id%TYPE default null
	);
end portal_element_config;
/
show errors

create or replace package body portal_element_config
as
	function new (
		config_id	in portal_element_configs.config_id%TYPE default null,
		portal_id	in acs_objects.object_id%TYPE default null,
		object_type	in acs_object_types.object_type%TYPE default 'portal_element_config',
		creation_date	in acs_objects.creation_date%TYPE 
				default sysdate,
		creation_user	in acs_objects.creation_user%TYPE 
				default null,
		creation_ip	in acs_objects.creation_ip%TYPE default null, 
		context_id	in acs_objects.context_id%TYPE default null 
	) return portal_element_configs.config_id%TYPE
	is
		v_config_id portal_element_configs.config_id%TYPE;
	begin
		v_config_id := acs_object.new (
			object_id	=> config_id,
			object_type	=> object_type,
			creation_date	=> creation_date,
			creation_user	=> creation_user,
			creation_ip	=> creation_ip,
			context_id	=> context_id
		);

		insert into portal_element_configs
			(config_id, portal_id)
		values
			(config_id, portal_id);

		return v_config_id;
	end new;

	procedure delete (
		config_id	in portal_element_configs.config_id%TYPE
	)
	is
	begin
		acs_object.delete(config_id);
	end delete;

	procedure assign (
		config_id	in portal_element_configs.config_id%TYPE default null,
		to_element	in portal_elements.element_id%TYPE default null,
		from_element	in portal_elements.element_id%TYPE default null,
		to_datasource	in portal_datasources.datasource_id%TYPE default null,
		from_datasource	in portal_datasources.datasource_id%TYPE default null
	)
	is
		v_config_id	portal_element_configs.config_id%TYPE;
	begin
		if config_id is not null
		then
			v_config_id := config_id;
		elsif from_element is not null
		then
			select config_id
			into v_config_id
			from portal_elements
			where element_id = assign.from_element;
		elsif from_datasource is not null
		then
			select config_id
			into v_config_id
			from portal_datasources
			where datasource_id = assign.from_datasource;
		else
			v_config_id := null;
		end if;

		if to_element is not null
		then
			update portal_elements set config_id = v_config_id where element_id = assign.to_element;
		end if;

		if to_datasource is not null
		then
			update portal_datasources
			set default_config_id = v_config_id
			where datasource_id = assign.to_datasource;
		end if;
		
	end assign;

end portal_element_config;
/
show errors


-- datasources
create or replace package portal_datasource
as
	function new (
		datasource_id		in portal_datasources.datasource_id%TYPE default null,
		data_type		in portal_datasources.data_type%TYPE default null,
		mime_type		in portal_datasources.mime_type%TYPE default null,
		default_config_id	in portal_datasources.default_config_id%TYPE default null,
		name			in portal_datasources.name%TYPE default null,
		description		in portal_datasources.description%TYPE default null,
		configurable_p		in portal_datasources.configurable_p%TYPE default null,
		content			in portal_datasources.content%TYPE default null,
		content_varchar		varchar default null,
		package_key		apm_package_types.package_key%TYPE default null,
		object_type		in acs_object_types.object_type%TYPE default 'portal_datasource',
		creation_date		in acs_objects.creation_date%TYPE 
					default sysdate,
		creation_user		in acs_objects.creation_user%TYPE 
					default null,
		creation_ip		in acs_objects.creation_ip%TYPE default null, 
		context_id		in acs_objects.context_id%TYPE default null 
	) return portal_datasources.datasource_id%TYPE;

	procedure delete (
		datasource_id	in portal_datasources.datasource_id%TYPE
	);
end portal_datasource;
/
show errors

create or replace package body portal_datasource
as
	function new (
		datasource_id		in portal_datasources.datasource_id%TYPE default null,
		data_type		in portal_datasources.data_type%TYPE default null,
		mime_type		in portal_datasources.mime_type%TYPE default null,
		default_config_id	in portal_datasources.default_config_id%TYPE default null,
		name			in portal_datasources.name%TYPE default null,
		description		in portal_datasources.description%TYPE default null,
		configurable_p		in portal_datasources.configurable_p%TYPE default null,
		content			in portal_datasources.content%TYPE default null,
		content_varchar		varchar default null,
		package_key		apm_package_types.package_key%TYPE default null,
		object_type		in acs_object_types.object_type%TYPE default 'portal_datasource',
		creation_date		in acs_objects.creation_date%TYPE 
					default sysdate,
		creation_user		in acs_objects.creation_user%TYPE 
					default null,
		creation_ip		in acs_objects.creation_ip%TYPE default null, 
		context_id		in acs_objects.context_id%TYPE default null 
	) return portal_datasources.datasource_id%TYPE
	is
		v_datasource_id portal_datasources.datasource_id%TYPE;
		v_content_loc	portal_datasources.content%TYPE;
	begin
		v_datasource_id := acs_object.new (
			object_id	=> datasource_id,
			object_type	=> object_type,
			creation_date	=> creation_date,
			creation_user	=> creation_user,
			creation_ip	=> creation_ip,
			context_id	=> context_id
		);

		if content_varchar is not null
		then
			dbms_lob.createtemporary(v_content_loc, TRUE);
			dbms_lob.write(v_content_loc, length(new.content_varchar), 1, new.content_varchar);
		else
			v_content_loc := content;
		end if;

		insert into portal_datasources
			(datasource_id, data_type,
			mime_type, default_config_id,
			name, description, configurable_p, content, package_key)
		values
			(v_datasource_id, data_type,
			mime_type, default_config_id,
			name, description, configurable_p, v_content_loc, package_key); 

		return v_datasource_id;
	end new;

	procedure delete (
		datasource_id	in portal_datasources.datasource_id%TYPE
	)
	is
	begin
		acs_object.delete(datasource_id);
	end delete;

end portal_datasource;
/
show errors


-- elements
create or replace package portal_element
as
	function new (
		element_id		in portal_elements.element_id%TYPE default null,
		name			in portal_elements.name%TYPE,
		description		in portal_elements.description%TYPE default null,
		exportable_p		in portal_elements.exportable_p%TYPE default null,
		datasource_id		in portal_elements.datasource_id%TYPE,
		controlling_portal_id	in portal_elements.controlling_portal_id%TYPE,
		template_id		in portal_elements.template_id%TYPE default null,
		config_id		in portal_elements.config_id%TYPE default null,
		default_config_id	in portal_elements.default_config_id%TYPE default null,
		object_type		in acs_object_types.object_type%TYPE default 'portal_element',
		creation_date		in acs_objects.creation_date%TYPE 
					default sysdate,
		creation_user		in acs_objects.creation_user%TYPE 
					default null,
		creation_ip		in acs_objects.creation_ip%TYPE default null, 
		context_id		in acs_objects.context_id%TYPE default null
	) return portal_elements.element_id%TYPE;

	procedure delete (
		element_id	in portal_elements.element_id%TYPE
	);

	procedure make_available (
		element_id 	in portal_available_element_map.element_id%TYPE,
		portal_id	in portal_available_element_map.portal_id%TYPE
	);

	procedure move (
		portal_id	in portal_element_map.portal_id%TYPE,
		sort_key	in portal_element_map.sort_key%TYPE,
		region		in portal_element_map.region%TYPE,
		direction	varchar
	);
end portal_element;
/
show errors

create or replace package body portal_element
as
	function new (
		element_id		in portal_elements.element_id%TYPE default null,
		name			in portal_elements.name%TYPE,
		description		in portal_elements.description%TYPE default null,
		exportable_p		in portal_elements.exportable_p%TYPE default null,
		datasource_id		in portal_elements.datasource_id%TYPE,
		controlling_portal_id	in portal_elements.controlling_portal_id%TYPE,
		template_id		in portal_elements.template_id%TYPE default null,
		config_id		in portal_elements.config_id%TYPE default null,
		default_config_id	in portal_elements.default_config_id%TYPE default null,
		object_type		in acs_object_types.object_type%TYPE default 'portal_element',
		creation_date		in acs_objects.creation_date%TYPE 
					default sysdate,
		creation_user		in acs_objects.creation_user%TYPE 
					default null,
		creation_ip		in acs_objects.creation_ip%TYPE default null, 
		context_id		in acs_objects.context_id%TYPE default null 
	) return portal_elements.element_id%TYPE
	is
		v_element_id portal_elements.element_id%TYPE;
		v_config_id portal_elements.config_id%TYPE;
	begin
		v_element_id := acs_object.new (
			object_id	=> element_id,
			object_type	=> object_type,
			creation_date	=> creation_date,
			creation_user	=> creation_user,
			creation_ip	=> creation_ip,
			context_id	=> context_id
		);

		-- use the default configuration if none was supplied.
		if config_id is null
		then
			select default_config_id
			into v_config_id
			from portal_datasources
			where datasource_id = new.datasource_id;
		else
			v_config_id := config_id;
		end if;

		insert into portal_elements
			(element_id, name, description, datasource_id,
			template_id, config_id, default_config_id, exportable_p,
			controlling_portal_id)
		values
			(v_element_id, name, description, datasource_id,
			template_id, v_config_id, default_config_id, exportable_p,
			controlling_portal_id);

		return v_element_id;
	end new;

	procedure delete (
		element_id	in portal_elements.element_id%TYPE
	)
	is
	begin
		acs_object.delete(element_id);
	end delete;

	procedure make_available (
		element_id 	in portal_available_element_map.element_id%TYPE,
		portal_id	in portal_available_element_map.portal_id%TYPE
	)
	is
		v_exists_p char(1);
	begin
		select 't' into v_exists_p
		from portal_available_element_map m
		where m.element_id = make_available.element_id and m.portal_id = make_available.portal_id;
	exception
		when no_data_found then
                  insert into portal_available_element_map
                    (element_id, portal_id)
                  values
                    (element_id, portal_id);
	end make_available;


	-- a procedure to swap an element in one position with the adjacent element, either 
	-- up or down.  This is so strange because the sort_key column is constrained to
	-- be unique, the keys are not guaranteed to be contiguous, and a single element can
	-- occur more than once in a region.
	procedure move (
		portal_id	in portal_element_map.portal_id%TYPE,
		sort_key	in portal_element_map.sort_key%TYPE,
		region		in portal_element_map.region%TYPE,
		direction	varchar
	)
	is
		v_other_key		portal_element_map.sort_key%TYPE;
		v_element_id		portal_element_map.element_id%TYPE;
		invalid_direction	exception;
	begin
		if direction = 'up'
		then
			-- get the previous sort key.
			select sort_key into v_other_key
			from (
			  select sort_key
			  from portal_element_map
			  where portal_id = move.portal_id and region = move.region and sort_key < move.sort_key order by sort_key desc
			) where rownum = 1;
		elsif direction = 'down'
		then
			-- get the next sort key.
			select sort_key into v_other_key
			from (
			  select sort_key
			  from portal_element_map
			  where portal_id = move.portal_id and region = move.region and sort_key > move.sort_key order by sort_key
			) where rownum = 1;	
		else
			raise invalid_direction;
		end if;

		-- get the element_id in that position.
		select element_id into v_element_id
		from portal_element_map
		where portal_id = move.portal_id and region = move.region and sort_key = move.sort_key;

		-- remove the one we know everything about.
		delete from portal_element_map
		where portal_id = move.portal_id and region = move.region and sort_key = move.sort_key;

		-- move the other one into its position.
		update portal_element_map
		set sort_key = move.sort_key
		where portal_id = move.portal_id and region=move.region and sort_key = v_other_key;

		-- reinsert the original entry.
		insert into portal_element_map (
		  portal_id, element_id, region, sort_key
		) values (
		  move.portal_id, v_element_id, move.region, v_other_key
		);

	exception
		-- there was no previous sort key, or something like that.
		when no_data_found then null;
		when invalid_direction then null;
	end move;

end portal_element;
/
show errors
