f-spot r3877 - in trunk: . semweb src
- From: sdelcroix svn gnome org
- To: svn-commits-list gnome org
- Subject: f-spot r3877 - in trunk: . semweb src
- Date: Wed, 7 May 2008 11:59:53 +0100 (BST)
Author: sdelcroix
Date: Wed May 7 10:59:52 2008
New Revision: 3877
URL: http://svn.gnome.org/viewvc/f-spot?rev=3877&view=rev
Log:
2008-05-07 Stephane Delcroix <sdelcroix novell com>
* src/MetadatStore.cs:
* semweb/ : updating to SemWeb 1.05
Added:
trunk/semweb/Euler.cs
trunk/semweb/GraphMatch.cs
trunk/semweb/Interfaces.cs
trunk/semweb/SparqlClient.cs
trunk/semweb/SpecialRelations.cs
Removed:
trunk/semweb/RSquary.cs
trunk/semweb/RSquaryFilters.cs
trunk/semweb/Remote.cs
trunk/semweb/XPathSemWebNavigator.cs
Modified:
trunk/ChangeLog
trunk/semweb/Algos.cs
trunk/semweb/AssemblyInfo.cs
trunk/semweb/Inference.cs
trunk/semweb/LiteralFilters.cs
trunk/semweb/Makefile.am
trunk/semweb/MemoryStore.cs
trunk/semweb/N3Reader.cs
trunk/semweb/N3Writer.cs
trunk/semweb/NamespaceManager.cs
trunk/semweb/Query.cs
trunk/semweb/RDFS.cs
trunk/semweb/README
trunk/semweb/RdfReader.cs
trunk/semweb/RdfWriter.cs
trunk/semweb/RdfXmlReader.cs
trunk/semweb/RdfXmlWriter.cs
trunk/semweb/Resource.cs
trunk/semweb/SQLStore.cs
trunk/semweb/Statement.cs
trunk/semweb/Store.cs
trunk/semweb/Util.cs
trunk/src/MetadataStore.cs
Modified: trunk/semweb/Algos.cs
==============================================================================
--- trunk/semweb/Algos.cs (original)
+++ trunk/semweb/Algos.cs Wed May 7 10:59:52 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: trunk/semweb/AssemblyInfo.cs
==============================================================================
--- trunk/semweb/AssemblyInfo.cs (original)
+++ trunk/semweb/AssemblyInfo.cs Wed May 7 10:59:52 2008
@@ -1,18 +1,18 @@
-using System.Reflection;
-using System.Runtime.CompilerServices;
-
-[assembly: AssemblyTitle("SemWeb")]
-[assembly: AssemblyDescription("A library for applications using RDF and the semantic web.")]
-[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("")]
-[assembly: AssemblyProduct("")]
-[assembly: AssemblyCopyright("Copyright (c) 2006 Joshua Tauberer <tauberer for net>")]
-[assembly: AssemblyTrademark("")]
-[assembly: AssemblyCulture("")]
-
-[assembly: AssemblyVersion("0.7.1.0")]
-[assembly: AssemblyDelaySign(false)]
-[assembly: AssemblyKeyFile("")]
-[assembly: AssemblyKeyName("")]
-
-
+using System.Reflection;
+using System.Runtime.CompilerServices;
+
+[assembly: AssemblyTitle("SemWeb")]
+[assembly: AssemblyDescription("A library for applications using RDF and the semantic web.")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("")]
+[assembly: AssemblyCopyright("Copyright (c) 2008 Joshua Tauberer <http://razor.occams.info>")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+[assembly: AssemblyVersion("1.0.5.0")]
+[assembly: AssemblyDelaySign(false)]
+[assembly: AssemblyKeyFile("")]
+[assembly: AssemblyKeyName("")]
+
+
Added: trunk/semweb/Euler.cs
==============================================================================
--- (empty file)
+++ trunk/semweb/Euler.cs Wed May 7 10:59:52 2008
@@ -0,0 +1,663 @@
+// Adapted from:
+// ---------------------------------------------------------------
+// Euler proof mechanism -- Jos De Roo
+// version = '$Id: euler.js,v 1.35 2006/12/17 01:25:01 josd Exp $'
+// http://eulersharp.cvs.sourceforge.net/eulersharp/2006/02swap/euler.js?view=log
+// ---------------------------------------------------------------
+// The original Euler code is licensed under the W3C Software License.
+// This is a very liberal translation of the original code into C#.
+
+using System;
+using System.Collections;
+
+using SemWeb;
+using SemWeb.Util;
+
+namespace SemWeb.Inference {
+
+ public class Euler : Reasoner {
+
+ static bool Debug = System.Environment.GetEnvironmentVariable("SEMWEB_DEBUG_EULER") != null;
+
+ Hashtable rules;
+
+ static Hashtable builtInRelations;
+
+ static Euler() {
+ RdfRelation[] rs = new RdfRelation[] {
+ new SemWeb.Inference.Relations.MathAbsoluteValueRelation(), new SemWeb.Inference.Relations.MathCosRelation(), new SemWeb.Inference.Relations.MathDegreesRelation(), new SemWeb.Inference.Relations.MathEqualToRelation(),
+ new SemWeb.Inference.Relations.MathNegationRelation(), new SemWeb.Inference.Relations.MathRoundedRelation(), new SemWeb.Inference.Relations.MathSinRelation(), new SemWeb.Inference.Relations.MathSinhRelation(), new SemWeb.Inference.Relations.MathTanRelation(), new SemWeb.Inference.Relations.MathTanhRelation(),
+ new SemWeb.Inference.Relations.MathAtan2Relation(), new SemWeb.Inference.Relations.MathDifferenceRelation(), new SemWeb.Inference.Relations.MathExponentiationRelation(), new SemWeb.Inference.Relations.MathIntegerQuotientRelation(),
+ new SemWeb.Inference.Relations.MathQuotientRelation(), new SemWeb.Inference.Relations.MathRemainderRelation(),
+ new SemWeb.Inference.Relations.MathSumRelation(), new SemWeb.Inference.Relations.MathProductRelation(),
+ new SemWeb.Inference.Relations.MathGreaterThanRelation(), new SemWeb.Inference.Relations.MathLessThanRelation(), new SemWeb.Inference.Relations.MathNotGreaterThanRelation(), new SemWeb.Inference.Relations.MathNotLessThanRelation(), new SemWeb.Inference.Relations.MathNotEqualToRelation()
+ };
+
+ builtInRelations = new Hashtable();
+ foreach (RdfRelation r in rs)
+ builtInRelations[r.Uri] = r;
+ }
+
+ public Euler(StatementSource rules) {
+ this.rules = RulesToCases(rules);
+ }
+
+ public override bool Distinct { get { return false; } } // not sure...
+
+ public override void Select(SelectFilter filter, SelectableSource targetModel, StatementSink sink) {
+ if (filter.Subjects == null) filter.Subjects = new Entity[] { new Variable("subject") };
+ if (filter.Predicates == null) filter.Predicates = new Entity[] { new Variable("predicate") };
+ if (filter.Objects == null) filter.Objects = new Entity[] { new Variable("object") };
+
+ if (filter.Metas == null) filter.Metas = new Entity[] { Statement.DefaultMeta };
+
+ foreach (Statement s in filter) { // until we can operate on filter directly
+ ArrayList evidence = prove(rules, targetModel, new Statement[] { s }, -1);
+ if (evidence == null)
+ continue; // not provable (in max number of steps, if that were given)
+
+ foreach (EvidenceItem ei in evidence) {
+ foreach (Statement h in ei.head) { // better be just one statement
+ if (filter.LiteralFilters != null
+ && !LiteralFilter.MatchesFilters(h.Object, filter.LiteralFilters, targetModel))
+ continue;
+
+ sink.Add(h);
+ }
+ }
+ }
+ }
+
+ private void QueryCheckArg(Statement[] graph) {
+ if (graph == null) throw new ArgumentNullException("graph");
+ foreach (Statement s in graph) {
+ if (s.Subject == null || s.Predicate == null || s.Object == null || s.Meta == null)
+ throw new ArgumentNullException("Graph statements cannot contain a null subject, predicate, or object. Use a Variable instance instead.");
+ if (s.Meta != Statement.DefaultMeta && !(s.Meta is Variable))
+ throw new NotSupportedException("Graph statements' meta fields must be Statement.DefaultMeta. Other values of meta are not currently supported.");
+ }
+ }
+
+ public override SemWeb.Query.MetaQueryResult MetaQuery(Statement[] graph, SemWeb.Query.QueryOptions options, SelectableSource targetModel) {
+ QueryCheckArg(graph);
+ SemWeb.Query.MetaQueryResult ret = new SemWeb.Query.MetaQueryResult();
+ ret.QuerySupported = true;
+ // TODO: Best to check also whether variables in the query are even known to us.
+ return ret;
+ }
+
+ public override void Query(Statement[] graph, SemWeb.Query.QueryOptions options, SelectableSource targetModel, SemWeb.Query.QueryResultSink sink) {
+ QueryCheckArg(graph);
+
+ // Try to do the inferencing.
+ ArrayList evidence = prove(rules, targetModel, graph, -1);
+ if (evidence == null)
+ return; // not provable (in max number of steps, if that were given)
+
+ // Then send the possible bindings to the QueryResultSink.
+
+ // Map variables to indexes.
+ Hashtable vars = new Hashtable();
+ foreach (Statement s in graph) {
+ if (s.Subject is Variable && !vars.ContainsKey(s.Subject)) vars[s.Subject] = vars.Count;
+ if (s.Predicate is Variable && !vars.ContainsKey(s.Predicate)) vars[s.Predicate] = vars.Count;
+ if (s.Object is Variable && !vars.ContainsKey(s.Object)) vars[s.Object] = vars.Count;
+ }
+
+ // Prepare the bindings array.
+ Variable[] varOrder = new Variable[vars.Count];
+ foreach (Variable v in vars.Keys)
+ varOrder[(int)vars[v]] = v;
+
+ // Initialize the sink.
+ sink.Init(varOrder);
+
+ // Send a binding set for each piece of evidence.
+ foreach (EvidenceItem ei in evidence) {
+ // Write a comment to the results with the actual proof. (nifty actually)
+ sink.AddComments(ei.ToProof().ToString());
+
+ // Create the binding array and send it on
+ Resource[] variableBindings = new Resource[varOrder.Length];
+ foreach (Variable v in vars.Keys)
+ if (ei.env.ContainsKey(v))
+ variableBindings[(int)vars[v]] = (Resource)ei.env[v];
+ sink.Add(new SemWeb.Query.VariableBindings(varOrder, variableBindings));
+ }
+
+ // Close the sink.
+ sink.Finished();
+ }
+
+ // Static methods that wrap the Euler engine
+
+ private static readonly Entity entLOGIMPLIES = "http://www.w3.org/2000/10/swap/log#implies";
+ private static readonly Entity entRDFFIRST = "http://www.w3.org/1999/02/22-rdf-syntax-ns#first";
+ private static readonly Entity entRDFREST = "http://www.w3.org/1999/02/22-rdf-syntax-ns#rest";
+ private static readonly Entity entRDFNIL = "http://www.w3.org/1999/02/22-rdf-syntax-ns#nil";
+
+ private class Sequent {
+ public Statement head; // consequent
+ public Statement[] body; // antecedent
+ public Hashtable callArgs; // encapsulates (...) arguments to user predicates
+
+ public Sequent(Statement head, Statement[] body, Hashtable callArgs) {
+ this.head = head;
+ this.body = body;
+ this.callArgs = callArgs;
+ }
+ public Sequent(Statement head) {
+ this.head = head;
+ this.body = new Statement[0];
+ }
+
+ public Rule ToRule() {
+ return new Rule(body, new Statement[] { head });
+ }
+
+ // override to satisfy a warning about overloading ==
+ public override bool Equals(object o) {
+ return this == (Sequent)o;
+ }
+
+ // override to satisfy a warning about overloading ==
+ public override int GetHashCode() {
+ return base.GetHashCode();
+ }
+
+ public override string ToString() {
+ string ret = "{ ";
+ foreach (Statement b in body) {
+ if (ret != "{ ") ret += ", ";
+ ret += b.ToString();
+ }
+ ret += " } => " + head;
+ return ret;
+ }
+
+ public static bool operator ==(Sequent a, Sequent b) {
+ if (object.ReferenceEquals(a, b)) return true;
+ if (object.ReferenceEquals(a, null) && object.ReferenceEquals(b, null)) return true;
+ if (object.ReferenceEquals(a, null) || object.ReferenceEquals(b, null)) return false;
+ if (a.head != b.head) return false;
+ if (a.body.Length != b.body.Length) return false;
+ for (int i = 0; i < a.body.Length; i++)
+ if (a.body[i] != b.body[i]) return false;
+ return true;
+ }
+ public static bool operator !=(Sequent a, Sequent b) {
+ return !(a == b);
+ }
+ }
+
+ private class Ground {
+ public Sequent src; // evidence
+ public Hashtable env; // substitution environment: Resource => Resource
+
+ public Ground(Sequent src, Hashtable env) {
+ this.src = src;
+ this.env = env;
+ }
+
+ public ProofStep ToProofStep() {
+ return new ProofStep(src.ToRule(), env);
+ }
+ }
+
+ private class EvidenceItem {
+ public Statement[] head;
+ public ArrayList body; // of Ground
+ public Hashtable env; // substitutions of goal-level variables
+
+ public Proof ToProof() {
+ ProofStep[] steps = new ProofStep[body.Count];
+ for (int i = 0; i < body.Count; i++)
+ steps[i] = ((Ground)body[i]).ToProofStep();
+ return new Proof(head, steps);
+ }
+
+ public override string ToString() {
+ string ret = "";
+ foreach (DictionaryEntry kv in env)
+ ret += "\t" + kv.Key + "=>" + kv.Value + "\n";
+ return ret;
+ }
+ }
+
+ private class QueueItem {
+ public Sequent rule;
+ public Sequent src;
+ public int ind;
+ public QueueItem parent;
+ public Hashtable env; // substitution environment: Resource => Resource
+ public ArrayList ground;
+
+ public QueueItem Clone() {
+ return (QueueItem)MemberwiseClone();
+ }
+
+ public override string ToString() {
+ string ret = "";
+ foreach (DictionaryEntry kv in env)
+ ret += kv.Key + "=>" + kv.Value + "; ";
+ ret += rule.ToString() + " @ " + ind + "/" + rule.body.Length;
+ return ret;
+ }
+ }
+
+ public Proof[] Prove(SelectableSource world, Statement[] goal) {
+ ArrayList evidence = prove(rules, world, goal, -1);
+ if (evidence == null)
+ throw new Exception("Could not complete proof within the maximum number of steps allowed.");
+
+ ArrayList ret = new ArrayList();
+ foreach (EvidenceItem ei in evidence)
+ ret.Add(ei.ToProof());
+
+ return (Proof[])ret.ToArray(typeof(Proof));
+ }
+
+ private static ArrayList prove(Hashtable cases, SelectableSource world, Statement[] goal, int maxNumberOfSteps) {
+ // This is the main routine from euler.js.
+
+ // cases: Resource (predicate) => IList of Sequent
+
+ if (world != null) // cache our queries to the world, if a world is provided
+ world = new SemWeb.Stores.CachedSource(world);
+
+ // Create the queue and add the first item.
+ ArrayList queue = new ArrayList();
+ {
+ QueueItem start = new QueueItem();
+ start.env = new Hashtable();
+ start.rule = new Sequent(Statement.All, goal, null);
+ start.src = null;
+ start.ind = 0;
+ start.parent = null;
+ start.ground = new ArrayList();
+ queue.Add(start);
+ if (Debug) Console.Error.WriteLine("Euler: Queue: " + start);
+ }
+
+ // The evidence array holds the results of this proof.
+ ArrayList evidence = new ArrayList();
+
+ // Track how many steps we take to not go over the limit.
+ int step = 0;
+
+ // Process the queue until it's empty or we reach our step limit.
+ while (queue.Count > 0) {
+ // deal with the QueueItem at the top of the queue
+ QueueItem c = (QueueItem)queue[queue.Count-1];
+ queue.RemoveAt(queue.Count-1);
+ ArrayList g = new ArrayList(c.ground);
+
+ // have we done too much?
+ step++;
+ if (maxNumberOfSteps != -1 && step >= maxNumberOfSteps) {
+ if (Debug) Console.Error.WriteLine("Euler: Maximum number of steps exceeded. Giving up.");
+ return null;
+ }
+
+ // if each statement in the body of the sequent has been proved
+ if (c.ind == c.rule.body.Length) {
+ // if this is the top-level sequent being proved; we've found a complete evidence for the goal
+ if (c.parent == null) {
+ EvidenceItem ev = new EvidenceItem();
+ ev.head = new Statement[c.rule.body.Length];
+ bool canRepresentHead = true;
+ for (int i = 0; i < c.rule.body.Length; i++) {
+ ev.head[i] = evaluate(c.rule.body[i], c.env);
+ if (ev.head[i].AnyNull) // can't represent it: literal in subject position, for instance
+ canRepresentHead = false;
+ }
+
+ ev.body = c.ground;
+ ev.env = c.env;
+
+ if (Debug) Console.Error.WriteLine("Euler: Found Evidence: " + ev);
+
+ if (!canRepresentHead)
+ continue;
+
+ evidence.Add(ev);
+
+ // this is a subproof of something; whatever it is a subgroup for can
+ // be incremented
+ } else {
+ // if the rule had no body, it was just an axiom and we represent that with Ground
+ if (c.rule.body.Length != 0) g.Add(new Ground(c.rule, c.env));
+
+ // advance the parent being proved and put the advanced
+ // parent into the queue; unify the parent variable assignments
+ // with this one's
+ QueueItem r = new QueueItem();
+ r.rule = c.parent.rule;
+ r.src = c.parent.src;
+ r.ind = c.parent.ind;
+ r.parent = c.parent.parent != null
+ ? c.parent.parent.Clone()
+ : null;
+ r.env = (Hashtable)c.parent.env.Clone();
+ r.ground = g;
+ unify(c.rule.head, c.env, r.rule.body[r.ind], r.env, true);
+ r.ind++;
+ queue.Add(r);
+ if (Debug) Console.Error.WriteLine("Euler: Queue Advancement: " + r);
+ }
+
+ // this sequent still has parts of the body left to be proved; try to
+ // find evidence for the next statement in the body, and if we find
+ // evidence, queue up that evidence
+ } else {
+ // this is the next part of the body that we need to try to prove
+ Statement t = c.rule.body[c.ind];
+
+ // Try to process this predicate with a user-provided custom
+ // function that resolves things like mathematical relations.
+ // euler.js provides similar functionality, but the system
+ // of user predicates is completely different here.
+ RdfRelation b = FindUserPredicate(t.Predicate);
+ if (b != null) {
+ Resource[] args;
+ Variable[] unifyResult;
+ Resource value = evaluate(t.Object, c.env);
+
+ if (!c.rule.callArgs.ContainsKey(t.Subject)) {
+ // The array of arguments to this relation is just the subject of the triple itself
+ args = new Resource[] { evaluate(t.Subject, c.env) };
+ unifyResult = new Variable[1];
+ if (t.Subject is Variable) unifyResult[0] = (Variable)t.Subject;
+
+ } else {
+ // The array of arguments to this relation comes from a pre-grouped arg list.
+ args = (Resource[])c.rule.callArgs[t.Subject];
+ unifyResult = new Variable[args.Length];
+
+ for (int i = 0; i < args.Length; i++) {
+ if (args[i] is Variable) {
+ unifyResult[i] = (Variable)args[i];
+ args[i] = evaluate(args[i], c.env);
+ }
+ }
+ }
+
+ // Run the user relation on the array of arguments (subject) and on the object.
+ if (b.Evaluate(args, ref value)) {
+ // If it succeeds, we press on.
+
+ // The user predicate may update the 'value' variable and the argument
+ // list array, and so we want to unify any variables in the subject
+ // or object of this user predicate with the values given to us
+ // by the user predicate.
+ Hashtable newenv = (Hashtable)c.env.Clone();
+ if (t.Object is Variable) unify(value, null, t.Object, newenv, true);
+ for (int i = 0; i < args.Length; i++)
+ if (unifyResult[i] != null)
+ unify(args[i], null, unifyResult[i], newenv, true);
+
+ Statement grnd = evaluate(t, newenv);
+ if (grnd != Statement.All) // sometimes not representable, like if literal as subject
+ g.Add(new Ground(new Sequent(grnd, new Statement[0], null), new Hashtable()));
+
+ QueueItem r = new QueueItem();
+ r.rule = c.rule;
+ r.src = c.src;
+ r.ind = c.ind;
+ r.parent = c.parent;
+ r.env = newenv;
+ r.ground = g;
+ r.ind++;
+ queue.Add(r);
+ }
+ continue;
+ }
+
+ // t can be proved either by the use of a rule
+ // or if t literally exists in the world
+
+ Statement t_resolved = evaluate(t, c.env);
+
+ // If resolving this statement requires putting a
+ // literal in subject or predicate position, we
+ // can't prove it.
+ if (t_resolved == Statement.All)
+ continue;
+
+ ArrayList tcases = new ArrayList();
+
+ // get all of the rules that apply to the predicate in question
+ if (t_resolved.Predicate != null && cases.ContainsKey(t_resolved.Predicate))
+ tcases.AddRange((IList)cases[t_resolved.Predicate]);
+
+ /*if (cases.ContainsKey("WILDCARD")) // not supported yet -- infinite regress not handled
+ tcases.AddRange((IList)cases["WILDCARD"]);*/
+
+ // if t has no unbound variables and we've matched something from
+ // the axioms, don't bother looking at the world, and don't bother
+ // proving it any other way than by the axiom.
+ bool lookAtWorld = true;
+ foreach (Sequent seq in tcases) {
+ if (seq.body.Length == 0 && seq.head == t_resolved) {
+ lookAtWorld = false;
+ tcases.Clear();
+ tcases.Add(seq);
+ break;
+ }
+ }
+
+ // if there is a seprate world, get all of the world
+ // statements that witness t
+ if (world != null && lookAtWorld) {
+ MemoryStore w = new MemoryStore();
+
+ if (Debug) Console.WriteLine("Euler: Ask World: " + t_resolved);
+ world.Select(t_resolved, w);
+ foreach (Statement s in w) {
+ if (Debug) Console.WriteLine("Euler: World Select Response: " + s);
+ Sequent seq = new Sequent(s);
+ tcases.Add(seq);
+ }
+ }
+
+ // If there is no evidence or potential evidence (i.e. rules)
+ // for t, then we will dump this QueueItem by not queuing any
+ // subproofs.
+
+ // Otherwise we try each piece of evidence in turn.
+ foreach (Sequent rl in tcases) {
+ ArrayList g2 = (ArrayList)c.ground.Clone();
+ if (rl.body.Length == 0) g2.Add(new Ground(rl, new Hashtable()));
+
+ QueueItem r = new QueueItem();
+ r.rule = rl;
+ r.src = rl;
+ r.ind = 0;
+ r.parent = c;
+ r.env = new Hashtable();
+ r.ground = g2;
+
+ if (unify(t, c.env, rl.head, r.env, true)) {
+ QueueItem ep = c; // euler path
+ while ((ep = ep.parent) != null)
+ if (ep.src == c.src && unify(ep.rule.head, ep.env, c.rule.head, c.env, false))
+ break;
+ if (ep == null) {
+ queue.Insert(0, r);
+ if (Debug) Console.Error.WriteLine("Euler: Queue from Axiom: " + r);
+ }
+ }
+ }
+ }
+ }
+
+ return evidence;
+ }
+
+ private static bool unify(Resource s, Hashtable senv, Resource d, Hashtable denv, bool f) {
+ if (s is Variable) {
+ Resource sval = evaluate(s, senv);
+ if (sval != null) return unify(sval, senv, d, denv, f);
+ else return true;
+ } else if (d is Variable) {
+ Resource dval = evaluate(d, denv);
+ if (dval != null) {
+ return unify(s, senv, dval, denv, f);
+ } else {
+ if (f) denv[d] = evaluate(s, senv);
+ return true;
+ }
+ } else if (s.Equals(d)) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ private static bool unify(Statement s, Hashtable senv, Statement d, Hashtable denv, bool f) {
+ if (s == Statement.All) return false;
+ if (!unify(s.Subject, senv, d.Subject, denv, f)) return false;
+ if (!unify(s.Predicate, senv, d.Predicate, denv, f)) return false;
+ if (!unify(s.Object, senv, d.Object, denv, f)) return false;
+ return true;
+ }
+
+ private static Resource evaluate(Resource t, Hashtable env) {
+ if (t is Variable) {
+ Resource val = (Resource)env[t];
+ if (val != null)
+ return evaluate(val, env);
+ else
+ return null;
+ }
+ return t;
+ }
+
+ private static Statement evaluate(Statement t, Hashtable env) {
+ Resource s = evaluate(t.Subject, env);
+ Resource p = evaluate(t.Predicate, env);
+ Resource o = evaluate(t.Object, env);
+
+ // If we can't represent this statement because it requires
+ // putting a literal in subject or predicate position,
+ // return Statement.All (i.e. null S/P/O).
+ if (s is Literal || p is Literal)
+ return Statement.All;
+
+ return new Statement((Entity)s, (Entity)p, o, Statement.DefaultMeta);
+ }
+
+ // The next few routines convert a set of axioms from a StatementSource
+ // into a data structure of use for the algorithm, with Sequents and things.
+
+
+
+ private static Hashtable RulesToCases(StatementSource rules) {
+ Hashtable cases = new Hashtable();
+ MemoryStore rules_store = new MemoryStore(rules);
+ foreach (Statement p in rules_store) {
+ if (p.Meta == Statement.DefaultMeta) {
+ if (p.Predicate == entLOGIMPLIES && p.Object is Entity) {
+ MemoryStore body = new MemoryStore();
+ MemoryStore head = new MemoryStore();
+
+ rules_store.Select(new Statement(null, null, null, (Entity)p.Subject), new RemoveMeta(body));
+ rules_store.Select(new Statement(null, null, null, (Entity)p.Object), new RemoveMeta(head));
+
+ // Any variables in the head not bound in the body represent existentially closed bnodes.
+ // (Euler's OWL test case does this. Wish they had used bnodes instead of vars...)
+ ResSet bodyvars = new ResSet();
+ foreach (Statement b in body) {
+ if (b.Subject is Variable) bodyvars.Add(b.Subject);
+ if (b.Predicate is Variable) bodyvars.Add(b.Predicate);
+ if (b.Object is Variable) bodyvars.Add(b.Object);
+ }
+ foreach (Entity v in head.GetEntities()) {
+ if (v is Variable && !bodyvars.Contains(v))
+ head.Replace(v, new BNode(((Variable)v).LocalName));
+ }
+
+ // Replace (...) lists in the body that are tied to the subjects
+ // of user predicates with callArgs objects.
+ Hashtable callArgs = new Hashtable();
+ CollectCallArgs(body, callArgs);
+
+ // Rules can't have more than one statement in their
+ // consequent. The best we can do is break up
+ // the consequent into multiple rules. (Since all head
+ // variables are bound in body, it's equivalent...?)
+ foreach (Statement h in head)
+ AddSequent(cases, new Sequent(h, body.ToArray(), callArgs));
+ } else {
+ AddSequent(cases, new Sequent(p, new Statement[0], null));
+ }
+ }
+ }
+
+ return cases;
+ }
+
+ private class RemoveMeta : StatementSink {
+ StatementSink sink;
+ public RemoveMeta(StatementSink s) { sink = s; }
+ public bool Add(Statement s) {
+ s.Meta = Statement.DefaultMeta;
+ return sink.Add(s);
+ }
+ }
+
+ private static void CollectCallArgs(MemoryStore body, Hashtable callArgs) {
+ foreach (Statement s in new MemoryStore(body)) { // make a copy since we remove statements from b within
+ if (FindUserPredicate(s.Predicate) == null) continue;
+
+ Entity subj = s.Subject;
+ if (!(subj is BNode)) continue; // it would be nice to exclude variables, but we've already converted bnodes to variables
+
+ BNode head = (BNode)subj;
+
+ ArrayList argList = new ArrayList();
+
+ while (true) {
+ Resource[] objs = body.SelectObjects(subj, entRDFFIRST);
+ if (objs.Length == 0) break; // if this is the first iteration, then we're just not looking at a list
+ // on later iterations, the list must not be well-formed, so we'll just
+ // stop reading it here
+
+ argList.Add(objs[0]); // assume a properly formed list
+ body.Remove(new Statement(subj, entRDFFIRST, null));
+
+ Resource[] rests = body.SelectObjects(subj, entRDFREST);
+ if (rests.Length == 0) break; // not well formed
+ body.Remove(new Statement(subj, entRDFREST, null));
+ if (rests[0] == entRDFNIL) break; // that's the end of the list
+ if (!(rests[0] is Entity)) break; // also not well formed
+
+ subj = (Entity)rests[0];
+ }
+
+ if (argList.Count > 0)
+ callArgs[head] = argList.ToArray(typeof(Resource));
+ }
+ }
+
+ private static void AddSequent(Hashtable cases, Sequent s) {
+ object key = s.head.Predicate;
+ if (key is Variable) key = "WILDCARD";
+ ArrayList list = (ArrayList)cases[key];
+ if (list == null) {
+ list = new ArrayList();
+ cases[key] = list;
+ }
+ list.Add(s);
+ }
+
+ // Lastly, we have special and built-in predicates.
+
+ private static RdfRelation FindUserPredicate(Entity predicate) {
+ if (predicate.Uri == null) return null;
+ if (builtInRelations.ContainsKey(predicate.Uri))
+ return (RdfRelation)builtInRelations[predicate.Uri];
+ return null;
+ }
+ }
+}
Added: trunk/semweb/GraphMatch.cs
==============================================================================
--- (empty file)
+++ trunk/semweb/GraphMatch.cs Wed May 7 10:59:52 2008
@@ -0,0 +1,680 @@
+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 + (returnLimit > 0 ? " [limit " + returnLimit + "/" + localLimit + "]" : "") + " => " + 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.
+ // This involves creating binding rows that:
+ // Match on the intersection of variables from the two sets
+ // But include only interestingVariables variables, which
+ // are distinguished variables plus any variables we will
+ // encounter in later parts of the query
+
+ // Get a list of variables the old and new have in common, and
+ // a list for the common variables of their indexes in the old
+ // and new sets
+ int nCommonVars;
+ int[,] commonVars = IntersectVariables(bindings.Variables, vars, out nCommonVars);
+
+ ResSet retainedVariables = null;
+ if (interestingVariables != null) {
+ retainedVariables = new ResSet();
+ foreach (Variable v in bindings.Variables)
+ if (interestingVariables.Contains(v))
+ retainedVariables.Add(v);
+ foreach (Variable v in vars)
+ if (interestingVariables.Contains(v))
+ retainedVariables.Add(v);
+ }
+
+ BindingSet newbindings = new BindingSet();
+ if (retainedVariables == null)
+ newbindings.Variables = new Variable[bindings.Variables.Length + vars.Length - nCommonVars];
+ else
+ newbindings.Variables = new Variable[retainedVariables.Count];
+
+ // Make a list of the variables in the final set, and a mapping
+ // from indexes in the old/new set to indexes in the final set.
+ int ctr = 0;
+ int[] variableMapLeft = new int[bindings.Variables.Length];
+ for (int i = 0; i < variableMapLeft.Length; i++) {
+ if (retainedVariables == null || retainedVariables.Contains(bindings.Variables[i])) {
+ variableMapLeft[i] = ctr;
+ newbindings.Variables[ctr++] = bindings.Variables[i];
+ } else {
+ variableMapLeft[i] = -1;
+ }
+ }
+
+ int[] variableMapRight = new int[vars.Length];
+ for (int i = 0; i < variableMapRight.Length; i++) {
+ if ((retainedVariables == null || retainedVariables.Contains(vars[i]))
+ && Array.IndexOf(newbindings.Variables, vars[i]) == -1) {
+ variableMapRight[i] = ctr;
+ newbindings.Variables[ctr++] = vars[i];
+ } else {
+ variableMapRight[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];
+ CopyValues(left == null ? null : left.Values, right.Values, newValues, variableMapLeft, variableMapRight);
+
+ nMatches++;
+ if (!quickDupCheckIsDup(newbindings, newValues, nMatches))
+ 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];
+ CopyValues(left.Values, right.Values, newValues, variableMapLeft, variableMapRight);
+
+ nMatches++;
+ if (!quickDupCheckIsDup(newbindings, newValues, nMatches))
+ 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 *= 5;
+
+ } // 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;
+ }
+
+ #if !DOTNET2
+ static void CopyValues(Resource[] left, Resource[] right, Resource[] newValues, int[] variableMapLeft, int[] variableMapRight) {
+ #else
+ static void CopyValues(IList<Resource> left, IList<Resource> right, Resource[] newValues, int[] variableMapLeft, int[] variableMapRight) {
+ #endif
+ for (int i = 0; i < variableMapLeft.Length; i++)
+ if (variableMapLeft[i] != -1)
+ newValues[variableMapLeft[i]] = left[i];
+ for (int i = 0; i < variableMapRight.Length; i++)
+ if (variableMapRight[i] != -1)
+ newValues[variableMapRight[i]] = right[i];
+ }
+
+ static bool quickDupCheckIsDup(BindingSet newbindings, Resource[] newValues, int nMatches) {
+ // 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++) {
+ 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: trunk/semweb/Inference.cs
==============================================================================
--- trunk/semweb/Inference.cs (original)
+++ trunk/semweb/Inference.cs Wed May 7 10:59:52 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: trunk/semweb/Interfaces.cs
==============================================================================
--- (empty file)
+++ trunk/semweb/Interfaces.cs Wed May 7 10:59:52 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: trunk/semweb/LiteralFilters.cs
==============================================================================
--- trunk/semweb/LiteralFilters.cs (original)
+++ trunk/semweb/LiteralFilters.cs Wed May 7 10:59:52 2008
@@ -110,6 +110,17 @@
}
}
+ public class StringEndsWithFilter : LiteralFilter {
+ public readonly string Pattern;
+ public StringEndsWithFilter(string pattern) {
+ Pattern = pattern;
+ }
+ public override bool Filter(Literal resource, SelectableSource targetModel) {
+ string v = resource.Value;
+ return v.EndsWith(Pattern);
+ }
+ }
+
public class NumericCompareFilter : LiteralFilter {
public readonly Decimal Number;
public readonly CompType Type;
@@ -125,7 +136,7 @@
Decimal i = Decimal.Parse(v);
int c = i.CompareTo(Number);
return CompareFilter(c, Type);
- } catch (Exception e) {
+ } catch (Exception) {
return false;
}
}
@@ -146,7 +157,7 @@
DateTime i = DateTime.Parse(v);
int c = i.CompareTo(Value);
return CompareFilter(c, Type);
- } catch (Exception e) {
+ } catch (Exception) {
return false;
}
}
@@ -167,7 +178,7 @@
TimeSpan i = TimeSpan.Parse(v);
int c = i.CompareTo(Value);
return CompareFilter(c, Type);
- } catch (Exception e) {
+ } catch (Exception) {
return false;
}
}
Modified: trunk/semweb/Makefile.am
==============================================================================
--- trunk/semweb/Makefile.am (original)
+++ trunk/semweb/Makefile.am Wed May 7 10:59:52 2008
@@ -3,6 +3,11 @@
ASSEMBLY_NAME = SemWeb
ASSEMBLY_SOURCES = \
+ $(srcdir)/SparqlClient.cs \
+ $(srcdir)/Euler.cs \
+ $(srcdir)/Interfaces.cs \
+ $(srcdir)/GraphMatch.cs \
+ $(srcdir)/SpecialRelations.cs \
$(srcdir)/AssemblyInfo.cs \
$(srcdir)/NamespaceManager.cs \
$(srcdir)/Util.cs \
@@ -18,14 +23,11 @@
$(srcdir)/RdfWriter.cs \
$(srcdir)/RdfXmlWriter.cs \
$(srcdir)/N3Writer.cs \
- $(srcdir)/RSquary.cs \
$(srcdir)/LiteralFilters.cs \
$(srcdir)/Query.cs \
$(srcdir)/Inference.cs \
$(srcdir)/RDFS.cs \
- $(srcdir)/Algos.cs \
- $(srcdir)/Remote.cs \
- $(srcdir)/XPathSemWebNavigator.cs
+ $(srcdir)/Algos.cs
REFS = \
-r:System.Data \
Modified: trunk/semweb/MemoryStore.cs
==============================================================================
--- trunk/semweb/MemoryStore.cs (original)
+++ trunk/semweb/MemoryStore.cs Wed May 7 10:59:52 2008
@@ -6,8 +6,81 @@
using SemWeb.Util;
namespace SemWeb {
- public class MemoryStore : Store, SupportsPersistableBNodes, IEnumerable {
- StatementList statements;
+ 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();
Hashtable statementsAboutObject = new Hashtable();
@@ -19,29 +92,33 @@
string guid = null;
Hashtable pbnodeToId = null;
- Hashtable pbnodeFromId = null;
-
- public MemoryStore() {
+ Hashtable pbnodeFromId = null;
+
+ 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() {
+ 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();
@@ -67,9 +149,14 @@
from[entity] = ret;
}
return ret;
+ }
+
+ bool StatementSink.Add(Statement statement) {
+ Add(statement);
+ return true;
}
- public override void Add(Statement statement) {
+ 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];
@@ -105,11 +192,16 @@
GetIndexArray(statementsAboutObject, statement.Object).Remove(statement);
}
}
+ }
+
+ public void RemoveAll(Statement[] statements) {
+ foreach (Statement t in statements)
+ Remove(t);
}
- public override Entity[] GetEntities() {
+ 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));
}
@@ -135,9 +227,13 @@
private void ShorterList(ref StatementList list1, StatementList list2) {
if (list2.Count < list1.Count)
list1 = list2;
+ }
+
+ public void Select(StatementSink result) {
+ Select(Statement.All, result);
}
- public override void Select(Statement template, StatementSink result) {
+ public void Select(Statement template, StatementSink result) {
StatementList source = statements;
// The first time select is called, turn indexing on for the store.
@@ -155,17 +251,26 @@
if (template.Subject != null) ShorterList(ref source, GetIndexArray(statementsAboutSubject, template.Subject));
else if (template.Object != null) ShorterList(ref source, GetIndexArray(statementsAboutObject, template.Object));
- if (source == null) return;
+ 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;
+ 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),
@@ -180,9 +285,23 @@
if (filter.LiteralFilters != null && !LiteralFilter.MatchesFilters(st.Object, filter.LiteralFilters, this)) continue;
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: trunk/semweb/N3Reader.cs
==============================================================================
--- trunk/semweb/N3Reader.cs (original)
+++ trunk/semweb/N3Reader.cs Wed May 7 10:59:52 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); } }
}
@@ -64,8 +65,8 @@
private bool ReadStatement(ParseContext context) {
Location loc = context.Location;
- bool reverse;
- Resource subject = ReadResource(context, out reverse);
+ bool reverse, forgetBNode;
+ Resource subject = ReadResource(context, true, out reverse, out forgetBNode);
if (subject == null) return false;
if (reverse) OnError("is...of not allowed on a subject", loc);
@@ -75,7 +76,8 @@
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);
+ bool fb2;
+ Resource uri = ReadResource(context, false, out reverse, out fb2);
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,15 +109,32 @@
return true;
}
+ if ((object)subject == (object)BaseResource) {
+ loc = context.Location;
+ bool fb2;
+ Resource uri = ReadResource(context, false, out reverse, out fb2);
+ 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.
if (NextPunc(context.source) == '.') {
context.source.Read();
+ if (forgetBNode) DoForget(subject, context);
return true;
}
if (NextPunc(context.source) == '}') {
context.source.Read();
+ if (forgetBNode) DoForget(subject, context);
return false; // end of block
}
@@ -125,6 +144,7 @@
if (period != '.' && period != '}')
OnError("Expected a period but found '" + period + "'", loc);
if (period == '}') return false;
+ if (forgetBNode) DoForget(subject, context);
return true;
}
@@ -146,12 +166,18 @@
}
private char ReadPredicate(Resource subject, ParseContext context) {
- bool reverse;
+ bool reverse, forgetBNode;
Location loc = context.Location;
- Resource predicate = ReadResource(context, out reverse);
+ Resource predicate = ReadResource(context, false, out reverse, out forgetBNode);
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);
@@ -161,24 +187,30 @@
if (punctuation != '.' && punctuation != ';' && punctuation != ']' && punctuation != '}')
OnError("Expecting a period, semicolon, comma, close-bracket, or close-brace but found '" + punctuation + "'", loc);
+ if (forgetBNode) DoForget(predicate, context);
+
return punctuation;
}
private void ReadObject(Resource subject, Entity predicate, ParseContext context, bool reverse) {
- bool reverse2;
+ bool reverse2, forgetBNode;
Location loc = context.Location;
- Resource value = ReadResource(context, out reverse2);
+ Resource value = ReadResource(context, false, out reverse2, out forgetBNode);
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 {
if (value is Literal) OnError("A literal cannot be the object of a reverse-predicate statement", loc);
Add(context.store, new Statement((Entity)value, predicate, subject, context.meta), loc);
}
+
+ if (forgetBNode) DoForget(value, context);
}
private void ReadWhitespace(MyReader source) {
@@ -356,7 +388,7 @@
// A variable: \?[a-zA-Z_][a-zA-Z0-9_]*
while (true) {
int c = source.Peek();
- if (c == -1 || (!char.IsLetterOrDigit((char)c) && c != '-' && c != '_' && c != ':')) break;
+ if (c == -1 || (!Entity.ValidateUriIsIUnreserved((char)c) && c != ':') || c == '.') break;
b.Append((char)source.Read());
}
@@ -380,6 +412,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,18 +442,18 @@
return b.ToString();
}
- private Resource ReadResource(ParseContext context, out bool reverse) {
+ private Resource ReadResource(ParseContext context, bool allowDirective, out bool reverse, out bool forgetBNode) {
Location loc = context.Location;
- Resource res = ReadResource2(context, out reverse);
+ Resource res = ReadResource2(context, allowDirective, out reverse, out forgetBNode);
ReadWhitespace(context.source);
while (context.source.Peek() == '!' || context.source.Peek() == '^' || (context.source.Peek() == '.' && context.source.Peek2() != -1 && char.IsLetter((char)context.source.Peek2())) ) {
int pathType = context.source.Read();
- bool reverse2;
+ bool reverse2, forgetBNode2;
loc = context.Location;
- Resource path = ReadResource2(context, out reverse2);
+ Resource path = ReadResource2(context, false, out reverse2, out forgetBNode2);
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);
@@ -432,7 +469,11 @@
Add(context.store, s, loc);
+ if (forgetBNode) DoForget(res, context);
+ if (forgetBNode2) DoForget(path, context);
+
res = anon;
+ forgetBNode = true;
ReadWhitespace(context.source);
}
@@ -473,8 +514,9 @@
}
}
- private Resource ReadResource2(ParseContext context, out bool reverse) {
+ private Resource ReadResource2(ParseContext context, bool allowDirective, out bool reverse, out bool forgetBNode) {
reverse = false;
+ forgetBNode = false;
Location loc = context.Location;
@@ -485,15 +527,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 +572,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, out forgetBNode);
if (str == "@is") {
// Reverse predicate
bool reversetemp;
- Resource pred = ReadResource2(context, out reversetemp);
+ Resource pred = ReadResource2(context, false, out reversetemp, out forgetBNode);
reverse = true;
string of = ReadToken(context.source, context) as string;
@@ -541,6 +602,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);
}
@@ -576,6 +640,7 @@
} else {
context.source.Read();
}
+ forgetBNode = true;
return ret;
}
@@ -583,15 +648,16 @@
if (str == "(") {
// A list
- Entity ent = null;
+ Entity head = null, ent = null;
while (true) {
- bool rev2;
- Resource res = ReadResource(context, out rev2);
+ bool rev2, fb2;
+ Resource res = ReadResource(context, false, out rev2, out fb2);
if (res == null)
break;
if (ent == null) {
ent = new BNode();
+ head = ent;
} else {
Entity sub = new BNode();
Add(context.store, new Statement(ent, entRDFREST, sub, context.meta), loc);
@@ -599,12 +665,14 @@
}
Add(context.store, new Statement(ent, entRDFFIRST, res, context.meta), loc);
+ if (fb2) DoForget(res, context);
}
- if (ent == null) // No list items.
- ent = entRDFNIL; // according to Turtle spec
+ if (head == null) // No list items.
+ head = 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 +684,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 +707,18 @@
// 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");
+ }
+
+ //BOOLEAN LITERAL
+
+ if (str == "true" || str == "false") {
+ return new Literal(str,null,NS.XMLSCHEMA+"boolean");
+ }
// If @keywords is used, alphanumerics that aren't keywords
// are local names in the default namespace.
@@ -653,27 +735,27 @@
}
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);
- }
+ }*/
-
+ void DoForget(Resource ent, ParseContext context) {
+ CanForgetBNodes x = context.store as CanForgetBNodes;
+ if (x == null) return;
+ x.ForgetBNode((BNode)ent);
+ }
}
internal class MyReader {
@@ -721,6 +803,7 @@
return c;
}
+
}
internal struct Location {
Modified: trunk/semweb/N3Writer.cs
==============================================================================
--- trunk/semweb/N3Writer.cs (original)
+++ trunk/semweb/N3Writer.cs Wed May 7 10:59:52 2008
@@ -6,11 +6,12 @@
using SemWeb;
namespace SemWeb {
- public class N3Writer : RdfWriter {
+ public class N3Writer : RdfWriter, CanForgetBNodes {
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,18 @@
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);
+ }
+ }
+
+ void CanForgetBNodes.ForgetBNode(BNode bnode) {
+ anonNames.Remove(bnode);
+ anonNameMap.Remove(bnode);
+ }
}
}
Modified: trunk/semweb/NamespaceManager.cs
==============================================================================
--- trunk/semweb/NamespaceManager.cs (original)
+++ trunk/semweb/NamespaceManager.cs Wed May 7 10:59:52 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: trunk/semweb/Query.cs
==============================================================================
--- trunk/semweb/Query.cs (original)
+++ trunk/semweb/Query.cs Wed May 7 10:59:52 2008
@@ -1,957 +1,293 @@
-using System;
-using System.Collections;
-using System.IO;
-
-using SemWeb;
-using SemWeb.Filters;
-using SemWeb.Stores;
-using SemWeb.Util;
-
-namespace SemWeb.Query {
-
- public class QueryFormatException : ApplicationException {
- public QueryFormatException(string message) : base(message) { }
- public QueryFormatException(string message, Exception cause) : base(message, cause) { }
- }
-
- public class QueryExecutionException : ApplicationException {
- public QueryExecutionException(string message) : base(message) { }
- public QueryExecutionException(string message, Exception cause) : base(message, cause) { }
+using System;
+using System.IO;
+
+using SemWeb;
+using SemWeb.Filters;
+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) { }
+ }
+
+ public class QueryExecutionException : ApplicationException {
+ public QueryExecutionException(string message) : base(message) { }
+ public QueryExecutionException(string message, Exception cause) : base(message, cause) { }
}
public abstract class RdfFunction {
public abstract string Uri { get; }
public abstract Resource Evaluate(Resource[] args);
- }
-
- public abstract class Query {
- int start = 0;
- int limit = -1;
- Entity queryMeta = null;
-
- public int ReturnStart { get { return start; } set { start = value; if (start < 0) start = 0; } }
-
- public int ReturnLimit { get { return limit; } set { limit = value; } }
-
- public Entity QueryMeta { get { return queryMeta; } set { queryMeta = value; } }
-
- public virtual void Run(SelectableSource source, TextWriter output) {
- Run(source, new SparqlXmlQuerySink(output));
- }
-
- public abstract void Run(SelectableSource source, QueryResultSink resultsink);
-
- 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;
- }
-
- private void CheckInit() {
- lock (sync) {
- if (!init) {
- Init();
- init = true;
- }
- }
- }
-
- 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 GraphMatch(RdfReader query) :
- this(new MemoryStore(query),
- query.BaseUri == null ? null : new Entity(query.BaseUri)) {
- }
-
- public GraphMatch(Store queryModel) : this(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);
- }
-
- // 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);
- }
- }
-
- 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 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;
- }
-
-
- result.Finished();
- }
-
- 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;
- }
- }
-
- 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;
- }
- }
- }
-
- 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;
- }
-
- static Resource[] ResourceArrayNull = new Resource[] { null };
-
- 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;
- }
-
- 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;
- }
- }
-
- 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 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) {
- // 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);
- }
- return template;
- }
- }
-}
-
+ }
+
+ public abstract class Query {
+ int start = 0;
+ int limit = -1;
+ Entity queryMeta = null;
+
+ public int ReturnStart { get { return start; } set { start = value; if (start < 0) start = 0; } }
+
+ public int ReturnLimit { get { return limit; } set { limit = value; } }
+
+ 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));
+ }
+
+ public abstract void Run(SelectableSource source, QueryResultSink resultsink);
+
+ public abstract string GetExplanation();
+ }
+
+ public abstract class QueryResultSink {
+ public virtual void Init(Variable[] variables) {
+ }
+
+ public abstract bool Add(VariableBindings result);
+
+ public virtual void Finished() {
+ }
+
+ public virtual void AddComments(string comments) {
+ }
+ }
+
+ public class QueryResultBuffer : QueryResultSink
+ #if !DOTNET2
+ , IEnumerable
+ #else
+ , IEnumerable<VariableBindings>
+ #endif
+ {
+ Variable[] variables;
+
+ #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);
+ }
+
+ public override bool Add(VariableBindings result) {
+ bindings.Add(result);
+ return true;
+ }
+
+ public override void AddComments(string comment) {
+ comments.Add(comment);
+ }
+
+ public Variable[] Variables { get { return variables; } }
+
+ #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
+
+ System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
+ return Bindings.GetEnumerator();
+ }
+ #if DOTNET2
+ IEnumerator<VariableBindings> IEnumerable<VariableBindings>.GetEnumerator() {
+ return Bindings.GetEnumerator();
+ }
+ #endif
+ }
+
+ public class VariableBindings {
+ Variable[] vars;
+ Resource[] vals;
+
+ 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.");
+ }
+
+ public int Count { get { return vars.Length; } }
+
+ #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
+
+ public Resource this[Variable variable] {
+ get {
+ for (int i = 0; i < vars.Length; i++)
+ if (vars[i] == variable)
+ return vals[i];
+ throw new ArgumentException();
+ }
+ }
+
+ 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 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.
+ 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: trunk/semweb/RDFS.cs
==============================================================================
--- trunk/semweb/RDFS.cs (original)
+++ trunk/semweb/RDFS.cs Wed May 7 10:59:52 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: trunk/semweb/README
==============================================================================
--- trunk/semweb/README (original)
+++ trunk/semweb/README Wed May 7 10:59:52 2008
@@ -1,16 +1,126 @@
-Semantic Web Library for C#/.NET
-================================
+SemWeb: A Semantic Web Library for C#/.NET
+==========================================
-By Joshua Tauberer <tauberer for net>
+By Joshua Tauberer <http://razor.occams.info>
-http://taubz.for.net/code/semweb
+http://razor.occams.info/code/semweb
-Copyright 2005 Joshua Tauberer. This package is released
-under the terms of the Creative Commons Attribution License:
-
- http://creativecommons.org/licenses/by/2.0/
-
-The license basically means you can copy, distribute and
-modify this package however you like, but you need to
-give me due credit. I think that's fair.
+USAGE
+-----
+
+SemWeb is a library for use in other C# and .NET applications on either
+Mono or Microsoft's .NET. The library comes as a collection of
+.NET assemblies. There are two directories for the compiled assemblies:
+
+ bin: Binaries compiled for .NET 1.1 (no generics).
+ bin_generics: Binaries compiled for .NET 2.0 (generics).
+
+Each directory contains the files:
+
+ SemWeb.dll
+ This is the core library.
+
+ SemWeb.MySQLStore.dll, SemWeb.PostgreSQLStore.dll, SemWeb.SqliteStore.dll
+ Assemblies providing SQLStore implementations for
+ those RDBMSs. (details to be entered here later)
+
+ SemWeb.Sparql.dll
+ An assembly providing the SPARQL engine class. It requires
+ the auxiliary assemblies listed next.
+
+ IKVM.GNU.Classpath.dll, IKVM.Runtime.dll
+ sparql-core.dll
+ Auxiliary assemblies required for SPARQL.
+
+ rdfstorage.exe
+ A command-line tool for converting files between RDF formats
+ and loading RDF files into databases.
+
+ rdfquery.exe
+ A command-line tool for running SPARQL and simple graph
+ matching (in N3) queries against a data source.
+
+ euler.exe
+ A command-line tool for performing general rule-based
+ reasoning.
+
+ Mono.GetOptions.dll
+ This library from Mono is a dependency of all of the command-line
+ tools listed above.
+
+ .mdb files are debugging symbol files for Mono. Running
+ under MS .NET, they are useless. Running under Mono, they
+ are optional unless you want debugging info in stack traces.
+
+ To use any of the .dll assemblies, reference them in your
+ project, and make sure they and any of their dependencies
+ are findable at runtime (which in MS .NET is usually the
+ case if you just reference them).
+
+
+DOCUMENTATION
+-------------
+
+For more information, view doc/index.html and the API documentation
+in apidocs/index.html.
+
+
+BUILD INSTRUCTIONS
+------------------
+
+Run make if you're in Linux. Nothing complicated here. You'll need
+Mono installed (and the MySQL/Connector and Sqlite Client DLLs for SQL
+database support, optionally). It'll build .NET 1.1 binaries to the
+bin directory and .NET 2.0 binaries with generics to the bin_generics
+directory.
+
+A MonoDevelop solution file (semweb.mds) and a Visual Studio 2005 solution
+file (SemWeb.sln) are included too. They build .NET 2.0 binaries with
+generics to the bin_generics directory.
+
+If you build the MySQL and SQLite .cs files, you'll need to reference
+MySQL's MySql.Data.dll and Sqlite Client assemblies (see www.mono-project.com). Otherwise just leave out those .cs files.
+Put MySql.Data.dll in a "lib" directory within the SemWeb directory.
+
+The sources are set up with a conditional compilation flag "DOTNET2" so
+that the sources can be compiled for both .NET 1.1 and 2.0, taking
+advantage of generics.
+
+
+LICENSE
+-------
+
+The source files and binaries are all GPL-compatible.
+
+Most of the source files were written by me, some source files were
+written by or are derived from other work, and the binaries are a mix of
+the above. So the particular license that applies in each case may be
+different. However, everything included can be reused under the terms
+of the GPL (if not something more permissive, depending on what it is).
+
+The portions of this library not written by someone else are Copyright
+2005-2008 Joshua Tauberer, and are dual-licensed under both the GNU GPL
+(version 2 or later) and the Creative Commons Attribution License. All
+source files not listed below were written originally by me. Thus for
+those source files written by me, you have two license options.
+
+The following components of this library are derived from other works:
+
+sparql-core.dll is based on the SPARQL Engine by Ryan Levering,
+which is covered by the GNU LGPL. The original Java JAR was
+coverted to a .NET assembly using IKVM (see below). Actually, I've
+made numerous changes to the library so it can take advantage of
+faster API paths in SemWeb.
+See: http://sparql.sourceforge.net/
+
+IKVM*.dll are auxiliary assemblies for running the SPARQL
+engine. IKVM was written by Jeroen Frijters. See http://www.ikvm.net.
+The IVKM license is the zlib license, which is GPL compatible.
+
+Euler.cs is adapted from Jos De Roo's JavaScript Euler inferencing
+engine. See: http://www.agfa.com/w3c/euler/ The original source
+code (and thus this derived file) was licensed under the W3C Software
+License, which is GPL compatible.
+SQLServerStore.cs was contributed by Khaled Hammouda and is licensed
+under the GPL.
Modified: trunk/semweb/RdfReader.cs
==============================================================================
--- trunk/semweb/RdfReader.cs (original)
+++ trunk/semweb/RdfReader.cs Wed May 7 10:59:52 2008
@@ -1,148 +1,194 @@
-using System;
-using System.Collections;
-using System.IO;
-using System.Web;
-
-namespace SemWeb {
- public class ParserException : ApplicationException {
- public ParserException (string message) : base (message) {}
- public ParserException (string message, Exception cause) : base (message, cause) {}
- }
-
- public abstract class RdfReader : StatementSource, IDisposable {
- Entity meta = Statement.DefaultMeta;
- string baseuri = null;
- ArrayList warnings = new ArrayList();
- Hashtable variables = new Hashtable();
- bool reuseentities = false;
- NamespaceManager nsmgr = new NamespaceManager();
-
- public Entity Meta {
- get {
- return meta;
- }
- set {
- meta = value;
- }
- }
-
- public string BaseUri {
- get {
- return baseuri;
- }
- set {
- baseuri = value;
- }
- }
-
- public bool ReuseEntities {
- get {
- return reuseentities;
- }
- set {
- reuseentities = value;
- }
- }
-
- bool StatementSource.Distinct { get { return false; } }
-
- public NamespaceManager Namespaces { get { return nsmgr; } }
-
- public ICollection Variables { get { return variables.Keys; } }
-
- public IList Warnings { get { return ArrayList.ReadOnly(warnings); } }
-
- protected void AddVariable(Variable variable) {
- variables[variable] = variable;
- }
-
- public abstract void Select(StatementSink sink);
-
- public virtual void Dispose() {
- }
-
- public static RdfReader Create(string type, string source) {
- 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);
- }
- }
-
- public static RdfReader LoadFromUri(Uri webresource) {
- // TODO: Add Accept header for HTTP resources.
-
- System.Net.WebRequest rq = System.Net.WebRequest.Create(webresource);
- System.Net.WebResponse resp = rq.GetResponse();
-
- string mimetype = resp.ContentType;
- if (mimetype.IndexOf(';') > -1)
- mimetype = mimetype.Substring(0, mimetype.IndexOf(';'));
-
- switch (mimetype.Trim()) {
- case "text/xml":
- case "application/xml":
- case "application/rss+xml":
- case "application/rdf+xml":
- return 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));
- }
-
- if (webresource.LocalPath.EndsWith(".rdf") || webresource.LocalPath.EndsWith(".xml") || webresource.LocalPath.EndsWith(".rss"))
- return 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));
-
- throw new InvalidOperationException("Could not determine the RDF format of the resource.");
- }
-
- internal static TextReader GetReader(string file) {
- if (file == "-") return Console.In;
- return new StreamReader(file);
- }
-
- protected void OnWarning(string message) {
- warnings.Add(message);
- }
-
- internal string GetAbsoluteUri(string baseuri, string uri) {
+using System;
+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 {
+ public ParserException (string message) : base (message) {}
+ public ParserException (string message, Exception cause) : base (message, cause) {}
+ }
+
+ public abstract class RdfReader : StatementSource, IDisposable {
+ Entity meta = Statement.DefaultMeta;
+ string baseuri = null;
+ WarningsList warnings = new WarningsList();
+ VariableSet variables = new VariableSet();
+ bool reuseentities = false;
+ NamespaceManager nsmgr = new NamespaceManager();
+
+ public Entity Meta {
+ get {
+ return meta;
+ }
+ set {
+ meta = value;
+ }
+ }
+
+ public string BaseUri {
+ get {
+ return baseuri;
+ }
+ set {
+ baseuri = value;
+ }
+ }
+
+ public bool ReuseEntities {
+ get {
+ return reuseentities;
+ }
+ set {
+ reuseentities = value;
+ }
+ }
+
+ bool StatementSource.Distinct { get { return false; } }
+
+ public NamespaceManager Namespaces { get { return nsmgr; } }
+
+ 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;
+ }
+
+ public abstract void Select(StatementSink sink);
+
+ 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":
+ return new RdfXmlReader(source);
+ case "n3":
+ return new N3Reader(source);
+ default:
+ 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.
+
+ System.Net.WebRequest rq = System.Net.WebRequest.Create(webresource);
+ System.Net.WebResponse resp = rq.GetResponse();
+
+ string mimetype = resp.ContentType;
+ if (mimetype.IndexOf(';') > -1)
+ mimetype = mimetype.Substring(0, mimetype.IndexOf(';'));
+
+ mimetype = NormalizeMimeType(mimetype.Trim());
+
+ RdfReader reader;
+
+ if (mimetype == "xml" || mimetype == "application/rss+xml")
+ reader = new RdfXmlReader(resp.GetResponseStream());
+
+ else if (mimetype == "n3")
+ reader = new N3Reader(new StreamReader(resp.GetResponseStream(), System.Text.Encoding.UTF8));
+
+ else if (webresource.LocalPath.EndsWith(".rdf") || webresource.LocalPath.EndsWith(".xml") || webresource.LocalPath.EndsWith(".rss"))
+ reader = new RdfXmlReader(resp.GetResponseStream());
+
+ 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));
+
+ 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) {
+ if (file == "-") return Console.In;
+ return new StreamReader(file);
+ }
+
+ protected void OnWarning(string message) {
+ warnings.Add(message);
+ }
+
+ 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;
- try {
- 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) {
- return baseuri + uri;
- }
- }
-
- }
-
- internal class MultiRdfReader : RdfReader {
- private ArrayList parsers = new ArrayList();
-
- public ArrayList Parsers { get { return parsers; } }
-
- public override void Select(StatementSink storage) {
- foreach (RdfReader p in Parsers)
- p.Select(storage);
- }
- }
-}
-
+ }
+ if (uri.IndexOf(':') != -1) return uri;
+ try {
+ UriBuilder b = new UriBuilder(baseuri);
+ b.Fragment = null; // per W3 RDF/XML test suite
+ return new Uri(b.Uri, uri, true).ToString();
+ } catch (UriFormatException) {
+ return baseuri + uri;
+ }
+ }
+
+ }
+
+ internal class MultiRdfReader : RdfReader {
+ private ArrayList parsers = new ArrayList();
+
+ public ArrayList Parsers { get { return parsers; } }
+
+ public override void Select(StatementSink storage) {
+ foreach (RdfReader p in Parsers)
+ p.Select(storage);
+ }
+ }
+}
+
Modified: trunk/semweb/RdfWriter.cs
==============================================================================
--- trunk/semweb/RdfWriter.cs (original)
+++ trunk/semweb/RdfWriter.cs Wed May 7 10:59:52 2008
@@ -20,8 +20,8 @@
protected object GetResourceKey(Resource resource) {
return resource.GetResourceKey(this);
- }
-
+ }
+
protected void SetResourceKey(Resource resource, object value) {
resource.SetResourceKey(this, value);
}
@@ -41,7 +41,7 @@
public virtual void Close() {
if (closed) return;
- closed = true;
+ closed = true;
}
public virtual void Write(StatementSource source) {
@@ -51,5 +51,39 @@
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);
+ }
+ }
+ }
+
+ public interface CanForgetBNodes {
+ void ForgetBNode(BNode bnode);
}
}
Modified: trunk/semweb/RdfXmlReader.cs
==============================================================================
--- trunk/semweb/RdfXmlReader.cs (original)
+++ trunk/semweb/RdfXmlReader.cs Wed May 7 10:59:52 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);
@@ -303,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: trunk/semweb/RdfXmlWriter.cs
==============================================================================
--- trunk/semweb/RdfXmlWriter.cs (original)
+++ trunk/semweb/RdfXmlWriter.cs Wed May 7 10:59:52 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: trunk/semweb/Resource.cs
==============================================================================
--- trunk/semweb/Resource.cs (original)
+++ trunk/semweb/Resource.cs Wed May 7 10:59:52 2008
@@ -1,9 +1,14 @@
using System;
using System.Collections;
+using System.Xml;
namespace SemWeb {
- public abstract class Resource : IComparable {
+ public abstract class Resource : IComparable
+#if DOTNET2
+ , IComparable<Resource>
+#endif
+ {
internal object ekKey, ekValue;
internal ArrayList extraKeys;
@@ -18,7 +23,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 +31,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 +41,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 +51,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 +69,14 @@
extraKeys.Add(k);
}
+#if DOTNET2
int IComparable.CompareTo(object other) {
+ return CompareTo((Resource)other);
+ }
+ 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 +91,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 +120,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 +163,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;
+ }
+ internal 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 +401,7 @@
if (LocalName != null)
return "_:" + LocalName;
else
- return "_:bnode" + GetHashCode();
+ return "_:bnode" + Math.Abs(GetHashCode());
}
}
@@ -198,13 +416,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 +429,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 +521,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 +552,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: trunk/semweb/SQLStore.cs
==============================================================================
--- trunk/semweb/SQLStore.cs (original)
+++ trunk/semweb/SQLStore.cs Wed May 7 10:59:52 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,80 @@
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 SupportsLimitClause { get { return true; } }
+ 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);
+ // method: 0 == startswith, 1 == contains, 2 == ends with
+ 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 +243,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 +265,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 +330,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.
@@ -213,20 +369,25 @@
if (ret != null) return (int)ret;
} else {
StringBuilder b = cmdBuffer; cmdBuffer.Length = 0;
- b.Append("SELECT id FROM ");
+ b.Append("SELECT ");
+ if (!SupportsLimitClause)
+ b.Append("TOP 1 ");
+ b.Append("id FROM ");
b.Append(table);
b.Append("_literals WHERE hash =");
- b.Append("\"");
+ b.Append(quote);
b.Append(GetLiteralHash(literal));
- b.Append("\"");
- b.Append(" LIMIT 1;");
+ b.Append(quote);
+ if (SupportsLimitClause)
+ b.Append(" LIMIT 1");
+ b.Append(';');
object id = RunScalar(b.ToString());
if (id != null) return AsInt(id);
}
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 +396,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 +410,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 +428,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 +444,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 +473,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 +488,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 +511,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 +546,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 +567,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 +577,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 +611,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 +672,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 +764,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 +784,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 +797,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 +822,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 +848,7 @@
}
}
- public override void Remove(Statement template) {
+ public void Remove(Statement template) {
Init();
RunAddBuffer();
@@ -657,18 +861,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,8 +912,13 @@
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;
+ if (Debug) Console.Error.WriteLine("(" + id + " " + r + ")");
cmd.Append('(');
cmd.Append(col);
cmd.Append('=');
@@ -783,6 +997,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 +1053,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 +1115,111 @@
}
}
+ 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) {
+ Hashtable seen_e = new Hashtable();
+ Hashtable seen_l = new Hashtable();
+
+ int resStart = 0;
+ while (resStart < resources.Count) {
+
+ StringBuilder cmd_e = new StringBuilder();
+ cmd_e.Append("SELECT id, value FROM ");
+ cmd_e.Append(table);
+ cmd_e.Append("_entities WHERE value IN (");
+ 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;
+
+ int ctr = 0;
+ while (resStart < resources.Count && ctr < 1000) {
+ Resource r = (Resource)resources[resStart++];
+
+ if ((object)r == (object)Statement.DefaultMeta || GetResourceKey(r) != null) // no need to prefetch
+ continue;
+
+ ctr++;
+
+ 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 +1244,46 @@
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 (!SupportsLimitClause && limit >= 1) {
+ cmd.Append("TOP ");
+ cmd.Append(limit);
+ cmd.Append(' ');
+ }
+ if (!HasUniqueStatementsConstraint)
cmd.Append("DISTINCT ");
SelectFilterColumns(columns, cmd);
cmd.Append(" FROM ");
@@ -960,7 +1308,7 @@
}
}
- if (limit >= 1) {
+ if (SupportsLimitClause && limit >= 1) {
cmd.Append(" LIMIT ");
cmd.Append(limit);
}
@@ -1003,7 +1351,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 +1363,418 @@
} // 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 !DOTNET2
+ bool hasLitFilter = (options.VariableLiteralFilters != null && options.VariableLiteralFilters[v] != null);
+ #else
+ bool hasLitFilter = (options.VariableLiteralFilters != null && options.VariableLiteralFilters.ContainsKey(v));
+ #endif
+ if (distinguishedVars.Contains(v) || hasLitFilter) {
+ 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) {
+ // Is there a literal value filter?
+ if (options.VariableLiteralFilters == null) continue;
+ #if !DOTNET2
+ if (options.VariableLiteralFilters[v] == null) continue;
+ #else
+ if (!options.VariableLiteralFilters.ContainsKey(v)) continue;
+ #endif
+
+ // If this variable was not used in a literal column, then
+ // we cannot filter its value. Really, it will never be a literal.
+ if (!(bool)varSelectedLiteral[v]) continue;
+
+ 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("DROP VIEW IF EXISTS ");
+ cmd.Append(viewname);
+ cmd.Append("; CREATE VIEW ");
+ cmd.Append(viewname);
+ cmd.Append(" AS ");
+
+ outercmd.Append("SELECT ");
+ }
+ cmd.Append("SELECT ");
+
+ if (!SupportsLimitClause && options.Limit > 0) {
+ cmd.Append("TOP ");
+ cmd.Append(options.Limit);
+ cmd.Append(' ');
+ }
+
+ 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 (SupportsLimitClause && 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 +1795,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 +1809,15 @@
}
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.StringEndsWithFilter) {
+ SemWeb.Filters.StringEndsWithFilter f = (SemWeb.Filters.StringEndsWithFilter)filter;
+ return CreateLikeTest(col, f.Pattern, 2); // 2==ends-with
}
if (filter is SemWeb.Filters.NumericCompareFilter) {
SemWeb.Filters.NumericCompareFilter f = (SemWeb.Filters.NumericCompareFilter)filter;
@@ -1069,34 +1838,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];
@@ -1105,9 +1861,16 @@
case '\\':
case '\"':
case '*':
+ case '\'':
b.Append('\\');
b.Append(c);
break;
+ case '%':
+ case '_':
+ if (forLike)
+ b.Append('\\');
+ b.Append(c);
+ break;
default:
b.Append(c);
break;
@@ -1124,7 +1887,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 +1895,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 +1916,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 +1934,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 +1970,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 +1990,7 @@
if (ret is int) return (int)ret;
try {
return int.Parse(ret.ToString());
- } catch (FormatException e) {
+ } catch (FormatException) {
return def;
}
}
@@ -1232,7 +2003,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 ;");
@@ -1244,7 +2019,7 @@
try {
RunCommand(cmd);
} catch (Exception e) {
- if (Debug) Console.Error.WriteLine(e);
+ if (Debug && e.Message.IndexOf("already exists") == -1) Console.Error.WriteLine(e);
}
}
}
@@ -1278,11 +2053,12 @@
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);",
"CREATE UNIQUE INDEX literal_index ON " + table + "_literals(hash);",
+ "CREATE INDEX literal_value_index ON " + table + "_literals(value(20));",
"CREATE UNIQUE INDEX entity_index ON " + table + "_entities(value(255));"
};
}
Added: trunk/semweb/SparqlClient.cs
==============================================================================
--- (empty file)
+++ trunk/semweb/SparqlClient.cs Wed May 7 10:59:52 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();
+ }
+ }
+
+}
Added: trunk/semweb/SpecialRelations.cs
==============================================================================
--- (empty file)
+++ trunk/semweb/SpecialRelations.cs Wed May 7 10:59:52 2008
@@ -0,0 +1,254 @@
+using System;
+
+using SemWeb;
+
+namespace SemWeb.Inference {
+ public abstract class RdfRelation : SemWeb.Query.RdfFunction {
+ public override Resource Evaluate (Resource[] args) {
+ Resource r = null;
+ if (Evaluate(args, ref r))
+ return r;
+ return null;
+ }
+
+ public abstract bool Evaluate(Resource[] args, ref Resource @object);
+ }
+
+ namespace Relations {
+ internal abstract class MathUnaryRelation : RdfRelation {
+ protected abstract Decimal EvaluateForward(Decimal left);
+ protected abstract Decimal EvaluateReverse(Decimal right);
+
+ public override bool Evaluate(Resource[] args, ref Resource @object) {
+ if (args.Length != 1) return false;
+ if (args[0] == null && @object == null) return false;
+ if ((args[0] != null && !(args[0] is Literal)) || (@object != null && !(@object is Literal))) return false;
+
+ try {
+
+ if (args[0] == null) {
+ Decimal right = (Decimal)Convert.ChangeType( ((Literal)@object).ParseValue() , typeof(Decimal) );
+ Decimal left = EvaluateReverse(right);
+ if (left == Decimal.MinValue) return false;
+ args[0] = Literal.FromValue(left);
+ return true;
+ } else {
+ Decimal left = (Decimal)Convert.ChangeType( ((Literal)args[0]).ParseValue() , typeof(Decimal) );
+ Decimal right = EvaluateForward(left);
+ if (@object == null) {
+ @object = Literal.FromValue(right);
+ return true;
+ } else {
+ Decimal right2 = (Decimal)Convert.ChangeType( ((Literal)@object).ParseValue() , typeof(Decimal) );
+ return right == right2;
+ }
+ }
+
+ } catch (FormatException) {
+ return false;
+ }
+ }
+ }
+
+ internal class MathAbsoluteValueRelation : MathUnaryRelation {
+ public override string Uri { get { return "http://www.w3.org/2000/10/swap/math#absoluteValue"; } }
+ protected override Decimal EvaluateForward(Decimal left) { return left >= 0 ? left : -left; }
+ protected override Decimal EvaluateReverse(Decimal right) { return Decimal.MinValue; }
+ }
+ internal class MathCosRelation : MathUnaryRelation {
+ public override string Uri { get { return "http://www.w3.org/2000/10/swap/math#cos"; } }
+ protected override Decimal EvaluateForward(Decimal left) { return (Decimal)Math.Cos((double)left); }
+ protected override Decimal EvaluateReverse(Decimal right) { return (Decimal)Math.Acos((double)right); }
+ }
+ internal class MathDegreesRelation : MathUnaryRelation {
+ public override string Uri { get { return "http://www.w3.org/2000/10/swap/math#degrees"; } }
+ protected override Decimal EvaluateForward(Decimal left) { return (Decimal)((double)left * Math.PI / 180.0); }
+ protected override Decimal EvaluateReverse(Decimal right) { return (Decimal)((double)right * 180.0 / Math.PI); }
+ }
+ internal class MathEqualToRelation : MathUnaryRelation {
+ public override string Uri { get { return "http://www.w3.org/2000/10/swap/math#equalTo"; } }
+ protected override Decimal EvaluateForward(Decimal left) { return left; }
+ protected override Decimal EvaluateReverse(Decimal right) { return right; }
+ }
+ internal class MathNegationRelation : MathUnaryRelation {
+ public override string Uri { get { return "http://www.w3.org/2000/10/swap/math#negation"; } }
+ protected override Decimal EvaluateForward(Decimal left) { return -left; }
+ protected override Decimal EvaluateReverse(Decimal right) { return -right; }
+ }
+ internal class MathRoundedRelation : MathUnaryRelation {
+ public override string Uri { get { return "http://www.w3.org/2000/10/swap/math#rounded"; } }
+ protected override Decimal EvaluateForward(Decimal left) { return Decimal.Floor(left); }
+ protected override Decimal EvaluateReverse(Decimal right) { return Decimal.MinValue; }
+ }
+ internal class MathSinRelation : MathUnaryRelation {
+ public override string Uri { get { return "http://www.w3.org/2000/10/swap/math#sin"; } }
+ protected override Decimal EvaluateForward(Decimal left) { return (Decimal)Math.Sin((double)left); }
+ protected override Decimal EvaluateReverse(Decimal right) { return (Decimal)Math.Asin((double)right); }
+ }
+ internal class MathSinhRelation : MathUnaryRelation {
+ public override string Uri { get { return "http://www.w3.org/2000/10/swap/math#sinh"; } }
+ protected override Decimal EvaluateForward(Decimal left) { return (Decimal)Math.Sinh((double)left); }
+ protected override Decimal EvaluateReverse(Decimal right) { return Decimal.MinValue; }
+ }
+ internal class MathTanRelation : MathUnaryRelation {
+ public override string Uri { get { return "http://www.w3.org/2000/10/swap/math#tan"; } }
+ protected override Decimal EvaluateForward(Decimal left) { return (Decimal)Math.Tan((double)left); }
+ protected override Decimal EvaluateReverse(Decimal right) { return (Decimal)Math.Atan((double)right); }
+ }
+ internal class MathTanhRelation : MathUnaryRelation {
+ public override string Uri { get { return "http://www.w3.org/2000/10/swap/math#tanh"; } }
+ protected override Decimal EvaluateForward(Decimal left) { return (Decimal)Math.Tanh((double)left); }
+ protected override Decimal EvaluateReverse(Decimal right) { return Decimal.MinValue; }
+ }
+
+ internal abstract class MathPairRelation : RdfRelation {
+ protected abstract Decimal Evaluate(Decimal left, Decimal right);
+
+ public override bool Evaluate(Resource[] args, ref Resource @object) {
+ if (args.Length != 2) return false;
+ if (args[0] == null || !(args[0] is Literal)) return false;
+ if (args[1] == null || !(args[1] is Literal)) return false;
+
+ try {
+
+ Decimal left = (Decimal)Convert.ChangeType( ((Literal)args[0]).ParseValue() , typeof(Decimal) );
+ Decimal right = (Decimal)Convert.ChangeType( ((Literal)args[1]).ParseValue() , typeof(Decimal) );
+ Resource newvalue = Literal.FromValue(Evaluate(left, right));
+ if (@object == null) {
+ @object = newvalue;
+ return true;
+ } else {
+ return @object.Equals(newvalue);
+ }
+
+ } catch (FormatException) {
+ return false;
+ }
+ }
+ }
+
+ internal class MathAtan2Relation : MathPairRelation {
+ public override string Uri { get { return "http://www.w3.org/2000/10/swap/math#atan2"; } }
+ protected override Decimal Evaluate(Decimal left, Decimal right) { return (Decimal)Math.Atan2((double)left, (double)right); }
+ }
+ internal class MathDifferenceRelation : MathPairRelation {
+ public override string Uri { get { return "http://www.w3.org/2000/10/swap/math#difference"; } }
+ protected override Decimal Evaluate(Decimal left, Decimal right) { return left - right; }
+ }
+ internal class MathExponentiationRelation : MathPairRelation {
+ public override string Uri { get { return "http://www.w3.org/2000/10/swap/math#exponentiation"; } }
+ protected override Decimal Evaluate(Decimal left, Decimal right) { return (Decimal)Math.Pow((double)left, (double)right); }
+ }
+ internal class MathIntegerQuotientRelation : MathPairRelation {
+ public override string Uri { get { return "http://www.w3.org/2000/10/swap/math#integerQuotient"; } }
+ protected override Decimal Evaluate(Decimal left, Decimal right) { return Decimal.Floor((left / right)); }
+ }
+ internal class MathQuotientRelation : MathPairRelation {
+ public override string Uri { get { return "http://www.w3.org/2000/10/swap/math#quotient"; } }
+ protected override Decimal Evaluate(Decimal left, Decimal right) { return left / right; }
+ }
+ internal class MathRemainderRelation : MathPairRelation {
+ public override string Uri { get { return "http://www.w3.org/2000/10/swap/math#remainder"; } }
+ protected override Decimal Evaluate(Decimal left, Decimal right) { return left % right; }
+ }
+
+ internal abstract class MathListRelation : RdfRelation {
+ protected abstract Decimal InitialValue { get; }
+ protected abstract Decimal Combine(Decimal left, Decimal right);
+
+ public override bool Evaluate(Resource[] args, ref Resource @object) {
+ Decimal sum = InitialValue;
+ foreach (Resource r in args) {
+ if (r == null) return false;
+ if (!(r is Literal)) return false;
+ try {
+ Decimal v = (Decimal)Convert.ChangeType( ((Literal)r).ParseValue() , typeof(Decimal) );
+ sum = Combine(sum, v);
+ } catch (FormatException) {
+ return false;
+ }
+ }
+ Resource newvalue = Literal.FromValue(sum);
+ if (@object == null) {
+ @object = newvalue;
+ return true;
+ } else {
+ return @object.Equals(newvalue);
+ }
+ }
+ }
+
+ internal class MathSumRelation : MathListRelation {
+ public override string Uri { get { return "http://www.w3.org/2000/10/swap/math#sum"; } }
+ protected override Decimal InitialValue { get { return Decimal.Zero; } }
+ protected override Decimal Combine(Decimal left, Decimal right) { return left + right; }
+ }
+ internal class MathProductRelation : MathListRelation {
+ public override string Uri { get { return "http://www.w3.org/2000/10/swap/math#product"; } }
+ protected override Decimal InitialValue { get { return Decimal.One; } }
+ protected override Decimal Combine(Decimal left, Decimal right) { return left * right; }
+ }
+
+ internal abstract class MathComparisonRelation : RdfRelation {
+ public override Resource Evaluate (Resource[] args) {
+ if (args.Length != 2)
+ throw new InvalidOperationException("This relation takes two arguments.");
+
+ Resource left = args[0];
+ Resource right = args[1];
+ bool result = Evaluate(new Resource[] { left }, ref right);
+ return Literal.FromValue(result);
+ }
+
+ public abstract bool Evaluate(Decimal left, Decimal right);
+
+ public override bool Evaluate(Resource[] args, ref Resource @object) {
+ if (args.Length != 1) return false;
+ if (args[0] == null || @object == null) return false;
+ if (!(args[0] is Literal) || !(@object is Literal)) return false;
+
+ try {
+ Decimal left = (Decimal)Convert.ChangeType( ((Literal)args[0]).ParseValue() , typeof(Decimal) );
+ Decimal right = (Decimal)Convert.ChangeType( ((Literal)@object).ParseValue() , typeof(Decimal) );
+ return Evaluate(left, right);
+ } catch (FormatException) {
+ return false;
+ }
+ }
+ }
+
+ internal class MathGreaterThanRelation : MathComparisonRelation {
+ public override string Uri { get { return "http://www.w3.org/2000/10/swap/math#greaterThan"; } }
+ public override bool Evaluate(Decimal left, Decimal right) {
+ return left > right;
+ }
+ }
+ internal class MathLessThanRelation : MathComparisonRelation {
+ public override string Uri { get { return "http://www.w3.org/2000/10/swap/math#lessThan"; } }
+ public override bool Evaluate(Decimal left, Decimal right) {
+ return left < right;
+ }
+ }
+ internal class MathNotGreaterThanRelation : MathComparisonRelation {
+ public override string Uri { get { return "http://www.w3.org/2000/10/swap/math#notGreaterThan"; } }
+ public override bool Evaluate(Decimal left, Decimal right) {
+ return !(left > right);
+ }
+ }
+ internal class MathNotLessThanRelation : MathComparisonRelation {
+ // NOTE: The schema lists this as "notlessThan" with a lowercase
+ // L! I've put it in here with a capital L.
+ public override string Uri { get { return "http://www.w3.org/2000/10/swap/math#notLessThan"; } }
+ public override bool Evaluate(Decimal left, Decimal right) {
+ return !(left < right);
+ }
+ }
+ internal class MathNotEqualToRelation : MathComparisonRelation {
+ public override string Uri { get { return "http://www.w3.org/2000/10/swap/math#notEqualTo"; } }
+ public override bool Evaluate(Decimal left, Decimal right) {
+ return !(left == right);
+ }
+ }
+ }
+
+}
Modified: trunk/semweb/Statement.cs
==============================================================================
--- trunk/semweb/Statement.cs (original)
+++ trunk/semweb/Statement.cs Wed May 7 10:59:52 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;
@@ -77,11 +84,18 @@
!resourceMap.ContainsKey(Meta) ? Meta : (Entity)resourceMap[Meta]
);
}
+
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;
@@ -118,7 +137,7 @@
int cmp(Resource a, Resource b) {
if (a == null && b == null) return 0;
if (a == null) return -1;
- if (b == null) return 1;
+ if (b == null) return 1;
return ((IComparable)a).CompareTo(b);
}
@@ -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: trunk/semweb/Store.cs
==============================================================================
--- trunk/semweb/Store.cs (original)
+++ trunk/semweb/Store.cs Wed May 7 10:59:52 2008
@@ -1,592 +1,978 @@
-using System;
-using System.Collections;
-using System.Data;
+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);
- }
-
- 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);
- }
-
- 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;
- }
- }
-
- 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 (!(s is SelectableSource)) s = new MemoryStore(s);
- return new SemWeb.Inference.RDFS(s, (SelectableSource)s);
- }
- return (StatementSource)Create(spec, false);
- }
-
- public static StatementSink CreateForOutput(string spec) {
- return (StatementSink)Create(spec, true);
- }
-
- 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(':');
- if (c != -1) {
- type = spec.Substring(0, c);
- spec = spec.Substring(c+1);
- } else {
- spec = "";
- }
-
- Type ttype;
-
- switch (type) {
- case "mem":
- return new MemoryStore();
- case "xml":
- if (spec == "") throw new ArgumentException("Use: xml:filename");
- if (output) {
- return new RdfXmlWriter(spec);
- } else {
- return new RdfXmlReader(spec);
- }
- case "n3":
- case "ntriples":
- case "nt":
- case "turtle":
- if (spec == "") throw new ArgumentException("Use: format:filename");
- if (output) {
- N3Writer ret = new N3Writer(spec); // turtle is default format
- switch (type) {
- case "nt": case "ntriples":
- ret.Format = N3Writer.Formats.NTriples;
- break;
- }
- return ret;
- } else {
- return new N3Reader(spec);
- }
- /*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":
- if (spec == "") throw new ArgumentException("Use: sqlite|mysql|postgresql:table:connection-string");
-
- c = spec.IndexOf(':');
- if (c == -1) throw new ArgumentException("Invalid format for SQL spec parameter (table:constring).");
- string table = spec.Substring(0, c);
- spec = spec.Substring(c+1);
-
- string classtype = null;
- if (type == "sqlite") {
- classtype = "SemWeb.Stores.SqliteStore, SemWeb.SqliteStore";
- spec = spec.Replace(";", ",");
- } else if (type == "mysql") {
- classtype = "SemWeb.Stores.MySQLStore, SemWeb.MySQLStore";
- } else if (type == "postgresql") {
- classtype = "SemWeb.Stores.PostgreSQLStore, SemWeb.PostgreSQLStore";
- }
- ttype = Type.GetType(classtype);
- if (ttype == null)
- throw new NotSupportedException("The storage type in <" + classtype + "> could not be found.");
- return Activator.CreateInstance(ttype, new object[] { spec, table });
- /*case "bdb":
- return new SemWeb.Stores.BDBStore(spec);*/
- case "sparql-http":
- return new SemWeb.Remote.SparqlHttpSource(spec);
- case "class":
- ttype = Type.GetType(spec);
- if (ttype == null)
- throw new NotSupportedException("The class <" + spec + "> could not be found.");
- return Activator.CreateInstance(ttype);
- default:
- throw new ArgumentException("Unknown parser type: " + type);
- }
- }
-
- protected Store() {
- rdfType = new Entity("http://www.w3.org/1999/02/22-rdf-syntax-ns#type");
- }
-
- void IDisposable.Dispose() {
- Close();
- }
-
- public virtual void Close() {
- }
-
- public abstract bool Distinct { get; }
-
- 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);
- }
-
- return (Entity[])entities.ToArray(typeof(Entity));
- }
-
- bool StatementSink.Add(Statement statement) {
- Add(statement);
- return true;
- }
-
- public abstract void Add(Statement statement);
-
- public abstract void Remove(Statement statement);
-
- public virtual void Import(StatementSource source) {
- source.Select(this);
- }
-
- public void RemoveAll(Statement[] templates) {
- foreach (Statement t in templates)
- Remove(t);
- }
-
- public abstract Entity[] GetEntities();
-
- public abstract Entity[] GetPredicates();
-
- public abstract Entity[] GetMetas();
-
- public virtual bool Contains(Statement template) {
- return DefaultContains(this, template);
- }
-
- public static bool DefaultContains(SelectableSource source, Statement template) {
- StatementExistsSink sink = new StatementExistsSink();
- SelectFilter filter = new SelectFilter(template);
- filter.Limit = 1;
- source.Select(filter, sink);
- return sink.Exists;
- }
-
- public static void DefaultSelect(SelectableSource source, SelectFilter filter, StatementSink sink) {
- // This method should 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)
- foreach (Entity predicate in filter.Predicates == null ? new Entity[] { null } : filter.Predicates)
- foreach (Resource objct in filter.Objects == null ? new Resource[] { null } : filter.Objects)
- foreach (Entity meta in filter.Metas == null ? new Entity[] { null } : filter.Metas)
- 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);
- }
-
- public SelectResult Select(SelectFilter filter) {
- return new SelectResult.Multi(this, filter);
- }
-
- 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));
- }
- public Entity[] SelectSubjects(Entity predicate, Resource @object) {
- Hashtable resources = new Hashtable();
- 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));
- }
- class ResourceCollector : StatementSink {
- public Hashtable Table;
- public int SPO;
- public bool Add(Statement s) {
- if (SPO == 0) Table[s.Subject] = Table;
- if (SPO == 2) Table[s.Object] = Table;
- 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));
- }
-
- foreach (Statement s in additions) {
- Add(s);
- }
- }
-
- 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);
- }
- }
-
- protected object GetResourceKey(Resource resource) {
- return resource.GetResourceKey(this);
- }
-
- protected void SetResourceKey(Resource resource, object value) {
- resource.SetResourceKey(this, value);
- }
-
- }
-
- 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;
- }
- 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);
- }
- }
- }
-}
-
-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);
- }
-
- public void Remove(SelectableSource store) {
- stores.Remove(store);
- allsources.Remove(store);
- }
-
- public void Remove(string uri) {
- allsources.Remove(namedgraphs[uri]);
- namedgraphs.Remove(uri);
- }
-
- public override int StatementCount {
- get {
- int ret = 0;
- foreach (StatementSource s in allsources) {
- if (s is Store)
- ret += ((Store)s).StatementCount;
- else
- throw new InvalidOperationException("Not all sources are stores supporting 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();
- foreach (StatementSource s in allsources) {
- if (s is Store) {
- foreach (Resource r in ((Store)s).GetEntities())
- h[r] = h;
- } else {
- throw new InvalidOperationException("Not all sources are stores supporting GetEntities.");
- }
- }
- return (Entity[])new ArrayList(h.Keys).ToArray(typeof(Entity));
- }
-
- public override Entity[] GetPredicates() {
- Hashtable h = new Hashtable();
- foreach (StatementSource s in allsources) {
- if (s is Store) {
- foreach (Resource r in ((Store)s).GetPredicates())
- h[r] = h;
- } else {
- throw new InvalidOperationException("Not all sources are stores supporting GetEntities.");
- }
- }
- return (Entity[])new ArrayList(h.Keys).ToArray(typeof(Entity));
- }
-
- public override Entity[] GetMetas() {
- Hashtable h = new Hashtable();
- foreach (StatementSource s in allsources) {
- if (s is Store) {
- foreach (Resource r in ((Store)s).GetMetas())
- h[r] = h;
- } else {
- throw new InvalidOperationException("Not all sources are stores supporting GetEntities.");
- }
- }
- return (Entity[])new ArrayList(h.Keys).ToArray(typeof(Entity));
- }
-
- public override void Add(Statement statement) { throw new InvalidOperationException("Add is not a valid operation on a MultiStore."); }
-
- 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 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 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);
- if (sources == null) return;
- template.Meta = null;
- foreach (SelectableSource s in sources)
- s.Select(template, result);
- }
-
- 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);
- if (sources == null) continue;
- foreach (SelectableSource s in sources)
- s.Select(filter, result);
- }
- }
-
- public override void Replace(Entity a, Entity b) { throw new InvalidOperationException("Replace is not a valid operation on a MultiStore."); }
-
- public override void Replace(Statement find, Statement replacement) { throw new InvalidOperationException("Replace is not a valid operation on a MultiStore."); }
-
- }
-
- public abstract class SimpleSourceWrapper : SelectableSource {
-
- public virtual bool Distinct { get { return true; } }
-
- public virtual void Select(StatementSink sink) {
- // The default implementation does not return
- // anything for this call.
- }
-
- public virtual bool Contains(Statement template) {
- template.Object = null; // reduce to another case (else there would be recursion)
- return Store.DefaultContains(this, template);
- }
-
- protected virtual void SelectAllSubject(Entity subject, StatementSink sink) {
- }
-
- protected virtual void SelectAllObject(Resource @object, StatementSink sink) {
- }
-
- protected virtual void SelectRelationsBetween(Entity subject, Resource @object, StatementSink sink) {
- }
-
- protected virtual void SelectAllPairs(Entity predicate, StatementSink sink) {
- }
-
- protected virtual void SelectSubjects(Entity predicate, Resource @object, StatementSink sink) {
- }
-
- protected virtual void SelectObjects(Entity subject, Entity predicate, StatementSink sink) {
- }
-
- public void Select(Statement template, StatementSink sink) {
- if (template.Meta != null && template.Meta != Statement.DefaultMeta) return;
- if (template.Predicate != null && template.Predicate.Uri == null) return;
-
- if (template.Subject == null && template.Predicate == null && template.Object == null) {
- Select(sink);
- } else if (template.Subject != null && template.Predicate != null && template.Object != null) {
- template.Meta = Statement.DefaultMeta;
- if (Contains(template))
- sink.Add(template);
- } else if (template.Predicate == null) {
- if (template.Subject == null)
- SelectAllObject(template.Object, sink);
- else if (template.Object == null)
- SelectAllSubject(template.Subject, sink);
- else
- SelectRelationsBetween(template.Subject, template.Object, sink);
- } else if (template.Subject != null && template.Object == null) {
- SelectObjects(template.Subject, template.Predicate, sink);
- } else if (template.Subject == null && template.Object != null) {
- SelectSubjects(template.Predicate, template.Object, sink);
- } else if (template.Subject == null && template.Object == null) {
- SelectAllPairs(template.Predicate, sink);
- }
- }
-
- public void Select(SelectFilter filter, StatementSink sink) {
- Store.DefaultSelect(this, filter, 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
+
+namespace SemWeb {
+
+ public class Store : StatementSource, StatementSink,
+ SelectableSource, QueryableSource, StaticSource, ModifiableSource,
+ IDisposable {
+
+ // Static helper methods for creating data sources and sinks
+ // from spec strings.
+
+ 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)
+ ret.AddReasoner(new Euler(ret)); // loads it all into memory!
+
+ return ret;
+ }
+
+ public static StatementSource CreateForInput(string spec) {
+ if (spec.StartsWith("debug+")) {
+ StatementSource s = CreateForInput(spec.Substring(6));
+ if (!(s is SelectableSource)) s = new MemoryStore(s);
+ return new SemWeb.Stores.DebuggedSource((SelectableSource)s, System.Console.Error);
+ }
+ return (StatementSource)Create(spec, false);
+ }
+
+ public static StatementSink CreateForOutput(string spec) {
+ return (StatementSink)Create(spec, true);
+ }
+
+ private static object Create(string spec, bool output) {
+ string type = spec;
+
+ int c = spec.IndexOf(':');
+ if (c != -1) {
+ type = spec.Substring(0, c);
+ spec = spec.Substring(c+1);
+ } else {
+ spec = "";
+ }
+
+ Type ttype;
+
+ switch (type) {
+ case "mem":
+ return new MemoryStore();
+ case "xml":
+ if (spec == "") throw new ArgumentException("Use: xml:filename");
+ if (output) {
+ #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);
+ }
+ case "n3":
+ case "ntriples":
+ case "nt":
+ case "turtle":
+ if (spec == "") throw new ArgumentException("Use: format:filename");
+ if (output) {
+ N3Writer ret = new N3Writer(spec); // turtle is default format
+ switch (type) {
+ case "nt": case "ntriples":
+ ret.Format = N3Writer.Formats.NTriples;
+ break;
+ }
+ return ret;
+ } 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":
+ case "sqlserver":
+ if (spec == "") throw new ArgumentException("Use: sqlite|mysql|postgresql|sqlserver:table:connection-string");
+
+ c = spec.IndexOf(':');
+ if (c == -1) throw new ArgumentException("Invalid format for SQL spec parameter (table:constring).");
+ string table = spec.Substring(0, c);
+ spec = spec.Substring(c+1);
+
+ string classtype = null;
+ if (type == "sqlite") {
+ classtype = "SemWeb.Stores.SqliteStore, SemWeb.SqliteStore";
+ spec = spec.Replace(";", ",");
+ } else if (type == "mysql") {
+ classtype = "SemWeb.Stores.MySQLStore, SemWeb.MySQLStore";
+ } else if (type == "postgresql") {
+ classtype = "SemWeb.Stores.PostgreSQLStore, SemWeb.PostgreSQLStore";
+ } else if( type == "sqlserver" ) {
+ classtype = "SemWeb.Stores.SQLServerStore, SemWeb.SQLServerStore";
+ }
+ ttype = Type.GetType(classtype);
+ if (ttype == null)
+ throw new NotSupportedException("The storage type in <" + classtype + "> could not be found.");
+ return Activator.CreateInstance(ttype, new object[] { spec, table });
+ /*case "bdb":
+ return new SemWeb.Stores.BDBStore(spec);*/
+ case "sparql-http":
+ return new SemWeb.Remote.SparqlHttpSource(spec);
+ case "class":
+ ttype = Type.GetType(spec);
+ if (ttype == null)
+ throw new NotSupportedException("The class <" + spec + "> could not be found.");
+ return Activator.CreateInstance(ttype);
+ default:
+ throw new ArgumentException("Unknown parser type: " + 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
+
+ ReasonerList reasoners = new ReasonerList(); // a list of reasoning engines applied to this data model, which are run in order
+
+ // GENERAL METHODS
+
+ public Store() {
+ }
+
+ public Store(StatementSource source) {
+ AddSource(new MemoryStore.StoreImpl(source));
+ }
+
+ public Store(SelectableSource source) {
+ AddSource(source);
+ }
+
+ #if !DOTNET2
+ public IList DataSources {
+ get {
+ return ArrayList.ReadOnly(allsources);
+ }
+ }
+ #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 virtual void AddReasoner(Reasoner reasoner) {
+ reasoners.Add(reasoner);
+ }
+
+ public void Write(System.IO.TextWriter writer) {
+ using (RdfWriter w = new N3Writer(writer)) {
+ Select(w);
+ }
+ }
+
+ // INTERFACE IMPLEMENTATIONS and related methods
+
+ // 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();
+ }
+
+ // StatementSource
+
+ 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 void Select(StatementSink result) {
+ Select(Statement.All, result);
+ }
+
+ // SelectableSource
+
+ 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);
+ filter.Limit = 1;
+ source.Select(filter, sink);
+ 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 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)
+ foreach (Entity predicate in filter.Predicates == null ? new Entity[] { null } : filter.Predicates)
+ foreach (Resource objct in filter.Objects == null ? new Resource[] { null } : filter.Objects)
+ foreach (Entity meta in filter.Metas == null ? new Entity[] { null } : filter.Metas)
+ source.Select(new Statement(subject, predicate, objct, meta), sink);
+ }
+
+ public SelectResult Select(Statement template) {
+ return new SelectResult.Single(this, template);
+ }
+
+ public SelectResult Select(SelectFilter filter) {
+ return new SelectResult.Multi(this, filter);
+ }
+
+ public Resource[] SelectObjects(Entity subject, Entity predicate) {
+ 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) {
+ ResSet resources = new ResSet();
+ ResourceCollector collector = new ResourceCollector();
+ collector.SPO = 0;
+ collector.Table = resources;
+ Select(new Statement(null, predicate, @object, null), collector);
+ return resources.ToEntityArray();
+ }
+ class ResourceCollector : StatementSink {
+ public ResSet Table;
+ public int SPO;
+ public bool Add(Statement s) {
+ if (SPO == 0) Table.Add(s.Subject);
+ if (SPO == 2) Table.Add(s.Object);
+ return true;
+ }
+ }
+ 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;
+ }
+ 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;
+ }
+ 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);
+ }
+ }
+ }
+
+ // 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);
+ }
+
+ 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;
+ }
+
+ // 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;
+ }
+
+ // 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);
+ }
+
+ 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;
+ }
+
+ 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);
+ }
+
+ 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
+ #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;
+ }
+
+ // StaticSource
+
+ public int StatementCount {
+ get {
+ int ret = 0;
+ foreach (StatementSource s in allsources) {
+ if (s is StaticSource)
+ ret += ((StaticSource)s).StatementCount;
+ else
+ throw new InvalidOperationException("Not all data sources are support StatementCount.");
+ }
+ return ret;
+ }
+ }
+
+ public Entity[] GetEntities() {
+ ResSet h = new ResSet();
+ foreach (StatementSource s in allsources) {
+ if (s is StaticSource) {
+ foreach (Resource r in ((StaticSource)s).GetEntities())
+ h.Add(r);
+ } else {
+ throw new InvalidOperationException("Not all data sources support GetEntities.");
+ }
+ }
+ return h.ToEntityArray();
+ }
+
+ public Entity[] GetPredicates() {
+ ResSet h = new ResSet();
+ foreach (StatementSource s in allsources) {
+ if (s is StaticSource) {
+ foreach (Resource r in ((StaticSource)s).GetPredicates())
+ h.Add(r);
+ } else {
+ throw new InvalidOperationException("Not data sources support GetPredicates.");
+ }
+ }
+ return h.ToEntityArray();
+ }
+
+ public Entity[] GetMetas() {
+ ResSet h = new ResSet();
+ foreach (StatementSource s in allsources) {
+ if (s is StaticSource) {
+ foreach (Resource r in ((StaticSource)s).GetMetas())
+ h.Add(r);
+ } else {
+ throw new InvalidOperationException("Not all data sources support GetMetas.");
+ }
+ }
+ return h.ToEntityArray();
+ }
+
+ public Entity[] GetEntitiesOfType(Entity type) {
+ return SelectSubjects(rdfType, type);
+ }
+
+ 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 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 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 > 1) 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;
+
+ 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 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;
+
+ 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.");
+
+ foreach (ModifiableSource source in allsources)
+ source.Replace(find, replacement);
+ }
+
+ 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; } }
+
+ public virtual void Select(StatementSink sink) {
+ // 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)
+ return Store.DefaultContains(this, template);
+ }
+
+ protected virtual void SelectAllSubject(Entity subject, StatementSink sink) {
+ }
+
+ protected virtual void SelectAllObject(Resource @object, StatementSink sink) {
+ }
+
+ protected virtual void SelectRelationsBetween(Entity subject, Resource @object, StatementSink sink) {
+ }
+
+ protected virtual void SelectAllPairs(Entity predicate, StatementSink sink) {
+ }
+
+ protected virtual void SelectSubjects(Entity predicate, Resource @object, StatementSink sink) {
+ }
+
+ protected virtual void SelectObjects(Entity subject, Entity predicate, StatementSink sink) {
+ }
+
+ public void Select(Statement template, StatementSink sink) {
+ if (template.Meta != null && template.Meta != Statement.DefaultMeta) return;
+ if (template.Predicate != null && template.Predicate.Uri == null) return;
+
+ if (template.Subject == null && template.Predicate == null && template.Object == null) {
+ Select(sink);
+ } else if (template.Subject != null && template.Predicate != null && template.Object != null) {
+ template.Meta = Statement.DefaultMeta;
+ if (Contains(template))
+ sink.Add(template);
+ } else if (template.Predicate == null) {
+ if (template.Subject == null)
+ SelectAllObject(template.Object, sink);
+ else if (template.Object == null)
+ SelectAllSubject(template.Subject, sink);
+ else
+ SelectRelationsBetween(template.Subject, template.Object, sink);
+ } else if (template.Subject != null && template.Object == null) {
+ SelectObjects(template.Subject, template.Predicate, sink);
+ } else if (template.Subject == null && template.Object != null) {
+ SelectSubjects(template.Predicate, template.Object, sink);
+ } else if (template.Subject == null && template.Object == null) {
+ SelectAllPairs(template.Predicate, sink);
+ }
+ }
+
+ public void Select(SelectFilter filter, StatementSink sink) {
+ Store.DefaultSelect(this, filter, sink);
+ }
}
- public class DebuggedSource : SelectableSource {
+ public class DebuggedSource : QueryableSource {
SelectableSource source;
System.IO.TextWriter output;
@@ -595,50 +981,97 @@
this.output = output;
}
- public bool Distinct { get { return source.Distinct; } }
+ public bool Distinct { get { return source.Distinct; } }
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);
return source.Contains(template);
}
-
+
public void Select(Statement template, StatementSink sink) {
output.WriteLine("SELECT: " + template);
source.Select(template, sink);
}
-
+
public void Select(SelectFilter filter, StatementSink sink) {
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();
public CachedSource(SelectableSource s) { source = s; }
- public bool Distinct { get { return source.Distinct; } }
+ public bool Distinct { get { return source.Distinct; } }
public void Select(StatementSink sink) {
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);
@@ -646,15 +1079,95 @@
}
((MemoryStore)selectresults[template]).Select(sink);
}
-
+
public void Select(SelectFilter filter, StatementSink sink) {
+ if (source == null) return;
if (!selfilterresults.ContainsKey(filter)) {
MemoryStore s = new MemoryStore();
source.Select(filter, s);
selfilterresults[filter] = s;
}
((MemoryStore)selfilterresults[filter]).Select(sink);
- }
+ }
+
+ }
+
+ 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: trunk/semweb/Util.cs
==============================================================================
--- trunk/semweb/Util.cs (original)
+++ trunk/semweb/Util.cs Wed May 7 10:59:52 2008
@@ -11,11 +11,24 @@
public ResSet() {
}
-
- public ResSet(ICollection items) {
+
+ #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;
}
@@ -24,8 +37,12 @@
items[res] = items;
keys = null;
}
-
- public void AddRange(ICollection items) {
+
+ #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);
Modified: trunk/src/MetadataStore.cs
==============================================================================
--- trunk/src/MetadataStore.cs (original)
+++ trunk/src/MetadataStore.cs Wed May 7 10:59:52 2008
@@ -349,13 +349,5 @@
return false;
}
}
-
- public void DumpNode (XPathSemWebNavigator navi, int depth)
- {
- do {
- System.Console.WriteLine ("node [{0}] {1} {2}", depth, navi.Name, navi.Value);
- } while (navi.MoveToNext ());
- }
-
}
}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]