PageRenderTime 63ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/store/ARC2_StoreSelectQueryHandler.php

http://github.com/semsol/arc2
PHP | 1983 lines | 1702 code | 168 blank | 113 comment | 406 complexity | 0f14a804b2567a3c2f3bdb14177b6ea1 MD5 | raw file
Possible License(s): GPL-3.0

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

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