PageRenderTime 45ms CodeModel.GetById 10ms RepoModel.GetById 1ms app.codeStats 0ms

/libraries/ARC2/store/ARC2_StoreSelectQueryHandler.php

https://code.google.com/p/ontowiki/
PHP | 1824 lines | 1565 code | 129 blank | 130 comment | 400 complexity | d9c12edcc193dc77259b89a358c3cf7e MD5 | raw file
Possible License(s): GPL-2.0, BSD-3-Clause

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

  1. <?php
  2. /**
  3. * ARC2 RDF Store SELECT Query Handler
  4. *
  5. * @author Benjamin Nowack
  6. * @license http://arc.semsol.org/license
  7. * @homepage <http://arc.semsol.org/>
  8. * @package ARC2
  9. * @version 2010-06-22
  10. *
  11. */
  12. ARC2::inc('StoreQueryHandler');
  13. class ARC2_StoreSelectQueryHandler extends ARC2_StoreQueryHandler {
  14. function __construct($a = '', &$caller) {/* caller has to be a store */
  15. parent::__construct($a, $caller);
  16. }
  17. function __init() {/* db_con */
  18. parent::__init();
  19. $this->store = $this->caller;
  20. $con = $this->store->getDBCon();
  21. $this->handler_type = 'select';
  22. $this->engine_type = $this->v('store_engine_type', 'MyISAM', $this->a);
  23. $this->cache_results = $this->v('store_cache_results', 0, $this->a);
  24. }
  25. /* */
  26. function runQuery($infos) {
  27. $con = $this->store->getDBCon();
  28. $rf = $this->v('result_format', '', $infos);
  29. $this->infos = $infos;
  30. $this->infos['null_vars'] = array();
  31. $this->indexes = array();
  32. $this->pattern_order_offset = 0;
  33. $q_sql = $this->validateSqlQuery($this->getSQL());
  34. /* debug result formats */
  35. if ($rf == 'sql') return $q_sql;
  36. if ($rf == 'structure') return $this->infos;
  37. if ($rf == 'index') return $this->indexes;
  38. /* create intermediate results (ID-based) */
  39. $tmp_tbl = $this->createTempTable($q_sql);
  40. /* join values */
  41. $r = $this->getFinalQueryResult($q_sql, $tmp_tbl);
  42. /* remove intermediate results */
  43. if (!$this->cache_results) {
  44. $this->queryDB('DROP TABLE IF EXISTS ' . $tmp_tbl, $con);
  45. }
  46. return $r;
  47. }
  48. /*
  49. * workaround replaces the first LEFT JOIN with
  50. * a FROM in a SELECT-query without a FROM
  51. */
  52. function validateSqlQuery($sql){
  53. if(strpos($sql,"FROM") == false){
  54. if(strpos($sql,"JOIN") != false){
  55. try{
  56. $tmp = preg_split("#LEFT JOIN#", $sql);
  57. $select = $tmp[0];
  58. $from = $tmp[1];
  59. $query_exclude_select = "";
  60. for($i = 2; $i < sizeof($tmp); $i++)
  61. {
  62. $query_exclude_select .= " LEFT JOIN ".$tmp[$i];
  63. }
  64. $tmp = preg_split("#ON#", $from);
  65. $from = $tmp[0];
  66. return $select."FROM ".$from." ".$query_exclude_select;
  67. }
  68. catch(Exception $e){
  69. $this->addError($e->getMessage());
  70. return $sql;
  71. }
  72. }
  73. else{
  74. $this->addError('SELECT-Query without FROM and without JOIN!');
  75. return $sql;
  76. }
  77. }
  78. else
  79. return $sql;
  80. }
  81. function getSQL() {
  82. $r = '';
  83. $nl = "\n";
  84. $this->buildInitialIndexes();
  85. foreach ($this->indexes as $i => $index) {
  86. $this->index = array_merge($this->getEmptyIndex(), $index);
  87. $this->analyzeIndex($this->getPattern('0'));
  88. $sub_r = $this->getQuerySQL();
  89. $r .= $r ? $nl . 'UNION' . $this->getDistinctSQL() . $nl : '';
  90. $r .= $this->is_union_query ? '(' . $sub_r . ')' : $sub_r;
  91. $this->indexes[$i] = $this->index;
  92. }
  93. $r .= $this->is_union_query ? $this->getLIMITSQL() : '';
  94. if ($this->v('order_infos', 0, $this->infos['query'])) {
  95. $r = preg_replace('/SELECT(\s+DISTINCT)?\s*/', 'SELECT\\1 NULL AS `_pos_`, ', $r);
  96. }
  97. if ($pd_count = $this->problematicDependencies()) {
  98. /* re-arranging the patterns sometimes reduces the LEFT JOIN dependencies */
  99. $set_sql = 0;
  100. if (!$this->pattern_order_offset) $set_sql = 1;
  101. if (!$set_sql && ($pd_count < $this->opt_sql_pd_count)) $set_sql = 1;
  102. if (!$set_sql && ($pd_count == $this->opt_sql_pd_count) && (strlen($r) < strlen($this->opt_sql))) $set_sql = 1;
  103. if ($set_sql) {
  104. $this->opt_sql = $r;
  105. $this->opt_sql_pd_count = $pd_count;
  106. }
  107. $this->pattern_order_offset++;
  108. if ($this->pattern_order_offset > 5) {
  109. return $this->opt_sql;
  110. }
  111. return $this->getSQL();
  112. }
  113. return $r;
  114. }
  115. function buildInitialIndexes() {
  116. $this->dependency_log = array();
  117. $this->index = $this->getEmptyIndex();
  118. $this->buildIndex($this->infos['query']['pattern'], 0);
  119. $tmp = $this->index;
  120. $this->analyzeIndex($this->getPattern('0'));
  121. $this->initial_index = $this->index;
  122. $this->index = $tmp;
  123. $this->is_union_query = $this->index['union_branches'] ? 1 : 0;
  124. $this->indexes = $this->is_union_query ? $this->getUnionIndexes($this->index) : array($this->index);
  125. }
  126. function createTempTable($q_sql) {
  127. $con = $this->store->getDBCon();
  128. $v = $this->store->getDBVersion();
  129. if ($this->cache_results) {
  130. $tbl = $this->store->getTablePrefix() . 'Q' . md5($q_sql);
  131. }
  132. else {
  133. $tbl = $this->store->getTablePrefix() . 'Q' . md5($q_sql . time() . uniqid(rand()));
  134. }
  135. if (strlen($tbl) > 64) $tbl = 'Q' . md5($tbl);
  136. $tmp_sql = 'CREATE TEMPORARY TABLE ' . $tbl . ' ( ' . $this->getTempTableDef($tbl, $q_sql) . ') ';
  137. $tmp_sql .= (($v < '04-01-00') && ($v >= '04-00-18')) ? 'ENGINE' : (($v >= '04-01-02') ? 'ENGINE' : 'TYPE');
  138. $tmp_sql .= '=' . $this->engine_type;/* HEAP doesn't support AUTO_INCREMENT, and MySQL breaks on MEMORY sometimes */
  139. if (!$this->queryDB($tmp_sql, $con) && !$this->queryDB(str_replace('CREATE TEMPORARY', 'CREATE', $tmp_sql), $con)) {
  140. return $this->addError(mysql_error($con));
  141. }
  142. mysql_unbuffered_query('INSERT INTO ' . $tbl . ' ' . "\n" . $q_sql, $con);
  143. if ($er = mysql_error($con)) $this->addError($er);
  144. return $tbl;
  145. }
  146. function getEmptyIndex() {
  147. return array(
  148. 'from' => array(),
  149. 'join' => array(),
  150. 'left_join' => array(),
  151. 'vars' => array(), 'graph_vars' => array(), 'graph_uris' => array(),
  152. 'bnodes' => array(),
  153. 'triple_patterns' => array(),
  154. 'sub_joins' => array(),
  155. 'constraints' => array(),
  156. 'union_branches'=> array(),
  157. 'patterns' => array(),
  158. 'havings' => array()
  159. );
  160. }
  161. function getTempTableDef($tmp_tbl, $q_sql) {
  162. $col_part = preg_replace('/^SELECT\s*(DISTINCT)?(.*)FROM.*$/s', '\\2', $q_sql);
  163. $parts = explode(',', $col_part);
  164. $has_order_infos = $this->v('order_infos', 0, $this->infos['query']);
  165. $r = '';
  166. $added = array();
  167. foreach ($parts as $part) {
  168. if (preg_match('/\.?(.+)\s+AS\s+`(.+)`/U', trim($part), $m) && !isset($added[$m[2]])) {
  169. $col = $m[1];
  170. $alias = $m[2];
  171. if ($alias == '_pos_') continue;
  172. $r .= $r ? ',' : '';
  173. $r .= "\n `" . $alias . "` int UNSIGNED";
  174. $added[$alias] = 1;
  175. }
  176. }
  177. if ($has_order_infos) {
  178. $r = "\n" . '`_pos_` mediumint NOT NULL AUTO_INCREMENT PRIMARY KEY, ' . $r;
  179. }
  180. return $r ? $r . "\n" : '';
  181. }
  182. function getFinalQueryResult($q_sql, $tmp_tbl) {
  183. /* var names */
  184. $vars = array();
  185. $aggregate_vars = array();
  186. foreach ($this->infos['query']['result_vars'] as $entry) {
  187. if ($entry['aggregate']) {
  188. $vars[] = $entry['alias'];
  189. $aggregate_vars[] = $entry['alias'];
  190. }
  191. else {
  192. $vars[] = $entry['var'];
  193. }
  194. }
  195. /* result */
  196. $r = array('variables' => $vars);
  197. $v_sql = $this->getValueSQL($tmp_tbl, $q_sql);
  198. //echo "\n\n" . $v_sql;
  199. $t1 = ARC2::mtime();
  200. $con = $this->store->getDBCon();
  201. $rs = mysql_unbuffered_query($v_sql, $con);
  202. if ($er = mysql_error($con)) {
  203. $this->addError($er);
  204. }
  205. $t2 = ARC2::mtime();
  206. $rows = array();
  207. $types = array(0 => 'uri', 1 => 'bnode', 2 => 'literal');
  208. if ($rs) {
  209. while ($pre_row = mysql_fetch_array($rs)) {
  210. $row = array();
  211. foreach ($vars as $var) {
  212. if (isset($pre_row[$var])) {
  213. $row[$var] = $pre_row[$var];
  214. $row[$var . ' type'] = isset($pre_row[$var . ' type']) ? $types[$pre_row[$var . ' type']] : (in_array($var, $aggregate_vars) ? 'literal' : 'uri');
  215. if (isset($pre_row[$var . ' lang_dt']) && ($lang_dt = $pre_row[$var . ' lang_dt'])) {
  216. if (preg_match('/^([a-z]+(\-[a-z0-9]+)*)$/i', $lang_dt)) {
  217. $row[$var . ' lang'] = $lang_dt;
  218. }
  219. else {
  220. $row[$var . ' datatype'] = $lang_dt;
  221. }
  222. }
  223. }
  224. }
  225. if ($row || !$vars) {
  226. $rows[] = $row;
  227. }
  228. }
  229. }
  230. $r['rows'] = $rows;
  231. return $r;
  232. }
  233. /* */
  234. function buildIndex($pattern, $id) {
  235. $pattern['id'] = $id;
  236. $type = $this->v('type', '', $pattern);
  237. if (($type == 'filter') && $this->v('constraint', 0, $pattern)) {
  238. $sub_pattern = $pattern['constraint'];
  239. $sub_pattern['parent_id'] = $id;
  240. $sub_id = $id . '_0';
  241. $this->buildIndex($sub_pattern, $sub_id);
  242. $pattern['constraint'] = $sub_id;
  243. }
  244. else {
  245. $sub_patterns = $this->v('patterns', array(), $pattern);
  246. $keys = array_keys($sub_patterns);
  247. $spc = count($sub_patterns);
  248. if (($spc > 4) && $this->pattern_order_offset) {
  249. $keys = array();
  250. for ($i = 0 ; $i < $spc; $i++) {
  251. $keys[$i] = $i + $this->pattern_order_offset;
  252. while ($keys[$i] >= $spc) $keys[$i] -= $spc;
  253. }
  254. }
  255. foreach ($keys as $i => $key) {
  256. $sub_pattern = $sub_patterns[$key];
  257. $sub_pattern['parent_id'] = $id;
  258. $sub_id = $id . '_' . $key;
  259. $this->buildIndex($sub_pattern, $sub_id);
  260. $pattern['patterns'][$i] = $sub_id;
  261. if ($type == 'union') {
  262. $this->index['union_branches'][] = $sub_id;
  263. }
  264. }
  265. }
  266. $this->index['patterns'][$id] = $pattern;
  267. }
  268. /* */
  269. function analyzeIndex($pattern) {
  270. $type = $pattern['type'];
  271. $id = $pattern['id'];
  272. /* triple */
  273. if ($type == 'triple') {
  274. foreach (array('s', 'p', 'o') as $term) {
  275. if ($pattern[$term . '_type'] == 'var') {
  276. $val = $pattern[$term];
  277. $this->index['vars'][$val] = array_merge($this->v($val, array(), $this->index['vars']), array(array('table' => $pattern['id'], 'col' =>$term)));
  278. }
  279. if ($pattern[$term . '_type'] == 'bnode') {
  280. $val = $pattern[$term];
  281. $this->index['bnodes'][$val] = array_merge($this->v($val, array(), $this->index['bnodes']), array(array('table' => $pattern['id'], 'col' =>$term)));
  282. }
  283. }
  284. $this->index['triple_patterns'][] = $pattern['id'];
  285. /* joins */
  286. $opt = $this->isOptionalPattern($id);
  287. if ($opt) {
  288. $this->index['left_join'][] = $id;
  289. }
  290. elseif (!$this->index['from']) {
  291. $this->index['from'][] = $id;
  292. }
  293. elseif (!$this->getJoinInfos($id)) {
  294. $this->index['from'][] = $id;
  295. }
  296. else {
  297. $this->index['join'][] = $id;
  298. }
  299. /* graph infos, graph vars */
  300. $this->index['patterns'][$id]['graph_infos'] = $this->getGraphInfos($id);
  301. foreach ($this->index['patterns'][$id]['graph_infos'] as $info) {
  302. if ($info['type'] == 'graph') {
  303. if ($info['var']) {
  304. $val = $info['var']['value'];
  305. $this->index['graph_vars'][$val] = array_merge($this->v($val, array(), $this->index['graph_vars']), array(array('table' => $id)));
  306. }
  307. elseif ($info['uri']) {
  308. $val = $info['uri'];
  309. $this->index['graph_uris'][$val] = array_merge($this->v($val, array(), $this->index['graph_uris']), array(array('table' => $id)));
  310. }
  311. }
  312. }
  313. }
  314. $sub_ids = $this->v('patterns', array(), $pattern);
  315. foreach ($sub_ids as $sub_id) {
  316. $this->analyzeIndex($this->getPattern($sub_id));
  317. }
  318. }
  319. /* */
  320. function getGraphInfos($id) {
  321. $r = array();
  322. if ($id) {
  323. $pattern = $this->index['patterns'][$id];
  324. $type = $pattern['type'];
  325. /* graph */
  326. if ($type == 'graph') {
  327. $r[] = array('type' => 'graph', 'var' => $pattern['var'], 'uri' => $pattern['uri']);
  328. }
  329. $p_pattern = $this->index['patterns'][$pattern['parent_id']];
  330. if (isset($p_pattern['graph_infos'])) {
  331. return array_merge($p_pattern['graph_infos'], $r);
  332. }
  333. return array_merge($this->getGraphInfos($pattern['parent_id']), $r);
  334. }
  335. /* FROM / FROM NAMED */
  336. else {
  337. if (isset($this->infos['query']['dataset'])) {
  338. foreach ($this->infos['query']['dataset'] as $set) {
  339. $r[] = array_merge(array('type' => 'dataset'), $set);
  340. }
  341. }
  342. }
  343. return $r;
  344. }
  345. /* */
  346. function getPattern($id) {
  347. if (is_array($id)) {
  348. return $id;
  349. }
  350. return $this->v($id, array(), $this->index['patterns']);
  351. }
  352. function getInitialPattern($id) {
  353. return $this->v($id, array(), $this->initial_index['patterns']);
  354. }
  355. /* */
  356. function getUnionIndexes($pre_index) {
  357. $r = array();
  358. $branches = array();
  359. $min_depth = 1000;
  360. /* only process branches with minimum depth */
  361. foreach ($pre_index['union_branches'] as $id) {
  362. $branches[$id] = count(preg_split('/\_/', $id));
  363. $min_depth = min($min_depth, $branches[$id]);
  364. }
  365. foreach ($branches as $branch_id => $depth) {
  366. if ($depth == $min_depth) {
  367. $union_id = preg_replace('/\_[0-9]+$/', '', $branch_id);
  368. $index = array('keeping' => $branch_id, 'union_branches' => array(), 'patterns' => $pre_index['patterns']);
  369. $old_branches = $index['patterns'][$union_id]['patterns'];
  370. $skip_id = ($old_branches[0] == $branch_id) ? $old_branches[1] : $old_branches[0];
  371. $index['patterns'][$union_id]['type'] = 'group';
  372. $index['patterns'][$union_id]['patterns'] = array($branch_id);
  373. $has_sub_unions = 0;
  374. foreach ($index['patterns'] as $pattern_id => $pattern) {
  375. if (preg_match('/^' .$skip_id. '/', $pattern_id)) {
  376. unset($index['patterns'][$pattern_id]);
  377. }
  378. elseif ($pattern['type'] == 'union') {
  379. foreach ($pattern['patterns'] as $sub_union_branch_id) {
  380. $index['union_branches'][] = $sub_union_branch_id;
  381. }
  382. }
  383. }
  384. if ($index['union_branches']) {
  385. $r = array_merge($r, $this->getUnionIndexes($index));
  386. }
  387. else {
  388. $r[] = $index;
  389. }
  390. }
  391. }
  392. return $r;
  393. }
  394. /* */
  395. function isOptionalPattern($id) {
  396. $pattern = $this->getPattern($id);
  397. if ($this->v('type', '', $pattern) == 'optional') {
  398. return 1;
  399. }
  400. if ($this->v('parent_id', '0', $pattern) == '0') {
  401. return 0;
  402. }
  403. return $this->isOptionalPattern($pattern['parent_id']);
  404. }
  405. function getOptionalPattern($id) {
  406. $pn = $this->getPattern($id);
  407. do {
  408. $pn = $this->getPattern($pn['parent_id']);
  409. } while ($pn['parent_id'] && ($pn['type'] != 'optional'));
  410. return $pn['id'];
  411. }
  412. function sameOptional($id, $id2) {
  413. return $this->getOptionalPattern($id) == $this->getOptionalPattern($id2);
  414. }
  415. /* */
  416. function isUnionPattern($id) {
  417. $pattern = $this->getPattern($id);
  418. if ($this->v('type', '', $pattern) == 'union') {
  419. return 1;
  420. }
  421. if ($this->v('parent_id', '0', $pattern) == '0') {
  422. return 0;
  423. }
  424. return $this->isUnionPattern($pattern['parent_id']);
  425. }
  426. /* */
  427. function getValueTable($col) {
  428. return $this->store->getTablePrefix() . (preg_match('/^(s|o)$/', $col) ? $col . '2val' : 'id2val');
  429. }
  430. function getGraphTable() {
  431. return $this->store->getTablePrefix() . 'g2t';
  432. }
  433. /* */
  434. function getQuerySQL() {
  435. $nl = "\n";
  436. $where_sql = $this->getWHERESQL(); /* pre-fills $index['sub_joins'] $index['constraints'] */
  437. $order_sql = $this->getORDERSQL(); /* pre-fills $index['sub_joins'] $index['constraints'] */
  438. $return = '';
  439. $return .= ($this->is_union_query ? 'SELECT' : 'SELECT' . $this->getDistinctSQL()) . $nl;
  440. $return .= $this->getResultVarsSQL() . $nl;
  441. $return .= $this->getFROMSQL();
  442. $return .= $this->getAllJoinsSQL();
  443. $return .= $this->getWHERESQL();
  444. $return .= $this->getGROUPSQL();
  445. $return .= $this->getORDERSQL();
  446. $return .= ($this->is_union_query ? '' : $this->getLIMITSQL()) . $nl . '';
  447. return $return;
  448. }
  449. /* */
  450. function getDistinctSQL() {
  451. if ($this->is_union_query) {
  452. return ($this->v('distinct', 0, $this->infos['query']) || $this->v('reduced', 0, $this->infos['query'])) ? '' : ' ALL';
  453. }
  454. return ($this->v('distinct', 0, $this->infos['query']) || $this->v('reduced', 0, $this->infos['query'])) ? ' DISTINCT' : '';
  455. }
  456. /* */
  457. function getResultVarsSQL() {
  458. $r = '';
  459. $vars = $this->infos['query']['result_vars'];
  460. $nl = "\n";
  461. $added = array();
  462. foreach ($vars as $var) {
  463. $var_name = $var['var'];
  464. $tbl_alias = '';
  465. if ($tbl_infos = $this->getVarTableInfos($var_name, 0)) {
  466. $tbl = $tbl_infos['table'];
  467. $col = $tbl_infos['col'];
  468. $tbl_alias = $tbl_infos['table_alias'];
  469. }
  470. elseif ($var_name == 1) {/* ASK query */
  471. $r .= '1 AS `success`';
  472. }
  473. else {
  474. $this->addError('Result variable "' .$var_name. '" not used in query.');
  475. }
  476. if ($tbl_alias) {
  477. /* aggregate */
  478. if ($var['aggregate']) {
  479. $conv_code = '';
  480. if (strtolower($var['aggregate']) != 'count') {
  481. $tbl_alias = 'V_' . $tbl . '_' . $col . '.val';
  482. $conv_code = '0 + ';
  483. }
  484. if (!isset($added[$var['alias']])) {
  485. $r .= $r ? ',' . $nl . ' ' : ' ';
  486. $distinct_code = (strtolower($var['aggregate']) == 'count') && $this->v('distinct', 0, $this->infos['query']) ? 'DISTINCT ' : '';
  487. $r .= $var['aggregate'] . '(' . $conv_code . $distinct_code . $tbl_alias. ') AS `' . $var['alias'] . '`';
  488. $added[$var['alias']] = 1;
  489. }
  490. }
  491. /* normal var */
  492. else {
  493. if (!isset($added[$var_name])) {
  494. $r .= $r ? ',' . $nl . ' ' : ' ';
  495. $r .= $tbl_alias . ' AS `' . $var_name . '`';
  496. $is_s = ($col == 's');
  497. $is_p = ($col == 'p');
  498. $is_o = ($col == 'o');
  499. if ($tbl_alias == 'NULL') {
  500. /* type / add in UNION queries? */
  501. if ($is_s || $is_o) {
  502. $r .= ', ' . $nl . ' NULL AS `' . $var_name . ' type`';
  503. }
  504. /* lang_dt / always add it in UNION queries, the var may be used as s/p/o */
  505. if ($is_o || $this->is_union_query) {
  506. $r .= ', ' . $nl . ' NULL AS `' . $var_name . ' lang_dt`';
  507. }
  508. }
  509. else {
  510. /* type */
  511. if ($is_s || $is_o) {
  512. $r .= ', ' . $nl . ' ' .$tbl_alias . '_type AS `' . $var_name . ' type`';
  513. }
  514. /* lang_dt / always add it in UNION queries, the var may be used as s/p/o */
  515. if ($is_o) {
  516. $r .= ', ' . $nl . ' ' .$tbl_alias . '_lang_dt AS `' . $var_name . ' lang_dt`';
  517. }
  518. elseif ($this->is_union_query) {
  519. $r .= ', ' . $nl . ' NULL AS `' . $var_name . ' lang_dt`';
  520. }
  521. }
  522. $added[$var_name] = 1;
  523. }
  524. }
  525. if (!in_array($tbl_alias, $this->index['sub_joins'])) {
  526. $this->index['sub_joins'][] = $tbl_alias;
  527. }
  528. }
  529. }
  530. return $r ? $r : '1 AS `success`';
  531. }
  532. function getVarTableInfos($var, $ignore_initial_index = 1) {
  533. if ($var == '*') {
  534. return array('table' => '', 'col' => '', 'table_alias' => '*');
  535. }
  536. if ($infos = $this->v($var, 0, $this->index['vars'])) {
  537. $infos[0]['table_alias'] = 'T_' . $infos[0]['table'] . '.' . $infos[0]['col'];
  538. return $infos[0];
  539. }
  540. if ($infos = $this->v($var, 0, $this->index['graph_vars'])) {
  541. $infos[0]['col'] = 'g';
  542. $infos[0]['table_alias'] = 'G_' . $infos[0]['table'] . '.' . $infos[0]['col'];
  543. return $infos[0];
  544. }
  545. if ($this->is_union_query && !$ignore_initial_index) {
  546. if (($infos = $this->v($var, 0, $this->initial_index['vars'])) || ($infos = $this->v($var, 0, $this->initial_index['graph_vars']))) {
  547. if (!in_array($var, $this->infos['null_vars'])) {
  548. $this->infos['null_vars'][] = $var;
  549. }
  550. $infos[0]['table_alias'] = 'NULL';
  551. $infos[0]['col'] = !isset($infos[0]['col']) ? '' : $infos[0]['col'];
  552. return $infos[0];
  553. }
  554. }
  555. return 0;
  556. }
  557. /* */
  558. function getFROMSQL() {
  559. $r = '';
  560. foreach ($this->index['from'] as $id) {
  561. $r .= $r ? ', ' : 'FROM (';
  562. $r .= $this->getTripleTable($id) . ' T_' . $id;
  563. }
  564. return $r ? $r . ')' : '';
  565. }
  566. /* */
  567. function getOrderedJoinIDs() {
  568. return array_merge($this->index['from'], $this->index['join'], $this->index['left_join']);
  569. }
  570. function getJoinInfos($id) {
  571. $r = array();
  572. $tbl_ids = $this->getOrderedJoinIDs();
  573. $pattern = $this->getPattern($id);
  574. foreach ($tbl_ids as $tbl_id) {
  575. $tbl_pattern = $this->getPattern($tbl_id);
  576. if ($tbl_id != $id) {
  577. foreach (array('s', 'p', 'o') as $tbl_term) {
  578. foreach (array('var', 'bnode', 'uri') as $term_type) {
  579. if ($tbl_pattern[$tbl_term . '_type'] == $term_type) {
  580. foreach (array('s', 'p', 'o') as $term) {
  581. if (($pattern[$term . '_type'] == $term_type) && ($tbl_pattern[$tbl_term] == $pattern[$term])) {
  582. $r[] = array('term' => $term, 'join_tbl' => $tbl_id, 'join_term' => $tbl_term);
  583. }
  584. }
  585. }
  586. }
  587. }
  588. }
  589. }
  590. return $r;
  591. }
  592. function getAllJoinsSQL() {
  593. $js = $this->getJoins();
  594. $ljs = $this->getLeftJoins();
  595. $entries = array_merge($js, $ljs);
  596. $id2code = array();
  597. foreach ($entries as $entry) {
  598. if (preg_match('/([^\s]+) ON (.*)/s', $entry, $m)) {
  599. $id2code[$m[1]] = $entry;
  600. }
  601. }
  602. $deps = array();
  603. foreach ($id2code as $id => $code) {
  604. $deps[$id]['rank'] = 0;
  605. foreach ($id2code as $other_id => $other_code) {
  606. $deps[$id]['rank'] += ($id != $other_id) && preg_match('/' . $other_id . '/', $code) ? 1 : 0;
  607. $deps[$id][$other_id] = ($id != $other_id) && preg_match('/' . $other_id . '/', $code) ? 1 : 0;
  608. }
  609. }
  610. $r = '';
  611. do {
  612. /* get next 0-rank */
  613. $next_id = 0;
  614. foreach ($deps as $id => $infos) {
  615. if ($infos['rank'] == 0) {
  616. $next_id = $id;
  617. break;
  618. }
  619. }
  620. if ($next_id) {
  621. $r .= "\n" . $id2code[$next_id];
  622. unset($deps[$next_id]);
  623. foreach ($deps as $id => $infos) {
  624. $deps[$id]['rank'] = 0;
  625. unset($deps[$id][$next_id]);
  626. foreach ($infos as $k => $v) {
  627. if (!in_array($k, array('rank', $next_id))) {
  628. $deps[$id]['rank'] += $v;
  629. $deps[$id][$k] = $v;
  630. }
  631. }
  632. }
  633. }
  634. }
  635. while ($next_id);
  636. if ($deps) {
  637. $this->addError('Not all patterns could be rewritten to SQL JOINs');
  638. }
  639. return $r;
  640. }
  641. function getJoins() {
  642. $r = array();
  643. $nl = "\n";
  644. foreach ($this->index['join'] as $id) {
  645. $sub_r = $this->getJoinConditionSQL($id);
  646. $r[] = 'JOIN ' . $this->getTripleTable($id) . ' T_' . $id . ' ON (' . $sub_r . $nl . ')';
  647. }
  648. foreach (array_merge($this->index['from'], $this->index['join']) as $id) {
  649. if ($sub_r = $this->getRequiredSubJoinSQL($id)) {
  650. $r[] = $sub_r;
  651. }
  652. }
  653. return $r;
  654. }
  655. function getLeftJoins() {
  656. $r = array();
  657. $nl = "\n";
  658. foreach ($this->index['left_join'] as $id) {
  659. $sub_r = $this->getJoinConditionSQL($id);
  660. $r[] = 'LEFT JOIN ' . $this->getTripleTable($id) . ' T_' . $id . ' ON (' . $sub_r . $nl . ')';
  661. }
  662. foreach ($this->index['left_join'] as $id) {
  663. if ($sub_r = $this->getRequiredSubJoinSQL($id, 'LEFT')) {
  664. $r[] = $sub_r;
  665. }
  666. }
  667. return $r;
  668. }
  669. function getJoinConditionSQL($id) {
  670. $r = '';
  671. $nl = "\n";
  672. $infos = $this->getJoinInfos($id);
  673. $pattern = $this->getPattern($id);
  674. $tbl = 'T_' . $id;
  675. /* core dependency */
  676. $d_tbls = $this->getDependentJoins($id);
  677. foreach ($d_tbls as $d_tbl) {
  678. if (preg_match('/^T_([0-9\_]+)\.[spo]+/', $d_tbl, $m) && ($m[1] != $id)) {
  679. if ($this->isJoinedBefore($m[1], $id) && !in_array($m[1], array_merge($this->index['from'], $this->index['join']))) {
  680. $r .= $r ? $nl . ' AND ' : $nl . ' ';
  681. $r .= '(' . $d_tbl . ' IS NOT NULL)';
  682. }
  683. $this->logDependency($id, $d_tbl);
  684. }
  685. }
  686. /* triple-based join info */
  687. foreach ($infos as $info) {
  688. if ($this->isJoinedBefore($info['join_tbl'], $id) && $this->joinDependsOn($id, $info['join_tbl'])) {
  689. $r .= $r ? $nl . ' AND ' : $nl . ' ';
  690. $r .= '(' . $tbl . '.' . $info['term'] . ' = T_' . $info['join_tbl'] . '.' . $info['join_term'] . ')';
  691. }
  692. }
  693. /* filters etc */
  694. if ($sub_r = $this->getPatternSQL($pattern, 'join__T_' . $id)) {
  695. $r .= $r ? $nl . ' AND ' . $sub_r : $nl . ' ' . '(' . $sub_r . ')';
  696. }
  697. return $r;
  698. }
  699. /**
  700. * A log of identified table join dependencies in getJoinConditionSQL
  701. *
  702. */
  703. function logDependency($id, $tbl) {
  704. if (!isset($this->dependency_log[$id])) $this->dependency_log[$id] = array();
  705. if (!in_array($tbl, $this->dependency_log[$id])) {
  706. $this->dependency_log[$id][] = $tbl;
  707. }
  708. }
  709. /**
  710. * checks whether entries in the dependecy log could perhaps be optimized
  711. * (triggers re-ordering of patterns
  712. */
  713. function problematicDependencies() {
  714. foreach ($this->dependency_log as $id => $tbls) {
  715. if (count($tbls) > 1) return count($tbls);
  716. }
  717. return 0;
  718. }
  719. function isJoinedBefore($tbl_1, $tbl_2) {
  720. $tbl_ids = $this->getOrderedJoinIDs();
  721. foreach ($tbl_ids as $id) {
  722. if ($id == $tbl_1) {
  723. return 1;
  724. }
  725. if ($id == $tbl_2) {
  726. return 0;
  727. }
  728. }
  729. }
  730. function joinDependsOn($id, $id2) {
  731. if (in_array($id2, array_merge($this->index['from'], $this->index['join']))) {
  732. return 1;
  733. }
  734. $d_tbls = $this->getDependentJoins($id2);
  735. //echo $id . ' :: ' . $id2 . '=>' . print_r($d_tbls, 1);
  736. foreach ($d_tbls as $d_tbl) {
  737. if (preg_match('/^T_' .$id. '\./', $d_tbl)) {
  738. return 1;
  739. }
  740. }
  741. return 0;
  742. }
  743. function getDependentJoins($id) {
  744. $r = array();
  745. /* sub joins */
  746. foreach ($this->index['sub_joins'] as $alias) {
  747. if (preg_match('/^(T|V|G)_' . $id . '/', $alias)) {
  748. $r[] = $alias;
  749. }
  750. }
  751. /* siblings in shared optional */
  752. $o_id = $this->getOptionalPattern($id);
  753. foreach ($this->index['sub_joins'] as $alias) {
  754. if (preg_match('/^(T|V|G)_' . $o_id . '/', $alias) && !in_array($alias, $r)) {
  755. $r[] = $alias;
  756. }
  757. }
  758. foreach ($this->index['left_join'] as $alias) {
  759. if (preg_match('/^' . $o_id . '/', $alias) && !in_array($alias, $r)) {
  760. $r[] = 'T_' . $alias . '.s';
  761. }
  762. }
  763. return $r;
  764. }
  765. /* */
  766. function getRequiredSubJoinSQL($id, $prefix = '') {/* id is a triple pattern id. Optional FILTERS and GRAPHs are getting added to the join directly */
  767. $nl = "\n";
  768. $r = '';
  769. foreach ($this->index['sub_joins'] as $alias) {
  770. if (preg_match('/^V_' . $id . '_([a-z\_]+)\.val$/', $alias, $m)) {
  771. $col = $m[1];
  772. $sub_r = '';
  773. if ($this->isOptionalPattern($id)) {
  774. $pattern = $this->getPattern($id);
  775. do {
  776. $pattern = $this->getPattern($pattern['parent_id']);
  777. } while ($pattern['parent_id'] && ($pattern['type'] != 'optional'));
  778. $sub_r = $this->getPatternSQL($pattern, 'sub_join__V_' . $id);
  779. }
  780. $sub_r = $sub_r ? $nl . ' AND (' . $sub_r . ')' : '';
  781. /* lang dt only on literals */
  782. if ($col == 'o_lang_dt') {
  783. $sub_sub_r = 'T_' . $id . '.o_type = 2';
  784. $sub_r .= $nl . ' AND (' . $sub_sub_r . ')';
  785. }
  786. //$cur_prefix = $prefix ? $prefix . ' ' : 'STRAIGHT_';
  787. $cur_prefix = $prefix ? $prefix . ' ' : '';
  788. if ($col == 'g') {
  789. $r .= trim($cur_prefix . 'JOIN '. $this->getValueTable($col) . ' V_' .$id . '_' . $col. ' ON (' .$nl. ' (G_' . $id . '.' . $col. ' = V_' . $id. '_' . $col. '.id) ' . $sub_r . $nl . ')');
  790. }
  791. else {
  792. $r .= trim($cur_prefix . 'JOIN '. $this->getValueTable($col) . ' V_' .$id . '_' . $col. ' ON (' .$nl. ' (T_' . $id . '.' . $col. ' = V_' . $id. '_' . $col. '.id) ' . $sub_r . $nl . ')');
  793. }
  794. }
  795. elseif (preg_match('/^G_' . $id . '\.g$/', $alias, $m)) {
  796. $pattern = $this->getPattern($id);
  797. $sub_r = $this->getPatternSQL($pattern, 'graph_sub_join__G_' . $id);
  798. $sub_r = $sub_r ? $nl . ' AND ' . $sub_r : '';
  799. /* dataset restrictions */
  800. $gi = $this->getGraphInfos($id);
  801. $sub_sub_r = '';
  802. $added_gts = array();
  803. foreach ($gi as $set) {
  804. if (isset($set['graph']) && !in_array($set['graph'], $added_gts)) {
  805. $sub_sub_r .= $sub_sub_r !== '' ? ',' : '';
  806. $sub_sub_r .= $this->getTermID($set['graph'], 'g');
  807. $added_gts[] = $set['graph'];
  808. }
  809. }
  810. $sub_r .= ($sub_sub_r !== '') ? $nl . ' AND (G_' . $id . '.g IN (' . $sub_sub_r . '))' : ''; // /* ' . str_replace('#' , '::', $set['graph']) . ' */';
  811. /* other graph join conditions */
  812. foreach ($this->index['graph_vars'] as $var => $occurs) {
  813. $occur_tbls = array();
  814. foreach ($occurs as $occur) {
  815. $occur_tbls[] = $occur['table'];
  816. if ($occur['table'] == $id) break;
  817. }
  818. foreach($occur_tbls as $tbl) {
  819. if (($tbl != $id) && in_array($id, $occur_tbls) && $this->isJoinedBefore($tbl, $id)) {
  820. $sub_r .= $nl . ' AND (G_' .$id. '.g = G_' .$tbl. '.g)';
  821. }
  822. }
  823. }
  824. //$cur_prefix = $prefix ? $prefix . ' ' : 'STRAIGHT_';
  825. $cur_prefix = $prefix ? $prefix . ' ' : '';
  826. $r .= trim($cur_prefix . 'JOIN '. $this->getGraphTable() . ' G_' .$id . ' ON (' .$nl. ' (T_' . $id . '.t = G_' .$id. '.t)' . $sub_r . $nl . ')');
  827. }
  828. }
  829. return $r;
  830. }
  831. /* */
  832. function getWHERESQL() {
  833. $r = '';
  834. $nl = "\n";
  835. /* standard constraints */
  836. $sub_r = $this->getPatternSQL($this->getPattern('0'), 'where');
  837. /* additional constraints */
  838. foreach ($this->index['from'] as $id) {
  839. if ($sub_sub_r = $this->getConstraintSQL($id)) {
  840. $sub_r .= $sub_r ? $nl . ' AND ' . $sub_sub_r : $sub_sub_r;
  841. }
  842. }
  843. $r .= $sub_r ? $sub_r : '';
  844. /* left join dependencies */
  845. foreach ($this->index['left_join'] as $id) {
  846. $d_joins = $this->getDependentJoins($id);
  847. $added = array();
  848. $d_aliases = array();
  849. //echo $id . ' =>' . print_r($d_joins, 1);
  850. $id_alias = 'T_' . $id . '.s';
  851. foreach ($d_joins as $alias) {
  852. if (preg_match('/^(T|V|G)_([0-9\_]+)(_[spo])?\.([a-z\_]+)/', $alias, $m)) {
  853. $tbl_type = $m[1];
  854. $tbl_pattern_id = $m[2];
  855. $suffix = $m[3];
  856. if (($tbl_pattern_id >= $id) && $this->sameOptional($tbl_pattern_id, $id)) {/* get rid of dependency permutations and nested optionals */
  857. if (!in_array($tbl_type . '_' . $tbl_pattern_id . $suffix, $added)) {
  858. $sub_r .= $sub_r ? ' AND ' : '';
  859. $sub_r .= $alias . ' IS NULL';
  860. $d_aliases[] = $alias;
  861. $added[] = $tbl_type . '_' . $tbl_pattern_id . $suffix;
  862. $id_alias = ($tbl_pattern_id == $id) ? $alias : $id_alias;
  863. }
  864. }
  865. }
  866. }
  867. if (count($d_aliases) > 2) {/* @@todo fix this! */
  868. $sub_r1 = ' /* '.$id_alias.' dependencies */';
  869. $sub_r2 = '((' . $id_alias . ' IS NULL) OR (CONCAT(' . join(', ', $d_aliases) . ') IS NOT NULL))';
  870. $r .= $r ? $nl . $sub_r1 . $nl . ' AND ' .$sub_r2 : $sub_r1 . $nl . $sub_r2;
  871. }
  872. }
  873. return $r ? $nl . 'WHERE ' . $r : '';
  874. }
  875. /* */
  876. function addConstraintSQLEntry($id, $sql) {
  877. if (!isset($this->index['constraints'][$id])) {
  878. $this->index['constraints'][$id] = array();
  879. }
  880. if (!in_array($sql, $this->index['constraints'][$id])) {
  881. $this->index['constraints'][$id][] = $sql;
  882. }
  883. }
  884. function getConstraintSQL($id) {
  885. $r = '';
  886. $nl = "\n";
  887. $constraints = $this->v($id, array(), $this->index['constraints']);
  888. foreach ($constraints as $constraint) {
  889. $r .= $r ? $nl . ' AND ' . $constraint : $constraint;
  890. }
  891. return $r;
  892. }
  893. /* */
  894. function getPatternSQL($pattern, $context) {
  895. $type = $pattern['type'];
  896. $m = 'get' . ucfirst($type) . 'PatternSQL';
  897. return method_exists($this, $m) ? $this->$m($pattern, $context) : $this->getDefaultPatternSQL($pattern, $context);
  898. }
  899. function getDefaultPatternSQL($pattern, $context) {
  900. $r = '';
  901. $nl = "\n";
  902. $sub_ids = $this->v('patterns', array(), $pattern);
  903. foreach ($sub_ids as $sub_id) {
  904. $sub_r = $this->getPatternSQL($this->getPattern($sub_id), $context);
  905. $r .= ($r && $sub_r) ? $nl . ' AND (' . $sub_r . ')' : ($sub_r ? $sub_r : '');
  906. }
  907. return $r ? $r : '';
  908. }
  909. function getTriplePatternSQL($pattern, $context) {
  910. $r = '';
  911. $nl = "\n";
  912. $id = $pattern['id'];
  913. /* s p o */
  914. $vars = array();
  915. foreach (array('s', 'p', 'o') as $term) {
  916. $sub_r = '';
  917. $type = $pattern[$term . '_type'];
  918. if ($type == 'uri') {
  919. $term_id = $this->getTermID($pattern[$term], $term);
  920. $sub_r = '(T_' . $id . '.' . $term . ' = ' . $term_id . ') /* ' . str_replace('#' , '::', $pattern[$term]) . ' */';
  921. }
  922. elseif ($type == 'literal') {
  923. $term_id = $this->getTermID($pattern[$term], $term);
  924. $sub_r = '(T_' . $id . '.' . $term . ' = ' . $term_id . ') /* ' . preg_replace('/[\#\n]/' , ' ', $pattern[$term]) . ' */';
  925. if (($lang_dt = $this->v1($term . '_lang', '', $pattern)) || ($lang_dt = $this->v1($term . '_datatype', '', $pattern))) {
  926. $lang_dt_id = $this->getTermID($lang_dt);
  927. $sub_r .= $nl . ' AND (T_' . $id . '.' .$term. '_lang_dt = ' . $lang_dt_id . ') /* ' . str_replace('#' , '::', $lang_dt) . ' */';
  928. }
  929. }
  930. elseif ($type == 'var') {
  931. $val = $pattern[$term];
  932. if (isset($vars[$val])) {/* repeated var in pattern */
  933. $sub_r = '(T_' . $id . '.' . $term . '=' . 'T_' . $id . '.' . $vars[$val] . ')';
  934. }
  935. $vars[$val] = $term;
  936. if ($infos = $this->v($val, 0, $this->index['graph_vars'])) {/* graph var in triple pattern */
  937. $sub_r .= $sub_r ? $nl . ' AND ' : '';
  938. $tbl = $infos[0]['table'];
  939. $sub_r .= 'G_' . $tbl . '.g = T_' . $id . '.' . $term;
  940. }
  941. }
  942. if ($sub_r) {
  943. if (preg_match('/^(join)/', $context) || (preg_match('/^where/', $context) && in_array($id, $this->index['from']))) {
  944. $r .= $r ? $nl . ' AND ' . $sub_r : $sub_r;
  945. }
  946. }
  947. }
  948. /* g */
  949. if ($infos = $pattern['graph_infos']) {
  950. $tbl_alias = 'G_' . $id . '.g';
  951. if (!in_array($tbl_alias, $this->index['sub_joins'])) {
  952. $this->index['sub_joins'][] = $tbl_alias;
  953. }
  954. $sub_r = array('graph_var' => '', 'graph_uri' => '', 'from' => '', 'from_named' => '');
  955. foreach ($infos as $info) {
  956. $type = $info['type'];
  957. if ($type == 'graph') {
  958. if ($info['uri']) {
  959. $term_id = $this->getTermID($info['uri'], 'g');
  960. $sub_r['graph_uri'] .= $sub_r['graph_uri'] ? $nl . ' AND ' : '';
  961. $sub_r['graph_uri'] .= '(' .$tbl_alias. ' = ' . $term_id . ') /* ' . str_replace('#' , '::', $info['uri']) . ' */';
  962. }
  963. }
  964. }
  965. if ($sub_r['from'] && $sub_r['from_named']) {
  966. $sub_r['from_named'] = '';
  967. }
  968. if (!$sub_r['from'] && !$sub_r['from_named']) {
  969. $sub_r['graph_var'] = '';
  970. }
  971. if (preg_match('/^(graph_sub_join)/', $context)) {
  972. foreach ($sub_r as $g_type => $g_sql) {
  973. if ($g_sql) {
  974. $r .= $r ? $nl . ' AND ' . $g_sql : $g_sql;
  975. }
  976. }
  977. }
  978. }
  979. /* optional sibling filters? */
  980. if (preg_match('/^(join|sub_join)/', $context) && $this->isOptionalPattern($id)) {
  981. $o_pattern = $pattern;
  982. do {
  983. $o_pattern = $this->getPattern($o_pattern['parent_id']);
  984. } while ($o_pattern['parent_id'] && ($o_pattern['type'] != 'optional'));
  985. if ($sub_r = $this->getPatternSQL($o_pattern, 'optional_filter' . preg_replace('/^(.*)(__.*)$/', '\\2', $context))) {
  986. $r .= $r ? $nl . ' AND ' . $sub_r : $sub_r;
  987. }
  988. /* created constraints */
  989. if ($sub_r = $this->getConstraintSQL($id)) {
  990. $r .= $r ? $nl . ' AND ' . $sub_r : $sub_r;
  991. }
  992. }
  993. /* result */
  994. if (preg_match('/^(where)/', $context) && $this->isOptionalPattern($id)) {
  995. return '';
  996. }
  997. return $r;
  998. }
  999. /* */
  1000. function getFilterPatternSQL($pattern, $context) {
  1001. $r = '';
  1002. $id = $pattern['id'];
  1003. $constraint_id = $this->v1('constraint', '', $pattern);
  1004. $constraint = $this->getPattern($constraint_id);
  1005. $constraint_type = $constraint['type'];
  1006. if ($constraint_type == 'built_in_call') {
  1007. $r = $this->getBuiltInCallSQL($constraint, $context);
  1008. }
  1009. elseif ($constraint_type == 'expression') {
  1010. $r = $this->getExpressionSQL($constraint, $context, '', 'filter');
  1011. }
  1012. else {
  1013. $m = 'get' . ucfirst($constraint_type) . 'ExpressionSQL';
  1014. if (method_exists($this, $m)) {
  1015. $r = $this->$m($constraint, $context, '', 'filter');
  1016. }
  1017. }
  1018. if ($this->isOptionalPattern($id) && !preg_match('/^(join|optional_filter)/', $context)) {
  1019. return '';
  1020. }
  1021. /* unconnected vars in FILTERs eval to false */
  1022. $sub_r = $this->hasUnconnectedFilterVars($id);
  1023. if ($sub_r) {
  1024. if ($sub_r == 'alias') {
  1025. if (!in_array($r, $this->index['havings'])) $this->index['havings'][] = $r;
  1026. return '';
  1027. }
  1028. elseif (preg_match('/^T([^\s]+\.)g (.*)$/s', $r, $m)) {/* graph filter */
  1029. return 'G' . $m[1] . 't ' . $m[2];
  1030. }
  1031. elseif (preg_match('/^\(*V[^\s]+_g\.val .*$/s', $r, $m)) {/* graph value filter, @@improveMe */
  1032. //return $r;
  1033. }
  1034. else {
  1035. return 'FALSE';
  1036. }
  1037. }
  1038. /* some really ugly tweaks */
  1039. /* empty language filter: FILTER ( lang(?v) = '' ) */
  1040. $r = preg_replace('/\(\/\* language call \*\/ ([^\s]+) = ""\)/s', '((\\1 = "") OR (\\1 LIKE "%:%"))', $r);
  1041. return $r;
  1042. }
  1043. /**
  1044. * Checks if vars in the given (filter) pattern are used within the filter's scope.
  1045. */
  1046. function hasUnconnectedFilterVars($filter_pattern_id) {
  1047. $scope_id = $this->getFilterScope($filter_pattern_id);
  1048. $vars = $this->getFilterVars($filter_pattern_id);
  1049. $r = 0;
  1050. foreach ($vars as $var_name) {
  1051. if ($this->isUsedTripleVar($var_name, $scope_id)) continue;
  1052. if ($this->isAliasVar($var_name)) {
  1053. $r = 'alias';
  1054. break;
  1055. }
  1056. $r = 1;
  1057. break;
  1058. }
  1059. return $r;
  1060. }
  1061. /**
  1062. * Returns the given filter pattern's scope (the id of the parent group pattern).
  1063. */
  1064. function getFilterScope($filter_pattern_id) {
  1065. $patterns = $this->initial_index['patterns'];
  1066. $r = '';
  1067. foreach ($patterns as $id => $p) {
  1068. /* the id has to be sub-part of the given filter id */
  1069. if (!preg_match('/^' . $id . '.+/', $filter_pattern_id)) continue;
  1070. /* we are looking for a group or union */
  1071. if (!preg_match('/^(group|union)$/', $p['type'])) continue;
  1072. /* we are looking for the longest/deepest match */
  1073. if (strlen($id) > strlen($r)) $r = $id;
  1074. }
  1075. return $r;
  1076. }
  1077. /**
  1078. * Builds a list of vars used in the given (filter) pattern.
  1079. */
  1080. function getFilterVars($filter_pattern_id) {
  1081. $r = array();
  1082. $patterns = $this->initial_index['patterns'];
  1083. /* find vars in the given filter (i.e. the given id is part of their pattern id) */
  1084. foreach ($patterns as $id => $p) {
  1085. if (!preg_match('/^' . $filter_pattern_id . '.+/', $id)) continue;
  1086. $var_name = '';
  1087. if ($p['type'] == 'var') {
  1088. $var_name = $p['value'];
  1089. }
  1090. elseif (($p['type'] == 'built_in_call') && ($p['call'] == 'bound')) {
  1091. $var_name = $p['args'][0]['value'];
  1092. }
  1093. if ($var_name && !in_array($var_name, $r)) {
  1094. $r[] = $var_name;
  1095. }
  1096. }
  1097. return $r;
  1098. }
  1099. /**
  1100. * Checks if $var_name appears as result projection alias.
  1101. */
  1102. function isAliasVar($var_name) {
  1103. foreach ($this->infos['query']['result_vars'] as $r_var) {
  1104. if ($r_var['alias'] == $var_name) return 1;
  1105. }
  1106. return 0;
  1107. }
  1108. /**
  1109. * Checks if $var_name is used in a triple pattern in the given scope
  1110. */
  1111. function isUsedTripleVar($var_name, $scope_id = '0') {
  1112. $patterns = $this->initial_index['patterns'];
  1113. foreach ($patterns as $id => $p) {
  1114. if ($p['type'] != 'triple') continue;
  1115. if (!preg_match('/^' . $scope_id . '.+/', $id)) continue;
  1116. foreach (array('s', 'p', 'o') as $term) {
  1117. if ($p[$term . '_type'] != 'var') continue;
  1118. if ($p[$term] == $var_name) return 1;
  1119. }
  1120. }
  1121. }
  1122. /* */
  1123. function getExpressionSQL($pattern, $context, $val_type = '', $parent_type = '') {
  1124. $r = '';
  1125. $nl = "\n";
  1126. $type = $this->v1('type', '', $pattern);
  1127. $sub_type = $this->v1('sub_type', $type, $pattern);
  1128. if (preg_match('/^(and|or)$/', $sub_type)) {
  1129. foreach ($pattern['patterns'] as $sub_id) {
  1130. $sub_pattern = $this->getPattern($sub_id);
  1131. $sub_pattern_type = $sub_pattern['type'];
  1132. if ($sub_pattern_type == 'built_in_call') {
  1133. $sub_r = $this->getBuiltInCallSQL($sub_pattern, $context, '', $parent_type);
  1134. }
  1135. else {
  1136. $sub_r = $this->getExpressionSQL($sub_pattern, $context, '', $parent_type);
  1137. }
  1138. if ($sub_r) {
  1139. $r .= $r ? ' ' . strtoupper($sub_type). ' (' .$sub_r. ')' : '(' . $sub_r . ')';
  1140. }
  1141. }
  1142. }
  1143. elseif ($sub_type == 'built_in_call') {
  1144. $r = $this->getBuiltInCallSQL($pattern, $context, $val_type, $parent_type);
  1145. }
  1146. elseif (preg_match('/literal/', $sub_type)) {
  1147. $r = $this->getLiteralExpressionSQL($pattern, $context, $val_type, $parent_type);
  1148. }
  1149. elseif ($sub_type) {
  1150. $m = 'get' . ucfirst($sub_type) . 'ExpressionSQL';
  1151. if (method_exists($this, $m)) {
  1152. $r = $this->$m($pattern, $context, '', $parent_type);
  1153. }
  1154. }
  1155. /* skip expressions that reference non-yet-joined tables */
  1156. if (preg_match('/__(T|V|G)_(.+)$/', $context, $m)) {
  1157. $context_pattern_id = $m[2];
  1158. $context_table_type = $m[1];
  1159. if (preg_match_all('/((T|V|G)(\_[0-9])+)/', $r, $m)) {
  1160. $aliases = $m[1];
  1161. $keep = 1;
  1162. foreach ($aliases as $alias) {
  1163. if (preg_match('/(T|V|G)_(.*)$/', $alias, $m)) {
  1164. $tbl_type = $m[1];
  1165. $tbl = $m[2];
  1166. if (!$this->isJoinedBefore($tbl, $context_pattern_id)) {
  1167. $keep = 0;
  1168. }
  1169. elseif (($context_pattern_id == $tbl) && preg_match('/(TV)/', $context_table_type . $tbl_type)) {
  1170. $keep = 0;
  1171. }
  1172. }
  1173. }
  1174. $r = $keep ? $r : '';
  1175. }
  1176. }
  1177. return $r ? '(' . $r . ')' : $r;
  1178. }
  1179. function detectExpressionValueType($pattern_ids) {
  1180. foreach ($pattern_ids as $id) {
  1181. $pattern = $this->getPattern($id);
  1182. $type = $this->v('type', '', $pattern);
  1183. if (($type == 'literal') && isset($pattern['datatype'])) {
  1184. if (in_array($pattern['datatype'], array($this->xsd . 'integer', $this->xsd . 'float', $this->xsd . 'double'))) {
  1185. return 'numeric';
  1186. }
  1187. }
  1188. }
  1189. return '';
  1190. }
  1191. /* */
  1192. function getRelationalExpressionSQL($pattern, $context, $val_type = '', $parent_type = '') {
  1193. $r = '';
  1194. $val_type = $this->detectExpressionValueType($pattern['patterns']);
  1195. $op = $pattern['operator'];
  1196. foreach ($pattern['patterns'] as $sub_id) {
  1197. $sub_pattern = $this->getPattern($sub_id);
  1198. $sub_pattern['parent_op'] = $op;
  1199. $sub_type = $sub_pattern['type'];
  1200. $m = ($sub_type == 'built_in_call') ? 'getBuiltInCallSQL' : 'get' . ucfirst($sub_type) . 'ExpressionSQL';
  1201. $m = str_replace('ExpressionExpression', 'Expression', $m);
  1202. $sub_r = method_exists($this, $m) ? $this->$m($sub_pattern, $context, $val_type, 'relational') : '';
  1203. $r .= $r ? ' ' . $op . ' ' . $sub_r : $sub_r;
  1204. }
  1205. return $r ? '(' . $r . ')' : $r;
  1206. }
  1207. function getAdditiveExpressionSQL($pattern, $context, $val_type = '', $parent_type = '') {
  1208. $r = '';
  1209. $val_type = $this->detectExpressionValueType($pattern['patterns']);
  1210. foreach ($pattern['patterns'] as $sub_id) {
  1211. $sub_pattern = $this->getPattern($sub_id);
  1212. $sub_type = $this->v('type', '', $sub_pattern);
  1213. $m = ($sub_type == 'built_in_call') ? 'getBuiltInCallSQL' : 'get' . ucfirst($sub_type) . 'ExpressionSQL';
  1214. $m = str_replace('ExpressionExpression', 'Expression', $m);
  1215. $sub_r = method_exists($this, $m) ? $this->$m($sub_pattern, $context, $val_type, 'additive') : '';
  1216. $r .= $r ? ' ' . $sub_r : $sub_r;
  1217. }
  1218. return $r;
  1219. }
  1220. function getMultiplicativeExpressionSQL($pattern, $context, $val_type = '', $parent_type = '') {
  1221. $r = '';
  1222. $val_type = $this->detectExpressionValueType($pattern['patterns']);
  1223. foreach ($pattern['patterns'] as $sub_id) {
  1224. $sub_pattern = $this->getPattern($sub_id);
  1225. $sub_type = $sub_pattern['type'];
  1226. $m = ($sub_type == 'built_in_call') ? 'getBuiltInCallSQL' : 'get' . ucfirst($sub_type) . 'ExpressionSQL';
  1227. $m = str_replace('ExpressionExpression', 'Expression', $m);
  1228. $sub_r = method_exists($this, $m) ? $this->$m($sub_pattern, $context, $val_type, 'multiplicative') : '';
  1229. $r .= $r ? ' ' . $sub_r : $sub_r;
  1230. }
  1231. return $r;
  1232. }
  1233. /* */
  1234. function getVarExpressionSQL($pattern, $context, $val_type = '', $parent_type = '') {
  1235. $var = $pattern['value'];
  1236. $info = $this->getVarTableInfos($var);
  1237. if (!$tbl = $info['table']) {
  1238. /* might be an aggregate var */
  1239. $vars = $this->infos['query']['result_vars'];
  1240. foreach ($vars as $test_var) {
  1241. if ($test_var['alias'] == $pattern['value']) {
  1242. return '`' . $pattern['value'] . '`';
  1243. }
  1244. }
  1245. return '';
  1246. }
  1247. $col = $info['col'];
  1248. if (($context == 'order') && ($col == 'o')) {
  1249. $tbl_alias = 'T_' . $tbl . '.o_comp';
  1250. }
  1251. elseif ($context == 'sameterm') {
  1252. $tbl_alias = 'T_' . $tbl . '.' . $col;
  1253. }
  1254. elseif (($parent_type == 'relational') && ($col == 'o') && (preg_match('/[\<\>]/', $this->v('parent_op', '', $pattern)))) {
  1255. $tbl_alias = 'T_' . $tbl . '.o_comp';
  1256. }
  1257. else {
  1258. $tbl_alias = 'V_' . $tbl . '_' . $col . '.val';
  1259. if (!in_array($tbl_alias, $this->index['sub_joins'])) {
  1260. $this->index['sub_joins'][] = $tbl_alias;
  1261. }
  1262. }
  1263. $op = $this->v('operator', '', $pattern);
  1264. if (preg_match('/^(filter|and)/', $parent_type)) {
  1265. if ($op == '!') {
  1266. $r = '(((' . $tbl_alias . ' = 0) AND (CONCAT("1", ' . $tbl_alias . ') != 1))'; /* 0 and no string */
  1267. $r .= ' OR (' . $tbl_alias . ' IN ("", "false")))'; /* or "", or "false" */
  1268. }
  1269. else {
  1270. $r = '((' . $tbl_alias . ' != 0)'; /* not null */
  1271. $r .= ' OR ((CONCAT("1", ' . $tbl_alias . ') = 1) AND (' . $tbl_alias . ' NOT IN ("", "false"))))'; /* string, and not "" or "false" */
  1272. }
  1273. }
  1274. else {
  1275. $r = trim($op . ' ' . $tbl_alias);
  1276. if ($val_type == 'numeric') {
  1277. if (preg_match('/__(T|V|G)_(.+)$/', $context, $m)) {
  1278. $context_pattern_id = $m[2];
  1279. $context_table_type = $m[1];
  1280. }
  1281. else {
  1282. $context_pattern_id = $pattern['id'];
  1283. $context_table_type = 'T';
  1284. }
  1285. if ($this->isJoinedBefore($tbl, $context_pattern_id)) {
  1286. $add = ($tbl != $context_pattern_id) ? 1 : 0;
  1287. $add = (!$add && ($context_table_type == 'V')) ? 1 : 0;
  1288. if ($add) {
  1289. $this->addConstraintSQLEntry($context_pattern_id, '(' .$r. ' = "0" OR ' . $r . '*1.0 != 0)');
  1290. }
  1291. }
  1292. }
  1293. }
  1294. return $r;
  1295. }
  1296. /* */
  1297. function getUriExpressionSQL($pattern, $context, $val_type = '') {
  1298. $val = $pattern['uri'];
  1299. $r = $pattern['operator'];
  1300. $r .= is_numeric($val) ? ' ' . $val : ' "' . mysql_real_escape_string($val, $this->store->getDBCon()) . '"';
  1301. return $r;
  1302. }
  1303. /* */
  1304. function getLiteralExpressionSQL($pattern, $context, $val_type = '', $parent_type = '') {
  1305. $val = $pattern['value'];
  1306. $r = $pattern['operator'];
  1307. if (is_numeric($val) && $this->v('datatype', 0, $pattern)) {
  1308. $r .= ' ' . $val;
  1309. }
  1310. elseif (preg_match('/^(true|false)$/i', $val) && ($this->v1('datatype', '', $pattern) == 'http://www.w3.org/2001/XMLSchema#boolean')) {
  1311. $r .= ' ' . strtoupper($val);
  1312. }
  1313. elseif ($parent_type == 'regex') {
  1314. $sub_r = mysql_real_escape_string($val, $this->store->getDBCon());
  1315. $r .= ' "' . preg_replace('/\x5c\x5c/', '\\', $sub_r) . '"';
  1316. }
  1317. else {
  1318. $r .= ' "' . mysql_real_escape_string($val, $this->store->getDBCon()) . '"';
  1319. }
  1320. if (($lang_dt = $this->v1('lang', '', $pattern)) || ($lang_dt = $this->v1('datatype', '', $pattern))) {
  1321. /* try table/alias via var in siblings */
  1322. if ($var = $this->findSiblingVarExpression($pattern['id'])) {
  1323. if (isset($this->index['vars'][$var])) {
  1324. $infos = $this->index['vars'][$var];
  1325. foreach ($infos as $info) {
  1326. if ($info['col'] == 'o') {
  1327. $tbl = $info['table'];
  1328. $term_id = $this->getTermID($lang_dt);
  1329. if ($pattern['operator'] != '!=') {
  1330. if (preg_match('/__(T|V|G)_(.+)$/', $context, $m)) {
  1331. $context_pattern_id = $m[2];
  1332. $context_table_type = $m[1];
  1333. }
  1334. elseif ($context == 'where') {
  1335. $context_pattern_i

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