r86122 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r86121‎ | r86122 | r86123 >
Date:17:34, 15 April 2011
Author:mkroetzsch
Status:deferred
Tags:
Comment:
initial code for SPARQL store communication abstraction; will still change significantly ...
Modified paths:
  • /trunk/extensions/SemanticMediaWiki/includes/SMW_Setup.php (modified) (history)
  • /trunk/extensions/SemanticMediaWiki/includes/sparql (added) (history)
  • /trunk/extensions/SemanticMediaWiki/includes/sparql/SMW_SparqlDatabase.php (added) (history)
  • /trunk/extensions/SemanticMediaWiki/includes/sparql/SMW_SparqlResultParser.php (added) (history)
  • /trunk/extensions/SemanticMediaWiki/includes/sparql/SMW_SparqlResultWrapper.php (added) (history)

Diff [purge]

Index: trunk/extensions/SemanticMediaWiki/includes/sparql/SMW_SparqlDatabase.php
@@ -0,0 +1,159 @@
 2+<?php
 3+/**
 4+ * Base classes for SMW's binding to SPARQL stores.
 5+ *
 6+ * @file
 7+ * @ingroup SMWSparql
 8+ *
 9+ * @author Markus Krötzsch
 10+ */
 11+
 12+/**
 13+ * This group contains all parts of SMW that relate to communication with
 14+ * storage backends and clients via SPARQL.
 15+ *
 16+ * @defgroup SMWSparql SWMSparql
 17+ * @ingroup SMW
 18+ */
 19+
 20+/**
 21+ * Class to escalate SPARQL query errors to the interface. We only do this for
 22+ * malformed queries or permission issues. Connection problems are usually
 23+ * ignored so as to keep the wiki running even if the RDF backend is down.
 24+ *
 25+ * @ingroup SMWSparql
 26+ */
 27+class SMWSparqlDatabaseError extends Exception {
 28+
 29+ /// Error code: malformed query
 30+ const ERROR_MALFORMED = 1;
 31+ /// Error code: service refused to handle the request
 32+ const ERROR_REFUSED = 2;
 33+ /// Error code: the query required a graph that does not exist
 34+ const ERROR_GRAPH_NOEXISTS = 3;
 35+ /// Error code: some existing graph should not exist to run this query
 36+ const ERROR_GRAPH_EXISTS = 4;
 37+ /// Error code: unknown error
 38+ const ERROR_OTHER = 5;
 39+
 40+ /**
 41+ * SPARQL query that caused the problem.
 42+ * @var string
 43+ */
 44+ public $queryText;
 45+ /**
 46+ * Error code
 47+ * @var integer
 48+ */
 49+ public $errorCode;
 50+
 51+ function __construct( $errorCode, $queryText, $endpoint, $httpCode = '<not given>' ) {
 52+ switch ( $errorCode ) {
 53+ case self::ERROR_MALFORMED:
 54+ $errorName = 'Malformed query';
 55+ break;
 56+ case self::ERROR_REFUSED:
 57+ $errorName = 'Query refused';
 58+ break;
 59+ case self::ERROR_GRAPH_NOEXISTS:
 60+ $errorName = 'Graph not existing';
 61+ break;
 62+ case self::ERROR_GRAPH_EXISTS:
 63+ $errorName = 'Graph already exists';
 64+ break;
 65+ case self::ERROR_OTHER: default:
 66+ $errorName = 'Unkown error';
 67+ break;
 68+ }
 69+ $message = "A SPARQL query error has occurred\n" .
 70+ "Query: $queryText\n" .
 71+ "Error: $errorName\n" .
 72+ "Endpoint: $endpoint\n" .
 73+ "HTTP response code: $httpCode\n";
 74+
 75+ parent::__construct( $message );
 76+ $this->errorCode = $errorCode;
 77+ $this->queryText = $queryText;
 78+ }
 79+
 80+}
 81+
 82+/**
 83+ * Basic database connector for exchanging data via SPARQL.
 84+ *
 85+ * @ingroup SMWSparql
 86+ *
 87+ * @author Markus Krötzsch
 88+ */
 89+class SMWSparqlDatabase {
 90+
 91+ /**
 92+ * The URL of the endpoint to contact the database.
 93+ * @var string
 94+ */
 95+ protected $m_endpoint;
 96+
 97+ /**
 98+ * The curl handle we use for communicating.
 99+ * @var resource
 100+ */
 101+ protected $m_curlhandle;
 102+
 103+ /**
 104+ * Constructor.
 105+ * @param $endpoint string of URL to contact the database at
 106+ */
 107+ public function __construct( $endpoint ) {
 108+ $this->m_endpoint = $endpoint;
 109+ $this->m_curlhandle = curl_init();
 110+ curl_setopt( $this->m_curlhandle, CURLOPT_FORBID_REUSE, false );
 111+ curl_setopt( $this->m_curlhandle, CURLOPT_FRESH_CONNECT, false );
 112+ curl_setopt( $this->m_curlhandle, CURLOPT_RETURNTRANSFER, true ); // put result into variable
 113+ curl_setopt( $this->m_curlhandle, CURLOPT_CONNECTTIMEOUT, 10 ); // timeout in seconds
 114+ curl_setopt( $this->m_curlhandle, CURLOPT_FAILONERROR, true );
 115+ }
 116+
 117+ /**
 118+ * Check if the database can be contacted,
 119+ *
 120+ * @return boolean to indicate success
 121+ */
 122+ public function ping(){
 123+ curl_setopt( $this->m_curlhandle, CURLOPT_URL, $this->m_endpoint );
 124+ curl_setopt( $this->m_curlhandle, CURLOPT_NOBODY, 1 );
 125+ curl_exec( $this->m_curlhandle );
 126+ return ( curl_errno( $this->m_curlhandle ) == 0 );
 127+ }
 128+
 129+ public function doQuery( $sparql ) {
 130+ curl_setopt( $this->m_curlhandle, CURLOPT_URL, $this->m_endpoint );
 131+ curl_setopt( $this->m_curlhandle, CURLOPT_POST, true );
 132+ $parameterString = "query=" . urlencode( $sparql );
 133+ curl_setopt( $this->m_curlhandle, CURLOPT_POSTFIELDS, $parameterString );
 134+ $xmlResult = curl_exec( $this->m_curlhandle );
 135+ $error = curl_errno( $this->m_curlhandle );
 136+ if ( $error == 0 ) {
 137+ $xmlParser = new SMWSparqlResultParser();
 138+ $resultWrapper = $xmlParser->makeResultFromXml( $xmlResult );
 139+ } elseif ( $error == CURLE_COULDNT_CONNECT ) { // fail gracefully if backend is down
 140+ $resultWrapper = new SMWSparqlResultWrapper( array(), array(), SMWSparqlResultWrapper::ERROR_UNREACHABLE );
 141+ } elseif ( $error == 22 ) { // 22 == CURLE_HTTP_RETURNED_ERROR, but this constant is not defined in PHP, it seems
 142+ $httpCode = curl_getinfo( $this->m_curlhandle, CURLINFO_HTTP_CODE );
 143+ if ( $httpCode == 400 ) { // malformed query
 144+ throw new SMWSparqlDatabaseError( SMWSparqlDatabaseError::ERROR_MALFORMED, $sparql, $this->m_endpoint, $error );
 145+ } elseif ( $httpCode == 500 ) { // query refused; maybe fail gracefully here (depending on how stores use this)
 146+ throw new SMWSparqlDatabaseError( SMWSparqlDatabaseError::ERROR_REFUSED, $sparql, $this->m_endpoint, $error );
 147+ } elseif ( $httpCode == 404 ) { // endpoint not found, maybe down; fail gracefully
 148+ $resultWrapper = new SMWSparqlResultWrapper( array(), array(), SMWSparqlResultWrapper::ERROR_UNREACHABLE );
 149+ } else {
 150+ throw new SMWSparqlDatabaseError( SMWSparqlDatabaseError::ERROR_OTHER, $sparql, $this->m_endpoint, $error );
 151+ }
 152+ } else {
 153+ throw new Exception( "Failed to communicate with SPARQL store.\n Endpoint: " . $this->m_endpoint . "\n Curl error: '" . curl_error( $this->m_curlhandle ) . "' ($error)" );
 154+ }
 155+
 156+ return $resultWrapper;
 157+ }
 158+
 159+}
 160+
Property changes on: trunk/extensions/SemanticMediaWiki/includes/sparql/SMW_SparqlDatabase.php
___________________________________________________________________
Added: svn:eol-style
1161 + native
Index: trunk/extensions/SemanticMediaWiki/includes/sparql/SMW_SparqlResultParser.php
@@ -0,0 +1,136 @@
 2+<?php
 3+/**
 4+ * Parser class for the SPARQL XML result format.
 5+ *
 6+ * @file
 7+ * @ingroup SMWSparql
 8+ *
 9+ * @author Markus Krötzsch
 10+ */
 11+
 12+/**
 13+ * Class for parsing SPARQL results in XML format.
 14+ *
 15+ * @ingroup SMWSparql
 16+ */
 17+class SMWSparqlResultParser {
 18+
 19+ /**
 20+ * Associative array mapping SPARQL variable names to column indices.
 21+ * @var array of integer
 22+ */
 23+ protected $m_header;
 24+
 25+ /**
 26+ * List of result rows. Individual entries can be null if a cell in the
 27+ * SPARQL result table is empty (this is different from finding a blank
 28+ * node).
 29+ * @var array of array of (SMWExpElement or null)
 30+ */
 31+ protected $m_data;
 32+
 33+ /**
 34+ * Stack of open XML tags during parsing.
 35+ * @var array of string
 36+ */
 37+ protected $m_xml_opentags;
 38+ /**
 39+ * Integer index of the column that the current result binding fills.
 40+ * @var integer
 41+ */
 42+ protected $m_xml_bindidx;
 43+ /**
 44+ * Datatype URI for the current literal, or empty if none.
 45+ * @var string
 46+ */
 47+ protected $m_xml_datatype;
 48+
 49+ /**
 50+ * Parse the given XML result and return an SMWSparqlResultWrapper for
 51+ * the contained data.
 52+ *
 53+ * @param $xmlQueryResult string
 54+ */
 55+ public function makeResultFromXml( $xmlQueryResult ) {
 56+ $parser = xml_parser_create ();
 57+ xml_parser_set_option( $parser, XML_OPTION_SKIP_WHITE, 0 );
 58+ xml_parser_set_option( $parser, XML_OPTION_TARGET_ENCODING, 'UTF-8' );
 59+ xml_parser_set_option( $parser, XML_OPTION_CASE_FOLDING, 0 );
 60+ xml_set_object( $parser, $this );
 61+ xml_set_element_handler( $parser, 'xmlHandleOpen', 'xmlHandleClose' );
 62+ xml_set_character_data_handler($parser, 'xmlHandleCData' );
 63+ //xml_set_start_namespace_decl_handler($parser, 'xmlHandleNsDeclaration' );
 64+
 65+ $this->m_xml_opentags = array();
 66+ $this->m_header = array();
 67+ $this->m_data = array();
 68+
 69+ xml_parse( $parser, $xmlQueryResult, true );
 70+
 71+ xml_parser_free( $parser );
 72+
 73+ return new SMWSparqlResultWrapper( $this->m_header, $this->m_data );
 74+ }
 75+
 76+ /**
 77+ * Handle an opening XML tag during parsing.
 78+ * @see xml_set_element_handler
 79+ */
 80+ protected function xmlHandleOpen( $parser, $tagName, $attributes ) {
 81+ print " ($tagName( ";
 82+ $prevTag = end( $this->m_xml_opentags );
 83+ $this->m_xml_opentags[] = $tagName;
 84+ if ( ( $tagName == 'binding' ) && ( $prevTag == 'result' ) ) {
 85+ if ( ( array_key_exists( 'name', $attributes ) ) &&
 86+ ( array_key_exists( $attributes['name'], $this->m_header ) ) ) {
 87+ $this->m_xml_bindidx = $this->m_header[$attributes['name']];
 88+ }
 89+ } elseif ( ( $tagName == 'result' ) && ( $prevTag == 'results' ) ) {
 90+ $row = array();
 91+ for ( $i = 0; $i < count( $this->m_header ); ++$i ) {
 92+ $row[] = null;
 93+ }
 94+ $this->m_data[] = $row;
 95+ } elseif ( ( $tagName == 'literal' ) && ( $prevTag == 'binding' ) ) {
 96+ if ( array_key_exists( 'datatype', $attributes ) ) {
 97+ $this->m_xml_datatype = $attributes['datatype'];
 98+ } else {
 99+ $this->m_xml_datatype = false;
 100+ }
 101+ /// TODO handle xml:lang attributes here as well?
 102+ } elseif ( ( $tagName == 'variable' ) && ( $prevTag == 'head' ) ) {
 103+ if ( array_key_exists( 'name', $attributes ) ) {
 104+ $this->m_header[$attributes['name']] = count( $this->m_header );
 105+ }
 106+ }
 107+ }
 108+
 109+ /**
 110+ * Handle a closing XML tag during parsing.
 111+ * @see xml_set_element_handler
 112+ */
 113+ protected function xmlHandleClose( $parser, $tagName ) {
 114+ print " )$tagName)";
 115+ array_pop( $this->m_xml_opentags );
 116+ }
 117+
 118+ /**
 119+ * Handle XML character data during parsing.
 120+ * @see xml_set_character_data_handler
 121+ */
 122+ protected function xmlHandleCData( $parser, $dataString ) {
 123+ $prevTag = end( $this->m_xml_opentags );
 124+ $rowcount = count( $this->m_data );
 125+ if ( $prevTag == 'uri' ) {
 126+ $this->m_data[$rowcount][$this->m_xml_bindidx] = new SMWExpResource( $dataString );
 127+ } elseif ( $prevTag == 'literal' ) {
 128+ $this->m_data[$rowcount][$this->m_xml_bindidx] = new SMWExpLiteral( $dataString, null, $this->m_xml_datatype );
 129+ } elseif ( $prevTag == 'bnode' ) {
 130+ $this->m_data[$rowcount][$this->m_xml_bindidx] = new SMWExpResource( '_' . $dataString );
 131+ } elseif ( $prevTag == 'boolean' ) { // no "results" in this case
 132+ $literal = new SMWExpLiteral( $dataString, null, 'http://www.w3.org/2001/XMLSchema#boolean' );
 133+ $this->m_data = array( array( $literal ) );
 134+ }
 135+ }
 136+
 137+}
\ No newline at end of file
Property changes on: trunk/extensions/SemanticMediaWiki/includes/sparql/SMW_SparqlResultParser.php
___________________________________________________________________
Added: svn:eol-style
1138 + native
Index: trunk/extensions/SemanticMediaWiki/includes/sparql/SMW_SparqlResultWrapper.php
@@ -0,0 +1,124 @@
 2+<?php
 3+/**
 4+ * Class for representing SPARQL query results.
 5+ *
 6+ * @file
 7+ * @ingroup SMWSparql
 8+ *
 9+ * @author Markus Krötzsch
 10+ */
 11+
 12+/**
 13+ * Class for accessing SPARQL query results in a unified form. The data is
 14+ * structured in tabular form, with each cell containing some SMWExpElement.
 15+ * Rows should always have the same number of columns, but the datatype of the
 16+ * cells in each column may not be uniform throughout the result.
 17+ *
 18+ * @ingroup SMWSparql
 19+ */
 20+class SMWSparqlResultWrapper implements Iterator {
 21+
 22+ /// Error code: no errors occurred.
 23+ const ERROR_NOERROR = 0;
 24+ /// Error code: service unreachable; result will be empty
 25+ const ERROR_UNREACHABLE = 1;
 26+
 27+ /**
 28+ * Associative array mapping SPARQL variable names to column indices.
 29+ * @var array of integer
 30+ */
 31+ protected $m_header;
 32+
 33+ /**
 34+ * List of result rows. Individual entries can be null if a cell in the
 35+ * SPARQL result table is empty (this is different from finding a blank
 36+ * node).
 37+ * @var array of array of (SMWExpElement or null)
 38+ */
 39+ protected $m_data;
 40+
 41+ /**
 42+ * Error code.
 43+ * @var integer
 44+ */
 45+ protected $m_errorCode;
 46+
 47+ /**
 48+ * Initialise a result set from a result string in SPARQL XML format.
 49+ *
 50+ * @param $header array mapping SPARQL variable names to column indices
 51+ * @param $data array of array of (SMWExpElement or null)
 52+ */
 53+ public function __construct( array $header, array $data, $errorCode = self::ERROR_NOERROR ) {
 54+ $this->m_header = $header;
 55+ $this->m_data = $data;
 56+ $this->m_errorCode = $errorCode;
 57+ reset( $this->m_data );
 58+ }
 59+
 60+ /**
 61+ * Get the number of rows in the result object.
 62+ *
 63+ * @return interger number of result rows
 64+ */
 65+ public function numRows() {
 66+ return count( $this->m_data );
 67+ }
 68+
 69+ /**
 70+ * Return error code. SMWSparqlResultWrapper::ERROR_NOERROR (0)
 71+ * indicates that no error occurred.
 72+ *
 73+ * @return interger error code
 74+ */
 75+ public function getErrorCode() {
 76+ return $this->m_errorCode;
 77+ }
 78+
 79+ /**
 80+ * Reset iterator to position 0. Standard method of Iterator.
 81+ */
 82+ public function rewind() {
 83+ reset( $this->m_data );
 84+ }
 85+
 86+ /**
 87+ * Return the current result row. Standard method of Iterator.
 88+ *
 89+ * @return array of (SMWExpElement or null), or false at end of data
 90+ */
 91+ public function current() {
 92+ return current( $this->m_data );
 93+ }
 94+
 95+ /**
 96+ * Return the next result row and advance the internal pointer.
 97+ * Standard method of Iterator.
 98+ *
 99+ * @return array of (SMWExpElement or null), or false at end of data
 100+ */
 101+ public function next() {
 102+ return next( $this->m_data );
 103+ }
 104+
 105+ /**
 106+ * Return the next result row and advance the internal pointer.
 107+ * Standard method of Iterator.
 108+ *
 109+ * @return array of (SMWExpElement or null), or false at end of data
 110+ */
 111+ public function key() {
 112+ return key( $this->m_data );
 113+ }
 114+
 115+ /**
 116+ * Return true if the internal pointer refers to a valid element.
 117+ * Standard method of Iterator.
 118+ *
 119+ * @return boolean
 120+ */
 121+ public function valid() {
 122+ return ( current( $this->m_data ) !== false );
 123+ }
 124+
 125+}
\ No newline at end of file
Property changes on: trunk/extensions/SemanticMediaWiki/includes/sparql/SMW_SparqlResultWrapper.php
___________________________________________________________________
Added: svn:eol-style
1126 + native
Index: trunk/extensions/SemanticMediaWiki/includes/SMW_Setup.php
@@ -195,6 +195,11 @@
196196 $wgAutoloadClasses['SMWRecordDescription'] = $smwgIP . 'includes/SMW_Record_Descriptions.php';
197197 $wgAutoloadClasses['SMWRecordFieldDescription'] = $smwgIP . 'includes/SMW_Record_Descriptions.php';
198198
 199+ $wgAutoloadClasses['SMWSparqlDatabase'] = $smwgIP . 'includes/sparql/SMW_SparqlDatabase.php';
 200+ $wgAutoloadClasses['SMWSparqlDatabaseError'] = $smwgIP . 'includes/sparql/SMW_SparqlDatabase.php';
 201+ $wgAutoloadClasses['SMWSparqlResultWrapper'] = $smwgIP . 'includes/sparql/SMW_SparqlResultWrapper.php';
 202+ $wgAutoloadClasses['SMWSparqlResultParser'] = $smwgIP . 'includes/sparql/SMW_SparqlResultParser.php';
 203+
199204 $stoDir = $smwgIP . 'includes/storage/';
200205 $wgAutoloadClasses['SMWQuery'] = $stoDir . 'SMW_Query.php';
201206 $wgAutoloadClasses['SMWQueryResult'] = $stoDir . 'SMW_QueryResult.php';

Status & tagging log