3 * ARC2 RDF Store SELECT Query Handler
5 * @author Benjamin Nowack
6 * @license http://arc.semsol.org/license
7 * @homepage <http://arc.semsol.org/>
13 ARC2::inc('StoreQueryHandler');
15 class ARC2_StoreSelectQueryHandler extends ARC2_StoreQueryHandler {
17 function __construct($a = '', &$caller) {/* caller has to be a store */
18 parent::__construct($a, $caller);
21 function ARC2_StoreSelectQueryHandler($a = '', &$caller) {
22 $this->__construct($a, $caller);
25 function __init() {/* db_con */
27 $this->store =& $this->caller;
28 $con = $this->store->getDBCon();
29 $this->handler_type = 'select';
30 $this->engine_type = $this->v('store_engine_type', 'MyISAM', $this->a);
31 $this->cache_results = $this->v('store_cache_results', 0, $this->a);
36 function runQuery($infos) {
37 $con = $this->store->getDBCon();
38 $rf = $this->v('result_format', '', $infos);
39 $this->infos = $infos;
40 $this->infos['null_vars'] = array();
41 $this->indexes = array();
42 $this->pattern_order_offset = 0;
43 $q_sql = $this->getSQL();
45 /* debug result formats */
46 if ($rf == 'sql') return $q_sql;
47 if ($rf == 'structure') return $this->infos;
48 if ($rf == 'index') return $this->indexes;
49 /* create intermediate results (ID-based) */
50 $tmp_tbl = $this->createTempTable($q_sql);
52 $r = $this->getFinalQueryResult($q_sql, $tmp_tbl);
53 /* remove intermediate results */
54 if (!$this->cache_results) {
55 $this->queryDB('DROP TABLE IF EXISTS ' . $tmp_tbl, $con);
63 $this->buildInitialIndexes();
64 foreach ($this->indexes as $i => $index) {
65 $this->index = array_merge($this->getEmptyIndex(), $index);
66 $this->analyzeIndex($this->getPattern('0'));
67 $sub_r = $this->getQuerySQL();
68 $r .= $r ? $nl . 'UNION' . $this->getDistinctSQL() . $nl : '';
69 $r .= $this->is_union_query ? '(' . $sub_r . ')' : $sub_r;
70 $this->indexes[$i] = $this->index;
72 $r .= $this->is_union_query ? $this->getLIMITSQL() : '';
73 if ($this->v('order_infos', 0, $this->infos['query'])) {
74 $r = preg_replace('/SELECT(\s+DISTINCT)?\s*/', 'SELECT\\1 NULL AS `_pos_`, ', $r);
76 if ($pd_count = $this->problematicDependencies()) {
77 /* re-arranging the patterns sometimes reduces the LEFT JOIN dependencies */
79 if (!$this->pattern_order_offset) $set_sql = 1;
80 if (!$set_sql && ($pd_count < $this->opt_sql_pd_count)) $set_sql = 1;
81 if (!$set_sql && ($pd_count == $this->opt_sql_pd_count) && (strlen($r) < strlen($this->opt_sql))) $set_sql = 1;
84 $this->opt_sql_pd_count = $pd_count;
86 $this->pattern_order_offset++;
87 if ($this->pattern_order_offset > 5) {
88 return $this->opt_sql;
90 return $this->getSQL();
95 function buildInitialIndexes() {
96 $this->dependency_log = array();
97 $this->index = $this->getEmptyIndex();
98 $this->buildIndex($this->infos['query']['pattern'], 0);
100 $this->analyzeIndex($this->getPattern('0'));
101 $this->initial_index = $this->index;
103 $this->is_union_query = $this->index['union_branches'] ? 1 : 0;
104 $this->indexes = $this->is_union_query ? $this->getUnionIndexes($this->index) : array($this->index);
107 function createTempTable($q_sql) {
108 $con = $this->store->getDBCon();
109 $v = $this->store->getDBVersion();
110 if ($this->cache_results) {
111 $tbl = $this->store->getTablePrefix() . 'Q' . md5($q_sql);
114 $tbl = $this->store->getTablePrefix() . 'Q' . md5($q_sql . time() . uniqid(rand()));
116 if (strlen($tbl) > 64) $tbl = 'Q' . md5($tbl);
117 $tmp_sql = 'CREATE TEMPORARY TABLE ' . $tbl . ' ( ' . $this->getTempTableDef($tbl, $q_sql) . ') ';
118 $tmp_sql .= (($v < '04-01-00') && ($v >= '04-00-18')) ? 'ENGINE' : (($v >= '04-01-02') ? 'ENGINE' : 'TYPE');
119 $tmp_sql .= '=' . $this->engine_type;/* HEAP doesn't support AUTO_INCREMENT, and MySQL breaks on MEMORY sometimes */
120 if (!$this->queryDB($tmp_sql, $con) && !$this->queryDB(str_replace('CREATE TEMPORARY', 'CREATE', $tmp_sql), $con)) {
121 return $this->addError(mysql_error($con));
123 mysql_unbuffered_query('INSERT INTO ' . $tbl . ' ' . "\n" . $q_sql, $con);
124 if ($er = mysql_error($con)) $this->addError($er);
128 function getEmptyIndex() {
132 'left_join' => array(),
133 'vars' => array(), 'graph_vars' => array(), 'graph_uris' => array(),
135 'triple_patterns' => array(),
136 'sub_joins' => array(),
137 'constraints' => array(),
138 'union_branches'=> array(),
139 'patterns' => array(),
144 function getTempTableDef($tmp_tbl, $q_sql) {
145 $col_part = preg_replace('/^SELECT\s*(DISTINCT)?(.*)FROM.*$/s', '\\2', $q_sql);
146 $parts = explode(',', $col_part);
147 $has_order_infos = $this->v('order_infos', 0, $this->infos['query']);
150 foreach ($parts as $part) {
151 if (preg_match('/\.?(.+)\s+AS\s+`(.+)`/U', trim($part), $m) && !isset($added[$m[2]])) {
154 if ($alias == '_pos_') continue;
156 $r .= "\n `" . $alias . "` int UNSIGNED";
160 if ($has_order_infos) {
161 $r = "\n" . '`_pos_` mediumint NOT NULL AUTO_INCREMENT PRIMARY KEY, ' . $r;
163 return $r ? $r . "\n" : '';
166 function getFinalQueryResult($q_sql, $tmp_tbl) {
169 $aggregate_vars = array();
170 foreach ($this->infos['query']['result_vars'] as $entry) {
171 if ($entry['aggregate']) {
172 $vars[] = $entry['alias'];
173 $aggregate_vars[] = $entry['alias'];
176 $vars[] = $entry['var'];
180 $r = array('variables' => $vars);
181 $v_sql = $this->getValueSQL($tmp_tbl, $q_sql);
182 //echo "\n\n" . $v_sql;
184 $con = $this->store->getDBCon();
185 $rs = mysql_unbuffered_query($v_sql, $con);
186 if ($er = mysql_error($con)) {
187 $this->addError($er);
191 $types = array(0 => 'uri', 1 => 'bnode', 2 => 'literal');
193 while ($pre_row = mysql_fetch_array($rs)) {
195 foreach ($vars as $var) {
196 if (isset($pre_row[$var])) {
197 $row[$var] = $pre_row[$var];
198 $row[$var . ' type'] = isset($pre_row[$var . ' type']) ? $types[$pre_row[$var . ' type']] : (in_array($var, $aggregate_vars) ? 'literal' : 'uri');
199 if (isset($pre_row[$var . ' lang_dt']) && ($lang_dt = $pre_row[$var . ' lang_dt'])) {
200 if (preg_match('/^([a-z]+(\-[a-z0-9]+)*)$/i', $lang_dt)) {
201 $row[$var . ' lang'] = $lang_dt;
204 $row[$var . ' datatype'] = $lang_dt;
209 if ($row || !$vars) {
220 function buildIndex($pattern, $id) {
221 $pattern['id'] = $id;
222 $type = $this->v('type', '', $pattern);
223 if (($type == 'filter') && $this->v('constraint', 0, $pattern)) {
224 $sub_pattern = $pattern['constraint'];
225 $sub_pattern['parent_id'] = $id;
226 $sub_id = $id . '_0';
227 $this->buildIndex($sub_pattern, $sub_id);
228 $pattern['constraint'] = $sub_id;
231 $sub_patterns = $this->v('patterns', array(), $pattern);
232 $keys = array_keys($sub_patterns);
233 $spc = count($sub_patterns);
234 if (($spc > 4) && $this->pattern_order_offset) {
236 for ($i = 0 ; $i < $spc; $i++) {
237 $keys[$i] = $i + $this->pattern_order_offset;
238 while ($keys[$i] >= $spc) $keys[$i] -= $spc;
241 foreach ($keys as $i => $key) {
242 $sub_pattern = $sub_patterns[$key];
243 $sub_pattern['parent_id'] = $id;
244 $sub_id = $id . '_' . $key;
245 $this->buildIndex($sub_pattern, $sub_id);
246 $pattern['patterns'][$i] = $sub_id;
247 if ($type == 'union') {
248 $this->index['union_branches'][] = $sub_id;
252 $this->index['patterns'][$id] = $pattern;
257 function analyzeIndex($pattern) {
258 $type = $pattern['type'];
259 $id = $pattern['id'];
261 if ($type == 'triple') {
262 foreach (array('s', 'p', 'o') as $term) {
263 if ($pattern[$term . '_type'] == 'var') {
264 $val = $pattern[$term];
265 $this->index['vars'][$val] = array_merge($this->v($val, array(), $this->index['vars']), array(array('table' => $pattern['id'], 'col' =>$term)));
267 if ($pattern[$term . '_type'] == 'bnode') {
268 $val = $pattern[$term];
269 $this->index['bnodes'][$val] = array_merge($this->v($val, array(), $this->index['bnodes']), array(array('table' => $pattern['id'], 'col' =>$term)));
272 $this->index['triple_patterns'][] = $pattern['id'];
274 if ($this->isOptionalPattern($id)) {
275 $this->index['left_join'][] = $id;
277 elseif (!$this->index['from']) {
278 $this->index['from'][] = $id;
280 elseif (!$this->getJoinInfos($id)) {
281 $this->index['from'][] = $id;
284 $this->index['join'][] = $id;
286 /* graph infos, graph vars */
287 $this->index['patterns'][$id]['graph_infos'] = $this->getGraphInfos($id);
288 foreach ($this->index['patterns'][$id]['graph_infos'] as $info) {
289 if ($info['type'] == 'graph') {
291 $val = $info['var']['value'];
292 $this->index['graph_vars'][$val] = array_merge($this->v($val, array(), $this->index['graph_vars']), array(array('table' => $id)));
294 elseif ($info['uri']) {
296 $this->index['graph_uris'][$val] = array_merge($this->v($val, array(), $this->index['graph_uris']), array(array('table' => $id)));
301 $sub_ids = $this->v('patterns', array(), $pattern);
302 foreach ($sub_ids as $sub_id) {
303 $this->analyzeIndex($this->getPattern($sub_id));
309 function getGraphInfos($id) {
312 $pattern = $this->index['patterns'][$id];
313 $type = $pattern['type'];
315 if ($type == 'graph') {
316 $r[] = array('type' => 'graph', 'var' => $pattern['var'], 'uri' => $pattern['uri']);
318 $p_pattern = $this->index['patterns'][$pattern['parent_id']];
319 if (isset($p_pattern['graph_infos'])) {
320 return array_merge($p_pattern['graph_infos'], $r);
322 return array_merge($this->getGraphInfos($pattern['parent_id']), $r);
324 /* FROM / FROM NAMED */
326 if (isset($this->infos['query']['dataset'])) {
327 foreach ($this->infos['query']['dataset'] as $set) {
328 $r[] = array_merge(array('type' => 'dataset'), $set);
337 function getPattern($id) {
341 return $this->v($id, array(), $this->index['patterns']);
344 function getInitialPattern($id) {
345 return $this->v($id, array(), $this->initial_index['patterns']);
350 function getUnionIndexes($pre_index) {
354 /* only process branches with minimum depth */
355 foreach ($pre_index['union_branches'] as $id) {
356 $branches[$id] = count(preg_split('/\_/', $id));
357 $min_depth = min($min_depth, $branches[$id]);
359 foreach ($branches as $branch_id => $depth) {
360 if ($depth == $min_depth) {
361 $union_id = preg_replace('/\_[0-9]+$/', '', $branch_id);
362 $index = array('keeping' => $branch_id, 'union_branches' => array(), 'patterns' => $pre_index['patterns']);
363 $old_branches = $index['patterns'][$union_id]['patterns'];
364 $skip_id = ($old_branches[0] == $branch_id) ? $old_branches[1] : $old_branches[0];
365 $index['patterns'][$union_id]['type'] = 'group';
366 $index['patterns'][$union_id]['patterns'] = array($branch_id);
368 foreach ($index['patterns'] as $pattern_id => $pattern) {
369 if (preg_match('/^' .$skip_id. '/', $pattern_id)) {
370 unset($index['patterns'][$pattern_id]);
372 elseif ($pattern['type'] == 'union') {
373 foreach ($pattern['patterns'] as $sub_union_branch_id) {
374 $index['union_branches'][] = $sub_union_branch_id;
378 if ($index['union_branches']) {
379 $r = array_merge($r, $this->getUnionIndexes($index));
391 function isOptionalPattern($id) {
392 $pattern = $this->getPattern($id);
393 if ($this->v('type', '', $pattern) == 'optional') {
396 if ($this->v('parent_id', '0', $pattern) == '0') {
399 return $this->isOptionalPattern($pattern['parent_id']);
402 function getOptionalPattern($id) {
403 $pn = $this->getPattern($id);
405 $pn = $this->getPattern($pn['parent_id']);
406 } while ($pn['parent_id'] && ($pn['type'] != 'optional'));
410 function sameOptional($id, $id2) {
411 return $this->getOptionalPattern($id) == $this->getOptionalPattern($id2);
416 function isUnionPattern($id) {
417 $pattern = $this->getPattern($id);
418 if ($this->v('type', '', $pattern) == 'union') {
421 if ($this->v('parent_id', '0', $pattern) == '0') {
424 return $this->isUnionPattern($pattern['parent_id']);
429 function getValueTable($col) {
430 return $this->store->getTablePrefix() . (preg_match('/^(s|o)$/', $col) ? $col . '2val' : 'id2val');
433 function getGraphTable() {
434 return $this->store->getTablePrefix() . 'g2t';
439 function getQuerySQL() {
441 $where_sql = $this->getWHERESQL(); /* pre-fills $index['sub_joins'] $index['constraints'] */
442 $order_sql = $this->getORDERSQL(); /* pre-fills $index['sub_joins'] $index['constraints'] */
444 ($this->is_union_query ? 'SELECT' : 'SELECT' . $this->getDistinctSQL()) . $nl .
445 $this->getResultVarsSQL() . $nl . /* fills $index['sub_joins'] */
446 $this->getFROMSQL() .
447 $this->getAllJoinsSQL() .
448 $this->getWHERESQL() .
449 $this->getGROUPSQL() .
450 $this->getORDERSQL() .
451 ($this->is_union_query ? '' : $this->getLIMITSQL()) .
458 function getDistinctSQL() {
459 if ($this->is_union_query) {
460 return ($this->v('distinct', 0, $this->infos['query']) || $this->v('reduced', 0, $this->infos['query'])) ? '' : ' ALL';
462 return ($this->v('distinct', 0, $this->infos['query']) || $this->v('reduced', 0, $this->infos['query'])) ? ' DISTINCT' : '';
467 function getResultVarsSQL() {
469 $vars = $this->infos['query']['result_vars'];
472 foreach ($vars as $var) {
473 $var_name = $var['var'];
475 if ($tbl_infos = $this->getVarTableInfos($var_name, 0)) {
476 $tbl = $tbl_infos['table'];
477 $col = $tbl_infos['col'];
478 $tbl_alias = $tbl_infos['table_alias'];
480 elseif ($var_name == 1) {/* ASK query */
481 $r .= '1 AS `success`';
484 $this->addError('Result variable "' .$var_name. '" not used in query.');
488 if ($var['aggregate']) {
490 if (strtolower($var['aggregate']) != 'count') {
491 $tbl_alias = 'V_' . $tbl . '_' . $col . '.val';
494 if (!isset($added[$var['alias']])) {
495 $r .= $r ? ',' . $nl . ' ' : ' ';
496 $distinct_code = (strtolower($var['aggregate']) == 'count') && $this->v('distinct', 0, $this->infos['query']) ? 'DISTINCT ' : '';
497 $r .= $var['aggregate'] . '(' . $conv_code . $distinct_code . $tbl_alias. ') AS `' . $var['alias'] . '`';
498 $added[$var['alias']] = 1;
503 if (!isset($added[$var_name])) {
504 $r .= $r ? ',' . $nl . ' ' : ' ';
505 $r .= $tbl_alias . ' AS `' . $var_name . '`';
506 $is_s = ($col == 's');
507 $is_p = ($col == 'p');
508 $is_o = ($col == 'o');
509 if ($tbl_alias == 'NULL') {
510 /* type / add in UNION queries? */
511 if ($is_s || $is_o) {
512 $r .= ', ' . $nl . ' NULL AS `' . $var_name . ' type`';
514 /* lang_dt / always add it in UNION queries, the var may be used as s/p/o */
515 if ($is_o || $this->is_union_query) {
516 $r .= ', ' . $nl . ' NULL AS `' . $var_name . ' lang_dt`';
521 if ($is_s || $is_o) {
522 $r .= ', ' . $nl . ' ' .$tbl_alias . '_type AS `' . $var_name . ' type`';
524 /* lang_dt / always add it in UNION queries, the var may be used as s/p/o */
526 $r .= ', ' . $nl . ' ' .$tbl_alias . '_lang_dt AS `' . $var_name . ' lang_dt`';
528 elseif ($this->is_union_query) {
529 $r .= ', ' . $nl . ' NULL AS `' . $var_name . ' lang_dt`';
532 $added[$var_name] = 1;
535 if (!in_array($tbl_alias, $this->index['sub_joins'])) {
536 $this->index['sub_joins'][] = $tbl_alias;
540 return $r ? $r : '1 AS `success`';
543 function getVarTableInfos($var, $ignore_initial_index = 1) {
545 return array('table' => '', 'col' => '', 'table_alias' => '*');
547 if ($infos = $this->v($var, 0, $this->index['vars'])) {
548 $infos[0]['table_alias'] = 'T_' . $infos[0]['table'] . '.' . $infos[0]['col'];
551 if ($infos = $this->v($var, 0, $this->index['graph_vars'])) {
552 $infos[0]['col'] = 'g';
553 $infos[0]['table_alias'] = 'G_' . $infos[0]['table'] . '.' . $infos[0]['col'];
556 if ($this->is_union_query && !$ignore_initial_index) {
557 if (($infos = $this->v($var, 0, $this->initial_index['vars'])) || ($infos = $this->v($var, 0, $this->initial_index['graph_vars']))) {
558 if (!in_array($var, $this->infos['null_vars'])) {
559 $this->infos['null_vars'][] = $var;
561 $infos[0]['table_alias'] = 'NULL';
562 $infos[0]['col'] = !isset($infos[0]['col']) ? '' : $infos[0]['col'];
571 function getFROMSQL() {
573 foreach ($this->index['from'] as $id) {
574 $r .= $r ? ', ' : 'FROM (';
575 $r .= $this->getTripleTable($id) . ' T_' . $id;
577 return $r ? $r . ')' : '';
582 function getOrderedJoinIDs() {
583 return array_merge($this->index['from'], $this->index['join'], $this->index['left_join']);
586 function getJoinInfos($id) {
588 $tbl_ids = $this->getOrderedJoinIDs();
589 $pattern = $this->getPattern($id);
590 foreach ($tbl_ids as $tbl_id) {
591 $tbl_pattern = $this->getPattern($tbl_id);
592 if ($tbl_id != $id) {
593 foreach (array('s', 'p', 'o') as $tbl_term) {
594 foreach (array('var', 'bnode', 'uri') as $term_type) {
595 if ($tbl_pattern[$tbl_term . '_type'] == $term_type) {
596 foreach (array('s', 'p', 'o') as $term) {
597 if (($pattern[$term . '_type'] == $term_type) && ($tbl_pattern[$tbl_term] == $pattern[$term])) {
598 $r[] = array('term' => $term, 'join_tbl' => $tbl_id, 'join_term' => $tbl_term);
609 function getAllJoinsSQL() {
610 $js = $this->getJoins();
611 $ljs = $this->getLeftJoins();
612 $entries = array_merge($js, $ljs);
614 foreach ($entries as $entry) {
615 if (preg_match('/([^\s]+) ON (.*)/s', $entry, $m)) {
616 $id2code[$m[1]] = $entry;
620 foreach ($id2code as $id => $code) {
621 $deps[$id]['rank'] = 0;
622 foreach ($id2code as $other_id => $other_code) {
623 $deps[$id]['rank'] += ($id != $other_id) && preg_match('/' . $other_id . '/', $code) ? 1 : 0;
624 $deps[$id][$other_id] = ($id != $other_id) && preg_match('/' . $other_id . '/', $code) ? 1 : 0;
629 /* get next 0-rank */
631 foreach ($deps as $id => $infos) {
632 if ($infos['rank'] == 0) {
638 $r .= "\n" . $id2code[$next_id];
639 unset($deps[$next_id]);
640 foreach ($deps as $id => $infos) {
641 $deps[$id]['rank'] = 0;
642 unset($deps[$id][$next_id]);
643 foreach ($infos as $k => $v) {
644 if (!in_array($k, array('rank', $next_id))) {
645 $deps[$id]['rank'] += $v;
654 $this->addError('Not all patterns could be rewritten to SQL JOINs');
659 function getJoins() {
662 foreach ($this->index['join'] as $id) {
663 $sub_r = $this->getJoinConditionSQL($id);
664 $r[] = 'JOIN ' . $this->getTripleTable($id) . ' T_' . $id . ' ON (' . $sub_r . $nl . ')';
666 foreach (array_merge($this->index['from'], $this->index['join']) as $id) {
667 if ($sub_r = $this->getRequiredSubJoinSQL($id)) {
674 function getLeftJoins() {
677 foreach ($this->index['left_join'] as $id) {
678 $sub_r = $this->getJoinConditionSQL($id);
679 $r[] = 'LEFT JOIN ' . $this->getTripleTable($id) . ' T_' . $id . ' ON (' . $sub_r . $nl . ')';
681 foreach ($this->index['left_join'] as $id) {
682 if ($sub_r = $this->getRequiredSubJoinSQL($id, 'LEFT')) {
689 function getJoinConditionSQL($id) {
692 $infos = $this->getJoinInfos($id);
693 $pattern = $this->getPattern($id);
696 /* core dependency */
697 $d_tbls = $this->getDependentJoins($id);
698 foreach ($d_tbls as $d_tbl) {
699 if (preg_match('/^T_([0-9\_]+)\.[spo]+/', $d_tbl, $m) && ($m[1] != $id)) {
700 if ($this->isJoinedBefore($m[1], $id) && !in_array($m[1], array_merge($this->index['from'], $this->index['join']))) {
701 $r .= $r ? $nl . ' AND ' : $nl . ' ';
702 $r .= '(' . $d_tbl . ' IS NOT NULL)';
704 $this->logDependency($id, $d_tbl);
707 /* triple-based join info */
708 foreach ($infos as $info) {
709 if ($this->isJoinedBefore($info['join_tbl'], $id) && $this->joinDependsOn($id, $info['join_tbl'])) {
710 $r .= $r ? $nl . ' AND ' : $nl . ' ';
711 $r .= '(' . $tbl . '.' . $info['term'] . ' = T_' . $info['join_tbl'] . '.' . $info['join_term'] . ')';
715 if ($sub_r = $this->getPatternSQL($pattern, 'join__T_' . $id)) {
716 $r .= $r ? $nl . ' AND ' . $sub_r : $nl . ' ' . '(' . $sub_r . ')';
722 * A log of identified table join dependencies in getJoinConditionSQL
726 function logDependency($id, $tbl) {
727 if (!isset($this->dependency_log[$id])) $this->dependency_log[$id] = array();
728 if (!in_array($tbl, $this->dependency_log[$id])) {
729 $this->dependency_log[$id][] = $tbl;
734 * checks whether entries in the dependecy log could perhaps be optimized
735 * (triggers re-ordering of patterns
738 function problematicDependencies() {
739 foreach ($this->dependency_log as $id => $tbls) {
740 if (count($tbls) > 1) return count($tbls);
745 function isJoinedBefore($tbl_1, $tbl_2) {
746 $tbl_ids = $this->getOrderedJoinIDs();
747 foreach ($tbl_ids as $id) {
757 function joinDependsOn($id, $id2) {
758 if (in_array($id2, array_merge($this->index['from'], $this->index['join']))) {
761 $d_tbls = $this->getDependentJoins($id2);
762 //echo $id . ' :: ' . $id2 . '=>' . print_r($d_tbls, 1);
763 foreach ($d_tbls as $d_tbl) {
764 if (preg_match('/^T_' .$id. '\./', $d_tbl)) {
771 function getDependentJoins($id) {
774 foreach ($this->index['sub_joins'] as $alias) {
775 if (preg_match('/^(T|V|G)_' . $id . '/', $alias)) {
779 /* siblings in shared optional */
780 $o_id = $this->getOptionalPattern($id);
781 foreach ($this->index['sub_joins'] as $alias) {
782 if (preg_match('/^(T|V|G)_' . $o_id . '/', $alias) && !in_array($alias, $r)) {
786 foreach ($this->index['left_join'] as $alias) {
787 if (preg_match('/^' . $o_id . '/', $alias) && !in_array($alias, $r)) {
788 $r[] = 'T_' . $alias . '.s';
796 function getRequiredSubJoinSQL($id, $prefix = '') {/* id is a triple pattern id. Optional FILTERS and GRAPHs are getting added to the join directly */
799 foreach ($this->index['sub_joins'] as $alias) {
800 if (preg_match('/^V_' . $id . '_([a-z\_]+)\.val$/', $alias, $m)) {
803 if ($this->isOptionalPattern($id)) {
804 $pattern = $this->getPattern($id);
806 $pattern = $this->getPattern($pattern['parent_id']);
807 } while ($pattern['parent_id'] && ($pattern['type'] != 'optional'));
808 $sub_r = $this->getPatternSQL($pattern, 'sub_join__V_' . $id);
810 $sub_r = $sub_r ? $nl . ' AND (' . $sub_r . ')' : '';
811 /* lang dt only on literals */
812 if ($col == 'o_lang_dt') {
813 $sub_sub_r = 'T_' . $id . '.o_type = 2';
814 $sub_r .= $nl . ' AND (' . $sub_sub_r . ')';
816 //$cur_prefix = $prefix ? $prefix . ' ' : 'STRAIGHT_';
817 $cur_prefix = $prefix ? $prefix . ' ' : '';
819 $r .= trim($cur_prefix . 'JOIN '. $this->getValueTable($col) . ' V_' .$id . '_' . $col. ' ON (' .$nl. ' (G_' . $id . '.' . $col. ' = V_' . $id. '_' . $col. '.id) ' . $sub_r . $nl . ')');
822 $r .= trim($cur_prefix . 'JOIN '. $this->getValueTable($col) . ' V_' .$id . '_' . $col. ' ON (' .$nl. ' (T_' . $id . '.' . $col. ' = V_' . $id. '_' . $col. '.id) ' . $sub_r . $nl . ')');
825 elseif (preg_match('/^G_' . $id . '\.g$/', $alias, $m)) {
826 $pattern = $this->getPattern($id);
827 $sub_r = $this->getPatternSQL($pattern, 'graph_sub_join__G_' . $id);
828 $sub_r = $sub_r ? $nl . ' AND ' . $sub_r : '';
829 /* dataset restrictions */
830 $gi = $this->getGraphInfos($id);
832 $added_gts = array();
833 foreach ($gi as $set) {
834 if (isset($set['graph']) && !in_array($set['graph'], $added_gts)) {
835 $sub_sub_r .= $sub_sub_r !== '' ? ',' : '';
836 $sub_sub_r .= $this->getTermID($set['graph'], 'g');
837 $added_gts[] = $set['graph'];
840 $sub_r .= ($sub_sub_r !== '') ? $nl . ' AND (G_' . $id . '.g IN (' . $sub_sub_r . '))' : ''; // /* ' . str_replace('#' , '::', $set['graph']) . ' */';
841 /* other graph join conditions */
842 foreach ($this->index['graph_vars'] as $var => $occurs) {
843 $occur_tbls = array();
844 foreach ($occurs as $occur) {
845 $occur_tbls[] = $occur['table'];
846 if ($occur['table'] == $id) break;
848 foreach($occur_tbls as $tbl) {
849 if (($tbl != $id) && in_array($id, $occur_tbls) && $this->isJoinedBefore($tbl, $id)) {
850 $sub_r .= $nl . ' AND (G_' .$id. '.g = G_' .$tbl. '.g)';
854 //$cur_prefix = $prefix ? $prefix . ' ' : 'STRAIGHT_';
855 $cur_prefix = $prefix ? $prefix . ' ' : '';
856 $r .= trim($cur_prefix . 'JOIN '. $this->getGraphTable() . ' G_' .$id . ' ON (' .$nl. ' (T_' . $id . '.t = G_' .$id. '.t)' . $sub_r . $nl . ')');
864 function getWHERESQL() {
867 /* standard constraints */
868 $sub_r = $this->getPatternSQL($this->getPattern('0'), 'where');
869 /* additional constraints */
870 foreach ($this->index['from'] as $id) {
871 if ($sub_sub_r = $this->getConstraintSQL($id)) {
872 $sub_r .= $sub_r ? $nl . ' AND ' . $sub_sub_r : $sub_sub_r;
875 $r .= $sub_r ? $sub_r : '';
876 /* left join dependencies */
877 foreach ($this->index['left_join'] as $id) {
878 $d_joins = $this->getDependentJoins($id);
880 $d_aliases = array();
881 //echo $id . ' =>' . print_r($d_joins, 1);
882 $id_alias = 'T_' . $id . '.s';
883 foreach ($d_joins as $alias) {
884 if (preg_match('/^(T|V|G)_([0-9\_]+)(_[spo])?\.([a-z\_]+)/', $alias, $m)) {
886 $tbl_pattern_id = $m[2];
888 if (($tbl_pattern_id >= $id) && $this->sameOptional($tbl_pattern_id, $id)) {/* get rid of dependency permutations and nested optionals */
889 if (!in_array($tbl_type . '_' . $tbl_pattern_id . $suffix, $added)) {
890 $sub_r .= $sub_r ? ' AND ' : '';
891 $sub_r .= $alias . ' IS NULL';
892 $d_aliases[] = $alias;
893 $added[] = $tbl_type . '_' . $tbl_pattern_id . $suffix;
894 $id_alias = ($tbl_pattern_id == $id) ? $alias : $id_alias;
899 if (count($d_aliases) > 2) {/* @@todo fix this! */
900 $sub_r1 = ' /* '.$id_alias.' dependencies */';
901 $sub_r2 = '((' . $id_alias . ' IS NULL) OR (CONCAT(' . join(', ', $d_aliases) . ') IS NOT NULL))';
902 $r .= $r ? $nl . $sub_r1 . $nl . ' AND ' .$sub_r2 : $sub_r1 . $nl . $sub_r2;
905 return $r ? $nl . 'WHERE ' . $r : '';
910 function addConstraintSQLEntry($id, $sql) {
911 if (!isset($this->index['constraints'][$id])) {
912 $this->index['constraints'][$id] = array();
914 if (!in_array($sql, $this->index['constraints'][$id])) {
915 $this->index['constraints'][$id][] = $sql;
919 function getConstraintSQL($id) {
922 $constraints = $this->v($id, array(), $this->index['constraints']);
923 foreach ($constraints as $constraint) {
924 $r .= $r ? $nl . ' AND ' . $constraint : $constraint;
931 function getPatternSQL($pattern, $context) {
932 $type = $pattern['type'];
933 $m = 'get' . ucfirst($type) . 'PatternSQL';
934 return method_exists($this, $m) ? $this->$m($pattern, $context) : $this->getDefaultPatternSQL($pattern, $context);
937 function getDefaultPatternSQL($pattern, $context) {
940 $sub_ids = $this->v('patterns', array(), $pattern);
941 foreach ($sub_ids as $sub_id) {
942 $sub_r = $this->getPatternSQL($this->getPattern($sub_id), $context);
943 $r .= ($r && $sub_r) ? $nl . ' AND (' . $sub_r . ')' : ($sub_r ? $sub_r : '');
948 function getTriplePatternSQL($pattern, $context) {
951 $id = $pattern['id'];
954 foreach (array('s', 'p', 'o') as $term) {
956 $type = $pattern[$term . '_type'];
957 if ($type == 'uri') {
958 $term_id = $this->getTermID($pattern[$term], $term);
959 $sub_r = '(T_' . $id . '.' . $term . ' = ' . $term_id . ') /* ' . str_replace('#' , '::', $pattern[$term]) . ' */';
961 elseif ($type == 'literal') {
962 $term_id = $this->getTermID($pattern[$term], $term);
963 $sub_r = '(T_' . $id . '.' . $term . ' = ' . $term_id . ') /* ' . preg_replace('/[\#\n]/' , ' ', $pattern[$term]) . ' */';
964 if (($lang_dt = $this->v1($term . '_lang', '', $pattern)) || ($lang_dt = $this->v1($term . '_datatype', '', $pattern))) {
965 $lang_dt_id = $this->getTermID($lang_dt);
966 $sub_r .= $nl . ' AND (T_' . $id . '.' .$term. '_lang_dt = ' . $lang_dt_id . ') /* ' . str_replace('#' , '::', $lang_dt) . ' */';
969 elseif ($type == 'var') {
970 $val = $pattern[$term];
971 if (isset($vars[$val])) {/* repeated var in pattern */
972 $sub_r = '(T_' . $id . '.' . $term . '=' . 'T_' . $id . '.' . $vars[$val] . ')';
975 if ($infos = $this->v($val, 0, $this->index['graph_vars'])) {/* graph var in triple pattern */
976 $sub_r .= $sub_r ? $nl . ' AND ' : '';
977 $tbl = $infos[0]['table'];
978 $sub_r .= 'G_' . $tbl . '.g = T_' . $id . '.' . $term;
982 if (preg_match('/^(join)/', $context) || (preg_match('/^where/', $context) && in_array($id, $this->index['from']))) {
983 $r .= $r ? $nl . ' AND ' . $sub_r : $sub_r;
988 if ($infos = $pattern['graph_infos']) {
989 $tbl_alias = 'G_' . $id . '.g';
990 if (!in_array($tbl_alias, $this->index['sub_joins'])) {
991 $this->index['sub_joins'][] = $tbl_alias;
993 $sub_r = array('graph_var' => '', 'graph_uri' => '', 'from' => '', 'from_named' => '');
994 foreach ($infos as $info) {
995 $type = $info['type'];
996 if ($type == 'graph') {
998 $term_id = $this->getTermID($info['uri'], 'g');
999 $sub_r['graph_uri'] .= $sub_r['graph_uri'] ? $nl . ' AND ' : '';
1000 $sub_r['graph_uri'] .= '(' .$tbl_alias. ' = ' . $term_id . ') /* ' . str_replace('#' , '::', $info['uri']) . ' */';
1004 if ($sub_r['from'] && $sub_r['from_named']) {
1005 $sub_r['from_named'] = '';
1007 if (!$sub_r['from'] && !$sub_r['from_named']) {
1008 $sub_r['graph_var'] = '';
1010 if (preg_match('/^(graph_sub_join)/', $context)) {
1011 foreach ($sub_r as $g_type => $g_sql) {
1013 $r .= $r ? $nl . ' AND ' . $g_sql : $g_sql;
1018 /* optional sibling filters? */
1019 if (preg_match('/^(join|sub_join)/', $context) && $this->isOptionalPattern($id)) {
1020 $o_pattern = $pattern;
1022 $o_pattern = $this->getPattern($o_pattern['parent_id']);
1023 } while ($o_pattern['parent_id'] && ($o_pattern['type'] != 'optional'));
1024 if ($sub_r = $this->getPatternSQL($o_pattern, 'optional_filter' . preg_replace('/^(.*)(__.*)$/', '\\2', $context))) {
1025 $r .= $r ? $nl . ' AND ' . $sub_r : $sub_r;
1027 /* created constraints */
1028 if ($sub_r = $this->getConstraintSQL($id)) {
1029 $r .= $r ? $nl . ' AND ' . $sub_r : $sub_r;
1033 if (preg_match('/^(where)/', $context) && $this->isOptionalPattern($id)) {
1041 function getFilterPatternSQL($pattern, $context) {
1043 $id = $pattern['id'];
1044 $constraint_id = $this->v1('constraint', '', $pattern);
1045 $constraint = $this->getPattern($constraint_id);
1046 $constraint_type = $constraint['type'];
1047 if ($constraint_type == 'built_in_call') {
1048 $r = $this->getBuiltInCallSQL($constraint, $context);
1050 elseif ($constraint_type == 'expression') {
1051 $r = $this->getExpressionSQL($constraint, $context, '', 'filter');
1054 $m = 'get' . ucfirst($constraint_type) . 'ExpressionSQL';
1055 if (method_exists($this, $m)) {
1056 $r = $this->$m($constraint, $context, '', 'filter');
1059 if ($this->isOptionalPattern($id) && !preg_match('/^(join|optional_filter)/', $context)) {
1062 /* unconnected vars in FILTERs eval to false */
1063 if ($sub_r = $this->hasUnconnectedFilterVars($id)) {
1064 if ($sub_r == 'alias') {
1065 if (!in_array($r, $this->index['havings'])) $this->index['havings'][] = $r;
1068 elseif (preg_match('/^T([^\s]+\.)g (.*)$/s', $r, $m)) {/* graph filter */
1069 return 'G' . $m[1] . 't ' . $m[2];
1071 elseif (preg_match('/^\(*V[^\s]+_g\.val .*$/s', $r, $m)) {/* graph value filter, @@improveMe */
1078 /* some really ugly tweaks */
1079 /* empty language filter: FILTER ( lang(?v) = '' ) */
1080 $r = preg_replace('/\(\/\* language call \*\/ ([^\s]+) = ""\)/s', '((\\1 = "") OR (\\1 LIKE "%:%"))', $r);
1086 function hasUnconnectedFilterVars($filter_id) {
1087 $pattern = $this->getInitialPattern($filter_id);
1088 $gp = $this->getInitialPattern($pattern['parent_id']);
1090 foreach ($this->initial_index['patterns'] as $id => $p) {
1091 /* vars in given filter */
1092 if (preg_match('/^' .$filter_id. '.+/', $id)) {
1093 if ($p['type'] == 'var') {
1094 $vars[$p['value']][] = 'filter';
1096 if (($p['type'] == 'built_in_call') && ($p['call'] == 'bound')) {
1097 $vars[$p['args'][0]['value']][] = 'filter';
1100 /* triple patterns if their scope is in the parent path of the filter */
1101 if ($p['type'] == 'triple') {
1105 $tp = $this->getInitialPattern($tp['parent_id']);
1106 if ($tp['type'] == 'group') {
1108 if (isset($tp['parent_id']) && ($p_tp = $this->getInitialPattern($tp['parent_id'])) && ($p_tp['type'] == 'union')) {
1114 $fp_id = $filter_id;
1117 $fp = $this->getInitialPattern($fp_id);
1118 $fp_id = $fp['parent_id'];
1119 if (($fp['type'] != 'group') && ($fp_id === $tp_id)) {
1123 } while (($fp['parent_id'] != $fp['id']) && ($fp['type'] != 'group'));
1125 foreach (array('s', 'p', 'o') as $term) {
1126 if ($p[$term . '_type'] == 'var') {
1127 $vars[$p[$term]][] = 'triple';
1133 foreach ($vars as $var => $types) {
1134 if (!in_array('triple', $types)) {
1135 /* might be an alias */
1137 foreach ($this->infos['query']['result_vars'] as $r_var) {
1138 if ($r_var['alias'] == $var) {
1142 //if ($r_var['alias'] == $var) $r = 0;
1145 //if (in_array('filter', $types)) $r = 0;
1154 function getExpressionSQL($pattern, $context, $val_type = '', $parent_type = '') {
1157 $type = $this->v1('type', '', $pattern);
1158 $sub_type = $this->v1('sub_type', $type, $pattern);
1159 if (preg_match('/^(and|or)$/', $sub_type)) {
1160 foreach ($pattern['patterns'] as $sub_id) {
1161 $sub_pattern = $this->getPattern($sub_id);
1162 $sub_pattern_type = $sub_pattern['type'];
1163 if ($sub_pattern_type == 'built_in_call') {
1164 $sub_r = $this->getBuiltInCallSQL($sub_pattern, $context, '', $parent_type);
1167 $sub_r = $this->getExpressionSQL($sub_pattern, $context, '', $parent_type);
1170 $r .= $r ? ' ' . strtoupper($sub_type). ' (' .$sub_r. ')' : '(' . $sub_r . ')';
1174 elseif ($sub_type == 'built_in_call') {
1175 $r = $this->getBuiltInCallSQL($pattern, $context, $val_type, $parent_type);
1177 elseif (preg_match('/literal/', $sub_type)) {
1178 $r = $this->getLiteralExpressionSQL($pattern, $context, $val_type, $parent_type);
1180 elseif ($sub_type) {
1181 $m = 'get' . ucfirst($sub_type) . 'ExpressionSQL';
1182 if (method_exists($this, $m)) {
1183 $r = $this->$m($pattern, $context, '', $parent_type);
1186 /* skip expressions that reference non-yet-joined tables */
1187 if (preg_match('/__(T|V|G)_(.+)$/', $context, $m)) {
1188 $context_pattern_id = $m[2];
1189 $context_table_type = $m[1];
1190 if (preg_match_all('/((T|V|G)(\_[0-9])+)/', $r, $m)) {
1193 foreach ($aliases as $alias) {
1194 if (preg_match('/(T|V|G)_(.*)$/', $alias, $m)) {
1197 if (!$this->isJoinedBefore($tbl, $context_pattern_id)) {
1200 elseif (($context_pattern_id == $tbl) && preg_match('/(TV)/', $context_table_type . $tbl_type)) {
1205 $r = $keep ? $r : '';
1208 return $r ? '(' . $r . ')' : $r;
1211 function detectExpressionValueType($pattern_ids) {
1212 foreach ($pattern_ids as $id) {
1213 $pattern = $this->getPattern($id);
1214 $type = $this->v('type', '', $pattern);
1215 if (($type == 'literal') && isset($pattern['datatype'])) {
1216 if (in_array($pattern['datatype'], array($this->xsd . 'integer', $this->xsd . 'float', $this->xsd . 'double'))) {
1226 function getRelationalExpressionSQL($pattern, $context, $val_type = '', $parent_type = '') {
1228 $val_type = $this->detectExpressionValueType($pattern['patterns']);
1229 $op = $pattern['operator'];
1230 foreach ($pattern['patterns'] as $sub_id) {
1231 $sub_pattern = $this->getPattern($sub_id);
1232 $sub_pattern['parent_op'] = $op;
1233 $sub_type = $sub_pattern['type'];
1234 $m = ($sub_type == 'built_in_call') ? 'getBuiltInCallSQL' : 'get' . ucfirst($sub_type) . 'ExpressionSQL';
1235 $m = str_replace('ExpressionExpression', 'Expression', $m);
1236 $sub_r = method_exists($this, $m) ? $this->$m($sub_pattern, $context, $val_type, 'relational') : '';
1237 $r .= $r ? ' ' . $op . ' ' . $sub_r : $sub_r;
1239 return $r ? '(' . $r . ')' : $r;
1242 function getAdditiveExpressionSQL($pattern, $context, $val_type = '', $parent_type = '') {
1244 $val_type = $this->detectExpressionValueType($pattern['patterns']);
1245 foreach ($pattern['patterns'] as $sub_id) {
1246 $sub_pattern = $this->getPattern($sub_id);
1247 $sub_type = $this->v('type', '', $sub_pattern);
1248 $m = ($sub_type == 'built_in_call') ? 'getBuiltInCallSQL' : 'get' . ucfirst($sub_type) . 'ExpressionSQL';
1249 $m = str_replace('ExpressionExpression', 'Expression', $m);
1250 $sub_r = method_exists($this, $m) ? $this->$m($sub_pattern, $context, $val_type, 'additive') : '';
1251 $r .= $r ? ' ' . $sub_r : $sub_r;
1256 function getMultiplicativeExpressionSQL($pattern, $context, $val_type = '', $parent_type = '') {
1258 $val_type = $this->detectExpressionValueType($pattern['patterns']);
1259 foreach ($pattern['patterns'] as $sub_id) {
1260 $sub_pattern = $this->getPattern($sub_id);
1261 $sub_type = $sub_pattern['type'];
1262 $m = ($sub_type == 'built_in_call') ? 'getBuiltInCallSQL' : 'get' . ucfirst($sub_type) . 'ExpressionSQL';
1263 $m = str_replace('ExpressionExpression', 'Expression', $m);
1264 $sub_r = method_exists($this, $m) ? $this->$m($sub_pattern, $context, $val_type, 'multiplicative') : '';
1265 $r .= $r ? ' ' . $sub_r : $sub_r;
1272 function getVarExpressionSQL($pattern, $context, $val_type = '', $parent_type = '') {
1273 $var = $pattern['value'];
1274 $info = $this->getVarTableInfos($var);
1275 if (!$tbl = $info['table']) {
1276 /* might be an aggregate var */
1277 $vars = $this->infos['query']['result_vars'];
1278 foreach ($vars as $test_var) {
1279 if ($test_var['alias'] == $pattern['value']) {
1280 return '`' . $pattern['value'] . '`';
1285 $col = $info['col'];
1286 if (($context == 'order') && ($col == 'o')) {
1287 $tbl_alias = 'T_' . $tbl . '.o_comp';
1289 elseif ($context == 'sameterm') {
1290 $tbl_alias = 'T_' . $tbl . '.' . $col;
1292 elseif (($parent_type == 'relational') && ($col == 'o') && (preg_match('/[\<\>]/', $this->v('parent_op', '', $pattern)))) {
1293 $tbl_alias = 'T_' . $tbl . '.o_comp';
1296 $tbl_alias = 'V_' . $tbl . '_' . $col . '.val';
1297 if (!in_array($tbl_alias, $this->index['sub_joins'])) {
1298 $this->index['sub_joins'][] = $tbl_alias;
1301 $op = $this->v('operator', '', $pattern);
1302 if (preg_match('/^(filter|and)/', $parent_type)) {
1304 $r = '(((' . $tbl_alias . ' = 0) AND (CONCAT("1", ' . $tbl_alias . ') != 1))'; /* 0 and no string */
1305 $r .= ' OR (' . $tbl_alias . ' IN ("", "false")))'; /* or "", or "false" */
1308 $r = '((' . $tbl_alias . ' != 0)'; /* not null */
1309 $r .= ' OR ((CONCAT("1", ' . $tbl_alias . ') = 1) AND (' . $tbl_alias . ' NOT IN ("", "false"))))'; /* string, and not "" or "false" */
1313 $r = trim($op . ' ' . $tbl_alias);
1314 if ($val_type == 'numeric') {
1315 if (preg_match('/__(T|V|G)_(.+)$/', $context, $m)) {
1316 $context_pattern_id = $m[2];
1317 $context_table_type = $m[1];
1320 $context_pattern_id = $pattern['id'];
1321 $context_table_type = 'T';
1323 if ($this->isJoinedBefore($tbl, $context_pattern_id)) {
1324 $add = ($tbl != $context_pattern_id) ? 1 : 0;
1325 $add = (!$add && ($context_table_type == 'V')) ? 1 : 0;
1327 $this->addConstraintSQLEntry($context_pattern_id, '(' .$r. ' = "0" OR ' . $r . '*1.0 != 0)');
1337 function getUriExpressionSQL($pattern, $context, $val_type = '') {
1338 $val = $pattern['uri'];
1339 $r = $pattern['operator'];
1340 $r .= is_numeric($val) ? ' ' . $val : ' "' . mysql_real_escape_string($val, $this->store->getDBCon()) . '"';
1346 function getLiteralExpressionSQL($pattern, $context, $val_type = '', $parent_type = '') {
1347 $val = $pattern['value'];
1348 $r = $pattern['operator'];
1349 if (is_numeric($val) && $this->v('datatype', 0, $pattern)) {
1352 elseif (preg_match('/^(true|false)$/i', $val) && ($this->v1('datatype', '', $pattern) == 'http://www.w3.org/2001/XMLSchema#boolean')) {
1353 $r .= ' ' . strtoupper($val);
1355 elseif ($parent_type == 'regex') {
1356 $sub_r = mysql_real_escape_string($val, $this->store->getDBCon());
1357 $r .= ' "' . preg_replace('/\x5c\x5c/', '\\', $sub_r) . '"';
1360 $r .= ' "' . mysql_real_escape_string($val, $this->store->getDBCon()) . '"';
1362 if (($lang_dt = $this->v1('lang', '', $pattern)) || ($lang_dt = $this->v1('datatype', '', $pattern))) {
1363 /* try table/alias via var in siblings */
1364 if ($var = $this->findSiblingVarExpression($pattern['id'])) {
1365 if (isset($this->index['vars'][$var])) {
1366 $infos = $this->index['vars'][$var];
1367 foreach ($infos as $info) {
1368 if ($info['col'] == 'o') {
1369 $tbl = $info['table'];
1370 $term_id = $this->getTermID($lang_dt);
1371 if ($pattern['operator'] != '!=') {
1372 if (preg_match('/__(T|V|G)_(.+)$/', $context, $m)) {
1373 $context_pattern_id = $m[2];
1374 $context_table_type = $m[1];
1376 elseif ($context == 'where') {
1377 $context_pattern_id = $tbl;
1380 $context_pattern_id = $pattern['id'];
1382 if ($tbl == $context_pattern_id) {/* @todo better dependency check */
1383 if ($term_id || ($lang_dt != 'http://www.w3.org/2001/XMLSchema#integer')) {/* skip if simple int, but no id */
1384 $this->addConstraintSQLEntry($context_pattern_id, 'T_' . $tbl . '.o_lang_dt = ' . $term_id . ' /* ' . str_replace('#' , '::', $lang_dt) . ' */');
1397 function findSiblingVarExpression($id) {
1398 $pattern = $this->getPattern($id);
1400 $pattern = $this->getPattern($pattern['parent_id']);
1401 } while ($pattern['parent_id'] && ($pattern['type'] != 'expression'));
1402 $sub_patterns = $this->v('patterns', array(), $pattern);
1403 foreach ($sub_patterns as $sub_id) {
1404 $sub_pattern = $this->getPattern($sub_id);
1405 if ($sub_pattern['type'] == 'var') {
1406 return $sub_pattern['value'];
1414 function getFunctionExpressionSQL($pattern, $context, $val_type = '', $parent_type = '') {
1415 $fnc_uri = $pattern['uri'];
1416 $op = $this->v('operator', '', $pattern);
1417 if ($op) $op .= ' ';
1418 if ($this->allow_extension_functions) {
1419 /* mysql functions */
1420 if (preg_match('/^http\:\/\/web\-semantics\.org\/ns\/mysql\/(.*)$/', $fnc_uri, $m)) {
1421 $fnc_name = strtoupper($m[1]);
1423 foreach ($pattern['args'] as $arg) {
1424 $sub_r .= $sub_r ? ', ' : '';
1425 $sub_r .= $this->getExpressionSQL($arg, $context, $val_type, $parent_type);
1427 return $op . $fnc_name . '(' . $sub_r . ')';
1429 /* any other: ignore */
1431 /* simple type conversions */
1432 if (strpos($fnc_uri, 'http://www.w3.org/2001/XMLSchema#') === 0) {
1433 return $op . $this->getExpressionSQL($pattern['args'][0], $context, $val_type, $parent_type);
1440 function getBuiltInCallSQL($pattern, $context) {
1441 $call = $pattern['call'];
1442 $m = 'get' . ucfirst($call) . 'CallSQL';
1443 if (method_exists($this, $m)) {
1444 return $this->$m($pattern, $context);
1447 $this->addError('Unknown built-in call "' . $call . '"');
1452 function getBoundCallSQL($pattern, $context) {
1454 $var = $pattern['args'][0]['value'];
1455 $info = $this->getVarTableInfos($var);
1456 if (!$tbl = $info['table']) {
1459 $col = $info['col'];
1460 $tbl_alias = 'T_' . $tbl . '.' . $col;
1461 if ($pattern['operator'] == '!') {
1462 return $tbl_alias . ' IS NULL';
1464 return $tbl_alias . ' IS NOT NULL';
1467 function getHasTypeCallSQL($pattern, $context, $type) {
1469 $var = $pattern['args'][0]['value'];
1470 $info = $this->getVarTableInfos($var);
1471 if (!$tbl = $info['table']) {
1474 $col = $info['col'];
1475 $tbl_alias = 'T_' . $tbl . '.' . $col . '_type';
1476 return $tbl_alias . ' ' .$this->v('operator', '', $pattern) . '= ' . $type;
1479 function getIsliteralCallSQL($pattern, $context) {
1480 return $this->getHasTypeCallSQL($pattern, $context, 2);
1483 function getIsblankCallSQL($pattern, $context) {
1484 return $this->getHasTypeCallSQL($pattern, $context, 1);
1487 function getIsiriCallSQL($pattern, $context) {
1488 return $this->getHasTypeCallSQL($pattern, $context, 0);
1491 function getIsuriCallSQL($pattern, $context) {
1492 return $this->getHasTypeCallSQL($pattern, $context, 0);
1495 function getStrCallSQL($pattern, $context) {
1496 $sub_pattern = $pattern['args'][0];
1497 $sub_type = $sub_pattern['type'];
1498 $m = 'get' . ucfirst($sub_type) . 'ExpressionSQL';
1499 if (method_exists($this, $m)) {
1500 return $this->$m($sub_pattern, $context);
1504 function getFunctionCallSQL($pattern, $context) {
1505 $f_uri = $pattern['uri'];
1506 if (preg_match('/(integer|double|float|string)$/', $f_uri)) {/* skip conversions */
1507 $sub_pattern = $pattern['args'][0];
1508 $sub_type = $sub_pattern['type'];
1509 $m = 'get' . ucfirst($sub_type) . 'ExpressionSQL';
1510 if (method_exists($this, $m)) {
1511 return $this->$m($sub_pattern, $context);
1516 function getLangDatatypeCallSQL($pattern, $context) {
1518 if (isset($pattern['patterns'])) { /* proceed with first argument only (assumed as base type for type promotion) */
1519 $sub_pattern = array('args' => array($pattern['patterns'][0]));
1520 return $this->getLangDatatypeCallSQL($sub_pattern, $context);
1522 if (!isset($pattern['args'])) {
1525 $sub_type = $pattern['args'][0]['type'];
1526 if ($sub_type != 'var') {
1527 return $this->getLangDatatypeCallSQL($pattern['args'][0], $context);
1529 $var = $pattern['args'][0]['value'];
1530 $info = $this->getVarTableInfos($var);
1531 if (!$tbl = $info['table']) {
1535 $tbl_alias = 'V_' . $tbl . '_' . $col . '.val';
1536 if (!in_array($tbl_alias, $this->index['sub_joins'])) {
1537 $this->index['sub_joins'][] = $tbl_alias;
1539 $op = $this->v('operator', '', $pattern);
1540 $r = trim($op . ' ' . $tbl_alias);
1544 function getDatatypeCallSQL($pattern, $context) {
1545 return '/* datatype call */ ' . $this->getLangDatatypeCallSQL($pattern, $context);
1548 function getLangCallSQL($pattern, $context) {
1549 return '/* language call */ ' . $this->getLangDatatypeCallSQL($pattern, $context);
1552 function getLangmatchesCallSQL($pattern, $context) {
1553 if (count($pattern['args']) == 2) {
1554 $arg_1 = $pattern['args'][0];
1555 $arg_2 = $pattern['args'][1];
1556 $sub_r_1 = $this->getBuiltInCallSQL($arg_1, $context);/* adds value join */
1557 $sub_r_2 = $this->getExpressionSQL($arg_2, $context);
1558 $op = $this->v('operator', '', $pattern);
1559 if (preg_match('/^([\"\'])([^\'\"]+)/', $sub_r_2, $m)) {
1561 $r = ($op == '!') ? 'NOT (' . $sub_r_1 . ' REGEXP "^[a-zA-Z\-]+$"' . ')' : $sub_r_1 . ' REGEXP "^[a-zA-Z\-]+$"';
1564 $r = ($op == '!') ? $sub_r_1 . ' NOT LIKE ' . $m[1] . $m[2] . '%' . $m[1] : $sub_r_1 . ' LIKE ' . $m[1] . $m[2] . '%' . $m[1];
1568 $r = ($op == '!') ? $sub_r_1 . ' NOT LIKE CONCAT(' . $sub_r_2 . ', "%")' : $sub_r_1 . ' LIKE CONCAT(' . $sub_r_2 . ', "%")';
1575 function getSametermCallSQL($pattern, $context) {
1576 if (count($pattern['args']) == 2) {
1577 $arg_1 = $pattern['args'][0];
1578 $arg_2 = $pattern['args'][1];
1579 $sub_r_1 = $this->getExpressionSQL($arg_1, 'sameterm');
1580 $sub_r_2 = $this->getExpressionSQL($arg_2, 'sameterm');
1581 $op = $this->v('operator', '', $pattern);
1582 $r = $sub_r_1 . ' ' . $op . '= ' . $sub_r_2;
1588 function getRegexCallSQL($pattern, $context) {
1589 $ac = count($pattern['args']);
1591 foreach ($pattern['args'] as $i => $arg) {
1592 $var = 'sub_r_' . ($i + 1);
1593 $$var = $this->getExpressionSQL($arg, $context, '', 'regex');
1595 $sub_r_3 = (isset($sub_r_3) && preg_match('/[\"\'](.+)[\"\']/', $sub_r_3, $m)) ? strtolower($m[1]) : '';
1596 $op = ($this->v('operator', '', $pattern) == '!') ? ' NOT' : '';
1597 if (!$sub_r_1 || !$sub_r_2) return '';
1598 $is_simple_search = preg_match('/^[\(\"]+(\^)?([a-z0-9\_\-\s]+)(\$)?[\)\"]+$/is', $sub_r_2, $m);
1599 $is_simple_search = preg_match('/^[\(\"]+(\^)?([^\\\*\[\]\}\{\(\)\"\'\?\+\.]+)(\$)?[\)\"]+$/is', $sub_r_2, $m);
1600 $is_o_search = preg_match('/o\.val\)*$/', $sub_r_1);
1601 /* fulltext search */
1602 if ($is_simple_search && $is_o_search && !$op && (strlen($m[2]) > 4) && $this->store->hasFulltextIndex()) {
1603 return 'MATCH(' . trim($sub_r_1, '()') . ') AGAINST("' . $m[2] . '")';
1606 if ($is_simple_search && ($sub_r_3 == 'i')) {
1607 $sub_r_2 = $m[1] ? $m[2] : '%' . $m[2];
1608 $sub_r_2 .= isset($m[3]) && $m[3] ? '' : '%';
1609 return $sub_r_1 . $op . ' LIKE "' . $sub_r_2 . '"';
1612 $opt = ($sub_r_3 == 'i') ? '' : 'BINARY ';
1613 return $sub_r_1 . $op . ' REGEXP ' . $opt . $sub_r_2;
1620 function getGROUPSQL() {
1623 $infos = $this->v('group_infos', array(), $this->infos['query']);
1624 foreach ($infos as $info) {
1625 $var = $info['value'];
1626 if ($tbl_infos = $this->getVarTableInfos($var, 0)) {
1627 $tbl_alias = $tbl_infos['table_alias'];
1628 $r .= $r ? ', ' : 'GROUP BY ';
1633 foreach ($this->index['havings'] as $having) {
1634 $hr .= $hr ? ' AND' : ' HAVING';
1635 $hr .= '(' . $having . ')';
1638 return $r ? $nl . $r : $r;
1643 function getORDERSQL() {
1646 $infos = $this->v('order_infos', array(), $this->infos['query']);
1647 foreach ($infos as $info) {
1648 $type = $info['type'];
1649 $ms = array('expression' => 'getExpressionSQL', 'built_in_call' => 'getBuiltInCallSQL', 'function_call' => 'getFunctionCallSQL');
1650 $m = isset($ms[$type]) ? $ms[$type] : 'get' . ucfirst($type) . 'ExpressionSQL';
1651 if (method_exists($this, $m)) {
1652 $sub_r = '(' . $this->$m($info, 'order') . ')';
1653 $sub_r .= $this->v('direction', '', $info) == 'desc' ? ' DESC' : '';
1654 $r .= $r ? ',' .$nl . $sub_r : $sub_r;
1657 return $r ? $nl . 'ORDER BY ' . $r : '';
1662 function getLIMITSQL() {
1665 $limit = $this->v('limit', -1, $this->infos['query']);
1666 $offset = $this->v('offset', -1, $this->infos['query']);
1668 $offset = ($offset == -1) ? 0 : mysql_real_escape_string($offset, $this->store->getDBCon());
1669 $r = 'LIMIT ' . $offset . ',' . $limit;
1671 elseif ($offset != -1) {
1672 $r = 'LIMIT ' . mysql_real_escape_string($offset, $this->store->getDBCon()) . ',999999999999'; /* mysql doesn't support stand-alone offsets .. */
1674 return $r ? $nl . $r : '';
1679 function getValueSQL($q_tbl, $q_sql) {
1682 $vars = $this->infos['query']['result_vars'];
1684 $v_tbls = array('JOIN' => array(), 'LEFT JOIN' => array());
1686 foreach ($vars as $var) {
1687 $var_name = $var['var'];
1688 $r .= $r ? ',' . $nl . ' ' : ' ';
1691 if ($var_name != '*') {
1692 if (in_array($var_name, $this->infos['null_vars'])) {
1693 if (isset($this->initial_index['vars'][$var_name])) {
1694 $col = $this->initial_index['vars'][$var_name][0]['col'];
1695 $tbl = $this->initial_index['vars'][$var_name][0]['table'];
1697 if (isset($this->initial_index['graph_vars'][$var_name])) {
1699 $tbl = $this->initial_index['graph_vars'][$var_name][0]['table'];
1702 elseif (isset($this->index['vars'][$var_name])) {
1703 $col = $this->index['vars'][$var_name][0]['col'];
1704 $tbl = $this->index['vars'][$var_name][0]['table'];
1707 if ($var['aggregate']) {
1708 $r .= 'TMP.`' . $var['alias'] . '`';
1711 $join_type = in_array($tbl, array_merge($this->index['from'], $this->index['join'])) ? 'JOIN' : 'LEFT JOIN';/* val may be NULL */
1712 $v_tbls[$join_type][] = array('t_col' => $col, 'q_col' => $var_name, 'vc' => $vc);
1713 $r .= 'V' . $vc . '.val AS `' . $var_name . '`';
1714 if (in_array($col, array('s', 'o'))) {
1715 if (strpos($q_sql, '`' . $var_name . ' type`')) {
1716 $r .= ', ' . $nl . ' TMP.`' . $var_name . ' type` AS `' . $var_name . ' type`';
1717 //$r .= ', ' . $nl . ' CASE TMP.`' . $var_name . ' type` WHEN 2 THEN "literal" WHEN 1 THEN "bnode" ELSE "uri" END AS `' . $var_name . ' type`';
1720 $r .= ', ' . $nl . ' NULL AS `' . $var_name . ' type`';
1725 $v_tbls[$join_type][] = array('t_col' => 'id', 'q_col' => $var_name . ' lang_dt', 'vc' => $vc);
1726 if (strpos($q_sql, '`' . $var_name . ' lang_dt`')) {
1727 $r .= ', ' .$nl. ' V' . $vc . '.val AS `' . $var_name . ' lang_dt`';
1731 $r .= ', ' .$nl. ' NULL AS `' . $var_name . ' lang_dt`';
1738 $r .= $nl . 'FROM (' . $q_tbl . ' TMP)';
1739 foreach (array('JOIN', 'LEFT JOIN') as $join_type) {
1740 foreach ($v_tbls[$join_type] as $v_tbl) {
1741 $tbl = $this->getValueTable($v_tbl['t_col']);
1742 $var_name = preg_replace('/^([^\s]+)(.*)$/', '\\1', $v_tbl['q_col']);
1743 $cur_join_type = in_array($var_name, $this->infos['null_vars']) ? 'LEFT JOIN' : $join_type;
1744 if (!strpos($q_sql, '`' . $v_tbl['q_col'].'`')) continue;
1745 $r .= $nl . ' ' . $cur_join_type . ' ' . $tbl . ' V' . $v_tbl['vc'] . ' ON (
1746 (V' . $v_tbl['vc'] . '.id = TMP.`' . $v_tbl['q_col'].'`)
1750 /* create pos columns, id needed */
1751 if ($this->v('order_infos', array(), $this->infos['query'])) {
1752 $r .= $nl . ' ORDER BY _pos_';
1754 return 'SELECT' . $nl . $r;