beagle r4421 - in branches/beagle-rdf/Util: . SemWeb
- From: dbera svn gnome org
- To: svn-commits-list gnome org
- Subject: beagle r4421 - in branches/beagle-rdf/Util: . SemWeb
- Date: Sat, 26 Jan 2008 23:26:03 +0000 (GMT)
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]