1162 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1162 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*  $Id$
 | 
						|
 | 
						|
    Part of SWI-Prolog
 | 
						|
 | 
						|
    Author:        Jan Wielemaker
 | 
						|
    E-mail:        wielemak@science.uva.nl
 | 
						|
    WWW:           http://www.swi-prolog.org
 | 
						|
    Copyright (C): 2006, University of Amsterdam
 | 
						|
 | 
						|
    This program is free software; you can redistribute it and/or
 | 
						|
    modify it under the terms of the GNU General Public License
 | 
						|
    as published by the Free Software Foundation; either version 2
 | 
						|
    of the License, or (at your option) any later version.
 | 
						|
 | 
						|
    This program is distributed in the hope that it will be useful,
 | 
						|
    but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
						|
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
						|
    GNU General Public License for more details.
 | 
						|
 | 
						|
    You should have received a copy of the GNU Lesser General Public
 | 
						|
    License along with this library; if not, write to the Free Software
 | 
						|
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 | 
						|
*/
 | 
						|
 | 
						|
#ifdef HAVE_CONFIG_H
 | 
						|
#include <config.h>
 | 
						|
#endif
 | 
						|
 | 
						|
#include <SWI-Stream.h>
 | 
						|
#include <SWI-Prolog.h>
 | 
						|
#include "avl.h"
 | 
						|
#include "lock.h"
 | 
						|
#include "atom.h"
 | 
						|
#include "debug.h"
 | 
						|
#include <string.h>
 | 
						|
#include <assert.h>
 | 
						|
#ifdef __WINDOWS__
 | 
						|
#define inline __inline
 | 
						|
#endif
 | 
						|
 | 
						|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | 
						|
This file realises the low-level support   for  indexing literals in the
 | 
						|
semantic web library. The idea is to   make a map from abstracted tokens
 | 
						|
from each literal to  the  exact   literals.  Abstraction  introduces  a
 | 
						|
certain amount of ambiguity that  makes   fuzzy  matching possible. Good
 | 
						|
abstraction candidates are the Porter Stem  or Snowbal algorithm and the
 | 
						|
Double Metaphone algorithm. Both  are  provide   by  the  SWI-Prolog NLP
 | 
						|
package.
 | 
						|
 | 
						|
Basic query provides a  set  of   abstracted  terms  and  requests those
 | 
						|
literals containing all of them. We   maintain  ordered sets of literals
 | 
						|
and do set-intersection on them to achieve good linear performance.
 | 
						|
 | 
						|
Some current E-culture project statistics (porter stem)
 | 
						|
 | 
						|
	  # stems: 0.4 million
 | 
						|
	  # literals: 0.9 million
 | 
						|
	  # stem->literal relations: 3.1 million
 | 
						|
 | 
						|
Av. literals/stem: about 8.
 | 
						|
 | 
						|
Searching is done using
 | 
						|
 | 
						|
	rdf_find_literal_map(Map, SetOfAbstract, -Literals)
 | 
						|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
 | 
						|
 | 
						|
#define AM_MAGIC	0x6ab19e8e
 | 
						|
 | 
						|
typedef struct atom_map
 | 
						|
{ long		magic;			/* AM_MAGIC */
 | 
						|
  size_t	value_count;		/* total # values */
 | 
						|
  rwlock	lock;			/* Multi-threaded access */
 | 
						|
  avl_tree	tree;			/* AVL tree */
 | 
						|
} atom_map;
 | 
						|
 | 
						|
typedef void *datum;
 | 
						|
 | 
						|
#define S_MAGIC 0x8734abcd
 | 
						|
 | 
						|
typedef struct atom_set
 | 
						|
{ size_t  size;				/* # cells in use */
 | 
						|
  size_t  allocated;			/* # cells allocated */
 | 
						|
  datum *atoms;			/* allocated cells */
 | 
						|
#ifdef O_SECURE
 | 
						|
  long	  magic;
 | 
						|
#endif
 | 
						|
} atom_set;
 | 
						|
 | 
						|
 | 
						|
#define ND_MAGIC 0x67b49a23
 | 
						|
#define ND_MAGIC_EX 0x753ab3c
 | 
						|
 | 
						|
typedef struct node_data
 | 
						|
{ datum		key;
 | 
						|
  atom_set     *values;
 | 
						|
#ifdef O_SECURE
 | 
						|
  long		magic;
 | 
						|
#endif
 | 
						|
} node_data;
 | 
						|
 | 
						|
typedef struct node_data_ex
 | 
						|
{ node_data	data;
 | 
						|
  atom_info	atom;
 | 
						|
#ifdef O_SECURE
 | 
						|
  long		magic;
 | 
						|
#endif
 | 
						|
} node_data_ex;
 | 
						|
 | 
						|
 | 
						|
#define RDLOCK(map)			rdlock(&map->lock)
 | 
						|
#define WRLOCK(map, allowreaders)	wrlock(&map->lock, allowreaders)
 | 
						|
#define LOCKOUT_READERS(map)		lockout_readers(&map->lock)
 | 
						|
#define REALLOW_READERS(map)		reallow_readers(&map->lock)
 | 
						|
#define WRUNLOCK(map)			unlock(&map->lock, FALSE)
 | 
						|
#define RDUNLOCK(map)			unlock(&map->lock, TRUE)
 | 
						|
 | 
						|
		 /*******************************
 | 
						|
		 *	     BASIC STUFF	*
 | 
						|
		 *******************************/
 | 
						|
 | 
						|
static functor_t FUNCTOR_error2;
 | 
						|
static functor_t FUNCTOR_type_error2;
 | 
						|
static functor_t FUNCTOR_domain_error2;
 | 
						|
static functor_t FUNCTOR_resource_error1;
 | 
						|
static functor_t FUNCTOR_atom_map1;
 | 
						|
static functor_t FUNCTOR_size2;
 | 
						|
static functor_t FUNCTOR_not1;
 | 
						|
static atom_t	 ATOM_all;
 | 
						|
static atom_t	 ATOM_case;
 | 
						|
static atom_t	 ATOM_prefix;
 | 
						|
static atom_t	 ATOM_le;
 | 
						|
static atom_t	 ATOM_ge;
 | 
						|
static atom_t	 ATOM_between;
 | 
						|
static atom_t	 ATOM_key;
 | 
						|
 | 
						|
#define MKFUNCTOR(n,a) \
 | 
						|
	FUNCTOR_ ## n ## a = PL_new_functor(PL_new_atom(#n), a)
 | 
						|
#define MKATOM(n) \
 | 
						|
	ATOM_ ## n = PL_new_atom(#n)
 | 
						|
 | 
						|
static void
 | 
						|
init_functors()
 | 
						|
{ FUNCTOR_atom_map1 = PL_new_functor(PL_new_atom("$literal_map"), 1);
 | 
						|
 | 
						|
  MKFUNCTOR(error, 2);
 | 
						|
  MKFUNCTOR(type_error, 2);
 | 
						|
  MKFUNCTOR(domain_error, 2);
 | 
						|
  MKFUNCTOR(resource_error, 1);
 | 
						|
  MKFUNCTOR(size, 2);
 | 
						|
  MKFUNCTOR(not, 1);
 | 
						|
 | 
						|
  MKATOM(all);
 | 
						|
  MKATOM(case);
 | 
						|
  MKATOM(prefix);
 | 
						|
  MKATOM(le);
 | 
						|
  MKATOM(ge);
 | 
						|
  MKATOM(between);
 | 
						|
  MKATOM(key);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static int
 | 
						|
type_error(term_t actual, const char *expected)
 | 
						|
{ term_t ex;
 | 
						|
 | 
						|
  if ( (ex = PL_new_term_ref()) &&
 | 
						|
       PL_unify_term(ex,
 | 
						|
		     PL_FUNCTOR, FUNCTOR_error2,
 | 
						|
		       PL_FUNCTOR, FUNCTOR_type_error2,
 | 
						|
		         PL_CHARS, expected,
 | 
						|
		         PL_TERM, actual,
 | 
						|
		       PL_VARIABLE) )
 | 
						|
    return PL_raise_exception(ex);
 | 
						|
 | 
						|
  return FALSE;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static int
 | 
						|
domain_error(term_t actual, const char *expected)
 | 
						|
{ term_t ex;
 | 
						|
 | 
						|
  if ( (ex = PL_new_term_ref()) &&
 | 
						|
       PL_unify_term(ex,
 | 
						|
		     PL_FUNCTOR, FUNCTOR_error2,
 | 
						|
		       PL_FUNCTOR, FUNCTOR_domain_error2,
 | 
						|
		         PL_CHARS, expected,
 | 
						|
		         PL_TERM, actual,
 | 
						|
		       PL_VARIABLE) )
 | 
						|
    return PL_raise_exception(ex);
 | 
						|
 | 
						|
  return FALSE;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static int
 | 
						|
resource_error(const char *what)
 | 
						|
{ term_t ex;
 | 
						|
 | 
						|
  if ( (ex = PL_new_term_ref()) &&
 | 
						|
       PL_unify_term(ex,
 | 
						|
		     PL_FUNCTOR, FUNCTOR_error2,
 | 
						|
		       PL_FUNCTOR, FUNCTOR_resource_error1,
 | 
						|
		         PL_CHARS, what,
 | 
						|
		       PL_VARIABLE) )
 | 
						|
    return PL_raise_exception(ex);
 | 
						|
 | 
						|
  return FALSE;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static int
 | 
						|
representation_error(const char *what)
 | 
						|
{ term_t ex = PL_new_term_ref();
 | 
						|
 | 
						|
  if ( (ex = PL_new_term_ref()) &&
 | 
						|
       PL_unify_term(ex,
 | 
						|
		     PL_FUNCTOR, FUNCTOR_error2,
 | 
						|
		       PL_FUNCTOR_CHARS, "representation_error", 1,
 | 
						|
		         PL_CHARS, what,
 | 
						|
		       PL_VARIABLE) )
 | 
						|
    return PL_raise_exception(ex);
 | 
						|
 | 
						|
  return FALSE;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
 | 
						|
static int
 | 
						|
get_atom_ex(term_t t, atom_t *a)
 | 
						|
{ if ( PL_get_atom(t, a) )
 | 
						|
    return TRUE;
 | 
						|
 | 
						|
  return type_error(t, "atom");
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static int
 | 
						|
get_long_ex(term_t t, long *v)
 | 
						|
{ if ( PL_get_long(t, v) )
 | 
						|
    return TRUE;
 | 
						|
 | 
						|
  return type_error(t, "integer");
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static int
 | 
						|
get_atom_map(term_t t, atom_map **map)
 | 
						|
{ if ( PL_is_functor(t, FUNCTOR_atom_map1) )
 | 
						|
  { term_t a = PL_new_term_ref();
 | 
						|
    void *ptr;
 | 
						|
 | 
						|
    _PL_get_arg(1, t, a);
 | 
						|
    if ( PL_get_pointer(a, &ptr) )
 | 
						|
    { atom_map *am = ptr;
 | 
						|
 | 
						|
      if ( am->magic == AM_MAGIC )
 | 
						|
      { *map = am;
 | 
						|
        return TRUE;
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  return type_error(t, "atom_map");
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static int
 | 
						|
unify_atom_map(term_t t, atom_map *map)
 | 
						|
{ return PL_unify_term(t, PL_FUNCTOR, FUNCTOR_atom_map1,
 | 
						|
		            PL_POINTER, map);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
		 /*******************************
 | 
						|
		 *	       DATUM		*
 | 
						|
		 *******************************/
 | 
						|
 | 
						|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | 
						|
Datum is either an atom or a 31-bit  signed integer. Atoms are shifted 7
 | 
						|
bits
 | 
						|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
 | 
						|
 | 
						|
#define ATOM_TAG_BITS 7
 | 
						|
#define ATOM_TAG 0x1
 | 
						|
 | 
						|
#define tag(d)		((long)(d)&0x1)
 | 
						|
#define isAtomDatum(d)  ((long)(d)&ATOM_TAG)
 | 
						|
#define isIntDatum(d)	!isAtomDatum(d)
 | 
						|
 | 
						|
#define MAP_MIN_INT	(-(long)(1L<<(sizeof(long)*8 - 1 - 1)))
 | 
						|
#define MAP_MAX_INT	(-MAP_MIN_INT - 1)
 | 
						|
 | 
						|
static intptr_t atom_mask;
 | 
						|
 | 
						|
static void
 | 
						|
init_datum_store()
 | 
						|
{ atom_t a = PL_new_atom("[]");
 | 
						|
 | 
						|
  atom_mask = a & ((1<<(ATOM_TAG_BITS-1))-1);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static inline atom_t
 | 
						|
atom_from_datum(datum d)
 | 
						|
{ unsigned long v = (unsigned long)d;
 | 
						|
  atom_t a;
 | 
						|
 | 
						|
  a  = ((v&~0x1)<<(ATOM_TAG_BITS-1))|atom_mask;
 | 
						|
  DEBUG(9, Sdprintf("0x%lx --> %s\n", v, PL_atom_chars(a)));
 | 
						|
  return a;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static inline long
 | 
						|
long_from_datum(datum d)
 | 
						|
{ long v = (long)d;
 | 
						|
 | 
						|
  return (v>>1);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static inline datum
 | 
						|
atom_to_datum(atom_t a)
 | 
						|
{ uintptr_t v = (a>>(ATOM_TAG_BITS-1))|ATOM_TAG;
 | 
						|
 | 
						|
  SECURE(assert(atom_from_datum((datum)v) == a));
 | 
						|
  DEBUG(9, Sdprintf("Atom %s --> 0x%lx\n", PL_atom_chars(a), v));
 | 
						|
 | 
						|
  return (datum)v;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static inline datum
 | 
						|
long_to_datum(long v)
 | 
						|
{ return (datum)(v<<1);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static int
 | 
						|
get_datum(term_t t, datum* d)
 | 
						|
{ atom_t a;
 | 
						|
  long l;
 | 
						|
 | 
						|
  if ( PL_get_atom(t, &a) )
 | 
						|
  { *d = atom_to_datum(a);
 | 
						|
    return TRUE;
 | 
						|
  } else if ( PL_get_long(t, &l) )
 | 
						|
  { if ( l < MAP_MIN_INT || l > MAP_MAX_INT )
 | 
						|
      return representation_error("integer_range");
 | 
						|
 | 
						|
    *d = long_to_datum(l);
 | 
						|
    return TRUE;
 | 
						|
  }
 | 
						|
 | 
						|
  return type_error(t, "atom or integer");
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static int
 | 
						|
get_search_datum(term_t t, node_data_ex *search)
 | 
						|
{ atom_t a;
 | 
						|
  long l;
 | 
						|
 | 
						|
  SECURE(search->magic = ND_MAGIC_EX);
 | 
						|
 | 
						|
  if ( PL_get_atom(t, &a) )
 | 
						|
  { search->data.key = atom_to_datum(a);
 | 
						|
    search->atom.handle   = a;
 | 
						|
    search->atom.resolved = FALSE;
 | 
						|
    return TRUE;
 | 
						|
  } else if ( PL_get_long(t, &l) )
 | 
						|
  { if ( l < MAP_MIN_INT || l > MAP_MAX_INT )
 | 
						|
      return representation_error("integer_range");
 | 
						|
 | 
						|
    search->data.key = long_to_datum(l);
 | 
						|
    return TRUE;
 | 
						|
  }
 | 
						|
 | 
						|
  return type_error(t, "atom or integer");
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static int
 | 
						|
unify_datum(term_t t, datum d)
 | 
						|
{ unsigned long v = (unsigned long)d;
 | 
						|
 | 
						|
  if ( isAtomDatum(v) )
 | 
						|
    return PL_unify_atom(t, atom_from_datum(d));
 | 
						|
  else
 | 
						|
    return PL_unify_integer(t, long_from_datum(d));
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static void
 | 
						|
lock_datum(datum d)
 | 
						|
{ unsigned long v = (unsigned long)d;
 | 
						|
 | 
						|
  if ( isAtomDatum(v) )
 | 
						|
    PL_register_atom(atom_from_datum(d));
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static void
 | 
						|
unlock_datum(datum d)
 | 
						|
{ unsigned long v = (unsigned long)d;
 | 
						|
 | 
						|
  if ( isAtomDatum(v) )
 | 
						|
    PL_unregister_atom(atom_from_datum(d));
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static const char *
 | 
						|
format_datum(datum d, char *buf)
 | 
						|
{ static char tmp[20];
 | 
						|
 | 
						|
  if ( isAtomDatum(d) )
 | 
						|
    return PL_atom_chars(atom_from_datum(d));
 | 
						|
 | 
						|
  if ( !buf )
 | 
						|
    buf = tmp;
 | 
						|
  Ssprintf(buf, "%ld", long_from_datum(d));
 | 
						|
 | 
						|
  return buf;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
 | 
						|
		 /*******************************
 | 
						|
		 *	     ATOM SETS		*
 | 
						|
		 *******************************/
 | 
						|
 | 
						|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | 
						|
A set of atoms (literals) is a   sorted  array of atom-handles. They are
 | 
						|
sorted simply by handle as we are  not   interested  in the value in the
 | 
						|
actual atom.  Search is implemeted as binary search.
 | 
						|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
 | 
						|
 | 
						|
#define AS_INITIAL_SIZE 4
 | 
						|
 | 
						|
static atom_set *
 | 
						|
new_atom_set(datum a0)
 | 
						|
{ atom_set *as;
 | 
						|
 | 
						|
  if ( (as = malloc(sizeof(*as))) &&
 | 
						|
       (as->atoms = malloc(sizeof(datum)*AS_INITIAL_SIZE)) )
 | 
						|
  { lock_datum(a0);
 | 
						|
    as->size = 1;
 | 
						|
    as->allocated = AS_INITIAL_SIZE;
 | 
						|
    as->atoms[0] = a0;
 | 
						|
    SECURE(as->magic = S_MAGIC);
 | 
						|
  }
 | 
						|
 | 
						|
  return as;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | 
						|
find_in_atom_set(atom_set *as, datum  a)  returns   a  pointer  to  the
 | 
						|
location of the atom or, if the atom  isn't there, to the first location
 | 
						|
*after* the atom
 | 
						|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
 | 
						|
 | 
						|
static datum *
 | 
						|
find_in_atom_set(atom_set *as, datum a, int *found)
 | 
						|
{ const datum *ap = (const datum *)as->atoms;
 | 
						|
  const datum *ep = &ap[as->size];
 | 
						|
 | 
						|
  SECURE(assert(as->magic == S_MAGIC));
 | 
						|
 | 
						|
  for(;;)
 | 
						|
  { const datum *cp = ap+(ep-ap)/2;
 | 
						|
 | 
						|
    if ( a < *cp )
 | 
						|
    { if ( ep == cp )
 | 
						|
      { *found = FALSE;
 | 
						|
	return (datum*)cp;
 | 
						|
      }
 | 
						|
      ep = cp;
 | 
						|
    } else if ( a > *cp )
 | 
						|
    { if ( ap == cp )
 | 
						|
      { cp++;
 | 
						|
	*found = FALSE;
 | 
						|
	return (datum*)cp;
 | 
						|
      }
 | 
						|
      ap = cp;
 | 
						|
    } else
 | 
						|
    { *found = TRUE;
 | 
						|
      return (datum*)cp;
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static int
 | 
						|
in_atom_set(atom_set *as, datum a)
 | 
						|
{ int found;
 | 
						|
 | 
						|
  find_in_atom_set(as, a, &found);
 | 
						|
 | 
						|
  return found;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
#define ptr_diff(p1, p2) ((char *)(p1) - (char *)(p2))
 | 
						|
 | 
						|
static int
 | 
						|
insert_atom_set(atom_set *as, datum a)
 | 
						|
{ int found;
 | 
						|
  datum *ap = find_in_atom_set(as, a, &found);
 | 
						|
 | 
						|
  if ( !found )
 | 
						|
  { lock_datum(a);
 | 
						|
 | 
						|
    if ( as->size == as->allocated )
 | 
						|
    { datum *na;
 | 
						|
      size_t newsize = as->allocated*2;
 | 
						|
 | 
						|
      if ( !(na = realloc(as->atoms, sizeof(datum)*newsize)) )
 | 
						|
	return -1;
 | 
						|
      ap += na-as->atoms;
 | 
						|
      as->atoms = na;
 | 
						|
      as->allocated = newsize;
 | 
						|
    }
 | 
						|
    assert(as->size < as->allocated);
 | 
						|
 | 
						|
    memmove(ap+1, ap, ptr_diff(&as->atoms[as->size], ap));
 | 
						|
    as->size++;
 | 
						|
    *ap = a;
 | 
						|
 | 
						|
    return 1;
 | 
						|
  }
 | 
						|
 | 
						|
  return 0;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static int
 | 
						|
delete_atom_set(atom_set *as, datum a)
 | 
						|
{ int found;
 | 
						|
  datum *ap = find_in_atom_set(as, a, &found);
 | 
						|
 | 
						|
  if ( found )
 | 
						|
  { unlock_datum(a);
 | 
						|
    as->size--;
 | 
						|
    memmove(ap, ap+1, ptr_diff(&as->atoms[as->size], ap));
 | 
						|
  }
 | 
						|
 | 
						|
  return found;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static void
 | 
						|
destroy_atom_set(atom_set *as)
 | 
						|
{ size_t i;
 | 
						|
 | 
						|
  for(i=0; i<as->size; i++)
 | 
						|
    unlock_datum(as->atoms[i]);
 | 
						|
 | 
						|
  free(as->atoms);
 | 
						|
  free(as);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static void
 | 
						|
free_node_data(void *ptr)
 | 
						|
{ node_data *data = ptr;
 | 
						|
 | 
						|
  DEBUG(2,
 | 
						|
	char b[20];
 | 
						|
	Sdprintf("Destroying node with key = %s\n",
 | 
						|
		 format_datum(data->key, b)));
 | 
						|
 | 
						|
  unlock_datum(data->key);
 | 
						|
  destroy_atom_set(data->values);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
		 /*******************************
 | 
						|
		 *	   TREE INTERFACE	*
 | 
						|
		 *******************************/
 | 
						|
 | 
						|
static int
 | 
						|
cmp_node_data(void *l, void *r, NODE type)
 | 
						|
{ node_data_ex *e1 = l;
 | 
						|
  node_data *n2 = r;
 | 
						|
  datum *d1 = e1->data.key;
 | 
						|
  datum *d2 = n2->key;
 | 
						|
  int d;
 | 
						|
 | 
						|
  SECURE(assert(e1->magic == ND_MAGIC_EX));
 | 
						|
 | 
						|
  if ( (d=(tag(d1)-tag(d2))) == 0 )
 | 
						|
  { if ( isAtomDatum(d1) )
 | 
						|
    { return cmp_atom_info(&e1->atom, atom_from_datum(d2));
 | 
						|
    } else
 | 
						|
    { long l1 = long_from_datum(d1);
 | 
						|
      long l2 = long_from_datum(d2);
 | 
						|
 | 
						|
      return l1 > l2 ? 1 : l1 < l2 ? -1 : 0;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  return d;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static void
 | 
						|
init_tree_map(atom_map *m)
 | 
						|
{ avlinit(&m->tree,
 | 
						|
	  NULL, sizeof(node_data),
 | 
						|
	  cmp_node_data,
 | 
						|
	  free_node_data,		/* destroy */
 | 
						|
	  NULL,				/* alloc */
 | 
						|
	  NULL);			/* free */
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static foreign_t
 | 
						|
new_atom_map(term_t handle)
 | 
						|
{ atom_map *m;
 | 
						|
 | 
						|
  if ( !(m=malloc(sizeof(*m))) )
 | 
						|
    return resource_error("memory");
 | 
						|
 | 
						|
  memset(m, 0, sizeof(*m));
 | 
						|
  init_lock(&m->lock);
 | 
						|
  init_tree_map(m);
 | 
						|
  m->magic = AM_MAGIC;
 | 
						|
 | 
						|
  return unify_atom_map(handle, m);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static foreign_t
 | 
						|
destroy_atom_map(term_t handle)
 | 
						|
{ atom_map *m;
 | 
						|
 | 
						|
  if ( !get_atom_map(handle, &m) )
 | 
						|
    return FALSE;
 | 
						|
 | 
						|
  WRLOCK(m, FALSE);
 | 
						|
  avlfree(&m->tree);
 | 
						|
  m->magic = 0;
 | 
						|
  WRUNLOCK(m);
 | 
						|
  destroy_lock(&m->lock);
 | 
						|
  free(m);
 | 
						|
 | 
						|
  return TRUE;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
		 /*******************************
 | 
						|
		 *	       INSERT		*
 | 
						|
		 *******************************/
 | 
						|
 | 
						|
 | 
						|
static foreign_t
 | 
						|
insert_atom_map4(term_t handle, term_t from, term_t to, term_t keys)
 | 
						|
{ atom_map *map;
 | 
						|
  datum a2;
 | 
						|
  node_data_ex search;
 | 
						|
  node_data *data;
 | 
						|
 | 
						|
  if ( !get_atom_map(handle, &map) ||
 | 
						|
       !get_search_datum(from, &search) ||
 | 
						|
       !get_datum(to, &a2) )
 | 
						|
    return FALSE;
 | 
						|
 | 
						|
  if ( !WRLOCK(map, FALSE) )
 | 
						|
    return FALSE;
 | 
						|
 | 
						|
  if ( (data=avlfind(&map->tree, &search)) )
 | 
						|
  { int rc;
 | 
						|
 | 
						|
    SECURE(assert(data->magic == ND_MAGIC));
 | 
						|
 | 
						|
    if ( (rc=insert_atom_set(data->values, a2)) < 0 )
 | 
						|
      return resource_error("memory");
 | 
						|
 | 
						|
    if ( rc )
 | 
						|
      map->value_count++;
 | 
						|
  } else
 | 
						|
  { if ( keys && !PL_unify_integer(keys, map->tree.count+1) )
 | 
						|
    { WRUNLOCK(map);
 | 
						|
      return FALSE;
 | 
						|
    }
 | 
						|
    if ( !(search.data.values = new_atom_set(a2)) )
 | 
						|
      return resource_error("memory");
 | 
						|
    lock_datum(search.data.key);
 | 
						|
    SECURE(search.magic = ND_MAGIC);
 | 
						|
 | 
						|
    data = avlins(&map->tree, &search);
 | 
						|
    assert(!data);
 | 
						|
    map->value_count++;
 | 
						|
  }
 | 
						|
 | 
						|
  WRUNLOCK(map);
 | 
						|
 | 
						|
  return TRUE;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static foreign_t
 | 
						|
insert_atom_map3(term_t handle, term_t from, term_t to)
 | 
						|
{ return insert_atom_map4(handle, from, to, 0);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
		 /*******************************
 | 
						|
		 *	       DELETE		*
 | 
						|
		 *******************************/
 | 
						|
 | 
						|
static foreign_t
 | 
						|
delete_atom_map2(term_t handle, term_t from)
 | 
						|
{ atom_map *map;
 | 
						|
  node_data_ex search;
 | 
						|
  node_data *data;
 | 
						|
 | 
						|
  if ( !get_atom_map(handle, &map) ||
 | 
						|
       !get_search_datum(from, &search) )
 | 
						|
    return FALSE;
 | 
						|
 | 
						|
  if ( !WRLOCK(map, TRUE) )
 | 
						|
    return FALSE;
 | 
						|
 | 
						|
					/* TBD: Single pass? */
 | 
						|
  if ( (data = avlfind(&map->tree, &search)) )
 | 
						|
  { LOCKOUT_READERS(map);
 | 
						|
    map->value_count -= data->values->size;
 | 
						|
    search.data = *data;
 | 
						|
    avldel(&map->tree, &search);
 | 
						|
    REALLOW_READERS(map);
 | 
						|
  }
 | 
						|
 | 
						|
  WRUNLOCK(map);
 | 
						|
 | 
						|
  return TRUE;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static foreign_t
 | 
						|
delete_atom_map3(term_t handle, term_t from, term_t to)
 | 
						|
{ atom_map *map;
 | 
						|
  node_data_ex search;
 | 
						|
  node_data *data;
 | 
						|
  datum a2;
 | 
						|
 | 
						|
  if ( !get_atom_map(handle, &map) ||
 | 
						|
       !get_search_datum(from, &search) ||
 | 
						|
       !get_datum(to, &a2) )
 | 
						|
    return FALSE;
 | 
						|
 | 
						|
  if ( !WRLOCK(map, TRUE) )
 | 
						|
    return FALSE;
 | 
						|
 | 
						|
  if ( (data = avlfind(&map->tree, &search)) &&
 | 
						|
       in_atom_set(data->values, a2) )
 | 
						|
  { atom_set *as = data->values;
 | 
						|
 | 
						|
    LOCKOUT_READERS(map);
 | 
						|
    if ( delete_atom_set(as, a2) )
 | 
						|
    { map->value_count--;
 | 
						|
      if ( as->size == 0 )
 | 
						|
      { search.data = *data;
 | 
						|
	avldel(&map->tree, &search);
 | 
						|
      }
 | 
						|
    }
 | 
						|
    REALLOW_READERS(map);
 | 
						|
  }
 | 
						|
 | 
						|
  WRUNLOCK(map);
 | 
						|
 | 
						|
  return TRUE;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
		 /*******************************
 | 
						|
		 *	      SEARCH		*
 | 
						|
		 *******************************/
 | 
						|
 | 
						|
typedef struct
 | 
						|
{ atom_set *set;
 | 
						|
  int      neg;				/* not(Lit) */
 | 
						|
} pn_set;
 | 
						|
 | 
						|
 | 
						|
static int
 | 
						|
cmp_atom_set_size(const void *p1, const void *p2)
 | 
						|
{ const pn_set *ap1 = p1;
 | 
						|
  const pn_set *ap2 = p2;
 | 
						|
 | 
						|
  if ( ap1->neg != ap2->neg )
 | 
						|
    return ap1->neg ? 1 : -1;		/* all negatives at the end */
 | 
						|
 | 
						|
  return ap1->set->size == ap2->set->size ? 0 :
 | 
						|
         ap1->set->size < ap2->set->size ? -1 : 1;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
#define MAX_SETS 100
 | 
						|
 | 
						|
static foreign_t
 | 
						|
find_atom_map(term_t handle, term_t keys, term_t literals)
 | 
						|
{ atom_map  *map;
 | 
						|
  pn_set    as[MAX_SETS];		/* TBD */
 | 
						|
  int ns = 0;
 | 
						|
  term_t tmp = PL_new_term_ref();
 | 
						|
  term_t tail = PL_copy_term_ref(keys);
 | 
						|
  term_t head = PL_new_term_ref();
 | 
						|
  atom_set *s0;
 | 
						|
  size_t ca;
 | 
						|
 | 
						|
  if ( !get_atom_map(handle, &map) )
 | 
						|
    return FALSE;
 | 
						|
 | 
						|
  if ( !RDLOCK(map) )
 | 
						|
    return FALSE;
 | 
						|
 | 
						|
  while(PL_get_list(tail, head, tail))
 | 
						|
  { node_data *data;
 | 
						|
    node_data_ex search;
 | 
						|
    int neg = FALSE;
 | 
						|
 | 
						|
    if ( PL_is_functor(head, FUNCTOR_not1) )
 | 
						|
    { _PL_get_arg(1, head, tmp);
 | 
						|
      if ( !get_search_datum(tmp, &search) )
 | 
						|
	goto failure;
 | 
						|
      neg = TRUE;
 | 
						|
    } else
 | 
						|
    { if ( !get_search_datum(head, &search) )
 | 
						|
	goto failure;
 | 
						|
    }
 | 
						|
 | 
						|
    if ( (data = avlfind(&map->tree, &search)) )
 | 
						|
    { if ( ns+1 >= MAX_SETS )
 | 
						|
	return resource_error("max_search_atoms");
 | 
						|
 | 
						|
      as[ns].set = data->values;
 | 
						|
      as[ns].neg = neg;
 | 
						|
      DEBUG(2, Sdprintf("Found atom-set of size %d\n", as[ns].set->size));
 | 
						|
      ns++;
 | 
						|
    } else if ( !neg )
 | 
						|
    { RDUNLOCK(map);		/* failure on positive literal: empty */
 | 
						|
 | 
						|
      return PL_unify_nil(literals);
 | 
						|
    }
 | 
						|
  }
 | 
						|
  if ( !PL_get_nil(tail) )
 | 
						|
  { type_error(tail, "list");
 | 
						|
    goto failure;
 | 
						|
  }
 | 
						|
 | 
						|
  qsort(as, ns, sizeof(*as), cmp_atom_set_size);
 | 
						|
  if ( ns==0 || as[0].neg )
 | 
						|
  { domain_error(keys, "keywords");
 | 
						|
    goto failure;
 | 
						|
  }
 | 
						|
 | 
						|
  s0 = as[0].set;
 | 
						|
 | 
						|
  PL_put_term(tail, literals);
 | 
						|
 | 
						|
  for(ca=0; ca<s0->size; ca++)
 | 
						|
  { datum a = s0->atoms[ca];
 | 
						|
    int i;
 | 
						|
 | 
						|
    for(i=1; i<ns; i++)
 | 
						|
    { if ( !as[i].neg )
 | 
						|
      { if ( !in_atom_set(as[i].set, a) )
 | 
						|
	{ if ( a > as[i].set->atoms[as[i].set->size-1] )
 | 
						|
	    goto empty;
 | 
						|
	  goto next;
 | 
						|
	}
 | 
						|
      } else
 | 
						|
      { if ( in_atom_set(as[i].set, a) )
 | 
						|
	  goto next;
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    if ( !PL_unify_list(tail, head, tail) ||
 | 
						|
	 !unify_datum(head, a) )
 | 
						|
      goto failure;
 | 
						|
next:;
 | 
						|
  }
 | 
						|
 | 
						|
empty:
 | 
						|
  RDUNLOCK(map);
 | 
						|
  return PL_unify_nil(tail);
 | 
						|
 | 
						|
failure:
 | 
						|
  RDUNLOCK(map);
 | 
						|
  return FALSE;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | 
						|
rdf_keys_in_literal_map(+Map, +Spec, -Keys)
 | 
						|
 | 
						|
Spec is one of
 | 
						|
 | 
						|
	* all
 | 
						|
	* prefix(Text)			atoms only
 | 
						|
	* ge(Low)			integers only
 | 
						|
	* le(High)
 | 
						|
	* between(Low, High)
 | 
						|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
 | 
						|
 | 
						|
/* TBD: should use avlwalk(), but this isn't fine as it does not allow a
 | 
						|
   failure return and does not allow passing our term-handles
 | 
						|
*/
 | 
						|
 | 
						|
static int
 | 
						|
unify_keys(term_t head, term_t tail, AVLnode *node)
 | 
						|
{ node_data *data;
 | 
						|
 | 
						|
  if ( node )
 | 
						|
  { if ( node->subtree[LEFT] )
 | 
						|
    { if ( !unify_keys(head, tail, node->subtree[LEFT]) )
 | 
						|
	return FALSE;
 | 
						|
    }
 | 
						|
 | 
						|
    data = (node_data*)node->data;
 | 
						|
    if ( !PL_unify_list(tail, head, tail) ||
 | 
						|
	 !unify_datum(head, data->key) )
 | 
						|
      return FALSE;
 | 
						|
 | 
						|
    if ( node->subtree[RIGHT] )
 | 
						|
      return unify_keys(head, tail, node->subtree[RIGHT]);
 | 
						|
  }
 | 
						|
 | 
						|
  return TRUE;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static int
 | 
						|
between_keys(atom_map *map, long min, long max, term_t head, term_t tail)
 | 
						|
{ avl_enum state;
 | 
						|
  node_data *data;
 | 
						|
  node_data_ex search;
 | 
						|
 | 
						|
  DEBUG(2, Sdprintf("between %ld .. %ld\n", min, max));
 | 
						|
 | 
						|
  search.data.key = long_to_datum(min);
 | 
						|
  SECURE(search.magic = ND_MAGIC_EX);
 | 
						|
 | 
						|
  if ( (data = avlfindfirst(&map->tree, &search, &state)) &&
 | 
						|
       isIntDatum(data->key) )
 | 
						|
  { for(;;)
 | 
						|
    { if ( long_from_datum(data->key) > max )
 | 
						|
	break;
 | 
						|
 | 
						|
      if ( !PL_unify_list(tail, head, tail) ||
 | 
						|
	   !unify_datum(head, data->key) )
 | 
						|
      { avlfinddestroy(&state);
 | 
						|
	return FALSE;
 | 
						|
      }
 | 
						|
 | 
						|
      if ( !(data = avlfindnext(&state)) ||
 | 
						|
	   !isIntDatum(data->key) )
 | 
						|
	break;
 | 
						|
    }
 | 
						|
 | 
						|
    avlfinddestroy(&state);
 | 
						|
  }
 | 
						|
 | 
						|
  return TRUE;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static foreign_t
 | 
						|
rdf_keys_in_literal_map(term_t handle, term_t spec, term_t keys)
 | 
						|
{ atom_map *map;
 | 
						|
  term_t tail = PL_copy_term_ref(keys);
 | 
						|
  term_t head = PL_new_term_ref();
 | 
						|
  atom_t name;
 | 
						|
  int arity;
 | 
						|
 | 
						|
  if ( !get_atom_map(handle, &map) )
 | 
						|
    return FALSE;
 | 
						|
 | 
						|
  if ( !RDLOCK(map) )
 | 
						|
    return FALSE;
 | 
						|
 | 
						|
  if ( !PL_get_name_arity(spec, &name, &arity) )
 | 
						|
    type_error(spec, "key-specifier");
 | 
						|
 | 
						|
  if ( name == ATOM_all )
 | 
						|
  { AVLnode *node = map->tree.root;
 | 
						|
 | 
						|
    if ( !unify_keys(head, tail, node) )
 | 
						|
      goto failure;
 | 
						|
  } else if ( name == ATOM_key && arity == 1 )
 | 
						|
  { term_t a = PL_new_term_ref();
 | 
						|
    node_data *data;
 | 
						|
    node_data_ex search;
 | 
						|
 | 
						|
    _PL_get_arg(1, spec, a);
 | 
						|
    if ( !get_search_datum(a, &search) )
 | 
						|
      goto failure;
 | 
						|
 | 
						|
    if ( (data = avlfind(&map->tree, &search)) )
 | 
						|
    { long size = (long)data->values->size;
 | 
						|
 | 
						|
      RDUNLOCK(map);
 | 
						|
      assert(size > 0);
 | 
						|
 | 
						|
      return PL_unify_integer(keys, size);
 | 
						|
    }
 | 
						|
    goto failure;
 | 
						|
  } else if ( (name == ATOM_prefix || name == ATOM_case) && arity == 1 )
 | 
						|
  { term_t a = PL_new_term_ref();
 | 
						|
    atom_t prefix, first_a;
 | 
						|
    avl_enum state;
 | 
						|
    node_data *data;
 | 
						|
    node_data_ex search;
 | 
						|
    int match = (name == ATOM_prefix ? STR_MATCH_PREFIX : STR_MATCH_EXACT);
 | 
						|
 | 
						|
    _PL_get_arg(1, spec, a);
 | 
						|
    if ( !get_atom_ex(a, &prefix) )
 | 
						|
      goto failure;
 | 
						|
    first_a = first_atom(prefix, STR_MATCH_PREFIX);
 | 
						|
 | 
						|
    search.data.key = atom_to_datum(first_a);
 | 
						|
    search.atom.handle = first_a;
 | 
						|
    search.atom.resolved = FALSE;
 | 
						|
    SECURE(search.magic = ND_MAGIC_EX);
 | 
						|
 | 
						|
    for(data = avlfindfirst(&map->tree, &search, &state);
 | 
						|
	data;
 | 
						|
	data=avlfindnext(&state))
 | 
						|
    { assert(isAtomDatum(data->key));
 | 
						|
 | 
						|
      if ( !match_atoms(match,
 | 
						|
			first_a, atom_from_datum(data->key)) )
 | 
						|
	break;
 | 
						|
 | 
						|
      if ( !PL_unify_list(tail, head, tail) ||
 | 
						|
	   !unify_datum(head, data->key) )
 | 
						|
      { avlfinddestroy(&state);
 | 
						|
	goto failure;
 | 
						|
      }
 | 
						|
    }
 | 
						|
    avlfinddestroy(&state);
 | 
						|
  } else if ( (name == ATOM_ge || name == ATOM_le) && arity == 1 )
 | 
						|
  { term_t a = PL_new_term_ref();
 | 
						|
    long val, min, max;
 | 
						|
 | 
						|
    _PL_get_arg(1, spec, a);
 | 
						|
    if ( !get_long_ex(a, &val) )
 | 
						|
      goto failure;
 | 
						|
 | 
						|
    if ( name == ATOM_ge )
 | 
						|
      min = val, max = MAP_MAX_INT;
 | 
						|
    else
 | 
						|
      max = val, min = MAP_MIN_INT;
 | 
						|
 | 
						|
    if ( !between_keys(map, min, max, head, tail) )
 | 
						|
      goto failure;
 | 
						|
  } else if ( name == ATOM_between && arity == 2 )
 | 
						|
  { term_t a = PL_new_term_ref();
 | 
						|
    long min, max;
 | 
						|
 | 
						|
    _PL_get_arg(1, spec, a);
 | 
						|
    if ( !get_long_ex(a, &min) )
 | 
						|
      goto failure;
 | 
						|
    _PL_get_arg(2, spec, a);
 | 
						|
    if ( !get_long_ex(a, &max) )
 | 
						|
      goto failure;
 | 
						|
 | 
						|
    if ( !between_keys(map, min, max, head, tail) )
 | 
						|
      goto failure;
 | 
						|
  } else
 | 
						|
  { type_error(spec, "key-specifier");
 | 
						|
    goto failure;
 | 
						|
  }
 | 
						|
 | 
						|
  RDUNLOCK(map);
 | 
						|
 | 
						|
  return PL_unify_nil(tail);
 | 
						|
 | 
						|
failure:
 | 
						|
  RDUNLOCK(map);
 | 
						|
  return FALSE;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
		 /*******************************
 | 
						|
		 *	      RESET		*
 | 
						|
		 *******************************/
 | 
						|
 | 
						|
static foreign_t
 | 
						|
rdf_reset_literal_map(term_t handle)
 | 
						|
{ atom_map *map;
 | 
						|
 | 
						|
  if ( !get_atom_map(handle, &map) )
 | 
						|
    return FALSE;
 | 
						|
 | 
						|
  if ( !WRLOCK(map, FALSE) )
 | 
						|
    return FALSE;
 | 
						|
  avlfree(&map->tree);
 | 
						|
  init_tree_map(map);
 | 
						|
  map->value_count = 0;
 | 
						|
  WRUNLOCK(map);
 | 
						|
 | 
						|
  return TRUE;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
 | 
						|
		 /*******************************
 | 
						|
		 *	    STATISTICS		*
 | 
						|
		 *******************************/
 | 
						|
 | 
						|
 | 
						|
term_t
 | 
						|
rdf_statistics_literal_map(term_t map, term_t key)
 | 
						|
{ atom_map *m;
 | 
						|
 | 
						|
  if ( !get_atom_map(map, &m) )
 | 
						|
    return FALSE;
 | 
						|
 | 
						|
  if ( PL_is_functor(key, FUNCTOR_size2) )
 | 
						|
  { term_t a = PL_new_term_ref();
 | 
						|
 | 
						|
    _PL_get_arg(1, key, a);
 | 
						|
    if ( !PL_unify_integer(a, m->tree.count) )
 | 
						|
      return FALSE;
 | 
						|
    _PL_get_arg(2, key, a);
 | 
						|
 | 
						|
    return PL_unify_integer(a, m->value_count);
 | 
						|
  }
 | 
						|
 | 
						|
  return type_error(key, "statistics_key");
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
 | 
						|
 | 
						|
		 /*******************************
 | 
						|
		 *	     REGISTER		*
 | 
						|
		 *******************************/
 | 
						|
 | 
						|
#define PRED(n,a,f,o) PL_register_foreign(n,a,f,o)
 | 
						|
 | 
						|
install_t
 | 
						|
install_atom_map()
 | 
						|
{ init_functors();
 | 
						|
  init_datum_store();
 | 
						|
 | 
						|
  PRED("rdf_new_literal_map",	     1,	new_atom_map,		    0);
 | 
						|
  PRED("rdf_destroy_literal_map",    1,	destroy_atom_map,	    0);
 | 
						|
  PRED("rdf_reset_literal_map",	     1, rdf_reset_literal_map,	    0);
 | 
						|
  PRED("rdf_insert_literal_map",     3,	insert_atom_map3,	    0);
 | 
						|
  PRED("rdf_insert_literal_map",     4,	insert_atom_map4,	    0);
 | 
						|
  PRED("rdf_delete_literal_map",     3,	delete_atom_map3,	    0);
 | 
						|
  PRED("rdf_delete_literal_map",     2,	delete_atom_map2,	    0);
 | 
						|
  PRED("rdf_find_literal_map",	     3,	find_atom_map,		    0);
 | 
						|
  PRED("rdf_keys_in_literal_map",    3,	rdf_keys_in_literal_map,    0);
 | 
						|
  PRED("rdf_statistics_literal_map", 2,	rdf_statistics_literal_map, 0);
 | 
						|
}
 |