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 |
1 | 161 | + 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 |
1 | 138 | + 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 |
1 | 126 | + native |
Index: trunk/extensions/SemanticMediaWiki/includes/SMW_Setup.php |
— | — | @@ -195,6 +195,11 @@ |
196 | 196 | $wgAutoloadClasses['SMWRecordDescription'] = $smwgIP . 'includes/SMW_Record_Descriptions.php'; |
197 | 197 | $wgAutoloadClasses['SMWRecordFieldDescription'] = $smwgIP . 'includes/SMW_Record_Descriptions.php'; |
198 | 198 | |
| 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 | + |
199 | 204 | $stoDir = $smwgIP . 'includes/storage/'; |
200 | 205 | $wgAutoloadClasses['SMWQuery'] = $stoDir . 'SMW_Query.php'; |
201 | 206 | $wgAutoloadClasses['SMWQueryResult'] = $stoDir . 'SMW_QueryResult.php'; |