--
--  This file is part of the OpenLink Software Virtuoso Open-Source (VOS)
--  project.
--
--  Copyright (C) 1998-2024 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_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_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 ('CatFilter_DAV_AUTHENTICATE (', id, what, req, auth_uname, auth_pwd, auth_uid, ')');
  declare cfc_id integer;
  declare rfc_spath, tmp_perms varchar;
  declare rfc_list_cond, rfc_del_action any;
  declare rc, spath_id, n integer;

  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))
      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_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) = __tag of XML)
    val := cast (val as varchar);

  len := length (val);
  if (len = 0)
    return '! empty property value !';

  if (len > 70)
  {
    val := subseq (val, 0, 65);
    lastspace := strrchr (val, ' ');
    if (lastspace is not null)
      val := subseq (val, 0, lastspace) || ' . . .';
    else
      val := val || '...';
  }

  ses := string_output ();
  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 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;
  }

  return split_and_decode (catval, 0, '^');
}
;


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 function "CatFilter_ACC_FILTER_DATA" (
  inout filter any,
  inout filter_data any)
{
  declare ctr, len integer;
  declare crop_mode integer;
  declare pred any;

  len := length (filter_data);
  len := len - mod (len, 4);
  for (ctr := 0; ctr < len; ctr := ctr + 4)
  {
    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
{
  -- dbg_obj_princ ('CatFilter_DAV_SEARCH_ID_IMPL (', detcol_id, path_parts, what, ')');
  declare schema_uri, res_name, colpath, orig_fnameext varchar;
  declare filter, orig_id any;
  declare path_len, len integer;
  declare execstate, execmessage varchar;
  declare execmeta, execrows any;
  declare qry_text varchar;

  path_len := length (path_parts);
  if (0 = path_len)
    return -1;

  if (not isstring (rfc_spath))
  {
    if (0 > "CatFilter_GET_CONDITION" (detcol_id, cfc_id, rfc_spath, rfc_list_cond, rfc_del_action))
      return -1;
  }

  res_name := path_parts [path_len - 1];
  if ('' = res_name)
  {
    if ('R' = what)
      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 DB.DBA.CatFilter__id (detcol_id, null, schema_uri, filter_data);
  }

  "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 );
  if (length (execrows) <> 1)
    return -1;

  return DB.DBA.CatFilter__id (detcol_id, execrows[0][0], schema_uri, filter_data);
}
;


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
{
  -- dbg_obj_princ ('"CatFilter_DAV_AUTHENTICATE_HTTP" (', id, what, req, can_write_http, a_lines, a_uname, a_pwd, a_uid, a_gid, _perms, ')');
  declare cfc_id integer;
  declare rfc_spath, tmp_perms varchar;
  declare rfc_list_cond, rfc_del_action any;
  declare rc, spath_id, n integer;

  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))
        {
          tmp_perms := '100';
        }
      else
        {
          tmp_perms := '110';
        }

      if (not (tmp_perms like req))
        return -13;

      return a_uid;
    }

  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;
  }

  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
{
  -- dbg_obj_princ ('CatFilter_DAV_DELETE (', detcol_id, path_parts, what, silent, auth_uid, ')');
  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;

  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))
    return -13;

  if ('R' <> what)
    return -20;

  whole_rdf := (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;

    if (1 = crop)
      return -13; --TBD: search for appropriate full text and set sample to the full text.

    sample := filter_data [ctr + 2];
    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
{
  -- dbg_obj_princ ('CatFilter_DAV_RES_UPLOAD (', detcol_id, path_parts, ', [content], ', type, permissions, uid, gid, auth_uid, ')');
  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;

  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))
    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 DB.DBA.CatFilter__id (detcol_id, orig_id, schema_uri, filter_data);
}
;

create function "CatFilter_DAV_PROP_REMOVE" (
  in id any,
  in st char(1),
  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(1),
  in propname varchar,
  in propvalue any,
  in overwrite integer,
  in auth_uid integer) returns 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(1),
  in propname varchar,
  in auth_uid integer)
{
  -- dbg_obj_princ ('CatFilter_DAV_PROP_GET (', id, what, propname, auth_uid, ')');
  declare ret varchar;

  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(1),
  in propmask varchar,
  in auth_uid integer)
{
  -- dbg_obj_princ ('CatFilter_DAV_PROP_LIST (', id, what, propmask, auth_uid, ')');
  declare ret any;

  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(1),
  in path any,
  in auth_uid integer) returns any
{
  -- dbg_obj_princ ('CatFilter_DAV_DIR_SINGLE (', id, what, path, auth_uid, ')');

  if (isnull (path))
    path := DAV_SEARCH_PATH (id, what);

  if ('C' = what)
  {
    declare cfc_id integer;
    declare rfc_spath varchar;
    declare dirsingle, 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))
    {
      return -1;
    }

    dirsingle := DAV_DIR_SINGLE_INT (id[1], 'C', 'fake', null, null, null, 0);

    -- Can't execute in CatFilter so place zero chars.
    subcol_perms := dirsingle[5];
    subcol_perms[2] := 48;
    subcol_perms[5] := 48;
    subcol_perms[8] := 48;
    set_readonly := 0;
    if (length (rfc_del_action) < length (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)
    {
      -- Can't write in CatFilter so place zero chars.
      subcol_perms[1] := 48;
      subcol_perms[4] := 48;
      subcol_perms[7] := 48;
    }
    loc_name := path [length (path) - 2];
    return vector (DAV_CONCAT_PATH (path, null), 'C', 0, now (), id, subcol_perms, dirsingle[6], dirsingle[7], 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;

    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))
      {
        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             4   5          6          7          8            9         10
    return vector (DAV_CONCAT_PATH ('/', path), 'R', clen, RES_MOD_TIME, id, RES_PERMS, RES_GROUP, RES_OWNER, RES_CR_TIME, RES_TYPE, merged);
  }
  return -1;
}
;


create function "CatFilter_LIST_SCHEMAS" () 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
{
  -- dbg_obj_princ ('CatFilter_LIST_SCHEMA_PROPS (', rfc_spath, rfc_list_cond, schema_uri, filter_data, auth_uid, ')');
  declare prop_catnames, res any;
  declare len, ctr integer;

  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 function "CatFilter_GET_RDF_INVERSE_HITS_DISTVALS" (
  in cfc_id integer,
  inout filter_data any,
  inout distval_dict any,
  in auth_uid integer)
{
  -- dbg_obj_princ ('CatFilter_GET_RDF_INVERSE_HITS_DISTVALS (', cfc_id, filter_data, auth_uid, ')');
  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;

  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
  {
    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
{
  -- dbg_obj_princ ('CatFilter_LIST_PROP_DISTVALS (', detcol_id, cfc_id, rfc_spath, rfc_list_cond, schema_uri, filter_data, auth_uid, ')');
  declare filter any;
  -- declare compilation any;
  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;

  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;
    }

  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 [length (filter_data)-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
{
  -- dbg_obj_princ ('CatFilter_DAV_DIR_LIST (', detcol_id, path_parts, detcol_path, name_mask, recursive, auth_uid, ')');
  declare cfc_id integer;
  declare rfc_spath varchar;
  declare dirsingle, rfc_list_cond, rfc_del_action any;
  declare davpath, schema_uri, subcol_perms varchar;
  declare depth integer;
  declare res, resources, itm, reps, filter_data any;
  declare itm_ctr, itm_count, set_readonly integer;
  declare filter any;

  if ((recursive = -1) and isarray (detcol_id))
    return DB.DBA.CatFilter_DAV_DIR_SINGLE (detcol_id, 'C', null, auth_uid);

  vectorbld_init (res);
  if (0 > "CatFilter_GET_CONDITION" (detcol_id, cfc_id, rfc_spath, rfc_list_cond, rfc_del_action))
  {
    goto final_res;
  }

  depth := length (path_parts);
  filter_data := null;

  set_readonly := 0;
  if (length (rfc_del_action) < length (rfc_list_cond))
    {
      set_readonly := 1;
    }
  else if (-1 = recursive)
    {
      if ((2 = depth) or (mod (depth, 4) = 2))
        set_readonly := 1;
    }
  else
    {
      if ((1 = depth) or (mod (depth, 4) = 0))
        set_readonly := 1;
    }

  dirsingle := DAV_DIR_SINGLE_INT (detcol_id, 'C', 'fake', null, null, null, 0);

  -- Can't execute in CatFilter so place zero chars.
  subcol_perms := dirsingle[5];
  subcol_perms[2] := 48;
  subcol_perms[5] := 48;
  subcol_perms[8] := 48;
  if (set_readonly)
    {
      -- Can't write in CatFilter so place zero chars.
      subcol_perms[1] := 48;
      subcol_perms[4] := 48;
      subcol_perms[7] := 48;
    }

  -- level 0 -- schemas;
  if (1 = depth)
    {
      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" ();
      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 (), DB.DBA.CatFilter__id (detcol_id, null, sch[0], null), subcol_perms, dirsingle[6], dirsingle[7], 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,  name_mask, recursive, auth_uid));
            }
        }
      goto final_res;
    }

    if ("CatFilter_PATH_PARTS_TO_FILTER" (path_parts, schema_uri, filter_data) < 0)
      {
        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;

      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 (), DB.DBA.CatFilter__id (detcol_id, null, schema_uri, filter_data), subcol_perms, dirsingle[6], dirsingle[7], 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 (), DB.DBA.CatFilter__id (detcol_id, null, schema_uri, vector_concat (filter_data, vector (val))), subcol_perms, dirsingle[6], dirsingle[7], 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, 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 (), DB.DBA.CatFilter__id (detcol_id, null, schema_uri, filter_data), subcol_perms, dirsingle[6], dirsingle[7], 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 (), DB.DBA.CatFilter__id (detcol_id, null, prop[0], null), subcol_perms, dirsingle[6], dirsingle[7], 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, 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 := (
      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] := DB.DBA.CatFilter__id (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);

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
{
  -- dbg_obj_princ ('CatFilter_DAV_DIR_FILTER (', detcol_id, path_parts, detcol_path, compilation, recursive, auth_uid, ')');
  declare cfc_id integer;
  declare rfc_spath varchar;
  declare rfc_list_cond, rfc_del_action any;
  declare davpath varchar;
  declare res, itm, reps any;
  declare itm_ctr, itm_count integer;

  if (0 > "CatFilter_GET_CONDITION" (detcol_id, cfc_id, rfc_spath, rfc_list_cond, rfc_del_action))
  {
    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] := DB.DBA.CatFilter__id (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
{
  -- dbg_obj_princ ('CatFilter_DAV_SEARCH_ID (', detcol_id, path_parts, what, ')');
  declare cfc_id integer;
  declare rfc_spath varchar;
  declare rfc_list_cond, rfc_del_action any;
  declare filter_data any;

  rfc_spath := null;
  return "CatFilter_DAV_SEARCH_ID_IMPL" (detcol_id, path_parts, what, cfc_id, rfc_spath, rfc_list_cond, rfc_del_action, filter_data);
}
;


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 (select RES_FULL_PATH from WS.WS.SYS_DAV_RES where RES_ID = id[2]);

  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,
  in auth_uname varchar := null,
  in auth_pwd varchar := null,
  in extern integer := 1,
  in check_locks any := 1) returns any
{
  -- dbg_obj_princ ('CatFilter_DAV_RES_UPLOAD_COPY (', detcol_id, path_parts, source_id, what, overwrite, permissions, uid, gid, auth_uid, ')');
  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;

  if ('R' <> what)
    return -2;

  if ('' = path_parts [length (path_parts) - 1])
    return -2;

  if (0 > "CatFilter_GET_CONDITION" (detcol_id, cfc_id, rfc_spath, rfc_list_cond, rfc_del_action))
    return -2;

  if (length (rfc_del_action) < length (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 (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), ''),
        auth_uname,
        auth_pwd,
        extern);
      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,
  in auth_uname varchar := null,
  in auth_pwd varchar := null,
  in extern integer := 1,
  in check_locks any := 1) returns any
{
  -- dbg_obj_princ ('CatFilter_DAV_RES_UPLOAD_MOVE (', detcol_id, path_parts, source_id, what, overwrite, auth_uid, ')');
  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;

  if ('R' <> what)
    return -2;

  if ('' = path_parts [length (path_parts) - 1])
    return -2;

  if (0 > "CatFilter_GET_CONDITION" (detcol_id, cfc_id, rfc_spath, rfc_list_cond, rfc_del_action))
    return -2;

  if (length (rfc_del_action) < length (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 (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,
        auth_uname,
        auth_pwd,
        extern,
        check_locks);
      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 what 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 ('CatFilter_DAV_LOCK (', path, id, what, locktype, scope, token, owner_name, owned_tokens, depth, timeout_sec, auth_uid, ')');

  if ('R' <> what)
    return -20;

  if (DAV_HIDE_ERROR (id) is null)
    return -20;

  if (isarray (id))
    return DAV_LOCK_INT (path, id[2], what, 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 what char(1),
  in owned_tokens varchar) returns integer
{
  -- dbg_obj_princ ('CatFilter_DAV_IS_LOCKED (', id, what, owned_tokens, ')');
  declare rc integer;
  declare orig_id any;
  declare orig_what char(1);

  orig_id := id;
  id := orig_id[2];
  rc := DAV_IS_LOCKED_INT (id, what, owned_tokens);
  if (rc <> 0)
    return rc;

  id := orig_id[1];
  orig_what := what;
  what := 'C';
  rc := DAV_IS_LOCKED_INT (id, what, owned_tokens);
  if (rc <> 0)
    return rc;

  id := orig_id;
  what := orig_what;
  return 0;
}
;


create function "CatFilter_DAV_LIST_LOCKS" (
  in id any,
  in what char(1),
  in recursive integer) returns any
{
  -- dbg_obj_princ ('CatFilter_DAV_LIST_LOCKS" (', id, what, recursive);
  declare res any;

  id := id[2];
  if (isarray (id))
    return call (cast (id[0] as varchar) || '_DAV_LIST_LOCKS') (id, what, 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 = what) 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 id any,
  in params any,
  in path varchar,
  in filter any,
  in auth_uname varchar := null,
  in auth_upwd varchar := null,
  in auth_uid integer := null) returns integer
{
  declare cfid, rc, ctr integer;
  declare colPath varchar;
  declare compilation, del_act any;

  if (not isnull ("CatFilter_VERIFY" (DB.DBA.DAV_SEARCH_PATH (id, 'C'), vector ('params', params, 'path', path, 'filter', filter))))
    return -38;

  colPath := DAV_SEARCH_PATH (id, 'C');
  if (DAV_HIDE_ERROR (colPath) is null)
    return colPath;

  rc := DAV_SEARCH_ID (path, 'C');
  if (DAV_HIDE_ERROR (rc) is null)
    return rc;

  compilation := vector ('', filter);
  rc := DAV_DIR_FILTER_INT (path, 1, compilation, auth_uname, auth_upwd, auth_uid);
  if (isinteger (rc))
    return rc;

  rc := DAV_PROP_SET_INT (colPath, 'virt:Filter-Params', params, null, null, 0, 1, 1);
  if (DAV_HIDE_ERROR (rc) is null)
    return rc;

  rc := DAV_PROP_SET_INT (colPath, 'virt:ResFilter-SearchPath', path, null, null, 0, 1, 1);
  if (DAV_HIDE_ERROR (rc) is null)
    return rc;

  rc := DAV_PROP_SET_INT (colPath, '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);
  rc := DAV_PROP_SET_INT (colPath, 'virt:ResFilter-DelAction', "ResFilter_ENCODE_FILTER" (del_act), null, null, 0, 1, 1);
  if (DAV_HIDE_ERROR (rc) is null)
    return rc;

  cfid := (select CF_ID from WS.WS.SYS_DAV_CATFILTER where CF_SEARCH_PATH = path);
  if (cfid is null)
  {
    declare path_z varchar;

    cfid := WS.WS.GETID ('CF');
    insert into WS.WS.SYS_DAV_CATFILTER (CF_ID, CF_SEARCH_PATH)
      values (cfid, path);

    path_z := 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 path and 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, COL_FULL_PATH from WS.WS.SYS_DAV_COL where COL_DET is not null and not (COL_DET like '%Filter')) do
    {
      if ("LEFT" (COL_FULL_PATH, length (path)) = path)
      {
        insert replacing WS.WS.SYS_DAV_CATFILTER_DETS (CFD_CF_ID, CFD_DET_SUBCOL_ID, CFD_DET)
          values (cfid, COL_ID, COL_DET);
      }
    }
  }
  rc := DAV_PROP_SET_INT (colPath, 'virt:CatFilter-ID', cast (cfid as varchar), null, null, 0, 1, 1);
  if (DAV_HIDE_ERROR (rc) is null)
    return rc;

  -- set DET Type Value
  DB.DBA.CatFilter__paramSet (id, 'C', ':virtdet', DB.DBA.CatFilter__detName (), 0, 0, 0);
}
;

create function "CatFilter_VERIFY" (
  in path integer,
  in params any)
{
  declare tmp any;

  tmp := get_keyword ('path', params);
  if (tmp between path and (path || '\255\255\255\255'))
    return sprintf ('Search path (%s) can not contains in folder full path (%s)!', tmp, path);

  return null;
}
;

create function DB.DBA.CatFilter__id (
  in detcol_id integer,
  in orig_id integer,
  in schema_uri varchar := null,
  in filter_data any := null)
{
  if (isnull (schema_uri))
    return vector (DB.DBA.CatFilter__detName (), detcol_id, orig_id);

  return vector (DB.DBA.CatFilter__detName (), detcol_id, orig_id, schema_uri, case when length (filter_data) then filter_data else null end);
}
;

create function DB.DBA.CatFilter__detName ()
{
  return UNAME'CatFilter';
}
;

create function DB.DBA.CatFilter__paramSet (
  in _id any,
  in _what varchar,
  in _propName varchar,
  in _propValue any,
  in _serialized integer := 1,
  in _prefixed integer := 1,
  in _encrypt integer := 0)
{
  -- dbg_obj_princ ('DB.DBA.CatFilter__paramSet', _propName, _propValue, ')');
  declare retValue any;

  if (_serialized)
    _propValue := serialize (_propValue);

  if (_encrypt)
    _propValue := pwd_magic_calc (DB.DBA.CatFilter__detName (), _propValue);

  if (_prefixed)
    _propName := 'virt:CatFilter-' || _propName;

  retValue := DB.DBA.DAV_PROP_SET_RAW (_id, _what, _propName, _propValue, 1, http_dav_uid ());

  return retValue;
}
;

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

  if (__tag of long varchar handle = __tag (propval))
    {
      pv := blob_to_string (propval);
    }
  else
    {
      if ((not isstring (propval)) or (propval = ''))
        return;

      pv := propval;
    }

  if (__tag of vector <> pv[0])
    return;

  doc := null;
  if (cfid is not null)
    {
      path_head := '/';
      goto cfid_found;
    }
  else
    {
      resfullpath := (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 := (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 := (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 function "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;
  }
}
;


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') and (NP.PROP_TYPE = 'R'))
    "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
{
  if ((OP.PROP_NAME = 'http://local.virt/DAV-RDF') and (OP.PROP_TYPE = 'R'))
    "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
{
  if ((OP.PROP_NAME = 'http://local.virt/DAV-RDF') and (OP.PROP_TYPE = 'R'))
    "CatFilter_FEED_DAV_RDF_INVERSE" (OP.PROP_VALUE, OP.PROP_PARENT_ID, 1);

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