6709 lines
		
	
	
		
			151 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
		
		
			
		
	
	
			6709 lines
		
	
	
		
			151 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
|   | /*  $Id$
 | ||
|  | 
 | ||
|  |     Part of SWI-Prolog | ||
|  | 
 | ||
|  |     Author:        Jan Wielemaker | ||
|  |     E-mail:        J.Wielemaker@uva.nl | ||
|  |     WWW:           http://www.swi-prolog.org
 | ||
|  |     Copyright (C): 1985-2009, University of Amsterdam | ||
|  | 
 | ||
|  |     This library is free software; you can redistribute it and/or | ||
|  |     modify it under the terms of the GNU Lesser General Public | ||
|  |     License as published by the Free Software Foundation; either | ||
|  |     version 2.1 of the License, or (at your option) any later version. | ||
|  | 
 | ||
|  |     This library 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 | ||
|  |     Lesser 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
 | ||
|  | 
 | ||
|  | #define WITH_MD5 1
 | ||
|  | #define WITH_PL_MUTEX 1
 | ||
|  | #define _GNU_SOURCE 1			/* get rwlocks from glibc */
 | ||
|  | 
 | ||
|  | #ifdef _REENTRANT
 | ||
|  | #ifdef __WINDOWS__
 | ||
|  | #include <malloc.h>			/* alloca() */
 | ||
|  | #define inline __inline
 | ||
|  | #ifndef SIZEOF_LONG
 | ||
|  | #define SIZEOF_LONG 4
 | ||
|  | #endif
 | ||
|  | #else
 | ||
|  | #if (!defined(__GNUC__) || defined(__hpux)) && defined(HAVE_ALLOCA_H)
 | ||
|  | #include <alloca.h>
 | ||
|  | #endif
 | ||
|  | #include <errno.h>
 | ||
|  | #endif
 | ||
|  | #endif
 | ||
|  | 
 | ||
|  | #include <SWI-Stream.h>
 | ||
|  | #include <SWI-Prolog.h>
 | ||
|  | #include "rdf_db.h"
 | ||
|  | #include <assert.h>
 | ||
|  | #include <string.h>
 | ||
|  | #include <wchar.h>
 | ||
|  | #include <wctype.h>
 | ||
|  | #include <ctype.h>
 | ||
|  | #include "avl.h"
 | ||
|  | #ifdef WITH_MD5
 | ||
|  | #include "md5.h"
 | ||
|  | #include "atom.h"
 | ||
|  | #include "debug.h"
 | ||
|  | #include "hash.h"
 | ||
|  | #include "murmur.h"
 | ||
|  | 
 | ||
|  | #undef UNLOCK
 | ||
|  | 
 | ||
|  | static void md5_triple(triple *t, md5_byte_t *digest); | ||
|  | static void sum_digest(md5_byte_t *digest, md5_byte_t *add); | ||
|  | static void dec_digest(md5_byte_t *digest, md5_byte_t *add); | ||
|  | #endif
 | ||
|  | 
 | ||
|  | 
 | ||
|  | /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | ||
|  | The ids form a mask. This must be kept consistent with monitor_mask/2 in | ||
|  | rdf_db.pl! | ||
|  | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ | ||
|  | 
 | ||
|  | typedef enum | ||
|  | { EV_ASSERT      = 0x0001,		/* triple */ | ||
|  |   EV_ASSERT_LOAD = 0x0002,		/* triple */ | ||
|  |   EV_RETRACT     = 0x0004,		/* triple */ | ||
|  |   EV_UPDATE      = 0x0008,		/* old, new */ | ||
|  |   EV_NEW_LITERAL = 0x0010,		/* literal */ | ||
|  |   EV_OLD_LITERAL = 0x0020,		/* literal */ | ||
|  |   EV_TRANSACTION = 0x0040,		/* id, begin/end */ | ||
|  |   EV_LOAD	 = 0x0080,		/* id, begin/end */ | ||
|  |   EV_REHASH	 = 0x0100		/* begin/end */ | ||
|  | } broadcast_id; | ||
|  | 
 | ||
|  | static int broadcast(broadcast_id id, void *a1, void *a2); | ||
|  | 
 | ||
|  | 
 | ||
|  | /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | ||
|  | We now use malloc/free/realloc  calls  with   explicit  sizes  to  allow | ||
|  | maintaining statistics as well as to   prepare  for dealing with special | ||
|  | memory  pools  associated  with  databases.  Using  -DDIRECT_MALLOC  the | ||
|  | library uses plain malloc to facilitate malloc debuggers. | ||
|  | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ | ||
|  | 
 | ||
|  | #ifdef DIRECT_MALLOC
 | ||
|  | 
 | ||
|  | #define rdf_malloc(db, size)		malloc(size)
 | ||
|  | #define rdf_free(db, ptr, size)     	free(ptr)
 | ||
|  | #define rdf_realloc(db, ptr, old, new)  realloc(ptr, new)
 | ||
|  | 
 | ||
|  | #else /*DIRECT_MALLOC*/
 | ||
|  | 
 | ||
|  | #if CHECK_MALLOC_SIZES
 | ||
|  | static void * | ||
|  | rdf_malloc(rdf_db *db, size_t size) | ||
|  | { size_t bytes = size + sizeof(size_t); | ||
|  |   size_t *ptr = PL_malloc(bytes); | ||
|  | 
 | ||
|  |   *ptr++ = size; | ||
|  |   if ( db ) | ||
|  |     db->core += size; | ||
|  | 
 | ||
|  |   return ptr; | ||
|  | } | ||
|  | 
 | ||
|  | static void | ||
|  | rdf_free(rdf_db *db, void *ptr, size_t size) | ||
|  | { size_t *p = ptr; | ||
|  | 
 | ||
|  |   assert(p[-1] == size); | ||
|  | 
 | ||
|  |   db->core -= size; | ||
|  |   PL_free(&p[-1]); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static void * | ||
|  | rdf_realloc(rdf_db *db, void *ptr, size_t old, size_t new) | ||
|  | { size_t *p = ptr; | ||
|  |   size_t bytes = new + sizeof(size_t); | ||
|  | 
 | ||
|  |   assert(p[-1] == old); | ||
|  |   p = PL_realloc(&p[-1], bytes); | ||
|  |   *p++ = new; | ||
|  |   db->core< += new-old; | ||
|  | 
 | ||
|  |   return p; | ||
|  | } | ||
|  | 
 | ||
|  | #else /*CHECK_MALLOC_SIZES*/
 | ||
|  | 
 | ||
|  | static void * | ||
|  | rdf_malloc(rdf_db *db, size_t size) | ||
|  | { if ( db ) | ||
|  |     db->core += size; | ||
|  | 
 | ||
|  |   return PL_malloc(size); | ||
|  | } | ||
|  | 
 | ||
|  | static void | ||
|  | rdf_free(rdf_db *db, void *ptr, size_t size) | ||
|  | { db->core -= size; | ||
|  | 
 | ||
|  |   PL_free(ptr); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static void * | ||
|  | rdf_realloc(rdf_db *db, void *ptr, size_t old, size_t new) | ||
|  | { db->core += new-old; | ||
|  | 
 | ||
|  |   return PL_realloc(ptr, new); | ||
|  | } | ||
|  | 
 | ||
|  | #endif /*CHECK_MALLOC_SIZES*/
 | ||
|  | #endif /*DIRECT_MALLOC*/
 | ||
|  | 
 | ||
|  | static functor_t FUNCTOR_literal1; | ||
|  | static functor_t FUNCTOR_literal2; | ||
|  | static functor_t FUNCTOR_error2; | ||
|  | static functor_t FUNCTOR_type_error2; | ||
|  | static functor_t FUNCTOR_domain_error2; | ||
|  | static functor_t FUNCTOR_colon2; | ||
|  | 
 | ||
|  | static functor_t FUNCTOR_triples1; | ||
|  | static functor_t FUNCTOR_triples2; | ||
|  | static functor_t FUNCTOR_subjects1; | ||
|  | static functor_t FUNCTOR_predicates1; | ||
|  | static functor_t FUNCTOR_duplicates1; | ||
|  | static functor_t FUNCTOR_literals1; | ||
|  | static functor_t FUNCTOR_subject1; | ||
|  | static functor_t FUNCTOR_predicate1; | ||
|  | static functor_t FUNCTOR_object1; | ||
|  | static functor_t FUNCTOR_graph1; | ||
|  | static functor_t FUNCTOR_indexed8; | ||
|  | 
 | ||
|  | static functor_t FUNCTOR_exact1; | ||
|  | static functor_t FUNCTOR_plain1; | ||
|  | static functor_t FUNCTOR_substring1; | ||
|  | static functor_t FUNCTOR_word1; | ||
|  | static functor_t FUNCTOR_prefix1; | ||
|  | static functor_t FUNCTOR_like1; | ||
|  | 
 | ||
|  | static functor_t FUNCTOR_symmetric1; | ||
|  | static functor_t FUNCTOR_inverse_of1; | ||
|  | static functor_t FUNCTOR_transitive1; | ||
|  | static functor_t FUNCTOR_rdf_subject_branch_factor1;    /* S --> BF*O */ | ||
|  | static functor_t FUNCTOR_rdf_object_branch_factor1;	/* O --> BF*S */ | ||
|  | static functor_t FUNCTOR_rdfs_subject_branch_factor1;	/* S --> BF*O */ | ||
|  | static functor_t FUNCTOR_rdfs_object_branch_factor1;	/* O --> BF*S */ | ||
|  | 
 | ||
|  | static functor_t FUNCTOR_searched_nodes1; | ||
|  | static functor_t FUNCTOR_lang2; | ||
|  | static functor_t FUNCTOR_type2; | ||
|  | 
 | ||
|  | static functor_t FUNCTOR_gc2; | ||
|  | static functor_t FUNCTOR_rehash2; | ||
|  | static functor_t FUNCTOR_core1; | ||
|  | 
 | ||
|  | static functor_t FUNCTOR_assert4; | ||
|  | static functor_t FUNCTOR_retract4; | ||
|  | static functor_t FUNCTOR_update5; | ||
|  | static functor_t FUNCTOR_new_literal1; | ||
|  | static functor_t FUNCTOR_old_literal1; | ||
|  | static functor_t FUNCTOR_transaction2; | ||
|  | static functor_t FUNCTOR_load2; | ||
|  | static functor_t FUNCTOR_rehash1; | ||
|  | static functor_t FUNCTOR_begin1; | ||
|  | static functor_t FUNCTOR_end1; | ||
|  | 
 | ||
|  | static atom_t   ATOM_user; | ||
|  | static atom_t	ATOM_exact; | ||
|  | static atom_t	ATOM_plain; | ||
|  | static atom_t	ATOM_prefix; | ||
|  | static atom_t	ATOM_substring; | ||
|  | static atom_t	ATOM_word; | ||
|  | static atom_t	ATOM_like; | ||
|  | static atom_t	ATOM_error; | ||
|  | static atom_t	ATOM_begin; | ||
|  | static atom_t	ATOM_end; | ||
|  | static atom_t	ATOM_infinite; | ||
|  | 
 | ||
|  | static atom_t	ATOM_subPropertyOf; | ||
|  | 
 | ||
|  | static predicate_t PRED_call1; | ||
|  | 
 | ||
|  | #define MATCH_EXACT 		0x01	/* exact triple match */
 | ||
|  | #define MATCH_SUBPROPERTY	0x02	/* Use subPropertyOf relations */
 | ||
|  | #define MATCH_SRC		0x04	/* Match graph location */
 | ||
|  | #define MATCH_INVERSE		0x08	/* use symmetric match too */
 | ||
|  | #define MATCH_QUAL		0x10	/* Match qualifiers too */
 | ||
|  | #define MATCH_DUPLICATE		(MATCH_EXACT|MATCH_QUAL)
 | ||
|  | 
 | ||
|  | static int WANT_GC(rdf_db *db); | ||
|  | static int match_triples(triple *t, triple *p, unsigned flags); | ||
|  | static int update_duplicates_add(rdf_db *db, triple *t); | ||
|  | static void update_duplicates_del(rdf_db *db, triple *t); | ||
|  | static void unlock_atoms(triple *t); | ||
|  | static void lock_atoms(triple *t); | ||
|  | static void unlock_atoms_literal(literal *lit); | ||
|  | static int  update_hash(rdf_db *db); | ||
|  | static int  triple_hash(rdf_db *db, triple *t, int which); | ||
|  | static unsigned long object_hash(triple *t); | ||
|  | static void	reset_db(rdf_db *db); | ||
|  | 
 | ||
|  | static void	record_transaction(rdf_db *db, | ||
|  | 				   tr_type type, triple *t); | ||
|  | static void	record_md5_transaction(rdf_db *db, | ||
|  | 				       graph *src, md5_byte_t *digest); | ||
|  | static void	create_reachability_matrix(rdf_db *db, predicate_cloud *cloud); | ||
|  | static int	get_predicate(rdf_db *db, term_t t, predicate **p); | ||
|  | static predicate_cloud *new_predicate_cloud(rdf_db *db, predicate **p, size_t count); | ||
|  | static int	unify_literal(term_t lit, literal *l); | ||
|  | static int	check_predicate_cloud(predicate_cloud *c); | ||
|  | 
 | ||
|  | 
 | ||
|  | 		 /*******************************
 | ||
|  | 		 *	       LOCKING		* | ||
|  | 		 *******************************/ | ||
|  | 
 | ||
|  | #define RDLOCK(db)			rdlock(&db->lock)
 | ||
|  | #define WRLOCK(db, allowreaders)	wrlock(&db->lock, allowreaders)
 | ||
|  | #define LOCKOUT_READERS(db)		lockout_readers(&db->lock)
 | ||
|  | #define REALLOW_READERS(db)		reallow_readers(&db->lock)
 | ||
|  | #define WRUNLOCK(db)			unlock(&db->lock, FALSE)
 | ||
|  | #define RDUNLOCK(db)			unlock(&db->lock, TRUE)
 | ||
|  | #define LOCK_MISC(db)			lock_misc(&db->lock)
 | ||
|  | #define UNLOCK_MISC(db)			unlock_misc(&db->lock)
 | ||
|  | #define INIT_LOCK(db)			init_lock(&db->lock)
 | ||
|  | 
 | ||
|  | 
 | ||
|  | 		 /*******************************
 | ||
|  | 		 *	       ERRORS		* | ||
|  | 		 *******************************/ | ||
|  | 
 | ||
|  | static int | ||
|  | instantiation_error(term_t actual) | ||
|  | { term_t ex; | ||
|  | 
 | ||
|  |   if ( (ex = PL_new_term_ref()) && | ||
|  |        PL_unify_term(ex, | ||
|  | 		     PL_FUNCTOR, FUNCTOR_error2, | ||
|  | 		       PL_CHARS, "instantiation_error", | ||
|  | 		       PL_VARIABLE) ) | ||
|  |     return PL_raise_exception(ex); | ||
|  | 
 | ||
|  |   return FALSE; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | 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 | ||
|  | permission_error(const char *op, const char *type, const char *obj, | ||
|  | 		 const char *msg) | ||
|  | { term_t ex, ctx; | ||
|  | 
 | ||
|  |   if ( !(ex = PL_new_term_ref()) || | ||
|  |        !(ctx = PL_new_term_ref()) ) | ||
|  |     return FALSE; | ||
|  | 
 | ||
|  |   if ( msg ) | ||
|  |   { if ( !PL_unify_term(ctx, PL_FUNCTOR_CHARS, "context", 2, | ||
|  | 			       PL_VARIABLE, | ||
|  | 			       PL_CHARS, msg) ) | ||
|  |       return FALSE; | ||
|  |   } | ||
|  | 
 | ||
|  |   if ( !PL_unify_term(ex, PL_FUNCTOR_CHARS, "error", 2, | ||
|  | 		      PL_FUNCTOR_CHARS, "permission_error", 3, | ||
|  | 		        PL_CHARS, op, | ||
|  | 		        PL_CHARS, type, | ||
|  | 		        PL_CHARS, obj, | ||
|  | 		      PL_TERM, ctx) ) | ||
|  |     return FALSE; | ||
|  | 
 | ||
|  |   return PL_raise_exception(ex); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | 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_double_ex(term_t t, double *v) | ||
|  | { if ( PL_get_float(t, v) ) | ||
|  |     return TRUE; | ||
|  | 
 | ||
|  |   return type_error(t, "float"); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static int | ||
|  | get_atom_or_var_ex(term_t t, atom_t *a) | ||
|  | { if ( PL_get_atom(t, a) ) | ||
|  |     return TRUE; | ||
|  |   if ( PL_is_variable(t) ) | ||
|  |   { *a = 0L; | ||
|  |     return TRUE; | ||
|  |   } | ||
|  | 
 | ||
|  |   return type_error(t, "atom"); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static int | ||
|  | get_resource_or_var_ex(term_t t, atom_t *a) | ||
|  | { if ( PL_get_atom(t, a) ) | ||
|  |     return TRUE; | ||
|  |   if ( PL_is_variable(t) ) | ||
|  |   { *a = 0L; | ||
|  |     return TRUE; | ||
|  |   } | ||
|  |   if ( PL_is_functor(t, FUNCTOR_literal1) ) | ||
|  |     return FALSE;			/* fail on rdf(literal(_), ...) */ | ||
|  | 
 | ||
|  |   return type_error(t, "atom"); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static int | ||
|  | get_bool_arg_ex(int a, term_t t, int *val) | ||
|  | { term_t arg = PL_new_term_ref(); | ||
|  | 
 | ||
|  |   if ( !PL_get_arg(a, t, arg) ) | ||
|  |     return type_error(t, "compound"); | ||
|  |   if ( !PL_get_bool(arg, val) ) | ||
|  |     return type_error(arg, "bool"); | ||
|  | 
 | ||
|  |   return TRUE; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | 
 | ||
|  | 		 /*******************************
 | ||
|  | 		 *	   DEBUG SUPPORT	* | ||
|  | 		 *******************************/ | ||
|  | 
 | ||
|  | #ifdef O_DEBUG
 | ||
|  | 
 | ||
|  | #define PRT_SRC	0x1
 | ||
|  | 
 | ||
|  | static void | ||
|  | print_literal(literal *lit) | ||
|  | { switch(lit->objtype) | ||
|  |   { case OBJ_STRING: | ||
|  |       switch(lit->qualifier) | ||
|  |       { case Q_TYPE: | ||
|  | 	  Sdprintf("%s^^\"%s\"", | ||
|  | 		   PL_atom_chars(lit->value.string), | ||
|  | 		   PL_atom_chars(lit->type_or_lang)); | ||
|  | 	  break; | ||
|  | 	case Q_LANG: | ||
|  | 	  Sdprintf("%s@\"%s\"", | ||
|  | 		   PL_atom_chars(lit->value.string), | ||
|  | 		   PL_atom_chars(lit->type_or_lang)); | ||
|  | 	  break; | ||
|  | 	default: | ||
|  | 	{ size_t len; | ||
|  | 	  const char *s; | ||
|  | 	  const wchar_t *w; | ||
|  | 
 | ||
|  | 	  if ( (s = PL_atom_nchars(lit->value.string, &len)) ) | ||
|  | 	  { if ( strlen(s) == len ) | ||
|  | 	      Sdprintf("\"%s\"", s); | ||
|  | 	    else | ||
|  | 	      Sdprintf("\"%s\" (len=%d)", s, len); | ||
|  | 	  } else if ( (w = PL_atom_wchars(lit->value.string, &len)) ) | ||
|  | 	  { unsigned int i; | ||
|  | 	    Sputc('L', Serror); | ||
|  | 	    Sputc('"', Serror); | ||
|  | 	    for(i=0; i<len; i++) | ||
|  | 	    { if ( w[i] < 0x7f ) | ||
|  | 		Sputc(w[i], Serror); | ||
|  | 	      else | ||
|  | 		Sfprintf(Serror, "\\\\u%04x", w[i]); | ||
|  | 	    } | ||
|  | 	    Sputc('"', Serror); | ||
|  | 	  } | ||
|  | 	  break; | ||
|  | 	} | ||
|  |       } | ||
|  |       break; | ||
|  |     case OBJ_INTEGER: | ||
|  |       Sdprintf("%ld", lit->value.integer); | ||
|  |       break; | ||
|  |     case OBJ_DOUBLE: | ||
|  |       Sdprintf("%f", lit->value.real); | ||
|  |       break; | ||
|  |     case OBJ_TERM: | ||
|  |     { fid_t fid = PL_open_foreign_frame(); | ||
|  |       term_t term = PL_new_term_ref(); | ||
|  | 
 | ||
|  |       PL_recorded_external(lit->value.term.record, term); | ||
|  |       PL_write_term(Serror, term, 1200, | ||
|  | 		    PL_WRT_QUOTED|PL_WRT_NUMBERVARS|PL_WRT_PORTRAY); | ||
|  |       PL_discard_foreign_frame(fid); | ||
|  |       break; | ||
|  |     } | ||
|  |     default: | ||
|  |       assert(0); | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static void | ||
|  | print_object(triple *t) | ||
|  | { if ( t->object_is_literal ) | ||
|  |   { print_literal(t->object.literal); | ||
|  |   } else | ||
|  |   { Sdprintf("%s", PL_atom_chars(t->object.resource)); | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static void | ||
|  | print_src(triple *t) | ||
|  | { if ( t->line == NO_LINE ) | ||
|  |     Sdprintf(" [%s]", PL_atom_chars(t->graph)); | ||
|  |   else | ||
|  |     Sdprintf(" [%s:%ld]", PL_atom_chars(t->graph), t->line); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static void | ||
|  | print_triple(triple *t, int flags) | ||
|  | { Sdprintf("<%s %s ", | ||
|  | 	   PL_atom_chars(t->subject), | ||
|  | 	   PL_atom_chars(t->predicate.r->name)); | ||
|  |   print_object(t); | ||
|  |   if ( (flags & PRT_SRC) ) | ||
|  |     print_src(t); | ||
|  |   Sdprintf(">"); | ||
|  | } | ||
|  | 
 | ||
|  | #endif
 | ||
|  | 
 | ||
|  | 		 /*******************************
 | ||
|  | 		 *	     STORAGE		* | ||
|  | 		 *******************************/ | ||
|  | 
 | ||
|  | /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | ||
|  | Our one and only database (for the time being). | ||
|  | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ | ||
|  | 
 | ||
|  | static rdf_db *DB; | ||
|  | 
 | ||
|  | 
 | ||
|  | 		 /*******************************
 | ||
|  | 		 *	      LISTS		* | ||
|  | 		 *******************************/ | ||
|  | 
 | ||
|  | static int | ||
|  | add_list(rdf_db *db, list *list, void *value) | ||
|  | { cell *c; | ||
|  | 
 | ||
|  |   for(c=list->head; c; c=c->next) | ||
|  |   { if ( c->value == value ) | ||
|  |       return FALSE;			/* already a member */ | ||
|  |   } | ||
|  | 
 | ||
|  |   c = rdf_malloc(db, sizeof(*c)); | ||
|  |   c->value = value; | ||
|  |   c->next = NULL; | ||
|  | 
 | ||
|  |   if ( list->tail ) | ||
|  |     list->tail->next = c; | ||
|  |   else | ||
|  |     list->head = c; | ||
|  | 
 | ||
|  |   list->tail = c; | ||
|  | 
 | ||
|  |   return TRUE; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static int | ||
|  | del_list(rdf_db *db, list *list, void *value) | ||
|  | { cell *c, *p = NULL; | ||
|  | 
 | ||
|  |   for(c=list->head; c; p=c, c=c->next) | ||
|  |   { if ( c->value == value ) | ||
|  |     { if ( p ) | ||
|  | 	p->next = c->next; | ||
|  |       else | ||
|  | 	list->head = c->next; | ||
|  | 
 | ||
|  |       if ( !c->next ) | ||
|  | 	list->tail = p; | ||
|  | 
 | ||
|  |       rdf_free(db, c, sizeof(*c)); | ||
|  | 
 | ||
|  |       return TRUE; | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   return FALSE;				/* not a member */ | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static void | ||
|  | free_list(rdf_db *db, list *list) | ||
|  | { cell *c, *n; | ||
|  | 
 | ||
|  |   for(c=list->head; c; c=n) | ||
|  |   { n = c->next; | ||
|  |     rdf_free(db, c, sizeof(*c)); | ||
|  |   } | ||
|  | 
 | ||
|  |   list->head = list->tail = NULL; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | 		 /*******************************
 | ||
|  | 		 *	     ATOM SETS		* | ||
|  | 		 *******************************/ | ||
|  | 
 | ||
|  | 
 | ||
|  | #define CHUNKSIZE 1024
 | ||
|  | 
 | ||
|  | typedef struct mchunk | ||
|  | { struct mchunk *next; | ||
|  |   size_t used; | ||
|  |   char buf[CHUNKSIZE]; | ||
|  | } mchunk; | ||
|  | 
 | ||
|  | typedef struct | ||
|  | { avl_tree tree; | ||
|  |   mchunk *node_store; | ||
|  |   mchunk store0; | ||
|  | } atomset; | ||
|  | 
 | ||
|  | 
 | ||
|  | static void * | ||
|  | alloc_node_atomset(void *ptr, size_t size) | ||
|  | { void *p; | ||
|  |   atomset *as = ptr; | ||
|  | 
 | ||
|  |   assert(size < CHUNKSIZE); | ||
|  | 
 | ||
|  |   if ( as->node_store->used + size > CHUNKSIZE ) | ||
|  |   { mchunk *ch = malloc(sizeof(mchunk)); | ||
|  | 
 | ||
|  |     ch->used = 0; | ||
|  |     ch->next = as->node_store; | ||
|  |     as->node_store = ch; | ||
|  |   } | ||
|  | 
 | ||
|  |   p = &as->node_store->buf[as->node_store->used]; | ||
|  |   as->node_store->used += size; | ||
|  | 
 | ||
|  |   return p; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static void | ||
|  | free_node_atomset(void *ptr, void *data, size_t size) | ||
|  | { assert(0); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static int | ||
|  | cmp_long_ptr(void *p1, void *p2, NODE type) | ||
|  | { long *l1 = p1; | ||
|  |   long *l2 = p2; | ||
|  | 
 | ||
|  |   return *l1 < *l2 ? -1 : *l1 > *l2 ? 1 : 0; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static void | ||
|  | init_atomset(atomset *as) | ||
|  | { avlinit(&as->tree, as, sizeof(atom_t), | ||
|  | 	  cmp_long_ptr, | ||
|  | 	  NULL, | ||
|  | 	  alloc_node_atomset, | ||
|  | 	  free_node_atomset); | ||
|  | 
 | ||
|  |   as->node_store = &as->store0; | ||
|  |   as->node_store->next = NULL; | ||
|  |   as->node_store->used = 0; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static void | ||
|  | destroy_atomset(atomset *as) | ||
|  | { mchunk *ch, *next; | ||
|  | 
 | ||
|  |   for(ch=as->node_store; ch != &as->store0; ch = next) | ||
|  |   { next = ch->next; | ||
|  |     free(ch); | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static int | ||
|  | add_atomset(atomset *as, atom_t atom) | ||
|  | { return avlins(&as->tree, &atom) ? FALSE : TRUE; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | 		 /*******************************
 | ||
|  | 		 *	    PREDICATES		* | ||
|  | 		 *******************************/ | ||
|  | 
 | ||
|  | /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | ||
|  | Predicates are represented as first class   citizens  for three reasons: | ||
|  | quickly  answer  on  the  transitive   rdfs:subPropertyOf  relation  for | ||
|  | rdf_hash/3,  keep  track  of  statistics  that   are  useful  for  query | ||
|  | optimization  (#triples,  branching   factor)    and   keep   properties | ||
|  | (inverse/transitive). | ||
|  | 
 | ||
|  | To answer the rdfs:subPropertyOf quickly,   predicates  are organised in | ||
|  | `clouds', where a cloud defines a   set  of predicates connected through | ||
|  | rdfs:subPropertyOf triples. The cloud numbers  its members and maintains | ||
|  | a bit-matrix that contains the closure  of the reachability. Initially a | ||
|  | predicate has a simple cloud of size 1. merge_clouds() and split_cloud() | ||
|  | deals with adding  and  deleting   rdfs:subPropertyOf  relations.  These | ||
|  | operations try to modify the clouds that have   no triples, so it can be | ||
|  | done without a rehash. If this fails, the predicates keep their own hash | ||
|  | to make search without rdfs:subPropertyOf  still   possible  (so  we can | ||
|  | avoid frequent updates while loading triples),   sets  the cloud `dirty' | ||
|  | flag and the DB's need_update flag. Queries that need rdfs:subPropertyOf | ||
|  | find the need_update flag,  which   calls  organise_predicates(),  which | ||
|  | cause a rehash if some predicates  have   changed  hash-code  to the new | ||
|  | cloud they have become part of. | ||
|  | 
 | ||
|  | TBD: We can do a partial re-hash in that case! | ||
|  | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ | ||
|  | 
 | ||
|  | 
 | ||
|  | static void | ||
|  | init_pred_table(rdf_db *db) | ||
|  | { int bytes = sizeof(predicate*)*INITIAL_PREDICATE_TABLE_SIZE; | ||
|  | 
 | ||
|  |   db->pred_table = rdf_malloc(db, bytes); | ||
|  |   memset(db->pred_table, 0, bytes); | ||
|  |   db->pred_table_size = INITIAL_PREDICATE_TABLE_SIZE; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static predicate * | ||
|  | existing_predicate(rdf_db *db, atom_t name) | ||
|  | { int hash = atom_hash(name) % db->pred_table_size; | ||
|  |   predicate *p; | ||
|  | 
 | ||
|  |   LOCK_MISC(db); | ||
|  |   for(p=db->pred_table[hash]; p; p = p->next) | ||
|  |   { if ( p->name == name ) | ||
|  |     { UNLOCK_MISC(db); | ||
|  |       return p; | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   UNLOCK_MISC(db); | ||
|  |   return NULL; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static predicate * | ||
|  | lookup_predicate(rdf_db *db, atom_t name) | ||
|  | { int hash = atom_hash(name) % db->pred_table_size; | ||
|  |   predicate *p; | ||
|  |   predicate_cloud *cp; | ||
|  | 
 | ||
|  |   LOCK_MISC(db); | ||
|  |   for(p=db->pred_table[hash]; p; p = p->next) | ||
|  |   { if ( p->name == name ) | ||
|  |     { UNLOCK_MISC(db); | ||
|  |       return p; | ||
|  |     } | ||
|  |   } | ||
|  |   p = rdf_malloc(db, sizeof(*p)); | ||
|  |   memset(p, 0, sizeof(*p)); | ||
|  |   p->name = name; | ||
|  |   cp = new_predicate_cloud(db, &p, 1); | ||
|  |   p->hash = cp->hash; | ||
|  |   PL_register_atom(name); | ||
|  |   p->next = db->pred_table[hash]; | ||
|  |   db->pred_table[hash] = p; | ||
|  |   db->pred_count++; | ||
|  |   DEBUG(5, Sdprintf("Pred %s (count = %d)\n", | ||
|  | 		    PL_atom_chars(name), db->pred_count)); | ||
|  |   UNLOCK_MISC(db); | ||
|  | 
 | ||
|  |   return p; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static const char * | ||
|  | pname(predicate *p) | ||
|  | { if ( p->name ) | ||
|  |     return PL_atom_chars(p->name); | ||
|  |   else | ||
|  |   { static char *ring[10]; | ||
|  |     static int ri = 0; | ||
|  |     char buf[25]; | ||
|  |     char *r; | ||
|  | 
 | ||
|  |     Ssprintf(buf, "__D%p", p); | ||
|  |     ring[ri++] = r = strdup(buf); | ||
|  |     if ( ri == 10 ) | ||
|  |     { ri = 0; | ||
|  |       free(ring[ri]); | ||
|  |     } | ||
|  | 
 | ||
|  |     return (const char*)r; | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static int | ||
|  | organise_predicates(rdf_db *db)		/* TBD: rename&move */ | ||
|  | { predicate **ht; | ||
|  |   int i; | ||
|  |   int changed = 0; | ||
|  | 
 | ||
|  |   DEBUG(2, Sdprintf("rdf_db: fixing predicate clouds\n")); | ||
|  | 
 | ||
|  |   for(i=0,ht = db->pred_table; i<db->pred_table_size; i++, ht++) | ||
|  |   { predicate *p; | ||
|  | 
 | ||
|  |     for( p = *ht; p; p = p->next ) | ||
|  |     { predicate_cloud *cloud = p->cloud; | ||
|  | 
 | ||
|  |       if ( cloud->dirty ) | ||
|  |       { predicate **cp; | ||
|  | 	int i2; | ||
|  | 
 | ||
|  | 	for(i2=0, cp = cloud->members; i2 < cloud->size; i2++, cp++) | ||
|  | 	{ if ( (*cp)->hash != cloud->hash ) | ||
|  | 	  { (*cp)->hash = cloud->hash; | ||
|  | 	    if ( (*cp)->triple_count > 0 ) | ||
|  | 	      changed++; | ||
|  | 	  } | ||
|  | 	} | ||
|  | 	cloud->dirty = FALSE; | ||
|  |       } | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   return changed; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | 		 /*******************************
 | ||
|  | 		 *	 PREDICATE CLOUDS	* | ||
|  | 		 *******************************/ | ||
|  | 
 | ||
|  | static predicate_cloud * | ||
|  | new_predicate_cloud(rdf_db *db, predicate **p, size_t count) | ||
|  | { predicate_cloud *cloud = rdf_malloc(db, sizeof(*cloud)); | ||
|  | 
 | ||
|  |   memset(cloud, 0, sizeof(*cloud)); | ||
|  |   cloud->hash = db->next_hash++; | ||
|  |   if ( count ) | ||
|  |   { int i; | ||
|  |     predicate **p2; | ||
|  | 
 | ||
|  |     cloud->size = count; | ||
|  |     cloud->members = rdf_malloc(db, sizeof(predicate*)*count); | ||
|  |     memcpy(cloud->members, p, sizeof(predicate*)*count); | ||
|  | 
 | ||
|  |     for(i=0, p2=cloud->members; i<cloud->size; i++, p2++) | ||
|  |       (*p2)->cloud = cloud; | ||
|  |   } | ||
|  |   create_reachability_matrix(db, cloud); | ||
|  | 
 | ||
|  |   return cloud; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static void | ||
|  | free_predicate_cloud(rdf_db *db, predicate_cloud *cloud) | ||
|  | { if ( cloud->members ) | ||
|  |   { rdf_free(db, cloud->members, sizeof(predicate*)*cloud->size); | ||
|  |   } | ||
|  | 
 | ||
|  |   rdf_free(db, cloud, sizeof(*cloud)); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static long | ||
|  | triples_in_predicate_cloud(predicate_cloud *cloud) | ||
|  | { long triples = 0; | ||
|  |   predicate **p; | ||
|  |   int i; | ||
|  | 
 | ||
|  |   for(i=0, p=cloud->members; i<cloud->size; i++, p++) | ||
|  |     triples += (*p)->triple_count; | ||
|  | 
 | ||
|  |   return triples; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /* Add the predicates of c2 to c1 and destroy c2.  Returns c1 */ | ||
|  | 
 | ||
|  | static predicate_cloud * | ||
|  | append_clouds(rdf_db *db, predicate_cloud *c1, predicate_cloud *c2, int update_hash) | ||
|  | { predicate **p; | ||
|  |   int i; | ||
|  | 
 | ||
|  |   for(i=0, p=c2->members; i<c2->size; i++, p++) | ||
|  |   { (*p)->cloud = c1; | ||
|  |     if ( update_hash ) | ||
|  |       (*p)->hash = c1->hash; | ||
|  |   } | ||
|  | 
 | ||
|  |   if ( c1->size > 0 && c2->size > 0 ) | ||
|  |   { c1->members = rdf_realloc(db, c1->members, | ||
|  | 			      c1->size*sizeof(predicate*), | ||
|  | 			      (c1->size+c2->size)*sizeof(predicate*)); | ||
|  |     memcpy(&c1->members[c1->size], c2->members, c2->size*sizeof(predicate*)); | ||
|  |     c1->size += c2->size; | ||
|  |     free_predicate_cloud(db, c2); | ||
|  |   } else if ( c2->size > 0 ) | ||
|  |   { c1->members = c2->members; | ||
|  |     c1->size = c2->size; | ||
|  |     c2->members = NULL; | ||
|  |     free_predicate_cloud(db, c2); | ||
|  |   } else | ||
|  |   { free_predicate_cloud(db, c2); | ||
|  |   } | ||
|  | 
 | ||
|  |   return c1; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /* merge two predicate clouds.  If either of them has no triples we
 | ||
|  |    can do the merge without rehashing the database.  Note that this | ||
|  |    code is only called from addSubPropertyOf().  If c1==c2, we added | ||
|  |    an rdfs:subPropertyOf between two predicates in the same cloud. | ||
|  |    we must still update the matrix, though we could do it a bit more | ||
|  |    efficient.  I doubt this is worth the trouble though. | ||
|  | */ | ||
|  | 
 | ||
|  | static predicate_cloud * | ||
|  | merge_clouds(rdf_db *db, predicate_cloud *c1, predicate_cloud *c2) | ||
|  | { predicate_cloud *cloud; | ||
|  | 
 | ||
|  |   if ( c1 != c2 ) | ||
|  |   { if ( triples_in_predicate_cloud(c1) == 0 ) | ||
|  |     { cloud = append_clouds(db, c2, c1, TRUE); | ||
|  |     } else if ( triples_in_predicate_cloud(c2) == 0 ) | ||
|  |     { cloud = append_clouds(db, c1, c2, TRUE); | ||
|  |     } else | ||
|  |     { cloud = append_clouds(db, c1, c2, FALSE); | ||
|  |       cloud->dirty = TRUE; | ||
|  |       db->need_update++; | ||
|  |     } | ||
|  |   } else | ||
|  |   { cloud = c1; | ||
|  |   } | ||
|  | 
 | ||
|  |   DEBUG(1, if ( !db->need_update ) | ||
|  | 	   { check_predicate_cloud(cloud); | ||
|  | 	   }); | ||
|  | 
 | ||
|  |   create_reachability_matrix(db, cloud); | ||
|  | 
 | ||
|  |   return cloud; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /* split a cloud into multiple disjoint clouds.  The first cloud is
 | ||
|  |    given the hash of the original, so we only need to update if new | ||
|  |    clouds are created.  Ideally we should se whether it is possible | ||
|  |    to give the orginal hash to the one and only non-empty cloud to | ||
|  |    avoid re-hashing alltogether. | ||
|  | */ | ||
|  | 
 | ||
|  | static void | ||
|  | pred_reachable(predicate *start, char *visited, predicate **nodes, int *size) | ||
|  | { if ( !visited[start->label] ) | ||
|  |   { cell *c; | ||
|  | 
 | ||
|  |     visited[start->label] = TRUE; | ||
|  |     nodes[(*size)++] = start; | ||
|  |     for(c=start->subPropertyOf.head; c; c=c->next) | ||
|  |       pred_reachable(c->value, visited, nodes, size); | ||
|  |     for(c=start->siblings.head; c; c=c->next) | ||
|  |       pred_reachable(c->value, visited, nodes, size); | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static int | ||
|  | split_cloud(rdf_db *db, predicate_cloud *cloud, | ||
|  | 	    predicate_cloud **parts, int size) | ||
|  | { char *done        = alloca(cloud->size*sizeof(char)); | ||
|  |   predicate **graph = alloca(cloud->size*sizeof(predicate*)); | ||
|  |   int found = 0; | ||
|  |   int i; | ||
|  | 
 | ||
|  |   memset(done, 0, cloud->size*sizeof(char)); | ||
|  |   for(i=0; i<cloud->size; i++) | ||
|  |   { if ( !done[i] ) | ||
|  |     { predicate *start = cloud->members[i]; | ||
|  |       predicate_cloud *new_cloud; | ||
|  |       int gsize = 0; | ||
|  | 
 | ||
|  |       pred_reachable(start, done, graph, &gsize); | ||
|  |       new_cloud = new_predicate_cloud(db, graph, gsize); | ||
|  |       if ( found == 0 ) | ||
|  |       { new_cloud->hash = cloud->hash; | ||
|  |       } else | ||
|  |       { new_cloud->dirty = TRUE;	/* preds come from another cloud */ | ||
|  | 	db->need_update++; | ||
|  |       } | ||
|  |       parts[found++] = new_cloud; | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   free_predicate_cloud(db, cloud); | ||
|  | 
 | ||
|  |   return found; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static unsigned long | ||
|  | predicate_hash(predicate *p) | ||
|  | { return p->hash; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static void | ||
|  | addSubPropertyOf(rdf_db *db, predicate *sub, predicate *super) | ||
|  | { /*DEBUG(2, Sdprintf("addSubPropertyOf(%s, %s)\n", pname(sub), pname(super)));*/ | ||
|  | 
 | ||
|  |   if ( add_list(db, &sub->subPropertyOf, super) ) | ||
|  |   { add_list(db, &super->siblings, sub); | ||
|  |     merge_clouds(db, sub->cloud, super->cloud); | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /* deleting an rdfs:subPropertyOf.  This is a bit naughty.  If the
 | ||
|  |    cloud is still connected we only need to refresh the reachability | ||
|  |    matrix.  Otherwise the cloud breaks in maximum two clusters.  We | ||
|  |    can decide to leave it as is, which saves a re-hash of the triples | ||
|  |    but harms indexing.  Alternative we can create a new cloud for one | ||
|  |    of the clusters and re-hash. | ||
|  | */ | ||
|  | 
 | ||
|  | static void | ||
|  | delSubPropertyOf(rdf_db *db, predicate *sub, predicate *super) | ||
|  | { if ( del_list(db, &sub->subPropertyOf, super) ) | ||
|  |   { del_list(db, &super->siblings, sub); | ||
|  |  /* if ( not worth the trouble )
 | ||
|  |       create_reachability_matrix(db, sub->cloud); | ||
|  |     else */ | ||
|  |     { predicate_cloud *parts[2]; | ||
|  |       split_cloud(db, sub->cloud, parts, 2); | ||
|  |     } | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | ||
|  | Reachability matrix. | ||
|  | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ | ||
|  | 
 | ||
|  | #define WBITSIZE (sizeof(int)*8)
 | ||
|  | 
 | ||
|  | static size_t | ||
|  | byte_size_bitmatrix(size_t w, size_t h) | ||
|  | { size_t wsize = ((w*h)+WBITSIZE-1)/WBITSIZE; | ||
|  | 
 | ||
|  |   return (size_t)(intptr_t)&((bitmatrix*)NULL)->bits[wsize]; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static bitmatrix * | ||
|  | alloc_bitmatrix(rdf_db *db, size_t w, size_t h) | ||
|  | { size_t size = byte_size_bitmatrix(w, h); | ||
|  |   bitmatrix *m = rdf_malloc(db, size); | ||
|  | 
 | ||
|  |   memset(m, 0, size); | ||
|  |   m->width = w; | ||
|  |   m->heigth = h; | ||
|  | 
 | ||
|  |   return m; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static void | ||
|  | free_bitmatrix(rdf_db *db, bitmatrix *bm) | ||
|  | { size_t size = byte_size_bitmatrix(bm->width, bm->heigth); | ||
|  | 
 | ||
|  |   rdf_free(db, bm, size); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | #undef setbit				/* conflict in HPUX 11.23 */
 | ||
|  | 
 | ||
|  | static void | ||
|  | setbit(bitmatrix *m, int i, int j) | ||
|  | { size_t ij = m->width*i+j; | ||
|  |   size_t word = ij/WBITSIZE; | ||
|  |   int bit  = ij%WBITSIZE; | ||
|  | 
 | ||
|  |   m->bits[word] |= 1<<bit; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static int | ||
|  | testbit(bitmatrix *m, int i, int j) | ||
|  | { size_t ij = m->width*i+j; | ||
|  |   size_t word = ij/WBITSIZE; | ||
|  |   int bit  = ij%WBITSIZE; | ||
|  | 
 | ||
|  |   return ((m->bits[word] & (1<<bit)) != 0); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static int | ||
|  | label_predicate_cloud(predicate_cloud *cloud) | ||
|  | { predicate **p; | ||
|  |   int i; | ||
|  | 
 | ||
|  |   for(i=0, p=cloud->members; i<cloud->size; i++, p++) | ||
|  |     (*p)->label = i; | ||
|  | 
 | ||
|  |   return i; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static void | ||
|  | fill_reachable(bitmatrix *bm, predicate *p0, predicate *p) | ||
|  | { if ( !testbit(bm, p0->label, p->label) ) | ||
|  |   { cell *c; | ||
|  | 
 | ||
|  |     DEBUG(1, Sdprintf("    Reachable [%s (%d)]\n", pname(p), p->label)); | ||
|  |     setbit(bm, p0->label, p->label); | ||
|  |     for(c = p->subPropertyOf.head; c; c=c->next) | ||
|  |       fill_reachable(bm, p0, c->value); | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static void | ||
|  | create_reachability_matrix(rdf_db *db, predicate_cloud *cloud) | ||
|  | { bitmatrix *m = alloc_bitmatrix(db, cloud->size, cloud->size); | ||
|  |   predicate **p; | ||
|  |   int i; | ||
|  | 
 | ||
|  |   label_predicate_cloud(cloud); | ||
|  |   for(i=0, p=cloud->members; i<cloud->size; i++, p++) | ||
|  |   { DEBUG(1, Sdprintf("Reachability for %s (%d)\n", pname(*p), (*p)->label)); | ||
|  | 
 | ||
|  |     fill_reachable(m, *p, *p); | ||
|  |   } | ||
|  | 
 | ||
|  |   if ( cloud->reachable ) | ||
|  |     free_bitmatrix(db, cloud->reachable); | ||
|  | 
 | ||
|  |   cloud->reachable = m; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static int | ||
|  | isSubPropertyOf(predicate *sub, predicate *p) | ||
|  | { if ( sub->cloud == p->cloud ) | ||
|  |     return testbit(sub->cloud->reachable, sub->label, p->label); | ||
|  | 
 | ||
|  |   return FALSE; | ||
|  | } | ||
|  | 
 | ||
|  | 		 /*******************************
 | ||
|  | 		 *   PRINT PREDICATE HIERARCHY	* | ||
|  | 		 *******************************/ | ||
|  | 
 | ||
|  | static int | ||
|  | check_predicate_cloud(predicate_cloud *c) | ||
|  | { predicate **p; | ||
|  |   int errors = 0; | ||
|  |   int i; | ||
|  | 
 | ||
|  |   DEBUG(1, if ( c->dirty ) Sdprintf("Cloud is dirty\n")); | ||
|  | 
 | ||
|  |   for(i=0, p=c->members; i<c->size; i++, p++) | ||
|  |   { if ( !c->dirty ) | ||
|  |     { if ( (*p)->hash != c->hash ) | ||
|  |       { Sdprintf("Hash of %s doesn't match cloud hash\n", pname(*p)); | ||
|  | 	errors++; | ||
|  |       } | ||
|  |     } | ||
|  |     if ( (*p)->cloud != c ) | ||
|  |     { Sdprintf("Wrong cloud of %s\n", pname(*p)); | ||
|  |       errors++; | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   return errors; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static void | ||
|  | print_reachability_cloud(predicate *p) | ||
|  | { int x, y; | ||
|  |   predicate_cloud *cloud = p->cloud; | ||
|  | 
 | ||
|  |   check_predicate_cloud(cloud); | ||
|  | 
 | ||
|  |   Sdprintf("Reachability matrix:\n"); | ||
|  |   for(x=0; x<cloud->reachable->width; x++) | ||
|  |     Sdprintf("%d", x%10); | ||
|  |   Sdprintf("\n"); | ||
|  |   for(y=0; y<cloud->reachable->heigth; y++) | ||
|  |   { for(x=0; x<cloud->reachable->width; x++) | ||
|  |     { if ( testbit(cloud->reachable, x, y) ) | ||
|  | 	Sdprintf("X"); | ||
|  |       else | ||
|  | 	Sdprintf("."); | ||
|  |     } | ||
|  | 
 | ||
|  |     Sdprintf(" %2d %s\n", y, PL_atom_chars(cloud->members[y]->name)); | ||
|  |     assert(cloud->members[y]->label == y); | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static foreign_t | ||
|  | rdf_print_predicate_cloud(term_t t) | ||
|  | { predicate *p; | ||
|  |   rdf_db *db = DB; | ||
|  | 
 | ||
|  |   if ( !get_predicate(db, t, &p) ) | ||
|  |     return FALSE; | ||
|  | 
 | ||
|  |   print_reachability_cloud(p); | ||
|  | 
 | ||
|  |   return TRUE; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | 
 | ||
|  | /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | ||
|  | Branching  factors  are  crucial  in  ordering    the  statements  of  a | ||
|  | conjunction. These functions compute  the   average  branching factor in | ||
|  | both directions ("subject --> P  -->  object"   and  "object  -->  P --> | ||
|  | subject") by determining the number of unique   values at either side of | ||
|  | the predicate. This number  is  only   recomputed  if  it  is considered | ||
|  | `dirty'. | ||
|  | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ | ||
|  | 
 | ||
|  | static int | ||
|  | update_predicate_counts(rdf_db *db, predicate *p, int which) | ||
|  | { long total = 0; | ||
|  | 
 | ||
|  |   if ( which == DISTINCT_DIRECT ) | ||
|  |   { long changed = abs(p->triple_count - p->distinct_updated[DISTINCT_DIRECT]); | ||
|  | 
 | ||
|  |     if ( changed < p->distinct_updated[DISTINCT_DIRECT] ) | ||
|  |       return TRUE; | ||
|  | 
 | ||
|  |     if ( p->triple_count == 0 ) | ||
|  |     { p->distinct_count[which]    = 0; | ||
|  |       p->distinct_subjects[which] = 0; | ||
|  |       p->distinct_objects[which]  = 0; | ||
|  | 
 | ||
|  |       return TRUE; | ||
|  |     } | ||
|  |   } else | ||
|  |   { long changed = db->generation - p->distinct_updated[DISTINCT_SUB]; | ||
|  | 
 | ||
|  |     if ( changed < p->distinct_count[DISTINCT_SUB] ) | ||
|  |       return TRUE; | ||
|  |   } | ||
|  | 
 | ||
|  |   if ( !update_hash(db) ) | ||
|  |     return FALSE; | ||
|  | 
 | ||
|  |   { atomset subject_set; | ||
|  |     atomset object_set; | ||
|  |     triple t; | ||
|  |     triple *byp; | ||
|  | 
 | ||
|  |     memset(&t, 0, sizeof(t)); | ||
|  |     t.predicate.r = p; | ||
|  |     t.indexed |= BY_P; | ||
|  | 
 | ||
|  |     init_atomset(&subject_set); | ||
|  |     init_atomset(&object_set); | ||
|  |     for(byp = db->table[t.indexed][triple_hash(db, &t, t.indexed)]; | ||
|  | 	byp; | ||
|  | 	byp = byp->next[t.indexed]) | ||
|  |     { if ( !byp->erased && !byp->is_duplicate ) | ||
|  |       { if ( (which == DISTINCT_DIRECT && byp->predicate.r == p) || | ||
|  | 	     (which != DISTINCT_DIRECT && isSubPropertyOf(byp->predicate.r, p)) ) | ||
|  | 	{ total++; | ||
|  | 	  add_atomset(&subject_set, byp->subject); | ||
|  | 	  add_atomset(&object_set, object_hash(byp)); /* NOTE: not exact! */ | ||
|  | 	} | ||
|  |       } | ||
|  |     } | ||
|  | 
 | ||
|  |     p->distinct_count[which]    = total; | ||
|  |     p->distinct_subjects[which] = subject_set.tree.count; | ||
|  |     p->distinct_objects[which]  = object_set.tree.count; | ||
|  | 
 | ||
|  |     destroy_atomset(&subject_set); | ||
|  |     destroy_atomset(&object_set); | ||
|  | 
 | ||
|  |     if ( which == DISTINCT_DIRECT ) | ||
|  |       p->distinct_updated[DISTINCT_DIRECT] = total; | ||
|  |     else | ||
|  |       p->distinct_updated[DISTINCT_SUB] = db->generation; | ||
|  | 
 | ||
|  |     DEBUG(1, Sdprintf("%s: distinct subjects (%s): %ld, objects: %ld\n", | ||
|  | 		      PL_atom_chars(p->name), | ||
|  | 		      (which == DISTINCT_DIRECT ? "rdf" : "rdfs"), | ||
|  | 		      p->distinct_subjects[which], | ||
|  | 		      p->distinct_objects[which])); | ||
|  |   } | ||
|  | 
 | ||
|  |   return TRUE; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static void | ||
|  | invalidate_distinct_counts(rdf_db *db) | ||
|  | { predicate **ht; | ||
|  |   int i; | ||
|  | 
 | ||
|  |   for(i=0,ht = db->pred_table; i<db->pred_table_size; i++, ht++) | ||
|  |   { predicate *p; | ||
|  | 
 | ||
|  |     for( p = *ht; p; p = p->next ) | ||
|  |     { p->distinct_updated[DISTINCT_SUB] = 0; | ||
|  |       p->distinct_count[DISTINCT_SUB] = 0; | ||
|  |       p->distinct_subjects[DISTINCT_SUB] = 0; | ||
|  |       p->distinct_objects[DISTINCT_SUB] = 0; | ||
|  |     } | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static double | ||
|  | subject_branch_factor(rdf_db *db, predicate *p, int which) | ||
|  | { if ( !update_predicate_counts(db, p, which) ) | ||
|  |     return FALSE; | ||
|  | 
 | ||
|  |   if ( p->distinct_subjects[which] == 0 ) | ||
|  |     return 0.0;				/* 0 --> 0 */ | ||
|  | 
 | ||
|  |   return (double)p->distinct_count[which] / | ||
|  |          (double)p->distinct_subjects[which]; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static double | ||
|  | object_branch_factor(rdf_db *db, predicate *p, int which) | ||
|  | { if ( !update_predicate_counts(db, p, which) ) | ||
|  |     return FALSE; | ||
|  | 
 | ||
|  |   if ( p->distinct_objects[which] == 0 ) | ||
|  |     return 0.0;				/* 0 --> 0 */ | ||
|  | 
 | ||
|  |   return (double)p->distinct_count[which] / | ||
|  |          (double)p->distinct_objects[which]; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | 
 | ||
|  | 
 | ||
|  | 		 /*******************************
 | ||
|  | 		 *	   NAMED GRAPHS		* | ||
|  | 		 *******************************/ | ||
|  | 
 | ||
|  | /* MT: all calls must be locked
 | ||
|  | */ | ||
|  | 
 | ||
|  | static void | ||
|  | init_graph_table(rdf_db *db) | ||
|  | { int bytes = sizeof(predicate*)*INITIAL_GRAPH_TABLE_SIZE; | ||
|  | 
 | ||
|  |   db->graph_table = rdf_malloc(db, bytes); | ||
|  |   memset(db->graph_table, 0, bytes); | ||
|  |   db->graph_table_size = INITIAL_GRAPH_TABLE_SIZE; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static graph * | ||
|  | lookup_graph(rdf_db *db, atom_t name, int create) | ||
|  | { int hash = atom_hash(name) % db->graph_table_size; | ||
|  |   graph *src; | ||
|  | 
 | ||
|  |   LOCK_MISC(db); | ||
|  |   for(src=db->graph_table[hash]; src; src = src->next) | ||
|  |   { if ( src->name == name ) | ||
|  |     { UNLOCK_MISC(db); | ||
|  |       return src; | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   if ( !create ) | ||
|  |   { UNLOCK_MISC(db); | ||
|  |     return NULL; | ||
|  |   } | ||
|  | 
 | ||
|  |   src = rdf_malloc(db, sizeof(*src)); | ||
|  |   memset(src, 0, sizeof(*src)); | ||
|  |   src->name = name; | ||
|  |   src->md5 = TRUE; | ||
|  |   PL_register_atom(name); | ||
|  |   src->next = db->graph_table[hash]; | ||
|  |   db->graph_table[hash] = src; | ||
|  |   UNLOCK_MISC(db); | ||
|  | 
 | ||
|  |   return src; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static void | ||
|  | erase_graphs(rdf_db *db) | ||
|  | { graph **ht; | ||
|  |   int i; | ||
|  | 
 | ||
|  |   for(i=0,ht = db->graph_table; i<db->graph_table_size; i++, ht++) | ||
|  |   { graph *src, *n; | ||
|  | 
 | ||
|  |     for( src = *ht; src; src = n ) | ||
|  |     { n = src->next; | ||
|  | 
 | ||
|  |       PL_unregister_atom(src->name); | ||
|  |       if ( src->source ) | ||
|  | 	PL_unregister_atom(src->source); | ||
|  |       rdf_free(db, src, sizeof(*src)); | ||
|  |     } | ||
|  | 
 | ||
|  |     *ht = NULL; | ||
|  |   } | ||
|  | 
 | ||
|  |   db->last_graph = NULL; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static void | ||
|  | register_graph(rdf_db *db, triple *t) | ||
|  | { graph *src; | ||
|  | 
 | ||
|  |   if ( !t->graph ) | ||
|  |     return; | ||
|  | 
 | ||
|  |   if ( db->last_graph && db->last_graph->name == t->graph ) | ||
|  |   { src = db->last_graph; | ||
|  |   } else | ||
|  |   { src = lookup_graph(db, t->graph, TRUE); | ||
|  |     db->last_graph = src; | ||
|  |   } | ||
|  | 
 | ||
|  |   src->triple_count++; | ||
|  | #ifdef WITH_MD5
 | ||
|  |   if ( src->md5 ) | ||
|  |   { md5_byte_t digest[16]; | ||
|  |     md5_triple(t, digest); | ||
|  |     sum_digest(src->digest, digest); | ||
|  |   } | ||
|  | #endif
 | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static void | ||
|  | unregister_graph(rdf_db *db, triple *t) | ||
|  | { graph *src; | ||
|  | 
 | ||
|  |   if ( !t->graph ) | ||
|  |     return; | ||
|  | 
 | ||
|  |   if ( db->last_graph && db->last_graph->name == t->graph ) | ||
|  |   { src = db->last_graph; | ||
|  |   } else | ||
|  |   { src = lookup_graph(db, t->graph, TRUE); | ||
|  |     db->last_graph = src; | ||
|  |   } | ||
|  | 
 | ||
|  |   src->triple_count--; | ||
|  | #ifdef WITH_MD5
 | ||
|  |   if ( src->md5 ) | ||
|  |   { md5_byte_t digest[16]; | ||
|  |     md5_triple(t, digest); | ||
|  |     dec_digest(src->digest, digest); | ||
|  |   } | ||
|  | #endif
 | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | ||
|  | rdf_graphs_(-ListOfGraphs) | ||
|  | 
 | ||
|  | Return a list holding the names  of   all  currently defined graphs. We | ||
|  | return a list to avoid the need for complicated long locks. | ||
|  | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ | ||
|  | 
 | ||
|  | static foreign_t | ||
|  | rdf_graphs(term_t list) | ||
|  | { int i; | ||
|  |   term_t tail = PL_copy_term_ref(list); | ||
|  |   term_t head = PL_new_term_ref(); | ||
|  |   rdf_db *db = DB; | ||
|  | 
 | ||
|  |   if ( !RDLOCK(db) ) | ||
|  |     return FALSE; | ||
|  |   for(i=0; i<db->graph_table_size; i++) | ||
|  |   { graph *src; | ||
|  | 
 | ||
|  |     for(src=db->graph_table[i]; src; src = src->next) | ||
|  |     { if ( !PL_unify_list(tail, head, tail) || | ||
|  | 	   !PL_unify_atom(head, src->name) ) | ||
|  |       { RDUNLOCK(db); | ||
|  | 	return FALSE; | ||
|  |       } | ||
|  |     } | ||
|  |   } | ||
|  |   RDUNLOCK(db); | ||
|  | 
 | ||
|  |   return PL_unify_nil(tail); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static foreign_t | ||
|  | rdf_graph_source(term_t graph_name, term_t source, term_t modified) | ||
|  | { atom_t gn; | ||
|  |   int rc = FALSE; | ||
|  |   rdf_db *db = DB; | ||
|  | 
 | ||
|  |   if ( !get_atom_or_var_ex(graph_name, &gn) ) | ||
|  |     return FALSE; | ||
|  | 
 | ||
|  |   if ( gn ) | ||
|  |   { graph *s; | ||
|  | 
 | ||
|  |     if ( !RDLOCK(db) ) | ||
|  |       return FALSE; | ||
|  |     if ( (s = lookup_graph(db, gn, FALSE)) && s->source) | ||
|  |     { rc = ( PL_unify_atom(source, s->source) && | ||
|  | 	     PL_unify_float(modified, s->modified) ); | ||
|  |     } | ||
|  |     RDUNLOCK(db); | ||
|  |   } else | ||
|  |   { atom_t src; | ||
|  | 
 | ||
|  |     if ( get_atom_ex(source, &src) ) | ||
|  |     { int i; | ||
|  |       graph **ht; | ||
|  | 
 | ||
|  |       if ( !RDLOCK(db) ) | ||
|  | 	return FALSE; | ||
|  | 
 | ||
|  |       for(i=0,ht = db->graph_table; i<db->graph_table_size; i++, ht++) | ||
|  |       { graph *s; | ||
|  | 
 | ||
|  | 	for( s = *ht; s; s = s->next ) | ||
|  | 	{ if ( s->source == src ) | ||
|  | 	  { rc = ( PL_unify_atom(graph_name, s->name) && | ||
|  | 		   PL_unify_float(modified, s->modified) ); | ||
|  | 	  } | ||
|  | 	} | ||
|  |       } | ||
|  | 
 | ||
|  |       RDUNLOCK(db); | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   return rc; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static foreign_t | ||
|  | rdf_set_graph_source(term_t graph_name, term_t source, term_t modified) | ||
|  | { atom_t gn, src; | ||
|  |   int rc = FALSE; | ||
|  |   rdf_db *db = DB; | ||
|  |   graph *s; | ||
|  |   double mtime; | ||
|  | 
 | ||
|  |   if ( !get_atom_ex(graph_name, &gn) || | ||
|  |        !get_atom_ex(source, &src) || | ||
|  |        !get_double_ex(modified, &mtime) ) | ||
|  |     return FALSE; | ||
|  | 
 | ||
|  |   if ( !RDLOCK(db) ) | ||
|  |     return FALSE; | ||
|  |   if ( (s = lookup_graph(db, gn, TRUE)) ) | ||
|  |   { if ( s->source != src ) | ||
|  |     { if ( s->source ) | ||
|  | 	PL_unregister_atom(s->source); | ||
|  |       s->source = src; | ||
|  |       PL_register_atom(s->source); | ||
|  |     } | ||
|  |     s->modified = mtime; | ||
|  |     rc = TRUE; | ||
|  |   } | ||
|  |   RDUNLOCK(db); | ||
|  | 
 | ||
|  |   return rc; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static foreign_t | ||
|  | rdf_unset_graph_source(term_t graph_name) | ||
|  | { atom_t gn; | ||
|  |   rdf_db *db = DB; | ||
|  |   graph *s; | ||
|  | 
 | ||
|  |   if ( !get_atom_ex(graph_name, &gn) ) | ||
|  |     return FALSE; | ||
|  |   if ( (s = lookup_graph(db, gn, TRUE)) ) | ||
|  |   { if ( s->source ) | ||
|  |     { PL_unregister_atom(s->source); | ||
|  |       s->source = 0; | ||
|  |     } | ||
|  |     s->modified = 0.0; | ||
|  |   } | ||
|  |   if ( !RDLOCK(db) ) | ||
|  |     return FALSE; | ||
|  | 
 | ||
|  |   RDUNLOCK(db); | ||
|  | 
 | ||
|  |   return TRUE; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | 
 | ||
|  | 		 /*******************************
 | ||
|  | 		 *	     LITERALS		* | ||
|  | 		 *******************************/ | ||
|  | 
 | ||
|  | #define LITERAL_EX_MAGIC 0x2b97e881
 | ||
|  | 
 | ||
|  | typedef struct literal_ex | ||
|  | { literal  *literal; | ||
|  |   atom_info atom; | ||
|  | #ifdef O_SECURE
 | ||
|  |   long	    magic; | ||
|  | #endif
 | ||
|  | } literal_ex; | ||
|  | 
 | ||
|  | 
 | ||
|  | static inline void | ||
|  | prepare_literal_ex(literal_ex *lex) | ||
|  | { SECURE(lex->magic = 0x2b97e881); | ||
|  | 
 | ||
|  |   if ( lex->literal->objtype == OBJ_STRING ) | ||
|  |   { lex->atom.handle = lex->literal->value.string; | ||
|  |     lex->atom.resolved = FALSE; | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static literal * | ||
|  | new_literal(rdf_db *db) | ||
|  | { literal *lit = rdf_malloc(db, sizeof(*lit)); | ||
|  |   memset(lit, 0, sizeof(*lit)); | ||
|  |   lit->references = 1; | ||
|  | 
 | ||
|  |   return lit; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static void | ||
|  | free_literal(rdf_db *db, literal *lit) | ||
|  | { if ( --lit->references == 0 ) | ||
|  |   { unlock_atoms_literal(lit); | ||
|  | 
 | ||
|  |     if ( lit->shared && !db->resetting ) | ||
|  |     { literal_ex lex; | ||
|  | 
 | ||
|  |       lit->shared = FALSE; | ||
|  |       broadcast(EV_OLD_LITERAL, lit, NULL); | ||
|  |       DEBUG(2, | ||
|  | 	    Sdprintf("Delete %p from literal table: ", lit); | ||
|  | 	    print_literal(lit); | ||
|  | 	    Sdprintf("\n")); | ||
|  | 
 | ||
|  |       lex.literal = lit; | ||
|  |       prepare_literal_ex(&lex); | ||
|  | 
 | ||
|  |       if ( !avldel(&db->literals, &lex) ) | ||
|  |       { Sdprintf("Failed to delete %p (size=%ld): ", lit, db->literals.count); | ||
|  | 	print_literal(lit); | ||
|  | 	Sdprintf("\n"); | ||
|  | 	assert(0); | ||
|  |       } | ||
|  |     } | ||
|  | 
 | ||
|  |     if ( lit->objtype == OBJ_TERM && | ||
|  | 	 lit->value.term.record ) | ||
|  |     { if ( lit->term_loaded ) | ||
|  | 	rdf_free(db, lit->value.term.record, lit->value.term.len); | ||
|  |       else | ||
|  | 	PL_erase_external(lit->value.term.record); | ||
|  |     } | ||
|  |     rdf_free(db, lit, sizeof(*lit)); | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static literal * | ||
|  | copy_literal(rdf_db *db, literal *lit) | ||
|  | { lit->references++; | ||
|  |   return lit; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static void | ||
|  | alloc_literal_triple(rdf_db *db, triple *t) | ||
|  | { if ( !t->object_is_literal ) | ||
|  |   { t->object.literal = new_literal(db); | ||
|  |     t->object_is_literal = TRUE; | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static void | ||
|  | lock_atoms_literal(literal *lit) | ||
|  | { if ( !lit->atoms_locked ) | ||
|  |   { lit->atoms_locked = TRUE; | ||
|  | 
 | ||
|  |     switch(lit->objtype) | ||
|  |     { case OBJ_STRING: | ||
|  | 	PL_register_atom(lit->value.string); | ||
|  | 	if ( lit->qualifier ) | ||
|  | 	  PL_register_atom(lit->type_or_lang); | ||
|  | 	break; | ||
|  |     } | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static void | ||
|  | unlock_atoms_literal(literal *lit) | ||
|  | { if ( lit->atoms_locked ) | ||
|  |   { lit->atoms_locked = FALSE; | ||
|  | 
 | ||
|  |     switch(lit->objtype) | ||
|  |     { case OBJ_STRING: | ||
|  | 	PL_unregister_atom(lit->value.string); | ||
|  | 	if ( lit->qualifier ) | ||
|  | 	  PL_unregister_atom(lit->type_or_lang); | ||
|  | 	break; | ||
|  |     } | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | 		 /*******************************
 | ||
|  | 		 *	     LITERAL DB		* | ||
|  | 		 *******************************/ | ||
|  | 
 | ||
|  | /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | ||
|  | compare_literals() sorts literals.  Ordering is defined as: | ||
|  | 
 | ||
|  | 	* Numeric literals < string literals < term literals | ||
|  | 	* Numeric literals (int and float) are sorted by value | ||
|  | 	* String literals are sorted alhabetically | ||
|  | 		- case independent, but uppercase before lowercase | ||
|  | 		- locale (strcoll) sorting? | ||
|  | 		- delete dyadrics | ||
|  | 		- first on string, then on type, then on language | ||
|  | 	* Terms are sorted on Prolog standard order of terms | ||
|  | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ | ||
|  | 
 | ||
|  | static int | ||
|  | compare_literals(void *p1, void *p2, NODE type) | ||
|  | { literal_ex *lex = p1; | ||
|  |   literal *l1 = lex->literal; | ||
|  |   literal *l2 = *(literal**)p2; | ||
|  | 
 | ||
|  |   SECURE(assert(lex->magic == LITERAL_EX_MAGIC)); | ||
|  | 
 | ||
|  |   if ( l1->objtype == l2->objtype ) | ||
|  |   { switch(l1->objtype) | ||
|  |     { case OBJ_INTEGER: | ||
|  |       { int64_t v1 = l1->value.integer; | ||
|  | 	int64_t v2 = l2->value.integer; | ||
|  | 	return v1 < v2 ? -1 : v1 > v2 ? 1 : 0; | ||
|  |       } | ||
|  |       case OBJ_DOUBLE: | ||
|  |       { double v1 = l1->value.real; | ||
|  | 	double v2 = l2->value.real; | ||
|  | 	return v1 < v2 ? -1 : v1 > v2 ? 1 : 0; | ||
|  |       } | ||
|  |       case OBJ_STRING: | ||
|  |       { int rc = cmp_atom_info(&lex->atom, l2->value.string); | ||
|  | 
 | ||
|  | 	if ( rc == 0 ) | ||
|  | 	{ if ( l1->qualifier == l2->qualifier ) | ||
|  | 	    return cmp_atoms(l1->type_or_lang, l2->type_or_lang); | ||
|  | 	  return l1->qualifier - l2->qualifier; | ||
|  | 	} | ||
|  | 	return rc; | ||
|  |       } | ||
|  |       case OBJ_TERM: | ||
|  |       { fid_t fid = PL_open_foreign_frame(); | ||
|  | 	term_t t1 = PL_new_term_ref(); | ||
|  | 	term_t t2 = PL_new_term_ref(); | ||
|  | 	int rc; | ||
|  | 
 | ||
|  | 	PL_recorded_external(l1->value.term.record, t1); /* can also be handled in literal_ex */ | ||
|  | 	PL_recorded_external(l2->value.term.record, t2); | ||
|  | 	rc = PL_compare(t1, t2); | ||
|  | 
 | ||
|  | 	PL_discard_foreign_frame(fid); | ||
|  | 	return rc; | ||
|  |       } | ||
|  |       default: | ||
|  | 	assert(0); | ||
|  |         return 0; | ||
|  |     } | ||
|  |   } else if ( l1->objtype == OBJ_INTEGER && l2->objtype == OBJ_DOUBLE ) | ||
|  |   { double v1 = (double)l1->value.integer; | ||
|  |     double v2 = l2->value.real; | ||
|  |     return v1 < v2 ? -1 : v1 > v2 ? 1 : -1; | ||
|  |   } else if ( l1->objtype == OBJ_DOUBLE && l2->objtype == OBJ_INTEGER ) | ||
|  |   { double v1 = l1->value.real; | ||
|  |     double v2 = (double)l2->value.integer; | ||
|  |     return v1 < v2 ? -1 : v1 > v2 ? 1 : 1; | ||
|  |   } else | ||
|  |   { return l1->objtype - l2->objtype; | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static void* | ||
|  | avl_malloc(void *ptr, size_t size) | ||
|  | { return rdf_malloc(ptr, size); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static void | ||
|  | avl_free(void *ptr, void *data, size_t size) | ||
|  | { rdf_free(ptr, data, size); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | ||
|  | Create the sorted literal tree. Note  that   we  do  not register a free | ||
|  | handler  for  the  tree  as  nodes   are  either  already  destroyed  by | ||
|  | free_literal() or by rdf_reset_db(). | ||
|  | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ | ||
|  | 
 | ||
|  | static void | ||
|  | init_literal_table(rdf_db *db) | ||
|  | { avlinit(&db->literals, | ||
|  | 	  db, sizeof(literal*), | ||
|  | 	  compare_literals, | ||
|  | 	  NULL, | ||
|  | 	  avl_malloc, | ||
|  | 	  avl_free); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | ||
|  | share_literal() takes a literal  and  replaces   it  with  one  from the | ||
|  | literal database if there is a match.   On a match, the argument literal | ||
|  | is destroyed. Without a match it adds   the  literal to the database and | ||
|  | returns it. | ||
|  | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ | ||
|  | 
 | ||
|  | static literal * | ||
|  | share_literal(rdf_db *db, literal *from) | ||
|  | { literal **data; | ||
|  |   literal_ex lex; | ||
|  | 
 | ||
|  |   lex.literal = from; | ||
|  |   prepare_literal_ex(&lex); | ||
|  | 
 | ||
|  |   if ( (data = avlins(&db->literals, &lex)) ) | ||
|  |   { literal *l2 = *data; | ||
|  | 
 | ||
|  |     DEBUG(2, | ||
|  | 	  Sdprintf("Replace %p by %p:\n", from, l2); | ||
|  | 	  Sdprintf("\tfrom: "); print_literal(from); | ||
|  | 	  Sdprintf("\n\tto: "); print_literal(l2); | ||
|  | 	  Sdprintf("\n")); | ||
|  | 
 | ||
|  |     l2->references++; | ||
|  |     free_literal(db, from); | ||
|  | 
 | ||
|  |     return l2; | ||
|  |   } else | ||
|  |   { DEBUG(2, | ||
|  | 	  Sdprintf("Insert %p into literal table: ", from); | ||
|  | 	  print_literal(from); | ||
|  | 	  Sdprintf("\n")); | ||
|  | 
 | ||
|  |     from->shared = TRUE; | ||
|  |     broadcast(EV_NEW_LITERAL, from, NULL); | ||
|  |     return from; | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | #ifdef O_SECURE
 | ||
|  | static literal ** | ||
|  | add_literals(AVLtree node, literal **p) | ||
|  | { literal **litp; | ||
|  | 
 | ||
|  |   if ( node->subtree[LEFT] ) | ||
|  |     p = add_literals(node->subtree[LEFT], p); | ||
|  |   litp = (literal**)node->data; | ||
|  |   *p++ = *litp; | ||
|  |   if ( node->subtree[RIGHT] ) | ||
|  |     p = add_literals(node->subtree[RIGHT], p); | ||
|  | 
 | ||
|  |   return p; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static foreign_t | ||
|  | check_transitivity() | ||
|  | { rdf_db *db = DB; | ||
|  |   literal **array = malloc(sizeof(literal*)*db->literals.count); | ||
|  |   literal **p = array; | ||
|  |   int i,j; | ||
|  | 
 | ||
|  |   add_literals(db->literals.root, p); | ||
|  |   Sdprintf("Checking %ld literals ...\n", db->literals.count); | ||
|  | 
 | ||
|  |   for(i=0; i<db->literals.count; i++) | ||
|  |   { int end; | ||
|  | 
 | ||
|  |     Sdprintf("\r%6ld", i); | ||
|  |     end = i+100; | ||
|  |     if ( end > db->literals.count ) | ||
|  |       end = db->literals.count; | ||
|  | 
 | ||
|  |     for(j=i+1; j<end; j++) | ||
|  |     { literal_ex lex; | ||
|  | 
 | ||
|  |       lex.literal = &array[i]; | ||
|  |       prepare_literal_ex(&lex); | ||
|  | 
 | ||
|  |       if ( compare_literals(&lex, &array[j], IS_NULL) >= 0 ) | ||
|  |       { Sdprintf("\nERROR: i,j=%d,%d: ", i, j); | ||
|  | 	print_literal(array[i]); | ||
|  | 	Sdprintf(" >= "); | ||
|  | 	print_literal(array[j]); | ||
|  | 	Sdprintf("\n"); | ||
|  |       } | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   free(array); | ||
|  | 
 | ||
|  |   return TRUE; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static void | ||
|  | dump_lnode(AVLtree node) | ||
|  | { literal **litp; | ||
|  | 
 | ||
|  |   if ( node->subtree[LEFT] ) | ||
|  |     dump_lnode(node->subtree[LEFT]); | ||
|  |   litp = (literal**)node->data; | ||
|  |   print_literal(*litp); | ||
|  |   Sdprintf("\n"); | ||
|  |   if ( node->subtree[RIGHT] ) | ||
|  |     dump_lnode(node->subtree[RIGHT]); | ||
|  | } | ||
|  | 
 | ||
|  | static foreign_t | ||
|  | dump_literals() | ||
|  | { rdf_db *db = DB; | ||
|  | 
 | ||
|  |   dump_lnode(db->literals.root); | ||
|  |   return TRUE; | ||
|  | } | ||
|  | #endif
 | ||
|  | 
 | ||
|  | 
 | ||
|  | 
 | ||
|  | 		 /*******************************
 | ||
|  | 		 *	      TRIPLES		* | ||
|  | 		 *******************************/ | ||
|  | 
 | ||
|  | static void | ||
|  | init_tables(rdf_db *db) | ||
|  | { int i; | ||
|  |   int bytes = sizeof(triple*)*INITIAL_TABLE_SIZE; | ||
|  |   int cbytes = sizeof(int)*INITIAL_TABLE_SIZE; | ||
|  | 
 | ||
|  |   db->table[0] = &db->by_none; | ||
|  |   db->tail[0]  = &db->by_none_tail; | ||
|  | 
 | ||
|  |   for(i=BY_S; i<=BY_OP; i++) | ||
|  |   { if ( i == BY_SO ) | ||
|  |       continue; | ||
|  | 
 | ||
|  |     db->table[i] = rdf_malloc(db, bytes); | ||
|  |     memset(db->table[i], 0, bytes); | ||
|  |     db->tail[i] = rdf_malloc(db, bytes); | ||
|  |     memset(db->tail[i], 0, bytes); | ||
|  |     db->counts[i] = rdf_malloc(db, cbytes); | ||
|  |     memset(db->counts[i], 0, cbytes); | ||
|  |     db->table_size[i] = INITIAL_TABLE_SIZE; | ||
|  |   } | ||
|  | 
 | ||
|  |   init_pred_table(db); | ||
|  |   init_graph_table(db); | ||
|  |   init_literal_table(db); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static rdf_db * | ||
|  | new_db() | ||
|  | { rdf_db *db = rdf_malloc(NULL, sizeof(*db)); | ||
|  | 
 | ||
|  |   memset(db, 0, sizeof(*db)); | ||
|  |   INIT_LOCK(db); | ||
|  |   init_tables(db); | ||
|  | 
 | ||
|  |   return db; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static triple * | ||
|  | new_triple(rdf_db *db) | ||
|  | { triple *t = rdf_malloc(db, sizeof(*t)); | ||
|  |   memset(t, 0, sizeof(*t)); | ||
|  |   t->allocated = TRUE; | ||
|  | 
 | ||
|  |   return t; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static void | ||
|  | free_triple(rdf_db *db, triple *t) | ||
|  | { unlock_atoms(t); | ||
|  | 
 | ||
|  |   if ( t->object_is_literal && t->object.literal ) | ||
|  |     free_literal(db, t->object.literal); | ||
|  | 
 | ||
|  |   if ( t->allocated ) | ||
|  |     rdf_free(db, t, sizeof(*t)); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | #define HASHED 0x80000000
 | ||
|  | 
 | ||
|  | static unsigned int | ||
|  | literal_hash(literal *lit) | ||
|  | { if ( lit->hash & HASHED ) | ||
|  |   { return lit->hash; | ||
|  |   } else | ||
|  |   { unsigned int hash; | ||
|  | 
 | ||
|  |     switch(lit->objtype) | ||
|  |     { case OBJ_STRING: | ||
|  | 	hash = atom_hash_case(lit->value.string); | ||
|  |         break; | ||
|  |       case OBJ_INTEGER: | ||
|  |       case OBJ_DOUBLE: | ||
|  | 	hash = rdf_murmer_hash(&lit->value.integer, | ||
|  | 			       sizeof(lit->value.integer), | ||
|  | 			       MURMUR_SEED); | ||
|  |         break; | ||
|  |       case OBJ_TERM: | ||
|  | 	hash = rdf_murmer_hash(lit->value.term.record, | ||
|  | 			       (int)lit->value.term.len, | ||
|  | 			       MURMUR_SEED); | ||
|  | 	break; | ||
|  |       default: | ||
|  | 	assert(0); | ||
|  | 	return 0; | ||
|  |     } | ||
|  | 
 | ||
|  |     lit->hash = (hash | HASHED); | ||
|  |     return lit->hash; | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static unsigned long | ||
|  | object_hash(triple *t) | ||
|  | { if ( t->object_is_literal ) | ||
|  |   { return literal_hash(t->object.literal); | ||
|  |   } else | ||
|  |   { return atom_hash(t->object.resource); | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static int | ||
|  | triple_hash(rdf_db *db, triple *t, int which) | ||
|  | { unsigned long v; | ||
|  | 
 | ||
|  |   switch(which) | ||
|  |   { case BY_NONE: | ||
|  |       return 0; | ||
|  |     case BY_S: | ||
|  |       v = atom_hash(t->subject); | ||
|  |       break; | ||
|  |     case BY_P: | ||
|  |       v = predicate_hash(t->predicate.r); | ||
|  |       break; | ||
|  |     case BY_O: | ||
|  |       v = object_hash(t); | ||
|  |       break; | ||
|  |     case BY_SP: | ||
|  |       v = atom_hash(t->subject) ^ predicate_hash(t->predicate.r); | ||
|  |       break; | ||
|  |     case BY_OP: | ||
|  |       v = predicate_hash(t->predicate.r) ^ object_hash(t); | ||
|  |       break; | ||
|  |     default: | ||
|  |       v = 0;				/* make compiler silent */ | ||
|  |       assert(0); | ||
|  |   } | ||
|  | 
 | ||
|  |   return (int)(v % (long)db->table_size[which]); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | ||
|  | by_inverse[] returns the index key to use   for inverse search as needed | ||
|  | to realise symmetric and inverse predicates. | ||
|  | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ | ||
|  | 
 | ||
|  | static int by_inverse[8] = | ||
|  | { BY_NONE,				/* BY_NONE = 0 */ | ||
|  |   BY_O,					/* BY_S    = 1 */ | ||
|  |   BY_P,					/* BY_P    = 2 */ | ||
|  |   BY_OP,				/* BY_SP   = 3 */ | ||
|  |   BY_S,					/* BY_O    = 4 */ | ||
|  |   BY_SO,				/* BY_SO   = 5 */ | ||
|  |   BY_SP,				/* BY_OP   = 6 */ | ||
|  |   BY_SPO,				/* BY_SPO  = 7 */ | ||
|  | }; | ||
|  | 
 | ||
|  | 
 | ||
|  | /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | ||
|  | triple *first(atom_t subject) | ||
|  |     Find the first triple on subject.  The first is marked to generate a | ||
|  |     unique subjects quickly; | ||
|  | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ | ||
|  | 
 | ||
|  | static triple * | ||
|  | first(rdf_db *db, atom_t subject) | ||
|  | { triple *t, tmp; | ||
|  |   int hash; | ||
|  | 
 | ||
|  |   tmp.subject = subject; | ||
|  |   hash = triple_hash(db, &tmp, BY_S); | ||
|  | 
 | ||
|  |   for(t=db->table[BY_S][hash]; t; t = t->next[BY_S]) | ||
|  |   { if ( t->subject == subject && !t->erased ) | ||
|  |       return t; | ||
|  |   } | ||
|  | 
 | ||
|  |   return NULL; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static void | ||
|  | link_triple_hash(rdf_db *db, triple *t) | ||
|  | { int i; | ||
|  | 
 | ||
|  |   for(i=1; i<=BY_OP; i++) | ||
|  |   { if ( db->table[i] ) | ||
|  |     { int hash = triple_hash(db, t, i); | ||
|  | 
 | ||
|  |       if ( db->tail[i][hash] ) | ||
|  |       { db->tail[i][hash]->next[i] = t; | ||
|  |       } else | ||
|  |       { db->table[i][hash] = t; | ||
|  |       } | ||
|  |       db->tail[i][hash] = t; | ||
|  |       db->counts[i][hash]++; | ||
|  |     } | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | typedef enum | ||
|  | { DUP_NONE, | ||
|  |   DUP_DUPLICATE, | ||
|  |   DUP_DISCARDED | ||
|  | } dub_state; | ||
|  | 
 | ||
|  | 
 | ||
|  | static dub_state | ||
|  | discard_duplicate(rdf_db *db, triple *t) | ||
|  | { triple *d; | ||
|  |   const int indexed = BY_SP; | ||
|  |   dub_state rc = DUP_NONE; | ||
|  | 
 | ||
|  |   assert(t->is_duplicate == FALSE); | ||
|  |   assert(t->duplicates == 0); | ||
|  | 
 | ||
|  |   if ( WANT_GC(db) )			/* (*) See above */ | ||
|  |     update_hash(db); | ||
|  |   d = db->table[indexed][triple_hash(db, t, indexed)]; | ||
|  |   for( ; d && d != t; d = d->next[indexed] ) | ||
|  |   { if ( match_triples(d, t, MATCH_DUPLICATE) ) | ||
|  |     { if ( d->graph == t->graph && | ||
|  | 	   (d->line == NO_LINE || d->line == t->line) ) | ||
|  |       { free_triple(db, t); | ||
|  | 
 | ||
|  | 	return DUP_DISCARDED; | ||
|  |       } | ||
|  | 
 | ||
|  |       rc = DUP_DUPLICATE; | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   return rc; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /* MT: must be locked by caller */ | ||
|  | 
 | ||
|  | static int | ||
|  | link_triple_silent(rdf_db *db, triple *t) | ||
|  | { triple *one; | ||
|  |   dub_state dup; | ||
|  | 
 | ||
|  |   if ( t->resolve_pred ) | ||
|  |   { t->predicate.r = lookup_predicate(db, t->predicate.u); | ||
|  |     t->resolve_pred = FALSE; | ||
|  |   } | ||
|  | 
 | ||
|  |   if ( (dup=discard_duplicate(db, t)) == DUP_DISCARDED ) | ||
|  |     return FALSE; | ||
|  | 
 | ||
|  |   if ( db->by_none_tail ) | ||
|  |     db->by_none_tail->next[BY_NONE] = t; | ||
|  |   else | ||
|  |     db->by_none = t; | ||
|  |   db->by_none_tail = t; | ||
|  | 
 | ||
|  |   link_triple_hash(db, t); | ||
|  |   if ( t->object_is_literal ) | ||
|  |     t->object.literal = share_literal(db, t->object.literal); | ||
|  | 
 | ||
|  |   if ( dup == DUP_DUPLICATE && update_duplicates_add(db, t) ) | ||
|  |     goto ok;				/* is a duplicate */ | ||
|  | 
 | ||
|  | 					/* keep track of subjects */ | ||
|  |   one = first(db, t->subject); | ||
|  |   if ( !one->first ) | ||
|  |   { one->first = TRUE; | ||
|  |     db->subjects++; | ||
|  |   } | ||
|  | 
 | ||
|  | 					/* keep track of subPropertyOf */ | ||
|  |   if ( t->predicate.r->name == ATOM_subPropertyOf && | ||
|  |        t->object_is_literal == FALSE ) | ||
|  |   { predicate *me    = lookup_predicate(db, t->subject); | ||
|  |     predicate *super = lookup_predicate(db, t->object.resource); | ||
|  | 
 | ||
|  |     addSubPropertyOf(db, me, super); | ||
|  |   } | ||
|  | 
 | ||
|  | ok: | ||
|  |   db->created++; | ||
|  |   t->predicate.r->triple_count++; | ||
|  |   register_graph(db, t); | ||
|  | 
 | ||
|  |   return TRUE; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static inline void | ||
|  | link_triple(rdf_db *db, triple *t) | ||
|  | { if ( link_triple_silent(db, t) ) | ||
|  |     broadcast(EV_ASSERT, t, NULL); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | ||
|  | rehash_triples() | ||
|  | 
 | ||
|  | Relink the triples in the hash-chains after the hash-keys for properties | ||
|  | have changed or the tables have  been   resized.  The caller must ensure | ||
|  | there are no active queries and the tables are of the proper size. | ||
|  | 
 | ||
|  | At the same time, this predicate actually removes erased triples. | ||
|  | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ | ||
|  | 
 | ||
|  | static long | ||
|  | tbl_size(long triples) | ||
|  | { long s0 = 1024; | ||
|  | 
 | ||
|  |   triples /= MIN_HASH_FACTOR; | ||
|  | 
 | ||
|  |   while(s0 < triples) | ||
|  |     s0 *= 2; | ||
|  | 
 | ||
|  |   return s0; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static void | ||
|  | rehash_triples(rdf_db *db) | ||
|  | { int i; | ||
|  |   triple *t, *t2; | ||
|  |   long count = db->created - db->freed; | ||
|  |   long tsize = tbl_size(count); | ||
|  | 
 | ||
|  |   DEBUG(1, Sdprintf("(%ld triples; %ld entries) ...", count, tsize)); | ||
|  |   broadcast(EV_REHASH, (void*)ATOM_begin, NULL); | ||
|  | 
 | ||
|  |   for(i=1; i<INDEX_TABLES; i++) | ||
|  |   { if ( db->table[i] ) | ||
|  |     { long bytes   = sizeof(triple*) * tsize; | ||
|  |       long cbytes  = sizeof(int)     * tsize; | ||
|  |       long obytes  = sizeof(triple*) * db->table_size[i]; | ||
|  |       long ocbytes = sizeof(int)     * db->table_size[i]; | ||
|  | 
 | ||
|  |       db->table[i]  = rdf_realloc(db, db->table[i],  obytes,  bytes); | ||
|  |       db->tail[i]   = rdf_realloc(db, db->tail[i],   obytes,  bytes); | ||
|  |       db->counts[i] = rdf_realloc(db, db->counts[i], ocbytes, cbytes); | ||
|  |       db->table_size[i] = tsize; | ||
|  | 
 | ||
|  |       memset(db->table[i],  0, bytes); | ||
|  |       memset(db->tail[i],   0, bytes); | ||
|  |       memset(db->counts[i], 0, cbytes); | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  | 					/* delete leading erased triples */ | ||
|  |   for(t=db->by_none; t && t->erased; t=t2) | ||
|  |   { t2 = t->next[BY_NONE]; | ||
|  | 
 | ||
|  |     free_triple(db, t); | ||
|  |     db->freed++; | ||
|  | 
 | ||
|  |     db->by_none = t2; | ||
|  |   } | ||
|  | 
 | ||
|  |   for(t=db->by_none; t; t = t2) | ||
|  |   { triple *t3; | ||
|  | 
 | ||
|  |     t2 = t->next[BY_NONE]; | ||
|  | 
 | ||
|  |     for(i=1; i<INDEX_TABLES; i++) | ||
|  |       t->next[i] = NULL; | ||
|  | 
 | ||
|  |     assert(t->erased == FALSE); | ||
|  |     link_triple_hash(db, t); | ||
|  | 
 | ||
|  |     for( ; t2 && t2->erased; t2=t3 ) | ||
|  |     { t3 = t2->next[BY_NONE]; | ||
|  | 
 | ||
|  |       free_triple(db, t2); | ||
|  |       db->freed++; | ||
|  |     } | ||
|  | 
 | ||
|  |     t->next[BY_NONE] = t2; | ||
|  |     if ( !t2 ) | ||
|  |       db->by_none_tail = t; | ||
|  |   } | ||
|  | 
 | ||
|  |   if ( db->by_none == NULL ) | ||
|  |     db->by_none_tail = NULL; | ||
|  | 
 | ||
|  |   broadcast(EV_REHASH, (void*)ATOM_end, NULL); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | ||
|  | update_hash(). Note this may be called by  readers and writers, but must | ||
|  | be done only onces and certainly   not concurrently by multiple readers. | ||
|  | Hence we need a seperate lock. | ||
|  | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ | ||
|  | 
 | ||
|  | static int | ||
|  | WANT_GC(rdf_db *db) | ||
|  | { if ( db->gc_blocked ) | ||
|  |   { return FALSE; | ||
|  |   } else | ||
|  |   { long dirty = db->erased - db->freed; | ||
|  |     long count = db->created - db->erased; | ||
|  | 
 | ||
|  |     if ( dirty > 1000 && dirty > count ) | ||
|  |       return TRUE; | ||
|  |     if ( count > db->table_size[1]*MAX_HASH_FACTOR ) | ||
|  |       return TRUE; | ||
|  | 
 | ||
|  |     return FALSE; | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static int | ||
|  | update_hash(rdf_db *db) | ||
|  | { int want_gc = WANT_GC(db); | ||
|  | 
 | ||
|  |   if ( want_gc ) | ||
|  |     DEBUG(1, Sdprintf("rdf_db: want GC\n")); | ||
|  | 
 | ||
|  |   if ( db->need_update || want_gc ) | ||
|  |   { LOCK_MISC(db); | ||
|  | 
 | ||
|  |     if ( db->need_update )		/* check again */ | ||
|  |     { if ( organise_predicates(db) ) | ||
|  |       { long t0 = (long)PL_query(PL_QUERY_USER_CPU); | ||
|  | 
 | ||
|  | 	DEBUG(1, Sdprintf("Re-hash ...")); | ||
|  | 	invalidate_distinct_counts(db); | ||
|  | 	rehash_triples(db); | ||
|  | 	db->generation += (db->created-db->erased); | ||
|  | 	db->rehash_count++; | ||
|  | 	db->rehash_time += ((double)(PL_query(PL_QUERY_USER_CPU)-t0))/1000.0; | ||
|  | 	DEBUG(1, Sdprintf("ok\n")); | ||
|  |       } | ||
|  |       db->need_update = 0; | ||
|  |     } else if ( WANT_GC(db) ) | ||
|  |     { long t0 = (long)PL_query(PL_QUERY_USER_CPU); | ||
|  | 
 | ||
|  |       DEBUG(1, Sdprintf("rdf_db: GC ...")); | ||
|  |       rehash_triples(db); | ||
|  |       db->gc_count++; | ||
|  |       db->gc_time += ((double)(PL_query(PL_QUERY_USER_CPU)-t0))/1000.0; | ||
|  |       DEBUG(1, Sdprintf("ok\n")); | ||
|  |     } | ||
|  | 
 | ||
|  |     UNLOCK_MISC(db); | ||
|  |   } | ||
|  | 
 | ||
|  |   return TRUE; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /* MT: Must be locked */ | ||
|  | 
 | ||
|  | static void | ||
|  | erase_triple_silent(rdf_db *db, triple *t) | ||
|  | { if ( !t->erased ) | ||
|  |   { t->erased = TRUE; | ||
|  | 
 | ||
|  |     update_duplicates_del(db, t); | ||
|  | 
 | ||
|  |     if ( t->predicate.r->name == ATOM_subPropertyOf && | ||
|  | 	 t->object_is_literal == FALSE ) | ||
|  |     { predicate *me    = lookup_predicate(db, t->subject); | ||
|  |       predicate *super = lookup_predicate(db, t->object.resource); | ||
|  | 
 | ||
|  |       delSubPropertyOf(db, me, super); | ||
|  |     } | ||
|  | 
 | ||
|  |     if ( t->first ) | ||
|  |     { triple *one = first(db, t->subject); | ||
|  | 
 | ||
|  |       if ( one ) | ||
|  | 	one->first = TRUE; | ||
|  |       else | ||
|  | 	db->subjects--; | ||
|  |     } | ||
|  |     db->erased++; | ||
|  |     t->predicate.r->triple_count--; | ||
|  |     unregister_graph(db, t); | ||
|  | 
 | ||
|  |     if ( t->object_is_literal ) | ||
|  |     { literal *lit = t->object.literal; | ||
|  | 
 | ||
|  |       t->object.literal = NULL; | ||
|  |       free_literal(db, lit);		/* TBD: thread-safe? */ | ||
|  |     } | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static inline void | ||
|  | erase_triple(rdf_db *db, triple *t) | ||
|  | { broadcast(EV_RETRACT, t, NULL); | ||
|  |   erase_triple_silent(db, t); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static int | ||
|  | match_object(triple *t, triple *p, unsigned flags) | ||
|  | { if ( p->object_is_literal ) | ||
|  |   { if ( t->object_is_literal ) | ||
|  |     { literal *plit = p->object.literal; | ||
|  |       literal *tlit = t->object.literal; | ||
|  | 
 | ||
|  |       if ( !plit->objtype && !plit->qualifier ) | ||
|  | 	return TRUE; | ||
|  | 
 | ||
|  |       if ( plit->objtype && plit->objtype != tlit->objtype ) | ||
|  | 	return FALSE; | ||
|  | 
 | ||
|  |       switch( plit->objtype ) | ||
|  |       { case 0: | ||
|  | 	  if ( plit->qualifier && | ||
|  | 	       tlit->qualifier != plit->qualifier ) | ||
|  | 	    return FALSE; | ||
|  | 	  return TRUE; | ||
|  | 	case OBJ_STRING: | ||
|  | 	  if ( (flags & MATCH_QUAL) || | ||
|  | 	       p->match == STR_MATCH_PLAIN ) | ||
|  | 	  { if ( tlit->qualifier != plit->qualifier ) | ||
|  | 	      return FALSE; | ||
|  | 	  } else | ||
|  | 	  { if ( plit->qualifier && tlit->qualifier && | ||
|  | 		 tlit->qualifier != plit->qualifier ) | ||
|  | 	      return FALSE; | ||
|  | 	  } | ||
|  | 	  if ( plit->type_or_lang && | ||
|  | 	       tlit->type_or_lang != plit->type_or_lang ) | ||
|  | 	    return FALSE; | ||
|  | 	  if ( plit->value.string ) | ||
|  | 	  { if ( tlit->value.string != plit->value.string ) | ||
|  | 	    { if ( p->match >= STR_MATCH_EXACT ) | ||
|  | 	      { return match_atoms(p->match, | ||
|  | 				   plit->value.string, tlit->value.string); | ||
|  | 	      } else | ||
|  | 	      { return FALSE; | ||
|  | 	      } | ||
|  | 	    } | ||
|  | 	  } | ||
|  | 	  return TRUE; | ||
|  | 	case OBJ_INTEGER: | ||
|  | 	  return tlit->value.integer == plit->value.integer; | ||
|  | 	case OBJ_DOUBLE: | ||
|  | 	  return tlit->value.real == plit->value.real; | ||
|  | 	case OBJ_TERM: | ||
|  | 	  if ( plit->value.term.record && | ||
|  | 	       plit->value.term.len != tlit->value.term.len ) | ||
|  | 	    return FALSE; | ||
|  | 	  return memcmp(tlit->value.term.record, plit->value.term.record, | ||
|  | 			plit->value.term.len) == 0; | ||
|  | 	default: | ||
|  | 	  assert(0); | ||
|  |       } | ||
|  |     } | ||
|  |     return FALSE; | ||
|  |   } else | ||
|  |   { if ( p->object.resource ) | ||
|  |     { if ( t->object_is_literal || | ||
|  | 	   (p->object.resource != t->object.resource) ) | ||
|  | 	return FALSE; | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   return TRUE; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | 
 | ||
|  | /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | ||
|  | Match triple t to pattern p.  Erased triples are always skipped. | ||
|  | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ | ||
|  | 
 | ||
|  | static int | ||
|  | match_triples(triple *t, triple *p, unsigned flags) | ||
|  | { /* DEBUG(3, Sdprintf("match_triple(");
 | ||
|  | 	   print_triple(t, 0); | ||
|  | 	   Sdprintf(")\n")); | ||
|  |   */ | ||
|  | 
 | ||
|  |   if ( t->erased ) | ||
|  |     return FALSE; | ||
|  |   if ( p->subject && t->subject != p->subject ) | ||
|  |     return FALSE; | ||
|  |   if ( !match_object(t, p, flags) ) | ||
|  |     return FALSE; | ||
|  |   if ( flags & MATCH_SRC ) | ||
|  |   { if ( p->graph && t->graph != p->graph ) | ||
|  |       return FALSE; | ||
|  |     if ( p->line && t->line != p->line ) | ||
|  |       return FALSE; | ||
|  |   } | ||
|  | 					/* last; may be expensive */ | ||
|  |   if ( p->predicate.r && t->predicate.r != p->predicate.r ) | ||
|  |   { if ( (flags & MATCH_SUBPROPERTY) ) | ||
|  |       return isSubPropertyOf(t->predicate.r, p->predicate.r); | ||
|  |     else | ||
|  |       return FALSE; | ||
|  |   } | ||
|  |   return TRUE; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | 		 /*******************************
 | ||
|  | 		 *	      SAVE/LOAD		* | ||
|  | 		 *******************************/ | ||
|  | 
 | ||
|  | /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | ||
|  | The RDF triple format.  This format is intended for quick save and load | ||
|  | and not for readability or exchange.  Parts are based on the SWI-Prolog | ||
|  | Quick Load Format (implemented in pl-wic.c). | ||
|  | 
 | ||
|  | 	<file> 		::= <magic> | ||
|  | 			    <version> | ||
|  | 			    ['S' <graph-name>] | ||
|  | 			    ['F' <graph-source>] | ||
|  | 		            ['t' <modified>] | ||
|  | 			    ['M' <md5>] | ||
|  | 			    {<triple>} | ||
|  | 			    'E' | ||
|  | 
 | ||
|  | 	<magic> 	::= "RDF-dump\n" | ||
|  | 	<version> 	::= <integer> | ||
|  | 
 | ||
|  | 	<md5>		::= <byte>* 		(16 bytes digest) | ||
|  | 
 | ||
|  | 	<triple>	::= 'T' | ||
|  | 	                    <subject> | ||
|  | 			    <predicate> | ||
|  | 			    <object> | ||
|  | 			    <graph> | ||
|  | 
 | ||
|  | 	<subject>	::= <resource> | ||
|  | 	<predicate>	::= <resource> | ||
|  | 
 | ||
|  | 	<object>	::= "R" <resource> | ||
|  | 			  | "L" <atom> | ||
|  | 
 | ||
|  | 	<resource>	::= <atom> | ||
|  | 
 | ||
|  | 	<atom>		::= "X" <integer> | ||
|  | 			    "A" <string> | ||
|  | 			    "W" <utf-8 string> | ||
|  | 
 | ||
|  | 	<string>	::= <integer><bytes> | ||
|  | 
 | ||
|  | 	<graph-name>	::= <atom> | ||
|  | 	<graph-source>	::= <atom> | ||
|  | 
 | ||
|  | 	<graph>	::= <graph-file> | ||
|  | 			    <line> | ||
|  | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ | ||
|  | 
 | ||
|  | #define SAVE_MAGIC "RDF-dump\n"
 | ||
|  | #define SAVE_VERSION 2
 | ||
|  | 
 | ||
|  | typedef struct saved | ||
|  | { atom_t name; | ||
|  |   long   as; | ||
|  |   struct saved *next; | ||
|  | } saved; | ||
|  | 
 | ||
|  | 
 | ||
|  | typedef struct save_context | ||
|  | { saved ** saved_table; | ||
|  |   long     saved_size; | ||
|  |   long     saved_id; | ||
|  | } save_context; | ||
|  | 
 | ||
|  | 
 | ||
|  | long | ||
|  | next_table_size(long s0) | ||
|  | { long size = 2; | ||
|  | 
 | ||
|  |   while(size < s0) | ||
|  |     size *= 2; | ||
|  | 
 | ||
|  |   return size; | ||
|  | } | ||
|  | 
 | ||
|  | static void | ||
|  | init_saved(rdf_db *db, save_context *ctx) | ||
|  | { long size = next_table_size((db->created - db->erased)/8); | ||
|  |   long bytes = size * sizeof(*ctx->saved_table); | ||
|  | 
 | ||
|  |   ctx->saved_table = rdf_malloc(db, bytes); | ||
|  |   memset(ctx->saved_table, 0, bytes); | ||
|  |   ctx->saved_size = size; | ||
|  |   ctx->saved_id = 0; | ||
|  | } | ||
|  | 
 | ||
|  | static void | ||
|  | destroy_saved(rdf_db *db, save_context *ctx) | ||
|  | { if ( ctx->saved_table ) | ||
|  |   { saved **s = ctx->saved_table; | ||
|  |     int i; | ||
|  | 
 | ||
|  |     for(i=0; i<ctx->saved_size; i++, s++) | ||
|  |     { saved *c, *n; | ||
|  | 
 | ||
|  |       for(c=*s; c; c = n) | ||
|  |       { n = c->next; | ||
|  | 	free(c); | ||
|  |       } | ||
|  |     } | ||
|  | 
 | ||
|  |     rdf_free(db, ctx->saved_table, ctx->saved_size*sizeof(*ctx->saved_table)); | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | #define INT64BITSIZE (sizeof(int64_t)*8)
 | ||
|  | #define PLMINLONG   ((int64_t)((uint64_t)1<<(INT64BITSIZE-1)))
 | ||
|  | 
 | ||
|  | static void | ||
|  | save_int(IOSTREAM *fd, int64_t n) | ||
|  | { int m; | ||
|  |   int64_t absn = (n >= 0 ? n : -n); | ||
|  | 
 | ||
|  |   if ( n != PLMINLONG ) | ||
|  |   { if ( absn < ((intptr_t)1 << 5) ) | ||
|  |     { Sputc((int)(n & 0x3f), fd); | ||
|  |       return; | ||
|  |     } else if ( absn < ((intptr_t)1 << 13) ) | ||
|  |     { Sputc((int)(((n >> 8) & 0x3f) | (1 << 6)), fd); | ||
|  |       Sputc((int)(n & 0xff), fd); | ||
|  |       return; | ||
|  |     } else if ( absn < ((intptr_t)1 << 21) ) | ||
|  |     { Sputc((int)(((n >> 16) & 0x3f) | (2 << 6)), fd); | ||
|  |       Sputc((int)((n >> 8) & 0xff), fd); | ||
|  |       Sputc((int)(n & 0xff), fd); | ||
|  |       return; | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   for(m = sizeof(n); ; m--) | ||
|  |   { int b = (int)((absn >> (((m-1)*8)-1)) & 0x1ff); | ||
|  | 
 | ||
|  |     if ( b == 0 ) | ||
|  |       continue; | ||
|  |     break; | ||
|  |   } | ||
|  | 
 | ||
|  |   Sputc(m | (3 << 6), fd); | ||
|  | 
 | ||
|  |   for( ; m > 0; m--) | ||
|  |   { int b = (int)((n >> ((m-1)*8)) & 0xff); | ||
|  | 
 | ||
|  |     Sputc(b, fd); | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | #define BYTES_PER_DOUBLE sizeof(double)
 | ||
|  | #ifdef WORDS_BIGENDIAN
 | ||
|  | static const int double_byte_order[] = { 7,6,5,4,3,2,1,0 }; | ||
|  | #else
 | ||
|  | static const int double_byte_order[] = { 0,1,2,3,4,5,6,7 }; | ||
|  | #endif
 | ||
|  | 
 | ||
|  | static int | ||
|  | save_double(IOSTREAM *fd, double f) | ||
|  | { unsigned char *cl = (unsigned char *)&f; | ||
|  |   unsigned int i; | ||
|  | 
 | ||
|  |   for(i=0; i<BYTES_PER_DOUBLE; i++) | ||
|  |     Sputc(cl[double_byte_order[i]], fd); | ||
|  | 
 | ||
|  |   return TRUE; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static int | ||
|  | save_atom(rdf_db *db, IOSTREAM *out, atom_t a, save_context *ctx) | ||
|  | { int hash = atom_hash(a) % ctx->saved_size; | ||
|  |   saved *s; | ||
|  |   size_t len; | ||
|  |   const char *chars; | ||
|  |   unsigned int i; | ||
|  |   const wchar_t *wchars; | ||
|  | 
 | ||
|  |   for(s=ctx->saved_table[hash]; s; s= s->next) | ||
|  |   { if ( s->name == a ) | ||
|  |     { Sputc('X', out); | ||
|  |       save_int(out, s->as); | ||
|  | 
 | ||
|  |       return TRUE; | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   s = rdf_malloc(db, sizeof(*s)); | ||
|  |   s->name = a; | ||
|  |   s->as = ctx->saved_id++; | ||
|  |   s->next = ctx->saved_table[hash]; | ||
|  |   ctx->saved_table[hash] = s; | ||
|  | 
 | ||
|  |   if ( (chars = PL_atom_nchars(a, &len)) ) | ||
|  |   { Sputc('A', out); | ||
|  |     save_int(out, len); | ||
|  |     for(i=0; i<len; i++, chars++) | ||
|  |       Sputc(*chars&0xff, out); | ||
|  |   } else if ( (wchars = PL_atom_wchars(a, &len)) ) | ||
|  |   { IOENC enc = out->encoding; | ||
|  | 
 | ||
|  |     Sputc('W', out); | ||
|  |     save_int(out, len); | ||
|  |     out->encoding = ENC_UTF8; | ||
|  |     for(i=0; i<len; i++, wchars++) | ||
|  |     { wint_t c = *wchars; | ||
|  | 
 | ||
|  |       SECURE(assert(c>=0 && c <= 0x10ffff)); | ||
|  |       Sputcode(c, out); | ||
|  |     } | ||
|  |     out->encoding = enc; | ||
|  |   } else | ||
|  |     return FALSE; | ||
|  | 
 | ||
|  |   return TRUE; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static void | ||
|  | write_triple(rdf_db *db, IOSTREAM *out, triple *t, save_context *ctx) | ||
|  | { Sputc('T', out); | ||
|  | 
 | ||
|  |   save_atom(db, out, t->subject, ctx); | ||
|  |   save_atom(db, out, t->predicate.r->name, ctx); | ||
|  | 
 | ||
|  |   if ( t->object_is_literal ) | ||
|  |   { literal *lit = t->object.literal; | ||
|  | 
 | ||
|  |     if ( lit->qualifier ) | ||
|  |     { assert(lit->type_or_lang); | ||
|  |       Sputc(lit->qualifier == Q_LANG ? 'l' : 't', out); | ||
|  |       save_atom(db, out, lit->type_or_lang, ctx); | ||
|  |     } | ||
|  | 
 | ||
|  |     switch(lit->objtype) | ||
|  |     { case OBJ_STRING: | ||
|  | 	Sputc('L', out); | ||
|  | 	save_atom(db, out, lit->value.string, ctx); | ||
|  | 	break; | ||
|  |       case OBJ_INTEGER: | ||
|  | 	Sputc('I', out); | ||
|  | 	save_int(out, lit->value.integer); | ||
|  | 	break; | ||
|  |       case OBJ_DOUBLE: | ||
|  |       {	Sputc('F', out); | ||
|  | 	save_double(out, lit->value.real); | ||
|  | 	break; | ||
|  |       } | ||
|  |       case OBJ_TERM: | ||
|  |       { const char *s = lit->value.term.record; | ||
|  | 	size_t len = lit->value.term.len; | ||
|  | 
 | ||
|  | 	Sputc('T', out); | ||
|  | 	save_int(out, len); | ||
|  | 	while(len-- > 0) | ||
|  | 	  Sputc(*s++, out); | ||
|  | 
 | ||
|  | 	break; | ||
|  |       } | ||
|  |       default: | ||
|  | 	assert(0); | ||
|  |     } | ||
|  |   } else | ||
|  |   { Sputc('R', out); | ||
|  |     save_atom(db, out, t->object.resource, ctx); | ||
|  |   } | ||
|  | 
 | ||
|  |   save_atom(db, out, t->graph, ctx); | ||
|  |   save_int(out, t->line); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static void | ||
|  | write_source(rdf_db *db, IOSTREAM *out, atom_t src, save_context *ctx) | ||
|  | { graph *s = lookup_graph(db, src, FALSE); | ||
|  | 
 | ||
|  |   if ( s && s->source ) | ||
|  |   { Sputc('F', out); | ||
|  |     save_atom(db, out, s->source, ctx); | ||
|  |     Sputc('t', out); | ||
|  |     save_double(out, s->modified); | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static void | ||
|  | write_md5(rdf_db *db, IOSTREAM *out, atom_t src) | ||
|  | { graph *s = lookup_graph(db, src, FALSE); | ||
|  | 
 | ||
|  |   if ( s ) | ||
|  |   { md5_byte_t *p = s->digest; | ||
|  |     int i; | ||
|  | 
 | ||
|  |     Sputc('M', out); | ||
|  |     for(i=0; i<16; i++) | ||
|  |       Sputc(*p++, out); | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static int | ||
|  | save_db(rdf_db *db, IOSTREAM *out, atom_t src) | ||
|  | { triple *t; | ||
|  |   save_context ctx; | ||
|  | 
 | ||
|  |   if ( !RDLOCK(db) ) | ||
|  |     return FALSE; | ||
|  |   init_saved(db, &ctx); | ||
|  | 
 | ||
|  |   Sfprintf(out, "%s", SAVE_MAGIC); | ||
|  |   save_int(out, SAVE_VERSION); | ||
|  |   if ( src ) | ||
|  |   { Sputc('S', out);			/* start of graph header */ | ||
|  |     save_atom(db, out, src, &ctx); | ||
|  |     write_source(db, out, src, &ctx); | ||
|  |     write_md5(db, out, src); | ||
|  |   } | ||
|  |   if ( Sferror(out) ) | ||
|  |   { RDUNLOCK(db); | ||
|  |     return FALSE; | ||
|  |   } | ||
|  | 
 | ||
|  |   for(t = db->by_none; t; t = t->next[BY_NONE]) | ||
|  |   { if ( !t->erased && | ||
|  | 	 (!src || t->graph == src) ) | ||
|  |     { write_triple(db, out, t, &ctx); | ||
|  |       if ( Sferror(out) ) | ||
|  | 	return FALSE; | ||
|  |     } | ||
|  |   } | ||
|  |   Sputc('E', out); | ||
|  |   if ( Sferror(out) ) | ||
|  |   { RDUNLOCK(db); | ||
|  |     return FALSE; | ||
|  |   } | ||
|  | 
 | ||
|  |   destroy_saved(db, &ctx); | ||
|  |   RDUNLOCK(db); | ||
|  | 
 | ||
|  |   return TRUE; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static foreign_t | ||
|  | rdf_save_db(term_t stream, term_t graph) | ||
|  | { IOSTREAM *out; | ||
|  |   atom_t src; | ||
|  | 
 | ||
|  |   if ( !PL_get_stream_handle(stream, &out) ) | ||
|  |     return type_error(stream, "stream"); | ||
|  |   if ( !get_atom_or_var_ex(graph, &src) ) | ||
|  |     return FALSE; | ||
|  | 
 | ||
|  |   return save_db(DB, out, src); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static int64_t | ||
|  | load_int(IOSTREAM *fd) | ||
|  | { int64_t first = Sgetc(fd); | ||
|  |   int bytes, shift, b; | ||
|  | 
 | ||
|  |   if ( !(first & 0xc0) )		/* 99% of them: speed up a bit */ | ||
|  |   { first <<= (INT64BITSIZE-6); | ||
|  |     first >>= (INT64BITSIZE-6); | ||
|  | 
 | ||
|  |     return first; | ||
|  |   } | ||
|  | 
 | ||
|  |   bytes = (int) ((first >> 6) & 0x3); | ||
|  |   first &= 0x3f; | ||
|  | 
 | ||
|  |   if ( bytes <= 2 ) | ||
|  |   { for( b = 0; b < bytes; b++ ) | ||
|  |     { first <<= 8; | ||
|  |       first |= Sgetc(fd) & 0xff; | ||
|  |     } | ||
|  | 
 | ||
|  |     shift = (sizeof(first)-1-bytes)*8 + 2; | ||
|  |   } else | ||
|  |   { int m; | ||
|  | 
 | ||
|  |     bytes = (int)first; | ||
|  |     first = 0L; | ||
|  | 
 | ||
|  |     for(m=0; m<bytes; m++) | ||
|  |     { first <<= 8; | ||
|  |       first |= Sgetc(fd) & 0xff; | ||
|  |     } | ||
|  |     shift = (sizeof(first)-bytes)*8; | ||
|  |   } | ||
|  | 
 | ||
|  |   first <<= shift; | ||
|  |   first >>= shift; | ||
|  | 
 | ||
|  |   return first; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static int | ||
|  | load_double(IOSTREAM *fd, double *fp) | ||
|  | { double f; | ||
|  |   unsigned char *cl = (unsigned char *)&f; | ||
|  |   unsigned int i; | ||
|  | 
 | ||
|  |   for(i=0; i<BYTES_PER_DOUBLE; i++) | ||
|  |   { int c = Sgetc(fd); | ||
|  | 
 | ||
|  |     if ( c == -1 ) | ||
|  |     { *fp = 0.0; | ||
|  |       return FALSE; | ||
|  |     } | ||
|  |     cl[double_byte_order[i]] = c; | ||
|  |   } | ||
|  | 
 | ||
|  |   *fp = f; | ||
|  |   return TRUE; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | typedef struct ld_context | ||
|  | { long		loaded_id;		/* keep track of atoms */ | ||
|  |   atom_t       *loaded_atoms; | ||
|  |   long		atoms_size; | ||
|  |   atom_t	graph;			/* for single-graph files */ | ||
|  |   atom_t	graph_source; | ||
|  |   double	modified; | ||
|  |   int		has_digest; | ||
|  |   md5_byte_t    digest[16]; | ||
|  |   atom_hash    *graph_table;		/* multi-graph file */ | ||
|  | } ld_context; | ||
|  | 
 | ||
|  | 
 | ||
|  | static void | ||
|  | add_atom(rdf_db *db, atom_t a, ld_context *ctx) | ||
|  | { if ( ctx->loaded_id >= ctx->atoms_size ) | ||
|  |   { if ( ctx->atoms_size == 0 ) | ||
|  |     { ctx->atoms_size = 1024; | ||
|  |       ctx->loaded_atoms = rdf_malloc(db, sizeof(atom_t)*ctx->atoms_size); | ||
|  |     } else | ||
|  |     { long obytes = sizeof(atom_t)*ctx->atoms_size; | ||
|  |       long  bytes; | ||
|  | 
 | ||
|  |       ctx->atoms_size *= 2; | ||
|  |       bytes = sizeof(atom_t)*ctx->atoms_size; | ||
|  |       ctx->loaded_atoms = rdf_realloc(db, ctx->loaded_atoms, obytes, bytes); | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   ctx->loaded_atoms[ctx->loaded_id++] = a; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static atom_t | ||
|  | load_atom(rdf_db *db, IOSTREAM *in, ld_context *ctx) | ||
|  | { switch(Sgetc(in)) | ||
|  |   { case 'X': | ||
|  |     { intptr_t idx = (intptr_t)load_int(in); | ||
|  |       return ctx->loaded_atoms[idx]; | ||
|  |     } | ||
|  |     case 'A': | ||
|  |     { size_t len = (size_t)load_int(in); | ||
|  |       atom_t a; | ||
|  | 
 | ||
|  |       if ( len < 1024 ) | ||
|  |       { char buf[1024]; | ||
|  | 	Sfread(buf, 1, len, in); | ||
|  | 	a = PL_new_atom_nchars(len, buf); | ||
|  |       } else | ||
|  |       { char *buf = rdf_malloc(db, len); | ||
|  | 	Sfread(buf, 1, len, in); | ||
|  | 	a = PL_new_atom_nchars(len, buf); | ||
|  | 	rdf_free(db, buf, len); | ||
|  |       } | ||
|  | 
 | ||
|  |       add_atom(db, a, ctx); | ||
|  |       return a; | ||
|  |     } | ||
|  |     case 'W': | ||
|  |     { int len = (int)load_int(in); | ||
|  |       atom_t a; | ||
|  |       wchar_t buf[1024]; | ||
|  |       wchar_t *w; | ||
|  |       IOENC enc = in->encoding; | ||
|  |       int i; | ||
|  | 
 | ||
|  |       if ( len < 1024 ) | ||
|  | 	w = buf; | ||
|  |       else | ||
|  | 	w = rdf_malloc(db, len*sizeof(wchar_t)); | ||
|  | 
 | ||
|  |       in->encoding = ENC_UTF8; | ||
|  |       for(i=0; i<len; i++) | ||
|  |       { w[i] = Sgetcode(in); | ||
|  | 	SECURE(assert(w[i]>=0 && w[i] <= 0x10ffff)); | ||
|  |       } | ||
|  |       in->encoding = enc; | ||
|  | 
 | ||
|  |       a = PL_new_atom_wchars(len, w); | ||
|  |       if ( w != buf ) | ||
|  | 	rdf_free(db, w, len*sizeof(wchar_t)); | ||
|  | 
 | ||
|  |       add_atom(db, a, ctx); | ||
|  |       return a; | ||
|  |     } | ||
|  |     default: | ||
|  |     { assert(0); | ||
|  |       return 0; | ||
|  |     } | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static triple * | ||
|  | load_triple(rdf_db *db, IOSTREAM *in, ld_context *ctx) | ||
|  | { triple *t = new_triple(db); | ||
|  |   int c; | ||
|  | 
 | ||
|  |   t->subject   = load_atom(db, in, ctx); | ||
|  |   t->predicate.u = load_atom(db, in, ctx); | ||
|  |   t->resolve_pred = TRUE; | ||
|  |   if ( (c=Sgetc(in)) == 'R' ) | ||
|  |   { t->object.resource = load_atom(db, in, ctx); | ||
|  |   } else | ||
|  |   { literal *lit = new_literal(db); | ||
|  | 
 | ||
|  |     t->object_is_literal = TRUE; | ||
|  |     t->object.literal = lit; | ||
|  | 
 | ||
|  |   value: | ||
|  |     switch(c) | ||
|  |     { case 'L': | ||
|  | 	lit->objtype = OBJ_STRING; | ||
|  | 	lit->value.string = load_atom(db, in, ctx); | ||
|  | 	break; | ||
|  |       case 'I': | ||
|  | 	lit->objtype = OBJ_INTEGER; | ||
|  | 	lit->value.integer = load_int(in); | ||
|  | 	break; | ||
|  |       case 'F': | ||
|  | 	lit->objtype = OBJ_DOUBLE; | ||
|  |         load_double(in, &lit->value.real); | ||
|  | 	break; | ||
|  |       case 'T': | ||
|  |       { unsigned int i; | ||
|  | 	char *s; | ||
|  | 
 | ||
|  | 	lit->objtype = OBJ_TERM; | ||
|  | 	lit->value.term.len = (size_t)load_int(in); | ||
|  | 	lit->value.term.record = rdf_malloc(db, lit->value.term.len); | ||
|  | 	lit->term_loaded = TRUE;	/* see free_literal() */ | ||
|  | 	s = (char *)lit->value.term.record; | ||
|  | 
 | ||
|  | 	for(i=0; i<lit->value.term.len; i++) | ||
|  | 	  s[i] = Sgetc(in); | ||
|  | 
 | ||
|  | 	break; | ||
|  |       } | ||
|  |       case 'l': | ||
|  | 	lit->qualifier = Q_LANG; | ||
|  | 	lit->type_or_lang = load_atom(db, in, ctx); | ||
|  | 	c = Sgetc(in); | ||
|  | 	goto value; | ||
|  |       case 't': | ||
|  | 	lit->qualifier = Q_TYPE; | ||
|  | 	lit->type_or_lang = load_atom(db, in, ctx); | ||
|  | 	c = Sgetc(in); | ||
|  | 	goto value; | ||
|  |       default: | ||
|  | 	assert(0); | ||
|  |         return NULL; | ||
|  |     } | ||
|  |   } | ||
|  |   t->graph = load_atom(db, in, ctx); | ||
|  |   t->line  = (unsigned long)load_int(in); | ||
|  |   if ( !ctx->graph ) | ||
|  |   { if ( !ctx->graph_table ) | ||
|  |       ctx->graph_table = new_atom_hash(64); | ||
|  |     add_atom_hash(ctx->graph_table, t->graph); | ||
|  |   } | ||
|  | 
 | ||
|  |   return t; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static int | ||
|  | load_magic(IOSTREAM *in) | ||
|  | { char *s = SAVE_MAGIC; | ||
|  | 
 | ||
|  |   for( ; *s; s++) | ||
|  |   { if ( Sgetc(in) != *s ) | ||
|  |       return FALSE; | ||
|  |   } | ||
|  | 
 | ||
|  |   return TRUE; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | ||
|  | Note that we have two types  of   saved  states.  One holding many named | ||
|  | graphs and one holding the content of exactly one named graph. | ||
|  | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ | ||
|  | 
 | ||
|  | #define LOAD_ERROR ((triple*)(intptr_t)-1)
 | ||
|  | 
 | ||
|  | static triple * | ||
|  | load_db(rdf_db *db, IOSTREAM *in, ld_context *ctx) | ||
|  | { int version; | ||
|  |   int c; | ||
|  |   triple *list = NULL, *tail = NULL; | ||
|  | 
 | ||
|  |   if ( !load_magic(in) ) | ||
|  |     return LOAD_ERROR; | ||
|  |   version = (int)load_int(in); | ||
|  | 
 | ||
|  |   while((c=Sgetc(in)) != EOF) | ||
|  |   { switch(c) | ||
|  |     { case 'T': | ||
|  |       { triple *t; | ||
|  | 
 | ||
|  | 	if ( !(t=load_triple(db, in, ctx)) ) | ||
|  | 	  return FALSE; | ||
|  | 
 | ||
|  | 	if ( tail ) | ||
|  | 	{ tail->next[BY_NONE] = t; | ||
|  | 	  tail = t; | ||
|  | 	} else | ||
|  | 	{ list = tail = t; | ||
|  | 	} | ||
|  | 
 | ||
|  |         break; | ||
|  |       } | ||
|  | 					/* file holding exactly one graph */ | ||
|  |       case 'S':				/* name of the graph */ | ||
|  |       { ctx->graph = load_atom(db, in, ctx); | ||
|  |         break; | ||
|  |       } | ||
|  |       case 'M':				/* MD5 of the graph */ | ||
|  |       { int i; | ||
|  | 
 | ||
|  | 	for(i=0; i<16; i++) | ||
|  | 	  ctx->digest[i] = Sgetc(in); | ||
|  | 	ctx->has_digest = TRUE; | ||
|  | 
 | ||
|  | 	break; | ||
|  |       } | ||
|  |       case 'F':				/* file of the graph */ | ||
|  | 	ctx->graph_source = load_atom(db, in, ctx); | ||
|  | 	break;				/* end of one-graph handling */ | ||
|  |       case 't': | ||
|  | 	load_double(in, &ctx->modified); | ||
|  |         break; | ||
|  |       case 'E':				/* end of file */ | ||
|  | 	return list; | ||
|  |       default: | ||
|  | 	break; | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   PL_warning("Illegal RDF triple file"); | ||
|  | 
 | ||
|  |   return LOAD_ERROR; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static int | ||
|  | link_loaded_triples(rdf_db *db, triple *t, ld_context *ctx) | ||
|  | { long created0 = db->created; | ||
|  |   graph *graph; | ||
|  | 
 | ||
|  |   if ( ctx->graph )			/* lookup named graph */ | ||
|  |   { graph = lookup_graph(db, ctx->graph, TRUE); | ||
|  |     if ( ctx->graph_source && graph->source != ctx->graph_source ) | ||
|  |     { if ( graph->source ) | ||
|  | 	PL_unregister_atom(graph->source); | ||
|  |       graph->source = ctx->graph_source; | ||
|  |       PL_register_atom(graph->source); | ||
|  |       graph->modified = ctx->modified; | ||
|  |     } | ||
|  | 
 | ||
|  |     if ( ctx->has_digest ) | ||
|  |     { if ( graph->md5 ) | ||
|  |       { if ( db->tr_first ) | ||
|  | 	{ record_md5_transaction(db, graph, NULL); | ||
|  | 	} else | ||
|  | 	{ graph->md5 = FALSE;		/* kill repetitive MD5 update */ | ||
|  | 	} | ||
|  |       } else | ||
|  |       { ctx->has_digest = FALSE; | ||
|  |       } | ||
|  |     } | ||
|  |   } else | ||
|  |   { graph = NULL; | ||
|  |   } | ||
|  | 
 | ||
|  | 
 | ||
|  |   if ( db->tr_first )			/* loading in a transaction */ | ||
|  |   { triple *next; | ||
|  | 
 | ||
|  |     for( ; t; t = next ) | ||
|  |     { next = t->next[BY_NONE]; | ||
|  | 
 | ||
|  |       t->next[BY_NONE] = NULL; | ||
|  |       lock_atoms(t); | ||
|  |       record_transaction(db, TR_ASSERT, t); | ||
|  |     } | ||
|  |   } else | ||
|  |   { triple *next; | ||
|  | 
 | ||
|  |     for( ; t; t = next ) | ||
|  |     { next = t->next[BY_NONE]; | ||
|  | 
 | ||
|  |       t->next[BY_NONE] = NULL; | ||
|  |       lock_atoms(t); | ||
|  |       if ( link_triple_silent(db, t) ) | ||
|  | 	broadcast(EV_ASSERT_LOAD, t, NULL); | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  | 					/* update the graph info */ | ||
|  |   if ( ctx->has_digest ) | ||
|  |   { if ( db->tr_first ) | ||
|  |     { md5_byte_t *d = rdf_malloc(db, sizeof(ctx->digest)); | ||
|  |       memcpy(d, ctx->digest, sizeof(ctx->digest)); | ||
|  |       record_md5_transaction(db, graph, d); | ||
|  |     } else | ||
|  |     { sum_digest(graph->digest, ctx->digest); | ||
|  |     } | ||
|  |     graph->md5 = TRUE; | ||
|  |   } | ||
|  | 
 | ||
|  |   db->generation += (db->created-created0); | ||
|  | 
 | ||
|  |   return TRUE; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static int | ||
|  | append_graph_to_list(ptr_hash_node *node, void *closure) | ||
|  | { atom_t graph = (atom_t)node->value; | ||
|  |   term_t tail  = (term_t)closure; | ||
|  |   term_t head  = PL_new_term_ref(); | ||
|  |   int rc; | ||
|  | 
 | ||
|  |   rc = (PL_unify_list(tail, head, tail) && | ||
|  | 	PL_unify_atom(head, graph)); | ||
|  |   PL_reset_term_refs(head); | ||
|  | 
 | ||
|  |   return rc; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static foreign_t | ||
|  | rdf_load_db(term_t stream, term_t id, term_t graphs) | ||
|  | { ld_context ctx; | ||
|  |   rdf_db *db = DB; | ||
|  |   IOSTREAM *in; | ||
|  |   triple *list; | ||
|  |   int rc; | ||
|  | 
 | ||
|  |   if ( !PL_get_stream_handle(stream, &in) ) | ||
|  |     return type_error(stream, "stream"); | ||
|  | 
 | ||
|  |   memset(&ctx, 0, sizeof(ctx)); | ||
|  |   if ( (list=load_db(db, in, &ctx)) == LOAD_ERROR ) | ||
|  |     return FALSE; | ||
|  | 
 | ||
|  |   if ( !WRLOCK(db, FALSE) ) | ||
|  |     return FALSE; | ||
|  |   broadcast(EV_LOAD, (void*)id, (void*)ATOM_begin); | ||
|  | 
 | ||
|  |   if ( (rc=link_loaded_triples(db, list, &ctx)) ) | ||
|  |   { if ( ctx.graph_table ) | ||
|  |     { term_t tail = PL_copy_term_ref(graphs); | ||
|  | 
 | ||
|  |       rc = ( for_atom_hash(ctx.graph_table, append_graph_to_list, (void*)tail) && | ||
|  | 	     PL_unify_nil(tail) ); | ||
|  | 
 | ||
|  |       destroy_atom_hash(ctx.graph_table); | ||
|  |     } else | ||
|  |     { rc = PL_unify_atom(graphs, ctx.graph); | ||
|  |     } | ||
|  |   } | ||
|  |   broadcast(EV_LOAD, (void*)id, (void*)ATOM_end); | ||
|  |   WRUNLOCK(db); | ||
|  | 
 | ||
|  |   PL_release_stream(in); | ||
|  |   if ( ctx.loaded_atoms ) | ||
|  |   { atom_t *ap, *ep; | ||
|  | 
 | ||
|  |     for(ap=ctx.loaded_atoms, ep=ap+ctx.loaded_id; ap<ep; ap++) | ||
|  |       PL_unregister_atom(*ap); | ||
|  | 
 | ||
|  |     rdf_free(db, ctx.loaded_atoms, sizeof(atom_t)*ctx.atoms_size); | ||
|  |   } | ||
|  | 
 | ||
|  |   return rc; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | #ifdef WITH_MD5
 | ||
|  | 		 /*******************************
 | ||
|  | 		 *	     MD5 SUPPORT	* | ||
|  | 		 *******************************/ | ||
|  | 
 | ||
|  | /* md5_type is used to keep the MD5 independent from the internal
 | ||
|  |    numbers | ||
|  | */ | ||
|  | static const char md5_type[] = | ||
|  | { 0x0,					/* OBJ_UNKNOWN */ | ||
|  |   0x3,					/* OBJ_INTEGER */ | ||
|  |   0x4,					/* OBJ_DOUBLE */ | ||
|  |   0x2,					/* OBJ_STRING */ | ||
|  |   0x5					/* OBJ_TERM */ | ||
|  | }; | ||
|  | 
 | ||
|  | static void | ||
|  | md5_triple(triple *t, md5_byte_t *digest) | ||
|  | { md5_state_t state; | ||
|  |   size_t len; | ||
|  |   md5_byte_t tmp[2]; | ||
|  |   const char *s; | ||
|  |   literal *lit; | ||
|  | 
 | ||
|  |   md5_init(&state); | ||
|  |   s = PL_blob_data(t->subject, &len, NULL); | ||
|  |   md5_append(&state, (const md5_byte_t *)s, (int)len); | ||
|  |   md5_append(&state, (const md5_byte_t *)"P", 1); | ||
|  |   s = PL_blob_data(t->predicate.r->name, &len, NULL); | ||
|  |   md5_append(&state, (const md5_byte_t *)s, (int)len); | ||
|  |   tmp[0] = 'O'; | ||
|  |   if ( t->object_is_literal ) | ||
|  |   { lit = t->object.literal; | ||
|  |     tmp[1] = md5_type[lit->objtype]; | ||
|  | 
 | ||
|  |     switch(lit->objtype) | ||
|  |     { case OBJ_STRING: | ||
|  | 	s = PL_blob_data(lit->value.string, &len, NULL); | ||
|  | 	break; | ||
|  |       case OBJ_INTEGER:			/* TBD: byte order issues */ | ||
|  | 	s = (const char *)&lit->value.integer; | ||
|  | 	len = sizeof(lit->value.integer); | ||
|  | 	break; | ||
|  |       case OBJ_DOUBLE: | ||
|  | 	s = (const char *)&lit->value.real; | ||
|  | 	len = sizeof(lit->value.real); | ||
|  | 	break; | ||
|  |       case OBJ_TERM: | ||
|  | 	s = (const char *)lit->value.term.record; | ||
|  | 	len = lit->value.term.len; | ||
|  | 	break; | ||
|  |       default: | ||
|  | 	assert(0); | ||
|  |     } | ||
|  |   } else | ||
|  |   { s = PL_blob_data(t->object.resource, &len, NULL); | ||
|  |     tmp[1] = 0x1;			/* old OBJ_RESOURCE */ | ||
|  |     lit = NULL; | ||
|  |   } | ||
|  |   md5_append(&state, tmp, 2); | ||
|  |   md5_append(&state, (const md5_byte_t *)s, (int)len); | ||
|  |   if ( lit && lit->qualifier ) | ||
|  |   { assert(lit->type_or_lang); | ||
|  |     md5_append(&state, | ||
|  | 	       (const md5_byte_t *)(lit->qualifier == Q_LANG ? "l" : "t"), | ||
|  | 	       1); | ||
|  |     s = PL_blob_data(lit->type_or_lang, &len, NULL); | ||
|  |     md5_append(&state, (const md5_byte_t *)s, (int)len); | ||
|  |   } | ||
|  |   if ( t->graph ) | ||
|  |   { md5_append(&state, (const md5_byte_t *)"S", 1); | ||
|  |     s = PL_blob_data(t->graph, &len, NULL); | ||
|  |     md5_append(&state, (const md5_byte_t *)s, (int)len); | ||
|  |   } | ||
|  | 
 | ||
|  |   md5_finish(&state, digest); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static void | ||
|  | sum_digest(md5_byte_t *digest, md5_byte_t *add) | ||
|  | { md5_byte_t *p, *q; | ||
|  |   int n; | ||
|  | 
 | ||
|  |   for(p=digest, q=add, n=16; --n>=0; ) | ||
|  |     *p++ += *q++; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static void | ||
|  | dec_digest(md5_byte_t *digest, md5_byte_t *add) | ||
|  | { md5_byte_t *p, *q; | ||
|  |   int n; | ||
|  | 
 | ||
|  |   for(p=digest, q=add, n=16; --n>=0; ) | ||
|  |     *p++ -= *q++; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static int | ||
|  | md5_unify_digest(term_t t, md5_byte_t digest[16]) | ||
|  | { char hex_output[16*2]; | ||
|  |   int di; | ||
|  |   char *pi; | ||
|  |   static char hexd[] = "0123456789abcdef"; | ||
|  | 
 | ||
|  |   for(pi=hex_output, di = 0; di < 16; ++di) | ||
|  |   { *pi++ = hexd[(digest[di] >> 4) & 0x0f]; | ||
|  |     *pi++ = hexd[digest[di] & 0x0f]; | ||
|  |   } | ||
|  | 
 | ||
|  |   return PL_unify_atom_nchars(t, 16*2, hex_output); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static foreign_t | ||
|  | rdf_md5(term_t graph_name, term_t md5) | ||
|  | { atom_t src; | ||
|  |   int rc; | ||
|  |   rdf_db *db = DB; | ||
|  | 
 | ||
|  |   if ( !get_atom_or_var_ex(graph_name, &src) ) | ||
|  |     return FALSE; | ||
|  | 
 | ||
|  |   if ( src ) | ||
|  |   { graph *s; | ||
|  | 
 | ||
|  |     if ( !RDLOCK(db) ) | ||
|  |       return FALSE; | ||
|  |     if ( (s = lookup_graph(db, src, FALSE)) ) | ||
|  |     { rc = md5_unify_digest(md5, s->digest); | ||
|  |     } else | ||
|  |     { md5_byte_t digest[16]; | ||
|  | 
 | ||
|  |       memset(digest, 0, sizeof(digest)); | ||
|  |       rc = md5_unify_digest(md5, digest); | ||
|  |     } | ||
|  |     RDUNLOCK(db); | ||
|  |   } else | ||
|  |   { md5_byte_t digest[16]; | ||
|  |     graph **ht; | ||
|  |     int i; | ||
|  | 
 | ||
|  |     memset(&digest, 0, sizeof(digest)); | ||
|  | 
 | ||
|  |     if ( !RDLOCK(db) ) | ||
|  |       return FALSE; | ||
|  | 
 | ||
|  |     for(i=0,ht = db->graph_table; i<db->graph_table_size; i++, ht++) | ||
|  |     { graph *s; | ||
|  | 
 | ||
|  |       for( s = *ht; s; s = s->next ) | ||
|  | 	sum_digest(digest, s->digest); | ||
|  |     } | ||
|  | 
 | ||
|  |     rc = md5_unify_digest(md5, digest); | ||
|  |     RDUNLOCK(db); | ||
|  |   } | ||
|  | 
 | ||
|  |   return rc; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static foreign_t | ||
|  | rdf_atom_md5(term_t text, term_t times, term_t md5) | ||
|  | { char *s; | ||
|  |   int n, i; | ||
|  |   size_t len; | ||
|  |   md5_byte_t digest[16]; | ||
|  | 
 | ||
|  |   if ( !PL_get_nchars(text, &len, &s, CVT_ALL) ) | ||
|  |     return type_error(text, "text"); | ||
|  |   if ( !PL_get_integer(times, &n) ) | ||
|  |     return type_error(times, "integer"); | ||
|  |   if ( n < 1 ) | ||
|  |     return domain_error(times, "positive_integer"); | ||
|  | 
 | ||
|  |   for(i=0; i<n; i++) | ||
|  |   { md5_state_t state; | ||
|  |     md5_init(&state); | ||
|  |     md5_append(&state, (const md5_byte_t *)s, (int)len); | ||
|  |     md5_finish(&state, digest); | ||
|  |     s = (char *)digest; | ||
|  |     len = sizeof(digest); | ||
|  |   } | ||
|  | 
 | ||
|  |   return md5_unify_digest(md5, digest); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | 
 | ||
|  | #endif /*WITH_MD5*/
 | ||
|  | 
 | ||
|  | 
 | ||
|  | 		 /*******************************
 | ||
|  | 		 *	       ATOMS		* | ||
|  | 		 *******************************/ | ||
|  | 
 | ||
|  | /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | ||
|  | Lock atoms in triple against AGC. Note that the predicate name is locked | ||
|  | in the predicate structure. | ||
|  | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ | ||
|  | 
 | ||
|  | static void | ||
|  | lock_atoms(triple *t) | ||
|  | { if ( !t->atoms_locked ) | ||
|  |   { t->atoms_locked = TRUE; | ||
|  | 
 | ||
|  |     PL_register_atom(t->subject); | ||
|  |     if ( t->object_is_literal ) | ||
|  |     { lock_atoms_literal(t->object.literal); | ||
|  |     } else | ||
|  |     { PL_register_atom(t->object.resource); | ||
|  |     } | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static void | ||
|  | unlock_atoms(triple *t) | ||
|  | { if ( t->atoms_locked ) | ||
|  |   { t->atoms_locked = FALSE; | ||
|  | 
 | ||
|  |     PL_unregister_atom(t->subject); | ||
|  |     if ( !t->object_is_literal ) | ||
|  |     { PL_unregister_atom(t->object.resource); | ||
|  |     } | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | 		 /*******************************
 | ||
|  | 		 *      PROLOG CONVERSION	* | ||
|  | 		 *******************************/ | ||
|  | 
 | ||
|  | #define LIT_TYPED	0x1
 | ||
|  | #define LIT_NOERROR	0x2
 | ||
|  | #define LIT_PARTIAL	0x4
 | ||
|  | 
 | ||
|  | static int | ||
|  | get_lit_atom_ex(term_t t, atom_t *a, int flags) | ||
|  | { if ( PL_get_atom(t, a) ) | ||
|  |     return TRUE; | ||
|  |   if ( (flags & LIT_PARTIAL) && PL_is_variable(t) ) | ||
|  |   { *a = 0L; | ||
|  |     return TRUE; | ||
|  |   } | ||
|  | 
 | ||
|  |   return type_error(t, "atom"); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | ||
|  | get_literal() processes the argument  of  a   literal/1  term  passes as | ||
|  | object. | ||
|  | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ | ||
|  | 
 | ||
|  | static int | ||
|  | get_literal(rdf_db *db, term_t litt, triple *t, int flags) | ||
|  | { literal *lit; | ||
|  | 
 | ||
|  |   alloc_literal_triple(db, t); | ||
|  |   lit = t->object.literal; | ||
|  | 
 | ||
|  |   if ( PL_get_atom(litt, &lit->value.string) ) | ||
|  |   { lit->objtype = OBJ_STRING; | ||
|  |   } else if ( PL_is_integer(litt) && PL_get_int64(litt, &lit->value.integer) ) | ||
|  |   { lit->objtype = OBJ_INTEGER; | ||
|  |   } else if ( PL_get_float(litt, &lit->value.real) ) | ||
|  |   { lit->objtype = OBJ_DOUBLE; | ||
|  |   } else if ( PL_is_functor(litt, FUNCTOR_lang2) ) | ||
|  |   { term_t a = PL_new_term_ref(); | ||
|  | 
 | ||
|  |     _PL_get_arg(1, litt, a); | ||
|  |     if ( !get_lit_atom_ex(a, &lit->type_or_lang, flags) ) | ||
|  |       return FALSE; | ||
|  |     _PL_get_arg(2, litt, a); | ||
|  |     if ( !get_lit_atom_ex(a, &lit->value.string, flags) ) | ||
|  |       return FALSE; | ||
|  | 
 | ||
|  |     lit->qualifier = Q_LANG; | ||
|  |     lit->objtype = OBJ_STRING; | ||
|  |   } else if ( PL_is_functor(litt, FUNCTOR_type2) && | ||
|  | 	      !(flags & LIT_TYPED) )	/* avoid recursion */ | ||
|  |   { term_t a = PL_new_term_ref(); | ||
|  | 
 | ||
|  |     _PL_get_arg(1, litt, a); | ||
|  |     if ( !get_lit_atom_ex(a, &lit->type_or_lang, flags) ) | ||
|  |       return FALSE; | ||
|  |     lit->qualifier = Q_TYPE; | ||
|  |     _PL_get_arg(2, litt, a); | ||
|  | 
 | ||
|  |     return get_literal(db, a, t, LIT_TYPED|flags); | ||
|  |   } else if ( !PL_is_ground(litt) ) | ||
|  |   { if ( !(flags & LIT_PARTIAL) ) | ||
|  |       return type_error(litt, "rdf_object"); | ||
|  |     if ( !PL_is_variable(litt) ) | ||
|  |       lit->objtype = OBJ_TERM; | ||
|  |   } else | ||
|  |   { lit->value.term.record = PL_record_external(litt, &lit->value.term.len); | ||
|  |     lit->objtype = OBJ_TERM; | ||
|  |   } | ||
|  | 
 | ||
|  |   return TRUE; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static int | ||
|  | get_object(rdf_db *db, term_t object, triple *t) | ||
|  | { if ( PL_get_atom(object, &t->object.resource) ) | ||
|  |   { assert(!t->object_is_literal); | ||
|  |   } else if ( PL_is_functor(object, FUNCTOR_literal1) ) | ||
|  |   { term_t a = PL_new_term_ref(); | ||
|  | 
 | ||
|  |     _PL_get_arg(1, object, a); | ||
|  |     return get_literal(db, a, t, 0); | ||
|  |   } else | ||
|  |     return type_error(object, "rdf_object"); | ||
|  | 
 | ||
|  |   return TRUE; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static int | ||
|  | get_src(term_t src, triple *t) | ||
|  | { if ( src && !PL_is_variable(src) ) | ||
|  |   { if ( PL_get_atom(src, &t->graph) ) | ||
|  |     { t->line = NO_LINE; | ||
|  |     } else if ( PL_is_functor(src, FUNCTOR_colon2) ) | ||
|  |     { term_t a = PL_new_term_ref(); | ||
|  |       long line; | ||
|  | 
 | ||
|  |       _PL_get_arg(1, src, a); | ||
|  |       if ( !get_atom_or_var_ex(a, &t->graph) ) | ||
|  | 	return FALSE; | ||
|  |       _PL_get_arg(2, src, a); | ||
|  |       if ( PL_get_long(a, &line) ) | ||
|  | 	t->line = line; | ||
|  |       else if ( !PL_is_variable(a) ) | ||
|  | 	return type_error(a, "integer"); | ||
|  |     } else | ||
|  |       return type_error(src, "rdf_graph"); | ||
|  |   } | ||
|  | 
 | ||
|  |   return TRUE; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | ||
|  | Return values: | ||
|  | 	-1: exception | ||
|  | 	 0: no predicate | ||
|  | 	 1: the predicate | ||
|  | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ | ||
|  | 
 | ||
|  | static int | ||
|  | get_existing_predicate(rdf_db *db, term_t t, predicate **p) | ||
|  | { atom_t name; | ||
|  | 
 | ||
|  |   if ( !PL_get_atom(t, &name ) ) | ||
|  |   { if ( PL_is_functor(t, FUNCTOR_literal1) ) | ||
|  |       return 0;				/* rdf(_, literal(_), _) */ | ||
|  |     return type_error(t, "atom"); | ||
|  |   } | ||
|  | 
 | ||
|  |   if ( (*p = existing_predicate(db, name)) ) | ||
|  |     return 1; | ||
|  | 
 | ||
|  |   DEBUG(5, Sdprintf("No predicate %s\n", PL_atom_chars(name))); | ||
|  |   return 0;				/* no predicate */ | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static int | ||
|  | get_predicate(rdf_db *db, term_t t, predicate **p) | ||
|  | { atom_t name; | ||
|  | 
 | ||
|  |   if ( !get_atom_ex(t, &name ) ) | ||
|  |     return FALSE; | ||
|  | 
 | ||
|  |   *p = lookup_predicate(db, name); | ||
|  |   return TRUE; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static int | ||
|  | get_triple(rdf_db *db, | ||
|  | 	   term_t subject, term_t predicate, term_t object, | ||
|  | 	   triple *t) | ||
|  | { if ( !get_atom_ex(subject, &t->subject) || | ||
|  |        !get_predicate(db, predicate, &t->predicate.r) || | ||
|  |        !get_object(db, object, t) ) | ||
|  |     return FALSE; | ||
|  | 
 | ||
|  |   return TRUE; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | ||
|  | get_partial_triple() creates a triple  for   matching  purposes.  It can | ||
|  | return FALSE for  two  reasons.  Mostly   (type)  errors,  but  also  if | ||
|  | resources are accessed that do not   exist  and therefore the subsequent | ||
|  | matching will always fail. This  is   notably  the  case for predicates, | ||
|  | which are first class citizens to this library. | ||
|  | 
 | ||
|  | Return values: | ||
|  | 	1: ok | ||
|  | 	0: no predicate | ||
|  |        -1: error | ||
|  | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ | ||
|  | 
 | ||
|  | static int | ||
|  | get_partial_triple(rdf_db *db, | ||
|  | 		   term_t subject, term_t predicate, term_t object, | ||
|  | 		   term_t src, triple *t) | ||
|  | { int rc; | ||
|  | 
 | ||
|  |   if ( subject && !get_resource_or_var_ex(subject, &t->subject) ) | ||
|  |     return FALSE; | ||
|  |   if ( !PL_is_variable(predicate) && | ||
|  |        (rc=get_existing_predicate(db, predicate, &t->predicate.r)) != 1 ) | ||
|  |     return rc; | ||
|  | 					/* the object */ | ||
|  |   if ( object && !PL_is_variable(object) ) | ||
|  |   { if ( PL_get_atom(object, &t->object.resource) ) | ||
|  |     { assert(!t->object_is_literal); | ||
|  |     } else if ( PL_is_functor(object, FUNCTOR_literal1) ) | ||
|  |     { term_t a = PL_new_term_ref(); | ||
|  | 
 | ||
|  |       _PL_get_arg(1, object, a); | ||
|  |       if ( !get_literal(db, a, t, LIT_PARTIAL) ) | ||
|  | 	return FALSE; | ||
|  |     } else if ( PL_is_functor(object, FUNCTOR_literal2) ) | ||
|  |     { term_t a = PL_new_term_ref(); | ||
|  |       literal *lit; | ||
|  | 
 | ||
|  |       alloc_literal_triple(db, t); | ||
|  |       lit = t->object.literal; | ||
|  | 
 | ||
|  |       _PL_get_arg(1, object, a); | ||
|  |       if ( PL_is_functor(a, FUNCTOR_exact1) ) | ||
|  | 	t->match = STR_MATCH_EXACT; | ||
|  |       else if ( PL_is_functor(a, FUNCTOR_plain1) ) | ||
|  | 	t->match = STR_MATCH_PLAIN; | ||
|  |       else if ( PL_is_functor(a, FUNCTOR_substring1) ) | ||
|  | 	t->match = STR_MATCH_SUBSTRING; | ||
|  |       else if ( PL_is_functor(a, FUNCTOR_word1) ) | ||
|  | 	t->match = STR_MATCH_WORD; | ||
|  |       else if ( PL_is_functor(a, FUNCTOR_prefix1) ) | ||
|  | 	t->match = STR_MATCH_PREFIX; | ||
|  |       else if ( PL_is_functor(a, FUNCTOR_like1) ) | ||
|  | 	t->match = STR_MATCH_LIKE; | ||
|  |       else | ||
|  | 	return domain_error(a, "match_type"); | ||
|  | 
 | ||
|  |       _PL_get_arg(1, a, a); | ||
|  |       if ( !get_atom_or_var_ex(a, &lit->value.string) ) | ||
|  | 	return FALSE; | ||
|  |       lit->objtype = OBJ_STRING; | ||
|  |     } else | ||
|  |       return type_error(object, "rdf_object"); | ||
|  |   } | ||
|  | 					/* the graph */ | ||
|  |   if ( !get_src(src, t) ) | ||
|  |     return FALSE; | ||
|  | 
 | ||
|  |   if ( t->subject ) | ||
|  |     t->indexed |= BY_S; | ||
|  |   if ( t->predicate.r ) | ||
|  |     t->indexed |= BY_P; | ||
|  |   if ( t->object_is_literal ) | ||
|  |   { literal *lit = t->object.literal; | ||
|  | 
 | ||
|  |     if ( lit->objtype == OBJ_STRING && | ||
|  | 	 lit->value.string && | ||
|  | 	 t->match <= STR_MATCH_EXACT ) | ||
|  |       t->indexed |= BY_O; | ||
|  |   } else if ( t->object.resource ) | ||
|  |     t->indexed |= BY_O; | ||
|  | 
 | ||
|  |   db->indexed[t->indexed]++;		/* statistics */ | ||
|  | 
 | ||
|  |   switch(t->indexed) | ||
|  |   { case BY_SPO: | ||
|  |       t->indexed = BY_SP; | ||
|  |       break; | ||
|  |     case BY_SO: | ||
|  |       t->indexed = BY_S; | ||
|  |       break; | ||
|  |   } | ||
|  | 
 | ||
|  |   return TRUE; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static int | ||
|  | inverse_partial_triple(triple *t) | ||
|  | { predicate *i = 0; | ||
|  | 
 | ||
|  |   if ( !t->inversed && | ||
|  |        (!t->predicate.r || (i=t->predicate.r->inverse_of)) && | ||
|  |        !t->object_is_literal ) | ||
|  |   { atom_t o = t->object.resource; | ||
|  | 
 | ||
|  |     t->object.resource = t->subject; | ||
|  |     t->subject = o; | ||
|  | 
 | ||
|  |     if ( t->predicate.r ) | ||
|  |       t->predicate.r = i; | ||
|  | 
 | ||
|  |     t->indexed  = by_inverse[t->indexed]; | ||
|  |     t->inversed = TRUE; | ||
|  | 
 | ||
|  |     return TRUE; | ||
|  |   } | ||
|  | 
 | ||
|  |   return FALSE; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static int | ||
|  | get_graph(term_t src, triple *t) | ||
|  | { if ( PL_get_atom(src, &t->graph) ) | ||
|  |   { t->line = NO_LINE; | ||
|  |     return TRUE; | ||
|  |   } | ||
|  | 
 | ||
|  |   if ( PL_is_functor(src, FUNCTOR_colon2) ) | ||
|  |   { term_t a = PL_new_term_ref(); | ||
|  |     long line; | ||
|  | 
 | ||
|  |     _PL_get_arg(1, src, a); | ||
|  |     if ( !get_atom_ex(a, &t->graph) ) | ||
|  |       return FALSE; | ||
|  |     _PL_get_arg(2, src, a); | ||
|  |     if ( !get_long_ex(a, &line) ) | ||
|  |       return FALSE; | ||
|  |     t->line = line; | ||
|  | 
 | ||
|  |     return TRUE; | ||
|  |   } | ||
|  | 
 | ||
|  |   return type_error(src, "rdf_graph"); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static int | ||
|  | unify_graph(term_t src, triple *t) | ||
|  | { switch( PL_term_type(src) ) | ||
|  |   { case PL_VARIABLE: | ||
|  |     { if ( t->line == NO_LINE ) | ||
|  | 	return PL_unify_atom(src, t->graph); | ||
|  |       else | ||
|  | 	goto full_term; | ||
|  |     } | ||
|  |     case PL_ATOM: | ||
|  |     { atom_t a; | ||
|  |       return (PL_get_atom(src, &a) && | ||
|  | 	      a == t->graph); | ||
|  |     } | ||
|  |     case PL_TERM: | ||
|  |     { if ( t->line == NO_LINE ) | ||
|  |       { return PL_unify_term(src, | ||
|  | 			     PL_FUNCTOR, FUNCTOR_colon2, | ||
|  | 			       PL_ATOM, t->graph, | ||
|  | 			       PL_VARIABLE); | ||
|  |       } else | ||
|  |       { full_term: | ||
|  | 	return PL_unify_term(src, | ||
|  | 			     PL_FUNCTOR, FUNCTOR_colon2, | ||
|  | 			       PL_ATOM, t->graph, | ||
|  | 			       PL_LONG, t->line); | ||
|  |       } | ||
|  |     } | ||
|  |     default: | ||
|  |       return type_error(src, "rdf_graph"); | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static int | ||
|  | same_graph(triple *t1, triple *t2) | ||
|  | { return t1->line  == t2->line && | ||
|  |          t1->graph == t2->graph; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | 
 | ||
|  | static int | ||
|  | put_literal_value(term_t v, literal *lit) | ||
|  | { switch(lit->objtype) | ||
|  |   { case OBJ_STRING: | ||
|  |       PL_put_atom(v, lit->value.string); | ||
|  |       break; | ||
|  |     case OBJ_INTEGER: | ||
|  |       PL_put_variable(v); | ||
|  |       return PL_unify_int64(v, lit->value.integer); | ||
|  |     case OBJ_DOUBLE: | ||
|  |       return PL_put_float(v, lit->value.real); | ||
|  |     case OBJ_TERM: | ||
|  |       return PL_recorded_external(lit->value.term.record, v); | ||
|  |     default: | ||
|  |       assert(0); | ||
|  |       return FALSE; | ||
|  |   } | ||
|  | 
 | ||
|  |   return TRUE; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static int | ||
|  | unify_literal(term_t lit, literal *l) | ||
|  | { term_t v = PL_new_term_ref(); | ||
|  | 
 | ||
|  |   if ( !put_literal_value(v, l) ) | ||
|  |     return FALSE; | ||
|  | 
 | ||
|  |   if ( l->qualifier ) | ||
|  |   { functor_t qf; | ||
|  | 
 | ||
|  |     assert(l->type_or_lang); | ||
|  | 
 | ||
|  |     if ( l->qualifier == Q_LANG ) | ||
|  |       qf = FUNCTOR_lang2; | ||
|  |     else | ||
|  |       qf = FUNCTOR_type2; | ||
|  | 
 | ||
|  |     if ( PL_unify_term(lit, PL_FUNCTOR, qf, | ||
|  | 			 PL_ATOM, l->type_or_lang, | ||
|  | 			 PL_TERM, v) ) | ||
|  |       return TRUE; | ||
|  | 
 | ||
|  |     return PL_unify(lit, v);		/* allow rdf(X, Y, literal(foo)) */ | ||
|  |   } else if ( PL_unify(lit, v) ) | ||
|  |   { return TRUE; | ||
|  |   } else if ( PL_is_functor(lit, FUNCTOR_lang2) && | ||
|  | 	      l->objtype == OBJ_STRING ) | ||
|  |   { term_t a = PL_new_term_ref(); | ||
|  |     _PL_get_arg(2, lit, a); | ||
|  |     return PL_unify(a, v); | ||
|  |   } else if ( PL_is_functor(lit, FUNCTOR_type2) ) | ||
|  |   { term_t a = PL_new_term_ref(); | ||
|  |     _PL_get_arg(2, lit, a); | ||
|  |     return PL_unify(a, v); | ||
|  |   } else | ||
|  |     return FALSE; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | 
 | ||
|  | static int | ||
|  | unify_object(term_t object, triple *t) | ||
|  | { if ( t->object_is_literal ) | ||
|  |   { term_t lit = PL_new_term_ref(); | ||
|  | 
 | ||
|  |     if ( PL_unify_functor(object, FUNCTOR_literal1) ) | ||
|  |       _PL_get_arg(1, object, lit); | ||
|  |     else if ( PL_is_functor(object, FUNCTOR_literal2) ) | ||
|  |       _PL_get_arg(2, object, lit); | ||
|  |     else | ||
|  |       return FALSE; | ||
|  | 
 | ||
|  |     return unify_literal(lit, t->object.literal); | ||
|  |   } else | ||
|  |   { return PL_unify_atom(object, t->object.resource); | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static int | ||
|  | unify_triple(term_t subject, term_t pred, term_t object, | ||
|  | 	     term_t src, triple *t, int inversed) | ||
|  | { predicate *p = t->predicate.r; | ||
|  |   fid_t fid; | ||
|  | 
 | ||
|  |   if ( inversed ) | ||
|  |   { term_t tmp = object; | ||
|  |     object = subject; | ||
|  |     subject = tmp; | ||
|  | 
 | ||
|  |     if ( !(p = p->inverse_of) ) | ||
|  |       return FALSE; | ||
|  |   } | ||
|  | 
 | ||
|  |   fid = PL_open_foreign_frame(); | ||
|  | 
 | ||
|  |   if ( !PL_unify_atom(subject, t->subject) || | ||
|  |        !PL_unify_atom(pred, p->name) || | ||
|  |        !unify_object(object, t) || | ||
|  |        (src && !unify_graph(src, t)) ) | ||
|  |   { PL_discard_foreign_frame(fid); | ||
|  |     return FALSE; | ||
|  |   } else | ||
|  |   { PL_close_foreign_frame(fid); | ||
|  |     return TRUE; | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | 		 /*******************************
 | ||
|  | 		 *	DUBLICATE HANDLING	* | ||
|  | 		 *******************************/ | ||
|  | 
 | ||
|  | /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | ||
|  | According to the RDF specs, duplicate triples  have no meaning, but they | ||
|  | slow down search and often produce   duplicate results in search. Worse, | ||
|  | some coding styles proposed in the  OWL documents introduce huge amounts | ||
|  | of duplicate triples. We cannot  simply  ignore   a  triple  if  it is a | ||
|  | duplicate as a subsequent retract  would   delete  the final triple. For | ||
|  | example, after loading two  files  that   contain  the  same  triple and | ||
|  | unloading one of these files the database would be left without triples. | ||
|  | 
 | ||
|  | In our solution, if a triple is added as a duplicate, it is flagged such | ||
|  | using  the  flag  is_duplicate.  The  `principal'  triple  has  a  count | ||
|  | `duplicates',  indicating  the  number  of   duplicate  triples  in  the | ||
|  | database. | ||
|  | 
 | ||
|  | It might make sense to  introduce  the   BY_SPO  table  as fully indexed | ||
|  | lookups are frequent with the introduction of duplicate detection. | ||
|  | 
 | ||
|  | (*) Iff too many triples are  added,  it   may  be  time  to enlarge the | ||
|  | hashtable. Note that we do not call  update_hash() blindly as this would | ||
|  | cause each triple that  modifies  the   predicate  hierarchy  to force a | ||
|  | rehash. As we are not searching using subPropertyOf semantics during the | ||
|  | duplicate update, there is no point updating. If it is incorrect it will | ||
|  | be updated on the first real query. | ||
|  | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ | ||
|  | 
 | ||
|  | 
 | ||
|  | static int | ||
|  | update_duplicates_add(rdf_db *db, triple *t) | ||
|  | { triple *d; | ||
|  |   const int indexed = BY_SP; | ||
|  | 
 | ||
|  |   assert(t->is_duplicate == FALSE); | ||
|  |   assert(t->duplicates == 0); | ||
|  | 
 | ||
|  |   if ( WANT_GC(db) )			/* (*) See above */ | ||
|  |     update_hash(db); | ||
|  |   d = db->table[indexed][triple_hash(db, t, indexed)]; | ||
|  |   for( ; d && d != t; d = d->next[indexed] ) | ||
|  |   { if ( match_triples(d, t, MATCH_DUPLICATE) ) | ||
|  |     { t->is_duplicate = TRUE; | ||
|  |       assert( !d->is_duplicate ); | ||
|  | 
 | ||
|  |       d->duplicates++; | ||
|  | 
 | ||
|  |       DEBUG(2, | ||
|  | 	    print_triple(t, PRT_SRC); | ||
|  | 	    Sdprintf(" %p: %d-th duplicate: ", t, d->duplicates); | ||
|  | 	    Sdprintf("Principal: %p at", d); | ||
|  | 	    print_src(d); | ||
|  | 	    Sdprintf("\n")); | ||
|  | 
 | ||
|  |       assert(d->duplicates);		/* check overflow */ | ||
|  |       db->duplicates++; | ||
|  |       return TRUE; | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   return FALSE; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static void				/* t is about to be deleted */ | ||
|  | update_duplicates_del(rdf_db *db, triple *t) | ||
|  | { const int indexed = BY_SP; | ||
|  | 
 | ||
|  |   if ( t->duplicates )			/* I am the principal one */ | ||
|  |   { triple *d; | ||
|  | 
 | ||
|  |     DEBUG(2, | ||
|  | 	  print_triple(t, PRT_SRC); | ||
|  | 	  Sdprintf(": DEL principal %p, %d duplicates: ", t, t->duplicates)); | ||
|  | 
 | ||
|  |     db->duplicates--; | ||
|  |     d = db->table[indexed][triple_hash(db, t, indexed)]; | ||
|  |     for( ; d; d = d->next[indexed] ) | ||
|  |     { if ( d != t && match_triples(d, t, MATCH_DUPLICATE) ) | ||
|  |       { assert(d->is_duplicate); | ||
|  | 	d->is_duplicate = FALSE; | ||
|  | 	d->duplicates = t->duplicates-1; | ||
|  | 	DEBUG(2, | ||
|  | 	      Sdprintf("New principal: %p at", d); | ||
|  | 	      print_src(d); | ||
|  | 	      Sdprintf("\n")); | ||
|  | 
 | ||
|  | 	return; | ||
|  |       } | ||
|  |     } | ||
|  |     assert(0); | ||
|  |   } else if ( t->is_duplicate )		/* I am a duplicate */ | ||
|  |   { triple *d; | ||
|  | 
 | ||
|  |     DEBUG(2, | ||
|  | 	  print_triple(t, PRT_SRC); | ||
|  | 	  Sdprintf(": DEL: is a duplicate: ")); | ||
|  | 
 | ||
|  |     db->duplicates--; | ||
|  |     d = db->table[indexed][triple_hash(db, t, indexed)]; | ||
|  |     for( ; d; d = d->next[indexed] ) | ||
|  |     { if ( d != t && match_triples(d, t, MATCH_DUPLICATE) ) | ||
|  |       { if ( d->duplicates ) | ||
|  | 	{ d->duplicates--; | ||
|  | 	  DEBUG(2, | ||
|  | 		Sdprintf("Principal %p at ", d); | ||
|  | 		print_src(d); | ||
|  | 		Sdprintf(" has %d duplicates\n", d->duplicates)); | ||
|  | 	  return; | ||
|  | 	} | ||
|  |       } | ||
|  |     } | ||
|  |     Sdprintf("FATAL\n"); | ||
|  |     PL_halt(1); | ||
|  |     assert(0); | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | 		 /*******************************
 | ||
|  | 		 *	    TRANSACTIONS	* | ||
|  | 		 *******************************/ | ||
|  | 
 | ||
|  | static void | ||
|  | append_transaction(rdf_db *db, transaction_record *tr) | ||
|  | { if ( db->tr_last ) | ||
|  |   { tr->next = NULL; | ||
|  |     tr->previous = db->tr_last; | ||
|  |     db->tr_last->next = tr; | ||
|  |     db->tr_last = tr; | ||
|  |   } else | ||
|  |   { tr->next = tr->previous = NULL; | ||
|  |     db->tr_first = db->tr_last = tr; | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static void | ||
|  | open_transaction(rdf_db *db) | ||
|  | { transaction_record *tr = rdf_malloc(db, sizeof(*tr)); | ||
|  | 
 | ||
|  |   memset(tr, 0, sizeof(*tr)); | ||
|  |   tr->type = TR_MARK; | ||
|  | 
 | ||
|  |   if ( db->tr_first ) | ||
|  |     db->tr_nesting++; | ||
|  |   else | ||
|  |     db->tr_nesting = 0; | ||
|  | 
 | ||
|  |   append_transaction(db, tr); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static void | ||
|  | record_transaction(rdf_db *db, tr_type type, triple *t) | ||
|  | { transaction_record *tr = rdf_malloc(db, sizeof(*tr)); | ||
|  | 
 | ||
|  |   memset(tr, 0, sizeof(*tr)); | ||
|  |   tr->type = type; | ||
|  |   tr->triple = t; | ||
|  | 
 | ||
|  |   append_transaction(db, tr); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static void | ||
|  | record_md5_transaction(rdf_db *db, graph *src, md5_byte_t *digest) | ||
|  | { transaction_record *tr = rdf_malloc(db, sizeof(*tr)); | ||
|  | 
 | ||
|  |   memset(tr, 0, sizeof(*tr)); | ||
|  |   tr->type = TR_UPDATE_MD5, | ||
|  |   tr->update.md5.graph = src; | ||
|  |   tr->update.md5.digest = digest; | ||
|  | 
 | ||
|  |   append_transaction(db, tr); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static void | ||
|  | record_update_transaction(rdf_db *db, triple *t, triple *new) | ||
|  | { transaction_record *tr = rdf_malloc(db, sizeof(*tr)); | ||
|  | 
 | ||
|  |   memset(tr, 0, sizeof(*tr)); | ||
|  |   tr->type = TR_UPDATE, | ||
|  |   tr->triple = t; | ||
|  |   tr->update.triple = new; | ||
|  | 
 | ||
|  |   append_transaction(db, tr); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static void | ||
|  | record_update_src_transaction(rdf_db *db, triple *t, | ||
|  | 			      atom_t src, unsigned long line) | ||
|  | { transaction_record *tr = rdf_malloc(db, sizeof(*tr)); | ||
|  | 
 | ||
|  |   memset(tr, 0, sizeof(*tr)); | ||
|  |   tr->type = TR_UPDATE_SRC, | ||
|  |   tr->triple = t; | ||
|  |   tr->update.src.atom = src; | ||
|  |   tr->update.src.line = line; | ||
|  | 
 | ||
|  |   append_transaction(db, tr); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static void | ||
|  | void_transaction(rdf_db *db, transaction_record *tr) | ||
|  | { switch(tr->type) | ||
|  |   { case TR_ASSERT: | ||
|  |       free_triple(db, tr->triple); | ||
|  |       break; | ||
|  |     case TR_UPDATE: | ||
|  |       free_triple(db, tr->update.triple); | ||
|  |       break; | ||
|  |     case TR_UPDATE_MD5: | ||
|  |       if ( tr->update.md5.digest ) | ||
|  | 	rdf_free(db, tr->update.md5.digest, sizeof(*tr->update.md5.digest)); | ||
|  |       break; | ||
|  |     default: | ||
|  |       break; | ||
|  |   } | ||
|  | 
 | ||
|  |   tr->type = TR_VOID; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static void | ||
|  | free_transaction(rdf_db *db, transaction_record *tr) | ||
|  | { void_transaction(db, tr); | ||
|  | 
 | ||
|  |   rdf_free(db, tr, sizeof(*tr)); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | ||
|  | This must deal  with  multiple  operations   on  the  same  triple. Most | ||
|  | probably the most important thing is to   merge  update records. We must | ||
|  | also make-up our mind with regard to  updated records that are erased or | ||
|  | records that are erased after updating, etc. | ||
|  | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ | ||
|  | 
 | ||
|  | static void | ||
|  | clean_transaction(rdf_db *db, transaction_record *tr0) | ||
|  | { | ||
|  | #if 0
 | ||
|  |   transaction_record *tr; | ||
|  | 
 | ||
|  |   for(tr=tr0; tr; tr=tr->next) | ||
|  |   { if ( TR_RETRACT ) | ||
|  |     { transaction_record *tr2; | ||
|  | 
 | ||
|  |       for(tr2=tr->next; tr2; tr2=tr2->next) | ||
|  |       { if ( tr2->triple == tr->triple ) | ||
|  | 	{ switch(tr2->type) | ||
|  | 	  { case TR_RETRACT: | ||
|  | 	    case TR_UPDATE: | ||
|  | 	    case TR_UPDATE_SRC: | ||
|  | 	      void_transaction(db, tr2); | ||
|  | 	    default: | ||
|  | 	      ; | ||
|  | 	  } | ||
|  | 	} | ||
|  |       } | ||
|  |     } | ||
|  |   } | ||
|  | #endif
 | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static void | ||
|  | truncate_transaction(rdf_db *db, transaction_record *last) | ||
|  | { db->tr_last = last; | ||
|  |   if ( last ) | ||
|  |   { db->tr_last->next = NULL; | ||
|  |   } else | ||
|  |   { db->tr_first = NULL; | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | ||
|  | discard_transaction()  simply  destroys  all   actions    in   the  last | ||
|  | transaction. | ||
|  | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ | ||
|  | 
 | ||
|  | static void | ||
|  | discard_transaction(rdf_db *db) | ||
|  | { transaction_record *tr, *prev; | ||
|  | 
 | ||
|  |   for(tr=db->tr_last; tr; tr = prev) | ||
|  |   { prev = tr->previous; | ||
|  | 
 | ||
|  |     if ( tr->type == TR_SUB_END ) | ||
|  |     { if ( tr->update.transaction_id ) | ||
|  | 	PL_erase(tr->update.transaction_id); | ||
|  |     } | ||
|  | 
 | ||
|  |     if ( tr->type == TR_MARK ) | ||
|  |     { rdf_free(db, tr, sizeof(*tr)); | ||
|  |       truncate_transaction(db, prev); | ||
|  |       db->tr_nesting--; | ||
|  |       return; | ||
|  |     } | ||
|  | 
 | ||
|  |     free_transaction(db, tr); | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | int | ||
|  | put_begin_end(term_t t, functor_t be, int level) | ||
|  | { term_t av; | ||
|  | 
 | ||
|  |   return ( (av = PL_new_term_ref()) && | ||
|  | 	   PL_put_integer(av, level) && | ||
|  | 	   PL_cons_functor_v(t, be, av) ); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | 
 | ||
|  | 
 | ||
|  | /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | ||
|  | Note  (*)  rdf-monitors  can  modify  the    database   by  opening  new | ||
|  | transactions. Therefore we first close the  transaction to allow opening | ||
|  | new ones. TBD: get  this  clear.   Monitors  have  only  restricted read | ||
|  | access? | ||
|  | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ | ||
|  | 
 | ||
|  | static int | ||
|  | commit_transaction_int(rdf_db *db, term_t id) | ||
|  | { transaction_record *tr, *next; | ||
|  |   int tr_level = 0;			/* nesting level */ | ||
|  | 
 | ||
|  |   if ( db->tr_nesting > 0 )		/* commit nested transaction */ | ||
|  |   { tr=db->tr_last; | ||
|  | 
 | ||
|  |     if ( tr->type == TR_MARK )		/* empty nested transaction */ | ||
|  |     { truncate_transaction(db, tr->previous); | ||
|  |       rdf_free(db, tr, sizeof(*tr)); | ||
|  |       db->tr_nesting--; | ||
|  | 
 | ||
|  |       return TRUE; | ||
|  |     } | ||
|  | 
 | ||
|  |     for(; tr; tr = tr->previous)	/* not the last (tested above) */ | ||
|  |     {					/* not the first (we are nested) */ | ||
|  |       if ( tr->type == TR_MARK ) | ||
|  |       { transaction_record *end = rdf_malloc(db, sizeof(*end)); | ||
|  | 
 | ||
|  | 	memset(end, 0, sizeof(*end)); | ||
|  | 	end->type = TR_SUB_END; | ||
|  | 	end->update.transaction_id = PL_record(id); | ||
|  | 	append_transaction(db, end); | ||
|  | 
 | ||
|  | 	tr->type = TR_SUB_START; | ||
|  | 	tr->update.transaction_id = end->update.transaction_id; | ||
|  | 	db->tr_nesting--; | ||
|  | 
 | ||
|  | 	return TRUE; | ||
|  |       } | ||
|  |     } | ||
|  | 
 | ||
|  |     assert(0); | ||
|  |     return FALSE; | ||
|  |   } | ||
|  | 
 | ||
|  |   while( (tr=db->tr_first) )		/* See above (*) */ | ||
|  |   { db->tr_first = db->tr_last = NULL; | ||
|  | 
 | ||
|  |     clean_transaction(db, tr); | ||
|  | 					/* real commit */ | ||
|  |     for(; tr; tr = next) | ||
|  |     { next = tr->next; | ||
|  | 
 | ||
|  |       switch(tr->type) | ||
|  |       { case TR_MARK: | ||
|  | 	  break; | ||
|  | 	case TR_SUB_START: | ||
|  | 	{ term_t id = PL_new_term_ref(); | ||
|  | 	  term_t be = PL_new_term_ref(); | ||
|  | 	  if ( !PL_recorded(tr->update.transaction_id, id) || | ||
|  | 	       !put_begin_end(be, FUNCTOR_begin1, ++tr_level) || | ||
|  | 	       !broadcast(EV_TRANSACTION, (void*)id, (void*)be) ) | ||
|  | 	    return FALSE; | ||
|  | 	  break; | ||
|  | 	} | ||
|  | 	case TR_SUB_END: | ||
|  | 	{ term_t id = PL_new_term_ref(); | ||
|  | 	  term_t be = PL_new_term_ref(); | ||
|  | 	  if ( !PL_recorded(tr->update.transaction_id, id) ) | ||
|  | 	    return FALSE; | ||
|  | 	  PL_erase(tr->update.transaction_id); | ||
|  | 	  if ( !put_begin_end(be, FUNCTOR_end1, tr_level--) || | ||
|  | 	       !broadcast(EV_TRANSACTION, (void*)id, (void*)be)	) | ||
|  | 	    return FALSE; | ||
|  | 	  break; | ||
|  | 	} | ||
|  | 	case TR_ASSERT: | ||
|  | 	  link_triple(db, tr->triple); | ||
|  | 	  db->generation++; | ||
|  | 	  break; | ||
|  | 	case TR_RETRACT: | ||
|  | 	  if ( !tr->triple->erased )	/* already erased */ | ||
|  | 	  { erase_triple(db, tr->triple); | ||
|  | 	    db->generation++; | ||
|  | 	  } | ||
|  | 	  break; | ||
|  | 	case TR_UPDATE: | ||
|  | 	  if ( !tr->triple->erased ) | ||
|  | 	  { if ( !broadcast(EV_UPDATE, tr->triple, tr->update.triple) ) | ||
|  | 	      return FALSE;		/* TBD: how to handle? */ | ||
|  | 	    if ( !tr->triple->erased ) | ||
|  | 	    { erase_triple_silent(db, tr->triple); | ||
|  | 	      link_triple_silent(db, tr->update.triple); | ||
|  | 	      db->generation++; | ||
|  | 	    } | ||
|  | 	  } | ||
|  | 	  break; | ||
|  | 	case TR_UPDATE_SRC: | ||
|  | 	  if ( !tr->triple->erased ) | ||
|  | 	  { if ( tr->triple->graph != tr->update.src.atom ) | ||
|  | 	    { if ( tr->triple->graph ) | ||
|  | 		unregister_graph(db, tr->triple); | ||
|  | 	      tr->triple->graph = tr->update.src.atom; | ||
|  | 	      if ( tr->triple->graph ) | ||
|  | 		register_graph(db, tr->triple); | ||
|  | 	    } | ||
|  | 	    tr->triple->line = tr->update.src.line; | ||
|  | 	    db->generation++; | ||
|  | 	  } | ||
|  | 	  break; | ||
|  | 	case TR_UPDATE_MD5: | ||
|  | 	{ graph *src = tr->update.md5.graph; | ||
|  | 	  md5_byte_t *digest = tr->update.md5.digest; | ||
|  | 	  if ( digest ) | ||
|  | 	  { sum_digest(digest, src->digest); | ||
|  | 	    src->md5 = TRUE; | ||
|  | 	    rdf_free(db, digest, sizeof(md5_byte_t)*16); | ||
|  | 	  } else | ||
|  | 	  { src->md5 = FALSE; | ||
|  | 	  } | ||
|  | 	  break; | ||
|  | 	} | ||
|  | 	case TR_RESET: | ||
|  | 	  db->tr_reset = FALSE; | ||
|  | 	  reset_db(db); | ||
|  | 	  break; | ||
|  | 	case TR_VOID: | ||
|  | 	  break; | ||
|  | 	default: | ||
|  | 	  assert(0); | ||
|  |       } | ||
|  | 
 | ||
|  |       rdf_free(db, tr, sizeof(*tr)); | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   return TRUE; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static int | ||
|  | commit_transaction(rdf_db *db, term_t id) | ||
|  | { int rc; | ||
|  | 
 | ||
|  |   db->gc_blocked++; | ||
|  |   rc = commit_transaction_int(db, id); | ||
|  |   db->gc_blocked--; | ||
|  | 
 | ||
|  |   return rc; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static foreign_t | ||
|  | rdf_transaction(term_t goal, term_t id) | ||
|  | { int rc; | ||
|  |   rdf_db *db = DB; | ||
|  |   active_transaction me; | ||
|  | 
 | ||
|  |   if ( !WRLOCK(db, TRUE) ) | ||
|  |     return FALSE; | ||
|  | 
 | ||
|  |   open_transaction(db); | ||
|  |   me.parent = db->tr_active; | ||
|  |   me.id = id; | ||
|  |   db->tr_active = &me; | ||
|  | 
 | ||
|  |   rc = PL_call_predicate(NULL, PL_Q_PASS_EXCEPTION, PRED_call1, goal); | ||
|  | 
 | ||
|  |   if ( rc ) | ||
|  |   { int empty = (db->tr_last == NULL || db->tr_last->type == TR_MARK); | ||
|  | 
 | ||
|  |     if ( empty || db->tr_nesting > 0 ) | ||
|  |     { commit_transaction(db, id); | ||
|  |     } else | ||
|  |     { term_t be; | ||
|  | 
 | ||
|  |       if ( !(be=PL_new_term_ref()) || | ||
|  | 	   !put_begin_end(be, FUNCTOR_begin1, 0) || | ||
|  | 	   !broadcast(EV_TRANSACTION, (void*)id, (void*)be) || | ||
|  | 	   !put_begin_end(be, FUNCTOR_end1, 0) ) | ||
|  | 	return FALSE; | ||
|  | 
 | ||
|  |       if ( !LOCKOUT_READERS(db) )	/* interrupt, timeout */ | ||
|  |       { broadcast(EV_TRANSACTION, (void*)id, (void*)be); | ||
|  | 	rc = FALSE; | ||
|  | 	goto discard; | ||
|  |       } | ||
|  |       commit_transaction(db, id); | ||
|  |       REALLOW_READERS(db); | ||
|  |       if ( !broadcast(EV_TRANSACTION, (void*)id, (void*)be) ) | ||
|  | 	return FALSE; | ||
|  |     } | ||
|  |   } else | ||
|  |   { discard: | ||
|  |     discard_transaction(db); | ||
|  |   } | ||
|  |   db->tr_active = me.parent; | ||
|  |   WRUNLOCK(db); | ||
|  | 
 | ||
|  |   return rc; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static foreign_t | ||
|  | rdf_active_transactions(term_t list) | ||
|  | { rdf_db *db = DB; | ||
|  |   term_t tail = PL_copy_term_ref(list); | ||
|  |   term_t head = PL_new_term_ref(); | ||
|  |   active_transaction *ot; | ||
|  | 
 | ||
|  |   for(ot = db->tr_active; ot; ot=ot->parent) | ||
|  |   { if ( !PL_unify_list(tail, head, tail) || | ||
|  | 	 !PL_unify(head, ot->id) ) | ||
|  |       return FALSE; | ||
|  |   } | ||
|  | 
 | ||
|  |   return PL_unify_nil(tail); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | 		 /*******************************
 | ||
|  | 		 *	     PREDICATES		* | ||
|  | 		 *******************************/ | ||
|  | 
 | ||
|  | static foreign_t | ||
|  | rdf_assert4(term_t subject, term_t predicate, term_t object, term_t src) | ||
|  | { rdf_db *db = DB; | ||
|  |   triple *t = new_triple(db); | ||
|  | 
 | ||
|  |   if ( !get_triple(db, subject, predicate, object, t) ) | ||
|  |   { free_triple(db, t); | ||
|  |     return FALSE; | ||
|  |   } | ||
|  |   if ( src ) | ||
|  |   { if ( !get_graph(src, t) ) | ||
|  |     { free_triple(db, t); | ||
|  |       return FALSE; | ||
|  |     } | ||
|  |   } else | ||
|  |   { t->graph = ATOM_user; | ||
|  |     t->line = NO_LINE; | ||
|  |   } | ||
|  | 
 | ||
|  |   lock_atoms(t); | ||
|  |   if ( !WRLOCK(db, FALSE) ) | ||
|  |   { free_triple(db, t); | ||
|  |     return FALSE; | ||
|  |   } | ||
|  | 
 | ||
|  |   if ( db->tr_first ) | ||
|  |   { record_transaction(db, TR_ASSERT, t); | ||
|  |   } else | ||
|  |   { link_triple(db, t); | ||
|  |     db->generation++; | ||
|  |   } | ||
|  |   WRUNLOCK(db); | ||
|  | 
 | ||
|  |   return TRUE; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static foreign_t | ||
|  | rdf_assert3(term_t subject, term_t predicate, term_t object) | ||
|  | { return rdf_assert4(subject, predicate, object, 0); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | ||
|  | inc_active_queries(rdf_db *db); | ||
|  | dec_active_queries(rdf_db *db); | ||
|  | 
 | ||
|  | TBD: Either delete this or use atomic inc/dec. | ||
|  | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ | ||
|  | 
 | ||
|  | static void | ||
|  | inc_active_queries(rdf_db *db) | ||
|  | { LOCK_MISC(db); | ||
|  |   db->active_queries++; | ||
|  |   UNLOCK_MISC(db); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static void | ||
|  | dec_active_queries(rdf_db *db) | ||
|  | { LOCK_MISC(db); | ||
|  |   db->active_queries--; | ||
|  |   assert(db->active_queries>=0); | ||
|  |   UNLOCK_MISC(db); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | typedef struct search_state | ||
|  | { rdf_db       *db;			/* our database */ | ||
|  |   term_t	subject;		/* Prolog term references */ | ||
|  |   term_t 	object; | ||
|  |   term_t 	predicate; | ||
|  |   term_t 	src; | ||
|  |   term_t 	realpred; | ||
|  |   unsigned 	locked : 1;		/* State has been locked */ | ||
|  |   unsigned	allocated : 1;		/* State has been allocated */ | ||
|  |   unsigned	flags;			/* Misc flags controlling search */ | ||
|  |   atom_t	prefix;			/* prefix and like search */ | ||
|  |   avl_enum     *literal_state;		/* Literal search state */ | ||
|  |   literal      *literal_cursor;		/* pointer in current literal */ | ||
|  |   literal_ex    lit_ex;			/* extended literal for fast compare */ | ||
|  |   triple       *cursor;			/* Pointer in triple DB */ | ||
|  |   triple	pattern;		/* Pattern triple */ | ||
|  | } search_state; | ||
|  | 
 | ||
|  | 
 | ||
|  | static void	free_search_state(search_state *state); | ||
|  | 
 | ||
|  | static void | ||
|  | init_cursor_from_literal(search_state *state, literal *cursor) | ||
|  | { triple *p = &state->pattern; | ||
|  |   unsigned long iv; | ||
|  |   int i; | ||
|  | 
 | ||
|  |   DEBUG(3, | ||
|  | 	Sdprintf("Trying literal search for "); | ||
|  | 	print_literal(cursor); | ||
|  | 	Sdprintf("\n")); | ||
|  | 
 | ||
|  |   p->indexed |= BY_O; | ||
|  |   p->indexed &= ~BY_S;			/* we do not have index BY_SO */ | ||
|  |   switch(p->indexed) | ||
|  |   { case BY_O: | ||
|  |       iv = literal_hash(cursor); | ||
|  |       break; | ||
|  |     case BY_OP: | ||
|  |       iv = predicate_hash(p->predicate.r) ^ literal_hash(cursor); | ||
|  |       break; | ||
|  |     default: | ||
|  |       iv = 0;				/* make compiler silent */ | ||
|  |       assert(0); | ||
|  |   } | ||
|  | 
 | ||
|  |   i = (int)(iv % (long)state->db->table_size[p->indexed]); | ||
|  |   state->cursor = state->db->table[p->indexed][i]; | ||
|  |   state->literal_cursor = cursor; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | ||
|  | (*) update_hash() is there to update  the   hash  after  a change to the | ||
|  | predicate organization. If we do  not  have   a  predicate  or we do not | ||
|  | search using rdf_has/3, this is not needed. | ||
|  | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ | ||
|  | 
 | ||
|  | static int | ||
|  | init_search_state(search_state *state) | ||
|  | { triple *p = &state->pattern; | ||
|  | 
 | ||
|  |   if ( get_partial_triple(state->db, | ||
|  | 			  state->subject, state->predicate, state->object, | ||
|  | 			  state->src, p) != TRUE ) | ||
|  |   { free_triple(state->db, p); | ||
|  |     return FALSE; | ||
|  |   } | ||
|  | 
 | ||
|  |   if ( !RDLOCK(state->db) ) | ||
|  |   { free_triple(state->db, p); | ||
|  |     return FALSE; | ||
|  |   } | ||
|  |   state->locked = TRUE; | ||
|  |   if ( p->predicate.r && (state->flags & MATCH_SUBPROPERTY) ) /* See (*) */ | ||
|  |   { if ( !update_hash(state->db) ) | ||
|  |     { free_search_state(state); | ||
|  |       return FALSE; | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   if ( (p->match == STR_MATCH_PREFIX ||	p->match == STR_MATCH_LIKE) && | ||
|  |        p->indexed != BY_SP && | ||
|  |        (state->prefix = first_atom(p->object.literal->value.string, p->match))) | ||
|  |   { literal lit; | ||
|  |     literal **rlitp; | ||
|  | 
 | ||
|  |     lit = *p->object.literal; | ||
|  |     lit.value.string = state->prefix; | ||
|  |     state->literal_state = rdf_malloc(state->db, | ||
|  | 				      sizeof(*state->literal_state)); | ||
|  |     state->lit_ex.literal = &lit; | ||
|  |     prepare_literal_ex(&state->lit_ex); | ||
|  |     rlitp = avlfindfirst(&state->db->literals, &state->lit_ex, state->literal_state); | ||
|  |     if ( rlitp ) | ||
|  |     { init_cursor_from_literal(state, *rlitp); | ||
|  |     } else | ||
|  |     { free_search_state(state); | ||
|  |       return FALSE; | ||
|  |     } | ||
|  |   } else | ||
|  |   { state->cursor = state->db->table[p->indexed] | ||
|  |     				    [triple_hash(state->db, p, p->indexed)]; | ||
|  |   } | ||
|  | 
 | ||
|  |   return TRUE; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static void | ||
|  | free_search_state(search_state *state) | ||
|  | { if ( state->locked ) | ||
|  |   { RDUNLOCK(state->db); | ||
|  |   } | ||
|  | 
 | ||
|  |   free_triple(state->db, &state->pattern); | ||
|  |   if ( state->prefix ) | ||
|  |     PL_unregister_atom(state->prefix); | ||
|  |   if ( state->literal_state ) | ||
|  |     rdf_free(state->db, state->literal_state, sizeof(*state->literal_state)); | ||
|  |   if ( state->allocated )		/* also means redo! */ | ||
|  |   { dec_active_queries(state->db); | ||
|  |     rdf_free(state->db, state, sizeof(*state)); | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static foreign_t | ||
|  | allow_retry_state(search_state *state) | ||
|  | { if ( !state->allocated ) | ||
|  |   { search_state *copy = rdf_malloc(state->db, sizeof(*copy)); | ||
|  |     *copy = *state; | ||
|  |     copy->allocated = TRUE; | ||
|  |     inc_active_queries(state->db); | ||
|  | 
 | ||
|  |     state = copy; | ||
|  |   } | ||
|  | 
 | ||
|  |   PL_retry_address(state); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /* TBD: simplify.   Maybe split for resource and literal search, as
 | ||
|  |    both involve mutual exclusive complications to this routine, | ||
|  | */ | ||
|  | 
 | ||
|  | static int | ||
|  | next_search_state(search_state *state) | ||
|  | { triple *t = state->cursor; | ||
|  |   triple *p = &state->pattern; | ||
|  | 
 | ||
|  | retry: | ||
|  |   for( ; t; t = t->next[p->indexed]) | ||
|  |   { if ( t->is_duplicate && !state->src ) | ||
|  |       continue; | ||
|  | 
 | ||
|  | 					/* hash-collision, skip */ | ||
|  |     if ( state->literal_state ) | ||
|  |     { if ( !(t->object_is_literal && | ||
|  | 	     t->object.literal == state->literal_cursor) ) | ||
|  | 	continue; | ||
|  |     } | ||
|  | 
 | ||
|  |     if ( match_triples(t, p, state->flags) ) | ||
|  |     { term_t retpred = state->realpred ? state->realpred : state->predicate; | ||
|  |       if ( !unify_triple(state->subject, retpred, state->object, | ||
|  | 			 state->src, t, p->inversed) ) | ||
|  | 	continue; | ||
|  |       if ( state->realpred && PL_is_variable(state->predicate) ) | ||
|  |       { if ( !PL_unify(state->predicate, retpred) ) | ||
|  | 	  return FALSE; | ||
|  |       } | ||
|  | 
 | ||
|  |       t=t->next[p->indexed]; | ||
|  |     inv_alt: | ||
|  |       for(; t; t = t->next[p->indexed]) | ||
|  |       { if ( state->literal_state ) | ||
|  | 	{ if ( !(t->object_is_literal && | ||
|  | 		 t->object.literal == state->literal_cursor) ) | ||
|  | 	    continue; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	if ( match_triples(t, p, state->flags) ) | ||
|  | 	{ state->cursor = t; | ||
|  | 
 | ||
|  | 	  return TRUE;			/* non-deterministic */ | ||
|  | 	} | ||
|  |       } | ||
|  | 
 | ||
|  |       if ( (state->flags & MATCH_INVERSE) && inverse_partial_triple(p) ) | ||
|  |       { t = state->db->table[p->indexed][triple_hash(state->db, p, p->indexed)]; | ||
|  | 	goto inv_alt; | ||
|  |       } | ||
|  | 
 | ||
|  |       state->cursor = NULL;		/* deterministic */ | ||
|  |       return TRUE; | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   if ( (state->flags & MATCH_INVERSE) && inverse_partial_triple(p) ) | ||
|  |   { t = state->db->table[p->indexed][triple_hash(state->db, p, p->indexed)]; | ||
|  |     goto retry; | ||
|  |   } | ||
|  | 
 | ||
|  |   if ( state->literal_state ) | ||
|  |   { literal **litp; | ||
|  | 
 | ||
|  |     if ( (litp = avlfindnext(state->literal_state)) ) | ||
|  |     { if ( state->prefix ) | ||
|  |       { literal *lit = *litp; | ||
|  | 
 | ||
|  | 	if ( !match_atoms(STR_MATCH_PREFIX, state->prefix, lit->value.string) ) | ||
|  | 	{ DEBUG(1, | ||
|  | 		Sdprintf("Terminated literal iteration from "); | ||
|  | 		print_literal(lit); | ||
|  | 		Sdprintf("\n")); | ||
|  | 	  return FALSE;			/* no longer a prefix */ | ||
|  | 	} | ||
|  |       } | ||
|  | 
 | ||
|  |       init_cursor_from_literal(state, *litp); | ||
|  |       t = state->cursor; | ||
|  | 
 | ||
|  |       goto retry; | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   return FALSE; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | 
 | ||
|  | static foreign_t | ||
|  | rdf(term_t subject, term_t predicate, term_t object, | ||
|  |     term_t src, term_t realpred, control_t h, unsigned flags) | ||
|  | { rdf_db *db = DB; | ||
|  |   search_state *state; | ||
|  | 
 | ||
|  |   switch(PL_foreign_control(h)) | ||
|  |   { case PL_FIRST_CALL: | ||
|  |     { search_state buf; | ||
|  | 
 | ||
|  |       state = &buf; | ||
|  |       memset(state, 0, sizeof(*state)); | ||
|  |       state->db	       = db; | ||
|  |       state->subject   = subject; | ||
|  |       state->object    = object; | ||
|  |       state->predicate = predicate; | ||
|  |       state->src       = src; | ||
|  |       state->realpred  = realpred; | ||
|  |       state->flags     = flags; | ||
|  | 
 | ||
|  |       if ( !init_search_state(state) ) | ||
|  | 	return FALSE; | ||
|  | 
 | ||
|  |       goto search; | ||
|  |     } | ||
|  |     case PL_REDO: | ||
|  |     { int rc; | ||
|  | 
 | ||
|  |       state = PL_foreign_context_address(h); | ||
|  |       assert(state->subject == subject); | ||
|  | 
 | ||
|  |     search: | ||
|  |       if ( (rc=next_search_state(state)) ) | ||
|  |       { if ( state->cursor || state->literal_state ) | ||
|  | 	  return allow_retry_state(state); | ||
|  |       } | ||
|  | 
 | ||
|  |       free_search_state(state); | ||
|  |       return rc; | ||
|  |     } | ||
|  |     case PL_CUTTED: | ||
|  |     { search_state *state = PL_foreign_context_address(h); | ||
|  | 
 | ||
|  |       free_search_state(state); | ||
|  |       return TRUE; | ||
|  |     } | ||
|  |     default: | ||
|  |       assert(0); | ||
|  |       return FALSE; | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | ||
|  | rdf(Subject, Predicate, Object) | ||
|  | 
 | ||
|  | Search specifications: | ||
|  | 
 | ||
|  | 	Predicate: | ||
|  | 
 | ||
|  | 		subPropertyOf(X) = P | ||
|  | 
 | ||
|  | 	Object: | ||
|  | 
 | ||
|  | 		literal(substring(X), L) | ||
|  | 		literal(word(X), L) | ||
|  | 		literal(exact(X), L) | ||
|  | 		literal(prefix(X), L) | ||
|  | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ | ||
|  | 
 | ||
|  | 
 | ||
|  | static foreign_t | ||
|  | rdf3(term_t subject, term_t predicate, term_t object, control_t h) | ||
|  | { return rdf(subject, predicate, object, 0, 0, h, | ||
|  | 	     MATCH_EXACT); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static foreign_t | ||
|  | rdf4(term_t subject, term_t predicate, term_t object, | ||
|  |      term_t src, control_t h) | ||
|  | { return rdf(subject, predicate, object, src, 0, h, | ||
|  | 	     MATCH_EXACT|MATCH_SRC); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static foreign_t | ||
|  | rdf_has(term_t subject, term_t predicate, term_t object, | ||
|  | 	term_t realpred, control_t h) | ||
|  | { return rdf(subject, predicate, object, 0, realpred, h, | ||
|  | 	     MATCH_SUBPROPERTY|MATCH_INVERSE); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | ||
|  | rdf_estimate_complexity(+S,+P,+O,-C) | ||
|  | 
 | ||
|  | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ | ||
|  | 
 | ||
|  | static foreign_t | ||
|  | rdf_estimate_complexity(term_t subject, term_t predicate, term_t object, | ||
|  | 		        term_t complexity) | ||
|  | { triple t; | ||
|  |   long c; | ||
|  |   rdf_db *db = DB; | ||
|  |   int rc; | ||
|  | 
 | ||
|  |   memset(&t, 0, sizeof(t)); | ||
|  |   if ( (rc=get_partial_triple(db, subject, predicate, object, 0, &t)) != TRUE ) | ||
|  |   { if ( rc == -1 ) | ||
|  |     { return FALSE;			/* error */ | ||
|  |     } else | ||
|  |     { return PL_unify_integer(complexity, 0); 	/* no predicate */ | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   if ( !RDLOCK(db) ) | ||
|  |     return FALSE; | ||
|  |   if ( !update_hash(db) )			/* or ignore this problem? */ | ||
|  |   { RDUNLOCK(db); | ||
|  |     free_triple(db, &t); | ||
|  |     return FALSE; | ||
|  |   } | ||
|  | 
 | ||
|  |   if ( t.indexed == BY_NONE ) | ||
|  |   { c = db->created - db->erased;		/* = totale triple count */ | ||
|  | #if 0
 | ||
|  |   } else if ( t.indexed == BY_P ) | ||
|  |   { c = t.predicate.r->triple_count;		/* must sum over children */ | ||
|  | #endif
 | ||
|  |   } else | ||
|  |   { c = db->counts[t.indexed][triple_hash(db, &t, t.indexed)]; | ||
|  |   } | ||
|  | 
 | ||
|  |   rc = PL_unify_integer(complexity, c); | ||
|  |   RDUNLOCK(db); | ||
|  |   free_triple(db, &t); | ||
|  | 
 | ||
|  |   return rc; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | ||
|  | current_literal(?Literals) | ||
|  | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ | ||
|  | 
 | ||
|  | static foreign_t | ||
|  | rdf_current_literal(term_t t, control_t h) | ||
|  | { rdf_db *db = DB; | ||
|  |   literal **data; | ||
|  |   avl_enum *state; | ||
|  |   int rc; | ||
|  | 
 | ||
|  |   switch(PL_foreign_control(h)) | ||
|  |   { case PL_FIRST_CALL: | ||
|  |       if ( PL_is_variable(t) ) | ||
|  |       { state = rdf_malloc(db, sizeof(*state)); | ||
|  | 
 | ||
|  | 	RDLOCK(db); | ||
|  | 	inc_active_queries(db); | ||
|  | 	data = avlfindfirst(&db->literals, NULL, state); | ||
|  | 	goto next; | ||
|  |       } else | ||
|  |       { return FALSE;			/* TBD */ | ||
|  |       } | ||
|  |     case PL_REDO: | ||
|  |       state = PL_foreign_context_address(h); | ||
|  |       data = avlfindnext(state); | ||
|  |     next: | ||
|  |       for(; data; data=avlfindnext(state)) | ||
|  |       { literal *lit = *data; | ||
|  | 
 | ||
|  | 	if ( unify_literal(t, lit) ) | ||
|  | 	{ PL_retry_address(state); | ||
|  | 	} | ||
|  |       } | ||
|  | 
 | ||
|  |       rc = FALSE; | ||
|  |       goto cleanup; | ||
|  |     case PL_CUTTED: | ||
|  |       rc = TRUE; | ||
|  | 
 | ||
|  |     cleanup: | ||
|  |       state = PL_foreign_context_address(h); | ||
|  |       avlfinddestroy(state); | ||
|  |       rdf_free(db, state, sizeof(*state)); | ||
|  |       RDUNLOCK(db); | ||
|  |       dec_active_queries(db); | ||
|  | 
 | ||
|  |       return rc; | ||
|  |     default: | ||
|  |       assert(0); | ||
|  |       return FALSE; | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | ||
|  | rdf_update(+Subject, +Predicate, +Object, +Action) | ||
|  | 
 | ||
|  | Update a triple. Please note this is actually erase+assert as the triple | ||
|  | needs to be updated in  the  linked   lists  while  erase simply flags a | ||
|  | triple as `erases' without deleting it   to support queries which active | ||
|  | choicepoints. | ||
|  | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ | ||
|  | 
 | ||
|  | static int | ||
|  | update_triple(rdf_db *db, term_t action, triple *t) | ||
|  | { term_t a = PL_new_term_ref(); | ||
|  |   triple tmp, *new; | ||
|  |   int i; | ||
|  | 					/* Create copy in local memory */ | ||
|  |   tmp = *t; | ||
|  |   tmp.allocated = FALSE; | ||
|  |   tmp.atoms_locked = FALSE; | ||
|  |   if ( t->object_is_literal ) | ||
|  |     tmp.object.literal = copy_literal(db, t->object.literal); | ||
|  | 
 | ||
|  |   if ( !PL_get_arg(1, action, a) ) | ||
|  |     return type_error(action, "rdf_action"); | ||
|  | 
 | ||
|  |   if ( PL_is_functor(action, FUNCTOR_subject1) ) | ||
|  |   { atom_t s; | ||
|  | 
 | ||
|  |     if ( !get_atom_ex(a, &s) ) | ||
|  |       return FALSE; | ||
|  |     if ( tmp.subject == s ) | ||
|  |       return TRUE;			/* no change */ | ||
|  | 
 | ||
|  |     tmp.subject = s; | ||
|  |   } else if ( PL_is_functor(action, FUNCTOR_predicate1) ) | ||
|  |   { predicate *p; | ||
|  | 
 | ||
|  |     if ( !get_predicate(db, a, &p) ) | ||
|  |       return FALSE; | ||
|  |     if ( tmp.predicate.r == p ) | ||
|  |       return TRUE;			/* no change */ | ||
|  | 
 | ||
|  |     tmp.predicate.r = p; | ||
|  |   } else if ( PL_is_functor(action, FUNCTOR_object1) ) | ||
|  |   { triple t2; | ||
|  | 
 | ||
|  |     memset(&t2, 0, sizeof(t2)); | ||
|  | 
 | ||
|  |     if ( !get_object(db, a, &t2) ) | ||
|  |     { free_triple(db, &t2); | ||
|  |       return FALSE; | ||
|  |     } | ||
|  |     if ( match_object(&t2, &tmp, MATCH_QUAL) ) | ||
|  |     { free_triple(db, &t2); | ||
|  |       return TRUE; | ||
|  |     } | ||
|  | 
 | ||
|  |     if ( tmp.object_is_literal ) | ||
|  |       free_literal(db, tmp.object.literal); | ||
|  |     if ( (tmp.object_is_literal = t2.object_is_literal) ) | ||
|  |     { tmp.object.literal = t2.object.literal; | ||
|  |     } else | ||
|  |     { tmp.object.resource = t2.object.resource; | ||
|  |     } | ||
|  |   } else if ( PL_is_functor(action, FUNCTOR_graph1) ) | ||
|  |   { triple t2; | ||
|  | 
 | ||
|  |     if ( !get_graph(a, &t2) ) | ||
|  |       return FALSE; | ||
|  |     if ( t2.graph == t->graph && t2.line == t->line ) | ||
|  |       return TRUE; | ||
|  |     if ( db->tr_first ) | ||
|  |     { record_update_src_transaction(db, t, t2.graph, t2.line); | ||
|  |     } else | ||
|  |     { if ( t->graph ) | ||
|  | 	unregister_graph(db, t); | ||
|  |       t->graph = t2.graph; | ||
|  |       t->line = t2.line; | ||
|  |       if ( t->graph ) | ||
|  | 	register_graph(db, t); | ||
|  |     } | ||
|  | 
 | ||
|  |     return TRUE;			/* considered no change */ | ||
|  |   } else | ||
|  |     return domain_error(action, "rdf_action"); | ||
|  | 
 | ||
|  |   for(i=0; i<INDEX_TABLES; i++) | ||
|  |     tmp.next[i] = NULL; | ||
|  | 
 | ||
|  |   new = new_triple(db); | ||
|  |   new->subject		 = tmp.subject; | ||
|  |   new->predicate.r	 = tmp.predicate.r; | ||
|  |   if ( (new->object_is_literal = tmp.object_is_literal) ) | ||
|  |   { new->object.literal = copy_literal(db, tmp.object.literal); | ||
|  |   } else | ||
|  |   { new->object.resource = tmp.object.resource; | ||
|  |   } | ||
|  |   new->graph		 = tmp.graph; | ||
|  |   new->line		 = tmp.line; | ||
|  | 
 | ||
|  |   free_triple(db, &tmp); | ||
|  |   lock_atoms(new); | ||
|  | 
 | ||
|  |   if ( db->tr_first ) | ||
|  |   { record_update_transaction(db, t, new); | ||
|  |   } else | ||
|  |   { broadcast(EV_UPDATE, t, new); | ||
|  |     erase_triple_silent(db, t); | ||
|  |     link_triple_silent(db, new); | ||
|  |     db->generation++; | ||
|  |   } | ||
|  | 
 | ||
|  |   return TRUE; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | 
 | ||
|  | static foreign_t | ||
|  | rdf_update5(term_t subject, term_t predicate, term_t object, term_t src, | ||
|  | 	    term_t action) | ||
|  | { triple t, *p; | ||
|  |   int indexed = BY_SP; | ||
|  |   int done = 0; | ||
|  |   rdf_db *db = DB; | ||
|  | 
 | ||
|  |   memset(&t, 0, sizeof(t)); | ||
|  | 
 | ||
|  |   if ( !get_src(src, &t) || | ||
|  |        !get_triple(db, subject, predicate, object, &t) ) | ||
|  |     return FALSE; | ||
|  | 
 | ||
|  |   if ( !WRLOCK(db, FALSE) ) | ||
|  |   { free_triple(db, &t); | ||
|  |     return FALSE; | ||
|  |   } | ||
|  |   if ( !update_hash(db) ) | ||
|  |   { WRUNLOCK(db); | ||
|  |     free_triple(db, &t); | ||
|  |     return FALSE; | ||
|  |   } | ||
|  |   p = db->table[indexed][triple_hash(db, &t, indexed)]; | ||
|  |   for( ; p; p = p->next[indexed]) | ||
|  |   { if ( match_triples(p, &t, MATCH_EXACT) ) | ||
|  |     { if ( !update_triple(db, action, p) ) | ||
|  |       { WRUNLOCK(db); | ||
|  | 	free_triple(db, &t); | ||
|  | 	return FALSE;			/* type errors */ | ||
|  |       } | ||
|  |       done++; | ||
|  |     } | ||
|  |   } | ||
|  |   free_triple(db, &t); | ||
|  |   WRUNLOCK(db); | ||
|  | 
 | ||
|  |   return done ? TRUE : FALSE; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static foreign_t | ||
|  | rdf_update(term_t subject, term_t predicate, term_t object, term_t action) | ||
|  | { return rdf_update5(subject, predicate, object, 0, action); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static foreign_t | ||
|  | rdf_retractall4(term_t subject, term_t predicate, term_t object, term_t src) | ||
|  | { triple t, *p; | ||
|  |   rdf_db *db = DB; | ||
|  | 
 | ||
|  |   memset(&t, 0, sizeof(t)); | ||
|  |   switch( get_partial_triple(db, subject, predicate, object, src, &t) ) | ||
|  |   { case 0:				/* no such predicate */ | ||
|  |       return TRUE; | ||
|  |     case -1:				/* error */ | ||
|  |       return FALSE; | ||
|  |   } | ||
|  | 
 | ||
|  |   if ( t.graph	)		/* speedup for rdf_retractall(_,_,_,DB) */ | ||
|  |   { graph *gr = lookup_graph(db, t.graph, FALSE); | ||
|  | 
 | ||
|  |     if ( !gr || gr->triple_count == 0 ) | ||
|  |       return TRUE; | ||
|  |   } | ||
|  | 
 | ||
|  |   if ( !WRLOCK(db, FALSE) ) | ||
|  |     return FALSE; | ||
|  | /*			No need, as we do not search with subPropertyOf
 | ||
|  |   if ( !update_hash(db) ) | ||
|  |   { WRUNLOCK(db); | ||
|  |     return FALSE; | ||
|  |   } | ||
|  | */ | ||
|  |   p = db->table[t.indexed][triple_hash(db, &t, t.indexed)]; | ||
|  |   for( ; p; p = p->next[t.indexed]) | ||
|  |   { if ( match_triples(p, &t, MATCH_EXACT|MATCH_SRC) ) | ||
|  |     { if ( t.object_is_literal && t.object.literal->objtype == OBJ_TERM ) | ||
|  |       { fid_t fid = PL_open_foreign_frame(); | ||
|  | 	int rc = unify_object(object, p); | ||
|  | 	PL_discard_foreign_frame(fid); | ||
|  | 	if ( !rc ) | ||
|  | 	  continue; | ||
|  |       } | ||
|  | 
 | ||
|  |       if ( db->tr_first ) | ||
|  |       { if ( db->tr_reset ) | ||
|  | 	{ WRUNLOCK(db); | ||
|  | 	  return permission_error("retract", "triple", "", | ||
|  | 				  "rdf_retractall cannot follow " | ||
|  | 				  "rdf_reset_db in one transaction"); | ||
|  | 	} | ||
|  | 	record_transaction(db, TR_RETRACT, p); | ||
|  |       } else | ||
|  |       { erase_triple(db, p); | ||
|  | 	db->generation++; | ||
|  |       } | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   WRUNLOCK(db); | ||
|  |   free_triple(db, &t); | ||
|  | 
 | ||
|  |   return TRUE; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static foreign_t | ||
|  | rdf_retractall3(term_t subject, term_t predicate, term_t object) | ||
|  | { return rdf_retractall4(subject, predicate, object, 0); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | 		 /*******************************
 | ||
|  | 		 *	     MONITOR		* | ||
|  | 		 *******************************/ | ||
|  | 
 | ||
|  | typedef struct broadcast_callback | ||
|  | { struct broadcast_callback *next; | ||
|  |   predicate_t		     pred; | ||
|  |   long			     mask; | ||
|  | } broadcast_callback; | ||
|  | 
 | ||
|  | static long joined_mask = 0L; | ||
|  | static broadcast_callback *callback_list; | ||
|  | static broadcast_callback *callback_tail; | ||
|  | 
 | ||
|  | static void | ||
|  | do_broadcast(term_t term, long mask) | ||
|  | { if ( callback_list ) | ||
|  |   { broadcast_callback *cb; | ||
|  | 
 | ||
|  |     for(cb = callback_list; cb; cb = cb->next) | ||
|  |     { qid_t qid; | ||
|  |       term_t ex; | ||
|  | 
 | ||
|  |       if ( !(cb->mask & mask) ) | ||
|  | 	continue; | ||
|  | 
 | ||
|  |       qid = PL_open_query(NULL, PL_Q_CATCH_EXCEPTION, cb->pred, term); | ||
|  |       if ( !PL_next_solution(qid) && (ex = PL_exception(qid)) ) | ||
|  |       { term_t av = PL_new_term_refs(2); | ||
|  | 
 | ||
|  | 	PL_cut_query(qid); | ||
|  | 
 | ||
|  | 	PL_put_atom(av+0, ATOM_error); | ||
|  | 	PL_put_term(av+1, ex); | ||
|  | 
 | ||
|  | 	PL_call_predicate(NULL, PL_Q_NORMAL, | ||
|  | 			  PL_predicate("print_message", 2, "user"), | ||
|  | 			  av); | ||
|  |       } else | ||
|  |       { PL_close_query(qid); | ||
|  |       } | ||
|  |     } | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /* No longer used, but we keep it for if we need it again
 | ||
|  | static foreign_t | ||
|  | rdf_broadcast(term_t term, term_t mask) | ||
|  | { long msk; | ||
|  | 
 | ||
|  |   if ( !get_long_ex(mask, &msk) ) | ||
|  |     return FALSE; | ||
|  | 
 | ||
|  |   do_broadcast(term, msk); | ||
|  |   return TRUE; | ||
|  | } | ||
|  | */ | ||
|  | 
 | ||
|  | static int | ||
|  | broadcast(broadcast_id id, void *a1, void *a2) | ||
|  | { if ( (joined_mask & id) ) | ||
|  |   { fid_t fid; | ||
|  |     term_t term; | ||
|  |     functor_t funct; | ||
|  | 
 | ||
|  |     if ( !(fid = PL_open_foreign_frame()) || | ||
|  | 	 !(term = PL_new_term_ref()) ) | ||
|  |       return FALSE; | ||
|  | 
 | ||
|  |     switch(id) | ||
|  |     { case EV_ASSERT: | ||
|  |       case EV_ASSERT_LOAD: | ||
|  | 	funct = FUNCTOR_assert4; | ||
|  |         goto assert_retract; | ||
|  |       case EV_RETRACT: | ||
|  | 	funct = FUNCTOR_retract4; | ||
|  |       assert_retract: | ||
|  |       { triple *t = a1; | ||
|  | 	term_t tmp; | ||
|  | 
 | ||
|  | 	if ( !(tmp = PL_new_term_refs(4)) || | ||
|  | 	     !PL_put_atom(tmp+0, t->subject) || | ||
|  | 	     !PL_put_atom(tmp+1, t->predicate.r->name) || | ||
|  | 	     !unify_object(tmp+2, t) || | ||
|  | 	     !unify_graph(tmp+3, t) || | ||
|  | 	     !PL_cons_functor_v(term, funct, tmp) ) | ||
|  | 	  return FALSE; | ||
|  | 	break; | ||
|  |       } | ||
|  |       case EV_UPDATE: | ||
|  |       { triple *t = a1; | ||
|  | 	triple *new = a2; | ||
|  | 	term_t tmp, a; | ||
|  | 	functor_t action; | ||
|  | 	int rc; | ||
|  | 
 | ||
|  | 	if ( !(tmp = PL_new_term_refs(5)) || | ||
|  | 	     !(a = PL_new_term_ref()) || | ||
|  | 	     !PL_put_atom(tmp+0, t->subject) || | ||
|  | 	     !PL_put_atom(tmp+1, t->predicate.r->name) || | ||
|  | 	     !unify_object(tmp+2, t) || | ||
|  | 	     !unify_graph(tmp+3, t) ) | ||
|  | 	  return FALSE; | ||
|  | 
 | ||
|  | 	if ( t->subject != new->subject ) | ||
|  | 	{ action = FUNCTOR_subject1; | ||
|  | 	  rc = PL_put_atom(a, new->subject); | ||
|  | 	} else if ( t->predicate.r != new->predicate.r ) | ||
|  | 	{ action = FUNCTOR_predicate1; | ||
|  | 	  rc = PL_put_atom(a, new->predicate.r->name); | ||
|  | 	} else if ( !match_object(t, new, MATCH_QUAL) ) | ||
|  | 	{ action = FUNCTOR_object1; | ||
|  | 	  rc = unify_object(a, new); | ||
|  | 	} else if ( !same_graph(t, new) ) | ||
|  | 	{ action = FUNCTOR_graph1; | ||
|  | 	  rc = unify_graph(a, new); | ||
|  | 	} else | ||
|  | 	{ return TRUE;			/* no change */ | ||
|  | 	} | ||
|  | 
 | ||
|  |         if ( !rc || | ||
|  | 	     !PL_cons_functor_v(tmp+4, action, a) || | ||
|  | 	     !PL_cons_functor_v(term, FUNCTOR_update5, tmp) ) | ||
|  | 	  return FALSE; | ||
|  | 	break; | ||
|  |       } | ||
|  |       case EV_NEW_LITERAL: | ||
|  |       { literal *lit = a1; | ||
|  | 	term_t tmp; | ||
|  | 
 | ||
|  | 	if ( !(tmp = PL_new_term_refs(1)) || | ||
|  | 	     !unify_literal(tmp, lit) || | ||
|  | 	     !PL_cons_functor_v(term, FUNCTOR_new_literal1, tmp) ) | ||
|  | 	  return FALSE; | ||
|  | 	break; | ||
|  |       } | ||
|  |       case EV_OLD_LITERAL: | ||
|  |       { literal *lit = a1; | ||
|  | 	term_t tmp; | ||
|  | 
 | ||
|  | 	if ( !(tmp = PL_new_term_refs(1)) || | ||
|  | 	     !unify_literal(tmp, lit) || | ||
|  | 	     !PL_cons_functor_v(term, FUNCTOR_old_literal1, tmp) ) | ||
|  | 	  return FALSE; | ||
|  | 	break; | ||
|  |       } | ||
|  |       case EV_LOAD: | ||
|  |       { term_t ctx = (term_t)a1; | ||
|  | 	atom_t be  = (atom_t)a2; | ||
|  | 	term_t tmp; | ||
|  | 
 | ||
|  | 	if ( !(tmp = PL_new_term_refs(2)) || | ||
|  | 	     !PL_put_atom(tmp+0, be) ||		/* begin/end */ | ||
|  | 	     !PL_put_term(tmp+1, ctx) || | ||
|  | 	     !PL_cons_functor_v(term, FUNCTOR_load2, tmp) ) | ||
|  | 	  return FALSE; | ||
|  | 	break; | ||
|  |       } | ||
|  |       case EV_TRANSACTION: | ||
|  |       { term_t ctx = (term_t)a1; | ||
|  | 	term_t be  = (term_t)a2; | ||
|  | 	term_t tmp; | ||
|  | 
 | ||
|  | 	if ( !(tmp = PL_new_term_refs(2)) || | ||
|  | 	     !PL_put_term(tmp+0, be) ||		/* begin/end */ | ||
|  | 	     !PL_put_term(tmp+1, ctx) || | ||
|  | 	     !PL_cons_functor_v(term, FUNCTOR_transaction2, tmp) ) | ||
|  | 	  return FALSE; | ||
|  | 	break; | ||
|  |       } | ||
|  |       case EV_REHASH: | ||
|  |       { atom_t be = (atom_t)a1; | ||
|  | 	term_t tmp = PL_new_term_refs(1); | ||
|  | 
 | ||
|  | 	if ( !(tmp = PL_new_term_refs(1)) || | ||
|  | 	     !PL_put_atom(tmp+0, be) || | ||
|  | 	     !PL_cons_functor_v(term, FUNCTOR_rehash1, tmp) ) | ||
|  | 	  return FALSE; | ||
|  | 	break; | ||
|  |       } | ||
|  |       default: | ||
|  | 	assert(0); | ||
|  |     } | ||
|  | 
 | ||
|  |     do_broadcast(term, id); | ||
|  | 
 | ||
|  |     PL_discard_foreign_frame(fid); | ||
|  |   } | ||
|  | 
 | ||
|  |   return TRUE; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static foreign_t | ||
|  | rdf_monitor(term_t goal, term_t mask) | ||
|  | { atom_t name; | ||
|  |   broadcast_callback *cb; | ||
|  |   predicate_t p; | ||
|  |   long msk; | ||
|  |   module_t m = NULL; | ||
|  | 
 | ||
|  |   PL_strip_module(goal, &m, goal); | ||
|  | 
 | ||
|  |   if ( !get_atom_ex(goal, &name) || | ||
|  |        !get_long_ex(mask, &msk) ) | ||
|  |     return FALSE; | ||
|  | 
 | ||
|  |   p = PL_pred(PL_new_functor(name, 1), m); | ||
|  | 
 | ||
|  |   for(cb=callback_list; cb; cb = cb->next) | ||
|  |   { if ( cb->pred == p ) | ||
|  |     { broadcast_callback *cb2; | ||
|  |       cb->mask = msk; | ||
|  | 
 | ||
|  |       joined_mask = 0L; | ||
|  |       for(cb2=callback_list; cb2; cb2 = cb2->next) | ||
|  | 	joined_mask |= cb2->mask; | ||
|  |       DEBUG(2, Sdprintf("Set mask to 0x%x\n", joined_mask)); | ||
|  | 
 | ||
|  |       return TRUE; | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   cb = PL_malloc(sizeof(*cb)); | ||
|  |   cb->next = NULL; | ||
|  |   cb->mask = msk; | ||
|  |   cb->pred = p; | ||
|  |   if ( callback_list ) | ||
|  |   { callback_tail->next = cb; | ||
|  |     callback_tail = cb; | ||
|  |   } else | ||
|  |   { callback_list = callback_tail = cb; | ||
|  |   } | ||
|  |   joined_mask |= msk; | ||
|  | 
 | ||
|  |   return TRUE; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | 
 | ||
|  | 		 /*******************************
 | ||
|  | 		 *	       QUERY		* | ||
|  | 		 *******************************/ | ||
|  | 
 | ||
|  | /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | ||
|  | Enumerate the known subjects. This uses the   `first' flag on triples to | ||
|  | avoid returning the same resource multiple   times.  As the `by_none' is | ||
|  | never re-hashed, we don't mark this query in the `active_queries'. | ||
|  | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ | ||
|  | 
 | ||
|  | static foreign_t | ||
|  | rdf_subject(term_t subject, control_t h) | ||
|  | { triple *t; | ||
|  |   rdf_db *db = DB; | ||
|  | 
 | ||
|  |   switch(PL_foreign_control(h)) | ||
|  |   { case PL_FIRST_CALL: | ||
|  |     { if ( PL_is_variable(subject) ) | ||
|  |       { t = db->table[BY_NONE][0]; | ||
|  | 	goto next; | ||
|  |       } else | ||
|  |       { atom_t a; | ||
|  | 
 | ||
|  | 	if ( get_atom_ex(subject, &a) ) | ||
|  | 	{ if ( first(db, a) ) | ||
|  | 	    return TRUE; | ||
|  | 	  return FALSE; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	return FALSE; | ||
|  |       } | ||
|  |     } | ||
|  |     case PL_REDO: | ||
|  |       t = PL_foreign_context_address(h); | ||
|  |     next: | ||
|  |       for(; t; t = t->next[BY_NONE]) | ||
|  |       { if ( t->first && !t->erased ) | ||
|  | 	{ if ( !PL_unify_atom(subject, t->subject) ) | ||
|  | 	    return FALSE; | ||
|  | 
 | ||
|  | 	  t = t->next[BY_NONE]; | ||
|  | 	  if ( t ) | ||
|  | 	    PL_retry_address(t); | ||
|  | 	  return TRUE; | ||
|  | 	} | ||
|  |       } | ||
|  |       return FALSE; | ||
|  |     case PL_CUTTED: | ||
|  |       return TRUE; | ||
|  |     default: | ||
|  |       assert(0); | ||
|  |       return FALSE; | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static foreign_t | ||
|  | rdf_set_predicate(term_t pred, term_t option) | ||
|  | { predicate *p; | ||
|  |   rdf_db *db = DB; | ||
|  | 
 | ||
|  |   if ( !get_predicate(db, pred, &p) ) | ||
|  |     return FALSE; | ||
|  | 
 | ||
|  |   if ( PL_is_functor(option, FUNCTOR_symmetric1) ) | ||
|  |   { int val; | ||
|  | 
 | ||
|  |     if ( !get_bool_arg_ex(1, option, &val) ) | ||
|  |       return FALSE; | ||
|  | 
 | ||
|  |     p->inverse_of = p; | ||
|  |     return TRUE; | ||
|  |   } else if ( PL_is_functor(option, FUNCTOR_inverse_of1) ) | ||
|  |   { term_t a = PL_new_term_ref(); | ||
|  |     predicate *i; | ||
|  | 
 | ||
|  |     _PL_get_arg(1, option, a); | ||
|  |     if ( PL_get_nil(a) ) | ||
|  |     { if ( p->inverse_of ) | ||
|  |       { p->inverse_of->inverse_of = NULL; | ||
|  | 	p->inverse_of = NULL; | ||
|  |       } | ||
|  |     } else | ||
|  |     { if ( !get_predicate(db, a, &i) ) | ||
|  | 	return FALSE; | ||
|  | 
 | ||
|  |       p->inverse_of = i; | ||
|  |       i->inverse_of = p; | ||
|  |     } | ||
|  |     return TRUE; | ||
|  |   } else if ( PL_is_functor(option, FUNCTOR_transitive1) ) | ||
|  |   { int val; | ||
|  | 
 | ||
|  |     if ( !get_bool_arg_ex(1, option, &val) ) | ||
|  |       return FALSE; | ||
|  | 
 | ||
|  |     p->transitive = val; | ||
|  | 
 | ||
|  |     return TRUE; | ||
|  |   } else | ||
|  |     return type_error(option, "predicate_option"); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | #define PRED_PROPERTY_COUNT 9
 | ||
|  | static functor_t predicate_key[PRED_PROPERTY_COUNT]; | ||
|  | 
 | ||
|  | static int | ||
|  | unify_predicate_property(rdf_db *db, predicate *p, term_t option, functor_t f) | ||
|  | { if ( f == FUNCTOR_symmetric1 ) | ||
|  |     return PL_unify_term(option, PL_FUNCTOR, f, | ||
|  | 			 PL_BOOL, p->inverse_of == p ? TRUE : FALSE); | ||
|  |   else if ( f == FUNCTOR_inverse_of1 ) | ||
|  |   { if ( p->inverse_of ) | ||
|  |       return PL_unify_term(option, PL_FUNCTOR, f, | ||
|  | 			   PL_ATOM, p->inverse_of->name); | ||
|  |     else | ||
|  |       return FALSE; | ||
|  |   } else if ( f == FUNCTOR_transitive1 ) | ||
|  |   { return PL_unify_term(option, PL_FUNCTOR, f, | ||
|  | 			 PL_BOOL, p->transitive); | ||
|  |   } else if ( f == FUNCTOR_triples1 ) | ||
|  |   { return PL_unify_term(option, PL_FUNCTOR, f, | ||
|  | 			 PL_LONG, p->triple_count); | ||
|  |   } else if ( f == FUNCTOR_rdf_subject_branch_factor1 ) | ||
|  |   { return PL_unify_term(option, PL_FUNCTOR, f, | ||
|  | 		 PL_FLOAT, subject_branch_factor(db, p, DISTINCT_DIRECT)); | ||
|  |   } else if ( f == FUNCTOR_rdf_object_branch_factor1 ) | ||
|  |   { return PL_unify_term(option, PL_FUNCTOR, f, | ||
|  | 		 PL_FLOAT, object_branch_factor(db, p, DISTINCT_DIRECT)); | ||
|  |   } else if ( f == FUNCTOR_rdfs_subject_branch_factor1 ) | ||
|  |   { return PL_unify_term(option, PL_FUNCTOR, f, | ||
|  | 		 PL_FLOAT, subject_branch_factor(db, p, DISTINCT_SUB)); | ||
|  |   } else if ( f == FUNCTOR_rdfs_object_branch_factor1 ) | ||
|  |   { return PL_unify_term(option, PL_FUNCTOR, f, | ||
|  | 		 PL_FLOAT, object_branch_factor(db, p, DISTINCT_SUB)); | ||
|  |   } else | ||
|  |   { assert(0); | ||
|  |     return FALSE; | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static foreign_t | ||
|  | rdf_current_predicates(term_t preds) | ||
|  | { rdf_db *db = DB; | ||
|  |   int i; | ||
|  |   term_t head = PL_new_term_ref(); | ||
|  |   term_t tail = PL_copy_term_ref(preds); | ||
|  | 
 | ||
|  |   LOCK_MISC(db); | ||
|  |   for(i=0; i<db->pred_table_size; i++) | ||
|  |   { predicate *p; | ||
|  | 
 | ||
|  |     for(p=db->pred_table[i]; p; p = p->next) | ||
|  |     { if ( !PL_unify_list(tail, head, tail) || | ||
|  | 	   !PL_unify_atom(head, p->name) ) | ||
|  |       { UNLOCK_MISC(db); | ||
|  | 	return FALSE; | ||
|  |       } | ||
|  |     } | ||
|  |   } | ||
|  |   UNLOCK_MISC(db); | ||
|  | 
 | ||
|  |   return PL_unify_nil(tail); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static foreign_t | ||
|  | rdf_predicate_property(term_t pred, term_t option, control_t h) | ||
|  | { int n; | ||
|  |   predicate *p; | ||
|  |   rdf_db *db = DB; | ||
|  | 
 | ||
|  |   if ( !predicate_key[0] ) | ||
|  |   { int i = 0; | ||
|  | 
 | ||
|  |     predicate_key[i++] = FUNCTOR_symmetric1; | ||
|  |     predicate_key[i++] = FUNCTOR_inverse_of1; | ||
|  |     predicate_key[i++] = FUNCTOR_transitive1; | ||
|  |     predicate_key[i++] = FUNCTOR_triples1; | ||
|  |     predicate_key[i++] = FUNCTOR_rdf_subject_branch_factor1; | ||
|  |     predicate_key[i++] = FUNCTOR_rdf_object_branch_factor1; | ||
|  |     predicate_key[i++] = FUNCTOR_rdfs_subject_branch_factor1; | ||
|  |     predicate_key[i++] = FUNCTOR_rdfs_object_branch_factor1; | ||
|  |     assert(i < PRED_PROPERTY_COUNT); | ||
|  |   } | ||
|  | 
 | ||
|  |   switch(PL_foreign_control(h)) | ||
|  |   { case PL_FIRST_CALL: | ||
|  |     { functor_t f; | ||
|  | 
 | ||
|  |       if ( PL_is_variable(option) ) | ||
|  |       { n = 0; | ||
|  | 	goto redo; | ||
|  |       } else if ( PL_get_functor(option, &f) ) | ||
|  |       { for(n=0; predicate_key[n]; n++) | ||
|  | 	{ if ( predicate_key[n] == f ) | ||
|  | 	  { if ( !get_predicate(db, pred, &p) ) | ||
|  | 	      return FALSE; | ||
|  | 	    return unify_predicate_property(db, p, option, f); | ||
|  | 	  } | ||
|  | 	} | ||
|  | 	return domain_error(option, "rdf_predicate_property"); | ||
|  |       } else | ||
|  | 	return type_error(option, "rdf_predicate_property"); | ||
|  |     } | ||
|  |     case PL_REDO: | ||
|  |       n = (int)PL_foreign_context(h); | ||
|  |     redo: | ||
|  |       if ( !get_predicate(db, pred, &p) ) | ||
|  | 	return FALSE; | ||
|  |       for( ; predicate_key[n]; n++ ) | ||
|  |       { if ( unify_predicate_property(db, p, option, predicate_key[n]) ) | ||
|  | 	{ n++; | ||
|  | 	  if ( predicate_key[n] ) | ||
|  | 	    PL_retry(n); | ||
|  | 	  return TRUE; | ||
|  | 	} | ||
|  |       } | ||
|  |       return FALSE; | ||
|  |     case PL_CUTTED: | ||
|  |       return TRUE; | ||
|  |     default: | ||
|  |       assert(0); | ||
|  |       return TRUE; | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | 		 /*******************************
 | ||
|  | 		 *     TRANSITIVE RELATIONS	* | ||
|  | 		 *******************************/ | ||
|  | 
 | ||
|  | typedef struct visited | ||
|  | { struct visited *next;			/* next in list */ | ||
|  |   struct visited *hash_link;		/* next in hashed link */ | ||
|  |   atom_t resource;			/* visited resource */ | ||
|  |   uintptr_t distance;			/* Distance */ | ||
|  | } visited; | ||
|  | 
 | ||
|  | 
 | ||
|  | #define AGENDA_LOCAL_MAGIC 742736360
 | ||
|  | #define AGENDA_SAVED_MAGIC 742736362
 | ||
|  | 
 | ||
|  | typedef struct agenda | ||
|  | { visited *head;			/* visited list */ | ||
|  |   visited *tail;			/* tail of visited list */ | ||
|  |   visited *to_expand;			/* next to expand */ | ||
|  |   visited *to_return;			/* next to return */ | ||
|  |   visited **hash;			/* hash-table for cycle detection */ | ||
|  |   int	  magic;			/* AGENDA_*_MAGIC */ | ||
|  |   int	  hash_size; | ||
|  |   int     size;				/* size of the agenda */ | ||
|  |   uintptr_t max_d;			/* max distance */ | ||
|  |   triple  pattern;			/* partial triple used as pattern */ | ||
|  |   atom_t  target;			/* resource we are seaching for */ | ||
|  |   struct chunk  *chunk;			/* node-allocation chunks */ | ||
|  | } agenda; | ||
|  | 
 | ||
|  | #ifndef offsetof
 | ||
|  | #define offsetof(structure, field) ((size_t) &(((structure *)NULL)->field))
 | ||
|  | #endif
 | ||
|  | #define CHUNK_SIZE(n) offsetof(chunk, nodes[n])
 | ||
|  | 
 | ||
|  | typedef struct chunk | ||
|  | { struct chunk *next; | ||
|  |   int	 used;				/* # used elements */ | ||
|  |   int	 size;				/* size of the chunk */ | ||
|  |   struct visited nodes[1];		/* nodes in the chunk */ | ||
|  | } chunk; | ||
|  | 
 | ||
|  | 
 | ||
|  | static visited * | ||
|  | alloc_node_agenda(rdf_db *db, agenda *a) | ||
|  | { chunk *c; | ||
|  |   int size; | ||
|  | 
 | ||
|  |   if ( (c=a->chunk) ) | ||
|  |   { if ( c->used < c->size ) | ||
|  |     { visited *v = &c->nodes[c->used++]; | ||
|  | 
 | ||
|  |       return v; | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   size = (a->size == 0 ? 8 : 1024); | ||
|  |   c = rdf_malloc(db, CHUNK_SIZE(size)); | ||
|  |   c->size = size; | ||
|  |   c->used = 1; | ||
|  |   c->next = a->chunk; | ||
|  |   a->chunk = c; | ||
|  | 
 | ||
|  |   return &c->nodes[0]; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static void | ||
|  | empty_agenda(rdf_db *db, agenda *a) | ||
|  | { chunk *c, *n; | ||
|  | 
 | ||
|  |   for(c=a->chunk; c; c = n) | ||
|  |   { n = c->next; | ||
|  |     rdf_free(db, c, CHUNK_SIZE(c->size)); | ||
|  |   } | ||
|  |   if ( a->hash ) | ||
|  |     rdf_free(db, a->hash, sizeof(visited*)*a->hash_size); | ||
|  | 
 | ||
|  |   if ( a->magic == AGENDA_SAVED_MAGIC ) | ||
|  |   {  a->magic = 0; | ||
|  |      rdf_free(db, a, sizeof(*a)); | ||
|  |   } else | ||
|  |   { a->magic = 0; | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static void | ||
|  | unlock_and_empty_agenda(rdf_db *db, agenda *a) | ||
|  | { RDUNLOCK(db); | ||
|  |   empty_agenda(db, a); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static agenda * | ||
|  | save_agenda(rdf_db *db, agenda *a) | ||
|  | { agenda *r = rdf_malloc(db, sizeof(*r)); | ||
|  | 
 | ||
|  |   assert(a->magic == AGENDA_LOCAL_MAGIC); | ||
|  |   *r = *a; | ||
|  |   r->magic = AGENDA_SAVED_MAGIC; | ||
|  | 
 | ||
|  |   return r; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static void | ||
|  | hash_agenda(rdf_db *db, agenda *a, int size) | ||
|  | { if ( a->hash ) | ||
|  |     rdf_free(db, a->hash, sizeof(*a->hash)); | ||
|  |   if ( size > 0 ) | ||
|  |   { visited *v; | ||
|  | 
 | ||
|  |     a->hash = rdf_malloc(db, sizeof(visited*)*size); | ||
|  |     memset(a->hash, 0, sizeof(visited*)*size); | ||
|  |     a->hash_size = size; | ||
|  | 
 | ||
|  |     for(v=a->head; v; v = v->next) | ||
|  |     { int key = atom_hash(v->resource)&(size-1); | ||
|  | 
 | ||
|  |       v->hash_link = a->hash[key]; | ||
|  |       a->hash[key] = v; | ||
|  |     } | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static int | ||
|  | in_aganda(agenda *a, atom_t resource) | ||
|  | { visited *v; | ||
|  | 
 | ||
|  |   if ( a->hash ) | ||
|  |   { int key = atom_hash(resource)&(a->hash_size-1); | ||
|  |     v = a->hash[key]; | ||
|  | 
 | ||
|  |     for( ; v; v = v->hash_link ) | ||
|  |     { if ( v->resource == resource ) | ||
|  | 	return TRUE; | ||
|  |     } | ||
|  |   } else | ||
|  |   { v = a->head; | ||
|  | 
 | ||
|  |     for( ; v; v = v->next ) | ||
|  |     { if ( v->resource == resource ) | ||
|  | 	return TRUE; | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   return FALSE; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static visited * | ||
|  | append_agenda(rdf_db *db, agenda *a, atom_t res, uintptr_t d) | ||
|  | { visited *v = a->head; | ||
|  | 
 | ||
|  |   if ( in_aganda(a, res) ) | ||
|  |     return NULL; | ||
|  | 
 | ||
|  |   db->agenda_created++;			/* statistics */ | ||
|  | 
 | ||
|  |   a->size++; | ||
|  |   if ( !a->hash_size && a->size > 32 ) | ||
|  |     hash_agenda(db, a, 64); | ||
|  |   else if ( a->size > a->hash_size * 4 ) | ||
|  |     hash_agenda(db, a, a->hash_size * 4); | ||
|  | 
 | ||
|  |   v = alloc_node_agenda(db, a); | ||
|  |   v->resource = res; | ||
|  |   v->distance = d; | ||
|  |   v->next = NULL; | ||
|  |   if ( a->tail ) | ||
|  |   { a->tail->next = v; | ||
|  |     a->tail = v; | ||
|  |   } else | ||
|  |   { a->head = a->tail = v; | ||
|  |   } | ||
|  | 
 | ||
|  |   if ( a->hash_size ) | ||
|  |   { int key = atom_hash(res)&(a->hash_size-1); | ||
|  | 
 | ||
|  |     v->hash_link = a->hash[key]; | ||
|  |     a->hash[key] = v; | ||
|  |   } | ||
|  | 
 | ||
|  |   return v; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static int | ||
|  | can_reach_target(rdf_db *db, agenda *a) | ||
|  | { int indexed = a->pattern.indexed; | ||
|  |   int rc = FALSE; | ||
|  |   triple *p; | ||
|  | 
 | ||
|  |   if ( indexed & BY_S )			/* subj ---> */ | ||
|  |   { a->pattern.object.resource = a->target; | ||
|  |     indexed |= BY_O; | ||
|  |   } else | ||
|  |   { a->pattern.subject = a->target; | ||
|  |     indexed |= BY_S; | ||
|  |   } | ||
|  | 
 | ||
|  |   p = db->table[indexed][triple_hash(db, &a->pattern, indexed)]; | ||
|  |   for( ; p; p = p->next[indexed]) | ||
|  |   { if ( match_triples(p, &a->pattern, MATCH_SUBPROPERTY) ) | ||
|  |     { rc = TRUE; | ||
|  |       break; | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   if ( a->pattern.indexed & BY_S ) | ||
|  |   { a->pattern.object.resource = 0; | ||
|  |   } else | ||
|  |   { a->pattern.subject = 0; | ||
|  |   } | ||
|  | 
 | ||
|  |   return rc; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | 
 | ||
|  | static visited * | ||
|  | bf_expand(rdf_db *db, agenda *a, atom_t resource, uintptr_t d) | ||
|  | { triple *p; | ||
|  |   int indexed = a->pattern.indexed; | ||
|  |   visited *rc = NULL; | ||
|  | 
 | ||
|  |   if ( indexed & BY_S )			/* subj ---> */ | ||
|  |   { a->pattern.subject = resource; | ||
|  |   } else | ||
|  |   { a->pattern.object.resource = resource; | ||
|  |   } | ||
|  | 
 | ||
|  |   if ( a->target && can_reach_target(db, a) ) | ||
|  |   { return append_agenda(db, a, a->target, d); | ||
|  |   } | ||
|  | 
 | ||
|  |   p = db->table[indexed][triple_hash(db, &a->pattern, indexed)]; | ||
|  |   for( ; p; p = p->next[indexed]) | ||
|  |   { if ( match_triples(p, &a->pattern, MATCH_SUBPROPERTY) ) | ||
|  |     { atom_t found; | ||
|  |       visited *v; | ||
|  | 
 | ||
|  |       if ( indexed & BY_S ) | ||
|  |       { if ( p->object_is_literal ) | ||
|  | 	  continue; | ||
|  | 	found = p->object.resource; | ||
|  |       } else | ||
|  |       { found = p->subject; | ||
|  |       } | ||
|  | 
 | ||
|  |       v = append_agenda(db, a, found, d); | ||
|  |       if ( !rc ) | ||
|  | 	rc = v; | ||
|  |       if ( found == a->target ) | ||
|  | 	break; | ||
|  |     } | ||
|  |   } | ||
|  | 					/* TBD: handle owl:inverseOf */ | ||
|  | 					/* TBD: handle owl:sameAs */ | ||
|  |   return rc; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static visited * | ||
|  | next_agenda(rdf_db *db, agenda *a) | ||
|  | { visited *v; | ||
|  | 
 | ||
|  |   if ( (v=a->to_return) ) | ||
|  |   { ok: | ||
|  | 
 | ||
|  |     a->to_return = a->to_return->next; | ||
|  | 
 | ||
|  |     return v; | ||
|  |   } | ||
|  | 
 | ||
|  |   while( a->to_expand ) | ||
|  |   { uintptr_t next_d = a->to_expand->distance+1; | ||
|  | 
 | ||
|  |     if ( next_d >= a->max_d ) | ||
|  |       return NULL; | ||
|  | 
 | ||
|  |     a->to_return = bf_expand(db, a, | ||
|  | 			     a->to_expand->resource, | ||
|  | 			     next_d); | ||
|  |     a->to_expand = a->to_expand->next; | ||
|  | 
 | ||
|  |     if ( (v=a->to_return) ) | ||
|  |       goto ok; | ||
|  |   } | ||
|  | 
 | ||
|  |   return NULL; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | ||
|  | rdf_reachable(+Subject, +Predicate, -Object) | ||
|  | rdf_reachable(-Subject, +Predicate, ?Object) | ||
|  |     Examine transitive relations, reporting all `Object' that can be | ||
|  |     reached from `Subject' using Predicate without going into a loop | ||
|  |     if the relation is cyclic. | ||
|  | 
 | ||
|  | directly_attached() deals with the posibility that  the predicate is not | ||
|  | defined and Subject and Object are  the   same.  Should  use clean error | ||
|  | handling, but that means a lot of changes. For now this will do. | ||
|  | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ | ||
|  | 
 | ||
|  | static int | ||
|  | directly_attached(term_t pred, term_t from, term_t to) | ||
|  | { if ( PL_is_atom(pred) && PL_is_atom(from) ) | ||
|  |     return PL_unify(to, from); | ||
|  | 
 | ||
|  |   return FALSE; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static int | ||
|  | unify_distance(term_t d, uintptr_t dist) | ||
|  | { if ( d ) | ||
|  |     return PL_unify_integer(d, dist); | ||
|  | 
 | ||
|  |   return TRUE; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static foreign_t | ||
|  | rdf_reachable(term_t subj, term_t pred, term_t obj, | ||
|  | 	      term_t max_d, term_t d, | ||
|  | 	      control_t h) | ||
|  | { rdf_db *db = DB; | ||
|  | 
 | ||
|  |   switch(PL_foreign_control(h)) | ||
|  |   { case PL_FIRST_CALL: | ||
|  |     { agenda a; | ||
|  |       visited *v; | ||
|  |       term_t target_term; | ||
|  |       int is_det = FALSE; | ||
|  | 
 | ||
|  |       if ( PL_is_variable(pred) ) | ||
|  | 	return instantiation_error(pred); | ||
|  | 
 | ||
|  |       memset(&a, 0, sizeof(a)); | ||
|  |       a.magic = AGENDA_LOCAL_MAGIC; | ||
|  |       if ( max_d ) | ||
|  |       { long md; | ||
|  | 	atom_t inf; | ||
|  | 
 | ||
|  | 	if ( PL_get_atom(max_d, &inf) && inf == ATOM_infinite ) | ||
|  | 	  a.max_d = (uintptr_t)-1; | ||
|  | 	if ( !get_long_ex(max_d, &md) || md < 0 ) | ||
|  | 	  return FALSE; | ||
|  | 	a.max_d = md; | ||
|  |       } else | ||
|  |       { a.max_d = (uintptr_t)-1; | ||
|  |       } | ||
|  | 
 | ||
|  |       if ( !PL_is_variable(subj) )		/* subj .... obj */ | ||
|  |       { switch(get_partial_triple(db, subj, pred, 0, 0, &a.pattern)) | ||
|  | 	{ case 0: | ||
|  | 	    return directly_attached(pred, subj, obj) && | ||
|  | 		   unify_distance(d, 0); | ||
|  | 	  case -1: | ||
|  | 	    return FALSE; | ||
|  | 	} | ||
|  | 	is_det = PL_is_ground(obj); | ||
|  | 	target_term = obj; | ||
|  |       } else if ( !PL_is_variable(obj) )	/* obj .... subj */ | ||
|  |       {	switch(get_partial_triple(db, 0, pred, obj, 0, &a.pattern)) | ||
|  | 	{ case 0: | ||
|  | 	    return directly_attached(pred, obj, subj); | ||
|  | 	  case -1: | ||
|  | 	    return FALSE; | ||
|  | 	} | ||
|  | 	if ( a.pattern.object_is_literal ) | ||
|  | 	  return FALSE;			/* rdf_reachable(-,+,literal(...)) */ | ||
|  | 	target_term = subj; | ||
|  |       } else | ||
|  | 	return instantiation_error(subj); | ||
|  | 
 | ||
|  |       if ( !RDLOCK(db) ) | ||
|  | 	return FALSE; | ||
|  |       if ( !update_hash(db) ) | ||
|  | 	return FALSE; | ||
|  |       if ( (a.pattern.indexed & BY_S) ) 	/* subj ... */ | ||
|  | 	append_agenda(db, &a, a.pattern.subject, 0); | ||
|  |       else | ||
|  | 	append_agenda(db, &a, a.pattern.object.resource, 0); | ||
|  |       a.to_return = a.head; | ||
|  |       a.to_expand = a.head; | ||
|  | 
 | ||
|  |       while( (v=next_agenda(db, &a)) ) | ||
|  |       { if ( PL_unify_atom(target_term, v->resource) ) | ||
|  | 	{ if ( is_det )		/* mode(+, +, +) */ | ||
|  | 	  { int rc = unify_distance(d, v->distance); | ||
|  | 	    unlock_and_empty_agenda(db, &a); | ||
|  | 	    return rc; | ||
|  | 	  } else if ( unify_distance(d, v->distance) ) | ||
|  | 	  {				/* mode(+, +, -) or mode(-, +, +) */ | ||
|  | 	    agenda *ra = save_agenda(db, &a); | ||
|  | 	    inc_active_queries(db); | ||
|  | 	    DEBUG(9, Sdprintf("Saved agenta to %p\n", ra)); | ||
|  | 	    PL_retry_address(ra); | ||
|  | 	  } | ||
|  | 	} | ||
|  |       } | ||
|  |       unlock_and_empty_agenda(db, &a); | ||
|  |       return FALSE; | ||
|  |     } | ||
|  |     case PL_REDO: | ||
|  |     { agenda *a = PL_foreign_context_address(h); | ||
|  |       term_t target_term; | ||
|  |       visited *v; | ||
|  | 
 | ||
|  |       assert(a->magic == AGENDA_SAVED_MAGIC); | ||
|  | 
 | ||
|  |       if ( !PL_is_variable(subj) )	/* +, +, - */ | ||
|  | 	target_term = obj; | ||
|  |       else | ||
|  | 	target_term = subj;		/* -, +, + */ | ||
|  | 
 | ||
|  |       while( (v=next_agenda(db, a)) ) | ||
|  |       { if ( PL_unify_atom(target_term, v->resource) && | ||
|  | 	     unify_distance(d, v->distance) ) | ||
|  | 	{ assert(a->magic == AGENDA_SAVED_MAGIC); | ||
|  | 	  PL_retry_address(a); | ||
|  | 	} | ||
|  |       } | ||
|  | 
 | ||
|  |       dec_active_queries(db); | ||
|  |       unlock_and_empty_agenda(db, a); | ||
|  |       return FALSE; | ||
|  |     } | ||
|  |     case PL_CUTTED: | ||
|  |     { agenda *a = PL_foreign_context_address(h); | ||
|  | 
 | ||
|  |       DEBUG(9, Sdprintf("Cutted; agenda = %p\n", a)); | ||
|  | 
 | ||
|  |       assert(a->magic == AGENDA_SAVED_MAGIC); | ||
|  | 
 | ||
|  |       dec_active_queries(db); | ||
|  |       unlock_and_empty_agenda(db, a); | ||
|  |       return TRUE; | ||
|  |     } | ||
|  |     default: | ||
|  |       assert(0); | ||
|  |       return FALSE; | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | static foreign_t | ||
|  | rdf_reachable3(term_t subj, term_t pred, term_t obj, control_t h) | ||
|  | { return rdf_reachable(subj, pred, obj, 0, 0, h); | ||
|  | } | ||
|  | 
 | ||
|  | static foreign_t | ||
|  | rdf_reachable5(term_t subj, term_t pred, term_t obj, term_t max_d, term_t d, | ||
|  | 	       control_t h) | ||
|  | { return rdf_reachable(subj, pred, obj, max_d, d, h); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | 		 /*******************************
 | ||
|  | 		 *	     STATISTICS		* | ||
|  | 		 *******************************/ | ||
|  | 
 | ||
|  | static functor_t keys[16];		/* initialised in install_rdf_db() */ | ||
|  | 
 | ||
|  | static int | ||
|  | unify_statistics(rdf_db *db, term_t key, functor_t f) | ||
|  | { int64_t v; | ||
|  | 
 | ||
|  |   if ( f == FUNCTOR_triples1 ) | ||
|  |   { v = db->created - db->erased; | ||
|  |   } else if ( f == FUNCTOR_subjects1 ) | ||
|  |   { v = db->subjects; | ||
|  |   } else if ( f == FUNCTOR_predicates1 ) | ||
|  |   { v = db->pred_count; | ||
|  |   } else if ( f == FUNCTOR_core1 ) | ||
|  |   { v = db->core; | ||
|  |   } else if ( f == FUNCTOR_indexed8 ) | ||
|  |   { int i; | ||
|  |     term_t a = PL_new_term_ref(); | ||
|  | 
 | ||
|  |     if ( !PL_unify_functor(key, FUNCTOR_indexed8) ) | ||
|  |       return FALSE; | ||
|  |     for(i=0; i<8; i++) | ||
|  |     { if ( !PL_get_arg(i+1, key, a) || | ||
|  | 	   !PL_unify_integer(a, db->indexed[i]) ) | ||
|  | 	return FALSE; | ||
|  |     } | ||
|  | 
 | ||
|  |     return TRUE; | ||
|  |   } else if ( f == FUNCTOR_searched_nodes1 ) | ||
|  |   { v = db->agenda_created; | ||
|  |   } else if ( f == FUNCTOR_duplicates1 ) | ||
|  |   { v = db->duplicates; | ||
|  |   } else if ( f == FUNCTOR_literals1 ) | ||
|  |   { v = db->literals.count; | ||
|  |   } else if ( f == FUNCTOR_triples2 && PL_is_functor(key, f) ) | ||
|  |   { graph *src; | ||
|  |     term_t a = PL_new_term_ref(); | ||
|  |     atom_t name; | ||
|  | 
 | ||
|  |     _PL_get_arg(1, key, a); | ||
|  |     if ( !PL_get_atom(a, &name) ) | ||
|  |       return type_error(a, "atom"); | ||
|  |     if ( (src = lookup_graph(db, name, FALSE)) ) | ||
|  |       v = src->triple_count; | ||
|  |     else | ||
|  |       v = 0; | ||
|  | 
 | ||
|  |     _PL_get_arg(2, key, a); | ||
|  |     return PL_unify_int64(a, v); | ||
|  |   } else if ( f == FUNCTOR_gc2 ) | ||
|  |   { return PL_unify_term(key, | ||
|  | 			 PL_FUNCTOR, f, | ||
|  | 			   PL_INT, db->gc_count, | ||
|  | 			   PL_FLOAT, db->gc_time); 	/* time spent */ | ||
|  |   } else if ( f == FUNCTOR_rehash2 ) | ||
|  |   { return PL_unify_term(key, | ||
|  | 			 PL_FUNCTOR, f, | ||
|  | 			   PL_INT, db->rehash_count, | ||
|  | 			   PL_FLOAT, db->rehash_time); | ||
|  |   } else | ||
|  |     assert(0); | ||
|  | 
 | ||
|  |   return PL_unify_term(key, PL_FUNCTOR, f, PL_INT64, v); | ||
|  | } | ||
|  | 
 | ||
|  | static foreign_t | ||
|  | rdf_statistics(term_t key, control_t h) | ||
|  | { int n; | ||
|  |   rdf_db *db = DB; | ||
|  | 
 | ||
|  |   switch(PL_foreign_control(h)) | ||
|  |   { case PL_FIRST_CALL: | ||
|  |     { functor_t f; | ||
|  | 
 | ||
|  |       if ( PL_is_variable(key) ) | ||
|  |       { n = 0; | ||
|  | 	goto redo; | ||
|  |       } else if ( PL_get_functor(key, &f) ) | ||
|  |       { for(n=0; keys[n]; n++) | ||
|  | 	{ if ( keys[n] == f ) | ||
|  | 	    return unify_statistics(db, key, f); | ||
|  | 	} | ||
|  | 	return domain_error(key, "rdf_statistics"); | ||
|  |       } else | ||
|  | 	return type_error(key, "rdf_statistics"); | ||
|  |     } | ||
|  |     case PL_REDO: | ||
|  |       n = (int)PL_foreign_context(h); | ||
|  |     redo: | ||
|  |       unify_statistics(db, key, keys[n]); | ||
|  |       n++; | ||
|  |       if ( keys[n] ) | ||
|  | 	PL_retry(n); | ||
|  |     case PL_CUTTED: | ||
|  |       return TRUE; | ||
|  |     default: | ||
|  |       assert(0); | ||
|  |       return TRUE; | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static foreign_t | ||
|  | rdf_generation(term_t t) | ||
|  | { rdf_db *db = DB; | ||
|  | 
 | ||
|  |   return PL_unify_integer(t, db->generation); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | 		 /*******************************
 | ||
|  | 		 *	       RESET		* | ||
|  | 		 *******************************/ | ||
|  | 
 | ||
|  | static void | ||
|  | erase_triples(rdf_db *db) | ||
|  | { triple *t, *n; | ||
|  |   int i; | ||
|  | 
 | ||
|  |   for(t=db->by_none; t; t=n) | ||
|  |   { n = t->next[BY_NONE]; | ||
|  | 
 | ||
|  |     free_triple(db, t); | ||
|  |     db->freed++; | ||
|  |   } | ||
|  |   db->by_none = db->by_none_tail = NULL; | ||
|  | 
 | ||
|  |   for(i=BY_S; i<=BY_OP; i++) | ||
|  |   { if ( db->table[i] ) | ||
|  |     { int bytes = sizeof(triple*) * db->table_size[i]; | ||
|  | 
 | ||
|  |       memset(db->table[i], 0, bytes); | ||
|  |       memset(db->tail[i], 0, bytes); | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   db->created = 0; | ||
|  |   db->erased = 0; | ||
|  |   db->freed = 0; | ||
|  |   db->erased = 0; | ||
|  |   db->subjects = 0; | ||
|  |   db->rehash_count = 0; | ||
|  |   memset(db->indexed, 0, sizeof(db->indexed)); | ||
|  |   db->duplicates = 0; | ||
|  |   db->generation = 0; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static void | ||
|  | erase_predicates(rdf_db *db) | ||
|  | { predicate **ht; | ||
|  |   int i; | ||
|  | 
 | ||
|  |   for(i=0,ht = db->pred_table; i<db->pred_table_size; i++, ht++) | ||
|  |   { predicate *p, *n; | ||
|  | 
 | ||
|  |     for( p = *ht; p; p = n ) | ||
|  |     { n = p->next; | ||
|  | 
 | ||
|  |       free_list(db, &p->subPropertyOf); | ||
|  |       free_list(db, &p->siblings); | ||
|  |       if ( ++p->cloud->deleted == p->cloud->size ) | ||
|  | 	free_predicate_cloud(db, p->cloud); | ||
|  | 
 | ||
|  |       rdf_free(db, p, sizeof(*p)); | ||
|  |     } | ||
|  | 
 | ||
|  |     *ht = NULL; | ||
|  |   } | ||
|  | 
 | ||
|  |   db->pred_count = 0; | ||
|  |   db->next_hash = 0; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static void | ||
|  | reset_db(rdf_db *db) | ||
|  | { db->resetting = TRUE; | ||
|  | 
 | ||
|  |   erase_triples(db); | ||
|  |   erase_predicates(db); | ||
|  |   erase_graphs(db); | ||
|  |   db->need_update = FALSE; | ||
|  |   db->agenda_created = 0; | ||
|  |   avlfree(&db->literals); | ||
|  |   init_literal_table(db); | ||
|  | 
 | ||
|  |   db->resetting = FALSE; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static foreign_t | ||
|  | rdf_reset_db() | ||
|  | { rdf_db *db = DB; | ||
|  | 
 | ||
|  |   if ( !WRLOCK(db, FALSE) ) | ||
|  |     return FALSE; | ||
|  | 
 | ||
|  |   if ( db->tr_first ) | ||
|  |   { record_transaction(db, TR_RESET, NULL); | ||
|  |     db->tr_reset = TRUE; | ||
|  |   } else | ||
|  |     reset_db(db); | ||
|  | 
 | ||
|  |   WRUNLOCK(db); | ||
|  | 
 | ||
|  |   return TRUE; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | 		 /*******************************
 | ||
|  | 		 *	       MATCH		* | ||
|  | 		 *******************************/ | ||
|  | 
 | ||
|  | 
 | ||
|  | static foreign_t | ||
|  | match_label(term_t how, term_t search, term_t label) | ||
|  | { atom_t h, f, l; | ||
|  |   int type; | ||
|  | 
 | ||
|  |   if ( !get_atom_ex(how, &h) || | ||
|  |        !get_atom_ex(search, &f) || | ||
|  |        !get_atom_ex(label, &l) ) | ||
|  |     return FALSE; | ||
|  | 
 | ||
|  |   if ( h == ATOM_exact ) | ||
|  |     type = STR_MATCH_EXACT; | ||
|  |   else if ( h == ATOM_substring ) | ||
|  |     type = STR_MATCH_SUBSTRING; | ||
|  |   else if ( h == ATOM_word ) | ||
|  |     type = STR_MATCH_WORD; | ||
|  |   else if ( h == ATOM_prefix ) | ||
|  |     type = STR_MATCH_PREFIX; | ||
|  |   else if ( h == ATOM_like ) | ||
|  |     type = STR_MATCH_LIKE; | ||
|  |   else | ||
|  |     return domain_error(how, "search_method"); | ||
|  | 
 | ||
|  |   return match_atoms(type, f, l); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | static foreign_t | ||
|  | lang_matches(term_t lang, term_t pattern) | ||
|  | { atom_t l, p; | ||
|  | 
 | ||
|  |   if ( !get_atom_ex(lang, &l) || | ||
|  |        !get_atom_ex(pattern, &p) ) | ||
|  |     return FALSE; | ||
|  | 
 | ||
|  |   return atom_lang_matches(l, p); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | 
 | ||
|  | 
 | ||
|  | 		 /*******************************
 | ||
|  | 		 *	       VERSION		* | ||
|  | 		 *******************************/ | ||
|  | 
 | ||
|  | static foreign_t | ||
|  | rdf_version(term_t v) | ||
|  | { return PL_unify_integer(v, RDF_VERSION); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | 		 /*******************************
 | ||
|  | 		 *	     MORE STUFF		* | ||
|  | 		 *******************************/ | ||
|  | 
 | ||
|  | #include "quote.c"
 | ||
|  | 
 | ||
|  | 		 /*******************************
 | ||
|  | 		 *	     REGISTER		* | ||
|  | 		 *******************************/ | ||
|  | 
 | ||
|  | #define MKFUNCTOR(n, a) \
 | ||
|  | 	FUNCTOR_ ## n ## a = PL_new_functor(PL_new_atom(#n), a) | ||
|  | #define NDET PL_FA_NONDETERMINISTIC
 | ||
|  | #define META PL_FA_TRANSPARENT
 | ||
|  | 
 | ||
|  | install_t | ||
|  | install_rdf_db() | ||
|  | { int i=0; | ||
|  |   extern install_t install_atom_map(void); | ||
|  | 
 | ||
|  |   MKFUNCTOR(literal, 1); | ||
|  |   MKFUNCTOR(error, 2); | ||
|  |   MKFUNCTOR(type_error, 2); | ||
|  |   MKFUNCTOR(domain_error, 2); | ||
|  |   MKFUNCTOR(triples, 1); | ||
|  |   MKFUNCTOR(triples, 2); | ||
|  |   MKFUNCTOR(subjects, 1); | ||
|  |   MKFUNCTOR(predicates, 1); | ||
|  |   MKFUNCTOR(subject, 1); | ||
|  |   MKFUNCTOR(predicate, 1); | ||
|  |   MKFUNCTOR(object, 1); | ||
|  |   MKFUNCTOR(graph, 1); | ||
|  |   MKFUNCTOR(indexed, 8); | ||
|  |   MKFUNCTOR(exact, 1); | ||
|  |   MKFUNCTOR(plain, 1); | ||
|  |   MKFUNCTOR(substring, 1); | ||
|  |   MKFUNCTOR(word, 1); | ||
|  |   MKFUNCTOR(prefix, 1); | ||
|  |   MKFUNCTOR(like, 1); | ||
|  |   MKFUNCTOR(literal, 2); | ||
|  |   MKFUNCTOR(searched_nodes, 1); | ||
|  |   MKFUNCTOR(duplicates, 1); | ||
|  |   MKFUNCTOR(literals, 1); | ||
|  |   MKFUNCTOR(symmetric, 1); | ||
|  |   MKFUNCTOR(transitive, 1); | ||
|  |   MKFUNCTOR(inverse_of, 1); | ||
|  |   MKFUNCTOR(lang, 2); | ||
|  |   MKFUNCTOR(type, 2); | ||
|  |   MKFUNCTOR(rdf_subject_branch_factor, 1); | ||
|  |   MKFUNCTOR(rdf_object_branch_factor, 1); | ||
|  |   MKFUNCTOR(rdfs_subject_branch_factor, 1); | ||
|  |   MKFUNCTOR(rdfs_object_branch_factor, 1); | ||
|  |   MKFUNCTOR(gc, 2); | ||
|  |   MKFUNCTOR(rehash, 2); | ||
|  |   MKFUNCTOR(core, 1); | ||
|  |   MKFUNCTOR(assert, 4); | ||
|  |   MKFUNCTOR(retract, 4); | ||
|  |   MKFUNCTOR(update, 5); | ||
|  |   MKFUNCTOR(new_literal, 1); | ||
|  |   MKFUNCTOR(old_literal, 1); | ||
|  |   MKFUNCTOR(transaction, 2); | ||
|  |   MKFUNCTOR(load, 2); | ||
|  |   MKFUNCTOR(rehash, 1); | ||
|  |   MKFUNCTOR(begin, 1); | ||
|  |   MKFUNCTOR(end, 1); | ||
|  | 
 | ||
|  |   FUNCTOR_colon2 = PL_new_functor(PL_new_atom(":"), 2); | ||
|  | 
 | ||
|  |   ATOM_user	     = PL_new_atom("user"); | ||
|  |   ATOM_exact	     = PL_new_atom("exact"); | ||
|  |   ATOM_plain	     = PL_new_atom("plain"); | ||
|  |   ATOM_prefix	     = PL_new_atom("prefix"); | ||
|  |   ATOM_like	     = PL_new_atom("like"); | ||
|  |   ATOM_substring     = PL_new_atom("substring"); | ||
|  |   ATOM_word	     = PL_new_atom("word"); | ||
|  |   ATOM_subPropertyOf = PL_new_atom(URL_subPropertyOf); | ||
|  |   ATOM_error	     = PL_new_atom("error"); | ||
|  |   ATOM_begin	     = PL_new_atom("begin"); | ||
|  |   ATOM_end	     = PL_new_atom("end"); | ||
|  |   ATOM_infinite	     = PL_new_atom("infinite"); | ||
|  | 
 | ||
|  |   PRED_call1         = PL_predicate("call", 1, "user"); | ||
|  | 
 | ||
|  | 					/* statistics */ | ||
|  |   keys[i++] = FUNCTOR_triples1; | ||
|  |   keys[i++] = FUNCTOR_subjects1; | ||
|  |   keys[i++] = FUNCTOR_indexed8; | ||
|  |   keys[i++] = FUNCTOR_predicates1; | ||
|  |   keys[i++] = FUNCTOR_searched_nodes1; | ||
|  |   keys[i++] = FUNCTOR_duplicates1; | ||
|  |   keys[i++] = FUNCTOR_literals1; | ||
|  |   keys[i++] = FUNCTOR_triples2; | ||
|  |   keys[i++] = FUNCTOR_gc2; | ||
|  |   keys[i++] = FUNCTOR_rehash2; | ||
|  |   keys[i++] = FUNCTOR_core1; | ||
|  |   keys[i++] = 0; | ||
|  | 
 | ||
|  | 					/* setup the database */ | ||
|  |   DB = new_db(); | ||
|  | 
 | ||
|  |   PL_register_foreign("rdf_version",    1, rdf_version,     0); | ||
|  |   PL_register_foreign("rdf_assert",	3, rdf_assert3,	    0); | ||
|  |   PL_register_foreign("rdf_assert",	4, rdf_assert4,	    0); | ||
|  |   PL_register_foreign("rdf_update",	4, rdf_update,      0); | ||
|  |   PL_register_foreign("rdf_update",	5, rdf_update5,     0); | ||
|  |   PL_register_foreign("rdf_retractall",	3, rdf_retractall3, 0); | ||
|  |   PL_register_foreign("rdf_retractall",	4, rdf_retractall4, 0); | ||
|  |   PL_register_foreign("rdf_subject",	1, rdf_subject,	    NDET); | ||
|  |   PL_register_foreign("rdf",		3, rdf3,	    NDET); | ||
|  |   PL_register_foreign("rdf",		4, rdf4,	    NDET); | ||
|  |   PL_register_foreign("rdf_has",	4, rdf_has,	    NDET); | ||
|  |   PL_register_foreign("rdf_statistics_",1, rdf_statistics,  NDET); | ||
|  |   PL_register_foreign("rdf_generation", 1, rdf_generation,  0); | ||
|  |   PL_register_foreign("rdf_match_label",3, match_label,     0); | ||
|  |   PL_register_foreign("rdf_save_db_",   2, rdf_save_db,     0); | ||
|  |   PL_register_foreign("rdf_load_db_",   3, rdf_load_db,     0); | ||
|  |   PL_register_foreign("rdf_reachable",  3, rdf_reachable3,  NDET); | ||
|  |   PL_register_foreign("rdf_reachable",  5, rdf_reachable5,  NDET); | ||
|  |   PL_register_foreign("rdf_reset_db_",  0, rdf_reset_db,    0); | ||
|  |   PL_register_foreign("rdf_set_predicate", | ||
|  | 					2, rdf_set_predicate, 0); | ||
|  |   PL_register_foreign("rdf_predicate_property_", | ||
|  | 					2, rdf_predicate_property, NDET); | ||
|  |   PL_register_foreign("rdf_current_predicates", | ||
|  | 					1, rdf_current_predicates, 0); | ||
|  |   PL_register_foreign("rdf_current_literal", | ||
|  | 					1, rdf_current_literal, NDET); | ||
|  |   PL_register_foreign("rdf_graphs_",    1, rdf_graphs,      0); | ||
|  |   PL_register_foreign("rdf_set_graph_source", 3, rdf_set_graph_source, 0); | ||
|  |   PL_register_foreign("rdf_unset_graph_source", 1, rdf_unset_graph_source, 0); | ||
|  |   PL_register_foreign("rdf_graph_source_", 3, rdf_graph_source, 0); | ||
|  |   PL_register_foreign("rdf_estimate_complexity", | ||
|  | 					4, rdf_estimate_complexity, 0); | ||
|  |   PL_register_foreign("rdf_transaction_",2, rdf_transaction, META); | ||
|  |   PL_register_foreign("rdf_active_transactions_", | ||
|  | 					1, rdf_active_transactions, 0); | ||
|  |   PL_register_foreign("rdf_monitor_",   2, rdf_monitor,     META); | ||
|  | /*PL_register_foreign("rdf_broadcast_", 2, rdf_broadcast,   0);*/ | ||
|  | #ifdef WITH_MD5
 | ||
|  |   PL_register_foreign("rdf_md5",	2, rdf_md5,	    0); | ||
|  |   PL_register_foreign("rdf_atom_md5",	3, rdf_atom_md5,    0); | ||
|  | #endif
 | ||
|  |   PL_register_foreign("rdf_quote_uri",	2, rdf_quote_uri,   0); | ||
|  | 
 | ||
|  | #ifdef O_DEBUG
 | ||
|  |   PL_register_foreign("rdf_debug",      1, rdf_debug,       0); | ||
|  |   PL_register_foreign("rdf_print_predicate_cloud", 1, rdf_print_predicate_cloud, 0); | ||
|  | #endif
 | ||
|  | #ifdef O_SECURE
 | ||
|  |   PL_register_foreign("rdf_dump_literals", 0, dump_literals, 0); | ||
|  |   PL_register_foreign("rdf_check_literals", 0, check_transitivity, 0); | ||
|  | #endif
 | ||
|  |   PL_register_foreign("lang_matches", 2, lang_matches, 0); | ||
|  | 
 | ||
|  |   install_atom_map(); | ||
|  | } |