create function DB.DBA.RDF_LONG_CMP (in long1 any, in long2 any) returns integer
{
  declare tag1, tag2 integer;
  if (long1 is null or long2 is null)
    return NULL;
  tag1 := __tag (long1);
  tag2 := __tag (long2);
  if (tag1 = __tag of rdf_box)
    {
      if (tag2 = __tag of rdf_box)
        return rdf_box_strcmp (long1, long2);
       return null;
     }
  if (tag1 in (__tag of integer, __tag of double precision, __tag of numeric))
    {
      if (tag2 in (__tag of integer, __tag of double precision, __tag of numeric))
        {
          if (long1 <> long2)
            {
              if (long1 < long2)
                return -1;
              return 1;
            }
          return 0;
        }
      return null;
    }
  if (tag1 = __tag of datetime)
    {
      if (tag2 = __tag of datetime)
        {
          if (long1 <> long2)
            {
              if (long1 < long2)
                return -1;
              return 1;
            }
          return 0;
        }
      return null;
    }
  return NULL;
}
;


--!AWK PUBLIC
create function DB.DBA.RDF_DIST_SER_LONG (in val any) returns any
{
  if (not (isstring (val)))
    {
      if (__tag of rdf_box = __tag (val))
        {
	  if (rdf_box_is_storeable (val))
	    return serialize (val);
          return serialize (vector (rdf_box_data (val), rdf_box_type (val), rdf_box_lang (val), rdf_box_is_complete (val)));
	}
      return serialize (val);
    }
  if ('' = val)
    return val;
  if (val[0] < 128)
    return val;
  return serialize (val);
}
;

--!AWK PUBLIC
create function DB.DBA.RDF_DIST_DESER_LONG (in strg any) returns any
{
  if (not (isstring (strg)))
    return strg;
  if ('' = strg)
    return strg;
  if (strg[0] < 128)
    return strg;
  declare res any;
  res := deserialize (strg);
  if (__tag of vector <> __tag (res))
    return res;
  return rdf_box (res[0], res[1], res[2], 0, res[3]);
}
;

-----
-- JSO procedures

create function DB.DBA.JSO_MAKE_INHERITANCE (in jgraph varchar, in class varchar, in rootinst varchar, in destinst varchar, in dest_iid iri_id, inout noinherits any, inout inh_stack any)
{
  declare base_iid iri_id;
  declare baseinst varchar;
  -- dbg_obj_princ ('JSO_MAKE_INHERITANCE (', jgraph, class, rootinst, destinst, ')');
  inh_stack := vector_concat (inh_stack, vector (destinst));
  baseinst := null;
  if (not exists (sparql
      define input:storage ""
      prefix virtrdf: <http://www.openlinksw.com/schemas/virtrdf#>
      ask where {
        graph ?:jgraph { ?:dest_iid rdf:type `iri(?:class)`
          } } ) )
    signal ('22023', 'JSO_MAKE_INHERITANCE has not found object <' || destinst || '> of type <' || class || '>');
/* This fails. !!!TBD: fix sparql2sql.c to preserve data about equalities, fixed values and globals when triples are moved from gp to gp
  for (sparql
    define input:storage ""
    prefix virtrdf: <http://www.openlinksw.com/schemas/virtrdf#>
    select ?srcinst
    where {
        graph ?:jgraph {
            { {
                ?destnode rdf:type `iri(?:class)` .
                filter (?destnode = iri(?:destinst)) }
              union
              {
                ?destnode rdf:type `iri(?:class)` .
                ?destnode rdf:name `iri(?:destinst)` } } .
            ?destnode virtrdf:inheritFrom ?srcinst .
            ?srcinst rdf:type `iri(?:class)` .
          } } ) do
*/
  for (sparql
    define input:storage ""
    define output:valmode "LONG"
    prefix virtrdf: <http://www.openlinksw.com/schemas/virtrdf#>
    select ?src_iid
    where {
        graph ?:jgraph { ?:dest_iid virtrdf:inheritFrom ?src_iid } } ) do
    {
      declare srcinst varchar;
      srcinst := id_to_iri_nosignal ("src_iid");
      if (baseinst is null)
        {
          if (not exists (sparql
              define input:storage ""
              prefix virtrdf: <http://www.openlinksw.com/schemas/virtrdf#>
              ask where { graph ?:jgraph { ?:"src_iid" rdf:type `iri(?:class)` } } ) )
            signal ('22023', 'JSO_MAKE_INHERITANCE has found that the object <' || destinst || '> has wrong virtrdf:inheritFrom <' || srcinst || '> that is not an instance of type <' || class || '>');
          base_iid := "src_iid";
          baseinst := srcinst;
        }
      else if (baseinst <> srcinst)
        signal ('22023', 'JSO_MAKE_INHERITANCE has found that the object <' || destinst || '> has multiple virtrdf:inheritFrom declarations: <' || baseinst || '> and <' || srcinst || '>');
    }
  if (position (baseinst, inh_stack))
    signal ('22023', 'JSO_MAKE_INHERITANCE has found that the object <' || baseinst || '> is recursively inherited from itself');
-- This fails. !!!TBD: fix sparql2sql.c to preserve data about equalities, fixed values and globals when triples are moved from gp to gp
--  for (sparql
--    define input:storage ""
--    prefix virtrdf: <http://www.openlinksw.com/schemas/virtrdf#>
--    select ?pred
--    where {
--        graph ?:jgraph {
--            { {
--                ?destnode rdf:type `iri(?:class)` .
--                filter (?destnode = iri(?:destinst)) }
--              union
--              {
--                ?destnode rdf:type `iri(?:class)` .
--                ?destnode rdf:name `iri(?:destinst)` } } .
--            ?destnode virtrdf:noInherit ?pred .
--           } } ) do
  for (sparql
    define input:storage ""
    prefix virtrdf: <http://www.openlinksw.com/schemas/virtrdf#>
    select ?pred
    where {
        graph ?:jgraph {
            ?:dest_iid virtrdf:noInherit ?pred
          } } ) do
    {
      if (baseinst is null)
        signal ('22023', 'JSO_MAKE_INHERITANCE has found that the object <' || destinst || '> has set virtrdf:noInherit but has no virtrdf:inheritFrom');
      dict_put (noinherits, "pred", destinst);
    }
  if (baseinst is null)
    return;
  for (select "pred_id", "predval"
    from (sparql
      define input:storage ""
      define output:valmode "LONG"
      prefix virtrdf: <http://www.openlinksw.com/schemas/virtrdf#>
      select ?pred_id, ?predval
      where {
          graph ?:jgraph {
              ?:base_iid ?pred_id ?predval
            } } ) as "t00"
      where not exists (sparql
          define input:storage ""
          prefix virtrdf: <http://www.openlinksw.com/schemas/virtrdf#>
          ask where { graph ?:jgraph { ?:"t00"."pred_id" virtrdf:loadAs virtrdf:jsoTriple } } )
      ) do
    {
      declare "pred" any;
      "pred" := id_to_iri ("pred_id");
      if (DB.DBA.RDF_LANGUAGE_OF_LONG ("predval", null) is not null)
        signal ('22023', 'JSO_MAKE_INHERITANCE does not support language marks on objects');
      if ('http://www.w3.org/1999/02/22-rdf-syntax-ns#type' = "pred")
        ;
      else if ('http://www.w3.org/1999/02/22-rdf-syntax-ns#name' = "pred")
        ;
      else if ('http://www.openlinksw.com/schemas/virtrdf#inheritFrom' = "pred")
        ;
      else if ('http://www.openlinksw.com/schemas/virtrdf#noInherit' = "pred")
        ;
      else if (dict_get (noinherits, "pred", baseinst) = baseinst) -- trick here, instead of (dict_get (noinherits, pred, null) is null) that does not handle inheritance of booleans properly.
        {
          jso_set (class, rootinst, "pred", __rdf_sqlval_of_obj ("predval"), isiri_id ("predval"));
          dict_put (noinherits, "pred", baseinst);
        }
    }
  DB.DBA.JSO_MAKE_INHERITANCE (jgraph, class, rootinst, baseinst, base_iid, noinherits, inh_stack);
}
;

create function DB.DBA.JSO_LOAD_INSTANCE (in jgraph varchar, in jinst varchar, in delete_first integer, in make_new integer, in jsubj_iid iri_id := null)
{
  declare jinst_iid, jgraph_iid IRI_ID;
  declare jclass varchar;
  declare noinherits, inh_stack, "p" any;
  -- dbg_obj_princ ('JSO_LOAD_INSTANCE (', jgraph, ')');
  noinherits := dict_new ();
  jinst_iid := iri_ensure (jinst);
  jgraph_iid := iri_ensure (jgraph);
  if (jsubj_iid is null)
    {
      jsubj_iid := (sparql
        define input:storage ""
        define output:valmode "LONG"
        select ?s
        where { graph ?:jgraph { ?s rdf:name ?:jinst } } );
      if (jsubj_iid is null)
        jsubj_iid := jinst_iid;
    }
  jclass := (sparql
    define input:storage ""
    select ?t
    where {
      graph ?:jgraph { ?:jsubj_iid rdf:type ?t } } );
  if (jclass is null)
    {
      if (exists (sparql
          define input:storage ""
          select ?x
            where { graph ?:jgraph {
                { ?:jinst ?x ?o }
                union
                { ?x rdf:name ?ji .
                  filter (str (?ji) = ?:jinst)
                  } } } ) )
        signal ('22023', 'JSO_LOAD_INSTANCE can not detect the type of <' || jinst || '>');
      else
        signal ('22023', 'JSO_LOAD_INSTANCE can not find an object <' || jinst || '>');
    }
  if (delete_first)
    jso_delete (jclass, jinst, 1);
  if (make_new)
    jso_new (jclass, jinst);
  for (select "p_id", coalesce ("o2", "o1") as "o"
      from (sparql
          define input:storage ""
          define output:valmode "LONG"
          select ?p_id ?o1 ?o2
          where {
          graph ?:jgraph_iid {
              { ?:jsubj_iid ?p_id ?o1 }  optional { ?o1 rdf:name ?o2 }
            } }
        ) as "t00"
      where not exists (sparql
          define input:storage ""
          prefix virtrdf: <http://www.openlinksw.com/schemas/virtrdf#>
          ask where { graph ?:jgraph_iid { ?:"t00"."p_id" virtrdf:loadAs virtrdf:jsoTriple } } ) option (quietcast)
      ) do
    {
      "p" := id_to_iri ("p_id");
      if (DB.DBA.RDF_LANGUAGE_OF_LONG ("o", null) is not null)
        signal ('22023', 'JSO_LOAD_INSTANCE does not support language marks on objects');
      if ('http://www.w3.org/1999/02/22-rdf-syntax-ns#type' = "p")
        {
	  if (__rdf_sqlval_of_obj ("o") <> jclass)
            signal ('22023', 'JSO_LOAD_INSTANCE has found that the object <' || jinst || '> has multiple type declarations');
	}
      else if ('http://www.w3.org/1999/02/22-rdf-syntax-ns#name' = "p")
        ;
      else if ('http://www.openlinksw.com/schemas/virtrdf#inheritFrom' = "p")
        ;
      else if ('http://www.openlinksw.com/schemas/virtrdf#noInherit' = "p")
        ;
      else
        {
          jso_set (jclass, jinst, "p", __rdf_sqlval_of_obj ("o"), isiri_id ("o"));
          dict_put (noinherits, "p", jinst);
        }
    }
  inh_stack := vector ();
  DB.DBA.JSO_MAKE_INHERITANCE (jgraph, jclass, jinst, jinst, jsubj_iid, noinherits, inh_stack);
}
;

create procedure DB.DBA.JSO_LIST_INSTANCES_OF_GRAPH (in jgraph varchar, out instances any)
{
  declare md, res, st, msg any;
  if (not isiri_id (jgraph))
    jgraph := __i2id (jgraph);
  st:= '00000';
  exec (
    'select DB.DBA.VECTOR_AGG (
      vector (
        id_to_iri ("jclass"),
        id_to_iri ("jinst"),
        coalesce ("s", "jinst") ) )
    from ( sparql
      define output:valmode "LONG"
      define input:storage ""
      select ?jclass ?jinst ?s
      where {
        graph ?? {
          { ?jinst rdf:type ?jclass .
            filter (!isBLANK (?jinst)) }
          union
          { ?s rdf:type ?jclass .
            ?s rdf:name ?jinst .
            filter (isBLANK (?s))
            } } }
      ) as inst',
    st, msg, vector (jgraph), 1, md, res);
  if (st <> '00000') signal (st, msg);
 	instances := res[0][0];
}
;


create procedure DB.DBA.CL_EXEC_AND_LOG (in txt varchar, in args any)
{
  DB.DBA.CL_EXEC (txt, args);
  DB.DBA.CL_EXEC ('log_text_array (?, ?)', vector (txt, args), 1);
}
;

create function DB.DBA.JSO_LOAD_GRAPH_MEMONLY (in jgraph varchar, in pin_now integer, in instances any, in triples any, in report_errors integer := 0) returns any
{
  declare chk, rep, errors_acc any;
  if (report_errors)
    vectorbld_init (errors_acc);
  else
    errors_acc := null;
/* Pass 1. Deleting all obsolete instances. */
  foreach (any j in instances) do
    jso_delete (j[0], j[1], 1);
/* Pass 2. Creating all instances. */
  foreach (any j in instances) do
    jso_new (j[0], j[1]);
/* Pass 3. Loading all instances, including loading inherited values. */
  foreach (any j in instances) do
    DB.DBA.JSO_LOAD_INSTANCE (jgraph, j[1], 0, 0, j[2]);
/* Pass 4. Validation all instances then pinning them, all in one atomic operation (probably under one exclusively obtained rwlock). */
  rep := jso_validate_and_pin_batch (instances, 1, 1);
  if (report_errors)
    {
      foreach (any r in rep) do
        {
          -- dbg_obj_princ ('Reported error/warning: ', r);
          if (r[2])
            vectorbld_acc (errors_acc, vector (r[0], r[1], NULL, '22023', r[3]));
        }
    }
  else
    {
      foreach (any r in rep) do
        {
          if (r[2])
            signal ('22023', r[3]);
        }
    }
/* Pass 5. Load all separate triples */
  foreach (any t in triples) do
    jso_triple_add (t[0], t[1], t[2]);
  chk := jso_triple_get_objs (
    UNAME'http://www.openlinksw.com/schemas/virtrdf#loadAs',
    UNAME'http://www.openlinksw.com/schemas/virtrdf#loadAs' );
  if ((1 <> length (chk)) or (cast (chk[0] as varchar) <> 'http://www.openlinksw.com/schemas/virtrdf#jsoTriple'))
    signal ('22023', 'JSO_LOAD_GRAPH_MEMONLY has not found expected metadata in the graph');
  if (report_errors)
    vectorbld_final (errors_acc);
  return errors_acc;
}
;

create function DB.DBA.JSO_LOAD_GRAPH (in jgraph varchar, in pin_now integer := 1, in report_errors integer := 0, in drop_procedures integer := 1) returns any
{
  declare jgraph_iid IRI_ID;
  declare qry, stat, msg varchar;
  declare instances, mdata, rset, triples, res any;
  -- dbg_obj_princ ('JSO_LOAD_GRAPH (', jgraph, ')');
  jgraph_iid := iri_ensure (jgraph);
  DB.DBA.JSO_LIST_INSTANCES_OF_GRAPH (jgraph, instances);
  qry := 'sparql
    define input:storage ""
    define sql:table-option "LOOP"
    prefix virtrdf: <http://www.openlinksw.com/schemas/virtrdf#>
    select (sql:VECTOR_AGG (bif:vector (?s, ?p, ?o)))
    where { graph <' || id_to_iri (jgraph_iid) || '> { ?p virtrdf:loadAs virtrdf:jsoTriple . ?s ?p ?o } }';
  stat := '00000';
  exec (qry, stat, msg, vector(), 1, mdata, rset);
  if (stat <> '00000')
    signal (stat, msg);
  triples := rset[0][0];
  res := DB.DBA.JSO_LOAD_GRAPH_MEMONLY (jgraph, pin_now, instances, triples, report_errors);
  if (drop_procedures)
    {
      for (select P_NAME from SYS_PROCEDURES
        where (
          (P_NAME > 'DB.DBA.SPARQL_DESC_DICT') and
          (P_NAME < 'DB.DBA.SPARQL_DESC_DICU') and
          (
            (P_NAME like 'DB.DBA.SPARQL_DESC_DICT_QMV1_%') or
            (P_NAME like 'DB.DBA.SPARQL_DESC_DICT_CBD_QMV1_%') or
            (P_NAME like 'DB.DBA.SPARQL_DESC_DICT_OBJCBD_QMV1_%') or
            (P_NAME like 'DB.DBA.SPARQL_DESC_DICT_SCBD_QMV1_%') ) )
        for update) do
        {
          exec ('drop procedure DB.DBA."' || subseq (P_NAME, 7) || '"');
        }
      commit work;
    }
  return res;
}
;

--!AWK PUBLIC
create function DB.DBA.JSO_SYS_GRAPH () returns varchar
{
  return 'http://www.openlinksw.com/schemas/virtrdf#';
}
;

create function DB.DBA.JSO_DUMP_IRI (in v varchar, inout ses any)
{
--            0         1         2         3      %
--            01234567890123456789012345678901234567-
  if (v like 'http://www.w3.org/2000/01/rdf-schema#%')
    { http ('rdfs:' || subseq (v, 37), ses); return; }
--            0         1         2         3         4  %
--            01234567890123456789012345678901234567890123-
  if (v like 'http://www.w3.org/1999/02/22-rdf-syntax-ns#%')
    { http ('rdf:' || subseq (v, 43), ses); return; }
--            0         1         2         %
--            0123456789012345678901234567890-
  if (v like 'http://www.w3.org/2002/07/owl#%')
    { http ('owl:' || subseq (v, 30), ses); return; }
--            0         1         2         3  %
--            0123456789012345678901234567890123-
  if (v like 'http://www.w3.org/2001/XMLSchema#%')
    { http ('xsd:' || subseq (v, 33), ses); return; }
--            0         1         2         3         4 %
--            0123456789012345678901234567890123456789012-
  if (v like 'http://www.openlinksw.com/schemas/virtrdf#%')
    { http ('virtrdf:' || subseq (v, 42), ses); return; }
--            0         1         2         3         4      %
--            012345678901234567890123456789012345678901234567-
  if (v like 'http://www.openlinksw.com/virtrdf-data-formats#%')
    { http ('rdfdf:' || subseq (v, 47), ses); return; }
  http ('<', ses);
  http_escape (v, 12, ses, 1, 1);
  http ('>', ses);
}
;

create function DB.DBA.JSO_DUMP_FLD (in v any, inout ses any)
{
  declare v_tag integer;
  v_tag := __tag(v);
  if (v_tag = __tag of UNAME)
    DB.DBA.JSO_DUMP_IRI (cast (v as varchar), ses);
  else if (v_tag = 243)
    DB.DBA.JSO_DUMP_IRI (id_to_iri (v), ses);
  else if (v_tag = 203)
    http (jso_dbg_dump_rtti (v), ses);
  else if (v_tag = __tag of varchar)
    {
      http ('"', ses);
      http_escape (v, 11, ses, 1, 1);
      http ('"', ses);
    }
  else if (isinteger (v))
    http_value (v, 0, ses);
  else if (v_tag = __tag of rdf_box)
    DB.DBA.RDF_LONG_TO_TTL (v, ses);
  else
    {
      http ('"', ses);
      http_escape (__rdf_strsqlval (v), 11, ses, 1, 1);
      http ('"^^<', ses);
      http_escape (cast (__xsd_type (v) as varchar), 12, ses, 1, 1);
      http ('>', ses);
    }
}
;

create function DB.DBA.JSO_VECTOR_TO_TTL (inout proplist any) returns any
{
  declare prev_obj, ses any;
  declare ctr, len integer;
  ses := string_output ();
  len := length (proplist);
  gvector_sort (proplist, 1, 0, 1);
  prev_obj := null;
  for (ctr := 0; ctr < len; ctr := ctr+1)
    {
      declare obj, p, o any;
      declare base IRI_ID;
      obj := proplist[ctr][0];
      p := proplist[ctr][1];
      o := proplist[ctr][2];
      if (obj = prev_obj)
        http (' ;\n  ', ses);
      else
        {
	  if (prev_obj is null)
	    http (
'@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix virtrdf: <http://www.openlinksw.com/schemas/virtrdf#> .
@prefix rdfdf: <http://www.openlinksw.com/virtrdf-data-formats#> .
', ses );
          else
	    http (' .\n', ses);
	  prev_obj := obj;
	  DB.DBA.JSO_DUMP_FLD (obj, ses);
          http ('\n  ', ses);
	}
      DB.DBA.JSO_DUMP_FLD (p, ses);
      http ('\t', ses);
      DB.DBA.JSO_DUMP_FLD (o, ses);
    }
  if (prev_obj is not null)
    http (' .\n', ses);
  return ses;
}
;

create function DB.DBA.JSO_FILTERED_PROPLIST (in only_custom integer := 0, in loading_status integer := 1, in long_valmode integer := 1) returns any
{
  declare proplist, res any;
  declare sys_dict, sys_inh any;
  declare sys_txt, sys_vect any;
  declare ctr, len integer;
  declare inh_id IRI_ID;
  if (loading_status = 0)
    {
      proplist := ((select DB.DBA.VECTOR_AGG (vector ("sub"."s", "sub"."p", "sub"."o")) from (
            sparql define input:storage "" define output:valmode "LONG"
            select ?s ?p ?o from <http://www.openlinksw.com/schemas/virtrdf#>
             where { ?s ?p ?o } ) as "sub"));
    }
  else
    {
      proplist := vector_concat (jso_proplist (loading_status), jso_triple_list ());
    }
  gvector_sort (proplist, 1, 0, 1);
  if (not only_custom and not long_valmode)
    return proplist;
  if (only_custom)
    {
      sys_txt := cast ( DB.DBA.XML_URI_GET (
          'http://www.openlinksw.com/sparql/virtrdf-data-formats.ttl', '' ) as varchar );
      sys_dict := DB.DBA.RDF_TTL2HASH (sys_txt, '');
      sys_vect := dict_list_keys (sys_dict, 0);
      -- dbg_obj_princ ('Part of sys_dict is', subseq (sys_vect, 0, 10));
      inh_id := iri_ensure ('http://www.openlinksw.com/schemas/virtrdf#inheritFrom');
      sys_inh := dict_new (127);
      foreach (any triple in sys_vect) do
        {
          if (triple[1] = inh_id)
            dict_put (sys_inh, triple[0], triple[2]);
        }
    }
  len := length (proplist);
  vectorbld_init (res);
  for (ctr := 0; ctr < len; ctr := ctr+1)
    {
      declare obj, p, o any;
      declare obj_long, p_long, o_long any;
      declare base IRI_ID;
      obj := proplist[ctr][0];
      p := proplist[ctr][1];
      o := proplist[ctr][2];
          if (__tag of UNAME = __tag (obj))
            obj_long := iri_ensure (obj);
          else
            obj_long := obj;
          if (__tag of UNAME = __tag (p))
            p_long := iri_ensure (p);
          else
            p_long := p;
          if (__tag of UNAME = __tag (o))
            o_long := iri_ensure (o);
          else
            o_long := __rdf_long_of_obj (DB.DBA.RDF_MAKE_OBJ_OF_SQLVAL (o));
          -- dbg_obj_princ ('key sample: ', vector (obj_long, p_long, o_long));
      if (only_custom)
        {
          if (dict_get (sys_dict, vector (obj_long, p_long, o_long)) is not null)
            goto end_of_step;
          base := dict_get (sys_inh, obj_long);
          if (base is not null and dict_get (sys_dict, vector (base, p_long, o_long)) is not null)
            goto end_of_step;
        }
      if (long_valmode)
        vectorbld_acc (res, vector (obj_long, p_long, o_long));
      else
        vectorbld_acc (res, proplist[ctr]);
end_of_step: ;
    }
  vectorbld_final (res);
  return res;
}
;

create function DB.DBA.JSO_DUMP_ALL (in only_custom integer := 0, in loading_status integer := 1) returns any
    {
  declare proplist any;
  proplist := DB.DBA.JSO_FILTERED_PROPLIST (only_custom, loading_status, 0);
  return DB.DBA.JSO_VECTOR_TO_TTL (proplist);
}
;

-----
-- Metadata audit / backup / restore / recovery

create function DB.DBA.RDF_BACKUP_METADATA (in save_to_file integer := 0, in backup_name varchar := null) returns varchar
{
  declare proplist any;
  proplist := DB.DBA.JSO_FILTERED_PROPLIST (1, 0, 1);
  if (backup_name is null)
    backup_name := replace (cast (now() as varchar), ' ', 'T');
  if (save_to_file)
    {
      declare proplist_debug any;
      proplist_debug := DB.DBA.JSO_FILTERED_PROPLIST (0, 0, 1);
      string_to_file (backup_name || '.ttl', '# RDF_BACKUP_METADATA #\n', -2);
      string_to_file (backup_name || '.ttl', DB.DBA.JSO_VECTOR_TO_TTL (proplist), -1);
      string_to_file (backup_name || '-DEBUG.ttl', '# RDF_BACKUP_METADATA - DEBUG #\n', -2);
      string_to_file (backup_name || '-DEBUG.ttl', DB.DBA.JSO_VECTOR_TO_TTL (proplist_debug), -1);
      return backup_name || '.ttl';
    }
  else
    {
      if (exists (sparql define input:storage "" ask where { graph `iri(?:backup_name)` { ?s ?p ?o }}))
        signal ('22023', sprintf ('Can not backup RDF metadata into nonempty graph <%.300s>', backup_name));
      foreach (any triple in proplist) do
        {
          declare s,p,o any;
          s := triple[0];
          p := triple[1];
          o := triple[2];
          sparql insert into graph iri(?:backup_name) { ?:s ?:p ?:o };
        }
      commit work;
    }
  return backup_name;
}
;

create function DB.DBA.RDF_RESTORE_METADATA (in read_from_file integer, in backup_name varchar) returns any
{
  declare graphiri_id IRI_ID;
  declare proplist any;
  if (read_from_file)
    {
      declare txt any;
      txt := file_to_string (backup_name);
      if (not ("LEFT" (txt, 23) = '# RDF_BACKUP_METADATA #'))
        signal ('22023', sprintf ('Can not restore RDF metadata from file %.300s: file does not start with signature "# RDF_BACKUP_METADATA #"', backup_name));
      proplist := dict_list_keys (DB.DBA.RDF_TTL2HASH (txt, ''), 1);
    }
  else
    proplist := ((select DB.DBA.VECTOR_AGG (vector ("sub"."s", "sub"."p", "sub"."o")) from (
          sparql define input:storage "" define output:valmode "LONG"
          select ?s ?p ?o where { graph `iri(?:backup_name)` { ?s ?p ?o } } ) as "sub"));
  if (0 = length (proplist))
    signal ('22023', sprintf ('There are no metadata in %.200 to restore', backup_name));
  graphiri_id := iri_ensure (DB.DBA.JSO_SYS_GRAPH ());
  sparql define input:storage "" clear graph ?:graphiri_id;
  commit work;
  DB.DBA.SPARQL_RELOAD_QM_GRAPH ();
  commit work;
  foreach (any triple in proplist) do
    {
      declare sl,pl,ol any;
      sl := triple[0];
      pl := triple[1];
      ol := triple[2];
      if (pl <> iri_ensure ('http://www.openlinksw.com/schemas/virtrdf#status'))
        {
          -- dbg_obj_princ ('s=', sl, ' p=', pl, ' o=', ol);
          sparql define input:storage "" insert into graph ?:graphiri_id { ?:sl ?:pl ?:ol };
        }
    }
  commit work;
  DB.DBA.SPARQL_RELOAD_QM_GRAPH ();
  commit work;
  return backup_name;
}
;

create procedure DB.DBA.RDF_AUDIT_METADATA (in fix_bugs integer := 0, in unlocker varchar := null, in graphiri varchar := null, in call_result_names integer := 1)
{
  declare chksum varchar;
  declare chksum_acc any;
  declare STAT, MSG varchar;
  declare graphiri_id IRI_ID;
  declare all_lists, prev_list, prev_subj any;
  declare loop_count integer;
  loop_count := 10;
  if (call_result_names)
    result_names (STAT, MSG);
  if (graphiri is null)
    graphiri := DB.DBA.JSO_SYS_GRAPH ();
  else
    {
      if (graphiri <> DB.DBA.JSO_SYS_GRAPH ())
      result ('00000', 'Note: non-default graph IRI <' || graphiri || '> is used');
    }
  graphiri_id := iri_to_id (graphiri);
  vectorbld_init (chksum_acc);
  for (sparql define input:storage ""
    select ?st ?trx where { graph ?:graphiri_id {
            ?st virtrdf:qsAlterInProgress ?trx } } ) do
    {
      result ('00000', 'Quad storage <' || "st" || '> is flagged as being edited ' || cast ("trx" as varchar));
      vectorbld_acc (chksum_acc, vector ("st", "trx"));
    }
  vectorbld_final (chksum_acc);
  if (length (chksum_acc))
    {
      chksum := md5 (serialize (chksum_acc));
      if ((fix_bugs = 0) or (unlocker is null) or ((chksum <> unlocker) and (unlocker <> '*')))
        {
          result ('42000', 'Can not process data that are being edited by someone else.');
          result ('00000', 'To force tests/bugfixing, pass 1 as first argument and either ''' || chksum || ''' or ''*'' as second argument of the DB.DBA.RDF_AUDIT_METADATA() call');
          return;
        }
      sparql define input:storage ""
      delete from graph ?:graphiri_id {
          ?st virtrdf:qsAlterInProgress ?trx }
      where { graph ?:graphiri_id {
              ?st virtrdf:qsAlterInProgress ?trx } };
    }
retry_reload:
  if ((graphiri = DB.DBA.JSO_SYS_GRAPH ()) and fix_bugs)
    {
      declare txt1,txt2 varchar;
      declare dict1, dict2, lst any;
      result ('00000', 'Reloading built-in metadata, this might fix some errors without accurate reporting that they did exist');
      txt1 := cast ( DB.DBA.XML_URI_GET (
          'http://www.openlinksw.com/sparql/virtrdf-data-formats.ttl', '' ) as varchar );
      txt2 := cast ( DB.DBA.XML_URI_GET (
          'http://www.openlinksw.com/sparql/virtrdf-quad-storages.ttl', '' ) as varchar );
      dict1 := DB.DBA.RDF_TTL2HASH (txt1, '');
      dict2 := DB.DBA.RDF_TTL2HASH (txt2, '');
      lst := vector_concat (dict_list_keys (dict1, 1), dict_list_keys (dict2, 1));
      foreach (any triple in lst) do
        {
          delete from DB.DBA.RDF_QUAD table option (index RDF_QUAD) where G = graphiri_id and S = triple[0] and P = triple[1];
        }
      DB.DBA.RDF_INSERT_TRIPLES (graphiri_id, lst);
      commit work;
      foreach (any triple in lst) do
        {
          declare ms, mp, mo any;
          ms := triple[0];
          mp := triple[1];
          mo := triple[2];
          if (not exists (sparql define input:storage "" select 1 where { graph `iri(?:graphiri_id)` { ?:ms ?:mp ?:mo }}))
            result ('00100', sprintf ('Error on reloading metadata, %s has lost property %s value; corrupted indicies?', id_to_iri_nosignal (ms), id_to_iri_nosignal (mp)));
        }
      result ('00000', sprintf ('Built-in metadata were reloaded (%d triples)', length (lst)));
      if (fix_bugs > 1)
        {
          for (sparql define input:storage ""
            prefix virtrdf: <http://www.openlinksw.com/schemas/virtrdf#>
            select ?s from <http://www.openlinksw.com/schemas/virtrdf#> where {
                ?s rdf:type virtrdf:array-of-QuadMap } ) do
            {
              if (DB.DBA.RDF_QM_GC_SUBTREE ("s", 3) is null)
                result ('00000', 'Quad map array <' || "s" || '> is not used, removed');
            }
          for (sparql define input:storage ""
            prefix virtrdf: <http://www.openlinksw.com/schemas/virtrdf#>
            select ?s from <http://www.openlinksw.com/schemas/virtrdf#> where {
                ?s rdf:type virtrdf:QuadMap } ) do
            {
              if (DB.DBA.RDF_QM_GC_MAPPING_SUBTREE ("s", 1) is null)
                result ('00000', 'Quad map <' || "s" || '> is not used, removed');
            }
          for (sparql define input:storage ""
            prefix virtrdf: <http://www.openlinksw.com/schemas/virtrdf#>
            select ?s from <http://www.openlinksw.com/schemas/virtrdf#> where {
                ?s rdf:type virtrdf:array-of-QuadMapFormat } ) do
            {
              if (DB.DBA.RDF_QM_GC_SUBTREE ("s", 3) is null)
                result ('00000', 'Quad map format array <' || "s" || '> is not used, removed');
            }
          for (sparql define input:storage ""
            prefix virtrdf: <http://www.openlinksw.com/schemas/virtrdf#>
            select ?s from <http://www.openlinksw.com/schemas/virtrdf#> where {
                ?s rdf:type virtrdf:QuadMapFormat } ) do
            {
              if (DB.DBA.RDF_QM_GC_SUBTREE ("s", 3) is null)
                result ('00000', 'Quad map format <' || "s" || '> is not used, removed');
            }
        }
    }
  for (select * from (sparql define input:storage "" define output:valmode "LONG"
      select ?lst ?p ?itm where { graph ?:graphiri_id {
              ?lst ?p ?itm .
              optional { ?itm a ?t } .
              filter (! bound (?t) && isiri (?itm) && str(?p) > str(rdf:_) && str(?p) < str(rdf:_A))
               } } ) as sub for update) do
    {
      result ('00000', 'List <' || id_to_iri("lst") || '> contains IRI <' || id_to_iri("itm") || '> that has no type');
      if (fix_bugs)
        {
          sparql define input:storage ""
          delete from graph ?:graphiri_id { ?:"lst" ?:"p" ?:"itm" };
        }
    }
  vectorbld_init (all_lists);
  prev_subj := #i0;
  for (
    select "sub"."lst", cast ("sub"."idx" as integer) as "idx", serialize ("sub"."itm") as "itmsz", "sub"."itmstr", "sub"."itmislit", "sub"."t"
    from (sparql define input:storage "" define output:valmode "LONG"
      select ?lst
        (bif:aref (bif:sprintf_inverse (str(?p),
            bif:concat (str (rdf:_), "%d"), 2 ),
          0 ) ) as ?idx
       ?itm
       (str(?itm)) as ?itmstr
       (isliteral(?itm)) as ?itmislit
       ?t
     where { graph ?:graphiri_id {
              ?lst ?p ?itm .
              optional { ?itm a ?t } .
              filter (
              str(?p) > str(rdf:_) && str(?p) < str(rdf:_A))
               } } ) as "sub"
    order by 1, 2, 3, 4 ) do
    {
      if (prev_subj <> "lst")
        {
          if (prev_subj <> #i0)
            {
              vectorbld_final (prev_list);
              vectorbld_acc (all_lists, vector (prev_subj, prev_list));
            }
          prev_subj := "lst";
          vectorbld_init (prev_list);
        }
      vectorbld_acc (prev_list, vector ("idx", deserialize("itmsz"), "itmstr", "itmislit", "t"));
    }
  if (prev_subj <> #i0)
    {
      vectorbld_final (prev_list);
      vectorbld_acc (all_lists, vector (prev_subj, prev_list));
    }
  vectorbld_final (all_lists);
  foreach (any pair in all_lists) do
    {
      declare subj, items any;
      declare pos, len, last_idx, list_needs_rebuild integer;
      list_needs_rebuild := 0;
      last_idx := 0;
      subj := pair[0];
      items := pair[1];
      len := length (items);
      last_idx := 0;
      for (pos := 0; pos < len; pos := pos+1)
        {
          declare curr_idx integer;
          curr_idx := items[pos][0];
          if (curr_idx <= last_idx)
            {
              result ('42000', sprintf ('Item rdf:_%d is out of order in list <%s>', curr_idx, id_to_iri (subj)));
              list_needs_rebuild := 1;
            }
          else if ((last_idx + 3) < curr_idx)
            {
              result ('42000', sprintf ('Items rdf:_%d to rdf:_%d are not set in list <%s>', last_idx + 1, curr_idx - 1, id_to_iri (subj)));
              list_needs_rebuild := 1;
            }
          else
            {
              while ((last_idx + 1) < curr_idx)
                {
                  result ('42000', sprintf ('Item rdf:_%d is not set in list <%s>', last_idx + 1, id_to_iri (subj)));
                  last_idx := last_idx + 1;
                  list_needs_rebuild := 1;
                }
            }
          last_idx := curr_idx;
        }
      if (fix_bugs and list_needs_rebuild)
        {
          for (pos := 0; pos < len; pos := pos+1)
            {
              declare curr_idx integer;
              curr_idx := items[pos][0];
              sparql define input:storage ""
              delete from graph ?:graphiri_id {
                `iri(?:subj)` ?p ?o }
              where { graph ?:graphiri_id {
                `iri(?:subj)` ?p ?o .
                filter (?p = iri (bif:sprintf ("%s%d", str (rdf:_), ?:curr_idx))) } };
            }
          for (pos := 0; pos < len; pos := pos+1)
            {
              declare curr_idx integer;
              declare obj any;
              declare objstr varchar;
              declare objislit integer;
              curr_idx := items[pos][0];
              obj := items[pos][1];
              objstr := items[pos][2];
              objislit := items[pos][3];
              if (objislit)
                {
                  sparql define input:storage ""
                  insert into graph ?:graphiri_id {
                    `iri(?:subj)` `iri (bif:sprintf ("%s%d", str (rdf:_), 1 + ?:pos))` ?:obj };
                }
              else
                {
                  sparql define input:storage ""
                  insert into graph ?:graphiri_id {
                    `iri(?:subj)` `iri (bif:sprintf ("%s%d", str (rdf:_), 1 + ?:pos))` `iri(?:objstr)` };
                }
            }
        }
    }
  commit work;
  if ((graphiri = DB.DBA.JSO_SYS_GRAPH ()) and fix_bugs)
    {
      whenever sqlstate '*' goto jso_load_failed;
      if ((fix_bugs > 2) and (loop_count > 0))
        {
          declare load_res any;
          loop_count := loop_count - 1;
          load_res := DB.DBA.JSO_LOAD_GRAPH (graphiri, 1, 0, 1);
          if (length (load_res))
            {
              foreach (any r in load_res) do
                {
                  declare rsubj any;
                  dbg_obj_princ ('Error loading RDF metadata: ', r);
                  result (r[3], r[4]);
                  rsubj := r[1];
                  sparql define input:storage "" delete where { graph `iri(?:graphiri_id)` { `iri(?:rsubj)` ?p ?o . } };
                  sparql define input:storage "" delete where { graph `iri(?:graphiri_id)` { ?s ?p `iri(?:rsubj)` . } };
                  result ('00000', 'Deleting references from/to <' || id_to_iri_nosignal (rsubj) || '> from the system graph');
                }
              goto retry_reload;
            }
        }
      else
        {
          DB.DBA.JSO_LOAD_GRAPH (graphiri, 1, 0, 1);
        }
      result ('00000', 'Metadata from system graph are cached in memory-resident JSOs (JavaScript Objects)');
      return;
    }
  return;

jso_load_failed:
  result (__SQL_STATE, __SQL_MESSAGE);
  if ((fix_bugs > 2) or graphiri <> DB.DBA.JSO_SYS_GRAPH ())
    result ('42000', 'The previous error can not be fixed automatically. Sorry.');
  else
    {
      result ('42000', 'The previous error can not be fixed automatically now.');
      result ('42000', 'You may wish to try the DB.DBA.RDF_AUDIT_METADATA with first parameter set to 3.');
      result ('42000', 'With the mode 3, the procedure will erase data about incorrectly described subjects.');
      result ('42000', 'This is especially useful to quickly return the storage to some functional state.');
      result ('42000', 'Before trying mode 3, use RDF_BACKUP_METADATA().');
    }
  return;
}
;

-----
-- Internal routines for SPARQL macro library and quad map syntax extensions

create procedure DB.DBA.RDF_QM_CHANGE (in warninglist any)
{
  declare STATE, MESSAGE varchar;
  result_names (STATE, MESSAGE);
  foreach (any warnings in warninglist) do
    {
     foreach (any warning in warnings) do
       result (warning[0], warning[1]);
    }
  commit work;
}
;

create procedure DB.DBA.RDF_QM_CHANGE_OPT (in cmdlist any)
{
  declare cmdctr, cmdcount integer;
  declare eaqs varchar;
  declare STATE, MESSAGE varchar;
  cmdcount := length (cmdlist);
  result_names (STATE, MESSAGE);
  eaqs := '';
  for (cmdctr := 0; cmdctr < cmdcount; cmdctr := cmdctr + 1)
    {
      declare cmd, exectext, arglist, warnings,md,rs any;
      declare argctr, argcount integer;
      cmd := cmdlist[cmdctr];
      exectext := string_output();
      http ('select ', exectext);
      http_value (cmd[0], 0, exectext);
      http (' (', exectext);
      if (length (cmd) > 2)
        arglist := vector_concat (cmd[1], vector (cmd[2]));
      else
        arglist := cmd[1];
      argcount := length (arglist);
      for (argctr := 0; argctr < argcount; argctr := argctr + 1)
        {
          if (argctr > 0)
            http (',', exectext);
          http ('?', exectext);
        }
      http (')', exectext);
      STATE := '00000';
      warnings := exec (string_output_string (exectext), STATE, MESSAGE, arglist, 10000, md, rs);
      -- dbg_obj_princ ('md = ', md, ' rs = ', rs, ' warnings = ', warnings, STATE, MESSAGE);
      if (__tag of vector <> __tag (warnings) and __tag of vector = __tag (rs))
        warnings := case (length (rs)) when 0 then null else rs[0][0] end;
      -- dbg_obj_princ ('warnings = ', warnings);
      if (__tag of vector = __tag (warnings))
        {
          foreach (any warning in warnings) do
            result (warning[0], warning[1]);
        }
      commit work;
      if (STATE <> '00000')
        {
          result (STATE, MESSAGE);
          if ('' <> eaqs)
            exec ('DB.DBA.RDF_QM_END_ALTER_QUAD_STORAGE (?)', STATE, MESSAGE, vector (eaqs));
          DB.DBA.RDF_AUDIT_METADATA (1, null, null, 0);
          return;
        }
      if (UNAME'DB.DBA.RDF_QM_BEGIN_ALTER_QUAD_STORAGE' = cmd[0])
        eaqs := arglist[0];
      else if (UNAME'DB.DBA.RDF_QM_END_ALTER_QUAD_STORAGE' = cmd[0])
        eaqs := '';
    }
  result ('00000', sprintf ('%d RDF metadata manipulation operations done', cmdcount));
}
;

create function DB.DBA.RDF_QM_APPLY_CHANGES (in deleted any, in affected any) returns any
{
  declare ctr, len integer;
  commit work;
  DB.DBA.JSO_LOAD_GRAPH (DB.DBA.JSO_SYS_GRAPH(), 1, 0, 1);
  len := length (deleted);
  for (ctr := 0; ctr < len; ctr := ctr + 2)
    {
      jso_delete (deleted [ctr], deleted [ctr+1], 1);
      log_text ('jso_delete (?,?,1)', deleted [ctr], deleted [ctr+1]);
    }
  len := length (affected);
  for (ctr := 0; ctr < len; ctr := ctr + 1)
    {
      jso_mark_affected (affected [ctr]);
      log_text ('jso_mark_affected (?)', affected [ctr]);
    }
  return vector (vector ('00000', 'Transaction committed, SPARQL compiler re-configured'));
}
;

create function DB.DBA.RDF_QM_ASSERT_JSO_TYPE (in inst varchar, in expected varchar, in allow_missing integer := 0) returns integer
{
  declare actual varchar;
  if (expected is null)
    {
      actual := coalesce ((sparql
          define input:storage ""
          prefix virtrdf: <http://www.openlinksw.com/schemas/virtrdf#>
          select ?t where {
            graph <http://www.openlinksw.com/schemas/virtrdf#> { `iri(?:inst)` rdf:type ?t } } ));
      if (actual is not null)
        signal ('22023', 'The RDF QM schema object <' || inst || '> already exists, type <' || cast (actual as varchar) || '>');
    }
  else
    {
      declare hit integer;
      hit := 0;
      for (sparql
        define input:storage ""
        prefix virtrdf: <http://www.openlinksw.com/schemas/virtrdf#>
        select ?t where {
          graph <http://www.openlinksw.com/schemas/virtrdf#> { `iri(?:inst)` rdf:type ?t } } ) do
        {
          if ("t" <> expected)
            signal ('22023', 'The RDF QM schema object <' || inst || '> has type <' || cast (actual as varchar) || '>, cannot use same identifier for <' || expected || '>');
          hit := 1;
        }
      if (not hit)
        {
          if (allow_missing)
            return 0;
          signal ('22023', 'The RDF QM schema object <' || inst || '> does not exist, should be of type <' || expected || '>');
        }
    }
  return 1;
}
;

create procedure DB.DBA.RDF_QM_ASSERT_STORAGE_FLAG (in storage varchar, in req_flag integer)
{
  declare graphiri varchar;
  graphiri := DB.DBA.JSO_SYS_GRAPH ();
  DB.DBA.RDF_QM_ASSERT_JSO_TYPE (storage, 'http://www.openlinksw.com/schemas/virtrdf#QuadStorage');
  for (sparql define input:storage ""
    prefix rdfdf: <http://www.openlinksw.com/virtrdf-data-formats#>
    select ?startdt where {
        graph ?:graphiri {
            `iri(?:storage)` virtrdf:qsAlterInProgress ?startdt .
          } } ) do
    {
      if (req_flag)
        return;
      signal ('22023', 'The quad storage "' || storage || '" is edited by other client, started ' || cast ("startdt" as varchar));
    }
  if (not req_flag)
    return;
  signal ('22023', 'The quad storage "' || storage || '" is not flagged as being edited, cannot change it' );
}
;

create procedure DB.DBA.RDF_QM_ASSERT_STORAGE_CONTAINS_MAPPING (in storage varchar, in qmid varchar, in must_contain integer)
{
  declare graphiri varchar;
  graphiri := DB.DBA.JSO_SYS_GRAPH ();
  if (exists (sparql define input:storage ""
        ask where {
          graph ?:graphiri {
            { `iri(?:storage)` virtrdf:qsDefaultMap `iri(?:qmid)` }
            union
            { `iri(?:storage)` virtrdf:qsUserMaps ?qmlist .
              ?qmlist ?p `iri(?:qmid)` .
            } } } ) )
    {
      if (must_contain)
        return;
      signal ('22023', 'The quad storage "' || storage || '" contains quad map ' || qmid );
    }
  if (not must_contain)
    return;
  signal ('22023', 'The quad storage "' || storage || '" does not contains quad map ' || qmid );
}
;

create function DB.DBA.RDF_QM_GC_SUBTREE (in seed any, in gc_flags integer := 0) returns integer
{ -- gc_flags: 0x1 = quick gc only, 0x2 = override virtrdf:isGcResistantType
  declare graphiri varchar;
  declare seed_id, graphiri_id, subjs, objs any;
  declare o_to_s, s_to_o any;
  declare subjs_of_o, objs_of_s any;
  set isolation = 'serializable';
  -- dbg_obj_princ ('DB.DBA.RDF_QM_GC_SUBTREE (', seed, '), ', seed, '=', id_to_iri(iri_to_id(seed)));
  o_to_s := dict_new ();
  s_to_o := dict_new ();
  graphiri := DB.DBA.JSO_SYS_GRAPH ();
  graphiri_id := iri_to_id (graphiri);
  seed_id := iri_to_id (seed);
  for (sparql define input:storage ""
    define output:valmode "LONG"
    select ?s
    from <http://www.openlinksw.com/schemas/virtrdf#>
    where { ?s virtrdf:item ?:seed_id } ) do
    {
      -- dbg_obj_princ ('DB.DBA.RDF_QM_GC_SUBTREE (', seed, ') found virtrdf:item subject ', "s");
      return "s";
    }
  if (not bit_and (gc_flags, 2))
    {
      for (sparql define input:storage ""
        define output:valmode "LONG"
        select ?t ?n
        from <http://www.openlinksw.com/schemas/virtrdf#>
        where { ?:seed_id a ?t . ?t virtrdf:isGcResistantType ?n } ) do
        {
          -- dbg_obj_princ ('DB.DBA.RDF_QM_GC_SUBTREE (', seed, ') has gc-resistant type ', "t", ' resistance ', "n");
          return "t";
        }
    }
  for (sparql define input:storage ""
    define output:valmode "LONG"
    select ?s
    from <http://www.openlinksw.com/schemas/virtrdf#>
    where { ?s a [] ; ?p ?:seed_id } ) do
    {
      -- dbg_obj_princ ('DB.DBA.RDF_QM_GC_SUBTREE (', seed, ') found use case ', "s");
      if (bit_and (gc_flags, 1))
        return "s";
      goto do_full_gc;
    }
  vectorbld_init (objs_of_s);
  for (sparql define input:storage ""
    define output:valmode "LONG"
    define sql:table-option "LOOP"
    select ?o
    from <http://www.openlinksw.com/schemas/virtrdf#>
    where { ?:seed_id a [] ; ?p ?o . ?o a [] } ) do
    {
      vectorbld_acc (objs_of_s, "o");
    }
  vectorbld_final (objs_of_s);
  -- dbg_obj_princ ('DB.DBA.RDF_QM_GC_SUBTREE (', seed, ') found descendants ', objs_of_s);
  delete from DB.DBA.RDF_QUAD where G = graphiri_id and S = seed_id;
  sparql define input:storage ""
  prefix rdfdf: <http://www.openlinksw.com/virtrdf-data-formats#>
  delete from graph <http://www.openlinksw.com/schemas/virtrdf#> { ?s virtrdf:isSubClassOf ?o }
  from <http://www.openlinksw.com/schemas/virtrdf#>
  where { ?s virtrdf:isSubClassOf ?o . filter (?o = iri(?:seed_id)) };

  commit work;
  foreach (IRI_ID descendant in objs_of_s) do
    {
      DB.DBA.RDF_QM_GC_SUBTREE (descendant, 1);
    }
  -- dbg_obj_princ ('DB.DBA.RDF_QM_GC_SUBTREE (', seed, ') done in quick way');
  return null;

do_full_gc:
  for (sparql define input:storage ""
    define output:valmode "LONG"
    define sql:table-option "LOOP"
    select ?s ?o
    from <http://www.openlinksw.com/schemas/virtrdf#>
    where { ?s a [] ; ?p ?o . ?o a [] } ) do
    {
      -- dbg_obj_princ ('DB.DBA.RDF_QM_GC_SUBTREE () caches ', "s", ' -> ', "o");
      subjs_of_o := dict_get (o_to_s, "o", NULL);
      if (subjs_of_o is null)
        dict_put (o_to_s, "o", vector ("s"));
      else if (0 >= position ("s", subjs_of_o))
        dict_put (o_to_s, "o", vector_concat (vector ("s"), subjs_of_o));
      objs_of_s := dict_get (s_to_o, "s", NULL);
      if (objs_of_s is null)
        dict_put (s_to_o, "s", vector ("o"));
      else if (0 >= position ("o", objs_of_s))
        dict_put (s_to_o, "s", vector_concat (vector ("o"), objs_of_s));
    }
  subjs := vector (seed_id);
again:
  vectorbld_init (objs);
  foreach (IRI_ID nod in subjs) do
    {
      -- dbg_obj_princ ('DB.DBA.RDF_QM_GC_SUBTREE () tries to delete ', nod, id_to_iri_nosignal (nod));
      declare subjs_of_nod, objs_of_nod any;
      subjs_of_nod := dict_get (o_to_s, nod, NULL);
      if (subjs_of_nod is not null)
        {
          -- dbg_obj_princ ('DB.DBA.RDF_QM_GC_SUBTREE () does not delete ', nod, id_to_iri_nosignal (nod), ': side links ', subjs_of_nod);
          if (nod = seed_id)
            return subjs_of_nod[0];
          goto nop_nod; -- see below;
        }
--      sparql define input:storage ""
--        delete from graph <http://www.openlinksw.com/schemas/virtrdf#> { `iri(?:nod)` ?p ?o }
--        where { graph ?:graphiri { `iri(?:nod)` ?p ?o } };
      delete from DB.DBA.RDF_QUAD where G = graphiri_id and S = nod;
      objs_of_nod := dict_get (s_to_o, nod, NULL);
      dict_remove (s_to_o, nod);
      foreach (IRI_ID sub in objs_of_nod) do
        {
          declare subjs_of_sub any;
          declare nod_pos integer;
          subjs_of_sub := dict_get (o_to_s, sub, NULL);
          nod_pos := position (nod, subjs_of_sub);
          if (0 < nod_pos)
            subjs_of_sub := vector_concat (subseq (subjs_of_sub, 0, nod_pos - 1), subseq (subjs_of_sub, nod_pos));
          if (0 = length (subjs_of_sub))
            {
              -- dbg_obj_princ ('DB.DBA.RDF_QM_GC_SUBTREE () condemns ', sub, id_to_iri_nosignal (sub));
              dict_remove (o_to_s, sub);
              vectorbld_acc (objs, sub);
            }
          else
            {
              dict_put (o_to_s, sub, subjs_of_sub);
              -- dbg_obj_princ ('DB.DBA.RDF_QM_GC_SUBTREE () stores subjects ', subjs_of_sub, ' for not condemned ', sub, id_to_iri_nosignal (sub));
            }
        }
nop_nod: ;
    }
  vectorbld_final (objs);
  if (0 < length (objs))
    {
      -- dbg_obj_princ ('DB.DBA.RDF_QM_GC_SUBTREE () sets a new list of subjects: ', subjs);
      subjs := objs;
      goto again; -- see above
    }
  -- dbg_obj_princ ('DB.DBA.RDF_QM_GC_SUBTREE () finishes GC of ', seed);
  return NULL;
}
;

create function DB.DBA.RDF_QM_GC_MAPPING_SUBTREE (in mapname any, in gc_flags integer) returns any
{
  declare gc_res, submaps any;
  submaps := (select DB.DBA.VECTOR_AGG (s1."subm") from (
      sparql define input:storage ""
      select ?subm where {
          graph <http://www.openlinksw.com/schemas/virtrdf#> {
            `iri(?:mapname)` virtrdf:qmUserSubMaps ?submlist .
                    ?submlist ?p ?subm . filter (?p != rdf:type) . ?subm a [] } } ) as s1 );
  gc_res := DB.DBA.RDF_QM_GC_SUBTREE (mapname, gc_flags);
  if (gc_res is not null)
    return gc_res;
  commit work;
  foreach (any submapname in submaps) do
    {
      DB.DBA.RDF_QM_GC_MAPPING_SUBTREE (submapname, gc_flags);
    }
  return NULL;
}
;

create function DB.DBA.RDF_QM_DROP_MAPPING (in storage varchar, in mapname any) returns any
{
  declare graphiri varchar;
  declare qmid, qmgraph varchar;
  declare silent integer;
  qmid := get_keyword_ucase ('ID', mapname, NULL);
  qmgraph := get_keyword_ucase ('GRAPH', mapname, NULL);
  silent := get_keyword_ucase ('SILENT', mapname, 0);
  graphiri := DB.DBA.JSO_SYS_GRAPH ();
  if (qmid is null)
    {
      qmid := coalesce ((sparql
          define input:storage ""
          prefix virtrdf: <http://www.openlinksw.com/schemas/virtrdf#>
          select ?s from <http://www.openlinksw.com/schemas/virtrdf#> where {
              ?s rdf:type virtrdf:QuadMap .
              ?s virtrdf:qmGraphRange-rvrFixedValue `iri(?:qmgraph)` .
              ?s virtrdf:qmTableName "" .
              } ));
      if (qmid is null)
        return vector (vector ('00100', 'Quad map for graph <' || qmgraph || '> is not found'));
    }
  else
    {
      if (silent and not exists ((sparql define input:storage ""
        prefix rdfdf: <http://www.openlinksw.com/virtrdf-data-formats#>
        select (1) where {
            graph ?:graphiri {
                `iri(?:qmid)` a ?t } } ) ) )
        return vector (vector ('00000', 'Quad map <' || qmid || '> does not exist, the DROP statement is ignored due to SILENT option'));
    }
  DB.DBA.RDF_QM_ASSERT_JSO_TYPE (qmid, 'http://www.openlinksw.com/schemas/virtrdf#QuadMap');
  if (storage is null)
    {
      declare report, storages any;
      vectorbld_init (storages);
      for (sparql
        define input:storage ""
        select ?st where {
            graph <http://www.openlinksw.com/schemas/virtrdf#> {
                  { ?st virtrdf:qsUserMaps ?subm .
                    ?subm ?p `iri(?:qmid)` }
                union
                  { ?st virtrdf:qsDefaultMap `iri(?:qmid)` }
              } } ) do
        {
          DB.DBA.RDF_QM_ASSERT_STORAGE_FLAG ("st", 0);
          vectorbld_acc (storages, cast ("st" as varchar));
        }
      vectorbld_final (storages);
      vectorbld_init (report);
      foreach (varchar alt_st in storages) do
        {
          -- dbg_obj_princ ('Will run DB.DBA.RDF_QM_DELETE_MAPPING_FROM_STORAGE (', alt_st, ', NULL, ', qmid, ')');
          DB.DBA.RDF_QM_DELETE_MAPPING_FROM_STORAGE (alt_st, NULL, qmid);
          vectorbld_acc (report, vector ('00000', 'Quad map <' || qmid || '> is no longer used in storage <' || alt_st || '>'));
        }
      DB.DBA.RDF_QM_GC_MAPPING_SUBTREE (qmid, 0);
      vectorbld_acc (report, vector ('00000', 'Quad map <' || qmid || '> is deleted'));
      vectorbld_final (report);
      if (length (storages))
        report := vector_concat (report, DB.DBA.RDF_QM_APPLY_CHANGES (null, storages));
      return report;
    }
  else
    {
      if (not exists (sparql
        define input:storage ""
        select ?st where {
            graph <http://www.openlinksw.com/schemas/virtrdf#> {
                  { ?st virtrdf:qsUserMaps ?subm .
                    ?subm ?p `iri(?:qmid)` }
                union
                  { ?st virtrdf:qsDefaultMap `iri(?:qmid)` }
                filter (?st = iri(?:storage))
              } } ) )
        {
          if (silent)
            return vector (vector ('00000', 'Quad map <' || qmid || '> is not used in storage <' || storage || '>, the DROP statement is ignored due to SILENT option'));
          signal ('22023', 'Quad map <' || qmid || '> is not used in storage <' || storage || '>');
        }
      DB.DBA.RDF_QM_DELETE_MAPPING_FROM_STORAGE (storage, NULL, qmid);
      DB.DBA.RDF_QM_GC_MAPPING_SUBTREE (qmid, 1);
      return vector (vector ('00000', 'Quad map <' || qmid || '> is no longer used in storage <' || storage || '>'));
    }
}
;

create function DB.DBA.RDF_QM_MACROEXPAND_TEMPLATE (in iritmpl varchar) returns varchar
{
  declare pos integer;
  pos := strstr (iritmpl, '^{URIQADefaultHost}^');
  if (pos is not null)
    {
      declare host varchar;
      host := registry_get ('URIQADefaultHost');
      if (not isstring (host))
        signal ('22023', 'Can not use ^{URIQADefaultHost}^ in IRI template if there is no DefaultHost parameter in [URIQA] section of Virtuoso configuration file');
      iritmpl := replace (iritmpl, '^{URIQADefaultHost}^', host);
    }
  pos := strstr (iritmpl, '^{DynamicLocalFormat}^');
  if (pos is not null)
    {
      declare host varchar;
      host := registry_get ('URIQADefaultHost');
      if (not isstring (host))
        signal ('22023', 'Can not use ^{DynamicLocalFormat}^ in IRI template if there is no DefaultHost parameter in [URIQA] section of Virtuoso configuration file');
--      if (atoi (coalesce (virtuoso_ini_item_value ('URIQA', 'DynamicLocal'), '0')))
--        signal ('22023', 'Can not use ^{DynamicLocalFormat}^ in IRI template if DynamicLocal is not set to 1 in [URIQA] section of Virtuoso configuration file');
      if ((pos > 0) and (pos < 10) and strchr (subseq (iritmpl, 0, pos), ':') is not null)
        signal ('22023', 'Misplaced ^{DynamicLocalFormat}^: its expansion will contain protocol prefix but the template contains one already');
      if (strchr (host, ':') is not null)
        iritmpl := replace (iritmpl, '^{DynamicLocalFormat}^', 'http://%{WSHostName}U:%{WSHostPort}U');
      else
        iritmpl := replace (iritmpl, '^{DynamicLocalFormat}^', 'http://%{WSHost}U');
    }
  pos := strstr (iritmpl, '^{');
  if (pos is not null)
    {
      declare pos2 integer;
      pos2 := strstr (subseq (iritmpl, pos), '^}');
      if (pos2 is not null)
        signal ('22023', 'The macro ' || subseq (iritmpl, pos, pos + pos2 + 2) || ' is not known, supported names are ^{URIQADefaultHost}^ and ^{DynamicLocalFormat}^');
    }
  return iritmpl;
}
;

create function DB.DBA.RDF_QM_CBD_OF_IRI_CLASS (in classiri varchar) returns any
{
  declare descr any;
  descr := ((sparql define input:storage ""
      construct {
        <class> ?cp ?co .
        <class> virtrdf:qmfValRange-rvrSprintffs <sprintffs> .
        <sprintffs> ?sffp ?sffo .
        <class> virtrdf:qmfSuperFormats <sups> .
        <sups> ?supp ?supo . }
      from <http://www.openlinksw.com/schemas/virtrdf#>
      where {
          {
            `iri(?:classiri)` ?cp ?co .
            filter (!(?cp in (virtrdf:qmfValRange-rvrSprintffs, virtrdf:qmfSuperFormats)))
          } union {
            `iri(?:classiri)` virtrdf:qmfValRange-rvrSprintffs ?sffs .
            optional { ?sffs ?sffp ?sffo . }
          } union {
            `iri(?:classiri)` virtrdf:qmfSuperFormats ?sups .
            optional { ?sups ?supp ?supo . FILTER (str(?supo) != bif:concat (str(?:classiri), '-nullable')) }
          } } ) );
  descr := dict_list_keys (descr, 2);
  rowvector_digit_sort (descr, 0, 1);
  rowvector_digit_sort (descr, 1, 1);
  return descr;
}
;

create function DB.DBA.RDF_QM_DEFINE_IRI_CLASS_FORMAT (in classiri varchar, in iritmpl varchar, in arglist any, in options any, in origclassiri varchar := null) returns any
{
  declare graphiri varchar;
  declare sprintffsid, superformatsid, nullablesuperformatid varchar;
  declare basetype, basetypeiri varchar;
  declare bij, deref integer;
  declare sffs, res any;
  declare argctr, arglist_len, isnotnull, sff_ctr, sff_count, bij_sff_count integer;
  declare needs_arg_dtps integer;
  declare arg_dtps varchar;
  graphiri := DB.DBA.JSO_SYS_GRAPH ();
  if (get_keyword_ucase ('DATATYPE', options) is not null or get_keyword_ucase ('LANG', options) is not null)
    signal ('22023', 'IRI class <' || classiri || '> can not have DATATYPE or LANG options specified');
  bij := get_keyword_ucase ('BIJECTION', options, 0);
  deref := get_keyword_ucase ('DEREF', options, 0);
  sffs := get_keyword_ucase ('RETURNS', options);
  if (sffs is null)
    sffs := vector (iritmpl); -- note that this is before macroexpand
  sff_count := length (sffs);
  iritmpl := DB.DBA.RDF_QM_MACROEXPAND_TEMPLATE (iritmpl);
  sprintffsid := classiri || '--Sprintffs';
  superformatsid := classiri || '--SuperFormats';
  nullablesuperformatid := null;
  res := vector ();
  foreach (any arg in arglist) do
    if (UNAME'in' <> arg[0])
      signal ('22023', 'Only "in" parameters are now supported in argument lists of class formats, "' || arg[0] || '" is not supported in CREATE IRI CLASS <' || classiri || '>' );
  arglist_len := length (arglist);
  isnotnull := 1;
  needs_arg_dtps := 0;
  arg_dtps := '';
  if (arglist_len <> 1)
    {
      declare type_name varchar;
      declare dtp integer;
      if (arglist_len = 0)
        basetype := 'zeropart-uri';
      else
        basetype := 'multipart-uri';
      for (argctr := 0; (argctr < arglist_len) and isnotnull; argctr := argctr + 1)
        {
          if (not (coalesce (arglist[argctr][3], 0)))
            isnotnull := 0;
          type_name := lower (arglist[argctr][2]);
          dtp := case (type_name)
            when 'integer' then __tag of integer
            when 'smallint' then __tag of integer
            when 'bigint' then __tag of integer
            when 'varchar' then __tag of varchar
            when 'date' then __tag of date
            when 'datetime' then __tag of datetime
            when 'double precision' then __tag of double precision
            when 'numeric' then __tag of numeric
            when 'nvarchar' then __tag of nvarchar
            else 255 end;
          if (type_name = 'nvarchar')
            needs_arg_dtps := 1;
          arg_dtps := arg_dtps || chr (bit_and (127, dtp));
        }
    }
  else /* arglist is 1 item long */
    {
      basetype := lower (arglist[0][2]);
      if (not (basetype in ('integer', 'varchar', 'date', /* 'datetime', 'double precision',*/ 'numeric', 'nvarchar')))
        signal ('22023', 'The datatype "' || basetype || '" is not supported in CREATE IRI CLASS <' || classiri || '>' );
      basetype := 'sql-' || replace (basetype, ' ', '') || '-uri';
      if (not (coalesce (arglist[0][3], 0)))
        isnotnull := 0;
      if (basetype = 'nvarchar')
        {
          needs_arg_dtps := 1;
          arg_dtps := chr (bit_and (127, __tag of nvarchar));
        }
    }
  if (not isnotnull)
    basetype := basetype || '-nullable';
  basetypeiri := 'http://www.openlinksw.com/virtrdf-data-formats#' || basetype;
  if (origclassiri is null)
    {
      if (isnotnull and (arglist_len > 0))
        {
          declare arglist_copy any;
          if (classiri like '%-nullable')
            signal ('22023', 'The name of non-nullable IRI class in CREATE IRI CLASS <' || classiri || '> is misleading' );
          arglist_copy := arglist;
          for (argctr := 0; (argctr < arglist_len); argctr := argctr + 1)
            arglist_copy[argctr][3] := 0;
          nullablesuperformatid := classiri || '-nullable';
          res := vector_concat (res,
            DB.DBA.RDF_QM_DEFINE_IRI_CLASS_FORMAT (nullablesuperformatid, iritmpl, arglist_copy, options, NULL) );
        }
      origclassiri := classiri;
    }
  if (DB.DBA.RDF_QM_ASSERT_JSO_TYPE (classiri, 'http://www.openlinksw.com/schemas/virtrdf#QuadMapFormat', 1))
    {
      declare side_s IRI_ID;
      side_s := DB.DBA.RDF_QM_GC_SUBTREE (classiri, 2);
      if (side_s is not null)
        {
          declare tmpname varchar;
          declare old_descr, new_descr any;
          tmpname := uuid();
          { declare exit handler for sqlstate '*' {
              signal ('22023', 'Can not change IRI class <' || classiri || '> because it is used by other quad map objects, e.g., <' || id_to_iri_nosignal (side_s) || '>; moreover, the new declaration may be invalid.'); };
            DB.DBA.RDF_QM_DEFINE_IRI_CLASS_FORMAT (tmpname, iritmpl, arglist, options, classiri);
          }
          old_descr := DB.DBA.RDF_QM_CBD_OF_IRI_CLASS(classiri);
          new_descr := DB.DBA.RDF_QM_CBD_OF_IRI_CLASS(tmpname);
          -- dbg_obj_princ ('old descr is ', old_descr);
          -- dbg_obj_princ ('new descr is ', new_descr);
          if (md5 (serialize (old_descr)) = md5 (serialize (new_descr)))
            {
              sparql define input:storage ""
              delete from graph <http://www.openlinksw.com/schemas/virtrdf#>  { `iri(?:tmpname)` ?p ?o }
              where { `iri(?:tmpname)` ?p ?o };
              return vector (vector ('00000', 'Previous definition of IRI class <' || classiri || '> is identical to the new one, not touched'));
            }
          signal ('22023', 'Can not change IRI class <' || classiri || '> because it is used by other quad map objects, e.g., <' || id_to_iri_nosignal (side_s) || '>');
        }
      res := vector_concat (res, vector (vector ('00000', 'Previous definition of IRI class <' || classiri || '> has been dropped')));
    }
  else
    res := vector ();
  if (bij)
    {
      if (__sprintff_is_proven_unparseable (iritmpl))
        signal ('22023', 'IRI class <' || classiri || '> has OPTION (BIJECTION) but its format string can not be unambiguously parsed by sprintf_inverse()');
    }
  else
    {
      if (__sprintff_is_proven_bijection (iritmpl))
        bij := 1;
    }
  bij_sff_count := 0;
  sparql define input:storage ""
  prefix rdfdf: <http://www.openlinksw.com/virtrdf-data-formats#>
  delete from graph <http://www.openlinksw.com/schemas/virtrdf#> { ?s ?p ?o }
  from <http://www.openlinksw.com/schemas/virtrdf#>
  where { ?s ?p ?o . filter (?s = iri(?:sprintffsid)) };
  for (sff_ctr := 0; sff_ctr < sff_count; sff_ctr := sff_ctr + 1)
    {
      declare sff varchar;
      sff := sffs [sff_ctr];
      sff := DB.DBA.RDF_QM_MACROEXPAND_TEMPLATE (sff);
      if ((not bij) and __sprintff_is_proven_bijection (sff))
        bij_sff_count := bij_sff_count + 1;
      sparql define input:storage ""
      prefix rdfdf: <http://www.openlinksw.com/virtrdf-data-formats#>
      insert in graph <http://www.openlinksw.com/schemas/virtrdf#> {
          `iri(?:sprintffsid)`
            `iri (bif:sprintf ("%s%d", str (rdf:_), ?:sff_ctr+1))` ?:sff };
    }
  if ((not bij) and (bij_sff_count = sff_count) and (bij_sff_count > 0))
    bij := 1;
  if (not needs_arg_dtps)
    arg_dtps := NULL;
  sparql define input:storage ""
  prefix rdfdf: <http://www.openlinksw.com/virtrdf-data-formats#>
  delete from graph <http://www.openlinksw.com/schemas/virtrdf#> { ?s ?p ?o }
  from <http://www.openlinksw.com/schemas/virtrdf#>
  where { ?s ?p ?o . filter (?s = iri(?:classiri)) };
  sparql define input:storage ""
  prefix rdfdf: <http://www.openlinksw.com/virtrdf-data-formats#>
  delete from graph <http://www.openlinksw.com/schemas/virtrdf#> { ?s ?p ?o }
  from <http://www.openlinksw.com/schemas/virtrdf#>
  where { ?s ?p ?o . filter (?s = iri(?:superformatsid)) };
  sparql define input:storage ""
  prefix rdfdf: <http://www.openlinksw.com/virtrdf-data-formats#>
  insert in graph <http://www.openlinksw.com/schemas/virtrdf#>
    {
      `iri(?:classiri)`
        rdf:type virtrdf:QuadMapFormat ;
        virtrdf:inheritFrom `iri(?:basetypeiri)`;
        virtrdf:noInherit virtrdf:qmfName ;
        virtrdf:noInherit virtrdf:qmfCustomString1 ;
        virtrdf:qmfName `bif:concat (?:basetype, '-user-', ?:origclassiri)` ;
        virtrdf:qmfCustomString1 ?:iritmpl ;
        virtrdf:qmfColumnCount ?:arglist_len ;
        virtrdf:qmfSuperFormats `iri(?:superformatsid)` ;
        virtrdf:qmfIsBijection ?:bij ;
        virtrdf:qmfDerefFlags ?:deref ;
        virtrdf:qmfArgDtps ?:arg_dtps ;
        virtrdf:qmfValRange-rvrRestrictions
          virtrdf:SPART_VARR_IS_REF ,
          virtrdf:SPART_VARR_IS_IRI ,
          virtrdf:SPART_VARR_SPRINTFF ;
        virtrdf:qmfValRange-rvrSprintffs `iri(?:sprintffsid)` ;
        virtrdf:qmfValRange-rvrSprintffCount ?:sff_count .
      `iri(?:sprintffsid)`
        rdf:type virtrdf:array-of-string .
      `iri(?:superformatsid)`
        rdf:type virtrdf:array-of-QuadMapFormat };
  if (isnotnull and (arglist_len > 0))
    {
      sparql define input:storage ""
      prefix rdfdf: <http://www.openlinksw.com/virtrdf-data-formats#>
      insert in graph <http://www.openlinksw.com/schemas/virtrdf#> {
          `iri(?:classiri)`
            virtrdf:qmfValRange-rvrRestrictions
              virtrdf:SPART_VARR_NOT_NULL .
          `iri(?:superformatsid)`
            rdf:_1 `iri(?:nullablesuperformatid)` };
    }
  commit work;
  return vector_concat (res, vector_concat (res, vector (vector ('00000', 'IRI class <' || classiri || '> has been defined (inherited from rdfdf:' || basetype || ')'))));
}
;

create function DB.DBA.RDF_QM_DEFINE_IRI_CLASS_FUNCTIONS (in classiri varchar, in fheaders any, in options any, in origclassiri varchar := null) returns any
{
/*
fheaders is, say,
     vector ( '
            vector ( 'DB.DBA.RDF_DF_GRANTEE_ID_URI' ,
                vector (
                    vector ( 306,  'id' ,  'integer' ,  NULL ) ),  'varchar' ,  NULL ),
            vector ( 'DB.DBA.RDF_DF_GRANTEE_ID_URI_INVERSE' ,
                vector (
                    vector ( 306,  'id_iri' ,  'varchar' ,  NULL ) ),  'integer' ,  NULL ) ) )
*/
  declare uriprint any;
  declare uriprintname, uriparsename varchar;
  declare arglist_len, isnotnull integer;
  declare graphiri varchar;
  declare superformatsid, nullablesuperformatid varchar;
  declare bij, deref integer;
  declare sffs any;
  declare res any;
  graphiri := DB.DBA.JSO_SYS_GRAPH ();
  superformatsid := classiri || '--SuperFormats';
  nullablesuperformatid := null;
  if (get_keyword_ucase ('DATATYPE', options) is not null or get_keyword_ucase ('LANG', options) is not null)
    signal ('22023', 'IRI class <' || classiri || '> can not have DATATYPE or LANG options specified');
  bij := get_keyword_ucase ('BIJECTION', options, 0);
  deref := get_keyword_ucase ('DEREF', options, 0);
  sffs := get_keyword_ucase ('RETURNS', options);
  DB.DBA.RDF_QM_ASSERT_JSO_TYPE (classiri, NULL);
  DB.DBA.RDF_QM_CHECK_CLASS_FUNCTION_HEADERS (fheaders, 1, 0, 'IRI composing', 'IRI parsing', bij, deref);
  uriprint := fheaders[0];
  uriprintname := uriprint[0];
  declare arglist, basetype, basetypeiri varchar;
  arglist := uriprint[1];
  arglist_len := length (arglist);
  if (arglist_len <> 1)
    {
      if (arglist_len = 0)
        basetype := 'zeropart-uri-fn-nullable';
      else
        basetype := 'multipart-uri-fn-nullable';
      isnotnull := 0;
    }
  else
    {
      basetype := lower (arglist[0][2]);
      if (not (basetype in ('integer', 'varchar', 'date', 'datetime', 'double precision', 'numeric', 'nvarchar')))
        signal ('22023', 'The datatype "' || basetype || '" is not supported in CREATE IRI CLASS <' || classiri || '> USING FUNCTION' );
      basetype := 'sql-' || replace (basetype, ' ', '') || '-uri-fn';
      if (coalesce (arglist[0][3], 0))
        isnotnull := 1;
      else
        {
          basetype := basetype || '-nullable';
          isnotnull := 0;
        }
    }
  basetypeiri := 'http://www.openlinksw.com/virtrdf-data-formats#' || basetype;
  if (origclassiri is null)
    origclassiri := classiri;
  if (DB.DBA.RDF_QM_ASSERT_JSO_TYPE (classiri, 'http://www.openlinksw.com/schemas/virtrdf#QuadMapFormat', 1))
    {
      declare side_s IRI_ID;
      side_s := DB.DBA.RDF_QM_GC_SUBTREE (classiri, 2);
      if (side_s is not null)
        {
          declare tmpname varchar;
          declare old_descr, new_descr any;
          tmpname := uuid();
          { declare exit handler for sqlstate '*' {
              signal ('22023', 'Can not change IRI class <' || classiri || '> because it is used by other quad map objects, e.g., <' || id_to_iri_nosignal (side_s) || '>; moreover, the new declaration may be invalid.'); };
            DB.DBA.RDF_QM_DEFINE_IRI_CLASS_FUNCTIONS (tmpname, fheaders, options, classiri);
          }
          old_descr := DB.DBA.RDF_QM_CBD_OF_IRI_CLASS(classiri);
          new_descr := DB.DBA.RDF_QM_CBD_OF_IRI_CLASS(tmpname);
          if (md5 (serialize (old_descr)) = md5 (serialize (new_descr)))
            {
              sparql define input:storage ""
              delete from graph <http://www.openlinksw.com/schemas/virtrdf#>  { `iri(?:tmpname)` ?p ?o }
              where { `iri(?:tmpname)` ?p ?o };
              return vector (vector ('00000', 'Previous definition of IRI class <' || classiri || '> is identical to the new one, not touched'));
            }
            signal ('22023', 'Can not change class <' || classiri || '> because it is used by other quad map objects, e.g., <' || id_to_iri_nosignal (side_s) || '>');
        }
      res := vector (vector ('00000', 'Previous definition of class <' || classiri || '> has been dropped'));
    }
  else
    res := vector ();
  sparql define input:storage ""
  prefix rdfdf: <http://www.openlinksw.com/virtrdf-data-formats#>
  delete from graph <http://www.openlinksw.com/schemas/virtrdf#> { ?s ?p ?o }
  from <http://www.openlinksw.com/schemas/virtrdf#>
  where { ?s ?p ?o . filter (?s = iri(?:classiri)) };
  sparql define input:storage ""
  prefix rdfdf: <http://www.openlinksw.com/virtrdf-data-formats#>
  delete from graph <http://www.openlinksw.com/schemas/virtrdf#> { ?s ?p ?o }
  from <http://www.openlinksw.com/schemas/virtrdf#>
  where { ?s ?p ?o . filter (?s = iri(?:superformatsid)) };
  sparql define input:storage ""
  prefix rdfdf: <http://www.openlinksw.com/virtrdf-data-formats#>
  insert in graph <http://www.openlinksw.com/schemas/virtrdf#> {
      `iri(?:classiri)`
        rdf:type virtrdf:QuadMapFormat;
        virtrdf:inheritFrom `iri(?:basetypeiri)`;
        virtrdf:noInherit virtrdf:qmfName ;
        virtrdf:noInherit virtrdf:qmfCustomString1 ;
        virtrdf:qmfName `bif:concat (?:basetype, '-user-', ?:origclassiri)` ;
        virtrdf:qmfColumnCount ?:arglist_len ;
        virtrdf:qmfCustomString1 ?:uriprintname ;
        virtrdf:qmfSuperFormats `iri(?:superformatsid)` ;
        virtrdf:qmfIsBijection ?:bij ;
        virtrdf:qmfDerefFlags ?:deref ;
        virtrdf:qmfValRange-rvrRestrictions
          virtrdf:SPART_VARR_IS_REF ,
          virtrdf:SPART_VARR_IS_IRI ,
          virtrdf:SPART_VARR_IRI_CALC .
      `iri(?:superformatsid)`
        rdf:type virtrdf:array-of-QuadMapFormat };
  if (isnotnull)
    {
      sparql define input:storage ""
      prefix rdfdf: <http://www.openlinksw.com/virtrdf-data-formats#>
      insert in graph <http://www.openlinksw.com/schemas/virtrdf#> {
          `iri(?:classiri)`
            virtrdf:qmfValRange-rvrRestrictions
              virtrdf:SPART_VARR_NOT_NULL };
    }
  if (sffs is not null)
    {
      declare sff_count, sff_ctr integer;
      declare sffsid varchar;
      sffsid := classiri || '--Sprintffs';
      sff_count := length (sffs);
      sparql define input:storage ""
      prefix rdfdf: <http://www.openlinksw.com/virtrdf-data-formats#>
      delete from graph <http://www.openlinksw.com/schemas/virtrdf#> { ?s ?p ?o }
      from <http://www.openlinksw.com/schemas/virtrdf#>
      where { ?s ?p ?o . filter (?s = iri(?:sffsid)) };
      sparql define input:storage ""
      prefix rdfdf: <http://www.openlinksw.com/virtrdf-data-formats#>
      insert in graph <http://www.openlinksw.com/schemas/virtrdf#> {
          `iri(?:classiri)`
            virtrdf:qmfValRange-rvrRestrictions
              virtrdf:SPART_VARR_SPRINTFF ;
            virtrdf:qmfValRange-rvrSprintffs `iri(?:sffsid)` ;
            virtrdf:qmfValRange-rvrSprintffCount ?:sff_count .
          `iri(?:sffsid)`
            rdf:type virtrdf:array-of-string };
      for (sff_ctr := 0; sff_ctr < sff_count; sff_ctr := sff_ctr + 1)
        {
          declare sff varchar;
          sff := sffs [sff_ctr];
          sff := DB.DBA.RDF_QM_MACROEXPAND_TEMPLATE (sff);
          sparql define input:storage ""
          prefix rdfdf: <http://www.openlinksw.com/virtrdf-data-formats#>
          insert in graph <http://www.openlinksw.com/schemas/virtrdf#> {
              `iri(?:sffsid)`
                `iri (bif:sprintf ("%s%d", str (rdf:_), ?:sff_ctr+1))` ?:sff };
        }
    }
  commit work;
  return vector_concat (res, vector (vector ('00000', 'IRI class <' || classiri || '> has been defined (inherited from rdfdf:' || basetype || ') using ' || uriprintname)));
}
;

create function DB.DBA.RDF_QM_DEFINE_LITERAL_CLASS_FORMAT (in classiri varchar, in iritmpl varchar, in arglist any, in options any, in origclassiri varchar := null) returns any
{
  declare graphiri varchar;
  declare sprintffsid, superformatsid, nullablesuperformatid varchar;
  declare basetype, basetypeiri varchar;
  declare const_dt, dt_expn, const_lang varchar;
  declare bij, deref integer;
  declare sffs, res any;
  declare argctr, arglist_len, isnotnull, sff_ctr, sff_count, bij_sff_count integer;
  declare needs_arg_dtps integer;
  declare arg_dtps varchar;
  graphiri := DB.DBA.JSO_SYS_GRAPH ();
  const_dt := get_keyword_ucase ('DATATYPE', options);
  const_lang := get_keyword_ucase ('LANG', options);
  bij := get_keyword_ucase ('BIJECTION', options, 0);
  deref := get_keyword_ucase ('DEREF', options, 0);
  sffs := get_keyword_ucase ('RETURNS', options);
  if (sffs is null)
    sffs := vector (iritmpl); -- note that this is before macroexpand
  sff_count := length (sffs);
  iritmpl := DB.DBA.RDF_QM_MACROEXPAND_TEMPLATE (iritmpl);
  sprintffsid := classiri || '--Sprintffs';
  superformatsid := classiri || '--SuperFormats';
  nullablesuperformatid := null;
  res := vector ();
  foreach (any arg in arglist) do
    if (UNAME'in' <> arg[0])
      signal ('22023', 'Only "in" parameters are now supported in argument lists of class formats, "' || arg[0] || '" is not supported in CREATE IRI CLASS <' || classiri || '>' );
  arglist_len := length (arglist);
  isnotnull := 1;
  needs_arg_dtps := 0;
  arg_dtps := '';
  if (arglist_len <> 1)
    {
      declare type_name varchar;
      declare dtp integer;
      if (arglist_len = 0)
        basetype := 'zeropart-literal';
      else
        basetype := 'multipart-literal';
      for (argctr := 0; (argctr < arglist_len) and isnotnull; argctr := argctr + 1)
        {
          if (not (coalesce (arglist[argctr][3], 0)))
            isnotnull := 0;
          type_name := lower (arglist[argctr][2]);
          dtp := case (type_name)
            when 'integer' then __tag of integer
            when 'smallint' then __tag of integer
            when 'bigint' then __tag of integer
            when 'varchar' then __tag of varchar
            when 'date' then __tag of date
            when 'datetime' then __tag of datetime
            when 'double precision' then __tag of double precision
            when 'numeric' then __tag of numeric
            when 'nvarchar' then __tag of nvarchar
            else 255 end;
          if (type_name = 'nvarchar')
            needs_arg_dtps := 1;
          arg_dtps := arg_dtps || chr (bit_and (127, dtp));
        }
    }
  else /* arglist is 1 item long */
    {
      basetype := lower (arglist[0][2]);
      if (not (basetype in ('integer', 'varchar', 'date', 'datetime', 'double precision', 'numeric', 'nvarchar')))
        signal ('22023', 'The datatype "' || basetype || '" is not supported in CREATE LITERAL CLASS <' || classiri || '>' );
      basetype := 'sql-' || replace (basetype, ' ', '') || '-literal';
      if (not (coalesce (arglist[0][3], 0)))
        isnotnull := 0;
      if (basetype = 'nvarchar')
        {
          needs_arg_dtps := 1;
          arg_dtps := chr (bit_and (127, __tag of nvarchar));
        }
    }
  if (not isnotnull)
    basetype := basetype || '-nullable';
  basetypeiri := 'http://www.openlinksw.com/virtrdf-data-formats#' || basetype;
  if (const_dt is not null)
    dt_expn := ' ' || WS.WS.STR_SQL_APOS (const_dt);
  else
    dt_expn := NULL;
  if (origclassiri is null)
    {
      if (isnotnull and (arglist_len > 0))
        {
          declare arglist_copy any;
          if (classiri like '%-nullable')
            signal ('22023', 'The name of non-nullable literal class in CREATE LITERAL CLASS <' || classiri || '> is misleading' );
          arglist_copy := arglist;
          for (argctr := 0; (argctr < arglist_len); argctr := argctr + 1)
            arglist_copy[argctr][3] := 0;
          nullablesuperformatid := classiri || '-nullable';
          res := vector_concat (res,
            DB.DBA.RDF_QM_DEFINE_IRI_CLASS_FORMAT (nullablesuperformatid, iritmpl, arglist_copy, options, NULL) );
        }
      origclassiri := classiri;
    }
  if (DB.DBA.RDF_QM_ASSERT_JSO_TYPE (classiri, 'http://www.openlinksw.com/schemas/virtrdf#QuadMapFormat', 1))
    {
      declare side_s IRI_ID;
      side_s := DB.DBA.RDF_QM_GC_SUBTREE (classiri, 2);
      if (side_s is not null)
        {
          declare tmpname varchar;
          declare old_descr, new_descr any;
          tmpname := uuid();
          { declare exit handler for sqlstate '*' {
              signal ('22023', 'Can not change literal class <' || classiri || '> because it is used by other quad map objects, e.g., <' || id_to_iri_nosignal (side_s) || '>; moreover, the new declaration may be invalid.'); };
            DB.DBA.RDF_QM_DEFINE_LITERAL_CLASS_FORMAT (tmpname, iritmpl, arglist, options, classiri);
          }
          old_descr := DB.DBA.RDF_QM_CBD_OF_IRI_CLASS(classiri);
          new_descr := DB.DBA.RDF_QM_CBD_OF_IRI_CLASS(tmpname);
          if (md5 (serialize (old_descr)) = md5 (serialize (new_descr)))
            {
              sparql define input:storage ""
              delete from graph <http://www.openlinksw.com/schemas/virtrdf#>  { `iri(?:tmpname)` ?p ?o }
              where { `iri(?:tmpname)` ?p ?o };
              return vector (vector ('00000', 'Previous definition of literal class <' || classiri || '> is identical to the new one, not touched'));
            }
          signal ('22023', 'Can not change IRI class <' || classiri || '> because it is used by other quad map objects, e.g., <' || id_to_iri_nosignal (side_s) || '>');
        }
      res := vector_concat (res, vector (vector ('00000', 'Previous definition of IRI class <' || classiri || '> has been dropped')));
    }
  else
    res := vector ();
  if (bij)
    {
      if (__sprintff_is_proven_unparseable (iritmpl))
        signal ('22023', 'Literal class <' || classiri || '> has OPTION (BIJECTION) but its format string can not be unambiguously parsed by sprintf_inverse()');
    }
  else
    {
      if (__sprintff_is_proven_bijection (iritmpl))
        bij := 1;
    }
  bij_sff_count := 0;
  sparql define input:storage ""
  prefix rdfdf: <http://www.openlinksw.com/virtrdf-data-formats#>
  delete from graph <http://www.openlinksw.com/schemas/virtrdf#> { ?s ?p ?o }
  from <http://www.openlinksw.com/schemas/virtrdf#>
  where { ?s ?p ?o . filter (?s = iri(?:sprintffsid)) };
  for (sff_ctr := 0; sff_ctr < sff_count; sff_ctr := sff_ctr + 1)
    {
      declare sff varchar;
      sff := sffs [sff_ctr];
      sff := DB.DBA.RDF_QM_MACROEXPAND_TEMPLATE (sff);
      if ((not bij) and __sprintff_is_proven_bijection (sff))
        bij_sff_count := bij_sff_count + 1;
      sparql define input:storage ""
      prefix rdfdf: <http://www.openlinksw.com/virtrdf-data-formats#>
      insert in graph <http://www.openlinksw.com/schemas/virtrdf#> {
          `iri(?:sprintffsid)`
            `iri (bif:sprintf ("%s%d", str (rdf:_), ?:sff_ctr+1))` ?:sff };
    }
  if ((not bij) and (bij_sff_count = sff_count) and (bij_sff_count > 0))
    bij := 1;
  if (not needs_arg_dtps)
    arg_dtps := NULL;
  sparql define input:storage ""
  prefix rdfdf: <http://www.openlinksw.com/virtrdf-data-formats#>
  delete from graph <http://www.openlinksw.com/schemas/virtrdf#> { ?s ?p ?o }
  from <http://www.openlinksw.com/schemas/virtrdf#>
  where { ?s ?p ?o . filter (?s = iri(?:classiri)) };
  sparql define input:storage ""
  prefix rdfdf: <http://www.openlinksw.com/virtrdf-data-formats#>
  delete from graph <http://www.openlinksw.com/schemas/virtrdf#> { ?s ?p ?o }
  from <http://www.openlinksw.com/schemas/virtrdf#>
  where { ?s ?p ?o . filter (?s = iri(?:superformatsid)) };
  sparql define input:storage ""
  prefix rdfdf: <http://www.openlinksw.com/virtrdf-data-formats#>
  insert in graph <http://www.openlinksw.com/schemas/virtrdf#>
    {
      `iri(?:classiri)`
        rdf:type virtrdf:QuadMapFormat ;
        virtrdf:inheritFrom `iri(?:basetypeiri)`;
        virtrdf:noInherit virtrdf:qmfName ;
        virtrdf:noInherit virtrdf:qmfCustomString1 ;
        virtrdf:qmfName `bif:concat (?:basetype, '-user-', ?:origclassiri)` ;
        virtrdf:qmfCustomString1 ?:iritmpl ;
        virtrdf:qmfDatatypeOfShortTmpl ?:dt_expn ;
        virtrdf:qmfColumnCount ?:arglist_len ;
        virtrdf:qmfSuperFormats `iri(?:superformatsid)` ;
        virtrdf:qmfIsBijection ?:bij ;
        virtrdf:qmfDerefFlags ?:deref ;
        virtrdf:qmfArgDtps ?:arg_dtps ;
        virtrdf:qmfValRange-rvrRestrictions virtrdf:SPART_VARR_IS_LIT, virtrdf:SPART_VARR_IRI_CALC;
        virtrdf:qmfValRange-rvrDatatype ?:const_dt ;
        virtrdf:qmfValRange-rvrLanguage ?:const_lang ;
        virtrdf:qmfValRange-rvrSprintffs `iri(?:sprintffsid)` ;
        virtrdf:qmfValRange-rvrSprintffCount ?:sff_count .
      `iri(?:sprintffsid)`
        rdf:type virtrdf:array-of-string .
      `iri(?:superformatsid)`
        rdf:type virtrdf:array-of-QuadMapFormat };
  if (const_dt is not null)
    {
      sparql define input:storage ""
      prefix rdfdf: <http://www.openlinksw.com/virtrdf-data-formats#>
      insert in graph <http://www.openlinksw.com/schemas/virtrdf#> {
          `iri(?:classiri)`
            virtrdf:qmfValRange-rvrRestrictions virtrdf:SPART_VARR_TYPED };
    }
  commit work;
  return vector_concat (res, vector_concat (res, vector (vector ('00000', 'Literal class <' || classiri || '> has been defined (inherited from rdfdf:' || basetype || ')'))));
}
;

create function DB.DBA.RDF_QM_DEFINE_LITERAL_CLASS_FUNCTIONS (in classiri varchar, in fheaders any, in options any, in origclassiri varchar := null) returns any
{
/*
fheaders is identical to DB.DBA.RDF_QM_DEFINE_IRI_CLASS_FUNCTIONS
*/
  declare uriprint any;
  declare uriprintname, uriparsename varchar;
  declare arglist_len integer;
  declare superformatsid, nullablesuperformatid varchar;
  declare res any;
  declare const_dt, dt_expn, const_lang varchar;
  declare bij, deref integer;
  superformatsid := classiri || '--SuperFormats';
  nullablesuperformatid := null;
  const_dt := get_keyword_ucase ('DATATYPE', options);
  const_lang := get_keyword_ucase ('LANG', options);
  bij := get_keyword_ucase ('BIJECTION', options, 0);
  deref := get_keyword_ucase ('DEREF', options, 0);
  DB.DBA.RDF_QM_ASSERT_JSO_TYPE (classiri, NULL);
  DB.DBA.RDF_QM_CHECK_CLASS_FUNCTION_HEADERS (fheaders, 0, 0, 'LITERAL composing', 'LITERAL parsing', bij, deref);
  uriprint := fheaders[0];
  uriprintname := uriprint[0];
  declare arglist, basetype, basetypeiri varchar;
  arglist := uriprint[1];
  arglist_len := length (arglist);
  if (arglist_len <> 1)
    {
      if (arglist_len = 0)
        basetype := 'zeropart-literal-fn-nullable';
      else
        basetype := 'multipart-literal-fn-nullable';
    }
  else
    {
      basetype := lower (arglist[0][2]);
      if (not (basetype in ('integer', 'varchar' /*, 'date', 'double precision'*/, 'nvarchar')))
        signal ('22023', 'The datatype "' || basetype || '" is not supported in CREATE IRI CLASS <' || classiri || '> USING FUNCTION' );
      basetype := 'sql-' || replace (basetype, ' ', '') || '-literal-fn';
      if (not (coalesce (arglist[0][3], 0)))
        basetype := basetype || '-nullable';
    }
  basetypeiri := 'http://www.openlinksw.com/virtrdf-data-formats#' || basetype;
  if (const_dt is not null)
    dt_expn := ' ' || WS.WS.STR_SQL_APOS (const_dt);
  else
    dt_expn := NULL;
  if (origclassiri is null)
    origclassiri := classiri;
  if (DB.DBA.RDF_QM_ASSERT_JSO_TYPE (classiri, 'http://www.openlinksw.com/schemas/virtrdf#QuadMapFormat', 1))
    {
      declare side_s IRI_ID;
      side_s := DB.DBA.RDF_QM_GC_SUBTREE (classiri, 2);
      if (side_s is not null)
        {
          declare tmpname varchar;
          declare old_descr, new_descr any;
          tmpname := uuid();
          { declare exit handler for sqlstate '*' {
              signal ('22023', 'Can not change IRI class <' || classiri || '> because it is used by other quad map objects, e.g., <' || id_to_iri_nosignal (side_s) || '>; moreover, the new declaration may be invalid.'); };
            DB.DBA.RDF_QM_DEFINE_LITERAL_CLASS_FUNCTIONS (tmpname, fheaders, options, classiri);
          }
          old_descr := DB.DBA.RDF_QM_CBD_OF_IRI_CLASS(classiri);
          new_descr := DB.DBA.RDF_QM_CBD_OF_IRI_CLASS(tmpname);
          if (md5 (serialize (old_descr)) = md5 (serialize (new_descr)))
            {
              sparql define input:storage ""
              delete from graph <http://www.openlinksw.com/schemas/virtrdf#>  { `iri(?:tmpname)` ?p ?o }
              where { `iri(?:tmpname)` ?p ?o };
              return vector (vector ('00000', 'Previous definition of literal class <' || classiri || '> is identical to the new one, not touched'));
            }
          signal ('22023', 'Can not change class <' || classiri || '> because it is used by other quad map objects, e.g., <' || id_to_iri_nosignal (side_s) || '>');
        }
      res := vector (vector ('00000', 'Previous definition of class <' || classiri || '> has been dropped'));
    }
  else
    res := vector ();
  sparql define input:storage ""
  prefix rdfdf: <http://www.openlinksw.com/virtrdf-data-formats#>
  delete from graph <http://www.openlinksw.com/schemas/virtrdf#> { ?s ?p ?o }
  from <http://www.openlinksw.com/schemas/virtrdf#>
  where { ?s ?p ?o . filter (?s = iri(?:classiri)) };
  sparql define input:storage ""
  prefix rdfdf: <http://www.openlinksw.com/virtrdf-data-formats#>
  delete from graph <http://www.openlinksw.com/schemas/virtrdf#> { ?s ?p ?o }
  from <http://www.openlinksw.com/schemas/virtrdf#>
  where { ?s ?p ?o . filter (?s = iri(?:superformatsid)) };
  sparql define input:storage ""
  prefix rdfdf: <http://www.openlinksw.com/virtrdf-data-formats#>
  insert in graph <http://www.openlinksw.com/schemas/virtrdf#> {
      `iri(?:classiri)`
        rdf:type virtrdf:QuadMapFormat ;
        virtrdf:inheritFrom `iri(?:basetypeiri)` ;
        virtrdf:noInherit virtrdf:qmfName ;
        virtrdf:noInherit virtrdf:qmfCustomString1 ;
        virtrdf:qmfName `bif:concat (?:basetype, '-user-', ?:origclassiri)` ;
        virtrdf:qmfColumnCount ?:arglist_len ;
        virtrdf:qmfCustomString1 ?:uriprintname ;
        virtrdf:qmfDatatypeOfShortTmpl ?:dt_expn ;
        virtrdf:qmfIsBijection ?:bij ;
        virtrdf:qmfDerefFlags ?:deref ;
        virtrdf:qmfValRange-rvrRestrictions virtrdf:SPART_VARR_IS_LIT ;
        virtrdf:qmfValRange-rvrDatatype ?:const_dt ;
        virtrdf:qmfValRange-rvrLanguage ?:const_lang ;
        virtrdf:qmfSuperFormats `iri(?:superformatsid)` .
      `iri(?:superformatsid)`
        rdf:type virtrdf:array-of-QuadMapFormat };
  if (const_dt is not null)
    {
      sparql define input:storage ""
      prefix rdfdf: <http://www.openlinksw.com/virtrdf-data-formats#>
      insert in graph <http://www.openlinksw.com/schemas/virtrdf#> {
          `iri(?:classiri)`
            virtrdf:qmfValRange-rvrRestrictions virtrdf:SPART_VARR_TYPED };
    }
  commit work;
  return vector_concat (res, vector (vector ('00000', 'LITERAL class <' || classiri || '> has been defined (inherited from rdfdf:' || basetype || ') using ' || uriprintname)));
}
;

create function DB.DBA.RDF_QM_DEFINE_LITERAL_CLASS_WITH_FIXED_LANG (in coltype varchar, in o_lang varchar, in is_nullable integer := 0) returns any
{
  declare src_lname, res_lname, src_fmtid, res_fmtid, src_baseid, res_baseid, superformatsid, nullablesuperformatid, o_lang_str varchar;
  nullablesuperformatid := null;
  if (not is_nullable)
    nullablesuperformatid := DB.DBA.RDF_QM_DEFINE_LITERAL_CLASS_WITH_FIXED_LANG (coltype, o_lang, 1);
  src_baseid := 'http://www.openlinksw.com/virtrdf-data-formats#' || 'sql-' || replace (coltype, ' ', '') || '-fixedlang-x-any' ;
  res_baseid := 'http://www.openlinksw.com/virtrdf-data-formats#' || 'sql-' || replace (coltype, ' ', '') || '-fixedlang-' || o_lang ;
  src_lname := 'sql-' || replace (coltype, ' ', '') || '-fixedlang-x-any' || case when is_nullable then '-nullable' else '' end;
  res_lname := 'sql-' || replace (coltype, ' ', '') || '-fixedlang-' || o_lang || case when is_nullable then '-nullable' else '' end ;
  src_fmtid := 'http://www.openlinksw.com/virtrdf-data-formats#' || src_lname;
  res_fmtid := 'http://www.openlinksw.com/virtrdf-data-formats#' || res_lname;
  superformatsid := res_fmtid || '--SuperFormats';
  if (exists (sparql define input:storage ""
      prefix rdfdf: <http://www.openlinksw.com/virtrdf-data-formats#>
      prefix virtrdf: <http://www.openlinksw.com/schemas/virtrdf#>
      ask where { graph virtrdf: { `iri(?:res_fmtid)` a virtrdf:QuadMapFormat } } ) )
    {
      -- dbg_obj_princ ('DB.DBA.RDF_QM_DEFINE_LITERAL_CLASS_WITH_FIXED_LANG (', coltype, o_lang, is_nullable, ') exists');
      return res_fmtid;
    }
  if (not exists (sparql define input:storage ""
      prefix rdfdf: <http://www.openlinksw.com/virtrdf-data-formats#>
      prefix virtrdf: <http://www.openlinksw.com/schemas/virtrdf#>
      ask where { graph virtrdf: { `iri(?:src_fmtid)` a virtrdf:QuadMapFormat } } ) )
    {
      -- dbg_obj_princ ('DB.DBA.RDF_QM_DEFINE_LITERAL_CLASS_WITH_FIXED_LANG (', coltype, o_lang, is_nullable, '): ', src_fmtid, 'does not exist');
      signal ('22023', 'Unable to find appropriate quad map format to make its analog for a fixed language');
    }
  -- dbg_obj_princ ('DB.DBA.RDF_QM_DEFINE_LITERAL_CLASS_WITH_FIXED_LANG (', coltype, o_lang, is_nullable, '): will make ', res_fmtid, ' from ', src_fmtid);
  o_lang_str := WS.WS.STR_SQL_APOS (o_lang);
  sparql define input:storage ""
  prefix rdfdf: <http://www.openlinksw.com/virtrdf-data-formats#>
  prefix virtrdf: <http://www.openlinksw.com/schemas/virtrdf#>
  prefix xsd: <http://www.w3.org/2001/XMLSchema#>
  with virtrdf:
  delete { `iri(?:res_fmtid)` ?p ?o }
  where  { `iri(?:res_fmtid)` ?p ?o };
  sparql define input:storage ""
  prefix rdfdf: <http://www.openlinksw.com/virtrdf-data-formats#>
  prefix virtrdf: <http://www.openlinksw.com/schemas/virtrdf#>
  prefix xsd: <http://www.w3.org/2001/XMLSchema#>
  with virtrdf:
  delete { `iri(?:superformatsid)` ?p ?o }
  where  { `iri(?:superformatsid)` ?p ?o };
  sparql define input:storage ""
  prefix rdfdf: <http://www.openlinksw.com/virtrdf-data-formats#>
  prefix virtrdf: <http://www.openlinksw.com/schemas/virtrdf#>
  prefix xsd: <http://www.w3.org/2001/XMLSchema#>
  insert in virtrdf:
    {
      `iri(?:res_fmtid)` ?p
            `if (isref (?o) || isnumeric (?o) || datatype(?o) != xsd:string,
                if (isref (?o) && (?o = iri(?:src_baseid)), iri(?:res_baseid), ?o),
                bif:replace (?o, "'x-any'", ?:o_lang_str) ) ` ;
        virtrdf:qmfSuperFormats `iri(?:superformatsid)` .
      `iri(?:superformatsid)`
        rdf:type virtrdf:array-of-QuadMapFormat ;
        rdf:_1 `iri(?:nullablesuperformatid)` .
    }
  from virtrdf:
  where
    {
      `iri(?:src_fmtid)` ?p ?o .
      filter (?p != virtrdf:qmfSuperFormats ) };
  commit work;
  return res_fmtid;
}
;

--!AWK PUBLIC
create function DB.DBA.RDF_BAD_CLASS_INV_FUNCTION (inout val any) returns any
{
  return NULL;
}
;

--!AWK PUBLIC
create function DB.DBA.SQLNAME_NOTATION_TO_NAME (in str varchar) returns varchar
{
  if ('' = str)
    return NULL;
  if (34 = str[0])
    return subseq (str, 1, length (str) - 1);
  return fix_identifier_case (str);
}
;

--!AWK PUBLIC
create function DB.DBA.SQLQNAME_NOTATION_TO_QNAME (in str varchar, in expected_part_count integer) returns varchar
{
  declare part_ctr, dot_pos integer;
  declare name, res varchar;
  res := '';
  part_ctr := 1;
next_dot:
  dot_pos := strchr (str, '.');
  if (dot_pos is not null)
    {
      if (0 = dot_pos)
        {
          if (2 = part_ctr)
            res := res || USER || '.';
          else
            return NULL;
        }
      else
        {
          name := DB.DBA.SQLNAME_NOTATION_TO_NAME(subseq (str, 0, dot_pos));
          if (name is null)
            return NULL;
          res := res || name  || '.';
        }
      str := subseq (str, dot_pos + 1);
      part_ctr := part_ctr + 1;
      goto next_dot;
    }
  if (expected_part_count <> part_ctr)
    return NULL;
  name := DB.DBA.SQLNAME_NOTATION_TO_NAME (str);
  if (name is null)
    return NULL;
  return res || name;
}
;

create procedure DB.DBA.RDF_QM_CHECK_CLASS_FUNCTION_HEADERS (inout fheaders any, in is_iri_decl integer, in only_one_arg integer, in pdesc varchar, in invdesc varchar, in bij integer, in deref integer)
{
  declare uriprint any;
  declare uriprintname varchar;
  declare argctr, argcount integer;
  uriprint := fheaders[0];
  uriprintname := uriprint[0];
  argcount := length (uriprint[1]);
  if (only_one_arg and (1 <> length (uriprint[1])))
    signal ('22023', pdesc || ' function "' || uriprintname || '" should have exactly one argument');
  if (1 = length (fheaders))
    {
      if (bij or deref)
        {
          if (0 = argcount)
            signal ('22023',
              sprintf ('%s function "%s" can not be used in a class with OPTION (BIJECTION) or OPTION (DEREF), because it has no arguments.',
                pdesc, uriprintname ) );
          signal ('22023',
            sprintf ('%s function "%s" can not be used in a class with OPTION (BIJECTION) or OPTION (DEREF) without related %d inverse functions',
              pdesc, uriprintname, argcount ) );
        }
    }
  if (is_iri_decl and (uriprint[2] <> 'varchar'))
    signal ('22023', pdesc || ' function "' || uriprintname || '" should return varchar, not ' || uriprint[2]);
  foreach (any arg in uriprint[1]) do
    if (UNAME'in' <> arg[0])
      signal ('22023', 'Only "in" parameters are now supported in argument lists of ' || pdesc || ' functions, not "' || arg[0] || '"');
  if (argcount <> (length (fheaders) - 1))
    {
      if ((1 <> length (fheaders)) or (0 = argcount))
        signal ('22023',
          sprintf ('%s function "%s" has %d arguments but %d inverse functions',
            pdesc, uriprintname, argcount, (length (fheaders) - 1)
            ) );
      declare inv any;
      inv := vector ('DB.DBA.RDF_BAD_CLASS_INV_FUNCTION', vector (vector ('in', 'val', 'any', 0)), 'any', 0);
      fheaders := make_array (1 + argcount, 'any');
      fheaders[0] := uriprint;
      for (argctr := 0; argctr < argcount; argctr := argctr + 1)
        {
          inv[2] := uriprint[1][argctr][2];
          fheaders[argctr+1] := inv;
        }
    }
  else if (1 = argcount)
    {
      declare uriparsename varchar;
      if (uriprintname like '%"')
        uriparsename := subseq (uriprintname, 0, length (uriprintname)-1) || '_INVERSE"';
      else
        uriparsename := uriprintname || '_INVERSE';
      if (fheaders[1][0] <> uriparsename)
        signal ('22023', 'Name of ' || invdesc || ' function should be ' || uriparsename || ', not ' || fheaders[1][0] || ', other variants are not supported by the current version' );
    }
  else
    {
      for (argctr := 0; argctr < argcount; argctr := argctr + 1)
        {
          declare uriparsename varchar;
          if (uriprintname like '%"')
            uriparsename := sprintf ('%s_INV_%d"', subseq (uriprintname, 0, length (uriprintname)-1), argctr+1);
          else
            uriparsename := sprintf ('%s_INV_%d', uriprintname, argctr+1);
          if (fheaders[argctr + 1][0] <> uriparsename)
            signal ('22023', 'Name of inverse function should be ' || uriparsename || ', not ' || fheaders[argctr + 1][0] || ', other variants are not supported by the current version' );
        }
    }
  for (argctr := 0; argctr < argcount; argctr := argctr + 1)
    {
      declare uriparse any;
      uriparse := fheaders [argctr + 1];
      if (1 <> length (uriparse[1]))
        signal ('22023', invdesc || ' function ' || uriparse[0] || ' should have only one argument');
      if (UNAME'in' <> uriparse[1][0][0])
        signal ('22023', 'Only "in" parameters are now supported in argument lists of ' || invdesc || ' functions, not "' || uriparse[1][0][0] || '"');
      if ((uriparse[1][0][2] <> uriprint[2]) and (uriparse[1][0][2] <> 'any'))
        signal ('22023', invdesc || ' function "' || uriparse[0] || '" should have argument of type ' || uriprint[2] || ', not ' || uriparse[1][0][2]);
      if ((uriparse[2] <> uriprint[1][argctr][2]) and (uriprint[1][argctr][2] <> 'any'))
        signal ('22023', 'The return value of "' || uriparse[0] || '" and the argument #' || cast (argctr+1 as varchar) || ' of "' || uriprintname || '" should be of the same data type');
      if (coalesce (uriparse[1][0][3], 0))
        signal ('22023', invdesc || ' function ' || uriparse[0] || ' should have nullable argument');
    }
}
;

create function DB.DBA.RDF_QM_DEFINE_SUBCLASS (in subclassiri varchar, in superclassiri varchar) returns any
{
  DB.DBA.RDF_QM_ASSERT_JSO_TYPE (subclassiri, 'http://www.openlinksw.com/schemas/virtrdf#QuadMapFormat');
  DB.DBA.RDF_QM_ASSERT_JSO_TYPE (superclassiri, 'http://www.openlinksw.com/schemas/virtrdf#QuadMapFormat');
  sparql define input:storage ""
  prefix rdfdf: <http://www.openlinksw.com/virtrdf-data-formats#>
  insert in graph <http://www.openlinksw.com/schemas/virtrdf#> {
      `iri(?:subclassiri)` virtrdf:isSubclassOf `iri(?:superclassiri)` };
  commit work;
  return vector (vector ('00000', 'IRI class <' || subclassiri || '> is now known as a subclass of <' || superclassiri || '>'));
}
;

create function DB.DBA.RDF_QM_DROP_CLASS (in classiri varchar, in silent integer := 0) returns any
{
  declare graphiri varchar;
  graphiri := DB.DBA.JSO_SYS_GRAPH ();
  if (silent and not exists ((sparql define input:storage ""
    prefix rdfdf: <http://www.openlinksw.com/virtrdf-data-formats#>
    select (1) where {
        graph ?:graphiri {
            `iri(?:classiri)` a ?t } } ) ) )
    return vector (vector ('00000', 'Class <' || classiri || '> does not exist, the DROP statement is ignored due to SILENT option'));
  if (DB.DBA.RDF_QM_ASSERT_JSO_TYPE (classiri, 'http://www.openlinksw.com/schemas/virtrdf#QuadMapFormat', 1))
    {
      declare side_s IRI_ID;
      side_s := DB.DBA.RDF_QM_GC_SUBTREE (classiri, 2);
      if (side_s is not null)
        signal ('22023', 'Can not drop class <' || classiri || '> because it is used by other quad map objects, e.g., <' || id_to_iri_nosignal (side_s) || '>');
    }
  commit work;
  return vector (vector ('00000', 'Previous definition of class <' || classiri || '> has been dropped'));
}
;

create function DB.DBA.RDF_QM_DROP_QUAD_STORAGE (in storage varchar, in silent integer := 0) returns any
{
  declare graphiri varchar;
  graphiri := DB.DBA.JSO_SYS_GRAPH ();
  if (silent and not exists ((sparql define input:storage ""
    prefix rdfdf: <http://www.openlinksw.com/virtrdf-data-formats#>
    select (1) where {
        graph ?:graphiri {
            `iri(?:storage)` a ?t } } ) ) )
    return vector (vector ('00000', 'Quad storage <' || storage || '> does not exist, the DROP statement is ignored due to SILENT option'));
  DB.DBA.RDF_QM_ASSERT_STORAGE_FLAG (storage, 0);
  DB.DBA.RDF_QM_GC_SUBTREE (storage);
  sparql define input:storage ""
  prefix rdfdf: <http://www.openlinksw.com/virtrdf-data-formats#>
  delete from graph <http://www.openlinksw.com/schemas/virtrdf#> {
      `iri(?:storage)` ?p ?o
    }
  where { graph ?:graphiri { `iri(?:storage)` ?p ?o } };
  commit work;
  return vector (vector ('00000', 'Quad storage <' || storage || '> is removed from the quad mapping schema'));
}
;

create function DB.DBA.RDF_QM_DEFINE_QUAD_STORAGE (in storage varchar) returns any
{
  declare graphiri, qsusermaps varchar;
  graphiri := DB.DBA.JSO_SYS_GRAPH ();
  DB.DBA.RDF_QM_ASSERT_JSO_TYPE (storage, NULL);
  qsusermaps := storage || '--UserMaps';
  sparql define input:storage ""
  prefix rdfdf: <http://www.openlinksw.com/virtrdf-data-formats#>
  delete from graph <http://www.openlinksw.com/schemas/virtrdf#> { ?s ?p ?o }
  from <http://www.openlinksw.com/schemas/virtrdf#>
  where { ?s ?p ?o . filter (?s = iri(?:storage)) };
  sparql define input:storage ""
  prefix rdfdf: <http://www.openlinksw.com/virtrdf-data-formats#>
  delete from graph <http://www.openlinksw.com/schemas/virtrdf#> { ?s ?p ?o }
  from <http://www.openlinksw.com/schemas/virtrdf#>
  where { ?s ?p ?o . filter (?s = iri(?:qsusermaps)) };
  sparql define input:storage ""
  prefix rdfdf: <http://www.openlinksw.com/virtrdf-data-formats#>
  insert in graph <http://www.openlinksw.com/schemas/virtrdf#> {
      `iri(?:storage)`
        rdf:type virtrdf:QuadStorage ;
        virtrdf:qsUserMaps `iri(?:qsusermaps)` .
      `iri(?:qsusermaps)`
        rdf:type virtrdf:array-of-QuadMap };
  commit work;
  return vector (vector ('00000', 'A new empty quad storage <' || storage || '> is added to the quad mapping schema'));
}
;

create function DB.DBA.RDF_QM_BEGIN_ALTER_QUAD_STORAGE (in storage varchar) returns any
{
  declare graphiri varchar;
  graphiri := DB.DBA.JSO_SYS_GRAPH ();
  DB.DBA.RDF_QM_ASSERT_STORAGE_FLAG (storage, 0);
  sparql define input:storage ""
  prefix rdfdf: <http://www.openlinksw.com/virtrdf-data-formats#>
  insert in graph <http://www.openlinksw.com/schemas/virtrdf#> {
      `iri(?:storage)` virtrdf:qsAlterInProgress `bif:now NIL` };
  commit work;
  return vector (vector ('00000', 'Quad storage <' || storage || '> is flagged as being edited'));
}
;

create function DB.DBA.RDF_QM_END_ALTER_QUAD_STORAGE (in storage varchar) returns any
{
  declare graphiri varchar;
  graphiri := DB.DBA.JSO_SYS_GRAPH ();
  DB.DBA.RDF_QM_ASSERT_JSO_TYPE (storage, 'http://www.openlinksw.com/schemas/virtrdf#QuadStorage');
  sparql define input:storage ""
  prefix rdfdf: <http://www.openlinksw.com/virtrdf-data-formats#>
  delete from graph <http://www.openlinksw.com/schemas/virtrdf#> {
      `iri(?:storage)` virtrdf:qsAlterInProgress ?dtstart }
  where { graph ?:graphiri {
          `iri(?:storage)` virtrdf:qsAlterInProgress ?dtstart } };
  commit work;
  return vector (vector ('00000', 'Quad storage <' || storage || '> is unflagged and can be edited by other transactions'));
}
;

create function DB.DBA.RDF_QM_STORE_ATABLES (in qmvid varchar, in atablesid varchar, inout atables any)
{
  declare atablectr, atablecount integer;
  atablecount := length (atables);
  for (atablectr := 0; atablectr < atablecount; atablectr := atablectr + 1)
    {
      declare pair any;
      declare qtable, alias, inner_id varchar;
      pair := atables [atablectr];
      alias := pair[0];
      qtable := pair[1];
      if (starts_with (qtable, '/*[sqlquery[*/'))
        {
          qtable := '(' || qtable || ')';
          inner_id := qmvid || '-atable-' || alias || '-sql-query';
        }
      else
        inner_id := qmvid || '-atable-' || alias || '-' || qtable;
      sparql define input:storage ""
      prefix rdfdf: <http://www.openlinksw.com/virtrdf-data-formats#>
      delete from graph <http://www.openlinksw.com/schemas/virtrdf#> { ?s ?p ?o }
      from <http://www.openlinksw.com/schemas/virtrdf#>
      where { ?s ?p ?o . filter (?s = iri(?:inner_id)) };
      sparql define input:storage ""
      prefix rdfdf: <http://www.openlinksw.com/virtrdf-data-formats#>
      insert in graph <http://www.openlinksw.com/schemas/virtrdf#> {
          `iri(?:atablesid)`
            `iri (bif:sprintf ("%s%d", str (rdf:_), ?:atablectr+1))` `iri(?:inner_id)` .
          `iri(?:inner_id)`
            rdf:type virtrdf:QuadMapATable ;
            virtrdf:qmvaAlias ?:alias ;
            virtrdf:qmvaTableName ?:qtable };
    }
}
;

create function DB.DBA.RDF_QM_FT_USAGE (in ft_type varchar, in ft_alias varchar, in ft_aliased_col any, in sqlcols any, in conds any, in options any := null)
{
  declare ft_tbl, ft_col, ftid, ftcondsid varchar;
  declare condctr, condcount, ft_isxml integer;
  ft_tbl := ft_aliased_col[0];
  ft_col := ft_aliased_col[2];
  ft_isxml := case (isnull (ft_type)) when 0 then 1 else null end;
  if (ft_alias <> ft_aliased_col[1])
    signal ('22023', sprintf ('"TEXT LITERAL %I.%I" should be at the end of "FROM ... AS %I" declaration', ft_aliased_col[1], ft_aliased_col, ft_alias));
  condcount := length (conds);
  ftid := 'sys:ft-' || md5 (serialize (vector (ft_alias, ft_tbl, ft_col, conds, options)));
  if (condcount > 0)
    ftcondsid := ftid || '-conds';
  else
    ftcondsid := NULL;
/* Trick to avoid repeating re-declarations */
  if (exists (sparql define input:storage ""
    prefix rdfdf: <http://www.openlinksw.com/virtrdf-data-formats#>
    ask where {
        graph <http://www.openlinksw.com/schemas/virtrdf#> {
          ?:ftid
            rdf:type virtrdf:QuadMapFText ;
            virtrdf:qmvftAlias ?:ft_alias ;
            virtrdf:qmvftTableName ?:ft_tbl ;
            virtrdf:qmvftColumnName ?:ft_col ;
            virtrdf:qmvftConds `iri(?:ftcondsid)` } } ) )
    return ftid;
  if (ftcondsid is not null)
    DB.DBA.RDF_QM_GC_SUBTREE (ftcondsid);
  sparql define input:storage ""
  prefix rdfdf: <http://www.openlinksw.com/virtrdf-data-formats#>
  delete from graph <http://www.openlinksw.com/schemas/virtrdf#> { ?s ?p ?o }
  from <http://www.openlinksw.com/schemas/virtrdf#>
  where { ?s ?p ?o . filter (?s = iri(?:ftid)) };
  sparql define input:storage ""
  prefix rdfdf: <http://www.openlinksw.com/virtrdf-data-formats#>
  delete from graph <http://www.openlinksw.com/schemas/virtrdf#> { ?s ?p ?o }
  from <http://www.openlinksw.com/schemas/virtrdf#>
  where { ?s ?p ?o . filter (?s = iri(?:ftcondsid)) };
  sparql define input:storage ""
  prefix rdfdf: <http://www.openlinksw.com/virtrdf-data-formats#>
  insert in graph <http://www.openlinksw.com/schemas/virtrdf#> {
      `iri(?:ftid)`
        rdf:type virtrdf:QuadMapFText ;
        virtrdf:qmvftAlias ?:ft_alias ;
        virtrdf:qmvftTableName ?:ft_tbl ;
        virtrdf:qmvftColumnName ?:ft_col ;
        virtrdf:qmvftXmlIndex ?:ft_isxml ;
        virtrdf:qmvftConds `iri(?:ftcondsid)` .
      `iri(?:ftcondsid)`
        rdf:type virtrdf:array-of-string };
  for (condctr := 0; condctr < condcount; condctr := condctr + 1)
    {
      declare sqlcond varchar;
      sqlcond := conds [condctr];
      sparql define input:storage ""
      prefix rdfdf: <http://www.openlinksw.com/virtrdf-data-formats#>
      insert in graph <http://www.openlinksw.com/schemas/virtrdf#> {
          `iri(?:ftcondsid)`
            `iri (bif:sprintf ("%s%d", str (rdf:_), ?:condctr+1))` ?:sqlcond };
    }
  return ftid;
}
;

create function DB.DBA.RDF_QM_CHECK_COLUMNS_FORM_KEY (in sqlcols any) returns integer
{
  declare alias, tbl varchar;
  declare colctr, colcount integer;
  colcount := length (sqlcols);
  if (0 = colcount)
    return 0;
  tbl := sqlcols[0][0];
  alias := sqlcols[0][1];
  for (colctr := 1; colctr < colcount; colctr := colctr + 1)
    {
      if ((sqlcols[colctr][0] <> tbl) or (sqlcols[colctr][1] <> alias))
        return 0;
    }
  for (select KEY_ID, KEY_N_SIGNIFICANT from DB.DBA.SYS_KEYS where (KEY_TABLE = tbl) and KEY_IS_UNIQUE) do
    {
      declare keycolnames any;
      if (KEY_N_SIGNIFICANT > colcount)
        goto no_match;
      for (select "COLUMN" as COL
        from DB.DBA.SYS_KEY_PARTS join DB.DBA.SYS_COLS on (KP_COL = COL_ID)
        where KP_KEY_ID = KEY_ID and KP_NTH < KEY_N_SIGNIFICANT ) do
        {
          for (colctr := 0; colctr < colcount; colctr := colctr + 1)
            {
              if (sqlcols[colctr][2] = COL)
                goto col_ok;
            }
          goto no_match;
col_ok: ;
        }
      return 1;

no_match: ;
    }
  return 0;
}
;

registry_set ('DB.DBA.RDF_QM_PEDANTIC_GC', '')
;

create function DB.DBA.RDF_QM_DEFINE_MAP_VALUE (in qmv any, in fldname varchar, inout tablename varchar, in o_dt any := null, in o_lang any := null) returns varchar
{
/* iqi qmv: vector ( UNAME'http://www.openlinksw.com/schemas/oplsioc#user_iri' ,
    vector ( vector ('alias1', 'DB.DBA.SYS_USERS')),
   vector ( vector ('DB.DBA.SYS_USERS', 'alias1', 'U_ID') ),
   vector ('^{alias1.}^.U+IS_ROLE = 0'),
   NULL
 ) */
  declare atables, sqlcols, conds, items_for_pedantic_gc any;
  declare ftextid varchar;
  declare qry_metas any;
  declare atablectr, atablecount integer;
  declare colctr, colcount, fmtcolcount integer;
  declare condctr, condcount integer;
  declare columnsformkey integer;
  declare fmtid, iriclassid, qmvid, qmvatablesid, qmvcolsid, qmvcondsid varchar;
  -- dbg_obj_princ ('DB.DBA.RDF_QM_DEFINE_MAP_VALUE (', qmv, fldname, tablename, ')');
  fmtid := qmv[0];
  atables := qmv[1];
  sqlcols := qmv[2];
  conds := qmv[3];
  ftextid := qmv[4];
  qry_metas := null;
  atablecount := length (atables);
  colcount := length (sqlcols);
  condcount := length (conds);
  items_for_pedantic_gc := NULL;
  if (fmtid <> UNAME'literal')
    {
      DB.DBA.RDF_QM_ASSERT_JSO_TYPE (fmtid, 'http://www.openlinksw.com/schemas/virtrdf#QuadMapFormat');
      if (o_dt is not null)
        signal ('22023', 'Only default literal class can have DATATYPE clause in the mapping, <' || fmtid || '> can not');
      if (o_lang is not null)
        signal ('22023', 'Only default literal class can have LANG clause in the mapping, <' || fmtid || '> can not');
      fmtcolcount := ((sparql define input:storage ""
          select ?cc from <http://www.openlinksw.com/schemas/virtrdf#>
          where { `iri(?:fmtid)` virtrdf:qmfColumnCount ?cc } ) );
      if (fmtcolcount <> colcount)
        signal ('22023', 'Number of columns of quad map value does not match number of arguments of format <' || fmtid || '>');
    }
  for (colctr := 0; colctr < colcount; colctr := colctr + 1)
    {
      declare sqlcol any;
      declare alias_msg_txt, final_tblname, final_colname varchar;
      sqlcol := sqlcols [colctr];
      final_colname := DB.DBA.SQLNAME_NOTATION_TO_NAME (sqlcol[2]);
      if (sqlcol[1] is not null)
        alias_msg_txt := ' (alias ' || sqlcol[1] || ')';
      else
        alias_msg_txt := ' (without alias)';
      if (starts_with (sqlcol[0], '/*[sqlquery[*/'))
        {
          declare qry varchar;
          declare qry_colcount, qry_colctr integer;
          declare qry_mdata any;
          qry := sqlcol[0];
          if (qry_metas is null)
            qry_metas := dict_new (5);
          qry_mdata := dict_get (qry_metas, qry, null);
          if (qry_mdata is null)
            {
              declare stat, msg varchar;
              declare exec_metas any;
              stat := '00000';
              exec_metadata (sqlcol[0], stat, msg, exec_metas);
              if (stat <> '00000')
                signal ('22023', 'The compilation of SQLQUERY' || alias_msg_txt || ' results in Error ' || stat || ': ' || msg);
              if (exec_metas[1] <> 1)
                signal ('R2RML', 'Dangerous DML in SQLQUERY' || alias_msg_txt);
              exec_metas := exec_metas[0];
              qry_colcount := length (exec_metas);
              qry_mdata := make_array (qry_colcount*2, 'any');
              for (qry_colctr := 0; qry_colctr < qry_colcount; qry_colctr := qry_colctr + 1)
                {
                  qry_mdata[qry_colctr*2] := exec_metas[qry_colctr][0];
                  qry_mdata[qry_colctr*2+1] := exec_metas[qry_colctr];
                }
              dict_put (qry_metas, qry, qry_mdata);
              -- dbg_obj_princ ('DB.DBA.RDF_QM_DEFINE_MAP_VALUE(): storing metadata ', qry_mdata, ' for ', qry);
            }
          -- dbg_obj_princ ('DB.DBA.RDF_QM_DEFINE_MAP_VALUE(): final_colname = ', final_colname);
          if (get_keyword (final_colname, qry_mdata) is null)
            signal ('22023', 'The result of SQLQUERY' || alias_msg_txt || ' does not contain column ' || sqlcol[2] || ', please check spelling and character case');
        }
      else
        {
          final_tblname := DB.DBA.SQLQNAME_NOTATION_TO_QNAME (sqlcol[0], 3);
          if (not exists (select top 1 1 from DB.DBA.TABLE_COLS where "TABLE" = final_tblname))
            signal ('22023', 'No table ' || sqlcol[0] || alias_msg_txt || ' in database, please check spelling and character case');
          if (not exists (select top 1 1 from DB.DBA.TABLE_COLS where "TABLE" = final_tblname and "COLUMN" = final_colname))
            signal ('22023', 'No column ' || sqlcol[2] || ' in table ' || sqlcol[0] || alias_msg_txt || ' in database, please check spelling and character case');
        }
      if (tablename is null)
        tablename := sqlcol[0];
      else if (tablename <> sqlcol[0])
        tablename := '';
    }
  if (tablename is null)
    tablename := '';
  if (fmtid = UNAME'literal')
    {
      declare sqlcol any;
      declare final_tblname, final_colname varchar;
      declare coldtp, colnullable integer;
      declare coltype varchar;
      sqlcol := sqlcols [0];
      final_colname := DB.DBA.SQLNAME_NOTATION_TO_NAME (sqlcol[2]);
      if (starts_with (sqlcol[0], '/*[sqlquery[*/'))
        {
          declare col_mdata any;
          col_mdata := get_keyword (final_colname, dict_get (qry_metas, sqlcol[0], null));
          coldtp := col_mdata[1];
          colnullable := col_mdata[4];
        }
      else
        {
          final_tblname := DB.DBA.SQLQNAME_NOTATION_TO_QNAME (sqlcol[0], 3);
          select COL_DTP, coalesce (COL_NULLABLE, 1) into coldtp, colnullable
          from DB.DBA.TABLE_COLS where "TABLE" = final_tblname and "COLUMN" = final_colname;
        }
      coltype := case (coldtp)
        when __tag of long varchar then 'longvarchar'
        when __tag of timestamp then 'datetime' -- timestamp
        when __tag of date then 'date'
        when __tag of time then 'time'
        when __tag of long varbinary then 'longvarbinary'
        when __tag of varbinary then 'longvarbinary'
        when __tag of integer then 'integer'
        when __tag of smallint then 'integer'
        when __tag of smallint then 'integer'
        when __tag of varchar then 'varchar'
        when __tag of real then 'double precision' -- actually single precision float
        when __tag of double precision then 'double precision'
        when 192 then 'varchar' -- actually character
        when __tag of datetime then 'datetime'
        when __tag of numeric then 'numeric'
        when __tag of nvarchar then 'nvarchar'
        when __tag of long nvarchar then 'longnvarchar'
        when __tag of bigint then 'integer'
        else NULL end;
      if (coltype is null)
        signal ('22023', 'The datatype of column "' || sqlcols[0][2] ||
          '" of table "' || sqlcols[0][0] || '" (COL_DTP=' || cast (coldtp as varchar) ||
          ') can not be mapped to an RDF literal in current version of Virtuoso' );
      if (o_lang is not null and not (coltype in ('varchar', 'long varchar', 'nvarchar', 'long nvarchar')))
        signal ('22023', 'The datatype of column "' || sqlcols[0][2] ||
          '" of table "' || sqlcols[0][0] || '" (COL_DTP=' || cast (coldtp as varchar) ||
          ') conflicts with LANG clause, only strings may have language' );
      if (o_dt is not null and not (coltype in ('varchar', 'long varchar', 'nvarchar', 'long nvarchar')))
        signal ('22023', 'Current version of Virtuoso does not support DATATYPE clause for columns other than varchar/nvarchar; the column "' || sqlcols[0][2] ||
          '" of table "' || sqlcols[0][0] || '" has COL_DTP=' || cast (coldtp as varchar) );
      fmtid := 'http://www.openlinksw.com/virtrdf-data-formats#sql-' || replace (coltype, ' ', '');
      if (o_dt is not null)
        {
          if (__tag (o_dt) = __tag of vector)
            {
              if (o_dt[1] <> sqlcols[0][1])
                signal ('22023', 'The alias in DATATYPE clause and the alias in object column should be the same');
              fmtid := fmtid || '-dt';
              sqlcols := vector_concat (sqlcols, vector (o_dt));
              colcount := colcount + 1;
            }
          else
            fmtid := DB.DBA.RDF_QM_DEFINE_LITERAL_CLASS_WITH_FIXED_DT (coltype, o_dt);
        }
      if (o_lang is not null)
        {
          if (__tag (o_lang) = __tag of vector)
            {
              if (o_lang[1] <> sqlcols[0][1])
                signal ('22023', 'The alias in LANG clause and the alias in object column should be the same');
              fmtid := fmtid || '-lang';
              sqlcols := vector_concat (sqlcols, vector (o_lang));
              colcount := colcount + 1;
            }
          else
            fmtid := DB.DBA.RDF_QM_DEFINE_LITERAL_CLASS_WITH_FIXED_LANG (coltype, o_lang);
        }
      if (colnullable)
        fmtid := fmtid || '-nullable';
      iriclassid := null;
    }
  else
    {
      if (exists (sparql define input:storage ""
          ask where {
              graph <http://www.openlinksw.com/schemas/virtrdf#> { `iri (?:fmtid)` virtrdf:qmfValRange-rvrRestrictions virtrdf:SPART_VARR_IS_REF } } ) )
        iriclassid := fmtid;
      else
        iriclassid := null;
    }
  qmvid := 'sys:qmv-' || md5 (serialize (vector (fmtid, sqlcols)));
  qmvatablesid := qmvid || '-atables';
  qmvcolsid := qmvid || '-cols';
  qmvcondsid := qmvid || '-conds';
/* Trick to avoid repeating re-declarations */
  if (exists (sparql define input:storage ""
    prefix rdfdf: <http://www.openlinksw.com/virtrdf-data-formats#>
    ask where {
        graph <http://www.openlinksw.com/schemas/virtrdf#> {
          ?:qmvid
            rdf:type virtrdf:QuadMapValue ;
            virtrdf:qmvATables `iri(?:qmvatablesid)` ;
            virtrdf:qmvColumns `iri(?:qmvcolsid)` ;
            virtrdf:qmvConds `iri(?:qmvcondsid)` ;
            virtrdf:qmvFormat `iri(?:fmtid)` . } } ) )
    return qmvid;
/* Create everything if qmv has not been found */
  if (registry_get ('DB.DBA.RDF_QM_PEDANTIC_GC') <> '')
    {
      vectorbld_init (items_for_pedantic_gc);
      for (sparql define input:storage ""
        prefix rdfdf: <http://www.openlinksw.com/virtrdf-data-formats#>
        select ?atable where { graph <http://www.openlinksw.com/schemas/virtrdf#> {
                `iri(?:qmvatablesid)` ?p ?atable . filter (?p != rdf:type) } } ) do {
          vectorbld_acc (items_for_pedantic_gc, "atable");
        }
      for (sparql define input:storage ""
        prefix rdfdf: <http://www.openlinksw.com/virtrdf-data-formats#>
        select ?col where { graph <http://www.openlinksw.com/schemas/virtrdf#> {
                `iri(?:qmvcolsid)` ?p ?col . filter (?p != rdf:type) } } ) do {
          vectorbld_acc (items_for_pedantic_gc, "col");
        }
      vectorbld_final (items_for_pedantic_gc);
    }
  sparql define input:storage ""
  prefix rdfdf: <http://www.openlinksw.com/virtrdf-data-formats#>
  delete from graph <http://www.openlinksw.com/schemas/virtrdf#> {
    `iri(?:qmvid)` ?p ?o . }
  where { graph <http://www.openlinksw.com/schemas/virtrdf#> {
          `iri(?:qmvid)` ?p ?o .
        } };
  sparql define input:storage ""
  prefix rdfdf: <http://www.openlinksw.com/virtrdf-data-formats#>
  delete from graph <http://www.openlinksw.com/schemas/virtrdf#> {
    `iri(?:qmvatablesid)` ?p ?o }
  where { graph <http://www.openlinksw.com/schemas/virtrdf#> {
          `iri(?:qmvatablesid)` ?p ?o .
        } };
  sparql define input:storage ""
  prefix rdfdf: <http://www.openlinksw.com/virtrdf-data-formats#>
  delete from graph <http://www.openlinksw.com/schemas/virtrdf#> {
    `iri(?:qmvcolsid)` ?p ?o }
  where { graph <http://www.openlinksw.com/schemas/virtrdf#> {
          `iri(?:qmvcolsid)` ?p ?o .
        } };
  sparql define input:storage ""
  prefix rdfdf: <http://www.openlinksw.com/virtrdf-data-formats#>
  delete from graph <http://www.openlinksw.com/schemas/virtrdf#> {
    `iri(?:qmvcondsid)` ?p ?o }
  where { graph <http://www.openlinksw.com/schemas/virtrdf#> {
          `iri(?:qmvcondsid)` ?p ?o .
        } };
  if (items_for_pedantic_gc is not null)
    {
      foreach (any i in items_for_pedantic_gc) do
        {
          DB.DBA.RDF_QM_GC_SUBTREE (i);
        }
    }
  if (0 = atablecount)
    qmvatablesid := NULL;
  if (0 = condcount)
    qmvcondsid := NULL;
  columnsformkey := DB.DBA.RDF_QM_CHECK_COLUMNS_FORM_KEY (sqlcols);
  sparql define input:storage ""
  prefix rdfdf: <http://www.openlinksw.com/virtrdf-data-formats#>
  insert in graph <http://www.openlinksw.com/schemas/virtrdf#> {
      `iri(?:qmvid)`
        rdf:type virtrdf:QuadMapValue ;
        virtrdf:qmvTableName ?:tablename ;
        virtrdf:qmvATables `iri(?:qmvatablesid)` ;
        virtrdf:qmvColumns `iri(?:qmvcolsid)` ;
        virtrdf:qmvConds `iri(?:qmvcondsid)` ;
        virtrdf:qmvFormat `iri(?:fmtid)` ;
        virtrdf:qmvFText `iri(?:ftextid)` ;
        virtrdf:qmvIriClass `iri(?:iriclassid)` ;
        virtrdf:qmvColumnsFormKey ?:columnsformkey .
      `iri(?:qmvatablesid)`
        rdf:type virtrdf:array-of-QuadMapATable .
      `iri(?:qmvcolsid)`
        rdf:type virtrdf:array-of-QuadMapColumn .
      `iri(?:qmvcondsid)`
        rdf:type virtrdf:array-of-string };
  DB.DBA.RDF_QM_STORE_ATABLES (qmvid, qmvatablesid, atables);
  for (colctr := 0; colctr < colcount; colctr := colctr + 1)
    {
      declare sqlcol any;
      declare qtable, alias, colname, inner_id varchar;
      sqlcol := sqlcols [colctr];
      alias := sqlcol[1];
      colname := sqlcol[2];
      inner_id := qmvid || '-col-' || alias || '-' || colname;
      sparql define input:storage ""
      prefix rdfdf: <http://www.openlinksw.com/virtrdf-data-formats#>
      delete from graph <http://www.openlinksw.com/schemas/virtrdf#> { ?s ?p ?o }
      from <http://www.openlinksw.com/schemas/virtrdf#>
      where { ?s ?p ?o . filter (?s = iri(?:inner_id)) };
      sparql define input:storage ""
      prefix rdfdf: <http://www.openlinksw.com/virtrdf-data-formats#>
      insert in graph <http://www.openlinksw.com/schemas/virtrdf#> {
          `iri(?:qmvcolsid)`
            `iri (bif:sprintf ("%s%d", str (rdf:_), ?:colctr+1))` `iri(?:inner_id)` .
          `iri(?:inner_id)`
            rdf:type virtrdf:QuadMapColumn ;
            virtrdf:qmvcAlias ?:alias ;
            virtrdf:qmvcColumnName ?:colname };
    }
  for (condctr := 0; condctr < condcount; condctr := condctr + 1)
    {
      declare sqlcond varchar;
      sqlcond := conds [condctr];
      sparql define input:storage ""
      prefix rdfdf: <http://www.openlinksw.com/virtrdf-data-formats#>
      insert in graph <http://www.openlinksw.com/schemas/virtrdf#> {
          `iri(?:qmvcondsid)`
            `iri (bif:sprintf ("%s%d", str (rdf:_), ?:condctr+1))` ?:sqlcond };
    }
  return qmvid;
}
;

create procedure DB.DBA.RDF_QM_NORMALIZE_QMV (
  inout qmv any, inout qmvfix any, inout qmvid any,
  in can_be_literal integer, in fldname varchar, inout tablename varchar, in o_dt any := null, in o_lang any := null )
{
  -- dbg_obj_princ ('DB.DBA.RDF_QM_NORMALIZE_QMV (', qmv, ' ..., ..., ', can_be_literal, fldname, ')');
  qmvid := qmvfix := NULL;
  if ((__tag of vector = __tag (qmv)) and (5 = length (qmv)))
    qmvid := DB.DBA.RDF_QM_DEFINE_MAP_VALUE (qmv, fldname, tablename, o_dt, o_lang);
  else if (__tag of UNAME = __tag (qmv))
      qmvfix := iri_to_id (qmv);
  else if (qmv is not null and not can_be_literal)
    signal ('22023', sprintf ('Quad map declaration can not specify a literal (non-IRI) constant for its %s (tag %d, length %d)',
      fldname, __tag (qmv), length (qmv) ) );
  else if (__tag of vector = __tag (qmv))
    signal ('22023', sprintf ('Quad map declaration contains constant %s of unsupported type (tag %d, length %d)',
      fldname, __tag (qmv), length (qmv) ) );
  else
    qmvfix := qmv;
  -- dbg_obj_princ ('DB.DBA.RDF_QM_NORMALIZE_QMV has found ', fldname, tablename);
}
;

create function DB.DBA.RDF_QM_DEFINE_MAPPING (in storage varchar,
  in qmrawid varchar, in qmid varchar, in qmparentid varchar,
  in qmv_g any, in qmv_s any, in qmv_p any, in qmv_o any, in o_dt any, in o_lang any,
  in is_real integer, in atables any, in conds any, in opts any ) returns any
{
  declare old_actual_type varchar;
  declare tablename, qmvid_g, qmvid_s, qmvid_p, qmvid_o varchar;
  declare qmvfix_g, qmvfix_s, qmvfix_p, qmvfix_o, qmvfix_o_typed, qmvfix_o_dt any;
  declare qm_exclusive, qm_soft_exclusive, qm_empty, qm_is_default, qmusersubmapsid, atablesid, qmcondsid varchar;
  declare qm_order, atablectr, atablecount, condctr, condcount integer;
  -- dbg_obj_princ ('DB.DBA.RDF_QM_DEFINE_MAPPING (', storage, qmrawid, qmid, qmparentid, qmv_g, qmv_s, qmv_p, qmv_o, is_real, atables, conds, opts, ')');
  DB.DBA.RDF_QM_ASSERT_STORAGE_FLAG (storage, 1);
--  DB.DBA.RDF_QM_ASSERT_JSO_TYPE (qmid, NULL);
  old_actual_type := coalesce ((sparql define input:storage ""
      prefix virtrdf: <http://www.openlinksw.com/schemas/virtrdf#>
      select ?t where {
        graph <http://www.openlinksw.com/schemas/virtrdf#> {
            `iri(?:qmid)` rdf:type ?t } } ));
  if (old_actual_type is not null)
    {
      declare old_lstiri, old_side_use varchar;
      if (old_actual_type <> 'http://www.openlinksw.com/schemas/virtrdf#QuadMap')
        signal ('22023', 'The RDF QM schema object <' || qmid || '> already exists, type <' || old_actual_type || '>');
      old_lstiri := (sparql define input:storage ""
        select ?lst where { graph <http://www.openlinksw.com/schemas/virtrdf#> {
            `iri(?:storage)` virtrdf:qsUserMaps ?lst } } );
      old_side_use := coalesce ((sparql define input:storage ""
          select ?s where {
            graph <http://www.openlinksw.com/schemas/virtrdf#> {
                ?s ?p `iri(?:qmid)` filter ((?s != iri(?:storage)) && (?s != iri(?:old_lstiri))) } } ) );
      if (old_side_use is not null)
        signal ('22023', 'Can not re-create the RDF Quad Mapping <' || qmid || '> because it is referenced by <' || old_side_use || '>');
      DB.DBA.RDF_QM_DELETE_MAPPING_FROM_STORAGE (storage, NULL, qmid);
      DB.DBA.RDF_QM_GC_SUBTREE (qmid);
    }
  if (qmparentid is not null)
    DB.DBA.RDF_QM_ASSERT_JSO_TYPE (qmparentid, 'http://www.openlinksw.com/schemas/virtrdf#QuadMap');
  DB.DBA.RDF_QM_ASSERT_STORAGE_CONTAINS_MAPPING (storage, qmid, 0);
  tablename := NULL;
  DB.DBA.RDF_QM_NORMALIZE_QMV (qmv_g, qmvfix_g, qmvid_g, 0, 'graph', tablename);
  DB.DBA.RDF_QM_NORMALIZE_QMV (qmv_s, qmvfix_s, qmvid_s, 0, 'subject', tablename);
  DB.DBA.RDF_QM_NORMALIZE_QMV (qmv_p, qmvfix_p, qmvid_p, 0, 'predicate', tablename);
  DB.DBA.RDF_QM_NORMALIZE_QMV (qmv_o, qmvfix_o, qmvid_o, 1, 'object', tablename, o_dt, o_lang);
  if (get_keyword_ucase ('EXCLUSIVE', opts))
    qm_exclusive := 'http://www.openlinksw.com/schemas/virtrdf#SPART_QM_EXCLUSIVE';
  else
    qm_exclusive := NULL;
  if (get_keyword_ucase ('OK_FOR_ANY_QUAD', opts))
    qm_is_default := 'http://www.openlinksw.com/schemas/virtrdf#SPART_QM_OK_FOR_ANY_QUAD';
  else
    qm_is_default := NULL;
  if (get_keyword_ucase ('SOFT_EXCLUSIVE', opts))
    qm_soft_exclusive := 'http://www.openlinksw.com/schemas/virtrdf#SPART_QM_SOFT_EXCLUSIVE';
  else
    qm_soft_exclusive := NULL;
  if (not is_real)
    {
      qm_empty := 'http://www.openlinksw.com/schemas/virtrdf#SPART_QM_EMPTY';
    }
  else
    {
      qm_empty := NULL;
      if (tablename is null)
        {
          tablename := 'DB.DBA.SYS_IDONLY_ONE';
          if (0 < length (conds))
            signal ('22023', 'Quad Mapping <' || qmid || '> has four constants and no one quad map value; it does not access tables so it can not have WHERE conditions');
        }
    }
  if ('' = tablename)
    tablename := NULL;
  qm_order := get_keyword_ucase ('ORDER', opts);
  if (not is_real)
    {
      qmusersubmapsid := qmid || '--UserSubMaps';
      atablesid := NULL;
      qmcondsid := NULL;
    }
  else
    {
      qmusersubmapsid := NULL;
      atablesid := qmid || '--ATables';
      qmcondsid := qmid || '--Conds';
    }
  if (qm_is_default is not null)
    {
      if (qm_order is not null)
        signal ('22023', 'ORDER option is not applicable to default quad map');
      if (qmparentid is not null)
        signal ('22023', 'A default quad map can not be a sub-map of other quad map');
    }
  sparql define input:storage ""
  prefix rdfdf: <http://www.openlinksw.com/virtrdf-data-formats#>
  delete from graph <http://www.openlinksw.com/schemas/virtrdf#> {
    `iri(?:atablesid)` ?p ?o }
  where { graph <http://www.openlinksw.com/schemas/virtrdf#> {
          `iri(?:atablesid)` ?p ?o .
        } };
  sparql define input:storage ""
  prefix rdfdf: <http://www.openlinksw.com/virtrdf-data-formats#>
  delete from graph <http://www.openlinksw.com/schemas/virtrdf#> {
    `iri(?:qmcondsid)` ?p ?o }
  where { graph <http://www.openlinksw.com/schemas/virtrdf#> {
          `iri(?:qmcondsid)` ?p ?o .
        } };
  atablecount := length (atables);
  condcount := length (conds);
  if (0 = atablecount)
    atablesid := NULL;
  if (0 = condcount)
    qmcondsid := NULL;
  sparql define input:storage ""
  prefix rdfdf: <http://www.openlinksw.com/virtrdf-data-formats#>
  delete from graph <http://www.openlinksw.com/schemas/virtrdf#> { ?s ?p ?o }
  from <http://www.openlinksw.com/schemas/virtrdf#>
  where { ?s ?p ?o . filter (?s = iri(?:qmid)) };
  sparql define input:storage ""
  prefix rdfdf: <http://www.openlinksw.com/virtrdf-data-formats#>
  delete from graph <http://www.openlinksw.com/schemas/virtrdf#> { ?s ?p ?o }
  from <http://www.openlinksw.com/schemas/virtrdf#>
  where { ?s ?p ?o . filter (?s = iri(?:atablesid)) };
  sparql define input:storage ""
  prefix rdfdf: <http://www.openlinksw.com/virtrdf-data-formats#>
  delete from graph <http://www.openlinksw.com/schemas/virtrdf#> { ?s ?p ?o }
  from <http://www.openlinksw.com/schemas/virtrdf#>
  where { ?s ?p ?o . filter (?s = iri(?:qmcondsid)) };
  sparql define input:storage ""
  prefix rdfdf: <http://www.openlinksw.com/virtrdf-data-formats#>
  delete from graph <http://www.openlinksw.com/schemas/virtrdf#> { ?s ?p ?o }
  from <http://www.openlinksw.com/schemas/virtrdf#>
  where { ?s ?p ?o . filter (?s = iri(?:qmusersubmapsid)) };
-- This did not work for some reason:
--      `iri(?:qmid)`
--        virtrdf:qmObjectRange-rvrRestrictions
--            `if (((bif:isnotnull(datatype(?:qmvfix_o)) && (datatype(?:qmvfix_o) != xsd:string)) || bound(?:o_dt)), virtrdf:SPART_VARR_TYPED, ?:NULL)` ;
--        virtrdf:qmObjectRange-rvrDatatype
--            `if (bound (?:o_dt), ?:o_dt, if ((bif:isnotnull(datatype(?:qmvfix_o)) && (datatype(?:qmvfix_o) != xsd:string)), datatype (?:qmvfix_o), ?:NULL))` ;
-- ... so it's replaced with SQL
  qmvfix_o_typed := 0;
  if (o_dt is not null)
    {
      qmvfix_o_typed := 1;
      qmvfix_o_dt := o_dt;
    }
  else if (isstring (qmvfix_o) || iswidestring (qmvfix_o))
    {
      qmvfix_o_typed := 0;
      qmvfix_o_dt := NULL;
    }
  else
    {
      qmvfix_o_typed := 1;
      qmvfix_o_dt := __xsd_type (qmvfix_o);
    }
  sparql define input:storage ""
  prefix rdfdf: <http://www.openlinksw.com/virtrdf-data-formats#>
  insert in graph <http://www.openlinksw.com/schemas/virtrdf#> {
      `iri(?:qmid)`
        rdf:type virtrdf:QuadMap ;
        virtrdf:qmGraphRange-rvrFixedValue ?:qmvfix_g ;
        virtrdf:qmGraphRange-rvrRestrictions
            `if (bound(?:qmvfix_g), virtrdf:SPART_VARR_NOT_NULL, ?:NULL)` ,
            `if (bound(?:qmvfix_g), virtrdf:SPART_VARR_FIXED, ?:NULL)` ,
            `if (bound(?:qmvfix_g), virtrdf:SPART_VARR_IS_REF, ?:NULL)` ,
            `if (bound(?:qmvfix_g), virtrdf:SPART_VARR_IS_IRI, ?:NULL)` ;
        virtrdf:qmGraphMap `iri(?:qmvid_g)` ;
        virtrdf:qmSubjectRange-rvrFixedValue ?:qmvfix_s ;
        virtrdf:qmSubjectRange-rvrRestrictions
            `if (bound(?:qmvfix_s), virtrdf:SPART_VARR_NOT_NULL, ?:NULL)` ,
            `if (bound(?:qmvfix_s), virtrdf:SPART_VARR_FIXED, ?:NULL)` ,
            `if (bound(?:qmvfix_s), virtrdf:SPART_VARR_IS_REF, ?:NULL)` ,
            `if (bound(?:qmvfix_s), virtrdf:SPART_VARR_IS_IRI, ?:NULL)` ;
        virtrdf:qmSubjectMap `iri(?:qmvid_s)` ;
        virtrdf:qmPredicateRange-rvrFixedValue ?:qmvfix_p ;
        virtrdf:qmPredicateRange-rvrRestrictions
            `if (bound(?:qmvfix_p), virtrdf:SPART_VARR_NOT_NULL, ?:NULL)` ,
            `if (bound(?:qmvfix_p), virtrdf:SPART_VARR_FIXED, ?:NULL)` ,
            `if (bound(?:qmvfix_p), virtrdf:SPART_VARR_IS_REF, ?:NULL)` ,
            `if (bound(?:qmvfix_p), virtrdf:SPART_VARR_IS_IRI, ?:NULL)` ;
        virtrdf:qmPredicateMap `iri(?:qmvid_p)` ;
        virtrdf:qmObjectRange-rvrFixedValue ?:qmvfix_o ;
        virtrdf:qmObjectRange-rvrRestrictions
            `if (bound(?:qmvfix_o), virtrdf:SPART_VARR_NOT_NULL, ?:NULL)` ,
            `if (bound(?:qmvfix_o), virtrdf:SPART_VARR_FIXED, ?:NULL)` ,
            `if (bound(?:qmvfix_o), if (isREF(?:qmvfix_o), virtrdf:SPART_VARR_IS_REF, virtrdf:SPART_VARR_IS_LIT), ?:NULL)` ,
            `if (isIRI(?:qmvfix_o), virtrdf:SPART_VARR_IS_IRI, ?:NULL)` ,
            `if (?:qmvfix_o_typed, virtrdf:SPART_VARR_TYPED, ?:NULL)` ;
        virtrdf:qmObjectRange-rvrDatatype ?:qmvfix_o_dt ;
        virtrdf:qmObjectRange-rvrLanguage `if (<bif:length> (lang (?:qmvfix_o)), lang (?:qmvfix_o), ?:NULL)` ;
        virtrdf:qmObjectMap `iri(?:qmvid_o)` ;
        virtrdf:qmTableName ?:tablename ;
        virtrdf:qmATables `iri(?:atablesid)` ;
        virtrdf:qmConds `iri(?:qmcondsid)` ;
        virtrdf:qmUserSubMaps `iri(?:qmusersubmapsid)` ;
        virtrdf:qmMatchingFlags `iri(?:qm_exclusive)` ;
        virtrdf:qmMatchingFlags `iri(?:qm_empty)` ;
        virtrdf:qmMatchingFlags `iri(?:qm_is_default)` ;
        virtrdf:qmMatchingFlags `iri(?:qm_soft_exclusive)` ;
        virtrdf:qmPriorityOrder ?:qm_order .
      `iri(?:atablesid)`
        rdf:type virtrdf:array-of-QuadMapATable .
      `iri(?:qmcondsid)`
        rdf:type virtrdf:array-of-string .
      `iri(?:qmusersubmapsid)`
        rdf:type virtrdf:array-of-QuadMap };
  DB.DBA.RDF_QM_STORE_ATABLES (qmid, atablesid, atables);
  for (condctr := 0; condctr < condcount; condctr := condctr + 1)
    {
      declare sqlcond varchar;
      sqlcond := conds [condctr];
      sparql define input:storage ""
      prefix rdfdf: <http://www.openlinksw.com/virtrdf-data-formats#>
      insert in graph <http://www.openlinksw.com/schemas/virtrdf#> {
          `iri(?:qmcondsid)`
            `iri (bif:sprintf ("%s%d", str (rdf:_), ?:condctr+1))` ?:sqlcond };
    }
  DB.DBA.RDF_ADD_qmAliasesKeyrefdByQuad (qmid);
  commit work;
  if (qm_is_default is not null)
    DB.DBA.RDF_QM_SET_DEFAULT_MAPPING (storage, qmid);
  else
    DB.DBA.RDF_QM_ADD_MAPPING_TO_STORAGE (storage, qmparentid, qmid, qm_order);
  commit work;
  return vector (vector ('00000', 'Quad map <' || qmid || '> has been created and added to the <' || storage || '>'));
}
;

create function DB.DBA.RDF_QM_ATTACH_MAPPING (in storage varchar, in source varchar, in opts any) returns any
{
  declare graphiri varchar;
  declare qmid, qmgraph varchar;
  declare qm_order, qm_is_default integer;
  graphiri := DB.DBA.JSO_SYS_GRAPH ();
  qmid := get_keyword_ucase ('ID', opts, NULL);
  qmgraph := get_keyword_ucase ('GRAPH', opts, NULL);
  DB.DBA.RDF_QM_ASSERT_STORAGE_FLAG (storage, 1);
  DB.DBA.RDF_QM_ASSERT_STORAGE_FLAG (source, 0);
  if (qmid is null)
    {
      qmid := coalesce ((sparql define input:storage ""
          prefix virtrdf: <http://www.openlinksw.com/schemas/virtrdf#>
          select ?s where {
            graph ?:graphiri {
                ?s rdf:type virtrdf:QuadMap .
                ?s virtrdf:qmGraphRange-rvrFixedValue `iri(?:qmgraph)` .
                ?s virtrdf:qmMatchingFlags virtrdf:SPART_QM_EMPTY .
              } } ));
      if (qmid is null)
        return vector (vector ('00100', 'Quad map for graph <' || qmgraph || '> is not found'));
    }
  qm_order := coalesce ((sparql define input:storage ""
      select ?o where { graph ?:graphiri {
              `iri(?:qmid)` virtrdf:qmPriorityOrder ?o } } ) );
  if (exists (sparql define input:storage ""
      ask where { graph ?:graphiri {
              `iri(?:qmid)` virtrdf:qmMatchingFlags virtrdf:SPART_QM_OK_FOR_ANY_QUAD } } ) )
    qm_is_default := 1;
  else
    qm_is_default := 0;
  DB.DBA.RDF_QM_ASSERT_STORAGE_CONTAINS_MAPPING (storage, qmid, 0);
  DB.DBA.RDF_QM_ASSERT_STORAGE_CONTAINS_MAPPING (source, qmid, 1);
  DB.DBA.RDF_QM_ASSERT_JSO_TYPE (qmid, 'http://www.openlinksw.com/schemas/virtrdf#QuadMap');
  if (qm_is_default)
    DB.DBA.RDF_QM_SET_DEFAULT_MAPPING (storage, qmid);
  else
    DB.DBA.RDF_QM_ADD_MAPPING_TO_STORAGE (storage, NULL, qmid, NULL /* !!!TBD: place real value instead of constant NULL */);
  commit work;
  return vector (vector ('00000', 'Quad map <' || qmid || '> is added to the storage <' || storage || '>'));
}
;

create procedure DB.DBA.RDF_QM_ADD_MAPPING_TO_STORAGE (in storage varchar, in qmparent varchar, in qmid varchar, in qmorder integer)
{
  declare graphiri, lstiri varchar;
  declare iris_and_orders any;
  declare ctr, qmid_is_printed integer;
  -- dbg_obj_princ ('DB.DBA.RDF_QM_ADD_MAPPING_TO_STORAGE (', storage, qmparent, qmid, qmorder, ')');
  graphiri := DB.DBA.JSO_SYS_GRAPH ();
  if (qmparent is not null)
    lstiri := (sparql define input:storage ""
      select ?lst where { graph ?:graphiri {
          `iri(?:qmparent)` virtrdf:qmUserSubMaps ?lst } } );
  else
    lstiri := (sparql define input:storage ""
      select ?lst where { graph ?:graphiri {
          `iri(?:storage)` virtrdf:qsUserMaps ?lst } } );
  -- dbg_obj_princ ('DB.DBA.RDF_QM_ADD_MAPPING_TO_STORAGE: storage=', storage, ', qmparent=', qmparent, ', lstiri=', lstiri);
  if (qmorder is null)
    qmorder := 1999;
  iris_and_orders := (
    select DB.DBA.VECTOR_AGG (vector (sub."id", sub."p", sub."ord1"))
    from (
      select sp."id", sp."p", sp."ord1"
      from (
        sparql define input:storage ""
        select ?id ?p
          (bif:coalesce (?ord,
              1000 + bif:aref (
                bif:sprintf_inverse (
                  str(?p),
                  bif:concat (str (rdf:_), "%d"),
                  2),
                0 ) ) ) as ?ord1
        where { graph ?:graphiri {
                `iri(?:lstiri)` ?p ?id .
                filter (! bif:isnull (bif:aref (
                      bif:sprintf_inverse (
                        str(?p),
                        bif:concat (str (rdf:_), "%d"),
                        2),
                      0 ) ) ) .
                optional {?id virtrdf:qmPriorityOrder ?ord} } } ) as sp
      order by 3, 2, 1 ) as sub );
  -- dbg_obj_princ ('DB.DBA.RDF_QM_ADD_MAPPING_TO_STORAGE: found ', iris_and_orders);
  foreach (any itm in iris_and_orders) do
    {
      declare id, p varchar;
      id := itm[0];
      p := itm[1];
      sparql define input:storage ""
      delete from graph <http://www.openlinksw.com/schemas/virtrdf#> {
       `iri(?:lstiri)` `iri(?:p)` `iri(?:id)` };
    }
  ctr := 1;
  qmid_is_printed := 0;
  foreach (any itm in iris_and_orders) do
    {
      declare id varchar;
      declare ord integer;
      id := itm[0];
      ord := itm[2];
      if (ord > qmorder)
        {
          sparql define input:storage ""
          insert in graph <http://www.openlinksw.com/schemas/virtrdf#> {
           `iri(?:lstiri)`
             `iri(bif:sprintf("%s%d", str(rdf:_), ?:ctr))`
               `iri(?:qmid)` };
          -- dbg_obj_princ ('DB.DBA.RDF_QM_ADD_MAPPING_TO_STORAGE: qmid is printed: ', ctr);
          ctr := ctr + 1;
          qmid_is_printed := 1;
        }
      sparql define input:storage ""
      insert in graph <http://www.openlinksw.com/schemas/virtrdf#> {
       `iri(?:lstiri)`
         `iri(bif:sprintf("%s%d", str(rdf:_), ?:ctr))`
           `iri(?:id)` };
      ctr := ctr + 1;
    }
  if (not qmid_is_printed)
    {
      sparql define input:storage ""
      insert in graph <http://www.openlinksw.com/schemas/virtrdf#> {
       `iri(?:lstiri)`
         `iri(bif:sprintf("%s%d", str(rdf:_), ?:ctr))`
           `iri(?:qmid)` };
      -- dbg_obj_princ ('DB.DBA.RDF_QM_ADD_MAPPING_TO_STORAGE: qmid is printed: ', ctr);
      ctr := ctr + 1;
    }
}
;

create procedure DB.DBA.RDF_QM_DELETE_MAPPING_FROM_STORAGE (in storage varchar, in qmparent varchar, in qmid varchar)
{
  declare graphiri, lstiri varchar;
  declare iris_and_orders any;
  declare ctr integer;
  -- dbg_obj_princ ('DB.DBA.RDF_QM_DELETE_MAPPING_FROM_STORAGE (', storage, qmparent, qmid, ')');
  qmid := iri_to_id (qmid, 0, NULL);
  graphiri := DB.DBA.JSO_SYS_GRAPH ();
  if (qmparent is not null)
    lstiri := (sparql define input:storage ""
      select ?lst where { graph ?:graphiri {
          `iri(?:qmparent)` virtrdf:qmUserSubMaps ?lst } } );
  else
    lstiri := (sparql define input:storage ""
      select ?lst where { graph ?:graphiri {
          `iri(?:storage)` virtrdf:qsUserMaps ?lst } } );
  -- dbg_obj_princ ('DB.DBA.RDF_QM_DELETE_MAPPING_FROM_STORAGE: storage=', storage, ', qmparent=', qmparent, ', lstiri=', lstiri);
  iris_and_orders := (
    select DB.DBA.VECTOR_AGG (vector (sub."id", sub."p", sub."ord1"))
    from (
      select sp."id", sp."p", sp."ord1"
      from (
        sparql define input:storage ""
        select ?id ?p
          (bif:coalesce (?ord,
              1000 + bif:aref (
                bif:sprintf_inverse (
                  str(?p),
                  bif:concat (str (rdf:_), "%d"),
                  2),
                0 ) ) ) as ?ord1
        where { graph ?:graphiri {
                `iri(?:lstiri)` ?p ?id .
                filter (! bif:isnull (bif:aref (
                      bif:sprintf_inverse (
                        str(?p),
                        bif:concat (str (rdf:_), "%d"),
                        2),
                      0 ) ) ) .
                optional {?id virtrdf:qmPriorityOrder ?ord} } } ) as sp
      order by 3, 2, 1 ) as sub );
  -- dbg_obj_princ ('DB.DBA.RDF_QM_DELETE_MAPPING_FROM_STORAGE: found ', iris_and_orders);
  foreach (any itm in iris_and_orders) do
    {
      declare id, p varchar;
      id := itm[0];
      p := itm[1];
      sparql define input:storage ""
      delete from graph <http://www.openlinksw.com/schemas/virtrdf#> {
          `iri(?:lstiri)` `iri(?:p)` `iri(?:id)` . };
    }
  ctr := 1;
  foreach (any itm in iris_and_orders) do
    {
      declare id varchar;
      declare ord integer;
      id := itm[0];
      ord := itm[2];
      if (iri_to_id (id, 0, 0) <> qmid)
        {
          sparql define input:storage ""
          insert in graph <http://www.openlinksw.com/schemas/virtrdf#> {
             `iri(?:lstiri)`
               `iri(bif:sprintf("%s%d", str(rdf:_), ?:ctr))`
                 `iri(?:id)` . };
          -- dbg_obj_princ ('DB.DBA.RDF_QM_DELETE_MAPPING_FROM_STORAGE: reinsert ', itm, ' in rdf:_', ctr);
          ctr := ctr + 1;
        }
      else
        {
          -- dbg_obj_princ ('DB.DBA.RDF_QM_DELETE_MAPPING_FROM_STORAGE: skipping ', qmid);
          ;
        }
    }
}
;

create procedure DB.DBA.RDF_QM_SET_DEFAULT_MAPPING (in storage varchar, in qmid varchar)
{
  declare graphiri, old_qmid varchar;
  -- dbg_obj_princ ('DB.DBA.RDF_QM_SET_DEFAULT_MAPPING (', storage, qmid, ')');
  graphiri := DB.DBA.JSO_SYS_GRAPH ();
  old_qmid := coalesce ((sparql define input:storage ""
      select ?qm where { graph ?:graphiri {
              `iri(?:storage)` virtrdf:qsDefaultMap ?qm } } ) );
  if (old_qmid is not null)
    {
      if (cast (old_qmid as varchar) = cast (qmid as varchar))
        return;
      signal ('22023', 'Quad map storage <' || storage || '> has set a default quad map <' || old_qmid || '>, drop it before adding <' || qmid || '>');
    }
  sparql define input:storage ""
  insert in graph <http://www.openlinksw.com/schemas/virtrdf#> { `iri(?:storage)` virtrdf:qsDefaultMap `iri(?:qmid)` . };
  commit work;
}
;

create function DB.DBA.RDF_SML_DROP (in smliri varchar, in silent integer, in compose_report integer := 1) returns any
{
  declare report, affected any;
  report := '';
  vectorbld_init (affected);
  for (sparql define input:storage ""
    select ?storageiri
    from virtrdf:
    where { ?storageiri virtrdf:qsMacroLibrary `iri(?:smliri)` } ) do
    {
      report := report || 'SPARQL macro library <' || smliri || '> has been detached from quad storage <' || "storageiri" || '>\n';
      vectorbld_acc (affected, "storageiri");
    }
  vectorbld_final (affected);
  sparql define input:storage ""
  delete from virtrdf:
    { ?storageiri virtrdf:qsMacroLibrary `iri(?:smliri)` }
  from virtrdf:
    where { ?storageiri virtrdf:qsMacroLibrary `iri(?:smliri)` };
  commit work;
  if (not exists (
      sparql define input:storage ""
      select 1 from virtrdf: where { `iri(?:smliri)` ?p ?o } ) )
    {
      DB.DBA.RDF_QM_APPLY_CHANGES (null, affected);
      if (silent)
        {
          if (compose_report)
            return report || 'SPARQL macro library <' || smliri || '> does not exist, nothing to delete';
          else
            return 0;
        }
      else
        signal ('22023', 'SPARQL macro library <' || smliri || '> does not exist, nothing to delete');
    }
  DB.DBA.RDF_QM_ASSERT_JSO_TYPE (smliri, 'http://www.openlinksw.com/schemas/virtrdf#SparqlMacroLibrary');
  sparql define input:storage ""
  delete from graph virtrdf: {
      `iri(?:smliri)` ?p ?o }
  from virtrdf:
  where { `iri(?:smliri)` ?p ?o };
  DB.DBA.RDF_QM_APPLY_CHANGES (vector ('http://www.openlinksw.com/schemas/virtrdf#SparqlMacroLibrary', smliri), affected);
  if (compose_report)
    return report || 'SPARQL macro library <' || smliri || '> has been deleted';
  else
    return 1;
}
;

create function DB.DBA.RDF_SML_CREATE (in smliri varchar, in txt varchar) returns any
{
  declare stat, msg, smliri_copy varchar;
  declare mdata, rset, affected any;
  DB.DBA.RDF_QM_ASSERT_JSO_TYPE (smliri, 'http://www.openlinksw.com/schemas/virtrdf#SparqlMacroLibrary', 1);
  stat := '00000';
  if (__tag (txt) = __tag of nvarchar)
    txt := charset_recode (txt, '_WIDE_', 'UTF-8');
  exec ('sparql define input:macro-lib-ignore-create "yes" define input:disable-storage-macro-lib "yes" ' || txt, stat, msg, null, 1, mdata, rset);
  if (stat <> '00000')
    signal (stat, msg);
  if (length (rset))
    signal ('SPAR0', 'Assertion failed: the validation query of macro library should return nothing');
  vectorbld_init (affected);
  for (sparql define input:storage ""
    select ?storageiri
    from virtrdf:
    where { ?storageiri virtrdf:qsMacroLibrary `iri(?:smliri)` } ) do
    {
      vectorbld_acc (affected, "storageiri");
    }
  smliri_copy := smliri;
  vectorbld_acc (affected, smliri_copy);
  vectorbld_final (affected);
  sparql define input:storage ""
  delete from graph <http://www.openlinksw.com/schemas/virtrdf#> {
      `iri(?:smliri)` ?p ?o }
  from <http://www.openlinksw.com/schemas/virtrdf#>
  where { `iri(?:smliri)` ?p ?o };
  commit work;
  sparql define input:storage ""
  insert in graph <http://www.openlinksw.com/schemas/virtrdf#> {
      `iri(?:smliri)` a virtrdf:SparqlMacroLibrary ; virtrdf:smlSourceText ?:txt };
  DB.DBA.RDF_QM_APPLY_CHANGES (null, affected);
  return 'SPARQL macro library <' || smliri || '> has been (re)created';
}
;

create function DB.DBA.RDF_QM_DETACH_MACRO_LIBRARY (in storageiri varchar, in args any) returns any
{
  declare expected_smliri varchar;
  declare old_ctr, expected_found integer;
  declare silent, report any;
  expected_smliri := get_keyword_ucase ('ID', args, NULL);
  silent := get_keyword_ucase ('SILENT', args, 0);
  expected_found := 0;
  old_ctr := 0;
  vectorbld_init (report);
  for (sparql define input:storage ""
    select ?oldsmliri
    from virtrdf:
    where { ?storageiri virtrdf:qsMacroLibrary ?oldsmliri } ) do
    {
      if (expected_smliri is not null and cast (expected_smliri as nvarchar) <> cast ("oldsmliri" as nvarchar))
        {
          if (silent)
            vectorbld_acc (report, vector ('00100', 'The SPARQL macro library to detach from <' || storageiri || '> is <' || expected_smliri || '> but actually attached one is <' || "oldsmliri" || '>, nothing to do'));
          else
            signal ('22023', 'The SPARQL macro library to detach from <' || storageiri || '> is <' || expected_smliri || '> but actually attached one is <' || "oldsmliri" || '>');
        }
      else
        {
          if (expected_smliri is not null)
            expected_found := 1;
          vectorbld_acc (report, vector ('00000', 'SPARQL macro library <' || "oldsmliri" || '> has been detached from quad storage <' || storageiri || '>'));
        }
      old_ctr := old_ctr + 1;
    }
  if (expected_smliri is not null)
    {
      sparql define input:storage ""
      delete from virtrdf:
        { ?storageiri virtrdf:qsMacroLibrary ?smliri }
      from virtrdf:
        where { ?storageiri virtrdf:qsMacroLibrary ?smliri };
    }
  else
    {
      sparql define input:storage ""
      delete from virtrdf:
        { ?storageiri virtrdf:qsMacroLibrary ?smliri }
      from virtrdf:
        where { ?storageiri virtrdf:qsMacroLibrary ?smliri };
    }
  commit work;
  if (old_ctr > 1)
    vectorbld_acc (report, vector ('00100', 'Note that there was a configuration error: more than one macro library was attached to the quad storage <' || storageiri || '>'));
  else if (old_ctr = 0)
    {
      if (silent)
        vectorbld_acc (report, vector ('00100', 'No one SPARQL macro library is attached to the quad storage <' || storageiri || '>, nothing to detach'));
      else
        signal ('22023', 'No one SPARQL macro library is attached to the quad storage <' || storageiri || '>, nothing to detach');
    }
  vectorbld_final (report);
-- dbg_obj_princ ('DB.DBA.RDF_QM_DETACH_MACRO_LIBRARY (', storageiri, args, ') returns ', report);
  return report;
}
;

create function DB.DBA.RDF_QM_ATTACH_MACRO_LIBRARY (in storageiri varchar, in args any) returns any
{
  declare smliri varchar;
  smliri := get_keyword_ucase ('ID', args, NULL);
  DB.DBA.RDF_QM_ASSERT_JSO_TYPE (storageiri, 'http://www.openlinksw.com/schemas/virtrdf#QuadStorage');
  DB.DBA.RDF_QM_ASSERT_JSO_TYPE (smliri, 'http://www.openlinksw.com/schemas/virtrdf#SparqlMacroLibrary');
  declare report any;
  vectorbld_init (report);
  for (sparql define input:storage ""
    select ?oldsmliri
    from virtrdf:
    where { ?storageiri virtrdf:qsMacroLibrary ?oldsmliri } ) do
    {
      vectorbld_acc (report, vector ('00000', 'SPARQL macro library <' || "oldsmliri" || '> has been detached from quad storage <' || storageiri || '>'));
    }
  sparql define input:storage ""
  delete from virtrdf:
    { ?storageiri virtrdf:qsMacroLibrary ?oldsmliri }
  from virtrdf:
    where { ?storageiri virtrdf:qsMacroLibrary ?oldsmliri };
  commit work;
  sparql define input:storage ""
  prefix rdfdf: <http://www.openlinksw.com/virtrdf-data-formats#>
  insert in graph virtrdf: {
      `iri(?:storageiri)` virtrdf:qsMacroLibrary `iri(?:smliri)` };
  vectorbld_acc (report, vector ('00000', 'SPARQL macro library <' || smliri || '> has been attached to quad storage <' || storageiri || '>'));
  vectorbld_final (report);
  return report;
}
;

create procedure DB.DBA.RDF_ADD_qmAliasesKeyrefdByQuad (in qm_iri varchar)
{
  declare kr_iri varchar;
  declare good_ctr, all_ctr integer;
  kr_iri := qm_iri || '--qmAliasesKeyrefdByQuad';
  sparql define input:storage "" delete from virtrdf: { `iri(?:kr_iri)` ?p ?o } from virtrdf: where { `iri(?:kr_iri)` ?p ?o };
  sparql define input:storage "" insert in virtrdf: { `iri(?:qm_iri)` virtrdf:qmAliasesKeyrefdByQuad `iri(?:kr_iri)` . `iri(?:kr_iri)` a virtrdf:array-of-string };
  good_ctr := 0;
  all_ctr := 0;
  for ( sparql define input:storage ""
    select ?alias ?tbl (sql:VECTOR_AGG (str(?col))) as ?cols
    from virtrdf:
    where {
        `iri(?:qm_iri)` a virtrdf:QuadMap ;
          ?fld_p ?qmv .
        filter (?fld_p in (virtrdf:qmGraphMap , virtrdf:qmSubjectMap , virtrdf:qmPredicateMap , virtrdf:qmObjectMap))
        ?qmv a virtrdf:QuadMapValue ;
          virtrdf:qmvATables [
              ?qmvat_p [ a virtrdf:QuadMapATable ;
                  virtrdf:qmvaAlias ?alias ;
                  virtrdf:qmvaTableName ?tbl ] ] ;
          virtrdf:qmvColumns [
              ?qmvc_p [ a virtrdf:QuadMapColumn ;
                  virtrdf:qmvcAlias ?alias ;
                  virtrdf:qmvcColumnName ?col ] ] ;
          virtrdf:qmvFormat [ a virtrdf:QuadMapFormat ;
              virtrdf:qmfIsBijection ?bij ] .
        filter (?bij != 0)
      } ) do
    {
      -- dbg_obj_princ ('Quad map ', "qm_iri", ' has alias ', "alias", ' of table ', "tbl", ' with cols ', "cols");
      all_ctr := all_ctr + 1;
      for (select KEY_ID, KEY_N_SIGNIFICANT from DB.DBA.SYS_KEYS where KEY_TABLE = "tbl" and KEY_IS_UNIQUE) do
        {
          for (select "COLUMN" from DB.DBA.SYS_KEY_PARTS, DB.DBA.SYS_COLS
            where  KP_KEY_ID = KEY_ID and KP_NTH < KEY_N_SIGNIFICANT and COL_ID = KP_COL ) do
            {
              if (not position ("COLUMN", "cols"))
                {
                  -- dbg_obj_princ ("COLUMN", ' not in ', "cols");
                  goto wrong_key;
                }
            }
          good_ctr := good_ctr + 1;
          -- dbg_obj_princ ('Quad map ', qm_iri, ' can identify source rows in alias ', "alias", ' of table ', "tbl");
          sparql define input:storage "" insert in virtrdf: { `iri(?:kr_iri)` `iri(bif:sprintf("%s%d", str(rdf:_), ?:good_ctr))` ?:"alias" };
          goto right_key;
wrong_key: ;
        }
right_key: ;
    }
  -- dbg_obj_princ ('Quad map ', qm_iri, ' can identify source rows in ', good_ctr, ' of ', all_ctr, ' its aliases with bijections.');
}
;

create procedure DB.DBA.RDF_UPGRADE_QUAD_MAP (in qm_iri varchar)
{
  declare keyrefd any;
  if (not exists (sparql define input:storage "" select (1) from virtrdf: where { `iri(?:qm_iri)` a virtrdf:QuadMap }))
    signal ('RDFxx', sprintf ('Quad map <%s> does not exist, nothing to upgrade', qm_iri));
  if (not exists (sparql define input:storage "" select (1) from virtrdf: where { `iri(?:qm_iri)` virtrdf:qmAliasesKeyrefdByQuad ?keyrefs }))
    DB.DBA.RDF_ADD_qmAliasesKeyrefdByQuad (qm_iri);
}
;

create procedure DB.DBA.RDF_UPGRADE_METADATA ()
{
  for (sparql define input:storage "" select ?qm_iri from virtrdf: where { ?qm_iri a virtrdf:QuadMap }) do
    {
      DB.DBA.RDF_UPGRADE_QUAD_MAP ("qm_iri");
    }
  commit work;
}
;

-----
-- RDF parallel load


create procedure DB.DBA.TTLP_EV_TRIPLE_W (
  in g_iid IRI_ID, in s_uri varchar, in p_uri varchar,
  in o_uri varchar, in env any )
{
  -- dbg_obj_princ ('DB.DBA.TTLP_EV_TRIPLE_W (', g_iid, s_uri, p_uri, o_uri, env, ')');
  declare log_mode integer;
  declare s_iid, p_iid, o_iid IRI_ID;
  log_mode := env[0];
  if (isstring (registry_get ('DB.DBA.RDF_REPL')))
    repl_publish ('__rdf_repl', '__rdf_repl.log');

  if (log_mode = 1)
    {
      whenever sqlstate '40001' goto deadlock_1;
again_1:
      log_enable (1, 1);
      s_iid := iri_to_id (s_uri);
      p_iid := iri_to_id (p_uri);
      o_iid := iri_to_id (o_uri);
      commit work;
      log_enable (0, 1);
      insert soft DB.DBA.RDF_QUAD (G,S,P,O)
      values (g_iid, s_iid, p_iid, o_iid);
      commit work;
      -- dbg_obj_princ ('DB.DBA.TTLP_EV_TRIPLE_W (', g_iid, s_uri, p_uri, o_uri, env, ') done /1');
      return;
    }
  if (log_mode = 0)
    {
      whenever sqlstate '40001' goto deadlock_0;
again_0:
      log_enable (0, 1);
      s_iid := iri_to_id (s_uri);
      p_iid := iri_to_id (p_uri);
      o_iid := iri_to_id (o_uri);
      commit work;
      insert soft DB.DBA.RDF_QUAD (G,S,P,O) values (g_iid, s_iid, p_iid, o_iid);
      commit work;
      -- dbg_obj_princ ('DB.DBA.TTLP_EV_TRIPLE_W (', g_iid, s_uri, p_uri, o_uri, env, ') done /0');
      return;
    }
  whenever sqlstate '40001' goto deadlock_2;
again_2:
  log_enable (1, 1);
  s_iid := iri_to_id (s_uri);
  p_iid := iri_to_id (p_uri);
  o_iid := iri_to_id (o_uri);
  commit work;
  insert soft DB.DBA.RDF_QUAD (G,S,P,O) values (g_iid, s_iid, p_iid, o_iid);
  commit work;
  -- dbg_obj_princ ('DB.DBA.TTLP_EV_TRIPLE_W (', g_iid, s_uri, p_uri, o_uri, env, ') done /2');
  return;
deadlock_0:
  rollback work;
  goto again_0;
deadlock_1:
  rollback work;
  goto again_1;
deadlock_2:
  rollback work;
  goto again_2;
}
;


create procedure DB.DBA.TTLP_EV_TRIPLE_L_W (
  in g_iid IRI_ID, in s_uri varchar, in p_uri varchar,
  in o_val any, in o_type any, in o_lang any, in env any )
{
  -- dbg_obj_princ ('DB.DBA.TTLP_EV_TRIPLE_L_W (', g_iid, s_uri, p_uri, o_val, o_type, o_lang, env, ')');
  declare log_mode integer;
  declare ro_id_dict any;
  log_mode := env[0];
  ro_id_dict := env[1];
  declare s_iid, p_iid IRI_ID;
  if (isstring (o_type))
    {
      declare parsed any;
      parsed := __xqf_str_parse_to_rdf_box (o_val, o_type, isstring (o_val));
      if (parsed is not null)
        {
          if (__tag of rdf_box = __tag (parsed))
            {
	      if (256 = rdf_box_type (parsed))
		db..rdf_geo_add (parsed);
	      else
                rdf_box_set_type (parsed,
                  DB.DBA.RDF_TWOBYTE_OF_DATATYPE (iri_to_id (o_type)));
              parsed := DB.DBA.RDF_OBJ_ADD (257, parsed, 257, ro_id_dict);
              -- dbg_obj_princ ('rdf_box_type is set to ', rdf_box_type (parsed));
            }
          o_val := parsed;
        }
    }
  whenever sqlstate '40001' goto deadlck;
again:
  if (log_mode = 0)
    log_enable (0, 1);
  else
    log_enable (1, 1);
  s_iid := iri_to_id (s_uri);
  p_iid := iri_to_id (p_uri);
  if (isstring (o_val) or (__tag of XML = __tag (o_val)))
    {
      if (isstring (o_type) or isstring (o_lang))
        {
          if (not isstring (o_type))
            o_type := null;
          if (not isstring (o_lang))
            o_lang := null;
          o_val := DB.DBA.RDF_MAKE_OBJ_OF_TYPEDSQLVAL_FT (o_val,
              iri_to_id (o_type),
              o_lang, g_iid, p_iid, ro_id_dict );
	}
      else
        o_val := DB.DBA.RDF_MAKE_OBJ_OF_SQLVAL_FT (o_val, g_iid, p_iid, ro_id_dict);
    }
  else if (__tag of rdf_box = __tag (o_val))
    {
      if (__tag of varchar = rdf_box_data_tag (o_val) and __rdf_obj_ft_rule_check (g_iid, p_iid))
        o_val := DB.DBA.RDF_OBJ_ADD (257, o_val, 257, ro_id_dict);
      else if (0 < rdf_box_needs_digest (o_val))
        o_val := DB.DBA.RDF_OBJ_ADD (257, o_val, 257);
    }
  -- dbg_obj_princ ('final o_val = ', o_val);
  if (log_mode <= 1)
    log_enable (0, 1);
  insert soft DB.DBA.RDF_QUAD (G,S,P,O)
  values (g_iid, s_iid, p_iid, o_val);
  -- dbg_obj_princ ('DB.DBA.TTLP_EV_TRIPLE_L_W (', g_iid, s_uri, p_uri, o_val, o_type, o_lang, env, ') done');
  commit work;
  return;
deadlck:
  rollback work;
  goto again;
}
;

create procedure DB.DBA.TTLP_EV_NEW_GRAPH_A (inout g varchar, inout g_iid IRI_ID, inout app_env any) {
  -- dbg_obj_princ ('DB.DBA.TTLP_EV_NEW_GRAPH_A(', g, g_iid, app_env, ')');
  if (__rdf_obj_ft_rule_count_in_graph (g_iid))
    app_env[2][1] := dict_new (app_env[3]);
  else
    app_env[2][1] := null;
  if (__rdf_graph_is_in_enabled_repl (g_iid))
    app_env[4] := g;
  else
    app_env[4] := null;
}
;

create procedure DB.DBA.TTLP_EV_TRIPLE_A (
  inout g_iid IRI_ID, inout s_uri varchar, inout p_uri varchar,
  inout o_uri varchar,
  inout app_env any )
{
  -- dbg_obj_princ ('DB.DBA.TTLP_EV_TRIPLE_A (', g_iid, s_uri, p_uri, o_uri, app_env, ')');
  if (app_env[4] is not null)
    __rdf_repl_quad (84, app_env[4], iri_canonicalize (s_uri), iri_canonicalize (p_uri), iri_canonicalize (o_uri));
  app_env[1] := aq_request (
    app_env[0], 'DB.DBA.TTLP_EV_TRIPLE_W',
    vector (g_iid, s_uri, p_uri, o_uri, app_env[2]) );
  if (mod (app_env[1], 100000) = 0)
    {
      declare ro_id_dict any;
      ro_id_dict := app_env[2][1];
      if (ro_id_dict is not null)
        DB.DBA.RDF_OBJ_ADD_KEYWORD_FOR_GRAPH (g_iid, ro_id_dict);
      commit work;
      aq_wait_all (app_env[0]);
    }
}
;

create procedure DB.DBA.TTLP_EV_TRIPLE_L_A (
  inout g_iid IRI_ID, inout s_uri varchar, inout p_uri varchar,
  inout o_val any, inout o_type varchar, inout o_lang varchar,
  inout app_env any )
{
  -- dbg_obj_princ ('DB.DBA.TTLP_EV_TRIPLE_L_A (', g_iid, s_uri, p_uri, o_val, o_type, o_lang, app_env, ')');
  if (app_env[4] is not null)
    {
      if (isstring (o_type))
        __rdf_repl_quad (81, app_env[4], iri_canonicalize (s_uri), iri_canonicalize (p_uri), o_val, iri_canonicalize (o_type), NULL);
      else if (isstring (o_lang))
        __rdf_repl_quad (82, app_env[4], iri_canonicalize (s_uri), iri_canonicalize (p_uri), o_val, null, o_lang);
      else
        __rdf_repl_quad (80, app_env[4], iri_canonicalize (s_uri), iri_canonicalize (p_uri), o_val);
    }
  if (__tag of XML = __tag (o_val))
    {
      DB.DBA.TTLP_EV_TRIPLE_L_W (g_iid, s_uri, p_uri, o_val, o_type, o_lang, app_env[2]);
      return;
    }
  app_env[1] := aq_request (
    app_env[0], 'DB.DBA.TTLP_EV_TRIPLE_L_W',
    vector (g_iid, s_uri, p_uri, o_val, o_type, o_lang, app_env[2]) );
  if (mod (app_env[1], 100000) = 0)
    {
      declare ro_id_dict any;
      ro_id_dict := app_env[2][1];
      if (ro_id_dict is not null)
        DB.DBA.RDF_OBJ_ADD_KEYWORD_FOR_GRAPH (g_iid, ro_id_dict);
      commit work;
      aq_wait_all (app_env[0]);
    }
}
;

create procedure DB.DBA.TTLP_EV_COMMIT_A (
  inout graph_iri varchar, inout app_env any )
{
  -- dbg_obj_princ ('DB.DBA.TTLP_EV_COMMIT_A (', graph_iri, app_env, ')');
  commit work;
  aq_wait_all (app_env[0]);
  commit work;
  DB.DBA.TTLP_EV_COMMIT (graph_iri, app_env[2]);
  commit work;
}
;

-- for replication should not use AQ
create procedure DB.DBA.TTLP_EV_TRIPLE_R (
  inout g_iid IRI_ID, inout s_uri varchar, inout p_uri varchar,
  inout o_uri varchar,
  inout app_env any )
{
  -- dbg_obj_princ ('DB.DBA.TTLP_EV_TRIPLE_A (', g_iid, s_uri, p_uri, o_uri, app_env, ')');
  if (app_env[4] is not null)
    __rdf_repl_quad (84, app_env[4], iri_canonicalize (s_uri), iri_canonicalize (p_uri), iri_canonicalize (o_uri));
  commit work;
  app_env[1] := coalesce (app_env[1], 0) + 1;
  DB.DBA.TTLP_EV_TRIPLE_W (g_iid, s_uri, p_uri, o_uri, app_env[2]);
  if (mod (app_env[1], 100000) = 0)
    {
      declare ro_id_dict any;
      ro_id_dict := app_env[2][1];
      if (ro_id_dict is not null)
        DB.DBA.RDF_OBJ_ADD_KEYWORD_FOR_GRAPH (g_iid, ro_id_dict);
    }
}
;

create procedure DB.DBA.TTLP_EV_TRIPLE_L_R (
  inout g_iid IRI_ID, inout s_uri varchar, inout p_uri varchar,
  inout o_val any, inout o_type varchar, inout o_lang varchar,
  inout app_env any )
{
  -- dbg_obj_princ ('DB.DBA.TTLP_EV_TRIPLE_L_A (', g_iid, s_uri, p_uri, o_val, o_type, o_lang, app_env, ')');
  if (app_env[4] is not null)
    {
      if (isstring (o_type))
        __rdf_repl_quad (81, app_env[4], iri_canonicalize (s_uri), iri_canonicalize (p_uri), o_val, iri_canonicalize (o_type), NULL);
      else if (isstring (o_lang))
        __rdf_repl_quad (82, app_env[4], iri_canonicalize (s_uri), iri_canonicalize (p_uri), o_val, null, o_lang);
      else
        __rdf_repl_quad (80, app_env[4], iri_canonicalize (s_uri), iri_canonicalize (p_uri), o_val);
    }
  commit work;
  if (__tag of XML = __tag (o_val))
    {
      DB.DBA.TTLP_EV_TRIPLE_L_W (g_iid, s_uri, p_uri, o_val, o_type, o_lang, app_env[2]);
      return;
    }
  app_env[1] := coalesce (app_env[1], 0) + 1;
  DB.DBA.TTLP_EV_TRIPLE_L_W (g_iid, s_uri, p_uri, o_val, o_type, o_lang, app_env[2]);
  if (mod (app_env[1], 100000) = 0)
    {
      declare ro_id_dict any;
      ro_id_dict := app_env[2][1];
      if (ro_id_dict is not null)
        DB.DBA.RDF_OBJ_ADD_KEYWORD_FOR_GRAPH (g_iid, ro_id_dict);
    }
}
;

create procedure DB.DBA.TTLP_EV_COMMIT_R (
  inout graph_iri varchar, inout app_env any )
{
  -- dbg_obj_princ ('DB.DBA.TTLP_EV_COMMIT_A (', graph_iri, app_env, ')');
  DB.DBA.TTLP_EV_COMMIT (graph_iri, app_env[2]);
}
;

create function DB.DBA.TTLP_MT (in strg varchar, in base varchar, in graph varchar := null, in flags integer := 0,
				 in log_mode integer := 2, in threads integer := 3, in transactional int := 0)
{
  declare app_env any;
  if (graph = '')
    signal ('22023', 'Empty string is not a valid graph IRI in DB.DBA.TTLP_MT()');
  else if (graph is null)
    {
      graph := base;
      if ((graph is null) or (graph = ''))
        signal ('22023', 'DB.DBA.TTLP_MT() requires a valid IRI as a base argument if graph is not specified');
    }
  DB.DBA.TTLP_V (strg, base, graph, flags, threads, transactional, log_mode);
}
;

create function DB.DBA.TTLP_MT_LOCAL_FILE (in filename varchar, in base varchar, in graph varchar := null, in flags integer := 0,
				 in log_mode integer := 2, in threads integer := 3, in transactional int := 0)
{
  signal ('DEPRE', 'TTLP_MT_LOCAL_FILE  deprecated.  Use TTLP');
}
;

create function DB.DBA.RDF_LOAD_RDFXML_MT (in strg varchar, in base varchar, in graph varchar,
  in log_mode integer := 2, in threads integer := 3, in transactional int := 0)
{
  declare ro_id_dict, app_env any;
  if (graph = '')
    signal ('22023', 'Empty string is not a valid graph IRI in DB.DBA.RDF_LOAD_RDFXML_MT()');
  else if (graph is null)
    {
      graph := base;
      if ((graph is null) or (graph = ''))
        signal ('22023', 'DB.DBA.RDF_LOAD_RDFXML_MT() requires a valid IRI as a base argument if graph is not specified');
    }
  if (transactional = 0)
    {
      if (log_mode = 1 or log_mode = 0)
	log_mode := log_mode + 2;
    }
  DB.DBA.RDF_LOAD_RDFXML (strg, base, graph);
  return graph;
}
;


-----
-- Free text index on DB.DBA.RDF_OBJ

create function DB.DBA.VT_DECODE_KEYWORD_ITM (inout vtdata varchar, inout ofs integer)
{
  declare res integer;
  if ((5 <> vtdata[ofs]) or (0 <> vtdata[ofs+5]))
    signal ('23023', 'Invalid VT_WORD data in DB.DBA.VT_DECODE_KEYWORD_ITM');
  res := (((vtdata[ofs+1] * 256) + vtdata[ofs+2]) * 256 + vtdata[ofs+3]) * 256 + vtdata[ofs+4];
  ofs := ofs + 6;
  return res;
}
;

create procedure DB.DBA.VT_ENCODE_KEYWORD_ITM (in id integer, inout ses any)
{
  declare strg varchar;
  strg := '012345';
  strg[5] := 0;
  strg[4] := bit_and (id, 255); id := bit_shift (id, -8);
  strg[3] := bit_and (id, 255); id := bit_shift (id, -8);
  strg[2] := bit_and (id, 255); id := bit_shift (id, -8);
  strg[1] := bit_and (id, 255); if (id > 255) signal ('22023', 'Abnormally big document id in DB.DBA.VT_ENCODE_KEYWORD_ITM');
  strg[0] := 5;
  http (strg, ses);
}
;

create function DB.DBA.VT_COMPOSE_KEYWORD_INDEX_LINES (
  inout carry_d_id integer, -- Smallest doc id of carry
  inout carry_d_id_2 integer, -- largest doc id of carry
  inout carry_data varchar, -- carry as ready-to-insert varchar data
  in old_d_id integer, -- last read VT_D_ID, NULL when not found
  in old_d_id_2 integer, -- last read VT_D_ID_2, NULL when not found
  in old_data varchar, -- last read VT_DATA, NULL when not found
  inout ro_id_offset integer,	-- offset of first not-yet-used new id
  inout new_ro_ids any )	-- array of all new ids
  returns any -- returns vector of vector (d_id, d_id_2, data) of everything before or at current
{
  declare res_acc, mix_ses any;
  declare old_data_ofs, old_data_len, old_curr_id, mix_id integer;
  declare new_ro_id_idx, new_ro_ids_count, mix_d_id, mix_d_id_2, mix_count integer;
  -- dbg_obj_princ ('DB.DBA.VT_COMPOSE_KEYWORD_INDEX_LINES (', carry_d_id, carry_d_id_2, length (carry_data), ' bytes,', old_d_id, old_d_id_2, length (old_data), ' bytes,', ro_id_offset, length (new_ro_ids), ' items)');
  vectorbld_init (res_acc);
  mix_ses := string_output();
  if (carry_data <> '')
    {
      mix_d_id := carry_d_id;
      mix_d_id_2 := carry_d_id_2;
      http (carry_data, mix_ses);
      mix_count := length (carry_data) / 6;
    }
  else
    {
      mix_d_id := null;
      mix_d_id_2 := null;
      mix_count := 0;
    }
  old_data_ofs := 0;
  if (old_data is null)
    old_curr_id := null;
  else
    old_curr_id := DB.DBA.VT_DECODE_KEYWORD_ITM (old_data, old_data_ofs);
  old_data_len := length (old_data);
  new_ro_ids_count := length (new_ro_ids);
  new_ro_id_idx := ro_id_offset;
  if (new_ro_id_idx < new_ro_ids_count)
    mix_id := new_ro_ids [new_ro_id_idx];
  else
    mix_id := null;
  -- dbg_obj_princ ('DB.DBA.VT_COMPOSE_KEYWORD_INDEX_LINES starts/1: ', mix_d_id, old_curr_id, mix_id);
  mix_d_id := __min_notnull (mix_d_id, old_curr_id, mix_id);
  -- dbg_obj_princ ('DB.DBA.VT_COMPOSE_KEYWORD_INDEX_LINES starts/2: ', mix_d_id);

next_mix:
  if (old_curr_id is null)
    {
      if ((new_ro_id_idx >= new_ro_ids_count) or (new_ro_ids [new_ro_id_idx] > old_d_id_2))
        goto complete;
      mix_id := new_ro_ids [new_ro_id_idx];
      new_ro_id_idx := new_ro_id_idx + 1;
    }
  else
    {
      if ((new_ro_id_idx >= new_ro_ids_count) or (new_ro_ids [new_ro_id_idx] >= old_curr_id))
        {
          if ((new_ro_id_idx < new_ro_ids_count) and (new_ro_ids [new_ro_id_idx] = old_curr_id))
            new_ro_id_idx := new_ro_id_idx + 1;
          mix_id := old_curr_id;
          if (old_data_ofs >= old_data_len)
            old_curr_id := null;
          else
            old_curr_id := DB.DBA.VT_DECODE_KEYWORD_ITM (old_data, old_data_ofs);
        }
      else
        {
          mix_id := new_ro_ids [new_ro_id_idx];
          new_ro_id_idx := new_ro_id_idx + 1;
        }
    }
  if ((mix_count > 180) or ((mix_d_id_2 / 10000) <> (mix_id  / 10000)))
    {
      -- dbg_obj_princ ('DB.DBA._COMPOSE_KEYWORD_INDEX_LINES completed a row from ', mix_d_id, ' to ', mix_d_id_2);
      vectorbld_acc (res_acc, vector (mix_d_id, mix_d_id_2, string_output_string (mix_ses)));
      mix_ses := string_output ();
      mix_d_id := mix_id;
      mix_count := 0;
    }
  DB.DBA.VT_ENCODE_KEYWORD_ITM (mix_id, mix_ses);
  mix_d_id_2 := mix_id;
  mix_count := mix_count + 1;
  goto next_mix;

complete:
  ro_id_offset := new_ro_id_idx;
  if (mix_count > 150)
    {
      -- dbg_obj_princ ('DB.DBA._COMPOSE_KEYWORD_INDEX_LINES completed (last) row from ', mix_d_id, ' to ', mix_d_id_2);
      vectorbld_acc (res_acc, vector (mix_d_id, mix_d_id_2, string_output_string (mix_ses)));
      carry_data := '';
      carry_d_id := carry_d_id_2 := null;
    }
  else
    {
      carry_data := string_output_string (mix_ses);
      carry_d_id := mix_d_id;
      carry_d_id_2 := mix_d_id_2;
    }
  vectorbld_final (res_acc);
  -- dbg_obj_princ ('DB.DBA._COMPOSE_KEYWORD_INDEX_LINES completed, carry ', carry_d_id, carry_d_id_2, length (carry_data), ' bytes, res ', res_acc);
  return res_acc;
}
;

--!AWK PUBLIC
create function DB.DBA.RDF_OBJ_PATCH_CONTAINS_BY_GRAPH (in phrase varchar, in graph_iri varchar)
{
  declare graph_keyword any;
  whenever sqlstate '*' goto err;
  graph_keyword := iri_to_id (graph_iri, 0, 0);
  if (isinteger (graph_keyword))
    goto err;
  if (not sys_stat ('rdf_query_graph_keywords'))
    return sprintf ('[__enc "UTF-8"] %s', phrase);
  graph_keyword := WS.WS.STR_SQL_APOS (rdf_graph_keyword (graph_keyword));
  return sprintf ('[__enc "UTF-8"] ^%s AND (%s)', graph_keyword, phrase);
err:
  return '^"#nosuch"';
}
;

--!AWK PUBLIC
create function DB.DBA.RDF_OBJ_PATCH_CONTAINS_BY_MANY_GRAPHS (in phrase varchar, in graph_iris any)
{
  declare isfirst, gctr, gcount integer;
  declare ses, graph_keyword any;
  if (not sys_stat ('rdf_query_graph_keywords'))
    return sprintf ('[__enc "UTF-8"] %s', phrase);
  whenever sqlstate '*' goto err;
  gcount := length (graph_iris);
  ses := string_output ();
  isfirst := 1;
  for (gctr := 0; gctr < gcount; gctr := gctr + 1)
    {
      graph_keyword := iri_to_id (graph_iris[gctr], 0, 0);
      if (not isinteger (graph_keyword))
        {
          if (isfirst)
            {
              http ('^', ses);
              isfirst := 0;
            }
          else
            http (' OR ^', ses);
          http (WS.WS.STR_SQL_APOS (rdf_graph_keyword (graph_keyword)), ses);
        }
    }
  if (not isfirst)
    return sprintf ('[__enc "UTF-8"] (%s) AND (%s)', string_output_string (ses), phrase);
err:
  return '^"#nosuch"';
}
;

create procedure DB.DBA.RDF_OBJ_ADD_KEYWORD_FOR_GRAPH (in graph_iid IRI_ID, inout ro_id_dict any array, in daq any array := 0)
{
  declare ro_id_offset, ro_ids_count integer;
  declare new_ro_ids, vtb any;
  declare gwordump varchar;
  declare n_w, n_ins, n_upd, n_next integer;
  if (not sys_stat ('rdf_create_graph_keywords'))
    {
      dict_zap (ro_id_dict, 2);
      return;
    }
next_batch:
  new_ro_ids := dict_destructive_list_rnd_keys (ro_id_dict, 500000);
  ro_ids_count := length (new_ro_ids);
  if (0 = ro_ids_count)
    return;
  gwordump := ' ' || rdf_graph_keyword (graph_iid);
  gwordump[0] := length (gwordump) - 1;
  gvector_digit_sort (new_ro_ids, 1, 0, 1);
  if (0 = sys_stat ('cl_run_local_only'))
    {
      commit work;
      cl_g_words (new_ro_ids, gwordump, daq);
      goto next_batch;
    }
  vtb := vt_batch (__min (__max (ro_ids_count, 31), 500000));
  commit work;
  whenever sqlstate '40001' goto retry_add;
again:
  for (ro_id_offset := 0; ro_id_offset < ro_ids_count; ro_id_offset := ro_id_offset + 1)
    {
      vt_batch_d_id (vtb, new_ro_ids[ro_id_offset]);
      vt_batch_feed_wordump (vtb, gwordump, 0);
    }
  if (0 = sys_stat ('cl_run_local_only'))
    {
      declare is_local_daq int;
      if (0 = daq)
	{
	daq := daq (1);
	  is_local_daq := 1;
	}
      cl_g_text_index (vtb, daq);
      if (is_local_daq)
	{
	  while (daq_next (daq));
	  commit work;
	  goto next_batch;
	}
    }
  else
    "DB"."DBA"."VT_BATCH_PROCESS_DB_DBA_RDF_OBJ" (vtb, null);
  commit work;
  goto next_batch;
retry_add:
  rollback work;
  goto again;
}
;

create procedure DB.DBA.RDF_OBJ_ADD_KEYWORD_FOR_GRAPH_OLD (in graph_iid IRI_ID, inout ro_id_dict any)
{
  declare start_vt_d_id, aligned_start_vt_d_id, uncommited_ro_id_offset, ro_id_offset, ro_ids_count integer;
  declare old_d_id, old_d_id_2, carry_d_id, carry_d_id_2 integer;
  declare old_data, carry_data varchar;
  declare split_ctr, split_len integer;
  declare dbg_smallest_d_id, dbg_largest_d_id, dbg_prev_d_id, dbg_prev_d_id_2 integer;
  declare split any;
  declare cr cursor for (
    select VT_D_ID, VT_D_ID_2, coalesce (VT_DATA, cast (VT_LONG_DATA as varchar)) from RDF_OBJ_RO_FLAGS_WORDS
    where (VT_WORD = cast (graph_iid as varchar)) and (VT_D_ID >= aligned_start_vt_d_id) and VT_D_ID_2 >= start_vt_d_id for update);
  declare new_ro_ids any;
  -- dbg_obj_princ ('DB.DBA.RDF_OBJ_ADD_KEYWORD_FOR_GRAPH (', graph_iid, ro_id_dict, ')');
  new_ro_ids := dict_list_keys (ro_id_dict, 2);
  ro_ids_count := length (new_ro_ids);
  if (0 = ro_ids_count)
    return;
  gvector_digit_sort (new_ro_ids, 1, 0, 1);
-- debug begin
  dbg_smallest_d_id := new_ro_ids[0];
  dbg_largest_d_id := new_ro_ids[length (new_ro_ids) - 1];
  dbg_prev_d_id := 0;
  dbg_prev_d_id_2 := 0;
-- debug end
  commit work;
  whenever sqlstate '40001' goto retry_add;
  uncommited_ro_id_offset := 0;
again:
  ro_id_offset := uncommited_ro_id_offset;
  start_vt_d_id := new_ro_ids[ro_id_offset];
  aligned_start_vt_d_id := ((start_vt_d_id / 10000) * 10000);
  carry_d_id := 0;
  carry_d_id_2 := 0;
  carry_data := '';
  set isolation = 'serializable';
  whenever not found goto no_more_olds;
  open cr (prefetch 1);

next_split:
  fetch cr into old_d_id, old_d_id_2, old_data;
  split := DB.DBA.VT_COMPOSE_KEYWORD_INDEX_LINES (carry_d_id, carry_d_id_2, carry_data, old_d_id, old_d_id_2, old_data, ro_id_offset, new_ro_ids);
  split_len := length (split);
  split_ctr := 0;
  if ((split_len > 0) and (split[split_len-1][0] = old_d_id))
    {
      if ((old_d_id_2 = split[split_len-1][1]) and (old_data = split[split_len-1][2]))
        { ; }
      else
        update RDF_OBJ_RO_FLAGS_WORDS set VT_D_ID_2 = split[split_len-1][1], VT_DATA = split[split_len-1][2], VT_LONG_DATA = null
      where current of cr;
      split_len := split_len - 1;
    }
  if (split_len > 0)
    {
      delete from RDF_OBJ_RO_FLAGS_WORDS
        where (VT_WORD = cast (graph_iid as varchar)) and (VT_D_ID >= split[0][0]) and (VT_D_ID_2 <= split[split_len-1][1]);
    }
  for (split_ctr := 0; split_ctr < split_len; split_ctr := split_ctr+1)
    {
      insert replacing RDF_OBJ_RO_FLAGS_WORDS (VT_WORD, VT_D_ID, VT_D_ID_2, VT_DATA)
      values (cast (graph_iid as varchar), split[split_ctr][0], split[split_ctr][1], split[split_ctr][2]);
    }
  if (carry_data = '')
    {
      commit work;
      uncommited_ro_id_offset := ro_id_offset;
      if (ro_id_offset >= ro_ids_count)
        {
          start_vt_d_id := 1024 * 65536 * 65536 * 65536; -- well, a big number
          goto no_more_olds;
        }
      start_vt_d_id := new_ro_ids[uncommited_ro_id_offset];
      aligned_start_vt_d_id := ((start_vt_d_id / 10000) * 10000);
      close cr;
      open cr (prefetch 1);
    }
  goto next_split;

no_more_olds:
  split := DB.DBA.VT_COMPOSE_KEYWORD_INDEX_LINES (carry_d_id, carry_d_id_2, carry_data, null, null, null, ro_id_offset, new_ro_ids);
  split_len := length (split);
  split_ctr := 0;
  if (split_len > 0)
    {
      delete from RDF_OBJ_RO_FLAGS_WORDS
        where (VT_WORD = cast (graph_iid as varchar)) and (VT_D_ID >= split[0][0]) and (VT_D_ID_2 <= split[split_len-1][1]);
    }
  for (split_ctr := 0; split_ctr < split_len; split_ctr := split_ctr+1)
    {
      insert replacing RDF_OBJ_RO_FLAGS_WORDS (VT_WORD, VT_D_ID, VT_D_ID_2, VT_DATA)
      values (cast (graph_iid as varchar), split[split_ctr][0], split[split_ctr][1], split[split_ctr][2]);
    }
  if (length (carry_data)  <> 0)
    {
      insert replacing RDF_OBJ_RO_FLAGS_WORDS (VT_WORD, VT_D_ID, VT_D_ID_2, VT_DATA)
      values (cast (graph_iid as varchar), carry_d_id, carry_d_id_2, carry_data);
    }
  commit work;
-- debug begin
--  for (
--    select VT_WORD, VT_D_ID, VT_D_ID_2, coalesce (VT_DATA, cast (VT_LONG_DATA as varchar)) as vtd from RDF_OBJ_RO_FLAGS_WORDS
--    where (VT_WORD = cast (graph_iid as varchar))
--      and (VT_D_ID >= ((dbg_smallest_d_id / 10000) * 10000))
--      and (VT_D_ID_2 >= (((dbg_largest_d_id + 9999) / 10000) * 10000)) ) do
--    {
--      if (VT_D_ID > VT_D_ID_2)
--        {
--          -- dbg_obj_princ ('FT BUG: misordered bounds: ', VT_WORD, VT_D_ID, VT_D_ID_2);
          -- raw_exit ();
--          ;
--        }
--      if (VT_D_ID <= dbg_prev_d_id_2)
--        {
--          -- dbg_obj_princ ('FT BUG: overlapping ranges: ', VT_WORD, VT_D_ID, VT_D_ID_2, '; prev is ', dbg_prev_d_id, dbg_prev_d_id_2);
           -- raw_exit ();
--          ;
--        }
--    }

  return;
retry_add:
  close cr;
  rollback work;
  goto again;
}
;

create procedure DB.DBA.RDF_OBJ_FT_INS_ALL (in this_box_only integer := 0)
{
  declare id, f integer;
  if (not this_box_only)
    {
      cl_exec ('DB.DBA.RDF_OBJ_FT_INS_ALL (1)');
      return;
    }
  set triggers off;

  declare c1 cursor for select RO_ID, RO_FLAGS from DB.DBA.RDF_OBJ where not (bit_and (RO_FLAGS, 1)) and RO_VAL > '' and RO_VAL < '\xFF\xFF\xFF\xFF\xFF\xFF' and isstring (RO_VAL) and RO_LONG is null for update option (no cluster);
start_c1:
  open c1;
  whenever sqlstate '42000' goto deadl_c1;
  whenever not found goto done_c1;
again_c1:
  fetch c1 into id, f;
  update DB.DBA.RDF_OBJ set RO_FLAGS = bit_or (RO_FLAGS, 1) where current of c1;
  insert into VTLOG_DB_DBA_RDF_OBJ option (no cluster) (VTLOG_RO_ID, SNAPTIME, DMLTYPE) values (id, curdatetime (), 'I');
  commit work;
  goto again_c1;
done_c1:
  close c1;

  declare c2 cursor for select RO_ID, RO_FLAGS from DB.DBA.RDF_OBJ where not (bit_and (RO_FLAGS, 1)) and RO_LONG is not NULL for update option (no cluster);
start_c2:
  open c2;
  whenever sqlstate '42000' goto deadl_c2;
  whenever not found goto done_c2;
again_c2:
  fetch c2 into id, f;
  update DB.DBA.RDF_OBJ set RO_FLAGS = bit_or (RO_FLAGS, 1) where current of c2;
  insert into VTLOG_DB_DBA_RDF_OBJ option (no cluster) (VTLOG_RO_ID, SNAPTIME, DMLTYPE) values (id, curdatetime (), 'I');
  commit work;
  goto again_c2;
done_c2:
  close c2;

start_g:
  whenever sqlstate '42000' goto deadl_g;
  for (select distinct G as curr_g FROm DB.DBA.RDF_QUAD table option (index RDF_QUAD_GS, index_only) option (no cluster)) do
    {
      declare ro_id_dict any;
      ro_id_dict := dict_new (100000);
      for (select distinct rdf_box_ro_id (O) as o_id from DB.DBA.RDF_QUAD join DB.DBA.RDF_OBJ on (RO_ID = rdf_box_ro_id (O))
        where G = curr_g
        and 0 = bit_and (RO_FLAGS, 1) and __tag (coalesce (RO_LONG, RO_VAL)) in (__tag of varchar, __tag of XML) ) do
        {
          update DB.DBA.RDF_OBJ set RO_FLAGS = bit_or (RO_FLAGS, 1) where RO_ID = o_id;
          insert soft DB.DBA.VTLOG_DB_DBA_RDF_OBJ option (no cluster) (VTLOG_RO_ID, SNAPTIME, DMLTYPE) values (o_id, curdatetime (), 'I');
          --insert soft rdf_ft (rf_id, rf_o) values (id, obj);
          dict_put (ro_id_dict, o_id, 1);
          commit work;
          if (dict_size (ro_id_dict) > 100000)
            DB.DBA.RDF_OBJ_ADD_KEYWORD_FOR_GRAPH (curr_g, ro_id_dict);
        }
      DB.DBA.RDF_OBJ_ADD_KEYWORD_FOR_GRAPH (curr_g, ro_id_dict);
      commit work;
    }

  return;
deadl_c1:
  rollback work;
  close c1;
  goto start_c1;
deadl_c2:
  rollback work;
  close c2;
  goto start_c2;
deadl_g:
  rollback work;
  goto start_g;
}
;

create function DB.DBA.RDF_OBJ_FT_RULE_ADD (in rule_g varchar, in rule_p varchar, in reason varchar) returns integer
{
  declare rule_g_iid, rule_p_iid IRI_ID;
  declare ro_id_dict any;
  if (0 = sys_stat ('cl_run_local_only'))
    signal ('42000', 'DB.DBA.RDF_OBJ_FT_RULE_ADD() is not available in cluster. Do DB.DBA.CL_TEXT_INDEX (1) to enable text index on all future RDF loads on cluster.');
  set triggers off;
  if (rule_g is null)
    rule_g := '';
  if (rule_p is null)
    rule_p := '';
  rule_g_iid := case (rule_g) when '' then null else iri_to_id (rule_g) end;
  rule_p_iid := case (rule_p) when '' then null else iri_to_id (rule_p) end;
  if (reason is null)
    signal ('RDFXX', 'DB.DBA.RDF_OBJ_FT_RULE_ADD() expects string as argument 3');
  if (exists (
      select top 1 1 from DB.DBA.RDF_OBJ_FT_RULES
      where ROFR_G = rule_g and ROFR_P = rule_p and ROFR_REASON = reason))
    return 0;
  if (not exists (
      select top 1 1 from DB.DBA.RDF_OBJ_FT_RULES
      where (ROFR_G = rule_g or ROFR_G = '') and (ROFR_P = rule_p or ROFR_P = '') ) )
    {
      -- dbg_obj_princ ('DB.DBA.RDF_OBJ_FT_RULE_ADD: need scan');
      commit work;
      exec ('checkpoint');
      __atomic (1);
      declare exit handler for sqlstate '*' {
        __atomic (0);
        signal (__SQL_STATE, __SQL_MESSAGE); };
      if ((rule_g <> '') and (rule_p <> ''))
        {
          ro_id_dict := dict_new (100000);
          for (select distinct rdf_box_ro_id (O) as id from DB.DBA.RDF_QUAD join DB.DBA.RDF_OBJ on (RO_ID = rdf_box_ro_id (O))
            where G=rule_g_iid and P=rule_p_iid
            and 0 = bit_and (RO_FLAGS, 1) and __tag(coalesce (RO_LONG, RO_VAL)) in (__tag of varchar, __tag of XML) ) do
                    {
              update DB.DBA.RDF_OBJ set RO_FLAGS = bit_or (RO_FLAGS, 1) where RO_ID = id;
              insert soft DB.DBA.VTLOG_DB_DBA_RDF_OBJ option (no cluster) (VTLOG_RO_ID, SNAPTIME, DMLTYPE) values (id, curdatetime (), 'I');
		      --insert soft rdf_ft (rf_id, rf_o) values (id, obj);
                      dict_put (ro_id_dict, id, 1);
                  commit work;
              if (dict_size (ro_id_dict) > 100000)
                DB.DBA.RDF_OBJ_ADD_KEYWORD_FOR_GRAPH (rule_g_iid, ro_id_dict);
            }
          DB.DBA.RDF_OBJ_ADD_KEYWORD_FOR_GRAPH (rule_g_iid, ro_id_dict);
        }
      else if (rule_g <> '')
        {
          ro_id_dict := dict_new (100000);
          for (select distinct rdf_box_ro_id (O) as id from DB.DBA.RDF_QUAD join DB.DBA.RDF_OBJ on (RO_ID = rdf_box_ro_id (O))
            where G=rule_g_iid
            and 0 = bit_and (RO_FLAGS, 1) and __tag(coalesce (RO_LONG, RO_VAL)) in (__tag of varchar, __tag of XML) ) do
            {
              update DB.DBA.RDF_OBJ set RO_FLAGS = bit_or (RO_FLAGS, 1) where RO_ID = id;
              insert soft DB.DBA.VTLOG_DB_DBA_RDF_OBJ option (no cluster) (VTLOG_RO_ID, SNAPTIME, DMLTYPE) values (id, curdatetime (), 'I');
		      --insert soft rdf_ft (rf_id, rf_o) values (id, obj);
                      dict_put (ro_id_dict, id, 1);
                  commit work;
              if (dict_size (ro_id_dict) > 100000)
                DB.DBA.RDF_OBJ_ADD_KEYWORD_FOR_GRAPH (rule_g_iid, ro_id_dict);
            }
          DB.DBA.RDF_OBJ_ADD_KEYWORD_FOR_GRAPH (rule_g_iid, ro_id_dict);
        }
      else if (rule_p <> '')
        {
          declare old_g IRI_ID;
          old_g := #i0;
                      ro_id_dict := dict_new (100000);
          for (select G as curr_g, S as curr_s from DB.DBA.RDF_QUAD table option (index RDF_QUAD_GS, index_only)) do
                {
              for (select distinct rdf_box_ro_id (O) as id from DB.DBA.RDF_QUAD join DB.DBA.RDF_OBJ on (RO_ID = rdf_box_ro_id (O))
                where G = curr_g and P = rule_p_iid
                and 0 = bit_and (RO_FLAGS, 1) and __tag(coalesce (RO_LONG, RO_VAL)) in (__tag of varchar, __tag of XML) ) do
                    {
                      if (curr_g <> old_g)
                        {
                            DB.DBA.RDF_OBJ_ADD_KEYWORD_FOR_GRAPH (old_g, ro_id_dict);
                          old_g := curr_g;
                        }
                  update DB.DBA.RDF_OBJ set RO_FLAGS = bit_or (RO_FLAGS, 1) where RO_ID = id;
                  insert soft DB.DBA.VTLOG_DB_DBA_RDF_OBJ option (no cluster) (VTLOG_RO_ID, SNAPTIME, DMLTYPE) values (id, curdatetime (), 'I');
		      --insert soft rdf_ft (rf_id, rf_o) values (id, obj);
                      dict_put (ro_id_dict, id, 1);
                      commit work;
                      if (dict_size (ro_id_dict) > 100000)
                        DB.DBA.RDF_OBJ_ADD_KEYWORD_FOR_GRAPH (curr_g, ro_id_dict);
                    }
                }
          DB.DBA.RDF_OBJ_ADD_KEYWORD_FOR_GRAPH (old_g, ro_id_dict);
          commit work;
    }
      else
        DB.DBA.RDF_OBJ_FT_INS_ALL ();
  __atomic (0);
  exec ('checkpoint');
    }
  insert into DB.DBA.RDF_OBJ_FT_RULES (ROFR_G, ROFR_P, ROFR_REASON) values (rule_g, rule_p, reason);
  commit work;
  __rdf_obj_ft_rule_add (rule_g_iid, rule_p_iid, reason);
  return 1;
}
;

create function DB.DBA.RDF_OBJ_FT_RULE_DEL (in rule_g varchar, in rule_p varchar, in reason varchar) returns integer
{
  declare rule_g_iid, rule_p_iid IRI_ID;
  if (rule_g is null)
    rule_g := '';
  if (rule_p is null)
    rule_p := '';
  rule_g_iid := case (rule_g) when '' then null else iri_to_id (rule_g) end;
  rule_p_iid := case (rule_p) when '' then null else iri_to_id (rule_p) end;
  if (reason is null)
    signal ('RDFXX', 'DB.DBA.RDF_OBJ_FT_RULE_DEL() expects string as argument 3');
  if (not exists (
      select top 1 1 from DB.DBA.RDF_OBJ_FT_RULES
      where ROFR_G = rule_g and ROFR_P = rule_p and ROFR_REASON = reason))
    return 0;
  delete from DB.DBA.RDF_OBJ_FT_RULES where ROFR_G = rule_g and ROFR_P = rule_p and ROFR_REASON = reason;
  commit work;
  __rdf_obj_ft_rule_del (rule_g_iid, rule_p_iid, reason);
  return 1;
}
;

create procedure DB.DBA.RDF_OBJ_FT_RECOVER ()
{
  declare stat, msg, STRG varchar;
  declare metas, rset any;
  result_names (STRG);
  if (((0 = sys_stat ('cl_run_local_only')) and (1 = cast (registry_get ('cl_rdf_text_index') as integer)))
    or exists (select 1 from DB.DBA.RDF_OBJ_FT_RULES where ROFR_G = '' and ROFR_P = '') )
    {
      result ('One of rules requires total indexing, so the rest of rules will not require any selective processing...');
      DB.DBA.RDF_OBJ_FT_INS_ALL ();
    }
  else
    {
  exec ('
    select ROFR_G, ROFR_P, MAX (ROFR_REASON), COUNT (1), MIN (ROFR_REASON)
    from DB.DBA.RDF_OBJ_FT_RULES
    group by ROFR_G, ROFR_P
    order by (1024 * length (ROFR_G) + 1024 * length (ROFR_P))',
    stat, msg, vector (), 100000, metas, rset);
  foreach (any ftrule in rset) do
    {
      result (sprintf ('Temporary drop of rule "%s" for graph <%s> predicate <%s>...', ftrule[2], ftrule[0], ftrule[1]));
      { whenever sqlstate '*' goto add_back;
        DB.DBA.RDF_OBJ_FT_RULE_DEL (ftrule[0], ftrule[1], ftrule[2]);
        result ('... done'); }
add_back:
      result (sprintf ('Restoring rule "%s" for graph <%s> predicate <%s>...', ftrule[2], ftrule[0], ftrule[1]));
      { whenever sqlstate '*' goto restored;
        DB.DBA.RDF_OBJ_FT_RULE_ADD (ftrule[0], ftrule[1], ftrule[2]);
        result ('... done'); }
restored:
      if (ftrule[3] > 1)
        result (sprintf ('No need to re-apply additional %d rules for this graph and predicate, e.g., rule "%s"', ftrule[4]));
    }
    }
  result ('Now starting incremental update of free-text index...');
  VT_INC_INDEX_DB_DBA_RDF_OBJ();
  result ('... done');
}
;

-----
-- Security

create table DB.DBA.RDF_GRAPH_GROUP (
  RGG_IID IRI_ID not null primary key,
  RGG_IRI varchar not null,
  RGG_MEMBER_PATTERN varchar,
  RGG_COMMENT varchar
  )
alter index RDF_GRAPH_GROUP on DB.DBA.RDF_GRAPH_GROUP partition cluster replicated
create index RDF_GRAPH_GROUP_IRI on DB.DBA.RDF_GRAPH_GROUP (RGG_IRI) partition cluster replicated
;

create table DB.DBA.RDF_GRAPH_GROUP_MEMBER (
  RGGM_GROUP_IID IRI_ID not null,
  RGGM_MEMBER_IID IRI_ID not null,
  primary key (RGGM_GROUP_IID, RGGM_MEMBER_IID)
  )
alter index RDF_GRAPH_GROUP_MEMBER on DB.DBA.RDF_GRAPH_GROUP_MEMBER partition cluster replicated
;

create table DB.DBA.RDF_GRAPH_USER (
  RGU_GRAPH_IID IRI_ID not null,
  RGU_USER_ID integer not null,
  RGU_PERMISSIONS integer not null, -- 1 for read, 2 for write, 4 for sponge, 8 for list, 16 for admin, 256 for owner.
  primary key (RGU_GRAPH_IID, RGU_USER_ID)
  )
alter index RDF_GRAPH_USER on DB.DBA.RDF_GRAPH_USER partition cluster replicated
create index RDF_GRAPH_USER_USER_ID on DB.DBA.RDF_GRAPH_USER (RGU_USER_ID, RGU_GRAPH_IID, RGU_PERMISSIONS) partition cluster replicated
;

create procedure DB.DBA.RDF_GRAPH_CACHE_IID (in iid IRI_ID)
{
  declare iri any;
  if (#i0 = iid)
    signal ('22023', 'Invalid attempt to cache #i0 as an IRI ID of some graph');
  iri := __uname (id_to_canonicalized_iri (iid));
  dict_put (__rdf_graph_iri2id_dict(), iri, iid);
  dict_put (__rdf_graph_id2iri_dict(), iid, iri);
}
;

create procedure DB.DBA.RDF_GRAPH_GROUP_CREATE_MEMONLY (in group_iri varchar, in group_iid IRI_ID)
{
  group_iri := cast (group_iri as varchar);
  DB.DBA.RDF_GRAPH_CACHE_IID (group_iid);
  dict_put (__rdf_graph_group_dict(), group_iid, vector ());
  jso_mark_affected (group_iri);
  log_text ('jso_mark_affected (?)', group_iri);
  __rdf_cli_mark_qr_to_recompile ();
}
;

create function DB.DBA.RDF_GRAPH_GROUP_IRI_CHECK (in group_iri varchar, in fname varchar) returns IRI_ID
{
  declare group_iid IRI_ID;
  group_iri := cast (group_iri as varchar);
  group_iid := iri_to_id (group_iri);
  if (group_iri <> id_to_canonicalized_iri (group_iid))
    signal ('RDF99', sprintf ('Group IRI should be canonical, but the specified group IRI <%s> differs from its canonical form <%s>', group_iri, id_to_canonicalized_iri (group_iid)));
  for (select RGG_IID from DB.DBA.RDF_GRAPH_GROUP where RGG_IRI = group_iri) do
    {
      if (RGG_IID = group_iid)
        return RGG_IID;
      if (not exists (select top 1 1 from DB.DBA.RDF_GRAPH_GROUP where RGG_IID = group_iid))
        signal ('RDF99', sprintf ('Integrity violation in DB.DBA.RDF_GRAPH_GROUP table (found group IRI <%s>, not found group IRI ID %s)', group_iri, cast (group_iid as varchar)));
    }
  for (select RGG_IRI from DB.DBA.RDF_GRAPH_GROUP where RGG_IID = group_iid) do
    {
      if (RGG_IRI = id_to_canonicalized_iri (group_iid))
        signal ('RDF99', sprintf ('Table DB.DBA.RDF_GRAPH_GROUP contains group with IRI <%s>, not IRI <%s>, for group IRI ID %s', RGG_IRI, group_iri, cast (group_iid as varchar)));
      signal ('RDF99', sprintf ('Integrity violation in DB.DBA.RDF_GRAPH_GROUP table (not found group IRI <%s>, found group IRI ID %s)', group_iri, cast (group_iid as varchar)));
    }
  return NULL;
}
;

create procedure DB.DBA.RDF_GRAPH_GROUP_CREATE (in group_iri varchar, in quiet integer, in member_pattern varchar := null, in comment varchar := null)
{
  declare group_iid IRI_ID;
  group_iri := cast (group_iri as varchar);
  group_iid := iri_to_id (group_iri);
  DB.DBA.RDF_GRAPH_GROUP_IRI_CHECK (group_iri, 'DB.DBA.RDF_GRAPH_GROUP_CREATE');
  if (exists (select top 1 1 from DB.DBA.RDF_GRAPH_GROUP where RGG_IRI = group_iri))
    {
      if (quiet)
        return;
      signal ('RDF99', sprintf ('The graph group <%s> already exists (%s)', group_iri, coalesce (
          (select top 1 RGG_COMMENT from DB.DBA.RDF_GRAPH_GROUP where RGG_IRI = group_iri), 'group has no comment' ) ) );
    }
  insert into DB.DBA.RDF_GRAPH_GROUP (
    RGG_IID, RGG_IRI, RGG_MEMBER_PATTERN, RGG_COMMENT )
  values (group_iid, group_iri, member_pattern, comment);
  commit work;
  DB.DBA.SECURITY_CL_EXEC_AND_LOG ('DB.DBA.RDF_GRAPH_GROUP_CREATE_MEMONLY (?, ?)', vector (group_iri, group_iid));
}
;

create procedure DB.DBA.RDF_GRAPH_GROUP_DROP_MEMONLY (in group_iri varchar, in group_iid IRI_ID)
{
  group_iri := cast (group_iri as varchar);
  DB.DBA.RDF_GRAPH_CACHE_IID (group_iid);
  dict_put (__rdf_graph_group_dict(), group_iid, vector ());
  dict_remove (__rdf_graph_group_dict(), group_iid);
  jso_mark_affected (group_iri);
  log_text ('jso_mark_affected (?)', group_iri);
  if (group_iri = 'http://www.openlinksw.com/schemas/virtrdf#PrivateGraphs')
    {
      declare privates any;
      privates := dict_list_keys (__rdf_graph_group_of_privates_dict(), 2);
      foreach (IRI_ID iid in privates) do
        {
          jso_mark_affected (id_to_iri (iid));
          log_text ('jso_mark_affected (?)', id_to_iri (iid));
        }
    }
  __rdf_cli_mark_qr_to_recompile ();
}
;

create procedure DB.DBA.RDF_GRAPH_GROUP_DROP (in group_iri varchar, in quiet integer)
{
  declare group_iid IRI_ID;
  group_iri := cast (group_iri as varchar);
  group_iid := iri_to_id (group_iri);
  DB.DBA.RDF_GRAPH_GROUP_IRI_CHECK (group_iri, 'DB.DBA.RDF_GRAPH_GROUP_DROP');
  if (not exists (select top 1 1 from DB.DBA.RDF_GRAPH_GROUP where RGG_IRI = group_iri))
    {
      if (quiet)
        return;
      signal ('RDF99', sprintf ('The graph group <%s> does not exist (%s)', group_iri, coalesce (
          (select top 1 RGG_COMMENT from DB.DBA.RDF_GRAPH_GROUP where RGG_IRI = group_iri), 'group has no comment' ) ) );
    }
  if (group_iri = 'http://www.openlinksw.com/schemas/virtrdf#PrivateGraphs')
    signal ('RDF99', sprintf ('The graph group <%s> is a special one and used to control security, can not drop it' ) );
  delete from DB.DBA.RDF_GRAPH_GROUP_MEMBER where RGGM_GROUP_IID = group_iid;
  delete from DB.DBA.RDF_GRAPH_GROUP where RGG_IID = group_iid;
  commit work;
  DB.DBA.SECURITY_CL_EXEC_AND_LOG ('DB.DBA.RDF_GRAPH_GROUP_DROP_MEMONLY (?, ?)', vector (group_iri, group_iid));
}
;

create procedure DB.DBA.RDF_GRAPH_CHECK_VISIBILITY_CHANGE (in memb_iri varchar, in special_iid IRI_ID)
{
  declare memb_iid IRI_ID;
  memb_iid := iri_to_id (memb_iri);
  declare new_default_perms integer;
  new_default_perms := (select RGU_PERMISSIONS from DB.DBA.RDF_GRAPH_USER where RGU_GRAPH_IID = special_iid and RGU_USER_ID = http_nobody_uid());
  for (select g.RGU_PERMISSIONS as g_perms, s.RGU_PERMISSIONS as s_perms, g.RGU_USER_ID as uid
    from DB.DBA.RDF_GRAPH_USER as g left outer join DB.DBA.RDF_GRAPH_USER as s on (g.RGU_USER_ID = s.RGU_USER_ID and s.RGU_GRAPH_IID = special_iid)
    where g.RGU_GRAPH_IID = memb_iid ) do
    {
      if (s_perms is not null and bit_and (s_perms, bit_not (g_perms)))
        signal ('RDF99', sprintf ('Default %s permissions of user "%s" (UID %d) on RDF store can not be broader than permissions on specific graph <%s> so the graph can not be %s now',
          case (equ (special_iid, #i8192)) when 0 then '"world"' else '"private area"' end,
          (select U_NAME from DB.DBA.SYS_USERS where U_ID = uid),
          uid,
          memb_iri,
          case (equ (special_iid, #i8192)) when 0 then 'removed from the "private area"' else 'added to the "private area"' end ) );
      if (new_default_perms is not null and bit_and (new_default_perms, bit_not (g_perms)))
        signal ('RDF99', sprintf ('Default %s permissions of unauthenticated user on RDF store can not be broader than permissions of user "%s" (UID %d)  on specific graph <%s> so the graph can not be %s now',
          case (equ (special_iid, #i8192)) when 0 then '"world"' else '"private area"' end,
          (select U_NAME from DB.DBA.SYS_USERS where U_ID = uid),
          uid,
          memb_iri,
          case (equ (special_iid, #i8192)) when 0 then 'removed from the "private area"' else 'added to the "private area"' end ) );
    }
}
;

create procedure DB.DBA.RDF_GRAPH_GROUP_INS_MEMONLY (in group_iri varchar, in group_iid IRI_ID, in memb_iri varchar, in memb_iid IRI_ID)
{
  declare membs any;
  group_iri := cast (group_iri as varchar);
  memb_iri := cast (memb_iri as varchar);
  DB.DBA.RDF_GRAPH_CACHE_IID (group_iid);
  DB.DBA.RDF_GRAPH_CACHE_IID (memb_iid);
--  This is not scalable enough for big groups: N*N/2 time to create a group of size N.
--  dict_put (__rdf_graph_group_dict(), group_iid,
--    (select VECTOR_AGG (RGGM_MEMBER_IID) from DB.DBA.RDF_GRAPH_GROUP_MEMBER
--     where RGGM_GROUP_IID = group_iid
--     order by RGGM_MEMBER_IID ) );
  membs := dict_get (__rdf_graph_group_dict(), group_iid, null);
  if (membs is null)
    dict_put (__rdf_graph_group_dict(), group_iid, vector (memb_iid));
  else if (isvector (membs))
    {
      if (0 >= position (memb_iid, membs))
        {
          if (length (membs) < 1000)
            dict_put (__rdf_graph_group_dict(), group_iid, vector_concat (membs, vector (memb_iid)));
          else
            {
              declare new_membs any;
              new_membs := dict_new (1000);
              foreach (IRI_ID m in membs) do dict_put (new_membs, m, 1);
              dict_put (new_membs, memb_iid, 1);
              dict_put (__rdf_graph_group_dict(), group_iid, new_membs);
            }
        }
    }
  else
    {
      dict_put (membs, memb_iid, 1);
      dict_put (__rdf_graph_group_dict(), group_iid, membs);
    }
  jso_mark_affected (group_iri);
  log_text ('jso_mark_affected (?)', group_iri);
  if (group_iri = 'http://www.openlinksw.com/schemas/virtrdf#PrivateGraphs')
    {
      dict_put (__rdf_graph_group_of_privates_dict(), memb_iid, 1);
      jso_mark_affected (memb_iri);
      log_text ('jso_mark_affected (?)', memb_iri);
    }
}
;

create procedure DB.DBA.RDF_GRAPH_GROUP_INS (in group_iri varchar, in memb_iri varchar)
{
  declare group_iid, memb_iid IRI_ID;
  group_iri := cast (group_iri as varchar);
  DB.DBA.RDF_GRAPH_GROUP_IRI_CHECK (group_iri, 'DB.DBA.RDF_GRAPH_GROUP_INS');
  memb_iri := cast (memb_iri as varchar);
  group_iid := iri_to_id (group_iri);
  memb_iid := iri_to_id (memb_iri);
  set isolation = 'serializable';
  commit work;
  if (not exists (select top 1 1 from DB.DBA.RDF_GRAPH_GROUP where RGG_IRI = group_iri))
    signal ('RDF99', sprintf ('Graph group <%s> does not exist', group_iri));
  if (group_iri = 'http://www.openlinksw.com/schemas/virtrdf#PrivateGraphs')
    {
      DB.DBA.RDF_GRAPH_CHECK_VISIBILITY_CHANGE (memb_iri, #i8192);
      if (isstring (registry_get ('DB.DBA.RDF_REPL'))
        and exists (select top 1 1 from DB.DBA.RDF_GRAPH_GROUP_MEMBER
          where RGGM_GROUP_IID = iri_to_id (UNAME'http://www.openlinksw.com/schemas/virtrdf#rdf_repl_graph_group') and RGGM_MEMBER_IID = memb_iid)
        and not exists (select RGU_PERMISSIONS from DB.DBA.RDF_GRAPH_USER
          where RGU_GRAPH_IID = memb_iid and RGU_USER_ID = http_nobody_uid() and bit_and (RGU_PERMISSIONS, 1) )
        and not exists (select RGU_PERMISSIONS from DB.DBA.RDF_GRAPH_USER
          where RGU_GRAPH_IID = #i8192 and RGU_USER_ID = http_nobody_uid() and bit_and (RGU_PERMISSIONS, 1) )
        and not exists (select RGU_PERMISSIONS from DB.DBA.RDF_GRAPH_USER
          where RGU_GRAPH_IID = memb_iid and RGU_USER_ID = __rdf_repl_uid() and bit_and (RGU_PERMISSIONS, 1) )
        and not exists (select RGU_PERMISSIONS from DB.DBA.RDF_GRAPH_USER
          where RGU_GRAPH_IID = #i8192 and RGU_USER_ID = __rdf_repl_uid() and bit_and (RGU_PERMISSIONS, 1) ) )
        signal ('RDF99', 'Can not add graph <' || memb_iri || '> to group of private graphs <' || group_iri || '>; either stop the RDF replication of this graph or grant an explicit read permission to __rdf_repl account');
    }
  else if (group_iri = 'http://www.openlinksw.com/schemas/virtrdf#rdf_repl_graph_group')
    {
      if (memb_iri = DB.DBA.JSO_SYS_GRAPH())
        signal ('RDF99', 'Graph group <' || group_iri || '> is for RDF replication; can not enable RDF replication of <' || memb_iri || '> (the system metadata graph)');
      if (isstring (registry_get ('DB.DBA.RDF_REPL')) and not __rgs_ack_cbk (memb_iid, __rdf_repl_uid(), 1))
        signal ('RDF99', 'Graph group <' || group_iri || '> is for RDF replication; can not enable RDF replication of graph <' || memb_iri || '> because it is not readable by __rdf_repl account');
    }
  insert soft DB.DBA.RDF_GRAPH_GROUP_MEMBER (RGGM_GROUP_IID, RGGM_MEMBER_IID)
  values (group_iid, memb_iid);
  commit work;
  DB.DBA.SECURITY_CL_EXEC_AND_LOG ('DB.DBA.RDF_GRAPH_GROUP_INS_MEMONLY (?, ?, ?, ?)', vector (group_iri, group_iid, memb_iri, memb_iid));
}
;

create procedure DB.DBA.RDF_GRAPH_GROUP_DEL_MEMONLY (in group_iri varchar, in group_iid IRI_ID, in memb_iri varchar, in memb_iid IRI_ID)
{
  declare membs any;
  group_iri := cast (group_iri as varchar);
  memb_iri := cast (memb_iri as varchar);
  DB.DBA.RDF_GRAPH_CACHE_IID (group_iid);
  DB.DBA.RDF_GRAPH_CACHE_IID (memb_iid);
--  This is not scalable enough for big groups: N*N/2 time to drop a group of size N item by item.
--  dict_put (__rdf_graph_group_dict(), group_iid,
--    (select VECTOR_AGG (RGGM_MEMBER_IID) from DB.DBA.RDF_GRAPH_GROUP_MEMBER
--     where RGGM_GROUP_IID = group_iid
--     order by RGGM_MEMBER_IID ) );
  membs := dict_get (__rdf_graph_group_dict(), group_iid, null);
  if (membs is null)
    dict_put (__rdf_graph_group_dict(), group_iid, vector ());
  else if (isvector (membs))
    {
      declare p integer;
again:
      p := position (memb_iid, membs);
      if (p > 0)
        {
          membs := vector_concat (subseq (membs, 0, p-1), subseq (membs, p));
          goto again; -- Paranoidal check for multiple occurencies of memb_iid in the graph group
        }
      dict_put (__rdf_graph_group_dict(), group_iid, membs);
    }
  else
    {
      dict_remove (membs, memb_iid);
      dict_put (__rdf_graph_group_dict(), group_iid, membs);
    }
  jso_mark_affected (group_iri);
  log_text ('jso_mark_affected (?)', group_iri);
  if (group_iri = 'http://www.openlinksw.com/schemas/virtrdf#PrivateGraphs')
    {
      dict_remove (__rdf_graph_group_of_privates_dict(), memb_iid);
      jso_mark_affected (memb_iri);
      log_text ('jso_mark_affected (?)', memb_iri);
    }
}
;

create procedure DB.DBA.RDF_GRAPH_GROUP_DEL (in group_iri varchar, in memb_iri varchar)
{
  declare group_iid, memb_iid IRI_ID;
  group_iri := cast (group_iri as varchar);
  DB.DBA.RDF_GRAPH_GROUP_IRI_CHECK (group_iri, 'DB.DBA.RDF_GRAPH_GROUP_DEL');
  memb_iri := cast (memb_iri as varchar);
  group_iid := iri_to_id (group_iri);
  memb_iid := iri_to_id (memb_iri);
  set isolation = 'serializable';
  commit work;
  if (not exists (select top 1 1 from DB.DBA.RDF_GRAPH_GROUP where RGG_IRI = group_iri))
    signal ('RDF99', sprintf ('Graph group <%s> does not exist', group_iri));
  if (group_iri = 'http://www.openlinksw.com/schemas/virtrdf#PrivateGraphs')
    DB.DBA.RDF_GRAPH_CHECK_VISIBILITY_CHANGE (memb_iri, #i0);
  delete from DB.DBA.RDF_GRAPH_GROUP_MEMBER
  where RGGM_GROUP_IID = group_iid and RGGM_MEMBER_IID = memb_iid;
  commit work;
  DB.DBA.SECURITY_CL_EXEC_AND_LOG ('DB.DBA.RDF_GRAPH_GROUP_DEL_MEMONLY (?, ?, ?, ?)', vector (group_iri, group_iid, memb_iri, memb_iid));
}
;

create function DB.DBA.RDF_GRAPH_USER_PERMS_GET (in graph_iri varchar, in uid any) returns integer
{
  declare graph_iid IRI_ID;
  declare res integer;
  graph_iid := iri_to_id (graph_iri);
  if (isstring (uid))
    uid := ((select U_ID from DB.DBA.SYS_USERS where U_NAME = uid and (U_NAME='nobody' or (U_SQL_ENABLE and not U_ACCOUNT_DISABLED))));
  if (uid is null)
    return 0;
  if (uid = 0)
    return 1023;
  res := coalesce (
    (select RGU_PERMISSIONS from DB.DBA.RDF_GRAPH_USER where RGU_GRAPH_IID = graph_iid and RGU_USER_ID = uid),
    __rdf_graph_approx_perms (graph_iid, uid) );
  return res;
}
;

create function DB.DBA.RDF_GRAPH_USER_PERMS_ACK (in graph_iri any, in uid any, in req_perms integer) returns integer
{
  declare app_cbk, app_uid varchar;
  declare graph_iid IRI_ID;
  declare perms integer;
  -- dbg_obj_princ ('DB.DBA.RDF_GRAPH_USER_PERMS_ACK (', graph_iri, uid, req_perms, ')');
  graph_iid := iri_to_id (graph_iri);
  if (__tag (uid) = __tag of vector)
    {
      app_cbk := uid[1];
      app_uid := uid[2];
      uid := uid[0];
    }
  else
    app_cbk := NULL;
  if (isstring (uid))
    uid := ((select U_ID from DB.DBA.SYS_USERS where U_NAME = uid and (U_NAME='nobody' or (U_SQL_ENABLE and not U_ACCOUNT_DISABLED))));
  if (uid is null)
    perms := 0;
  else if (uid = 0)
    perms := 1023;
  else
    {
      perms := __rdf_graph_approx_perms (graph_iid, uid);
      if (bit_and (perms, req_perms) <> req_perms)
        perms := coalesce ((select RGU_PERMISSIONS from DB.DBA.RDF_GRAPH_USER where RGU_GRAPH_IID = graph_iid and RGU_USER_ID = uid), perms);
    }
  if (bit_and (perms, req_perms) <> req_perms)
    return 0;
  if (app_cbk is not null)
    {
      perms := call (app_cbk)(graph_iid, app_uid);
      if (bit_and (perms, req_perms) <> req_perms)
        return 0;
    }
  return 1;
}
;

--!AWK PUBLIC
create function DB.DBA.RDF_GRAPH_USER_PERM_TITLE (in perms integer) returns varchar
{
  if (bit_and (perms, 1))
    return 'read';
  if (bit_and (perms, 2))
    return 'write';
  if (bit_and (perms, 4))
    return 'sponge';
  if (bit_and (perms, 8))
    return 'get-group-list';
  return sprintf ('"%d"', perms);
}
;

create function DB.DBA.RDF_GRAPH_USER_PERMS_ASSERT (in graph_iri varchar, in uid any, in req_perms integer, in opname varchar) returns varchar
{
  declare app_cbk, app_uid varchar;
  declare graph_iid IRI_ID;
  declare perms integer;
  -- dbg_obj_princ ('DB.DBA.RDF_GRAPH_USER_PERMS_ASSERT (', graph_iri, uid, req_perms, opname, ')');
  return __rgs_assert_cbk (graph_iri, uid, req_perms, opname);
  graph_iid := iri_to_id (graph_iri);
  if (__tag (uid) = __tag of vector)
    {
      app_cbk := uid[1];
      app_uid := uid[2];
      uid := uid[0];
    }
  else
    app_cbk := NULL;
  if (isstring (uid))
    uid := ((select U_ID from DB.DBA.SYS_USERS where U_NAME = uid and (U_NAME='nobody' or (U_SQL_ENABLE and not U_ACCOUNT_DISABLED))));
  if (uid is null)
    perms := 0;
  else if (uid = 0)
    perms := 1023;
  else
    perms := coalesce (
      (select RGU_PERMISSIONS from DB.DBA.RDF_GRAPH_USER where RGU_GRAPH_IID = graph_iid and RGU_USER_ID = uid),
      (select RGU_PERMISSIONS from DB.DBA.RDF_GRAPH_USER where RGU_GRAPH_IID = graph_iid and RGU_USER_ID = http_nobody_uid()),
      (select RGU_PERMISSIONS from DB.DBA.RDF_GRAPH_USER where RGU_GRAPH_IID = #i0 and RGU_USER_ID = uid),
      (select RGU_PERMISSIONS from DB.DBA.RDF_GRAPH_USER where RGU_GRAPH_IID = #i0 and RGU_USER_ID = http_nobody_uid()),
      15 );
  if (bit_and (perms, req_perms) <> req_perms)
    signal ('RDF02', sprintf ('%s access denied: database user %s (%s) has no %s permission on graph %s',
      opname, cast (uid as varchar), coalesce ((select top 1 U_NAME from DB.DBA.SYS_USERS where U_ID=uid)),
      DB.DBA.RDF_GRAPH_USER_PERM_TITLE (bit_and (bit_not (perms), req_perms)),
      graph_iri ) );
  if (app_cbk is not null)
    {
      perms := call (app_cbk)(graph_iid, app_uid);
      if (bit_and (perms, req_perms) <> req_perms)
        signal ('RDF02', sprintf ('%s access denied: application user %s has no %s permission on graph %s',
          opname, cast (uid as varchar), coalesce ((select top 1 U_NAME from DB.DBA.SYS_USERS where U_ID=uid)),
          DB.DBA.RDF_GRAPH_USER_PERM_TITLE (bit_and (bit_not (perms), req_perms)),
          graph_iri ) );
    }
  return graph_iri;
}
;

create procedure DB.DBA.RDF_DEFAULT_USER_PERMS_SET_MEMONLY (in uname varchar, in uid integer, in perms integer, in special_iid IRI_ID, in set_private integer, in affected_jso any)
{
  if (perms is null)
    dict_remove (__rdf_graph_default_perms_of_user_dict (set_private), uid);
  else
    dict_put (__rdf_graph_default_perms_of_user_dict (set_private), uid, perms);
  if (uid = http_nobody_uid())
    {
      if (perms is null)
        dict_remove (__rdf_graph_public_perms_dict(), special_iid);
      else
        dict_put (__rdf_graph_public_perms_dict(), special_iid, perms);
    }
  foreach (varchar jso_key in affected_jso) do
    {
      jso_mark_affected (jso_key);
      log_text ('jso_mark_affected (?)', jso_key);
    }
}
;

create procedure DB.DBA.RDF_DEFAULT_USER_PERMS_SET (in uname varchar, in perms integer, in set_private integer := 0)
{
  declare uid integer;
  declare special_iid IRI_ID;
  declare affected_jso any;
  -- dbg_obj_princ ('gs_hist.sql'); string_to_file ('gs_hist.sql', sprintf ('-- DB.DBA.RDF_DEFAULT_USER_PERMS_SET (''%s'', %d, %d);\n', uname, perms, set_private), -1);
  if (perms is null)
    {
      DB.DBA.RDF_DEFAULT_USER_PERMS_DEL (uname, set_private);
      return;
    }
  uid := (select U_ID from DB.DBA.SYS_USERS where U_NAME = uname and (U_NAME='nobody' or (U_SQL_ENABLE and not U_ACCOUNT_DISABLED)));
  set isolation = 'serializable';
  commit work;
  if (uid is null)
    signal ('RDF99', sprintf ('No active SQL user "%s" found, can not set its default permissions on RDF quad storage', uname));
  if (set_private)
    {
      special_iid := #i8192;
      for (select RGU_GRAPH_IID, RGU_PERMISSIONS from DB.DBA.RDF_GRAPH_USER
        where RGU_GRAPH_IID <> #i0 and RGU_GRAPH_IID <> #i8192 and
          RGU_USER_ID = uid and bit_and (bit_not (RGU_PERMISSIONS), perms) <> 0 and
          exists (select top 1 1 from DB.DBA.RDF_GRAPH_GROUP_MEMBER where
              RGGM_GROUP_IID = iri_to_id ('http://www.openlinksw.com/schemas/virtrdf#PrivateGraphs') and
              RGGM_MEMBER_IID = RGU_GRAPH_IID ) ) do
        signal ('RDF99', sprintf ('Default "private area" permissions of user "%s" on RDF quad store can not become broader than permissions on specific "private" graph <%s>',
            uname, id_to_iri (RGU_GRAPH_IID) ) );
    }
  else
    {
      special_iid := #i0;
      for (select RGU_GRAPH_IID, RGU_PERMISSIONS from DB.DBA.RDF_GRAPH_USER
        where RGU_GRAPH_IID <> #i0 and RGU_GRAPH_IID <> #i8192 and
          RGU_USER_ID = uid and bit_and (bit_not (RGU_PERMISSIONS), perms) <> 0 and
          not exists (select top 1 1 from DB.DBA.RDF_GRAPH_GROUP_MEMBER where
              RGGM_GROUP_IID = iri_to_id ('http://www.openlinksw.com/schemas/virtrdf#PrivateGraphs') and
              RGGM_MEMBER_IID = RGU_GRAPH_IID ) ) do
        signal ('RDF99', sprintf ('Default "world" permissions of user "%s" on RDF quad store can not become broader than permissions on specific "world" graph <%s>',
            uname, id_to_iri (RGU_GRAPH_IID) ) );
    }
  if (uname='nobody')
    {
      for (select RGU_USER_ID, RGU_PERMISSIONS from DB.DBA.RDF_GRAPH_USER
        where RGU_USER_ID <> uid and RGU_GRAPH_IID = special_iid and bit_and (bit_not (RGU_PERMISSIONS), perms) <> 0 ) do
          signal ('RDF99', sprintf ('Default %s permissions of unauthenticated user ("nobody") on RDF quad store can not become broader than permissions of user %s (UID %d)',
            (case (set_private) when 0 then '"world"' else '"private area"' end),
            (select top 1 U_NAME from Db.DBA.SYS_USERS where U_ID = RGU_USER_ID), RGU_USER_ID) );
-- This is not required:
--      for (select RGU_GRAPH_IID, RGU_USER_ID, RGU_PERMISSIONS from DB.DBA.RDF_GRAPH_USER where RGU_USER_ID <> uid and RGU_GRAPH_IID <> #i0 and bit_and (bit_not (RGU_PERMISSIONS), perms) <> 0)
--        signal ('RDF99', sprintf ('Default permissions of unauthenticated user ("nobody") on RDF quad store can not become broader than permissions of user %s (UID %d) on specific graph <%s>',
--          (select top 1 U_NAME from Db.DBA.SYS_USER where U_ID = RGU_USER_ID), RGU_USER_ID, id_to_iri (RGU_GRAPH_IID) ) );
      if (isstring (registry_get ('DB.DBA.RDF_REPL')) and not (bit_and (perms, 1))
        and not exists (select top 1 1 from DB.DBA.RDF_GRAPH_USER where RGU_GRAPH_IID = special_iid and RGU_USER_ID = __rdf_repl_uid() and bit_and (RGU_PERMISSIONS, 1)))
        {
          if (set_private)
            {
              for (select RGGM_MEMBER_IID from DB.DBA.RDF_GRAPH_GROUP_MEMBER repl
                where repl.RGGM_GROUP_IID = iri_to_id (UNAME'http://www.openlinksw.com/schemas/virtrdf#rdf_repl_graph_group')
                  and exists (select top 1 1 from DB.DBA.RDF_GRAPH_GROUP_MEMBER priv
                    where priv.RGGM_GROUP_IID = iri_to_id (UNAME'http://www.openlinksw.com/schemas/virtrdf#PrivateGraphs')
                    and priv.RGGM_MEMBER_IID = repl.RGGM_MEMBER_IID )
                  and not exists (select top 1 1 from DB.DBA.RDF_GRAPH_USER where RGU_GRAPH_IID = repl.RGGM_MEMBER_IID and RGU_USER_ID = __rdf_repl_uid() and bit_and (RGU_PERMISSIONS, 1))
                  and not exists (select top 1 1 from DB.DBA.RDF_GRAPH_USER where RGU_GRAPH_IID = special_iid and RGU_USER_ID = __rdf_repl_uid() and bit_and (RGU_PERMISSIONS, 1)) ) do
                signal ('RDF99', 'Can not disable public read on private area while RDF replication is enabled and the replication account will loose its read permission on, e.g., <' || id_to_iri(RGGM_MEMBER_IID) || '>');
            }
          else
            {
              for (select RGGM_MEMBER_IID from DB.DBA.RDF_GRAPH_GROUP_MEMBER repl
                where repl.RGGM_GROUP_IID = iri_to_id (UNAME'http://www.openlinksw.com/schemas/virtrdf#rdf_repl_graph_group')
                  and not exists (select top 1 1 from DB.DBA.RDF_GRAPH_GROUP_MEMBER priv
                    where priv.RGGM_GROUP_IID = iri_to_id (UNAME'http://www.openlinksw.com/schemas/virtrdf#PrivateGraphs')
                    and priv.RGGM_MEMBER_IID = repl.RGGM_MEMBER_IID )
                  and not exists (select top 1 1 from DB.DBA.RDF_GRAPH_USER where RGU_GRAPH_IID = repl.RGGM_MEMBER_IID and RGU_USER_ID = __rdf_repl_uid() and bit_and (RGU_PERMISSIONS, 1))
                  and not exists (select top 1 1 from DB.DBA.RDF_GRAPH_USER where RGU_GRAPH_IID = special_iid and RGU_USER_ID = __rdf_repl_uid() and bit_and (RGU_PERMISSIONS, 1)) ) do
                signal ('RDF99', 'Can not disable public read on "world" area while RDF replication is enabled and the replication account will loose its read permission on, e.g., <' || id_to_iri(RGGM_MEMBER_IID) || '>');
            }
        }
    }
  if (uname <> 'dba')
    {
      if (not (exists (select top 1 1 from DB.DBA.RDF_GRAPH_USER where RGU_GRAPH_IID = #i0 and RGU_USER_ID = 0)))
        DB.DBA.RDF_DEFAULT_USER_PERMS_SET ('dba', 1023);
      if (not (exists (select top 1 1 from DB.DBA.RDF_GRAPH_USER where RGU_GRAPH_IID = #i8192 and RGU_USER_ID = 0)))
        DB.DBA.RDF_DEFAULT_USER_PERMS_SET ('dba', 1023, 1);
    }
  insert replacing DB.DBA.RDF_GRAPH_USER (RGU_GRAPH_IID, RGU_USER_ID, RGU_PERMISSIONS)
  values (special_iid, uid, perms);
  -- dbg_obj_princ ('gs_hist.sql'); string_to_file ('gs_hist.sql', sprintf ('insert replacing DB.DBA.RDF_GRAPH_USER (RGU_GRAPH_IID, RGU_USER_ID, RGU_PERMISSIONS) values (%s, %d, %d);\n', cast (special_iid as varchar), uid, perms), -1);
  commit work;
  if (uname = 'nobody')
    affected_jso := ((select DB.DBA.VECTOR_AGG (sub."jso_key")
        from (sparql
          define input:storage ""
          prefix virtrdf: <http://www.openlinksw.com/schemas/virtrdf#>
          select (str(?s)) as ?jso_key where {
            graph <http://www.openlinksw.com/schemas/virtrdf#> { { ?s a virtrdf:QuadMap } union { ?s a virtrdf:QuadMap } } } ) sub
            option (QUIETCAST) ));
  else
    affected_jso := vector (uname);
  commit work;
  DB.DBA.SECURITY_CL_EXEC_AND_LOG ('DB.DBA.RDF_DEFAULT_USER_PERMS_SET_MEMONLY (?,?,?,?,?,?)', vector (uname, uid, perms, special_iid, set_private, affected_jso));
}
;

create procedure DB.DBA.RDF_DEFAULT_USER_PERMS_DEL (in uname varchar, in set_private integer := 0)
{
  declare uid integer;
  declare special_iid IRI_ID;
  declare affected_jso any;
  -- dbg_obj_princ ('gs_hist.sql'); string_to_file ('gs_hist.sql', sprintf ('-- DB.DBA.RDF_DEFAULT_USER_PERMS_DEL (''%s'', %d);\n', uname, set_private), -1);
  if (uname in ('nobody', 'dba'))
    signal ('RDF99', sprintf ('Default permissions of "%s" can be changed but can not be deleted', uname));
  uid := (select U_ID from DB.DBA.SYS_USERS where U_NAME = uname);
  set isolation = 'serializable';
  commit work;
  if (uid is null)
    signal ('RDF99', sprintf ('No user "%s" found, can not delete its default permissions on RDF quad storage', uname));
  if (set_private)
    special_iid := #i8192;
  else
    special_iid := #i0;
  delete from DB.DBA.RDF_GRAPH_USER where RGU_GRAPH_IID = special_iid and RGU_USER_ID = uid;
  -- dbg_obj_princ ('gs_hist.sql'); string_to_file ('gs_hist.sql', sprintf ('delete from DB.DBA.RDF_GRAPH_USER where RGU_GRAPH_IID = %s and RGU_USER_ID = %d', cast (special_iid as varchar), uid), -1);
  commit work;
  affected_jso := vector (uname);
  commit work;
  DB.DBA.SECURITY_CL_EXEC_AND_LOG ('DB.DBA.RDF_DEFAULT_USER_PERMS_SET_MEMONLY (?,?,null,?,?,?)', vector (uname, uid, special_iid, set_private, affected_jso));
}
;

create procedure DB.DBA.RDF_VALIDATE_GRAPH_IRI (inout graph_iri any, in check_for_security_config integer)
{
  declare graph_iid IRI_ID;
  if (__tag (graph_iri) = __tag of UNAME)
    graph_iri := cast (graph_iri as varchar);
  else if (__tag (graph_iri) = __tag of IRI_ID)
    {
      declare new_graph_iri varchar;
      new_graph_iri := id_to_iri_nosignal (graph_iri);
      if (new_graph_iri is null)
        signal ('22023', sprintf ('Invalid graph IRI ID #i%d, no such IRI', iri_id_num (graph_iri)));
      graph_iri := new_graph_iri;
    }
  graph_iid := iri_to_id_nosignal (graph_iri);
  if (graph_iid is null)
    signal ('22023', sprintf ('Invalid graph IRI <%.500s>, can not convert to IRI ID', graph_iri));
  if (graph_iid >= min_bnode_iri_id())
    signal ('22023', sprintf ('Invalid graph reference <%.500s>, should be an IRI, not a blank node label', graph_iri));
  if (#i0 = graph_iid)
    signal ('22023', sprintf ('Invalid graph reference <%.500s>, #i0 is not a valid graph IRI ID', graph_iri));
  if (graph_iri = id_to_iri_nosignal (graph_iid))
    return;
  if (check_for_security_config)
    signal ('22023', sprintf ('Graph IRI <%.500s> can not be used in configuring security, because it is <%.500> if converted to IRI ID and back to IRI string', graph_iri, id_to_iri_nosignal (graph_iid)));
}
;

create procedure DB.DBA.RDF_GRAPH_USER_PERMS_SET_MEMONLY (in graph_iri varchar, in graph_iid IRI_ID, in uid integer, in perms integer)
{
  graph_iri := cast (graph_iri as varchar);
  DB.DBA.RDF_GRAPH_CACHE_IID (graph_iid);
  if (uid = http_nobody_uid())
    dict_put (__rdf_graph_public_perms_dict(), graph_iid, perms);
  else
    __rdf_graph_specific_perms_of_user (graph_iid, uid, perms);
  jso_mark_affected (graph_iri);
  log_text ('jso_mark_affected (?)', graph_iri);
}
;

create procedure DB.DBA.RDF_GRAPH_USER_PERMS_SET (in graph_iri varchar, in uname varchar, in perms integer)
{
  declare graph_iid IRI_ID;
  declare uid, graph_is_private, common_perms integer;
  declare special_iid IRI_ID;
  -- dbg_obj_princ ('gs_hist.sql'); string_to_file ('gs_hist.sql', sprintf ('-- DB.DBA.RDF_GRAPH_USER_PERMS_SET (''%s'', ''%s'', %d);\n', graph_iri, uname, perms), -1);
  DB.DBA.RDF_VALIDATE_GRAPH_IRI (graph_iri, 1);
  if (perms is null)
    {
      RDF_GRAPH_USER_PERMS_DEL (graph_iri, uname);
      return;
    }
  graph_iid := iri_to_id (graph_iri);
  uid := ((select U_ID from DB.DBA.SYS_USERS where U_NAME = uname and (U_NAME='nobody' or (U_SQL_ENABLE and not U_ACCOUNT_DISABLED))));
  set isolation = 'serializable';
  commit work;
  if (uid is null)
    signal ('RDF99', sprintf ('No active SQL user "%s" found, can not set its permissions on graph <%s>', uname, graph_iri));
  graph_is_private := (select count (1) from DB.DBA.RDF_GRAPH_GROUP_MEMBER where
    RGGM_GROUP_IID = iri_to_id ('http://www.openlinksw.com/schemas/virtrdf#PrivateGraphs') and
    RGGM_MEMBER_IID = graph_iid );
  if (graph_is_private)
    special_iid := #i8192;
  else
    special_iid := #i0;
  common_perms := coalesce (
    (select RGU_PERMISSIONS from DB.DBA.RDF_GRAPH_USER where RGU_GRAPH_IID = special_iid and RGU_USER_ID = uid),
    (select RGU_PERMISSIONS from DB.DBA.RDF_GRAPH_USER where RGU_GRAPH_IID = special_iid and RGU_USER_ID = http_nobody_uid()),
    15 );
  if (bit_and (bit_not (perms), common_perms))
    signal ('RDF99', sprintf ('Default permissions of user "%s" on RDF quad store are broader than new permissions on specific graph <%s>', uname, graph_iri));
  if (uname = 'nobody')
    {
      if (isstring (registry_get ('DB.DBA.RDF_REPL')) and not (bit_and (perms, 1))
        and exists (select top 1 1 from DB.DBA.RDF_GRAPH_GROUP_MEMBER
          where RGGM_GROUP_IID = iri_to_id (UNAME'http://www.openlinksw.com/schemas/virtrdf#rdf_repl_graph_group') and RGGM_MEMBER_IID = graph_iid)
        and not exists (select top 1 1 from DB.DBA.RDF_GRAPH_USER where RGU_GRAPH_IID = graph_iid and RGU_USER_ID = __rdf_repl_uid() and bit_and (RGU_PERMISSIONS, 1))
        and not exists (select top 1 1 from DB.DBA.RDF_GRAPH_USER where RGU_GRAPH_IID = special_iid and RGU_USER_ID = __rdf_repl_uid() and bit_and (RGU_PERMISSIONS, 1)) )
        signal ('RDF99', 'Can not disable public read access to <' || id_to_iri (graph_iid) || '> while it is included in RDF replication and the replication is enabled and the replication account will loose its read permission');
      jso_mark_affected (graph_iri);
      log_text ('jso_mark_affected (?)', graph_iri);
    }
  else
    {
      common_perms := coalesce (
        (select RGU_PERMISSIONS from DB.DBA.RDF_GRAPH_USER where RGU_GRAPH_IID = graph_iid and RGU_USER_ID = http_nobody_uid()),
        (select RGU_PERMISSIONS from DB.DBA.RDF_GRAPH_USER where RGU_GRAPH_IID = special_iid and RGU_USER_ID = http_nobody_uid()),
        15 );
      if (bit_and (bit_not (perms), common_perms))
        signal ('RDF99', sprintf ('Permissions of unauthenticated user are broader than new permissions of user "%s" on specific graph <%s>', uname, graph_iri));
      if ((uname = '__rdf_repl') and isstring (registry_get ('DB.DBA.RDF_REPL')) and not (bit_and (perms, 1)) and
            exists (select top 1 1 from DB.DBA.RDF_GRAPH_GROUP_MEMBER
              where RGGM_GROUP_IID = iri_to_id (UNAME'http://www.openlinksw.com/schemas/virtrdf#rdf_repl_graph_group') and RGGM_MEMBER_IID = graph_iid) )
        signal ('RDF99', 'Can not disable read access of __rdf_repl account to <' || id_to_iri (graph_iid) || '> while it is included in RDF replication and the replication is enabled');
    }
  if (not (exists (select top 1 1 from DB.DBA.RDF_GRAPH_USER where RGU_GRAPH_IID = #i0 and RGU_USER_ID = 0)))
    DB.DBA.RDF_DEFAULT_USER_PERMS_SET ('dba', 1023);
  if (not (exists (select top 1 1 from DB.DBA.RDF_GRAPH_USER where RGU_GRAPH_IID = #i8192 and RGU_USER_ID = 0)))
    DB.DBA.RDF_DEFAULT_USER_PERMS_SET ('dba', 1023, 1);
  insert replacing DB.DBA.RDF_GRAPH_USER (RGU_GRAPH_IID, RGU_USER_ID, RGU_PERMISSIONS)
  values (graph_iid, uid, perms);
  -- dbg_obj_princ ('gs_hist.sql'); string_to_file ('gs_hist.sql', sprintf ('insert replacing DB.DBA.RDF_GRAPH_USER (RGU_GRAPH_IID, RGU_USER_ID, RGU_PERMISSIONS) values (%s, %d, %d);\n', cast (graph_iid as varchar), uid, perms), -1);
  commit work;
  DB.DBA.SECURITY_CL_EXEC_AND_LOG ('DB.DBA.RDF_GRAPH_USER_PERMS_SET_MEMONLY (?,?,?,?)', vector (graph_iri, graph_iid, uid, perms));
}
;

create procedure DB.DBA.RDF_GRAPH_USER_PERMS_DEL_MEMONLY (in graph_iri varchar, in graph_iid IRI_ID, in uid integer)
{
  graph_iri := cast (graph_iri as varchar);
  DB.DBA.RDF_GRAPH_CACHE_IID (graph_iid);
  if (uid = http_nobody_uid())
    dict_remove (__rdf_graph_public_perms_dict(), graph_iid);
  else
    __rdf_graph_specific_perms_of_user (graph_iid, uid, -1);
  jso_mark_affected (graph_iri);
  log_text ('jso_mark_affected (?)', graph_iri);
}
;

create procedure DB.DBA.RDF_GRAPH_USER_PERMS_DEL (in graph_iri varchar, in uname varchar)
{
  declare graph_iid IRI_ID;
  declare uid integer;
  declare special_iid IRI_ID;
  -- dbg_obj_princ ('gs_hist.sql'); string_to_file ('gs_hist.sql', sprintf ('-- DB.DBA.RDF_GRAPH_USER_PERMS_SET (''%s'', ''%s'', %d);\n', graph_iri, uname, perms), -1);
  DB.DBA.RDF_VALIDATE_GRAPH_IRI (graph_iri, 1);
  graph_iid := iri_to_id (graph_iri);
  uid := (select U_ID from DB.DBA.SYS_USERS where U_NAME = uname);
  set isolation = 'serializable';
  commit work;
  if (uid is null)
    signal ('RDF99', sprintf ('No user "%s" found, can not change its permissions on graph <%s>', uname, graph_iri));
  delete from DB.DBA.RDF_GRAPH_USER where RGU_GRAPH_IID = graph_iid and RGU_USER_ID = uid;
  -- dbg_obj_princ ('gs_hist.sql'); string_to_file ('gs_hist.sql', sprintf ('delete from DB.DBA.RDF_GRAPH_USER where RGU_GRAPH_IID = %s and RGU_USER_ID = %d;\n', cast (graph_iid as varchar), uid), -1);
  commit work;
  DB.DBA.SECURITY_CL_EXEC_AND_LOG ('DB.DBA.RDF_GRAPH_USER_PERMS_DEL_MEMONLY (?,?,?)', vector (graph_iri, graph_iid, uid));
}
;

create procedure DB.DBA.RDF_ALL_USER_PERMS_DEL (in uname varchar, in uid integer := null)
{
  declare special_iid IRI_ID;
  declare graphs any;
  declare graphs_count, graphs_ctr integer;
  -- dbg_obj_princ ('gs_hist.sql'); string_to_file ('gs_hist.sql', sprintf ('-- DB.DBA.RDF_ALL_USER_PERMS_DEL (''%s'', %s);\n', uname, case (isnotnull (uid)) when 0 then 'null' else cast (uid as varchar) end), -1);
  set isolation = 'serializable';
  commit work;
  if (uid is null)
    {
      uid := (select U_ID from DB.DBA.SYS_USERS where U_NAME = uname);
      if (uid is null)
        signal ('RDF99', sprintf ('No user "%s" found, can not change its permissions on RDF graphs', uname));
    }
  if (uname is null)
    uname := (select U_NAME from DB.DBA.SYS_USERS where U_ID = uid);
  if (uid = http_nobody_uid() or uid = 0)
    {
      graphs := (select DB.DBA.VECTOR_AGG (RGU_GRAPH_IID) from DB.DBA.RDF_GRAPH_USER where RGU_USER_ID = uid and not (RGU_GRAPH_IID in (#i0, #i8192)));
      delete from DB.DBA.RDF_GRAPH_USER where RGU_USER_ID = uid and not (RGU_GRAPH_IID in (#i0, #i8192));
    }
  else
    {
      graphs := (select DB.DBA.VECTOR_AGG (RGU_GRAPH_IID) from DB.DBA.RDF_GRAPH_USER where RGU_USER_ID = uid);
      delete from DB.DBA.RDF_GRAPH_USER where RGU_USER_ID = uid;
    }
  gvector_digit_sort (graphs, 1, 0, 1);
  -- dbg_obj_princ ('graphs=', graphs);
  graphs_count := length (graphs);
  for (graphs_ctr := graphs_count-1; graphs_ctr >= 0; graphs_ctr := graphs_ctr-1)
    {
      declare g_iid IR_ID;
      g_iid := graphs [graphs_ctr];
      if (g_iid = #i0 or g_iid = #i8192)
        {
          declare affected_jso any;
          if (uname is null)
            affected_jso := vector ();
          else
            affected_jso := vector (uname);
          DB.DBA.SECURITY_CL_EXEC_AND_LOG ('DB.DBA.RDF_DEFAULT_USER_PERMS_SET_MEMONLY (?,?,null,?,?,?)',
            vector (uname, uid, g_iid, case (g_iid) when #i8192 then 1 else 0 end, affected_jso));
        }
      else
        DB.DBA.SECURITY_CL_EXEC_AND_LOG ('DB.DBA.RDF_GRAPH_USER_PERMS_DEL_MEMONLY (?,?,?)', vector (id_to_iri_nosignal (g_iid), g_iid, uid));
    }
  commit work;
}
;

create function DB.DBA.RDF_GRAPH_GROUP_LIST_GET (in group_iri any, in extra_graphs any, in uid any, in gs_app_cbk varchar, in gs_app_uid varchar, in req_perms integer) returns any
{
  declare group_iid IRI_ID;
  declare world_perms, private_perms, common_perms, perms integer;
  declare perms_dict, full_list, filtered_list any;
  -- dbg_obj_princ ('DB.DBA.RDF_GRAPH_GROUP_LIST_GET (', group_iri, extra_graphs, uid, req_perms, ')');
  if (isstring (uid))
    uid := ((select U_ID from DB.DBA.SYS_USERS where U_NAME = uid and (U_NAME='nobody' or (U_SQL_ENABLE and not U_ACCOUNT_DISABLED))));
  if (uid is null)
    return vector ();
  perms_dict := __rdf_graph_default_perms_of_user_dict(0);
  world_perms := coalesce (
    dict_get (perms_dict, uid, NULL),
    dict_get (perms_dict, http_nobody_uid(), NULL),
    15 );
  perms_dict := __rdf_graph_default_perms_of_user_dict(1);
  private_perms := coalesce (
    dict_get (perms_dict, uid, NULL),
    dict_get (perms_dict, http_nobody_uid(), NULL),
    15 );
  if (gs_app_cbk is not null)
    {
      world_perms := bit_and (world_perms, call (gs_app_cbk)(#i0, gs_app_uid));
      private_perms := bit_and (private_perms, call (gs_app_cbk)(#i8192, gs_app_uid));
    }
  common_perms := bit_and (world_perms, private_perms);
  -- dbg_obj_princ ('DB.DBA.RDF_GRAPH_GROUP_LIST_GET: common_perms = ', common_perms);
  if (__tag (group_iri) = __tag of vector)
    {
      vectorbld_init (full_list);
      foreach (any g_iri in group_iri) do
        {
          group_iid := iri_to_id (g_iri);
          if (not bit_and (common_perms, 8))
            {
              perms := __rdf_graph_approx_perms (group_iid, uid);
              if (not bit_and (perms, 8))
                perms := coalesce (
                  (select RGU_PERMISSIONS from DB.DBA.RDF_GRAPH_USER where RGU_GRAPH_IID = group_iid and RGU_USER_ID = uid),
                  perms );
              if (gs_app_cbk is not null and bit_and (perms, 8))
                perms := bit_and (perms, call (gs_app_cbk)(group_iid, gs_app_uid));
              -- dbg_obj_princ ('DB.DBA.RDF_GRAPH_GROUP_LIST_GET: perms for list = ', perms);
            }
          else
            perms := common_perms;
          if (bit_and (perms, 8))
            {
              declare membs any;
              membs := dict_get (__rdf_graph_group_dict(), group_iid, null);
              if (membs is not null)
                {
                  if (isvector (membs))
                    vectorbld_concat_acc (full_list, membs);
                  else
                    vectorbld_concat_acc (full_list, dict_list_keys (membs, 0));
                }
            }
        }
      vectorbld_final (full_list);
    }
  else
    {
      group_iid := iri_to_id (group_iri);
      if (not bit_and (common_perms, 8))
        {
          perms := __rdf_graph_approx_perms (group_iid, uid);
          if (not bit_and (perms, 8))
            perms := coalesce (
              (select RGU_PERMISSIONS from DB.DBA.RDF_GRAPH_USER where RGU_GRAPH_IID = group_iid and RGU_USER_ID = uid),
              perms );
          if (gs_app_cbk is not null and bit_and (perms, 8))
            perms := bit_and (perms, call (gs_app_cbk)(group_iid, gs_app_uid));
          -- dbg_obj_princ ('DB.DBA.RDF_GRAPH_GROUP_LIST_GET: perms for list = ', perms);
        }
      else
        perms := common_perms;
      if (bit_and (perms, 8))
        {
          full_list := dict_get (__rdf_graph_group_dict(), group_iid, null);
          if (full_list is null)
            full_list := vector ();
          else if (not isvector (full_list))
            full_list := dict_list_keys (full_list, 0);
        }
      else
        full_list := vector ();
    }
  if (bit_and (common_perms, req_perms) = req_perms)
    {
      declare ctr integer;
      if (extra_graphs is null)
        return full_list;
      ctr := length (extra_graphs);
      while (ctr > 0)
        {
          ctr := ctr - 1;
          extra_graphs [ctr] := iri_to_id (extra_graphs[ctr]);
        }
      full_list := vector_concat (full_list, extra_graphs);
      gvector_digit_sort (full_list, 1, 0, 1);
      return full_list;
    }
  vectorbld_init (filtered_list);
  foreach (IRI_ID member_iid in full_list) do
    {
      perms := __rdf_graph_approx_perms (member_iid, uid);
      if (bit_and (perms, req_perms) <> req_perms)
        perms := coalesce (
          (select RGU_PERMISSIONS from DB.DBA.RDF_GRAPH_USER where RGU_GRAPH_IID = member_iid and RGU_USER_ID = uid),
          dict_get (__rdf_graph_public_perms_dict(), member_iid, NULL),
          perms );
      if (gs_app_cbk is not null and bit_and (perms, req_perms) = req_perms)
        perms := bit_and (perms, call (gs_app_cbk)(member_iid, gs_app_uid));
      -- dbg_obj_princ ('DB.DBA.RDF_GRAPH_GROUP_LIST_GET: perms for ', member_iid, ' = ', perms);
      if (bit_and (perms, req_perms) = req_perms)
        vectorbld_acc (filtered_list, member_iid);
    }
  foreach (any g in extra_graphs) do
    {
      declare g_iid IRI_ID;
      g_iid := iri_to_id (g);
      perms := coalesce (
        (select RGU_PERMISSIONS from DB.DBA.RDF_GRAPH_USER where RGU_GRAPH_IID = g_iid and RGU_USER_ID = uid),
        dict_get (__rdf_graph_public_perms_dict(), g_iid, NULL),
        common_perms );
      if (gs_app_cbk is not null and bit_and (perms, req_perms) = req_perms)
        perms := bit_and (perms, call (gs_app_cbk)(g_iid, gs_app_uid));
      -- dbg_obj_princ ('DB.DBA.RDF_GRAPH_GROUP_LIST_GET: perms for ', g_iid, ' = ', perms);
      if (bit_and (perms, req_perms) = req_perms)
        vectorbld_acc (filtered_list, g_iid);
    }
  vectorbld_final (filtered_list);
  gvector_digit_sort (filtered_list, 1, 0, 1);
  return filtered_list;
}
;

create procedure DB.DBA.RDF_GRAPH_SECURITY_AUDIT (in recovery integer)
{
  declare SEVERITY, GRAPH_IRI, USER_NAME, MESSAGE varchar;
  declare GRAPH_IID IRI_ID;
  declare USER_ID integer;
  result_names (SEVERITY, GRAPH_IID, GRAPH_IRI, USER_ID, USER_NAME, MESSAGE);
  declare mem_dict, mem_dict_inv, pg_mem_dict, mem_vec, mem_vec_inv, fake any;
  declare mem_ctr, mem_count, pg_count, err_bad_count, err_recoverable_count, err_recoverable_count_total, err_count_total integer;
  declare sparql_u_id integer;
  declare user_sparql_half_protects_from_extra_access integer;
  err_recoverable_count_total := 0; err_count_total := 0;
  sparql_u_id := (select U_ID from DB.DBA.SYS_USERS where U_NAME='SPARQL');
  -- dbg_obj_princ ('Starting RDF Graph Security Audit, please be patient.');
  result ('', null, null, null, null, 'Inspecting caches of IRI_IDs of IRIs mentioned in security data...');
  err_bad_count := 0;
  err_recoverable_count := 0;
  mem_dict := __rdf_graph_iri2id_dict();
  mem_dict_inv := __rdf_graph_id2iri_dict();
  if (dict_size (mem_dict_inv) <> dict_size (mem_dict))
    {
      result ('ERROR', null, null, null, null,
        sprintf ('Cache of IRI_IDs of IRIs contains %d items, but cache of IRIs of IRI_IDs contains %d items, mismatch',
        dict_size (mem_dict_inv), dict_size (mem_dict) ) );
      err_recoverable_count := err_recoverable_count + 1;
    }
  mem_vec := dict_to_vector (mem_dict, 0);
  mem_vec_inv := dict_to_vector (mem_dict_inv, 0);
  mem_count := length (mem_vec);
  -- dbg_obj_princ ('Inspecting ', mem_count, ' IRIs cached in memory for structures related to security and graph groups...');
  for (mem_ctr := 0; mem_ctr < mem_count; mem_ctr := mem_ctr + 2)
    {
      declare iri varchar;
      declare iid IRI_ID;
      iri := mem_vec[mem_ctr];
      iid := mem_vec[mem_ctr+1];
      if ((__tag (iri) <> __tag of UNAME) or (__tag (iid) <> __tag of IRI_ID))
        {
          result ('ERROR', null, null, null, null,
            sprintf ('Unexpected datatypes: tag of IRI "%.300s" is %d, tag of IRI_ID "%.300s" is %d; should be %d and %d',
              cast (iri as varchar), __tag (iri),
              cast (iid as varchar), __tag (iid),
              __tag of UNAME, __tag of IRI_ID ) );
          err_recoverable_count := err_recoverable_count + 1;
          if (recovery)
            {
              dict_remove (mem_dict, iri);
              dict_remove (mem_dict_inv, iid);
            }
        }
      else if (iri_to_id (iri) is null or (iri_to_id (iri) <> iid))
        {
          result ('ERROR', null, null, null, null,
            sprintf ('Cached IRI_ID of IRI <%.300s> is %s, actual is %s, mismatch',
              cast (iri as varchar), cast (iid as varchar), cast (iri_to_id (iri) as varchar) ) );
          err_recoverable_count := err_recoverable_count + 1;
          if (recovery)
            {
              iid := iri_to_id_nosignal (iri);
              if (iid is not null)
                {
                  dict_put (mem_dict, iri, iid);
                  dict_put (mem_dict_inv, iid, iri);
                }
              else
                {
                  dict_remove (mem_dict, iri);
                  dict_remove (mem_dict_inv, iid);
                }
            }
        }
      else if (iid is not null and not (dict_contains_key (mem_dict_inv, iid)))
        {
          result ('ERROR', null, null, null, null,
            sprintf ('Cached IRI_ID of IRI <%.300s> is %s, but the IRI corresponding to %s is missing in the id_to_iri cache',
              cast (iri as varchar), cast (iid as varchar), cast (iid as varchar) ) );
          err_recoverable_count := err_recoverable_count + 1;
          if (recovery)
            dict_put (mem_dict_inv, iid, iri);
        }
      if (0 = mod (mem_ctr, 100000))
        {
          -- dbg_obj_princ ('...', mem_ctr, '/', mem_count, ' IRIs passed forward check...');
          ;
        }
    }
  mem_count := length (mem_vec_inv);
  -- dbg_obj_princ ('Inspecting ', mem_count, ' IRI IDs cached in memory for structures related to security and graph groups');
  for (mem_ctr := 0; mem_ctr < mem_count; mem_ctr := mem_ctr + 2)
    {
      declare iid IRI_ID;
      declare iri varchar;
      iid := mem_vec_inv[mem_ctr];
      iri := mem_vec_inv[mem_ctr+1];
      if ((__tag (iid) <> __tag of IRI_ID) or (__tag (iri) <> __tag of UNAME))
        {
          result ('ERROR', null, null, null, null,
            sprintf ('Unexpected datatypes: tag of IRI_ID "%.300s" is %d, tag of IRI "%.300s" is %d; should be %d and %d',
              cast (iid as varchar), __tag (iid),
              cast (iri as varchar), __tag (iri),
              __tag of IRI_ID, __tag of UNAME ) );
          err_recoverable_count := err_recoverable_count + 1;
          if (recovery)
            {
              dict_remove (mem_dict, iri);
              dict_remove (mem_dict_inv, iid);
            }
        }
      else if (id_to_iri (iid) is null or __uname (id_to_iri (iid)) <> iri)
        {
          result ('ERROR', null, null, null, null,
            sprintf ('Cached IRI of IRI_ID %s is <%.300s>, actual is <%.300s>, mismatch',
              cast (iid as varchar), cast (iri as varchar), cast (id_to_iri (iid) as varchar) ) );
          err_recoverable_count := err_recoverable_count + 1;
          if (recovery)
            {
              iri := id_to_iri_nosignal (iid);
              if (iri is not null)
                {
                  iri := __uname (iri);
                  dict_put (mem_dict_inv, iid, iri);
                  dict_put (mem_dict, iri, iid);
                }
              else
                {
                  dict_remove (mem_dict, iri);
                  dict_remove (mem_dict_inv, iid);
                }
            }
        }
      else if (iri_to_id (iri) is null or (iri_to_id (iri) <> iid))
        {
          result ('ERROR', null, null, null, null,
            sprintf ('Cached IRI_ID of IRI <%.300s> is %s, actual is %s, mismatch',
              cast (iri as varchar), cast (iid as varchar), cast (iri_to_id (iri) as varchar) ) );
          err_recoverable_count := err_recoverable_count + 1;
          if (recovery)
            {
              iid := iri_to_id_nosignal (iri);
              if (iid is not null)
                {
                  dict_put (mem_dict, iri, iid);
                  dict_put (mem_dict_inv, iid, iri);
                }
              else
                {
                  dict_remove (mem_dict, iri);
                  dict_remove (mem_dict_inv, iid);
                }
            }
        }
      else if (iri is not null and not (dict_contains_key (mem_dict, iri)))
        {
          result ('ERROR', null, null, null, null,
            sprintf ('Cached IRI of IRI_ID %s us <%.300s>, but the IRI_ID corresponding to that IRI is missing in the iri_to_id cache',
              cast (iid as varchar), cast (iri as varchar), cast (iri as varchar) ) );
          err_recoverable_count := err_recoverable_count + 1;
          if (recovery)
            dict_put (mem_dict, iri, iid);
        }
      if (0 = mod (mem_ctr, 100000))
        {
          -- dbg_obj_princ ('...', mem_ctr, '/', mem_count, ' IRI IDs passed reverse check');
          ;
        }
    }
  if (err_recoverable_count)
    {
      if (not recovery)
        {
          result ('FATAL', null, null, null, null,
            sprintf ('%d errors need urgent recovery, the rest of security audit has little sence while these errors persist',
              err_recoverable_count ) );
          return;
        }
      else
        {
          if (dict_size (mem_dict_inv) <> dict_size (mem_dict))
            {
              result ('FATAL', null, null, null, null,
                sprintf ('Cache of IRI_IDs of IRIs contains %d items, but cache of IRIs of IRI_IDs contains %d items, mismatch even after recovery',
                dict_size (mem_dict_inv), dict_size (mem_dict) ) );
              err_bad_count := err_bad_count + 1;
            }
        }
    }
  err_count_total := err_count_total + err_recoverable_count + err_bad_count;
  err_recoverable_count_total := err_recoverable_count_total + err_recoverable_count;
  -- dbg_obj_princ ('Inspecting completeness of IRI cache for graph groups...');
  result ('', null, null, null, null, 'Inspecting completeness of IRI cache for graph groups...');
  err_bad_count := 0;
  err_recoverable_count := 0;
  for (select RGG_IID from DB.DBA.RDF_GRAPH_GROUP where dict_get (__rdf_graph_id2iri_dict(), RGG_IID, null) is null
    union select RGGM_GROUP_IID from DB.DBA.RDF_GRAPH_GROUP_MEMBER where dict_get (__rdf_graph_id2iri_dict(), RGGM_GROUP_IID, null) is null
    for update ) do
    {
      if (id_to_iri_nosignal (RGG_IID) is null)
        result ('ERROR', RGG_IID, null, null, null,
          sprintf ('The IRI_ID %s of a graph group does not correspond to any IRI',
            cast (RGG_IID as varchar) ) );
      else
        result ('ERROR', RGG_IID, id_to_iri_nosignal (RGG_IID), null, null,
          sprintf ('The IRI <%.300s> of graph group IRI_ID %s is not cached',
            id_to_iri_nosignal (RGG_IID), cast (RGG_IID as varchar) ) );
      err_recoverable_count := err_recoverable_count + 1;
    }
  if (err_recoverable_count and recovery)
    {
      -- dbg_obj_princ ('Erasing invalid graph groups...');
      delete from DB.DBA.RDF_GRAPH_GROUP where id_to_iri_nosignal (RGG_IID) is null;
      -- dbg_obj_princ ('Erasing membership data about invalid graph groups...');
      delete from DB.DBA.RDF_GRAPH_GROUP_MEMBER where id_to_iri_nosignal (RGGM_GROUP_IID) is null;
      commit work;
      -- dbg_obj_princ ('Updating IRI caches for graph groups...');
      fake := (select
          count (dict_put (__rdf_graph_iri2id_dict(), __uname (id_to_canonicalized_iri (RGG_IID)), RGG_IID)) +
          count (dict_put (__rdf_graph_id2iri_dict(), RGG_IID, __uname (id_to_canonicalized_iri (RGG_IID))))
          from DB.DBA.RDF_GRAPH_GROUP );
      -- dbg_obj_princ ('Updating IRI caches for graph groups via memberships...');
      fake := (select
          count (dict_put (__rdf_graph_iri2id_dict(), __uname (id_to_canonicalized_iri (RGGM_GROUP_IID)), RGGM_GROUP_IID)) +
          count (dict_put (__rdf_graph_id2iri_dict(), RGGM_GROUP_IID, __uname (id_to_canonicalized_iri (RGGM_GROUP_IID))))
          from DB.DBA.RDF_GRAPH_GROUP_MEMBER );
      -- dbg_obj_princ ('Updating IRI caches for graph group members...');
      fake := (select
          count (dict_put (__rdf_graph_iri2id_dict(), __uname (id_to_canonicalized_iri (RGGM_MEMBER_IID)), RGGM_MEMBER_IID)) +
          count (dict_put (__rdf_graph_id2iri_dict(), RGGM_MEMBER_IID, __uname (id_to_canonicalized_iri (RGGM_MEMBER_IID))))
          from DB.DBA.RDF_GRAPH_GROUP_MEMBER );
    }
  err_count_total := err_count_total + err_recoverable_count + err_bad_count;
  err_recoverable_count_total := err_recoverable_count_total + err_recoverable_count;
  -- dbg_obj_princ ('Inspecting completeness of IRI cache for graph group members...');
  result ('', null, null, null, null, 'Inspecting completeness of IRI cache for graph group members...');
  err_bad_count := 0;
  err_recoverable_count := 0;
  for (select RGGM_MEMBER_IID, min (RGGM_GROUP_IID) as SAMPLE_GROUP_IID from DB.DBA.RDF_GRAPH_GROUP_MEMBER where dict_get (__rdf_graph_id2iri_dict(), RGGM_MEMBER_IID, null) is null group by RGGM_MEMBER_IID for update) do
    {
      if (id_to_iri_nosignal (RGGM_MEMBER_IID) is null)
        result ('ERROR', RGGM_MEMBER_IID, null, null, null,
          sprintf ('The IRI_ID %s of a member of a graph group <%.300s> does not correspond to any IRI',
            cast (RGGM_MEMBER_IID as varchar), id_to_iri_nosignal (SAMPLE_GROUP_IID) ) );
      else
        result ('ERROR', RGGM_MEMBER_IID, id_to_iri_nosignal (RGGM_MEMBER_IID), null, null,
          sprintf ('The IRI <%.300s> of IRI_ID %s of the member of a graph group <%.300s> is not cached',
            id_to_iri_nosignal (RGGM_MEMBER_IID), cast (RGGM_MEMBER_IID as varchar), id_to_iri_nosignal (SAMPLE_GROUP_IID) ) );
      err_recoverable_count := err_recoverable_count + 1;
    }
  if (err_recoverable_count and recovery)
    {
      delete from DB.DBA.RDF_GRAPH_GROUP_MEMBER where id_to_iri_nosignal (RGGM_MEMBER_IID) is null;
      commit work;
      fake := (select
          count (dict_put (__rdf_graph_iri2id_dict(), __uname (id_to_iri (RGGM_MEMBER_IID)), RGGM_MEMBER_IID)) +
          count (dict_put (__rdf_graph_id2iri_dict(), RGGM_MEMBER_IID, __uname (id_to_iri (RGGM_MEMBER_IID))))
          from DB.DBA.RDF_GRAPH_GROUP_MEMBER );
    }
  err_count_total := err_count_total + err_recoverable_count + err_bad_count;
  err_recoverable_count_total := err_recoverable_count_total + err_recoverable_count;

  -- dbg_obj_princ ('Check for mismatches between graph group IRIs and graph group IRI_IDs...');
  result ('', null, null, null, null, 'Check for mismatches between graph group IRIs and graph group IRI_IDs...');
  err_bad_count := 0;
  err_recoverable_count := 0;
  for (select RGG_IID, id_to_iri_nosignal (RGG_IID) as actual_iri, RGG_IRI from DB.DBA.RDF_GRAPH_GROUP where id_to_iri_nosignal (RGG_IID) <> __bft (RGG_IRI, 1)) do
    {
      if (actual_iri is not null)
        {
          result ('ERROR', RGG_IID, actual_iri, null, null,
            sprintf ('The IRI_ID %s of a graph group is the IRI_ID of <%.300s> IRI whereas the group declaration states it is supposed to be <%.300s>',
              cast (RGG_IID as varchar), actual_iri, RGG_IRI ) );
          err_recoverable_count := err_recoverable_count + 1;
        }
    }
  for (select RGG_IID, id_to_iri_nosignal (RGG_IID) as actual_iri, RGG_IRI from DB.DBA.RDF_GRAPH_GROUP
    where (id_to_iri_nosignal (RGG_IID) <> __bft (RGG_IRI, 1))
    and (id_to_iri_nosignal (RGG_IID) = 'http://www.openlinksw.com/schemas/virtrdf#PrivateGraphs' or RGG_IRI = 'http://www.openlinksw.com/schemas/virtrdf#PrivateGraphs') ) do
    {
      result ('FATAL', RGG_IID, actual_iri, null, null,
        sprintf ('The IRI_ID and IRI of a virtrdf:PrivateGraphs graph group does not match to each other, it means that some application has made a security hole. You may wish to disable any access to the database while the error is not fixed.',
          cast (RGG_IID as varchar), actual_iri, RGG_IRI ) );
      return;
    }
  -- dbg_obj_princ ('Check for memberships in nonexisting graph groups...');
  for (select distinct RGGM_GROUP_IID as new_group_iid, iri_to_id_nosignal (RGGM_GROUP_IID) as new_group_iri from DB.DBA.RDF_GRAPH_GROUP_MEMBER
    where not exists (select 1 from DB.DBA.RDF_GRAPH_GROUP where RGG_IID = RGGM_GROUP_IID) for update) do
    {
      if (new_group_iri is null)
        {
          result ('ERROR', new_group_iid, new_group_iri, null, null,
            sprintf ('Garbage in list of members of all groups: the group does not exist and group IRI ID is invalid') );
          err_recoverable_count := err_recoverable_count + 1;
          if (recovery)
            {
              delete from DB.DBA.RDF_GRAPH_GROUP_MEMBER where RGGM_GROUP_IID = new_group_iid;
              commit work;
            }
        }
      else if (exists (select 1 from DB.DBA.RDF_GRAPH_GROUP where RGG_IRI = new_group_iri))
        {
          result ('ERROR', new_group_iid, new_group_iri, null, null,
            sprintf ('Conflicting data in list of groups: the group does not exist, the group IRI is used in a corrupted group record') );
          err_recoverable_count := err_recoverable_count + 1;
          if (recovery)
            {
              delete from DB.DBA.RDF_GRAPH_GROUP_MEMBER where RGGM_GROUP_IID = new_group_iid;
              commit work;
            }
        }
      else
        {
          result ('ERROR', new_group_iid, new_group_iri, null, null,
            sprintf ('The record about graph group does not exist but there exists a list of members') );
          err_recoverable_count := err_recoverable_count + 1;
          if (recovery)
            {
              insert soft DB.DBA.RDF_GRAPH_GROUP (RGG_IID, RGG_IRI, RGG_MEMBER_PATTERN, RGG_COMMENT)
              values (new_group_iid, new_group_iri, NULL, sprintf ('Group created %s by DB.DBA.RDF_GRAPH_SECURITY_AUDIT()', cast (now() as varchar)));
              commit work;
            }
        }
    }
  err_count_total := err_count_total + err_recoverable_count + err_bad_count;
  err_recoverable_count_total := err_recoverable_count_total + err_recoverable_count;

  result ('', null, null, null, null, 'Inspecting caching of list of private graphs...');
  err_bad_count := 0;
  err_recoverable_count := 0;

  pg_mem_dict := mem_dict := __rdf_graph_group_of_privates_dict();
  pg_count := (select count (1) from DB.DBA.RDF_GRAPH_GROUP_MEMBER where RGGM_GROUP_IID = iri_to_id ('http://www.openlinksw.com/schemas/virtrdf#PrivateGraphs'));
  -- dbg_obj_princ ('Inspecting caching of list of private graphs (', pg_mem_dict, ' items in memory, ', pg_count, ' items in the table) ...');
  if (dict_size (mem_dict) <> pg_count)
    {
      result ('ERROR', null, null, null, null,
        sprintf ('Cache of list of private graphs contains %d items, but virtrdf:PrivateGraphs group contains %d members, mismatch',
        dict_size (mem_dict), pg_count ) );
      err_recoverable_count := err_recoverable_count + 1;
    }
  mem_vec := dict_list_keys (mem_dict, 0);
  mem_count := length (mem_vec);
  for (mem_ctr := 0; mem_ctr < mem_count; mem_ctr := mem_ctr + 1)
    {
      declare mem_iid IRI_ID;
      mem_iid := mem_vec[mem_ctr];
      if (not exists (select 1 from DB.DBA.RDF_GRAPH_GROUP_MEMBER
        where RGGM_GROUP_IID = iri_to_id ('http://www.openlinksw.com/schemas/virtrdf#PrivateGraphs')
        and RGGM_MEMBER_IID = mem_iid ) )
        {
          result ('ERROR', mem_iid, id_to_iri_nosignal (mem_iid), null, null,
            sprintf ('Cache of list of private graphs contains IRI_ID %s for graph IRI <%.300s> but virtrdf:PrivateGraphs group does not contain it',
              cast (mem_iid as varchar), id_to_iri_nosignal (mem_iid) ) );
          err_recoverable_count := err_recoverable_count + 1;
          if (recovery)
            {
              insert soft DB.DBA.RDF_GRAPH_GROUP_MEMBER (RGGM_GROUP_IID, RGGM_MEMBER_IID)
              values (iri_to_id ('http://www.openlinksw.com/schemas/virtrdf#PrivateGraphs'), mem_iid);
              commit work;
            }
        }
      if (0 = mod (mem_ctr, 100000))
        {
          -- dbg_obj_princ ('...', mem_ctr, '/', mem_count, ' in-memory private graphs done...');
          ;
        }
    }
  -- dbg_obj_princ ('...reverse check...');
  for (select RGGM_MEMBER_IID from DB.DBA.RDF_GRAPH_GROUP_MEMBER
    where RGGM_GROUP_IID = iri_to_id ('http://www.openlinksw.com/schemas/virtrdf#PrivateGraphs') ) do
    {
      if (not dict_get (mem_dict, RGGM_MEMBER_IID, 0))
        {
          result ('ERROR', RGGM_MEMBER_IID, id_to_iri_nosignal (RGGM_MEMBER_IID), null, null,
            sprintf ('Cache of list of private graphs does not contain IRI_ID %s of graph IRI <%.300s> but virtrdf:PrivateGraphs group contains it',
              cast (RGGM_MEMBER_IID as varchar), id_to_iri_nosignal (RGGM_MEMBER_IID) ) );
          err_recoverable_count := err_recoverable_count + 1;
          if (recovery)
            dict_put (mem_dict, RGGM_MEMBER_IID, 1);
        }
    }
  err_count_total := err_count_total + err_recoverable_count + err_bad_count;
  err_recoverable_count_total := err_recoverable_count_total + err_recoverable_count;

  -- dbg_obj_princ ('Inspecting permissions of users...');
  result ('', null, null, null, null, 'Inspecting permissions of users...');
  err_bad_count := 0;
  err_recoverable_count := 0;

  for (select RGU_USER_ID
    from (select distinct RGU_USER_ID from DB.DBA.RDF_GRAPH_USER rgu) dist_rgu
    where not exists (select 1 from DB.DBA.SYS_USERS where U_ID = RGU_USER_ID) ) do
    {
      result ('ERROR', NULL, NULL, RGU_USER_ID, null,
         sprintf ('Garbage in table DB.DBA.RDF_GRAPH_USER: permissions are specified for nonexisting user ID') );
      err_recoverable_count := err_recoverable_count + 1;
      if (recovery)
        DB.DBA.RDF_ALL_USER_PERMS_DEL (null, RGU_USER_ID);
    }
  for (select special.RGU_GRAPH_IID as s_g_iid, special.RGU_USER_ID as s_userid, special.RGU_PERMISSIONS as s_perms,
    common.RGU_GRAPH_IID as c_g_iid, common.RGU_USER_ID as c_userid, common.RGU_PERMISSIONS as c_p
    from DB.DBA.RDF_GRAPH_USER special, DB.DBA.RDF_GRAPH_USER common
    where (common.RGU_GRAPH_IID = special.RGU_GRAPH_IID
      and common.RGU_USER_ID = http_nobody_uid() and special.RGU_USER_ID <> http_nobody_uid()
      and bit_and (common.RGU_PERMISSIONS, special.RGU_PERMISSIONS) < common.RGU_PERMISSIONS )
    union select special.RGU_GRAPH_IID, special.RGU_USER_ID, special.RGU_PERMISSIONS,
    common.RGU_GRAPH_IID, common.RGU_USER_ID, common.RGU_PERMISSIONS
    from DB.DBA.RDF_GRAPH_USER special, DB.DBA.RDF_GRAPH_USER common
    where (common.RGU_GRAPH_IID = #i8192 and dict_get (pg_mem_dict, special.RGU_GRAPH_IID, 0)
      and common.RGU_USER_ID = http_nobody_uid() and special.RGU_USER_ID <> http_nobody_uid()
      and bit_and (common.RGU_PERMISSIONS, special.RGU_PERMISSIONS) < common.RGU_PERMISSIONS )
    union select special.RGU_GRAPH_IID, special.RGU_USER_ID, special.RGU_PERMISSIONS,
    common.RGU_GRAPH_IID, common.RGU_USER_ID, common.RGU_PERMISSIONS
    from DB.DBA.RDF_GRAPH_USER special, DB.DBA.RDF_GRAPH_USER common
    where (common.RGU_GRAPH_IID = #i0 and special.RGU_GRAPH_IID <> #i8192 and not dict_get (pg_mem_dict, special.RGU_GRAPH_IID, 0)
      and common.RGU_USER_ID = http_nobody_uid() and special.RGU_USER_ID <> http_nobody_uid()
      and bit_and (common.RGU_PERMISSIONS, special.RGU_PERMISSIONS) < common.RGU_PERMISSIONS )
    union select special.RGU_GRAPH_IID, special.RGU_USER_ID, special.RGU_PERMISSIONS,
    common.RGU_GRAPH_IID, common.RGU_USER_ID, common.RGU_PERMISSIONS
    from DB.DBA.RDF_GRAPH_USER special, DB.DBA.RDF_GRAPH_USER common
    where (common.RGU_GRAPH_IID = #i8192 and dict_get (pg_mem_dict, special.RGU_GRAPH_IID, 0)
      and common.RGU_USER_ID = special.RGU_USER_ID
      and bit_and (common.RGU_PERMISSIONS, special.RGU_PERMISSIONS) < common.RGU_PERMISSIONS )
    union select special.RGU_GRAPH_IID, special.RGU_USER_ID, special.RGU_PERMISSIONS,
    common.RGU_GRAPH_IID, common.RGU_USER_ID, common.RGU_PERMISSIONS
    from DB.DBA.RDF_GRAPH_USER special, DB.DBA.RDF_GRAPH_USER common
    where (common.RGU_GRAPH_IID = #i0 and special.RGU_GRAPH_IID <> #i8192 and not dict_get (pg_mem_dict, special.RGU_GRAPH_IID, 0)
      and common.RGU_USER_ID = special.RGU_USER_ID
      and bit_and (common.RGU_PERMISSIONS, special.RGU_PERMISSIONS) < common.RGU_PERMISSIONS )
    order by c_userid, c_g_iid ) do
    {
      declare c_g_iri_txt varchar;
      c_g_iri_txt := case (c_g_iid) when #i0 then 'default public graph' when #i8192 then 'default private graph' else sprintf ('graph <%.300s>', id_to_iri_nosignal (c_g_iid)) end;
      result ('ERROR', s_g_iid, id_to_iri_nosignal (s_g_iid), s_userid, (select U_NAME from DB.DBA.SYS_USERS where U_ID = s_userid),
         sprintf ('Specific permissions %x are smaller than %s permissions %x of user %s',
           s_perms, c_g_iri_txt, c_p, (select U_NAME from DB.DBA.SYS_USERS where U_ID = c_userid) ) );
      err_bad_count := err_bad_count + 1;
      if (s_g_iid = sparql_u_id)
        {
          user_sparql_half_protects_from_extra_access := 1;
          result ('ERROR', s_g_iid, id_to_iri_nosignal (s_g_iid), s_userid, (select U_NAME from DB.DBA.SYS_USERS where U_ID = s_userid),
            'Note that The fix of above error by removal of all SPARQL''s permissions can give more access rights to users of ill applications that re-used "SPARQL" account' );
        }
    }
  err_count_total := err_count_total + err_recoverable_count + err_bad_count;
  err_recoverable_count_total := err_recoverable_count_total + err_recoverable_count;
  -- dbg_obj_princ ('Inspecting SPARQL user...');
  result ('', null, null, null, null, 'Inspecting SPARQL user...');
  err_bad_count := 0;
  err_recoverable_count := 0;
  if (sparql_u_id is null)
    {
      result ('WARNING', null, null, sparql_u_id, 'SPARQL', 'The "SPARQL" user does not exist. It is not a security issue (no account --- no related leaks), just unusual');
    }
  else
    {
      if (exists (select 1 from DB.DBA.SYS_USERS where U_NAME='SPARQL' and not U_ACCOUNT_DISABLED))
        {
          result ('ERROR', null, null, sparql_u_id, 'SPARQL', 'The "SPARQL" user should be disabled. Applications should create separate accounts and grant SPARQL_SELECT etc., the account "SPARQL" is for system purposes only');
          err_recoverable_count := err_recoverable_count + 1;
          if (recovery)
            {
              update DB.DBA.SYS_USERS set U_ACCOUNT_DISABLED = 1 where U_NAME='SPARQL';
              commit work;
            }
        }
      if (not user_sparql_half_protects_from_extra_access)
        {
          declare user_sparql_has_perms integer;
          user_sparql_has_perms := 0;
          for (select RGU_GRAPH_IID from DB.DBA.RDF_GRAPH_USER where RGU_USER_ID = sparql_u_id) do
            {
              result ('ERROR', RGU_GRAPH_IID, id_to_iri_nosignal (RGU_GRAPH_IID), sparql_u_id, 'SPARQL', 'The "SPARQL" user has got some specific permissions. That''s strange and redundand, at best, it may also mislead somebody');
              err_recoverable_count := err_recoverable_count + 1;
              user_sparql_has_perms := 1;
            }
          if (user_sparql_has_perms and recovery)
            DB.DBA.RDF_ALL_USER_PERMS_DEL ('SPARQL');
        }
    }
  err_count_total := err_count_total + err_recoverable_count + err_bad_count;
  err_recoverable_count_total := err_recoverable_count_total + err_recoverable_count;

  if (0 = err_count_total)
    result ('', null, null, null, null,
      sprintf ('No errors found in RDF security', err_count_total) );
  else if (recovery)
    result ('', null, null, null, null,
      sprintf ('%d security errors were found, DB.DBA.RDF_GRAPH_SECURITY_AUDIT (0) will list errors that may remain unfixed', err_count_total) );
  else if (err_recoverable_count_total)
    result ('', null, null, null, null,
      sprintf ('%d security errors found, you may wish to run DB.DBA.RDF_GRAPH_SECURITY_AUDIT (1) to repair', err_count_total) );
  else
    result ('', null, null, null, null,
      sprintf ('%d security errors found and none of them can be repaired by DB.DBA.RDF_GRAPH_SECURITY_AUDIT (1)', err_count_total) );
  -- dbg_obj_princ ('Starting RDF Graph Security Audit complete, ', err_recoverable_count_total, '/', err_count_total, ' recoverable/total errors.');
}
;

-----
-- Loading default set of quad map metadata.

create procedure DB.DBA.SPARQL_RELOAD_QM_GRAPH ()
{
  declare ver varchar;
  declare inx int;
  ver := '2017-03-22 0001v7';
  if (USER <> 'dba')
    signal ('RDFXX', 'Only DBA can reload quad map metadata');
  if (not exists (sparql define input:storage "" ask where {
          graph <http://www.openlinksw.com/schemas/virtrdf#> {
              <http://www.openlinksw.com/sparql/virtrdf-data-formats.ttl>
                virtrdf:version ?:ver
            } } ) )
    {
      declare txt1, txt2 varchar;
      declare jso_sys_g_iid IRI_ID;
      declare dict1, lst1, dict2, lst2, sum_lst any;
      txt1 := cast ( DB.DBA.XML_URI_GET (
          'http://www.openlinksw.com/sparql/virtrdf-data-formats.ttl', '' ) as varchar );
      txt2 := cast ( DB.DBA.XML_URI_GET (
          'http://www.openlinksw.com/sparql/virtrdf-quad-storages.ttl', '' ) as varchar );
      jso_sys_g_iid := iri_to_id (JSO_SYS_GRAPH ());
      dict1 := DB.DBA.RDF_TTL2HASH (txt1, '');
      dict2 := DB.DBA.RDF_TTL2HASH (txt2, '');
      lst1 := dict_list_keys (dict1, 1);
      lst2 := dict_list_keys (dict2, 1);
      sum_lst := vector_concat (lst1, lst2);
      inx := 0;
      foreach (any triple in sum_lst) do
        {
          if ((triple[1] = iri_to_id ('http://www.w3.org/1999/02/22-rdf-syntax-ns#type')) and
            isiri_id (triple[2]) and (triple[2] = iri_to_id ('http://www.openlinksw.com/schemas/virtrdf#QuadMapFormat')))
            {
              -- dbg_obj_princ ('will delete whole ', id_to_iri (triple[0]));
              delete from DB.DBA.RDF_QUAD where G = jso_sys_g_iid and S = triple[0];
            }
          else
            delete from DB.DBA.RDF_QUAD where G = jso_sys_g_iid and S = triple[0] and P = triple[1];
	  if (
	      triple[0] = iri_to_id ('http://www.openlinksw.com/sparql/virtrdf-data-formats.ttl')
	      and
	      triple[1] = iri_to_id ('http://www.openlinksw.com/schemas/virtrdf#version')
	      and
	      triple[2] <> ver
	      )
	    {
	      log_message (sprintf ('RDF metadata version: %s', ver));
	      triple[2] := ver;
	      sum_lst[inx] := triple;
	    }
	  inx := inx + 1;
        }
      DB.DBA.RDF_INSERT_TRIPLES (jso_sys_g_iid, sum_lst);
      commit work;
      cl_exec ('checkpoint');
    }
  DB.DBA.JSO_LOAD_GRAPH (DB.DBA.JSO_SYS_GRAPH(), 1, 0, 1);
  sequence_set ('RDF_URL_IID_NAMED', 1010000, 1);
  sequence_set ('RDF_URL_IID_BLANK', iri_id_num (min_bnode_iri_id ()) + 10000, 1);
  sequence_set ('RDF_URL_IID_NAMED_BLANK', iri_id_num (min_named_bnode_iri_id ()) + 10000, 1);
  sequence_set ('RDF_PREF_SEQ', 101, 1);
  sequence_set ('RDF_RO_ID', 1001, 1);
}
;

create procedure DB.DBA.RDF_CREATE_SPARQL_ROLES ()
{
  declare state, msg varchar;
  declare cmds any;
  declare sparql_load_service_data_exists integer;
  sparql_load_service_data_exists := coalesce ((select 1 from DB.DBA.SYS_USERS where U_NAME='SPARQL_LOAD_SERVICE_DATA'), 0);
  cmds := vector (
    'create role SPARQL_SELECT',
    'create role SPARQL_SPONGE',
    'create role SPARQL_LOAD_SERVICE_DATA',
    'create role SPARQL_UPDATE',
    'grant SPARQL_SELECT to SPARQL_UPDATE',
    'grant SPARQL_SELECT to SPARQL_SPONGE',
    'grant SPARQL_LOAD_SERVICE_DATA to SPARQL_UPDATE',
    'grant SPARQL_SPONGE to SPARQL_UPDATE',
    'grant select on DB.DBA.RDF_QUAD to SPARQL_SELECT',
    'grant all on DB.DBA.RDF_QUAD to SPARQL_UPDATE',
    'grant select on DB.DBA.RDF_PREFIX to SPARQL_SELECT',
    'grant all on DB.DBA.RDF_PREFIX to SPARQL_UPDATE',
    'grant select on DB.DBA.RDF_IRI to SPARQL_SELECT',
    'grant all on DB.DBA.RDF_IRI to SPARQL_UPDATE',
    'grant select on DB.DBA.RDF_OBJ to SPARQL_SELECT',
    'grant all on DB.DBA.RDF_OBJ to SPARQL_UPDATE',
    --'grant select on DB.DBA.RDF_FT to SPARQL_SELECT',
    --'grant all on DB.DBA.RDF_FT to SPARQL_UPDATE',
    'grant select on DB.DBA.RDF_DATATYPE to SPARQL_SELECT',
    'grant all on DB.DBA.RDF_DATATYPE to SPARQL_UPDATE',
    'grant select on DB.DBA.RDF_LANGUAGE to SPARQL_SELECT',
    'grant all on DB.DBA.RDF_LANGUAGE to SPARQL_UPDATE',
    'grant select on DB.DBA.SYS_SPARQL_HOST to SPARQL_SELECT',
    'grant all on DB.DBA.SYS_SPARQL_HOST to SPARQL_UPDATE',
    'grant select on DB.DBA.RDF_EXPLICITLY_CREATED_GRAPH to SPARQL_SELECT',
    'grant all on DB.DBA.RDF_EXPLICITLY_CREATED_GRAPH to SPARQL_UPDATE',
    'grant select on DB.DBA.SYS_IDONLY_EMPTY to SPARQL_SELECT',
    'grant select on DB.DBA.SYS_IDONLY_ONE to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_GLOBAL_RESET to SPARQL_UPDATE',
    'grant execute on DB.DBA.RDF_MAKE_IID_OF_QNAME to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_MAKE_IID_OF_QNAME_SAFE to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_MAKE_IID_OF_LONG to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_QNAME_OF_IID to SPARQL_SELECT', -- DEPRECATED
    'grant execute on DB.DBA.RDF_MAKE_GRAPH_IIDS_OF_QNAMES to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_TWOBYTE_OF_DATATYPE to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_TWOBYTE_OF_LANGUAGE to SPARQL_SELECT',
    'grant execute on DB.DBA.RQ_LONG_OF_O to SPARQL_SELECT', -- DEPRECATED
    'grant execute on DB.DBA.RQ_SQLVAL_OF_O to SPARQL_SELECT', -- DEPRECATED
    'grant execute on DB.DBA.RQ_BOOL_OF_O to SPARQL_SELECT',
    'grant execute on DB.DBA.RQ_SPARQL_EBV_OF_O to SPARQL_SELECT',
    'grant execute on DB.DBA.RQ_IID_OF_O to SPARQL_SELECT', -- DEPRECATED
    'grant execute on DB.DBA.RQ_O_IS_LIT to SPARQL_SELECT', -- DEPRECATED
    'grant execute on DB.DBA.RDF_OBJ_ADD to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_OBJ_FIND_EXISTING to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_MAKE_OBJ_OF_SQLVAL to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_MAKE_OBJ_OF_SQLVAL_FT to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_MAKE_OBJ_OF_TYPEDSQLVAL to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_MAKE_OBJ_OF_TYPEDSQLVAL_FT to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_MAKE_OBJ_OF_TYPEDSQLVAL_STRINGS to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_LONG_OF_OBJ to SPARQL_SELECT', -- DEPRECATED
    'grant execute on DB.DBA.RDF_DATATYPE_OF_OBJ to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_LANGUAGE_OF_OBJ to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_SQLVAL_OF_OBJ to SPARQL_SELECT', -- DEPRECATED
    'grant execute on DB.DBA.RDF_BOOL_OF_OBJ to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_SPARQL_EBV_OF_OBJ to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_QNAME_OF_OBJ to SPARQL_SELECT', -- DEPRECATED
    'grant execute on DB.DBA.RDF_STRSQLVAL_OF_OBJ to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_OBJ_OF_LONG to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_OBJ_OF_SQLVAL to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_MAKE_LONG_OF_SQLVAL to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_MAKE_LONG_OF_TYPEDSQLVAL to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_MAKE_LONG_OF_TYPEDSQLVAL_STRINGS to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_QNAME_OF_LONG_SAFE to SPARQL_SELECT', -- DEPRECATED
    'grant execute on DB.DBA.RDF_SQLVAL_OF_LONG to SPARQL_SELECT', -- DEPRECATED
    'grant execute on DB.DBA.RDF_BOOL_OF_LONG to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_DATATYPE_OF_LONG to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_DATATYPE_IRI_OF_LONG to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_LANGUAGE_OF_LONG to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_STRSQLVAL_OF_LONG to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_WIDESTRSQLVAL_OF_LONG to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_LONG_OF_SQLVAL to SPARQL_SELECT',
    'grant execute on DB.DBA.rdf_strdt_impl to SPARQL_SELECT',
    'grant execute on DB.DBA.rdf_strlang_impl to SPARQL_SELECT',
    'grant execute on DB.DBA.rdf_uuid_impl to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_QUAD_URI to SPARQL_UPDATE',
    'grant execute on DB.DBA.RDF_QUAD_URI_L to SPARQL_UPDATE',
    'grant execute on DB.DBA.RDF_QUAD_URI_L_TYPED to SPARQL_UPDATE',
    'grant execute on DB.DBA.TTLP_EV_NEW_GRAPH to SPARQL_UPDATE',
    'grant execute on DB.DBA.TTLP_EV_NEW_BLANK to SPARQL_UPDATE',
    'grant execute on DB.DBA.TTLP_EV_GET_IID to SPARQL_UPDATE',
    'grant execute on DB.DBA.TTLP_EV_TRIPLE to SPARQL_UPDATE',
    'grant execute on DB.DBA.TTLP_EV_TRIPLE_L to SPARQL_UPDATE',
    'grant execute on DB.DBA.TTLP_EV_TRIPLE_XLAT to SPARQL_UPDATE',
    'grant execute on DB.DBA.TTLP_EV_TRIPLE_L_XLAT to SPARQL_UPDATE',
    'grant execute on DB.DBA.TTLP to SPARQL_UPDATE',
    'grant execute on DB.DBA.TTLP_WITH_IRI_TRANSLATION to SPARQL_UPDATE',
    'grant execute on DB.DBA.RDF_TTL2HASH_EXEC_NEW_GRAPH to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_TTL2HASH_EXEC_NEW_BLANK to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_TTL2HASH_EXEC_GET_IID to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_TTL2HASH_EXEC_TRIPLE to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_TTL2HASH_EXEC_TRIPLE_L to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_TTL2HASH to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_LOAD_RDFXML to SPARQL_UPDATE',
    'grant execute on DB.DBA.RDF_RDFA11_FETCH_PROFILES to SPARQL_UPDATE',
    'grant execute on DB.DBA.RDF_LOAD_RDFA to SPARQL_UPDATE',
    'grant execute on DB.DBA.RDF_LOAD_RDFA_WITH_IRI_TRANSLATION to SPARQL_UPDATE',
    'grant execute on DB.DBA.RDF_LOAD_XHTML_MICRODATA to SPARQL_UPDATE',
    'grant execute on DB.DBA.RDF_RDFXML_TO_DICT to SPARQL_UPDATE',
    'grant execute on DB.DBA.RDF_LONG_TO_TTL to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_TRIPLES_TO_TTL to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_TRIPLES_TO_TRIG to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_TRIPLES_TO_NT to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_GRAPH_TO_TTL to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_TRIPLES_TO_RDF_XML_TEXT to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_TRIPLES_TO_TALIS_JSON to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_TRIPLES_TO_JSON_LD to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_TRIPLES_TO_JSON_LD_CTX to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_TRIPLES_TO_CSV to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_TRIPLES_TO_TSV to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_TRIPLES_TO_RDFA_XHTML to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_TRIPLES_TO_HTML_UL to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_TRIPLES_TO_HTML_TR to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_TRIPLES_TO_HTML_MICRODATA to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_TRIPLES_TO_HTML_NICE_MICRODATA to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_TRIPLES_TO_HTML_NICE_TTL to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_TRIPLES_TO_HTML_SCRIPT_TTL to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_TRIPLES_TO_HTML_SCRIPT_LD_JSON to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_TRIPLES_TO_JSON_MICRODATA to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_TRIPLES_TO_ATOM_XML_TEXT to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_TRIPLES_TO_ODATA_JSON to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_FORMAT_RESULT_SET_AS_TTL_INIT to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_FORMAT_RESULT_SET_AS_TTL_ACC to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_FORMAT_RESULT_SET_AS_TTL_FIN to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_FORMAT_RESULT_SET_AS_NT_INIT to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_FORMAT_RESULT_SET_AS_NT_ACC to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_FORMAT_RESULT_SET_AS_NT_FIN to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_FORMAT_RESULT_SET_AS_RDF_XML_INIT to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_FORMAT_RESULT_SET_AS_RDF_XML_ACC to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_FORMAT_RESULT_SET_AS_RDF_XML_FIN to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_FORMAT_RESULT_SET_AS_HTML_NICE_TTL to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_FORMAT_RESULT_SET_AS_JSON_INIT to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_FORMAT_RESULT_SET_AS_JSON_ACC to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_FORMAT_RESULT_SET_AS_JSON_FIN to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_FORMAT_RESULT_SET_AS_CSV_INIT to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_FORMAT_RESULT_SET_AS_CSV_ACC to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_FORMAT_RESULT_SET_AS_TSV_ACC to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_FORMAT_RESULT_SET_AS_CSV_FIN to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_FORMAT_RESULT_SET_AS_CXML_INIT to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_FORMAT_RESULT_SET_AS_CXML_ACC to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_FORMAT_RESULT_SET_AS_CXML_FIN to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_FORMAT_TRIPLE_DICT_AS_ATOM_XML to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_FORMAT_TRIPLE_DICT_AS_ODATA_JSON to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_FORMAT_TRIPLE_DICT_AS_TTL to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_FORMAT_TRIPLE_DICT_AS_NICE_TTL to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_FORMAT_TRIPLE_DICT_AS_NT to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_FORMAT_TRIPLE_DICT_AS_RDF_XML to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_FORMAT_TRIPLE_DICT_AS_TALIS_JSON to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_FORMAT_TRIPLE_DICT_AS_JSON_LD to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_FORMAT_TRIPLE_DICT_AS_JSON_LD_CTX to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_FORMAT_TRIPLE_DICT_AS_HTML_MICRODATA to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_FORMAT_TRIPLE_DICT_AS_HTML_NICE_MICRODATA to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_FORMAT_TRIPLE_DICT_AS_HTML_NICE_TTL to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_FORMAT_TRIPLE_DICT_AS_JSON_MICRODATA to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_FORMAT_TRIPLE_DICT_AS_HTML_SCRIPT_LD_JSON to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_FORMAT_TRIPLE_DICT_AS_HTML_SCRIPT_TTL to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_FORMAT_TRIPLE_DICT_AS_CSV to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_FORMAT_TRIPLE_DICT_AS_TSV to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_FORMAT_TRIPLE_DICT_AS_RDFA_XHTML to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_FORMAT_TRIPLE_DICT_AS_CXML to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_FORMAT_TRIPLE_DICT_AS_CXML_QRCODE to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_INSERT_TRIPLES to SPARQL_UPDATE',
    'grant execute on DB.DBA.RDF_DELETE_TRIPLES to SPARQL_UPDATE',
    'grant execute on DB.DBA.RDF_DELETE_TRIPLES_AGG to SPARQL_UPDATE',
    'grant execute on DB.DBA.RDF_MODIFY_TRIPLES to SPARQL_UPDATE',
    'grant execute on DB.DBA.SPARQL_INS_OR_DEL_CTOR_IMPL to SPARQL_UPDATE',
    'grant execute on DB.DBA.SPARQL_DELETE_CTOR_ACC to SPARQL_UPDATE',
    'grant execute on DB.DBA.SPARQL_INSERT_CTOR_ACC to SPARQL_UPDATE',
    'grant execute on DB.DBA.SPARQL_MODIFY_CTOR_ACC to SPARQL_UPDATE',
    'grant execute on DB.DBA.SPARQL_INS_OR_DEL_OR_MODIFY_CTOR_FIN to SPARQL_UPDATE',
    'grant execute on DB.DBA.SPARQL_DELETE_CTOR to SPARQL_UPDATE',
    'grant execute on DB.DBA.SPARQL_INSERT_CTOR to SPARQL_UPDATE',
    'grant execute on DB.DBA.SPARQL_MODIFY_CTOR to SPARQL_UPDATE',
    'grant execute on DB.DBA.RDF_INSERT_QUADS to SPARQL_UPDATE',
    'grant execute on DB.DBA.RDF_DELETE_QUADS to SPARQL_UPDATE',
    'grant execute on DB.DBA.SPARQL_INSERT_DICT_CONTENT to SPARQL_UPDATE',
    'grant execute on DB.DBA.SPARQL_INSERT_QUAD_DICT_CONTENT to SPARQL_UPDATE',
    'grant execute on DB.DBA.SPARQL_DELETE_DICT_CONTENT to SPARQL_UPDATE',
    'grant execute on DB.DBA.SPARQL_DELETE_QUAD_DICT_CONTENT to SPARQL_UPDATE',
    'grant execute on DB.DBA.SPARQL_MODIFY_BY_DICT_CONTENTS to SPARQL_UPDATE',
    'grant execute on DB.DBA.SPARQL_MODIFY_BY_QUAD_DICT_CONTENTS to SPARQL_UPDATE',
    'grant execute on DB.DBA.SPARUL_ADD to SPARQL_UPDATE',
    'grant execute on DB.DBA.SPARUL_CLEAR to SPARQL_UPDATE',
    'grant execute on DB.DBA.SPARUL_COPY to SPARQL_UPDATE',
    'grant execute on DB.DBA.SPARUL_LOAD_SERVICE_DATA to SPARQL_SPONGE',
    'grant execute on DB.DBA.SPARUL_CREATE to SPARQL_UPDATE',
    'grant execute on DB.DBA.SPARUL_DROP to SPARQL_UPDATE',
    'grant execute on DB.DBA.SPARUL_LOAD to SPARQL_UPDATE',
    'grant execute on DB.DBA.SPARUL_LOAD_SERVICE_DATA to SPARQL_LOAD_SERVICE_DATA',
    'grant execute on DB.DBA.SPARUL_LOAD_SERVICE_DATA to SPARQL_UPDATE',
    'grant execute on DB.DBA.SPARUL_MOVE to SPARQL_UPDATE',
    'grant execute on DB.DBA.SPARUL_RUN to SPARQL_UPDATE',
    'grant execute on DB.DBA.SPARQL_SELECT_KNOWN_GRAPHS to SPARQL_SELECT',
    'grant execute on DB.DBA.SPARQL_DESC_AGG_INIT to SPARQL_SELECT',
    'grant execute on DB.DBA.SPARQL_DESC_AGG_ACC to SPARQL_SELECT',
    'grant execute on DB.DBA.SPARQL_DESC_AGG_FIN to SPARQL_SELECT',
    'grant execute on DB.DBA.SPARQL_DESC_DICT to SPARQL_SELECT',
    'grant execute on DB.DBA.SPARQL_DESC_DICT_SPO to SPARQL_SELECT',
    'grant execute on DB.DBA.SPARQL_DESC_DICT_SPO_PHYSICAL to SPARQL_SELECT',
    'grant execute on DB.DBA.SPARQL_DESC_DICT_CBD to SPARQL_SELECT',
    'grant execute on DB.DBA.SPARQL_DESC_DICT_CBD_PHYSICAL to SPARQL_SELECT',
    'grant execute on DB.DBA.SPARQL_DESC_DICT_OBJCBD to SPARQL_SELECT',
    'grant execute on DB.DBA.SPARQL_DESC_DICT_OBJCBD_PHYSICAL to SPARQL_SELECT',
    'grant execute on DB.DBA.SPARQL_DESC_DICT_SCBD to SPARQL_SELECT',
    'grant execute on DB.DBA.SPARQL_DESC_DICT_SCBD_PHYSICAL to SPARQL_SELECT',
    'grant execute on DB.DBA.SPARQL_CONSTRUCT_INIT to SPARQL_SELECT',
    'grant execute on DB.DBA.SPARQL_CONSTRUCT_ACC to SPARQL_SELECT',
    'grant execute on DB.DBA.SPARQL_CONSTRUCT_FIN to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_TYPEMIN_OF_OBJ to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_TYPEMAX_OF_OBJ to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_IID_CMP to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_OBJ_CMP to SPARQL_SELECT',
    'grant execute on DB.DBA.RDF_LONG_CMP to SPARQL_SELECT',
    'grant execute on DB.DBA.TTLP_MT to SPARQL_UPDATE',
    'grant execute on DB.DBA.TTLP_MT_LOCAL_FILE to SPARQL_UPDATE',
    'grant execute on DB.DBA.TTLP_EV_TRIPLE_W to SPARQL_UPDATE',
    'grant execute on DB.DBA.TTLP_EV_TRIPLE_L_W to SPARQL_UPDATE',
    'grant execute on DB.DBA.TTLP_EV_NEW_GRAPH_A to SPARQL_UPDATE',
    'grant execute on DB.DBA.TTLP_EV_TRIPLE_A to SPARQL_UPDATE',
    'grant execute on DB.DBA.TTLP_EV_TRIPLE_L_A to SPARQL_UPDATE',
    'grant execute on DB.DBA.TTLP_EV_REPORT_DEFAULT to SPARQL_UPDATE',
    'grant execute on DB.DBA.RDF_LOAD_RDFXML_MT to SPARQL_UPDATE',
    'grant execute on DB.DBA.RDF_LOAD_HTTP_RESPONSE to SPARQL_UPDATE',
    'grant execute on DB.DBA.RDF_FORGET_HTTP_RESPONSE to SPARQL_UPDATE',
    'grant execute on DB.DBA.TTLP_EV_COMMIT to SPARQL_UPDATE',
    'grant execute on DB.DBA.RDF_PROC_COLS to "SPARQL"',
    'grant execute on DB.DBA.RDF_GRAPH_USER_PERMS_ACK to SPARQL_SELECT', -- DEPRECATED
    'grant execute on DB.DBA.RDF_GRAPH_USER_PERMS_ASSERT to SPARQL_SELECT', -- DEPRECATED
    'grant execute on DB.DBA.RL_FLUSH to SPARQL_UPDATE',
    'grant execute on DB.DBA.RDF_OBJ_ADD_KEYWORD_FOR_GRAPH to SPARQL_UPDATE',
    'grant execute on DB.DBA.RDF_GRAPH_GROUP_LIST_GET to SPARQL_SELECT',
    'grant execute on L_O_LOOK to SPARQL_SPONGE',
    'grant execute on RL_I2ID_NP to SPARQL_SPONGE',
    'grant execute on rl_i2id to SPARQL_SPONGE',
    'grant execute on DB.DBA.TTLP_RL_TRIPLE to SPARQL_UPDATE',
    'grant execute on rdf_rl_type_id to SPARQL_UPDATE',
    'grant execute on rdf_rl_lang_id to SPARQL_UPDATE',
    'grant execute on DB.DBA.TTLP_RL_TRIPLE_L to SPARQL_UPDATE',
    'grant execute on DB.DBA.TTLP_RL_NEW_GRAPH to SPARQL_UPDATE',
    'grant execute on rl_local_dpipe to SPARQL_UPDATE',
    'grant execute on rl_local_dpipe_gs to SPARQL_UPDATE',
    'grant execute on RL_FLUSH to SPARQL_UPDATE',
    'grant execute on rl_send to SPARQL_UPDATE',
    'grant execute on DB.DBA.TTLP_RL_COMMIT to SPARQL_UPDATE',
    'grant execute on rl_send_gs to SPARQL_UPDATE',
    'grant execute on DB.DBA.TTLP_RL_GS_TRIPLE to SPARQL_UPDATE',
    'grant execute on DB.DBA.TTLP_RL_GS_TRIPLE_L to SPARQL_UPDATE',
    'grant execute on DB.DBA.TTLP_RL_GS_NEW_GRAPH to SPARQL_UPDATE',
    'grant execute on DB.DBA.TTLP_EV_NULL_IID to SPARQL_UPDATE',
    'grant execute on TTLP_V_GS to SPARQL_UPDATE',
    'grant execute on DB.DBA.TTLP_V to SPARQL_UPDATE',
    'grant execute on DB.DBA.RDF_LOAD_RDFXML_V to SPARQL_UPDATE',
    'grant execute on ID_TO_IRI_VEC to SPARQL_UPDATE' );
  foreach (varchar cmd in cmds) do
    {
      exec (cmd, state, msg);
    }
  if (not sparql_load_service_data_exists)
    exec ('grant SPARQL_LOAD_SERVICE_DATA to SPARQL_SELECT', state, msg);
}
;

create procedure DB.DBA.RDF_QUAD_AUDIT ()
{
  declare stat, msg varchar;
  declare err_dict any;
  result_names (stat, msg);
  if (exists (select top 1 1 from DB.DBA.SYS_COLS
    where "TABLE" = fix_identifier_case ('DB.DBA.RDF_OBJ_RO_FLAGS_WORDS')
    and "COLUMN" = fix_identifier_case ('VT_WORD') )
    and exists (select top 1 1 from DB.DBA.SYS_COLS
    where "TABLE" = fix_identifier_case ('DB.DBA.RDF_OBJ')
    and "COLUMN" = fix_identifier_case ('RO_FLAGS')
    and COL_DTP = 188 ) )
    goto check_new_style;
  err_dict := dict_new ();
  if (isstring (registry_get ('DB.DBA.RDF_QUAD_FT_UPGRADE')))
    {
      result ('ERRol', 'old layout but isstring (registry_get (''DB.DBA.RDF_QUAD_FT_UPGRADE''))');
      return;
    }
  for (select O as o_old from DB.DBA.RDF_QUAD where isstring (O)) do
    {
      declare o_old_len integer;
      declare o_long any;
      declare o_strval varchar;
      declare val_len, o_id integeR;
      declare o_dt, o_lang integeR;
      o_old_len := length (o_old);
      if (dict_size (err_dict) > 10000)
        {
          result ('ERRxx', 'Too many errors, bye');
          return;
        }
      if (dict_get (err_dict, o_old, 0))
        goto known_bug;
      if (o_old_len = 29)
        {
          if (o_old [22] <> 0)
            { result ('ERRol', sprintf ('ill literal |%U| (escaped like URL)', o_old)); dict_put (err_dict, o_old, 1); }
          else
            {
              o_long := jso_parse_digest (o_old);
              o_strval := (select case (isnull (RO_LONG)) when 0 then blob_to_string (RO_LONG) else RO_VAL end from DB.DBA.RDF_OBJ where RO_ID = o_long[3]);
              if (o_strval is null)
                { result ('ERRol', sprintf ('Non-existing RO_ID %d in literal |%U| (escaped like URL)', o_long[3], o_old)); dict_put (err_dict, o_old, 1); }
              else if ("LEFT" (o_strval, 20) <> o_long[1])
                { result ('ERRol', sprintf ('Full value of RO_ID %d starts with |%U|, does not match to literal |%U| (escaped like URL)', o_long[3], "LEFT" (o_strval, 20), o_old)); dict_put (err_dict, o_old, 1); }
              o_dt := o_long[0];
              o_lang := o_long[2];
            }
        }
      else if (o_old_len < 5)
        { result ('ERRot', sprintf ('ill literal |%U| (escaped like URL)', o_old)); dict_put (err_dict, o_old, 1); }
      else if (o_old_len < 29)
        {
          val_len := length (o_old) - 5;
          o_dt := o_old[0] + o_old[1]*256;
          o_lang := o_old[val_len+3] + o_old[val_len+4]*256;
          if ((o_old [val_len+2] <> 0) or 0 = o_old[0] or 0 = o_old[1] or 0 = o_old[val_len+3] or 0 = o_old[val_len+4])
            { result ('ERRos', sprintf ('ill short literal |%U| (escaped like URL)', o_old)); dict_put (err_dict, o_old, 1); }
        }
      else
        { result ('ERRoh', sprintf ('Too long literal |%U| (truncated, escaped like URL)', "LEFT" (o_old, 100))); dict_put (err_dict, o_old, 1); }
known_bug: ;
    }
  return;

check_new_style:
  if (not isstring (registry_get ('DB.DBA.RDF_QUAD_FT_UPGRADE')))
    result ('ERRft', 'new layout but not isstring (registry_get (''DB.DBA.RDF_QUAD_FT_UPGRADE''))');
}
;

create procedure DB.DBA.RDF_QUAD_OUTLINE_ALL (in force integer := 0)
{
  declare c_main, c_pogs, c_op integer;
  declare c_main_tmp, c_pogs_tmp, c_op_tmp, old_mode integer;
  declare c_main_fixed, c_pogs_fixed, c_op_fixed integer;
  declare c_check char;

  if ((registry_get ('__rb_id_only_for_plain_ro_obj') = '1') and not force)
    return;
  if (0 = sys_stat ('db_exists') or not exists (select top 1 1 from DB.DBA.RDF_QUAD option (no cluster)))
    {
      registry_set ('__rb_id_only_for_plain_ro_obj', '1');
      return;
    }
  log_message ('This database may contain RDF data that could cause indexing problems on previous versions of the server.');
  log_message ('The content of the DB.DBA.RDF_QUAD table will be checked and an update may automatically be performed if');
  log_message ('such data is found.');
  log_message ('This check will take some time but is made only once.');

  if (not exists (select top 1 1 from DB.DBA.RDF_QUAD table option (index RDF_QUAD_OP, index_only) where rdf_box_migrate_after_06_02_3129 (O)))
    {
      log_message ('No need to update DB.DBA.RDF_QUAD.');
      registry_set ('__rb_id_only_for_plain_ro_obj', '1');
      exec ('checkpoint');
      return;
    }
  if (coalesce (virtuoso_ini_item_value ('SPARQL', 'RecoveryMode'), '0') > '0')
    {
      log_message ('Update skipped in recovery mode');
      return;
    }
  log_message ('An update is required.');
  c_check := coalesce (virtuoso_ini_item_value ('Parameters', 'AnalyzeFixQuadStore'), '0');
  if (coalesce (virtuoso_ini_item_value ('Parameters', 'LiteMode'), '0') <> '0') c_check := '1';
  if (c_check <> '1')
    {
	log_message ('');
	log_message ('NOTICE: Before Virtuoso can continue fixing the DB.DBA.RDF_QUAD table and its indexes');
 	log_message ('        the DB Administrator should check make sure that:');
	log_message ('');
	log_message ('         * there is a recent backup of the database');
	log_message ('         * there is enough free disk space available to complete this conversion');
	log_message ('         * the database can be offline for the duration of this conversion');
	log_message ('');
	log_message ('        Since the update can take a considerable amount of time on large databases');
	log_message ('        it is advisable to schedule this at an appropriate time.'); 
	log_message ('');
	log_message ('To continue the DBA must change the virtuoso.ini file and add the following flag:');
	log_message ('');
	log_message ('    [Parameters]');
	log_message ('    AnalyzeFixQuadStore = 1');
	log_message ('');
	log_message ('For additional information please contact OpenLink Support <support@openlinksw.com>');
	log_message ('This process will now exit.');
	raw_exit();
    }

  log_message ('Please be patient.');
  log_message ('The table DB.DBA.RDF_QUAD and two of its additional indexes will be patched now.');
  log_message ('In case of error during the operation, delete the transaction log before restarting the server.');
  exec ('checkpoint');
  declare exit handler for sqlstate '*'
    {
      log_message (sprintf ('Error %s: %s', __SQL_STATE, __SQL_MESSAGE));
      log_message ('Do not forget to delete the transaction log before restarting the server.');
      raw_exit ();
    };
  log_enable (2);
  log_message ('Phase 1 of 9: Gathering statistics ...');
  c_main := (select count (1) from DB.DBA.RDF_QUAD table option (index RDF_QUAD) option (no cluster));
  c_pogs := (select count (1) from DB.DBA.RDF_QUAD table option (index RDF_QUAD_POGS) option (no cluster));
  if (c_main <> c_pogs)
    log_message ('* Existing indexes are damaged, will try to recover...');
  c_op := (select count (1) from DB.DBA.RDF_QUAD table option (index RDF_QUAD_OP, index_only) option (no cluster));
  log_message (sprintf (' * Index sizes before the processing: %09d RDF_QUAD, %09d POGS, %09d OP', c_main, c_pogs, c_op));
  delete from DB.DBA.RDF_QUAD_RECOV_TMP table option (index RDF_QUAD_RECOV_TMP, no cluster) option (index RDF_QUAD_RECOV_TMP, no cluster);
  delete from DB.DBA.RDF_QUAD_RECOV_TMP table option (index RDF_QUAD_RECOV_TMP_POGS, no cluster) option (index RDF_QUAD_RECOV_TMP_POGS, no cluster);
  delete from DB.DBA.RDF_QUAD_RECOV_TMP table option (index RDF_QUAD_RECOV_TMP_OP, index_only, no cluster) option (index RDF_QUAD_RECOV_TMP_OP, no cluster);
  log_message ('Phase 2 of 9: Copying all quads to a temporary table ...');
  insert soft DB.DBA.RDF_QUAD_RECOV_TMP index RDF_QUAD_RECOV_TMP option (no cluster) (G1,S1,P1,O1) select G,S,P,O from DB.DBA.RDF_QUAD table option (index RDF_QUAD) option (no cluster);
  insert soft DB.DBA.RDF_QUAD_RECOV_TMP index RDF_QUAD_RECOV_TMP_POGS option (no cluster) (G1,S1,P1,O1) select G,S,P,O from DB.DBA.RDF_QUAD table option (index RDF_QUAD_POGS) option (no cluster);
  insert soft DB.DBA.RDF_QUAD_RECOV_TMP index RDF_QUAD_RECOV_TMP_OP option (index_only, no cluster) (P1,O1) select P,O from DB.DBA.RDF_QUAD table option (index RDF_QUAD_OP, index_only) option (no cluster);
  if (c_main <> c_pogs) -- cluster should not do that
    {
      log_message ('* Recovering additional data from existing indexes');
      if (c_main < c_pogs)
        insert soft DB.DBA.RDF_QUAD_RECOV_TMP option (no cluster) (G1,S1,P1,O1) select G,S,P,O from DB.DBA.RDF_QUAD table option (index RDF_QUAD_POGS) option (no cluster);
      if (c_pogs < c_main)
        insert soft DB.DBA.RDF_QUAD_RECOV_TMP option (no cluster) (G1,S1,P1,O1) select G,S,P,O from DB.DBA.RDF_QUAD table option (index RDF_QUAD) option (no cluster);
    }
  c_op_tmp := (select count (1) from DB.DBA.RDF_QUAD_RECOV_TMP table option (index RDF_QUAD_RECOV_TMP_OP, index_only) option (no cluster));
  log_message (sprintf ('* Index sizes of temporary table: %09d OP', c_op_tmp));
  if (c_op_tmp < c_op)
    log_message ('** Some data are lost or the corruption was strong before the processing.');

  log_message ('Phase 3 of 9: Cleaning the quad storage ...');
  delete from DB.DBA.RDF_QUAD table option (index RDF_QUAD, no cluster) option (index RDF_QUAD, no cluster);
  delete from DB.DBA.RDF_QUAD table option (index RDF_QUAD_POGS, no cluster) option (index RDF_QUAD_POGS, no cluster);
  delete from DB.DBA.RDF_QUAD table option (index RDF_QUAD_OP, index_only, no cluster) option (index RDF_QUAD_OP, no cluster);
  log_message ('Phase 4 of 9: Refilling the quad storage from the temporary table...');
  insert soft DB.DBA.RDF_QUAD index RDF_QUAD option (no cluster) (G,S,P,O) select G1,S1,P1,O1 from DB.DBA.RDF_QUAD_RECOV_TMP table option (index RDF_QUAD_RECOV_TMP) option (no cluster);
  insert soft DB.DBA.RDF_QUAD index RDF_QUAD_POGS option (no cluster) (G,S,P,O) select G1,S1,P1,O1 from DB.DBA.RDF_QUAD_RECOV_TMP table option (index RDF_QUAD_RECOV_TMP_POGS) option (no cluster);
  insert soft DB.DBA.RDF_QUAD index RDF_QUAD_OP option (index_only, no cluster) (P,O) select P1,O1 from DB.DBA.RDF_QUAD_RECOV_TMP table option (index RDF_QUAD_RECOV_TMP_OP, index_only) option (no cluster);

  log_message ('Phase 5 of 9: Cleaning the temporary table ...');
  delete from DB.DBA.RDF_QUAD_RECOV_TMP table option (index RDF_QUAD_RECOV_TMP, no cluster) option (index RDF_QUAD_RECOV_TMP, no cluster);
  delete from DB.DBA.RDF_QUAD_RECOV_TMP table option (index RDF_QUAD_RECOV_TMP_POGS, no cluster) option (index RDF_QUAD_RECOV_TMP_POGS, no cluster);
  delete from DB.DBA.RDF_QUAD_RECOV_TMP table option (index RDF_QUAD_RECOV_TMP_OP, index_only, no cluster) option (index RDF_QUAD_RECOV_TMP_OP, no cluster);

  log_message ('Phase 6 of 9: Gathering statistics again ...');
  c_main_fixed := (select count (1) from DB.DBA.RDF_QUAD table option (index RDF_QUAD) option (no cluster));
  c_pogs_fixed := (select count (1) from DB.DBA.RDF_QUAD table option (index RDF_QUAD_POGS) option (no cluster));
  c_op_fixed := (select count (1) from DB.DBA.RDF_QUAD table option (index RDF_QUAD_OP, index_only) option (no cluster));
  log_message (sprintf ('* Index sizes after the processing: %09d RDF_QUAD, %09d POGS, %09d OP', c_main_fixed, c_pogs_fixed, c_op_fixed));
  if ((__min (c_main_fixed, c_pogs_fixed) < __max (c_main, c_pogs)) or (c_op_fixed < c_op))
    log_message ('** Some data are lost or the corruption was strong before the processing.');

--select * from DB.DBA.RDF_QUAD a table option (index RDF_QUAD) where not exists (select top 1 1 from DB.DBA.RDF_QUAD b table option (index RDF_QUAD) where a.G=b.G and a.S=b.S and a.P=b.P and a.O=b.O);
--select * from DB.DBA.RDF_QUAD a table option (index RDF_QUAD_POGS) where not exists (select top 1 1 from DB.DBA.RDF_QUAD b table option (index RDF_QUAD_POGS) where a.G=b.G and a.S=b.S and a.P=b.P and a.O=b.O);
--select * from DB.DBA.RDF_QUAD a table option (index RDF_QUAD_POGS) where not exists (select top 1 1 from DB.DBA.RDF_QUAD b table option (index RDF_QUAD) where a.G=b.G and a.S=b.S and a.P=b.P and a.O=b.O);
--select * from DB.DBA.RDF_QUAD a table option (index RDF_QUAD) where not exists (select top 1 1 from DB.DBA.RDF_QUAD b table option (index RDF_QUAD_POGS) where a.G=b.G and a.S=b.S and a.P=b.P and a.O=b.O);
--select * from DB.DBA.RDF_QUAD a table option (index RDF_QUAD_OP, index_only) where not exists (select top 1 1 from DB.DBA.RDF_QUAD b table option (index RDF_QUAD_OP, index_only) where a.P=b.P and a.O=b.O);
--select * from DB.DBA.RDF_QUAD a table option (index RDF_QUAD_OP, index_only) where not exists (select top 1 1 from DB.DBA.RDF_QUAD b table option (index RDF_QUAD) where a.P=b.P and a.O=b.O);
--select * from DB.DBA.RDF_QUAD a table option (index RDF_QUAD) where not exists (select top 1 1 from DB.DBA.RDF_QUAD b table option (index RDF_QUAD_OP, index_only) where a.P=b.P and a.O=b.O);

  log_message ('Phase 7 of 9: integrity check (completeness of index RDF_QUAD_POGS of DB.DBA.RDF_QUAD) ...');
  if (exists (select top 1 1 from DB.DBA.RDF_QUAD a table option (index RDF_QUAD) where not exists (select 1 from DB.DBA.RDF_QUAD b table option (loop, index RDF_QUAD_POGS) where a.g = b.g and a.p = b.p and a.o = b.o and a.s = b.s)))
    log_message ('** IMPORTANT WARNING: not all rows of DB.DBA.RDF_QUAD are found in RDF_QUAD_POGS, data reloading is strictly recommended.');

  log_message ('Phase 8 of 9: integrity check (completeness of primary key of DB.DBA.RDF_QUAD) ...');
  if (exists (select top 1 1 from DB.DBA.RDF_QUAD a table option (index RDF_QUAD_POGS) where not exists (select 1 from DB.DBA.RDF_QUAD b table option (loop, index primary key) where a.g = b.g and a.p = b.p and a.o = b.o and a.s = b.s)))
    log_message ('** IMPORTANT WARNING: not all rows of DB.DBA.RDF_QUAD are found in RDF_QUAD_POGS, data reloading is strictly recommended.');

  log_message ('Phase 9 of 9: final checkpoint...');
  registry_set ('__rb_id_only_for_plain_ro_obj', '1');
  exec ('checkpoint');
  log_enable (old_mode, 1);
  log_message ('Update complete.');
}
;

--!AFTER
DB.DBA.RDF_QUAD_OUTLINE_ALL ()
;


create procedure DB.DBA.RDF_QUAD_LOAD_CACHE ()
{
  declare fake integer;
  fake := (select count (rdf_cache_id ('t', RDT_QNAME, RDT_TWOBYTE)) from DB.DBA.RDF_DATATYPE);
  fake := (select count (rdf_cache_id ('l', RL_ID, RL_TWOBYTE)) from DB.DBA.RDF_LANGUAGE);
  fake := (select
      count (dict_put (__rdf_graph_iri2id_dict(), __uname (id_to_iri (RGG_IID)), RGG_IID)) +
      count (dict_put (__rdf_graph_id2iri_dict(), RGG_IID, __uname (id_to_iri (RGG_IID))))
      from DB.DBA.RDF_GRAPH_GROUP );
  fake := (select
      count (dict_put (__rdf_graph_iri2id_dict(), __uname (id_to_iri (RGGM_GROUP_IID)), RGGM_GROUP_IID)) +
      count (dict_put (__rdf_graph_id2iri_dict(), RGGM_GROUP_IID, __uname (id_to_iri (RGGM_GROUP_IID))))
      from DB.DBA.RDF_GRAPH_GROUP_MEMBER );
  fake := (select
      count (dict_put (__rdf_graph_iri2id_dict(), __uname (id_to_iri (RGGM_MEMBER_IID)), RGGM_MEMBER_IID)) +
      count (dict_put (__rdf_graph_id2iri_dict(), RGGM_MEMBER_IID, __uname (id_to_iri (RGGM_MEMBER_IID))))
      from DB.DBA.RDF_GRAPH_GROUP_MEMBER );
  fake := (select
      count (dict_put (__rdf_graph_iri2id_dict(), __uname (id_to_iri (RGU_GRAPH_IID)), RGU_GRAPH_IID)) +
      count (dict_put (__rdf_graph_id2iri_dict(), RGU_GRAPH_IID, __uname (id_to_iri (RGU_GRAPH_IID))))
      from DB.DBA.RDF_GRAPH_USER where RGU_GRAPH_IID <> #i8192 and RGU_GRAPH_IID <> #i0 );
  for select RGG_IID as group_iid from DB.DBA.RDF_GRAPH_GROUP do
    {
      declare membs any;
      membs := (select DB.DBA.VECTOR_AGG (RGGM_MEMBER_IID) from DB.DBA.RDF_GRAPH_GROUP_MEMBER where RGGM_GROUP_IID = group_iid);
      if (length (membs) < 1000)
        {
          gvector_digit_sort (membs, 1, 0, 1);
          dict_put (__rdf_graph_group_dict(), group_iid, membs);
        }
      else
        {
          declare new_membs any;
          new_membs := dict_new (length (membs));
          foreach (IRI_ID m in membs) do dict_put (new_membs, m, 1);
          dict_put (__rdf_graph_group_dict(), group_iid, new_membs);
        }
    }
  fake := (select count (dict_put (__rdf_graph_group_of_privates_dict(), RGGM_MEMBER_IID, 1))
    from DB.DBA.RDF_GRAPH_GROUP_MEMBER where RGGM_GROUP_IID = iri_to_id('http://www.openlinksw.com/schemas/virtrdf#PrivateGraphs'));
  fake := (select count (dict_put (__rdf_graph_default_perms_of_user_dict(0), RGU_USER_ID, RGU_PERMISSIONS))
    from DB.DBA.RDF_GRAPH_USER where RGU_GRAPH_IID = #i0 );
  fake := (select count (dict_put (__rdf_graph_default_perms_of_user_dict(1), RGU_USER_ID, RGU_PERMISSIONS))
    from DB.DBA.RDF_GRAPH_USER where RGU_GRAPH_IID = #i8192 );
  fake := (select count (dict_put (__rdf_graph_public_perms_dict(), RGU_GRAPH_IID, RGU_PERMISSIONS))
    from DB.DBA.RDF_GRAPH_USER where RGU_USER_ID = http_nobody_uid () );
}
;

create procedure DB.DBA.RDF_QUAD_FT_UPGRADE ()
{
  declare stat, msg varchar;
  declare fake integer;
  if (USER <> 'dba')
    signal ('RDFXX', 'Only DBA can alter DB.DBA.RDF_QUAD schema or initialize RDF storage');

  if (sys_stat ('disable_rdf_init') = 1)
    return;
  if (0 = sys_stat ('db_exists') and 1 = sys_stat ('cl_run_local_only'))
    {
      -- v7 index is on by default
      DB.DBA.RDF_OBJ_FT_RULE_ADD ('', '', 'ALL');
    }
  RDF_QUAD_FT_INIT ();
  DB.DBA.RDF_QUAD_LOAD_CACHE ();
  delete from DB.DBA.RDF_GRAPH_USER where not exists (select 1 from DB.DBA.SYS_USERS where RGU_USER_ID = U_ID);
  if (row_count ())
    log_message ('Non-existing users are removed from graph security list');
  fake := (select count (__rdf_graph_specific_perms_of_user (RGU_GRAPH_IID, RGU_USER_ID, RGU_PERMISSIONS))
    from DB.DBA.RDF_GRAPH_USER where RGU_USER_ID <> http_nobody_uid () and not (RGU_GRAPH_IID in (#i0, #i8192)) );
  if (coalesce (virtuoso_ini_item_value ('SPARQL', 'RecoveryMode'), '0') > '0')
    {
      log_message ('Switching to RecoveryMode as set in [SPARQL] section of the configuration.');
      log_message ('For safety, the use of SPARQL_UPDATE role is restricted.');
      exec ('revoke "SPARQL_UPDATE" from "SPARQL"', stat, msg);
      return;
    }
  commit work;
  if (1 <> sys_stat ('cl_run_local_only'))
    goto final_qm_reload;
  if (244 = coalesce ((select COL_DTP from SYS_COLS where "TABLE" = 'DB.DBA.RDF_QUAD' and "COLUMN"='G'), 0))
    {
      __set_64bit_min_bnode_iri_id();
      sequence_set ('RDF_URL_IID_BLANK', iri_id_num (min_bnode_iri_id ()), 1);
    }
  --exec ('create index RO_DIGEST on DB.DBA.RDF_OBJ (RO_DIGEST)', stat, msg);
  if (exists (select top 1 1 from DB.DBA.SYS_COLS
    where "TABLE" = fix_identifier_case ('DB.DBA.RDF_OBJ_RO_FLAGS_WORDS')
    and "COLUMN" = fix_identifier_case ('VT_WORD') )
    and exists (select top 1 1 from DB.DBA.SYS_COLS
    where "TABLE" = fix_identifier_case ('DB.DBA.RDF_OBJ')
    and "COLUMN" = fix_identifier_case ('RO_FLAGS')
    and COL_DTP = 188 ) )
    goto final_qm_reload;
  exec ('DB.DBA.vt_create_text_index (
    fix_identifier_case (''DB.DBA.RDF_OBJ''),
    fix_identifier_case (''RO_FLAGS''),
    fix_identifier_case (''RO_ID''),
    0, 0, vector (), 1, ''*ini*'', ''UTF-8-QR'')', stat, msg);
  __vt_index ('DB.DBA.RDF_QUAD', 'RDF_QUAD_OP', 'O', 'O', 'DB.DBA.RDF_OBJ_RO_FLAGS_WORDS');
  exec ('DB.DBA.vt_batch_update (fix_identifier_case (''DB.DBA.RDF_OBJ''), ''ON'', 1)', stat, msg);

final_qm_reload:
  DB.DBA.SPARQL_RELOAD_QM_GRAPH ();
  insert soft rdf_datatype (rdt_iid, rdt_twobyte, rdt_qname) values
    (iri_to_id ('http://www.openlinksw.com/schemas/virtrdf#Geometry'), 256, 'http://www.openlinksw.com/schemas/virtrdf#Geometry');
  if (0 = sys_stat ('db_exists') and 1 <> sys_stat ('cl_run_local_only'))
    cl_exec ('checkpoint');
  return;
}
;

--!AFTER
DB.DBA.RDF_QUAD_FT_UPGRADE ()
;

--#IF VER=5
--!AFTER __PROCEDURE__ DB.DBA.USER_CREATE !
--#ENDIF
DB.DBA.RDF_CREATE_SPARQL_ROLES ()
;

create aggregate VT_WORD_STRING_ID_AGG (in word_string varchar) returns any
  from vt_word_string_id_init, vt_word_string_id_acc, vt_word_string_id_final
;

create aggregate VT_WORD_STRING_RO_ID_AGG (in word_string varchar) returns any
  from vt_word_string_id_init, vt_word_string_id_acc, vt_word_string_ro_id_final
;


-- loading subclass inference ctxs


create procedure rdfs_pn (in is_class int)
{
  return case when is_class = 1 then iri_to_id ('http://www.w3.org/2000/01/rdf-schema#subClassOf')
  else  iri_to_id ('http://www.w3.org/2000/01/rdf-schema#subPropertyOf') end;
}
;


create procedure rdf_owl_sas_p (in gr iri_id, in name varchar, in super_c iri_id, in c iri_id, in visited any, inout supers any, in pos int)
{
  declare txt varchar;
  declare meta, cc, res any;
  txt := sprintf ('sparql define output:valmode "LONG"  define input:storage ""select ?o from <%s> where { <%s> <http://www.w3.org/2002/07/owl#sameAs> ?o }',
    id_to_iri(gr), id_to_iri(c) );
  exec (txt, null, null,  vector (), 0, meta, null, cc);
  while (0 = exec_next (cc, null, null, res))
    {
      rdfs_closure_1 (gr, name, super_c, res[0], 0, visited, supers, pos);
    }
  exec_close (cc);
  txt := sprintf ('sparql define output:valmode "LONG" define input:storage "" select ?s from <%s> where { ?s <http://www.w3.org/2002/07/owl#sameAs> <%s> }',
    id_to_iri(gr), id_to_iri(c) );
  exec (txt, null, null,  vector (), 0, meta, null, cc);
  while (0 = exec_next (cc, null, null, res))
    {
      rdfs_closure_1 (gr, name, super_c, res[0], 0, visited, supers, pos);
    }
  exec_close (cc);
}
;

create table DB.DBA.SYS_RDF_SCHEMA (RS_NAME VARCHAR , RS_URI VARCHAR, RS_G IRI_ID,
	PRIMARY KEY (RS_NAME, RS_URI))
alter index SYS_RDF_SCHEMA on DB.DBA.SYS_RDF_SCHEMA partition cluster replicated
;


create function rdfs_load_schema (in ri_name varchar, in gn varchar := null) returns integer
{
  declare gr iri_id;
  declare visited any;
  declare supers any;
  declare eq_c, eq_p iri_id;
  declare txt varchar;
  declare idx integer;
  declare cc, res, st, msg, meta  any;
  declare v any;
  declare inx int;
  declare from_text varchar;
  declare rules_count integer;
  from_text := '';
  res := 0;
  if (gn is null)
    {
      for (select RS_URI from DB.DBA.SYS_RDF_SCHEMA where RS_NAME=ri_name) do
        {
          from_text := from_text || sprintf (' from <%s>', RS_URI);
        }
    }
  else
    {
      if (isiri_id (gn))
        from_text := from_text || sprintf (' from <%s>', id_to_iri (gn));
      else
        from_text := from_text || sprintf (' from <%s>', gn);
    }
  if ('' = from_text)
    return 0;

  for (idx := 0; idx <= 4; idx := idx + 1)
    {
      txt := sprintf ('sparql define output:valmode "LONG" define input:storage "" select ?s ?o %s where { ?s <%s> ?o . filter (!isLITERAL (?o)) }',
        from_text, id_to_iri (case (idx) when 4 then rdf_sas_iri () else rdf_owl_iri (idx) end) );
      exec (txt, null, null, vector (), 0, meta, null, cc);
      while (0 = exec_next (cc, null, null, res))
        {
          declare s, o any;
          s := res[0]; o := res[1];
          if (idx = 4)
            {
              rdf_inf_dir (ri_name, s, o, 2);
              rdf_inf_dir (ri_name, s, o, 3);
              rules_count := rules_count + 2;
            }
          else
            {
              rdf_inf_dir (ri_name, o, s, idx);
              rules_count := rules_count + 1;
            }
        }
    }
  exec_close (cc);
-- Loading inverse functional properties
  txt := sprintf ('select DB.DBA.VECTOR_AGG (sub."s") from
  (sparql define output:valmode "LONG" define input:storage ""
    select distinct ?s %s
    where {
          { ?s a <http://www.w3.org/2002/07/owl#InverseFunctionalProperty> }
        union
          { ?s a <http://www.w3.org/2002/07/owl#FunctionalProperty> , <http://www.w3.org/2002/07/owl#SymmetricProperty> }
        union
          { ?s1 a <http://www.w3.org/2002/07/owl#FunctionalProperty> .
            ?s <http://www.w3.org/2002/07/owl#inverseOf> ?s1 }
        union
          { ?s1 a <http://www.w3.org/2002/07/owl#FunctionalProperty> .
            ?s1 <http://www.w3.org/2002/07/owl#inverseOf> ?s }
 }
    ) sub option (QUIETCAST)',
    from_text );
  exec (txt, null, null, vector (), 0, meta, res);
  v := res[0][0];
  if (0 < length (v))
    {
      txt := sprintf ('select DB.DBA.VECTOR_AGG (sub."s") from
      (sparql define output:valmode "LONG" define input:storage ""
        select ?s %s
        where {
            ?s <http://www.w3.org/2000/01/rdf-schema#subPropertyOf> ?sp option (TRANSITIVE, T_MIN 1) .
            filter (?sp = iri (?::0)) } ) sub option (QUIETCAST)',
        from_text );
      for (inx := length (v) - 1; 0 <= inx; inx := inx - 1)
        {
          declare meta1, res1 any;
          declare subprops any;
          exec (txt, null, null, vector (v[inx]), 0, meta1, res1);
          subprops := res1[0][0];
          foreach (IRI_ID subp in subprops) do
            {
              -- dbg_obj_princ ('Handled subproperty ', id_to_iri (subp), ' of ifp ', id_to_iri (v[inx]));
              if (0 >= position (subp, v))
                v := vector_concat (v, vector (subp));
            }
        }
      -- dbg_obj_princ ('known ifps are: '); foreach (IRI_ID i in v) do -- dbg_obj_princ (id_to_iri(i));
      gvector_digit_sort (v, 1, 0, 1);
      rdf_inf_set_ifp_list (ri_name, v); --- Note that this should be after all super/sub relations in order to fill ric_iid_to_rel_ifp
      rules_count := rules_count + length (v);
      txt := sprintf ('
        select vector_agg (sub."o") from
          (sparql define output:valmode "LONG" define input:storage ""
            select distinct ?o %s where {
                  { ?::0 <http://www.openlinksw.com/schemas/virtrdf#nullIFPValue> ?o .
                    filter (isREF (?o)) }
                union
                  {
                    ?subp <http://www.w3.org/2000/01/rdf-schema#subPropertyOf> ?superp option (TRANSITIVE, T_MIN 1) .
                    ?subp <http://www.openlinksw.com/schemas/virtrdf#nullIFPValue> ?o .
                    filter (?superp = ?::0)
                    filter (isREF (?o)) } } ) sub option (QUIETCAST)',
        from_text );
      for (inx := 0; inx < length (v); inx := inx + 1)
        {
          declare meta1, res1 any;
          declare excl any;
          exec (txt, null, null, vector (v[inx]), 0, meta1, res1);
          excl := meta1[0][0];
          if (length (excl) > 0)
            rdf_inf_set_ifp_exclude_list (ri_name, v[inx], excl);
        }
    }
-- Loading inverse functions
  txt := sprintf ('select DB.DBA.VECTOR_CONCAT_AGG (vector (sub."s", sub."o", sub."o", sub."s")) from
  (sparql define input:storage "" select ?s ?o %s where {
    ?s <http://www.w3.org/2002/07/owl#inverseOf> ?o .
    optional { ?o <http://www.w3.org/2002/07/owl#inverseOf> ?s2 . filter (?s2 = ?s ) }
    filter ((str (?s) <= str (?o)) || !BOUND(?s2)) }) sub option (QUIETCAST)',
    from_text );
  exec (txt, null, null, vector (), 0, meta, res);
  v := res[0][0];
  txt := sprintf ('select DB.DBA.VECTOR_CONCAT_AGG (vector (sub."s", sub."s")) from
  (sparql define input:storage "" select ?s %s where {
    ?s a <http://www.w3.org/2002/07/owl#SymmetricProperty> }) sub option (QUIETCAST)',
    from_text );
  exec (txt, null, null, vector (), 0, meta, res);
  v := vector_concat (v, res[0][0]);
  if (0 < length (v))
    {
      gvector_sort (v, 2, 0, 1);
      rdf_inf_set_inverses (ri_name, v);
      rules_count := rules_count + length (v);
    }
-- Loading bitmask properties of functions
  txt := sprintf ('select DB.DBA.VECTOR_CONCAT_AGG (vector (sub."s", 1)) from
  (sparql define input:storage "" select ?s %s where {
        { ?s a <http://www.w3.org/2002/07/owl#TransitiveProperty> }
      union
        { ?s <http://www.w3.org/2002/07/owl#inverseOf> [ a <http://www.w3.org/2002/07/owl#TransitiveProperty> ] }
      union
        { [ a <http://www.w3.org/2002/07/owl#TransitiveProperty> ] <http://www.w3.org/2002/07/owl#inverseOf> ?s }
    } ) sub option (QUIETCAST)',
    from_text );
  exec (txt, null, null, vector (), 0, meta, res);
  v := res[0][0];
  if (0 < length (v))
    {
      gvector_sort (v, 2, 0, 1);
      rdf_inf_set_prop_props (ri_name, v);
      rules_count := rules_count + length (v);
    }
  jso_mark_affected (ri_name);
  log_text ('jso_mark_affected (?)', ri_name);
--  if (not rules_count)
    rdf_inf_dir (ri_name, null, null, 0);
  return rules_count + 1;
}
;

create procedure rdf_schema_ld ()
{
  if (1 <> sys_stat ('cl_run_local_only'))
    return 0;
  return (select count (*) from (select distinct s.RS_NAME from DB.DBA.SYS_RDF_SCHEMA s) sub where 0 = rdfs_load_schema (sub.RS_NAME));
}
;

rdf_schema_ld ()
;


create function CL_RDF_INF_CHANGED_SRV (in name varchar) returns integer
{
  declare res integer;
  set isolation = 'committed';
  rdf_inf_clear (name);
  return case (rdfs_load_schema (name)) when 0 then 1 else 0 end;
  return res;
}
;

create procedure CL_RDF_INF_CHANGED (in name varchar)
{
  declare aq any;
  if (2 = sys_stat ('cl_run_local_only'))
    return;
  aq := async_queue (1, 4);
  aq_request (aq, 'DB.DBA.CL_RDF_INF_CHANGED_SRV', vector (name));
  aq_wait_all (aq);
}
;

create function rdfs_rule_set (in name varchar, in gn varchar, in remove int := 0) returns integer
{
  delete from DB.DBA.SYS_RDF_SCHEMA where RS_NAME = name and RS_URI = gn;
  if (not remove)
    {
      insert into DB.DBA.SYS_RDF_SCHEMA (RS_NAME, RS_URI) values (name, gn);
    }
  commit work;
  if (0 = sys_stat ('cl_run_local_only'))
    {
      DB.DBA.SECURITY_CL_EXEC_AND_LOG ('DB.DBA.CL_RDF_INF_CHANGED (?)', vector (name));
      return 1;
    }
  else
    {
      declare res integer;
      rdf_inf_clear (name);
      res := rdfs_load_schema (name);
      log_text ('db.dba.rdfs_load_schema (?)', name);
      return res;
    }
}
;

create function DB.DBA.RDF_IID_OF_QNAME (in qname varchar) returns IRI_ID
{
  whenever sqlstate '*' goto retnull;
  return iri_to_id (qname, 0, null);
  retnull:
  return null;
}
;

create procedure SPARQL_INI_PARAMS (inout metas any, inout dta any)
{
  declare item_cnt int;
  declare items any;
  declare item_name, item_value varchar;
  declare res_dict any;
  declare tmp any;

  item_cnt := cfg_item_count (virtuoso_ini_path (), 'SPARQL');
  tmp := string_output ();

  for (declare i int, i := 0; i < item_cnt; i := i + 1)
    {
      item_name := cfg_item_name (virtuoso_ini_path (), 'SPARQL', i);
      item_value := virtuoso_ini_item_value ('SPARQL', item_name);
      http (sprintf ('<http://www.openlinksw.com/schemas/virtini#SPARQL> <http://www.openlinksw.com/schemas/virtini#%U> "%s" .\r\n',
	    item_name, item_value), tmp);
    }
  tmp := string_output_string (tmp);
  res_dict := DB.DBA.RDF_TTL2HASH (tmp, '');
  metas := vector (vector (vector ('res_dict', 242, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0)), 1);
  dta := vector (vector (res_dict));
}
;


--
-- Make geometries for geo:long, geo:lat pairs
--
create procedure num_or_null (in n any)
{
  declare exit handler for sqlstate '*'{ return null; };
  return cast (cast (n as decimal) as real);
}
;

create procedure GEO_FILL_SRV  (in arr any, in fill int)
{
  declare lat, lng, s, g, l any;
  declare inx int;
  log_enable (2, 1);
  declare geop iri_id_8;
  declare gs, ss, os any array;
  gs := make_array (fill, 'any');
  ss := make_array (fill, 'any');
  os := make_array (fill, 'any');
  geop := iri_to_id ('http://www.w3.org/2003/01/geo/wgs84_pos#geometry');
  for (inx := 0; inx < fill; inx := inx + 1)
    {
      l := aref_set_0 (arr, inx);
      gs[inx]  := aref_set_0 (l, 0);
      ss[inx] := aref_set_0 (l, 1);
      {
	  -- can have some bad input so skip and do next
	  declare exit handler for sqlstate '*' {
	  os[inx] := null;
	  goto next;
	};
      os[inx] := st_point (aref_set_0 (l, 2), aref_set_0 (l, 3));
    }
      next:;
    }
  for vectored (in g1 iri_id_8 := gs, in s1 iri_id_8 := ss, in o1 any array := os)
    {
      if (o1 is not null)
	{
	  insert soft rdf_quad (g, s, p, o) values ("g1", "s1", geop,
	      rdf_geo_add (rdf_box (o1, 256, 257, 0, 1)));
	}
    }
}
;

create procedure rdf_geo_fill (in threads int := null, in batch int := 100000)
{
  declare arr, fill, aq, ctr any;
  if (threads is null) threads := sys_stat ('enable_qp');
  aq := async_queue (threads);
  arr := make_array (batch, 'any');
  fill := 0;
  ctr := 0;
  log_enable (2, 1);
  for (select "s", "long", "lat", "g" from (sparql define output:valmode "LONG"
      prefix geo: <http://www.w3.org/2003/01/geo/wgs84_pos#>
      select ?g ?s ?long ?lat where {
          graph ?g {
              ?s geo:long ?long . ?s geo:lat ?lat . } } ) as f) do
    {
      declare lat2, long2 any;
      long2 := num_or_null (rdf_box_data ("long"));
      lat2 := num_or_null (rdf_box_data ("lat"));
      if (isnumeric (long2) and isnumeric (lat2))
	{
	  arr[fill] := vector ("g", "s", long2, lat2);
	  fill := fill + 1;
	  if (batch = fill)
	    {
	      aq_request (aq, 'DB.DBA.GEO_FILL_SRV', vector (arr, fill));
	      ctr := ctr + 1;
	      if (ctr > 100)
		{
		  commit work;
		  aq_wait_all (aq);
		  ctr := 0;
		}
	      arr := make_array (batch, 'any');
	      fill := 0;
	    }
	}
    }
  geo_fill_srv (arr, fill);
  commit work;
  aq_wait_all (aq);
}
;
