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