source: pmb4.2/trunk/fuentes/pmb/classes/rdf/arc2/store/ARC2_StoreSelectQueryHandler.php @ 815

Last change on this file since 815 was 815, checked in by jrpelegrina, 4 years ago

Initial release of pmb 4.2

  • Property svn:executable set to *
File size: 61.6 KB
Line 
1<?php
2/**
3 * ARC2 RDF Store SELECT Query Handler
4 *
5 * @author    Benjamin Nowack
6 * @license   http://arc.semsol.org/license
7 * @homepage  <http://arc.semsol.org/>
8 * @package   ARC2
9 * @version   2010-11-16
10 *
11*/
12
13ARC2::inc('StoreQueryHandler');
14
15class ARC2_StoreSelectQueryHandler extends ARC2_StoreQueryHandler {
16
17  function __construct($a, &$caller) {/* caller has to be a store */
18    parent::__construct($a, $caller);
19  }
20 
21  function __init() {/* db_con */
22    parent::__init();
23    $this->store = $this->caller;
24    $con = $this->store->getDBCon();
25    $this->handler_type = 'select';
26    $this->engine_type = $this->v('store_engine_type', 'MyISAM', $this->a);
27    $this->cache_results = $this->v('store_cache_results', 0, $this->a);
28  }
29
30  /*  */
31
32  function runQuery($infos) {
33    $con = $this->store->getDBCon();
34    $rf = $this->v('result_format', '', $infos);
35    $this->infos = $infos;
36    $this->infos['null_vars'] = array();
37    $this->indexes = array();
38    $this->pattern_order_offset = 0;
39    $q_sql = $this->getSQL();
40
41    /* debug result formats */
42    if ($rf == 'sql') return $q_sql;
43    if ($rf == 'structure') return $this->infos;
44    if ($rf == 'index') return $this->indexes;
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->queryDB('DROP TABLE IF EXISTS ' . $tmp_tbl, $con);
52    }
53    return $r;
54  }
55
56  function getSQL() {
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      $r .= $this->is_union_query ? '(' . $sub_r . ')' : $sub_r;
66      $this->indexes[$i] = $this->index;
67    }
68    $r .= $this->is_union_query ? $this->getLIMITSQL() : '';
69    if ($this->v('order_infos', 0, $this->infos['query'])) {
70      $r = preg_replace('/SELECT(\s+DISTINCT)?\s*/', 'SELECT\\1 NULL AS `_pos_`, ', $r);
71    }
72    $pd_count = $this->problematicDependencies();
73    if ($pd_count) {
74      /* re-arranging the patterns sometimes reduces the LEFT JOIN dependencies */
75      $set_sql = 0;
76      if (!$this->pattern_order_offset) $set_sql = 1;
77      if (!$set_sql && ($pd_count < $this->opt_sql_pd_count)) $set_sql = 1;
78      if (!$set_sql && ($pd_count == $this->opt_sql_pd_count) && (strlen($r) < strlen($this->opt_sql))) $set_sql = 1;
79      if ($set_sql) {
80        $this->opt_sql = $r;
81        $this->opt_sql_pd_count = $pd_count;
82      }
83      $this->pattern_order_offset++;
84      if ($this->pattern_order_offset > 5) {
85        return $this->opt_sql;
86      }
87      return $this->getSQL();
88    }
89    return $r;
90  }
91
92  function buildInitialIndexes() {
93    $this->dependency_log = array();
94    $this->index = $this->getEmptyIndex();
95    $this->buildIndex($this->infos['query']['pattern'], 0);
96    $tmp = $this->index;
97    $this->analyzeIndex($this->getPattern('0'));
98    $this->initial_index = $this->index;
99    $this->index = $tmp;
100    $this->is_union_query = $this->index['union_branches'] ? 1 : 0;
101    $this->indexes = $this->is_union_query ? $this->getUnionIndexes($this->index) : array($this->index);
102  }
103
104  function createTempTable($q_sql) {
105    $con = $this->store->getDBCon();
106    $v = $this->store->getDBVersion();
107    if ($this->cache_results) {
108      $tbl = $this->store->getTablePrefix() . 'Q' . md5($q_sql);
109    }
110    else {
111      $tbl = $this->store->getTablePrefix() . 'Q' . md5($q_sql . time() . uniqid(rand()));
112    }
113    if (strlen($tbl) > 64) $tbl = 'Q' . md5($tbl);
114    $tmp_sql = 'CREATE TEMPORARY TABLE ' . $tbl . ' ( ' . $this->getTempTableDef($tbl, $q_sql) . ') ';
115    $tmp_sql .= (($v < '04-01-00') && ($v >= '04-00-18')) ? 'ENGINE' : (($v >= '04-01-02') ? 'ENGINE' : 'TYPE');
116    $tmp_sql .= '=' . $this->engine_type;/* HEAP doesn't support AUTO_INCREMENT, and MySQL breaks on MEMORY sometimes */
117    if (!$this->queryDB($tmp_sql, $con) && !$this->queryDB(str_replace('CREATE TEMPORARY', 'CREATE', $tmp_sql), $con)) {
118      return $this->addError(mysql_error($con));
119    }
120    mysql_unbuffered_query('INSERT INTO ' . $tbl . ' ' . "\n" . $q_sql, $con);
121    if ($er = mysql_error($con)) $this->addError($er);
122    return $tbl;
123  }
124
125  function getEmptyIndex() {
126    return array(
127      'from' => array(),
128      'join' => array(),
129      'left_join' => array(),
130      'vars' => array(), 'graph_vars' => array(), 'graph_uris' => array(),
131      'bnodes' => array(),
132      'triple_patterns' => array(),
133      'sub_joins' => array(),
134      'constraints' => array(),
135      'union_branches'=> array(),
136      'patterns' => array(),
137      'havings' => array()
138    );
139  }
140
141  function getTempTableDef($tmp_tbl, $q_sql) {
142    $col_part = preg_replace('/^SELECT\s*(DISTINCT)?(.*)FROM.*$/s', '\\2', $q_sql);
143    $parts = explode(',', $col_part);
144    $has_order_infos = $this->v('order_infos', 0, $this->infos['query']);
145    $r = '';
146    $added = array();
147    foreach ($parts as $part) {
148      if (preg_match('/\.?(.+)\s+AS\s+`(.+)`/U', trim($part), $m) && !isset($added[$m[2]])) {
149        $col = $m[1];
150        $alias = $m[2];
151        if ($alias == '_pos_') continue;
152        $r .= $r ? ',' : '';
153        $r .= "\n `" . $alias . "` int UNSIGNED";
154        $added[$alias] = 1;
155      }
156    }
157    if ($has_order_infos) {
158      $r = "\n" . '`_pos_` mediumint NOT NULL AUTO_INCREMENT PRIMARY KEY, ' . $r;
159    }
160    return  $r ? $r . "\n" : ''; 
161  }
162   
163  function getFinalQueryResult($q_sql, $tmp_tbl) {
164    /* var names */
165    $vars = array();
166    $aggregate_vars = array();
167    foreach ($this->infos['query']['result_vars'] as $entry) {
168      if ($entry['aggregate']) {
169        $vars[] = $entry['alias'];
170        $aggregate_vars[] = $entry['alias'];
171      }
172      else {
173        $vars[] = $entry['var'];
174      }
175    }
176    /* result */
177    $r = array('variables' => $vars);
178    $v_sql = $this->getValueSQL($tmp_tbl, $q_sql);
179    //echo "\n\n" . $v_sql;
180    $t1 = ARC2::mtime();
181    $con = $this->store->getDBCon();
182    $rs = mysql_unbuffered_query($v_sql, $con);
183    if ($er = mysql_error($con)) {
184      $this->addError($er);
185    }
186    $t2 = ARC2::mtime();
187    $rows = array();
188    $types = array(0 => 'uri', 1 => 'bnode', 2 => 'literal');
189    if ($rs) {
190                while ($pre_row = mysql_fetch_array($rs)) {
191        $row = array();
192        foreach ($vars as $var) {
193          if (isset($pre_row[$var])) {
194            $row[$var] = $pre_row[$var];
195            $row[$var . ' type'] = isset($pre_row[$var . ' type']) ? $types[$pre_row[$var . ' type']] : (in_array($var, $aggregate_vars) ? 'literal' : 'uri');
196            if (isset($pre_row[$var . ' lang_dt']) && ($lang_dt = $pre_row[$var . ' lang_dt'])) {
197              if (preg_match('/^([a-z]+(\-[a-z0-9]+)*)$/i', $lang_dt)) {
198                $row[$var . ' lang'] = $lang_dt;
199              }
200              else {
201                $row[$var . ' datatype'] = $lang_dt;
202              }
203            }
204          }
205        }
206        if ($row || !$vars) {
207          $rows[] = $row;
208        }
209      }
210    }
211    $r['rows'] = $rows;
212    return $r;
213  }
214 
215  /*  */
216 
217  function buildIndex($pattern, $id) {
218    $pattern['id'] = $id;
219    $type = $this->v('type', '', $pattern);
220    if (($type == 'filter') && $this->v('constraint', 0, $pattern)) {
221      $sub_pattern = $pattern['constraint'];
222      $sub_pattern['parent_id'] = $id;
223      $sub_id = $id . '_0';
224      $this->buildIndex($sub_pattern, $sub_id);
225      $pattern['constraint'] = $sub_id;
226    }
227    else {
228      $sub_patterns = $this->v('patterns', array(), $pattern);
229      $keys = array_keys($sub_patterns);
230      $spc = count($sub_patterns);
231      if (($spc > 4) && $this->pattern_order_offset) {
232        $keys = array();
233        for ($i = 0 ; $i < $spc; $i++) {
234          $keys[$i] = $i + $this->pattern_order_offset;
235          while ($keys[$i] >= $spc) $keys[$i] -= $spc;
236        }
237      }
238      foreach ($keys as $i => $key) {
239        $sub_pattern = $sub_patterns[$key];
240        $sub_pattern['parent_id'] = $id;
241        $sub_id = $id . '_' . $key;
242        $this->buildIndex($sub_pattern, $sub_id);
243        $pattern['patterns'][$i] = $sub_id;
244        if ($type == 'union') {
245          $this->index['union_branches'][] = $sub_id;
246        }
247      }
248    }
249    $this->index['patterns'][$id] = $pattern;
250  }
251 
252  /*  */
253
254  function analyzeIndex($pattern) {
255    $type = $this->v('type', '', $pattern);
256    if (!$type) {
257      //echo '<!-- ' . var_export($this->infos, 1) . ' -->';
258      return false;
259    }
260    $type = $pattern['type'];
261    $id = $pattern['id'];
262    /* triple */
263    if ($type == 'triple') {
264      foreach (array('s', 'p', 'o') as $term) {
265        if ($pattern[$term . '_type'] == 'var') {
266          $val = $pattern[$term];
267          $this->index['vars'][$val] = array_merge($this->v($val, array(), $this->index['vars']), array(array('table' => $pattern['id'], 'col' =>$term)));
268        }
269        if ($pattern[$term . '_type'] == 'bnode') {
270          $val = $pattern[$term];
271          $this->index['bnodes'][$val] = array_merge($this->v($val, array(), $this->index['bnodes']), array(array('table' => $pattern['id'], 'col' =>$term)));
272        }
273      }
274      $this->index['triple_patterns'][] = $pattern['id'];
275      /* joins */
276      if ($this->isOptionalPattern($id)) {
277        $this->index['left_join'][] = $id;
278      }
279      elseif (!$this->index['from']) {
280        $this->index['from'][] = $id;
281      }
282      elseif (!$this->getJoinInfos($id)) {
283        $this->index['from'][] = $id;
284      }
285      else {
286        $this->index['join'][] = $id;
287      }
288      /* graph infos, graph vars */
289      $this->index['patterns'][$id]['graph_infos'] = $this->getGraphInfos($id);
290      foreach ($this->index['patterns'][$id]['graph_infos'] as $info) {
291        if ($info['type'] == 'graph') {
292          if ($info['var']) {
293            $val = $info['var']['value'];
294            $this->index['graph_vars'][$val] = array_merge($this->v($val, array(), $this->index['graph_vars']), array(array('table' => $id)));
295          }
296          elseif ($info['uri']) {
297            $val = $info['uri'];
298            $this->index['graph_uris'][$val] = array_merge($this->v($val, array(), $this->index['graph_uris']), array(array('table' => $id)));
299          }
300        }
301      }
302    }
303    $sub_ids = $this->v('patterns', array(), $pattern);
304    foreach ($sub_ids as $sub_id) {
305      $this->analyzeIndex($this->getPattern($sub_id));
306    }
307  }
308 
309  /*  */
310
311  function getGraphInfos($id) {
312    $r = array();
313    if ($id) {
314      $pattern = $this->index['patterns'][$id];
315      $type = $pattern['type'];
316      /* graph */
317      if ($type == 'graph') {
318        $r[] = array('type' => 'graph', 'var' => $pattern['var'], 'uri' => $pattern['uri']);
319      }
320      $p_pattern = $this->index['patterns'][$pattern['parent_id']];
321      if (isset($p_pattern['graph_infos'])) {
322        return array_merge($p_pattern['graph_infos'], $r);
323      }
324      return array_merge($this->getGraphInfos($pattern['parent_id']), $r);
325    }
326    /* FROM / FROM NAMED */
327    else {
328      if (isset($this->infos['query']['dataset'])) {
329        foreach ($this->infos['query']['dataset'] as $set) {
330          $r[] = array_merge(array('type' => 'dataset'), $set);
331        }
332      }
333    }
334    return $r;
335  }
336 
337  /*  */
338
339  function getPattern($id) {
340    if (is_array($id)) {
341      return $id;
342    }
343    return $this->v($id, array(), $this->index['patterns']);
344  }
345
346  function getInitialPattern($id) {
347    return $this->v($id, array(), $this->initial_index['patterns']);
348  }
349
350  /*  */
351 
352  function getUnionIndexes($pre_index) {
353    $r = array();
354    $branches = array();
355    $min_depth = 1000;
356    /* only process branches with minimum depth */
357    foreach ($pre_index['union_branches'] as $id) {
358      $branches[$id] = count(preg_split('/\_/', $id));
359      $min_depth = min($min_depth, $branches[$id]);
360    }
361    foreach ($branches as $branch_id => $depth) {
362      if ($depth == $min_depth) {
363        $union_id = preg_replace('/\_[0-9]+$/', '', $branch_id);
364        $index = array('keeping' => $branch_id, 'union_branches' => array(), 'patterns' => $pre_index['patterns']);
365        $old_branches = $index['patterns'][$union_id]['patterns'];
366        $skip_id = ($old_branches[0] == $branch_id) ? $old_branches[1] : $old_branches[0];
367        $index['patterns'][$union_id]['type'] = 'group';
368        $index['patterns'][$union_id]['patterns'] = array($branch_id);
369        $has_sub_unions = 0;
370        foreach ($index['patterns'] as $pattern_id => $pattern) {
371          if (preg_match('/^' .$skip_id. '/', $pattern_id)) {
372             unset($index['patterns'][$pattern_id]);
373          }
374          elseif ($pattern['type'] == 'union') {
375            foreach ($pattern['patterns'] as $sub_union_branch_id) {
376              $index['union_branches'][] = $sub_union_branch_id;
377            }
378          }
379        }   
380        if ($index['union_branches']) {
381          $r = array_merge($r, $this->getUnionIndexes($index));
382        }
383        else {
384          $r[] = $index;
385        }
386      }
387    }
388    return $r;
389  }
390
391  /*  */
392
393  function isOptionalPattern($id) {
394    $pattern = $this->getPattern($id);
395    if ($this->v('type', '', $pattern) == 'optional') {
396      return 1;
397    }
398    if ($this->v('parent_id', '0', $pattern) == '0') {
399      return 0;
400    }
401    return $this->isOptionalPattern($pattern['parent_id']);
402  }
403
404  function getOptionalPattern($id) {
405    $pn = $this->getPattern($id);
406    do {
407      $pn = $this->getPattern($pn['parent_id']);
408    } while ($pn['parent_id'] && ($pn['type'] != 'optional'));
409    return $pn['id'];
410  }
411 
412  function sameOptional($id, $id2) {
413    return $this->getOptionalPattern($id) == $this->getOptionalPattern($id2);
414  }
415 
416  /*  */
417
418  function isUnionPattern($id) {
419    $pattern = $this->getPattern($id);
420    if ($this->v('type', '', $pattern) == 'union') {
421      return 1;
422    }
423    if ($this->v('parent_id', '0', $pattern) == '0') {
424      return 0;
425    }
426    return $this->isUnionPattern($pattern['parent_id']);
427  }
428 
429  /*  */
430
431  function getValueTable($col) {
432    return $this->store->getTablePrefix() . (preg_match('/^(s|o)$/', $col) ? $col . '2val' : 'id2val');
433  }
434 
435  function getGraphTable() {
436    return $this->store->getTablePrefix() . 'g2t';
437  }
438 
439  /*  */
440 
441  function getQuerySQL() {
442    $nl = "\n";
443    $where_sql = $this->getWHERESQL();  /* pre-fills $index['sub_joins'] $index['constraints'] */
444    $order_sql = $this->getORDERSQL();  /* pre-fills $index['sub_joins'] $index['constraints'] */
445    return '' .
446      ($this->is_union_query ? 'SELECT' : 'SELECT' . $this->getDistinctSQL()) . $nl .
447      $this->getResultVarsSQL() . $nl . /* fills $index['sub_joins'] */
448      $this->getFROMSQL() . 
449      $this->getAllJoinsSQL() . 
450      $this->getWHERESQL() . 
451      $this->getGROUPSQL() . 
452      $this->getORDERSQL() . 
453      ($this->is_union_query ? '' : $this->getLIMITSQL()) .
454      $nl .
455    '';
456  }
457
458  /*  */
459 
460  function getDistinctSQL() {
461    if ($this->is_union_query) {
462      return ($this->v('distinct', 0, $this->infos['query']) || $this->v('reduced', 0, $this->infos['query'])) ? '' : ' ALL'; 
463    }
464    return ($this->v('distinct', 0, $this->infos['query']) || $this->v('reduced', 0, $this->infos['query'])) ? ' DISTINCT' : ''; 
465  }
466
467  /*  */
468 
469  function getResultVarsSQL() {
470    $r = '';
471    $vars = $this->infos['query']['result_vars'];
472    $nl = "\n";
473    $added = array();
474    foreach ($vars as $var) {
475      $var_name = $var['var'];
476      $tbl_alias = '';
477      if ($tbl_infos = $this->getVarTableInfos($var_name, 0)) {
478        $tbl = $tbl_infos['table'];
479        $col = $tbl_infos['col'];
480        $tbl_alias = $tbl_infos['table_alias'];
481      }
482      elseif ($var_name == 1) {/* ASK query */
483        $r .= '1 AS `success`';
484      }
485      else {
486        $this->addError('Result variable "' .$var_name. '" not used in query.');
487      }
488      if ($tbl_alias) {
489        /* aggregate */
490        if ($var['aggregate']) {
491          $conv_code = '';
492          if (strtolower($var['aggregate']) != 'count') {
493            $tbl_alias = 'V_' . $tbl . '_' . $col . '.val';
494            $conv_code = '0 + ';
495          }
496          if (!isset($added[$var['alias']])) {
497            $r .= $r ? ',' . $nl . '  ' : '  ';
498            $distinct_code = (strtolower($var['aggregate']) == 'count') && $this->v('distinct', 0, $this->infos['query']) ? 'DISTINCT ' : '';
499            $r .= $var['aggregate'] . '(' . $conv_code . $distinct_code . $tbl_alias. ') AS `' . $var['alias'] . '`';
500            $added[$var['alias']] = 1;
501          }
502        }
503        /* normal var */
504        else {
505          if (!isset($added[$var_name])) {
506            $r .= $r ? ',' . $nl . '  ' : '  ';
507            $r .= $tbl_alias . ' AS `' . $var_name . '`';
508            $is_s = ($col == 's');
509            $is_p = ($col == 'p');
510            $is_o = ($col == 'o');
511            if ($tbl_alias == 'NULL') {
512              /* type / add in UNION queries? */
513              if ($is_s || $is_o) {
514                $r .= ', ' . $nl . '    NULL AS `' . $var_name . ' type`';
515              }
516              /* lang_dt / always add it in UNION queries, the var may be used as s/p/o */
517              if ($is_o || $this->is_union_query) {
518                $r .= ', ' . $nl . '    NULL AS `' . $var_name . ' lang_dt`';
519              }
520            }
521            else {
522              /* type */
523              if ($is_s || $is_o) {
524                $r .= ', ' . $nl . '    ' .$tbl_alias . '_type AS `' . $var_name . ' type`';
525              }
526              /* lang_dt / always add it in UNION queries, the var may be used as s/p/o */
527              if ($is_o) {
528                $r .= ', ' . $nl . '    ' .$tbl_alias . '_lang_dt AS `' . $var_name . ' lang_dt`';
529              }
530              elseif ($this->is_union_query) {
531                $r .= ', ' . $nl . '    NULL AS `' . $var_name . ' lang_dt`';
532              }
533            }
534            $added[$var_name] = 1;
535          }
536        }
537        if (!in_array($tbl_alias, $this->index['sub_joins'])) {
538          $this->index['sub_joins'][] = $tbl_alias;
539        }
540      }
541    }
542    return $r ? $r : '1 AS `success`';
543  }
544 
545  function getVarTableInfos($var, $ignore_initial_index = 1) {
546    if ($var == '*') {
547      return array('table' => '', 'col' => '', 'table_alias' => '*');
548    }
549    if ($infos = $this->v($var, 0, $this->index['vars'])) {
550      $infos[0]['table_alias'] = 'T_' . $infos[0]['table'] . '.' . $infos[0]['col'];
551      return $infos[0];
552    }
553    if ($infos = $this->v($var, 0, $this->index['graph_vars'])) {
554      $infos[0]['col'] = 'g';
555      $infos[0]['table_alias'] = 'G_' . $infos[0]['table'] . '.' . $infos[0]['col'];
556      return $infos[0];
557    }
558    if ($this->is_union_query && !$ignore_initial_index) {
559      if (($infos = $this->v($var, 0, $this->initial_index['vars'])) || ($infos = $this->v($var, 0, $this->initial_index['graph_vars']))) {
560        if (!in_array($var, $this->infos['null_vars'])) {
561          $this->infos['null_vars'][] = $var;
562        }
563        $infos[0]['table_alias'] = 'NULL';
564        $infos[0]['col'] = !isset($infos[0]['col']) ? '' : $infos[0]['col'];
565        return $infos[0];
566      }
567    }
568    return 0;
569  }
570 
571  /*  */
572 
573  function getFROMSQL() {
574    $from_ids = $this->index['from'];
575    $r = '';
576    foreach ($from_ids as $from_id) {
577      $r .= $r ? ', ' : '';
578      $r .= $this->getTripleTable($from_id) . ' T_' . $from_id;
579    }
580    /* MySQL 5 requires parentheses in case of multiple tables */
581    /* MySQL >5.5 (?) does not allow parentheses in case of a single table anymore! */
582    $r = (count($from_ids) > 1) ? '(' . $r . ')' : $r;
583    return $r ? 'FROM ' . $r : '';
584  }
585
586  /*  */
587 
588  function getOrderedJoinIDs() {
589    return array_merge($this->index['from'], $this->index['join'], $this->index['left_join']);
590  }
591
592  function getJoinInfos($id) {
593    $r = array();
594    $tbl_ids = $this->getOrderedJoinIDs();
595    $pattern = $this->getPattern($id);
596    foreach ($tbl_ids as $tbl_id) {
597      $tbl_pattern = $this->getPattern($tbl_id);
598      if ($tbl_id != $id) {
599        foreach (array('s', 'p', 'o') as $tbl_term) {
600          foreach (array('var', 'bnode', 'uri') as $term_type) {
601            if ($tbl_pattern[$tbl_term . '_type'] == $term_type) {
602              foreach (array('s', 'p', 'o') as $term) {
603                if (($pattern[$term . '_type'] == $term_type) && ($tbl_pattern[$tbl_term] == $pattern[$term])) {
604                  $r[] = array('term' => $term, 'join_tbl' => $tbl_id, 'join_term' => $tbl_term);
605                }
606              }
607            }
608          }
609        }
610      }
611    }
612    return $r;
613  }
614 
615  function getAllJoinsSQL() {
616    $js = $this->getJoins();
617    $ljs = $this->getLeftJoins();
618    $entries = array_merge($js, $ljs);
619    $id2code = array();
620    foreach ($entries as $entry) {
621      if (preg_match('/([^\s]+) ON (.*)/s', $entry, $m)) {
622        $id2code[$m[1]] = $entry;
623      }
624    }
625    $deps = array();
626    foreach ($id2code as $id => $code) {
627      $deps[$id]['rank'] = 0;
628      foreach ($id2code as $other_id => $other_code) {
629        $deps[$id]['rank'] += ($id != $other_id) && preg_match('/' . $other_id . '/', $code) ? 1 : 0;
630        $deps[$id][$other_id] = ($id != $other_id) && preg_match('/' . $other_id . '/', $code) ? 1 : 0;
631      }
632    }
633    $r = '';
634    do {
635      /* get next 0-rank */
636      $next_id = 0;
637      foreach ($deps as $id => $infos) {
638        if ($infos['rank'] == 0) {
639          $next_id = $id;
640          break;
641        }
642      }
643      if ($next_id) {
644        $r .= "\n" . $id2code[$next_id];
645        unset($deps[$next_id]);
646        foreach ($deps as $id => $infos) {
647          $deps[$id]['rank'] = 0;
648          unset($deps[$id][$next_id]);
649          foreach ($infos as $k => $v) {
650            if (!in_array($k, array('rank', $next_id))) {
651              $deps[$id]['rank'] += $v;
652              $deps[$id][$k] = $v;
653            }
654          }
655        }
656      }
657    }
658    while ($next_id);
659    if ($deps) {
660      $this->addError('Not all patterns could be rewritten to SQL JOINs');
661    }
662    return $r;
663  }
664 
665  function getJoins() {
666    $r = array();
667    $nl = "\n";
668    foreach ($this->index['join'] as $id) {
669      $sub_r = $this->getJoinConditionSQL($id);
670      $r[] = 'JOIN ' . $this->getTripleTable($id) . ' T_' . $id . ' ON (' . $sub_r . $nl . ')';
671    }
672    foreach (array_merge($this->index['from'], $this->index['join']) as $id) {
673      if ($sub_r = $this->getRequiredSubJoinSQL($id)) {
674        $r[] = $sub_r;
675      }
676    }
677    return $r;
678  }
679 
680  function getLeftJoins() {
681    $r = array();
682    $nl = "\n";
683    foreach ($this->index['left_join'] as $id) {
684      $sub_r = $this->getJoinConditionSQL($id);
685      $r[] = 'LEFT JOIN ' . $this->getTripleTable($id) . ' T_' . $id . ' ON (' . $sub_r . $nl . ')';
686    }
687    foreach ($this->index['left_join'] as $id) {
688      if ($sub_r = $this->getRequiredSubJoinSQL($id, 'LEFT')) {
689        $r[] = $sub_r;
690      }
691    }
692    return $r;
693  }
694 
695  function getJoinConditionSQL($id) {
696    $r = '';
697    $nl = "\n";
698    $infos = $this->getJoinInfos($id);
699    $pattern = $this->getPattern($id);
700   
701    $tbl = 'T_' . $id;
702    /* core dependency */
703    $d_tbls = $this->getDependentJoins($id);
704    foreach ($d_tbls as $d_tbl) {
705      if (preg_match('/^T_([0-9\_]+)\.[spo]+/', $d_tbl, $m) && ($m[1] != $id)) {
706        if ($this->isJoinedBefore($m[1], $id) && !in_array($m[1], array_merge($this->index['from'], $this->index['join']))) {
707          $r .= $r ? $nl . '  AND ' : $nl . '  ';
708          $r .= '(' . $d_tbl . ' IS NOT NULL)';
709        }
710        $this->logDependency($id, $d_tbl);
711      }
712    }
713    /* triple-based join info */
714    foreach ($infos as $info) {
715      if ($this->isJoinedBefore($info['join_tbl'], $id) && $this->joinDependsOn($id, $info['join_tbl'])) {
716        $r .= $r ? $nl . '  AND ' : $nl . '  ';
717        $r .= '(' . $tbl . '.' . $info['term'] . ' = T_' . $info['join_tbl'] . '.' . $info['join_term'] . ')';
718      }
719    }
720    /* filters etc */
721    if ($sub_r = $this->getPatternSQL($pattern, 'join__T_' . $id)) {
722      $r .= $r ? $nl . '  AND ' . $sub_r  : $nl . '  ' . '(' . $sub_r . ')';
723    }
724    return $r;
725  }
726
727  /**
728   * A log of identified table join dependencies in getJoinConditionSQL
729   *
730  */
731
732  function logDependency($id, $tbl) {
733    if (!isset($this->dependency_log[$id])) $this->dependency_log[$id] = array();
734    if (!in_array($tbl, $this->dependency_log[$id])) {
735      $this->dependency_log[$id][] = $tbl;
736    }
737  }
738
739  /**
740   * checks whether entries in the dependecy log could perhaps be optimized
741   * (triggers re-ordering of patterns
742  */
743
744  function problematicDependencies() {
745    foreach ($this->dependency_log as $id => $tbls) {
746      if (count($tbls) > 1) return count($tbls);
747    }
748    return 0;
749  }
750 
751  function isJoinedBefore($tbl_1, $tbl_2) {
752    $tbl_ids = $this->getOrderedJoinIDs();
753    foreach ($tbl_ids as $id) {
754      if ($id == $tbl_1) {
755        return 1;
756      }
757      if ($id == $tbl_2) {
758        return 0;
759      }
760    }
761  }
762 
763  function joinDependsOn($id, $id2) {
764    if (in_array($id2, array_merge($this->index['from'], $this->index['join']))) {
765      return 1;
766    }
767    $d_tbls = $this->getDependentJoins($id2);
768    //echo $id . ' :: ' . $id2 . '=>' . print_r($d_tbls, 1);
769    foreach ($d_tbls as $d_tbl) {
770      if (preg_match('/^T_' .$id. '\./', $d_tbl)) {
771        return 1;
772      }
773    }
774    return 0;
775  }
776 
777  function getDependentJoins($id) {
778    $r = array();
779    /* sub joins */
780    foreach ($this->index['sub_joins'] as $alias) {
781      if (preg_match('/^(T|V|G)_' . $id . '/', $alias)) {
782        $r[] = $alias;
783      }
784    }
785    /* siblings in shared optional */
786    $o_id = $this->getOptionalPattern($id);
787    foreach ($this->index['sub_joins'] as $alias) {
788      if (preg_match('/^(T|V|G)_' . $o_id . '/', $alias) && !in_array($alias, $r)) {
789        $r[] = $alias;
790      }
791    }
792    foreach ($this->index['left_join'] as $alias) {
793      if (preg_match('/^' . $o_id . '/', $alias) && !in_array($alias, $r)) {
794        $r[] = 'T_' . $alias . '.s';
795      }
796    }
797    return $r;
798  }
799 
800  /*  */
801 
802  function getRequiredSubJoinSQL($id, $prefix = '') {/* id is a triple pattern id. Optional FILTERS and GRAPHs are getting added to the join directly */
803    $nl = "\n";
804    $r = '';
805    foreach ($this->index['sub_joins'] as $alias) {
806      if (preg_match('/^V_' . $id . '_([a-z\_]+)\.val$/', $alias, $m)) {
807        $col = $m[1];
808        $sub_r = '';
809        if ($this->isOptionalPattern($id)) {
810          $pattern = $this->getPattern($id);
811          do {
812            $pattern = $this->getPattern($pattern['parent_id']);
813          } while ($pattern['parent_id'] && ($pattern['type'] != 'optional'));
814          $sub_r = $this->getPatternSQL($pattern, 'sub_join__V_' . $id);
815        }
816        $sub_r = $sub_r ? $nl . '  AND (' . $sub_r . ')' : '';
817        /* lang dt only on literals */
818        if ($col == 'o_lang_dt') {
819          $sub_sub_r = 'T_' . $id . '.o_type = 2'; 
820          $sub_r .= $nl . '  AND (' . $sub_sub_r . ')';
821        }
822        //$cur_prefix = $prefix ? $prefix . ' ' : 'STRAIGHT_';
823        $cur_prefix = $prefix ? $prefix . ' ' : '';
824        if ($col == 'g') {
825          $r .= trim($cur_prefix . 'JOIN '. $this->getValueTable($col) . ' V_' .$id . '_' . $col. ' ON (' .$nl. '  (G_' . $id . '.' . $col. ' = V_' . $id. '_' . $col. '.id) ' . $sub_r . $nl . ')');
826        }
827        else {
828          $r .= trim($cur_prefix . 'JOIN '. $this->getValueTable($col) . ' V_' .$id . '_' . $col. ' ON (' .$nl. '  (T_' . $id . '.' . $col. ' = V_' . $id. '_' . $col. '.id) ' . $sub_r . $nl . ')');
829        }
830      }
831      elseif (preg_match('/^G_' . $id . '\.g$/', $alias, $m)) {
832        $pattern = $this->getPattern($id);
833        $sub_r = $this->getPatternSQL($pattern, 'graph_sub_join__G_' . $id);
834        $sub_r = $sub_r ? $nl . '  AND ' . $sub_r : '';
835        /* dataset restrictions */
836        $gi = $this->getGraphInfos($id);
837        $sub_sub_r = '';
838        $added_gts = array();
839        foreach ($gi as $set) {
840          if (isset($set['graph']) && !in_array($set['graph'], $added_gts)) {
841            $sub_sub_r .= $sub_sub_r !== '' ? ',' : '';
842            $sub_sub_r .= $this->getTermID($set['graph'], 'g'); 
843            $added_gts[] = $set['graph'];
844          }
845        }
846        $sub_r .= ($sub_sub_r !== '') ? $nl . ' AND (G_' . $id . '.g IN (' . $sub_sub_r . '))' : ''; // /* ' . str_replace('#' , '::', $set['graph']) . ' */';
847        /* other graph join conditions */
848        foreach ($this->index['graph_vars'] as $var => $occurs) {
849          $occur_tbls = array();
850          foreach ($occurs as $occur) {
851            $occur_tbls[] = $occur['table'];
852            if ($occur['table'] == $id) break;
853          }
854          foreach($occur_tbls as $tbl) {
855            if (($tbl != $id) && in_array($id, $occur_tbls) && $this->isJoinedBefore($tbl, $id)) {
856              $sub_r .= $nl . '  AND (G_' .$id. '.g = G_' .$tbl. '.g)'; 
857            }
858          }
859        }
860        //$cur_prefix = $prefix ? $prefix . ' ' : 'STRAIGHT_';
861        $cur_prefix = $prefix ? $prefix . ' ' : '';
862        $r .= trim($cur_prefix . 'JOIN '. $this->getGraphTable() . ' G_' .$id . ' ON (' .$nl. '  (T_' . $id . '.t = G_' .$id. '.t)' . $sub_r . $nl . ')');
863      }
864    }
865    return $r;
866  }
867
868  /*  */
869
870  function getWHERESQL() {
871    $r = '';
872    $nl = "\n";
873    /* standard constraints */
874    $sub_r = $this->getPatternSQL($this->getPattern('0'), 'where');
875    /* additional constraints */
876    foreach ($this->index['from'] as $id) {
877      if ($sub_sub_r = $this->getConstraintSQL($id)) {
878        $sub_r .= $sub_r ? $nl . ' AND ' . $sub_sub_r : $sub_sub_r;
879      }
880    }
881    $r .= $sub_r ? $sub_r : '';
882    /* left join dependencies */
883    foreach ($this->index['left_join'] as $id) {
884      $d_joins = $this->getDependentJoins($id);
885      $added = array();
886      $d_aliases = array();
887      //echo $id . ' =>' . print_r($d_joins, 1);
888      $id_alias = 'T_' . $id . '.s';
889      foreach ($d_joins as $alias) {
890        if (preg_match('/^(T|V|G)_([0-9\_]+)(_[spo])?\.([a-z\_]+)/', $alias, $m)) {
891          $tbl_type = $m[1];
892          $tbl_pattern_id = $m[2];
893          $suffix = $m[3];
894          if (($tbl_pattern_id >= $id) && $this->sameOptional($tbl_pattern_id, $id)) {/* get rid of dependency permutations and nested optionals */
895            if (!in_array($tbl_type . '_' . $tbl_pattern_id . $suffix, $added)) {
896              $sub_r .= $sub_r ? ' AND ' : '';
897              $sub_r .= $alias . ' IS NULL';
898              $d_aliases[] = $alias;
899              $added[] = $tbl_type . '_' . $tbl_pattern_id . $suffix;
900              $id_alias = ($tbl_pattern_id == $id) ? $alias : $id_alias;
901            }
902          }
903        }
904      }
905      if (count($d_aliases) > 2) {/* @@todo fix this! */
906        $sub_r1 = '  /* '.$id_alias.' dependencies */';
907        $sub_r2 = '((' . $id_alias . ' IS NULL) OR (CONCAT(' . join(', ', $d_aliases) . ') IS NOT NULL))';
908        $r .= $r ? $nl . $sub_r1 . $nl . '  AND ' .$sub_r2 : $sub_r1 . $nl . $sub_r2;
909      }
910    }
911    return $r ? $nl . 'WHERE ' . $r : '';
912  }
913
914  /*  */
915 
916  function addConstraintSQLEntry($id, $sql) {
917    if (!isset($this->index['constraints'][$id])) {
918      $this->index['constraints'][$id] = array();
919    }
920    if (!in_array($sql, $this->index['constraints'][$id])) {
921      $this->index['constraints'][$id][] = $sql;
922    }
923  }
924 
925  function getConstraintSQL($id) {
926    $r = '';
927    $nl = "\n";
928    $constraints = $this->v($id, array(), $this->index['constraints']);
929    foreach ($constraints as $constraint) {
930      $r .= $r ? $nl . '  AND ' . $constraint : $constraint;
931    }
932    return $r;
933  }
934 
935  /*  */
936 
937  function getPatternSQL($pattern, $context) {
938    $type = $this->v('type', '', $pattern);
939    if (!$type) {
940      return '';
941    }
942    $m = 'get' . ucfirst($type) . 'PatternSQL';
943    return method_exists($this, $m) ? $this->$m($pattern, $context) : $this->getDefaultPatternSQL($pattern, $context);
944  }
945
946  function getDefaultPatternSQL($pattern, $context) {
947    $r = '';
948    $nl = "\n";
949    $sub_ids = $this->v('patterns', array(), $pattern);
950    foreach ($sub_ids as $sub_id) {
951      $sub_r = $this->getPatternSQL($this->getPattern($sub_id), $context);
952      $r .= ($r && $sub_r) ? $nl . '  AND (' . $sub_r . ')' : ($sub_r ? $sub_r  : '');
953    }
954    return $r ? $r : '';
955  }
956 
957  function getTriplePatternSQL($pattern, $context) {
958    $r = '';
959    $nl = "\n";
960    $id = $pattern['id'];
961    /* s p o */
962    $vars = array();
963    foreach (array('s', 'p', 'o') as $term) {
964      $sub_r = '';
965      $type = $pattern[$term . '_type'];
966      if ($type == 'uri') {
967        $term_id = $this->getTermID($pattern[$term], $term);
968        $sub_r = '(T_' . $id . '.' . $term . ' = ' . $term_id . ') /* ' . preg_replace('/[\#\*\>]/' , '::', $pattern[$term]) . ' */';
969      }
970      elseif ($type == 'literal') {
971        $term_id = $this->getTermID($pattern[$term], $term);
972        $sub_r = '(T_' . $id . '.' . $term . ' = ' . $term_id . ') /* ' . preg_replace('/[\#\n\*\>]/' , ' ', $pattern[$term]) . ' */';
973        if (($lang_dt = $this->v1($term . '_lang', '', $pattern)) || ($lang_dt = $this->v1($term . '_datatype', '', $pattern))) {
974          $lang_dt_id = $this->getTermID($lang_dt);
975          $sub_r .= $nl . '  AND (T_' . $id . '.' .$term. '_lang_dt = ' . $lang_dt_id . ') /* ' . preg_replace('/[\#\*\>]/' , '::', $lang_dt) . ' */';
976        }
977      }
978      elseif ($type == 'var') {
979        $val = $pattern[$term];
980        if (isset($vars[$val])) {/* repeated var in pattern */
981          $sub_r = '(T_' . $id . '.' . $term . '=' . 'T_' . $id . '.' . $vars[$val] . ')';
982        }
983        $vars[$val] = $term;
984        if ($infos = $this->v($val, 0, $this->index['graph_vars'])) {/* graph var in triple pattern */
985          $sub_r .= $sub_r ? $nl . '  AND ' : '';
986          $tbl = $infos[0]['table'];
987          $sub_r .= 'G_' . $tbl . '.g = T_' . $id . '.' . $term;
988        }
989      }
990      if ($sub_r) {
991        if (preg_match('/^(join)/', $context) || (preg_match('/^where/', $context) && in_array($id, $this->index['from']))) {
992          $r .= $r ? $nl . '  AND ' . $sub_r  : $sub_r;
993        }
994      }
995    }
996    /* g */
997    if ($infos = $pattern['graph_infos']) {
998      $tbl_alias = 'G_' . $id . '.g';
999      if (!in_array($tbl_alias, $this->index['sub_joins'])) {
1000        $this->index['sub_joins'][] = $tbl_alias;
1001      }
1002      $sub_r = array('graph_var' => '', 'graph_uri' => '', 'from' => '', 'from_named' => '');
1003      foreach ($infos as $info) {
1004        $type = $info['type'];
1005        if ($type == 'graph') {
1006          if ($info['uri']) {
1007            $term_id = $this->getTermID($info['uri'], 'g');
1008            $sub_r['graph_uri'] .= $sub_r['graph_uri'] ? $nl . ' AND ' : '';
1009            $sub_r['graph_uri'] .= '(' .$tbl_alias. ' = ' . $term_id . ') /* ' . preg_replace('/[\#\*\>]/' , '::', $info['uri']) . ' */';
1010          }
1011        }
1012      }
1013      if ($sub_r['from'] && $sub_r['from_named']) {
1014        $sub_r['from_named'] = '';
1015      }
1016      if (!$sub_r['from'] && !$sub_r['from_named']) {
1017        $sub_r['graph_var'] = '';
1018      }
1019      if (preg_match('/^(graph_sub_join)/', $context)) {
1020        foreach ($sub_r as $g_type => $g_sql) {
1021          if ($g_sql) {
1022            $r .= $r ? $nl . '  AND ' . $g_sql  : $g_sql;
1023          }
1024        }
1025      }
1026    }
1027    /* optional sibling filters? */
1028    if (preg_match('/^(join|sub_join)/', $context) && $this->isOptionalPattern($id)) {
1029      $o_pattern = $pattern;
1030      do {
1031        $o_pattern = $this->getPattern($o_pattern['parent_id']);
1032      } while ($o_pattern['parent_id'] && ($o_pattern['type'] != 'optional'));
1033      if ($sub_r = $this->getPatternSQL($o_pattern, 'optional_filter' . preg_replace('/^(.*)(__.*)$/', '\\2', $context))) {
1034        $r .= $r ? $nl . '  AND ' . $sub_r  : $sub_r;
1035      }
1036      /* created constraints */
1037      if ($sub_r = $this->getConstraintSQL($id)) {
1038        $r .= $r ? $nl . '  AND ' . $sub_r  : $sub_r;
1039      }
1040    }
1041    /* result */
1042    if (preg_match('/^(where)/', $context) && $this->isOptionalPattern($id)) {
1043      return '';
1044    }
1045    return $r;
1046  }
1047 
1048  /*  */
1049 
1050  function getFilterPatternSQL($pattern, $context) {
1051    $r = '';
1052    $id = $pattern['id'];
1053    $constraint_id = $this->v1('constraint', '', $pattern);
1054    $constraint = $this->getPattern($constraint_id);
1055    $constraint_type = $constraint['type'];
1056    if ($constraint_type == 'built_in_call') {
1057      $r = $this->getBuiltInCallSQL($constraint, $context);
1058    }
1059    elseif ($constraint_type == 'expression') {
1060      $r = $this->getExpressionSQL($constraint, $context, '', 'filter');
1061    }
1062    else {
1063      $m = 'get' . ucfirst($constraint_type) . 'ExpressionSQL';
1064      if (method_exists($this, $m)) {
1065        $r = $this->$m($constraint, $context, '', 'filter');
1066      }
1067    }
1068    if ($this->isOptionalPattern($id) && !preg_match('/^(join|optional_filter)/', $context)) {
1069      return '';
1070    }
1071    /* unconnected vars in FILTERs eval to false */
1072    $sub_r = $this->hasUnconnectedFilterVars($id);
1073    if ($sub_r) {
1074      if ($sub_r == 'alias') {
1075        if (!in_array($r, $this->index['havings'])) $this->index['havings'][] = $r;
1076        return '';
1077      }
1078      elseif (preg_match('/^T([^\s]+\.)g (.*)$/s', $r, $m)) {/* graph filter */
1079        return 'G' . $m[1] . 't ' . $m[2];
1080      }
1081      elseif (preg_match('/^\(*V[^\s]+_g\.val .*$/s', $r, $m)) {/* graph value filter, @@improveMe */
1082        //return $r;
1083      }
1084      else {
1085        return 'FALSE';
1086      }
1087    }
1088    /* some really ugly tweaks */
1089    /* empty language filter: FILTER ( lang(?v) = '' ) */
1090    $r = preg_replace('/\(\/\* language call \*\/ ([^\s]+) = ""\)/s', '((\\1 = "") OR (\\1 LIKE "%:%"))', $r);
1091    return $r;
1092  }
1093 
1094  /**
1095   * Checks if vars in the given (filter) pattern are used within the filter's scope.
1096   */
1097 
1098  function hasUnconnectedFilterVars($filter_pattern_id) {
1099    $scope_id = $this->getFilterScope($filter_pattern_id);
1100    $vars = $this->getFilterVars($filter_pattern_id);
1101    $r = 0;
1102    foreach ($vars as $var_name) {
1103      if ($this->isUsedTripleVar($var_name, $scope_id)) continue;
1104      if ($this->isAliasVar($var_name)) {
1105        $r = 'alias';
1106        break;
1107      }
1108      $r = 1;
1109      break;
1110    }
1111    return $r;
1112  }
1113
1114  /**
1115   * Returns the given filter pattern's scope (the id of the parent group pattern).
1116   */
1117
1118  function getFilterScope($filter_pattern_id) {
1119    $patterns = $this->initial_index['patterns'];
1120    $r = '';
1121    foreach ($patterns as $id => $p) {
1122      /* the id has to be sub-part of the given filter id */
1123      if (!preg_match('/^' . $id . '.+/', $filter_pattern_id)) continue;
1124      /* we are looking for a group or union */
1125      if (!preg_match('/^(group|union)$/', $p['type'])) continue;
1126      /* we are looking for the longest/deepest match */
1127      if (strlen($id) > strlen($r)) $r = $id;
1128    }
1129    return $r;
1130  }
1131
1132  /**
1133   * Builds a list of vars used in the given (filter) pattern.
1134   */
1135
1136  function getFilterVars($filter_pattern_id) {
1137    $r = array();
1138    $patterns = $this->initial_index['patterns'];
1139    /* find vars in the given filter (i.e. the given id is part of their pattern id) */
1140    foreach ($patterns as $id => $p) {
1141      if (!preg_match('/^' . $filter_pattern_id . '.+/', $id)) continue;
1142      $var_name = '';
1143      if ($p['type'] == 'var') {
1144        $var_name = $p['value'];
1145      }
1146      elseif (($p['type'] == 'built_in_call') && ($p['call'] == 'bound')) {
1147        $var_name = $p['args'][0]['value'];
1148      }
1149      if ($var_name && !in_array($var_name, $r)) {
1150        $r[] = $var_name;
1151      }
1152    }
1153    return $r;
1154  }
1155
1156  /**
1157   * Checks if $var_name appears as result projection alias.
1158   */
1159
1160  function isAliasVar($var_name) {
1161    foreach ($this->infos['query']['result_vars'] as $r_var) {
1162      if ($r_var['alias'] == $var_name) return 1;
1163    }
1164    return 0;
1165  }
1166
1167  /**
1168   * Checks if $var_name is used in a triple pattern in the given scope
1169   */
1170
1171  function isUsedTripleVar($var_name, $scope_id = '0') {
1172    $patterns = $this->initial_index['patterns'];
1173    foreach ($patterns as $id => $p) {
1174      if ($p['type'] != 'triple') continue;
1175      if (!preg_match('/^' . $scope_id . '.+/', $id)) continue;
1176      foreach (array('s', 'p', 'o') as $term) {
1177        if ($p[$term . '_type'] != 'var') continue;
1178        if ($p[$term] == $var_name) return 1;
1179      }
1180    }
1181  }
1182
1183  /*  */
1184
1185  function getExpressionSQL($pattern, $context, $val_type = '', $parent_type = '') {
1186    $r = '';
1187    $nl = "\n";
1188    $type = $this->v1('type', '', $pattern);
1189    $sub_type = $this->v1('sub_type', $type, $pattern);
1190    if (preg_match('/^(and|or)$/', $sub_type)) {
1191      foreach ($pattern['patterns'] as $sub_id) {
1192        $sub_pattern = $this->getPattern($sub_id);
1193        $sub_pattern_type = $sub_pattern['type'];
1194        if ($sub_pattern_type == 'built_in_call') {
1195          $sub_r = $this->getBuiltInCallSQL($sub_pattern, $context, '', $parent_type);
1196        }
1197        else {
1198          $sub_r = $this->getExpressionSQL($sub_pattern, $context, '', $parent_type);
1199        }
1200        if ($sub_r) {
1201          $r .= $r ? ' ' . strtoupper($sub_type). ' (' .$sub_r. ')' : '(' . $sub_r . ')';
1202        }
1203      }
1204    }
1205    elseif ($sub_type == 'built_in_call') {
1206      $r = $this->getBuiltInCallSQL($pattern, $context, $val_type, $parent_type);
1207    }
1208    elseif (preg_match('/literal/', $sub_type)) {
1209      $r = $this->getLiteralExpressionSQL($pattern, $context, $val_type, $parent_type);
1210    }
1211    elseif ($sub_type) {
1212      $m = 'get' . ucfirst($sub_type) . 'ExpressionSQL';
1213      if (method_exists($this, $m)) {
1214        $r = $this->$m($pattern, $context, '', $parent_type);
1215      }
1216    }
1217    /* skip expressions that reference non-yet-joined tables */
1218    if (preg_match('/__(T|V|G)_(.+)$/', $context, $m)) {
1219      $context_pattern_id = $m[2];
1220      $context_table_type = $m[1];
1221      if (preg_match_all('/((T|V|G)(\_[0-9])+)/', $r, $m)) {
1222        $aliases = $m[1];
1223        $keep = 1;
1224        foreach ($aliases as $alias) {
1225          if (preg_match('/(T|V|G)_(.*)$/', $alias, $m)) {
1226            $tbl_type = $m[1];
1227            $tbl = $m[2];
1228            if (!$this->isJoinedBefore($tbl, $context_pattern_id)) {
1229              $keep = 0;
1230            }
1231            elseif (($context_pattern_id == $tbl) && preg_match('/(TV)/', $context_table_type . $tbl_type)) {
1232              $keep = 0;
1233            }
1234          }
1235        }
1236        $r = $keep ? $r : '';
1237      }
1238    }
1239    return $r ? '(' . $r . ')' : $r;
1240  }
1241 
1242  function detectExpressionValueType($pattern_ids) {
1243    foreach ($pattern_ids as $id) {
1244      $pattern = $this->getPattern($id);
1245      $type = $this->v('type', '', $pattern);
1246      if (($type == 'literal') && isset($pattern['datatype'])) {
1247        if (in_array($pattern['datatype'], array($this->xsd . 'integer', $this->xsd . 'float', $this->xsd . 'double'))) {
1248          return 'numeric';
1249        }
1250      }
1251    }
1252    return '';
1253  }
1254
1255  /*  */
1256
1257  function getRelationalExpressionSQL($pattern, $context, $val_type = '', $parent_type = '') {
1258    $r = '';
1259    $val_type = $this->detectExpressionValueType($pattern['patterns']);
1260    $op = $pattern['operator'];
1261    foreach ($pattern['patterns'] as $sub_id) {
1262      $sub_pattern = $this->getPattern($sub_id);
1263      $sub_pattern['parent_op'] = $op;
1264      $sub_type = $sub_pattern['type'];
1265      $m = ($sub_type == 'built_in_call') ? 'getBuiltInCallSQL' : 'get' . ucfirst($sub_type) . 'ExpressionSQL';
1266      $m = str_replace('ExpressionExpression', 'Expression', $m);
1267      $sub_r = method_exists($this, $m) ? $this->$m($sub_pattern, $context, $val_type, 'relational') : '';
1268      $r .= $r ? ' ' . $op . ' ' . $sub_r : $sub_r;
1269    }
1270    return $r ? '(' . $r . ')' : $r;
1271  }
1272
1273  function getAdditiveExpressionSQL($pattern, $context, $val_type = '', $parent_type = '') {
1274    $r = '';
1275    $val_type = $this->detectExpressionValueType($pattern['patterns']);
1276    foreach ($pattern['patterns'] as $sub_id) {
1277      $sub_pattern = $this->getPattern($sub_id);
1278      $sub_type = $this->v('type', '', $sub_pattern);
1279      $m = ($sub_type == 'built_in_call') ? 'getBuiltInCallSQL' : 'get' . ucfirst($sub_type) . 'ExpressionSQL';
1280      $m = str_replace('ExpressionExpression', 'Expression', $m);
1281      $sub_r = method_exists($this, $m) ? $this->$m($sub_pattern, $context, $val_type, 'additive') : '';
1282      $r .= $r ? ' ' . $sub_r : $sub_r;
1283    }
1284    return $r;
1285  }
1286
1287  function getMultiplicativeExpressionSQL($pattern, $context, $val_type = '', $parent_type = '') {
1288    $r = '';
1289    $val_type = $this->detectExpressionValueType($pattern['patterns']);
1290    foreach ($pattern['patterns'] as $sub_id) {
1291      $sub_pattern = $this->getPattern($sub_id);
1292      $sub_type = $sub_pattern['type'];
1293      $m = ($sub_type == 'built_in_call') ? 'getBuiltInCallSQL' : 'get' . ucfirst($sub_type) . 'ExpressionSQL';
1294      $m = str_replace('ExpressionExpression', 'Expression', $m);
1295      $sub_r = method_exists($this, $m) ? $this->$m($sub_pattern, $context, $val_type, 'multiplicative') : '';
1296      $r .= $r ? ' ' . $sub_r : $sub_r;
1297    }
1298    return $r;
1299  }
1300
1301  /*  */
1302
1303  function getVarExpressionSQL($pattern, $context, $val_type = '', $parent_type = '') {
1304    $var = $pattern['value'];
1305    $info = $this->getVarTableInfos($var);
1306    if (!$tbl = $info['table']) {
1307      /* might be an aggregate var */
1308      $vars = $this->infos['query']['result_vars'];
1309      foreach ($vars as $test_var) {
1310        if ($test_var['alias'] == $pattern['value']) {
1311          return '`' . $pattern['value'] . '`';
1312        }
1313      }
1314      return '';
1315    }
1316    $col = $info['col'];
1317    if (($context == 'order') && ($col == 'o')) {
1318      $tbl_alias = 'T_' . $tbl . '.o_comp';
1319    }
1320    elseif ($context == 'sameterm') {
1321      $tbl_alias = 'T_' . $tbl . '.' . $col;
1322    }
1323    elseif (($parent_type == 'relational') && ($col == 'o') && (preg_match('/[\<\>]/', $this->v('parent_op', '', $pattern)))) {
1324      $tbl_alias = 'T_' . $tbl . '.o_comp';
1325    }
1326    else {
1327      $tbl_alias = 'V_' . $tbl . '_' . $col . '.val';
1328      if (!in_array($tbl_alias, $this->index['sub_joins'])) {
1329        $this->index['sub_joins'][] = $tbl_alias;
1330      }
1331    }
1332    $op = $this->v('operator', '', $pattern);
1333    if (preg_match('/^(filter|and)/', $parent_type)) {
1334      if ($op == '!') {
1335        $r = '(((' . $tbl_alias . ' = 0) AND (CONCAT("1", ' . $tbl_alias . ') != 1))'; /* 0 and no string */
1336        $r .= ' OR (' . $tbl_alias . ' IN ("", "false")))'; /* or "", or "false" */
1337      }
1338      else {
1339        $r = '((' . $tbl_alias . ' != 0)'; /* not null */
1340        $r .= ' OR ((CONCAT("1", ' . $tbl_alias . ') = 1) AND (' . $tbl_alias . ' NOT IN ("", "false"))))'; /* string, and not "" or "false" */
1341      }
1342    }
1343    else {
1344      $r = trim($op . ' ' . $tbl_alias);
1345      if ($val_type == 'numeric') {
1346        if (preg_match('/__(T|V|G)_(.+)$/', $context, $m)) {
1347          $context_pattern_id = $m[2];
1348          $context_table_type = $m[1];
1349        }
1350        else {
1351          $context_pattern_id = $pattern['id'];
1352          $context_table_type = 'T';
1353        }
1354        if ($this->isJoinedBefore($tbl, $context_pattern_id)) {
1355          $add = ($tbl != $context_pattern_id) ? 1 : 0;
1356          $add = (!$add && ($context_table_type == 'V')) ? 1 : 0;
1357          if ($add) {
1358            $this->addConstraintSQLEntry($context_pattern_id, '(' .$r. ' = "0" OR ' . $r . '*1.0 != 0)');
1359          }
1360        }
1361      }
1362    }
1363    return $r;
1364  }
1365 
1366  /*  */
1367
1368  function getUriExpressionSQL($pattern, $context, $val_type = '') {
1369    $val = $pattern['uri'];
1370    $r = $pattern['operator'];
1371    $r .= is_numeric($val) ? ' ' . $val : ' "' . mysql_real_escape_string($val, $this->store->getDBCon()) . '"';
1372    return $r;
1373  }
1374 
1375  /*  */
1376
1377  function getLiteralExpressionSQL($pattern, $context, $val_type = '', $parent_type = '') {
1378    $val = $pattern['value'];
1379    $r = $pattern['operator'];
1380    if (is_numeric($val) && $this->v('datatype', 0, $pattern)) {
1381      $r .= ' ' . $val;
1382    }
1383    elseif (preg_match('/^(true|false)$/i', $val) && ($this->v1('datatype', '', $pattern) == 'http://www.w3.org/2001/XMLSchema#boolean')) {
1384      $r .= ' ' . strtoupper($val);
1385    }
1386    elseif ($parent_type == 'regex') {
1387      $sub_r = mysql_real_escape_string($val, $this->store->getDBCon());
1388      $r .= ' "' . preg_replace('/\x5c\x5c/', '\\', $sub_r) . '"';
1389    }
1390    else {
1391      $r .= ' "' . mysql_real_escape_string($val, $this->store->getDBCon()) . '"';
1392    }
1393    if (($lang_dt = $this->v1('lang', '', $pattern)) || ($lang_dt = $this->v1('datatype', '', $pattern))) {
1394      /* try table/alias via var in siblings */
1395      if ($var = $this->findSiblingVarExpression($pattern['id'])) {
1396        if (isset($this->index['vars'][$var])) {
1397          $infos = $this->index['vars'][$var];
1398          foreach ($infos as $info) {
1399            if ($info['col'] == 'o') {
1400              $tbl = $info['table'];
1401              $term_id = $this->getTermID($lang_dt);
1402              if ($pattern['operator'] != '!=') {
1403                if (preg_match('/__(T|V|G)_(.+)$/', $context, $m)) {
1404                  $context_pattern_id = $m[2];
1405                  $context_table_type = $m[1];
1406                }
1407                elseif ($context == 'where') {
1408                  $context_pattern_id = $tbl;
1409                }
1410                else {
1411                  $context_pattern_id = $pattern['id'];
1412                }
1413                if ($tbl == $context_pattern_id) {/* @todo better dependency check */
1414                  if ($term_id || ($lang_dt != 'http://www.w3.org/2001/XMLSchema#integer')) {/* skip if simple int, but no id */
1415                    $this->addConstraintSQLEntry($context_pattern_id, 'T_' . $tbl . '.o_lang_dt = ' . $term_id . ' /* ' . preg_replace('/[\#\*\>]/' , '::', $lang_dt) . ' */');
1416                  }
1417                }
1418              }
1419              break;
1420            }
1421          }
1422        }
1423      }
1424    }
1425    return trim($r);
1426  }
1427 
1428  function findSiblingVarExpression($id) {
1429    $pattern = $this->getPattern($id);
1430    do {
1431      $pattern = $this->getPattern($pattern['parent_id']);
1432    } while ($pattern['parent_id'] && ($pattern['type'] != 'expression'));
1433    $sub_patterns = $this->v('patterns', array(), $pattern);
1434    foreach ($sub_patterns as $sub_id) {
1435      $sub_pattern = $this->getPattern($sub_id);
1436      if ($sub_pattern['type'] == 'var') {
1437        return $sub_pattern['value'];
1438      }
1439    }
1440    return '';
1441  }
1442 
1443  /*  */
1444
1445  function getFunctionExpressionSQL($pattern, $context, $val_type = '', $parent_type = '') {
1446    $fnc_uri = $pattern['uri'];
1447    $op = $this->v('operator', '', $pattern);
1448    if ($op) $op .= ' ';
1449    if ($this->allow_extension_functions) {
1450      /* mysql functions */
1451      if (preg_match('/^http\:\/\/web\-semantics\.org\/ns\/mysql\/(.*)$/', $fnc_uri, $m)) {
1452        $fnc_name = strtoupper($m[1]);
1453        $sub_r = '';
1454        foreach ($pattern['args'] as $arg) {
1455          $sub_r .= $sub_r ? ', ' : '';
1456          $sub_r .= $this->getExpressionSQL($arg, $context, $val_type, $parent_type);
1457        }
1458        return $op . $fnc_name . '(' . $sub_r . ')';
1459      }
1460      /* any other: ignore */
1461    }
1462    /* simple type conversions */
1463    if (strpos($fnc_uri, 'http://www.w3.org/2001/XMLSchema#') === 0) {
1464      return $op . $this->getExpressionSQL($pattern['args'][0], $context, $val_type, $parent_type);
1465    }
1466    return '';
1467  }
1468
1469  /*  */
1470
1471  function getBuiltInCallSQL($pattern, $context) {
1472    $call = $pattern['call'];
1473    $m = 'get' . ucfirst($call) . 'CallSQL';
1474    if (method_exists($this, $m)) {
1475      return $this->$m($pattern, $context);
1476    }
1477    else {
1478      $this->addError('Unknown built-in call "' . $call . '"');
1479    }
1480    return '';
1481  }
1482 
1483  function getBoundCallSQL($pattern, $context) {
1484    $r = '';
1485    $var = $pattern['args'][0]['value'];
1486    $info = $this->getVarTableInfos($var);
1487    if (!$tbl = $info['table']) {
1488      return '';
1489    }
1490    $col = $info['col'];
1491    $tbl_alias = 'T_' . $tbl . '.' . $col;
1492    if ($pattern['operator'] == '!') {
1493      return $tbl_alias . ' IS NULL';
1494    }
1495    return $tbl_alias . ' IS NOT NULL';
1496  }
1497
1498  function getHasTypeCallSQL($pattern, $context, $type) {
1499    $r = '';
1500    $var = $pattern['args'][0]['value'];
1501    $info = $this->getVarTableInfos($var);
1502    if (!$tbl = $info['table']) {
1503      return '';
1504    }
1505    $col = $info['col'];
1506    $tbl_alias = 'T_' . $tbl . '.' . $col . '_type';
1507    return $tbl_alias . ' ' .$this->v('operator', '', $pattern) . '= ' . $type;
1508  }
1509
1510  function getIsliteralCallSQL($pattern, $context) {
1511    return $this->getHasTypeCallSQL($pattern, $context, 2);
1512  }
1513
1514  function getIsblankCallSQL($pattern, $context) {
1515    return $this->getHasTypeCallSQL($pattern, $context, 1);
1516  }
1517
1518  function getIsiriCallSQL($pattern, $context) {
1519    return $this->getHasTypeCallSQL($pattern, $context, 0);
1520  }
1521
1522  function getIsuriCallSQL($pattern, $context) {
1523    return $this->getHasTypeCallSQL($pattern, $context, 0);
1524  }
1525
1526  function getStrCallSQL($pattern, $context) {
1527    $sub_pattern = $pattern['args'][0];
1528    $sub_type = $sub_pattern['type'];
1529    $m = 'get' . ucfirst($sub_type) . 'ExpressionSQL';
1530    if (method_exists($this, $m)) {
1531      return $this->$m($sub_pattern, $context);
1532    }
1533  }
1534 
1535  function getFunctionCallSQL($pattern, $context) {
1536    $f_uri = $pattern['uri'];
1537    if (preg_match('/(integer|double|float|string)$/', $f_uri)) {/* skip conversions */
1538      $sub_pattern = $pattern['args'][0];
1539      $sub_type = $sub_pattern['type'];
1540      $m = 'get' . ucfirst($sub_type) . 'ExpressionSQL';
1541      if (method_exists($this, $m)) {
1542        return $this->$m($sub_pattern, $context);
1543      }
1544    }
1545  }
1546 
1547  function getLangDatatypeCallSQL($pattern, $context) {
1548    $r = '';
1549    if (isset($pattern['patterns'])) { /* proceed with first argument only (assumed as base type for type promotion) */
1550      $sub_pattern = array('args' => array($pattern['patterns'][0]));
1551      return $this->getLangDatatypeCallSQL($sub_pattern, $context);
1552    }
1553    if (!isset($pattern['args'])) {
1554      return 'FALSE';
1555    }
1556    $sub_type = $pattern['args'][0]['type'];
1557    if ($sub_type != 'var') {
1558      return $this->getLangDatatypeCallSQL($pattern['args'][0], $context);
1559    }
1560    $var = $pattern['args'][0]['value'];
1561    $info = $this->getVarTableInfos($var);
1562    if (!$tbl = $info['table']) {
1563      return '';
1564    }
1565    $col = 'o_lang_dt';
1566    $tbl_alias = 'V_' . $tbl . '_' . $col . '.val';
1567    if (!in_array($tbl_alias, $this->index['sub_joins'])) {
1568      $this->index['sub_joins'][] = $tbl_alias;
1569    }
1570    $op = $this->v('operator', '', $pattern);
1571    $r = trim($op . ' ' . $tbl_alias);
1572    return $r;
1573  }
1574
1575  function getDatatypeCallSQL($pattern, $context) {
1576    return '/* datatype call */ ' . $this->getLangDatatypeCallSQL($pattern, $context);
1577  }
1578
1579  function getLangCallSQL($pattern, $context) {
1580    return '/* language call */ ' . $this->getLangDatatypeCallSQL($pattern, $context);
1581  }
1582 
1583  function getLangmatchesCallSQL($pattern, $context) {
1584    if (count($pattern['args']) == 2) {
1585      $arg_1 = $pattern['args'][0];
1586      $arg_2 = $pattern['args'][1];
1587      $sub_r_1 = $this->getBuiltInCallSQL($arg_1, $context);/* adds value join */
1588      $sub_r_2 = $this->getExpressionSQL($arg_2, $context);
1589      $op = $this->v('operator', '', $pattern);
1590      if (preg_match('/^([\"\'])([^\'\"]+)/', $sub_r_2, $m)) {
1591        if ($m[2] == '*') {
1592          $r = ($op == '!') ? 'NOT (' . $sub_r_1 . ' REGEXP "^[a-zA-Z\-]+$"' . ')' : $sub_r_1 . ' REGEXP "^[a-zA-Z\-]+$"';
1593        }
1594        else {
1595          $r = ($op == '!') ? $sub_r_1 . ' NOT LIKE ' . $m[1] . $m[2] . '%' . $m[1] : $sub_r_1 . ' LIKE ' . $m[1] . $m[2] . '%' . $m[1];
1596        }
1597      }
1598      else {
1599        $r = ($op == '!') ? $sub_r_1 . ' NOT LIKE CONCAT(' . $sub_r_2 . ', "%")' : $sub_r_1 . ' LIKE CONCAT(' . $sub_r_2 . ', "%")';
1600      }
1601      return $r;
1602    }
1603    return '';
1604  }
1605 
1606  function getSametermCallSQL($pattern, $context) {
1607    if (count($pattern['args']) == 2) {
1608      $arg_1 = $pattern['args'][0];
1609      $arg_2 = $pattern['args'][1];
1610      $sub_r_1 = $this->getExpressionSQL($arg_1, 'sameterm');
1611      $sub_r_2 = $this->getExpressionSQL($arg_2, 'sameterm');
1612      $op = $this->v('operator', '', $pattern);
1613      $r = $sub_r_1 . ' ' . $op . '= ' . $sub_r_2; 
1614      return $r;
1615    }
1616    return '';
1617  }
1618 
1619  function getRegexCallSQL($pattern, $context) {
1620    $ac = count($pattern['args']);
1621    if ($ac >= 2) {
1622      foreach ($pattern['args'] as $i => $arg) {
1623        $var = 'sub_r_' . ($i + 1);
1624        $$var = $this->getExpressionSQL($arg, $context, '', 'regex');
1625      }
1626      $sub_r_3 = (isset($sub_r_3) && preg_match('/[\"\'](.+)[\"\']/', $sub_r_3, $m)) ? strtolower($m[1]) : '';
1627      $op = ($this->v('operator', '', $pattern) == '!') ? ' NOT' : '';
1628      if (!$sub_r_1 || !$sub_r_2) return '';
1629      $is_simple_search = preg_match('/^[\(\"]+(\^)?([a-z0-9\_\-\s]+)(\$)?[\)\"]+$/is', $sub_r_2, $m);
1630      $is_simple_search = preg_match('/^[\(\"]+(\^)?([^\\\*\[\]\}\{\(\)\"\'\?\+\.]+)(\$)?[\)\"]+$/is', $sub_r_2, $m);
1631      $is_o_search = preg_match('/o\.val\)*$/', $sub_r_1);
1632      /* fulltext search (may have "|") */
1633      if ($is_simple_search && $is_o_search && !$op && (strlen($m[2]) > 8) && $this->store->hasFulltextIndex()) {
1634        /* MATCH variations */
1635        if (($val_parts = preg_split('/\|/', $m[2]))) {
1636          return 'MATCH(' . trim($sub_r_1, '()') . ') AGAINST("' . join(' ', $val_parts) . '")';
1637        }
1638        else {
1639          return 'MATCH(' . trim($sub_r_1, '()') . ') AGAINST("' . $m[2] . '")';
1640        }
1641      }
1642      if (preg_match('/\|/', $sub_r_2)) $is_simple_search = 0;
1643      /* LIKE */
1644      if ($is_simple_search && ($sub_r_3 == 'i')) {
1645        $sub_r_2 = $m[1] ? $m[2] : '%' . $m[2];
1646        $sub_r_2 .= isset($m[3]) && $m[3] ? '' : '%';
1647        return $sub_r_1 . $op . ' LIKE "' . $sub_r_2 . '"';
1648      }
1649      /* REGEXP */
1650      $opt = ($sub_r_3 == 'i') ? '' : 'BINARY ';
1651      return $sub_r_1 . $op . ' REGEXP ' . $opt . $sub_r_2;
1652    }
1653    return '';
1654  }
1655 
1656  /*  */
1657
1658  function getGROUPSQL() {
1659    $r = '';
1660    $nl = "\n";
1661    $infos = $this->v('group_infos', array(), $this->infos['query']);
1662    foreach ($infos as $info) {
1663      $var = $info['value'];
1664      if ($tbl_infos = $this->getVarTableInfos($var, 0)) {
1665        $tbl_alias = $tbl_infos['table_alias'];
1666        $r .= $r ? ', ' : 'GROUP BY '; 
1667        $r .= $tbl_alias;
1668      }
1669    }
1670    $hr = '';
1671    foreach ($this->index['havings'] as $having) {
1672      $hr .= $hr ? ' AND' : ' HAVING';
1673      $hr .= '(' . $having . ')';
1674    }
1675    $r .= $hr;
1676    return $r ? $nl . $r : $r;
1677  }
1678 
1679  /*  */
1680 
1681  function getORDERSQL() {
1682    $r = '';
1683    $nl = "\n";
1684    $infos = $this->v('order_infos', array(), $this->infos['query']);
1685    foreach ($infos as $info) {
1686      $type = $info['type'];
1687      $ms = array('expression' => 'getExpressionSQL', 'built_in_call' => 'getBuiltInCallSQL', 'function_call' => 'getFunctionCallSQL');
1688      $m = isset($ms[$type]) ? $ms[$type] : 'get' . ucfirst($type) . 'ExpressionSQL';
1689      if (method_exists($this, $m)) {
1690        $sub_r = '(' . $this->$m($info, 'order') . ')';
1691        $sub_r .= $this->v('direction', '', $info) == 'desc' ? ' DESC' : '';
1692        $r .= $r ? ',' .$nl . $sub_r : $sub_r;
1693      }
1694    }
1695    return $r ? $nl . 'ORDER BY ' . $r : '';
1696  }
1697 
1698  /*  */
1699 
1700  function getLIMITSQL() {
1701    $r = '';
1702    $nl = "\n";
1703    $limit = $this->v('limit', -1, $this->infos['query']);
1704    $offset = $this->v('offset', -1, $this->infos['query']);
1705    if ($limit != -1) {
1706      $offset = ($offset == -1) ? 0 : mysql_real_escape_string($offset, $this->store->getDBCon());
1707      $r = 'LIMIT ' . $offset . ',' . $limit; 
1708    }
1709    elseif ($offset != -1) {
1710      $r = 'LIMIT ' . mysql_real_escape_string($offset, $this->store->getDBCon()) . ',999999999999'; /* mysql doesn't support stand-alone offsets .. */
1711    }
1712    return $r ? $nl . $r : '';
1713  }
1714
1715  /*  */
1716 
1717  function getValueSQL($q_tbl, $q_sql) {
1718    $r = '';
1719    /* result vars */
1720    $vars = $this->infos['query']['result_vars'];
1721    $nl = "\n";
1722    $v_tbls = array('JOIN' => array(), 'LEFT JOIN' => array());
1723    $vc = 1;
1724    foreach ($vars as $var) {
1725      $var_name = $var['var'];
1726      $r .= $r ? ',' . $nl . '  ' : '  ';
1727      $col = '';
1728      $tbl = '';
1729      if ($var_name != '*') {
1730        if (in_array($var_name, $this->infos['null_vars'])) {
1731          if (isset($this->initial_index['vars'][$var_name])) {
1732            $col = $this->initial_index['vars'][$var_name][0]['col'];
1733            $tbl = $this->initial_index['vars'][$var_name][0]['table'];
1734          }
1735          if (isset($this->initial_index['graph_vars'][$var_name])) {
1736            $col = 'g';
1737            $tbl = $this->initial_index['graph_vars'][$var_name][0]['table'];
1738          }
1739        }
1740        elseif (isset($this->index['vars'][$var_name])) {
1741          $col = $this->index['vars'][$var_name][0]['col'];
1742          $tbl = $this->index['vars'][$var_name][0]['table'];
1743        }
1744      }
1745      if ($var['aggregate']) {
1746        $r .= 'TMP.`' . $var['alias'] . '`';
1747      }
1748      else {
1749        $join_type = in_array($tbl, array_merge($this->index['from'], $this->index['join'])) ? 'JOIN' : 'LEFT JOIN';/* val may be NULL */
1750        $v_tbls[$join_type][] = array('t_col' => $col, 'q_col' => $var_name, 'vc' => $vc);
1751        $r .= 'V' . $vc . '.val AS `' . $var_name . '`';
1752        if (in_array($col, array('s', 'o'))) {
1753          if (strpos($q_sql, '`' . $var_name . ' type`')) {
1754            $r .= ', ' . $nl . '    TMP.`' . $var_name . ' type` AS `' . $var_name . ' type`';
1755            //$r .= ', ' . $nl . '    CASE TMP.`' . $var_name . ' type` WHEN 2 THEN "literal" WHEN 1 THEN "bnode" ELSE "uri" END AS `' . $var_name . ' type`';
1756          }
1757          else {
1758            $r .= ', ' . $nl . '    NULL AS `' . $var_name . ' type`';
1759          }
1760        }
1761        $vc++;
1762        if ($col == 'o') {
1763          $v_tbls[$join_type][] = array('t_col' => 'id', 'q_col' => $var_name . ' lang_dt', 'vc' => $vc);
1764          if (strpos($q_sql, '`' . $var_name . ' lang_dt`')) {
1765            $r .= ', ' .$nl. '    V' . $vc . '.val AS `' . $var_name . ' lang_dt`';
1766            $vc++;
1767          }
1768          else {
1769            $r .= ', ' .$nl. '    NULL AS `' . $var_name . ' lang_dt`';
1770          }
1771        }
1772      }
1773    }
1774    if (!$r) $r = '*';
1775    /* from */
1776    $r .= $nl . 'FROM (' . $q_tbl . ' TMP)';
1777    foreach (array('JOIN', 'LEFT JOIN') as $join_type) {
1778      foreach ($v_tbls[$join_type] as $v_tbl) {
1779        $tbl = $this->getValueTable($v_tbl['t_col']);
1780        $var_name = preg_replace('/^([^\s]+)(.*)$/', '\\1', $v_tbl['q_col']);
1781        $cur_join_type = in_array($var_name, $this->infos['null_vars']) ? 'LEFT JOIN' : $join_type;
1782        if (!strpos($q_sql, '`' . $v_tbl['q_col'].'`')) continue;
1783        $r .= $nl . ' ' . $cur_join_type . ' ' . $tbl . ' V' . $v_tbl['vc'] . ' ON (
1784            (V' . $v_tbl['vc'] . '.id = TMP.`' . $v_tbl['q_col'].'`)
1785        )';
1786      }
1787    }
1788    /* create pos columns, id needed */
1789    if ($this->v('order_infos', array(), $this->infos['query'])) {
1790      $r .= $nl . ' ORDER BY _pos_';
1791    }
1792    return 'SELECT' . $nl . $r;
1793  }
1794 
1795  /*  */
1796
1797}
1798
1799
Note: See TracBrowser for help on using the repository browser.