Index: sparql.engine.inc
===================================================================
RCS file: sparql.engine.inc
diff -N sparql.engine.inc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ sparql.engine.inc	15 May 2009 07:14:04 -0000
@@ -0,0 +1,161 @@
+<?php
+// $Id$
+/**
+ * sparql.engine.inc - SPARQL query engine implementation.
+ *
+ * @author Arto Bendiken <http://bendiken.net/>
+ * @copyright Copyright (c) 2007-2008 Arto Bendiken. All rights reserved.
+ * @license GPL <http://creativecommons.org/licenses/GPL/2.0/>
+ * @package sparql.module
+ */
+
+//////////////////////////////////////////////////////////////////////////////
+// SPARQL query engine
+
+class SPARQL_Engine {
+
+  static function execute($query) {
+    module_invoke_all('sparql', $query->type, $query);
+
+    switch ($query->type) {
+      case 'ask':
+        return count(self::solutions($query->patterns)) > 0;
+
+      case 'select':
+        $solutions = self::solutions($query->patterns);
+
+        // DISTINCT/REDUCED
+        if ($query->distinct || $query->reduced) {
+          $solutions = array_unique($solutions); // FIXME
+        }
+
+        // ORDER BY
+        if (!empty($query->order_by)) {
+          $order_keys = array_keys($query->order_by);
+          $order_columns = array();
+          foreach ($solutions as $index => $solution) {
+            foreach ($order_keys as $var) {
+              $order_columns[$var][$index] = $solution[$var];
+            }
+          }
+          $args = array();
+          foreach ($order_keys as $order_key) {
+            $args[] = $order_columns[$order_key];
+            $args[] = $query->order_by[$order_key] == 'asc' ? SORT_ASC : SORT_DESC;
+          }
+          $args[] =& $solutions;
+          call_user_func_array('array_multisort', $args);
+        }
+
+        // OFFSET/LIMIT
+        if (!is_null($query->limit) || !is_null($query->offset)) {
+          $offset = !is_null($query->offset) ? $query->offset : 0;
+          $count  = !is_null($query->limit) ? $query->limit : count($solutions) - $offset;
+          $solutions = array_slice($solutions, $offset, $count);
+        }
+        return $solutions;
+
+      case 'construct':
+        // TODO;
+        return array();
+
+      case 'describe':
+        $subjects = !empty($query->uris) ? array_flip($query->uris) : array();
+        foreach (self::solutions($query->patterns) as $solution) {
+          foreach ($query->vars as $var) {
+            if (!empty($solution[$var])) {
+              // FIXME: handle blank nodes
+              $subjects[(string)$solution[$var]] = TRUE;
+            }
+          }
+        }
+        $data = array();
+        foreach (array_keys($subjects) as $subject) {
+          $data = array_merge($data, iterator_to_array(rdf_query($subject, NULL, NULL))); // TODO
+        }
+        return $data;
+    }
+  }
+
+  static function solutions($patterns) {
+    $solutions = array();
+
+    foreach ($patterns as $counter => $pattern) {
+      //var_dump(sprintf("[%d] Matching triple pattern: %s:", $counter, implode(' ', $pattern)));
+
+      @list($pattern, $options) = array_chunk($pattern, 3, TRUE);
+      $variables = array_filter($pattern, 'rdf_is_var');
+      if (count($variables) == 0) { // The simple case
+        if (count(self::query(self::make_query($pattern))) == 0) {
+          $solutions = array();
+          break;
+        }
+      }
+      else if (count($variables) == 3) {
+        $triples = self::query(self::make_query(NULL, NULL, NULL));
+        $solutions = array_merge($solutions, self::make_bindings($variables, $triples));
+      }
+      else if (count(self::get_touched_vars($variables, $solutions)) == 0) {
+        $triples = self::query(self::make_query($pattern));
+
+        if (empty($triples)) {
+          break; // TODO: check this
+        }
+
+        $solutions = array_merge($solutions, self::make_bindings($variables, $triples));
+      }
+      else {
+        foreach ($solutions as $index => &$bindings) {
+          $triples = self::query(self::make_query($pattern, $bindings));
+
+          if (empty($triples)) {
+            if (empty($options['optional'])) {
+              unset($solutions[$index]); // this solution failed
+            }
+          }
+          else {
+            $new_solutions = self::make_bindings($variables, $triples);
+            $bindings = array_merge($bindings, /*FIXME:*/ reset($new_solutions));
+          }
+        }
+      }
+    }
+
+    return $solutions;
+  }
+
+  static function query($triple) {
+    return iterator_to_array(calL_user_func_array('rdf_query', $triple));
+  }
+
+  static function make_query($pattern, $bindings = array()) {
+    list($s, $p, $o) = $pattern;
+    return array(
+      !rdf_is_var($s) ? $s : (isset($bindings[$s->name]) ? $bindings[$s->name] : NULL),
+      !rdf_is_var($p) ? $p : (isset($bindings[$p->name]) ? $bindings[$p->name] : NULL),
+      !rdf_is_var($o) ? $o : (isset($bindings[$o->name]) ? $bindings[$o->name] : NULL),
+    );
+  }
+
+  static function make_bindings($vars, $matches) {
+    $output = array();
+    foreach ($matches as $i => $match) {
+      foreach ($vars as $j => $var) {
+        $output[$i][$var->name] = $match[$j];
+      }
+    }
+    return $output;
+  }
+
+  static function get_touched_vars($vars, $bindings) {
+    $output = array();
+    foreach ($bindings as $binding) {
+      foreach ($vars as $var) {
+        if (isset($binding[$var->name]))
+          $output[$var->name] = TRUE;
+      }
+    }
+    return $output;
+  }
+
+} // class SPARQL_Engine
