Xataface 2.0
Xataface Application Framework
Dataface/QueryBuilder.php
Go to the documentation of this file.
00001 <?php
00002 /*-------------------------------------------------------------------------------
00003  * Xataface Web Application Framework
00004  * Copyright (C) 2005-2008 Web Lite Solutions Corp (shannah@sfu.ca)
00005  * 
00006  * This program is free software; you can redistribute it and/or
00007  * modify it under the terms of the GNU General Public License
00008  * as published by the Free Software Foundation; either version 2
00009  * of the License, or (at your option) any later version.
00010  * 
00011  * This program is distributed in the hope that it will be useful,
00012  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00013  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00014  * GNU General Public License for more details.
00015  * 
00016  * You should have received a copy of the GNU General Public License
00017  * along with this program; if not, write to the Free Software
00018  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
00019  *-------------------------------------------------------------------------------
00020  */
00021 
00022 /*******************************************************************************
00023  * File:                Dataface/QueryBuilder.php
00024  * Author:              Steve Hannah <shannah@sfu.ca>
00025  * Created:     Sept. 2, 2005
00026  * Description:
00027  *      Builds SQL queries based on key-value pair queries.
00028  *      
00029  ******************************************************************************/
00030  
00031 import( 'PEAR.php'); 
00032 import( 'Dataface/Table.php');
00033 import( 'Dataface/Error.php');
00034 import( 'Dataface/Serializer.php');
00035 import('Dataface/DB.php'); // for Blob registry.
00036 
00037 define('QUERYBUILDER_ERROR_EMPTY_SELECT', 1);
00038  
00039 class Dataface_QueryBuilder {
00040 
00050         var $_query;
00051         
00055         var $_tablename;
00056         
00060         var $_table;
00061         
00065         var $_fields;
00066         
00067         var $_mutableFields;
00068         
00072         var $_exactMatches = false;
00073         
00077         var $_omitBlobs = true;
00078         
00082         var $_omitPasswords = true;
00083         
00090         var $_security = array();
00091         
00092         
00093         var $_serializer;
00094         
00095         var $errors = array();
00096         
00097         /*
00098          * Whether or not meta data should be selected along with select statements.
00099          * Meta data includes calculations of the "lengths" of the data in particular
00100          * fields.  This is particularly handy for blob fields where we are interested
00101          * in the size of the blob.
00102          */
00103         var $selectMetaData = false;
00104         
00108         var $metadata = false;
00109         
00110         
00117         var $action = null;
00118         
00119         
00120                 
00121 
00122         
00130         function Dataface_QueryBuilder($tablename, $query=''){
00131                 $this->_tablename = $tablename;
00132                 $this->_table =& Dataface_Table::loadTable($tablename);
00133                 $this->_query = is_array($query) ? $query : array();
00134                 $this->_fields =& $this->_table->fields(false, true);
00135                 $this->_mutableFields =& $this->_table->fields();
00136                 $this->_serializer = new Dataface_Serializer($tablename);
00137                 $this->action = null;
00138                 
00139                 $app =& Dataface_Application::getInstance();
00140                 if ( @$app->_conf['metadata_enabled'] ){
00141                         $this->metadata = true;
00142                 }
00143                 
00144                 $keys = array_keys( $this->_query );
00145                 foreach ($keys as $key){
00146                         if ( $this->_query[$key] === ''){
00147                                 unset( $this->_query[$key] );
00148                         }
00149                 }
00150                 
00151                 
00152                 if ( isset( $GLOBALS['DATAFACE_QUERYBUILDER_SECURITY_CONSTRAINTS'][$tablename]) ){
00153                         $this->_security = $GLOBALS['DATAFACE_QUERYBUILDER_SECURITY_CONSTRAINTS'][$tablename];
00154                 }
00155                 
00156                 
00157         }
00158         
00159         function _opt($code, $isText=true){
00160                 switch ( $code ){
00161                         case '=':
00162                         case '>':
00163                         case '<':
00164                         case '>=':
00165                         case '<=':
00166                                 return $code;
00167                         default:
00168                                 return 'LIKE';
00169                 }
00170         }
00171         
00172         
00173         
00192         function search($queryStr, $columns='', $queryParams=array(), $nolimit=false){
00193                 $this->action='search';
00194                 $ret = $this->_select($columns);
00195                 $from = trim($this->_from($this->_table->tablename));
00196                 $indexOnly = ( (isset($queryParams['-ignoreIndex']) and $queryParams['-useIndex']) ? false : true );
00197                 $where = "WHERE ".$this->_match($queryStr, $indexOnly);
00198                 $from = trim( $this->_from($this->_table->tablename));
00199                 $order = trim($this->_orderby($queryParams));
00200                 $limit = trim($this->_limit($queryParams, $nolimit));
00201                 if ( strlen($from)>0 ) $ret .= ' '.$from;
00202                 if ( strlen($where)>0 ) $ret .= ' '.$where;
00203                 if ( strlen($order)>0 ) $ret .= ' '.$order;
00204                 if ( strlen($limit)>0 ) $ret .= ' '.$limit;
00205                 $this->action = null;
00206                 return $ret;
00207                 
00208         
00209         }
00215         function select($columns='', $query=array(), $nolimit=false, $tablename=null, $preview=false){
00216                 $this->action="select";
00217                 if ( !is_array($query) ){
00218                         $query = array();
00219                 }
00220                 $query = array_merge( $this->_query, $query);
00221                 
00222                 $ret = $this->_select($columns, array(), $preview);
00223                 if ( PEAR::isError($ret) ){
00224                         $ret->addUserInfo("Failed to select columns in select() ");
00225                         
00226                         return $ret;
00227                 }
00228                 $from = trim($this->_from($tablename));
00229                 $where = trim($this->_where($query));
00230                 $where = $this->_secure($where);
00231                 //$having = $this->_having($query);
00232                 
00233                 $order = trim($this->_orderby($query));
00234                 $limit = trim($this->_limit($query, $nolimit));
00235                 
00236                 if ( strlen($from)>0 ) $ret .= ' '.$from;
00237                 if ( strlen($where)>0 ) $ret .= ' '.$where;
00238                 //if ( strlen($having)>0 ) $ret .= ' '.$having;
00239                 if ( strlen($order)>0 ) $ret .= ' '.$order;
00240                 if ( strlen($limit)>0 ) $ret .= ' '.$limit;
00241                 $this->action = null;
00242                 //echo $ret;
00243                 return $ret;
00244         }
00245         
00252         function select_num_rows($query=array(), $tablename=null){
00253                 
00254                 $this->action='select_num_rows';
00255                 $query = array_merge( $this->_query, $query);
00256                 $ret = 'SELECT COUNT(*) as num';
00257                 $from = $this->_from($this->tablename($tablename));
00258                 $where = $this->_where($query);
00259                 $where = $this->_secure($where);
00260                 
00261                 if ( strlen($from)>0 ) $ret .= ' '.$from;
00262                 if ( strlen($where)>0 ) $ret .= ' '.$where;
00263                 $this->action = null;
00264                 return trim($ret);
00265         
00266         
00267         }
00268         
00269         
00276         function update(&$record, $key_vals=null, $tablename=null){
00277                 $app =& Dataface_Application::getInstance();
00278                 $this->action='update';
00279                 // Step 1:  Make sure that the input is valid
00280                 if ( !is_a($record, "Dataface_Record") ){
00281                         throw new Exception("Attempt to use QueryBuilder::update() where something other than a Dataface_Record object is defined.", E_USER_ERROR);
00282                 }
00283                 
00284                 // Step 2: Start building the sql query.  We use an array to build up the query, but 
00285                 // string concatenation would work just as well.  We switched to arrays in an attempt to 
00286                 // fix a bug related to updating BLOB fields, but it turned out that the problem was different.
00287                 $tableObj =& Dataface_Table::loadTable($this->tablename($tablename));
00288                 $dbObj =& Dataface_DB::getInstance();
00289                 $sql = array();
00290                 $sql[] = "UPDATE `".$this->tablename($tablename)."` SET ";
00291                 $fieldnames = array_keys($this->_mutableFields);
00292                         // Get all of the field names in this table.
00293                 $keys = array_keys($this->_table->keys());
00294                         // Get the names of the key fields in this table
00295                 $changed = false;
00296                         // A flag to indicate whether the record has been changed.
00297                 foreach ($fieldnames as $fieldname){
00298                         if ( isset($fieldArr) ) unset($fieldArr);
00299                         $fieldArr =& $tableObj->getField($fieldname);
00300                         if ( @$fieldArr['ignore'] ) continue;
00301                         if ( !$record->valueChanged($fieldname)  and !(isset($fieldArr['timestamp']) and strtolower($fieldArr['timestamp']) == 'update') ) {
00302                                 // If this field has not been changed then we don't need to update it.
00303                                 // Note that in order for valueChanged() to work properly, the Dataface_Record::setSnapshot()
00304                                 // method must be called at somepoint to indicate that that snapshot represents the last unchanged
00305                                 // state of the record.
00306                                 continue;
00307                         }
00308                         //echo "$fieldname changed\n";
00309                         
00310                         // If we made it this far, then the current field has indeed changed
00311                         $changed = true;
00312                         
00313                         $sval = $this->_serializer->serialize($fieldname, $record->getValue($fieldname));
00314                                 // Serialize the field's value to prepare it for the database
00315                         if ( !isset($sval) and @$fieldArr['timestamp'] != 'update' ){
00316                                 $sql[] = "`$fieldname` = NULL, ";
00317                         } else if ( $tableObj->isBlob($fieldname) and @$app->_conf['multilingual_content']){
00318                                 // This is a blob column... we don't place the data directly in the String because it would take
00319                                 // too long to parse when Dataface_DB needs to parse it.
00320                                 // Instead we register the BLOB and store its id number.
00321                                 $blobID = $dbObj->registerBlob($sval);
00322                                 $sql[] = "`$fieldname` = '-=-=B".$blobID."=-=-', ";
00323                         } else if ( $tableObj->isDate($fieldname)  and 
00324                                         isset($fieldArr['timestamp']) and 
00325                                         strtolower($fieldArr['timestamp']) == 'update'){
00326                                 $sql[] = "`$fieldname` = NOW(), ";
00327                                 
00328                         } else {
00329                                 $sql[] = "`$fieldname` = ".$this->prepareValue($fieldname, $sval).", ";
00330                         }               
00331                 }
00332                 if ( !$changed ) return '';
00333                         // If no fields have changed, then we will just return an empty string for the query.
00334                 
00335                 
00336                 $sql[count($sql)-1] = substr($sql[count($sql)-1], 0, strlen($sql[count($sql)-1])-2);
00337                         // chop off the trailing comma from the update clause
00338                 $vals = $record->snapshotExists() ? $record->snapshotKeys() : $record->getValues( array_keys($this->_table->keys()));
00339                         // If a snapshot has been set we will use its key values in the where clause
00340                 
00341                 if ( $key_vals === null ){
00342                         $query = unserialize(serialize($vals));
00343                         foreach ( array_keys($query) as $qkey){
00344                                 $query[$qkey] = "=".$this->_serializer->serialize($qkey, $query[$qkey]);
00345                         }
00346                 } else {
00347                         $query = $key_vals;
00348                         foreach (array_keys($query) as $qkey){
00349                                 $query[$qkey] = "=".$query[$qkey];
00350                         }
00351                 }
00352                 
00353                 $sql[] = " ".$this->_where($query);
00354                 $sql[] = " LIMIT 1";
00355                 
00356                 
00357                 
00358                 $sql = implode($sql);
00359                 //echo $sql;
00360                 $this->action = null;
00361                 return $sql;
00362         
00363         }
00364         
00365         
00366         
00367         function insert(&$record, $tablename=null){
00368                 $app =& Dataface_Application::getInstance();
00369                 $this->action = 'insert';
00370                 if ( !is_a($record, "Dataface_Record") ){
00371                         throw new Exception("First argument to QueryBuilder::insert() must be of type Dataface_Record, but received ".get_class($record), E_USER_ERROR);
00372                 }
00373                 // the keys are not complete... so this item does not exist.. create new record.
00374                 $tableObj =& Dataface_Table::loadTable($this->tablename($tablename));
00375                 $dbObj =& Dataface_DB::getInstance();
00376                 $fields = array_keys($this->_mutableFields);
00377                 $keys =& $this->_table->keys();
00378                 $insertedKeys = array();
00379                 $insertedValues = array();
00380                 
00381                 foreach ($this->_mutableFields as $key=>$field){
00382                         if ( @$field['ignore'] ) continue;
00383                         if ( $tableObj->isDate($key) ){
00384                                 // We must take special care for dates.
00385                                 if (isset($fieldArr)) unset($fieldArr);
00386                                 $fieldArr =& $tableObj->getField($key);
00387                                 if ( isset($fieldArr['timestamp']) and in_array(strtolower($fieldArr['timestamp']), array('insert','update')) ){
00388                                         $insertedKeys[] = '`'.$key.'`';
00389                                         $insertedValues[] = 'NOW()';
00390                                         
00391                                         continue;
00392                                 }
00393                         }
00394                         if ( !$record->hasValue($key) ) continue;
00395                         $val = $record->getValue($key);
00396                         if ( strtolower($this->_mutableFields[$key]['Extra']) == 'auto_increment' && !$val ){
00397                                 // This is a MySQL 5 fix.  In MySQL 5 it doesn't like it when you put blank values into
00398                                 // auto increment fields.
00399                                 continue;
00400                         }
00401                         if ( !isset($val)) continue;
00402                         $sval = $this->_serializer->serialize($key, $record->getValue($key) );
00403                         //if ( !$field['value'] && in_array($key, array_keys($keys)) ) continue;
00404                         if ( $tableObj->isBlob($key) and @$app->_conf['multilingual_content'] ){
00405                                 $blobID = $dbObj->registerBlob($sval);
00406                                 $sval2 = "-=-=B".$blobID."=-=-";
00407                         } else {
00408                                 $sval2 = $sval;
00409                         }
00410                         
00411                         if ( strlen(strval($sval2)) == 0 and strtolower($this->_mutableFields[$key]['Null']) == 'yes' ){
00412                                 $insertedKeys[] = '`'.$key.'`';
00413                                 $insertedValues[] = 'NULL';
00414                                 //$sql .= 'NULL,';
00415                         } else {
00416                                 $insertedKeys[] = '`'.$key.'`';
00417                                 
00418                                 $insertedValues[] = $this->prepareValue($key,$sval2);
00419                                 //$sql .= "'".addslashes($sval2)."',";
00420                         }
00421                 }
00422                 $sql = "INSERT INTO `".$this->tablename($tablename)."` (".
00423                         implode(',', $insertedKeys).') VALUES ('.
00424                         implode(',', $insertedValues).')';
00425                 $this->action = null;
00426                 return $sql;
00427         }
00428         
00429         
00430         function prepareValue($fieldname, $value,$serialize=false){
00431                 $quotes = true;
00432                 if ( $serialize ) $value = $this->_serializer->serialize($fieldname, $value);
00433                 if ( in_array( strtolower($this->_table->getType($fieldname)), array('timestamp','datetime')) ){
00434                         $value = "convert_tz('".addslashes($value)."','".addslashes(df_utc_offset())."','SYSTEM')";
00435                         $quotes = false;
00436                 }
00437                 if ( $quotes ) $value = "'".addslashes($value)."'";
00438                 $value = $this->_serializer->encrypt($fieldname,$value);
00439                 return $value;
00440                 
00441         }
00442         
00443         
00444         
00445         function delete($query=array(), $nolimit=false, $tablename=null){
00446                 $this->action = 'delete';
00447                 if ( !isset($tablename) ) $tablename = $this->_table->tablename;
00448                 $table =& Dataface_Table::loadTable($tablename);
00449                 
00450                 $query = array_merge($this->_query, $query);
00451                 $tsql=$table->sql();
00452                 $parent =& $table->getParent();
00453                 if ( isset($tsql) or isset($parent)){
00454                         $talias = $tablename.'__dforiginal__';
00455                         $joinclause = array();
00456                         foreach ( array_keys($table->keys() ) as $tkey){
00457                                 $joinclause[] = "`$talias`.`$tkey`=`$tablename`.`$tkey`";
00458                         }
00459                         $joinclause = implode(' AND ', $joinclause);
00460                         $from = "FROM `{$talias}` USING `{$tablename}` as `{$talias}` left join ".substr($this->_from($tablename), 5)." on ($joinclause)";
00461                         
00462                 } else {
00463                         $from = $this->_from($tablename);
00464                                         
00465                 }
00466                 $where = $this->_where($query);
00467                 $limit = $this->_limit($query, $nolimit);
00468                 $ret = "DELETE ".$from;
00469                 
00470                 if ( strlen($where)>0 )  $ret .= ' '.$where;
00471                 if ( strlen($limit)>0 )  $ret .= ' '.$limit;
00472                 $this->action = null;
00473                 return trim($ret);
00474                 
00475                 
00476         }
00477         
00478         function wc($tablename, $colname){
00479                 if ( in_array($this->action, array('select','delete', 'select_num_rows')) ){
00480                         return "`{$tablename}`.`{$colname}`";
00481                 } else {
00482                         return "`{$colname}`";
00483                 }
00484         }
00485         
00486         function _fieldWhereClause(&$field, $value, &$use_where, $tableAlias=null){
00487                 $key = $field['Field'];
00488                 $where = '';
00489                 $table =& Dataface_Table::loadTable($field['tablename']);
00490                 $changeTable = false;
00491                 if ( $this->_table->tablename != $table->tablename ){
00492                         $changeTable = true;
00493                         $oldTable =& $this->_table;
00494                         unset($this->_table);
00495                         $this->_table =& $table;
00496                         $oldSerializer =& $this->_serializer;
00497                         unset($this->_serializer);
00498                         $this->_serializer = new Dataface_Serializer($table->tablename);
00499                 }
00500                 if ( !isset($tableAlias) ) $tableAlias = $table->tablename;
00501                 if ( is_array($value) ){
00502                         throw new Exception("Attempt to use array in query clause");
00503                 }
00504                 $words = explode(' OR ', $value);
00505                 if ( count($words) > 1){
00506                         $where .= '(';
00507                         $conj = 'OR';
00508                 } else {
00509                         $conj = 'AND';
00510                 }
00511                 
00512                 // A value with a prefix of '<>' indicates we are searching for values NOT equal to...
00513                 if ( isset($field['repeat']) and $field['repeat']){
00514                         $repeat = true;
00515                         
00516                 } else {
00517                         $repeat = false;
00518                 }
00519                 foreach ($words as $value){
00520                         if ( $value === '' ) continue;
00521                         // A value with a prefix of '=' indicates that this is an exact match
00522                         if ( $value{0}=='=' ){
00523                                 $exact = true;
00524                                 $value = substr($value,1);
00525                         } else {
00526                                 $exact = false;
00527                         }
00528                         $factors = explode(' AND ', $value);
00529                         if ( count($factors) > 1 ){
00530                                 $where .= '(';
00531                         }
00532                         foreach ($factors as $value){
00533                                 if ( !$exact and (strpos($value, '!=')===0 or strpos($value, '<>') === 0)){
00534                                         $value = substr($value, 2);
00535                                         $value = $this->prepareValue( $key, $table->parse($key, $value), true );
00536                                         if ( $repeat ){
00537                                                 $where .= $this->wc($tableAlias, $key)." NOT RLIKE CONCAT('[[:<:]]',$value,'[[:>:]]') AND ";
00538                                         } else {
00539                                                 $where .= $this->wc($tableAlias, $key)." <> $value AND ";
00540                                         }
00541                                 
00542                                 // A value with a prefix of '<' indicates that we are searching for values less than
00543                                 // a field.
00544                                 } else if ( !$exact and strpos($value,'<')===0){
00545                                         if ( strpos($value,'=') === 1 ){
00546                                                 $value = substr($value,2);
00547                                                 $op = '<=';
00548                                         } else {
00549                                                 $value = substr($value, 1);
00550                                                 $op = '<';
00551                                         }
00552                                         $value = $this->prepareValue( $key, $table->parse($key, $value), true );
00553                                         $where .= $this->wc($tableAlias, $key)." $op $value AND ";
00554                                         
00555                                 // A value with a prefix of '>' indicates a greater than search
00556                                 } else if ( !$exact and strpos($value, '>')===0 ) {
00557                                         if ( strpos($value,'=') === 1 ){
00558                                                 $value = substr($value,2);
00559                                                 $op = '>=';
00560                                         } else {
00561                                                 $value = substr($value, 1);
00562                                                 $op = '>';
00563                                         }
00564                                         $value = $this->prepareValue( $key, $table->parse($key, $value), true );
00565                                         $where .= $this->wc($tableAlias, $key)." $op $value AND ";
00566                                         
00567                                         
00568                                 // If the query term has '..' any where it is interpreted as a range search
00569                                 } else if ( !$exact and strpos($value, '..')> 0 ){
00570                                         list($low,$high) = explode('..',$value);
00571                                         $low = trim($low); $high = trim($high);
00572                                         $low = $this->prepareValue( $key, $table->parse($key, $low), true);
00573                                         $high = $this->prepareValue( $key, $table->parse($key, $high), true);
00574                                         $where .= $this->wc($tableAlias, $key)." >= $low AND ".$this->wc($tableAlias, $key)." <= $high AND ";
00575                                 } else if ( !$exact and strpos($value, '~') === 0 ){
00576                                         $value = substr($value,1);
00577                                         $oldval = $value;
00578                                         $oper = 'LIKE';
00579                                         $value = $this->prepareValue( $key, $table->parse($key, $value), true);
00580                                         if (  strlen($oldval) > 0 ){
00581                                                 $where .= $this->wc($tableAlias,$key)." $oper $value AND ";
00582                                         } else {
00583                                                 $where .= '('.$this->wc($tableAlias,$key)." $oper '' OR ".$this->wc($tableAlias,$key)." IS NULL) AND ";
00584                                         }
00585                                 
00586                                 
00587                                 } else if ( $repeat ){
00588                                         $value = $this->prepareValue( $key, $table->parse($key, $value), true); 
00589                                         $where .= $this->wc($tableAlias, $key)." RLIKE CONCAT('[[:<:]]',$value,'[[:>:]]') AND ";
00590                                 }
00591                                 
00592                                 else if ( $this->_exactMatches || preg_match( '/int/i', $field['Type']) || $exact ){
00593                                         $oldval = $value;
00594                                         $oper = '=';
00595                                         $value = $this->prepareValue( $key, $table->parse($key, $value), true);
00596                                         if (  strlen($oldval) > 0 ){
00597                                                 $where .= $this->wc($tableAlias,$key)." $oper $value AND ";
00598                                         } else {
00599                                                 $where .= '('.$this->wc($tableAlias,$key)." $oper '' OR ".$this->wc($tableAlias,$key)." IS NULL) AND ";
00600                                         }
00601                                 } else {
00602                                         $value = $this->prepareValue( $key, $table->parse($key, $value), true); 
00603                                         $where .= $this->wc($tableAlias, $key)." LIKE CONCAT('%',$value,'%') AND ";
00604                                 }
00605                                 $use_where = true;
00606                         }
00607                         $where = substr($where, 0, strlen($where)-5);
00608                         if (count($factors) > 1){
00609                                 
00610                                 $where .= ')';
00611                         }
00612                         $where .= ' OR ';
00613 
00614                         
00615                 }
00616                 $where = substr($where, 0, strlen($where)-4);
00617                 if ( count($words) > 1){
00618                         
00619                         $where .= ')';
00620                 }
00621                 
00622                 if ($changeTable){
00623                         unset($this->_table);
00624                         $this->_table =& $oldTable;
00625                         unset($this->_serializer);
00626                         $this->_serializer =& $oldSerializer;
00627                 }
00628                 return $where;
00629         
00630         }
00631         
00632         
00638         function _where($query=array(), $merge=true){
00639                 if ( $merge ){
00640                         $query = array_merge( $this->_query, $query);
00641                         
00642                 }
00643                 foreach ($query as $key=>$value) {
00644                         if ( $value === null or $value === '' ){
00645                                 unset($query[$key]);
00646                         }
00647                 }
00648                 
00649                 if ( isset($query['__id__']) ){
00650                         $keys = array_keys($this->_table->keys());
00651                         if ( $keys ){
00652                                 $query[$keys[0]] = $query['__id__'];
00653                                 unset($query['__id__']);
00654                         }
00655                 }
00656                 
00657                 
00658                 
00659                 $where  = "WHERE ";
00660                 $missing_key = false;
00661                 $limit = '';
00662                 $use_where = false;
00663                         
00664                 $fields = array();
00665                 //print_r($query);
00666                 foreach ($query as $key=>$value){
00667                         if ( strpos($key,'-') !== 0 ) $fields[$key] = $value;
00668                 }
00669                 foreach ($fields as $key=>$value){
00670                         if ( isset($this->_fields[$key]) ){
00671                                 $field =& $this->_fields[$key];
00672                                 if ( !@$field['not_findable'] ){
00673                                         $where .= $this->_fieldWhereClause($field, $value, $use_where, $this->_tablename).' AND ';
00674                                 }
00675                                 unset($field);
00676                         }
00677                                 
00678                 }
00679                 $charFields = $this->_table->getCharFields(true, true);
00680                 if ( isset( $query['-search'] ) and strlen($query['-search']) and count($charFields)>0 ){
00681                         $status = $this->_table->getStatus();
00682                         //if ( $status['Engine'] == 'MyISAM' ){
00683                         //      // MyISAM has a match clause. that works quite well.
00684                         //      $where .= $this->_match($query['-search'])." AND ";
00685                         //} else {
00686                         //      // If the table type is not MyISAM, then we need to manually do the multi-field search.
00687                                 $words = explode(' ', $query['-search']);
00688                                 foreach ( $words as $word ){
00689                                         $where .= '(`'.implode('` LIKE \'%'.addslashes($word).'%\' OR `', $charFields).'` LIKE \'%'.addslashes($word).'%\') AND ';
00690                         //      }
00691                         }
00692                                                 
00693                         $use_where = true;
00694                 }
00695                 
00696                 if ( $this->metadata ){
00697                         $wfkeys = preg_grep('/^_metadata::/', array_keys($query));
00698                         $clause = array();
00699                         foreach ($wfkeys as $wfkey){
00700                                 $wf_col = substr($wfkey,11);
00701                                 if ( !$this->_table->fieldExists($wf_col) ) continue;
00702                                 $wf_col = $this->_tablename."__metadata.__{$wf_col}";
00703                                 $clause[] = "`{$wf_col}`='".addslashes($query[$wfkey])."'";
00704                         }
00705                         if ( count($clause)>0 ){
00706                                 $use_where = true;
00707                                 $where .= implode(' AND ', $clause).' AND ';
00708                         }
00709                 }
00710                 
00711                 // Now we will search related fields
00712                 $rkeys = preg_grep('/^[^\-].*\/.*$/', array_keys($query));
00713                 
00714                 $rquery = array();
00715                 foreach ($rkeys as $rkey ){
00716                         list($relationship, $rfield) = explode('/', $rkey);
00717                         $rquery[$relationship][] = $rfield;
00718                         
00719                 }
00720                 
00721                 foreach ( $rquery as $rname=>$rfields){
00722                         $r =& $this->_table->getRelationship($rname);
00723                         if ( PEAR::isError($r) ){
00724                                 unset($r);
00725                                 continue;
00726                         }
00727                         
00728                         
00729                         $pairs=array();
00730                         foreach ( $rfields as $rfield ){
00731                                 $rfieldDef =& $r->getField($rfield);
00732                                 $q = $query[$rname.'/'.$rfield];
00733                                 $ralias = $r->getTableAlias($rfieldDef['tablename']);
00734                                 if ( !$ralias ) $ralias = null;
00735                                 $pairs[] = $this->_fieldWhereClause($rfieldDef, $q, $use_where, $ralias );
00736                                 unset($rfieldDef);
00737                                 
00738                                 //$pairs[] = '`'.str_replace('`','',$rfield).'` LIKE \'%'.addslashes($query[$rname.'/'.$rfield]).'%\'';
00739                         }
00740                         if ( $pairs ){
00741                                 $subwhere = ' AND '.implode(' AND ',$pairs);
00742                         }
00743                         
00744                         $sql = $r->getSQL();
00745                         
00746                         $fkeys =& $r->getForeignKeyValues();
00747                         foreach ( $fkeys as $tname=>$tfields ){
00748                                 foreach ( $tfields as $tval ){
00749                                         if ( !is_scalar($tval) ) continue;
00750                                         if ( strlen($tval) > 0 ) $tval = substr($tval,1);
00751                                         $sql = preg_replace('/[\'"]?\$('.preg_quote($tval).')[\'"]?/', '`'.str_replace('`','',$this->_table->tablename).'`.`\1`', $sql);
00752                                 }
00753                         }
00754                         $where .= 'EXISTS ('.$sql.$subwhere.') AND ';
00755                         $use_where = true;
00756                         unset($r);
00757                         unset($fkeys);
00758                 }
00759                 
00760                 
00761                 
00762                 if ( $use_where ){
00763                         
00764                         $where = substr($where,0, strlen($where)-5);
00765                 } else {
00766                         $where = '';
00767                 }
00768                 
00769                 return $where;
00770         }
00771         
00772         
00773         
00778         function _from($tablename=null){
00779                 $app =& Dataface_Application::getInstance();
00780                 if ( !isset($tablename) ) $tablename = $this->_table->tablename;
00781                 
00782                 $table =& Dataface_Table::loadTable($tablename);
00783                 $proxyView = $table->getProxyView();
00784                 $tsql = $table->sql();
00785                 $fromq = '';
00786                 if ( $proxyView ){
00787                         $fromq = "`".$proxyView."`";
00788                 } else if ( isset($tsql) ){
00789                         $fromq = "(".$tsql.")";
00790                 } else {
00791                         $fromq = "`".$this->tablename($tablename)."`";
00792                 }
00793                 
00794                 $parent =& $table->getParent();
00795                 if ( isset($parent) ){
00796                         $qb2 = new Dataface_QueryBuilder($parent->tablename);
00797                         $pfrom = $qb2->_from();
00798                         $as_pos = ( ( strpos(strtolower($pfrom), ' as ') !== false ) ? (strlen($pfrom) - strpos(strrev(strtolower($pfrom)), ' sa ' )-3) : false);
00799                         if ( $as_pos !== false ){
00800                                 $pfrom = substr($pfrom, strlen('FROM '), $as_pos-strlen('FROM '));
00801                         } else {
00802                                 $pfrom = substr($pfrom, strlen('FROM '));
00803                         }
00804                         $pkeys = array_keys($parent->keys());
00805                         $ckeys = array_keys($table->keys());
00806                         $joinq = array();
00807                         for ($i=0; $i<count($pkeys); $i++){
00808                                 $joinq[] = '`t___child`.`'.$ckeys[$i].'`=`t___parent`.`'.$pkeys[$i].'`';
00809                         }
00810                         $joinq = implode(' and ', $joinq);
00811                         
00812                         
00813                         $out = "FROM (select * from ".$fromq." as `t___child` left join ".$pfrom." as `t___parent` on ($joinq)) as `".$this->tablename($tablename)."`" ;
00814                 
00815                         
00816                 } else if ( isset($tsql) or isset($proxyView) ){
00817                         $out = "FROM ".$fromq." as `".$this->tablename($tablename)."`";
00818                 } else {
00819                         $out = "FROM ".$fromq;
00820                 }
00821                 
00822                 
00823                 
00824                 
00825                 if ( $this->metadata and $this->action == 'select') {
00826                         $out .= " LEFT JOIN `{$tablename}__metadata` ON ";
00827                         $keys = array_keys($table->keys());
00828                         if ( count($keys) == 0 ) throw new Exception("The table '".$tablename."' has no primary key.", E_USER_ERROR);
00829                         
00830                         $clauses = array();
00831                         foreach ( $keys as $key ){
00832                                 $clauses[] = "`{$tablename}`.`{$key}`=`{$tablename}__metadata`.`{$key}`";
00833                         }
00834                         $out .= "(".implode(' and ', $clauses).")";
00835                 }
00836                 return $out;
00837         }
00838         
00839         
00845         function _select($columns='', $query=array(), $preview=false, $previewLen=null){
00846                 if ( !isset($previewLen) and defined('XATAFACE_DEFAULT_PREVIEW_LENGTH') and is_int(XATAFACE_DEFAULT_PREVIEW_LENGTH) ){
00847                         $previewLen = XATAFACE_DEFAULT_PREVIEW_LENGTH;
00848                 }
00849                 if ( !is_int($previewLen) ) $previewLen = 255;
00850                 $app =& Dataface_Application::getInstance();
00851                 $query = array_merge( $this->_query, $query);
00852                 foreach ($query as $key=>$value) {
00853                         if ( $value === null ){
00854                                 unset($query[$key]);
00855                         }
00856                 }
00857                 $select = "SELECT ";
00858                 $colcount = 0;
00859                 foreach ($this->_fields as $key=>$field){
00860                         if ( $this->selectMetaData ){
00861                                 $select .= "length(`{$this->_tablename}`.`".$key."`) as `__".$key."_length`,";
00862                                 
00863                         }
00864                         if ( is_array($columns) and !in_array($key, $columns) ) continue;
00865                                 // if the columns array is set then we only return the columns listed in that array.
00866                                 
00867                         
00868                 
00869                         if ( $this->_omitBlobs and $this->_table->isBlob($field['name']) ) continue;
00870                                 // if the omitBlobs flag is set then we don't select blob columns
00871                         if ( $this->_omitPasswords and $this->_table->isPassword($field['name']) ) continue;
00872                                 // if the omitPasswords flag is set then we don't select password columns
00873                         if ( $preview and $this->_table->isText($field['name']) and !@$field['struct'] and !$this->_table->isXML($field['name'])) 
00874                                 $select .= "SUBSTRING(`{$this->_tablename}`.`$key`, 1, ".$previewLen.") as `$key`,";
00875                         else if ( in_array(strtolower($this->_table->getType($key)),array('datetime','timestamp')) )
00876                                 $select .= "convert_tz(`".$this->_tablename."`.`".$key."`, 'SYSTEM', '".df_utc_offset()."') as `$key`,";
00877                         else 
00878                                 $select .= "`{$this->_tablename}`.`$key`,";
00879                         $colcount++;
00880 
00881                 }
00882                 if ( $this->metadata) {
00883                         $clauses = array();
00884                         foreach ( $this->_table->getMetadataColumns() as $mdc ){
00885                                 $clauses[] = "`{$this->_tablename}__metadata`.`{$mdc}`";
00886                         }
00887                         $select .= implode(',',$clauses).',';
00888                         
00889                 }
00890                 
00891                 if ( $colcount == 0 ){
00892                         return PEAR::raiseError(QUERYBUILDER_ERROR_EMPTY_SELECT, null,null,null, "No columns were selected in select statement.  Make sure that _omitBlobs property is disabled in QueryBuilder object if you are only wanting to return Blob columns.");
00893                 }
00894                 $select = substr($select, 0, strlen($select) -1);
00895                 return $select;
00896         }
00897         
00902         function _limit($query=array(), $nolimit=false){
00903                 if ( $nolimit ) return '';
00904                 $query = array_merge( $this->_query, $query);
00905                 foreach ($query as $key=>$value) {
00906                         if ( $value === null ){
00907                                 unset($query[$key]);
00908                         }
00909                 }
00910                 
00911                 $limit = '';
00912                 if ( isset( $query['-limit']) && isset($query['-skip'] ) ){
00913                         if ( preg_match('/^[0-9]+$/',$query['-limit']) &&
00914                                  preg_match('/^[0-9]+$/',$query['-skip']) ){
00915                                 $limit = "LIMIT ".$query['-skip'].",".$query['-limit'];
00916                         }
00917                 } else if ( isset( $query['-limit'] ) ){
00918                         if ( preg_match('/^[0-9]+$/', $query['-limit']) ){
00919                                 $limit = "LIMIT ".$query['-limit'];
00920                         }
00921                 } else if ( isset( $query['-skip']) ){
00922                         if ( preg_match('/^[0-9]+$/', $query['-skip']) ){
00923                                 $limit = "LIMIT ".$query['-skip'].", 100";
00924                         }
00925                 }
00926                 return $limit;
00927         }
00928         
00929         
00933         function _orderby($query = array()){
00934                 $query = array_merge( $this->_query, $query);
00935                 foreach ($query as $key=>$value) {
00936                         if ( $value === null ){
00937                                 unset($query[$key]);
00938                         }
00939                 }
00940                 
00941                 if ( isset($query['-sort']) ){
00942                         
00943                         return 'ORDER BY '.preg_replace_callback('/\b(\w+?)\b/',array(&$this, '_mysql_quote_idents'), $query['-sort']);
00944                 }
00945                 return '';
00946         
00947         }
00948         
00949         function _mysql_quote_idents($matches){
00950                 if (!in_array(strtolower($matches[1]), array('asc','desc') ) ){
00951                         return '`'.((strpos($matches[1],'.') === false) ?"{$this->_tablename}`.`":'').$matches[1].'`';
00952                 } else {
00953                         return $matches[1];
00954                 }
00955         }
00956         
00974         function _match($queryStr){
00975                 $version = mysql_get_server_info();
00976                 $matches = array();
00977                 preg_match('/(\d+)\.(\d)+\.(\d)+/', $version, $matches);
00978                 $majorVersion = intval($matches[1]);
00979                 
00980                 // We want to escape illegal characters, but in a boolean search
00981                 // double  quotes are allowed so we much unescape them.
00982                 $queryStr = addslashes($queryStr);
00983                 $queryStr = str_replace('\"', '"', $queryStr);          
00984                 
00985                 $out = 'MATCH (';
00986 
00987                 // We have at least version 4 so we can do boolean searches
00988                 $indexedFields =& $this->_table->getFullTextIndexedFields();
00989                 if ( count($indexedFields)>0){
00990                         $fields =& $indexedFields;
00991                 } else {
00992                         // There are no indexed fields so we will just do a search on all character fields.
00993                         $fields =& $this->_table->getCharFields();
00994                 }
00995                 
00996                 
00997                 $empty = true;
00998                         // flag to indicate if the query will be empty
00999                 foreach ($fields as $field){
01000                         $out .= "`{$this->_tablename}`.`$field`,";
01001                         $empty = false;
01002                                 // the query is NOT empty
01003                 }
01004                 
01005                 if ( $empty ){
01006                         throw new Exception("Query attempted when no queryable columns are available in table '".$this->_table->tablename."'.  Only tables with a full-text search defined on at least one column are eligiblle to be searched in this way.", E_USER_ERROR);
01007                 }
01008                 
01009                 $out = substr($out, 0, strlen($out)-1);
01010                 
01011                 $out .= ") AGAINST ('$queryStr'";
01012                 if ( $majorVersion >= 4 ) {
01013                         $out .= " IN BOOLEAN MODE";
01014                 }
01015                 $out .= ")";
01016                         
01017                 return $out;
01018         
01019         }
01020         
01021         
01022         function omitBlobs(){
01023         
01024                 $this->_omitBlobs = true;
01025         }
01026         
01027         function includeBlobs(){
01028                 $this->_omitBlobs = false;
01029         }
01030         
01031         
01032         function addSecurityConstraint($key, $value){
01033         
01034                 $this->_security[$key] = $value;
01035         
01036         }
01037         
01038         function addSecurityConstraints($constraints){
01039         
01040                 $this->_security = array_merge($this->_security, $constraints);
01041         }
01042         
01043         
01044         function removeSecurityConstraint($key){
01045                 unset( $this->_security[$key] );
01046         }
01047         
01048         function setSecurityConstraints( $constraints ){
01049                 $this->_security = $constraints;
01050         }
01051         
01052         function _secure($where){
01053                 
01054                 $swhere = $this->_where($this->_table->getSecurityFilter($this->_security), false);
01055                 // get rid of the leading "where"
01056                 $swhere = trim(substr($swhere, 5, strlen($swhere)-5));
01057                 
01058                 $where = trim($where);
01059                 if ( strlen($where)>0 ){
01060                         if (strlen($swhere)>0) {
01061                                 $where .= " AND ".$swhere;
01062                         }
01063                 } else if ( strlen($swhere)>0){
01064                         $where = "WHERE $swhere";
01065                 }
01066                 return $where;
01067                 
01068                 
01069         
01070         }
01071         
01072         
01076         function addRelatedRecord(&$relatedRecord, $sql=null){
01077                 if ( !is_a($relatedRecord, 'Dataface_RelatedRecord') ){
01078                         throw new Exception("In QueryBuilder::addRelatedRecord() expecting first argument to be type 'Dataface_RelatedRecord' but received '".get_class($relatedRecord)."'\n<br>", E_USER_ERROR);
01079                 }
01080                 $relationship =& $relatedRecord->_relationship;
01081                 $table_cols = $relatedRecord->getForeignKeyValues( $sql);
01082                 if ( count($this->errors) > 0 ){
01083                         $error = array_pop($this->errors);
01084                         $error->addUserInfo("Error getting foreign key values for relationship '$relationship_name'");
01085                         throw new Exception($error->toString());
01086                 }
01087                 
01088                 
01089                 $sql = array();
01090                 
01091                 // now generate the sql
01092                 // We will go through each table and insert the record for that 
01093                 // table separately.
01094                 foreach ( $table_cols as $table=>$cols ){
01095                         if ( isset($recordObj) ) unset($recordObj);
01096                         $recordObj = new Dataface_Record($table, $cols);
01097                         $recordVals =& $recordObj->vals();
01098                         if ( isset( $recordVals[ $recordObj->_table->getAutoIncrementField() ] ) ){
01099                                 // We don't want the auto-increment field to be inserted - though it may
01100                                 // have a placeholder value.
01101                                 $recordObj->setValue($recordObj->_table->getAutoIncrementField(), null);
01102                         }
01103                         $qb = new Dataface_QueryBuilder($table);
01104                         $sql[$table] = $qb->insert($recordObj);
01105                         
01106                 }
01107                 
01108                 return $sql;
01109                         
01110         }
01111         
01112         
01113         function addExistingRelatedRecord(&$relatedRecord){
01114                 $record =& $relatedRecord->_record;
01115                 $relationshipName =& $relatedRecord->_relationshipName;
01116                 $values =& $relatedRecord->getAbsoluteValues(true);
01117                 if ( !is_a($record, 'Dataface_Record') ){
01118                         throw new Exception("In Dataface_QueryBuilder::addExistingRelatedRecord() expected first argument to be of type 'Dataface_Record' but received '".get_class($record)."'.\n<br>", E_USER_ERROR);
01119                 }
01120                 if ( !is_array($values) ){
01121                         throw new Exception("In Dataface_QueryBuilder::addExistingRelatedRecord() expected third argument to be an array but received a scalar.", E_USER_ERROR);
01122                 }
01123                 $relationship =& $record->_table->getRelationship($relationshipName);
01124                 $foreignKeys = $relationship->getForeignKeyValues();
01125                 $foreignKeys_withValues = $relatedRecord->getForeignKeyValues();
01126                 
01127                 if ( count($this->errors) > 0 ){
01128                         $error = array_pop($this->errors);
01129                         $error->addUserInfo("Error getting foreign key values for relationship '$relationship_name'");
01130                         throw new Exception($error->toString());
01131                 }
01132                 
01133                 $sql = array();
01134                 foreach ($foreignKeys as $table=>$cols){
01135                         $skip = true;
01136                         foreach ($cols as $field_name=>$field_value){
01137                                 if ( $field_value != "__".$table."__auto_increment__" ) {
01138                                         $skip = false;
01139                                         break;
01140                                 }
01141                         }
01142                         if ( $skip ) continue;
01143                         $cols = $foreignKeys_withValues[$table];
01144                         if ( isset($recordObj) ) unset($recordObj);
01145                         $recordObj = new Dataface_Record($table, $cols);
01146                         $recordVals =& $recordObj->vals();
01147                         if ( isset( $recordVals[ $recordObj->_table->getAutoIncrementField() ] ) ){
01148                                 // We don't want the auto-increment field to be inserted - though it may
01149                                 // have a placeholder value.
01150                                 $recordObj->setValue($recordObj->_table->getAutoIncrementField(), null);
01151                         }
01152                         $qb = new Dataface_QueryBuilder($table);
01153                         $sql[$table] = $qb->insert($recordObj);
01154                         /*
01155                         $skip = true;
01156                                 // indicator to say whether or not to skip this table
01157                                 // we skip the table if it contains an unresolved autoincrement value
01158                                 
01159                         foreach ($cols as $field_name=>$field_value){
01160                                 if ( $field_value != "__".$table."__auto_increment__" ) {
01161                                         $skip = false;
01162                                         break;
01163                                 }
01164                         }
01165                         
01166                         if ( $skip == true ) continue;
01167                                 
01168                         
01169                         $cols = $foreignKeys_withValues[$table];
01170                         
01171                         
01172                         $query = "INSERT INTO `$table`";
01173                         $colnames = "";
01174                         $colvals = "";
01175                         
01176                         foreach ( $cols as $colname=>$colval){
01177                                 $colnames .= $colname.',';
01178                                 $colvals .= "'".addslashes($colval)."',";
01179                         }
01180                         
01181                         $colnames = substr($colnames, 0, strlen($colnames)-1);
01182                         $colvals = substr($colvals, 0, strlen($colvals)-1);
01183                         
01184                         $query .= " ($colnames) VALUES ($colvals)";
01185                         
01186                         $sql[$table] = $query;
01187                         */
01188                 
01189                 }
01190                 
01191                 return $sql;
01192                 
01193                 
01194         
01195         }
01196         
01197         function tablename($tablename=null){
01198                 if ( $tablename === null ) return $this->_tablename;
01199                 return $tablename;
01200         
01201         }
01202         
01203         
01204         
01205 
01206 
01207 }
 All Data Structures Namespaces Files Functions Variables Enumerations