--
--  $Id: DET_HostFs.sql,v 1.3 2008/04/18 13:09:07 mitko Exp $
--
--  This file is part of the OpenLink Software Virtuoso Open-Source (VOS)
--  project.
--
--  Copyright (C) 1998-2006 OpenLink Software
--
--  This project is free software; you can redistribute it and/or modify it
--  under the terms of the GNU General Public License as published by the
--  Free Software Foundation; only version 2 of the License, dated June 1991.
--
--  This program 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
--  General Public License for more details.
--
--  You should have received a copy of the GNU General Public License along
--  with this program; if not, write to the Free Software Foundation, Inc.,
--  51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
--

use DB
;

create table WS.WS.HOSTFS_COL
(
  COL_ID		integer not null primary key,
  COL_FULL_PATH		varchar not null,
  COL_PARENT_ID		integer,
  COL_CR_TIME		datetime,
  COL_MOD_TIME		datetime,
  COL_SCAN_TIME		datetime,
  COL_NEXT_SCAN_TIME	datetime,
  COL_NAME		varchar not null
  )
create index HOSTFS_COL_PARENT_ID on WS.WS.HOSTFS_COL (COL_PARENT_ID)
create index HOSTFS_COL_FULL_PATH on WS.WS.HOSTFS_COL (COL_FULL_PATH)
create index HOSTFS_COL_NEXT_SCAN_TIME on WS.WS.HOSTFS_COL (COL_NEXT_SCAN_TIME)
;

alter table WS.WS.HOSTFS_COL add COL_NAME varchar not null
;

create table WS.WS.HOSTFS_RES
(
  RES_ID 		integer not null primary key,
  RES_NAME 		varchar (256),
  RES_COL 		integer,
  RES_TYPE 		varchar,
  RES_FT_MODE		char (1), -- 'x' file XML, 't' file text, 'X' cached XML, 'T' cached text, 'N' no text data (so RES_NAME is indexed as text).
  RES_LENGTH		integer,
  RES_CR_TIME		datetime,	-- creation time
  RES_MOD_TIME		datetime,	-- modification time
  RES_SCAN_TIME		datetime,	-- last file scan time
  RES_NEXT_SCAN_TIME	datetime,
  RES_PERMS 		char (11)
)
create index HOSTFS_RES_COL on WS.WS.HOSTFS_RES (RES_COL, RES_NAME)
create index HOSTFS_RES_NEXT_SCAN_TIME on WS.WS.HOSTFS_RES (RES_NEXT_SCAN_TIME)
;

create table WS.WS.HOSTFS_RES_CACHE
(
  RESC_ID		integer not null primary key,
  RESC_MOD_SCAN_TIME	datetime,	-- time of the last scan such that it has detected change in the resource.
  RESC_DATA		long varchar,
  RESC_TOPCOL_ID	integer not null
)
create index HOSTFS_RES_CACHE_TOPCOL_ID on WS.WS.HOSTFS_RES_CACHE (RESC_TOPCOL_ID)
;

alter table WS.WS.HOSTFS_RES_CACHE add RESC_TOPCOL_ID integer not null
;

create table WS.WS.HOSTFS_RES_META
(
  RESM_ID		integer not null primary key,
  RESM_DATA		long XML,
  RESM_TOPCOL_ID	integer not null
)
create index HOSTFS_RES_META_TOPCOL_ID on WS.WS.HOSTFS_RES_META (RESM_TOPCOL_ID)
;

alter table WS.WS.HOSTFS_RES_META add RESM_TOPCOL_ID integer not null
;

create table WS.WS.HOSTFS_RDF_INVERSE
(
  HRI_TOPCOL_ID integer not null,
  HRI_PROP_CATID integer not null,
  HRI_CATVALUE varchar not null,
  HRI_RES_ID integer not null,
  primary key (HRI_TOPCOL_ID, HRI_PROP_CATID, HRI_CATVALUE, HRI_RES_ID)
)
;

create procedure WS.WS.HOSTFS_FEED_RDF_INVERSE (inout propval any, in r_id integer, in is_del integer, in topcol_id integer)
{
  declare resfullpath, path_head, pv varchar;
  declare triplets any;
  triplets := xpath_eval ('[xmlns:virt="virt"] /virt:rdf/virt:top-res/virt:prop[virt:value]', propval, 0);
  foreach (any prop in triplets) do
    {
      declare propname varchar;
      declare prop_catid integer;
      propname := cast (xpath_eval ('name(*[1])', prop) as varchar);
      prop_catid := coalesce ((select RPN_CATID from WS.WS.SYS_RDF_PROP_NAME where RPN_URI = propname));
      if (prop_catid is null)
        {
          prop_catid := WS.WS.GETID ('RPN');
          -- dbg_obj_princ ('HOSTFS_FEEDV_RDF_INVERSE: insert into WS.WS.SYS_RDF_PROP_NAME (RPN_URI, RPN_CATID) values (', propname, prop_catid, ')');
          insert into WS.WS.SYS_RDF_PROP_NAME (RPN_URI, RPN_CATID) values (propname, prop_catid);
        }
      if (is_del)
        delete from WS.WS.HOSTFS_RDF_INVERSE
        where
          (HRI_TOPCOL_ID = topcol_id) and (HRI_PROP_CATID = prop_catid) and
          (HRI_CATVALUE = "CatFilter_ENCODE_CATVALUE" (cast (xpath_eval ('[xmlns:virt="virt"] virt:value', prop) as varchar))) and
          (HRI_RES_ID = r_id);
      else
        insert soft WS.WS.HOSTFS_RDF_INVERSE (HRI_TOPCOL_ID, HRI_PROP_CATID, HRI_CATVALUE, HRI_RES_ID)
        values (
          topcol_id,
          prop_catid,
          "CatFilter_ENCODE_CATVALUE" (cast (xpath_eval ('[xmlns:virt="virt"] virt:value', prop) as varchar)),
          r_id );
    }
}
;


create trigger HOSTFS_RES_META_I after insert on WS.WS.HOSTFS_RES_META referencing new as NP
{
  if (NP.RESM_DATA is not null)
    WS.WS.HOSTFS_FEED_RDF_INVERSE (NP.RESM_DATA, NP.RESM_ID, 0, NP.RESM_TOPCOL_ID);
}
;


create trigger HOSTFS_RES_META_D before delete on WS.WS.HOSTFS_RES_META referencing old as OP
{
  if (OP.RESM_DATA is not null)
    WS.WS.HOSTFS_FEED_RDF_INVERSE (OP.RESM_DATA, OP.RESM_ID, 1, OP.RESM_TOPCOL_ID);
}
;


create trigger HOSTFS_RES_META_U after update on WS.WS.HOSTFS_RES_META referencing old as OP, new as NP
{
  if (OP.RESM_DATA is not null)
    WS.WS.HOSTFS_FEED_RDF_INVERSE (OP.RESM_DATA, OP.RESM_ID, 1, OP.RESM_TOPCOL_ID);
  if (NP.RESM_DATA is not null)
    WS.WS.HOSTFS_FEED_RDF_INVERSE (NP.RESM_DATA, NP.RESM_ID, 0, NP.RESM_TOPCOL_ID);
}
;


--#IF VER=5
--!AFTER __PROCEDURE__ DB.DBA.VT_CREATE_TEXT_INDEX !
--#ENDIF
DB.DBA.vt_create_text_index (fix_identifier_case ('WS.WS.HOSTFS_RES_META'), fix_identifier_case ('RESM_DATA'), fix_identifier_case ('RESM_ID'), 2, 0, NULL, 0, '*ini*', '*ini*')
;

--#IF VER=5
--!AFTER __PROCEDURE__ DB.DBA.VT_CREATE_TEXT_INDEX !
--#ENDIF
DB.DBA.vt_batch_update (fix_identifier_case ('WS.WS.HOSTFS_RES_META'), 'ON', 1)
;

create function WS.WS.HOSTFS_RES_TOPCOL_ID (in r_id integer) returns integer
{
  declare res, parent integer;
  whenever not found goto nf;
  res := parent := (select RES_COL from WS.WS.HOSTFS_RES where RES_ID = r_id);
  while (parent is not null)
    {
      res := parent;
      parent := (select COL_PARENT_ID from WS.WS.HOSTFS_COL where COL_ID = res);
    }
  return res;
nf:
  return 0;
}
;

create procedure WS.WS.HOSTFS_EXTRACT_AND_SAVE_RDF (in resid integer, in resname varchar, in restype varchar, inout rescontent any, in topcol_id integer)
{
  declare resttype varchar;
  declare old_prop_id integer;
  declare html_start, full_xml any;
  declare old_n3, addon_n3 any;
  -- dbg_obj_princ ('DAV_EXTRACT_AND_SAVE_RDF_INT (', resid, resname, restype, rescontent, ')');
  html_start := null;
  full_xml := null;
  --if (restype is null)
    restype := DAV_GUESS_MIME_TYPE (resname, rescontent, html_start);
  -- dbg_obj_princ ('restype is ', restype);
  if (restype is null)
    return;
  addon_n3 := call ('DAV_EXTRACT_RDF_' || restype)(resname, rescontent, html_start);
  -- dbg_obj_princ ('addon_n3 is', addon_n3);
  if (addon_n3 is null)
    return;
  insert replacing WS.WS.HOSTFS_RES_META (RESM_ID, RESM_DATA, RESM_TOPCOL_ID)
  values
    (resid, xml_tree_doc (DAV_RDF_PREPROCESS_RDFXML (addon_n3, N'http://local.virt/this', 1)), topcol_id);
  return;

no_op:
  ;
}
;

create procedure WS.WS.HOSTFS_TEST_RDF (in d_id integer)
{
  -- dbg_obj_princ ('WS.WS.HOSTFS_TEST_RDF (', d_id, ')');
  for select RES_COL, RES_NAME, RES_TYPE, RES_FT_MODE from WS.WS.HOSTFS_RES where RES_ID = d_id do
    {
      -- dbg_obj_princ ('Found (RES_COL, RES_NAME, RES_TYPE, RES_FT_MODE) = (', RES_COL, RES_NAME, RES_TYPE, RES_FT_MODE, ')');
      if ('N' = RES_FT_MODE)
        {
          return 1;
        }
      else if (('T' = RES_FT_MODE) or ('X' = RES_FT_MODE))
        {
          for select RESC_DATA, RESC_TOPCOL_ID from WS.WS.HOSTFS_RES_CACHE where RESC_ID = d_id do
            {
              WS.WS.HOSTFS_EXTRACT_AND_SAVE_RDF (d_id, RES_NAME, RES_TYPE, RESC_DATA, RESC_TOPCOL_ID);
            }
          return 1;
        }
      else if (('t' = RES_FT_MODE) or ('x' = RES_FT_MODE))
        {
          for select COL_FULL_PATH from WS.WS.HOSTFS_COL where COL_ID = RES_COL do
            {
              declare ses any;
	      -- dbg_obj_princ ('Found COL_FULL_PATH = ', COL_FULL_PATH);
              ses := file_to_string_output (COL_FULL_PATH || RES_NAME);
              WS.WS.HOSTFS_EXTRACT_AND_SAVE_RDF (d_id, RES_NAME, RES_TYPE, ses, WS.WS.HOSTFS_RES_TOPCOL_ID (d_id));
            }
          return 1;
        }
    }
  return 0;
}
;

create function
WS.WS.HOSTFS_RES_CACHE_RESC_DATA_INDEX_HOOK (inout vtb any, inout d_id integer) returns integer
{
  -- dbg_obj_princ ('WS.WS.HOSTFS_RES_CACHE_RESC_DATA_INDEX_HOOK ( [], ', d_id, ')');
  whenever sqlstate '*' goto done;
  for select RES_COL, RES_NAME, RES_TYPE, RES_FT_MODE from WS.WS.HOSTFS_RES where RES_ID = d_id do
    {
      -- dbg_obj_princ ('Found (RES_COL, RES_NAME, RES_TYPE, RES_FT_MODE) = (', RES_COL, RES_NAME, RES_TYPE, RES_FT_MODE, ')');
      if ('N' = RES_FT_MODE)
        {
          vt_batch_feed (vtb, RES_NAME, 0, 0);
          return 1;
        }
      else if (('T' = RES_FT_MODE) or ('X' = RES_FT_MODE))
        {
          for select RESC_DATA, RESC_TOPCOL_ID from WS.WS.HOSTFS_RES_CACHE where RESC_ID = d_id do
            {
              vt_batch_feed (vtb, RESC_DATA, 0, case (RES_FT_MODE) when 'X' then 2 else 0 end);
              WS.WS.HOSTFS_EXTRACT_AND_SAVE_RDF (d_id, RES_NAME, RES_TYPE, RESC_DATA, RESC_TOPCOL_ID);
            }
          return 1;
        }
      else if (('t' = RES_FT_MODE) or ('x' = RES_FT_MODE))
        {
          for select COL_FULL_PATH from WS.WS.HOSTFS_COL where COL_ID = RES_COL do
            {
              declare ses any;
	      -- dbg_obj_princ ('Found COL_FULL_PATH = ', COL_FULL_PATH);
              ses := file_to_string_output (COL_FULL_PATH || RES_NAME);
              vt_batch_feed (vtb, ses, 0, case (RES_FT_MODE) when 'x' then 2 else 0 end);
              WS.WS.HOSTFS_EXTRACT_AND_SAVE_RDF (d_id, RES_NAME, RES_TYPE, ses, WS.WS.HOSTFS_RES_TOPCOL_ID (d_id));
            }
          return 1;
        }
    }
done:
  -- dbg_obj_princ ('Failed WS.WS.HOSTFS_RES_CACHE_RESC_DATA_INDEX_HOOK (', d_id, ') :', __SQL_STATE, __SQL_MESSAGE);
  return 1;
}
;

create function
WS.WS.HOSTFS_RES_CACHE_RESC_DATA_UNINDEX_HOOK (inout vtb any, inout d_id integer) returns integer
{
  whenever sqlstate '*' goto done;
  for select RES_COL, RES_NAME, RES_TYPE, RES_FT_MODE from WS.WS.HOSTFS_RES where RES_ID = d_id do
    {
      if ('N' = RES_FT_MODE)
        {
          vt_batch_feed (vtb, RES_NAME, 1, 0);
          return 1;
        }
      else if (('T' = RES_FT_MODE) or ('X' = RES_FT_MODE))
        {
          for select RESC_DATA from WS.WS.HOSTFS_RES_CACHE where RESC_ID = d_id do
            vt_batch_feed (vtb, RESC_DATA, 1, case (RES_FT_MODE) when 'X' then 2 else 0 end);
          return 1;
        }
    }
done:
  return 1;
}
;

--#IF VER=5
--!AFTER __PROCEDURE__ DB.DBA.VT_CREATE_TEXT_INDEX !
--#ENDIF
DB.DBA.vt_create_text_index (fix_identifier_case ('WS.WS.HOSTFS_RES_CACHE'), fix_identifier_case ('RESC_DATA'), fix_identifier_case ('RESC_ID'), 2, 0, NULL, 1, '*ini*', '*ini*')
;

--#IF VER=5
--!AFTER __PROCEDURE__ DB.DBA.VT_CREATE_TEXT_INDEX !
--#ENDIF
DB.DBA.vt_batch_update (fix_identifier_case ('WS.WS.HOSTFS_RES_CACHE'), 'ON', 5)
;

create function
WS.WS.HOSTFS_FIND_COL (in full_path varchar) returns integer
{
  declare slash_pos, parent, len, res integer;
  declare parent_path, cname, tmp varchar;
  declare cr_time datetime;
  len := length (full_path);
  if ((len > 0) and full_path[len-1] = 47)
    full_path := subseq (full_path, 0, len-1);
  whenever not found goto not_found;
  select COL_ID into res from WS.WS.HOSTFS_COL where COL_FULL_PATH = full_path || '/';
  return res;
not_found:
  slash_pos := strrchr (full_path, '/');
  if (slash_pos is null)
    {
      parent := null;
      cname := full_path;
    }
  else
    {
      parent := WS.WS.HOSTFS_FIND_COL (subseq (full_path, 0, slash_pos));
      cname := subseq (full_path, slash_pos + 1);
    }
  tmp := file_stat (full_path);
  if (isstring (tmp))
    cr_time := cast (tmp as datetime);
  else
    cr_time := null;
  res := sequence_next ('WS.WS.HOSTFS_COL_ID') + 1;
  insert into WS.WS.HOSTFS_COL
    (COL_ID	, COL_FULL_PATH		, COL_PARENT_ID	, COL_CR_TIME	, COL_MOD_TIME	, COL_SCAN_TIME	, COL_NEXT_SCAN_TIME	, COL_NAME	)
  values
    (res	, full_path || '/'	, parent	, cr_time	, cr_time	, NULL		, now ()		, cname		);
  return res;
}
;

create procedure
WS.WS.HOSTFS_COL_DISAPPEARS (in full_path varchar)
{
  declare len integer;
  len := length (full_path);
  if ((len > 0) and full_path[len-1] = 47)
    full_path := subseq (full_path, 0, len-1);
  for select COL_ID from WS.WS.HOSTFS_COL where COL_FULL_PATH between full_path || '/' and full_path || '0' do
    {
      for select RES_ID from WS.WS.HOSTFS_RES where RES_COL = COL_ID do
        {
          delete from WS.WS.HOSTFS_RES_META where RESM_ID = RES_ID;
          delete from WS.WS.HOSTFS_RES_CACHE where RESC_ID = RES_ID;
        }
      delete from WS.WS.HOSTFS_RES where RES_COL = COL_ID;
    }
  delete from WS.WS.HOSTFS_COL where COL_FULL_PATH between full_path || '/' and full_path || '0';
}
;

create procedure
WS.WS.HOSTFS_HANDLE_RES_SCAN (in full_path varchar, in c_id integer, in flen integer, in cr_time datetime, in mod_time datetime, in mimetype varchar, in ft_mode varchar)
{
  declare len, slash_pos integer;
  declare r_id integer;
  len := length (full_path);
  if ((len = 0) or (full_path[len-1] = 47))
    return; -- There's no resource with empty name, for sure
  slash_pos := strrchr (full_path, '/');
  if (c_id is null)
    {
      if (slash_pos is null)
	c_id := WS.WS.HOSTFS_FIND_COL ('');
      else
	c_id := WS.WS.HOSTFS_FIND_COL (subseq (full_path, 0, slash_pos));
    }
  r_id := coalesce ((select RES_ID from WS.WS.HOSTFS_RES where RES_NAME = subseq (full_path, slash_pos + 1) and RES_COL = c_id));
  if (r_id is null)
    {
      r_id := sequence_next ('WS.WS.HOSTFS_RES_ID') + 1;
      insert into WS.WS.HOSTFS_RES
        (RES_ID	, RES_NAME, RES_COL, RES_TYPE, RES_FT_MODE, RES_LENGTH, RES_CR_TIME, RES_MOD_TIME, RES_SCAN_TIME, RES_NEXT_SCAN_TIME, RES_PERMS)
      values
        (r_id	, subseq (full_path, slash_pos + 1), c_id, mimetype, ft_mode, flen, cr_time, mod_time, now(), null, null);
-- TODO: add real support for 'X' and 'T' modes here.
      insert replacing WS.WS.HOSTFS_RES_CACHE
        (RESC_ID	, RESC_MOD_SCAN_TIME	, RESC_DATA	, RESC_TOPCOL_ID			)
      values
        (r_id		, now()			, null		, WS.WS.HOSTFS_RES_TOPCOL_ID (r_id)	);
    }
  else
    {
      if (exists (select top 1 1 from WS.WS.HOSTFS_RES
         where RES_ID = r_id and
           ((RES_LENGTH <> flen) or (RES_MOD_TIME <> mod_time) or (RES_TYPE <> mimetype) or (RES_FT_MODE <> ft_mode)) ) )
        {
          update WS.WS.HOSTFS_RES set RES_LENGTH = flen, RES_MOD_TIME = mod_time, RES_TYPE = mimetype, RES_FT_MODE = ft_mode, RES_SCAN_TIME = now() where RES_ID = r_id;
-- TODO: add real support for 'X' and 'T' modes here.
          update WS.WS.HOSTFS_RES_CACHE set RESC_MOD_SCAN_TIME = now();
        }
      else
        {
          update WS.WS.HOSTFS_RES set RES_SCAN_TIME = now() where (RES_ID = r_id) and RES_SCAN_TIME <> now ();
        }
    }
}
;

create procedure
WS.WS.HOSTFS_RES_DISAPPEARS (in full_path varchar)
{
  declare len, slash_pos integer;
  declare c_id, r_id integer;
  len := length (full_path);
  if ((len = 0) or full_path[len-1] = 47)
    return; -- There's no resource with empty name, for sure
  slash_pos := strrchr (full_path, '/');
  c_id := coalesce ((select COL_ID from WS.WS.HOSTFS_COL where COL_FULL_PATH = subseq (full_path, 0, slash_pos + 1)));
  if (c_id is null)
    return;
  r_id := coalesce ((select RES_ID from WS.WS.HOSTFS_RES where RES_NAME = subseq (full_path, slash_pos + 1) and RES_COL = c_id));
  if (r_id is null)
    return;
  delete from WS.WS.HOSTFS_RES_CACHE where RESC_ID = r_id;
  delete from WS.WS.HOSTFS_RES where RES_ID = r_id;
  update WS.WS.HOSTFS_COL set COL_MOD_TIME = now() where COL_ID = c_id and COL_MOD_TIME < now();
}
;

create function
WS.WS.HOSTFS_TOUCH_RES (in ospath varchar) returns integer
{
  declare mimetype, ft_mode varchar;
  declare cr_time, mod_time datetime;
  declare flen, rc integer;
  -- dbg_obj_princ ('WS.WS.HOSTFS_TOUCH_RES (', ospath, ')');
  rc := WS.WS.HOSTFS_PATH_STAT (ospath, flen, cr_time, mod_time);
  if (rc < 0)
    {
      WS.WS.HOSTFS_RES_DISAPPEARS (ospath);
      return -1;
    }
  WS.WS.HOSTFS_READ_TYPEINFO (ospath, mimetype, ft_mode);
  WS.WS.HOSTFS_HANDLE_RES_SCAN (ospath, null, flen, cr_time, mod_time, mimetype, ft_mode);
  return 0;
}
;

create procedure
WS.WS.HOSTFS_GLOBAL_RESET ()
{
  set isolation = 'serializable';
  delete from WS.WS.HOSTFS_RES_META;
  delete from WS.WS.HOSTFS_RES_CACHE;
  delete from WS.WS.HOSTFS_RES;
  delete from WS.WS.HOSTFS_COL;
  sequence_set ('WS.WS.HOSTFS_COL_ID', 0, 0);
  sequence_set ('WS.WS.HOSTFS_RES_ID', 0, 0);
}
;

create function
WS.WS.HOSTFS_PATH_STAT (in full_path varchar, out flen integer, out cr_time datetime, out mod_time datetime) returns integer
{
  declare tmp varchar;
  tmp := file_stat (full_path);
  if (not isstring (tmp))
    return -1;
  cr_time := mod_time := cast (tmp as datetime);
  flen := cast (file_stat (full_path, 1) as integer);
  return 0;
}
;

create procedure
WS.WS.HOSTFS_READ_TYPEINFO (in full_path varchar, out mimetype varchar, out ft_mode varchar)
{
  declare mt varchar;
  mt := http_mime_type (full_path);
  mimetype := mt;
  if ('text/html' = mt)
    ft_mode := 'x';
  else if ('text/xml' = mt)
    ft_mode := 'x';
  else if ('text/xhtml' = mt)
    ft_mode := 'x';
  else if ('%+xml' = mt)
    ft_mode := 'x';
  else if (mt like 'text/%')
    ft_mode := 't';
  else
    ft_mode := 'N';
}
;


create function "HostFs_DAV_AUTHENTICATE" (in id any, in what char(1), in req varchar, in auth_uname varchar, in auth_pwd varchar, in auth_uid integer)
{
  -- dbg_obj_princ ('HostFs_DAV_AUTHENTICATE (', id, what, req, auth_uname, auth_pwd, auth_uid, ')');
  declare puid, pgid integer;
  declare pperms varchar;
  if (auth_uid < 0)
    return auth_uid;
  puid := http_dav_uid();
  pgid := coalesce (
    ( select G_ID from WS.WS.SYS_DAV_GROUP
      where G_NAME = 'HostFs_' || coalesce ((select COL_NAME from WS.WS.SYS_DAV_COL where COL_ID=id[1] and COL_DET='HostFs'), '')
      ), http_admin_gid() );
  pperms := '110100100RR';
  if ((what <> 'R') and (what <> 'C'))
    return -14;
  if (DAV_CHECK_PERM (pperms, req, auth_uid, null, pgid, puid))
    return auth_uid;
  return -13;
}
;

create function "HostFs_DAV_AUTHENTICATE_HTTP" (in id any, in what char(1), in req varchar, in can_write_http integer, inout a_lines any, inout a_uname varchar, inout a_pwd varchar, inout a_uid integer, inout a_gid integer, inout _perms varchar) returns integer
{
  declare rc integer;
  declare puid, pgid integer;
  declare u_password, pperms varchar;
  declare allow_anon integer;
  if (length (req) <> 3)
    return -15;

  whenever not found goto nf_col_or_res;
  puid := http_dav_uid();
  pgid := coalesce (
    ( select G_ID from WS.WS.SYS_DAV_GROUP
      where G_NAME = 'HostFs_' || coalesce ((select COL_NAME from WS.WS.SYS_DAV_COL where COL_ID=id[1] and COL_DET='HostFs'), '')
      ), http_admin_gid() );
  pperms := '110100100RR';
  if ((what <> 'R') and (what <> 'C'))
    return -14;
  allow_anon := WS.WS.PERM_COMP (substring (cast (pperms as varchar), 7, 3), req);
  if (a_uid is null)
    {
      if ((not allow_anon) or ('' <> WS.WS.FINDPARAM (a_lines, 'Authorization:')))
      rc := WS.WS.GET_DAV_AUTH (a_lines, allow_anon, can_write_http, a_uname, u_password, a_uid, a_gid, _perms);
      if (rc < 0)
        return rc;
    }
  if (isinteger (a_uid))
    {
      if (a_uid < 0)
	return a_uid;
     if (a_uid = 1) -- Anonymous FTP
	{
          a_uid := http_nobody_uid ();
	  a_gid := http_nogroup_gid ();
	}
    }
  if (DAV_CHECK_PERM (pperms, req, a_uid, a_gid, pgid, puid))
    return a_uid;
  return -13;

nf_col_or_res:
  return -1;
}
;

create function "HostFs_DAV_GET_PARENT" (in id any, in st char(1), in path varchar) returns any
{
  -- dbg_obj_princ ('HostFs_DAV_GET_PARENT (', id, st, path, ')');
  return -20;
}
;

create function "HostFs_DAV_COL_CREATE" (in detcol_id any, in path_parts any, in permissions varchar, in uid integer, in gid integer, in auth_uid integer) returns any
{
  declare ospath varchar;
  -- dbg_obj_princ ('HostFs_DAV_COL_CREATE (', detcol_id, path_parts, permissions, uid, gid, auth_uid, ')');
  ospath := DAV_CONCAT_PATH ("HostFs_ID_TO_OSPATH" (detcol_id), path_parts);
  -- dbg_obj_princ ('cmd=', sprintf ('mkdir ''%s''', ospath));
  system (sprintf ('mkdir ''%s''', ospath));
  WS.WS.HOSTFS_FIND_COL (ospath);
  return vector (UNAME'HostFs', detcol_id, ospath);
}
;

create function "HostFs_DAV_COL_MOUNT" (in detcol_id any, in path_parts any, in full_mount_path varchar, in mount_det varchar, in permissions varchar, in uid integer, in gid integer, in auth_uid integer) returns any
{
  -- dbg_obj_princ ('HostFs_DAV_COL_MOUNT (', detcol_id, path_parts, full_mount_path, mount_det, permissions, uid, gid, auth_uid, ')');
  return -20;
}
;

create function "HostFs_DAV_COL_MOUNT_HERE" (in parent_id any, in full_mount_path varchar, in permissions varchar, in uid integer, in gid integer, in auth_uid integer) returns any
{
  -- dbg_obj_princ ('HostFs_DAV_COL_MOUNT (', parent_id, full_mount_path, permissions, uid, gid, auth_uid, ')');
  return -20;
}
;

create function "HostFs_DAV_DELETE" (in detcol_id any, in path_parts any, in what char(1), in silent integer, in auth_uid integer) returns integer
{
  declare ospath varchar;
  -- dbg_obj_princ ('HostFs_DAV_DELETE (', detcol_id, path_parts, what, silent, auth_uid, ')');
  ospath := DAV_CONCAT_PATH ("HostFs_ID_TO_OSPATH" (detcol_id), path_parts);
  -- dbg_obj_princ ('cmd=', sprintf ('rm -rf ''%s''', ospath));
  system (sprintf ('rm -rf ''%s''', ospath));
  return 1;
}
;

create table "HostFs_DAV_RES_UPLOAD" (ID varchar primary key, DT datetime, CNT long varchar)
;

create function "HostFs_DAV_RES_UPLOAD" (in detcol_id any, in path_parts any, inout content any, in type varchar, in permissions varchar, in uid integer, in gid integer, in auth_uid integer) returns any
{
  declare ospath varchar;
  declare rc integer;
  -- dbg_obj_princ ('HostFs_DAV_RES_UPLOAD (', detcol_id, path_parts, ', [content], ', type, permissions, uid, gid, auth_uid, ')');
  ospath := DAV_CONCAT_PATH ("HostFs_ID_TO_OSPATH" (detcol_id), path_parts);
  if (__tag (content) = 126)
    {
      declare p varchar;
      p := '[' || serialize (now()) || '][' || serialize (detcol_id) || '][' || serialize (path_parts) || ']';
      insert into "HostFs_DAV_RES_UPLOAD" values (p, now(), content);
-- in the next string, '1' is an invalid argument for string_to_file to guarantee 'Internal Server Error' visible by client
      string_to_file (ospath, coalesce ((select CNT from "HostFs_DAV_RES_UPLOAD" where ID=p), 1), -2);
    }
  else
    string_to_file (ospath, content, -2);
  rc := WS.WS.HOSTFS_TOUCH_RES (ospath);
  if (rc < 0)
    return -28;
  return vector (UNAME'HostFs', detcol_id, ospath);
}
;

create function "HostFs_DAV_PROP_REMOVE" (in id any, in what char(0), in propname varchar, in silent integer, in auth_uid integer) returns integer
{
  -- dbg_obj_princ ('HostFs_DAV_PROP_REMOVE (', id, what, propname, silent, auth_uid, ')');
  return -20;
}
;

create function "HostFs_DAV_PROP_SET" (in id any, in what char(0), in propname varchar, in propvalue any, in overwrite integer, in auth_uid integer) returns any
{
  -- dbg_obj_princ ('HostFs_DAV_PROP_SET (', id, what, propname, propvalue, overwrite, auth_uid, ')');
  if (propname[0] = 58)
    {
      return -16;
    }
  return -20;
}
;

create function "HostFs_DAV_PROP_GET" (in id any, in what char(0), in propname varchar, in auth_uid integer)
{
  declare ospath varchar;
  ospath := id[2];
  -- dbg_obj_princ ('HostFs_DAV_PROP_GET (', id, what, propname, auth_uid, ')');
  if (not isstring (file_stat (ospath)))
    {
      WS.WS.HOSTFS_COL_DISAPPEARS (ospath);
      WS.WS.HOSTFS_RES_DISAPPEARS (ospath);
      return -1;
    }
  return -11;
}
;

create function "HostFs_DAV_PROP_LIST" (in id any, in what char(0), in propmask varchar, in auth_uid integer)
{
  declare ospath varchar;
  ospath := id[2];
  -- dbg_obj_princ ('HostFs_DAV_PROP_LIST (', id, what, propmask, auth_uid, ')');
  if (not isstring (file_stat (ospath)))
    {
      WS.WS.HOSTFS_COL_DISAPPEARS (ospath);
      WS.WS.HOSTFS_RES_DISAPPEARS (ospath);
      return -1;
    }
  return vector ();
}
;

create function "HostFs_ID_TO_OSPATH" (in col any)
{
  declare res varchar;
  declare ctr, len integer;
  if (isinteger (col))
    return coalesce ((select COL_NAME from WS.WS.SYS_DAV_COL where COL_ID = col), ' no such ');
  return col[2];
}
;

create function "HostFs_DAV_DIR_SINGLE" (in id any, in what char(0), in path any, in auth_uid integer) returns any
{
  declare fullname, name, tmp, mimetype, ft_mode varchar;
  declare cr_time, mod_time datetime;
  declare puid, pgid, flen, rc integer;
  -- dbg_obj_princ ('HostFs_DAV_DIR_SINGLE (', id, what, path, auth_uid, ')');
  fullname := id[2];
  rc := WS.WS.HOSTFS_PATH_STAT (fullname, flen, cr_time, mod_time);
  if (rc < 0)
    {
      if ('R' = what)
	WS.WS.HOSTFS_RES_DISAPPEARS (fullname);
      else
	WS.WS.HOSTFS_COL_DISAPPEARS (fullname);
      return -1;
    }
  name := subseq (fullname, strrchr (fullname, '/') + 1);
  if (path is null)
    path := "HostFs_DAV_SEARCH_PATH" (id, what);
  puid := http_dav_uid();
  pgid := coalesce (
    ( select G_ID from WS.WS.SYS_DAV_GROUP
      where G_NAME = 'HostFs_' || coalesce ((select COL_NAME from WS.WS.SYS_DAV_COL where COL_ID=id[1] and COL_DET='HostFs'), '')
      ), puid+1);
  if ('R' = what)
    {
      WS.WS.HOSTFS_READ_TYPEINFO (fullname, mimetype, ft_mode);
      WS.WS.HOSTFS_HANDLE_RES_SCAN (fullname, null, flen, cr_time, mod_time, mimetype, ft_mode);
      return vector (path, 'R',	flen, mod_time, id, '110000000RR', pgid, puid, cr_time, mimetype, name);
    }
  if ('C' = what)
    {
      return vector (DAV_CONCAT_PATH (path, '/'), 'C', flen, mod_time, id, '110000000RR', pgid, puid, cr_time, 'dav/unix-directory', name);
    }
  return -20;
}
;

create function "HostFs_DAV_DIR_LIST" (in detcol_id any, in path_parts any, in detcol_path varchar, in name_mask varchar, in recursive integer, in auth_uid integer) returns any
{
  declare ospath, name, fullname, top_davpath varchar;
  declare stale_files, files, stale_dirs, dirs, res any;
  declare ctr, len integer;
  declare tmp, mimetype, ft_mode varchar;
  declare cr_time, mod_time datetime;
  declare puid, pgid, flen, rc, parent_c_id, r_id integer;
  -- dbg_obj_princ ('HostFs_DAV_DIR_LIST (', detcol_id, path_parts, detcol_path, name_mask, recursive, auth_uid, ')');
  ospath := DAV_CONCAT_PATH ("HostFs_ID_TO_OSPATH" (detcol_id), path_parts);
  top_davpath := DAV_CONCAT_PATH (detcol_path, path_parts);
  whenever sqlstate '39000' goto no_dir;
  dirs := sys_dirlist (ospath, 0);
  parent_c_id := WS.WS.HOSTFS_FIND_COL (ospath);
  if (parent_c_id is null)
    select VECTOR_AGG (COL_FULL_PATH) into stale_dirs from WS.WS.HOSTFS_COL where COL_PARENT_ID is null and 0 = position (COL_NAME, dirs);
  else
    select VECTOR_AGG (COL_FULL_PATH) into stale_dirs from WS.WS.HOSTFS_COL where COL_PARENT_ID = parent_c_id and 0 = position (COL_NAME, dirs);
  foreach (varchar stale_fullname in stale_dirs) do
    WS.WS.HOSTFS_COL_DISAPPEARS (stale_fullname);
  puid := http_dav_uid();
  pgid := coalesce (
    ( select G_ID from WS.WS.SYS_DAV_GROUP
      where G_NAME = 'HostFs_' || coalesce ((select COL_NAME from WS.WS.SYS_DAV_COL where COL_ID = detcol_id and COL_DET='HostFs'), '')
      ), puid+1);
  vectorbld_init (res);
  len := length (dirs);
  ctr := 0;
  while (ctr < len)
    {
      name := dirs [ctr];
      if ((name <> '.') and (name <> '..'))
        {
	  fullname := DAV_CONCAT_PATH (ospath, name);
	  -- dbg_obj_princ ('HostFs_DAV_DIR_LIST makes ', fullname);
	  rc := WS.WS.HOSTFS_PATH_STAT (fullname, flen, cr_time, mod_time);
	  if (rc < 0)
	    {
	      WS.WS.HOSTFS_COL_DISAPPEARS (fullname);
	    }
          else
            {
              vectorbld_acc (res, vector (
		DAV_CONCAT_PATH (top_davpath, name) || '/', 'C',
		flen,
		mod_time,
		vector (UNAME'HostFs', detcol_id, fullname),
		'110100000RR', pgid, puid,
		cr_time,
		'dav/unix-directory',
		name ) );
	      if (recursive > 0)
	        vectorbld_concat_acc (res,
		  "HostFs_DAV_DIR_LIST" (detcol_id,
		  vector_concat (subseq (path_parts, 0, length (path_parts)-1), vector (name, '')),
		  concat (DAV_CONCAT_PATH (detcol_path, name), '/'), name_mask, recursive, auth_uid) );
	    }
	}
      ctr := ctr + 1;
    }
  files := sys_dirlist (ospath, 1);
  if (parent_c_id is null)
    select VECTOR_AGG (RES_NAME) into stale_files from WS.WS.HOSTFS_RES where RES_COL is null and 0 = position (RES_NAME, files);
  else
    select VECTOR_AGG (RES_NAME) into stale_files from WS.WS.HOSTFS_RES where RES_COL = parent_c_id and 0 = position (RES_NAME, files);
  foreach (varchar stale_name in stale_files) do
    {
      r_id := coalesce ((select RES_ID from WS.WS.HOSTFS_RES where RES_COL = parent_c_id and RES_NAME = stale_name));
      delete from WS.WS.HOSTFS_RES_META where RESM_ID = r_id;
      delete from WS.WS.HOSTFS_RES_CACHE where RESC_ID = r_id;
      delete from WS.WS.HOSTFS_RES where RES_ID = r_id;
    }
  len := length (files);
  ctr := 0;
  while (ctr < len)
    {
      name := files [ctr];
      fullname := DAV_CONCAT_PATH (ospath, name);
      rc := WS.WS.HOSTFS_PATH_STAT (fullname, flen, cr_time, mod_time);
      if (rc < 0)
	{
	  delete from WS.WS.HOSTFS_RES_META where RESM_ID = r_id;
	  delete from WS.WS.HOSTFS_RES_CACHE where RESC_ID = r_id;
	  delete from WS.WS.HOSTFS_RES where RES_ID = r_id;
	}
      else
        {
	  WS.WS.HOSTFS_READ_TYPEINFO (fullname, mimetype, ft_mode);
	  WS.WS.HOSTFS_HANDLE_RES_SCAN (fullname, parent_c_id, flen, cr_time, mod_time, mimetype, ft_mode);
          if (name like name_mask)
	    {
	      -- dbg_obj_princ ('HostFs_DAV_DIR_LIST makes ', fullname);
	      vectorbld_acc (res, vector (
		DAV_CONCAT_PATH (top_davpath, name), 'R',
		flen,
		mod_time,
		vector (UNAME'HostFs', detcol_id, fullname),
		'110100000RR', pgid, puid,
		cr_time,
		mimetype,
		name ) );
	    }
         }
      ctr := ctr + 1;
    }
  update WS.WS.HOSTFS_COL set COL_MOD_TIME = now() where COL_ID = parent_c_id and COL_MOD_TIME < now();
  vectorbld_final (res);
  -- dbg_obj_princ ('HostFs_DAV_DIR_LIST returns ', res);
  return res;

no_dir:
  WS.WS.HOSTFS_COL_DISAPPEARS (ospath);
  return vector();
}
;


create function "HostFs_DAV_DIR_FILTER" (in detcol_id any, in path_parts any, in detcol_path varchar, in compilation varchar, in recursive integer, in auth_uid integer) returns any
{
  -- dbg_obj_princ ('HostFs_DAV_DIR_FILTER (', detcol_id, path_parts, detcol_path, compilation, recursive, auth_uid, ')');
  return vector ();
}
;


create function "HostFs_DAV_SEARCH_ID" (in detcol_id any, in path_parts any, in what char(1)) returns any
{
  declare ospath, stat varchar;
  -- dbg_obj_princ ('HostFs_DAV_SEARCH_ID (', detcol_id, path_parts, what, ')');
  ospath := DAV_CONCAT_PATH ("HostFs_ID_TO_OSPATH" (detcol_id), path_parts);
  stat := file_stat (ospath, 2);
  if (not isstring (stat))
    {
      -- dbg_obj_princ ('ospath=',ospath,', stat=',stat, ', stat is not a string');
      WS.WS.HOSTFS_COL_DISAPPEARS (ospath);
      WS.WS.HOSTFS_RES_DISAPPEARS (ospath);
      return -1;
    }
  if (what = 'R')
    {
      if (0 = bit_and (32768, cast (stat as integer)))
	{
	  -- dbg_obj_princ ('ospath=',ospath,', stat=',stat, ', stat is not a resource');
	  WS.WS.HOSTFS_RES_DISAPPEARS (ospath);
	  return -1;
	}
    }
  else -- what = 'C'
    {
      if (0 = bit_and (16384, cast (stat as integer)))
	{
	  -- dbg_obj_princ ('ospath=',ospath,', stat=',stat, ', stat is not a collection');
	  WS.WS.HOSTFS_COL_DISAPPEARS (ospath);
	  return -1;
	}
    }
  -- dbg_obj_princ ('ospath=',ospath,', stat=',stat, ', hit of type ', what);
  return vector (UNAME'HostFs', detcol_id, ospath);
}
;

create function "HostFs_DAV_SEARCH_PATH" (in id any, in what char(1)) returns any
{
  declare ospath varchar;
  declare slash_pos, detcol_fullpath integer;
  -- dbg_obj_princ ('HostFs_DAV_SEARCH_PATH (', id, what, ')');
  ospath := id[2];
  slash_pos := strchr (ospath, '/');
  detcol_fullpath := coalesce ((select WS.WS.COL_PATH (COL_ID) from WS.WS.SYS_DAV_COL where COL_ID = id[1] and COL_DET='HostFs'));
  if (detcol_fullpath is null)
    return -23;
  if (not isstring (file_stat (ospath)))
    {
      WS.WS.HOSTFS_COL_DISAPPEARS (ospath);
      WS.WS.HOSTFS_RES_DISAPPEARS (ospath);
      return -23;
    }
  return detcol_fullpath || subseq (ospath, slash_pos + 1);
}
;

create function "HostFs_DAV_RES_UPLOAD_COPY" (in detcol_id any, in path_parts any, in source_id any, in what char(1), in overwrite integer, in permissions varchar, in uid integer, in gid integer, in auth_uid integer) returns any
{
  declare ospath varchar;
  -- dbg_obj_princ ('HostFs_DAV_RES_UPLOAD_COPY (', detcol_id, path_parts, source_id, what, overwrite, permissions, uid, gid, auth_uid, ')');
  ospath := DAV_CONCAT_PATH ("HostFs_ID_TO_OSPATH" (detcol_id), path_parts);
  if (what = 'R')
    {
      declare cnt any;
      declare mime_type varchar;
      declare rc integer;
      rc := DAV_RES_CONTENT_INT (source_id, cnt, mime_type, 0, 0);
      if (rc < 0)
        {
          -- dbg_obj_princ ('DAV_RES_CONTENT_INT (', source_id, cnt, mime_type, 0, 0, ') returns ', rc);
          return rc;
        }
      string_to_file (ospath, case (__tag (cnt)) when 126 then blob_to_string (cnt) else cnt end, -2);
      rc := WS.WS.HOSTFS_TOUCH_RES (ospath);
      if (rc < 0)
	return -28;
      return vector (UNAME'HostFs', detcol_id, ospath);
    }
  return -20;
}
;

create function "HostFs_DAV_RES_UPLOAD_MOVE" (in detcol_id any, in path_parts any, in source_id any, in what char(1), in overwrite integer, in auth_uid integer) returns any
{
  declare ospath, src_path varchar;
  -- dbg_obj_princ ('HostFs_DAV_RES_UPLOAD_MOVE (', detcol_id, path_parts, source_id, what, overwrite, auth_uid, ')');
  ospath := DAV_CONCAT_PATH ("HostFs_ID_TO_OSPATH" (detcol_id), path_parts);
  if (what = 'R')
    {
      declare cnt any;
      declare mime_type varchar;
      declare rc integer;
      rc := DAV_RES_CONTENT_INT (source_id, cnt, mime_type, 0, 0);
      if (rc < 0)
        {
          -- dbg_obj_princ ('DAV_RES_CONTENT_INT (', source_id, cnt, mime_type, 0, 0, ') returns ', rc);
          return rc;
        }
      string_to_file (ospath, case (__tag (cnt)) when 126 then blob_to_string (cnt) else cnt end, -2);
      rc := WS.WS.HOSTFS_TOUCH_RES (ospath);
      if (rc < 0)
	return -28;
      src_path := DAV_SEARCH_PATH (source_id, 'R');
      if (src_path is not null)
        DAV_DELETE_INT (src_path, 1, null, null, 0);
      return vector (UNAME'HostFs', detcol_id, ospath);
    }
  return -20;
}
;

create function "HostFs_DAV_RES_CONTENT" (in id any, inout content any, out type varchar, in content_mode integer) returns integer
{
  -- dbg_obj_princ ('HostFs_DAV_RES_CONTENT (', id, ', [content], [type], ', content_mode, ')');
  whenever sqlstate '*' goto no_res;
  declare ft_mode varchar;
  if ((content_mode = 0) or (content_mode = 2))
    content := file_to_string (id[2]);
  else if (content_mode = 1)
    file_append_to_string_output (id[2], content);
  else if (content_mode = 3)
    http_file (id[2]);
  WS.WS.HOSTFS_READ_TYPEINFO (id[2], type, ft_mode);
  return 0;

no_res:
  -- dbg_obj_princ ('HostFs_DAV_RES_CONTENT (', id, ', [content], [type], ', content_mode, ') caught an error ', __SQL_STATE, __SQL_MESSAGE);
  WS.WS.HOSTFS_RES_DISAPPEARS (id[2]);
  return -1;
}
;

create function "HostFs_DAV_SYMLINK" (in detcol_id any, in path_parts any, in source_id any, in what char(1), in overwrite integer, in uid integer, in gid integer, in auth_uid integer) returns any
{
  -- dbg_obj_princ ('HostFs_DAV_SYMLINK (', detcol_id, path_parts, source_id, overwrite, uid, gid, auth_uid, ')');
  return -20;
}
;

create function "HostFs_DAV_LOCK" (in path any, in id any, in type char(1), inout locktype varchar, inout scope varchar, in token varchar, inout owner_name varchar, inout owned_tokens varchar, in depth varchar, in timeout_sec integer, in auth_uid integer) returns any
{
  -- dbg_obj_princ ('HostFs_DAV_LOCK (', path, id, type, locktype, scope, token, owner_name, owned_tokens, depth, timeout_sec, auth_uid, ')');
  return -20;
}
;

create function "HostFs_DAV_UNLOCK" (in id any, in type char(1), in token varchar, in auth_uid integer)
{
  -- dbg_obj_princ ('HostFs_DAV_UNLOCK (', id, type, token, auth_uid, ')');
  return -27;
}
;

create function "HostFs_DAV_IS_LOCKED" (inout id any, inout type char(1), in owned_tokens varchar) returns integer
{
  declare rc integer;
  declare orig_id any;
  declare orig_type char(1);
  -- dbg_obj_princ ('HostFs_DAV_IS_LOCKED (', id, type, owned_tokens, ')');
  orig_id := id;
  id := orig_id[1];
  orig_type := type;
  type := 'C';
  rc := DAV_IS_LOCKED_INT (id, type, owned_tokens);
  if (rc <> 0)
    return rc;
  id := orig_id;
  type := orig_type;
  return 0;
}
;

create function "HostFs_DAV_LIST_LOCKS" (in id any, in type char(1), in recursive integer) returns any
{
  -- dbg_obj_princ ('HostFs_DAV_LIST_LOCKS" (', id, type, recursive);
  return vector ();
}
;

create procedure "HostFs_CF_LIST_PROP_DISTVALS" (in detcol_id integer, in cfc_id integer, in rfc_spath varchar, inout rfc_list_cond any, in schema_uri varchar, inout filter_data any, inout distval_dict any, in auth_uid integer)
{
  declare topcol_name varchar;
  declare topcol_id integer;
  declare filter_length, p0_id, p1_id, p2_id, p3_id, p4_id, res0_id, res1_id, res2_id, res3_id, res4_id, res_last_id, res_id_max integer;
  declare plast_id integer;
  declare p0_val, p1_val, p2_val, p3_val, p4_val, v_last, v_max varchar;
  --declare auth_gid integer;
  --declare acl_bits any
  declare hit_ids any;
  declare c_last1 cursor for select             HRI_CATVALUE from WS.WS.HOSTFS_RDF_INVERSE
    where HRI_TOPCOL_ID = topcol_id and HRI_PROP_CATID = plast_id and (v_max is null or HRI_CATVALUE > v_max)
--    No per-resource security for a while, so there's nothing like this: and
--      exists (select top 1 1 from WS.WS.SYS_DAV_RES where RES_ID = HRI_RES_ID and case (DAV_CHECK_PERM (RES_PERMS, '1__', auth_uid, auth_gid, RES_GROUP, RES_OWNER)) when 0 then WS.WS.ACL_IS_GRANTED (RES_ACL, auth_uid, acl_bits) else 1 end)
    ;
  declare c_last2 cursor for select HRI_RES_ID, HRI_CATVALUE from WS.WS.HOSTFS_RDF_INVERSE
    where HRI_TOPCOL_ID = topcol_id and HRI_PROP_CATID = plast_id and (v_max is null or HRI_CATVALUE > v_max)
--    No per-resource security for a while, so there's nothing like this:and
--      exists (select top 1 1 from WS.WS.SYS_DAV_RES where RES_ID = HRI_RES_ID and case (DAV_CHECK_PERM (RES_PERMS, '1__', auth_uid, auth_gid, RES_GROUP, RES_OWNER)) when 0 then WS.WS.ACL_IS_GRANTED (RES_ACL, auth_uid, acl_bits) else 1 end)
    ;
  declare c0 cursor for select HRI_RES_ID from WS.WS.HOSTFS_RDF_INVERSE where HRI_TOPCOL_ID = topcol_id and HRI_PROP_CATID = p0_id and HRI_CATVALUE = p0_val and HRI_RES_ID >= res_id_max;
  declare c1 cursor for select HRI_RES_ID from WS.WS.HOSTFS_RDF_INVERSE where HRI_TOPCOL_ID = topcol_id and HRI_PROP_CATID = p1_id and HRI_CATVALUE = p1_val and HRI_RES_ID >= res_id_max;
  declare c2 cursor for select HRI_RES_ID from WS.WS.HOSTFS_RDF_INVERSE where HRI_TOPCOL_ID = topcol_id and HRI_PROP_CATID = p2_id and HRI_CATVALUE = p2_val and HRI_RES_ID >= res_id_max;
  declare c3 cursor for select HRI_RES_ID from WS.WS.HOSTFS_RDF_INVERSE where HRI_TOPCOL_ID = topcol_id and HRI_PROP_CATID = p3_id and HRI_CATVALUE = p3_val and HRI_RES_ID >= res_id_max;
  declare c4 cursor for select HRI_RES_ID from WS.WS.HOSTFS_RDF_INVERSE where HRI_TOPCOL_ID = topcol_id and HRI_PROP_CATID = p4_id and HRI_CATVALUE = p4_val and HRI_RES_ID >= res_id_max;
  -- dbg_obj_princ ('HostFs_CF_LIST_PROP_DISTVALS (', detcol_id, cfc_id, rfc_spath, rfc_list_cond, schema_uri, filter_data, auth_uid, ')');
  topcol_name := coalesce ((select COL_NAME from WS.WS.SYS_DAV_COL where COL_ID = detcol_id and COL_DET = 'HostFs'));
  if (topcol_name is null)
    {
      -- dbg_obj_princ ('HostFs_CF_LIST_PROP_DISTVALS: can not find the specified mount point', detcol_id);
      return;
    }
  topcol_id := coalesce ((select COL_ID from WS.WS.HOSTFS_COL where COL_PARENT_ID is null and COL_NAME = topcol_name), WS.WS.HOSTFS_FIND_COL (topcol_name));
  filter_length := length (filter_data);
  plast_id := filter_data [filter_length - 1];
  res_id_max := 0;
  v_max := null;
  --auth_gid := coalesce ((select U_GROUP from WS.WS.SYS_DAV_USER where U_ID = auth_uid), 0);
  --acl_bits := DAV_REQ_CHARS_TO_BITMASK ('1__');

  if (filter_length = 2) -- distinct propvals with no filtering in front -- a special case
    {
      whenever not found goto nf_c_last1;
      -- dbg_obj_princ ('HostFs_CF_LIST_PROP_DISTVALS: distinct propvals of ', plast_id, ' in ', cfc_id);
      open c_last1 (prefetch 1);
      while (1)
        {
          fetch c_last1 into v_last;
          -- dbg_obj_princ ('HostFs_CF_LIST_PROP_DISTVALS: v_last is ', v_last, ' v_max is ', v_max);
          if (v_max is null or (v_last > v_max))
            {
              v_max := v_last; -- note that vectorbld_acc() will destroy the value of v_last so this assignment should be before vectorbld_acc().
              dict_put (distval_dict, v_last, 1);
            }
        }
nf_c_last1:
      close c_last1;
      return;
    }

  res0_id := 0;
  res1_id := 0;
  res2_id := 0;
  res3_id := 0;
  res4_id := 0;
  hit_ids := dict_new ();

  p0_id := filter_data [1];
  p0_val := "CatFilter_ENCODE_CATVALUE" (filter_data [2]);
  if (filter_length = 6) -- distinct propvals with 1 fixed property
    {
      whenever not found goto get_distincts_0;
      open c0 (prefetch 1);
      while (1)
        {
          while (res0_id <= res_id_max)
            fetch c0 into res0_id;
          res_id_max := res0_id;
          -- dbg_obj_princ ('HostFs_CF_LIST_PROP_DISTVALS: put hit ', res_id_max);
          dict_put (hit_ids, res_id_max, 1);
        }
    }

  p1_id := filter_data [4+1];
  p1_val := "CatFilter_ENCODE_CATVALUE" (filter_data [4+2]);
  if (filter_length = 10) -- distinct propvals with 2 fixed property
    {
      whenever not found goto get_distincts_1;
      open c0 (prefetch 1);
      open c1 (prefetch 1);
      while (1)
        {
          while (res0_id <= res_id_max) fetch c0 into res0_id;
          if (res0_id > res_id_max) res_id_max := res0_id;
          while (res1_id < res_id_max) fetch c1 into res1_id;
          if (res1_id > res_id_max) res_id_max := res1_id;
          if ((res0_id = res_id_max) and (res1_id = res_id_max))
            {
              -- dbg_obj_princ ('HostFs_CF_LIST_PROP_DISTVALS: put hit ', res_id_max);
              dict_put (hit_ids, res_id_max, 1);
            }
          else
            res_id_max := res_id_max + 1;

        }
    }

  p2_id := filter_data [8+1];
  p2_val := "CatFilter_ENCODE_CATVALUE" (filter_data [8+2]);
  if (filter_length = 14) -- distinct propvals with 3 fixed property
    {
      whenever not found goto get_distincts_2;
      open c0 (prefetch 1);
      open c1 (prefetch 1);
      open c2 (prefetch 1);
      while (1)
        {
          -- dbg_obj_princ ('HostFs_CF_LIST_PROP_DISTVALS: res_id_max is ', res_id_max);
--        res_id_max := 0;
          while (res0_id <= res_id_max) fetch c0 into res0_id;
          -- dbg_obj_princ ('HostFs_CF_LIST_PROP_DISTVALS: res0_id is ', res0_id);
	  if (res0_id > res_id_max) res_id_max := res0_id;
          while (res1_id < res_id_max) fetch c1 into res1_id;
          -- dbg_obj_princ ('HostFs_CF_LIST_PROP_DISTVALS: res1_id is ', res1_id);
	  if (res1_id > res_id_max) res_id_max := res1_id;
          while (res2_id < res_id_max) fetch c2 into res2_id;
          -- dbg_obj_princ ('HostFs_CF_LIST_PROP_DISTVALS: res2_id is ', res2_id);
	  if (res2_id > res_id_max) res_id_max := res2_id;
          if ((res0_id = res_id_max) and (res1_id = res_id_max) and (res2_id = res_id_max))
            {
              -- dbg_obj_princ ('HostFs_CF_LIST_PROP_DISTVALS: put hit ', res_id_max);
              dict_put (hit_ids, res_id_max, 1);
            }
        }
    }

  p3_id := filter_data [12+1];
  p3_val := "CatFilter_ENCODE_CATVALUE" (filter_data [12+2]);
  if (filter_length = 18) -- distinct propvals with 4 fixed property
    {
      whenever not found goto get_distincts_3;
      open c0 (prefetch 1);
      open c1 (prefetch 1);
      open c2 (prefetch 1);
      open c3 (prefetch 1);
      while (1)
        {
          while (res0_id <= res_id_max) fetch c0 into res0_id;
	  if (res0_id > res_id_max) res_id_max := res0_id;
          while (res1_id < res_id_max) fetch c1 into res1_id;
	  if (res1_id > res_id_max) res_id_max := res1_id;
          while (res2_id < res_id_max) fetch c2 into res2_id;
	  if (res2_id > res_id_max) res_id_max := res2_id;
          while (res3_id < res_id_max) fetch c3 into res3_id;
	  if (res3_id > res_id_max) res_id_max := res3_id;
          if ((res0_id = res_id_max) and (res1_id = res_id_max) and (res2_id = res_id_max) and (res3_id = res_id_max))
            {
              -- dbg_obj_princ ('HostFs_CF_LIST_PROP_DISTVALS: put hit ', res_id_max);
              dict_put (hit_ids, res_id_max, 1);
            }
        }
    }

  p4_id := filter_data [16+1];
  p4_val := "CatFilter_ENCODE_CATVALUE" (filter_data [16+2]);
  if (filter_length = 22) -- distinct propvals with 5 fixed property
    {
      whenever not found goto get_distincts_4;
      open c0 (prefetch 1);
      open c1 (prefetch 1);
      open c2 (prefetch 1);
      open c3 (prefetch 1);
      open c4 (prefetch 1);
      while (1)
        {
          while (res0_id <= res_id_max) fetch c0 into res0_id;
	  if (res0_id > res_id_max) res_id_max := res0_id;
          while (res1_id < res_id_max) fetch c1 into res1_id;
	  if (res1_id > res_id_max) res_id_max := res1_id;
          while (res2_id < res_id_max) fetch c2 into res2_id;
	  if (res2_id > res_id_max) res_id_max := res2_id;
          while (res3_id < res_id_max) fetch c3 into res3_id;
	  if (res3_id > res_id_max) res_id_max := res3_id;
          while (res4_id < res_id_max) fetch c4 into res4_id;
	  if (res4_id > res_id_max) res_id_max := res4_id;
          if ((res0_id = res_id_max) and (res1_id = res_id_max) and (res2_id = res_id_max) and (res3_id = res_id_max) and (res4_id = res_id_max))
            dict_put (hit_ids, res_id_max, 1);
        }
    }

get_distincts_4:
  -- dbg_obj_princ ('HostFs_CF_LIST_PROP_DISTVALS: close c4');
  close c4;
get_distincts_3:
  -- dbg_obj_princ ('HostFs_CF_LIST_PROP_DISTVALS: close c3');
  close c3;
get_distincts_2:
  -- dbg_obj_princ ('HostFs_CF_LIST_PROP_DISTVALS: close c2');
  close c2;
get_distincts_1:
  -- dbg_obj_princ ('HostFs_CF_LIST_PROP_DISTVALS: close c1');
  close c1;
get_distincts_0:
  -- dbg_obj_princ ('HostFs_CF_LIST_PROP_DISTVALS: close c0');
  close c0;

  -- dbg_obj_princ ('HostFs_CF_LIST_PROP_DISTVALS: now search in all values of ', plast_id);
  whenever not found goto nf_c_last2;
  open c_last2 (prefetch 1);
  while (1)
    {
      fetch c_last2 into res_last_id, v_last;
      if (v_max is null or (v_last > v_max))
        {
          -- dbg_obj_princ ('HostFs_CF_LIST_PROP_DISTVALS: next value ', v_last, ' at ', res_last_id);
          if (dict_get (hit_ids, res_last_id, 0))
            {
              -- dbg_obj_princ ('HostFs_CF_LIST_PROP_DISTVALS: full hit at ', res_last_id);
              v_max := v_last; -- note that vectorbld_acc() will destroy the value of v_last so this assignment should be before vectorbld_acc().
              dict_put (distval_dict, v_last, 1);
            }
        }
    }
nf_c_last2:
      close c_last2;
}
;


create function "HostFs_CF_GET_RDF_HITS" (in detcol_id integer, in cfc_id integer, in rfc_spath varchar, inout rfc_list_cond any, in schema_uri varchar, inout filter_data any, in detcol_path varchar, in make_diritems integer, in auth_uid integer) returns any
{
  declare topcol_name varchar;
  declare topcol_id, acc_ctr, acc_len integer;
  declare filter_length, p0_id, p1_id, p2_id, p3_id, p4_id, res0_id, res1_id, res2_id, res3_id, res4_id, res_id_max integer;
  declare acc any;
  declare p0_val, p1_val, p2_val, p3_val, p4_val varchar;
  --declare acl_bits any;
  --declare auth_gid integer;
  declare c0 cursor for select HRI_RES_ID from WS.WS.HOSTFS_RDF_INVERSE where HRI_TOPCOL_ID = topcol_id and HRI_PROP_CATID = p0_id and HRI_CATVALUE = p0_val and HRI_RES_ID >= res_id_max
--    No per-resource security for a while, so there's nothing like this: and
--    exists (select top 1 1 from WS.WS.SYS_DAV_RES where RES_ID = HRI_RES_ID and case (DAV_CHECK_PERM (RES_PERMS, '1__', auth_uid, auth_gid, RES_GROUP, RES_OWNER)) when 0 then WS.WS.ACL_IS_GRANTED (RES_ACL, auth_uid, acl_bits) else 1 end)
    ;
  declare c1 cursor for select HRI_RES_ID from WS.WS.HOSTFS_RDF_INVERSE where HRI_TOPCOL_ID = topcol_id and HRI_PROP_CATID = p1_id and HRI_CATVALUE = p1_val and HRI_RES_ID >= res_id_max;
  declare c2 cursor for select HRI_RES_ID from WS.WS.HOSTFS_RDF_INVERSE where HRI_TOPCOL_ID = topcol_id and HRI_PROP_CATID = p2_id and HRI_CATVALUE = p2_val and HRI_RES_ID >= res_id_max;
  declare c3 cursor for select HRI_RES_ID from WS.WS.HOSTFS_RDF_INVERSE where HRI_TOPCOL_ID = topcol_id and HRI_PROP_CATID = p3_id and HRI_CATVALUE = p3_val and HRI_RES_ID >= res_id_max;
  declare c4 cursor for select HRI_RES_ID from WS.WS.HOSTFS_RDF_INVERSE where HRI_TOPCOL_ID = topcol_id and HRI_PROP_CATID = p4_id and HRI_CATVALUE = p4_val and HRI_RES_ID >= res_id_max;
  -- dbg_obj_princ ('HostFs_CF_GET_RDF_HITS (', detcol_id, cfc_id, rfc_spath, rfc_list_cond, schema_uri, filter_data, make_diritems, auth_uid, ')');
  topcol_name := coalesce ((select COL_NAME from WS.WS.SYS_DAV_COL where COL_ID = detcol_id and COL_DET = 'HostFs'));
  if (topcol_name is null)
    {
      -- dbg_obj_princ ('HostFs_CF_GET_RDF_HITS: can not find the specified mount point', detcol_id);
      return vector ();
    }
  topcol_id := coalesce ((select COL_ID from WS.WS.HOSTFS_COL where COL_PARENT_ID is null and COL_NAME = topcol_name), WS.WS.HOSTFS_FIND_COL (topcol_name));
  filter_length := length (filter_data);
  vectorbld_init (acc);

  res0_id := -1;
  res1_id := -1;
  res2_id := -1;
  res3_id := -1;
  res4_id := -1;
  res_id_max := 0;

  --auth_gid := coalesce ((select U_GROUP from WS.WS.SYS_DAV_USER where U_ID = auth_uid), 0);
  --acl_bits := DAV_REQ_CHARS_TO_BITMASK ('1__');

  p0_id := filter_data [1];
  p0_val := "CatFilter_ENCODE_CATVALUE" (filter_data [2]);
  if (filter_length = 4) -- resources with 1 fixed property
    {
      whenever not found goto get_distincts_0;
      open c0 (prefetch 1);
      while (1)
        {
          while (res0_id <= res_id_max)
            fetch c0 into res0_id;
          res_id_max := res0_id;
          -- dbg_obj_princ ('HostFs_CF_GET_RDF_HITS: put hit ', res_id_max);
          vectorbld_acc (acc, res0_id);
        }
    }

  p1_id := filter_data [4+1];
  p1_val := "CatFilter_ENCODE_CATVALUE" (filter_data [4+2]);
  if (filter_length = 8) -- resources with 2 fixed properties
    {
      whenever not found goto get_distincts_1;
      open c0 (prefetch 1);
      open c1 (prefetch 1);
      while (1)
        {
          while (res1_id < res_id_max) fetch c1 into res1_id;
          if (res1_id > res_id_max) res_id_max := res1_id;
          while (res0_id < res_id_max) fetch c0 into res0_id;
          if (res0_id > res_id_max) res_id_max := res0_id;
          if ((res0_id = res_id_max) and (res1_id = res_id_max))
            {
              -- dbg_obj_princ ('HostFs_CF_GET_RDF_HITS: put hit ', res_id_max);
	      vectorbld_acc (acc, res0_id);
              res_id_max := res_id_max + 1;
            }
        }
    }

  p2_id := filter_data [8+1];
  p2_val := "CatFilter_ENCODE_CATVALUE" (filter_data [8+2]);
  if (filter_length = 12) -- resources with 3 fixed properties
    {
      whenever not found goto get_distincts_2;
      open c0 (prefetch 1);
      open c1 (prefetch 1);
      open c2 (prefetch 1);
      while (1)
        {
          while (res1_id < res_id_max) fetch c1 into res1_id;
	  if (res1_id > res_id_max) res_id_max := res1_id;
          while (res2_id < res_id_max) fetch c2 into res2_id;
	  if (res2_id > res_id_max) res_id_max := res2_id;
          while (res0_id < res_id_max) fetch c0 into res0_id;
	  if (res0_id > res_id_max) res_id_max := res0_id;
          if ((res0_id = res_id_max) and (res1_id = res_id_max) and (res2_id = res_id_max))
            {
              -- dbg_obj_princ ('HostFs_CF_GET_RDF_HITS_RES_IDS: put hit ', res_id_max);
	      vectorbld_acc (acc, res0_id);
              res_id_max := res_id_max + 1;
            }
        }
    }

  p3_id := filter_data [12+1];
  p3_val := "CatFilter_ENCODE_CATVALUE" (filter_data [12+2]);
  if (filter_length = 16) -- resources with 4 fixed properties
    {
      whenever not found goto get_distincts_3;
      open c0 (prefetch 1);
      open c1 (prefetch 1);
      open c2 (prefetch 1);
      open c3 (prefetch 1);
      while (1)
        {
          while (res1_id < res_id_max) fetch c1 into res1_id;
	  if (res1_id > res_id_max) res_id_max := res1_id;
          while (res2_id < res_id_max) fetch c2 into res2_id;
	  if (res2_id > res_id_max) res_id_max := res2_id;
          while (res3_id < res_id_max) fetch c3 into res3_id;
	  if (res3_id > res_id_max) res_id_max := res3_id;
          while (res0_id < res_id_max) fetch c0 into res0_id;
	  if (res0_id > res_id_max) res_id_max := res0_id;
          if ((res0_id = res_id_max) and (res1_id = res_id_max) and (res2_id = res_id_max) and (res3_id = res_id_max))
            {
              -- dbg_obj_princ ('HostFs_CF_GET_RDF_HITS: put hit ', res_id_max);
	      vectorbld_acc (acc, res0_id);
              res_id_max := res_id_max + 1;
            }
        }
    }

  p4_id := filter_data [16+1];
  p4_val := "CatFilter_ENCODE_CATVALUE" (filter_data [16+2]);
  if (filter_length = 20) -- resources with 5 fixed properties
    {
      whenever not found goto get_distincts_4;
      open c0 (prefetch 1);
      open c1 (prefetch 1);
      open c2 (prefetch 1);
      open c3 (prefetch 1);
      open c4 (prefetch 1);
      while (1)
        {
          while (res1_id < res_id_max) fetch c1 into res1_id;
	  if (res1_id > res_id_max) res_id_max := res1_id;
          while (res2_id < res_id_max) fetch c2 into res2_id;
	  if (res2_id > res_id_max) res_id_max := res2_id;
          while (res3_id < res_id_max) fetch c3 into res3_id;
	  if (res3_id > res_id_max) res_id_max := res3_id;
          while (res4_id < res_id_max) fetch c4 into res4_id;
	  if (res4_id > res_id_max) res_id_max := res4_id;
          while (res0_id < res_id_max) fetch c0 into res0_id;
	  if (res0_id > res_id_max) res_id_max := res0_id;
          if ((res0_id = res_id_max) and (res1_id = res_id_max) and (res2_id = res_id_max) and (res3_id = res_id_max) and (res4_id = res_id_max))
            {
              -- dbg_obj_princ ('HostFs_CF_GET_RDF_HITS: put hit ', res_id_max);
	      vectorbld_acc (acc, res0_id);
              res_id_max := res_id_max + 1;
            }
        }
    }

get_distincts_4:
  close c4;
get_distincts_3:
  close c3;
get_distincts_2:
  close c2;
get_distincts_1:
  close c1;
get_distincts_0:
  close c0;

finalize:
  vectorbld_final (acc);
  acc_len := length (acc);
  acc_ctr := 0;
  while (acc_ctr < acc_len)
    {
      declare r_id integer;
      declare fullname varchar;
      declare full_id, diritm any;
      r_id := acc [acc_ctr];
      fullname := coalesce ((select top 1 COL_FULL_PATH || RES_NAME from WS.WS.HOSTFS_RES join WS.WS.HOSTFS_COL on (RES_COL = COL_ID) where RES_ID = r_id), '\377\377\377dead');
      full_id := vector (UNAME'HostFs', detcol_id, fullname);
      if (make_diritems = 1)
        {
	  diritm := "HostFs_DAV_DIR_SINGLE" (full_id, 'R', '(fake path)', auth_uid);
	  if (DAV_HIDE_ERROR (diritm) is not null)
	    {
	      diritm [0] := DAV_CONCAT_PATH (detcol_path, diritm[10]); -- now we can remove the fake path.
              acc [acc_ctr] := diritm;
	      acc_ctr := acc_ctr + 1;
	    }
	  else --collision in the air: someone just removed the resource from the disk :(
	    {
              if (acc_len > 1)
                {
                  acc [acc_ctr] := acc [acc_len - 1];
                  acc_len := acc_len - 1;
		  -- no need in acc_ctr := acc_ctr + 1;
                }
	    }
        }
      else
        {
          acc [acc_ctr] := full_id;
          acc_ctr := acc_ctr + 1;
        }
    }
  if (acc_len < length (acc)) -- There were collisions in the air
    {
      acc := subseq (acc, 0, acc_len);
    }
  -- dbg_obj_princ ('HostFs_CF_GET_RDF_HITS (', detcol_id, cfc_id, rfc_spath, rfc_list_cond, schema_uri, filter_data, make_diritems, auth_uid, ') returns ', acc);
  return acc;
}
;


create procedure "HostFs_RF_ID2SUFFIX" (in id any, in what char(1))
{
  if (what='C')
    {
      return sprintf ('HostDir-%d-%d',
        id[1], WS.WS.HOSTFS_FIND_COL (id[2]));
    }
  if (what='R')
    {
      declare full_path varchar;
      declare len, slash_pos integer;
      declare r_id, c_id integer;
      full_path := id[2];
      len := length (full_path);
      if ((len = 0) or (full_path[len-1] = 47))
        r_id := 0; -- There's no resource with empty name, for sure
      else
        {
          slash_pos := strrchr (full_path, '/');
	  if (c_id is null)
	    {
	      if (slash_pos is null)
		c_id := WS.WS.HOSTFS_FIND_COL ('');
	      else
		c_id := WS.WS.HOSTFS_FIND_COL (subseq (full_path, 0, slash_pos));
	    }
	  r_id := coalesce ((select RES_ID from WS.WS.HOSTFS_RES where RES_NAME = subseq (full_path, slash_pos + 1) and RES_COL = c_id), 0);
        }
      return sprintf ('HostFile-%d-%d', id[1], r_id);
    }
  signal ('OBLOM', 'Invalid arguments for HostFs_RF_ID2SUFFIX');
}
;

create procedure "HostFile_RF_SUFFIX2ID" (in suffix varchar, in what char(1))
{
  declare pairs any;
  declare r_id varchar;
  declare detcol_id integer;
  if ('R' <> what)
    return null;
  pairs := regexp_parse ('^([1-9][0-9]*)-([1-9][0-9]*)\044', suffix, 0);
  if (pairs is null)
    {
      -- dbg_obj_princ ('HostFile_RF_SUFFIX2ID (', suffix, ') failed to parse the argument');
      return null;
    }
  detcol_id := cast (subseq (suffix, pairs[2], pairs[3]) as integer);
  whenever not found goto oblom;
  select vector (UNAME'HostFs', detcol_id, COL_FULL_PATH || RES_NAME) into r_id
  from WS.WS.HOSTFS_RES join WS.WS.HOSTFS_COL on (RES_COL = COL_ID)
  where RES_ID = cast (subseq (suffix, pairs[4], pairs[5]) as integer);
  return r_id;
oblom:
  return null;
}
;

create procedure "HostDir_RF_SUFFIX2ID" (in suffix varchar, in what char(1))
{
  declare pairs any;
  declare c_id varchar;
  declare detcol_id integer;
  if ('C' <> what)
    return null;
  pairs := regexp_parse ('^([1-9][0-9]*)-([1-9][0-9]*)\044', suffix, 0);
  if (pairs is null)
    {
      -- dbg_obj_princ ('HostDir_RF_SUFFIX2ID (', suffix, ') failed to parse the argument');
      return null;
    }
  detcol_id := cast (subseq (suffix, pairs[2], pairs[3]) as integer);
  whenever not found goto oblom;
  select vector (UNAME'HostFs', detcol_id, COL_FULL_PATH) into c_id
    from WS.WS.HOSTFS_RES join WS.WS.HOSTFS_COL on (RES_COL = COL_ID);
  return c_id;
oblom:
  return NULL;
}
;