ARC2_Store.php 21 KB

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