beagle r4421 - in branches/beagle-rdf/Util: . SemWeb



Author: dbera
Date: Sat Jan 26 23:26:03 2008
New Revision: 4421
URL: http://svn.gnome.org/viewvc/beagle?rev=4421&view=rev

Log:
Update Semweb to the latest version. Remove Euler.cs from source since it is under GPL.


Added:
   branches/beagle-rdf/Util/SemWeb/GraphMatch.cs
   branches/beagle-rdf/Util/SemWeb/Interfaces.cs
   branches/beagle-rdf/Util/SemWeb/SparqlClient.cs
Removed:
   branches/beagle-rdf/Util/SemWeb/RSquary.cs
   branches/beagle-rdf/Util/SemWeb/Remote.cs
Modified:
   branches/beagle-rdf/Util/Makefile.am
   branches/beagle-rdf/Util/SemWeb/Algos.cs
   branches/beagle-rdf/Util/SemWeb/AssemblyInfo.cs
   branches/beagle-rdf/Util/SemWeb/Inference.cs
   branches/beagle-rdf/Util/SemWeb/LiteralFilters.cs
   branches/beagle-rdf/Util/SemWeb/MemoryStore.cs
   branches/beagle-rdf/Util/SemWeb/N3Reader.cs
   branches/beagle-rdf/Util/SemWeb/N3Writer.cs
   branches/beagle-rdf/Util/SemWeb/NamespaceManager.cs
   branches/beagle-rdf/Util/SemWeb/Query.cs
   branches/beagle-rdf/Util/SemWeb/RDFS.cs
   branches/beagle-rdf/Util/SemWeb/RdfReader.cs
   branches/beagle-rdf/Util/SemWeb/RdfWriter.cs
   branches/beagle-rdf/Util/SemWeb/RdfXmlReader.cs
   branches/beagle-rdf/Util/SemWeb/RdfXmlWriter.cs
   branches/beagle-rdf/Util/SemWeb/Resource.cs
   branches/beagle-rdf/Util/SemWeb/SQLStore.cs
   branches/beagle-rdf/Util/SemWeb/Statement.cs
   branches/beagle-rdf/Util/SemWeb/Store.cs
   branches/beagle-rdf/Util/SemWeb/Util.cs

Modified: branches/beagle-rdf/Util/Makefile.am
==============================================================================
--- branches/beagle-rdf/Util/Makefile.am	(original)
+++ branches/beagle-rdf/Util/Makefile.am	Sat Jan 26 23:26:03 2008
@@ -104,6 +104,7 @@
 	$(srcdir)/SemWeb/UriMap.cs              \
 	$(srcdir)/SemWeb/Resource.cs            \
 	$(srcdir)/SemWeb/Statement.cs           \
+	$(srcdir)/SemWeb/Interfaces.cs		\
 	$(srcdir)/SemWeb/Store.cs               \
 	$(srcdir)/SemWeb/MemoryStore.cs         \
 	$(srcdir)/SemWeb/SQLStore.cs            \
@@ -113,16 +114,22 @@
 	$(srcdir)/SemWeb/RdfWriter.cs           \
 	$(srcdir)/SemWeb/RdfXmlWriter.cs        \
 	$(srcdir)/SemWeb/N3Writer.cs            \
-	$(srcdir)/SemWeb/RSquary.cs             \
+	$(srcdir)/SemWeb/Query.cs	        \
 	$(srcdir)/SemWeb/LiteralFilters.cs      \
 	$(srcdir)/SemWeb/Query.cs               \
+	$(srcdir)/SemWeb/GraphMatch.cs		\
+	$(srcdir)/SemWeb/LiteralFilters.cs	\
 	$(srcdir)/SemWeb/Inference.cs           \
 	$(srcdir)/SemWeb/RDFS.cs                \
+	$(srcdir)/SemWeb/SpecialRelations.cs	\
 	$(srcdir)/SemWeb/Algos.cs               \
-	$(srcdir)/SemWeb/Remote.cs              \
+	$(srcdir)/SemWeb/SparqlClient.cs	\
 	$(srcdir)/SemWeb/XPathSemWebNavigator.cs\
 	$(srcdir)/PropertyKeywordAttribute.cs
 
+# Semweb compile flag
+CSFLAGS += -define:DOTNET2
+
 UTIL_ASSEMBLIES =				\
 	$(BEAGLED_LIBS)				\
 	-r:System.Data				\

Modified: branches/beagle-rdf/Util/SemWeb/Algos.cs
==============================================================================
--- branches/beagle-rdf/Util/SemWeb/Algos.cs	(original)
+++ branches/beagle-rdf/Util/SemWeb/Algos.cs	Sat Jan 26 23:26:03 2008
@@ -14,6 +14,9 @@
 			this.b = b;
 		}
 		public bool Distinct { get { return a.Distinct; } }
+		public bool Contains(Resource resource) {
+			return a.Contains(resource) || b.Contains(resource);
+		}
 		public bool Contains(Statement template) {
 			return Store.DefaultContains(this, template);
 		}
@@ -125,7 +128,7 @@
 				// against the whole store, rather than the MSG in
 				// isolation.  But that gets much too expensive.
 				MemoryStore msgremoved = new MemoryStore();
-				MakeLeanMSG(msg, msgg.GetBNodes(), msgremoved);
+				MakeLeanMSG(new Store(msg), msgg.GetBNodes(), msgremoved);
 				
 				// Whatever was removed from msg, remove it from the main graph.
 				store.RemoveAll(msgremoved.ToArray());
@@ -142,7 +145,7 @@
 				// The GraphMatch will treat all blank nodes in
 				// msg as variables.
 				GraphMatch match = new GraphMatch(msg);
-				QueryResultBufferSink sink = new QueryResultBufferSink();
+				QueryResultBuffer sink = new QueryResultBuffer();
 				match.Run(new SubtractionSource(store, msg), sink);
 				if (sink.Bindings.Count > 0) {
 					// This MSG can be removed.
@@ -508,7 +511,7 @@
 			return ret;
 		}
 		
-		public static void FindMSG(SelectableSource store, Entity node, Store msg) {
+		public static void FindMSG(SelectableSource store, Entity node, StatementSink msg) {
 			if (node.Uri != null) throw new ArgumentException("node must be anonymous");
 			
 			ResSet nodesSeen = new ResSet();
@@ -532,14 +535,14 @@
 		}
 		
 		private class Sink : StatementSink {
-			Store msg;
+			StatementSink msg;
 			ResSet add;
-			public Sink(Store msg, ResSet add) {
+			public Sink(StatementSink msg, ResSet add) {
 				this.msg = msg;
 				this.add = add;
 			}
 			public bool Add(Statement s) {
-				if (msg.Contains(s)) return true;
+				if (msg is SelectableSource && ((SelectableSource)msg).Contains(s)) return true;
 				msg.Add(s);
 				if (s.Subject.Uri == null) add.Add(s.Subject);
 				if (s.Predicate.Uri == null) add.Add(s.Predicate);

Modified: branches/beagle-rdf/Util/SemWeb/AssemblyInfo.cs
==============================================================================
--- branches/beagle-rdf/Util/SemWeb/AssemblyInfo.cs	(original)
+++ branches/beagle-rdf/Util/SemWeb/AssemblyInfo.cs	Sat Jan 26 23:26:03 2008
@@ -6,11 +6,11 @@
 [assembly: AssemblyConfiguration("")]
 [assembly: AssemblyCompany("")]
 [assembly: AssemblyProduct("")]
-[assembly: AssemblyCopyright("Copyright (c) 2006 Joshua Tauberer <tauberer for net>")]
+[assembly: AssemblyCopyright("Copyright (c) 2007 Joshua Tauberer <http://razor.occams.info>")]
 [assembly: AssemblyTrademark("")]
 [assembly: AssemblyCulture("")]		
 
-[assembly: AssemblyVersion("0.7.1.0")]
+[assembly: AssemblyVersion("1.0.2.1")]
 [assembly: AssemblyDelaySign(false)]
 [assembly: AssemblyKeyFile("")]
 [assembly: AssemblyKeyName("")]

Added: branches/beagle-rdf/Util/SemWeb/GraphMatch.cs
==============================================================================
--- (empty file)
+++ branches/beagle-rdf/Util/SemWeb/GraphMatch.cs	Sat Jan 26 23:26:03 2008
@@ -0,0 +1,640 @@
+using System;
+using System.IO;
+
+#if !DOTNET2
+using System.Collections;
+#else
+using System.Collections.Generic;
+#endif
+
+using SemWeb;
+using SemWeb.Filters;
+using SemWeb.Stores;
+using SemWeb.Util;
+
+#if !DOTNET2
+using VariableList = System.Collections.ArrayList;
+using BindingList = System.Collections.ArrayList;
+using VarKnownValuesType = System.Collections.Hashtable;
+using VarKnownValuesType2 = System.Collections.IDictionary;
+using LitFilterList = System.Collections.ArrayList;
+using LitFilterMap = System.Collections.Hashtable;
+using LitFilterMap2 = System.Collections.IDictionary;
+#else
+using VariableList = System.Collections.Generic.List<SemWeb.Variable>;
+using BindingList = System.Collections.Generic.List<SemWeb.Query.VariableBindings>;
+using VarKnownValuesType = System.Collections.Generic.Dictionary<SemWeb.Variable,System.Collections.Generic.ICollection<SemWeb.Resource>>;
+using VarKnownValuesType2 = System.Collections.Generic.IDictionary<SemWeb.Variable,System.Collections.Generic.ICollection<SemWeb.Resource>>;
+using LitFilterList = System.Collections.Generic.List<SemWeb.LiteralFilter>;
+using LitFilterMap = System.Collections.Generic.Dictionary<SemWeb.Variable,System.Collections.Generic.ICollection<SemWeb.LiteralFilter>>;
+using LitFilterMap2 = System.Collections.Generic.IDictionary<SemWeb.Variable,System.Collections.Generic.ICollection<SemWeb.LiteralFilter>>;
+#endif
+
+namespace SemWeb.Query {
+
+	public class GraphMatch : Query {
+		private static Entity qLimit = "http://purl.oclc.org/NET/rsquary/returnLimit";;
+		private static Entity qStart = "http://purl.oclc.org/NET/rsquary/returnStart";;
+		private static Entity qDistinctFrom = "http://purl.oclc.org/NET/rsquary/distinctFrom";;
+		private static Entity qOptional = "http://purl.oclc.org/NET/rsquary/optional";;
+		
+		StatementList graph = new StatementList();
+		VarKnownValuesType2 knownValues = new VarKnownValuesType();
+		LitFilterMap litFilters = new LitFilterMap();
+		VariableList distinguishedVars = new VariableList();
+	
+		public GraphMatch() {
+		}
+		
+		public GraphMatch(RdfReader query) :
+			this(new Store(query),
+				query.BaseUri == null ? null : new Entity(query.BaseUri)) {
+		}
+
+		public GraphMatch(StatementSource queryModel) : this(new Store(queryModel), null) {
+		}
+		
+		private GraphMatch(Store queryModel, Entity queryNode) {
+			// Find the query options
+			if (queryNode != null) {
+				ReturnStart = GetIntOption(queryModel, queryNode, qStart);
+				ReturnLimit = GetIntOption(queryModel, queryNode, qLimit);
+			}
+
+			foreach (Statement s in queryModel.Select(Statement.All)) {
+				if (IsQueryPredicate(s.Predicate)) continue;
+				
+				if (s.Meta == Statement.DefaultMeta)
+					AddGraphStatement(s);
+				else
+					throw new NotSupportedException("Subgraphs (meta statement relations) are not supported.");
+			}
+		}
+		
+		private int GetIntOption(Store queryModel, Entity query, Entity predicate) {
+			Resource[] rr = queryModel.SelectObjects(query, predicate);
+			if (rr.Length == 0) return -1;
+			Resource r = rr[0];
+			if (r == null || !(r is Literal)) return -1;
+			try {
+				return int.Parse(((Literal)r).Value);
+			} catch (Exception) {
+				return -1;
+			}
+		}		
+
+		private bool IsQueryPredicate(Entity e) {
+			if (e == qDistinctFrom) return true;
+			if (e == qLimit) return true;
+			if (e == qStart) return true;
+			if (e == qOptional) return true;
+			return false;
+		}
+		
+		public override string GetExplanation() {
+			string ret = "Query:\n";
+			foreach (Statement s in graph)
+				ret += " " + s + "\n";
+			return ret;
+		}
+		
+		public void AddGraphStatement(Statement statement) {
+			graph.Add(statement);
+		}
+		
+		#if !DOTNET2
+		public void SetVariableRange(Variable variable, ICollection range) {
+		#else
+		public void SetVariableRange(Variable variable, ICollection<Resource> range) {
+		#endif
+			knownValues[variable] = range;
+		}
+		
+		public void AddLiteralFilter(Variable variable, LiteralFilter filter) {
+			if (!litFilters.ContainsKey(variable))
+				litFilters[variable] = new LitFilterList();
+			((LitFilterList)litFilters[variable]).Add(filter);
+		}
+		
+		public void SetDistinguishedVariable(Variable variable) {
+			distinguishedVars.Add(variable);
+		}
+		
+		public override void Run(SelectableSource targetModel, QueryResultSink result) {
+			QueryPart[] parts = new QueryPart[graph.Count];
+			for (int i = 0; i < graph.Count; i++)
+				parts[i] = new QueryPart(graph[i], targetModel);
+			
+			RunGeneralQuery(parts, knownValues, litFilters, distinguishedVars.Count == 0 ? null : distinguishedVars, ReturnStart, ReturnLimit, false, result);
+		}
+		
+		internal struct QueryPart {
+			public readonly Statement[] Graph;
+			public readonly SelectableSource[] Sources;
+			
+			public QueryPart(Statement s, SelectableSource src) {
+				Graph = new Statement[] { s };
+				Sources = new SelectableSource[] { src };
+			}
+
+			public QueryPart(Statement s, SelectableSource[] sources) {
+				Graph = new Statement[] { s };
+				Sources = sources;
+			}
+
+			public QueryPart(Statement[] graph, QueryableSource src) {
+				Graph = graph;
+				Sources = new SelectableSource[] { src };
+			}
+		}
+		
+		struct BindingSet {
+			public Variable[] Variables;
+			public BindingList Rows;
+		}
+		
+		internal static void RunGeneralQuery(QueryPart[] queryParts,
+				VarKnownValuesType2 knownValues, LitFilterMap2 litFilters,
+				#if !DOTNET2
+				ICollection distinguishedVars,
+				#else
+				ICollection<Variable> distinguishedVars,
+				#endif
+				int returnStart, int returnLimit,
+				bool allowQueryableSource,
+				QueryResultSink result) {
+				
+			BindingSet bindings;
+			
+			// We use a sort of adaptive limiting technique in
+			// queries involving intersection. If a limit on
+			// A & B is specified, it is obviously incorrect to
+			// limit A and B separately, since the parts that
+			// intersect may be not at the beginning of either
+			// of A and B. However, in many cases such a limit
+			// is possible, and so we try the limits. But if
+			// that limit doesn't yield enough results, we back
+			// off and use a much larger limit the next time.
+			// Note that when the query involves no intersection,
+			// it *is* correct to pass the limit down, and so
+			// we never need to do a back-off in that case.
+			
+			int adaptiveLimitMultiplier = 1;
+			
+			// If intersection is involved in this query, then
+			// it's likely there will be some holes in the data
+			// and we should query more than the final limit
+			// from each source to start with. But if there's just
+			// one query part, we keep the multiplier at 1 because
+			// we want to pass it down.
+			if (queryParts.Length > 1)
+				adaptiveLimitMultiplier = 2;
+			
+			while (true) {
+			
+			bool adaptiveLimitDidLimit = false;
+			int localLimit = returnLimit * adaptiveLimitMultiplier;
+			
+			bindings = new BindingSet();
+			bindings.Variables = new Variable[0];
+			bindings.Rows = new BindingList();
+			bindings.Rows.Add(null); // we need a dummy row for the first intersection
+			
+			for (int iPart = 0; iPart < queryParts.Length; iPart++) {
+				QueryPart part = queryParts[iPart];
+			
+				// Get the statements in the target model that match this aspect of the query graph.
+				
+				// get a list of values already found for each variable
+				System.Collections.Hashtable foundValues = new System.Collections.Hashtable();
+				foreach (Variable v in bindings.Variables)
+					foundValues[v] = new ResSet();
+				foreach (VariableBindings row in bindings.Rows) {
+					if (row == null) continue; // null in first round
+					for (int i = 0; i < row.Count; i++)
+						((ResSet)foundValues[row.Variables[i]]).Add(row.Values[i]);
+				}
+				foreach (Variable v in bindings.Variables)
+					foundValues[v] = ((ResSet)foundValues[v]).ToArray();
+				
+				// matches holds the bindings that match this part of the query
+				BindingList matches;
+
+				// vars holds an ordered list of variables found in this part of the query
+				Variable[] vars;
+				
+				// Get a set of variables that we care about.  These are distinguished variables
+				// in the query plus any variables that we will encounter in a future queryPart.
+				// Any other variables are useless to us at this point and we will not do any
+				// duplicate row tests based on them.
+				ResSet interestingVariables = null;
+				if (distinguishedVars != null) {
+					interestingVariables = new ResSet();
+					interestingVariables.AddRange(distinguishedVars);
+					for (int jPart = iPart+1; jPart < queryParts.Length; jPart++) {
+						foreach (Statement s in queryParts[jPart].Graph) {
+							for (int jc = 0; jc < 4; jc++) {
+								if (s.GetComponent(jc) is Variable)
+									interestingVariables.Add(s.GetComponent(jc));
+							}
+						}
+					}
+				}
+				
+				// A QueryPart can either be:
+				//	A single statement to match against one or more SelectableSources, or one or more QueryableSources
+				//  A graph of statements to match against a single QueryableSource
+				
+				bool allSourcesQueryable = true;
+				foreach (SelectableSource source in part.Sources)
+					if (!(source is QueryableSource))
+						allSourcesQueryable = false;
+				
+				if (!allowQueryableSource || !allSourcesQueryable) {
+					Statement s = part.Graph[0];
+					
+					matches = new BindingList();
+					VariableList varCollector = new VariableList();
+					
+					// get a list of variables in this part
+					// the filter will have null components for variables, except
+					// for variables with known values, we plug those values in
+					SelectFilter f = new SelectFilter(s);
+					for (int i = 0; i < 4; i++) {
+						Resource r = s.GetComponent(i);
+					
+						if (r is Variable) {
+							Variable v = (Variable)r;
+							
+							if (!varCollector.Contains(v))
+								varCollector.Add(v);
+							
+							Resource[] values = null;
+							#if DOTNET2
+							if (foundValues.ContainsKey(v))
+							#endif
+								values = (Resource[])foundValues[v];
+							if (values == null && 
+							#if !DOTNET2
+							knownValues[v] != null
+							#else
+							knownValues.ContainsKey(v)
+							#endif
+							)
+								#if !DOTNET2
+								values = (Resource[])new ArrayList((ICollection)knownValues[v]).ToArray(typeof(Resource));
+								#else
+								values = new List<Resource>(knownValues[v]).ToArray();
+								#endif
+							
+							if (values == null) {
+								f.SetComponent(i, null);
+							} else if (i != 2) {
+								bool fail = false;
+								f.SetComponent(i, ToEntArray(values, ref fail));
+								if (fail) return;
+							} else {
+								f.SetComponent(i, values);
+							}
+						}
+					}
+					
+					#if !DOTNET2
+					if (litFilters != null && s.Object is Variable && litFilters[(Variable)s.Object] != null)
+						f.LiteralFilters = (LiteralFilter[])((LitFilterList)litFilters[(Variable)s.Object]).ToArray(typeof(LiteralFilter));
+					#else
+					if (litFilters != null && s.Object is Variable && litFilters.ContainsKey((Variable)s.Object))
+						f.LiteralFilters = ((LitFilterList)litFilters[(Variable)s.Object]).ToArray();
+					#endif
+					
+					vars = new Variable[varCollector.Count];
+					varCollector.CopyTo(vars, 0);
+
+					// get the matching statements; but if a variable was used twice in s,
+					// filter out matching statements that don't respect that (since that info
+					// was lost in the SelectFilter).
+					foreach (SelectableSource source in part.Sources) {
+						if (localLimit > 0) {
+							f.Limit = localLimit - matches.Count;
+							if (f.Limit <= 0)
+								break;
+						}
+						source.Select(f, new Filter(matches, s, vars));
+					}
+						
+					result.AddComments("SELECT: " + f + " => " + matches.Count);
+				
+				} else {
+					if (part.Sources.Length == 0)
+						throw new InvalidOperationException();
+					
+					// build a query
+					
+					QueryOptions opts = new QueryOptions();
+					
+					if (knownValues != null) {
+						foreach (Variable v in knownValues.Keys)
+							#if !DOTNET2
+							opts.SetVariableKnownValues(v, (System.Collections.ICollection)knownValues[v]);
+							#else
+							opts.SetVariableKnownValues(v, knownValues[v]);
+							#endif
+					}
+					foreach (Variable v in foundValues.Keys)
+						if (foundValues[v] != null)
+							opts.SetVariableKnownValues(v, (Resource[])foundValues[v]);
+					if (litFilters != null)
+						foreach (Variable v in litFilters.Keys)
+							if (litFilters[v] != null)
+								foreach (LiteralFilter f in (System.Collections.ICollection)litFilters[v]) 
+									opts.AddLiteralFilter(v, f);
+					
+					// The distinguished variables for this part are any that are distinguished for
+					// the query plus any that we need to tie these results to past and future
+					// bindings (any variable used previously or used later and used in this query).
+					if (distinguishedVars != null) {
+						VariableList dvars = new VariableList();
+						dvars.AddRange(distinguishedVars);
+						for (int jPart = 0; jPart < queryParts.Length; jPart++) {
+							if (jPart == iPart) continue;
+							foreach (Statement s in queryParts[jPart].Graph) {
+								for (int jc = 0; jc < 4; jc++) {
+									if (s.GetComponent(jc) is Variable) // don't bother checking if it's actually used in this query part
+										dvars.Add((Variable)s.GetComponent(jc));
+								}
+							}
+						}
+						opts.DistinguishedVariables = dvars;
+					}
+					
+					vars = null;
+					matches = null;
+					foreach (QueryableSource source in part.Sources) {
+						if (localLimit > 0) {
+							opts.Limit = localLimit - (matches == null ? 0 : matches.Count);
+							if (opts.Limit <= 0)
+								break;
+						}
+
+						QueryResultBuffer partsink = new QueryResultBuffer();
+						source.Query(part.Graph, opts, partsink);
+
+						foreach (string comment in partsink.Comments)
+							result.AddComments(source.ToString() + ": " + comment);
+							
+						if (vars == null) {
+							vars = new Variable[partsink.Variables.Length];
+							partsink.Variables.CopyTo(vars, 0);	
+							#if !DOTNET2
+							matches = new BindingList(partsink.Bindings);
+							#else
+							matches = partsink.Bindings;
+							#endif
+						} else {
+							// add in the bindings from this query, but the variables might
+							// be in a different order this time
+							foreach (VariableBindings b in partsink.Bindings) {
+								Resource[] vals = new Resource[vars.Length];
+								for (int i = 0; i < vars.Length; i++)
+									vals[i] = b[vars[i]];
+								VariableBindings c = new VariableBindings(vars, vals);
+								matches.Add(c);
+							}
+						}
+					}
+					
+					string qs = "";
+					foreach (Statement s in part.Graph)
+						qs += "\n\t" + s;
+
+					result.AddComments("QUERY: " + qs + "  => " + matches.Count);
+					
+					// If we got back at least as many rows as our local (adaptive) limit,
+					// then we know that the limiting (possibly) had an effect and we may
+					// need to loop again to get all of the rows.
+					if (matches.Count >= localLimit)
+						adaptiveLimitDidLimit = true;
+				}
+				
+				// Intersect the existing bindings with the new matches.
+				
+				// get a list of variables the old and new have in common
+				int nCommonVars;
+				int[,] commonVars = IntersectVariables(bindings.Variables, vars, out nCommonVars);
+				
+				BindingSet newbindings = new BindingSet();
+				
+				newbindings.Variables = new Variable[bindings.Variables.Length + vars.Length - nCommonVars];
+				bindings.Variables.CopyTo(newbindings.Variables, 0);
+				int ctr = bindings.Variables.Length;
+				int[] newindexes = new int[vars.Length];
+				for (int i = 0; i < vars.Length; i++) {
+					if (Array.IndexOf(newbindings.Variables, vars[i]) == -1) {
+						newbindings.Variables[ctr] = vars[i];
+						newindexes[i] = ctr;
+						ctr++;
+					} else {
+						newindexes[i] = -1;
+					}
+				}
+				newbindings.Rows = new BindingList();
+				
+				int nMatches = 0;
+
+				if (nCommonVars == 0) {
+					// no variables in common, make a cartesian product of the bindings
+					foreach (VariableBindings left in bindings.Rows) {
+						for (int i = 0; i < matches.Count; i++) {
+							VariableBindings right = (VariableBindings)matches[i];
+							
+							Resource[] newValues = new Resource[newbindings.Variables.Length];
+							if (left != null) // null on first intersection
+								left.Values.CopyTo(newValues, 0);
+							right.Values.CopyTo(newValues, bindings.Variables.Length);
+							
+							nMatches++;
+							if (!quickDupCheckIsDup(newbindings, newValues, nMatches, interestingVariables))
+								newbindings.Rows.Add(new VariableBindings(newbindings.Variables, newValues));
+						}
+					}
+				
+				} else {
+					// index the new matches by those variables
+				
+					System.Collections.Hashtable indexedMatches = new System.Collections.Hashtable();
+					foreach (VariableBindings right in matches) {
+						// traverse down the list of common variables making a tree
+						// structure indexing the matches by their variable values
+						System.Collections.Hashtable hash = indexedMatches;
+						for (int i = 0; i < nCommonVars; i++) {
+							Resource value = right.Values[commonVars[i, 1]];
+							if (i < nCommonVars - 1) {
+								if (hash[value] == null)
+									hash[value] = new System.Collections.Hashtable();
+								hash = (System.Collections.Hashtable)hash[value];
+							} else {
+								if (hash[value] == null)
+									hash[value] = new BindingList();
+								BindingList list = (BindingList)hash[value];
+								list.Add(right);
+							}
+						}
+					}
+					
+					// for each existing binding, find all of the new matches
+					// that match the common variables, by traversing the index tree
+					foreach (VariableBindings left in bindings.Rows) {
+						System.Collections.Hashtable hash = indexedMatches;
+						BindingList list = null;
+						
+						for (int i = 0; i < nCommonVars; i++) {
+							Resource value = left.Values[commonVars[i, 0]];
+							if (hash[value] == null) break;
+							if (i < nCommonVars - 1)
+								hash = (System.Collections.Hashtable)hash[value];
+							else
+								list = (BindingList)hash[value];
+						}
+						
+						// tree traversal didn't go to the end, meaning there was
+						// no corresponding match (with the same common variable values)
+						if (list == null) continue;
+					
+						for (int i = 0; i < list.Count; i++) {
+							VariableBindings right = (VariableBindings)list[i];
+							
+							Resource[] newValues = new Resource[newbindings.Variables.Length];
+							left.Values.CopyTo(newValues, 0);
+							for (int j = 0; j < newindexes.Length; j++)
+								if (newindexes[j] != -1)
+									newValues[newindexes[j]] = right.Values[j];
+
+							nMatches++;
+							if (!quickDupCheckIsDup(newbindings, newValues, nMatches, interestingVariables))
+								newbindings.Rows.Add(new VariableBindings(newbindings.Variables, newValues));
+						}
+					}
+					
+				}
+				
+				bindings = newbindings;
+				
+				// We go no intersections, so we can't get any results.
+				// No need to try further parts of the query.
+				if (bindings.Rows.Count == 0)
+					break;
+			}
+			
+			// If there's no limit specified, then we aren't doing adaptive limiting.
+			if (returnLimit == 0) break;
+			
+			// If this query has just one part (no intersection), then we aren't doing adaptive limiting.
+			if (queryParts.Length == 1) break;
+			
+			// If we found enough rows, then adaptive limiting worked.
+			if (bindings.Rows.Count >= returnLimit) break;
+			
+			// If the adaptive limit didn't actually limit: that is, we know
+			// that there are no more results than the limit for any part
+			// of the query, then no larger limit will help.
+			if (!adaptiveLimitDidLimit) break;
+			
+			// Increase the adaptive limit multiplier for next time.
+			adaptiveLimitMultiplier *= 3;
+			
+			} // end of adaptive limiting
+			
+			result.Init(bindings.Variables);
+			
+			int counter = 0;
+			foreach (VariableBindings row in bindings.Rows) {
+				counter++;
+				if (returnStart > 0 && counter < returnStart) continue;
+				
+				if (!result.Add(row))
+					break;
+
+				if (returnLimit > 0 && counter >= returnLimit) break;
+			}
+			
+			result.Finished();
+		}
+
+		static Entity[] ToEntArray(Resource[] res, ref bool fail) {
+			ResSet ents = new ResSet();
+			foreach (Resource r in res)
+				if (r is Entity)
+					ents.Add(r);
+			if (ents.Count == 0) { fail = true; return null; }
+			return ents.ToEntityArray();
+		}
+		
+		class Filter : StatementSink {
+			BindingList matches;
+			Statement filter;
+			Variable[] vars;
+			
+			public Filter(BindingList matches, Statement filter, Variable[] vars) { this.matches = matches; this.filter = filter; this.vars = vars; }
+			public bool Add(Statement s) {
+				if (filter.Subject == filter.Predicate && s.Subject != s.Predicate) return true;
+				if (filter.Subject == filter.Object && s.Subject != s.Object) return true;
+				if (filter.Subject == filter.Meta && s.Subject != s.Meta) return true;
+				if (filter.Predicate == filter.Object && s.Predicate != s.Object) return true;
+				if (filter.Predicate == filter.Meta && s.Predicate != s.Meta) return true;
+				if (filter.Object == filter.Meta && s.Object != s.Meta) return true;
+				
+				Resource[] vals = new Resource[vars.Length];
+				for (int i = 0; i < vars.Length; i++) {
+					if ((object)vars[i] == (object)filter.Subject) vals[i] = s.Subject;
+					else if ((object)vars[i] == (object)filter.Predicate) vals[i] = s.Predicate;
+					else if ((object)vars[i] == (object)filter.Object) vals[i] = s.Object;
+					else if ((object)vars[i] == (object)filter.Meta) vals[i] = s.Meta;
+				}
+				matches.Add(new VariableBindings(vars, vals));
+				return true;
+			}
+		}
+		
+		static int[,] IntersectVariables(Variable[] leftVars, Variable[] rightVars, out int nCommonVars) {
+			VariableList commonVars = new VariableList();
+			foreach (Variable v in leftVars)
+				if (Array.IndexOf(rightVars, v) != -1)
+					commonVars.Add(v);
+			
+			int[,] ret = new int[commonVars.Count, 2];
+			for (int i = 0; i < commonVars.Count; i++) {
+				ret[i,0] = Array.IndexOf(leftVars, commonVars[i]);
+				ret[i,1] = Array.IndexOf(rightVars, commonVars[i]);
+			}
+			
+			nCommonVars = commonVars.Count;
+			return ret;
+		}
+		
+		static bool quickDupCheckIsDup(BindingSet newbindings, Resource[] newValues, int nMatches, ResSet interestingVariables) {
+			// If there is a more than 10-to-1 ratio of rejected duplicates
+			// to unique rows, then we check all rows.  Otherwise we check the first 100.
+
+			bool isHighRejectRatio = newbindings.Rows.Count == 0 || (nMatches / newbindings.Rows.Count) > 10;
+			for (int i = 0; i < newbindings.Rows.Count; i++) {
+				if (i > 100 && !isHighRejectRatio) break;
+				bool dup = true;
+				for (int j = 0; j < newValues.Length; j++) {
+					if (interestingVariables != null && !interestingVariables.Contains(newbindings.Variables[j])) continue;
+					Resource left = ((VariableBindings)newbindings.Rows[i]).Values[j];
+					Resource right = newValues[j];
+					if ((object)left == null || (object)right == null) {
+						if (!((object)left == null && (object)right == null))
+							dup = false;
+					} else if (!left.Equals(right)) {
+						dup = false;
+						break;
+					}
+				}
+				if (dup)
+					return true;
+			}
+			return false;
+		}
+	}
+}

Modified: branches/beagle-rdf/Util/SemWeb/Inference.cs
==============================================================================
--- branches/beagle-rdf/Util/SemWeb/Inference.cs	(original)
+++ branches/beagle-rdf/Util/SemWeb/Inference.cs	Sat Jan 26 23:26:03 2008
@@ -1,8 +1,133 @@
 using System;
+
+#if !DOTNET2
 using System.Collections;
+#else
+using System.Collections.Generic;
+#endif
+
 using SemWeb;
+using SemWeb.Query;
 
 namespace SemWeb.Inference {
+
+	public abstract class Reasoner {
+		public abstract bool Distinct { get; } // assume targetModel.Distinct is true, *then* would Select be distinct?
+
+		public void Select(Statement template, SelectableSource targetModel, StatementSink sink) {
+			Select(new SelectFilter(template), targetModel, sink);
+		}
+		
+		public abstract void Select(SelectFilter filter, SelectableSource targetModel, StatementSink sink);
+		
+		public abstract MetaQueryResult MetaQuery(Statement[] graph, QueryOptions options, SelectableSource targetModel);
+		public abstract void Query(Statement[] graph, QueryOptions options, SelectableSource targetModel, QueryResultSink result);
+	}
+	
+	public class SimpleEntailment : Reasoner {
+		public override bool Distinct { get { return true; } }
+		
+		public override void Select(SelectFilter filter, SelectableSource targetModel, StatementSink sink) {
+			targetModel.Select(filter, sink);
+		}
+		
+		public override MetaQueryResult MetaQuery(Statement[] graph, SemWeb.Query.QueryOptions options, SelectableSource source) {
+			SemWeb.Query.MetaQueryResult ret = new SemWeb.Query.MetaQueryResult();
+			
+			ret.QuerySupported = true;
+			
+			ret.NoData = new bool[graph.Length];
+			for (int i = 0; i < graph.Length; i++) {
+				// Take this statement and replace variables by nulls
+				// to make it a statement template.
+				Statement st = graph[i];
+				for (int j = 0; j < 4; j++) {
+					if (st.GetComponent(j) is Variable)
+						st.SetComponent(j, null);
+				}
+				
+				// See if the store contains this template.
+				if (st != Statement.All && !source.Contains(st)) {
+					ret.NoData[i] = true;
+					continue;
+				}
+			
+				// Process it further in case we have variables
+				// with known values, in which case if none of the
+				// known values is in the store, we also know this
+				// statement is unanswerable.
+				for (int j = 0; j < 4; j++) {
+					Resource r = graph[i].GetComponent(j);
+					
+					// No need to check the following given the check above.
+					//if (r != null && !(r is Variable) && !source.Contains(r))
+					//	ret.NoData[i] = true;
+					
+					if (r != null && r is Variable && options.VariableKnownValues != null && 
+					#if !DOTNET2
+					options.VariableKnownValues.Contains((Variable)r)
+					#else
+					options.VariableKnownValues.ContainsKey((Variable)r)
+					#endif
+					) {
+						bool found = false;
+						#if !DOTNET2
+						foreach (Resource s in (ICollection)options.VariableKnownValues[(Variable)r]) {
+						#else
+						foreach (Resource s in (ICollection<Resource>)options.VariableKnownValues[(Variable)r]) {
+						#endif
+							if (source.Contains(s)) {
+								found = true;
+								break;
+							}
+						}
+						if (!found) {
+							ret.NoData[i] = true;
+						}
+					}
+				}
+			}
+			
+			return ret;
+		}
+		
+		public override void Query(Statement[] graph, SemWeb.Query.QueryOptions options, SelectableSource targetModel, QueryResultSink result) {
+			SemWeb.Query.GraphMatch q = new SemWeb.Query.GraphMatch();
+			foreach (Statement s in graph)
+				q.AddGraphStatement(s);
+				
+			q.ReturnLimit = options.Limit;
+			
+			if (options.VariableKnownValues != null) {
+			#if !DOTNET2
+			foreach (DictionaryEntry ent in options.VariableKnownValues)
+				q.SetVariableRange((Variable)ent.Key, (ICollection)ent.Value);
+			#else
+			foreach (KeyValuePair<Variable,ICollection<Resource>> ent in options.VariableKnownValues)
+				q.SetVariableRange(ent.Key, ent.Value);
+			#endif
+			}
+
+			if (options.VariableLiteralFilters != null) {			
+			#if !DOTNET2
+			foreach (DictionaryEntry ent in options.VariableLiteralFilters)
+				foreach (LiteralFilter filter in (ICollection)ent.Value)
+					q.AddLiteralFilter((Variable)ent.Key, filter);
+			#else
+			foreach (KeyValuePair<Variable,ICollection<LiteralFilter>> ent in options.VariableLiteralFilters)
+				foreach (LiteralFilter filter in ent.Value)
+					q.AddLiteralFilter(ent.Key, filter);
+			#endif
+			}
+			
+			if (options.DistinguishedVariables != null) {
+				foreach (Variable v in options.DistinguishedVariables)
+					q.SetDistinguishedVariable(v);
+			}
+
+			q.Run(targetModel, result);
+		}
+	}
 	
 	public class Rule {
 		public readonly Statement[] Antecedent;
@@ -14,22 +139,29 @@
 		}
 		
 		public override string ToString() {
-			string ret = "{";
-			foreach (Statement s in Antecedent)
-				ret += " " + s.ToString();
-			ret += " } => {";
+			string ret = "";
+			if (Antecedent.Length == 0) {
+				ret += "(axiom) ";
+			} else {
+				if (Antecedent.Length > 1) ret += "{";
+				foreach (Statement s in Antecedent)
+					ret += " " + s.ToString();
+				if (Antecedent.Length > 1) ret += " }";
+				ret += " => ";
+			}
+			if (Consequent.Length > 1) ret += "{";
 			foreach (Statement s in Consequent)
 				ret += " " + s.ToString();
-			ret += " }";
+			if (Consequent.Length > 1) ret += " }";
 			return ret;
 		}
 	}
 	
 	public class ProofStep {
 		public readonly Rule Rule;
-		public readonly IDictionary Substitutions;
+		public readonly System.Collections.IDictionary Substitutions;
 		
-		public ProofStep(Rule rule, IDictionary substitutions) {
+		public ProofStep(Rule rule, System.Collections.IDictionary substitutions) {
 			Rule = rule;
 			Substitutions = substitutions;
 		}
@@ -43,5 +175,32 @@
 			Proved = proved;
 			Steps = steps;
 		}
+		
+		public override string ToString () {
+			System.Text.StringBuilder ret = new System.Text.StringBuilder();
+
+			ret.Append("Proved: ");
+			foreach (Statement s in Proved)
+				ret.Append(s.ToString());
+			ret.Append("\n");
+
+			foreach (ProofStep step in Steps) {
+				ret.Append("\t");
+				ret.Append(step.Rule.ToString());
+				ret.Append("\n");
+				System.Collections.ArrayList vars = new System.Collections.ArrayList(step.Substitutions.Keys);
+				vars.Sort();
+				foreach (Variable v in vars) {
+					ret.Append("\t\t");
+					ret.Append(v);
+					ret.Append(" => ");
+					ret.Append(step.Substitutions[v]);
+					ret.Append("\n");
+				}
+			}
+			
+			return ret.ToString();
+		}
+
 	}
 }

Added: branches/beagle-rdf/Util/SemWeb/Interfaces.cs
==============================================================================
--- (empty file)
+++ branches/beagle-rdf/Util/SemWeb/Interfaces.cs	Sat Jan 26 23:26:03 2008
@@ -0,0 +1,72 @@
+using System;
+using System.Collections;
+using System.Data;
+
+using SemWeb.Util;
+
+namespace SemWeb {
+	public interface StatementSource {
+		bool Distinct { get; }
+		void Select(StatementSink sink);
+	}
+	
+	public interface SelectableSource : StatementSource {
+		bool Contains(Resource resource);
+		bool Contains(Statement template);
+		void Select(Statement template, StatementSink sink);
+		void Select(SelectFilter filter, StatementSink sink);
+	}
+
+	public interface QueryableSource : SelectableSource {
+		SemWeb.Query.MetaQueryResult MetaQuery(Statement[] graph, SemWeb.Query.QueryOptions options);
+		void Query(Statement[] graph, SemWeb.Query.QueryOptions options, SemWeb.Query.QueryResultSink sink);
+	}
+	
+	public interface StaticSource : SelectableSource {
+		int StatementCount { get; }
+
+		Entity[] GetEntities();
+		Entity[] GetPredicates();
+		Entity[] GetMetas();
+
+		string GetPersistentBNodeId(BNode node);
+		BNode GetBNodeFromPersistentId(string persistentId);
+	}
+	
+	public interface StatementSink {
+		bool Add(Statement statement);
+	}
+
+	public interface ModifiableSource : SelectableSource, StatementSink {
+		void Clear();
+		void Import(StatementSource source);
+		void Remove(Statement template);
+		void RemoveAll(Statement[] templates);
+		void Replace(Entity find, Entity replacement);
+		void Replace(Statement find, Statement replacement);
+	}
+	
+	internal class StatementCounterSink : StatementSink {
+		int counter = 0;
+		
+		public int StatementCount { get { return counter; } }
+		
+		public bool Add(Statement statement) {
+			counter++;
+			return true;
+		}
+	}
+
+	internal class StatementExistsSink : StatementSink {
+		bool exists = false;
+		
+		public bool Exists { get { return exists; } }
+		
+		public bool Add(Statement statement) {
+			exists = true;
+			return false;
+		}
+	}
+
+	
+}
\ No newline at end of file

Modified: branches/beagle-rdf/Util/SemWeb/LiteralFilters.cs
==============================================================================
--- branches/beagle-rdf/Util/SemWeb/LiteralFilters.cs	(original)
+++ branches/beagle-rdf/Util/SemWeb/LiteralFilters.cs	Sat Jan 26 23:26:03 2008
@@ -125,7 +125,7 @@
 				Decimal i = Decimal.Parse(v);
 				int c = i.CompareTo(Number);
 				return CompareFilter(c, Type);
-			} catch (Exception e) {
+			} catch (Exception) {
 				return false;
 			}
 		}
@@ -146,7 +146,7 @@
 				DateTime i = DateTime.Parse(v);
 				int c = i.CompareTo(Value);
 				return CompareFilter(c, Type);
-			} catch (Exception e) {
+			} catch (Exception) {
 				return false;
 			}
 		}
@@ -167,7 +167,7 @@
 				TimeSpan i = TimeSpan.Parse(v);
 				int c = i.CompareTo(Value);
 				return CompareFilter(c, Type);
-			} catch (Exception e) {
+			} catch (Exception) {
 				return false;
 			}
 		}

Modified: branches/beagle-rdf/Util/SemWeb/MemoryStore.cs
==============================================================================
--- branches/beagle-rdf/Util/SemWeb/MemoryStore.cs	(original)
+++ branches/beagle-rdf/Util/SemWeb/MemoryStore.cs	Sat Jan 26 23:26:03 2008
@@ -6,7 +6,80 @@
 using SemWeb.Util;
 
 namespace SemWeb {
-	public class MemoryStore : Store, SupportsPersistableBNodes, IEnumerable {
+	public class MemoryStore : Store, 
+#if DOTNET2
+	System.Collections.Generic.IEnumerable<Statement>
+#else
+	IEnumerable
+#endif
+	{
+	
+		internal StoreImpl impl;
+
+		public MemoryStore()
+			: this(new StoreImpl()) {
+		}
+
+		public MemoryStore(StatementSource source)
+			: this() {
+			Import(source);
+		}
+		
+		public MemoryStore(Statement[] statements)
+			: this(new StoreImpl(statements)) {
+		}
+		
+		private MemoryStore(StoreImpl impl) {
+			this.impl = impl;
+			AddSource2(impl);
+		}
+		
+		public override void AddSource(SelectableSource store) {
+			throw new InvalidOperationException("AddSource is not valid on the MemoryStore.");
+		}
+		
+		public override void AddSource(SelectableSource store, string uri) {
+			throw new InvalidOperationException("AddSource is not valid on the MemoryStore.");
+		}
+
+		public Statement this[int index] {
+			get {
+				return impl[index];
+			}
+		}
+
+		public Statement[] ToArray() {
+			return impl.ToArray();
+		}
+
+		#if DOTNET2
+		System.Collections.Generic.IEnumerator<Statement> System.Collections.Generic.IEnumerable<Statement>.GetEnumerator() {
+			return ((System.Collections.Generic.IEnumerable<Statement>)impl).GetEnumerator();
+		}
+		#endif
+		IEnumerator IEnumerable.GetEnumerator() {
+			return ((IEnumerable)impl).GetEnumerator();
+		}
+
+		internal bool allowIndexing { set { impl.allowIndexing = value; } }
+		internal bool checkForDuplicates { set { impl.checkForDuplicates = value; } }
+
+
+	internal class StoreImpl : SelectableSource, StaticSource, ModifiableSource, 
+#if DOTNET2
+	System.Collections.Generic.IEnumerable<Statement>
+#else
+	IEnumerable
+#endif
+	
+	{
+		#if DOTNET2
+		private class StatementList : System.Collections.Generic.List<Statement> {
+			public StatementList() : base() { }
+			public StatementList(Statement[] statements) : base(statements) { }
+		}
+		#endif
+
 		StatementList statements;
 		
 		Hashtable statementsAboutSubject = new Hashtable();
@@ -21,27 +94,31 @@
 		Hashtable pbnodeToId = null;
 		Hashtable pbnodeFromId = null;
 		
-		public MemoryStore() {
+		const string rdfli = NS.RDF + "_";
+		
+		public StoreImpl() {
 			statements = new StatementList();
 		}
 		
-		public MemoryStore(StatementSource source) : this() {
+		public StoreImpl(StatementSource source) : this() {
 			Import(source);
 		}
 		
-		public MemoryStore(Statement[] statements) {
+		public StoreImpl(Statement[] statements) {
 			this.statements = new StatementList(statements);
 		}
 
 		public Statement[] ToArray() {
+#if DOTNET2
+			return statements.ToArray();
+#else
 			return (Statement[])statements.ToArray(typeof(Statement));
+#endif
 		}
 
-		public IList Statements { get { return statements.ToArray(); } }
+		public bool Distinct { get { return distinct; } }
 		
-		public override bool Distinct { get { return distinct; } }
-		
-		public override int StatementCount { get { return statements.Count; } }
+		public int StatementCount { get { return statements.Count; } }
 		
 		public Statement this[int index] {
 			get {
@@ -49,11 +126,16 @@
 			}
 		}
 		
+#if DOTNET2
+		System.Collections.Generic.IEnumerator<Statement> System.Collections.Generic.IEnumerable<Statement>.GetEnumerator() {
+			return statements.GetEnumerator();
+		}
+#endif
 		IEnumerator IEnumerable.GetEnumerator() {
 			return statements.GetEnumerator();
 		}
 		
-		public override void Clear() {
+		public void Clear() {
 			statements.Clear();
 			statementsAboutSubject.Clear();
 			statementsAboutObject.Clear();
@@ -69,7 +151,12 @@
 			return ret;
 		}
 		
-		public override void Add(Statement statement) {
+		bool StatementSink.Add(Statement statement) {
+			Add(statement);
+			return true;
+		}
+		
+		public void Add(Statement statement) {
 			if (statement.AnyNull) throw new ArgumentNullException();
 			if (checkForDuplicates && Contains(statement)) return;
 			statements.Add(statement);
@@ -80,13 +167,13 @@
 			if (!checkForDuplicates) distinct = false;
 		}
 		
-		public override void Import(StatementSource source) {
+		public void Import(StatementSource source) {
 			bool newDistinct = checkForDuplicates || ((StatementCount==0) && source.Distinct);
-			base.Import(source); // distinct set to false if !checkForDuplicates
+			source.Select(this);
 			distinct = newDistinct;
 		}
 		
-		public override void Remove(Statement statement) {
+		public void Remove(Statement statement) {
 			if (statement.AnyNull) {
 				for (int i = 0; i < statements.Count; i++) {
 					Statement s = (Statement)statements[i];
@@ -107,9 +194,14 @@
 			}
 		}
 		
-		public override Entity[] GetEntities() {
+		public void RemoveAll(Statement[] statements) {
+			foreach (Statement t in statements)
+				Remove(t);
+		}
+		
+		public Entity[] GetEntities() {
 			Hashtable h = new Hashtable();
-			foreach (Statement s in Statements) {
+			foreach (Statement s in statements) {
 				if (s.Subject != null) h[s.Subject] = h;
 				if (s.Predicate != null) h[s.Predicate] = h;
 				if (s.Object != null && s.Object is Entity) h[s.Object] = h;
@@ -118,16 +210,16 @@
 			return (Entity[])new ArrayList(h.Keys).ToArray(typeof(Entity));
 		}
 		
-		public override Entity[] GetPredicates() {
+		public Entity[] GetPredicates() {
 			Hashtable h = new Hashtable();
-			foreach (Statement s in Statements)
+			foreach (Statement s in statements)
 				h[s.Predicate] = h;
 			return (Entity[])new ArrayList(h.Keys).ToArray(typeof(Entity));
 		}
 
-		public override Entity[] GetMetas() {
+		public Entity[] GetMetas() {
 			Hashtable h = new Hashtable();
-			foreach (Statement s in Statements)
+			foreach (Statement s in statements)
 				h[s.Meta] = h;
 			return (Entity[])new ArrayList(h.Keys).ToArray(typeof(Entity));
 		}
@@ -137,7 +229,11 @@
 				list1 = list2;
 		}
 		
-		public override void Select(Statement template, StatementSink result) {
+		public void Select(StatementSink result) {
+			Select(Statement.All, result);
+		}
+		
+		public void Select(Statement template, StatementSink result) {
 			StatementList source = statements;
 			
 			// The first time select is called, turn indexing on for the store.
@@ -157,15 +253,24 @@
 			
 			if (source == null) return;
 			
+			bool isRdfsMemberPredicate = (template.Predicate != null && template.Predicate.Uri != null
+				&& template.Predicate.Uri == NS.RDFS + "member");
+			if (isRdfsMemberPredicate)
+				template.Predicate = null;
+			
 			for (int i = 0; i < source.Count; i++) {
 				Statement statement = source[i];
 				if (!template.Matches(statement))
 					continue;
+				
+				if (isRdfsMemberPredicate && (statement.Predicate.Uri == null || !statement.Predicate.Uri.StartsWith(rdfli)))
+					continue;
+					
 				if (!result.Add(statement)) return;
 			}
 		}
 
-		public override void Select(SelectFilter filter, StatementSink result) {
+		public void Select(SelectFilter filter, StatementSink result) {
 			ResSet
 				s = filter.Subjects == null ? null : new ResSet(filter.Subjects),
 				p = filter.Predicates == null ? null : new ResSet(filter.Predicates),
@@ -181,8 +286,22 @@
 				if (!result.Add(st)) return;
 			}
 		}
+		
+		public bool Contains(Resource r) {
+			foreach (Statement s in statements) {
+				if (s.Subject == r) return true;
+				if (s.Predicate == r) return true;
+				if (s.Object == r) return true;
+				if (s.Meta == r) return true;
+			}
+			return false;
+		}
+		
+		public bool Contains(Statement template) {
+			return Store.DefaultContains(this, template);
+		}
 
-		public override void Replace(Entity a, Entity b) {
+		public void Replace(Entity a, Entity b) {
 			MemoryStore removals = new MemoryStore();
 			MemoryStore additions = new MemoryStore();
 			foreach (Statement statement in statements) {
@@ -195,38 +314,34 @@
 			Import(additions);
 		}
 		
-		public override void Replace(Statement find, Statement replacement) {
-			if (find.AnyNull) throw new ArgumentNullException("find");
-			if (replacement.AnyNull) throw new ArgumentNullException("replacement");
-			if (find == replacement) return;
-			
-			foreach (Statement match in Select(find)) {
-				Remove(match);
-				Add(replacement);
-				break; // should match just one statement anyway
-			}
+		public void Replace(Statement find, Statement replacement) {
+			Remove(find);
+			Add(replacement);
 		}
 
-		string SupportsPersistableBNodes.GetStoreGuid() {
+		private string GetStoreGuid() {
 			if (guid == null) guid = Guid.NewGuid().ToString("N");;
 			return guid;
 		}
 		
-		string SupportsPersistableBNodes.GetNodeId(BNode node) {
+		public string GetPersistentBNodeId(BNode node) {
 			if (pbnodeToId == null) {
 				pbnodeToId = new Hashtable();
 				pbnodeFromId = new Hashtable();
 			}
 			if (pbnodeToId.ContainsKey(node)) return (string)pbnodeToId[node];
-			string id = pbnodeToId.Count.ToString();
+			string id = GetStoreGuid() + ":" + pbnodeToId.Count.ToString();
 			pbnodeToId[node] = id;
 			pbnodeFromId[id] = node;
 			return id;
 		}
 		
-		BNode SupportsPersistableBNodes.GetNodeFromId(string persistentId) {
+		public BNode GetBNodeFromPersistentId(string persistentId) {
 			if (pbnodeFromId == null) return null;
 			return (BNode)pbnodeFromId[persistentId];
 		}
 	}
+
+	}
+
 }

Modified: branches/beagle-rdf/Util/SemWeb/N3Reader.cs
==============================================================================
--- branches/beagle-rdf/Util/SemWeb/N3Reader.cs	(original)
+++ branches/beagle-rdf/Util/SemWeb/N3Reader.cs	Sat Jan 26 23:26:03 2008
@@ -11,6 +11,7 @@
 	public class N3Reader : RdfReader {
 		Resource PrefixResource = new Literal("@prefix");
 		Resource KeywordsResource = new Literal("@keywords");
+		Resource BaseResource = new Literal("@base");
 		
 		TextReader sourcestream;
 		NamespaceManager namespaces = new NamespaceManager();
@@ -22,8 +23,7 @@
 		//Entity entOWLSAMEAS = "http://www.w3.org/2002/07/owl#sameAs";;
 		Entity entDAMLEQUIV = "http://www.daml.org/2000/12/daml+oil#equivalentTo";;
 		Entity entLOGIMPLIES = "http://www.w3.org/2000/10/swap/log#implies";;
-		
-		bool addFailuresAsWarnings = false;
+		Entity entGRAPHCONTAINS = "http://razor.occams.info/code/semweb/internaluris/graphContains";;
 		
 		public N3Reader(TextReader source) {
 			this.sourcestream = source;
@@ -44,6 +44,7 @@
 			public Entity meta;
 			public bool UsingKeywords;
 			public Hashtable Keywords;
+			public Entity overrideMeta;
 			
 			public Location Location { get { return new Location(source.Line, source.Col); } }
 		}
@@ -65,7 +66,7 @@
 			Location loc = context.Location;
 			
 			bool reverse;
-			Resource subject = ReadResource(context, out reverse);
+			Resource subject = ReadResource(context, true, out reverse);
 			if (subject == null) return false;
 			if (reverse) OnError("is...of not allowed on a subject", loc);
 			
@@ -75,7 +76,7 @@
 				if (qname == null || !qname.EndsWith(":")) OnError("When using @prefix, the prefix identifier must end with a colon", loc);
 				
 				loc = context.Location;
-				Resource uri = ReadResource(context, out reverse);
+				Resource uri = ReadResource(context, false, out reverse);
 				if (uri == null) OnError("Expecting a URI", loc);
 				if (reverse) OnError("is...of not allowed here", loc);
 				namespaces.AddNamespace(uri.Uri, qname.Substring(0, qname.Length-1));
@@ -107,6 +108,20 @@
 				return true;
 			}
 			
+			if ((object)subject == (object)BaseResource) {
+				loc = context.Location;
+				Resource uri = ReadResource(context, false, out reverse);
+				if (uri == null || uri.Uri == null) OnError("Expecting a URI", loc);
+				if (reverse) OnError("is...of not allowed here", loc);
+				BaseUri = uri.Uri;
+				
+				loc = context.Location;
+				char punc = ReadPunc(context.source);
+				if (punc != '.')
+					OnError("Expected a period but found '" + punc + "'", loc);
+				return true;
+			}
+
 			// It's possible to just assert the presence of an entity
 			// by following the entity with a period, or a } to end
 			// a reified context.
@@ -148,10 +163,16 @@
 		private char ReadPredicate(Resource subject, ParseContext context) {
 			bool reverse;
 			Location loc = context.Location;
-			Resource predicate = ReadResource(context, out reverse);
+			Resource predicate = ReadResource(context, false, out reverse);
 			if (predicate == null) OnError("Expecting a predicate", loc);
 			if (predicate is Literal) OnError("Predicates cannot be literals", loc);
 			
+			if (predicate == entGRAPHCONTAINS) {
+				context.overrideMeta = subject as Entity;
+			} else {
+				context.overrideMeta = null;
+			}
+			
 			char punctuation = ',';
 			while (punctuation == ',') {
 				ReadObject(subject, (Entity)predicate, context, reverse);
@@ -167,12 +188,14 @@
 		private void ReadObject(Resource subject, Entity predicate, ParseContext context, bool reverse) {
 			bool reverse2;
 			Location loc = context.Location;
-			Resource value = ReadResource(context, out reverse2);
+			Resource value = ReadResource(context, false, out reverse2);
 			if (value == null) OnError("Expecting a resource or literal object", loc);
 			if (reverse2) OnError("is...of not allowed on objects", loc);
 			
 			loc = context.Location;
-			if (!reverse) {
+			if (predicate == entGRAPHCONTAINS) {
+				// don't add the statement, it was enough to associate the meta node
+			} else if (!reverse) {
 				if (subject is Literal) OnError("Subjects of statements cannot be literals", loc);			
 				Add(context.store, new Statement((Entity)subject, predicate, value, context.meta), loc);
 			} else {
@@ -380,6 +403,11 @@
 			} else if (firstchar == '=') {
 				if (source.Peek() == (int)'>')
 					b.Append((char)source.Read());
+				
+				if (source.Peek() == (int)':' && source.Peek2() == (int)'>') { // SPECIAL EXTENSION "=:>"
+					b.Append((char)source.Read());
+					b.Append((char)source.Read());
+				}
 			
 			} else if (firstchar == '[') {
 				// The start of an anonymous node.
@@ -405,10 +433,10 @@
 			return b.ToString();
 		}
 		
-		private Resource ReadResource(ParseContext context, out bool reverse) {
+		private Resource ReadResource(ParseContext context, bool allowDirective, out bool reverse) {
 			Location loc = context.Location;
 			
-			Resource res = ReadResource2(context, out reverse);
+			Resource res = ReadResource2(context, allowDirective, out reverse);
 			
 			ReadWhitespace(context.source);
 			while (context.source.Peek() == '!' || context.source.Peek() == '^' || (context.source.Peek() == '.' && context.source.Peek2() != -1 && char.IsLetter((char)context.source.Peek2())) ) {
@@ -416,7 +444,7 @@
 				
 				bool reverse2;
 				loc = context.Location;
-				Resource path = ReadResource2(context, out reverse2);
+				Resource path = ReadResource2(context, false, out reverse2);
 				if (reverse || reverse2) OnError("is...of is not allowed in path expressions", loc);
 				if (!(path is Entity)) OnError("A path expression cannot be a literal", loc);
 				
@@ -473,7 +501,7 @@
 			}
 		}
 			
-		private Resource ReadResource2(ParseContext context, out bool reverse) {
+		private Resource ReadResource2(ParseContext context, bool allowDirective, out bool reverse) {
 			reverse = false;
 			
 			Location loc = context.Location;
@@ -485,15 +513,32 @@
 			string str = (string)tok;
 			if (str == "")
 				return null;
+				
+			// Directives
 			
-			// @ Keywords
+			if (str == "@prefix") {
+				if (allowDirective)
+					return PrefixResource;
+				else
+					OnError("The directive '" + str + "' is not allowed here", loc);
+			}
 
-			if (str == "@prefix")
-				return PrefixResource;
+			if (str == "@keywords") {
+				if (allowDirective)
+					return KeywordsResource;
+				else
+					OnError("The directive '" + str + "' is not allowed here", loc);
+			}
 
-			if (str == "@keywords")
-				return KeywordsResource;
+			if (str == "@base") {
+				if (allowDirective)
+					return BaseResource;
+				else
+					OnError("The directive '" + str + "' is not allowed here", loc);
+			}
 			
+			// @ Keywords
+
 			if (context.UsingKeywords && context.Keywords.Contains(str))
 				str = "@" + str;
 			if (!context.UsingKeywords &&
@@ -513,15 +558,17 @@
 			if (str == "<=") {
 				reverse = true;
 				return entLOGIMPLIES;
-			}				
+			}
+			if (str == "=:>") // SPECIAL EXTENSION!
+				return entGRAPHCONTAINS;
 
 			if (str == "@has") // ignore this token
-				return ReadResource2(context, out reverse);
+				return ReadResource2(context, false, out reverse);
 			
 			if (str == "@is") {
 				// Reverse predicate
 				bool reversetemp;
-				Resource pred = ReadResource2(context, out reversetemp);
+				Resource pred = ReadResource2(context, false, out reversetemp);
 				reverse = true;
 				
 				string of = ReadToken(context.source, context) as string;
@@ -541,6 +588,9 @@
 			
 			if (str.StartsWith("<") && str.EndsWith(">")) {
 				string uri = GetAbsoluteUri(BaseUri, str.Substring(1, str.Length-2));
+				string urierror = Entity.ValidateUri(uri);
+				if (urierror != null)
+					OnWarning(urierror, loc);
 				return GetResource(context, uri);
 			}
 			
@@ -583,15 +633,17 @@
 			
 			if (str == "(") {
 				// A list
-				Entity ent = null;
+				Entity head = null, ent = null;
 				while (true) {
 					bool rev2;
-					Resource res = ReadResource(context, out rev2);
+					Resource res = ReadResource(context, false, out rev2);
 					if (res == null)
 						break;
 					
 					if (ent == null) {
 						ent = new BNode();
+						if (head == null)
+							head = ent;
 					} else {
 						Entity sub = new BNode();
 						Add(context.store, new Statement(ent, entRDFREST, sub, context.meta), loc);
@@ -604,7 +656,8 @@
 					ent = entRDFNIL; // according to Turtle spec
 				else
 					Add(context.store, new Statement(ent, entRDFREST, entRDFNIL, context.meta), loc);
-				return ent;
+				
+				return head;
 			}
 			
 			if (str == ")")
@@ -616,8 +669,12 @@
 				// ParseContext is a struct, so this gives us a clone.
 				ParseContext newcontext = context;
 				
-				// The formula is denoted by a blank node
-				newcontext.meta = new BNode();
+				// The formula is denoted by a blank node, unless we set
+				// the override meta flag above.
+				if (context.overrideMeta == null)
+					newcontext.meta = new BNode();
+				else
+					newcontext.meta = context.overrideMeta;
 				
 				// According to the spec, _:xxx anonymous nodes are
 				// local to the formula.  But ?$variables (which aren't
@@ -635,8 +692,12 @@
 			
 			// In Turtle, numbers are restricted to [0-9]+, and are datatyped xsd:integer.
 			double numval;
-			if (double.TryParse(str, System.Globalization.NumberStyles.Any, null, out numval))
-				return new Literal(numval.ToString());
+			if (double.TryParse(str, System.Globalization.NumberStyles.Any, null, out numval)) {
+				if (numval >= long.MinValue && numval <= long.MaxValue && numval == (double)(long)numval)
+					return new Literal(((long)numval).ToString(), null, NS.XMLSCHEMA + "integer");
+				else
+					return new Literal(numval.ToString(), null, NS.XMLSCHEMA + "double");
+			}
 			
 			// If @keywords is used, alphanumerics that aren't keywords
 			// are local names in the default namespace.
@@ -653,25 +714,21 @@
 		}
 		
 		private void Add(StatementSink store, Statement statement, Location position) {
-			try {
-				store.Add(statement);
-			} catch (Exception e) {
-				if (!addFailuresAsWarnings)
-					OnError("Add failed on statement { " + statement + " }: " + e.Message, position, e);
-				else
-					OnWarning("Add failed on statement { " + statement + " }: " + e.Message, position, e);
-			}
+			store.Add(statement);
 		}
 		
 		private void OnError(string message, Location position) {
 			throw new ParserException(message + ", line " + position.Line + " col " + position.Col);
 		}
-		private void OnError(string message, Location position, Exception cause) {
+		private void OnWarning(string message, Location position) {
+			base.OnWarning(message + ", line " + position.Line + " col " + position.Col);
+		}
+		/*private void OnError(string message, Location position, Exception cause) {
 			throw new ParserException(message + ", line " + position.Line + " col " + position.Col, cause);
 		}
 		private void OnWarning(string message, Location position, Exception cause) {
 			OnWarning(message + ", line " + position.Line + " col " + position.Col);
-		}
+		}*/
 		
 	
 	}

Modified: branches/beagle-rdf/Util/SemWeb/N3Writer.cs
==============================================================================
--- branches/beagle-rdf/Util/SemWeb/N3Writer.cs	(original)
+++ branches/beagle-rdf/Util/SemWeb/N3Writer.cs	Sat Jan 26 23:26:03 2008
@@ -8,9 +8,10 @@
 namespace SemWeb {
 	public class N3Writer : RdfWriter {
 		TextWriter writer;
-		NamespaceManager ns = new NamespaceManager();
+		NamespaceManager2 ns = new NamespaceManager2();
 		bool hasWritten = false;
 		bool closed = false;
+		bool closeStream = false;
 		
 		string lastSubject = null, lastPredicate = null;
 		
@@ -19,13 +20,16 @@
 		
 		Formats format = Formats.Turtle;
 		
+		private const string xsdInteger = NS.XMLSCHEMA + "integer";
+		private const string xsdDouble = NS.XMLSCHEMA + "double";
+		
 		public enum Formats {
 			NTriples,
 			Turtle,
 			Notation3
 		}
 		
-		public N3Writer(string file) : this(GetWriter(file)) { }
+		public N3Writer(string file) : this(GetWriter(file)) { closeStream = true; }
 
 		public N3Writer(TextWriter writer) {
 			this.writer = writer;
@@ -38,7 +42,7 @@
 		public override void Add(Statement statement) {
 			if (statement.AnyNull) throw new ArgumentNullException();
 			WriteStatement2(URI(statement.Subject), URI(statement.Predicate),
-				statement.Object is Literal ? ((Literal)statement.Object).ToString() : URI((Entity)statement.Object));
+				statement.Object is Literal ? Literal((Literal)statement.Object) : URI((Entity)statement.Object));
 		}
 
 		public override void Close() {
@@ -48,9 +52,18 @@
 				writer.WriteLine(".");
 			closed = true;
 			hasWritten = false;
-			writer.Flush();
+			if (closeStream)
+				writer.Close();
+			else
+				writer.Flush();
 		}
 
+		private string Literal(Literal literal) {
+			if (format == Formats.NTriples || literal.DataType == null) return literal.ToString();
+			if (literal.DataType == xsdInteger) return literal.ParseValue().ToString();
+			if (literal.DataType == xsdDouble && format == Formats.Notation3) return literal.ParseValue().ToString();
+			return literal.ToString();
+		}
 		
 		private string URI(Entity entity) {
 			if (entity is Variable && ((Variable)entity).LocalName != null)
@@ -82,7 +95,7 @@
 				if (ok)
 					return ":" + uri.Substring(len);
 			}
-			if (Format == Formats.NTriples || ns == null) return "<" + Escape(uri) + ">";
+			if (Format == Formats.NTriples) return "<" + Escape(uri) + ">";
 			
 			string ret = ns.Normalize(uri);
 			if (ret[0] != '<') return ret;
@@ -151,14 +164,21 @@
 			closed = false;
 			
 			// Write the prefix directives at the beginning.
-			if (!hasWritten && ns != null && !(Format == Formats.NTriples)) {
-				foreach (string prefix in ns.GetPrefixes()) {
+			if (ns.addedPrefixes.Count > 0 && !(Format == Formats.NTriples)) {
+				if (hasWritten) {
+					writer.Write(".\n");
+					lastSubject = null;
+					lastPredicate = null;
+					hasWritten = false; // really means whether a statement is "open", missing a period
+				}
+				foreach (string prefix in ns.addedPrefixes) {
 					writer.Write("@prefix ");
 					writer.Write(prefix);
 					writer.Write(": <");
 					writer.Write(ns.GetNamespace(prefix));
 					writer.Write("> .\n");
 				}
+				ns.addedPrefixes.Clear();
 			}
 
 			// Repeated subject.
@@ -187,7 +207,7 @@
 			
 			// Start a new statement.
 			} else {
-				if (hasWritten)
+				if (hasWritten) // finish the last statement
 					writer.Write(".\n");
 					
 				WriteThing(subj);
@@ -205,5 +225,13 @@
 			writer.Write(text);
 			writer.Write(" ");
 		}
+	
+		private class NamespaceManager2 : NamespaceManager {
+			public ArrayList addedPrefixes = new ArrayList();
+			public override void AddNamespace(string uri, string prefix) {
+				base.AddNamespace(uri, prefix);
+				addedPrefixes.Add(prefix);
+			}
+		}
 	}
 }

Modified: branches/beagle-rdf/Util/SemWeb/NamespaceManager.cs
==============================================================================
--- branches/beagle-rdf/Util/SemWeb/NamespaceManager.cs	(original)
+++ branches/beagle-rdf/Util/SemWeb/NamespaceManager.cs	Sat Jan 26 23:26:03 2008
@@ -7,6 +7,8 @@
 		public const string RDF = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";;
 		public const string RDFS = "http://www.w3.org/2000/01/rdf-schema#";;
 	
+		public const string XMLSCHEMA = "http://www.w3.org/2001/XMLSchema#";;
+	
 		/*Entity entRDFTYPE = "http://www.w3.org/1999/02/22-rdf-syntax-ns#type";;
 		Entity entRDFFIRST = "http://www.w3.org/1999/02/22-rdf-syntax-ns#first";;
 		Entity entRDFREST = "http://www.w3.org/1999/02/22-rdf-syntax-ns#rest";;
@@ -27,11 +29,16 @@
 			this.parent = parent;
 		}
 		
-		public void AddNamespace(string uri, string prefix) {
+		public virtual void AddNamespace(string uri, string prefix) {
 			atob[uri] = prefix;
 			btoa[prefix] = uri;
 		}
 		
+		public void AddFrom(NamespaceManager nsmgr) {
+			foreach (string uri in nsmgr.GetNamespaces())
+				AddNamespace(uri, nsmgr.GetPrefix(uri));
+		}
+
 		public virtual string GetNamespace(string prefix) {
 			string ret = (string)btoa[prefix];
 			if (ret != null) return ret;
@@ -76,7 +83,7 @@
 			if (Normalize(uri, out prefix, out localname)) {
 				bool ok = true;
 				if (localname.Length == 0) ok = false;
-				else if (!char.IsLetter(localname[0])) ok = false;
+				else if (!char.IsLetter(localname[0]) && localname[0] != '_') ok = false;
 				foreach (char c in localname)
 					if (!char.IsLetterOrDigit(c) && c != '-' && c != '_')
 						ok = false;

Modified: branches/beagle-rdf/Util/SemWeb/Query.cs
==============================================================================
--- branches/beagle-rdf/Util/SemWeb/Query.cs	(original)
+++ branches/beagle-rdf/Util/SemWeb/Query.cs	Sat Jan 26 23:26:03 2008
@@ -1,5 +1,4 @@
 using System;
-using System.Collections;
 using System.IO;
 
 using SemWeb;
@@ -7,8 +6,120 @@
 using SemWeb.Stores;
 using SemWeb.Util;
 
+#if !DOTNET2
+using System.Collections;
+#else
+using System.Collections.Generic;
+#endif
+
+#if !DOTNET2
+using ResList = System.Collections.ICollection;
+using LitFilterMap = System.Collections.Hashtable;
+using LitFilterList = System.Collections.ArrayList;
+#else
+using ResList = System.Collections.Generic.ICollection<SemWeb.Resource>;
+using LitFilterMap = System.Collections.Generic.Dictionary<SemWeb.Variable,System.Collections.Generic.ICollection<SemWeb.LiteralFilter>>;
+using LitFilterList = System.Collections.Generic.List<SemWeb.LiteralFilter>;
+#endif
+
 namespace SemWeb.Query {
 
+	public struct QueryOptions {
+		public int Limit; // 0 means no limit, otherwise the maximum number of results to give
+		
+		#if !DOTNET2
+		public ICollection DistinguishedVariables; // if null, all variables are reported back in bindings; otherwise, a list of just the variables whose bindings are to be reported
+		public IDictionary VariableKnownValues; // a map from variables to lists of values that the variable must be drawn from
+		public IDictionary VariableLiteralFilters; // a map from variables to lists of literal value filters that its values must match
+		#else
+		public ICollection<Variable> DistinguishedVariables;
+		public IDictionary<Variable,ICollection<Resource>> VariableKnownValues;
+		public IDictionary<Variable,ICollection<LiteralFilter>> VariableLiteralFilters;
+		#endif
+		
+		public void AddDistinguishedVariable(Variable variable) {
+			if (DistinguishedVariables == null)
+				#if !DOTNET2
+				DistinguishedVariables = new ArrayList();
+				#else
+				DistinguishedVariables = new List<Variable>();
+				#endif
+			#if !DOTNET2
+			((IList)DistinguishedVariables).Add(variable);
+			#else
+			((IList<Variable>)DistinguishedVariables).Add(variable);
+			#endif
+		}
+		
+		public void SetVariableKnownValues(Variable variable, ResList knownValues) {
+			if (VariableKnownValues == null)
+			#if !DOTNET2
+				VariableKnownValues = new Hashtable();
+			#else
+				VariableKnownValues = new Dictionary<Variable,ICollection<Resource>>();
+			#endif
+			
+			VariableKnownValues[variable] = knownValues;
+		}
+		
+		public void AddLiteralFilter(Variable variable, LiteralFilter filter) {
+			if (VariableLiteralFilters == null)
+				VariableLiteralFilters = new LitFilterMap();
+			LitFilterList list = null;
+			#if DOTNET2
+			if (VariableLiteralFilters.ContainsKey(variable))
+			#endif
+				list = (LitFilterList)VariableLiteralFilters[variable];
+			if (list == null) {
+			 	list  = new LitFilterList();
+				VariableLiteralFilters[variable] = list;
+			}
+			list.Add(filter);
+		}
+		
+		internal QueryOptions Clone() {
+			QueryOptions ret = new QueryOptions();
+			ret.Limit = Limit;
+
+			#if !DOTNET2
+			if (DistinguishedVariables != null)
+				ret.DistinguishedVariables = new ArrayList(DistinguishedVariables);
+			if (VariableKnownValues != null) {
+				ret.VariableKnownValues = new Hashtable();
+				foreach (Variable v in VariableKnownValues.Keys)
+					ret.VariableKnownValues[v] = new ArrayList((ICollection)VariableKnownValues[v]);
+			}
+			if (VariableLiteralFilters != null) {
+				ret.VariableLiteralFilters = new Hashtable();
+				foreach (Variable v in VariableLiteralFilters.Keys)
+					ret.VariableLiteralFilters[v] = new ArrayList((ICollection)VariableLiteralFilters[v]);
+			}
+			#else
+			if (DistinguishedVariables != null)
+				ret.DistinguishedVariables = new List<Variable>(DistinguishedVariables);
+			if (VariableKnownValues != null) {
+				ret.VariableKnownValues = new Dictionary<Variable,ICollection<Resource>>();
+				foreach (Variable v in VariableKnownValues.Keys)
+					ret.VariableKnownValues[v] = new List<Resource>(VariableKnownValues[v]);
+			}
+			if (VariableLiteralFilters != null) {
+				ret.VariableLiteralFilters = new Dictionary<Variable,ICollection<LiteralFilter>>();
+				foreach (Variable v in VariableLiteralFilters.Keys)
+					ret.VariableLiteralFilters[v] = new List<LiteralFilter>(VariableLiteralFilters[v]);
+			}
+			#endif
+			
+			return ret;
+		}
+	}
+	
+	public struct MetaQueryResult {
+		public bool QuerySupported;
+		public bool[] NoData;
+		public bool[] IsDefinitive;
+	}
+	
+
 	public class QueryFormatException : ApplicationException {
 		public QueryFormatException(string message) : base(message) { }
 		public QueryFormatException(string message, Exception cause) : base(message, cause) { }
@@ -36,6 +147,15 @@
 		
 		public Entity QueryMeta { get { return queryMeta; } set { queryMeta = value; } }
 		
+		public virtual string MimeType {
+			get {
+				return SparqlXmlQuerySink.MimeType;
+			}
+			set {
+				throw new NotSupportedException();
+			}
+		}
+		
 		public virtual void Run(SelectableSource source, TextWriter output) {
 			Run(source, new SparqlXmlQuerySink(output));
 		}
@@ -45,913 +165,129 @@
 		public abstract string GetExplanation();
 	}
 
-	public class GraphMatch : Query {
-		// Setup information
-	
-		ArrayList setupVariablesDistinct = new ArrayList();
-		ArrayList setupValueFilters = new ArrayList();
-		ArrayList setupStatements = new ArrayList();
-		ArrayList setupOptionalStatements = new ArrayList();
-		
-		// Query model information
-		
-		bool init = false;
-		object sync = new object();
-		Variable[] variables;
-		SemWeb.Variable[] variableEntities;
-		QueryStatement[][] statements;
-		ArrayList novariablestatements = new ArrayList();
-		
-		// contains functional and inverse functional properties
-		ResSet fps = new ResSet(),
-		          ifps = new ResSet();
-		
-		private struct Variable {
-			public SemWeb.Variable Entity;
-			public LiteralFilter[] Filters;
-		}
-		
-		private struct VarOrAnchor {
-			public bool IsVariable;
-			public int VarIndex;
-			public Resource Anchor;
-			public Resource[] ArrayOfAnchor;
-			
-			public override string ToString() {
-				if (IsVariable)
-					return "?" + VarIndex;
-				else
-					return Anchor.ToString();
-			}
-			
-			public Resource[] GetValues(QueryResult union, bool entities) {
-				if (!IsVariable) {
-					if (entities)
-						return new Entity[] { (Entity)Anchor };
-					else
-						return ArrayOfAnchor;
-				} else {
-					if (union.Bindings[VarIndex] == null) return null;
-					Resource[] res = union.Bindings[VarIndex].ToArray();
-					if (!entities) return res;
-					
-					ArrayList ret = new ArrayList();
-					foreach (Resource r in res)
-						if (r is Entity)
-							ret.Add(r);
-					return (Entity[])ret.ToArray(typeof(Entity));
-				}
-			}
-		}
-		
-		private class QueryStatement { // class because of use with IComparer
-			public bool Optional;
-			public VarOrAnchor 
-				Subject,
-				Predicate,
-				Object;
-			
-			public int NumVars() {
-				return (Subject.IsVariable ? 1 : 0)
-					+ (Predicate.IsVariable ? 1 : 0)
-					+ (Object.IsVariable ? 1 : 0);
-			}
-			
-			public override string ToString() {
-				return Subject + " " + Predicate + " " + Object;
-			}
-		}
-		
-		class QueryResult {
-			public ResSet[] Bindings;
-			public bool[] StatementMatched;
-			
-			public QueryResult(GraphMatch q) {
-				Bindings = new ResSet[q.variables.Length];
-				StatementMatched = new bool[q.statements.Length];
-			}
-			private QueryResult(int x, int y) {
-				Bindings = new ResSet[x];
-				StatementMatched = new bool[y];
-			}
-			public void Add(QueryStatement qs, Statement bs) {
-				if (qs.Subject.IsVariable) Add(qs.Subject.VarIndex, bs.Subject);
-				if (qs.Predicate.IsVariable) Add(qs.Predicate.VarIndex, bs.Predicate);
-				if (qs.Object.IsVariable) Add(qs.Object.VarIndex, bs.Object);
-			}
-			void Add(int varIndex, Resource binding) {
-				if (Bindings[varIndex] == null) Bindings[varIndex] = new ResSet();
-				Bindings[varIndex].Add(binding);
-			}
-			public void Clear(QueryStatement qs) {
-				if (qs.Subject.IsVariable && Bindings[qs.Subject.VarIndex] != null) Bindings[qs.Subject.VarIndex].Clear();
-				if (qs.Predicate.IsVariable && Bindings[qs.Predicate.VarIndex] != null) Bindings[qs.Predicate.VarIndex].Clear();
-				if (qs.Object.IsVariable && Bindings[qs.Object.VarIndex] != null) Bindings[qs.Object.VarIndex].Clear();
-			}
-			public void Set(QueryStatement qs, Statement bs) {
-				if (qs.Subject.IsVariable) Set(qs.Subject.VarIndex, bs.Subject);
-				if (qs.Predicate.IsVariable) Set(qs.Predicate.VarIndex, bs.Predicate);
-				if (qs.Object.IsVariable) Set(qs.Object.VarIndex, bs.Object);
-			}
-			void Set(int varIndex, Resource binding) {
-				if (Bindings[varIndex] == null) Bindings[varIndex] = new ResSet();
-				else Bindings[varIndex].Clear();
-				Bindings[varIndex].Add(binding);
-			}
-			public QueryResult Clone() {
-				QueryResult r = new QueryResult(Bindings.Length, StatementMatched.Length);
-				for (int i = 0; i < Bindings.Length; i++)
-					if (Bindings[i] != null)
-						r.Bindings[i] = Bindings[i].Clone();
-				for (int i = 0; i < StatementMatched.Length; i++)
-					r.StatementMatched[i] = StatementMatched[i];
-				return r;
-			}
-		}
-		
-		class BindingSet {
-			public ArrayList Results = new ArrayList();
-			public QueryResult Union;
-			
-			public BindingSet(GraphMatch q) {
-				Union = new QueryResult(q);
-			}
-		}
-		
-		public void MakeDistinct(BNode a, BNode b) {
-			SetupVariablesDistinct d = new SetupVariablesDistinct();
-			d.a = a;
-			d.b = b;
-			setupVariablesDistinct.Add(d);
-		}
-		
-		public void AddValueFilter(Entity entity, LiteralFilter filter) {
-			SetupValueFilter d = new SetupValueFilter();
-			d.a = entity;
-			d.b = filter;
-			setupValueFilters.Add(d);
-		}
-		
-		public void AddEdge(Statement filter) {
-			setupStatements.Add(filter);
-		}
-
-		public void AddOptionalEdge(Statement filter) {
-			setupOptionalStatements.Add(filter);
-		}
-		
-		private class SetupVariablesDistinct {
-			public Entity a, b;
-		}
-		private class SetupValueFilter {
-			public Entity a;
-			public LiteralFilter b;
+	public abstract class QueryResultSink {
+		public virtual void Init(Variable[] variables) {
 		}
 		
-		private void CheckInit() {
-			lock (sync) {
-				if (!init) {
-					Init();
-					init = true;
-				}
-			}
-		}
+		public abstract bool Add(VariableBindings result);
 
-		private static Entity qLimit = "http://purl.oclc.org/NET/rsquary/returnLimit";;
-		private static Entity qStart = "http://purl.oclc.org/NET/rsquary/returnStart";;
-		private static Entity qDistinctFrom = "http://purl.oclc.org/NET/rsquary/distinctFrom";;
-		private static Entity qOptional = "http://purl.oclc.org/NET/rsquary/optional";;
-		
-		public GraphMatch() {
+		public virtual void Finished() {
 		}
 		
-		public GraphMatch(RdfReader query) :
-			this(new MemoryStore(query),
-				query.BaseUri == null ? null : new Entity(query.BaseUri)) {
+		public virtual void AddComments(string comments) {
 		}
+	}
+	
+	public class QueryResultBuffer : QueryResultSink
+	#if !DOTNET2
+	, IEnumerable
+	#else
+	, IEnumerable<VariableBindings>
+	#endif
+	{
+		Variable[] variables;
 
-		public GraphMatch(Store queryModel) : this(queryModel, null) {
+		#if !DOTNET2
+		ArrayList bindings = new ArrayList();
+		ArrayList comments = new ArrayList();
+		#else
+		List<VariableBindings> bindings = new List<VariableBindings>();
+		List<string> comments = new List<string>();
+		#endif
+		
+
+		public override void Init(Variable[] variables) {
+			this.variables = new Variable[variables.Length];
+			variables.CopyTo(this.variables, 0);
 		}
-		
-		private GraphMatch(Store queryModel, Entity queryNode) {
-			// Find the query options
-			if (queryNode != null) {
-				ReturnStart = GetIntOption(queryModel, queryNode, qStart);
-				ReturnLimit = GetIntOption(queryModel, queryNode, qLimit);
-			}
 
-			// Search the query for 'distinct' predicates between variables.
-			foreach (Statement s in queryModel.Select(new Statement(null, qDistinctFrom, null))) {
-				if (!(s.Object is BNode)) continue;
-				MakeDistinct((BNode)s.Subject, (BNode)s.Object);
-			}
-			
-			// Add all statements except the query predicates and value filters into a
-			// new store with just the statements relevant to the search.
-			foreach (Statement s in queryModel.Select(Statement.All)) {
-				if (IsQueryPredicate(s.Predicate)) continue;
-				
-				/*if (s.Predicate.Uri != null && extraValueFilters != null && extraValueFilters.Contains(s.Predicate.Uri)) {
-					ValueFilterFactory f = (ValueFilterFactory)extraValueFilters[s.Predicate.Uri];
-					AddValueFilter(s.Subject, f.GetValueFilter(s.Predicate.Uri, s.Object));
-					continue;
-				} else {
-					ValueFilter f = ValueFilter.GetValueFilter(s.Predicate, s.Object);
-					if (f != null) {
-						AddValueFilter(s.Subject, f);
-						continue;
-					}
-				}*/
-				
-				if (s.Meta == Statement.DefaultMeta)
-					AddEdge(s);
-				else if (queryNode != null && queryModel.Contains(new Statement(queryNode, qOptional, s.Meta)))
-					AddOptionalEdge(s);
-			}
+		public override bool Add(VariableBindings result) {
+			bindings.Add(result);
+			return true;
 		}
 		
-		private int GetIntOption(Store queryModel, Entity query, Entity predicate) {
-			Resource[] rr = queryModel.SelectObjects(query, predicate);
-			if (rr.Length == 0) return -1;
-			Resource r = rr[0];
-			if (r == null || !(r is Literal)) return -1;
-			try {
-				return int.Parse(((Literal)r).Value);
-			} catch (Exception e) {
-				return -1;
-			}
-		}		
-
-		private bool IsQueryPredicate(Entity e) {
-			if (e == qDistinctFrom) return true;
-			if (e == qLimit) return true;
-			if (e == qStart) return true;
-			if (e == qOptional) return true;
-			return false;
-		}
-		
-		public override string GetExplanation() {
-			CheckInit();
-			string ret = "Query:\n";
-			foreach (Statement s in novariablestatements)
-				ret += " Check: " + s + "\n";
-			foreach (QueryStatement[] sgroup in statements) {
-				ret += " ";
-				if (sgroup.Length != 1)
-					ret += "{";
-				foreach (QueryStatement s in sgroup) {
-					ret += s.ToString();
-					if (s.Optional) ret += " (Optional)";
-					if (sgroup.Length != 1)
-						ret += " & ";
-				}
-				if (sgroup.Length != 1)
-					ret += "}";
-				ret += "\n";
-			}
-			return ret;
+		public override void AddComments(string comment) {
+			comments.Add(comment);
 		}
 		
-		public override void Run(SelectableSource targetModel, QueryResultSink result) {
-			CheckInit();
-			
-			foreach (Statement s in novariablestatements)
-				if (!targetModel.Contains(s))
-					return;
-			
-			VariableBinding[] finalbindings = new VariableBinding[variables.Length];
-			for (int i = 0; i < variables.Length; i++)
-				finalbindings[i].Variable = variableEntities[i];
-			
-			result.Init(finalbindings, true, false);
-			
-			Debug("Begnning Query");
-			
-			BindingSet bindings = new BindingSet(this);
-			for (int group = 0; group < statements.Length; group++) {
-				bool ret = Query(group, bindings, targetModel);
-				if (!ret) {
-					// A false return value indicates the query
-					// certainly failed -- a non-optional statement
-					// failed to match at all.
-					result.Finished();
-					return;
-				}
-			}
-
-			int ctr = -1;
-			foreach (QueryResult r in bindings.Results) {
-				Permutation permutation = new Permutation(r.Bindings);
-				do {
-					ctr++;
-					if (ctr < ReturnStart) continue;
-					for (int i = 0; i < variables.Length; i++)
-						finalbindings[i].Target = permutation[i];
-					result.Add(finalbindings);
-					if (ReturnLimit != -1 && ctr == ReturnStart+ReturnLimit) break;	
-				} while (permutation.Next());
-				if (ReturnLimit != -1 && ctr == ReturnStart+ReturnLimit) break;	
-			}
+		public Variable[] Variables { get { return variables; } }
 
-			
-			result.Finished();
-		}
+		#if !DOTNET2
+		public IList Bindings { get { return bindings; } }
+		public IList Comments { get { return comments; } }
+		#else
+		public List<VariableBindings> Bindings { get { return bindings; } }
+		public List<string> Comments { get { return comments; } }
+		#endif
 		
-		class Permutation {
-			public int[] index;
-			public Resource[][] values;
-			
-			public Resource this[int i] {
-				get {
-					return values[i][index[i]];
-				}
-			}
-			
-			public Permutation(ResSet[] bindings) {
-				index = new int[bindings.Length];
-				values = new Resource[bindings.Length][];
-				for (int i = 0; i < bindings.Length; i++) {
-					values[i] = new Resource[bindings[i] == null ? 1 : bindings[i].Count];
-					if (bindings[i] != null) {
-						int ctr = 0;
-						foreach (Resource r in bindings[i])
-							values[i][ctr++] = r;
-					}
-				}
-			}
-			public bool Next() {
-				for (int i = 0; i < index.Length; i++) {
-					index[i]++;
-					if (index[i] < values[i].Length) break;
-					
-					index[i] = 0;
-					if (i == index.Length-1) return false;
-				}
-				return true;
-			}
+		System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
+			return Bindings.GetEnumerator();
 		}
-		
-		private void Debug(string message) {
-			//Console.Error.WriteLine(message);
-		}
-		
-		class BindingEnumerator {
-			IEnumerator[] loops = new IEnumerator[3];
-			int loop = 0;
-			
-			public BindingEnumerator(QueryStatement qs, QueryResult bindings) {
-				loops[0] = GetBindings(qs.Subject, bindings);
-				loops[1] = GetBindings(qs.Predicate, bindings);
-				loops[2] = GetBindings(qs.Object, bindings);
-			}
-			
-			public bool MoveNext(out Entity s, out Entity p, out Resource o) {
-				while (true) {
-					bool b = loops[loop].MoveNext();
-					if (!b) {
-						 if (loop == 0) { s = null; p = null; o = null; return false; }
-						 loops[loop].Reset();
-						 loop--;
-						 continue;
-					}
-					
-					if (loop <= 1) {
-						object obj = loops[loop].Current;
-						if (obj != null && !(obj is Entity)) continue;
-					}
-					
-					if (loop < 2) { loop++; continue; }
-					
-					s = loops[0].Current as Entity; 
-					p = loops[1].Current as Entity; 
-					o = loops[2].Current as Resource;
-					return true; 
-				}
-			}
+		#if DOTNET2
+		IEnumerator<VariableBindings> IEnumerable<VariableBindings>.GetEnumerator() {
+			return Bindings.GetEnumerator();
 		}
+		#endif
+	}
+	
+	public class VariableBindings {
+		Variable[] vars;
+		Resource[] vals;
 
-		private bool Query(int groupindex, BindingSet bindings, SelectableSource targetModel) {
-			QueryStatement[] group = statements[groupindex];
-			
-			QueryStatement qs = group[0];
-			
-			int numMultiplyBound = IsMultiplyBound(qs.Subject, bindings)
-				+ IsMultiplyBound(qs.Predicate, bindings)
-				+ IsMultiplyBound(qs.Object, bindings);
-			
-			if (numMultiplyBound >= 1) {
-				// If there is one or more multiply-bound variable,
-				// then we need to iterate through the permutations
-				// of the variables in the statement.
-				
-				Debug(qs.ToString() + " Something Multiply Bound");
-				
-				MemoryStore matches = new MemoryStore();
-				targetModel.Select(
-					new SelectFilter(
-						(Entity[])qs.Subject.GetValues(bindings.Union, true),
-						(Entity[])qs.Predicate.GetValues(bindings.Union, true),
-						qs.Object.GetValues(bindings.Union, false),
-						QueryMeta == null ? null : new Entity[] { QueryMeta }
-						),
-					new ClearMetaDupCheck(matches));
-				
-				Debug("\t" + matches.StatementCount + " Matches");
-				
-				if (matches.StatementCount == 0) {
-					// This statement doesn't match any of
-					// the existing bindings.  If this was
-					// optional, preserve the bindings.
-					return qs.Optional;
-				}
-				
-				// We need to preserve the pairings of
-				// the multiply bound variable with the matching
-				// statements.
-				
-				ArrayList newbindings = new ArrayList();
-				
-				if (!qs.Optional) bindings.Union.Clear(qs);
-				
-				foreach (QueryResult binding in bindings.Results) {
-					// Break apart the permutations in this binding.
-					BindingEnumerator enumer2 = new BindingEnumerator(qs, binding);
-					Entity s, p;
-					Resource o;
-					while (enumer2.MoveNext(out s, out p, out o)) {
-						// Get the matching statements from the union query
-						Statement bs = new Statement(s, p, o);
-						MemoryStore innermatches = matches.Select(bs).Load();
-						
-						// If no matches, the binding didn't match the filter.
-						if (innermatches.StatementCount == 0) {
-							if (qs.Optional) {
-								// Preserve the binding.
-								QueryResult bc = binding.Clone();
-								bc.Set(qs, bs);
-								newbindings.Add(bc);
-								continue;
-							} else {
-								// Toss out the binding.
-								continue;
-							}
-						}
-						
-						for (int si = 0; si < innermatches.StatementCount; si++) {
-							Statement m = innermatches[si];
-							if (!MatchesFilters(m, qs, targetModel)) {
-								if (qs.Optional) {
-									QueryResult bc = binding.Clone();
-									bc.Set(qs, bs);
-									newbindings.Add(bc);
-								}
-								continue;
-							}
-							bindings.Union.Add(qs, m);
-							
-							QueryResult r = binding.Clone();
-							r.Set(qs, m);
-							r.StatementMatched[groupindex] = true;
-							newbindings.Add(r);
-						}
-					}
-				}
-				
-				bindings.Results = newbindings;
-				
-			} else {
-				// There are no multiply bound variables, but if
-				// there are more than two unbound variables,
-				// we need to be sure to preserve the pairings
-				// of the matching values.
-			
-				int numUnbound = IsUnbound(qs.Subject, bindings)
-					+ IsUnbound(qs.Predicate, bindings)
-					+ IsUnbound(qs.Object, bindings);
-					
-				bool sunbound = IsUnbound(qs.Subject, bindings) == 1;
-				bool punbound = IsUnbound(qs.Predicate, bindings) == 1;
-				bool ounbound = IsUnbound(qs.Object, bindings) == 1;
-				
-				Statement s = GetStatement(qs, bindings);
-				
-				// If we couldn't get a statement out of this,
-				// then if this was not an optional filter,
-				// fail.  If this was optional, don't change
-				// the bindings any. 
-				if (s == StatementFailed) return qs.Optional;
-				
-				if (numUnbound == 0) {
-					Debug(qs.ToString() + " All bound");
-					
-					// All variables are singly bound already.
-					// We can just test if the statement exists.
-					if (targetModel.Contains(s)) {
-						// Mark each binding that it matched this statement.
-						foreach (QueryResult r in bindings.Results)
-							r.StatementMatched[groupindex] = true;
-					} else {
-						return qs.Optional;
-					}
-				
-				} else if (numUnbound == 1) {
-					Debug(qs.ToString() + " 1 Unbound");
-				
-					// There is just one unbound variable.  The others
-					// are not multiply bound, so they must be uniquely
-					// bound (but they may not be bound in all results).
-					// Run a combined select to find all possible values
-					// of the unbound variable at once, and set these to
-					// be the values of the variable for matching results.
-					
-					ResSet values = new ResSet();
-					MemoryStore ms = new MemoryStore();
-					targetModel.Select(s, ms);
-					for (int si = 0; si < ms.StatementCount; si++) {
-						Statement match = ms[si];
-						if (!MatchesFilters(match, qs, targetModel)) continue;
-						if (sunbound) values.Add(match.Subject);
-						if (punbound) values.Add(match.Predicate);
-						if (ounbound) values.Add(match.Object);
-					}
-					
-					Debug("\t" + values.Count + " matches");
-					
-					if (values.Count == 0)
-						return qs.Optional;
-						
-					int varIndex = -1;
-					if (sunbound) varIndex = qs.Subject.VarIndex;
-					if (punbound) varIndex = qs.Predicate.VarIndex;
-					if (ounbound) varIndex = qs.Object.VarIndex;
-					
-					if (bindings.Results.Count == 0)
-						bindings.Results.Add(new QueryResult(this));
-					
-					bindings.Union.Bindings[varIndex] = new ResSet();
-					foreach (Resource r in values)
-						bindings.Union.Bindings[varIndex].Add(r);
-						
-					foreach (QueryResult r in bindings.Results) {
-						// Check that the bound variables are bound in this result.
-						// If it is bound, it will be bound to the correct resource,
-						// but it might not be bound at all if an optional statement
-						// failed to match -- in which case, don't modify the binding.
-						if (qs.Subject.IsVariable && !sunbound && r.Bindings[qs.Subject.VarIndex] == null) continue;
-						if (qs.Predicate.IsVariable && !punbound && r.Bindings[qs.Predicate.VarIndex] == null) continue;
-						if (qs.Object.IsVariable && !ounbound && r.Bindings[qs.Object.VarIndex] == null) continue;
-					
-						r.Bindings[varIndex] = values;
-						r.StatementMatched[groupindex] = true;
-					}
-					
-				} else {
-					// There are two or more unbound variables, the
-					// third variable being uniquely bound, if bound.
-					// Keep track of the pairing of unbound variables.
-					
-					if (numUnbound == 3)
-						throw new QueryExecutionException("Query would select all statements in the store.");
-					
-					Debug(qs.ToString() + " 2 or 3 Unbound");
-				
-					if (bindings.Results.Count == 0)
-						bindings.Results.Add(new QueryResult(this));
-						
-					ArrayList newbindings = new ArrayList();
-					MemoryStore ms = new MemoryStore();
-					targetModel.Select(s, ms);
-					for (int si = 0; si < ms.StatementCount; si++) {
-						Statement match = ms[si];
-						if (!MatchesFilters(match, qs, targetModel)) continue;
-						bindings.Union.Add(qs, match);
-						foreach (QueryResult r in bindings.Results) {
-							if (numUnbound == 2) {
-								// Check that the bound variable is bound in this result.
-								// If it is bound, it will be bound to the correct resource,
-								// but it might not be bound at all if an optional statement
-								// failed to match -- in which case, preserve the binding if
-								// this was an optional statement.
-								bool matches = true;
-								if (qs.Subject.IsVariable && !sunbound && r.Bindings[qs.Subject.VarIndex] == null) matches = false;
-								if (qs.Predicate.IsVariable && !punbound && r.Bindings[qs.Predicate.VarIndex] == null) matches = false;
-								if (qs.Object.IsVariable && !ounbound && r.Bindings[qs.Object.VarIndex] == null) matches = false;
-								if (!matches) {
-									if (qs.Optional)
-										newbindings.Add(r);
-									continue;
-								}
-							}
-						
-							QueryResult r2 = r.Clone();
-							r2.Add(qs, match);
-							r2.StatementMatched[groupindex] = true;
-							newbindings.Add(r2);
-						}
-					}
-					if (newbindings.Count == 0)
-						return qs.Optional; // don't clear out bindings if this was optional and it failed
-					bindings.Results = newbindings;
-				}
-			}
-			
-			return true;
+		public VariableBindings(Variable[] vars, Resource[] vals) {
+			this.vars = vars;
+			this.vals = vals;
+			if (vars.Length != vals.Length) throw new ArgumentException("Arrays do not have the same length.");
 		}
 		
-		static Resource[] ResourceArrayNull = new Resource[] { null };
+		public int Count { get { return vars.Length; } }
 		
-		private static IEnumerator GetBindings(VarOrAnchor e, QueryResult bindings) {
-			if (!e.IsVariable) {
-				if (e.ArrayOfAnchor == null)
-					e.ArrayOfAnchor = new Resource[] { e.Anchor };
-				return e.ArrayOfAnchor.GetEnumerator();
-			}
-			if (bindings.Bindings[e.VarIndex] == null) return ResourceArrayNull.GetEnumerator();
-			return bindings.Bindings[e.VarIndex].Items.GetEnumerator();
-		}
-		private int IsMultiplyBound(VarOrAnchor e, BindingSet bindings) {
-			if (!e.IsVariable) return 0;
-			if (bindings.Union.Bindings[e.VarIndex] == null) return 0;
-			if (bindings.Union.Bindings[e.VarIndex].Items.Count == 1) return 0;
-			return 1;
-		}
-		private int IsUnbound(VarOrAnchor e, BindingSet bindings) {
-			if (!e.IsVariable) return 0;
-			if (bindings.Union.Bindings[e.VarIndex] == null) return 1;
-			return 0;
-		}
-		
-		private Resource GetUniqueBinding(VarOrAnchor e, BindingSet bindings) {
-			if (!e.IsVariable) return e.Anchor;
-			if (bindings.Union.Bindings[e.VarIndex] == null || bindings.Union.Bindings[e.VarIndex].Count == 0) return null;
-			if (bindings.Union.Bindings[e.VarIndex].Count > 1) throw new Exception();
-			foreach (Resource r in bindings.Union.Bindings[e.VarIndex].Items)
-				return r;
-			throw new Exception();
-		}
-		
-		Statement StatementFailed = new Statement(null, null, null);
-		
-		private Statement GetStatement(QueryStatement sq, BindingSet bindings) {
-			Resource s = GetUniqueBinding(sq.Subject, bindings);
-			Resource p = GetUniqueBinding(sq.Predicate, bindings);
-			Resource o = GetUniqueBinding(sq.Object, bindings);
-			if (s is Literal || p is Literal) return StatementFailed;
-			return new Statement((Entity)s, (Entity)p, o, QueryMeta);
-		}
-		
-		bool MatchesFilters(Statement s, QueryStatement q, SelectableSource targetModel) {
-			return MatchesFilters(s.Subject, q.Subject, targetModel)
-				&& MatchesFilters(s.Predicate, q.Predicate, targetModel)
-				&& MatchesFilters(s.Object, q.Object, targetModel);
-		}
-		
-		bool MatchesFilters(Resource e, VarOrAnchor var, SelectableSource targetModel) {
-			if (!var.IsVariable) return true;
-			/*foreach (ValueFilter f in variables[var.VarIndex].Filters) {
-				if (!f.Filter(e, targetModel)) return false;
-			}*/
-			return true;
-		}
+		#if !DOTNET2
+		public Variable[] Variables { get { return vars; } }
+		public Resource[] Values { get { return vals; } }
+		#else
+		public IList<Variable> Variables { get { return vars; } }
+		public IList<Resource> Values { get { return vals; } }
+		#endif
 		
-		class ClearMetaDupCheck : StatementSink {
-			MemoryStore m;
-			public ClearMetaDupCheck(MemoryStore m) { this.m = m; }
-			public bool Add(Statement s) {
-				// remove meta information
-				s = new Statement(s.Subject, s.Predicate, s.Object);
-				if (!m.Contains(s))
-					m.Add(s);
-				return true;
+		public Resource this[Variable variable] {
+			get {
+				for (int i = 0; i < vars.Length; i++)
+					if (vars[i] == variable)
+						return vals[i];
+				throw new ArgumentException();
 			}
 		}
-		
-		private void Init() {
-			// Get the list of variables, which is the set
-			// of anonymous nodes in the statements to match.
-			ArrayList setupVariables = new ArrayList();
-			
-			if (setupStatements.Count == 0)
-				throw new QueryFormatException("A query must have at least one non-optional statement.");
-			
-			foreach (Statement s in setupStatements) {
-				InitAnonVariable(s.Subject, setupVariables);
-				InitAnonVariable(s.Predicate, setupVariables);
-				InitAnonVariable(s.Object, setupVariables);
-			}
-			foreach (Statement s in setupOptionalStatements) {
-				InitAnonVariable(s.Subject, setupVariables);
-				InitAnonVariable(s.Predicate, setupVariables);
-				InitAnonVariable(s.Object, setupVariables);
-			}
-		
-			// Set up the variables array.
-			variables = new Variable[setupVariables.Count];
-			variableEntities = new SemWeb.Variable[variables.Length];
-			Hashtable varIndex = new Hashtable();
-			for (int i = 0; i < variables.Length; i++) {
-				variables[i].Entity = (SemWeb.Variable)setupVariables[i];
-				variableEntities[i] = variables[i].Entity;
-				varIndex[variables[i].Entity] = i;
-				
-				ArrayList filters = new ArrayList();
-				foreach (SetupValueFilter filter in setupValueFilters) {
-					if (filter.a == variables[i].Entity)
-						filters.Add(filter.b);
-				}
-				
-				//variables[i].Filters = (ValueFilter[])filters.ToArray(typeof(ValueFilter));
-			}
-			
-			// Set up the statements
-			ArrayList statements = new ArrayList();
-			foreach (Statement st in setupStatements)
-				InitSetStatement(st, statements, varIndex, false);
-			foreach (Statement st in setupOptionalStatements)
-				InitSetStatement(st, statements, varIndex, true);
-			
-			// Order the statements in the most efficient order
-			// for the recursive query.
-			Hashtable setVars = new Hashtable();
-			ArrayList sgroups = new ArrayList();
-			while (statements.Count > 0) {
-				QueryStatement[] group = InitBuildNode(statements, setVars);
-				sgroups.Add(group);
-				foreach (QueryStatement qs in group) {
-					if (qs.Subject.IsVariable) setVars[qs.Subject.VarIndex] = setVars;
-					if (qs.Predicate.IsVariable) setVars[qs.Predicate.VarIndex] = setVars;
-					if (qs.Object.IsVariable) setVars[qs.Object.VarIndex] = setVars;
-				}
-			}
-			
-			this.statements = (QueryStatement[][])sgroups.ToArray(typeof(QueryStatement[]));
-		}
-		
-		private void InitAnonVariable(Resource r, ArrayList setupVariables) {
-			if (r is SemWeb.Variable)
-				setupVariables.Add(r);
-		}
-		
-		private void InitSetStatement(Statement st, ArrayList statements, Hashtable varIndex, bool optional) {
-			QueryStatement qs = new QueryStatement();
-			
-			InitSetStatement(st.Subject, ref qs.Subject, varIndex);
-			InitSetStatement(st.Predicate, ref qs.Predicate, varIndex);
-			InitSetStatement(st.Object, ref qs.Object, varIndex);
-			
-			qs.Optional = optional;
-			
-			// If this statement has no variables, add it to a separate list.
-			if (!qs.Subject.IsVariable && !qs.Predicate.IsVariable && !qs.Object.IsVariable)
-				novariablestatements.Add(st);
-			else
-				statements.Add(qs);
-		}
-		
-		private void InitSetStatement(Resource ent, ref VarOrAnchor st, Hashtable varIndex) {
-			if (!varIndex.ContainsKey(ent)) {
-				st.IsVariable = false;
-				st.Anchor = ent;
-			} else {
-				st.IsVariable = true;
-				st.VarIndex = (int)varIndex[ent];
-			}
-		}
-		
-		private class QueryStatementComparer : IComparer {
-			Hashtable setVars;
-			ResSet fps, ifps;
-			
-			public QueryStatementComparer(Hashtable setVars, ResSet fps, ResSet ifps) {
-				this.setVars = setVars;
-				this.fps = fps;
-				this.ifps = ifps;
-			}
-		
-			int IComparer.Compare(object a, object b) {
-				return Compare((QueryStatement)a, (QueryStatement)b);
-			}
-			
-			public int Compare(QueryStatement a, QueryStatement b) {
-				int optional = a.Optional.CompareTo(b.Optional);
-				if (optional != 0) return optional;
-				
-				int numvars = NumVars(a).CompareTo(NumVars(b));
-				if (numvars != 0) return numvars;
-				
-				int complexity = Complexity(a).CompareTo(Complexity(b));
-				return complexity;
-			}
-			
-			private int NumVars(QueryStatement s) {
-				int ret = 0;
-				if (s.Subject.IsVariable && !setVars.ContainsKey(s.Subject.VarIndex))
-					ret++;
-				if (s.Predicate.IsVariable && !setVars.ContainsKey(s.Predicate.VarIndex))
-					ret++;
-				if (s.Object.IsVariable && !setVars.ContainsKey(s.Object.VarIndex))
-					ret++;
-				return ret;
-			}
-			
-			private int Complexity(QueryStatement s) {
-				if (s.Predicate.IsVariable) return 2;
-				if ((!s.Subject.IsVariable || setVars.ContainsKey(s.Subject.VarIndex))
-					&& fps.Contains(s.Predicate.Anchor))
-					return 0;
-				if ((!s.Object.IsVariable || setVars.ContainsKey(s.Object.VarIndex))
-					&& ifps.Contains(s.Predicate.Anchor))
-					return 0;
-				return 1;
-			}
-		}
-		
-		private QueryStatement[] InitBuildNode(ArrayList statements, Hashtable setVars) {
-			// Get the best statements to consider
-			// Because we can consider statements in groups, we need
-			// a list of lists.
-			QueryStatementComparer comparer = new QueryStatementComparer(setVars, fps, ifps);
-			ArrayList considerations = new ArrayList();
-			for (int i = 0; i < statements.Count; i++) {
-				QueryStatement next = (QueryStatement)statements[i];
-				int comp = 1;
-				if (considerations.Count > 0) {
-					QueryStatement curcomp = (QueryStatement) ((ArrayList)considerations[0])[0];
-					comp = comparer.Compare(curcomp, next);
-				}
-				
-				if (comp < 0) // next is worse than current
-					continue;
-				
-				if (comp > 0) // clear out worse possibilities
-					considerations.Clear();
-				
-				ArrayList group = new ArrayList();
-				group.Add(next);
-				considerations.Add(group);
-			}
-			
-			// Pick the group with the most number of statements.
-			ArrayList bestgroup = null;
-			foreach (ArrayList g in considerations) {
-				if (bestgroup == null || bestgroup.Count < g.Count)
-					bestgroup = g;
-			}
-			
-			foreach (QueryStatement qs in bestgroup)
-				statements.Remove(qs);
-			
-			return (QueryStatement[])bestgroup.ToArray(typeof(QueryStatement));
-		}
-
-	}
-	
-	public abstract class QueryResultSink {
-		public virtual void Init(VariableBinding[] variables, bool distinct, bool ordered) {
-		}
-		
-		public abstract bool Add(VariableBinding[] result);
 
-		public virtual void Finished() {
-		}
-		
-		public virtual void AddComments(string comments) {
-		}
-	}
-	
-	internal class QueryResultBufferSink : QueryResultSink {
-		public ArrayList Bindings = new ArrayList();
-		public override bool Add(VariableBinding[] result) {
-			Bindings.Add(result.Clone());
-			return true;
-		}
-	}
-
-	public struct VariableBinding {
-		Variable v;
-		Resource t;
-		
-		public VariableBinding(Variable variable, Resource target) {
-			v = variable;
-			t = target;
+		public Resource this[string variableName] {
+			get {
+				for (int i = 0; i < vars.Length; i++)
+					if (vars[i].LocalName != null && vars[i].LocalName == variableName)
+						return vals[i];
+				throw new ArgumentException();
+			}
 		}
 		
-		public Variable Variable { get { return v; } set { v = value; } }
-		public string Name { get { return v.LocalName; } }
-		public Resource Target { get { return t; } set { t = value; } }
-
-		public static Statement Substitute(VariableBinding[] variables, Statement template) {
+		public Statement Substitute(Statement template) {
 			// This may throw an InvalidCastException if a variable binds
 			// to a literal but was used as the subject, predicate, or meta
 			// of the template.
-			foreach (VariableBinding v in variables) {
-				if (v.Variable == template.Subject) template = new Statement((Entity)v.Target, template.Predicate, template.Object, template.Meta);
-				if (v.Variable == template.Predicate) template = new Statement(template.Subject, (Entity)v.Target, template.Object, template.Meta);
-				if (v.Variable == template.Object) template = new Statement(template.Subject, template.Predicate, v.Target, template.Meta);
-				if (v.Variable == template.Meta) template = new Statement(template.Subject, template.Predicate, template.Object, (Entity)v.Target);
+			for (int i = 0; i < vars.Length; i++) {
+				if (vars[i] == template.Subject) template = new Statement((Entity)vals[i], template.Predicate, template.Object, template.Meta);
+				if (vars[i] == template.Predicate) template = new Statement(template.Subject, (Entity)vals[i], template.Object, template.Meta);
+				if (vars[i] == template.Object) template = new Statement(template.Subject, template.Predicate, vals[i], template.Meta);
+				if (vars[i] == template.Meta) template = new Statement(template.Subject, template.Predicate, template.Object, (Entity)vals[i]);
 			}
 			return template;
 		}
+		
+		public override string ToString() {
+			String ret = "";
+			for (int i = 0; i < vars.Length; i++) {
+				ret += vars[i] + "=>" + vals[i] + "; ";
+			}
+			return ret;
+		}
 	}
 }
 

Modified: branches/beagle-rdf/Util/SemWeb/RDFS.cs
==============================================================================
--- branches/beagle-rdf/Util/SemWeb/RDFS.cs	(original)
+++ branches/beagle-rdf/Util/SemWeb/RDFS.cs	Sat Jan 26 23:26:03 2008
@@ -5,14 +5,23 @@
 using SemWeb.Stores;
 using SemWeb.Util;
 
+#if !DOTNET2
+using ResourceList = System.Collections.ICollection;
+using VarKnownValuesType = System.Collections.Hashtable;
+#else
+using ResourceList = System.Collections.Generic.ICollection<SemWeb.Resource>;
+using VarKnownValuesType = System.Collections.Generic.Dictionary<SemWeb.Variable,System.Collections.Generic.ICollection<SemWeb.Resource>>;
+#endif
+
 namespace SemWeb.Inference {
 
-	public class RDFS : SelectableSource, SupportsPersistableBNodes, IDisposable {
+	public class RDFS : Reasoner {
 		static readonly Entity type = NS.RDF + "type";
 		static readonly Entity subClassOf = NS.RDFS + "subClassOf";
 		static readonly Entity subPropertyOf = NS.RDFS + "subPropertyOf";
 		static readonly Entity domain = NS.RDFS + "domain";
 		static readonly Entity range = NS.RDFS + "range";
+		static readonly Entity rdfsresource = NS.RDFS + "Resource";
 	
 		// Each of these hashtables relates an entity
 		// to a ResSet of other entities, including itself.
@@ -29,61 +38,52 @@
 		Hashtable domainof = new Hashtable();
 		Hashtable rangeof = new Hashtable();
 		
-		SelectableSource data;
-		
 		StatementSink schemasink;
 		
-		public RDFS(SelectableSource data) {
-			this.data = data;
+		public RDFS() {
 			schemasink = new SchemaSink(this);
 		}
 		
-		public RDFS(StatementSource schema, SelectableSource data)
-		: this(data) {
+		public RDFS(StatementSource schema) : this() {
 			LoadSchema(schema);
 		}
 		
-		void IDisposable.Dispose() {
-			if (data is IDisposable)
-				((IDisposable)data).Dispose();
-		}
-		
 		public StatementSink Schema { get { return schemasink; } }
 		
-		string SupportsPersistableBNodes.GetStoreGuid() { if (data is SupportsPersistableBNodes) return ((SupportsPersistableBNodes)data).GetStoreGuid(); return null; }
-		
-		string SupportsPersistableBNodes.GetNodeId(BNode node) { if (data is SupportsPersistableBNodes) return ((SupportsPersistableBNodes)data).GetNodeId(node); return null; }
-		
-		BNode SupportsPersistableBNodes.GetNodeFromId(string persistentId) { if (data is SupportsPersistableBNodes) return ((SupportsPersistableBNodes)data).GetNodeFromId(persistentId); return null; }
-
 		class SchemaSink : StatementSink {
 			RDFS rdfs;
 			public SchemaSink(RDFS parent) { rdfs = parent; }
-			bool StatementSink.Add(Statement s) { rdfs.Add(s); return true; }
+			bool StatementSink.Add(Statement s) { rdfs.AddAxiom(s); return true; }
 		}
 		
-		void Add(Statement schemastatement) {
-			if (schemastatement.Predicate == subClassOf && schemastatement.Object is Entity)
-				AddRelation(schemastatement.Subject, (Entity)schemastatement.Object, superclasses, subclasses, true);
+		void AddAxiom(Statement schemastatement) {
+			if (schemastatement.Predicate == subClassOf && schemastatement.Object is Entity) {
+				AddRelation(schemastatement.Subject, (Entity)schemastatement.Object, superclasses, subclasses);
+				AddRelation(schemastatement.Subject, rdfsresource, superclasses, subclasses);
+				AddRelation((Entity)schemastatement.Object, rdfsresource, superclasses, subclasses);
+			}
 			if (schemastatement.Predicate == subPropertyOf && schemastatement.Object is Entity)
-				AddRelation(schemastatement.Subject, (Entity)schemastatement.Object, superprops, subprops, true);
-			if (schemastatement.Predicate == domain && schemastatement.Object is Entity)
-				AddRelation(schemastatement.Subject, (Entity)schemastatement.Object, domains, domainof, false);
-			if (schemastatement.Predicate == range && schemastatement.Object is Entity)
-				AddRelation(schemastatement.Subject, (Entity)schemastatement.Object, ranges, rangeof, false);
+				AddRelation(schemastatement.Subject, (Entity)schemastatement.Object, superprops, subprops);
+			if (schemastatement.Predicate == domain && schemastatement.Object is Entity) {
+				AddRelation(schemastatement.Subject, (Entity)schemastatement.Object, domains, domainof);
+				AddRelation((Entity)schemastatement.Object, rdfsresource, superclasses, subclasses);
+			}
+			if (schemastatement.Predicate == range && schemastatement.Object is Entity) {
+				AddRelation(schemastatement.Subject, (Entity)schemastatement.Object, ranges, rangeof);
+				AddRelation((Entity)schemastatement.Object, rdfsresource, superclasses, subclasses);
+			}
 		}
 		
-		void AddRelation(Entity a, Entity b, Hashtable supers, Hashtable subs, bool incself) {
-			AddRelation(a, b, supers, incself);
-			AddRelation(b, a, subs, incself);
+		void AddRelation(Entity a, Entity b, Hashtable supers, Hashtable subs) {
+			AddRelation(a, b, supers);
+			AddRelation(b, a, subs);
 		}
 		
-		void AddRelation(Entity a, Entity b, Hashtable h, bool incself) {
+		void AddRelation(Entity a, Entity b, Hashtable h) {
 			ResSet r = (ResSet)h[a];
 			if (r == null) {
 				r = new ResSet();
 				h[a] = r;
-				if (incself) r.Add(a);
 			}
 			r.Add(b);
 		}
@@ -100,24 +100,9 @@
 			}
 		}
 		
-		public bool Distinct { get { return false; } }
-		
-		public void Select(StatementSink sink) { data.Select(sink); }
+		public override bool Distinct { get { return false; } }
 		
-		public bool Contains(Statement template) {
-			return Store.DefaultContains(this, template);
-		}
-		
-		public void Select(Statement template, StatementSink sink) {
-			if (template.Predicate == null) {
-				data.Select(template, sink);
-				return;
-			}
-			
-			Select(new SelectFilter(template), sink);
-		}
-		
-		public void Select(SelectFilter filter, StatementSink sink) {
+		public override void Select(SelectFilter filter, SelectableSource data, StatementSink sink) {
 			if (filter.Predicates == null || filter.LiteralFilters != null) {
 				data.Select(filter, sink);
 				return;
@@ -137,30 +122,32 @@
 						// or what things have those types?
 						
 						// Expand objects by the subclass closure of the objects
-						data.Select(new SelectFilter(subjects, new Entity[] { p }, GetClosure(objects, subclasses), metas), sink);
+						data.Select(new SelectFilter(subjects, new Entity[] { p }, GetClosure(objects, subclasses, true), metas), sink);
 						
 						// Process domains and ranges.
 						ResSet dom = new ResSet(), ran = new ResSet();
 						Hashtable domPropToType = new Hashtable();
 						Hashtable ranPropToType = new Hashtable();
-						foreach (Entity e in objects) {
-							Entity[] dc = GetClosure((ResSet)domainof[e], subprops);
+						foreach (Entity e in GetClosure(objects, subclasses, true)) {
+							Entity[] dc = GetClosure((ResSet)domainof[e], subprops, true);
 							if (dc != null)
 							foreach (Entity c in dc) {
 								dom.Add(c);
-								AddRelation(c, e, domPropToType, false);
+								AddRelation(c, e, domPropToType);
 							}
 							
-							dc = GetClosure((ResSet)rangeof[e], subprops);
+							dc = GetClosure((ResSet)rangeof[e], subprops, true);
 							if (dc != null)
 							foreach (Entity c in dc) {
 								ran.Add(c);
-								AddRelation(c, e, ranPropToType, false);
+								AddRelation(c, e, ranPropToType);
 							}
 						}
 						
 						// If it's in the domain of any of these properties,
-						// we know its type.
+						// we know its type.  Only do this if subjects are given,
+						// since otherwise we have to select for all of the values
+						// of all of these properties, and that doesn't scale well.
 						if (subjects != null) {
 							if (dom.Count > 0) data.Select(new SelectFilter(subjects, dom.ToEntityArray(), null, metas), new ExpandDomRan(0, domPropToType, sink));
 							if (ran.Count > 0) data.Select(new SelectFilter(null, ran.ToEntityArray(), subjects, metas), new ExpandDomRan(1, ranPropToType, sink));
@@ -189,17 +176,17 @@
 					
 					if (subjects != null && objects != null) {
 						// Expand objects by the subs closure of the objects.
-						data.Select(new SelectFilter(subjects, new Entity[] { p }, GetClosure(objects, subs), metas), sink);
+						data.Select(new SelectFilter(subjects, new Entity[] { p }, GetClosure(objects, subs, true), metas), sink);
 					} else if (subjects != null) {
 						// get all of the supers of all of the subjects
 						foreach (Entity s in subjects)
-							foreach (Entity o in GetClosure(new Entity[] { s }, supers))
+							foreach (Entity o in GetClosure(s, supers, false))
 								sink.Add(new Statement(s, p, o));
 					} else if (objects != null) {
 						// get all of the subs of all of the objects
 						foreach (Resource o in objects) {
 							if (o is Literal) continue;
-							foreach (Entity s in GetClosure(new Entity[] { (Entity)o }, subs))
+							foreach (Entity s in GetClosure((Entity)o, subs, false))
 								sink.Add(new Statement(s, p, (Entity)o));
 						}
 					} else {
@@ -220,8 +207,8 @@
 				ResSet qprops = new ResSet();
 				Hashtable propfrom = new Hashtable();
 				foreach (Entity p in remainingPredicates) { 
-					foreach (Entity sp in GetClosure(new Entity[] { p }, subprops)) {
-						AddRelation(sp, p, propfrom, false);
+					foreach (Entity sp in GetClosure(p, subprops, true)) {
+						AddRelation(sp, p, propfrom);
 						qprops.Add(sp);
 					}
 				}
@@ -236,27 +223,33 @@
 			}
 		}
 		
-		static Entity[] GetClosure(ResSet starts, Hashtable table) {
+		static Entity[] GetClosure(Entity start, Hashtable table, bool includeStart) {
+			return GetClosure( new Resource[] { start } , table, includeStart);
+		}
+
+		static Entity[] GetClosure(ResSet starts, Hashtable table, bool includeStarts) {
 			if (starts == null) return null;
-			return GetClosure(starts.ToArray(), table);
+			return GetClosure(starts.ToArray(), table, includeStarts);
 		}
 		
-		static Entity[] GetClosure(Resource[] starts, Hashtable table) {
+		static Entity[] GetClosure(Resource[] starts, Hashtable table, bool includeStarts) {
 			ResSet ret = new ResSet();
 			ResSet toadd = new ResSet(starts);
+			bool firstRound = true;
 			while (toadd.Count > 0) {
 				ResSet newadd = new ResSet();
 				
 				foreach (Resource e in toadd) {
 					if (!(e is Entity)) continue;
 					if (ret.Contains(e)) continue;
-					ret.Add(e);
+					if (!(firstRound && !includeStarts)) ret.Add(e);
 					if (table.ContainsKey(e))
 						newadd.AddRange((ResSet)table[e]);
 				}
 				
 				toadd.Clear();
 				toadd.AddRange(newadd);
+				firstRound = false;
 			}
 			return ret.ToEntityArray();
 		}
@@ -266,7 +259,7 @@
 			StatementSink sink;
 			public Expand(Hashtable t, StatementSink s) { table = t; sink = s; }
 			public bool Add(Statement s) {
-				foreach (Entity e in RDFS.GetClosure(new Resource[] { s.Object }, table))
+				foreach (Entity e in RDFS.GetClosure(new Resource[] { s.Object }, table, true))
 					if (!sink.Add(new Statement(s.Subject, s.Predicate, e, s.Meta)))
 						return false;
 				return true;
@@ -310,7 +303,7 @@
 				if (domran == 1 && !(s.Object is Entity)) return true;
 				ResSet rs = (ResSet)table[s.Predicate];
 				if (rs == null) return true;
-				foreach (Entity e in RDFS.GetClosure(rs, superclasses)) {
+				foreach (Entity e in RDFS.GetClosure(rs, superclasses, true)) {
 					Statement s1 = new Statement(
 						domran == 0 ? s.Subject : (Entity)s.Object,
 						type,
@@ -361,6 +354,109 @@
 				}
 			}
 		}
+		
+		public override SemWeb.Query.MetaQueryResult MetaQuery(Statement[] graph, SemWeb.Query.QueryOptions options, SelectableSource data) {
+			Statement[] graph2;
+			SemWeb.Query.QueryOptions options2;
+			RewriteGraph(graph, options, out graph2, out options2, null);
+			
+			if (!(data is QueryableSource))
+				return new SimpleEntailment().MetaQuery(graph2, options2, data);
+			else
+				return ((QueryableSource)data).MetaQuery(graph2, options2);
+		}
+
+		public override void Query(Statement[] graph, SemWeb.Query.QueryOptions options, SelectableSource data, SemWeb.Query.QueryResultSink sink) {
+			Statement[] graph2;
+			SemWeb.Query.QueryOptions options2;
+			RewriteGraph(graph, options, out graph2, out options2, sink);
+			
+			// TODO: Because we add variables to the query when we replace things with closures,
+			// we should filter the query results so we don't pass back the bindings for those
+			// variables to the caller.
+		
+			if (!(data is QueryableSource))
+				new SimpleEntailment().Query(graph2, options2, data, sink);
+			else
+				((QueryableSource)data).Query(graph2, options2, sink);
+		}
+
+		void RewriteGraph(Statement[] graph, SemWeb.Query.QueryOptions options, out Statement[] graph2, out SemWeb.Query.QueryOptions options2, SemWeb.Query.QueryResultSink sink) {
+			graph2 = new Statement[graph.Length];
+			options2 = new SemWeb.Query.QueryOptions();
+			
+			options2.DistinguishedVariables = options.DistinguishedVariables;
+			options2.Limit = options.Limit;
+			options2.VariableKnownValues = (options.VariableKnownValues == null ? new VarKnownValuesType() : new VarKnownValuesType(options.VariableKnownValues));
+			options2.VariableLiteralFilters = options.VariableLiteralFilters;
+			
+			for (int i = 0; i < graph.Length; i++) {
+				graph2[i] = graph[i];
+			
+				//ResSet subj = GetQueryRes(graph[i], 0, options);
+				ResSet pred = GetQueryRes(graph[i], 1, options);
+				ResSet obj = GetQueryRes(graph[i], 2, options);
+				
+				if (pred.Count == 1 && pred.Contains(type)) {
+					// in an ?x rdf:type ___ query, replace ___ with the subclass closure of ___.
+					if (obj.Count > 0) {
+						Entity[] sc = GetClosure(obj, subclasses, true);
+						if (sc.Length != obj.Count && sink != null)
+							sink.AddComments("Expanding object of " + graph[i] + " with subclass closure to [" + ToString(sc) + "]");
+						SetQueryRes(ref graph2[i], 2, options2, sc);
+					}
+				}
+				
+				// expand properties into subproperties after the above tests,
+				// because we want to be sure the property was originally
+				// just one of the recognized properties
+
+				if (pred.Count > 0) {
+					Entity[] pc = GetClosure(pred, subprops, true);
+					SetQueryRes(ref graph2[i], 1, options2, pc);
+					if (pc.Length != pred.Count && sink != null)
+						sink.AddComments("Expanding predicate of " + graph[i] + " with subproperty closure to [" + ToString(pc) + "]");
+				}
+			}
+		}
+
+		ResSet GetQueryRes(Statement s, int i, SemWeb.Query.QueryOptions options) {
+			ResSet ret = new ResSet();
+			Resource r = s.GetComponent(i);
+			if (r == null) return ret;
+
+			if (!(r is Variable)) ret.Add(r);
+			
+			if (options.VariableKnownValues != null && r is Variable
+#if !DOTNET2
+				&& options.VariableKnownValues.Contains((Variable)r)) {
+#else
+				&& options.VariableKnownValues.ContainsKey((Variable)r)) {
+#endif
+				ret.AddRange((ResourceList)options.VariableKnownValues[(Variable)r]);
+			}
+			return ret;
+		}
+		
+		void SetQueryRes(ref Statement s, int i, SemWeb.Query.QueryOptions options, Entity[] values) {
+			// TODO: what if s had originally a variable in position i?
+			if (values.Length == 0)
+				s.SetComponent(i, null);
+			else if (values.Length == 1)
+				s.SetComponent(i, values[0]);
+			else {
+				Variable v = new Variable();
+				s.SetComponent(i, v);
+				options.VariableKnownValues[v] = values;
+			}
+		}
+		
+		string ToString(Entity[] ents) {
+			string[] names = new string[ents.Length];
+			for (int i = 0; i < ents.Length; i++)
+				names[i] = ents[i].ToString();
+			return String.Join(" , ", names);
+		}
 	}
 
 }

Modified: branches/beagle-rdf/Util/SemWeb/RdfReader.cs
==============================================================================
--- branches/beagle-rdf/Util/SemWeb/RdfReader.cs	(original)
+++ branches/beagle-rdf/Util/SemWeb/RdfReader.cs	Sat Jan 26 23:26:03 2008
@@ -2,6 +2,16 @@
 using System.Collections;
 using System.IO;
 using System.Web;
+
+#if !DOTNET2
+using VariableSet = System.Collections.Hashtable;
+using VariableList = System.Collections.ICollection;
+using WarningsList = System.Collections.ArrayList;
+#else
+using VariableSet = System.Collections.Generic.Dictionary<SemWeb.Variable,SemWeb.Variable>;
+using VariableList = System.Collections.Generic.ICollection<SemWeb.Variable>;
+using WarningsList = System.Collections.Generic.List<string>;
+#endif
  
 namespace SemWeb {
 	public class ParserException : ApplicationException {
@@ -12,8 +22,8 @@
 	public abstract class RdfReader : StatementSource, IDisposable {
 		Entity meta = Statement.DefaultMeta;
 		string baseuri = null;
-		ArrayList warnings = new ArrayList();
-		Hashtable variables = new Hashtable();
+		WarningsList warnings = new WarningsList();
+		VariableSet variables = new VariableSet();
 		bool reuseentities = false;
 		NamespaceManager nsmgr = new NamespaceManager();
 
@@ -48,9 +58,13 @@
 		
 		public NamespaceManager Namespaces { get { return nsmgr; } }
 		
-		public ICollection Variables { get { return variables.Keys; } }
+		public VariableList Variables { get { return variables.Keys; } }
 		
+		#if !DOTNET2
 		public IList Warnings { get { return ArrayList.ReadOnly(warnings); } }
+		#else
+		public System.Collections.Generic.ICollection<string> Warnings { get { return warnings.AsReadOnly(); } }
+		#endif
 		
 		protected void AddVariable(Variable variable) {
 			variables[variable] = variable;
@@ -61,19 +75,50 @@
 		public virtual void Dispose() {
 		}
 		
+		internal static string NormalizeMimeType(string type) {
+			switch (type) {
+				case "text/xml":
+				case "application/xml":
+				case "application/rdf+xml":
+					return "xml";
+
+				case "text/n3":
+				case "text/rdf+n3":
+				case "application/n3":
+				case "application/turtle":
+				case "application/x-turtle":
+					return "n3";
+			}
+			
+			return type;
+		}
+		
 		public static RdfReader Create(string type, string source) {
+			type = NormalizeMimeType(type);
+		
 			switch (type) {
 				case "xml":
-				case "text/xml":
 					return new RdfXmlReader(source);
 				case "n3":
-				case "text/n3":
 					return new N3Reader(source);
 				default:
-					throw new ArgumentException("Unknown parser type: " + type);
+					throw new ArgumentException("Unknown parser or MIME type: " + type);
 			}
 		}
 		
+		public static RdfReader Create(string type, Stream source) {
+			type = NormalizeMimeType(type);
+
+			switch (type) {
+				case "xml":
+					return new RdfXmlReader(source);
+				case "n3":
+					return new N3Reader(new StreamReader(source, System.Text.Encoding.UTF8));
+				default:
+					throw new ArgumentException("Unknown parser or MIME type: " + type);
+			}
+		}
+
 		public static RdfReader LoadFromUri(Uri webresource) {
 			// TODO: Add Accept header for HTTP resources.
 			
@@ -83,28 +128,29 @@
 			string mimetype = resp.ContentType;
 			if (mimetype.IndexOf(';') > -1)
 				mimetype = mimetype.Substring(0, mimetype.IndexOf(';'));
+				
+			mimetype = NormalizeMimeType(mimetype.Trim());
 			
-			switch (mimetype.Trim()) {
-				case "text/xml":
-				case "application/xml":
-				case "application/rss+xml":
-				case "application/rdf+xml":
-					return new RdfXmlReader(resp.GetResponseStream());
+			RdfReader reader;
+			
+			if (mimetype == "xml" || mimetype == "application/rss+xml")
+				reader = new RdfXmlReader(resp.GetResponseStream());
 					
-				case "text/rdf+n3":
-				case "application/n3":
-				case "application/turtle":
-				case "application/x-turtle":
-					return new N3Reader(new StreamReader(resp.GetResponseStream(), System.Text.Encoding.UTF8));
-			}
+			else if (mimetype == "n3")
+				reader = new N3Reader(new StreamReader(resp.GetResponseStream(), System.Text.Encoding.UTF8));
 			
-			if (webresource.LocalPath.EndsWith(".rdf") || webresource.LocalPath.EndsWith(".xml") || webresource.LocalPath.EndsWith(".rss"))
-				return new RdfXmlReader(resp.GetResponseStream());
+			else if (webresource.LocalPath.EndsWith(".rdf") || webresource.LocalPath.EndsWith(".xml") || webresource.LocalPath.EndsWith(".rss"))
+				reader = new RdfXmlReader(resp.GetResponseStream());
 			
-			if (webresource.LocalPath.EndsWith(".n3") || webresource.LocalPath.EndsWith(".ttl") || webresource.LocalPath.EndsWith(".nt"))
-				return new N3Reader(new StreamReader(resp.GetResponseStream(), System.Text.Encoding.UTF8));
+			else if (webresource.LocalPath.EndsWith(".n3") || webresource.LocalPath.EndsWith(".ttl") || webresource.LocalPath.EndsWith(".nt"))
+				reader = new N3Reader(new StreamReader(resp.GetResponseStream(), System.Text.Encoding.UTF8));
 
-			throw new InvalidOperationException("Could not determine the RDF format of the resource.");
+			else
+				throw new InvalidOperationException("Could not determine the RDF format of the resource.");
+				
+			reader.BaseUri = resp.ResponseUri.ToString();
+			
+			return reader;
 		}
 		
 		internal static TextReader GetReader(string file) {
@@ -118,8 +164,8 @@
 		
 		internal string GetAbsoluteUri(string baseuri, string uri) {
 			if (baseuri == null) {
-				//if (uri == "")
-				//throw new ParserException("An empty relative URI was found in the document but could not be converted into an absolute URI because no base URI is known for the document.");
+				if (uri == "")
+					throw new ParserException("An empty relative URI was found in the document but could not be converted into an absolute URI because no base URI is known for the document.");
 				return uri;
 			}
 			if (uri.IndexOf(':') != -1) return uri;
@@ -127,7 +173,7 @@
 				UriBuilder b = new UriBuilder(baseuri);
 				b.Fragment = null; // per W3 RDF/XML test suite
 				return new Uri(b.Uri, uri, true).ToString();
-			} catch (UriFormatException e) {
+			} catch (UriFormatException) {
 				return baseuri + uri;
 			}			
 		}

Modified: branches/beagle-rdf/Util/SemWeb/RdfWriter.cs
==============================================================================
--- branches/beagle-rdf/Util/SemWeb/RdfWriter.cs	(original)
+++ branches/beagle-rdf/Util/SemWeb/RdfWriter.cs	Sat Jan 26 23:26:03 2008
@@ -51,5 +51,35 @@
 		void IDisposable.Dispose() {
 			Close();
 		}
+		
+		public static RdfWriter Create(string type, TextWriter output) {
+			switch (RdfReader.NormalizeMimeType(type)) {
+				case "xml":
+					#if !SILVERLIGHT
+						return new RdfXmlWriter(output);
+					#else
+						throw new NotSupportedException("RDF/XML output is not supported by the Silverlight build of the SemWeb library.");
+					#endif
+				case "n3":
+					return new N3Writer(output);
+				default:
+					throw new ArgumentException("Unknown parser or MIME type: " + type);
+			}
+		}
+
+		public static RdfWriter Create(string type, string file) {
+			switch (RdfReader.NormalizeMimeType(type)) {
+				case "xml":
+					#if !SILVERLIGHT
+						return new RdfXmlWriter(file);
+					#else
+						throw new NotSupportedException("RDF/XML output is not supported by the Silverlight build of the SemWeb library.");
+					#endif
+				case "n3":
+					return new N3Writer(file);
+				default:
+					throw new ArgumentException("Unknown parser or MIME type: " + type);
+			}
+		}
 	}
 }

Modified: branches/beagle-rdf/Util/SemWeb/RdfXmlReader.cs
==============================================================================
--- branches/beagle-rdf/Util/SemWeb/RdfXmlReader.cs	(original)
+++ branches/beagle-rdf/Util/SemWeb/RdfXmlReader.cs	Sat Jan 26 23:26:03 2008
@@ -28,25 +28,57 @@
 			rdfObject = "http://www.w3.org/1999/02/22-rdf-syntax-ns#object";,
 			rdfStatement = "http://www.w3.org/1999/02/22-rdf-syntax-ns#Statement";;
 		
+		#if !SILVERLIGHT
 		public RdfXmlReader(XmlDocument document) {
 			xml = new XmlBaseAwareReader(new XmlNodeReader(document));
+			LoadNamespaces();
 		}
+		#endif
 		
 		public RdfXmlReader(XmlReader document) {
-			XmlValidatingReader reader = new XmlValidatingReader(document);
+			XmlValidatingReader reader = new XmlValidatingReader(document); // decodes entity definitions
 			reader.ValidationType = ValidationType.None;
 			xml = new XmlBaseAwareReader(reader);
+			LoadNamespaces();
 		}
 		
 		public RdfXmlReader(TextReader document) : this(new XmlTextReader(document)) {
 		}
 
-		public RdfXmlReader(Stream document) : this(new XmlTextReader(document)) {
+		public RdfXmlReader(Stream document) : this(new StreamReader(document)) {
 		}
 
-		public RdfXmlReader(string file) : this(GetReader(file)) {
+		public RdfXmlReader(TextReader document, string baseUri) : this(document) {
+			BaseUri = baseUri;
+		}
+
+		public RdfXmlReader(Stream document, string baseUri) : this(new StreamReader(document), baseUri) {
+		}
+
+		public RdfXmlReader(string file, string baseUri) : this(GetReader(file), baseUri) {
+		}
+
+		public RdfXmlReader(string file) : this(GetReader(file), "file:///" + file) {
 		}
 		
+		private void LoadNamespaces() {
+			// Move to the document element and load any namespace
+			// declarations on the node.
+
+			while (xml.Read()) {
+				if (xml.NodeType != XmlNodeType.Element) continue;
+
+				if (xml.MoveToFirstAttribute()) {
+					do {
+						if (xml.Prefix == "xmlns")
+							Namespaces.AddNamespace(xml.Value, xml.LocalName);
+					} while (xml.MoveToNextAttribute());
+					xml.MoveToElement();
+				}
+				break;
+			}
+		}
+
 		public override void Select(StatementSink storage) {
 			// Read past the processing instructions to
 			// the document element.  If it is rdf:RDF,
@@ -55,8 +87,14 @@
 			// description.
 			
 			this.storage = storage;
-									
-			while (xml.Read()) {
+
+			bool first = true; // on the first iteration don't
+											   // advance to the next node -- we already did that
+			while (first || xml.Read()) {
+				first = false;
+
+				if (xml.NodeType != XmlNodeType.Element) continue;
+				
 				if (xml.NamespaceURI == NS.RDF && xml.LocalName == "RDF" ) {
 					// If there is an xml:base here, set BaseUri so
 					// the application can recover it.  It doesn't
@@ -69,8 +107,12 @@
 						if (xml.NodeType == XmlNodeType.Element)
 							ParseDescription();
 					}
-					break;
+					
+				} else {
+					ParseDescription();
+				
 				}
+				break;
 			}
 
 			xml.Close();
@@ -87,7 +129,14 @@
 			if (xml.NamespaceURI == "" && BaseUri == null)
 				return "#" + xml.LocalName;
 
-			return xml.NamespaceURI + xml.LocalName;
+			return CheckUri(xml.NamespaceURI + xml.LocalName);
+		}
+		
+		private string CheckUri(string uri) {
+			string error = Entity.ValidateUri(uri);
+			if (error != null)
+				OnWarning("The URI <" + uri + "> is not valid: " + error);
+			return uri;
 		}
 		
 		private int isset(string attribute) {
@@ -95,7 +144,7 @@
 		}
 		
 		private string Unrelativize(string uri) {
-			return GetAbsoluteUri(xml.BaseURI != "" ? xml.BaseURI : BaseUri, uri);
+			return CheckUri(GetAbsoluteUri(xml.BaseURI != "" ? xml.BaseURI : BaseUri, uri));
 		}
 		
 		private Entity GetBlankNode(string nodeID) {
@@ -127,8 +176,6 @@
 			
 			string nodeID = xml.GetAttribute("nodeID", NS.RDF);
 			string about = xml.GetAttribute("about", NS.RDF);
-			//if (about == null)
-			//	about = xml.GetAttribute("about");
 			string ID = xml.GetAttribute("ID", NS.RDF);
 			if (isset(nodeID) + isset(about) + isset(ID) > 1)
 				OnError("An entity description cannot specify more than one of rdf:nodeID, rdf:about, and rdf:ID");
@@ -155,11 +202,12 @@
 			
 			// If the name of the element is not rdf:Description,
 			// then the name gives its type.
-			if (CurNode() != NS.RDF + "Description") {
-				if (IsRestrictedName(CurNode()) || IsDeprecatedName(CurNode()))
+			string curnode = CurNode();
+			if (curnode != NS.RDF + "Description") {
+				if (IsRestrictedName(curnode) || IsDeprecatedName(curnode))
 					OnError(xml.Name + " cannot be the type of a resource.");
-				if (CurNode() == NS.RDF + "li") OnError("rdf:li cannot be the type of a resource");
-				storage.Add(new Statement(entity, rdfType, (Entity)CurNode(), Meta));
+				if (curnode == NS.RDF + "li") OnError("rdf:li cannot be the type of a resource");
+				storage.Add(new Statement(entity, rdfType, (Entity)curnode, Meta));
 			}
 			
 			ParsePropertyAttributes(entity);
@@ -271,9 +319,6 @@
 
 			string ID = xml.GetAttribute("ID", NS.RDF);
 
-			//Console.WriteLine ("({0})({1})({2})({3})({4})({5})({6})",
-			//	nodeID, resource, parseType, datatype, lang, predicate, ID);
-
 			if (nodeID != null && !IsValidXmlName(nodeID))
 				OnWarning("'" + nodeID + "' is not a valid XML Name");
 			if (ID != null && !IsValidXmlName(ID))
@@ -306,8 +351,10 @@
 				}
 			
 			} else if (parseType != null && parseType == "Literal") {
-				if (datatype == null)
-					datatype = "http://www.w3.org/1999/02/22-rdf-syntax-ns#XMLLiteral";;
+				if (datatype != null)
+					OnError("The attribute rdf:datatype is not valid on a predicate whose parseType is Literal.");
+
+				datatype = "http://www.w3.org/1999/02/22-rdf-syntax-ns#XMLLiteral";;
 				
 				if (ParsePropertyAttributes(new BNode()))
 					OnError("Property attributes are not valid when parseType is Literal");

Modified: branches/beagle-rdf/Util/SemWeb/RdfXmlWriter.cs
==============================================================================
--- branches/beagle-rdf/Util/SemWeb/RdfXmlWriter.cs	(original)
+++ branches/beagle-rdf/Util/SemWeb/RdfXmlWriter.cs	Sat Jan 26 23:26:03 2008
@@ -6,13 +6,43 @@
 
 using SemWeb;
 
+// Since this class relies on the XmlDocument class,
+// it must be excluded completely from the Silverlight
+// build.
+#if !SILVERLIGHT
+
 namespace SemWeb {
 	public class RdfXmlWriter : RdfWriter {
+	
+		public class Options {
+			public bool UseTypedNodes = true;
+			public bool UseRdfID = true;
+			public bool UseRdfLI = true;
+			public bool EmbedNamedNodes = true;
+			public bool UsePredicateAttributes = true;
+			public bool UseParseTypeLiteral = true;
+			
+			internal bool UseParseTypeResource = false; // this is broken because it uses Clone(), which breaks references in Hashtables
+			
+			public static Options Full = new Options();
+			public static Options XMP;
+			
+			static Options() {
+				XMP = new Options();
+				XMP.UseTypedNodes = false;
+				XMP.UseRdfID = false;
+				XMP.UseParseTypeLiteral = false;
+				XMP.UsePredicateAttributes = false;
+			}
+		}
+				
+		Options opts;
 		XmlWriter writer;
 		NamespaceManager ns = new NamespaceManager();
 		
 		XmlDocument doc;
 		bool initialized = false;
+		bool closeStream = false;
 		
 		Hashtable nodeMap = new Hashtable();
 		
@@ -21,14 +51,37 @@
 		Hashtable nameAlloc = new Hashtable();
 		Hashtable nodeReferences = new Hashtable();
 		ArrayList predicateNodes = new ArrayList();
+		Hashtable nodeLiCounter = new Hashtable();
 		
 		static Entity rdftype = "http://www.w3.org/1999/02/22-rdf-syntax-ns#type";;
+		static Entity rdfli = "http://www.w3.org/1999/02/22-rdf-syntax-ns#li";;
+		static string RDFNS_ = NS.RDF + "_";
+		
+		public RdfXmlWriter(XmlDocument dest) : this(dest, Options.Full) { }
+		
+		public RdfXmlWriter(string file) : this (file, Options.Full) { }
+
+		public RdfXmlWriter(TextWriter writer) : this(writer, Options.Full) { }
+		
+		public RdfXmlWriter(XmlWriter writer) : this(writer, Options.Full) { }
 		
-		public RdfXmlWriter(XmlDocument dest) { doc = dest; }
+		public RdfXmlWriter(XmlDocument dest, Options style) {
+			if (dest == null) throw new ArgumentNullException("dest");
+			if (style == null) throw new ArgumentNullException("style");
+			doc = dest;
+			opts = style;
+		}
 		
-		public RdfXmlWriter(string file) : this(GetWriter(file)) { }
+		public RdfXmlWriter(string file, Options style) : this(GetWriter(file), style) { closeStream = true; }
 
-		public RdfXmlWriter(TextWriter writer) : this(NewWriter(writer)) { }
+		public RdfXmlWriter(TextWriter writer, Options style) : this(NewWriter(writer), style) { }
+		
+		public RdfXmlWriter(XmlWriter writer, Options style) {
+			if (writer == null) throw new ArgumentNullException("writer");
+			if (style == null) throw new ArgumentNullException("style");
+			this.writer = writer;
+			this.opts = style;
+		}
 		
 		private static XmlWriter NewWriter(TextWriter writer) {
 			XmlTextWriter ret = new XmlTextWriter(writer);
@@ -39,10 +92,6 @@
 			return ret;
 		}
 		
-		public RdfXmlWriter(XmlWriter writer) {
-			this.writer = writer;
-		}
-		
 		private void Start() {
 			if (initialized) return;
 			initialized = true;
@@ -50,7 +99,6 @@
 			if (doc == null) doc = new XmlDocument();
 			
 			doc.AppendChild(doc.CreateXmlDeclaration("1.0", null, null));
-			
 			string rdfprefix = ns.GetPrefix(NS.RDF);
 			if (rdfprefix == null) {
 				if (ns.GetNamespace("rdf") == null) {
@@ -104,16 +152,13 @@
 			
 			// TODO: Make sure the local name (here and anywhere in this
 			// class) is a valid XML name.
+			
 			if (Namespaces.GetPrefix(n) != null) {
 				prefix = Namespaces.GetPrefix(n);
 				return;
 			}
 			
 			prefix = uri.Substring(prev+1, last-prev-1);
-
-			if (prefix == "xmlns")
-				prefix = "";
-
 			
 			// Remove all non-xmlable (letter) characters.
 			StringBuilder newprefix = new StringBuilder();
@@ -122,8 +167,8 @@
 					newprefix.Append(c);
 			prefix = newprefix.ToString();
 			
-			if (prefix.Length == 0) {
-				// There were no letters in the prefix!
+			if (prefix.Length == 0 || prefix == "xmlns") {
+				// There were no letters in the prefix or the prefix was "xmlns", which isn't valid!
 				prefix = "ns";
 			}
 			
@@ -161,17 +206,34 @@
 				// Check if we have to add new type information to the existing node.
 				if (ret.NamespaceURI + ret.LocalName == NS.RDF + "Description") {
 					// Replace the untyped node with a typed node, copying in
-					// all of the children of the old node.
+					// all of the attributes and children of the old node.
 					string prefix, localname;
 					Normalize(type, out prefix, out localname);
 					XmlElement newnode = doc.CreateElement(prefix + ":" + localname, ns.GetNamespace(prefix));
 					
-					foreach (XmlNode childnode in ret) {
-						newnode.AppendChild(childnode.Clone());
+					ArrayList children = new ArrayList();
+					foreach (XmlNode childnode in ret)
+						children.Add(childnode);
+					foreach (XmlNode childnode in children) {
+						ret.RemoveChild(childnode);
+						newnode.AppendChild(childnode);
 					}
-					
+						
+					foreach (XmlAttribute childattr in ret.Attributes)
+						newnode.Attributes.Append((XmlAttribute)childattr.Clone());
+						
 					ret.ParentNode.ReplaceChild(newnode, ret);
+					
 					nodeMap[entity] = newnode;
+					if (nodeReferences.ContainsKey(ret)) {
+						nodeReferences[newnode] = nodeReferences[ret];
+						nodeReferences.Remove(ret);
+					}
+					if (nodeLiCounter.ContainsKey(ret)) {
+						nodeLiCounter[newnode] = nodeLiCounter[ret];
+						nodeLiCounter.Remove(ret);
+					}
+					
 					return newnode;
 				} else {
 					// The node is already typed, so just add a type predicate.
@@ -198,6 +260,8 @@
 					SetAttribute(node, NS.RDF, ns.GetPrefix(NS.RDF), "about", uri);
 				else if (fragment.Length == 0)
 					SetAttribute(node, NS.RDF, ns.GetPrefix(NS.RDF), "about", "");
+				else if (!opts.UseRdfID)
+					SetAttribute(node, NS.RDF, ns.GetPrefix(NS.RDF), "about", uri);
 				else
 					SetAttribute(node, NS.RDF, ns.GetPrefix(NS.RDF), "ID", fragment.Substring(1)); // chop off hash
 			} else {
@@ -216,7 +280,19 @@
 		
 		private XmlElement CreatePredicate(XmlElement subject, Entity predicate) {
 			if (predicate.Uri == null)
-				throw new InvalidOperationException("Predicates cannot be blank nodes.");
+				throw new InvalidOperationException("Predicates cannot be blank nodes when serializing RDF to XML.");
+			
+			if (opts.UseRdfLI && predicate.Uri.StartsWith(RDFNS_)) {
+				try {
+					int n = int.Parse(predicate.Uri.Substring(RDFNS_.Length));
+					int expected = nodeLiCounter.ContainsKey(subject) ? (int)nodeLiCounter[subject] : 1;
+					if (n == expected) {
+						predicate = rdfli;
+						nodeLiCounter[subject] = expected+1;
+					}
+				} catch {
+				}
+			}			
 			
 			string prefix, localname;
 			Normalize(predicate.Uri, out prefix, out localname);
@@ -231,14 +307,27 @@
 		
 			XmlElement subjnode;
 			
-			bool hastype = statement.Predicate == rdftype && statement.Object.Uri != null;
+			bool hastype = opts.UseTypedNodes && statement.Predicate == rdftype && statement.Object.Uri != null;
 			subjnode = GetNode(statement.Subject, hastype ? statement.Object.Uri : null, null);
 			if (hastype) return;
 
 			XmlElement prednode = CreatePredicate(subjnode, statement.Predicate);
 			
 			if (!(statement.Object is Literal)) {
-				if (nodeMap.ContainsKey(statement.Object)) {
+				if (!nodeMap.ContainsKey(statement.Object) && (opts.EmbedNamedNodes || statement.Object.Uri == null)) {
+					// Embed the object node right within the predicate node
+					// if we haven't already created a node for the object
+					// and if we're allowed to do so.
+					GetNode((Entity)statement.Object, null, prednode);
+
+				} else {
+					// Otherwise, we will reference the object with a
+					// rdf:resource or rdf:nodeID attribute.
+					
+					// Create the object node at top-level if a node doesn't exist.
+					if (!nodeMap.ContainsKey(statement.Object))
+						GetNode((Entity)statement.Object, null, null);
+
 					if (statement.Object.Uri != null) {
 						string uri = statement.Object.Uri, fragment;
 						if (Relativize(statement.Object.Uri, out fragment))
@@ -257,12 +346,10 @@
 						nodeReferences[nodeMap[statement.Object]] = null;
 					else
 						nodeReferences[nodeMap[statement.Object]] = prednode;
-				} else {
-					GetNode((Entity)statement.Object, null, prednode);
 				}
 			} else {
 				Literal literal = (Literal)statement.Object;
-				if (literal.DataType != null && literal.DataType == "http://www.w3.org/1999/02/22-rdf-syntax-ns#XMLLiteral";) {
+				if (opts.UseParseTypeLiteral && literal.DataType != null && literal.DataType == "http://www.w3.org/1999/02/22-rdf-syntax-ns#XMLLiteral";) {
 					prednode.InnerXml = literal.Value;
 					SetAttribute(prednode, NS.RDF, ns.GetPrefix(NS.RDF), "parseType", "Literal");
 				} else {
@@ -290,18 +377,38 @@
 			}
 		}
 		
-		public override void Close() {
-			Start(); // make sure the document node was written
-
+		void MakeDocumentNice() {
 			// For any node that was referenced by exactly one predicate,
 			// move the node into that predicate, provided the subject
 			// isn't itself!
 			foreach (DictionaryEntry e in nodeReferences) {
-				if (e.Value == null) continue; // referenced by more than one predicate
 				XmlElement node = (XmlElement)e.Key;
 				XmlElement predicate = (XmlElement)e.Value;
-				if (node.ParentNode != node.OwnerDocument.DocumentElement) continue; // already referenced somewhere
-				if (predicate.ParentNode == node) continue; // can't insert node as child of itself
+				
+				// Node is already embedded somewhere.
+				if (node.ParentNode != node.OwnerDocument.DocumentElement)
+					continue;
+				
+				// Node is referenced by more than one predicate
+				if (predicate == null) continue;
+
+				// The option to do this for named nodes is turned off.
+				if (!opts.EmbedNamedNodes && node.HasAttribute("about", NS.RDF))
+					continue;
+				
+				// we can have circular references between nodes (also
+				// between a node and itself),
+				// which we can't nicely collapse this way.  Make sure
+				// that the predicate we want to insert ourselves into
+				// is not a descendant of the node we're moving!
+				XmlNode ancestry = predicate.ParentNode;
+				bool canMove = true;
+				while (ancestry != null) {
+					if (ancestry == node) { canMove = false; break; }
+					ancestry = ancestry.ParentNode;
+				}
+				if (!canMove) continue;
+
 				node.ParentNode.RemoveChild(node);
 				predicate.AppendChild(node);
 				predicate.RemoveAttribute("resource", NS.RDF); // it's on the lower node
@@ -328,15 +435,19 @@
 				if (obj.Attributes.Count == 1 && obj.Attributes[0].NamespaceURI+obj.Attributes[0].LocalName != NS.RDF+"about") continue;
 				
 				// See if all its predicates are literal with no attributes.
+				bool hasSimpleLits = false;
 				bool allSimpleLits = true;
 				foreach (XmlElement opred in obj.ChildNodes) {
 					if (opred.FirstChild is XmlElement)
 						allSimpleLits = false;
 					if (opred.Attributes.Count > 0)
 						allSimpleLits = false;
+					hasSimpleLits = true;
 				}
 						
-				if (allSimpleLits) {
+				if (hasSimpleLits && allSimpleLits && obj.ChildNodes.Count <= 3) {
+					if (!opts.UsePredicateAttributes) continue;
+				
 					// Condense by moving all of obj's elements to attributes of the predicate,
 					// and turning a rdf:about into a rdf:resource, and then remove obj completely.
 					if (obj.Attributes.Count == 1)
@@ -347,7 +458,18 @@
 					
 					if (pred.ChildNodes.Count == 0) pred.IsEmpty = true;
 
+				} else if (obj.ChildNodes.Count == 0 && obj.Attributes.Count == 1) {
+				
+					// Condense by turning a rdf:about into a rdf:resource,
+					// and then remove obj completely.
+					SetAttribute(pred, NS.RDF, ns.GetPrefix(NS.RDF), "resource", obj.Attributes[0].Value);
+					pred.RemoveChild(obj);
+					
+					if (pred.ChildNodes.Count == 0) pred.IsEmpty = true;
+					
 				} else if (obj.Attributes.Count == 0) { // no rdf:about
+					if (!opts.UseParseTypeResource) continue;
+
 					// Condense this node using parseType=Resource
 					pred.RemoveChild(obj);
 					foreach (XmlElement opred in obj.ChildNodes)
@@ -355,12 +477,21 @@
 					SetAttribute(pred, NS.RDF, ns.GetPrefix(NS.RDF), "parseType", "Resource");
 				}
 			}
+		}
+		
+		public override void Close() {
+			Start(); // make sure the document node was written
+			
+			MakeDocumentNice();
 
 			base.Close();
 			
 			if (writer != null) {
 				doc.WriteTo(writer);
-				//writer.Close();
+				if (closeStream)
+					writer.Close();
+				else
+					writer.Flush();
 			}
 		}
 		
@@ -375,5 +506,6 @@
 		}
 		
 	}
-
 }
+
+#endif

Modified: branches/beagle-rdf/Util/SemWeb/Resource.cs
==============================================================================
--- branches/beagle-rdf/Util/SemWeb/Resource.cs	(original)
+++ branches/beagle-rdf/Util/SemWeb/Resource.cs	Sat Jan 26 23:26:03 2008
@@ -1,9 +1,16 @@
 using System;
 using System.Collections;
+using System.Xml;
 
 namespace SemWeb {
 	
-	public abstract class Resource : IComparable {
+	public abstract class Resource :
+#if DOTNET2
+	IComparable<Resource>
+#else
+	IComparable
+#endif
+	{
 		internal object ekKey, ekValue;
 		internal ArrayList extraKeys;
 		
@@ -18,7 +25,7 @@
 		internal Resource() {
 		}
 		
-		// These get rid of the warning about overring ==, !=.
+		// These gets rid of the warning about overring ==, !=.
 		// Since Entity and Literal override these, we're ok.
 		public override bool Equals(object other) {
 			return base.Equals(other);
@@ -26,7 +33,7 @@
 		public override int GetHashCode() {
 			return base.GetHashCode();
 		}
-		
+
 		public static bool operator ==(Resource a, Resource b) {
 			if ((object)a == null && (object)b == null) return true;
 			if ((object)a == null || (object)b == null) return false;
@@ -36,7 +43,7 @@
 			return !(a == b);
 		}
 		
-		internal object GetResourceKey(object key) {
+		public object GetResourceKey(object key) {
 			if (ekKey == key) return ekValue;
 			if (extraKeys == null) return null;
 			for (int i = 0; i < extraKeys.Count; i++) {
@@ -46,7 +53,7 @@
 			}
 			return null;
 		}
-		internal void SetResourceKey(object key, object value) {
+		public void SetResourceKey(object key, object value) {
 			if (ekKey == null || ekKey == key) {
 				ekKey = key;
 				ekValue = value;
@@ -64,7 +71,11 @@
 			extraKeys.Add(k);
 		}
 
-		int IComparable.CompareTo(object other) {
+#if DOTNET2
+		public int CompareTo(Resource other) {
+#else
+		public int CompareTo(object other) {
+#endif
 			// We'll make an ordering over resources.
 			// First named entities, then bnodes, then literals.
 			// Named entities are sorted by URI.
@@ -79,7 +90,16 @@
 			
 			if (Uri != null) return String.Compare(Uri, r.Uri, false, System.Globalization.CultureInfo.InvariantCulture);
 			
-			if (this is BNode) return GetHashCode().CompareTo(r.GetHashCode());
+			if (this is BNode) {
+				BNode me = (BNode)this, o = (BNode)other;
+				if (me.LocalName != null || o.LocalName != null) {
+					if (me.LocalName == null) return -1;
+					if (o.LocalName == null) return -1;
+					int x = String.Compare(me.LocalName, o.LocalName, false, System.Globalization.CultureInfo.InvariantCulture);
+					if (x != 0) return x;
+				}
+				return GetHashCode().CompareTo(r.GetHashCode());
+			}
 
 			if (this is Literal) {
 				int x = String.Compare(((Literal)this).Value, ((Literal)r).Value, false, System.Globalization.CultureInfo.InvariantCulture);
@@ -99,7 +119,7 @@
 		
 		public Entity(string uri) {
 			if (uri == null) throw new ArgumentNullException("To construct entities with no URI, use the BNode class.");
-			//if (uri.Length == 0) throw new ArgumentException("uri cannot be the empty string");
+			if (uri.Length == 0) throw new ArgumentException("uri cannot be the empty string");
 			this.uri = uri;
 		}
 		
@@ -142,6 +162,203 @@
 		public override string ToString() {
 			return "<" + Uri + ">";
 		}
+		
+		public static string ValidateUri(string uri) {
+			// Validates the uri as an RDF IRI Reference, 
+			// i.e. an absolute URI with optional fragment,
+			// based on the RDF Concepts document and RFC 3987
+			// (and RFCs recursively referenced therein).
+			
+			// From the IRI RFC 3987, we are accepting:
+			//
+			//   scheme ":" ihier-part [ "?" iquery ] [ "#" ifragment ]
+			//
+			//   scheme         = ALPHA *( ALPHA | DIGIT | "+" | "-" | "." )
+			//   ihier-part     = "//" iauthority ipath-abempty | ipath-absolute | ipath-rootless | ipath-empty
+			//   iauthority     = [ iuserinfo "@" ] ihost [ ":" port ]
+			//   iuserinfo      = *( iunreserved / pct-encoded / sub-delims / ":" )
+			//   ihost          = IP-literal / IPv4address / ireg-name
+			//   port           = *DIGIT
+			//   IP-literal     = "[" ( IPv6address / IPvFuture  ) "]"
+			//   IPv4address    = dec-octet "." dec-octet "." dec-octet "." dec-octet
+			//   ipath-absolute = "/" [ isegment-nz *( "/" isegment ) ]
+			//   ipath-rootless = isegment-nz *( "/" isegment )
+			//   isegment       = *ipchar
+			//   isegment-nz    = 1*ipchar
+			//   ipath-empty    = (nothing)
+			//   iquery         = *( ipchar | iprivate | "/" | "?" )
+			//   ifragment      = *( ipchar | "/" | "?" )
+			//   ipchar         = iunreserved | pct-encoded | sub-delims | ":" | "@"
+			//   iunreserved    = ALPHA | DIGIT | "-" | "." | "_" | "~" | ucschar
+			//   pct-encoded    = "%" HEXDIG HEXDIG
+			//   sub-delims     = "!" | "$" | "&" | "'" | "(" | ")" | "*" | "+" | "," | ";" | "="
+			//   iprivate       = %xE000-F8FF | %xF0000-FFFFD | %x100000-10FFFD
+			//   ALPHA          =  %x41-5A | %x61-7A   ; A-Z / a-z
+			//   DIGIT          =  %x30-39 ; 0-9
+			//   HEXDIG         =  DIGIT / "A" / "B" / "C" / "D" / "E" / "F"
+			
+			char state = 's';
+			
+			foreach (char c in uri) {
+				// From 'RDF Concepts' section 6.4,
+				// a URI cannot contain control characters (#x00-#x1F, #x7F-#x9F)
+				if (c <= 0x1F || (c >= 0x7F && c <= 0x9F))
+					return "The control character '" + c + "' (" + ((int)c).ToString("x") + ") is not allowed.";
+			
+				switch (state) {
+
+				// scheme =  ALPHA *( ALPHA | DIGIT | "+" | "-" | "." )
+				// The scheme is terminated by a colon after the first character.
+					
+				case 's': // first character in scheme
+					if (!ValidateUriIsAlpha(c))
+						return "The character '" + c + "' (" + ((int)c).ToString("x") + ") is not allowed as the first character in a URI, which is the start of the scheme.";
+					state = 'S';
+					break;
+				
+				case 'S': // non-first character in scheme
+					if (c == ':') // transition to ihier-part
+						state = 'H';
+					else if (!ValidateUriIsAlpha(c) && !ValidateUriIsDigit(c) && c != '+' && c != '-' && c != '.')
+						return "The character '" + c + "' (" + ((int)c).ToString("x") + ") is not allowed in the scheme portion of the URI.";
+					break;
+					
+				//   ihier-part     = "//" iauthority ipath-abempty | ipath-absolute | ipath-rootless | ipath-empty
+				case 'H': // start of ihier-part (just read the colon)
+					if (c == '/')        // either start of //+iauthority+ipath-abempty or ipath-absolute 
+						state = '/';
+					else if (c == '?')   // empty ihier-part, start of query
+						state = 'q';
+					else if (c == '#')   // empty ihier-part, start of fragment
+						state = 'f';
+					else {
+						// This is the first character of ipath-rootless, which must be an ipchar
+						if (!ValidateUriIsIpchar(c))
+							return "The character '" + c + "' (" + ((int)c).ToString("x") + ") is not allowed at the start of the (rootless) path portion of the URI.";
+						state = 'r';
+					}
+					break;
+				
+				case '/': // either 2nd slash of //+iauthority+ipath-abempty or 1st character past slash in ipath-absolute
+					if (c == '/')
+						state = 'a'; // iauthority, to lead into ipath-abempty
+					else if (!ValidateUriIsIpchar(c))
+						return "The character '" + c + "' (" + ((int)c).ToString("x") + ") is not allowed at the start of the (absolute without authority) path portion of the URI.";
+					// For the rest of ipath-absolute, we go to state r.
+					state = 'r';
+					break;
+					
+				case 'r': // 2nd character and later of ipath-rootless, or
+				          // 3rd character and later of ipath-absolute, or
+				          // 2nd character and later of ipath-abempty,
+				          // all of which are *( "/" isegment ) and terminate the ihier-part of the URI.
+					if (c == '?')   // start of query
+						state = 'q';
+					else if (c == '#')   // start of fragment
+						state = 'f';
+					else if (c != '/' && !ValidateUriIsIpchar(c))
+						return "The character '" + c + "' (" + ((int)c).ToString("x") + ") is not allowed in the path portion of the URI.";
+					// stay in this state
+					break;
+
+				case 'a': // the start of iauthority, which then goes to ipath-abempty, which then terminates the ihier-part
+					// We very loosely check this part because we can't do this easily deterministically.
+					// (For instance, we don't know if we are looking at a username or host until we
+					// find an @ sign or the end.) So we allow any of the allowed characters in
+					// this region, until we can be sure we are moving into ipath-abempty with a
+					// slash, or into the query with a question mark, or fragment with a hash. 
+					// None of those three characters can occur in this part (fortunately).
+					
+					if (c == '?')   // start of query
+						state = 'q';
+					else if (c == '#')   // start of fragment
+						state = 'f';
+					else if (c == '/')   // start of a non-empty ipath-abempty, which is *( "/" isegment )
+						state = 'r';
+						
+					// The allowed characters are:
+					// iauthority: '@' | ':'
+					// iuserinfo: iunreserved / pct-encoded / sub-delims / ':'
+					// ihost: 
+					// port: DIGIT
+					// IP-literal: '[' ']'
+					// IPv6address: HEXDIG ':'
+					// IPvFuture: 'v' HEXDIG '.' unreserved subdelims ':'
+					// IPv4address: DIGIT '.'
+					// ireg-name: iunreserved / pct-encoded / sub-delims
+						
+					else if (c != '@' && c != ':' && c != '[' && c != ']' && c != '.' && c != ':' && c != '%'
+						&& !ValidateUriIsIUnreserved(c) && !ValidateUriIsSubdelim(c))
+						return "The character '" + c + "' (" + ((int)c).ToString("x") + ") is not allowed in the authority (user, host, and port) portion of the URI.";
+
+					// stay in this state
+
+					break;
+				
+				case 'q': // start of query string
+					// iquery         = *( ipchar / iprivate / "/" / "?" )
+					if (c == '#')   // start of fragment
+						state = 'f';
+					else if (c != '/' && c != '?' && !ValidateUriIsIpchar(c) && !ValidateUriIsIPrivate(c))
+						return "The character '" + c + "' (" + ((int)c).ToString("x") + ") is not allowed in the query string portion of the URI.";
+					// stay in this state
+					break;
+					
+				case 'f': // start of fragment
+					// ifragment      = *( ipchar / "/" / "?" )
+					if (c != '/' && c != '?' && !ValidateUriIsIpchar(c))
+						return "The character '" + c + "' (" + ((int)c).ToString("x") + ") is not allowed in the fragment portion of the URI.";
+					// stay in this state
+					break;
+				}
+			}
+			
+			// Which state did we end up in? If we end in some states, we didn't finish the URI.
+			switch (state) {
+			case 's': // first character in scheme: the URI was empty
+				return "The URI is empty.";
+			case 'S': // non-first character in scheme
+				return "The URI must start with a scheme name (e.g. \"http:\").";
+			case 'H': // start of ihier-part
+				return "After the scheme (e.g. \"http:\"), something must follow, such as double-slashes.";
+			case '/': // just read first slash of "//" or the starting slash in a path
+			case 'r': // various
+				// no problem: we can end here
+				break;
+			case 'a': // just read second slash starting the authority part
+				return "After the double-slashes, a host name (or a user-plus- -sign) must follow.";
+			case 'q': // start of query 
+			case 'f': // start of fragment
+				// no problem: we can have empty query strings and fragments
+				break;
+			}
+			
+			// This is an OK IRI.
+			return null;
+		}
+		
+		private static bool ValidateUriIsAlpha(char c) {
+			return (c >= 0x41 && c <= 0x5A) || (c >= 0x61 && c <= 0x7A);
+		}
+		private static bool ValidateUriIsDigit(char c) {
+			return c >= 0x30 && c <= 0x39;
+		}
+		private static bool ValidateUriIsIUnreserved(char c) {
+			return ValidateUriIsAlpha(c) || ValidateUriIsDigit(c) || c == '-' || c == '.' || c == '_' || c == '~'
+				|| (c >= 0xA0 && c <= 0xD7FF) || (c >= 0xF900 && c <= 0xFDCF) || (c >= 0xFDF0 && c <= 0xFFEF); // ucschar
+		}
+		private static bool ValidateUriIsSubdelim(char c) {
+			return c == '!' || c == '$' || c == '&' || c == '\'' || c == '(' || c == ')' || c == '*' || c == '+' || c ==  ',' || c == ';' || c == '=';
+		}
+		private static bool ValidateUriIsIpchar(char c) {
+			// also could be pct-encoded char, but we don't have look-ahead so we just
+			// check the percent -- the rest will be OK because the HEXDIG chars could appear alone
+			return ValidateUriIsIUnreserved(c) || ValidateUriIsSubdelim(c) || c == ':' || c == '@' || c == '%';
+		}
+		private static bool ValidateUriIsIPrivate(char c) {
+			return c >= 0xE000 && c <= 0xF8FF;
+		}
+		
 	}
 	
 	public class BNode : Entity {
@@ -183,7 +400,7 @@
 			if (LocalName != null)
 				return "_:" + LocalName;
 			else
-				return "_:bnode" + GetHashCode();
+				return "_:bnode" + Math.Abs(GetHashCode());
 		}
 	}
 	
@@ -198,13 +415,11 @@
 			if (LocalName != null)
 				return "?" + LocalName;
 			else
-				return "?var" + GetHashCode();
+				return "?var" + Math.Abs(GetHashCode());
 		}
 	}
 
 	public sealed class Literal : Resource { 
-		private const string XMLSCHEMANS = "http://www.w3.org/2001/XMLSchema#";;
-
 		private string value, lang, type;
 		
 		public Literal(string value) : this(value, null, null) {
@@ -213,7 +428,7 @@
 		public Literal(string value, string language, string dataType) {
 		  if (value == null)
 			  throw new ArgumentNullException("value");
-		  this.value = string.Intern(value);
+		  this.value = value;
 		  this.lang = language;
 		  this.type = dataType;
 		  
@@ -305,24 +520,28 @@
 		
 		public object ParseValue() {
 			string dt = DataType;
-			if (dt == null || !dt.StartsWith(XMLSCHEMANS)) return Value;
-			dt = dt.Substring(XMLSCHEMANS.Length);
+			if (dt == null || !dt.StartsWith(NS.XMLSCHEMA)) return Value;
+			dt = dt.Substring(NS.XMLSCHEMA.Length);
 			
 			if (dt == "string" || dt == "normalizedString" || dt == "anyURI") return Value;
-			if (dt == "boolean") return (Value == "true" || Value == "1");
-			if (dt == "decimal" || dt == "integer" || dt == "nonPositiveInteger" || dt == "negativeInteger" || dt == "nonNegativeInteger" || dt == "positiveInteger") return Decimal.Parse(Value);
-			if (dt == "float") return float.Parse(Value);
-			if (dt == "double") return double.Parse(Value);
-			if (dt == "duration") return TimeSpan.Parse(Value); // syntax?
-			if (dt == "dateTime" || dt == "time" || dt == "date") return DateTime.Parse(Value); // syntax?
-			if (dt == "long") return long.Parse(Value);
-			if (dt == "int") return int.Parse(Value);
-			if (dt == "short") return short.Parse(Value);
-			if (dt == "byte") return sbyte.Parse(Value);
-			if (dt == "unsignedLong") return ulong.Parse(Value);
-			if (dt == "unsignedInt") return uint.Parse(Value);
-			if (dt == "unsignedShort") return ushort.Parse(Value);
-			if (dt == "unsignedByte") return byte.Parse(Value);
+			if (dt == "boolean") return XmlConvert.ToBoolean(Value);
+			if (dt == "decimal" || dt == "integer" || dt == "nonPositiveInteger" || dt == "negativeInteger" || dt == "nonNegativeInteger" || dt == "positiveInteger") return XmlConvert.ToDecimal(Value);
+			if (dt == "float") return XmlConvert.ToSingle(Value);
+			if (dt == "double") return XmlConvert.ToDouble(Value);
+			if (dt == "duration") return XmlConvert.ToTimeSpan(Value);
+			#if !DOTNET2
+			if (dt == "dateTime" || dt == "time" || dt == "date") return XmlConvert.ToDateTime(Value);
+			#else
+			if (dt == "dateTime" || dt == "time" || dt == "date") return XmlConvert.ToDateTime(Value, XmlDateTimeSerializationMode.Utc);
+			#endif
+			if (dt == "long") return XmlConvert.ToInt64(Value);
+			if (dt == "int") return XmlConvert.ToInt32(Value);
+			if (dt == "short") return XmlConvert.ToInt16(Value);
+			if (dt == "byte") return XmlConvert.ToSByte(Value);
+			if (dt == "unsignedLong") return XmlConvert.ToUInt64(Value);
+			if (dt == "unsignedInt") return XmlConvert.ToUInt32(Value);
+			if (dt == "unsignedShort") return XmlConvert.ToUInt16(Value);
+			if (dt == "unsignedByte") return XmlConvert.ToByte(Value);
 			
 			return Value;
 		}
@@ -332,8 +551,82 @@
 			return new Literal(ParseValue().ToString(), Language, DataType);
 		}
 		
-		public static Literal Create(bool value) {
-			return new Literal(value ? "true" : "false", null, XMLSCHEMANS + "boolean");
+		public static Literal FromValue(float value) {
+			return new Literal(value.ToString(), null, NS.XMLSCHEMA + "float");
+		}
+		public static Literal FromValue(double value) {
+			return new Literal(value.ToString(), null, NS.XMLSCHEMA + "double");
+		}
+		public static Literal FromValue(byte value) {
+			if (value <= 127)
+				return new Literal(value.ToString(), null, NS.XMLSCHEMA + "byte");
+			else
+				return new Literal(value.ToString(), null, NS.XMLSCHEMA + "unsignedByte");
+		}
+		public static Literal FromValue(short value) {
+			return new Literal(value.ToString(), null, NS.XMLSCHEMA + "short");
+		}
+		public static Literal FromValue(int value) {
+			return new Literal(value.ToString(), null, NS.XMLSCHEMA + "int");
+		}
+		public static Literal FromValue(long value) {
+			return new Literal(value.ToString(), null, NS.XMLSCHEMA + "long");
+		}
+		public static Literal FromValue(sbyte value) {
+			return new Literal(value.ToString(), null, NS.XMLSCHEMA + "byte");
+		}
+		public static Literal FromValue(ushort value) {
+			return new Literal(value.ToString(), null, NS.XMLSCHEMA + "unsignedShort");
+		}
+		public static Literal FromValue(uint value) {
+			return new Literal(value.ToString(), null, NS.XMLSCHEMA + "unsignedInt");
+		}
+		public static Literal FromValue(ulong value) {
+			return new Literal(value.ToString(), null, NS.XMLSCHEMA + "unsignedLong");
+		}
+		public static Literal FromValue(Decimal value) {
+			return new Literal(value.ToString(), null, NS.XMLSCHEMA + "decimal");
+		}
+		public static Literal FromValue(bool value) {
+			return new Literal(value ? "true" : "false", null, NS.XMLSCHEMA + "boolean");
+		}
+		public static Literal FromValue(string value) {
+			return new Literal(value, null, NS.XMLSCHEMA + "string");
+		}
+		public static Literal FromValue(Uri value) {
+			return new Literal(value.ToString(), null, NS.XMLSCHEMA + "anyURI");
+		}
+		public static Literal FromValue(DateTime value) {
+			return FromValue(value, true, false);
+		}
+		public static Literal FromValue(DateTime value, bool withTime, bool isLocalTime) {
+			if (withTime && isLocalTime)
+				return new Literal(value.ToString("yyyy-MM-ddTHH\\:mm\\:ss.FFFFFFF0zzz"), null, NS.XMLSCHEMA + "dateTime");
+			else if (withTime)
+				return new Literal(value.ToString("yyyy-MM-ddTHH\\:mm\\:ss.FFFFFFF0"), null, NS.XMLSCHEMA + "dateTime");
+			else
+				return new Literal(value.ToString("yyyy-MM-dd"), null, NS.XMLSCHEMA + "date");
+		}
+		public static Literal FromValue(TimeSpan value) {
+			return FromValue(value, false, false);
+		}
+		public static Literal FromValue(TimeSpan value, bool asTime, bool isLocalTime) {
+			if (!asTime) {
+				string ret = (value.Ticks >= 0 ? "P" : "-P");
+				if (value.Days != 0) {
+					ret += value.Days + "D";
+					if (value.Hours != 0 || value.Minutes != 0 || value.Seconds != 0 || value.Milliseconds != 0)
+						ret += "T";
+				}
+				if (value.Hours != 0) ret += value.Hours + "H";
+				if (value.Minutes != 0) ret += value.Minutes + "M";
+				if (value.Seconds != 0 || value.Milliseconds != 0) ret += (value.Seconds + value.Milliseconds/1000) + "S";
+				return new Literal(ret, null, NS.XMLSCHEMA + "duration");
+			} else if (isLocalTime) {
+				return new Literal((DateTime.Today + value).ToString("HH\\:mm\\:ss.FFFFFFF0zzz"), null, NS.XMLSCHEMA + "time");
+			} else {
+				return new Literal((DateTime.Today + value).ToString("HH\\:mm\\:ss.FFFFFFF0"), null, NS.XMLSCHEMA + "time");
+			}
 		}
 	}
 

Modified: branches/beagle-rdf/Util/SemWeb/SQLStore.cs
==============================================================================
--- branches/beagle-rdf/Util/SemWeb/SQLStore.cs	(original)
+++ branches/beagle-rdf/Util/SemWeb/SQLStore.cs	Sat Jan 26 23:26:03 2008
@@ -1,3 +1,49 @@
+/**
+ * SQLStore.cs: An abstract implementation of an RDF triple store
+ * using an SQL-based backend.  This class is extended by the
+ * MySQLStore, SQLiteStore, and PostreSQLStore classes.
+ *
+ * The SQLStore creates three tables to store its data.  The tables
+ * are organizes follows:
+ *   table                columns
+ * PREFIX_entites    id (int), value (case-sensitive string)
+ * PREFIX_literals   id (int), value (case-sens. string), language (short case-insens. string),
+                     datatype (case-sense. string), hash (28byte case-sense string)
+ * PREFIX_statements subject (int), predicate (int), object (int), meta (int), objecttype (tiny int)
+ *
+ * Every resource (named node, bnode, and literal) is given a numeric ID.
+ * Zero is reserved.  One is used for the bnode in the static field Statement.DefaultMeta.
+ * The numbers for entities and literals are drawn from the same set of numbers,
+ * so there cannot be an entity and a literal with the same ID.
+ *
+ * The subject, predicate, object, and meta columns in the _statements table
+ * refer to the numeric IDs of resources.  objecttype is zero if the object
+ * is an entity (named or blank), otherwise one if it is a literal.  Some databases
+ * (i.e. MySQL) add a UNIQUE constraint over the subject, predicate, object,
+ * and meta columns so that the database is guaranteed not to have duplicate
+ * rows.  Not all databases will do this, though.
+ *
+ * All literals have a row in the _literals table.  The value, language, and
+ * datatype columns have the obvious things.  Notably, the datatype column
+ * is a string column, even though you might think of it as an entity that
+ * should be in the entities table.  The hash column contains a SHA1 hash
+ * over the three fields and is used to speed up look-ups for literals.  It
+ * also is used for creating a UNIQUE index over the table.  Because literal
+ * values can be arbitrarily long, creating a fixed-size hash is the only
+ * way to reliably create a UNIQUE constraint over the table.  A literal
+ * value is entered into this table at most once.  You can't have two rows
+ * that have the exact same value, language, and datatype.
+ *
+ * The _entities table contains all *named* entities.  Basically it is just
+ * a mapping between entity IDs in the _statements table and their URIs.
+ * Importantly, bnodes are not represented in this table (it would be a
+ * waste of space since they have nothing to map IDs to).  A UNIQUE constraint
+ * is placed over the value column to ensure that a URI ends up in the table
+ * at most once.  Because bnodes are not in this table, the only way to
+ * get a list of them is to see what IDs are used in _statements that are
+ * not in this table or in the _literals table.
+ */
+
 using System;
 using System.Collections;
 using System.Collections.Specialized;
@@ -9,47 +55,93 @@
 using SemWeb.Util;
 
 namespace SemWeb.Stores {
-	// TODO: It's not safe to have two concurrent accesses to the same database
-	// because the creation of new entities will use the same IDs.
+	public abstract class SQLStore : QueryableSource, StaticSource, ModifiableSource, IDisposable {
+		// Table initialization, etc.
+		// --------------------------
 	
-	public abstract class SQLStore : Store, SupportsPersistableBNodes {
+		// This is a version number representing the current 'schema' implemented
+		// by this class in case of future updates.
 		int dbformat = 1;
 	
+		// 'table' is the prefix of the tables used by this store, i.e.
+		// {table}_statements, {table}_literals, {table}_entities.  This is
+		// set in the constructor.
 		string table;
+		
+		// 'guid' is a GUID assigned to this store.  It is created the
+		// first time the SQL table structure is made and is saved in
+		// the info block of the literal with ID zero.
 		string guid;
 		
+		// this flag tracks the first access to the backend, when it
+		// creates tables and indexes if necessary
 		bool firstUse = true;
+
+		// Importing
+		// ------------
 		
+		// The SQL store operates in two modes, isImporting == false and
+		// isImporting == true.  The first mode is the usual mode, where
+		// calls to Add are executed immediately.  The second mode, which
+		// is activated by the Import() method, batches Add calls to make
+		// insertions faster.  While importing, no public methods should be
+		// called except by this class itself.
 		bool isImporting = false;
+		
+		// Each time we need to add a resource, we need to find it an ID.
+		// When importing, we track the next ID available in this field
+		// and increment it as necessary.  When not importing, we do a
+		// DB query to find the next available ID.
 		int cachedNextId = -1;
+		
+		// These variables hold on to the IDs of literals and entities during
+		// importing. They are periodically cleared.
 		Hashtable entityCache = new Hashtable();
 		Hashtable literalCache = new Hashtable();
 		
+		// This is a buffer of statements waiting to be processed.
+		StatementList addStatementBuffer = null;
+		
+		// These track the performance of our buffer so we can adjust its size
+		// on the fly to maximize performance.
+		int importAddBufferSize = 200, importAddBufferRotation = 0;
+		TimeSpan importAddBufferTime = TimeSpan.MinValue;
+		
+		// Other Flags
+		// -----------
+		
+		// When adding a statement that has a bnode in it (not while importing),
+		// we have to do a two-staged procedure.  This holds on to a list of
+		// GUIDs that we've temporarily assigned to bnodes that are cleared
+		// at the end of Add().
 		ArrayList anonEntityHeldIds = new ArrayList();
 		
+		// Tracks whether any statements have been removed from the store by this
+		// object.  When Close() is called, if true, the entities and literals
+		// tables are cleaned up to remove unreferenced resoures.
 		bool statementsRemoved = false;
 
+		// Debugging flags from environment variables.
 		static bool Debug = System.Environment.GetEnvironmentVariable("SEMWEB_DEBUG_SQL") != null;
+		static bool DebugLogSpeed = System.Environment.GetEnvironmentVariable("SEMWEB_DEBUG_SQL_LOG_SPEED") != null;
+		static bool NoSQLView = System.Environment.GetEnvironmentVariable("SEMWEB_SQL_NOVIEWS") != null;
+		static string InitCommands = System.Environment.GetEnvironmentVariable("SEMWEB_SQL_INIT_COMMANDS");
 		
+		// This guy is reused in various calls to avoid allocating a new one of
+		// these all the time.
 		StringBuilder cmdBuffer = new StringBuilder();
-		
-		// Buffer statements to process together.
-		StatementList addStatementBuffer = null;
-		
-		string 	INSERT_INTO_LITERALS_VALUES,
-				INSERT_INTO_STATEMENTS_VALUES,
-				INSERT_INTO_ENTITIES_VALUES;
+		 
+		// The quote character that surrounds strings in SQL statements.
+		// Initialized in the constructor.
 		char quote;
 		
+		// Ensure that calls to Select() and Query() are synchronized to make these methods thread-safe.
 		object syncroot = new object();
 		
-		Hashtable metaEntities;
-		
+		// Our SHA1 object which we use to create hashes of literal values.
 		SHA1 sha = SHA1.Create();
 		
-		int importAddBufferSize = 200, importAddBufferRotation = 0;
-		TimeSpan importAddBufferTime = TimeSpan.MinValue;
-				
+		// This class is placed inside entities to cache their numeric IDs.
 		private class ResourceKey {
 			public int ResId;
 			
@@ -59,41 +151,78 @@
 			public override bool Equals(object other) { return (other is ResourceKey) && ((ResourceKey)other).ResId == ResId; }
 		}
 		
+		// Some helpers.
+		
+		const string rdfs_member = NS.RDFS + "member";
+		const string rdf_li = NS.RDF + "_";
+		
 		private static readonly string[] fourcols = new string[] { "subject", "predicate", "object", "meta" };
 		private static readonly string[] predcol = new string[] { "predicate" };
 		private static readonly string[] metacol = new string[] { "meta" };
 
+		private string INSERT_INTO_LITERALS_VALUES { get { return "INSERT INTO " + table + "_literals VALUES "; } }
+		private string INSERT_INTO_ENTITIES_VALUES { get { return "INSERT INTO " + table + "_entities VALUES "; } }
+		private string INSERT_INTO_STATEMENTS_VALUES { get { return "INSERT " + (HasUniqueStatementsConstraint ? InsertIgnoreCommand : "") + " INTO " + table + "_statements VALUES "; } }
+			
+
+		// The constructor called by subclasses.
 		protected SQLStore(string table) {
 			this.table = table;
 			
-			INSERT_INTO_LITERALS_VALUES = "INSERT INTO " + table + "_literals VALUES ";
-			INSERT_INTO_ENTITIES_VALUES = "INSERT INTO " + table + "_entities VALUES ";
-			INSERT_INTO_STATEMENTS_VALUES = "INSERT " + (SupportsInsertIgnore ? "IGNORE " : "") + "INTO " + table + "_statements VALUES ";
-			
 			quote = GetQuoteChar();
 		}
 		
 		protected string TableName { get { return table; } }
 		
-		protected abstract bool SupportsNoDuplicates { get; }
-		protected abstract bool SupportsInsertIgnore { get; }
+		// The next few abstract and virtual methods allow implementors to control
+		// what features the SQLStore takes advantage of and controls the SQL
+		// language use.
+
+		// See the API docs for more on these.
+		protected abstract bool HasUniqueStatementsConstraint { get; } // may not return true unless INSERT (IGNORE COMMAND) is supported
+		protected abstract string InsertIgnoreCommand { get; }
 		protected abstract bool SupportsInsertCombined { get; }
-		protected virtual bool SupportsFastJoin { get { return true; } }
 		protected abstract bool SupportsSubquery { get; }
+		protected virtual bool SupportsViews { get { return false; } }
+		protected virtual int MaximumUriLength { get { return -1; } }
 		
 		protected abstract void CreateNullTest(string column, System.Text.StringBuilder command);
+		protected abstract void CreateLikeTest(string column, string prefix, int method, System.Text.StringBuilder command);
+		
+		protected virtual bool CreateEntityPrefixTest(string column, string prefix, System.Text.StringBuilder command) {
+			command.Append('(');
+			command.Append(column);
+			command.Append(" IN (SELECT id from ");
+			command.Append(TableName);
+			command.Append("_entities WHERE ");
+			CreateLikeTest("value", prefix, 0, command);
+			command.Append("))");
+			return true;
+		}
 		
+		// If this is the first use, initialize the table and index structures.
+		// CreateTable() will create tables if they don't already exist.
+		// CreateIndexes() will only be run if this is a new database, so that
+		// the user may customize the indexes after the table is first created
+		// without SemWeb adding its own indexes the next time again.
 		private void Init() {
 			if (!firstUse) return;
 			firstUse = false;
 			
 			CreateTable();
-			CreateIndexes();
-			CreateVersion();
+			if (CreateVersion()) // tests if this is a new table
+				CreateIndexes();
+
+			if (InitCommands != null)
+				RunCommand(InitCommands);
 		}
 		
-		private void CreateVersion() {	
+		// Creates the info block in the literal row with ID zero.  Returns true
+		// if it created a new info block (i.e. this is a new database).
+		private bool CreateVersion() {	
 			string verdatastr = RunScalarString("SELECT value FROM " + table + "_literals WHERE id = 0");
+			bool isNew = (verdatastr == null);
+			
 			NameValueCollection verdata = ParseVersionInfo(verdatastr);
 			
 			if (verdatastr != null && verdata["ver"] == null)
@@ -112,7 +241,9 @@
 			if (verdatastr == null)
 				RunCommand("INSERT INTO " + table + "_literals (id, value) VALUES (0, " + Escape(newverdata, true) + ")");
 			else if (verdatastr != newverdata)
-				RunCommand("UPDATE " + table + "_literals SET value = " + newverdata + " WHERE id = 0");
+				RunCommand("UPDATE " + table + "_literals SET value = " + Escape(newverdata, true) + " WHERE id = 0");
+				
+			return isNew;
 		}
 		
 		NameValueCollection ParseVersionInfo(string verdata) {
@@ -132,27 +263,44 @@
 			return ret;
 		}
 		
-		public override bool Distinct { get { return true; } }
+		// Now we get to the Store implementation.
 		
-		public override int StatementCount { get { Init(); RunAddBuffer(); return RunScalarInt("select count(subject) from " + table + "_statements", 0); } }
+		// Why do we return true here?
+		public bool Distinct { get { return true; } }
+		
+		public int StatementCount {
+			get {
+				Init();
+				RunAddBuffer();
+				return RunScalarInt("select count(subject) from " + table + "_statements", 0);
+			}
+		}
 		
 		public string GetStoreGuid() { return guid; }
 		
-		public string GetNodeId(BNode node) {
+		public string GetPersistentBNodeId(BNode node) {
 			ResourceKey rk = (ResourceKey)GetResourceKey(node);
 			if (rk == null) return null;
-			return rk.ResId.ToString();
+			return GetStoreGuid() + ":" + rk.ResId.ToString();
 		}
 		
-		public BNode GetNodeFromId(string persistentId) {
+		public BNode GetBNodeFromPersistentId(string persistentId) {
 			try {
-				int id = int.Parse(persistentId);
+				int colon = persistentId.IndexOf(':');
+				if (colon == -1) return null;
+				if (GetStoreGuid() != persistentId.Substring(0, colon)) return null;
+				int id = int.Parse(persistentId.Substring(colon+1));
 				return (BNode)MakeEntity(id, null, null);
-			} catch (Exception e) {
+			} catch (Exception) {
 				return null;
 			}
 		}
 		
+		// Returns the next ID available for a resource.  If we're importing,
+		// use and increment the cached ID.  Otherwise, scan the tables for
+		// the highest ID in use and use that plus one.  (We have to scan all
+		// tables because bnode IDs are only in the statements table and
+		// entities and literals may be orphaned so those are only in those tables.)
 		private int NextId() {
 			if (isImporting && cachedNextId != -1)
 				return ++cachedNextId;
@@ -180,30 +328,36 @@
 			if (maxid >= nextid) nextid = maxid + 1;
 		}
 		
-		public override void Clear() {
+		// Implements Store.Clear() by dropping the tables entirely.
+		public void Clear() {
 			// Drop the tables, if they exist.
-			try { RunCommand("DROP TABLE " + table + "_statements;"); } catch (Exception e) { }
-			try { RunCommand("DROP TABLE " + table + "_literals;"); } catch (Exception e) { }
-			try { RunCommand("DROP TABLE " + table + "_entities;"); } catch (Exception e) { }
+			try { RunCommand("DROP TABLE " + table + "_statements;"); } catch (Exception) { }
+			try { RunCommand("DROP TABLE " + table + "_literals;"); } catch (Exception) { }
+			try { RunCommand("DROP TABLE " + table + "_entities;"); } catch (Exception) { }
 			firstUse = true;
 		
 			Init();
 			if (addStatementBuffer != null) addStatementBuffer.Clear();
 			
-			metaEntities = null;
-
 			//RunCommand("DELETE FROM " + table + "_statements;");
 			//RunCommand("DELETE FROM " + table + "_literals;");
 			//RunCommand("DELETE FROM " + table + "_entities;");
 		}
 		
+		// Computes a hash for a literal value to put in the hash column of the _literals table.
 		private string GetLiteralHash(Literal literal) {
 			byte[] data = System.Text.Encoding.Unicode.GetBytes(literal.ToString());
 			byte[] hash = sha.ComputeHash(data);
 			return Convert.ToBase64String(hash);
 		}
 		
-		private int GetLiteralId(Literal literal, bool create, StringBuilder buffer, bool insertCombined) {
+		// Gets the ID of a literal in the database given an actual Literal object.
+		// If create is false, return 0 if no such literal exists in the database.
+		// Otherwise, create a row for the literal if none exists putting the
+		// SQL insertion statement into the buffer argument.
+		// If we're in isImporting mode, we expect that the literal's ID has already
+		// been pre-fetched and put into literalCache (if the literal exists in the DB).
+		private int GetLiteralId(Literal literal, bool create, StringBuilder buffer, bool insertCombined, ref bool firstInsert) {
 			// Returns the literal ID associated with the literal.  If a literal
 			// doesn't exist and create is true, a new literal is created,
 			// otherwise 0 is returned.
@@ -216,9 +370,9 @@
 				b.Append("SELECT id FROM ");
 				b.Append(table);
 				b.Append("_literals WHERE hash =");
-				b.Append("\"");
+				b.Append(quote);
 				b.Append(GetLiteralHash(literal));
-				b.Append("\"");
+				b.Append(quote);
 				b.Append(" LIMIT 1;");
 				
 				object id = RunScalar(b.ToString());
@@ -226,7 +380,7 @@
 			}
 				
 			if (create) {
-				int id = AddLiteral(literal, buffer, insertCombined);
+				int id = AddLiteral(literal, buffer, insertCombined, ref firstInsert);
 				if (isImporting)
 					literalCache[literal] = id;
 				return id;
@@ -235,7 +389,8 @@
 			return 0;
 		}
 		
-		private int AddLiteral(Literal literal, StringBuilder buffer, bool insertCombined) {
+		// Creates the SQL command to add a literal to the _literals table.
+		private int AddLiteral(Literal literal, StringBuilder buffer, bool insertCombined, ref bool firstInsert) {
 			int id = NextId();
 			
 			StringBuilder b;
@@ -248,8 +403,9 @@
 			if (!insertCombined) {
 				b.Append(INSERT_INTO_LITERALS_VALUES);
 			} else {
-				if (b.Length > 0)
+				if (!firstInsert)
 					b.Append(',');
+				firstInsert = false;
 			}
 			b.Append('(');
 			b.Append(id);
@@ -265,9 +421,11 @@
 				EscapedAppend(b, literal.DataType);
 			else
 				b.Append("NULL");
-			b.Append(",\"");
+			b.Append(',');
+			b.Append(quote);
 			b.Append(GetLiteralHash(literal));
-			b.Append("\")");
+			b.Append(quote);
+			b.Append(')');
 			if (!insertCombined)
 				b.Append(';');
 			
@@ -279,7 +437,13 @@
 			return id;
 		}
 
-		private int GetEntityId(string uri, bool create, StringBuilder entityInsertBuffer, bool insertCombined, bool checkIfExists) {
+		// Gets the ID of an entity in the database given a URI.
+		// If create is false, return 0 if no such entity exists in the database.
+		// Otherwise, create a row for the entity if none exists putting the
+		// SQL insertion statement into the entityInsertBuffer argument.
+		// If we're in isImporting mode, we expect that the entity's ID has already
+		// been pre-fetched and put into entityCache (if the entity exists in the DB).
+		private int GetEntityId(string uri, bool create, StringBuilder entityInsertBuffer, bool insertCombined, bool checkIfExists, ref bool firstInsert) {
 			// Returns the resource ID associated with the URI.  If a resource
 			// doesn't exist and create is true, a new resource is created,
 			// otherwise 0 is returned.
@@ -302,8 +466,8 @@
 			
 			// If we got here, no such resource exists and create is true.
 			
-			if (uri.Length > 255)
-				throw new NotSupportedException("URIs must be a maximum of 255 characters for this store due to indexing constraints (before MySQL 4.1.2).");
+			if (MaximumUriLength != -1 && uri.Length > MaximumUriLength)
+				throw new NotSupportedException("URI exceeds maximum length supported by data store.");
 
 			id = NextId();
 			
@@ -317,8 +481,9 @@
 			if (!insertCombined) {
 				b.Append(INSERT_INTO_ENTITIES_VALUES);
 			} else {
-				if (b.Length > 0)
+				if (!firstInsert)
 					b.Append(',');
+				firstInsert = false;
 			}
 			b.Append('(');
 			b.Append(id);
@@ -339,16 +504,30 @@
 			return id;
 		}
 		
+		// Gets the ID of an entity in the database given a URI.
+		// If create is false, return 0 if no such entity exists in the database.
+		// Otherwise, create a row for the entity if none exists.
 		private int GetResourceId(Resource resource, bool create) {
-			return GetResourceIdBuffer(resource, create, null, null, false);
+			bool firstLiteralInsert = true, firstEntityInsert = true;
+			return GetResourceIdBuffer(resource, create, null, null, false, ref firstLiteralInsert, ref firstEntityInsert);
 		}
 		
-		private int GetResourceIdBuffer(Resource resource, bool create, StringBuilder literalInsertBuffer, StringBuilder entityInsertBuffer, bool insertCombined) {
+		// Gets the ID of an entity or literal in the database given a Resource object.
+		// If create is false, return 0 if no such entity exists in the database.
+		// Otherwise, create a row for the resource if none exists putting the
+		// SQL insertion statements into the literalInsertBuffer and entityInsertBuffer arguments.
+		// If we're in isImporting mode, we expect that the resources's ID has already
+		// been pre-fetched and put into one of the cache variables (if the resource exists in the DB).
+		// If we're trying to get the ID for a bnode (i.e. we want to add it to the database),
+		//   if we're isImporting, we know the next ID we can use -- increment and return that.
+		//   otherwise we have to create a temporary row in the _entities table to hold onto
+		//   the ID just until we add the statement, at which point the row can be removed.
+		private int GetResourceIdBuffer(Resource resource, bool create, StringBuilder literalInsertBuffer, StringBuilder entityInsertBuffer, bool insertCombined, ref bool firstLiteralInsert, ref bool firstEntityInsert) {
 			if (resource == null) return 0;
 			
 			if (resource is Literal) {
 				Literal lit = (Literal)resource;
-				return GetLiteralId(lit, create, literalInsertBuffer, insertCombined);
+				return GetLiteralId(lit, create, literalInsertBuffer, insertCombined, ref firstLiteralInsert);
 			}
 			
 			if (object.ReferenceEquals(resource, Statement.DefaultMeta))
@@ -360,7 +539,7 @@
 			int id;
 			
 			if (resource.Uri != null) {
-				id = GetEntityId(resource.Uri, create, entityInsertBuffer, insertCombined, true);
+				id = GetEntityId(resource.Uri, create, entityInsertBuffer, insertCombined, true, ref firstEntityInsert);
 			} else {
 				// This anonymous node didn't come from the database
 				// since it didn't have a resource key.  If !create,
@@ -381,7 +560,7 @@
 					// removed.
 					string guid = "semweb-bnode-guid://taubz.for.net,2006/"
 						+ Guid.NewGuid().ToString("N");
-					id = GetEntityId(guid, create, entityInsertBuffer, insertCombined, false);
+					id = GetEntityId(guid, create, entityInsertBuffer, insertCombined, false, ref firstEntityInsert);
 					anonEntityHeldIds.Add(id);
 				}
 			}
@@ -391,11 +570,14 @@
 			return id;
 		}
 
+		// Gets the type of the Resource, 0 for entities; 1 for literals.
 		private int ObjectType(Resource r) {
 			if (r is Literal) return 1;
 			return 0;
 		}
 		
+		// Creates an entity given its ID and its URI, and put it into
+		// the cache argument if the argument is not null.
 		private Entity MakeEntity(int resourceId, string uri, Hashtable cache) {
 			if (resourceId == 0)
 				return null;
@@ -422,36 +604,20 @@
 			return ent;
 		}
 		
-		public override void Add(Statement statement) {
+		// Adds a statement to the store.
+		// If we're isImoprting, buffer the statement, and if the buffer is full,
+		// run the buffer.
+		// Otherwise, add it immediately.
+		bool StatementSink.Add(Statement statement) {
+			Add(statement);
+			return true;
+		}
+		public void Add(Statement statement) {
 			if (statement.AnyNull) throw new ArgumentNullException();
 			
-			metaEntities = null;
-
 			if (addStatementBuffer != null) {
 				addStatementBuffer.Add(statement);
-				
-				// This complicated code here adjusts the size of the add
-				// buffer dynamically to maximize performance.
-				int thresh = importAddBufferSize;
-				if (importAddBufferRotation == 1) thresh += 100; // experiment with changing
-				if (importAddBufferRotation == 2) thresh -= 100; // the buffer size
-				
-				if (addStatementBuffer.Count >= thresh) {
-					DateTime start = DateTime.Now;
-					RunAddBuffer();
-					TimeSpan duration = DateTime.Now - start;
-					
-					// If there was an improvement in speed, per statement, on an 
-					// experimental change in buffer size, keep the change.
-					if (importAddBufferRotation != 0
-						&& duration.TotalSeconds/thresh < importAddBufferTime.TotalSeconds/importAddBufferSize
-						&& thresh >= 200 && thresh <= 4000)
-						importAddBufferSize = thresh;
-
-					importAddBufferTime = duration;
-					importAddBufferRotation++;
-					if (importAddBufferRotation == 3) importAddBufferRotation = 0;
-				}
+				RunAddBufferDynamic();
 				return;
 			}
 			
@@ -499,6 +665,33 @@
 			}
 		}
 		
+		private void RunAddBufferDynamic() {
+			// This complicated code here adjusts the size of the add
+			// buffer dynamically to maximize performance.
+			int thresh = importAddBufferSize;
+			if (importAddBufferRotation == 1) thresh += 100; // experiment with changing
+			if (importAddBufferRotation == 2) thresh -= 100; // the buffer size
+			
+			if (addStatementBuffer.Count >= thresh) {
+				DateTime start = DateTime.Now;
+				RunAddBuffer();
+				TimeSpan duration = DateTime.Now - start;
+				
+				if (DebugLogSpeed)
+					Console.Error.WriteLine(thresh + "\t" + thresh/duration.TotalSeconds);
+				
+				// If there was an improvement in speed, per statement, on an 
+				// experimental change in buffer size, keep the change.
+				if (importAddBufferRotation != 0
+					&& duration.TotalSeconds/thresh < importAddBufferTime.TotalSeconds/importAddBufferSize
+					&& thresh >= 200 && thresh <= 10000)
+					importAddBufferSize = thresh;
+				importAddBufferTime = duration;
+				importAddBufferRotation++;
+				if (importAddBufferRotation == 3) importAddBufferRotation = 0;
+			}
+		}
+		
 		private void RunAddBuffer() {
 			if (addStatementBuffer == null || addStatementBuffer.Count == 0) return;
 			
@@ -564,9 +757,9 @@
 					
 					if (hasLiterals)
 						cmd.Append(" , ");
-					cmd.Append('"');
+					cmd.Append(quote);
 					cmd.Append(hash);
-					cmd.Append('"');
+					cmd.Append(quote);
 					hasLiterals = true;
 					litseen[hash] = lit;
 				}
@@ -584,6 +777,11 @@
 				
 				StringBuilder entityInsertions = new StringBuilder();
 				StringBuilder literalInsertions = new StringBuilder();
+				if (insertCombined) entityInsertions.Append(INSERT_INTO_ENTITIES_VALUES);
+				if (insertCombined) literalInsertions.Append(INSERT_INTO_LITERALS_VALUES);
+				int entityInsertionsInitialLength = entityInsertions.Length;
+				int literalInsertionsInitialLength = literalInsertions.Length;
+				bool firstLiteralInsert = true, firstEntityInsert = true; // only used if insertCombined is true
 				
 				cmd = new StringBuilder();
 				if (insertCombined)
@@ -592,11 +790,11 @@
 				for (int i = 0; i < statements.Count; i++) {
 					Statement statement = (Statement)statements[i];
 				
-					int subj = GetResourceIdBuffer(statement.Subject, true, literalInsertions, entityInsertions, insertCombined);
-					int pred = GetResourceIdBuffer(statement.Predicate, true,  literalInsertions, entityInsertions, insertCombined);
+					int subj = GetResourceIdBuffer(statement.Subject, true, literalInsertions, entityInsertions, insertCombined, ref firstLiteralInsert, ref firstEntityInsert);
+					int pred = GetResourceIdBuffer(statement.Predicate, true,  literalInsertions, entityInsertions, insertCombined, ref firstLiteralInsert, ref firstEntityInsert);
 					int objtype = ObjectType(statement.Object);
-					int obj = GetResourceIdBuffer(statement.Object, true, literalInsertions, entityInsertions, insertCombined);
-					int meta = GetResourceIdBuffer(statement.Meta, true, literalInsertions, entityInsertions, insertCombined);
+					int obj = GetResourceIdBuffer(statement.Object, true, literalInsertions, entityInsertions, insertCombined, ref firstLiteralInsert, ref firstEntityInsert);
+					int meta = GetResourceIdBuffer(statement.Meta, true, literalInsertions, entityInsertions, insertCombined, ref firstLiteralInsert, ref firstEntityInsert);
 					
 					if (!insertCombined)
 						cmd.Append(INSERT_INTO_STATEMENTS_VALUES);
@@ -617,22 +815,21 @@
 						cmd.Append("),");
 				}
 				
-				if (literalInsertions.Length > 0) {
-					if (insertCombined) {
-						literalInsertions.Insert(0, INSERT_INTO_LITERALS_VALUES);
+				if (literalInsertions.Length > literalInsertionsInitialLength) {
+					if (insertCombined)
 						literalInsertions.Append(';');
-					}
+					if (Debug) Console.Error.WriteLine(literalInsertions.ToString());
 					RunCommand(literalInsertions.ToString());
 				}
 				
-				if (entityInsertions.Length > 0) {
-					if (insertCombined) {
-						entityInsertions.Insert(0, INSERT_INTO_ENTITIES_VALUES);
+				if (entityInsertions.Length > entityInsertionsInitialLength) {
+					if (insertCombined)
 						entityInsertions.Append(';');
-					}
+					if (Debug) Console.Error.WriteLine(entityInsertions.ToString());
 					RunCommand(entityInsertions.ToString());
 				}
 				
+				if (Debug) Console.Error.WriteLine(cmd.ToString());
 				RunCommand(cmd.ToString());
 			
 			} finally {
@@ -644,7 +841,7 @@
 			}
 		}
 		
-		public override void Remove(Statement template) {
+		public void Remove(Statement template) {
 			Init();
 			RunAddBuffer();
 
@@ -657,18 +854,23 @@
 			RunCommand(cmd.ToString());
 			
 			statementsRemoved = true;
-			metaEntities = null;
 		}
 		
-		public override Entity[] GetEntities() {
+		public void RemoveAll(Statement[] templates) {
+			// TODO: Optimize this.
+			foreach (Statement t in templates)
+				Remove(t);
+		}
+		
+		public Entity[] GetEntities() {
 			return GetAllEntities(fourcols);
 		}
 			
-		public override Entity[] GetPredicates() {
+		public Entity[] GetPredicates() {
 			return GetAllEntities(predcol);
 		}
 		
-		public override Entity[] GetMetas() {
+		public Entity[] GetMetas() {
 			return GetAllEntities(metacol);
 		}
 
@@ -703,6 +905,10 @@
 				if (!AppendMultiRes((MultiRes)r, cmd)) return false;
 				cmd.Append(" ))");
 			} else {
+				if (r.Uri != null && r.Uri == rdfs_member) {
+					if (CreateEntityPrefixTest(col, rdf_li, cmd)) return true;
+				}
+				
 				int id = GetResourceId(r, false);
 				if (id == 0) return false;
 				cmd.Append('(');
@@ -783,6 +989,18 @@
 			builder.Append(text);
 		}
 		
+		///////////////////////////
+		// QUERYING THE DATABASE //
+		///////////////////////////
+		
+		public bool Contains(Resource resource) {
+			return GetResourceId(resource, false) != 0;
+		}
+		
+		public bool Contains(Statement template) {
+			return Store.DefaultContains(this, template);
+		}
+
 		internal struct SelectColumnFilter {
 			public bool SubjectId, PredicateId, ObjectId, MetaId;
 			public bool SubjectUri, PredicateUri, ObjectData, MetaUri;
@@ -827,8 +1045,12 @@
 				cmd.Append("_entities AS muri ON q.meta = muri.id");
 			}
 		}
+		
+		public void Select(StatementSink result) {
+			Select(Statement.All, result);
+		}
 
-		public override void Select(SelectFilter filter, StatementSink result) {
+		public void Select(SelectFilter filter, StatementSink result) {
 			if (result == null) throw new ArgumentNullException();
 			foreach (Entity[] s in SplitArray(filter.Subjects))
 			foreach (Entity[] p in SplitArray(filter.Predicates))
@@ -885,13 +1107,100 @@
 			}
 		}
 		
+		void CleanMultiRes(MultiRes res) {
+			ArrayList newitems = new ArrayList();
+			foreach (Resource r in res.items)
+				if ((object)r == (object)Statement.DefaultMeta || GetResourceKey(r) != null)
+					newitems.Add(r);
+			res.items = (Resource[])newitems.ToArray(typeof(Resource));
+		}
+		
 		void CacheMultiObjects(Hashtable entMap, Resource obj) {
 			if (!(obj is MultiRes)) return;
 			foreach (Resource r in ((MultiRes)obj).items)
 				entMap[GetResourceId(r, false)] = r;
 		}
 		
-		public override void Select(Statement template, StatementSink result) {
+		bool isOrContains(Resource r, string uri) {
+			if (r == null) return false;
+			if (r is MultiRes) {
+				foreach (Resource rr in ((MultiRes)r).items)
+					if (isOrContains(rr, uri))
+						return true;
+			} else {
+				if (r.Uri != null && r.Uri == uri)
+					return true;
+			}
+			return false;
+		}
+		
+		void PrefetchResourceIds(IList resources) {
+			StringBuilder cmd_e = new StringBuilder();
+			cmd_e.Append("SELECT id, value FROM ");
+			cmd_e.Append(table);
+			cmd_e.Append("_entities WHERE value IN (");
+			Hashtable seen_e = new Hashtable();
+			bool hasEnts = false;
+
+			StringBuilder cmd_l = new StringBuilder();
+			cmd_l.Append("SELECT id, hash FROM ");
+			cmd_l.Append(table);
+			cmd_l.Append("_literals WHERE hash IN (");
+			bool hasLiterals = false;
+			Hashtable seen_l = new Hashtable();
+
+			foreach (Resource r in resources) {
+				if ((object)r == (object)Statement.DefaultMeta || GetResourceKey(r) != null) // no need to prefetch
+					continue;
+			
+				if (r.Uri != null) {
+					if (seen_e.ContainsKey(r.Uri)) continue;
+					if (hasEnts)
+						cmd_e.Append(" , ");
+					EscapedAppend(cmd_e, r.Uri);
+					hasEnts = true;
+					seen_e[r.Uri] = r;
+				}
+
+				Literal lit = r as Literal;
+				if (lit != null) {
+					string hash = GetLiteralHash(lit);
+					if (seen_l.ContainsKey(hash)) continue;
+					
+					if (hasLiterals)
+						cmd_l.Append(" , ");
+					cmd_l.Append(quote);
+					cmd_l.Append(hash);
+					cmd_l.Append(quote);
+					hasLiterals = true;
+					seen_l[hash] = lit;
+				}
+			}
+			if (hasEnts) {
+				cmd_e.Append(");");
+				if (Debug) Console.Error.WriteLine(cmd_e.ToString());
+				using (IDataReader reader = RunReader(cmd_e.ToString())) {
+					while (reader.Read()) {
+						int id = reader.GetInt32(0);
+						string uri = AsString(reader[1]);
+						SetResourceKey((Entity)seen_e[uri], new ResourceKey(id));
+					}
+				}
+			}
+				
+			if (hasLiterals) {
+				cmd_l.Append(");");
+				using (IDataReader reader = RunReader(cmd_l.ToString())) {
+					while (reader.Read()) {
+						int id = reader.GetInt32(0);
+						string hash = AsString(reader[1]);
+						SetResourceKey((Literal)seen_l[hash], new ResourceKey(id));
+					}
+				}
+			}
+		}
+		
+		public void Select(Statement template, StatementSink result) {
 			if (result == null) throw new ArgumentNullException();
 			Select(template.Subject, template.Predicate, template.Object, template.Meta, null, result, 0);
 		}
@@ -916,26 +1225,41 @@
 			columns.ObjectData = templateObject == null || (templateObject is MultiRes && ((MultiRes)templateObject).ContainsLiterals());
 			columns.MetaUri = templateMeta == null;
 			
+			if (isOrContains(templatePredicate, rdfs_member)) {
+				columns.PredicateId = true;
+				columns.PredicateUri = true;
+			}
+			
 			// Meta URIs tend to be repeated a lot, so we don't
 			// want to ever select them from the database.
 			// This preloads them, although it makes the first
 			// select quite slow.
-			if (templateMeta == null && SupportsSubquery) {
+			/*if (templateMeta == null && SupportsSubquery) {
 				LoadMetaEntities();
 				columns.MetaUri = false;
-			}
+			}*/
 			
 			// Have to select something
 			if (!columns.SubjectId && !columns.PredicateId && !columns.ObjectId && !columns.MetaId)
 				columns.SubjectId = true;
 				
+			// Pre-cache the IDs of resources in a MultiRes. TODO: Pool these into one array.
+			foreach (Resource r in new Resource[] { templateSubject, templatePredicate, templateObject, templateMeta }) {
+				MultiRes mr = r as MultiRes;
+				if (mr == null) continue;
+				PrefetchResourceIds(mr.items);
+				CleanMultiRes(mr);
+				if (mr.items.Length == 0) // no possible values
+					return;
+			}
+				
 			// SQLite has a problem with LEFT JOIN: When a condition is made on the
 			// first table in the ON clause (q.objecttype=0/1), when it fails,
 			// it excludes the row from the first table, whereas it should only
 			// exclude the results of the join.
 						
 			System.Text.StringBuilder cmd = new System.Text.StringBuilder("SELECT ");
-			if (!SupportsNoDuplicates)
+			if (!HasUniqueStatementsConstraint)
 				cmd.Append("DISTINCT ");
 			SelectFilterColumns(columns, cmd);
 			cmd.Append(" FROM ");
@@ -1003,7 +1327,7 @@
 					Entity subject = GetSelectedEntity(sid, suri, templateSubject, columns.SubjectId, columns.SubjectUri, entMap);
 					Entity predicate = GetSelectedEntity(pid, puri, templatePredicate, columns.PredicateId, columns.PredicateUri, entMap);
 					Resource objec = GetSelectedResource(oid, ot, ouri, lv, ll, ld, templateObject, columns.ObjectId, columns.ObjectData, entMap);
-					Entity meta = GetSelectedEntity(mid, muri, templateMeta, columns.MetaId, columns.MetaUri, templateMeta != null ? entMap : metaEntities);
+					Entity meta = GetSelectedEntity(mid, muri, templateMeta, columns.MetaId, columns.MetaUri, templateMeta != null ? entMap : null);
 
 					if (litFilters != null && !LiteralFilter.MatchesFilters(objec, litFilters, this))
 						continue;
@@ -1015,7 +1339,400 @@
 			
 			} // lock
 		}
+		
+		public SemWeb.Query.MetaQueryResult MetaQuery(Statement[] graph, SemWeb.Query.QueryOptions options) {
+			return new SemWeb.Inference.SimpleEntailment().MetaQuery(graph, options, this);
+		}
+		
+		public void Query(Statement[] graph, SemWeb.Query.QueryOptions options, SemWeb.Query.QueryResultSink sink) {
+			if (graph.Length == 0) throw new ArgumentException("graph array must have at least one element");
+
+			options = options.Clone(); // because we modify the knownvalues array
+			
+			// Order the variables mentioned in the graph.
+			Variable[] varOrder;
+			ResSet distinguishedVars = null;
+			bool useDistinct = false;
+			{
+				if (options.DistinguishedVariables != null)
+					distinguishedVars = new ResSet(options.DistinguishedVariables);
+				else
+					distinguishedVars = new ResSet();
+			
+				Hashtable seenvars = new Hashtable();
+				foreach (Statement filter in graph) {
+					for (int i = 0; i < 4; i++) {
+						Resource r = filter.GetComponent(i);
+						if (r == null)
+							throw new ArgumentException("The graph may not have any null components.  Use Variables instead.");
+
+						if (r is Variable) {
+							if (options.DistinguishedVariables != null) {
+								if (!distinguishedVars.Contains(r)) {
+									// If we are omitting a column from the results because it is
+									// not distinguished, and it's not a meta column, then we'll
+									// use DISTINCT.
+									if (i != 3)
+										useDistinct = true;
+										
+									// Don't put this into seenvars.
+									continue;
+								}
+							} else {
+								distinguishedVars.Add(r); // all variables are distinguished
+							}
+							
+							seenvars[r] = r;
+						}
+					}
+				}
+				
+				varOrder = new Variable[seenvars.Count];
+				int ctr = 0;
+				foreach (Variable v in seenvars.Keys)
+					varOrder[ctr++] = v;
+			}
+			
+			bool useView = useDistinct && SupportsViews && !NoSQLView;
+			
+			// Set the initial bindings to the result sink
+
+			sink.Init(varOrder);
+			
+			Hashtable varLitFilters = new Hashtable();
+			
+			// Prefetch the IDs of all resources mentioned in the graph and in variable known values.
+			// For Resources in the graph that are not in the store, the query immediately fails.
+			{
+				ArrayList graphResources = new ArrayList();
+				foreach (Statement s in graph) {
+					for (int i = 0; i < 4; i++) {
+						Resource r = s.GetComponent(i);
+						if (!(r is BNode)) // definitely exclude variables, but bnodes are useless too
+							graphResources.Add(r);
+					}
+				}
+				if (options.VariableKnownValues != null)
+					foreach (ICollection values in options.VariableKnownValues.Values)
+						graphResources.AddRange(values);
+
+				PrefetchResourceIds(graphResources);
+				
+				// Check resources in graph and fail fast if any is not in the store.
+				foreach (Statement s in graph) {
+					for (int i = 0; i < 4; i++) {
+						Resource r = s.GetComponent(i);
+						if (r is Variable) continue;
+						if ((object)r != (object)Statement.DefaultMeta && GetResourceKey(r) == null) {
+							sink.AddComments("Resource " + r + " is not contained in the data model.");
+							sink.Finished();
+							return;
+						}
+					}
+				}
+				
+				// Check variable known values and remove any values not in the store.
+				// Don't do any fail-fasting here because there might be entries in this
+				// dictionary that aren't even used in this query (yes, poor design).
+				// We check later anyway.
+				if (options.VariableKnownValues != null) {
+					#if !DOTNET2
+					foreach (Variable v in new ArrayList(options.VariableKnownValues.Keys)) {
+					#else
+					foreach (Variable v in new System.Collections.Generic.List<Variable>(options.VariableKnownValues.Keys)) {
+					#endif
+						#if !DOTNET2
+						ArrayList newvalues = new ArrayList();
+						#else
+						System.Collections.Generic.List<Resource> newvalues = new System.Collections.Generic.List<Resource>();
+						#endif
+						
+						foreach (Resource r in (ICollection)options.VariableKnownValues[v]) {
+							if ((object)r == (object)Statement.DefaultMeta || GetResourceKey(r) != null)
+								newvalues.Add(r);
+						}
+
+						options.VariableKnownValues[v] = newvalues;
+					}
+				}
+			}
+			
+			// Helpers
+			
+			string[] colnames = { "subject", "predicate", "object", "meta" };
+			
+			// Lock the store and make sure we are initialized and any pending add's have been committed. 
+					
+			lock (syncroot) {
+			
+			Init();
+			RunAddBuffer();
+			
+			// Compile the SQL statement.
+
+			Hashtable varRef_Inner = new Hashtable(); // the column name representing the variable: if we're using VIEWs, then within the VIEW (i.e. name of column in underlying table)
+			Hashtable varRef_Outer = new Hashtable(); // if we're using VIEWs, then the column name representing the variable of the VIEW itself 
+			Hashtable varRef2 = new Hashtable();
+			Hashtable varSelectedLiteral = new Hashtable();
+			
+			StringBuilder fromClause = new StringBuilder();
+			StringBuilder whereClause = new StringBuilder();
+			StringBuilder outerSelectJoins = new StringBuilder();
+			StringBuilder outerWhereClause = new StringBuilder();
+			
+			for (int f = 0; f < graph.Length; f++) {
+				// For each filter, we select FROM the statements table with an
+				// alias: q#, where # is the filter's index.
+				
+				if (f > 0) fromClause.Append(',');
+				fromClause.Append(table);
+				fromClause.Append("_statements AS g");
+				fromClause.Append(f);
+				
+				// For each component of the filter...
+				
+				for (int i = 0; i < 4; i++) {
+					string myRef = "g" + f + "." + colnames[i];
+					
+					Variable v = graph[f].GetComponent(i) as Variable;
+					if (v != null) {
+						// If the component is a variable, then if this is
+						// the first time we're seeing the variable, we don't
+						// add any restrictions to the WHERE clause, but we
+						// note the variable's "name" in the world of SQL
+						// so we can refer back to it later and we add the
+						// necessary FROM tables so we can get its URI and
+						// literal value if it is a reported variable.
+						// If this isn't the first time, then we add a WHERE restriction so
+						// that the proper columns here and in a previous
+						// filter are forced to have the same value.
+					
+						if (!varRef_Inner.ContainsKey(v)) {
+							varRef_Inner[v] = myRef;
+							varRef_Outer[v] = "v" + Array.IndexOf(varOrder, v);
+							
+							int vIndex = varRef_Inner.Count;
+							varRef2[v] = vIndex;
+							
+							if (distinguishedVars.Contains(v)) {
+								StringBuilder joinTarget = fromClause;
+								if (useView) joinTarget = outerSelectJoins;
+								
+								string onRef = (string)(!useView ? varRef_Inner : varRef_Outer)[v];
+								
+								joinTarget.Append(" LEFT JOIN ");
+								joinTarget.Append(table);
+								joinTarget.Append("_entities AS vent");
+								joinTarget.Append(vIndex);
+								joinTarget.Append(" ON ");
+								joinTarget.Append(onRef);
+								joinTarget.Append("=");
+								joinTarget.Append("vent" + vIndex + ".id ");
+								
+								varSelectedLiteral[v] = (i == 2);
+								
+								if (i == 2) { // literals cannot be in any other column
+									joinTarget.Append(" LEFT JOIN ");
+									joinTarget.Append(table);
+									joinTarget.Append("_literals AS vlit");
+									joinTarget.Append(vIndex);
+									joinTarget.Append(" ON ");
+									joinTarget.Append(onRef);
+									joinTarget.Append("=");
+									joinTarget.Append("vlit" + vIndex + ".id ");
+								}
+							}
+							
+							if (options.VariableKnownValues != null) {
+								ICollection values = null;
+								#if DOTNET2
+								if (options.VariableKnownValues.ContainsKey(v))
+								#endif
+									values = (ICollection)options.VariableKnownValues[v];
+								if (values != null) {
+									if (values.Count == 0) {
+										sink.Finished();
+										return;
+									}
+									Resource r = ToMultiRes((Resource[])new ArrayList(values).ToArray(typeof(Resource)));
+									if (!WhereItem(myRef, r, whereClause, whereClause.Length != 0)) {
+										// We know at this point that the query cannot return any results.
+										sink.Finished();
+										return;
+									}
+								}
+							}
+							
+						} else {
+							if (whereClause.Length != 0) whereClause.Append(" AND ");
+							whereClause.Append('(');
+							whereClause.Append((string)varRef_Inner[v]);
+							whereClause.Append('=');
+							whereClause.Append(myRef);
+							whereClause.Append(')');
+						}
+					
+					} else {
+						// If this is not a variable, then it is a resource.
+					
+						if (!WhereItem(myRef, graph[f].GetComponent(i), whereClause, whereClause.Length != 0)) {
+							// We know at this point that the query cannot return any results.
+							sink.Finished();
+							return;
+						}
+
+					}
+				}
+			
+			} // graph filter 0...n
+			
+			// Add literal filters to the WHERE clause
+
+			foreach (Variable v in varOrder) {
+				if (options.VariableLiteralFilters == null) continue;
+				#if !DOTNET2
+				if (options.VariableLiteralFilters[v] == null) continue;
+				#else
+				if (!options.VariableLiteralFilters.ContainsKey(v)) continue;
+				#endif
+				
+				foreach (LiteralFilter filter in (ICollection)options.VariableLiteralFilters[v]) {
+					string s = FilterToSQL(filter, "vlit" + (int)varRef2[v] + ".value");
+					if (s == null) continue;
+
+					StringBuilder where = whereClause;
+					if (useView) where = outerWhereClause;
+					
+					if (where.Length != 0) where.Append(" AND ");
+					where.Append(s);
+				}
+			}
+
+			// Put the parts of the SQL statement together
+
+			StringBuilder cmd = new StringBuilder();
+			StringBuilder outercmd = new StringBuilder();
+			
+			string viewname = "queryview" + Math.Abs(GetHashCode());
+			if (useView) {
+				cmd.Append("CREATE VIEW ");
+				cmd.Append(viewname);
+				cmd.Append(" AS ");
+				
+				outercmd.Append("SELECT ");
+			}
+			cmd.Append("SELECT ");
+			
+			if (useDistinct) cmd.Append("DISTINCT ");
+			
+			for (int i = 0; i < varOrder.Length; i++) {
+				if (i > 0) cmd.Append(',');
+				cmd.Append((string)varRef_Inner[varOrder[i]]);
+				
+				StringBuilder c = cmd;
+				if (useView) {
+					cmd.Append(" AS ");
+					cmd.Append((string)varRef_Outer[varOrder[i]]);
+
+					if (i > 0) outercmd.Append(',');
+					outercmd.Append((string)varRef_Outer[varOrder[i]]);
+					c = outercmd;
+				}
+				
+				c.Append(", vent" + (int)varRef2[varOrder[i]] + ".value");
+				if ((bool)varSelectedLiteral[varOrder[i]]) {
+					c.Append(", vlit" + (int)varRef2[varOrder[i]] + ".value");
+					c.Append(", vlit" + (int)varRef2[varOrder[i]] + ".language");
+					c.Append(", vlit" + (int)varRef2[varOrder[i]] + ".datatype");
+				}
+			}
+			
+			cmd.Append(" FROM ");
+			cmd.Append(fromClause.ToString());
+			
+			if (whereClause.Length > 0)
+				cmd.Append(" WHERE ");
+			cmd.Append(whereClause.ToString());
+			
+			if (options.Limit > 0) {
+				cmd.Append(" LIMIT ");
+				cmd.Append(options.Limit);
+			}
+			
+			cmd.Append(';');
+
+			if (useView) {
+				outercmd.Append(" FROM ");
+				outercmd.Append(viewname);
+				outercmd.Append(outerSelectJoins);
+				
+				if (outerWhereClause.Length > 0)
+					outercmd.Append(" WHERE ");
+				outercmd.Append(outerWhereClause.ToString());
+			}
+			
+			
+			if (Debug) {
+				string cmd2 = cmd.ToString();
+				//if (cmd2.Length > 80) cmd2 = cmd2.Substring(0, 80);
+				Console.Error.WriteLine(cmd2);
+				if (useView)
+					Console.Error.WriteLine(outercmd.ToString());
+			}
+			
+			// Execute the query
+			
+			Hashtable entityCache = new Hashtable();
+			
+			if (useView) {
+				RunCommand(cmd.ToString());
+				cmd = outercmd;
+			}
+			
+			try {
+			using (IDataReader reader = RunReader(cmd.ToString())) {
+				while (reader.Read()) {
+					Resource[] variableBindings = new Resource[varOrder.Length];
+				
+					int col = 0;
+					for (int i = 0; i < varOrder.Length; i++) {
+						int id = reader.GetInt32(col++);
+						string uri = AsString(reader[col++]);
+						
+						string litvalue = null, litlanguage = null, litdatatype = null;
+
+						if ((bool)varSelectedLiteral[varOrder[i]]) {
+							litvalue = AsString(reader[col++]);
+							litlanguage = AsString(reader[col++]);
+							litdatatype = AsString(reader[col++]);
+						}
+						
+						if (litvalue != null) {
+							Literal lit = new Literal(litvalue, litlanguage, litdatatype);
+							variableBindings[i] = lit;
+							
+							ArrayList litFilters = (ArrayList)varLitFilters[varOrder[i]];
+							if (litFilters != null && !LiteralFilter.MatchesFilters(lit, (LiteralFilter[])litFilters.ToArray(typeof(LiteralFilter)), this))
+								continue;
+							
+						} else {
+							variableBindings[i] = MakeEntity(id, uri, entityCache);
+						}
+					}
+					
+					if (!sink.Add(new SemWeb.Query.VariableBindings(varOrder, variableBindings))) return;
+				}
+			}
+			} finally {
+				if (useView)
+					RunCommand("DROP VIEW " + viewname);
 
+				sink.Finished();
+			}
+				
+			} // lock
+		}
+		
 		Entity GetSelectedEntity(int id, string uri, Resource given, bool idSelected, bool uriSelected, Hashtable entMap) {
 			if (!idSelected) return (Entity)given;
 			if (!uriSelected) {
@@ -1036,6 +1753,12 @@
 			else
 				return new Literal(lv, ll, ld);
 		}
+		
+		private string CreateLikeTest(string column, string match, int method) {
+			StringBuilder s = new StringBuilder();
+			CreateLikeTest(column, match, method, s);
+			return s.ToString();
+		}
 
 		private string FilterToSQL(LiteralFilter filter, string col) {
 			if (filter is SemWeb.Filters.StringCompareFilter) {
@@ -1044,11 +1767,11 @@
 			}
 			if (filter is SemWeb.Filters.StringContainsFilter) {
 				SemWeb.Filters.StringContainsFilter f = (SemWeb.Filters.StringContainsFilter)filter;
-				return col + " LIKE " + quote + "%" + Escape(f.Pattern, false).Replace("%", "\\%") + "%" + quote;
+				return CreateLikeTest(col, f.Pattern, 1); // 1=contains
 			}
 			if (filter is SemWeb.Filters.StringStartsWithFilter) {
 				SemWeb.Filters.StringStartsWithFilter f = (SemWeb.Filters.StringStartsWithFilter)filter;
-				return col + " LIKE " + quote + Escape(f.Pattern, false).Replace("%", "\\%") + "%" + quote;
+				return CreateLikeTest(col, f.Pattern, 0); // 0=starts-with
 			}
 			if (filter is SemWeb.Filters.NumericCompareFilter) {
 				SemWeb.Filters.NumericCompareFilter f = (SemWeb.Filters.NumericCompareFilter)filter;
@@ -1069,34 +1792,21 @@
 			}			
 		}
 		
-		private void LoadMetaEntities() {
-			if (metaEntities != null) return;
-			metaEntities = new Hashtable();
-			// this misses meta entities that are anonymous, but that's ok
-			using (IDataReader reader = RunReader("select id, value from " + table + "_entities where id in (select distinct meta from " + table + "_statements)")) {
-				while (reader.Read()) {
-					int id = reader.GetInt32(0);
-					string uri = reader.GetString(1);
-					metaEntities[id] = MakeEntity(id, uri, null);
-				}
-			}
-		}
-		
 		private string Escape(string str, bool quotes) {
 			if (str == null) return "NULL";
 			StringBuilder b = new StringBuilder();
-			EscapedAppend(b, str, quotes);
+			EscapedAppend(b, str, quotes, false);
 			return b.ToString();
 		}
 		
 		protected void EscapedAppend(StringBuilder b, string str) {
-			EscapedAppend(b, str, true);
+			EscapedAppend(b, str, true, false);
 		}
 
 		protected virtual char GetQuoteChar() {
 			return '\"';
 		}
-		protected virtual void EscapedAppend(StringBuilder b, string str, bool quotes) {
+		protected virtual void EscapedAppend(StringBuilder b, string str, bool quotes, bool forLike) {
 			if (quotes) b.Append(quote);
 			for (int i = 0; i < str.Length; i++) {
 				char c = str[i];
@@ -1108,6 +1818,12 @@
 						b.Append('\\');
 						b.Append(c);
 						break;
+					case '%':
+					case '_':
+						if (forLike)
+							b.Append('\\');
+						b.Append(c);
+						break;
 					default:
 						b.Append(c);
 						break;
@@ -1124,7 +1840,7 @@
 			b.Replace("*", "\\*");
 		}*/
 
-		public override void Import(StatementSource source) {
+		public void Import(StatementSource source) {
 			if (source == null) throw new ArgumentNullException();
 			if (isImporting) throw new InvalidOperationException("Store is already importing.");
 			
@@ -1132,13 +1848,15 @@
 			RunAddBuffer();
 			
 			cachedNextId = -1;
+			NextId(); // get this before starting transaction because it relies on indexes which may be disabled
+			
 			addStatementBuffer = new StatementList();
 			
 			BeginTransaction();
 			
 			try {
 				isImporting = true;
-				base.Import(source);
+				source.Select(this);
 			} finally {
 				RunAddBuffer();
 				EndTransaction();
@@ -1151,7 +1869,7 @@
 			}
 		}
 
-		public override void Replace(Entity a, Entity b) {
+		public void Replace(Entity a, Entity b) {
 			Init();
 			RunAddBuffer();
 			int id = GetResourceId(b, true);
@@ -1169,10 +1887,9 @@
 				RunCommand(cmd.ToString());
 			}
 
-			metaEntities = null;
 		}
 		
-		public override void Replace(Statement find, Statement replacement) {
+		public void Replace(Statement find, Statement replacement) {
 			if (find.AnyNull) throw new ArgumentNullException("find");
 			if (replacement.AnyNull) throw new ArgumentNullException("replacement");
 			if (find == replacement) return;
@@ -1206,9 +1923,16 @@
 				return;
 			
 			RunCommand(cmd.ToString());
-			metaEntities = null;
 		}
 		
+		private object GetResourceKey(Resource resource) {
+			return resource.GetResourceKey(this);
+		}
+
+		private void SetResourceKey(Resource resource, object value) {
+			resource.SetResourceKey(this, value);
+		}
+
 		protected abstract void RunCommand(string sql);
 		protected abstract object RunScalar(string sql);
 		protected abstract IDataReader RunReader(string sql);
@@ -1219,7 +1943,7 @@
 			if (ret is int) return (int)ret;
 			try {
 				return int.Parse(ret.ToString());
-			} catch (FormatException e) {
+			} catch (FormatException) {
 				return def;
 			}
 		}
@@ -1232,7 +1956,11 @@
 			throw new FormatException("SQL store returned a literal value as " + ret);
 		}
 		
-		public override void Close() {
+		void IDisposable.Dispose() {
+			Close();
+		}
+		
+		public virtual void Close() {
 			if (statementsRemoved) {
 				RunCommand("DELETE FROM " + table + "_literals where (select count(*) from " + table + "_statements where object=id) = 0 and id > 0");
 				RunCommand("DELETE FROM " + table + "_entities where (select count(*) from " + table + "_statements where subject=id) = 0 and (select count(*) from " + table + "_statements where predicate=id) = 0 and (select count(*) from " + table + "_statements where object=id) = 0 and (select count(*) from " + table + "_statements where meta=id) = 0 ;");
@@ -1278,7 +2006,7 @@
 		internal static string[] GetCreateIndexCommands(string table) {
 			return new string[] {
 				"CREATE UNIQUE INDEX subject_full_index ON " + table + "_statements(subject, predicate, object, meta, objecttype);",
-				"CREATE INDEX predicate_index ON " + table + "_statements(predicate);",
+				"CREATE INDEX predicate_index ON " + table + "_statements(predicate, object);",
 				"CREATE INDEX object_index ON " + table + "_statements(object);",
 				"CREATE INDEX meta_index ON " + table + "_statements(meta);",
 			

Added: branches/beagle-rdf/Util/SemWeb/SparqlClient.cs
==============================================================================
--- (empty file)
+++ branches/beagle-rdf/Util/SemWeb/SparqlClient.cs	Sat Jan 26 23:26:03 2008
@@ -0,0 +1,683 @@
+using System;
+using System.Collections;
+#if DOTNET2
+using System.Collections.Generic;
+#endif
+using System.IO;
+using System.Text;
+using System.Web;
+using System.Xml;
+ 
+using SemWeb;
+using SemWeb.Query;
+using SemWeb.Util;
+ 
+namespace SemWeb.Remote {
+
+	public interface SparqlSource {
+		void RunSparqlQuery(string sparqlQuery, TextWriter output);
+		void RunSparqlQuery(string sparqlQuery, out bool askResult);
+		void RunSparqlQuery(string sparqlQuery, StatementSink statementResults);
+		void RunSparqlQuery(string sparqlQuery, QueryResultSink selectResults);
+	}
+
+	public class SparqlHttpSource : QueryableSource, SparqlSource {
+		static bool Debug = System.Environment.GetEnvironmentVariable("SEMWEB_DEBUG_HTTP") != null;
+	
+		string url;
+		
+		public SparqlHttpSource(string url) {
+			this.url = url;
+		}
+
+		public bool Distinct { get { return false; } }
+
+		public void RunSparqlQuery(string sparqlQuery, TextWriter output) {
+			Load(sparqlQuery, output);
+		}
+		
+		public void RunSparqlQuery(string sparqlQuery, out bool askResult) {
+			BooleanWrap bw = new BooleanWrap();
+			Load(sparqlQuery, bw);
+			askResult = bw.value;
+		}
+		
+		public void RunSparqlQuery(string sparqlQuery, StatementSink statementResults) {
+			Load(sparqlQuery, statementResults);
+		}
+		
+		public void RunSparqlQuery(string sparqlQuery, QueryResultSink selectResults) {
+			Load(sparqlQuery, selectResults);
+		}
+		
+		public bool Contains(Resource resource) {
+			throw new NotImplementedException();
+		}
+		
+		public bool Contains(Statement template) {
+			return Select(template, null, true);
+		}
+		
+		public void Select(StatementSink sink) {
+			Select(Statement.All, sink);
+		}
+		
+		public void Select(Statement template, StatementSink sink) {
+			Select(template, sink, false);
+		}
+		
+		bool Select(Statement template, StatementSink sink, bool ask) {
+			return Select(
+				template.Subject == null ? null : new Entity[] { template.Subject },
+				template.Predicate == null ? null : new Entity[] { template.Predicate },
+				template.Object == null ? null : new Resource[] { template.Object },
+				template.Meta == null ? null : new Entity[] { template.Meta },
+				null,
+				0,
+				sink,
+				ask
+				);
+		}
+		
+		public void Select(SelectFilter filter, StatementSink sink) {
+			Select(filter.Subjects, filter.Predicates, filter.Objects, filter.Metas, filter.LiteralFilters, filter.Limit, sink, false);
+		}
+		
+		bool Select(Entity[] subjects, Entity[] predicates, Resource[] objects, Entity[] metas, LiteralFilter[] litFilters, int limit, StatementSink sink, bool ask) {
+			// TODO: Change meta into named graphs.  Anything but a null or DefaultMeta
+			// meta returns no statements immediately.
+			if (metas != null && (metas.Length != 1 || metas[0] != Statement.DefaultMeta))
+				return false;
+		
+			string query;
+			bool nonull = false;
+		
+			if (subjects != null && subjects.Length == 1
+				&& predicates != null && predicates.Length == 1
+				&& objects != null && objects.Length == 1) {
+				query = "ASK WHERE { " + S(subjects[0], null) + " " + S(predicates[0], null) + " " + S(objects[0], null) + "}";
+				nonull = true;
+			} else {
+				if (ask)
+					query = "ASK { ";
+				else
+					query = "SELECT * WHERE { ";
+				query += S(subjects, "subject");
+				query += " ";
+				query += S(predicates, "predicate");
+				query += " ";
+				query += S(objects, "object");
+				query += " . ";
+				query += SL(subjects, "subject", false);
+				query += SL(predicates, "predicate", false);
+				query += SL(objects, "object", false);
+				query += " }";
+				
+				// TODO: Pass literal filters to server.
+			}
+			
+			if (limit >= 1)
+				query += " LIMIT " + limit;
+				
+			Statement d = new Statement(
+				(subjects != null && subjects.Length == 1) ? subjects[0] : null,
+				(predicates != null && predicates.Length == 1) ? predicates[0] : null,
+				(objects != null && objects.Length == 1) ? objects[0] : null);
+			
+			if (ask || nonull) {
+				BooleanWrap bw = new BooleanWrap();
+				Load(query, bw);
+				if (ask)
+					return bw.value;
+				else if (bw.value)
+					sink.Add(new Statement(subjects[0], predicates[0], objects[0]));
+				return false;
+			} else {
+				Load(query, new QueryToStatements(sink, litFilters, d));
+				return true;
+			}
+		}
+		
+		class QueryToStatements : QueryResultSink {
+			StatementSink sink;
+			LiteralFilter[] litFilters;
+			Statement d;
+			int si = -1, pi = -1, oi = -1;
+			
+			public QueryToStatements(StatementSink sink, LiteralFilter[] litFilters, Statement d) {
+				this.sink = sink;
+				this.litFilters = litFilters;
+				this.d = d;
+			}
+			
+			public override void Init(Variable[] variables) {
+				for (int i = 0; i < variables.Length; i++) {
+					if (variables[i].LocalName == "subject") si = i;
+					if (variables[i].LocalName == "predicate") pi = i;
+					if (variables[i].LocalName == "object") oi = i;
+				}
+			}
+			
+			public override bool Add(VariableBindings result) {
+				Resource subj = si == -1 ? d.Subject : result.Values[si];
+				Resource pred = pi == -1 ? d.Predicate : result.Values[pi];
+				Resource obj = oi == -1 ? d.Object : result.Values[oi];
+				if (!(subj is Entity) || !(pred is Entity)) return true;
+				if (litFilters != null && !LiteralFilter.MatchesFilters(obj, litFilters, null)) return true;
+				return sink.Add(new Statement((Entity)subj, (Entity)pred, obj));
+			}
+
+		}
+		
+		string S(Resource[] r, string v) {
+			if (r == null || r.Length != 1) return "?" + v;
+			return S(r[0], null);
+		}
+		string SL(Resource[] r, string v, bool includeIfJustOne) {
+			if (r == null || (r.Length <= 1 && !includeIfJustOne)) return "";
+			StringBuilder ret = new StringBuilder();
+			ret.Append("FILTER(");
+			bool first = true;
+			for (int i = 0; i < r.Length; i++) {
+				if (r[i].Uri == null) continue;
+				if (!first) ret.Append(" || "); first = false;
+				ret.Append('?');
+				ret.Append(v);
+				ret.Append("=<");
+				if (r[i].Uri != null)
+					ret.Append(r[i].Uri);
+				ret.Append('>');
+			}
+			ret.Append(").\n");
+			if (first) return "";
+			return ret.ToString();
+		}
+		#if !DOTNET2
+		string SL(object r, string v, bool includeIfJustOne) {
+			return SL((Resource[])new ArrayList((ICollection)r).ToArray(typeof(Resource)), v, includeIfJustOne);
+		}
+		#else
+		string SL(ICollection<Resource> r, string v, bool includeIfJustOne) {
+			return SL(new List<Resource>(r).ToArray(), v, includeIfJustOne);
+		}
+		#endif
+		
+		string S(Resource r, string v) {
+			if (r == null || r is Variable) {
+				return v;
+			} else if (r is Literal) {
+				return r.ToString();
+			} else if (r.Uri != null) {
+				if (r.Uri.IndexOf('>') != -1)
+					throw new ArgumentException("Invalid URI: " + r.Uri);
+				return "<" + r.Uri + ">";
+			} else {
+				throw new NotSupportedException("Blank node in select not supported.");
+			}
+		}
+		
+		class BooleanWrap {
+			public bool value;
+		}
+		
+		void Load(string query, object outputObj) {
+			string qstr = "query=" + System.Web.HttpUtility.UrlEncode(query);
+			
+			string method = "POST";
+			
+			System.Net.WebRequest rq;
+			
+			if (Debug) {
+				Console.Error.WriteLine("> " + url);
+				Console.Error.WriteLine(query);
+				Console.Error.WriteLine();
+			}
+			
+			if (method == "GET") {
+				string qurl = url + "?" + qstr;
+				rq = System.Net.WebRequest.Create(qurl);
+			} else {
+				Encoding encoding = new UTF8Encoding(); // ?
+				byte[] data = encoding.GetBytes(qstr);
+
+				rq = System.Net.WebRequest.Create(url);
+				rq.Method = "POST";
+				rq.ContentType="application/x-www-form-urlencoded";
+				rq.ContentLength = data.Length;
+				
+				using (Stream stream = rq.GetRequestStream())
+					stream.Write(data, 0, data.Length);
+			}
+			
+			System.Net.HttpWebResponse resp = (System.Net.HttpWebResponse)rq.GetResponse();
+			try {
+				string mimetype = resp.ContentType;
+				if (mimetype.IndexOf(';') > -1)
+					mimetype = mimetype.Substring(0, mimetype.IndexOf(';'));
+					
+				ProcessResponse(mimetype, resp.GetResponseStream(), outputObj);
+			} finally {
+				resp.Close();
+			}
+		}
+		
+		public static void ParseSparqlResponse(Stream sparqlResponse, QueryResultSink queryResults) {
+			ProcessResponse(null, sparqlResponse, queryResults);
+		}
+		
+		public static void ParseSparqlResponse(Stream sparqlResponse, out bool askResult) {
+			BooleanWrap bw = new BooleanWrap();
+			ProcessResponse(null, sparqlResponse, bw);
+			askResult = bw.value;
+		}
+
+		private static void ProcessResponse(string mimetype, Stream stream, object outputObj) {
+		
+			// If the user wants the output sent to a TextWriter, copy the response from
+			// the response stream to the TextWriter. TODO: Get encoding from HTTP header.
+			if (outputObj is TextWriter) {
+				TextWriter tw = (TextWriter)outputObj;
+				using (StreamReader reader = new StreamReader(stream, System.Text.Encoding.UTF8)) {
+					char[] buffer = new char[512];
+					while (true) {
+						int len = reader.Read(buffer, 0, buffer.Length);
+						if (len <= 0) break;
+						tw.Write(buffer, 0, len);
+					}
+				}
+				tw.Flush();
+				return;
+			}
+			
+			// If the user wants a boolean out of this, then we're expecting a
+			// SPARQL XML Results document with a boolean response element.
+			if (outputObj is BooleanWrap) {
+				BooleanWrap bw = (BooleanWrap)outputObj;
+				
+				if (mimetype != null && mimetype != "application/sparql-results+xml" && mimetype != "text/xml")
+					throw new ApplicationException("The result of the query was not a SPARQL Results document.");
+					
+				XmlReader xmldoc = new XmlTextReader(stream);
+				{
+					// Move to the document element
+					while (xmldoc.Read())
+						if (xmldoc.NodeType == XmlNodeType.Element) break;
+						
+					// Just check that it has the right local name.
+					if (xmldoc.LocalName != "sparql" || xmldoc.IsEmptyElement)
+						throw new ApplicationException("Invalid server response: Not a SPARQL results document.");
+								
+					// Move to the next node.
+					while (xmldoc.Read())
+						if (xmldoc.NodeType == XmlNodeType.Element) break;
+						
+					// If it's a head node, skip it.
+					if (xmldoc.LocalName == "head") {
+						xmldoc.Skip();
+						// Move to the 'boolean' element, it better be next
+						while (xmldoc.Read())
+							if (xmldoc.NodeType == XmlNodeType.Element) break;
+					}
+						
+					if (xmldoc.LocalName != "boolean")
+						throw new ApplicationException("Invalid server response: Missing 'boolean' element.");
+					
+					string value = xmldoc.ReadElementString();
+					bw.value = (value == "true");
+				}
+						
+				return;
+			}
+			
+			// If the user wants statements out of the response, read it with an RDFReader.
+			if (outputObj is StatementSink) {
+				// If the mime type is application/sparql-results+xml, just try to
+				// read it as if it were an RDF/XML MIME type.
+				if (mimetype != null && mimetype == "application/sparql-results+xml") mimetype = "text/xml";
+				using (RdfReader reader = RdfReader.Create(mimetype, stream))
+					reader.Select((StatementSink)outputObj);
+				return;
+			}
+			
+			// If the user wants query result bindings, read the response XML.
+			if (outputObj is QueryResultSink) {
+				QueryResultSink sink = (QueryResultSink)outputObj;
+			
+				if (mimetype != null && mimetype != "application/sparql-results+xml" && mimetype != "text/xml")
+					throw new ApplicationException("The result of the query was not a SPARQL Results document.");
+					
+				ArrayList variableNames = new ArrayList();
+				ArrayList variables = new ArrayList();
+				Variable[] variablesArray = null;
+				Hashtable bnodes = new Hashtable();
+
+				XmlReader xmldoc = new XmlTextReader(stream);
+				{
+					// Move to the document element
+					while (xmldoc.Read())
+						if (xmldoc.NodeType == XmlNodeType.Element) break;
+						
+					// Just check that it has the right local name.
+					if (xmldoc.LocalName != "sparql" || xmldoc.IsEmptyElement)
+						throw new ApplicationException("Invalid server response: Not a SPARQL results document.");
+								
+					// Move to the 'head' node, it better be the first element
+					while (xmldoc.Read())
+						if (xmldoc.NodeType == XmlNodeType.Element) break;
+						
+					if (xmldoc.LocalName != "head" || xmldoc.IsEmptyElement)
+						throw new ApplicationException("Invalid server response: Missing head full element.");
+						
+					// Read the head element
+					while (xmldoc.Read()) {
+						if (xmldoc.NodeType == XmlNodeType.Element && xmldoc.LocalName == "variable") {
+							if (xmldoc.GetAttribute("name") == null)
+								throw new ApplicationException("Invalid server response: Head/variable node missing name attribute.");
+							variableNames.Add(xmldoc.GetAttribute("name"));
+							variables.Add(new Variable(xmldoc.GetAttribute("name")));
+							if (!xmldoc.IsEmptyElement) xmldoc.Skip();
+						} else if (xmldoc.NodeType == XmlNodeType.EndElement) {
+							break;
+						}
+					}
+					
+					// Move to the 'results' element, it better be next
+					while (xmldoc.Read())
+						if (xmldoc.NodeType == XmlNodeType.Element) break;
+						
+					if (xmldoc.LocalName != "results")
+						throw new ApplicationException("Invalid server response: Missing results element.");
+					
+					variablesArray = (Variable[])variables.ToArray(typeof(Variable));
+					sink.Init(variablesArray);
+					
+					// Read the results
+					
+					while (xmldoc.Read()) {
+						if (xmldoc.NodeType == XmlNodeType.Element && xmldoc.LocalName == "result") {
+							// Read the bindings in this result
+							Resource[] valuesArray = new Resource[variablesArray.Length];
+							while (xmldoc.Read()) {
+								if (xmldoc.NodeType == XmlNodeType.Element && xmldoc.LocalName == "binding") {
+									if (xmldoc.IsEmptyElement)
+										throw new ApplicationException("Invalid server response: Binding element empty.");
+									if (xmldoc.GetAttribute("name") == null)
+										throw new ApplicationException("Invalid server response: Result binding node missing name attribute.");
+									int vIndex = variableNames.IndexOf(xmldoc.GetAttribute("name"));
+									if (vIndex == -1)
+										throw new ApplicationException("Invalid server response: Result binding name does not match a variable in the head.");
+									
+									Resource value = null;
+									
+									while (xmldoc.Read()) {
+										if (xmldoc.NodeType == XmlNodeType.Whitespace || xmldoc.NodeType == XmlNodeType.SignificantWhitespace) continue;
+										if (xmldoc.NodeType == XmlNodeType.Element && xmldoc.LocalName == "uri") {
+											value = new Entity(xmldoc.ReadElementString());
+											if (!xmldoc.IsEmptyElement) xmldoc.Skip();
+										} else if  (xmldoc.NodeType == XmlNodeType.Element && xmldoc.LocalName == "literal") {
+											string lang = xmldoc.XmlLang;
+											if (lang == "") lang = null;
+											string dt = xmldoc.GetAttribute("datatype");
+											value = new Literal(xmldoc.ReadElementString(), lang, dt);
+											if (!xmldoc.IsEmptyElement) xmldoc.Skip();
+										} else if  (xmldoc.NodeType == XmlNodeType.Element && xmldoc.LocalName == "bnode") {
+											string id = xmldoc.ReadElementString();
+											if (bnodes.ContainsKey(id)) {
+												value = (BNode)bnodes[id];
+											} else {
+												value = new BNode(id);
+												bnodes[id] = value;
+											}
+											if (!xmldoc.IsEmptyElement) xmldoc.Skip();
+										} else {
+											throw new ApplicationException("Invalid server response: Invalid content in binding node.");
+										}
+										break;
+									}
+									if (value == null)
+										throw new ApplicationException("Invalid server response: Result binding value is invalid.");
+									
+									valuesArray[vIndex] = value;
+
+								} else if (xmldoc.NodeType == XmlNodeType.EndElement) {
+									break;
+								}
+							}
+							
+							sink.Add(new VariableBindings(variablesArray, valuesArray));
+							
+						} else if (xmldoc.NodeType == XmlNodeType.EndElement) {
+							break;
+						}
+					}
+
+					sink.Finished();
+				}
+			}
+		}
+		
+		public MetaQueryResult MetaQuery(Statement[] graph, QueryOptions options) {
+			MetaQueryResult ret = new MetaQueryResult();
+			ret.QuerySupported = true;
+			return ret;
+		}
+
+		public void Query(Statement[] graph, QueryOptions options, QueryResultSink sink) {
+			StringBuilder query = new StringBuilder();
+			
+			query.Append("SELECT ");
+
+			// Get a list of variables and map them to fresh names
+			#if !DOTNET2
+			Hashtable variableNames = new Hashtable();
+			#else
+			Dictionary<Variable,string> variableNames = new Dictionary<Variable,string>();
+			#endif
+			Hashtable variableNames2 = new Hashtable();
+			foreach (Statement s in graph) {
+				for (int j = 0; j < 3; j++) {
+					Variable v = s.GetComponent(j) as Variable;
+					if (v == null) continue;
+					if (variableNames.ContainsKey(v)) continue;
+					variableNames2["v" + variableNames.Count] = v;
+					variableNames[v] = "?v" + variableNames.Count;
+				}
+			}
+			
+			// What variables will we select on?
+			ArrayList selectedVars = new ArrayList();
+			foreach (Variable v in
+				options.DistinguishedVariables != null
+					? options.DistinguishedVariables
+					: variableNames.Keys) {
+				if (!variableNames.ContainsKey(v)) continue; // in case distinguished variables list
+															 // has more than what actually appears in query
+				query.Append(variableNames[v]);
+				query.Append(' ');
+				selectedVars.Add(v);
+			}
+			
+			// Bnodes are not allowed here -- we can't query on them.
+			foreach (Statement s in graph) {
+				for (int j = 0; j < 3; j++) {
+					if (s.GetComponent(j) is BNode && !(s.GetComponent(j) is Variable)) {
+						Variable[] varArray = (Variable[])selectedVars.ToArray(typeof(Variable));
+						sink.Init(varArray);
+						sink.Finished();
+						return;
+					}
+				}
+			}
+			
+			// Build the graph pattern.
+			query.Append("WHERE {\n");
+			
+			ResSet firstVarUse = new ResSet();
+			foreach (Statement s in graph) {
+				for (int j = 0; j < 3; j++) {
+					Resource r = s.GetComponent(j);
+					query.Append(S(r, r is Variable && variableNames.ContainsKey((Variable)r) ? (string)variableNames[(Variable)r] : null));
+					query.Append(" ");
+				}
+				query.Append(" . \n");
+				if (options.VariableKnownValues != null) {
+					for (int j = 0; j < 3; j++) {
+						Resource r = s.GetComponent(j);
+						if (firstVarUse.Contains(r)) continue;
+						firstVarUse.Add(r);
+						if (r is Variable && variableNames.ContainsKey((Variable)r) &&
+						#if !DOTNET2
+						options.VariableKnownValues.Contains(r)
+						#else
+						options.VariableKnownValues.ContainsKey((Variable)r)
+						#endif
+						)
+							query.Append(SL(options.VariableKnownValues[(Variable)r], (string)variableNames[(Variable)r], true));
+					}
+				}
+				// And what about meta...?
+			}
+			
+			query.Append("}");
+			
+			if (options.Limit > 0) {
+				query.Append(" LIMIT ");
+				query.Append(options.Limit);
+			}
+			
+			Load(query.ToString(), new QueryResultsWrapper(sink, variableNames2));
+		}
+		
+		class QueryResultsWrapper : QueryResultSink {
+			QueryResultSink sink;
+			Hashtable variableNames;
+			Variable[] vars;
+			
+			public QueryResultsWrapper(QueryResultSink sink, Hashtable variableNames) {
+				this.sink = sink;
+				this.variableNames = variableNames;
+			}
+			
+			public override void Init(Variable[] variables) {
+				vars = new Variable[variables.Length];
+				for (int i = 0; i < variables.Length; i++)
+					vars[i] = (Variable)variableNames[variables[i].LocalName];
+				sink.Init(vars);
+			}
+			
+			public override bool Add(VariableBindings result) {
+				#if !DOTNET2
+				return sink.Add(new VariableBindings(vars, result.Values));
+				#else
+				Resource[] vals = new Resource[result.Values.Count];
+				result.Values.CopyTo(vals, 0);
+				return sink.Add(new VariableBindings(vars, vals));
+				#endif
+			}
+
+			public override void Finished() {
+				sink.Finished();
+			}
+			
+			public override void AddComments(string comments) {
+				sink.AddComments(comments);
+			}
+		}
+	}
+}
+
+namespace SemWeb.Query {
+	public class SparqlXmlQuerySink : QueryResultSink {
+		System.Xml.XmlWriter output;
+		
+		int blankNodeCounter = 0;
+		Hashtable blankNodes = new Hashtable();
+		
+		public const string MimeType = "application/sparql-results+xml";
+		
+		private static System.Xml.XmlWriter GetWriter(System.IO.TextWriter writer) {
+			System.Xml.XmlTextWriter w = new System.Xml.XmlTextWriter(writer);
+			w.Formatting = System.Xml.Formatting.Indented;
+			return w;
+		}
+		
+		public SparqlXmlQuerySink(TextWriter output)
+		 : this(GetWriter(output)) {
+		}
+
+		public SparqlXmlQuerySink(System.Xml.XmlWriter output) {
+			this.output = output;
+		}
+		
+		public override void AddComments(string comments) {
+			if (comments != null && comments.Length > 0)
+				output.WriteComment(comments);
+		}
+		
+		public override void Init(Variable[] variables) {
+			output.WriteStartElement("sparql");
+			output.WriteAttributeString("xmlns", "http://www.w3.org/2005/sparql-results#";);
+			output.WriteStartElement("head");
+			foreach (Variable var in variables) {
+				if (var.LocalName == null) continue;
+				output.WriteStartElement("variable");
+				output.WriteAttributeString("name", var.LocalName);
+				output.WriteEndElement();
+			}
+			output.WriteEndElement(); // head
+			output.WriteStartElement("results");
+			
+			// instead of <results>, we might want <boolean>true</boolean>
+		}
+		
+		public override bool Add(VariableBindings result) {
+			output.WriteStartElement("result");
+			for (int i = 0; i < result.Count; i++) {
+				Variable var = result.Variables[i];
+				Resource val = result.Values[i];
+				
+				if (var.LocalName == null) continue;
+				if (val == null) continue;
+				
+				output.WriteStartElement("binding");
+				output.WriteAttributeString("name", var.LocalName);
+				
+				if (val.Uri != null) {
+					output.WriteElementString("uri", val.Uri);
+				} else if (val is Literal) {
+					output.WriteStartElement("literal");
+					Literal literal = (Literal)val;
+					if (literal.DataType != null)
+						output.WriteAttributeString("datatype", literal.DataType);
+					if (literal.Language != null)
+						output.WriteAttributeString("xml", "lang", null, literal.Language);
+					output.WriteString(literal.Value);
+					output.WriteEndElement();				
+				} else {
+					string id;
+					if (blankNodes.ContainsKey(val))
+						id = (string)blankNodes[val];
+					else {
+						id = "r" + (++blankNodeCounter);
+						blankNodes[val] = id;
+					}
+					output.WriteStartElement("bnode");
+					output.WriteString(id);
+					output.WriteEndElement();
+				}
+				
+				output.WriteEndElement();
+			}
+			output.WriteEndElement();
+			
+			return true;
+		}
+		
+		public override void Finished() {
+			output.WriteEndElement(); // results
+			output.WriteEndElement(); // sparql
+			output.Flush();
+		}
+	}
+
+}	

Modified: branches/beagle-rdf/Util/SemWeb/Statement.cs
==============================================================================
--- branches/beagle-rdf/Util/SemWeb/Statement.cs	(original)
+++ branches/beagle-rdf/Util/SemWeb/Statement.cs	Sat Jan 26 23:26:03 2008
@@ -4,7 +4,14 @@
 using SemWeb.Util;
 
 namespace SemWeb {
-	public struct Statement : IComparable {
+	public struct Statement :
+#if DOTNET2
+	IEquatable<Statement>, IComparable<Statement>
+#else
+	IComparable
+#endif
+	{
+	
 		public Entity Subject;
 		public Entity Predicate;
 		public Resource Object;
@@ -78,10 +85,17 @@
 				);
 		}
 
+
 		public override bool Equals(object other) {
 			return (Statement)other == this;
 		}
 		
+#if DOTNET2
+		bool IEquatable<Statement>.Equals(Statement other) {
+			return other == this;
+		}
+#endif
+
 		public override int GetHashCode() {
 			int ret = 0;
 			if (Subject != null) ret = unchecked(ret + Subject.GetHashCode());
@@ -106,8 +120,13 @@
 			return !(a == b);
 		}
 
-		int IComparable.CompareTo(object obj) {
-			Statement s = (Statement)obj;
+#if !DOTNET2
+		int IComparable.CompareTo(object other) {
+			return CompareTo((Statement)other);
+		}
+#endif
+
+		public int CompareTo(Statement s) {
 			int x;
 			x = cmp(Subject, s.Subject); if (x != 0) return x;
 			x = cmp(Predicate, s.Predicate); if (x != 0) return x;
@@ -131,6 +150,15 @@
 			}
 			throw new ArgumentException("index");
 		}
+		internal void SetComponent(int index, Resource r) {
+			switch (index) {
+				case 0: Subject = (Entity)r; break;
+				case 1: Predicate = (Entity)r; break;
+				case 2: Object = r; break;
+				case 3: Meta = (Entity)r; break;
+				default: throw new ArgumentException("index");
+			}
+		}
 	}
 	
 	public struct SelectFilter : IEnumerable {
@@ -159,6 +187,26 @@
 			Metas = metas;
 		}
 		
+		internal Resource[] GetComponent(int index) {
+			switch (index) {
+				case 0: return Subjects;
+				case 1: return Predicates;
+				case 2: return Objects;
+				case 3: return Metas;
+			}
+			throw new ArgumentException("index");
+		}
+		
+		internal void SetComponent(int index, Resource[] res) {
+			switch (index) {
+				case 0: Subjects = (Entity[])res; break;
+				case 1: Predicates = (Entity[])res; break;
+				case 2: Objects = res; break;
+				case 3: Metas = (Entity[])res; break;
+				default: throw new ArgumentException("index");
+			}
+		}
+
 		public override string ToString() {
 			string ret =
 				ToString(Subjects) + " " +
@@ -204,13 +252,13 @@
 				&& eq(a.Predicates, b.Predicates)
 				&& eq(a.Objects, b.Objects)
 				&& eq(a.Metas, b.Metas)
-				&& eq(a.LiteralFilters, b.LiteralFilters)
+				&& a.LiteralFilters == b.LiteralFilters
 				&& a.Limit == b.Limit;
 		}
 		public static bool operator !=(SelectFilter a, SelectFilter b) {
 			return !(a == b);
 		}
-		static bool eq(object[] a, object[] b) {
+		static bool eq(Resource[] a, Resource[] b) {
 			if (a == b) return true;
 			if (a == null || b == null) return false;
 			if (a.Length != b.Length) return false;

Modified: branches/beagle-rdf/Util/SemWeb/Store.cs
==============================================================================
--- branches/beagle-rdf/Util/SemWeb/Store.cs	(original)
+++ branches/beagle-rdf/Util/SemWeb/Store.cs	Sat Jan 26 23:26:03 2008
@@ -1,73 +1,68 @@
 using System;
+#if !DOTNET2
 using System.Collections;
+#else
+using System.Collections.Generic;
+#endif
 using System.Data;
 
+using SemWeb.Inference;
 using SemWeb.Util;
 
-namespace SemWeb {
-	
-	public interface StatementSource {
-		bool Distinct { get; }
-		void Select(StatementSink sink);
-	}
-	
-	public interface SelectableSource : StatementSource {
-		bool Contains(Statement template);
-		void Select(Statement template, StatementSink sink);
-		void Select(SelectFilter filter, StatementSink sink);
-	}
+#if !DOTNET2
+using SourceList = System.Collections.ArrayList;
+using NamedSourceMap = System.Collections.Hashtable;
+using ReasonerList = System.Collections.ArrayList;
+#else
+using SourceList = System.Collections.Generic.List<SemWeb.SelectableSource>;
+using NamedSourceMap = System.Collections.Generic.Dictionary<string, SemWeb.SelectableSource>;
+using ReasonerList = System.Collections.Generic.List<SemWeb.Inference.Reasoner>;
+#endif
 
-	public interface QueryableSource : SelectableSource {
-		void Query(Statement[] graph, SemWeb.Query.QueryResultSink sink);
-		void Query(SelectFilter[] graph, SemWeb.Query.QueryResultSink sink);
-	}
-	
-	public interface StatementSink {
-		bool Add(Statement statement);
-	}
-
-	public interface ModifiableSource : StatementSink {
-		void Clear();
-		void Import(StatementSource source);
-		void Remove(Statement template);
-		void RemoveAll(Statement[] templates);
-		void Replace(Entity find, Entity replacement);
-		void Replace(Statement find, Statement replacement);
-	}
+namespace SemWeb {
 	
-	internal class StatementCounterSink : StatementSink {
-		int counter = 0;
-		
-		public int StatementCount { get { return counter; } }
-		
-		public bool Add(Statement statement) {
-			counter++;
-			return true;
-		}
-	}
-
-	internal class StatementExistsSink : StatementSink {
-		bool exists = false;
+	public class Store : StatementSource, StatementSink,
+		SelectableSource, QueryableSource, StaticSource, ModifiableSource,
+		IDisposable {
 		
-		public bool Exists { get { return exists; } }
+		// Static helper methods for creating data sources and sinks
+		// from spec strings.
 		
-		public bool Add(Statement statement) {
-			exists = true;
-			return false;
+		public static Store Create(string spec) {
+			Store ret = new Store();
+			
+			bool rdfs = false, euler = false;
+			
+			if (spec.StartsWith("rdfs+")) {
+				rdfs = true;
+				spec = spec.Substring(5);
+			}
+			if (spec.StartsWith("euler+")) {
+				euler = true;
+				spec = spec.Substring(6);
+			}
+				
+			foreach (string spec2 in spec.Split('\n', '|')) {
+				StatementSource s = CreateForInput(spec2.Trim());
+				if (s is SelectableSource)
+					ret.AddSource((SelectableSource)s);
+				else
+					ret.AddSource(new MemoryStore(s));
+			}
+			
+			if (rdfs)
+				ret.AddReasoner(new RDFS(ret));
+			if (euler)
+				throw new Exception ("Euler spec is not shipped with beagle");
+			
+			return ret;
 		}
-	}
-
-	public abstract class Store : StatementSource, StatementSink,
-		SelectableSource, ModifiableSource,
-		IDisposable {
-		
-		Entity rdfType;
 		
 		public static StatementSource CreateForInput(string spec) {
-			if (spec.StartsWith("rdfs+")) {
-				StatementSource s = CreateForInput(spec.Substring(5));
+			if (spec.StartsWith("debug+")) {
+				StatementSource s = CreateForInput(spec.Substring(6));
 				if (!(s is SelectableSource)) s = new MemoryStore(s);
-				return new SemWeb.Inference.RDFS(s, (SelectableSource)s);
+				return new SemWeb.Stores.DebuggedSource((SelectableSource)s, System.Console.Error);
 			}
 			return (StatementSource)Create(spec, false);
 		}		
@@ -77,21 +72,6 @@
 		}
 		
 		private static object Create(string spec, bool output) {
-			string[] multispecs = spec.Split('\n', '|');
-			if (multispecs.Length > 1) {
-				SemWeb.Stores.MultiStore multistore = new SemWeb.Stores.MultiStore();
-				foreach (string mspec in multispecs) {
-					object mstore = Create(mspec.Trim(), output);
-					if (mstore is SelectableSource) {
-						multistore.Add((SelectableSource)mstore);
-					} else if (mstore is StatementSource) {
-						MemoryStore m = new MemoryStore((StatementSource)mstore);
-						multistore.Add(m);
-					}
-				}
-				return multistore;
-			}
-		
 			string type = spec;
 			
 			int c = spec.IndexOf(':');
@@ -110,7 +90,11 @@
 				case "xml":
 					if (spec == "") throw new ArgumentException("Use: xml:filename");
 					if (output) {
-						return new RdfXmlWriter(spec);
+						#if !SILVERLIGHT
+							return new RdfXmlWriter(spec);
+						#else
+							throw new NotSupportedException("RDF/XML output is not supported in the Silverlight build of the SemWeb library.");
+						#endif
 					} else {
 						return new RdfXmlReader(spec);
 					}
@@ -130,10 +114,16 @@
 					} else {
 						return new N3Reader(spec);
 					}
+				
+				case "null":
+					if (!output) throw new ArgumentException("The null sink does not support reading.");
+					return new StatementCounterSink();
+				
 				/*case "file":
 					if (spec == "") throw new ArgumentException("Use: format:filename");
 					if (output) throw new ArgumentException("The FileStore does not support writing.");
 					return new SemWeb.Stores.FileStore(spec);*/
+
 				case "sqlite":
 				case "mysql":
 				case "postgresql":
@@ -171,62 +161,153 @@
 			}
 		}
 		
-		protected Store() {
-			rdfType = new Entity("http://www.w3.org/1999/02/22-rdf-syntax-ns#type";);
-		}
+		// START OF ACTUAL STORE IMPLEMENTATION
+		
+		readonly Entity rdfType = new Entity("http://www.w3.org/1999/02/22-rdf-syntax-ns#type";);
+
+		SourceList unnamedgraphs = new SourceList(); // a list of SelectableSources that aren't associated with graph URIs
+		NamedSourceMap namedgraphs = new NamedSourceMap(); // a mapping from graph URIs to a selectable source that represents that graph
+		
+		SourceList allsources = new SourceList(); // a list of the sources in unnamed graphs and namedgraphs
 		
-		void IDisposable.Dispose() {
-			Close();
+		ReasonerList reasoners = new ReasonerList(); // a list of reasoning engines applied to this data model, which are run in order
+		
+		// GENERAL METHODS
+		
+		public Store() {
 		}
 		
-		public virtual void Close() {
+		public Store(StatementSource source) {
+			AddSource(new MemoryStore.StoreImpl(source));
 		}
 		
-		public abstract bool Distinct { get; }
+		public Store(SelectableSource source) {
+			AddSource(source);
+		}
 		
-		public abstract int StatementCount { get; }
-
-		public abstract void Clear();
-
-		public Entity[] GetEntitiesOfType(Entity type) {
-			ArrayList entities = new ArrayList();
-			
-			IEnumerable result = Select(new Statement(null, rdfType, type));
-			foreach (Statement s in result) {
-				entities.Add(s.Subject);
+		#if !DOTNET2
+		public IList DataSources {
+			get {
+				return ArrayList.ReadOnly(allsources);
 			}
-			
-			return (Entity[])entities.ToArray(typeof(Entity));
 		}
-		
-		bool StatementSink.Add(Statement statement) {
-			Add(statement);
-			return true;
+		#else
+		public IList<SelectableSource> DataSources {
+			get {
+				return new List<SelectableSource>(allsources);
+			}
+		}
+		#endif
+				
+		public virtual void AddSource(SelectableSource source) {
+			if (source is MemoryStore) source = ((MemoryStore)source).impl;
+			unnamedgraphs.Add(source);
+			allsources.Add(source);
+		}
+		
+		public virtual void AddSource(SelectableSource source, string uri) {
+			if (namedgraphs.ContainsKey(uri))
+				throw new ArgumentException("URI has already been associated with a data source.");
+			if (source is MemoryStore) source = ((MemoryStore)source).impl;
+			namedgraphs[uri] = source;
+			allsources.Add(source);
+		}
+		
+		internal void AddSource2(SelectableSource store) {
+			// Used by MemoryStore only!
+			unnamedgraphs.Add(store);
+			allsources.Add(store);
 		}
 		
-		public abstract void Add(Statement statement);
+		public virtual void AddReasoner(Reasoner reasoner) {
+			reasoners.Add(reasoner);
+		}
 		
-		public abstract void Remove(Statement statement);
-
-		public virtual void Import(StatementSource source) {
-			source.Select(this);
+		public void Write(System.IO.TextWriter writer) {
+			using (RdfWriter w = new N3Writer(writer)) {
+				Select(w);
+			}
 		}
+
+		// INTERFACE IMPLEMENTATIONS and related methods
 		
-		public void RemoveAll(Statement[] templates) {
-			foreach (Statement t in templates)
-				Remove(t);
+		// IDisposable
+		
+		public void Dispose() {
+			foreach (SelectableSource s in allsources)
+				if (s is IDisposable)
+					((IDisposable)s).Dispose();
+			foreach (Reasoner r in reasoners)
+				if (r is IDisposable)
+					((IDisposable)r).Dispose();
 		}
 		
-		public abstract Entity[] GetEntities();
+		// StatementSource
 		
-		public abstract Entity[] GetPredicates();
+		public bool Distinct {
+			get {
+				if (allsources.Count > 1) return false;
+				foreach (Reasoner r in reasoners)
+					if (!r.Distinct)
+						return false;
+				if (allsources.Count == 0) return true;
+				return ((SelectableSource)allsources[0]).Distinct;
+			}
+		}
 		
-		public abstract Entity[] GetMetas();
+		public void Select(StatementSink result) {
+			Select(Statement.All, result);
+		}
+
+		// SelectableSource
 		
-		public virtual bool Contains(Statement template) {
-			return DefaultContains(this, template);
+		private SelectableSource[] GetSources(ref Entity graph) {
+			if (graph == null || namedgraphs.Count == 0)
+				#if !DOTNET2
+					return (SelectableSource[])allsources.ToArray(typeof(SelectableSource));
+				#else
+					return allsources.ToArray();
+				#endif
+			else if (graph == Statement.DefaultMeta)
+				#if !DOTNET2
+					return (SelectableSource[])unnamedgraphs.ToArray(typeof(SelectableSource));
+				#else
+					return unnamedgraphs.ToArray();
+				#endif
+			else if (graph.Uri != null && namedgraphs.ContainsKey(graph.Uri)) {
+				graph = Statement.DefaultMeta;
+				return new SelectableSource[] { (SelectableSource)namedgraphs[graph.Uri] };
+			} else
+				return null;
 		}
 
+		public bool Contains(Resource resource) {
+			foreach (SelectableSource s in allsources)
+				if (s.Contains(resource))
+					return true;
+			return false;
+			/*return (resource is Entity && Contains(new Statement((Entity)resource, null, null, null)))
+				|| (resource is Entity && Contains(new Statement(null, (Entity)resource, null, null)))
+				|| (                      Contains(new Statement(null, null, resource, null)))
+				|| (resource is Entity && Contains(new Statement(null, null, null, (Entity)resource)));*/
+		}
+		
+		public bool Contains(Statement template) {
+			// If reasoning is applied, use DefaultContains so that
+			// we use a Select call which will delegate the query
+			// to the reasoner.
+			ReasoningHelper rh = GetReasoningHelper(null);
+			if (rh != null)
+				return DefaultContains(this, template);
+			
+			SelectableSource[] sources = GetSources(ref template.Meta);
+			if (sources == null) return false;
+			foreach (SelectableSource s in sources)
+				if (s.Contains(template))
+					return true;
+			return false;
+		}
+			
 		public static bool DefaultContains(SelectableSource source, Statement template) {
 			StatementExistsSink sink = new StatementExistsSink();
 			SelectFilter filter = new SelectFilter(template);
@@ -235,8 +316,78 @@
 			return sink.Exists;
 		}
 		
+		private class ReasoningHelper {
+			public Reasoner reasoner;
+			public Store nextStore;
+		}
+		
+		private ReasoningHelper GetReasoningHelper(SelectableSource[] sources) {
+			if (reasoners.Count == 0)
+				return null;
+		
+			ReasoningHelper ret = new ReasoningHelper();
+			
+			ret.reasoner = (Reasoner)reasoners[reasoners.Count-1];
+			
+			ret.nextStore = new Store();
+			if (sources == null) {
+				ret.nextStore.unnamedgraphs = unnamedgraphs; // careful...
+				ret.nextStore.namedgraphs = namedgraphs;
+				ret.nextStore.allsources = allsources;
+			} else {
+				ret.nextStore.unnamedgraphs.AddRange(sources);
+				ret.nextStore.allsources.AddRange(sources);
+			}
+			for (int i = 0; i < reasoners.Count-1; i++)
+				ret.nextStore.reasoners.Add(reasoners[i]);
+			return ret;
+		}
+		
+		public void Select(Statement template, StatementSink result) {
+			// If reasoning is applied, delegate this call to the last reasoner
+			// and pass it a clone of this store but with itself removed.
+			ReasoningHelper rh = GetReasoningHelper(null);
+			if (rh != null) {
+				rh.reasoner.Select(template, rh.nextStore, result);
+				return;
+			}
+		
+			SelectableSource[] sources = GetSources(ref template.Meta);
+			if (sources == null) return;
+			foreach (SelectableSource s in sources)
+				s.Select(template, result);
+		}
+		
+		public void Select(SelectFilter filter, StatementSink result) {
+			Entity[] scanMetas = filter.Metas;
+			if (scanMetas == null || namedgraphs.Count == 0) scanMetas = new Entity[] { null };
+			foreach (Entity meta in scanMetas) {
+				Entity meta2 = meta;
+				SelectableSource[] sources = GetSources(ref meta2);
+				if (sources == null) continue;
+				
+				if (meta2 == null)
+					filter.Metas = null;
+				else
+					filter.Metas = new Entity[] { meta2 };
+
+				// If reasoning is applied, delegate this call to the last reasoner
+				// and pass it either:
+				// 		a clone of this store but with itself removed, if the meta we are processing now is null, or
+				//		that, but only with the sources that apply to this meta
+				ReasoningHelper rh = GetReasoningHelper(sources);
+				if (rh != null) {
+					rh.reasoner.Select(filter, rh.nextStore, result);
+					continue;
+				}
+				
+				foreach (SelectableSource s in sources)
+					s.Select(filter, result);
+			}
+		}
+		
 		public static void DefaultSelect(SelectableSource source, SelectFilter filter, StatementSink sink) {
-			// This method should be avoided...
+			// This method should really be avoided...
 			if (filter.LiteralFilters != null)
 				sink = new SemWeb.Filters.FilterSink(filter.LiteralFilters, sink, source);
 			foreach (Entity subject in filter.Subjects == null ? new Entity[] { null } : filter.Subjects)
@@ -246,14 +397,6 @@
 				source.Select(new Statement(subject, predicate, objct, meta), sink);
 		}		
 		
-		public void Select(StatementSink result) {
-			Select(Statement.All, result);
-		}
-		
-		public abstract void Select(Statement template, StatementSink result);
-		
-		public abstract void Select(SelectFilter filter, StatementSink result);
-		
 		public SelectResult Select(Statement template) {
 			return new SelectResult.Single(this, template);
 		}
@@ -263,266 +406,504 @@
 		}
 		
 		public Resource[] SelectObjects(Entity subject, Entity predicate) {
-			Hashtable resources = new Hashtable();
-			ResourceCollector collector = new ResourceCollector();
-			collector.SPO = 2;
-			collector.Table = resources;
-			Select(new Statement(subject, predicate, null, null), collector);
-			return (Resource[])new ArrayList(resources.Keys).ToArray(typeof(Resource));
+			if (predicate.Uri != null && predicate.Uri == NS.RDFS + "member") {
+				ResourceCollector2 collector = new ResourceCollector2();
+				Select(new Statement(subject, predicate, null, null), collector);
+				return collector.GetItems();
+			} else {
+				ResSet resources = new ResSet();
+				ResourceCollector collector = new ResourceCollector();
+				collector.SPO = 2;
+				collector.Table = resources;
+				Select(new Statement(subject, predicate, null, null), collector);
+				return resources.ToArray();
+			}
 		}
 		public Entity[] SelectSubjects(Entity predicate, Resource @object) {
-			Hashtable resources = new Hashtable();
+			ResSet resources = new ResSet();
 			ResourceCollector collector = new ResourceCollector();
 			collector.SPO = 0;
 			collector.Table = resources;
 			Select(new Statement(null, predicate, @object, null), collector);
-			return (Entity[])new ArrayList(resources.Keys).ToArray(typeof(Entity));
+			return resources.ToEntityArray();
 		}
 		class ResourceCollector : StatementSink {
-			public Hashtable Table;
+			public ResSet Table;
 			public int SPO;
 			public bool Add(Statement s) {
-				if (SPO == 0) Table[s.Subject] = Table;
-				if (SPO == 2) Table[s.Object] = Table;
+				if (SPO == 0) Table.Add(s.Subject);
+				if (SPO == 2) Table.Add(s.Object);
 				return true;
 			}
 		}
-		
-		public virtual void Replace(Entity find, Entity replacement) {
-			MemoryStore deletions = new MemoryStore();
-			MemoryStore additions = new MemoryStore();
-			
-			Select(new Statement(find, null, null, null), deletions);
-			Select(new Statement(null, find, null, null), deletions);
-			Select(new Statement(null, null, find, null), deletions);
-			Select(new Statement(null, null, null, find), deletions);
-			
-			foreach (Statement s in deletions) {
-				Remove(s);
-				additions.Add(s.Replace(find, replacement));
+		class ResourceCollector2 : StatementSink {
+			System.Collections.ArrayList items = new System.Collections.ArrayList();
+			ResSet other = new ResSet();
+			public bool Add(Statement s) {
+				if (s.Predicate.Uri == null || !s.Predicate.Uri.StartsWith(NS.RDF + "_")) {
+					other.Add(s.Object);
+				} else {
+					string num = s.Predicate.Uri.Substring(NS.RDF.Length+1);
+					try {
+						int idx = int.Parse(num);
+						items.Add(new Item(s.Object, idx));
+					} catch {
+						other.Add(s.Object);
+					}
+				}
+				return true;
 			}
-			
-			foreach (Statement s in additions) {
-				Add(s);
+			public Resource[] GetItems() {
+				items.Sort();
+				Resource[] ret = new Resource[items.Count + other.Count];
+				int ctr = 0;
+				foreach (Item item in items)
+					ret[ctr++] = item.r;
+				foreach (Resource item in other)
+					ret[ctr++] = item;
+				return ret;
 			}
-		}
-		
-		public virtual void Replace(Statement find, Statement replacement) {
-			Remove(find);
-			Add(replacement);
-		}
-				public void Write(System.IO.TextWriter writer) {
-			using (RdfWriter w = new N3Writer(writer)) {
-				Select(w);
+			class Item : IComparable {
+				public Resource r;
+				int idx;
+				public Item(Resource r, int idx) { this.r = r; this.idx = idx; }
+				public int CompareTo(object other) {
+					return idx.CompareTo(((Item)other).idx);
+				}
 			}
 		}
 		
-		protected object GetResourceKey(Resource resource) {
-			return resource.GetResourceKey(this);
+		// QueryableSource
+		
+		public SemWeb.Query.MetaQueryResult MetaQuery(Statement[] graph, SemWeb.Query.QueryOptions options) {
+			// If reasoning is applied, delegate this call to the last reasoner
+			// and pass it a clone of this store but with itself removed.
+			ReasoningHelper rh = GetReasoningHelper(null);
+			if (rh != null)
+				return rh.reasoner.MetaQuery(graph, options, rh.nextStore);
+			
+			// Special case for one wrapped data source that supports QueryableSource:
+			if (allsources.Count == 1 && allsources[0] is QueryableSource)
+				return ((QueryableSource)allsources[0]).MetaQuery(graph, options);
+		
+			return new SemWeb.Inference.SimpleEntailment().MetaQuery(graph, options, this);
 		}
 
-		protected void SetResourceKey(Resource resource, object value) {
-			resource.SetResourceKey(this, value);
-		}
-		
-	}
+		public void Query(Statement[] graph, SemWeb.Query.QueryOptions options, SemWeb.Query.QueryResultSink sink) {
+			// If reasoning is applied, delegate this call to the last reasoner
+			// and pass it a clone of this store but with itself removed.
+			ReasoningHelper rh = GetReasoningHelper(null);
+			if (rh != null) {
+				rh.reasoner.Query(graph, options, rh.nextStore, sink);
+				return;
+			}
 
-	public abstract class SelectResult : StatementSource, IEnumerable {
-		internal Store source;
-		MemoryStore ms;
-		internal SelectResult(Store source) { this.source = source; }
-		public bool Distinct { get { return source.Distinct; } }
-		public abstract void Select(StatementSink sink);
-		public IEnumerator GetEnumerator() {
-			return Buffer().Statements.GetEnumerator();
-		}
-		public long StatementCount { get { return Buffer().StatementCount; } }
-		public MemoryStore Load() { return Buffer(); }
-		public Statement[] ToArray() { return Load().ToArray(); }
-		private MemoryStore Buffer() {
-			if (ms != null) return ms;
-			ms = new MemoryStore();
-			ms.allowIndexing = false;
-			Select(ms);
-			return ms;
-		}
-		
-		internal class Single : SelectResult {
-			Statement template;
-			public Single(Store source, Statement template) : base(source) {
-				this.template = template;
+			// Special case for one wrapped data source that supports QueryableSource:
+			if (allsources.Count == 1 && allsources[0] is QueryableSource) {
+				((QueryableSource)allsources[0]).Query(graph, options, sink);
+				return;
 			}
-			public override void Select(StatementSink sink) {
-				source.Select(template, sink);
+
+			// Chunk the query graph as best we can.
+			SemWeb.Query.GraphMatch.QueryPart[] chunks = ChunkQuery(graph, options, sink);
+			
+			// If we couldn't chunk the graph, then just use the default GraphMatch implementation.
+			if (chunks == null) {
+				new SemWeb.Inference.SimpleEntailment().Query(graph, options, this, sink);
+				return;
 			}
+			
+			SemWeb.Query.GraphMatch.RunGeneralQuery(chunks, options.VariableKnownValues, options.VariableLiteralFilters, options.DistinguishedVariables,
+				0, options.Limit, true, sink);
 		}
 		
-		internal class Multi : SelectResult {
-			SelectFilter filter;
-			public Multi(Store source, SelectFilter filter)
-				: base(source) {
-				this.filter = filter;
+		private SemWeb.Query.GraphMatch.QueryPart[] ChunkQuery(Statement[] query, SemWeb.Query.QueryOptions options, SemWeb.Query.QueryResultSink sink) {
+			// MetaQuery the data sources to get their capabilities.
+			SemWeb.Query.MetaQueryResult[] mq = new SemWeb.Query.MetaQueryResult[allsources.Count];
+			for (int i = 0; i < allsources.Count; i++) {
+				if (!(allsources[i] is QueryableSource))
+					return null;
+				mq[i] = ((QueryableSource)allsources[i]).MetaQuery(query, options);
+				if (!mq[i].QuerySupported)
+					return null;
 			}
-			public override void Select(StatementSink sink) {
-				source.Select(filter, sink);
+		
+			System.Collections.ArrayList chunks = new System.Collections.ArrayList();
+			
+			int curSource = -1;
+			System.Collections.ArrayList curStatements = new System.Collections.ArrayList();
+			
+			for (int j = 0; j < query.Length; j++) {
+				if (curSource != -1) {
+					// If we have a curSource and it definitively answers this
+					// statement in the graph, include this statement in the
+					// current chunk.
+					if (mq[curSource].IsDefinitive != null && mq[curSource].IsDefinitive[j]) {
+						sink.AddComments(allsources[curSource] + " answers definitively: " + query[j]);
+						curStatements.Add(query[j]);
+						continue;
+					}
+					
+					// If we have a curSource and no other source answers this
+					// statement, also include this statement in the current chunk.
+					bool foundOther = false;
+					for (int i = 0; i < mq.Length; i++) {
+						if (i == curSource) continue;
+						if (mq[i].NoData != null && mq[i].NoData[j]) continue;
+						foundOther = true;
+						break;
+					}
+					if (!foundOther) {
+						curStatements.Add(query[j]);
+						continue;
+					}
+					
+					// Some other source could possibly answer this statement,
+					// so we complete the chunk we started.
+					SemWeb.Query.GraphMatch.QueryPart c = new SemWeb.Query.GraphMatch.QueryPart(
+						(Statement[])curStatements.ToArray(typeof(Statement)),
+						(QueryableSource)allsources[curSource]
+						);
+					chunks.Add(c);
+					
+					curSource = -1;
+					curStatements.Clear();
+				}
+			
+				// Find a definitive source for this statement
+				for (int i = 0; i < mq.Length; i++) {
+					if (mq[i].IsDefinitive != null && mq[i].IsDefinitive[j]) {
+						curSource = i;
+						curStatements.Add(query[j]);
+						sink.AddComments(allsources[i] + " answers definitively: " + query[j]);
+						break;
+					}
+				}
+				if (curSource != -1) // found a definitive source
+					continue;
+					
+				// See if only one source can answer this statement.
+				// Also build a list of sources that can answer the
+				// statement, so don't break out of this loop early.
+				System.Collections.ArrayList answerables = new System.Collections.ArrayList();
+				int findSource = -1;
+				for (int i = 0; i < mq.Length; i++) {
+					if (mq[i].NoData != null && mq[i].NoData[j]) continue;
+					answerables.Add(allsources[i]);
+					if (findSource == -1)
+						findSource = i;
+					else
+						findSource = -2; // found a second source that can answer this
+				}
+				if (findSource >= 0) {
+					curSource = findSource;
+					curStatements.Add(query[j]);
+					continue;
+				}
+				if (answerables.Count == 0) {
+					sink.AddComments("No data source could answer: " + query[j]);
+					return null;
+				}
+				
+				// More than one source can answer this, so make a one-statement chunk.
+				SemWeb.Query.GraphMatch.QueryPart cc = new SemWeb.Query.GraphMatch.QueryPart(
+					query[j],
+					(QueryableSource[])answerables.ToArray(typeof(QueryableSource))
+					);
+				chunks.Add(cc);
 			}
-		}
-	}
-}
 
-namespace SemWeb.Stores {
-
-	public interface SupportsPersistableBNodes {
-		string GetStoreGuid();
-		string GetNodeId(BNode node);
-		BNode GetNodeFromId(string persistentId);
-	}
-
-	public class MultiStore : Store {
-		ArrayList stores = new ArrayList();
-		Hashtable namedgraphs = new Hashtable();
-		ArrayList allsources = new ArrayList();
-		
-		public MultiStore() { }
-		
-		public override bool Distinct { get { return false; } }
-		
-		public void Add(SelectableSource store) {
-			stores.Add(store);
-			allsources.Add(store);
-		}
-		
-		public void Add(string uri, SelectableSource store) {
-			namedgraphs[uri] = store;
-			allsources.Add(store);
-		}
-		
-		public void Add(RdfReader source) {
-			MemoryStore store = new MemoryStore(source);
-			Add(store);
-		}
-		
-		public void Add(string uri, RdfReader source) {
-			MemoryStore store = new MemoryStore(source);
-			Add(uri, store);
+			if (curSource != -1) {
+				SemWeb.Query.GraphMatch.QueryPart c = new SemWeb.Query.GraphMatch.QueryPart(
+					(Statement[])curStatements.ToArray(typeof(Statement)),
+					(QueryableSource)allsources[curSource]
+					);
+				chunks.Add(c);
+			}
+			
+			return (SemWeb.Query.GraphMatch.QueryPart[])chunks.ToArray(typeof(SemWeb.Query.GraphMatch.QueryPart));
 		}
-		
-		public void Remove(SelectableSource store) {
-			stores.Remove(store);
-			allsources.Remove(store);
+
+		public
+		#if !DOTNET2
+		ICollection
+		#else
+		ICollection<SemWeb.Query.VariableBindings>
+		#endif
+		Query(Statement[] graph) {
+			SemWeb.Query.QueryOptions options = new SemWeb.Query.QueryOptions();
+			options.Limit = 1;
+			SemWeb.Query.QueryResultBuffer sink = new SemWeb.Query.QueryResultBuffer();
+			Query(graph, options, sink);
+			return sink.Bindings;
 		}
 		
-		public void Remove(string uri) {
-			allsources.Remove(namedgraphs[uri]);
-			namedgraphs.Remove(uri);
-		}
+		// StaticSource
 		
-		public override int StatementCount {
+		public int StatementCount {
 			get {
 				int ret = 0;
 				foreach (StatementSource s in allsources) {
-					if (s is Store)
-						ret += ((Store)s).StatementCount;
+					if (s is StaticSource)
+						ret += ((StaticSource)s).StatementCount;
 					else
-						throw new InvalidOperationException("Not all sources are stores supporting StatementCount.");
+						throw new InvalidOperationException("Not all data sources are support StatementCount.");
 				}
 				return ret;
 			}
 		}
-
-		public override void Clear() {
-			throw new InvalidOperationException("Clear is not a valid operation on a MultiStore.");
-		}
 		
-		public override Entity[] GetEntities() {
-			Hashtable h = new Hashtable();
+		public Entity[] GetEntities() {
+			ResSet h = new ResSet();
 			foreach (StatementSource s in allsources) {
-				if (s is Store) {
-					foreach (Resource r in ((Store)s).GetEntities())
-						h[r] = h;
+				if (s is StaticSource) {
+					foreach (Resource r in ((StaticSource)s).GetEntities())
+						h.Add(r);
 				} else {
-					throw new InvalidOperationException("Not all sources are stores supporting GetEntities.");
+					throw new InvalidOperationException("Not all data sources support GetEntities.");
 				}
 			}
-			return (Entity[])new ArrayList(h.Keys).ToArray(typeof(Entity));
+			return h.ToEntityArray();
 		}
 		
-		public override Entity[] GetPredicates() {
-			Hashtable h = new Hashtable();
+		public Entity[] GetPredicates() {
+			ResSet h = new ResSet();
 			foreach (StatementSource s in allsources) {
-				if (s is Store) {
-					foreach (Resource r in ((Store)s).GetPredicates())
-						h[r] = h;
+				if (s is StaticSource) {
+					foreach (Resource r in ((StaticSource)s).GetPredicates())
+						h.Add(r);
 				} else {
-					throw new InvalidOperationException("Not all sources are stores supporting GetEntities.");
+					throw new InvalidOperationException("Not data sources support GetPredicates.");
 				}
 			}
-			return (Entity[])new ArrayList(h.Keys).ToArray(typeof(Entity));
+			return h.ToEntityArray();
 		}
 
-		public override Entity[] GetMetas() {
-			Hashtable h = new Hashtable();
+		public Entity[] GetMetas() {
+			ResSet h = new ResSet();
 			foreach (StatementSource s in allsources) {
-				if (s is Store) {
-					foreach (Resource r in ((Store)s).GetMetas())
-						h[r] = h;
+				if (s is StaticSource) {
+					foreach (Resource r in ((StaticSource)s).GetMetas())
+						h.Add(r);
 				} else {
-					throw new InvalidOperationException("Not all sources are stores supporting GetEntities.");
+					throw new InvalidOperationException("Not all data sources support GetMetas.");
 				}
 			}
-			return (Entity[])new ArrayList(h.Keys).ToArray(typeof(Entity));
+			return h.ToEntityArray();
 		}
 
-		public override void Add(Statement statement) { throw new InvalidOperationException("Add is not a valid operation on a MultiStore."); }
+		public Entity[] GetEntitiesOfType(Entity type) {
+			return SelectSubjects(rdfType, type);
+		}
 		
-		SelectableSource[] GetSources(Entity graph) {
-			if (graph == null || namedgraphs.Count == 0)
-				return (SelectableSource[])allsources.ToArray(typeof(SelectableSource));
-			else if (graph == Statement.DefaultMeta)
-				return (SelectableSource[])stores.ToArray(typeof(SelectableSource));
-			else if (graph.Uri != null && namedgraphs.ContainsKey(graph.Uri))
-				return new SelectableSource[] { (SelectableSource)namedgraphs[graph.Uri] };
-			else
-				return null;
+		public string GetPersistentBNodeId(BNode node) {
+			foreach (SelectableSource source in allsources) {
+				if (source is StaticSource) {
+					string id = ((StaticSource)source).GetPersistentBNodeId(node);
+					if (id != null) return id;
+				}
+			}
+			return null;
 		}
 		
-		public override bool Contains(Statement statement) {
-			SelectableSource[] sources = GetSources(statement.Meta);
-			if (sources == null) return false;
-			foreach (SelectableSource s in sources)
-				if (s.Contains(statement))
-					return true;
-			return false;
+		public BNode GetBNodeFromPersistentId(string persistentId) {
+			foreach (SelectableSource source in allsources) {
+				if (source is StaticSource) {
+					BNode node = ((StaticSource)source).GetBNodeFromPersistentId(persistentId);
+					if (node != null) return node;
+				}
+			}
+			return null;
+		}
+		
+		
+		// StatementSink
+
+		bool StatementSink.Add(Statement statement) {
+			Add(statement);
+			return true;
 		}
-			
-		public override void Remove(Statement statement) { throw new InvalidOperationException("Remove is not a valid operation on a MultiStore."); }
 		
-		public override void Select(Statement template, StatementSink result) {
-			SelectableSource[] sources = GetSources(template.Meta);
+		public void Add(Statement statement) {
+			if (statement.AnyNull) throw new ArgumentNullException();
+			// We don't know where to put it unless we are wrapping just one store.
+			SelectableSource[] sources = GetSources(ref statement.Meta);
+			if (sources == null || sources.Length != 1) throw new InvalidOperationException("I don't know which data source to put the statement into.");
+			if (!(sources[0] is ModifiableSource)) throw new InvalidOperationException("The data source is not modifiable.");
+			((ModifiableSource)sources[0]).Add(statement);
+		}
+		
+		// ModifiableSource
+		
+		public void Clear() {
+			if (allsources.Count > 0) throw new InvalidOperationException("The Clear() method is not supported when multiple data sources are added to a Store.");
+			if (!(allsources[0] is ModifiableSource)) throw new InvalidOperationException("The data source is not modifiable.");
+			((ModifiableSource)allsources[0]).Clear();
+		}
+		
+		ModifiableSource[] GetModifiableSources(ref Entity graph) {
+			SelectableSource[] sources = GetSources(ref graph);
+			if (sources == null) return null;
+			
+			// check all are modifiable first
+			foreach (SelectableSource source in sources)
+				if (!(source is ModifiableSource)) throw new InvalidOperationException("Not all of the data sources are modifiable.");
+				
+			ModifiableSource[] sources2 = new ModifiableSource[sources.Length];
+			sources.CopyTo(sources2, 0);
+			return sources2;
+		}
+
+		public void Remove(Statement template) {
+			ModifiableSource[] sources = GetModifiableSources(ref template.Meta);
 			if (sources == null) return;
-			template.Meta = null;
-			foreach (SelectableSource s in sources)
-				s.Select(template, result);
+			
+			foreach (ModifiableSource source in sources)
+				source.Remove(template);
+		}
+
+		public void Import(StatementSource source) {
+			// We don't know where to put the data unless we are wrapping just one store.
+			if (allsources.Count != 1) throw new InvalidOperationException("I don't know which data source to put the statements into.");
+			if (!(allsources[0] is ModifiableSource)) throw new InvalidOperationException("The data source is not modifiable.");
+			((ModifiableSource)allsources[0]).Import(source);
 		}
 		
-		public override void Select(SelectFilter filter, StatementSink result) {
-			Entity[] scanMetas = filter.Metas;
-			filter.Metas = null;
-			if (scanMetas == null || namedgraphs.Count == 0) scanMetas = new Entity[] { null };
-			foreach (Entity meta in scanMetas) {
-				SelectableSource[] sources = GetSources(meta);
+		public void RemoveAll(Statement[] templates) {
+			// Not tested...
+			
+			System.Collections.ArrayList metas = new System.Collections.ArrayList();
+			foreach (Statement t in templates)
+				if (!metas.Contains(t.Meta))
+					metas.Add(t.Meta);
+					
+			foreach (Entity meta in metas) {
+				Entity meta2 = meta;
+				ModifiableSource[] sources = GetModifiableSources(ref meta2);
 				if (sources == null) continue;
-				foreach (SelectableSource s in sources)
-					s.Select(filter, result);
+				
+				StatementList templates2 = new StatementList();
+				foreach (Statement t in templates) {
+					if (t.Meta == meta) {
+						Statement t2 = t;
+						t2.Meta = meta2;
+						templates2.Add(t2);
+					}
+				}
+					
+				foreach (ModifiableSource source in sources)
+					source.RemoveAll(templates2);
 			}
 		}
+		
+		public void Replace(Entity find, Entity replacement) {
+			foreach (SelectableSource source in allsources)
+				if (!(source is ModifiableSource)) throw new InvalidOperationException("Not all of the data sources are modifiable.");
 
-		public override void Replace(Entity a, Entity b) { throw new InvalidOperationException("Replace is not a valid operation on a MultiStore."); }
+			foreach (ModifiableSource source in allsources)
+				source.Replace(find, replacement);
+		}
 		
-		public override void Replace(Statement find, Statement replacement) { throw new InvalidOperationException("Replace is not a valid operation on a MultiStore."); }
+		public void Replace(Statement find, Statement replacement) {
+			ModifiableSource[] sources = GetModifiableSources(ref find.Meta);
+			if (sources == null) return;
+				
+			foreach (ModifiableSource source in sources)
+				source.Replace(find, replacement);
+		}
+
+		public static void DefaultReplace(ModifiableSource source, Entity find, Entity replacement) {
+			MemoryStore deletions = new MemoryStore();
+			MemoryStore additions = new MemoryStore();
+			
+			source.Select(new Statement(find, null, null, null), deletions);
+			source.Select(new Statement(null, find, null, null), deletions);
+			source.Select(new Statement(null, null, find, null), deletions);
+			source.Select(new Statement(null, null, null, find), deletions);
+			
+			foreach (Statement s in deletions) {
+				source.Remove(s);
+				additions.Add(s.Replace(find, replacement));
+			}
+			
+			foreach (Statement s in additions) {
+				source.Add(s);
+			}
+		}
+		
+		public static void DefaultReplace(ModifiableSource source, Statement find, Statement replacement) {
+			source.Remove(find);
+			source.Add(replacement);
+		}
+		
+		
+	}
+
+	public abstract class SelectResult : StatementSource, 
+#if DOTNET2
+	System.Collections.Generic.IEnumerable<Statement>
+#else
+	IEnumerable
+#endif
+	{
+		internal Store source;
+		MemoryStore ms;
+		internal SelectResult(Store source) { this.source = source; }
+		public bool Distinct { get { return source.Distinct; } }
+		public abstract void Select(StatementSink sink);
+#if DOTNET2
+		System.Collections.Generic.IEnumerator<Statement> System.Collections.Generic.IEnumerable<Statement>.GetEnumerator() {
+			return ((System.Collections.Generic.IEnumerable<Statement>)Buffer()).GetEnumerator();
+		}
+#endif
+		System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
+			return ((System.Collections.IEnumerable)Buffer()).GetEnumerator();
+		}
+		public long StatementCount { get { return Buffer().StatementCount; } }
+		public MemoryStore Load() { return Buffer(); }
+		public Statement[] ToArray() { return Load().ToArray(); }
+		private MemoryStore Buffer() {
+			if (ms != null) return ms;
+			ms = new MemoryStore();
+			ms.allowIndexing = false;
+			Select(ms);
+			return ms;
+		}
+		
+		internal class Single : SelectResult {
+			Statement template;
+			public Single(Store source, Statement template) : base(source) {
+				this.template = template;
+			}
+			public override void Select(StatementSink sink) {
+				source.Select(template, sink);
+			}
+		}
 		
+		internal class Multi : SelectResult {
+			SelectFilter filter;
+			public Multi(Store source, SelectFilter filter)
+				: base(source) {
+				this.filter = filter;
+			}
+			public override void Select(StatementSink sink) {
+				source.Select(filter, sink);
+			}
+		}
 	}
+}
+		
+
+
+
+/////// AUXILIARY STORE WRAPPERS /////////
 	
+namespace SemWeb.Stores {
+
+	#if DOTNET2
+	using System.Collections;
+	#endif
+
 	public abstract class SimpleSourceWrapper : SelectableSource {
 	
 		public virtual bool Distinct { get { return true; } }
@@ -531,6 +912,8 @@
 			// The default implementation does not return
 			// anything for this call.
 		}
+		
+		public abstract bool Contains(Resource resource);
 
 		public virtual bool Contains(Statement template) {
 			template.Object = null; // reduce to another case (else there would be recursion)
@@ -586,7 +969,7 @@
 		}
 	}
 	
-	public class DebuggedSource : SelectableSource {
+	public class DebuggedSource : QueryableSource {
 		SelectableSource source;
 		System.IO.TextWriter output;
 		
@@ -600,6 +983,11 @@
 		public void Select(StatementSink sink) {
 			Select(Statement.All, sink);
 		}
+		
+		public bool Contains(Resource resource) {
+			output.WriteLine("CONTAINS: " + resource);
+			return source.Contains(resource);
+		}
 
 		public bool Contains(Statement template) {
 			output.WriteLine("CONTAINS: " + template);
@@ -615,12 +1003,45 @@
 			output.WriteLine("SELECT: " + filter);
 			source.Select(filter, sink);
 		}
+
+		public virtual SemWeb.Query.MetaQueryResult MetaQuery(Statement[] graph, SemWeb.Query.QueryOptions options) {
+			if (source is QueryableSource)
+				return ((QueryableSource)source).MetaQuery(graph, options);
+			else
+				return new SemWeb.Query.MetaQueryResult(); // QuerySupported is by default false
+		}
+
+		public void Query(Statement[] graph, SemWeb.Query.QueryOptions options, SemWeb.Query.QueryResultSink sink) {
+			output.WriteLine("QUERY:");
+			foreach (Statement s in graph)
+				output.WriteLine("\t" + s);
+			if (options.VariableKnownValues != null) {
+				#if !DOTNET2
+				foreach (System.Collections.DictionaryEntry ent in options.VariableKnownValues)
+				#else
+				foreach (KeyValuePair<Variable,ICollection<Resource>> ent in options.VariableKnownValues)
+				#endif
+					output.WriteLine("\twhere " + ent.Key + " in " + ToString((ICollection)ent.Value));	
+			}
+			if (source is QueryableSource) 
+				((QueryableSource)source).Query(graph, options, sink);
+			else
+				throw new NotSupportedException("Underlying source " + source + " is not a QueryableSource.");
+		}
+		
+		string ToString(ICollection resources) {
+			ArrayList s = new ArrayList();
+			foreach (Resource r in resources)
+				s.Add(r.ToString());
+			return String.Join(",", (string[])s.ToArray(typeof(string)));
+		}
 	}
 	
 	public class CachedSource : SelectableSource {
 		SelectableSource source;
 
-		StatementMap containsresults = new StatementMap();
+		Hashtable containsresource = new Hashtable();
+		StatementMap containsstmtresults = new StatementMap();
 		StatementMap selectresults = new StatementMap();
 		Hashtable selfilterresults = new Hashtable();
 	
@@ -632,13 +1053,22 @@
 			Select(Statement.All, sink);
 		}
 
+		public bool Contains(Resource resource) {
+			if (source == null) return false;
+			if (!containsresource.ContainsKey(resource))
+				containsresource[resource] = source.Contains(resource);
+			return (bool)containsresource[resource];
+		}
+
 		public bool Contains(Statement template) {
-			if (!containsresults.ContainsKey(template))
-				containsresults[template] = source.Contains(template);
-			return (bool)containsresults[template];
+			if (source == null) return false;
+			if (!containsstmtresults.ContainsKey(template))
+				containsstmtresults[template] = source.Contains(template);
+			return (bool)containsstmtresults[template];
 		}
 		
 		public void Select(Statement template, StatementSink sink) {
+			if (source == null) return;
 			if (!selectresults.ContainsKey(template)) {
 				MemoryStore s = new MemoryStore();
 				source.Select(template, s);
@@ -648,6 +1078,7 @@
 		}
 	
 		public void Select(SelectFilter filter, StatementSink sink) {
+			if (source == null) return;
 			if (!selfilterresults.ContainsKey(filter)) {
 				MemoryStore s = new MemoryStore();
 				source.Select(filter, s);
@@ -657,4 +1088,83 @@
 		}
 
 	}
+	
+	internal class DecoupledStatementSource : StatementSource {
+		StatementSource source;
+		int minbuffersize = 2000;
+		int maxbuffersize = 10000;
+		
+		bool bufferWanted = false;
+		System.Threading.AutoResetEvent bufferMayAcquire = new System.Threading.AutoResetEvent(false);
+		System.Threading.AutoResetEvent bufferReleased = new System.Threading.AutoResetEvent(false);
+
+		System.Threading.Thread sourceThread;
+		
+		StatementList buffer = new StatementList();
+		bool sourceFinished = false;
+		
+		public DecoupledStatementSource(StatementSource source) {
+			this.source = source;
+		}
+		
+		public bool Distinct { get { return source.Distinct; } }
+
+		public void Select(StatementSink sink) {
+			bufferWanted = false;
+			
+			sourceThread = new System.Threading.Thread(Go);
+			sourceThread.Start();
+			
+			while (true) {
+				bufferWanted = true;
+				if (!sourceFinished) bufferMayAcquire.WaitOne(); // wait until we can have the buffer
+				bufferWanted = false;
+				
+				Statement[] statements = buffer.ToArray();
+				buffer.Clear();
+				
+				bufferReleased.Set(); // notify that we don't need the buffer anymore
+
+				if (sourceFinished && statements.Length == 0) break;
+				
+				foreach (Statement s in statements)
+					sink.Add(s);
+			}
+		}
+		
+		private void Go() {
+			source.Select(new MySink(this));
+			sourceFinished = true;
+			bufferMayAcquire.Set(); // for the last batch
+		}
+		
+		private void SourceAdd(Statement s) {
+			if ((bufferWanted && buffer.Count > minbuffersize) || buffer.Count >= maxbuffersize) {
+				bufferMayAcquire.Set();
+				bufferReleased.WaitOne();
+			}
+			buffer.Add(s);
+		}
+		private void SourceAdd(Statement[] s) {
+			if ((bufferWanted && buffer.Count > minbuffersize) || buffer.Count >= maxbuffersize) {
+				bufferMayAcquire.Set();
+				bufferReleased.WaitOne();
+			}
+			foreach (Statement ss in s)
+				buffer.Add(ss);
+		}
+		
+		private class MySink : StatementSink {
+			DecoupledStatementSource x;
+			public MySink(DecoupledStatementSource x) { this.x = x; }
+			public bool Add(Statement s) {
+				x.SourceAdd(s);
+				return true;
+			}
+			public bool Add(Statement[] s) {
+				x.SourceAdd(s);
+				return true;
+			}
+		}
+	}
 }

Modified: branches/beagle-rdf/Util/SemWeb/Util.cs
==============================================================================
--- branches/beagle-rdf/Util/SemWeb/Util.cs	(original)
+++ branches/beagle-rdf/Util/SemWeb/Util.cs	Sat Jan 26 23:26:03 2008
@@ -12,10 +12,23 @@
 		public ResSet() {
 		}
 		
+		#if !DOTNET2
 		public ResSet(ICollection items) {
+		#else
+		public ResSet(System.Collections.Generic.ICollection<Resource> items) {
+		#endif
 			AddRange(items);
 		}
 
+		#if DOTNET2
+		// this is for some call in SQLStore; it seems to having a generics casting issue that I don't know if it's a mono bug or what...
+		internal ResSet(System.Collections.Generic.ICollection<Variable> items) {
+			if (items == null) return;
+			foreach (Resource r in items)
+				Add(r);
+		}
+		#endif
+
 		private ResSet(Hashtable items) {
 			this.items = items;
 		}
@@ -25,7 +38,11 @@
 			keys = null;
 		}
 		
+		#if !DOTNET2
 		public void AddRange(ICollection items) {
+		#else
+		public void AddRange<T>(System.Collections.Generic.ICollection<T> items) where T : Resource {
+		#endif
 			if (items == null) return;
 			foreach (Resource r in items)
 				Add(r);



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]