--
--  $Id: DET_CatFilter.sql,v 1.2.2.1 2012/03/08 12:54:59 source Exp $
--
--  This file is part of the OpenLink Software Virtuoso Open-Source (VOS)
--  project.
--
--  Copyright (C) 1998-2012 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
;

-- CatFilter ID structure:
-- for categories:
-- vector (UNAME'CatFilter', detcol_id, null, schema_uri, vector (prop1uri, prop1catid, prop1decodedcatvalue, prop1crop, ..., propNuri, propNcatid, propNdecodedcatvalue, propNcrop))
-- for resources:
-- vector (UNAME'CatFilter', detcol_id, original_resource_id, schema_uri, vector (prop1uri, prop1catid, prop1decodedcatvalue, prop1crop, ..., propNuri, propNcatid, propNdecodedcatvalue, propNcrop))

create function "CatFilter_DAV_AUTHENTICATE" (in id any, in what char(1), in req varchar, in auth_uname varchar, in auth_pwd varchar, in auth_uid integer)
{
  declare cfc_id integer;
  declare rfc_spath, tmp_perms varchar;
  declare rfc_list_cond, rfc_del_action any;
  declare rc, spath_id, n integer;
  -- dbg_obj_princ ('CatFilter_DAV_AUTHENTICATE (', id, what, req, auth_uname, auth_pwd, auth_uid, ')');
  rfc_spath := null;
  if (DAV_HIDE_ERROR ("CatFilter_GET_CONDITION" (id[1], cfc_id, rfc_spath, rfc_list_cond, rfc_del_action)) is null)
    return -1;
  if (not ('110' like req))
    return -13; -- Internals of ResFilter are not executable.
  spath_id := DAV_SEARCH_ID (rfc_spath, 'C');
  if (not isinteger (spath_id))
    return -13;
  if (DAV_HIDE_ERROR (spath_id) is null)
    return spath_id;
  rc := DAV_AUTHENTICATE (spath_id, 'C', req, auth_uname, auth_pwd, auth_uid);
  if (DAV_HIDE_ERROR (rc) is null)
    return rc;
  if ('C' = what)
    {
      n := length (id[4]);
      if ((n=0) or (mod (n, 4) = 2))
        tmp_perms := '100';
      else if (length (rfc_del_action) < length (rfc_list_cond))
        {
          -- dbg_obj_princ ('del_action = ', rfc_del_action, 'rfc_list_cond = ', rfc_list_cond);
          tmp_perms := '100';
        }
      else
        tmp_perms := '110';
      if (not (tmp_perms like req))
        return -13;
      return auth_uid;
    }
  else if ('R' = what)
    {
      return DAV_AUTHENTICATE (id [2], 'R', req, auth_uname, auth_pwd, auth_uid);
    }
  return -14;
}
;


create function "CatFilter_GET_CONDITION" (in detcol_id integer, out cfc_id integer, out rfc_spath varchar, out rfc_list_cond any, out rfc_del_action any)
{
  -- dbg_obj_princ ('CatFilter_GET_CONDITION (', detcol_id, '...)');
  whenever not found goto nf;
  if (isarray (detcol_id))
    return -20;
  select cast ("ResFilter_NORM" (PROP_VALUE) as integer) into cfc_id from WS.WS.SYS_DAV_PROP where PROP_NAME = 'virt:CatFilter-ID' and PROP_PARENT_ID = detcol_id and PROP_TYPE = 'C';
  select "ResFilter_NORM" (PROP_VALUE) into rfc_spath from WS.WS.SYS_DAV_PROP where PROP_NAME = 'virt:ResFilter-SearchPath' and PROP_PARENT_ID = detcol_id and PROP_TYPE = 'C';
  select "ResFilter_DECODE_FILTER" (PROP_VALUE) into rfc_list_cond from WS.WS.SYS_DAV_PROP where PROP_NAME = 'virt:ResFilter-ListCond' and PROP_PARENT_ID = detcol_id and PROP_TYPE = 'C';
  select "ResFilter_DECODE_FILTER" (PROP_VALUE) into rfc_del_action from WS.WS.SYS_DAV_PROP where PROP_NAME = 'virt:ResFilter-DelAction' and PROP_PARENT_ID = detcol_id and PROP_TYPE = 'C';
  return 0;
nf:
  return -1;
}
;


create function "CatFilter_ENCODE_CATVALUE" (in val varchar) returns varchar
{
  declare ses any;
  declare ctr, len integer;
  declare lastspace integer;
  if (val is null)
    return '! property is not set !';
  if (__tag (val) = 230)
    val := cast (val as varchar);
  ses := string_output ();
  len := length (val);
  if (len > 70)
    {
      val := subseq (val, 0, 65);
      lastspace := strrchr (val, ' ');
      if (lastspace is not null)
        val := subseq (val, 0, lastspace) || ' . . .';
      else
        val := val || '...';
    }
  if (len = 0)
    return '! empty property value !';
  len := length (val);
  for (ctr := 0; ctr < len; ctr := ctr + 1)
    {
      declare ch integer;
      ch := val [ctr];
      if ((ch < 32) or (ch = 47) or (ch = 92) or (ch = 37) or (ch = 58) or ((ch = 40) and (ctr = 0)))
        http (sprintf ('^%02x', ch), ses);
      else
        http (chr (ch), ses);
    }
  return string_output_string (ses);
}
;


create function "CatFilter_DECODE_CATVALUE" (in catval varchar, out crop integer)
{
  declare val varchar;
  declare catvallen integer;
  if ('! empty property value !' = catval)
    {
      crop := 0;
      return '';
    }
  if ('! property is not set !' = catval)
    {
      crop := 4;
      return null;
    }
  catvallen := length (catval);
  if ((catvallen >= 6) and (subseq (catval, catvallen - 6) = ' . . .'))
    {
      crop := 1;
      catvallen := catvallen - 6;
      catval := subseq (catval, 0, catvallen);
    }
  else
  if ((catvallen >= 3) and (subseq (catval, catvallen - 3) = '...'))
    {
      crop := 2;
      catvallen := catvallen - 3;
      catval := subseq (catval, 0, catvallen);
    }
  else
    crop := 0;
  val := split_and_decode (catval, 0, '^');
  return val;
}
;


create function "CatFilter_PATH_PARTS_TO_FILTER" (inout path_parts any, out schema_uri varchar, out filter_data any) returns integer
{
  declare prop_catnames varchar;
  declare pathctr, filtctr, pathlen integer;
  declare filt any;
  pathlen := length (path_parts) - 1;
  if (0 >= pathlen)
    {
      schema_uri := null;
      filter_data := null;
      return 0;
    }
-- First of all, schema should be located.

retry_after_recomp:
  whenever not found goto no_schema;
  select RS_URI, deserialize (blob_to_string(RS_PROP_CATNAMES)) into schema_uri, prop_catnames from WS.WS.SYS_RDF_SCHEMAS where RS_CATNAME = path_parts[0] and RS_PROP_CATNAMES is not null;
  filt := make_array (2 * (pathlen - 1), 'any');
  filtctr := 0;
  for (pathctr := 1; pathctr < pathlen; pathctr := pathctr + 2)
    {
      declare pos integer;
      pos := position (path_parts [pathctr], prop_catnames, 2, 6);
      if (0 = pos)
        {
          -- dbg_obj_princ ('CatFilter_PATH_PARTS_TO_FILTER (', path_parts, '...) failed to find catlabel ', path_parts [pathctr], ' in schema ', schema_uri);
          return -2;
        }
      filt [filtctr] := prop_catnames [pos - 2]; -- prop URI
      filt [filtctr + 1] := prop_catnames [pos]; -- prop catid
      if (pathctr < (pathlen - 1))
        {
          declare crop_mode integer;
          filt [filtctr + 2] := "CatFilter_DECODE_CATVALUE" (path_parts [pathctr + 1], crop_mode);
          filt [filtctr + 3] := crop_mode;
        }
      filtctr := filtctr + 4;
    }
  filter_data := filt;
  return 0;

no_schema:
  -- dbg_obj_princ ('CatFilter_PATH_PARTS_TO_FILTER (', path_parts, '...) failed to find schema with catlabel ', path_parts [0]);
  if (exists (select top 1 1 from WS.WS.SYS_RDF_SCHEMAS where RS_CATNAME = path_parts[0] and RS_PROP_CATNAMES is null))
    {
      DAV_GET_RDF_SCHEMA_N3 ((select RS_URI from WS.WS.SYS_RDF_SCHEMAS where RS_CATNAME = path_parts[0]));
      goto retry_after_recomp;
    }

  return -1;
}
;


create procedure "CatFilter_ACC_FILTER_DATA" (inout filter any, inout filter_data any)
{
  declare ctr, len integer;
  len := length (filter_data);
  len := len - mod (len, 4);
  for (ctr := 0; ctr < len; ctr := ctr + 4)
    {
      declare crop_mode integer;
      declare pred any;
      crop_mode := filter_data [ctr + 3];
      if (crop_mode = 0)
        pred := vector ('RDF_VALUE', '=', filter_data [ctr + 2], 'http://local.virt/DAV-RDF', filter_data [ctr]);
      else
      if (crop_mode = 4)
        pred := vector ('RDF_VALUE', 'is_null', 'http://local.virt/DAV-RDF', filter_data [ctr]);
      else
        pred := vector ('RDF_VALUE', 'starts_with', filter_data [ctr + 2], 'http://local.virt/DAV-RDF', filter_data [ctr]);
      vectorbld_acc (filter, pred);
    }
}
;


create function "CatFilter_DAV_SEARCH_ID_IMPL" (in detcol_id any, in path_parts any, in what char(1), inout cfc_id integer, inout rfc_spath varchar, inout rfc_list_cond any, inout rfc_del_action any, inout filter_data any) returns any
{
  declare schema_catname, schema_uri, res_name, colpath, orig_fnameext varchar;
  declare prop_catnames, filter, orig_id any;
  declare path_len, len, ctr integer;
  declare execstate, execmessage varchar;
  declare execmeta, execrows any;
  declare qry_text varchar;
  -- dbg_obj_princ ('CatFilter_DAV_SEARCH_ID_IMPL (', detcol_id, path_parts, what, ')');
  path_len := length (path_parts);
  if (not (isstring (rfc_spath)))
    {
      if (0 > "CatFilter_GET_CONDITION" (detcol_id, cfc_id, rfc_spath, rfc_list_cond, rfc_del_action))
	{
	  -- dbg_obj_princ ('broken filter - no items');
	  return -1;
	}
    }
  if (0 = path_len)
    return -1;
  res_name := path_parts [path_len - 1];
  if ('' = res_name)
    {
      if ('R' = what)
        {
          -- dbg_obj_princ ('resource with trailing slash - no items');
          return -1;
        }
    }
  else
    {
      if ('C' = what)
        {
          -- dbg_obj_princ ('collection without a trailing slash - no items');
          return -1;
        }
      if (1 = path_len)
        {
          -- dbg_obj_princ ('resource with path length = 1 - no items at depth of schemas');
          return -1;
        }
      if (2 = path_len)
        {
          -- dbg_obj_princ ('resource with path length = 2 - no items at depth of first property name under schemas');
          return -1;
        }
      if (1 = mod (path_len, 2))
        {
          -- dbg_obj_princ ('resource with even path length - no items at depth of distinct values');
          return -1;
        }
    }
  if (0 > "CatFilter_PATH_PARTS_TO_FILTER" (path_parts, schema_uri, filter_data))
    {
      -- dbg_obj_princ ('failed to convert path parts to filter - no items');
      return -1;
    }
  if ('C' = what)
    return vector (UNAME'CatFilter', detcol_id, null, schema_uri, case (length (filter_data)) when 0 then null else filter_data end);
  "ResFilter_FNSPLIT" (res_name, colpath, orig_fnameext, orig_id);

  -- dbg_obj_princ ('CatFilter_DAV_SEARCH_ID_IMPL: ', path_parts, colpath, orig_fnameext, orig_id, rfc_spath, rfc_list_cond, filter_data);

  if (isarray (orig_id)) -- TODO: remove this and make better processing to return -1 if path contains criteria that filter out orig_id
    return orig_id;
  len := length (filter_data);
  vectorbld_init (filter);
  "CatFilter_ACC_FILTER_DATA" (filter, filter_data);
  vectorbld_concat_acc (filter, get_keyword ('', rfc_list_cond));
  if (orig_id is not null)
    {
      if (isinteger (orig_id))
        vectorbld_acc (filter, vector ('RES_ID', '=', orig_id));
      else -- never happens for a while
        vectorbld_acc (filter, vector ('RES_ID_SERIALIZED', '=', serialize (orig_id)));
    }
  vectorbld_final (filter);

  qry_text := '
    select top 2 RES_ID
    from WS.WS.SYS_DAV_RES as _top ' || DAV_FC_PRINT_WHERE (filter, coalesce ((select COL_OWNER from WS.WS.SYS_DAV_COL where COL_ID = detcol_id), -1)) || ' and (_top.RES_NAME = ?) and (_top.RES_FULL_PATH between ? and ?)';
  -- dbg_obj_princ ('about to exec:\n', qry_text, '\nrfc_spath = ', rfc_spath);
  exec (qry_text,
    execstate, execmessage, vector (orig_fnameext, rfc_spath, DAV_COL_PATH_BOUNDARY (rfc_spath)), 100000000, execmeta, execrows );
  len := length (execrows);
  if (len <> 1)
    return -1;
  return vector (UNAME'CatFilter', detcol_id, execrows[0][0], schema_uri, case (length (filter_data)) when 0 then null else filter_data end);
}
;


create function "CatFilter_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 cfc_id integer;
  declare rfc_spath, tmp_perms varchar;
  declare rfc_list_cond, rfc_del_action any;
  declare rc, spath_id, n integer;
  -- dbg_obj_princ ('"CatFilter_DAV_AUTHENTICATE_HTTP" (', id, what, req, can_write_http, a_lines, a_uname, a_pwd, a_uid, a_gid, _perms, ')');
  rfc_spath := null;
  rc := DAV_HIDE_ERROR ("CatFilter_GET_CONDITION" (id[1], cfc_id, rfc_spath, rfc_list_cond, rfc_del_action));
  if (DAV_HIDE_ERROR (rc) is null)
    {
      -- dbg_obj_princ ('"CatFilter_DAV_AUTHENTICATE_HTTP" failed at CatFilter_GET_CONDITION, ', rc);
      return rc;
    }
  if (not ('110' like req))
    return -13; -- Internals of ResFilter/CatFilter are not executable.
  spath_id := DAV_SEARCH_ID (rfc_spath, 'C');
  -- dbg_obj_princ ('rfc_spath = ', rfc_spath, ', spath_id = ', spath_id);
  if (not isinteger (spath_id))
    return -13;
  if (DAV_HIDE_ERROR (spath_id) is null)
    return spath_id;
  rc := DAV_AUTHENTICATE_HTTP (spath_id, 'C', req, can_write_http, a_lines, a_uname, a_pwd, a_uid, a_gid, _perms);
  if (DAV_HIDE_ERROR (rc) is null)
    {
      return rc;
    }
  if ('C' = what)
    {
      n := length (id[4]);
      if ((n=0) or (mod (n, 4) = 2))
        tmp_perms := '100';
      else if (length (rfc_del_action) < length (rfc_list_cond))
        {
          -- dbg_obj_princ ('del_action = ', rfc_del_action, 'rfc_list_cond = ', rfc_list_cond);
          tmp_perms := '100';
        }
      else
        tmp_perms := '110';
      if (not (tmp_perms like req))
        return -13;
      return a_uid;
    }
  else if ('R' = what)
    {
      return DAV_AUTHENTICATE_HTTP (id[2], 'R', req, can_write_http, a_lines, a_uname, a_pwd, a_uid, a_gid, _perms);
    }
  return -14;
}
;


create function "CatFilter_DAV_GET_PARENT" (in id any, in st char(1), in path varchar) returns any
{
  -- dbg_obj_princ ('CatFilter_DAV_GET_PARENT (', id, st, path, ')');
  if (st = 'R')
    {
      id [2] := null;
      return id;
    }
  else if (st = 'C')
    {
      declare vlen integer;
      vlen := length (id[4]);
      if (vlen = 0)
        return id [1];
      id [4] := subseq (id [4], 0, vlen - 1);
      return id;
    }
  return -20;
}
;


create function "CatFilter_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
{
  -- dbg_obj_princ ('CatFilter_DAV_COL_CREATE (', detcol_id, path_parts, permissions, uid, gid, auth_uid, ')');
  return -20;
}
;


create function "CatFilter_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 ('CatFilter_DAV_COL_MOUNT (', detcol_id, path_parts, full_mount_path, mount_det, permissions, uid, gid, auth_uid, ')');
  return -20;
}
;


create function "CatFilter_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 ('CatFilter_DAV_COL_MOUNT (', parent_id, full_mount_path, permissions, uid, gid, auth_uid, ')');
  return -20;
}
;


create function "CatFilter_DAV_DELETE" (in detcol_id any, in path_parts any, in what char(1), in silent integer, in auth_uid integer) returns integer
{
  declare rc integer;
  declare cfc_id integer;
  declare rfc_spath, propname varchar;
  declare rfc_list_cond, rfc_del_action any;
  declare orig_id, filter_data, whole_rdf, vals, new_rdf any;
  -- dbg_obj_princ ('CatFilter_DAV_DELETE (', detcol_id, path_parts, what, silent, auth_uid, ')');
  rfc_spath := null;
  orig_id := "CatFilter_DAV_SEARCH_ID_IMPL" (detcol_id, path_parts, what, cfc_id, rfc_spath, rfc_list_cond, rfc_del_action, filter_data);
  if (DAV_HIDE_ERROR (orig_id) is null)
    return orig_id;
  if (length (rfc_del_action) < length (rfc_list_cond))
    {
      -- dbg_obj_princ ('del_action = ', rfc_del_action, 'rfc_list_cond = ', rfc_list_cond);
      return -13;
    }
  if ('R' <> what)
    return -20;
  whole_rdf := coalesce ((select PROP_VALUE from WS.WS.SYS_DAV_PROP where PROP_NAME = 'http://local.virt/DAV-RDF' and PROP_TYPE = 'R' and PROP_PARENT_ID = orig_id [2]));
  if (whole_rdf is null)
    return -1;
  if (not isstring (whole_rdf))
    whole_rdf := blob_to_string (whole_rdf);
  whole_rdf := xml_tree_doc (deserialize (whole_rdf));
  propname := filter_data [length (filter_data) - 4];
  -- dbg_obj_princ ('rdf is ', whole_rdf);
  vals := xpath_eval (
      '[xmlns:virt="virt"] /virt:rdf/virt:top-res/virt:prop[*[1][name(.) = \044propname]][virt:value]',
      whole_rdf, 0, vector ('propname', filter_data [length (filter_data) - 4]) );
  -- dbg_obj_princ ('vals=', vals);
  foreach (any val in vals) do
    {
      declare cval, decenc_val varchar;
      declare crop integer;
      cval := cast (xpath_eval ('[xmlns:virt="virt"] string (virt:value)', val, 1) as varchar);
      decenc_val := "CatFilter_DECODE_CATVALUE" ("CatFilter_ENCODE_CATVALUE" (cval), crop);
      -- dbg_obj_princ ('Found value ', val, ' of ', propname, 'decenc=', decenc_val);
      if (decenc_val = filter_data [length (filter_data) - 2])
        {
          -- dbg_obj_princ ('matches');
          XMLReplace (whole_rdf, val, null);
        }
    }
  new_rdf := xte_node (xte_head (UNAME' root'), whole_rdf);
  update WS.WS.SYS_DAV_PROP set prop_value = serialize (new_rdf) where PROP_NAME = 'http://local.virt/DAV-RDF' and PROP_TYPE = 'R' and PROP_PARENT_ID = orig_id [2];
  return 0;
}
;


create function "CatFilter_FILTER_TO_CONDITION" (inout schema_uri varchar, inout filter_data any, inout cond any) returns integer
{
  declare ctr, len integer;
  if (schema_uri is null)
    return -13;
  len := length (filter_data);
  if ((len = 0) or (0 <> mod (len, 4)))
    return -13;
  vectorbld_init (cond);
  for (ctr := 0; ctr < len; ctr := ctr + 4)
    {
      declare sample varchar;
      declare crop integer;
      crop := filter_data [ctr + 3];
--TBD proper support of crop 1,2,4
      if (2 = crop)
        return -13;
      sample := filter_data [ctr + 2];
      if (1 = crop)
        return -13; --TBD: search for appropriate full text and set sample to the full text.
      vectorbld_acc (cond, vector ('RDF_VALUE', '=', sample, 'http://local.virt/DAV-RDF', filter_data [ctr]));
    }
  vectorbld_final (cond);
  return 0;
}
;

create function "CatFilter_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 rc integer;
  declare cfc_id integer;
  declare rfc_spath, propname, schema_uri, _colpath, fnameext, orig_fnameext, orig_fullpath varchar;
  declare rfc_list_cond, rfc_del_action any;
  declare orig_id, filter_data, fit_cond any;
  -- dbg_obj_princ ('CatFilter_DAV_RES_UPLOAD (', detcol_id, path_parts, ', [content], ', type, permissions, uid, gid, auth_uid, ')');
  rfc_spath := null;
  rc := "CatFilter_GET_CONDITION" (detcol_id, cfc_id, rfc_spath, rfc_list_cond, rfc_del_action);
  if (DAV_HIDE_ERROR (rc) is null)
    return rc;
  if (length (rfc_del_action) < length (rfc_list_cond))
    {
      -- dbg_obj_princ ('del_action = ', rfc_del_action, 'rfc_list_cond = ', rfc_list_cond);
      return -13;
    }
  schema_uri := null;
  rc := "CatFilter_PATH_PARTS_TO_FILTER" (path_parts, schema_uri, filter_data);
  if (DAV_HIDE_ERROR (rc) is null)
    return rc;
  rc := "CatFilter_FILTER_TO_CONDITION" (schema_uri, filter_data, fit_cond);
  if (DAV_HIDE_ERROR (rc) is null)
    return rc;
  fit_cond := vector ('', vector_concat (fit_cond, get_keyword ('', rfc_list_cond)));
  -- dbg_obj_princ ('fit_cond=', fit_cond);
  fnameext := path_parts [length (path_parts) - 1];
  "ResFilter_FNSPLIT" (fnameext, _colpath, orig_fnameext, orig_id);
  orig_fullpath := null;
  if (orig_id is not null)
    orig_fullpath := DAV_HIDE_ERROR (DAV_SEARCH_PATH (orig_id, 'R'));
  if (orig_fullpath is null)
    orig_fullpath := DAV_CONCAT_PATH (rfc_spath, orig_fnameext);
  orig_id := DAV_RES_UPLOAD_STRSES_INT (
    orig_fullpath,
    content, '',
    permissions, '', '',
    null, null, 0,
    null, null, null,
    uid, gid, 1 );
  -- dbg_obj_princ ('Will call "ResFilter_FIT_INTO_CONDITION" (', orig_id, 'R', fit_cond, auth_uid);
  if (DAV_HIDE_ERROR (orig_id) is null)
    return orig_id;
  if (not (isinteger (orig_id)))
    return -13;
  "ResFilter_FIT_INTO_CONDITION" (orig_id, 'R', fit_cond, auth_uid);
  return vector (UNAME'CatFilter', detcol_id, orig_id, schema_uri, filter_data);
}
;

create function "CatFilter_DAV_PROP_REMOVE" (in id any, in st char(0), in propname varchar, in silent integer, in auth_uid integer) returns integer
{
  -- dbg_obj_princ ('CatFilter_DAV_PROP_REMOVE (', id, st, propname, silent, auth_uid, ')');
  if (st <> 'R')
    return -1;
  id := id[2];
  if (isarray (id))
    return call (cast (id[0] as varchar) || '_DAV_PROP_REMOVE') (id, st, propname, silent, auth_uid);
  return DAV_PROP_REMOVE_RAW (id, st, propname, silent, auth_uid);
}
;


create function "CatFilter_DAV_PROP_SET" (in id any, in st char(0), in propname varchar, in propvalue any, in overwrite integer, in auth_uid integer) returns any
{
  declare pid integer;
  declare resv any;
  -- dbg_obj_princ ('CatFilter_DAV_PROP_SET (', id, st, propname, propvalue, overwrite, auth_uid, ')');
  if (st <> 'R')
    return -1;
  id := id[2];
  if (isarray (id))
    return call (cast (id[0] as varchar) || '_DAV_PROP_SET') (id, st, propname, propvalue, overwrite, auth_uid);
  return DAV_PROP_SET_RAW (id, st, propname, propvalue, overwrite, auth_uid);
}
;


create function "CatFilter_DAV_PROP_GET" (in id any, in what char(0), in propname varchar, in auth_uid integer)
{
  declare ret varchar;
  -- dbg_obj_princ ('CatFilter_DAV_PROP_GET (', id, what, propname, auth_uid, ')');
  id := id[2];
  if (isarray (id))
    return call (cast (id[0] as varchar) || '_DAV_PROP_GET') (id, what, propname, auth_uid);
  if (propname[0] = 58)
    return DAV_PROP_GET_INT (id, what, propname, 0, null, null, auth_uid);
  whenever not found goto no_prop;
  select blob_to_string (PROP_VALUE) into ret from WS.WS.SYS_DAV_PROP where PROP_NAME = propname and PROP_PARENT_ID = id and PROP_TYPE = what;
  return ret;

no_prop:
    return -11;
}
;


create function "CatFilter_DAV_PROP_LIST" (in id any, in what char(0), in propmask varchar, in auth_uid integer)
{
  declare ret any;
  -- dbg_obj_princ ('CatFilter_DAV_PROP_LIST (', id, what, propmask, auth_uid, ')');
  id := id[2];
  if (isarray (id))
    return call (cast (id[0] as varchar) || '_DAV_PROP_LIST') (id, what, propmask, auth_uid);
  vectorbld_init (ret);
  for select PROP_NAME, PROP_VALUE from WS.WS.SYS_DAV_PROP where PROP_NAME like propmask and PROP_PARENT_ID = id and PROP_TYPE = what do {
      vectorbld_acc (ret, vector (PROP_NAME, blob_to_string (PROP_VALUE)));
    }
  vectorbld_final (ret);
  return ret;
}
;


create function "CatFilter_DAV_DIR_SINGLE" (in id any, in what char(0), in path any, in auth_uid integer) returns any
{
  -- dbg_obj_princ ('CatFilter_DAV_DIR_SINGLE (', id, what, path, auth_uid, ')');
  if ('C' = what)
    {
      declare cfc_id integer;
      declare rfc_spath varchar;
      declare rfc_list_cond, rfc_del_action any;
      declare loc_name, subcol_perms varchar;
      declare set_readonly integer;
      if (0 > "CatFilter_GET_CONDITION" (id[1], cfc_id, rfc_spath, rfc_list_cond, rfc_del_action))
	{
	-- dbg_obj_princ ('broken filter - no items');
	return -1;
	}
      subcol_perms := coalesce ((select COL_PERMS from WS.WS.SYS_DAV_COL where COL_ID = id[1]), '000000000N');
      subcol_perms[2] := 48; subcol_perms[5] := 48; subcol_perms[8] := 48; -- Can't execute in CatFilter so place zero chars.
      set_readonly := 0;
      if (length (rfc_del_action) < length (rfc_list_cond))
	{
          -- dbg_obj_princ ('del_action = ', rfc_del_action, 'rfc_list_cond = ', rfc_list_cond);
          set_readonly := 1;
	}
      else
        {
          declare filt_len integer;
          filt_len := length (id[4]);
          if ((0 = filt_len) or (mod (filt_len, 4) = 2))
            set_readonly := 1;
        }
      if (set_readonly)
        {
          subcol_perms[1] := 48; subcol_perms[4] := 48; subcol_perms[7] := 48; -- Can't write in CatFilter so place zero chars.
        }
      loc_name := path [length (path) - 2];
      return vector (path, 'C', 0, now (), id, subcol_perms, 0, auth_uid, now (), 'dav/unix-directory', loc_name );
    }
  if (isarray (id[2]))
    {
      declare diritem any;
      declare merged varchar;
      diritem := call (cast (id[0] as varchar) || '_DAV_DIR_SINGLE') (id[2], what, path, auth_uid);
      merged := "ResFilter_FNMERGE" (diritem[10], id[2]);
      diritem[0] := DAV_CONCAT_PATH (path, merged);
      diritem[10] := merged;
      -- dbg_obj_princ ('About to return in DAV_DIR_SINGLE: ', diritem);
      return diritem;
    }
  for select RES_FULL_PATH, RES_ID, length (RES_CONTENT) as clen, RES_MOD_TIME,
    RES_PERMS, RES_GROUP, RES_OWNER, RES_CR_TIME, RES_TYPE, RES_NAME as r1_RES_NAME
  from WS.WS.SYS_DAV_RES r1
  where RES_ID = id[2]
  do
    {
      declare merged varchar;
      -- dbg_obj_princ ('About to return in DAV_DIR_SINGLE: ', r1_RES_NAME, RES_ID);
      if (regexp_parse ('^([^/][^./]*) -Rf((Id[1-9][0-9]*)|([A-Z][A-Za-z0-9]+)-([A-Za-z0-9~+-]*))([.][^/]*)?\044', r1_RES_NAME, 0)) -- Suspicious names should be qualified
        {
          merged := "ResFilter_FNMERGE" (r1_RES_NAME, RES_ID);
        }
      else
        {
          declare cfc_id integer;
	  declare rfc_spath varchar;
	  declare rfc_list_cond, rfc_del_action varchar;
	  declare tmp_comp, namesakes any;
          declare namesakes_no integer;
	  if (0 > "CatFilter_GET_CONDITION" (id[1], cfc_id, rfc_spath, rfc_list_cond, rfc_del_action))
	    {
	      -- dbg_obj_princ ('broken filter - bad id in DIR_SINGLE');
	      return -1;
	    }
          tmp_comp := vector ('',
            vector_concat (
              vector (vector ('RES_NAME', '=', r1_RES_NAME)),
              get_keyword ('', rfc_list_cond) ) );
	  namesakes := DAV_DIR_FILTER_INT (rfc_spath, 1, tmp_comp, null, null, auth_uid);
	  namesakes_no := length (namesakes);
	  if (0 = namesakes_no)
	    return -1;
	  if (1 < namesakes_no)
	    merged := "ResFilter_FNMERGE" (r1_RES_NAME, RES_ID);
	  else
	    merged := r1_RES_NAME;
        }
      path [length (path) - 1] := merged;
--                   0                            1    2     3
      return vector (DAV_CONCAT_PATH ('/', path), 'R', clen, RES_MOD_TIME,
--       4   5          6          7          8            9         10
	 id, RES_PERMS, RES_GROUP, RES_OWNER, RES_CR_TIME, RES_TYPE, merged);
    }
  return -1;
}
;


create function "CatFilter_LIST_SCHEMAS" (in rfc_spath varchar, inout rfc_list_cond any, in auth_uid integer) returns any
{
  return (select VECTOR_AGG (vector (RS_URI, RS_CATNAME)) from WS.WS.SYS_RDF_SCHEMAS);
}
;

create function "CatFilter_LIST_SCHEMA_PROPS" (in rfc_spath varchar, inout rfc_list_cond any, inout schema_uri varchar, inout filter_data any, in auth_uid integer) returns any
{
  declare prop_catnames, res any;
  declare len, ctr integer;
  -- dbg_obj_princ ('CatFilter_LIST_SCHEMA_PROPS (', rfc_spath, rfc_list_cond, schema_uri, filter_data, auth_uid, ')');
  vectorbld_init (res);

retry_after_recomp:
  whenever not found goto schema_nf;
  select deserialize (cast (RS_PROP_CATNAMES as varchar)) into prop_catnames from WS.WS.SYS_RDF_SCHEMAS where RS_URI = schema_uri and RS_PROP_CATNAMES is not null;
  len := length (prop_catnames);
  for (ctr := 0; ctr < len; ctr := ctr + 6)
    {
      if (0 = position (prop_catnames [ctr], filter_data, 1, 4))
        vectorbld_acc (res, vector (prop_catnames [ctr], prop_catnames [ctr + 1]));
    }
  vectorbld_final (res);
  return res;

schema_nf:
  if (exists (select top 1 1 from WS.WS.SYS_RDF_SCHEMAS where RS_URI = schema_uri and RS_PROP_CATNAMES is null))
    {
      DAV_GET_RDF_SCHEMA_N3 (schema_uri);
      goto retry_after_recomp;
    }
  return vector();
}
;


create procedure "CatFilter_GET_RDF_INVERSE_HITS_DISTVALS" (in cfc_id integer, inout filter_data any, inout distval_dict any, in auth_uid 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, hit_ids any;
  declare c_last1 cursor for select             DRI_CATVALUE from WS.WS.SYS_DAV_RDF_INVERSE
    where DRI_CATF_ID = cfc_id and DRI_PROP_CATID = plast_id and (v_max is null or DRI_CATVALUE > v_max) and
      exists (select top 1 1 from WS.WS.SYS_DAV_RES where RES_ID = DRI_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 DRI_RES_ID, DRI_CATVALUE from WS.WS.SYS_DAV_RDF_INVERSE
    where DRI_CATF_ID = cfc_id and DRI_PROP_CATID = plast_id and (v_max is null or DRI_CATVALUE > v_max) and
      exists (select top 1 1 from WS.WS.SYS_DAV_RES where RES_ID = DRI_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 DRI_RES_ID from WS.WS.SYS_DAV_RDF_INVERSE where DRI_CATF_ID = cfc_id and DRI_PROP_CATID = p0_id and DRI_CATVALUE = p0_val and DRI_RES_ID >= res_id_max;
  declare c1 cursor for select DRI_RES_ID from WS.WS.SYS_DAV_RDF_INVERSE where DRI_CATF_ID = cfc_id and DRI_PROP_CATID = p1_id and DRI_CATVALUE = p1_val and DRI_RES_ID >= res_id_max;
  declare c2 cursor for select DRI_RES_ID from WS.WS.SYS_DAV_RDF_INVERSE where DRI_CATF_ID = cfc_id and DRI_PROP_CATID = p2_id and DRI_CATVALUE = p2_val and DRI_RES_ID >= res_id_max;
  declare c3 cursor for select DRI_RES_ID from WS.WS.SYS_DAV_RDF_INVERSE where DRI_CATF_ID = cfc_id and DRI_PROP_CATID = p3_id and DRI_CATVALUE = p3_val and DRI_RES_ID >= res_id_max;
  declare c4 cursor for select DRI_RES_ID from WS.WS.SYS_DAV_RDF_INVERSE where DRI_CATF_ID = cfc_id and DRI_PROP_CATID = p4_id and DRI_CATVALUE = p4_val and DRI_RES_ID >= res_id_max;
  -- dbg_obj_princ ('CatFilter_GET_RDF_INVERSE_HITS_DISTVALS (', cfc_id, filter_data, auth_uid, ')');
  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 ('CatFilter_GET_RDF_INVERSE_HITS_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 ('CatFilter_GET_RDF_INVERSE_HITS_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 ('CatFilter_GET_RDF_INVERSE_HITS_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 ('CatFilter_GET_RDF_INVERSE_HITS_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 ('CatFilter_GET_RDF_INVERSE_HITS_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 ('CatFilter_GET_RDF_INVERSE_HITS_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 ('CatFilter_GET_RDF_INVERSE_HITS_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 ('CatFilter_GET_RDF_INVERSE_HITS_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 ('CatFilter_GET_RDF_INVERSE_HITS_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 ('CatFilter_GET_RDF_INVERSE_HITS_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 ('CatFilter_GET_RDF_INVERSE_HITS_DISTVALS: close c4');
  close c4;
get_distincts_3:
  -- dbg_obj_princ ('CatFilter_GET_RDF_INVERSE_HITS_DISTVALS: close c3');
  close c3;
get_distincts_2:
  -- dbg_obj_princ ('CatFilter_GET_RDF_INVERSE_HITS_DISTVALS: close c2');
  close c2;
get_distincts_1:
  -- dbg_obj_princ ('CatFilter_GET_RDF_INVERSE_HITS_DISTVALS: close c1');
  close c1;
get_distincts_0:
  -- dbg_obj_princ ('CatFilter_GET_RDF_INVERSE_HITS_DISTVALS: close c0');
  close c0;

  -- dbg_obj_princ ('CatFilter_GET_RDF_INVERSE_HITS_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 ('CatFilter_GET_RDF_INVERSE_HITS_DISTVALS: next value ', v_last, ' at ', res_last_id);
          if (dict_get (hit_ids, res_last_id, 0))
            {
              -- dbg_obj_princ ('CatFilter_GET_RDF_INVERSE_HITS_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 "CatFilter_GET_RDF_INVERSE_HITS_RES_IDS" (in cfc_id integer, inout filter_data any, in auth_uid integer) returns any
{
  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 DRI_RES_ID from WS.WS.SYS_DAV_RDF_INVERSE where DRI_CATF_ID = cfc_id and DRI_PROP_CATID = p0_id and DRI_CATVALUE = p0_val and DRI_RES_ID >= res_id_max and
    exists (select top 1 1 from WS.WS.SYS_DAV_RES where RES_ID = DRI_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 DRI_RES_ID from WS.WS.SYS_DAV_RDF_INVERSE where DRI_CATF_ID = cfc_id and DRI_PROP_CATID = p1_id and DRI_CATVALUE = p1_val and DRI_RES_ID >= res_id_max;
  declare c2 cursor for select DRI_RES_ID from WS.WS.SYS_DAV_RDF_INVERSE where DRI_CATF_ID = cfc_id and DRI_PROP_CATID = p2_id and DRI_CATVALUE = p2_val and DRI_RES_ID >= res_id_max;
  declare c3 cursor for select DRI_RES_ID from WS.WS.SYS_DAV_RDF_INVERSE where DRI_CATF_ID = cfc_id and DRI_PROP_CATID = p3_id and DRI_CATVALUE = p3_val and DRI_RES_ID >= res_id_max;
  declare c4 cursor for select DRI_RES_ID from WS.WS.SYS_DAV_RDF_INVERSE where DRI_CATF_ID = cfc_id and DRI_PROP_CATID = p4_id and DRI_CATVALUE = p4_val and DRI_RES_ID >= res_id_max;
  -- dbg_obj_princ ('CatFilter_GET_RDF_INVERSE_HITS_RES_IDS (', cfc_id, filter_data, auth_uid, ')');
  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 ('CatFilter_GET_RDF_INVERSE_HITS_RES_IDS: 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 ('CatFilter_GET_RDF_INVERSE_HITS_RES_IDS: 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 ('CatFilter_GET_RDF_INVERSE_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 ('CatFilter_GET_RDF_INVERSE_HITS_RES_IDS: 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 ('CatFilter_GET_RDF_INVERSE_HITS_RES_IDS: 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);
  -- dbg_obj_princ ('CatFilter_GET_RDF_INVERSE_HITS_RES_IDS (', cfc_id, filter_data, auth_uid, ') returns ', acc);
  return acc;
}
;


create function "CatFilter_LIST_PROP_DISTVALS_AUX" (inout dict any, inout rfp varchar, inout vals any)
{
  -- dbg_obj_princ ('CatFilter_LIST_PROP_DISTVALS_AUX will store ', length (vals), 'values for ', rfp);
  foreach (any val in vals) do
    {
      -- dbg_obj_princ ('CatFilter_LIST_PROP_DISTVALS_AUX will store ', xpath_eval('..', val));
      dict_put (dict, "CatFilter_ENCODE_CATVALUE" (cast (val as varchar)), 1);
    }
  return 1;
}
;

create function "CatFilter_LIST_PROP_DISTVALS" (in detcol_id integer, in cfc_id integer, in rfc_spath varchar, inout rfc_list_cond any, inout schema_uri varchar, inout filter_data any, in auth_uid integer) returns any
{
  declare prop_catnames, filter, res any;
--  declare compilation any;
  declare len, ctr integer;
  declare execstate, execmessage varchar;
  declare execmeta, execrows any;
  declare qry_ft, qry_where, qry_text varchar;

  declare pred_metas, cmp_metas, table_metas any;
  declare used_tables any;
  declare dict any;
  declare auth_gid integer;

  -- dbg_obj_princ ('CatFilter_LIST_PROP_DISTVALS (', cfc_id, rfc_spath, rfc_list_cond, schema_uri, filter_data, auth_uid, ')');
  dict := dict_new ();

  if ((length (get_keyword ('', rfc_list_cond)) = 0) and (length (filter_data) > 0) and (length (filter_data) <= 22))
    { -- Optimized merge intersection on inverse hits
      "CatFilter_GET_RDF_INVERSE_HITS_DISTVALS" (cfc_id, filter_data, dict, auth_uid);
      goto plain_resources_passed;
    }

  len := length (filter_data);
  vectorbld_init (filter);
  "CatFilter_ACC_FILTER_DATA" (filter, filter_data);
  vectorbld_concat_acc (filter, get_keyword ('', rfc_list_cond));
  vectorbld_final (filter);

  -- dbg_obj_princ ('DAV_FC_PRINT_WHERE (', filter, auth_uid, ')');
  DAV_FC_PRED_METAS (pred_metas);
  DAV_FC_CMP_METAS (cmp_metas);
  DAV_FC_TABLE_METAS (table_metas);
  qry_ft := sprintf ('virt:rdf/virt:top-res/virt:prop[*[1][self::(!%s!)]]/virt:value', filter_data [len-2]);
  used_tables := vector (
    'SYS_DAV_RES', vector ('SYS_DAV_RES', '_top', null, vector(), vector(), vector()),
    'SYS_DAV_PROP, PROP_NAME=http://local.virt/DAV-RDF', vector ('SYS_DAV_PROP', '_rdf', '(_rdf.PROP_NAME = ''http://local.virt/DAV-RDF'')', vector(), vector(), vector('[' || qry_ft || ']'))
    );
  qry_where := DAV_FC_PRINT_WHERE_INT (filter, pred_metas, cmp_metas, table_metas, used_tables,
    coalesce ((select COL_OWNER from WS.WS.SYS_DAV_COL where COL_ID = detcol_id), -1) );

  auth_gid := coalesce ((select U_GROUP from WS.WS.SYS_DAV_USER where U_ID = auth_uid), 0);

--  compilation := vector ('', filter, 'DAV', DAV_FC_PRINT_WHERE (filter, auth_uid));
  qry_text := '
select count ( "CatFilter_LIST_PROP_DISTVALS_AUX" (?, _top.RES_FULL_PATH,
  xpath_eval (''[xmlns:virt="virt"] /' || qry_ft ||''',
    xml_tree_doc (deserialize (cast (_rdf.PROP_VALUE as varchar))),
    0 ) ) )
from WS.WS.SYS_DAV_RES as _top
' || qry_where || ' and
  (_top.RES_FULL_PATH between ' || WS.WS.STR_SQL_APOS (rfc_spath) || ' and ' || WS.WS.STR_SQL_APOS (DAV_COL_PATH_BOUNDARY (rfc_spath)) || ') and
  case (DAV_CHECK_PERM (_top.RES_PERMS, ''1__'', ?, ?, _top.RES_GROUP, _top.RES_OWNER)) when 0 then WS.WS.ACL_IS_GRANTED (_top.RES_ACL, ?, DAV_REQ_CHARS_TO_BITMASK (''1__'')) else 1 end
';
      -- dbg_obj_princ ('about to exec:\n', qry_text, '\nrfc_spath = ', rfc_spath);
  exec (qry_text,
    execstate, execmessage, vector (dict, auth_uid, auth_gid, auth_uid), 1, execmeta, execrows );
  -- dbg_obj_princ ('execstate = ', execstate, ' execmessage = ', execmessage);

plain_resources_passed:
  for
    select CFD_DET_SUBCOL_ID, CFD_DET from WS.WS.SYS_DAV_CATFILTER_DETS where CFD_CF_ID = cfc_id
  do
    {
      if (exists (select top 1 1 from SYS_PROCEDURES where P_NAME = fix_identifier_case('DB.DBA.') || CFD_DET || '_CF_LIST_PROP_DISTVALS'))
        call (CFD_DET || '_CF_LIST_PROP_DISTVALS') (CFD_DET_SUBCOL_ID, cfc_id, rfc_spath, rfc_list_cond, schema_uri, filter_data, dict, auth_uid);
    }
  return dict_list_keys (dict, 1);
}
;

create function "CatFilter_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 cfc_id integer;
  declare rfc_spath varchar;
  declare rfc_list_cond, rfc_del_action any;
  declare davpath, prev_raw_name, schema_uri, subcol_perms varchar;
  declare depth integer;
  declare res, resources, itm, reps, filter_data any;
  declare ctr, itm_ctr, itm_count, prev_is_patched, set_readonly integer;
  declare filter any;
  -- dbg_obj_princ ('CatFilter_DAV_DIR_LIST (', detcol_id, path_parts, detcol_path, name_mask, recursive, auth_uid, ')');
  vectorbld_init (res);
  filter_data := null;
  if (0 > "CatFilter_GET_CONDITION" (detcol_id, cfc_id, rfc_spath, rfc_list_cond, rfc_del_action))
    {
      -- dbg_obj_princ ('broken filter - no items');
      goto final_res;
    }
  subcol_perms := coalesce ((select COL_PERMS from WS.WS.SYS_DAV_COL where COL_ID = detcol_id), '000000000N');
  subcol_perms[2] := 48; subcol_perms[5] := 48; subcol_perms[8] := 48; -- Can't execute in CatFilter so place zero chars.
  if (1 < length(path_parts))
    {
      if ("CatFilter_PATH_PARTS_TO_FILTER" (path_parts, schema_uri, filter_data) < 0)
        {
          -- dbg_obj_princ ('"CatFilter_DAV_DIR_LIST" ends due to failed "CatFilter_PATH_PARTS_TO_FILTER"');
          goto final_res;
        }
    }
  else
    filter_data := null;
  set_readonly := 0;
  if (length (rfc_del_action) < length (rfc_list_cond))
    {
      -- dbg_obj_princ ('del_action = ', rfc_del_action, 'rfc_list_cond = ', rfc_list_cond);
      set_readonly := 1;
    }
  else
    if (-1 = recursive)
      {
        if ((2 = length(path_parts)) or (mod (length (filter_data), 4) = 2))
          set_readonly := 1;
      }
    else
      {
        if ((1 = length(path_parts)) or (mod (length (filter_data), 4) = 0))
          set_readonly := 1;
      }
  if (set_readonly)
    {
      subcol_perms[1] := 48; subcol_perms[4] := 48; subcol_perms[7] := 48; -- Can't write in CatFilter so place zero chars.
    }
  depth := length(path_parts);
-- level 0 -- schemas;
  if (1 = length(path_parts))
    {
      declare schemas any;
      -- dbg_obj_princ ('level of list of schemas');
      if ('' <> path_parts[0])
        {
          -- dbg_obj_princ ('no resources at level 0');
          return vector();
        }
      schemas := "CatFilter_LIST_SCHEMAS" (rfc_spath, rfc_list_cond, auth_uid);
      foreach (any sch in schemas) do
        {
          declare subcol_fullpath varchar;
          subcol_fullpath := DAV_CONCAT_PATH (detcol_path, sch[1] || '/');
          vectorbld_acc (res,
            vector (subcol_fullpath, 'C', 0, now (),
	      vector (UNAME'CatFilter', detcol_id, null, sch[0], null),
	      subcol_perms, 0, auth_uid, now (), 'dav/unix-directory', sch[1]) );
          if (recursive > 0)
            vectorbld_concat_acc (res,
              "CatFilter_DAV_DIR_LIST" (detcol_id,
                 vector_concat (subseq (path_parts, 0, length (path_parts) - 1), vector (sch[1], '')),
                 detcol_path, -- not subcol_fullpath,
                 name_mask, recursive, auth_uid ) );
        }
      goto final_res;
    }
  if ("CatFilter_PATH_PARTS_TO_FILTER" (path_parts, schema_uri, filter_data) < 0)
    {
      -- dbg_obj_princ ('"CatFilter_DAV_DIR_LIST" ends due to failed "CatFilter_PATH_PARTS_TO_FILTER"');
      goto final_res;
    }
  -- dbg_obj_princ ('"CatFilter_DAV_DIR_LIST" founds schema_uri = ', schema_uri, ' filter_data = ', filter_data);
-- We crop at 5 property values, i.e. 20 items in filter data. Sixth property is not displayed to keep URI short.
  if (mod (length (filter_data), 4) = 2)
    { -- list distinct values at odd levels
      declare distvals any;
      -- dbg_obj_princ ('level of distinct values');
      distvals := "CatFilter_LIST_PROP_DISTVALS" (detcol_id, cfc_id, rfc_spath, rfc_list_cond, schema_uri, filter_data, auth_uid);
      if (-1 = recursive)
        {
          --if (0 = length (distvals))
          --  return vector();
          return vector (
            vector (DAV_CONCAT_PATH (detcol_path, path_parts), 'C', 0, now (),
	      vector (UNAME'CatFilter', detcol_id, null, schema_uri, filter_data),
	      subcol_perms, 0, auth_uid, now (), 'dav/unix-directory', path_parts [depth - 2] ) );
        }
      foreach (varchar val in distvals) do
        {
          declare subcol_fullpath varchar;
          subcol_fullpath := DAV_CONCAT_PATH ( DAV_CONCAT_PATH (detcol_path, path_parts), val || '/');
          vectorbld_acc (res,
            vector (subcol_fullpath, 'C', 0, now (),
	      vector (UNAME'CatFilter', detcol_id, null, schema_uri, vector_concat (filter_data, vector (val))),
	      subcol_perms, 0, auth_uid, now (), 'dav/unix-directory', val) );
          if (recursive > 0)
            vectorbld_concat_acc (res,
              "CatFilter_DAV_DIR_LIST" (detcol_id,
                 vector_concat (subseq (path_parts, 0, length (path_parts) - 1), vector (val, '')),
                 detcol_path, -- not subcol_fullpath,
                 name_mask, recursive, auth_uid ) );
        }
      goto final_res;
    }
  else if (length (filter_data) <= 16)
    {
      declare sch_props any;
      -- dbg_obj_princ ('level of prop list');
      sch_props := "CatFilter_LIST_SCHEMA_PROPS" (rfc_spath, rfc_list_cond, schema_uri, filter_data, auth_uid);
      if (-1 = recursive)
        {
          --if (0 = length (sch_props))
          --  return vector();
          return vector (
            vector (DAV_CONCAT_PATH (detcol_path, path_parts), 'C', 0, now (),
	      vector (UNAME'CatFilter', detcol_id, null, schema_uri, filter_data),
	      subcol_perms, 0, auth_uid, now (), 'dav/unix-directory', path_parts [depth - 2] ) );
        }
-- The 'if' below disables infinite recursion.
-- All resources will be displayed, but not all subcollections.
-- This is the longest possible finite list CatFilter can offer to the application.
      if (length (filter_data) >= 4)
        recursive := 0;
      foreach (any prop in sch_props) do
        {
          declare subcol_fullpath varchar;
          subcol_fullpath := DAV_CONCAT_PATH (DAV_CONCAT_PATH (detcol_path, path_parts), prop[1] || '/');
          vectorbld_acc (res,
            vector (subcol_fullpath, 'C', 0, now (),
	      vector (UNAME'CatFilter', detcol_id, null, prop[0], null),
	      subcol_perms, 0, auth_uid, now (), 'dav/unix-directory', prop[1]) );
          if (recursive > 0)
            vectorbld_concat_acc (res,
              "CatFilter_DAV_DIR_LIST" (detcol_id,
                 vector_concat (subseq (path_parts, 0, length (path_parts) - 1), vector (prop[1], '')),
                 detcol_path, -- not subcol_fullpath,
                 name_mask, recursive, auth_uid ) );
        }
    }
  -- dbg_obj_princ ('res = ', res);
  if (0 = length (filter_data))
    {
      -- dbg_obj_princ ('no resources with 0 filter data len');
      goto final_res; -- otherwise all files are listed from all schemas.
    }

  if ((length (get_keyword ('', rfc_list_cond)) = 0) and (length (filter_data) > 0) and (length (filter_data) <= 20))
    { -- Optimized merge intersection on inverse hits
      declare res_ids, res_dir_single any;
      res_ids := "CatFilter_GET_RDF_INVERSE_HITS_RES_IDS" (cfc_id, filter_data, auth_uid);
      -- dbg_obj_princ ('resources = ', res_ids);
      itm_count := length (res_ids);
      vectorbld_init (resources);
      for (itm_ctr := 0; itm_ctr < itm_count; itm_ctr := itm_ctr + 1)
        {
          declare r_id integer;
          r_id := res_ids [itm_ctr];
          res_dir_single := coalesce ((
	    select
--                    0                                        1    2                     3
              vector (DAV_CONCAT_PATH (detcol_path, RES_NAME), 'R', length (RES_CONTENT), RES_MOD_TIME,
--              4     5          6          7          8            9         10
	        r_id, RES_PERMS, RES_GROUP, RES_OWNER, RES_CR_TIME, RES_TYPE, RES_NAME )
	    from WS.WS.SYS_DAV_RES
	    where RES_ID = r_id ) );
	  if (res_dir_single is not null)
	    vectorbld_acc (resources, res_dir_single);
        }
      for select CFD_DET_SUBCOL_ID, CFD_DET from WS.WS.SYS_DAV_CATFILTER_DETS where CFD_CF_ID = cfc_id do
        {
          declare det_res_ids any;
          if (exists (select top 1 1 from SYS_PROCEDURES where P_NAME = fix_identifier_case('DB.DBA.') || CFD_DET || '_CF_GET_RDF_HITS'))
            {
              det_res_ids := call (CFD_DET || '_CF_GET_RDF_HITS') (CFD_DET_SUBCOL_ID, cfc_id, rfc_spath, rfc_list_cond, schema_uri, filter_data, detcol_path, 1, auth_uid);
	      vectorbld_concat_acc (resources, det_res_ids);
            }
        }
      vectorbld_final (resources);
    }
  else
    {
      vectorbld_init (filter);
      "CatFilter_ACC_FILTER_DATA" (filter, filter_data);
      vectorbld_concat_acc (filter, get_keyword ('', rfc_list_cond));
      -- dbg_obj_princ ('name_mask = ', name_mask);
      if ('%' <> name_mask)
        {
          -- dbg_obj_princ ('Adding check for name mask');
          vectorbld_acc (filter, vector ('RES_NAME', 'like', name_mask));
        }
      vectorbld_final (filter);
      filter := vector ('', filter);
      resources := DAV_DIR_FILTER_INT (rfc_spath, 1, filter, null, null, auth_uid);
    }
  reps := dict_new ();
  itm_count := length (resources);
  for (itm_ctr := 0; itm_ctr < itm_count; itm_ctr := itm_ctr + 1)
    {
      declare rname varchar;
      declare orig_id any;
      itm := resources [itm_ctr];
      rname := itm [10];
      orig_id := itm[4];
      if (isarray (orig_id) or regexp_parse ('^([^/][^./]*) -Rf((Id[1-9][0-9]*)|([A-Z][A-Za-z0-9]+)-([A-Za-z0-9~+-]*))([.][^/]*)?\044', rname, 0)) -- Suspicious names should be qualified
        resources [itm_ctr][10] := rname := "ResFilter_FNMERGE" (rname, orig_id);
      dict_put (reps, rname, dict_get (reps, rname, 0) + 1);
    }
  for (itm_ctr := 0; itm_ctr < itm_count; itm_ctr := itm_ctr + 1)
    {
      declare rname varchar;
      declare orig_id integer;
      itm := resources [itm_ctr];
      rname := itm [10];
      orig_id := itm[4];
      resources[itm_ctr][4] := vector (UNAME'CatFilter', detcol_id, orig_id);
      if (dict_get (reps, rname, 0) > 1) -- Suspicious names should be qualified
        resources [itm_ctr][10] := rname := "ResFilter_FNMERGE" (rname, orig_id);
      resources[itm_ctr][0] := DAV_CONCAT_PATH (DAV_CONCAT_PATH (detcol_path, path_parts), rname);
    }
  vectorbld_concat_acc (res, resources);
  -- dbg_obj_princ ('res = ', res);

final_res:
  vectorbld_final (res);
  -- dbg_obj_princ ('\nCatFilter_DAV_DIR_LIST (', detcol_id, path_parts, detcol_path, name_mask, recursive, auth_uid, ') returns ', length (res), ' items:\n');
  -- foreach (any i in res) do -- dbg_obj_princ (i);
  return res;
}
;


create function "CatFilter_DAV_DIR_FILTER" (in detcol_id any, in path_parts any, in detcol_path varchar, inout compilation any, in recursive integer, in auth_uid integer) returns any
{
  declare cfc_id integer;
  declare rfc_spath varchar;
  declare rfc_list_cond, rfc_del_action any;
  declare davpath, prev_raw_name varchar;
  declare res, itm, reps any;
  declare itm_ctr, itm_count, prev_is_patched integer;
  -- dbg_obj_princ ('CatFilter_DAV_DIR_FILTER (', detcol_id, path_parts, detcol_path, compilation, recursive, auth_uid, ')');
  if (0 > "CatFilter_GET_CONDITION" (detcol_id, cfc_id, rfc_spath, rfc_list_cond, rfc_del_action))
    {
      -- dbg_obj_princ ('broken filter - no items');
      return vector();
    }
  if (0 = length (get_keyword ('', compilation)))
    res := DAV_DIR_FILTER_INT (rfc_spath, 1, rfc_list_cond, null, null, auth_uid);
  else
    {
      declare tmp_cond any;
      tmp_cond := vector ('',
        vector_concat (
          get_keyword ('', compilation),
          get_keyword ('', rfc_list_cond) ) );
      res := DAV_DIR_FILTER_INT (rfc_spath, 1, tmp_cond, null, null, auth_uid);
    }
  reps := dict_new ();
  itm_count := length (res);
  for (itm_ctr := 0; itm_ctr < itm_count; itm_ctr := itm_ctr + 1)
    {
      declare rname varchar;
      declare orig_id integer;
      itm := res [itm_ctr];
      rname := itm [10];
      orig_id := itm[4];
      if (isarray (orig_id) or regexp_parse ('^([^/][^./]*) -Rf((Id[1-9][0-9]*)|([A-Z][A-Za-z0-9]+)-([A-Za-z0-9~+-]*))([.][^/]*)?\044', rname, 0)) -- Suspicious names should be qualified
        res [itm_ctr][10] := rname := "ResFilter_FNMERGE" (rname, orig_id);
      dict_put (reps, rname, dict_get (reps, rname, 0) + 1);
    }
  for (itm_ctr := 0; itm_ctr < itm_count; itm_ctr := itm_ctr + 1)
    {
      declare rname varchar;
      declare orig_id integer;
      itm := res [itm_ctr];
      rname := itm [10];
      orig_id := itm[4];
      res[itm_ctr][4] := vector (UNAME'CatFilter', detcol_id, orig_id);
      if (dict_get (reps, rname, 0) > 1) -- Suspicious names should be qualified
        res [itm_ctr][10] := rname := "ResFilter_FNMERGE" (rname, orig_id);
      res[itm_ctr][0] := DAV_CONCAT_PATH (DAV_CONCAT_PATH (detcol_path, path_parts), rname);
    }
  return res;
}
;


create function "CatFilter_DAV_SEARCH_ID" (in detcol_id any, in path_parts any, in what char(1)) returns any
{
  declare cfc_id integer;
  declare rfc_spath varchar;
  declare rfc_list_cond, rfc_del_action any;
  declare orig_id, filter_data any;
  -- dbg_obj_princ ('CatFilter_DAV_SEARCH_ID (', detcol_id, path_parts, what, ')');
  rfc_spath := null;
  orig_id := "CatFilter_DAV_SEARCH_ID_IMPL" (detcol_id, path_parts, what, cfc_id, rfc_spath, rfc_list_cond, rfc_del_action, filter_data);
  return orig_id;
}
;


create function "CatFilter_DAV_SEARCH_PATH" (in id any, in what char(1)) returns any
{
  -- dbg_obj_princ ('CatFilter_DAV_SEARCH_PATH (', id, what, ')');
  if ('R' = what)
    return coalesce ((select RES_FULL_PATH from WS.WS.SYS_DAV_RES where RES_ID = id[2]), null);
  if ('C' = what)
    {
      declare res varchar;
      res := DAV_SEARCH_PATH (id[1], 'C');
      if (id[3] is not null)
        {
        -- TBD
          ;
        }
      if (id[4] is not null)
        {
        -- TBD
          ;
        }
      return res;
    }

  return -14;
}
;


create function "CatFilter_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 cfc_id integer;
  declare rfc_spath, schema_uri varchar;
  declare rfc_list_cond, rfc_del_action, filter_data, fit_cond any;
  declare rc integer;
  -- dbg_obj_princ ('CatFilter_DAV_RES_UPLOAD_COPY (', detcol_id, path_parts, source_id, what, overwrite, permissions, uid, gid, auth_uid, ')');
  if (0 > "CatFilter_GET_CONDITION" (detcol_id, cfc_id, rfc_spath, rfc_list_cond, rfc_del_action))
    {
      -- dbg_obj_princ ('broken filter - no items');
      return -2;
    }
  if (length (rfc_del_action) < length (rfc_list_cond))
    {
      -- dbg_obj_princ ('del_action = ', rfc_del_action, 'rfc_list_cond = ', rfc_list_cond);
      return -13;
    }
  rc := "CatFilter_PATH_PARTS_TO_FILTER" (path_parts, schema_uri, filter_data);
  if (DAV_HIDE_ERROR (rc) is null)
    return rc;
  rc := "CatFilter_FILTER_TO_CONDITION" (schema_uri, filter_data, fit_cond);
  if (DAV_HIDE_ERROR (rc) is null)
    return rc;
  fit_cond := vector ('', vector_concat (fit_cond, get_keyword ('', rfc_list_cond)));
  if ('R' <> what)
    return -2;
  if ('' = path_parts [length (path_parts) - 1])
    return -2;
  if (isinteger (source_id) and
     exists (select 1 from WS.WS.SYS_DAV_RES
         where RES_ID = source_id and RES_NAME = path_parts [length (path_parts) - 1] and (RES_FULL_PATH between rfc_spath and DAV_COL_PATH_BOUNDARY (rfc_spath)) ) )
    {
      "ResFilter_FIT_INTO_CONDITION" (source_id, what, fit_cond, auth_uid);
    }
  else
    {
      declare new_full_path varchar;
      new_full_path := DAV_CONCAT_PATH (rfc_spath, path_parts [length (path_parts) - 1]);
      rc := DAV_COPY_INT (DAV_SEARCH_PATH (source_id, what), new_full_path, overwrite, permissions,
         coalesce ((select U_NAME from WS.WS.SYS_DAV_USER where U_ID = uid), ''),
         coalesce ((select G_NAME from WS.WS.SYS_DAV_GROUP where G_ID = gid), ''),
         null, null, 0);
      if (DAV_HIDE_ERROR (rc) is null)
        return rc;
      source_id := DAV_SEARCH_ID (new_full_path, what);
      if (DAV_HIDE_ERROR (source_id) is null)
        return source_id;
      if (not (isinteger (source_id)))
        return -13;
      "ResFilter_FIT_INTO_CONDITION" (source_id, what, fit_cond, auth_uid);
    }
  return 1;
}
;


create function "CatFilter_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 cfc_id integer;
  declare rfc_spath, schema_uri varchar;
  declare rfc_list_cond, rfc_del_action, filter_data, fit_cond any;
  declare rc integer;
  -- dbg_obj_princ ('CatFilter_DAV_RES_UPLOAD_MOVE (', detcol_id, path_parts, source_id, what, overwrite, auth_uid, ')');
  if (0 > "CatFilter_GET_CONDITION" (detcol_id, cfc_id, rfc_spath, rfc_list_cond, rfc_del_action))
    {
      -- dbg_obj_princ ('broken filter - no items');
      return -2;
    }
  if (length (rfc_del_action) < length (rfc_list_cond))
    {
      -- dbg_obj_princ ('del_action = ', rfc_del_action, 'rfc_list_cond = ', rfc_list_cond);
      return -13;
    }
  rc := "CatFilter_PATH_PARTS_TO_FILTER" (path_parts, schema_uri, filter_data);
  if (DAV_HIDE_ERROR (rc) is null)
    return rc;
  rc := "CatFilter_FILTER_TO_CONDITION" (schema_uri, filter_data, fit_cond);
  if (DAV_HIDE_ERROR (rc) is null)
    return rc;
  fit_cond := vector ('', vector_concat (fit_cond, get_keyword ('', rfc_list_cond)));
  if ('R' <> what)
    return -2;
  if ('' = path_parts [length (path_parts) - 1])
    return -2;
  if (isinteger (source_id) and
    exists (select 1 from WS.WS.SYS_DAV_RES
        where RES_ID = source_id and RES_NAME = path_parts [length (path_parts) - 1] and (RES_FULL_PATH between rfc_spath and DAV_COL_PATH_BOUNDARY (rfc_spath))))
    {
      "ResFilter_FIT_INTO_CONDITION" (source_id, what, fit_cond, auth_uid);
    }
  else
    {
      declare new_full_path varchar;
      new_full_path := DAV_CONCAT_PATH (rfc_spath, path_parts [length (path_parts) - 1]);
      rc := DAV_MOVE_INT (DAV_SEARCH_PATH (source_id, what), new_full_path, overwrite, null, null, 0, 1);
      if (DAV_HIDE_ERROR (rc) is null)
        return rc;
      source_id := DAV_SEARCH_ID (new_full_path, what);
      if (DAV_HIDE_ERROR (source_id) is null)
        return source_id;
      if (not (isinteger (source_id)))
        return -13;
      "ResFilter_FIT_INTO_CONDITION" (source_id, what, fit_cond, auth_uid);
    }
  return 1;
}
;


create function "CatFilter_DAV_RES_CONTENT" (in id any, inout content any, out type varchar, in content_mode integer) returns integer
{
  -- dbg_obj_princ ('CatFilter_DAV_RES_CONTENT (', id, ', [content], [type], ', content_mode, ')');
  declare cont any;
  if ((content_mode = 0) or (content_mode = 2))
    select RES_CONTENT, RES_TYPE into content, type from WS.WS.SYS_DAV_RES where RES_ID = id[2];
  else if (content_mode = 1)
    select http (RES_CONTENT, content), RES_TYPE into cont, type from WS.WS.SYS_DAV_RES where RES_ID = id[2];
  else if (content_mode = 3)
    select http (RES_CONTENT), RES_TYPE into cont, type from WS.WS.SYS_DAV_RES where RES_ID = id[2];
  return id[2];
}
;


create function "CatFilter_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 ('CatFilter_DAV_SYMLINK (', detcol_id, path_parts, source_id, overwrite, uid, gid, auth_uid, ')');
  return -20;
}
;


create function "CatFilter_DAV_LOCK" (in path any, inout 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
{
  declare rc, u_token, new_token varchar;
  -- dbg_obj_princ ('CatFilter_DAV_LOCK (', path, id, type, locktype, scope, token, owner_name, owned_tokens, depth, timeout_sec, auth_uid, ')');
  if ('R' <> type)
    return -20;
  if (DAV_HIDE_ERROR (id) is null)
    return -20;
  if (isarray (id))
    return DAV_LOCK_INT (path, id[2], type, locktype, scope, token, owner_name, owned_tokens, depth, timeout_sec, null, null, auth_uid);
  return -20;
}
;


create function "CatFilter_DAV_UNLOCK" (in id any, in type char(1), in token varchar, in auth_uid integer)
{
  -- dbg_obj_princ ('CatFilter_DAV_UNLOCK (', id, type, token, auth_uid, ')');
  if (isarray (id))
    id := id [2];
  return DAV_UNLOCK_INT (id, type, token, null, null, auth_uid);
}
;


create function "CatFilter_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 ('CatFilter_DAV_IS_LOCKED (', id, type, owned_tokens, ')');
  orig_id := id;
  id := orig_id[2];
  rc := DAV_IS_LOCKED_INT (id, type, owned_tokens);
  if (rc <> 0)
    return rc;
  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 "CatFilter_DAV_LIST_LOCKS" (in id any, in type char(1), in recursive integer) returns any
{
  declare res any;
  -- dbg_obj_princ ('CatFilter_DAV_LIST_LOCKS" (', id, type, recursive);
  id := id[2];
  if (isarray (id))
    return call (cast (id[0] as varchar) || '_DAV_LIST_LOCKS') (id, type, recursive);
  res := vector();
  for select LOCK_TYPE, LOCK_SCOPE, LOCK_TOKEN, LOCK_TIMEOUT, LOCK_OWNER, LOCK_OWNER_INFO
    from WS.WS.SYS_DAV_LOCK where LOCK_PARENT_ID = id and LOCK_PARENT_TYPE = type do {
      res := vector_concat (res, vector (vector (LOCK_TYPE, LOCK_SCOPE, LOCK_TOKEN, LOCK_TIMEOUT, LOCK_OWNER, LOCK_OWNER_INFO)));
    }
  return res;
}
;


create function "CatFilter_CONFIGURE" (in col any, in search_path varchar, in filter any, in auth_uname varchar := 'dav', in auth_upwd varchar := 'dav', in auth_uid integer := null) returns integer
{
  declare cfid, rc, ctr integer;
  declare colname varchar;
  declare compilation, del_act any;
  compilation := vector ('', filter);
  rc := DAV_DIR_FILTER_INT (search_path, 1, compilation, auth_uname, auth_upwd, auth_uid);
  if (isinteger (rc))
    return rc;
  if (not isinteger (col))
    return -20;
  colname := DAV_SEARCH_PATH (col, 'C');
  if (not (isstring (colname)))
    return -23;
  rc := DAV_SEARCH_ID (search_path, 'C');
  if (DAV_HIDE_ERROR (rc) is null)
    return rc;
  if (search_path <> DAV_SEARCH_PATH (rc, 'C'))
    return -2;
  if (search_path between colname and (colname || '\255\255\255\255'))
    return -28;
  rc := DAV_PROP_SET_INT (colname, 'virt:ResFilter-SearchPath', search_path, null, null, 0, 1, 1);
  if (DAV_HIDE_ERROR (rc) is null)
    return rc;
  rc := DAV_PROP_SET_INT (colname, 'virt:ResFilter-ListCond', "ResFilter_ENCODE_FILTER" (compilation), null, null, 0, 1, 1);
  if (DAV_HIDE_ERROR (rc) is null)
    return rc;
  del_act := "ResFilter_MAKE_DEL_ACTION_FROM_CONDITION" (compilation);
  -- dbg_obj_princ ('ResFilter_CONFIGURE has made del_action ', del_act, ' from ', compilation);
  rc := DAV_PROP_SET_INT (colname, 'virt:ResFilter-DelAction', "ResFilter_ENCODE_FILTER" (del_act), null, null, 0, 1, 1);
  if (DAV_HIDE_ERROR (rc) is null)
    return rc;

  cfid := coalesce ((select CF_ID from WS.WS.SYS_DAV_CATFILTER where CF_SEARCH_PATH = search_path));
  if (cfid is null)
    {
      declare search_path_z varchar;
      cfid := WS.WS.GETID ('CF');
      insert into WS.WS.SYS_DAV_CATFILTER (CF_ID, CF_SEARCH_PATH) values (cfid, search_path);
      search_path_z := search_path || '\255\255\255\255';
      for (select p.PROP_VALUE, p.PROP_PARENT_ID
        from WS.WS.SYS_DAV_RES r join WS.WS.SYS_DAV_PROP p on (r.RES_ID = p.PROP_PARENT_ID)
        where (r.RES_FULL_PATH between search_path and search_path_z) and (p.PROP_NAME = 'http://local.virt/DAV-RDF') and (p.PROP_TYPE = 'R')) do
	{
	  "CatFilter_FEED_DAV_RDF_INVERSE" (PROP_VALUE, PROP_PARENT_ID, 0, cfid);
	  ctr := ctr + 1;
	  if (mod (ctr, 1000) = 0)
	    commit work;
	}
      commit work;
      for (select COL_ID, COL_DET, WS.WS.COL_PATH (COL_ID) as _c_path from WS.WS.SYS_DAV_COL where COL_DET is not null and not (COL_DET like '%Filter')) do
        {
          if ("LEFT" (_c_path, length (search_path)) = search_path)
	    {
              insert replacing WS.WS.SYS_DAV_CATFILTER_DETS (CFD_CF_ID, CFD_DET_SUBCOL_ID, CFD_DET)
	      values (cfid, COL_ID, COL_DET);
    	    }
	}
    }
--  if (search_path between colname and (colname || '\255\255\255\255'))
--    return -28;
--  if (colname between search_path and (search_path || '\255\255\255\255'))
--    return -28;
  rc := DAV_PROP_SET_INT (colname, 'virt:CatFilter-ID', cast (cfid as varchar), null, null, 0, 1, 1);
  if (DAV_HIDE_ERROR (rc) is null)
    return rc;
  update WS.WS.SYS_DAV_COL set COL_DET='CatFilter' where COL_ID=col;

  return 0;
}
;


create procedure "CatFilter_FEED_DAV_RDF_INVERSE" (inout propval any, inout propparent integer, in is_del integer := 0, in cfid integer := null)
{
  declare resfullpath, path_head, pv varchar;
  declare doc any;
  declare triplets any;

  if (126 = __tag (propval))
    pv := blob_to_string (propval);
  else
    {
      if ((not isstring (propval)) or (propval = ''))
	return;
      pv := propval;
    }
  if (193 <> pv[0])
    return;
  doc := null;
  if (cfid is not null)
    {
      path_head := '/';
      goto cfid_found;
    }
  else
    {
      resfullpath := coalesce ((select r.RES_FULL_PATH from WS.WS.SYS_DAV_RES r where r.RES_ID = propparent));
      if (resfullpath is null)
        return;
      path_head := subseq (resfullpath, 0, strrchr (resfullpath, '/'));
    }

next_cfid:
  while (1)
    {
      if (length (path_head) <= 1)
        return;
      cfid := coalesce ((select CF_ID from WS.WS.SYS_DAV_CATFILTER where CF_SEARCH_PATH = (path_head || '/')));
      path_head := subseq (path_head, 0, strrchr (path_head, '/'));
      if (cfid is not null)
        goto cfid_found;
    }

cfid_found:
  if (doc is null)
    {
      doc := deserialize (pv);
      if (0 = length (doc))
        return;
      doc := xml_tree_doc (doc);
    }
  -- dbg_obj_princ ('CatFilter_INS_DAV_RDF_INVERSE: saving res ', propparent, ' in catfilter ', cfid);
  triplets := xpath_eval ('[xmlns:virt="virt"] /virt:rdf/virt:top-res/virt:prop[virt:value]', doc, 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 ('CatFilter_INS_DAV_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.SYS_DAV_RDF_INVERSE
        where
          (DRI_CATF_ID = cfid) and (DRI_PROP_CATID = prop_catid) and
          (DRI_CATVALUE = "CatFilter_ENCODE_CATVALUE" (cast (xpath_eval ('[xmlns:virt="virt"] virt:value', prop) as varchar))) and
          (DRI_RES_ID = propparent);
      else
        insert soft WS.WS.SYS_DAV_RDF_INVERSE (DRI_CATF_ID, DRI_PROP_CATID, DRI_CATVALUE, DRI_RES_ID)
        values (
          cfid,
          prop_catid,
          "CatFilter_ENCODE_CATVALUE" (cast (xpath_eval ('[xmlns:virt="virt"] virt:value', prop) as varchar)),
          propparent );
    }
  goto next_cfid;
}
;


create trigger SYS_DAV_PROP_VALUE_RDF_I after insert on WS.WS.SYS_DAV_PROP order 10 referencing new as NP
{
  if (NP.PROP_NAME <> 'http://local.virt/DAV-RDF')
    return;
  if (NP.PROP_TYPE <> 'R')
    return;
  "CatFilter_FEED_DAV_RDF_INVERSE" (NP.PROP_VALUE, NP.PROP_PARENT_ID);
}
;


create trigger SYS_DAV_PROP_VALUE_RDF_D before delete on WS.WS.SYS_DAV_PROP order 10 referencing old as OP
{
  declare pv varchar;
  declare doc any;
  if (OP.PROP_NAME <> 'http://local.virt/DAV-RDF')
    return;
  if (OP.PROP_TYPE <> 'R')
    return;
  "CatFilter_FEED_DAV_RDF_INVERSE" (OP.PROP_VALUE, OP.PROP_PARENT_ID, 1);
}
;


create trigger SYS_DAV_PROP_VALUE_RDF_U after update on WS.WS.SYS_DAV_PROP order 10 referencing old as OP, new as NP
{
  declare pv varchar;
  declare doc any;
  if (OP.PROP_NAME <> 'http://local.virt/DAV-RDF')
    goto register_new_propvals;
  if (OP.PROP_TYPE <> 'R')
    goto register_new_propvals;
  "CatFilter_FEED_DAV_RDF_INVERSE" (OP.PROP_VALUE, OP.PROP_PARENT_ID, 1);

register_new_propvals:
  if (NP.PROP_NAME <> 'http://local.virt/DAV-RDF')
    return;
  if (NP.PROP_TYPE <> 'R')
    return;
  "CatFilter_FEED_DAV_RDF_INVERSE" (NP.PROP_VALUE, NP.PROP_PARENT_ID);
}
;


create procedure "CatFilter_INIT_SYS_DAV_RDF_INVERSE" (in run_if_once integer)
{
  declare ctr integer;
  set isolation = 'committed';
  if (run_if_once)
    {
      if (0 <> sequence_next('CatFilter_INIT_SYS_DAV_RDF_INVERSE'))
        return;
    }
  else
    {
-- For safety, schemas should be recompiled.
      update WS.WS.SYS_RDF_SCHEMAS set RS_PRECOMPILED = null, RS_PROP_CATNAMES = null;
      commit work;
      delete from WS.WS.SYS_DAV_RDF_INVERSE;
    }
  commit work;
  for (select PROP_VALUE, PROP_PARENT_ID from WS.WS.SYS_DAV_PROP where PROP_NAME = 'http://local.virt/DAV-RDF' and PROP_TYPE = 'R') do
    {
      "CatFilter_FEED_DAV_RDF_INVERSE" (PROP_VALUE, PROP_PARENT_ID);
      ctr := ctr + 1;
      if (mod (ctr, 1000) = 0)
        commit work;
    }
}
;
