PageRenderTime 51ms CodeModel.GetById 23ms RepoModel.GetById 1ms app.codeStats 0ms

/arc/store/ARC2_Store.php

https://github.com/damz/foafssl-drupal
PHP | 571 lines | 468 code | 67 blank | 36 comment | 87 complexity | 09649a08d80b605edf7bcc27b8938956 MD5 | raw file
  1. <?php
  2. /*
  3. homepage: http://arc.semsol.org/
  4. license: http://arc.semsol.org/license
  5. class: ARC2 RDF Store
  6. author: Benjamin Nowack
  7. version: 2009-02-13 (Tweak: removed Inferencer calls)
  8. */
  9. ARC2::inc('Class');
  10. class ARC2_Store extends ARC2_Class {
  11. function __construct($a = '', &$caller) {
  12. parent::__construct($a, $caller);
  13. }
  14. function ARC2_Store($a = '', &$caller) {
  15. $this->__construct($a, $caller);
  16. }
  17. function __init() {/* db_con */
  18. parent::__init();
  19. $this->table_lock = 0;
  20. $this->triggers = $this->v('store_triggers', array(), $this->a);
  21. $this->queue_queries = $this->v('store_queue_queries', 0, $this->a);
  22. $this->is_win = (strtolower(substr(PHP_OS, 0, 3)) == 'win') ? true : false;
  23. $this->max_split_tables = $this->v('store_max_split_tables', 10, $this->a);
  24. $this->split_predicates = $this->v('store_split_predicates', array(), $this->a);
  25. }
  26. /* */
  27. function getName() {
  28. return $this->v('store_name', 'arc', $this->a);
  29. }
  30. function getTablePrefix() {
  31. if (!isset($this->tbl_prefix)) {
  32. $r = $this->v('db_table_prefix', '', $this->a);
  33. $r .= $r ? '_' : '';
  34. $r .= $this->getName() . '_';
  35. $this->tbl_prefix = $r;
  36. }
  37. return $this->tbl_prefix;;
  38. }
  39. /* */
  40. function createDBCon() {
  41. foreach (array('db_host' => 'localhost', 'db_user' => '', 'db_pwd' => '', 'db_name' => '') as $k => $v) {
  42. $this->a[$k] = $this->v($k, $v, $this->a);
  43. }
  44. if (!$db_con = mysql_connect($this->a['db_host'], $this->a['db_user'], $this->a['db_pwd'])) {
  45. return $this->addError(mysql_error());
  46. }
  47. $this->a['db_con'] =& $db_con;
  48. if (!mysql_select_db($this->a['db_name'], $db_con)) {
  49. return $this->addError(mysql_error($db_con));
  50. }
  51. if (preg_match('/^utf8/', $this->getCollation())) {
  52. mysql_query("SET NAMES 'utf8'", $db_con);
  53. }
  54. return true;
  55. }
  56. function getDBCon($force = 0) {
  57. if ($force || !isset($this->a['db_con'])) {
  58. if (!$this->createDBCon()) {
  59. return false;
  60. }
  61. }
  62. if (!$force && !@mysql_thread_id($this->a['db_con'])) return $this->getDBCon(1);
  63. return $this->a['db_con'];
  64. }
  65. function closeDBCon() {
  66. if ($this->v('db_con', false, $this->a)) {
  67. @mysql_close($this->a['db_con']);
  68. }
  69. unset($this->a['db_con']);
  70. }
  71. function getDBVersion() {
  72. if (!$this->v('db_version')) {
  73. $this->db_version = preg_match("/^([0-9]+)\.([0-9]+)\.([0-9]+)/", mysql_get_server_info($this->getDBCon()), $m) ? sprintf("%02d-%02d-%02d", $m[1], $m[2], $m[3]) : '00-00-00';
  74. }
  75. return $this->db_version;
  76. }
  77. /* */
  78. function getCollation() {
  79. $rs = mysql_query('SHOW TABLE STATUS LIKE "' . $this->getTablePrefix(). 'setting"', $this->getDBCon());
  80. return ($rs && ($row = mysql_fetch_array($rs)) && isset($row['Collation'])) ? $row['Collation'] : '';
  81. }
  82. function getColumnType() {
  83. if (!$this->v('column_type')) {
  84. $tbl = $this->getTablePrefix() . 'g2t';
  85. $rs = mysql_query('SHOW COLUMNS FROM ' . $tbl . ' LIKE "t"', $this->getDBCon());
  86. $row = $rs ? mysql_fetch_array($rs) : array('Type' => 'mediumint');
  87. $this->column_type = preg_match('/mediumint/', $row['Type']) ? 'mediumint' : 'int';
  88. }
  89. return $this->column_type;
  90. }
  91. /* */
  92. function countDBProcesses() {
  93. return ($rs = mysql_query('SHOW PROCESSLIST', $this->getDBCon())) ? mysql_num_rows($rs) : 0;
  94. }
  95. /* */
  96. function getTables() {
  97. return array('triple', 'g2t', 'id2val', 's2val', 'o2val', 'setting');
  98. }
  99. /* */
  100. function isSetUp() {
  101. if ($con = $this->getDBCon()) {
  102. $tbl = $this->getTablePrefix() . 'setting';
  103. return mysql_query("SELECT 1 FROM " . $tbl . " LIMIT 0", $con) ? 1 : 0;
  104. }
  105. }
  106. function setUp($force = 0) {
  107. if (($force || !$this->isSetUp()) && ($con = $this->getDBCon())) {
  108. if ($this->getDBVersion() < '04-00-04') {
  109. /* UPDATE + JOINs */
  110. return $this->addError('MySQL version not supported. ARC requires version 4.0.4 or higher.');
  111. }
  112. ARC2::inc('StoreTableManager');
  113. $mgr = new ARC2_StoreTableManager($this->a, $this);
  114. $mgr->createTables();
  115. }
  116. }
  117. function extendColumns() {
  118. ARC2::inc('StoreTableManager');
  119. $mgr = new ARC2_StoreTableManager($this->a, $this);
  120. $mgr->extendColumns();
  121. $this->column_type = 'int';
  122. }
  123. function splitTables() {
  124. ARC2::inc('StoreTableManager');
  125. $mgr = new ARC2_StoreTableManager($this->a, $this);
  126. $mgr->splitTables();
  127. }
  128. /* */
  129. function hasSetting($k) {
  130. $tbl = $this->getTablePrefix() . 'setting';
  131. $sql = "SELECT val FROM " . $tbl . " WHERE k = '" .md5($k). "'";
  132. $rs = mysql_query($sql, $this->getDBCon());
  133. return ($rs && ($row = mysql_fetch_array($rs))) ? 1 : 0;
  134. }
  135. function getSetting($k, $default = 0) {
  136. $tbl = $this->getTablePrefix() . 'setting';
  137. $sql = "SELECT val FROM " . $tbl . " WHERE k = '" .md5($k). "'";
  138. $rs = mysql_query($sql, $this->getDBCon());
  139. if ($rs && ($row = mysql_fetch_array($rs))) {
  140. return unserialize($row['val']);
  141. }
  142. return $default;
  143. }
  144. function setSetting($k, $v) {
  145. $con = $this->getDBCon();
  146. $tbl = $this->getTablePrefix() . 'setting';
  147. if ($this->hasSetting($k)) {
  148. $sql = "UPDATE " .$tbl . " SET val = '" . mysql_real_escape_string(serialize($v), $con) . "' WHERE k = '" . md5($k) . "'";
  149. }
  150. else {
  151. $sql = "INSERT INTO " . $tbl . " (k, val) VALUES ('" . md5($k) . "', '" . mysql_real_escape_string(serialize($v), $con) . "')";
  152. }
  153. return mysql_query($sql, $con);
  154. }
  155. function removeSetting($k) {
  156. $tbl = $this->getTablePrefix() . 'setting';
  157. return mysql_query("DELETE FROM " . $tbl . " WHERE k = '" . md5($k) . "'", $this->getDBCon());
  158. }
  159. function getQueueTicket() {
  160. if (!$this->queue_queries) return 1;
  161. $t = 'ticket_' . substr(md5(uniqid(rand())), 0, 10);
  162. $con = $this->getDBCon();
  163. /* lock */
  164. $rs = mysql_query('LOCK TABLES ' . $this->getTablePrefix() . 'setting WRITE', $con);
  165. /* queue */
  166. $queue = $this->getSetting('query_queue', array());
  167. $queue[] = $t;
  168. $this->setSetting('query_queue', $queue);
  169. mysql_query('UNLOCK TABLES', $con);
  170. /* loop */
  171. $lc = 0;
  172. $queue = $this->getSetting('query_queue', array());
  173. while ($queue && ($queue[0] != $t) && ($lc < 30)) {
  174. if ($this->is_win) {
  175. sleep(1);
  176. $lc++;
  177. }
  178. else {
  179. usleep(100000);
  180. $lc += 0.1;
  181. }
  182. $queue = $this->getSetting('query_queue', array());
  183. }
  184. return ($lc < 30) ? $t : 0;
  185. }
  186. function removeQueueTicket($t) {
  187. if (!$this->queue_queries) return 1;
  188. $con = $this->getDBCon();
  189. /* lock */
  190. mysql_query('LOCK TABLES ' . $this->getTablePrefix() . 'setting WRITE', $con);
  191. /* queue */
  192. $vals = $this->getSetting('query_queue', array());
  193. $pos = array_search($t, $vals);
  194. $queue = ($pos < (count($vals) - 1)) ? array_slice($vals, $pos + 1) : array();
  195. $this->setSetting('query_queue', $queue);
  196. mysql_query('UNLOCK TABLES', $con);
  197. }
  198. /* */
  199. function reset($keep_settings = 0) {
  200. $con = $this->getDBCon();
  201. $tbls = $this->getTables();
  202. $prefix = $this->getTablePrefix();
  203. /* remove split tables */
  204. $ps = $this->getSetting('split_predicates', array());
  205. foreach ($ps as $p) {
  206. $tbl = 'triple_' . abs(crc32($p));
  207. mysql_query('DROP TABLE ' . $prefix . $tbl, $con);
  208. }
  209. $this->removeSetting('split_predicates');
  210. /* truncate tables */
  211. foreach ($tbls as $tbl) {
  212. if ($keep_settings && ($tbl == 'setting')) {
  213. continue;
  214. }
  215. mysql_query('TRUNCATE ' . $prefix . $tbl, $con);
  216. }
  217. }
  218. function drop() {
  219. $con = $this->getDBCon();
  220. $tbls = $this->getTables();
  221. $prefix = $this->getTablePrefix();
  222. foreach ($tbls as $tbl) {
  223. mysql_query('DROP TABLE ' . $prefix . $tbl, $con);
  224. }
  225. }
  226. function insert($doc, $g, $keep_bnode_ids = 0) {
  227. $doc = is_array($doc) ? $this->toTurtle($doc) : $doc;
  228. $infos = array('query' => array('url' => $g, 'target_graph' => $g));
  229. ARC2::inc('StoreLoadQueryHandler');
  230. $h =& new ARC2_StoreLoadQueryHandler($this->a, $this);
  231. $r = $h->runQuery($infos, $doc, $keep_bnode_ids);
  232. $this->processTriggers('insert', $infos);
  233. return $r;
  234. }
  235. function delete($doc, $g) {
  236. if (!$doc) {
  237. $infos = array('query' => array('target_graphs' => array($g)));
  238. ARC2::inc('StoreDeleteQueryHandler');
  239. $h =& new ARC2_StoreDeleteQueryHandler($this->a, $this);
  240. $r = $h->runQuery($infos);
  241. $this->processTriggers('delete', $infos);
  242. return $r;
  243. }
  244. }
  245. function replace($doc, $g, $doc_2) {
  246. return array($this->delete($doc, $g), $this->insert($doc_2, $g));
  247. }
  248. function dump() {
  249. ARC2::inc('StoreDumper');
  250. $d =& new ARC2_StoreDumper($this->a, $this);
  251. $d->dumpSPOG();
  252. }
  253. function createBackup($path, $q = '') {
  254. ARC2::inc('StoreDumper');
  255. $d =& new ARC2_StoreDumper($this->a, $this);
  256. $d->saveSPOG($path, $q);
  257. }
  258. function renameTo($name) {
  259. $con = $this->getDBCon();
  260. $tbls = $this->getTables();
  261. $old_prefix = $this->getTablePrefix();
  262. $new_prefix = $this->v('db_table_prefix', '', $this->a);
  263. $new_prefix .= $new_prefix ? '_' : '';
  264. $new_prefix .= $name . '_';
  265. foreach ($tbls as $tbl) {
  266. $rs = mysql_query('RENAME TABLE ' . $old_prefix . $tbl .' TO ' . $new_prefix . $tbl, $con);
  267. if ($er = mysql_error($con)) {
  268. return $this->addError($er);
  269. }
  270. }
  271. $this->a['store_name'] = $name;
  272. unset($this->tbl_prefix);
  273. }
  274. function replicateTo($name) {
  275. $conf = array_merge($this->a, array('store_name' => $name));
  276. $new_store = ARC2::getStore($conf);
  277. $new_store->setUp();
  278. $new_store->reset();
  279. $con = $this->getDBCon();
  280. $tbls = $this->getTables();
  281. $old_prefix = $this->getTablePrefix();
  282. $new_prefix = $new_store->getTablePrefix();
  283. foreach ($tbls as $tbl) {
  284. $rs = mysql_query('INSERT IGNORE INTO ' . $new_prefix . $tbl .' SELECT * FROM ' . $old_prefix . $tbl, $con);
  285. if ($er = mysql_error($con)) {
  286. return $this->addError($er);
  287. }
  288. }
  289. return $new_store->query('SELECT COUNT(*) AS t_count WHERE { ?s ?p ?o}', 'row');
  290. }
  291. /* */
  292. function query($q, $result_format = '', $src = '', $keep_bnode_ids = 0, $log_query = 0) {
  293. if ($log_query) $this->logQuery($q);
  294. $con = $this->getDBCon();
  295. if (preg_match('/^dump/i', $q)) {
  296. $infos = array('query' => array('type' => 'dump'));
  297. }
  298. else {
  299. ARC2::inc('SPARQLPlusParser');
  300. $p = & new ARC2_SPARQLPlusParser($this->a, $this);
  301. $p->parse($q, $src);
  302. $infos = $p->getQueryInfos();
  303. }
  304. if ($result_format == 'infos') return $infos;
  305. $infos['result_format'] = $result_format;
  306. if (!isset($p) || !$p->getErrors()) {
  307. $qt = $infos['query']['type'];
  308. if (!in_array($qt, array('select', 'ask', 'describe', 'construct', 'load', 'insert', 'delete', 'dump'))) {
  309. return $this->addError('Unsupported query type "'.$qt.'"');
  310. }
  311. $t1 = ARC2::mtime();
  312. $r = array('query_type' => $qt, 'result' => $this->runQuery($infos, $qt, $keep_bnode_ids, $q));
  313. $t2 = ARC2::mtime();
  314. $r['query_time'] = $t2 - $t1;
  315. /* query result */
  316. if ($result_format == 'raw') {
  317. return $r['result'];
  318. }
  319. if ($result_format == 'rows') {
  320. return $r['result']['rows'] ? $r['result']['rows'] : array();
  321. }
  322. if ($result_format == 'row') {
  323. return $r['result']['rows'] ? $r['result']['rows'][0] : array();
  324. }
  325. return $r;
  326. }
  327. return 0;
  328. }
  329. function runQuery($infos, $type, $keep_bnode_ids = 0, $q = '') {
  330. ARC2::inc('Store' . ucfirst($type) . 'QueryHandler');
  331. $cls = 'ARC2_Store' . ucfirst($type) . 'QueryHandler';
  332. $h =& new $cls($this->a, $this);
  333. $ticket = 1;
  334. $r = array();
  335. if ($q && ($type == 'select')) $ticket = $this->getQueueTicket($q);
  336. if ($ticket) {
  337. if ($type == 'load') {/* the LoadQH supports raw data as 2nd parameter */
  338. $r = $h->runQuery($infos, '', $keep_bnode_ids);
  339. }
  340. else {
  341. $r = $h->runQuery($infos, $keep_bnode_ids);
  342. }
  343. }
  344. if ($q && ($type == 'select')) $this->removeQueueTicket($ticket);
  345. $trigger_r = $this->processTriggers($type, $infos);
  346. return $r;
  347. }
  348. function processTriggers($type, $infos) {
  349. $r = array();
  350. $trigger_defs = $this->triggers;
  351. $this->triggers = array();
  352. if ($triggers = $this->v($type, array(), $trigger_defs)) {
  353. $r['trigger_results'] = array();
  354. $triggers = is_array($triggers) ? $triggers : array($triggers);
  355. $trigger_inc_path = $this->v('store_triggers_path', '', $this->a);
  356. foreach ($triggers as $trigger) {
  357. $trigger .= !preg_match('/Trigger$/', $trigger) ? 'Trigger' : '';
  358. if (ARC2::inc(ucfirst($trigger), $trigger_inc_path)) {
  359. $cls = 'ARC2_' . ucfirst($trigger);
  360. $config = array_merge($this->a, array('query_infos' => $infos));
  361. $trigger_obj = new $cls($config, $this);
  362. if (method_exists($trigger_obj, 'go')) {
  363. $r['trigger_results'][] = $trigger_obj->go();
  364. }
  365. }
  366. }
  367. }
  368. $this->triggers = $trigger_defs;
  369. return $r;
  370. }
  371. /* */
  372. function getTermID($val, $term = '') {
  373. $tbl = preg_match('/^(s|o)$/', $term) ? $term . '2val' : 'id2val';
  374. $con = $this->getDBCon();
  375. $sql = "SELECT id FROM " . $this->getTablePrefix() . $tbl . " WHERE val = BINARY '" . mysql_real_escape_string($val, $con) . "' LIMIT 1";
  376. if (($rs = mysql_query($sql, $con)) && mysql_num_rows($rs) && ($row = mysql_fetch_array($rs))) {
  377. return $row['id'];
  378. }
  379. return 0;
  380. }
  381. /* */
  382. function getLock($t_out = 10, $t_out_init = '') {
  383. if (!$t_out_init) $t_out_init = $t_out;
  384. $con = $this->getDBCon();
  385. $l_name = $this->a['db_name'] . '.' . $this->getTablePrefix() . '.write_lock';
  386. if ($rs = mysql_query('SELECT IS_FREE_LOCK("' . $l_name. '") AS success', $con)) {
  387. $row = mysql_fetch_array($rs);
  388. if (!$row['success']) {
  389. if ($t_out) {
  390. sleep(1);
  391. return $this->getLock($t_out - 1, $t_out_init);
  392. }
  393. }
  394. elseif ($rs = mysql_query('SELECT GET_LOCK("' . $l_name. '", ' . $t_out_init. ') AS success', $con)) {
  395. $row = mysql_fetch_array($rs);
  396. return $row['success'];
  397. }
  398. }
  399. return 0;
  400. }
  401. function releaseLock() {
  402. $con = $this->getDBCon();
  403. return mysql_query('DO RELEASE_LOCK("' . $this->a['db_name'] . '.' . $this->getTablePrefix() . '.write_lock")', $con);
  404. }
  405. /* */
  406. function optimizeTables($level = 2) {/* 1: triple + g2t, 2: triple + *2val, 3: all tables */
  407. $con = $this->getDBCon();
  408. $pre = $this->getTablePrefix();
  409. $tbls = $this->getTables();
  410. $sql = '';
  411. foreach ($tbls as $tbl) {
  412. if (($level < 3) && preg_match('/(backup|setting)$/', $tbl)) continue;
  413. if (($level < 2) && preg_match('/(val)$/', $tbl)) continue;
  414. $sql .= $sql ? ', ' : 'OPTIMIZE TABLE ';
  415. $sql .= $pre . $tbl;
  416. }
  417. mysql_query($sql, $con);
  418. if ($err = mysql_error($con)) $this->addError($err . ' in ' . $sql);
  419. }
  420. /* */
  421. function changeNamespaceURI($old_uri, $new_uri) {
  422. ARC2::inc('StoreHelper');
  423. $c = new ARC2_StoreHelper($this->a, $this);
  424. return $c->changeNamespaceURI($old_uri, $new_uri);
  425. }
  426. /* */
  427. function getResourceLabel($res, $unnamed_label = 'An unnamed resource') {
  428. if (!isset($this->resource_labels)) $this->resource_labels = array();
  429. if (isset($this->resource_labels[$res])) return $this->resource_labels[$res];
  430. if (!preg_match('/^[a-z0-9\_]+\:[^\s]+$/si', $res)) return $res;/* literal */
  431. $ps = $this->getLabelProps();
  432. if ($this->getSetting('store_label_properties', '-') != md5(serialize($ps))) {
  433. $this->inferLabelProps($ps);
  434. }
  435. //$sub_q .= $sub_q ? ' || ' : '';
  436. //$sub_q .= 'REGEX(str(?p), "(last_name|name|fn|title|label)$", "i")';
  437. $q = 'SELECT ?label WHERE { <' . $res . '> ?p ?label . ?p a <http://semsol.org/ns/arc#LabelProperty> } LIMIT 3';
  438. $r = '';
  439. if ($rows = $this->query($q, 'rows')) {
  440. foreach ($rows as $row) {
  441. $r = strlen($row['label']) > strlen($r) ? $row['label'] : $r;
  442. }
  443. }
  444. if (!$r && preg_match('/^\_\:/', $res)) {
  445. return $unnamed_label;
  446. }
  447. $r = $r ? $r : preg_replace("/^(.*[\/\#])([^\/\#]+)$/", '\\2', str_replace('#self', '', $res));
  448. $r = str_replace('_', ' ', $r);
  449. $r = preg_replace('/([a-z])([A-Z])/e', '"\\1 " . strtolower("\\2")', $r);
  450. $this->resource_labels[$res] = $r;
  451. return $r;
  452. }
  453. function getLabelProps() {
  454. return array_merge(
  455. $this->v('rdf_label_properties' , array(), $this->a),
  456. array(
  457. 'http://www.w3.org/2000/01/rdf-schema#label',
  458. 'http://xmlns.com/foaf/0.1/name',
  459. 'http://purl.org/dc/elements/1.1/title',
  460. 'http://purl.org/rss/1.0/title',
  461. 'http://www.w3.org/2004/02/skos/core#prefLabel',
  462. 'http://xmlns.com/foaf/0.1/nick',
  463. )
  464. );
  465. }
  466. function inferLabelProps($ps) {
  467. $this->query('DELETE FROM <label-properties>');
  468. $sub_q = '';
  469. foreach ($ps as $p) {
  470. $sub_q .= ' <' . $p . '> a <http://semsol.org/ns/arc#LabelProperty> . ';
  471. }
  472. $this->query('INSERT INTO <label-properties> { ' . $sub_q. ' }');
  473. $this->setSetting('store_label_properties', md5(serialize($ps)));
  474. }
  475. /* */
  476. function getResourcePredicates($res) {
  477. $r = array();
  478. if ($rows = $this->query('SELECT DISTINCT ?p WHERE { <' . $res . '> ?p ?o . }', 'rows')) {
  479. foreach ($rows as $row) {
  480. $r[$row['p']] = array();
  481. }
  482. }
  483. return $r;
  484. }
  485. function getDomains($p) {
  486. $r = array();
  487. foreach($this->query('SELECT DISTINCT ?type WHERE {?s <' . $p . '> ?o ; a ?type . }', 'rows') as $row) {
  488. $r[] = $row['type'];
  489. }
  490. return $r;
  491. }
  492. function getPredicateRange($p) {
  493. $row = $this->query('SELECT ?val WHERE {<' . $p . '> rdfs:range ?val . } LIMIT 1', 'row');
  494. return $row ? $row['val'] : '';
  495. }
  496. /* */
  497. function logQuery($q) {
  498. $fp = @fopen("arc_query_log.txt", "a");
  499. @fwrite($fp, date('Y-m-d\TH:i:s\Z', time()) . ' : ' . $q . '' . "\n\n");
  500. @fclose($fp);
  501. }
  502. /* */
  503. }