<?php
/**
* Branch id key
* @constant int XDBQ_ID
* @package tv2-engine
*/
define('XDBQ_ID', 0);
/**
* Branch type key
* @constant int XDBQ_TYPE
* @package tv2-engine
*/
define('XDBQ_TYPE', 2);
/**
* Branch weight key
* @constant int XDBQ_WEIGHT
* @package tv2-engine
*/
define('XDBQ_WEIGHT', 4);
/**
* Branch parent id key
* @constant int XDBQ_PARENT
* @package tv2-engine
*/
define('XDBQ_PARENT', 5);
/**
* Branch status key
* @constant int XDBQ_STATUS
* @package tv2-engine
*/
define('XDBQ_STATUS', 6);
/**
* Branch partial result key
* @constant int XDBQ_PARTIAL
* @package tv2-engine
*/
define('XDBQ_PARTIAL', 7);
/**
* Branch scope key
* @constant int XDBQ_SCOPE
* @package tv2-engine
*/
define('XDBQ_SCOPE', 8);
/**
* Branch children key
* @constant int XDBQ_CHILDREN
* @package tv2-engine
*/
define('XDBQ_CHILDREN',9);
/**
* Branch status: sleeping (partial result = all ids)
* @constant int XDBQ_SLEEP
* @package tv2-engine
*/
define('XDBQ_SLEEP',0);
/**
* Branch status: working (partial result changed)
* @constant int XDBQ_WORK
* @package tv2-engine
*/
define('XDBQ_WORK',1);
/**
* Branch type: OR branch (plus)
* @constant int XDBQ_QP
* @package tv2-engine
*/
define('XDBQ_QP',0);
/**
* Branch type: AND branch (multiply)
* @constant int XDBQ_QM
* @package tv2-engine
*/
define('XDBQ_QM',1);
/**
* Branch type: NOT branch
* @constant int XDBQ_QN
* @package tv2-engine
*/
define('XDBQ_QN',2);
/**
* Branch type: NULL branch (passes through partial result)
* @constant int XDBQ_Q0
* @package tv2-engine
*/
define('XDBQ_Q0',3);
/**
* Branch type: query on system field
* @constant int XDBQ_QS
* @package tv2-engine
*/
define('XDBQ_QS',4);
/**
* Branch type: query on index field
* @constant int XDBQ_QI
* @package tv2-engine
*/
define('XDBQ_QI',5);
/**
* Branch type: query on unindexed field (full query)
* @constant int XDBQ_QF
* @package tv2-engine
*/
define('XDBQ_QF',6);
require_once(ENGINE_DIR.'ctlSets.flib.php');
/**
* Query parser and processor.
*
* @package tv2-engine
* @author Emilis Dambauskas (hide@address.com)
* @copyright 20022003 Emilis Dambauskas under {@link http://opensource.org/licenses/artistic-license.php Artistic license}
* @version $Id: tv2Xquery.class.php,v 1.3 2003/07/12 00:52:15 lunaticlt Exp $
* @class tv2XQuery
*/
class tv2XQuery
{
/**
* Reference to {@link tv2Xdb::$fat} array
* @attribute private array $fat
*/
var $fat;
/**
* Query branch array
* @attribute private array $b
*/
var $b;
/**
* Query result
* @attribute private array $qr
*/
var $qr;
/**
* Array of all tv2Xdb document ids
* @attribute private array $iid
*/
var $iid;
/**
* Reference to {@link tv2XQuery:$iid} array
* @attribute private array $liid
*/
var $liid;
/**
* document ids indexed by document name
* @attribute private array $iname
*/
var $iname;
/**
* document ids indexed by document path
* @attribute private array $ipath
*/
var $ipath;
/**
* document ids indexed by document type
* @attribute private array $itype
*/
var $itype;
/**
* document ids indexed by document ctime
* @attribute private array $ictime
*/
var $ictime;
//var $iindex;
/**
* {@link ctlIndexer} object
* @attribute private object xi
*/
var $xi;
/**
* Experimental. document ids indexed by document version language
* @attribute private array $ilanguage
*/
var $ilanguage;
/**
* Experimental. document ids indexed by document version context
* @attribute private array $icontext
*/
var $icontext;
/**
* Experimental. document ids indexed by document version status
* @attribute private array $istatus
*/
var $istatus;
/**
* Experimental. document ids indexed by document version time
* @attribute private array $ivtime
*/
var $ivtime;
/**
* Constructor
*/
function tv2XQuery()
{
$this->ver = FALSE;
$this->ver_context = NULL;
$this->ver_filter = NULL;
}
//----------------- MAPPABLE METHODS -----------------------------------------
/**
* Error reporting
*
* @method private error
* @param string $msg - error message
* @return boolean always returns FALSE
* @use $errors
*/
function error($msg)
{
$GLOBALS['errors']->add(get_class($this).': '.$msg);
return FALSE;
}
//----------------- PRIVATE METHODS -----------------------------------------
//----------------- Wasp engine methods --------------------------------------
//*///------------------------------------------------------------------------
/**
* Sends partial result to parent branch
*
* @method private propagateUp
* @param int $p parent branch id
* @param array $r partial result
* @param mixed $c child branch id
*/
function propagateUp($p, $r, $c)
{
// update status & partial
if ($this->b[$p][XDBQ_STATUS] == XDBQ_SLEEP)
{
$this->b[$p][XDBQ_PARTIAL] = $r;
$this->b[$p][XDBQ_STATUS] = XDBQ_WORK;
}
else
{
if ($this->b[$p][XDBQ_TYPE] == XDBQ_QP)
$this->b[$p][XDBQ_PARTIAL] = ctlSetUnion($this->b[$p][XDBQ_PARTIAL], $r);
else if ($this->b[$p][XDBQ_TYPE] == XDBQ_QM)
$this->b[$p][XDBQ_PARTIAL] = ctlSetIntersect($this->b[$p][XDBQ_PARTIAL], $r);
}
// update scope
if ($this->b[$p][XDBQ_TYPE] == XDBQ_QP)
$this->b[$p][XDBQ_SCOPE] = ctlSetSubtract($this->b[$p][XDBQ_SCOPE], $this->b[$p][XDBQ_PARTIAL]);
else if ($this->b[$p][XDBQ_TYPE] == XDBQ_QM)
$this->b[$p][XDBQ_SCOPE] = $this->b[$p][XDBQ_PARTIAL];
// unset C if finished
if (($this->b[$c][XDBQ_TYPE] != XDBQ_QP && $this->b[$c][XDBQ_TYPE] != XDBQ_QM) XOR !sizeof($this->b[$c][XDBQ_CHILDREN]))
{
foreach ($this->b[$p][XDBQ_CHILDREN] as $k => $v)
if ($v == $c)
unset($this->b[$p][XDBQ_CHILDREN][$k]);
unset($this->b[$c]);
}
// if finished propagate up or return result
if (!sizeof($this->b[$p][XDBQ_CHILDREN]))
if ($this->b[$p][XDBQ_PARENT] !== NULL)
$this->propagateUp($this->b[$p][XDBQ_PARENT], $this->b[$p][XDBQ_PARTIAL], $p);
else
$this->fillResult($this->b[$p][XDBQ_PARTIAL]);
// else propagate down scope
else
foreach ($this->b[$p][XDBQ_CHILDREN] as $v)
$this->propagateDown($v, $this->b[$p][XDBQ_SCOPE]);
}
//*///------------------------------------------------------------------------
/**
* updates scope on child branch
*
* @method private propagateDown
* @param int $c child branch id
* @param array $s scope array
*/
function propagateDown($c, $s)
{
if ($this->b[$c][XDBQ_TYPE] == XDBQ_QP)
{
$this->b[$c][XDBQ_PARTIAL] = ctlSetIntersect($this->b[$c][XDBQ_PARTIAL], $s);
$this->b[$c][XDBQ_SCOPE] = ctlSetIntersect($this->b[$c][XDBQ_SCOPE], $s);
foreach ($this->b[$c][XDBQ_CHILDREN] as $v)
$this->propagateDown($v, $this->b[$c][XDBQ_SCOPE]);
}
else if ($this->b[$c][XDBQ_TYPE] == XDBQ_QM)
{
$this->b[$c][XDBQ_SCOPE] = $this->b[$c][XDBQ_PARTIAL] = ctlSetIntersect($this->b[$c][XDBQ_PARTIAL], $s);
foreach ($this->b[$c][XDBQ_CHILDREN] as $v)
$this->propagateDown($v, $this->b[$c][XDBQ_SCOPE]);
}
else
$this->b[$c][XDBQ_SCOPE] = $s;
}
//*///------------------------------------------------------------------------
/**
* Executes unknown branch
*
* @method private xQuery
* @param int $i branch id
*/
function xQuery($i)
{
// count result
/*if ($this->b[$i][XDBQ_TYPE] == XDBQ_QF)
$this->_fQuery($i);
else*/
if ($this->b[$i][XDBQ_TYPE] == XDBQ_QI) // indexed query
$this->iQuery($i);
else if ($this->b[$i][XDBQ_TYPE] == XDBQ_QS) // system query
$this->sQuery($i);
else
{
if (!sizeof($this->b[$i][XDBQ_CHILDREN]))
{
$this->fillResult($this->b[$i][XDBQ_PARTIAL]);
unset($this->b[$i]);
}
else
{
sort($this->b[$i][XDBQ_CHILDREN]);
foreach ($this->b[$i][XDBQ_CHILDREN] as $c)
$this->xQuery($c);
}
}
}
//*///------------------------------------------------------------------------
/**
* Executes system query branch
*
* @method private sQuery
* @param int $i branch id
* @todo implement actions != nm
*/
function sQuery($i)
{
/*
$branch[XDBQ_CHILDREN] array consists of:
0: system field name
1: action (one of =/!=/m/nm/>=/>/<\/</
2: action argument
this means query portion "name='myname'" translates into:
array('name','=','myname')
*/
// link corresponding key to $k:
$kn = 'i'.$this->b[$i][XDBQ_CHILDREN][0];
$k = &$this->$kn;
$this->b[$i][XDBQ_PARTIAL] = array();
// fill partial according to action:
switch ($this->b[$i][XDBQ_CHILDREN][1])
{
case '=':
$this->b[$i][XDBQ_PARTIAL] = @$k[$this->b[$i][XDBQ_CHILDREN][2]];
break;
case 'm':
$keys = array_values(preg_grep('/'.addCslashes($this->b[$i][XDBQ_CHILDREN][2], '/').'/', array_keys($k)));
foreach ($keys as $key)
$this->b[$i][XDBQ_PARTIAL] = array_merge($this->b[$i][XDBQ_PARTIAL], @$k[$key]);
break;
case '>=':
$this->b[$i][XDBQ_PARTIAL] = @$k[$this->b[$i][XDBQ_CHILDREN][2]];
case '>':
$keys = array_keys($k);
foreach ($keys as $key)
if ($key > $this->b[$i][XDBQ_CHILDREN][2])
$this->b[$i][XDBQ_PARTIAL] = array_merge($this->b[$i][XDBQ_PARTIAL], @$k[$key]);
break;
case '<=':
$this->b[$i][XDBQ_PARTIAL] = @$k[$this->b[$i][XDBQ_CHILDREN][2]];
case '<':
$keys = array_keys($k);
foreach ($keys as $key)
if ($key < $this->b[$i][XDBQ_CHILDREN][2])
$this->b[$i][XDBQ_PARTIAL] = array_merge($this->b[$i][XDBQ_PARTIAL], @$k[$key]);
break;
case '!=':
case 'nm':
/*case 'e': // system fields always exist
case 'ne':*/
default:
$this->b[$i][XDBQ_PARTIAL] = array();
}
// send partial result to parent or return result
if ($this->b[$i][XDBQ_PARENT] !== NULL)
$this->propagateUp($this->b[$i][XDBQ_PARENT], $this->b[$i][XDBQ_PARTIAL], $i);
else
{
$this->fillResult($this->b[$i][XDBQ_PARTIAL]);
unset($this->b[$i]);
}
}
//*///------------------------------------------------------------------------
/**
* Executes indexed query branch
*
* @method private iQuery
* @param int $i branch id
* @todo check search operations nm,e,ne,>,>=,<,<=
*/
function iQuery($i)
{
if (!$this->loadXIndex())
$this->b[$i][XDBQ_PARTIAL] = array();
else
{
// fill partial according to action:
switch ($this->b[$i][XDBQ_CHILDREN][1])
{
case '=':
$this->b[$i][XDBQ_PARTIAL] = $this->xi->getByValue($this->b[$i][XDBQ_CHILDREN][0],$this->b[$i][XDBQ_CHILDREN][2]);
break;
case '!=':
$r1 = $this->xi->getByValue($this->b[$i][XDBQ_CHILDREN][0],$this->b[$i][XDBQ_CHILDREN][2]);
$this->b[$i][XDBQ_PARTIAL] = array_values(array_diff($this->b[$i][XDBQ_PARTIAL],$r1));
break;
case 'm':
if (is_array($this->b[$i][XDBQ_CHILDREN][2]))
$this->b[$i][XDBQ_CHILDREN][2] = '/'.$this->b[$i][XDBQ_CHILDREN][2][0].'/'.$this->b[$i][XDBQ_CHILDREN][2][1];
else
$this->b[$i][XDBQ_CHILDREN][2] = '/'.$this->b[$i][XDBQ_CHILDREN][2].'/';
$this->b[$i][XDBQ_PARTIAL] = $this->xi->getByPreg($this->b[$i][XDBQ_CHILDREN][0],$this->b[$i][XDBQ_CHILDREN][2]);
break;
case 'nm':
if (is_array($this->b[$i][XDBQ_CHILDREN][2]))
$this->b[$i][XDBQ_CHILDREN][2] = '/'.$this->b[$i][XDBQ_CHILDREN][2][0].'/'.$this->b[$i][XDBQ_CHILDREN][2][1];
else
$this->b[$i][XDBQ_CHILDREN][2] = '/'.$this->b[$i][XDBQ_CHILDREN][2].'/';
$r1 = $this->xi->getByPreg($this->b[$i][XDBQ_CHILDREN][0],$this->b[$i][XDBQ_CHILDREN][2]);
$this->b[$i][XDBQ_PARTIAL] = array_values(array_diff($this->b[$i][XDBQ_PARTIAL],$r1));
break;
case 'e':
$this->b[$i][XDBQ_PARTIAL] = $this->xi->getByIndex($this->b[$i][XDBQ_CHILDREN][0]);
break;
case 'ne':
$r1 = $this->xi->getByIndex($this->b[$i][XDBQ_CHILDREN][0]);
$this->b[$i][XDBQ_PARTIAL] = array_values(array_diff($this->b[$i][XDBQ_PARTIAL],$r1));
break;
case '>=':
$this->b[$i][XDBQ_PARTIAL] = $this->xi->getByRange($this->b[$i][XDBQ_CHILDREN][0],$this->b[$i][XDBQ_CHILDREN][2]);
break;
case '>':
$this->b[$i][XDBQ_PARTIAL] = $this->xi->getByRange($this->b[$i][XDBQ_CHILDREN][0],$this->b[$i][XDBQ_CHILDREN][2], 1);
break;
case '<=':
$this->b[$i][XDBQ_PARTIAL] = $this->xi->getByRange(NULL,NULL,$this->b[$i][XDBQ_CHILDREN][0],$this->b[$i][XDBQ_CHILDREN][2]);
break;
case '<':
$this->b[$i][XDBQ_PARTIAL] = $this->xi->getByRange(NULL,NULL,$this->b[$i][XDBQ_CHILDREN][0],$this->b[$i][XDBQ_CHILDREN][2],1);
break;
default:
$this->b[$i][XDBQ_PARTIAL] = array();
}
}
if ($this->b[$i][XDBQ_PARENT] !== NULL)
$this->propagateUp($this->b[$i][XDBQ_PARENT], $this->b[$i][XDBQ_PARTIAL], $i);
else
{
$this->fillResult($this->b[$i][XDBQ_PARTIAL]);
unset($this->b[$i]);
}
}
//*///------------------------------------------------------------------------
/**
* Sorts branch array according to branch weights
*
* @method private sortB
* @return array sorted branch key array
*/
function sortB()
{
$bk = array_keys($this->b);
$wk = array();
$nb = array();
foreach ($bk as $i)
$wk[] = $this->b[$i][XDBQ_WEIGHT];
array_multisort($wk, $bk);
return $bk;
}
//*///------------------------------------------------------------------------
/**
* Removes NULL branches from branch array
*
* @method private removeNull
*/
function removeNull()
{
$bs = sizeof($this->b);
for ($i=0;$i<$bs;$i++)
if ($this->b[$i][XDBQ_TYPE] == XDBQ_Q0)
{
if ($this->b[$i][XDBQ_PARENT] !== NULL)
{
@$pk = @array_search($i, $this->b[$this->b[$i][XDBQ_PARENT]][XDBQ_CHILDREN]);
@$this->b[$this->b[$i][XDBQ_PARENT]][XDBQ_CHILDREN][$pk] = $this->b[$i][XDBQ_CHILDREN][0];
}
@$this->b[$this->b[$i][XDBQ_CHILDREN][0]][XDBQ_PARENT] = $this->b[$i][XDBQ_PARENT];
unset($this->b[$i]);
}
}
//*///------------------------------------------------------------------------
/**
* Inserts branch into branch array
*
* @method private addBNode
* @return int new branch id
* @param int $t branch type
* @param int $p parent branch id
* @param optional array $c children array
*/
function addBNode($t, $p, $c = array())
{
$id = sizeof($this->b);
/*
count branch weight based on it's type:
- system queries are fastest
- indexed queries are slower
- and opening every document (XDBQ_QF) would be very slow
- all other branches are logic ones, so they do not count
*/
if ($t == XDBQ_QS)
$w = 0.1;
else if ($t == XDBQ_QI)
$w = 0.25;
else if ($t == XDBQ_QF)
$w = 5;
else
$w = 0;
// let's fill in the branch with values
$this->b[$id] = array(
XDBQ_ID => $id, // branch id - key in branch array $this->b
XDBQ_TYPE => $t, // branch type
XDBQ_WEIGHT => $w, // branch weight
XDBQ_PARENT => $p, // parent branch id
XDBQ_STATUS => XDBQ_SLEEP, // we're not working on this branch
XDBQ_PARTIAL => array(), // partial result array
XDBQ_SCOPE => &$this->liid, // default scope -- all document ids
XDBQ_CHILDREN => $c); // children array. note: this has different meaning
// for query branches and logic branches
// add this branch to it's parent and recount parent weight
if ($p !== NULL)
{
$this->b[$p][XDBQ_CHILDREN][] = $id;
if ($w)
{
if (!$this->b[$p][XDBQ_WEIGHT])
$this->b[$p][XDBQ_WEIGHT] = $w;
else
{
if ($this->b[$p][XDBQ_TYPE] == XDBQ_QM)
$this->b[$p][XDBQ_WEIGHT] = $this->b[$p][XDBQ_WEIGHT] * $w;
else if ($this->b[$p][XDBQ_TYPE] == XDBQ_QP)
$this->b[$p][XDBQ_WEIGHT] = $this->b[$p][XDBQ_WEIGHT] + $w;
else if ($w > $this->b[$p][XDBQ_WEIGHT])
$this->b[$p][XDBQ_WEIGHT] = $w;
}
}
}
return $id;
}
//*///------------------------------------------------------------------------
/**
* Adds system query branch
*
* @method private addBQS
* @return int new branch id
* @param ref string $q query being parsed
* @param int $p parent branch id
*/
function addBQS(&$q, $p)
{
// find out system field name:
switch ($this->bgn($q, 4))
{
case 'ctim':
$c[] = 'ctime';
$len = 5;
break;
case 'lang':
$c[] = 'language';
$len = 8;
break;
case 'stat':
$c[] = 'status';
$len = 6;
break;
case 'cont':
$c[] = 'context';
$len = 7;
break;
default:
$c[] = $this->bgn($q, 4); // this includes 'name', 'type', 'path' and so on
$len = 4;
}
// cut off the field name from the query string:
$q = ltrim(substr($q, $len));
// find out operation:
$c[] = $this->matchOP($q);
// find out value
$c[] = $this->matchVAL($q);
// add a system query branch with filled children array:
return $this->addBNode(XDBQ_QS, $p, $c);
}
//*///------------------------------------------------------------------------
/**
* Add indexed query branch
*
* @method private addBQI
* @return int new branch id
* @param ref string $q query being parsed
* @param int $p parent branch id
*/
function addBQI(&$q, $p)
{
$q = substr($q, 6); // cut off 'index('
$sp = strpos($q, ')'); // find next ')'
$c[] = substr($q, 0, $sp); // cut out field name between index( and )
$q = substr($q, $sp+1); // cut off remaining parenthesis
// find out operation:
$c[] = $this->matchOP($q);
// find out value:
$c[] = $this->matchVAL($q);
// add an indexed query branch with filled children array
return $this->addBNode(XDBQ_QI, $p, $c);
}
//*///------------------------------------------------------------------------
/**
* Adds full query branch
*
* @method private addBQF
* @return int new node id
* @param ref string query being parsed
* @param int $p parent node id
*/
function addBQF(&$q, $p)
{
$q = substr($q, 5); // cut off 'full('
$sp = strpos($q, ')'); // find next ')'
$c[] = substr($q, 0, $sp); // cut out field name between full( and )
$q = substr($q, $sp+1); // cut off remaining parenthesis
// find out operation:
$c[] = $this->matchOP($q);
// find out value:
$c[] = $this->matchVAL($q);
// ad a full query branch with filled children array
return $this->addBNode(XDBQ_QF, $p, $c);
}
//*///------------------------------------------------------------------------
/**
* return up to $l symbols from the begining of the string lowercased
*
* @method private bgn
* @return string portion of the string
* @param string $s input string
* @param int $l portion length
*/
function bgn($s, $l)
{
return strtolower(substr($s, 0, $l));
}
//*///------------------------------------------------------------------------
/**
* Takes operation from the query and returns it in internal format
*
* @method private matchOP
* @return string internal operation representation
* @param ref string $q query string
*/
function matchOP(&$q)
{
$q = ltrim($q);
$r = '';
if ($q{0} == '=') {$r = '='; $q=substr($q, 1);} // s+i-x-
else if ($this->bgn($q, 2) == '!=' || $this->bgn($q, 2) == '<>') {$r = '!='; $q=substr($q, 2);} // s-i-x-
else if ($this->bgn($q, 2) == '>=') {$r = '>='; $q=substr($q, 2);} // s+i-x-
else if ($q{0} == '>') {$r = '>'; $q=substr($q, 1);} // s+i-x-
else if ($this->bgn($q, 2) == '<=') {$r = '<='; $q=substr($q, 2);} // s+i-x-
else if ($q{0} == '<') {$r = '<'; $q=substr($q, 1);} // s+i-x-
else if ($this->bgn($q, 7) == 'matches') {$r = 'm'; $q=substr($q, 7);} // s+i-x-
else if ($this->bgn($q, 8) == '!matches') {$r = 'nm'; $q=substr($q, 8);} // s-i-x-
else if ($this->bgn($q, 6) == 'exists') {$r = 'e'; $q=substr($q, 6);} // s-i-x-
else if ($this->bgn($q, 7) == '!exists') {$r = 'ne'; $q=substr($q, 7);} // s-i-x-
else return $this->error('unidentified string in matchOP: '.$q.'.');
$q = ltrim($q);
return $r;
}
//*///------------------------------------------------------------------------
/**
* Takes value from the query string
*
* @method private matchVAL
* @return mixed string value or regexp: array('pattern', 'modifiers')
* @param ref string $q query string
*/
function matchVAL(&$q)
{
$r = '';
// if numeric value:
if (is_numeric($q{0}))
{
while (is_numeric($q{0}))
{
$r .= $q{0};
$q = substr($q, 1);
}
}
// if string or regexp value:
else if ($q{0} == '\'')
{
$ok = TRUE;
while ($ok)
{
$q = substr($q, 1);
if ($q{0} == '\'' && substr($r, -1) != '\\')
if (@strstr('imsxeADSUXn', $q{1}))
{
$q = substr($q, 1);
$sp1 = strpos($q, ' ');
$sp2 = strpos($q, ')');
if ($sp1 === FALSE)
{
if ($sp2 === FALSE)
{
$pm = $q;
$q = '';
}
else
{
$pm = substr($q, 0, $sp2);
$q = substr($q, $sp2);
}
}
else if ($sp2 === FALSE)
{
$pm = substr($q, 0, $sp1);
$q = substr($q, $sp1);
}
else
{
if ($sp2 < $sp1)
$sp1 = $sp2;
$pm = substr($q, 0, $sp1);
$q = substr($q, $sp1);
}
$ok = FALSE;
}
else
{
$q = substr($q, 1);
$ok = FALSE;
}
else
$r .= $q{0};
}
}
else
return $this->error('bad query in matchVAL: '.$q.'.'); //return_result('ERROR');
if (isset($pm))
return array($r, $pm);
else
return $r;
}
//*///------------------------------------------------------------------------
/**
* Main query parsing loop
*
* @method private parseQuery
* @return boolean FALSE on error, NULL otherwise
* @param string $q query string
*/
function parseQuery($q)
{
$i = 0;
// clear branch array
$this->b = array();
// first of all we ad a NULL branch:
$this->addBNode(XDBQ_Q0, NULL);
/*
this loop imlpements query logic: where in the query string can you expect a
logic operator, where parenthesis, where subquery and so on.
portions of the query string are cut off from it's begining when branches
are added to the query branch tree.
$i is a branch counter, which increases with every new branch.
NULL branches (XDBQ_Q0) are added in places where you can expect a nested
parenthesis.
*/
while (strlen($q)) // work until we fully parse query string
{
$q = ltrim($q);
switch ($this->b[$i][XDBQ_TYPE])
{
case XDBQ_Q0:
case XDBQ_QN:
$i = $this->addBNode(XDBQ_Q0, $i);
case XDBQ_QP:
$i = $this->addBNode(XDBQ_Q0, $i);
case XDBQ_QM:
if ($q{0} == '(')
{
$i = $this->addBNode(XDBQ_Q0,$i);
$q = substr($q, 1);
}
else if ($this->bgn($q, 4) == 'not(')
$i = $this->addBNode(XDBQ_QN,$i);
else if ($this->bgn($q, 4) == 'name' || $this->bgn($q, 4) == 'path' || $this->bgn($q, 4) == 'type' || $this->bgn($q, 5) == 'ctime' || $this->bgn($q, 8) == 'language' || $this->bgn($q, 7) == 'context' || $this->bgn($q, 6) == 'status')
$i = $this->addBQS($q,$i);
else if ($this->bgn($q, 6) == 'index(')
$i = $this->addBQI($q,$i);
else if ($this->bgn($q, 5) == 'full(')
$i = $this->addBQF($q,$i);
else
return $this->error('unknown branch in parseQuery: '.$q.'.');
break;
case XDBQ_QS:
case XDBQ_QI:
case XDBQ_QF:
$i = $this->b[$i][XDBQ_PARENT];
if ($q{0} == ')')
{
$i = $this->b[$i][XDBQ_PARENT];
$i = $this->b[$i][XDBQ_PARENT];
$q = substr($q, 1);
}
else if ($this->bgn($q, 2) == 'or')
{
$i = $this->b[$i][XDBQ_PARENT];
$this->b[$i][XDBQ_TYPE] = XDBQ_QP;
$q = substr($q, 2);
}
else if ($this->bgn($q, 3) == 'and')
{
$this->b[$i][XDBQ_TYPE] = XDBQ_QM;
$q = substr($q, 3);
}
else
return $this->error('unknown branch in parseQuery: '.$q.'.');
break;
default:
return $this->error('unknown active branch type in parseQuery: '.$this->_b[$i][XDBQ_TYPE].'.');
}
}
}
//*///------------------------------------------------------------------------
/**
* Fills result array
*
* @method private fillResult
* @param array $r result array
*/
function fillResult($r)
{
if (is_array($r))
$this->qr = $r;
}
//*///------------------------------------------------------------------------
//----------------- PUBLIC METHODS -----------------------------------------
/**
* Fills system indexes with data, creates reference to all ids array
*
* @method public load
* @return boolean always TRUE
* @param ref array fat array from {@link tv2Xdb::$fat}
*/
function load(&$fat)
{
$this->fat = $fat;
foreach ($this->fat as $id=>$ttfat)
{
$this->iname[$ttfat['name']][] = $id;
$this->ipath[$ttfat['path']][] = $id;
$this->itype[$ttfat['type']][] = $id;
$this->ictime[@$ttfat['ctime']][] = $id;
}
$this->iid = array_keys($this->fat);
$this->liid = &$this->iid;
if ($this->ver)
{
$oids = array_keys($this->ver_index);
$viid = array_values(array_diff($this->iid, $oids));
$this->icontext[$this->ver_context] = $viid;
$this->ilanguage[$this->ver['language']] = $viid;
$this->istatus[$this->ver['status']] = $viid;
$this->ivtime[0] = $viid;
foreach ($this->ver_index as $id => $ttvi)
{
foreach ($ttvi as $tvi)
{
$this->ilanguage[$tvi['language']][] = $id;
$this->icontext[$tvi['context']][] = $id;
$this->istatus[$tvi['status']][] = $id;
$this->ivtime[$tvi['vtime']][] = $id;
}
} // end of foreach
} // end of versioning
return TRUE;
}
/**
* Queries Xdb using SQL-like synthax
*
* @method public query
* @param string $q SQL like query
* @param optional array $o order by: array('field', 'ASC'/'DESC')
* @param optional int $s start from (default 0)
* @param optional int $l limit result number to
* @return array document ID array
*/
function query($q, $o = NULL, $s = 0, $l = NULL)
{
// clean up nodes (b for branches)
$this->b = array();
// clean up result
$this->qr = array();
// parse query
$this->parseQuery($q);
// remove NULL branches
$this->removeNull();
// generate results for all system (s) branches:
$bk = array_keys($this->b);
foreach($bk as $i)
if (@$this->b[$i][XDBQ_TYPE] == XDBQ_QS)
$this->sQuery($i);
// if we have not result yet, sort other branches by their weights and generate results
if (!sizeof($this->qr))
{
$bk = $this->sortB();
foreach($bk as $i)
if (isset($this->b[$i]))
$this->xQuery($i);
}
// if result empty, return empty array
if (!is_array($this->qr) || !sizeof($this->qr))
return array();
// ORDER results
if (is_array($o) && sizeof($o))
{
if (@$o[1] == 'DESC')
$order = SORT_DESC;
else
$order = SORT_ASC;
if ($o[0] == 'name' || $o[0] == 'path' || $o[0] == 'type' || $o[0] == 'ctime')
foreach ($this->qr as $id)
$sa[] = $this->fat[$id][$o[0]];
else if ($o[0] == 'ident')
foreach ($this->qr as $id)
$sa[] = $this->fat[$id]['path'].$this->fat[$id]['name'];
else
return $this->error('Ordering results by non-system fields not implemented yet.');
array_multisort($sa, $order, $this->qr);
}
// LIMIT results
if ($s)
$this->qr = array_slice($this->qr, $s);
if ($l !== NULL)
$this->qr = array_slice($this->qr, 0, $l);
// return result
return $this->qr;
}
/**
* Loads {@link ctlIndexer} object into parameter $xi
*
* @method private loadXIndex
* @return boolean TRUE on success, FALSE on failure
* @use $config
*/
function loadXIndex()
{
if (isset($this->xi))
return TRUE;
global $config;
include_once(ENGINE_DIR.'ctlIndexer.class.php');
$this->xi = &new ctlIndexer();
return $this->xi->load($config['xindex']);
}
}
?>