PageRenderTime 66ms CodeModel.GetById 27ms RepoModel.GetById 0ms app.codeStats 0ms

/Erfurt/Store/Adapter/Mssql.php

http://github.com/AKSW/Erfurt
PHP | 1576 lines | 1111 code | 288 blank | 177 comment | 168 complexity | 7aa0695cc58752aa4db1bffeba57d92f MD5 | raw file

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. /**
  3. * This file is part of the {@link http://aksw.org/Projects/Erfurt Erfurt} project.
  4. *
  5. * @copyright Copyright (c) 2012, {@link http://aksw.org AKSW}
  6. * @license http://opensource.org/licenses/gpl-license.php GNU General Public License (GPL)
  7. */
  8. require_once 'Erfurt/Store.php';
  9. require_once 'Erfurt/Store/Adapter/Interface.php';
  10. require_once 'Erfurt/Store/Sql/Interface.php';
  11. /**
  12. * Erfurt RDF Store - Adapter for the {@link http://www4.wiwiss.fu-berlin.de/bizer/rdfapi/ RAP} schema (modified) with
  13. * Zend_Db database abstraction layer.
  14. *
  15. * @package Erfurt_Store_Adapter
  16. * @author Philipp Frischmuth <pfrischmuth@googlemail.com>
  17. * @copyright Copyright (c) 2012 {@link http://aksw.org aksw}
  18. * @license http://opensource.org/licenses/gpl-license.php GNU General Public License (GPL)
  19. */
  20. class Erfurt_Store_Adapter_Mssql implements Erfurt_Store_Adapter_Interface, Erfurt_Store_Sql_Interface
  21. {
  22. // ------------------------------------------------------------------------
  23. // --- Private properties -------------------------------------------------
  24. // ------------------------------------------------------------------------
  25. private $_modelCache = array();
  26. private $_modelInfoCache = null;
  27. private $_dbConn = false;
  28. /** @var array */
  29. private $_titleProperties = array(
  30. 'http://www.w3.org/2000/01/rdf-schema#label',
  31. 'http://purl.org/dc/elements/1.1/title'
  32. );
  33. // ------------------------------------------------------------------------
  34. // --- Magic methods ------------------------------------------------------
  35. // ------------------------------------------------------------------------
  36. /**
  37. * Constructor
  38. *
  39. * @param array $adapterOptions This adapter class needs the following parameters:
  40. * - 'host'
  41. * - 'username'
  42. * - 'password'
  43. * - 'dbname'
  44. */
  45. public function __construct($adapterOptions = array())
  46. {
  47. $adapter = $adapterOptions['dbtype'];
  48. $host = isset($adapterOptions['host']) ? $adapterOptions['host'] : 'localhost';
  49. $username = $adapterOptions['username'];
  50. $password = $adapterOptions['password'];
  51. $dbname = $adapterOptions['dbname'];
  52. $adapterOptions = array(
  53. 'host' => $host,
  54. 'username' => $username,
  55. 'password' => $password,
  56. 'dbname' => $dbname,
  57. 'profiler' => false
  58. );
  59. if (extension_loaded('sqlsrv')) {
  60. require_once 'Zend/Db/Adapter/Sqlsrv.php';
  61. $this->_dbConn = new Zend_Db_Adapter_Sqlsrv($adapterOptions);
  62. } else {
  63. require_once 'Erfurt/Exception.php';
  64. throw new Erfurt_Exception('Sqlsrv extension not found.', -1);
  65. }
  66. try {
  67. // try to initialize the connection
  68. $this->_dbConn->getConnection();
  69. } catch (Zend_Db_Adapter_Exception $e) {
  70. // maybe wrong login credentials or db-server not running?!
  71. require_once 'Erfurt/Exception.php';
  72. throw new Erfurt_Exception('Could not connect to database with name: "' . $dbname . '". Please check your credentials and whether the database exists and the server is running.', -1);
  73. } catch (Zend_Exception $e) {
  74. // maybe a needed php extension is not loaded?!
  75. require_once 'Erfurt/Exception.php';
  76. throw new Erfurt_Exception('An error with the specified database adapter occured.', -1);
  77. }
  78. // we want indexed results
  79. //$this->_dbConn->setFetchMode(Zend_Db::FETCH_NUM);
  80. // load title properties for model titles
  81. $config = Erfurt_App::getInstance()->getConfig();
  82. if (isset($config->properties->title)) {
  83. $this->_titleProperties = $config->properties->title->toArray();
  84. }
  85. }
  86. public function __destruct()
  87. {
  88. #$log = Erfurt_App::getInstance()->getLog();
  89. #$profiles = $this->_dbConn->getProfiler()->getQueryProfiles();
  90. #foreach ($profiles as $profile) {
  91. # $debugStr = 'Query: ' . $profile->getQuery() . PHP_EOL;
  92. # $debugStr .= 'Time: ' . $profile->getElapsedSecs() . PHP_EOL;
  93. #
  94. # $log->debug($debugStr);
  95. #}
  96. }
  97. // ------------------------------------------------------------------------
  98. // --- Public methods (derived from Erfurt_Store_Adapter_Abstract) --------
  99. // ------------------------------------------------------------------------
  100. /** @see Erfurt_Store_Adapter_Interface */
  101. public function addMultipleStatements($graphUri, array $statementsArray, array $options = array())
  102. {
  103. $modelInfoCache = $this->_getModelInfos();
  104. $graphId = $modelInfoCache[$graphUri]['modelId'];
  105. $sqlQuery = 'INSERT INTO ef_stmt (g,s,p,o,s_r,p_r,o_r,st,ot,ol,od,od_r) VALUES ';
  106. $insertArray = array();
  107. $counter = 0;
  108. foreach ($statementsArray as $subject => $predicatesArray) {
  109. foreach ($predicatesArray as $predicate => $objectsArray) {
  110. foreach ($objectsArray as $object) {
  111. $sqlString = '';
  112. $s = $subject;
  113. $p = $predicate;
  114. $o = $object;
  115. // check whether the subject is a blank node
  116. if (substr((string)$s, 0, 2) === '_:') {
  117. $s = substr((string)$s, 2);
  118. $subjectIs = '1';
  119. } else {
  120. $subjectIs = '0';
  121. }
  122. // check the type of the object
  123. if ($o['type'] === 'uri') {
  124. $objectIs = '0';
  125. $lang = false;
  126. $dType = false;
  127. } else if ($o['type'] === 'bnode') {
  128. if (substr((string)$o['value'], 0, 2) === '_:') {
  129. $o['value'] = substr((string)$o['value'], 2);
  130. }
  131. $objectIs = '1';
  132. $lang = false;
  133. $dType = false;
  134. } else {
  135. $objectIs = '2';
  136. $lang = isset($o['lang']) ? $o['lang'] : '';
  137. $dType = isset($o['datatype']) ? $o['datatype'] : '';
  138. }
  139. $sRef = false;
  140. if (strlen((string)$s) > $this->_getSchemaRefThreshold()) {
  141. $subjectHash = md5((string)$s);
  142. try {
  143. $sRef = $this->_insertValueInto('ef_uri', $graphId, $s, $subjectHash);
  144. } catch (Erfurt_Store_Adapter_Exception $e) {
  145. $this->_dbConn->rollback();
  146. require_once 'Erfurt/Store/Adapter/Exception.php';
  147. throw new Erfurt_Store_Adapter_Exception($e->getMessage());
  148. }
  149. $s = substr((string)$s, 0, 128) . $subjectHash;
  150. }
  151. $pRef = false;
  152. if (strlen((string)$p) > $this->_getSchemaRefThreshold()) {
  153. $predicateHash = md5((string)$p);
  154. try {
  155. $pRef = $this->_insertValueInto('ef_uri', $graphId, $p, $predicateHash);
  156. } catch (Erfurt_Store_Adapter_Exception $e) {
  157. $this->_dbConn->rollback();
  158. require_once 'Erfurt/Store/Adapter/Exception.php';
  159. throw new Erfurt_Store_Adapter_Exception($e->getMessage());
  160. }
  161. $p = substr((string)$p, 0, 128) . $predicateHash;
  162. }
  163. $oRef = false;
  164. if (strlen((string)$o['value']) > $this->_getSchemaRefThreshold()) {
  165. $objectHash = md5((string)$o['value']);
  166. if ($o['type'] === 'literal') {
  167. $tableName = 'ef_lit';
  168. } else {
  169. $tableName = 'ef_uri';
  170. }
  171. try {
  172. $oRef = $this->_insertValueInto($tableName, $graphId, $o['value'], $objectHash);
  173. } catch (Erfurt_Store_Adapter_Exception $e) {
  174. $this->_dbConn->rollback();
  175. require_once 'Erfurt/Store/Adapter/Exception.php';
  176. throw new Erfurt_Store_Adapter_Exception($e->getMessage());
  177. }
  178. $o['value'] = substr((string)$o['value'], 0, 128) . $objectHash;
  179. }
  180. $oValue = addslashes($o['value']);
  181. $sqlString .= "($graphId,'$s','$p','$oValue',";
  182. #$data = array(
  183. # 'g' => $graphId,
  184. # 's' => $subject,
  185. # 'p' => $predicate,
  186. # 'o' => $object['value'],
  187. # 'st' => $subjectIs,
  188. # 'ot' => $objectIs
  189. #);
  190. if ($sRef !== false) {
  191. $sqlString .= "$sRef,";
  192. } else {
  193. $sqlString .= "NULL,";
  194. }
  195. if ($pRef !== false) {
  196. $sqlString .= "$pRef,";
  197. } else {
  198. $sqlString .= "NULL,";
  199. }
  200. if ($oRef !== false) {
  201. $sqlString .= "$oRef,";
  202. } else {
  203. $sqlString .= "NULL,";
  204. }
  205. $sqlString .= "$subjectIs,$objectIs,'$lang',";
  206. #$data['ol'] = $lang;
  207. if (strlen((string)$dType) > $this->_getSchemaRefThreshold()) {
  208. $dTypeHash = md5((string)$dType);
  209. try {
  210. $dtRef = $this->_insertValueInto('ef_uri', $graphId, $dType, $dTypeHash);
  211. } catch (Erfurt_Store_Adapter_Exception $e) {
  212. $this->_dbConn->rollback();
  213. require_once 'Erfurt/Store/Adapter/Exception.php';
  214. throw new Erfurt_Store_Adapter_Exception($e->getMessage());
  215. }
  216. $dType = substr((string)$data['od'], 0, 128) . $dTypeHash;
  217. $data['od_r'] = $dtRef;
  218. $sqlString .= "'$dType',$dtRef)";
  219. } else {
  220. #$data['od'] = $dType;
  221. $sqlString .= "'$dType',NULL)";
  222. }
  223. // $insertArray[] = $sqlString;
  224. $counter++;
  225. $finalquery = 'IF NOT EXISTS(Select g, s, p, o, st, ot, ol, od from ef_stmt
  226. WHERE
  227. g = \''.$graphId.'\' AND
  228. s = \''.$s.'\' AND
  229. p = \''.$p.'\' AND
  230. o = \''.$oValue.'\' AND
  231. st = \''.$subjectIs.'\' AND
  232. ot = \''.$objectIs.'\' AND
  233. ol = \''.$lang.'\' AND
  234. od = \''.$dType.'\'
  235. )'.$sqlQuery. $sqlString;
  236. $this->sqlQuery((string)$finalquery);
  237. }
  238. }
  239. }
  240. if (defined('_EFDEBUG')) {
  241. $logger = Erfurt_App::getInstance()->getLog();
  242. $logger->info('ZendDb multiple statements added: ' . $counter);
  243. }
  244. if ($counter > 100) {
  245. $this->_optimizeTables();
  246. }
  247. }
  248. protected function _getNormalizedErrorCode()
  249. {
  250. if ($this->_dbConn instanceof Zend_Db_Adapter_Mysqli) {
  251. switch($this->_dbConn->getConnection()->errno) {
  252. case 1062:
  253. // duplicate entry
  254. return 1000;
  255. }
  256. } else {
  257. return -1;
  258. }
  259. }
  260. /** @see Erfurt_Store_Adapter_Interface */
  261. public function addStatement($graphUri, $subject, $predicate, $object, array $options = array())
  262. {
  263. $statementArray = array();
  264. $statementArray["$subject"] = array();
  265. $statementArray["$subject"]["$predicate"] = array();
  266. $statementArray["$subject"]["$predicate"][] = $object;
  267. try {
  268. $this->addMultipleStatements($graphUri, $statementArray);
  269. } catch (Erfurt_Store_Adapter_Exception $e) {
  270. require_once 'Erfurt/Store/Adapter/Exception.php';
  271. throw new Erfurt_Store_Adapter_Exception('Insertion of statement failed:' .
  272. $e->getMessage());
  273. }
  274. }
  275. /** @see Erfurt_Store_Adapter_Interface */
  276. public function countWhereMatches($graphIris, $whereSpec, $countSpec, $distinct = false)
  277. {
  278. $query = new Erfurt_Sparql_SimpleQuery();
  279. if(!$distinct){
  280. $query->setProloguePart("COUNT DISTINCT $countSpec"); // old way: distinct has no effect !!!
  281. } else {
  282. $query->setProloguePart("COUNT-DISTINCT $countSpec"); // i made a (unccol) hack to fix this, the "-" ist there because i didnt want to change tokenization
  283. }
  284. $query->setFrom($graphIris)
  285. ->setWherePart($whereSpec);
  286. $result = $this->sparqlQuery($query);
  287. if ($result) {
  288. return $result;
  289. }
  290. return 0;
  291. }
  292. /** @see Erfurt_Store_Sql_Interface */
  293. public function createTable($tableName, array $columns)
  294. {
  295. if ($this->_dbConn instanceof Zend_Db_Adapter_Mysqli) {
  296. return $this->_createTableMysql($tableName, $columns);
  297. }
  298. }
  299. /** @see Erfurt_Store_Adapter_Interface */
  300. public function createModel($graphUri, $type = Erfurt_Store::MODEL_TYPE_OWL)
  301. {
  302. $data = array(
  303. 'uri' => &$graphUri
  304. );
  305. $baseUri = $graphUri;
  306. if ($baseUri !== '') {
  307. $data['base'] = $baseUri;
  308. }
  309. // insert the new model into the database
  310. $this->_dbConn->insert('ef_graph', $data);
  311. $graphId = $this->lastInsertId();
  312. $uriRef = false;
  313. if (strlen($graphUri) > $this->_getSchemaRefThreshold()) {
  314. $uriHash = md5($uri);
  315. $uriData = array(
  316. 'g' => $graphid,
  317. 'v' => $uri,
  318. 'vh' => $uriHash);
  319. $uriRef = $this->_insertValueInto('ef_uri', $uriData);
  320. $updateData = array(
  321. 'uri' => $uriHash,
  322. 'uri_r' => $uriRef);
  323. $this->_dbConn->update('ef_graph', $updateData, "id = graphId");
  324. }
  325. $baseRef = false;
  326. if (strlen($baseUri) > $this->_getSchemaRefThreshold()) {
  327. $baseHash = md5($baseUri);
  328. $baseData = array(
  329. 'g' => $graphid,
  330. 'v' => $baseUri,
  331. 'vh' => $baseHash);
  332. $baseRef = $this->_insertValueInto('ef_uri', $baseData);
  333. $updateData = array(
  334. 'base' => $baseHash,
  335. 'base_r' => $baseRef);
  336. $this->_dbConn->update('ef_graph', $updateData, "id = graphId");
  337. }
  338. // invalidate the cache and fetch model infos again
  339. require_once 'Erfurt/App.php';
  340. $cache = Erfurt_App::getInstance()->getCache();
  341. $cache->clean(Zend_Cache::CLEANING_MODE_MATCHING_TAG, array('model_info'));
  342. $this->_modelInfoCache = null;
  343. if ($type === Erfurt_Store::MODEL_TYPE_OWL) {
  344. $this->addStatement($graphUri, $graphUri, EF_RDF_TYPE, array('type' => 'uri', 'value' => EF_OWL_ONTOLOGY));
  345. $this->_modelInfoCache = null;
  346. }
  347. }
  348. /** @see Erfurt_Store_Adapter_Interface */
  349. public function deleteMatchingStatements($graphUri, $subject, $predicate, $object, array $options = array())
  350. {
  351. $modelInfoCache = $this->_getModelInfos();
  352. $modelId = $modelInfoCache[$graphUri]['modelId'];
  353. if ($subject !== null && strlen($subject) > $this->_getSchemaRefThreshold()) {
  354. $subject = substr($subject, 0, 128) . md5($subject);
  355. }
  356. if ($predicate !== null && strlen($predicate) > $this->_getSchemaRefThreshold()) {
  357. $predicate = substr($predicate, 0, 128) . md5($predicate);
  358. }
  359. if ($object !== null && strlen($object['value']) > $this->_getSchemaRefThreshold()) {
  360. $object = substr($object['value'], 0, 128) . md5($object['value']);
  361. }
  362. $operator = array('0' => '', '1' => 'AND');
  363. $firstparam = 0;
  364. // determine the rows, which should be deleted by the given parameters
  365. if ($subject !== null) {
  366. $whereString .= " $operator[$firstparam] s = '$subject'";
  367. $firstparam = 1;
  368. }
  369. if ($predicate !== null) {
  370. $whereString .= " $operator[$firstparam] p = '$predicate'";
  371. $firstparam = 1;
  372. }
  373. if (null !== $subject) {
  374. if (substr($subject, 0, 2) === '_:') {
  375. $whereString .= " $operator[$firstparam] st = 1";
  376. $firstparam = 1;
  377. } else {
  378. $whereString .= " $operator[$firstparam] st = 0";
  379. $firstparam = 1;
  380. }
  381. }
  382. if (null !== $object) {
  383. if (isset($object['value'])) {
  384. $whereString .= $operator[$firstparam]. 'o = \''.$object['value'].'\'';
  385. $firstparam = 1;
  386. }
  387. if (isset($object['type'])) {
  388. switch ($object['type']) {
  389. case 'uri':
  390. $whereString .= $operator[$firstparam].' ot = 0';
  391. $firstparam = 1;
  392. break;
  393. case 'literal':
  394. $whereString .= $operator[$firstparam].' ot = 2';
  395. $firstparam = 1;
  396. break;
  397. case 'bnode':
  398. $whereString .= $operator[$firstparam].' ot = 1';
  399. $firstparam = 1;
  400. break;
  401. }
  402. }
  403. if (isset($object['lang'])) {
  404. $whereString .= $operator[$firstparam].' ol = \''. $object['lang'] . '\'';
  405. $firstparam = 1;
  406. }
  407. if (isset($object['datatype'])) {
  408. if (strlen($object['datatype']) > $this->_getSchemaRefThreshold()) {
  409. $whereString .= $operator[$firstparam].' od = \'' . substr($object['datatype'], 0, 128) .
  410. md5($object['datatype']) . '\'';
  411. $firstparam = 1;
  412. } else {
  413. $whereString .= $operator[$firstparam].' od = \'' . $object['datatype'] . '\'';
  414. $firstparam = 1;
  415. }
  416. }
  417. }
  418. // remove the specified statements from the database
  419. $ret = $this->_dbConn->delete('ef_stmt', $whereString);
  420. // Clean up ef_uri and ef_lit table
  421. $this->_cleanUpValueTables($graphUri);
  422. // return number of affected rows (>0 means there were triples deleted)
  423. return $ret;
  424. }
  425. /** @see Erfurt_Store_Adapter_Interface */
  426. public function deleteMultipleStatements($graphUri, array $statementsArray)
  427. {
  428. $modelInfoCache = $this->_getModelInfos();
  429. $modelId = $modelInfoCache[$graphUri]['modelId'];
  430. $this->_dbConn->beginTransaction();
  431. try {
  432. foreach ($statementsArray as $subject => $predicatesArray) {
  433. foreach ($predicatesArray as $predicate => $objectsArray) {
  434. foreach ($objectsArray as $object) {
  435. $whereString = 'g = ' . $modelId . ' ';
  436. // check whether the subject is a blank node
  437. if (substr($subject, 0, 2) === '_:') {
  438. $subject = substr($subject, 2);
  439. $whereString .= 'AND st = 1 ';
  440. } else {
  441. $whereString .= 'AND st = 0 ';
  442. }
  443. // check the type of the object
  444. if ($object['type'] === 'uri') {
  445. $whereString .= 'AND ot = 0 ';
  446. } else if ($object['type'] === 'bnode') {
  447. $whereString .= 'AND ot = 1 ';
  448. } else {
  449. $whereString .= 'AND ot = 2 ';
  450. $whereString .= isset($object['lang']) ? 'AND ol = \'' . $object['lang'] . '\' ' : '';
  451. $whereString .= isset($object['datatype']) ? 'AND od = \'' . $object['datatype'] .
  452. '\' ' : '';
  453. }
  454. if (strlen((string)$subject) > $this->_getSchemaRefThreshold()) {
  455. $subjectHash = md5((string)$subject);
  456. $subject = substr((string)$subject, 0, 128) . $subjectHash;
  457. }
  458. if (strlen((string)$predicate) > $this->_getSchemaRefThreshold()) {
  459. $predicateHash = md5((string)$predicate);
  460. $predicate = substr((string)$predicate, 0, 128) . $predicateHash;
  461. }
  462. if (strlen((string)$object['value']) > $this->_getSchemaRefThreshold()) {
  463. $objectHash = md5((string)$object['value']);
  464. $object = substr((string)$object['value'], 0, 128) . $objectHash;
  465. } else {
  466. $object = $object['value'];
  467. }
  468. $whereString .= 'AND s = \'' . $subject . '\' ';
  469. $whereString .= 'AND p = \'' . $predicate . '\' ';
  470. $whereString .= 'AND o = \'' . str_replace('\'', '\\\'', $object) . '\' ';
  471. $this->_dbConn->delete('ef_stmt', $whereString);
  472. }
  473. }
  474. }
  475. // if everything went ok... commit the changes to the database
  476. $this->_dbConn->commit();
  477. $this->_cleanUpValueTables($graphUri);
  478. } catch (Exception $e) {
  479. // something went wrong... rollback
  480. $this->_dbConn->rollback();
  481. require_once 'Erfurt/Store/Adapter/Exception.php';
  482. throw new Erfurt_Store_Adapter_Exception('Bulk deletion of statements failed.'.$e->getMessage());
  483. }
  484. }
  485. /** @see Erfurt_Store_Adapter_Interface */
  486. public function deleteModel($graphUri)
  487. {
  488. $modelInfoCache = $this->_getModelInfos();
  489. if (isset($modelInfoCache[$graphUri]['modelId'])) {
  490. $graphId = $modelInfoCache[$graphUri]['modelId'];
  491. } else {
  492. require_once 'Erfurt/Store/Adapter/Exception.php';
  493. throw new Erfurt_Store_Adapter_Exception('Model deletion failed: No db id found for model URL.');
  494. }
  495. // remove all rows with the specified modelID from the models, statements and namespaces tables
  496. $this->_dbConn->delete('ef_graph', "id = $graphId");
  497. $this->_dbConn->delete('ef_stmt', "g = $graphId");
  498. $this->_dbConn->delete('ef_uri', "g = $graphId");
  499. $this->_dbConn->delete('ef_lit', "g = $graphId");
  500. // invalidate the cache and fetch model infos again
  501. require_once 'Erfurt/App.php';
  502. $cache = Erfurt_App::getInstance()->getCache();
  503. $tags = array('model_info', $modelInfoCache[$graphUri]['modelId']);
  504. #$cache->clean(Zend_Cache::CLEANING_MODE_MATCHING_TAG, $tags);
  505. $this->_modelCache = array();
  506. $this->_modelInfoCache = null;
  507. }
  508. /** @see Erfurt_Store_Adapter_Interface */
  509. public function exportRdf($modelIri, $serializationType = 'xml', $filename = false)
  510. {
  511. require_once 'Erfurt/Store/Adapter/Exception.php';
  512. throw new Erfurt_Store_Adapter_Exception('Not implemented yet.');
  513. }
  514. /** @see Erfurt_Store_Adapter_Interface */
  515. public function getAvailableModels()
  516. {
  517. $modelInfoCache = $this->_getModelInfos();
  518. $models = array();
  519. foreach ($modelInfoCache as $mInfo) {
  520. $models[$mInfo['modelIri']] = true;
  521. }
  522. return $models;
  523. }
  524. public function getBackendName()
  525. {
  526. return 'ZendDb';
  527. }
  528. /** @see Erfurt_Store_Adapter_Interface */
  529. public function getBlankNodePrefix()
  530. {
  531. return 'bNode';
  532. }
  533. /**
  534. * Returns a list of graph uris, where each graph in the list contains at least
  535. * one statement where the given resource uri is subject.
  536. *
  537. * @param string $resourceUri
  538. * @return array
  539. */
  540. public function getGraphsUsingResource($resourceUri)
  541. {
  542. $sqlQuery = 'SELECT DISTINCT g.uri FROM ef_stmt s
  543. LEFT JOIN ef_graph g ON ( g.id = s.g)
  544. WHERE s.s = \'' . $resourceUri . '\'';
  545. $sqlResult = $this->sqlQuery($sqlQuery);
  546. $result = array();
  547. foreach ($sqlResult as $row) {
  548. $result[] = $row['uri'];
  549. }
  550. return $result;
  551. }
  552. /**
  553. * Recursively gets owl:imported model IRIs starting with $modelIri as root.
  554. *
  555. * @param string $modelIri
  556. */
  557. public function getImportsClosure($modelIri)
  558. {
  559. $modelInfoCache = $this->_getModelInfos();
  560. if (isset($modelInfoCache["$modelIri"]['imports'])) {
  561. return $modelInfoCache["$modelIri"]['imports'];
  562. } else {
  563. return array();
  564. }
  565. }
  566. /** @see Erfurt_Store_Adapter_Interface */
  567. public function getModel($modelIri)
  568. {
  569. // if model is already in cache return the cached value
  570. if (isset($this->_modelCache[$modelIri])) {
  571. return $this->_modelCache[$modelIri];
  572. }
  573. $modelInfoCache = $this->_getModelInfos();
  574. $baseUri = $modelInfoCache[$modelIri]['baseIri'];
  575. if ($baseUri === '') {
  576. $baseUri = null;
  577. }
  578. // choose the right type for the model instance and instanciate it
  579. if ($modelInfoCache[$modelIri]['type'] === 'owl') {
  580. require_once 'Erfurt/Owl/Model.php';
  581. $m = new Erfurt_Owl_Model($modelIri, $baseUri);
  582. } else if ($this->_modelInfoCache[$modelIri]['type'] === 'rdfs') {
  583. require_once 'Erfurt/Rdfs/Model.php';
  584. $m = new Erfurt_Rdfs_Model($modelIri, $baseUri);
  585. } else {
  586. require_once 'Erfurt/Rdf/Model.php';
  587. $m = new Erfurt_Rdf_Model($modelIri, $baseUri);
  588. }
  589. $this->_modelCache[$modelIri] = $m;
  590. return $m;
  591. }
  592. /** @see Erfurt_Store_Adapter_Interface */
  593. public function getNewModel($graphUri, $baseUri = '', $type = 'owl')
  594. {
  595. $data = array(
  596. 'uri' => &$graphUri
  597. );
  598. if ($baseUri !== '') {
  599. $data['base'] = $baseUri;
  600. }
  601. // insert the new model into the database
  602. $this->_dbConn->insert('ef_graph', $data);
  603. $graphId = $this->lastInsertId();
  604. $uriRef = false;
  605. if (strlen($graphUri) > $this->_getSchemaRefThreshold()) {
  606. $uriHash = md5($uri);
  607. $uriData = array(
  608. 'g' => $graphid,
  609. 'v' => $uri,
  610. 'vh' => $uriHash);
  611. $uriRef = $this->_insertValueInto('ef_uri', $uriData);
  612. $updateData = array(
  613. 'uri' => $uriHash,
  614. 'uri_r' => $uriRef);
  615. $this->_dbConn->update('ef_graph', $updateData, "id = graphId");
  616. }
  617. $baseRef = false;
  618. if (strlen($baseUri) > $this->_getSchemaRefThreshold()) {
  619. $baseHash = md5($baseUri);
  620. $baseData = array(
  621. 'g' => $graphid,
  622. 'v' => $baseUri,
  623. 'vh' => $baseHash);
  624. $baseRef = $this->_insertValueInto('ef_uri', $baseData);
  625. $updateData = array(
  626. 'base' => $baseHash,
  627. 'base_r' => $baseRef);
  628. $this->_dbConn->update('ef_graph', $updateData, "id = graphId");
  629. }
  630. // invalidate the cache and fetch model infos again
  631. require_once 'Erfurt/App.php';
  632. $cache = Erfurt_App::getInstance()->getCache();
  633. $cache->clean(Zend_Cache::CLEANING_MODE_MATCHING_TAG, array('model_info'));
  634. $this->_modelInfoCache = null;
  635. if ($type === 'owl') {
  636. $this->addStatement($graphUri, $graphUri, EF_RDF_TYPE, array('type' => 'uri', 'value' => EF_OWL_ONTOLOGY));
  637. $this->_modelInfoCache = null;
  638. }
  639. // instanciate the model
  640. $m = $this->getModel($graphUri);
  641. return $m;
  642. }
  643. /** @see Erfurt_Store_Adapter_Interface */
  644. public function getSupportedExportFormats()
  645. {
  646. return array();
  647. }
  648. /** @see Erfurt_Store_Adapter_Interface */
  649. public function getSupportedImportFormats()
  650. {
  651. return array();
  652. }
  653. /** @see Erfurt_Store_Adapter_Interface */
  654. public function importRdf($modelUri, $data, $type, $locator)
  655. {
  656. // TODO fix or remove
  657. if ($this->_dbConn instanceof Zend_Db_Adapter_Mysqli) {
  658. require_once 'Erfurt/Syntax/RdfParser.php';
  659. $parser = Erfurt_Syntax_RdfParser::rdfParserWithFormat($type);
  660. $parsedArray = $parser->parse($data, $locator, $modelUri, false);
  661. $modelInfoCache = $this->_getModelInfos();
  662. $modelId = $modelInfoCache["$modelUri"]['modelId'];
  663. // create file
  664. $tmpDir = Erfurt_App::getInstance()->getTmpDir();
  665. $filename = $tmpDir . 'import' . md5((string)time()) . '.csv';
  666. $fileHandle = fopen($filename, 'w');
  667. $count = 0;
  668. $longStatements = array();
  669. foreach ($parsedArray as $s => $pArray) {
  670. if (substr($s, 0, 2) === '_:') {
  671. $s = substr($s, 2);
  672. $sType = '1';
  673. } else {
  674. $sType = '0';
  675. }
  676. foreach ($pArray as $p => $oArray) {
  677. foreach ($oArray as $o) {
  678. // to long values need to be put in a different table, so we can't bulk insert these
  679. // values, for they need a foreign key
  680. if (strlen($s) > $this->_getSchemaRefThreshold() ||
  681. strlen($p) > $this->_getSchemaRefThreshold() ||
  682. strlen($o['value']) > $this->_getSchemaRefThreshold() ||
  683. (isset($o['datatype']) && strlen($o['datatype']) > $this->_getSchemaRefThreshold())) {
  684. $longStatements[] = array(
  685. 's' => $s,
  686. 'p' => $p,
  687. 'o' => $o
  688. );
  689. continue;
  690. }
  691. if ($o['type'] === 'literal') {
  692. $oType = '2';
  693. } else if ($o['type'] === 'bnode') {
  694. if (substr($o['value'], 0, 2) === '_:') {
  695. $o['value'] = substr($o['value'], 2);
  696. }
  697. $oType = '1';
  698. } else {
  699. $oType = '0';
  700. }
  701. $lineString = $modelId . ';' . $s . ';' . $p . ';' . $o['value'] . ';';
  702. $lineString .= "\N;\N;\N;";
  703. $lineString .= $sType . ';' . $oType . ';';
  704. if (isset($o['lang'])) {
  705. $lineString .= $o['lang'];
  706. } else {
  707. $lineString .= "\N";
  708. }
  709. $lineString .= ';';
  710. if (isset($o['datatype'])) {
  711. $lineString .= $o['datatype'] . ";\N";
  712. } else {
  713. $lineString .= "\N;\N";
  714. }
  715. $lineString .= PHP_EOL;
  716. $count++;
  717. fputs($fileHandle, $lineString);
  718. }
  719. }
  720. }
  721. fclose($fileHandle);
  722. if ($count > 10000) {
  723. $this->_dbConn->getConnection()->query('ALTER TABLE ef_stmt DISABLE KEYS');
  724. }
  725. $sql = "LOAD DATA INFILE '$filename' IGNORE INTO TABLE ef_stmt
  726. FIELDS TERMINATED BY ';'
  727. (g, s, p, o, s_r, p_r, o_r, st, ot, ol, od, od_r);";
  728. $this->_dbConn->getConnection()->query('START TRANSACTION;');
  729. $this->_dbConn->getConnection()->query($sql);
  730. $this->_dbConn->getConnection()->query('COMMIT');
  731. // Delete the temp file
  732. unlink($filename);
  733. // Now add the long-value-statements
  734. foreach($longStatements as $stm) {
  735. $sId = false;
  736. $pId = false;
  737. $oId = false;
  738. $dtId = false;
  739. $s = $stm['s'];
  740. $p = $stm['p'];
  741. $o = $stm['o']['value'];
  742. if (strlen($s) > $this->_getSchemaRefThreshold()) {
  743. $sHash = md5($s);
  744. $sId = $this->_insertValueInto('ef_uri', $modelId, $s, $sHash);
  745. $s = substr($s, 0, 128) . $sHash;
  746. }
  747. if (strlen($p) > $this->_getSchemaRefThreshold()) {
  748. $pHash = md5($p);
  749. $pId = $this->_insertValueInto('ef_uri', $modelId, $p, $pHash);
  750. $p = substr($p, 0, 128) . $pHash;
  751. }
  752. if (strlen($o) > $this->_getSchemaRefThreshold()) {
  753. $oHash = md5($o);
  754. if ($stm['o']['type'] === 'literal') {
  755. $oId = $this->_insertValueInto('ef_lit', $modelId, $o, $oHash);
  756. } else {
  757. $oId = $this->_insertValueInto('ef_uri', $modelId, $o, $oHash);
  758. }
  759. $o = substr($o, 0, 128) . $oHash;
  760. }
  761. if (isset($stm['o']['datatype']) && strlen($stm['o']['datatype']) > $this->_getSchemaRefThreshold()) {
  762. $oDtHash = md5($stm['o']['datatype']);
  763. $dtId = $this->_insertValueInto('ef_uri', $modelId, $stm['o']['datatype'], $oDtHash);
  764. $oDt = substr($oDt, 0, 128) . $oDtHash;
  765. }
  766. $sql = "INSERT INTO ef_stmt
  767. (g,s,p,o,s_r,p_r,o_r,st,ot,ol,od,od_r)
  768. VALUES ($modelId,'$s','$p','$o',";
  769. if ($sId !== false) {
  770. $sql .= $sId . ',';
  771. } else {
  772. $sql .= "NULL,";
  773. }
  774. if ($pId !== false) {
  775. $sql .= $pId . ',';
  776. } else {
  777. $sql .= "NULL,";
  778. }
  779. if ($oId !== false) {
  780. $sql .= $oId . ',';
  781. } else {
  782. $sql .= "NULL,";
  783. }
  784. if (substr($stm['s'], 0, 2) === '_:') {
  785. $sql .= '1,';
  786. } else {
  787. $sql .= '0,';
  788. }
  789. if ($stm['o']['type'] === 'literal') {
  790. $sql .= '2,';
  791. } else if ($stm['o']['type'] === 'uri') {
  792. $sql .= '0,';
  793. } else {
  794. $sql .= '1,';
  795. }
  796. if (isset($stm['o']['lang'])) {
  797. $sql .= '"' . $stm['o']['lang'] . '",';
  798. } else {
  799. $sql .= "NULL,";
  800. }
  801. if (isset($stm['o']['datatype'])) {
  802. if ($dtId !== false) {
  803. $sql .= '"' . $oDt . '",' . $dtId . ')';
  804. } else {
  805. $sql .= '"' . $stm['o']['datatype'] . '",' . "\N)";
  806. }
  807. } else {
  808. $sql .= "NULL,NULL)";
  809. }
  810. //$this->_dbConn->getConnection()->query($sql);
  811. }
  812. if ($count > 10000) {
  813. $this->_dbConn->getConnection()->query('ALTER TABLE ef_stmt ENABLE KEYS');
  814. }
  815. $this->_optimizeTables();
  816. } else {
  817. require_once 'Erfurt/Store/Adapter/Exception.php';
  818. throw new Erfurt_Store_Adapter_Exception('CSV import not supported for this database server.');
  819. }
  820. }
  821. public function init()
  822. {
  823. $this->_modelInfoCache = null;
  824. }
  825. /** @see Erfurt_Store_Adapter_Interface */
  826. public function isModelAvailable($modelIri)
  827. {
  828. $modelInfoCache = $this->_getModelInfos();
  829. if (isset($modelInfoCache[$modelIri])) {
  830. return true;
  831. } else {
  832. return false;
  833. }
  834. }
  835. /** @see Erfurt_Store_Sql_Interface */
  836. public function lastInsertId()
  837. {
  838. return $this->_dbConn->lastInsertId();
  839. }
  840. /** @see Erfurt_Store_Sql_Interface */
  841. public function listTables($prefix = '')
  842. {
  843. return $this->_dbConn->listTables();
  844. }
  845. /** @see Erfurt_Store_Adapter_Interface */
  846. public function sparqlAsk($query)
  847. {
  848. //TODO works for me...., why hasnt this be enabled earlier? is the same as sparqlQuery... looks like the engine supports it. but there is probably a reason for this not to be supported
  849. $start = microtime(true);
  850. require_once 'Erfurt/Sparql/EngineDb/Adapter/EfZendDb.php';
  851. $engine = new Erfurt_Sparql_EngineDb_Adapter_EfZendDb($this->_dbConn, $this->_getModelInfos());
  852. require_once 'Erfurt/Sparql/Parser.php';
  853. $parser = new Erfurt_Sparql_Parser();
  854. if(!($query instanceof Erfurt_Sparql_Query))
  855. $query = $parser->parse((string)$query);
  856. $result = $engine->queryModel($query);
  857. // Debug executed SPARQL queries in debug mode (7)
  858. $logger = Erfurt_App::getInstance()->getLog();
  859. $time = (microtime(true) - $start)*1000;
  860. $debugText = 'SPARQL Query (' . $time . ' ms)';
  861. $logger->debug($debugText);
  862. return $result;
  863. }
  864. /** @see Erfurt_Store_Adapter_Interface */
  865. public function sparqlQuery($query, $options=array())
  866. {
  867. $resultform =(isset($options[Erfurt_Store::RESULTFORMAT]))?$options[Erfurt_Store::RESULTFORMAT]:Erfurt_Store::RESULTFORMAT_PLAIN;
  868. $start = microtime(true);
  869. require_once 'Erfurt/Sparql/EngineDb/Adapter/EfZendDb.php';
  870. $engine = new Erfurt_Sparql_EngineDb_Adapter_EfZendDb($this->_dbConn, $this->_getModelInfos());
  871. require_once 'Erfurt/Sparql/Parser.php';
  872. $parser = new Erfurt_Sparql_Parser();
  873. if(!($query instanceof Erfurt_Sparql_Query)) {
  874. $query = $parser->parse((string)$query);
  875. }
  876. $result = $engine->queryModel($query, $resultform);
  877. // Debug executed SPARQL queries in debug mode (7)
  878. $logger = Erfurt_App::getInstance()->getLog();
  879. $time = (microtime(true) - $start)*1000;
  880. $debugText = 'SPARQL Query (' . $time . ' ms)';
  881. $logger->debug($debugText);
  882. return $result;
  883. }
  884. /** @see Erfurt_Store_Sql_Interface */
  885. public function sqlQuery($sqlQuery, $limit = PHP_INT_MAX, $offset = 0)
  886. {
  887. $start = microtime(true);
  888. // add limit/offset
  889. if ($limit < PHP_INT_MAX) {
  890. $sqlQuery = sprintf('%s LIMIT %d OFFSET %d', (string)$sqlQuery, (int)$limit, (int)$offset);
  891. }
  892. $queryType = strtolower(substr($sqlQuery, 0, 6));
  893. if ( $queryType === 'insert' ||
  894. $queryType === 'update' ||
  895. $queryType === 'create' ||
  896. $queryType === 'if not' ||
  897. $queryType === 'delete') {
  898. // Handle without ZendDb
  899. $result = $this->_dbConn->query($sqlQuery);
  900. //SQLSRVCHANGE - Hack - result kommt immer falsch zur?ck - Queries werden aber ausgef?hrt....?
  901. if($result != NULL)
  902. {
  903. $result = true;
  904. }
  905. if ($result !== true) {
  906. require_once 'Erfurt/Store/Adapter/Exception.php';
  907. throw new Erfurt_Store_Adapter_Exception(/*var_dump($result).'<br>'.$sqlQuery.'<br>*/'SQL query failed: ' .
  908. $this->_dbConn->getConnection()->error);
  909. }
  910. } else {
  911. try {
  912. $result = $this->_dbConn->fetchAll($sqlQuery);
  913. //SQLSRV - Result ist immer true und es kommt keine Exception...
  914. } catch (Zend_Db_Exception $e) { #return false;
  915. require_once 'Erfurt/Store/Adapter/Exception.php';
  916. throw new Erfurt_Store_Adapter_Exception($e->getMessage());
  917. }
  918. }
  919. // Debug executed SQL queries in debug mode (7)
  920. $logger = Erfurt_App::getInstance()->getLog();
  921. $time = (microtime(true) - $start)*1000;
  922. $debugText = 'SQL Query (' . $time . ' ms)';
  923. $logger->debug($debugText);
  924. return $result;
  925. }
  926. // ------------------------------------------------------------------------
  927. // --- Private methods ----------------------------------------------------
  928. // ------------------------------------------------------------------------
  929. /**
  930. * @throws Erfurt_Exception Throws exception if something goes wrong while initialization of database.
  931. */
  932. private function _createTables()
  933. {
  934. return $this->_createTablesSqlsrv();
  935. }
  936. /**
  937. * This internal function creates the table structure for MS SQL Server databases.
  938. */
  939. private function _createTablesSqlsrv()
  940. {
  941. //#####################################################################
  942. // Create table ef_info if not existing
  943. $sqlsrv ='IF NOT EXISTS
  944. ( SELECT * FROM SysObjects WHERE [Name] = \'ef_info\')
  945. CREATE TABLE ef_info (
  946. id TINYINT NOT NULL IDENTITY(1,1),
  947. schema_id VARCHAR(10) COLLATE SQL_Latin1_General_CP1_CS_AS NOT NULL
  948. ) ON [PRIMARY]';
  949. $success = $this->_dbConn->query($sqlsrv);
  950. if (!$success) {
  951. require_once 'Erfurt/Store/Adapter/Exception.php';
  952. throw new Erfurt_Store_Adapter_Exception('Creation of table "ef_info" failed: ' .
  953. $this->_dbConn->getConnection()->error);
  954. }
  955. // Insert id of the current schema into the ef_info table.
  956. $sql = 'INSERT INTO ef_info (schema_id) VALUES (1.0)';
  957. $success = false;
  958. $success = $this->_dbConn->query($sql);
  959. if (!$success) {
  960. require_once 'Erfurt/Store/Adapter/Exception.php';
  961. throw new Erfurt_Store_Adapter_Exception('Insertion of "schema_id" into "ef_info" failed: ' .
  962. $this->_dbConn->getConnection()->error);
  963. }
  964. //#####################################################################
  965. // Create table ef_graph if not existing
  966. $sqlsrv ='IF NOT EXISTS
  967. ( SELECT * FROM SysObjects WHERE [Name] = \'ef_graph\')
  968. BEGIN
  969. CREATE TABLE ef_graph (
  970. id TINYINT NOT NULL IDENTITY(1,1),
  971. uri VARCHAR(160)COLLATE SQL_Latin1_General_CP1_CS_AS NOT NULL,
  972. uri_r INT NULL,
  973. base VARCHAR(160)COLLATE SQL_Latin1_General_CP1_CS_AS NOT NULL,
  974. base_r INT NULL
  975. ) ON [PRIMARY]
  976. CREATE UNIQUE INDEX unique_uri ON ef_graph (uri)
  977. END';
  978. $success = $this->_dbConn->query($sqlsrv);
  979. if (!$success) {
  980. require_once 'Erfurt/Store/Adapter/Exception.php';
  981. throw new Erfurt_Store_Adapter_Exception('Creation of table "ef_info" failed: ' .
  982. $this->_dbConn->getConnection()->error);
  983. }
  984. //#####################################################################
  985. // Create table ef_stmt if not existing
  986. $sqlsrv ='IF NOT EXISTS
  987. ( SELECT * FROM SysObjects WHERE [Name] = \'ef_stmt\')
  988. BEGIN
  989. CREATE TABLE ef_stmt (
  990. id TINYINT NOT NULL IDENTITY(1,1),
  991. g INT NOT NULL,
  992. s VARCHAR(160)COLLATE SQL_Latin1_General_CP1_CS_AS NOT NULL,
  993. p VARCHAR(160)COLLATE SQL_Latin1_General_CP1_CS_AS NOT NULL,
  994. o VARCHAR(160)COLLATE SQL_Latin1_General_CP1_CS_AS NOT NULL,
  995. s_r INT DEFAULT NULL,
  996. p_r INT DEFAULT NULL,
  997. o_r INT DEFAULT NULL,
  998. st TINYINT NOT NULL,
  999. ot TINYINT NOT NULL,
  1000. ol VARCHAR(50)COLLATE SQL_Latin1_General_CP1_CS_AS NOT NULL,
  1001. od VARCHAR(160)COLLATE SQL_Latin1_General_CP1_CS_AS NOT NULL,
  1002. od_r INT DEFAULT NULL,
  1003. ) ON [PRIMARY]
  1004. CREATE UNIQUE NONCLUSTERED INDEX unique_stmt ON ef_stmt (g,s,p,o,st,ot,ol,od)
  1005. CREATE NONCLUSTERED INDEX idx_g_p_o_ot ON ef_stmt (g, p, o, ot)
  1006. CREATE NONCLUSTERED INDEX idx_g_o_ot ON ef_stmt (g, o, ot)
  1007. END';
  1008. $success = $this->_dbConn->query($sqlsrv);
  1009. if (!$success) {
  1010. require_once 'Erfurt/Store/Adapter/Exception.php';
  1011. throw new Erfurt_Store_Adapter_Exception('Creation of table "ef_info" failed: ' .
  1012. $this->_dbConn->getConnection()->error);
  1013. }
  1014. //#####################################################################
  1015. // Create table ef_uri if not existing
  1016. $sqlsrv ='IF NOT EXISTS
  1017. ( SELECT * FROM SysObjects WHERE [Name] = \'ef_uri\')
  1018. BEGIN
  1019. CREATE TABLE ef_uri (
  1020. id TINYINT NOT NULL IDENTITY(1,1),
  1021. g INT NOT NULL,
  1022. v TEXT COLLATE SQL_Latin1_General_CP1_CS_AS NOT NULL,
  1023. vh VARCHAR(32)COLLATE SQL_Latin1_General_CP1_CS_AS NOT NULL
  1024. ) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
  1025. CREATE UNIQUE NONCLUSTERED INDEX unique_uri ON ef_uri (g,vh)
  1026. END';
  1027. $success = $this->_dbConn->query($sqlsrv);
  1028. if (!$success) {
  1029. require_once 'Erfurt/Store/Adapter/Exception.php';
  1030. throw new Erfurt_Store_Adapter_Exception('Creation of table "ef_uri" failed: ' .
  1031. $this->_dbConn->getConnection()->error);
  1032. }
  1033. //#####################################################################
  1034. // Create table ef_lit if not existing
  1035. //v would b UTF8 - not shure if SQL_Latin1_general_CP850_bin is the right type
  1036. $sqlsrv ='IF NOT EXISTS
  1037. ( SELECT * FROM SysObjects WHERE [Name] = \'ef_lit\')
  1038. BEGIN
  1039. CREATE TABLE ef_lit (
  1040. id TINYINT NOT NULL IDENTITY(1,1),
  1041. g INT NOT NULL,
  1042. v TEXT COLLATE SQL_Latin1_General_CP1_CS_AS NOT NULL,
  1043. vh VARCHAR(32)COLLATE SQL_Latin1_General_CP1_CS_AS NOT NULL
  1044. ) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
  1045. CREATE UNIQUE NONCLUSTERED INDEX unique_lit ON ef_lit (g,vh)
  1046. END';
  1047. $success = $this->_dbConn->query($sqlsrv);
  1048. if (!$success) {
  1049. require_once 'Erfurt/Store/Adapter/Exception.php';
  1050. throw new Erfurt_Store_Adapter_Exception('Creation of table "ef_uri" failed: ' .
  1051. $this->_dbConn->getConnection()->error);
  1052. }
  1053. }
  1054. protected function _getModelInfos()
  1055. {
  1056. if (null === $this->_modelInfoCache) {
  1057. try {
  1058. // try to fetch model and namespace infos... if all tables are present this should not lead to an error.
  1059. $this->_fetchModelInfos();
  1060. } catch (Erfurt_Exception $e) {
  1061. // error while fetching model and namespace infos... should only be the case if the tables aren't present,
  1062. // for db connection is already established (in constructor)...…

Large files files are truncated, but you can click here to view the full file