Xataface 2.0
Xataface Application Framework
SQL/Parser/wrapper.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 define('SQL_PARSER_WRAPPER_ERROR', 101);
00022 require_once 'SQL/Parser.php';
00023 
00024 class SQL_Parser_wrapper {
00025         
00026         var $_data;
00027         var $_tableLookup;
00028         var $_parser;
00029         
00030         function SQL_Parser_wrapper(&$data, $dialect='MySQL'){
00031                 $this->_data =& $data;
00032                 $this->_tableLookup = array();
00033                 $this->_parser = new SQL_Parser(null, $dialect);
00034         }
00035         
00040         function getTableName($columnname){
00041                 if ( !isset( $this->_tableLookup[$columnname] ) ){
00042                         if ( strpos($columnname, '.') === false ) $this->_tableLookup[$columnname] = null;
00043                         else {
00044                                 $data =& $this->_data;
00045                                 list($table, $column) = explode('.', $columnname);
00046                                 if ( isset( $data['table_aliases'] ) ){
00047                                         for ($i=0; $i<count($data['table_aliases']); $i++){
00048                                                 if ( $data['table_aliases'][$i] == $table ){
00049                                                         $table = $data['table_names'][$i];
00050                                                         break;
00051                                                 }
00052                                         }
00053                                 }
00054                                 $this->_tableLookup[$columnname] = $table;
00055                         }
00056                         
00057                 }
00058                 return $this->_tableLookup[$columnname];
00059         }
00060         
00061         
00066         function getTableAlias($tablename){
00067                 $index = array_search($tablename, $this->_data['table_names']);
00068                 if ( $index === false ){
00069                         return PEAR::raiseError("Table not found in query", SQL_PARSER_WRAPPER_ERROR, E_USER_WARNING, null, "The table '$tablename' was requested in SQL_Parser_wrapper::getTableAlias() and not found in the sql query");
00070                  
00071                 }
00072                 if ( isset( $this->_data['table_aliases']) and isset( $this->_data['table_aliases'][$index]) and
00073                                 $this->_data['table_aliases'][$index] ) {
00074                         return $this->_data['table_aliases'][$index];
00075                 } else {
00076                         return $tablename;
00077                 }
00078         
00079         }
00080         
00081         
00082         
00087         function resolveColumnName($columnname){
00088         
00089                 $table = $this->getTableName($columnname);
00090                 if ( $table === null ){
00091                         return $columnname;
00092                 } else {
00093                         if ( strpos($columnname, ".") !== false ){
00094                                 list($junk, $col) = explode('.', $columnname);
00095                                 return $table.'.'.$col;
00096                         } else {
00097                                 return $col;
00098                         }
00099                 }
00100                         
00101         }
00102         
00103         
00113         function unresolveColumnName($columnname){
00114                 if ( strpos($columnname, '.') !== false ){
00115                         list($table,$column) = explode('.', $columnname);
00116                         $tablename = $this->getTableAlias($table);
00117                         if ( PEAR::isError($tablename) ){
00118                                 /*
00119                                  * There is no table by this name.  Check to see if it is already
00120                                  * an alias.
00121                                  */
00122                                 $index = array_search($table, $this->_data['table_aliases']);
00123                                 if ( $index !== false ){
00124                                         /*
00125                                          * The tablename is an alias so we can leave it unchanged.
00126                                          */
00127                                         $tablename = $table;
00128                                 } else {
00129                                         /*
00130                                          * The tablename is not an alias nor is it a valid table...
00131                                          * propogate the error upwards.
00132                                          */
00133                                         $tablename->addUserInfo("In SQL_Parser_wrapper attempted to unresolve column '$columnname' but the table does not exist as either an alias or a column name.");
00134                                         return $tablename;
00135                                 }
00136                         }
00137                         return $tablename.'.'.$column;
00138                 } else {
00139                         $index = $this->array_ereg_search('/\.'.$columnname.'$/', $this->_data['column_names']);
00140                         if ( $index !== false ){
00141                                 return $this->_data['column_names'][$index];
00142                         } else {
00143                                 return $columnname;
00144                         }
00145                 }
00146         
00147         }
00148         
00153         function array_ereg_search($needle, $haystack){
00154                 foreach ( array_keys($haystack) as $index ){
00155                         if ( preg_match($needle, $haystack[$index]) ){
00156                                 return $index;
00157                         }
00158                 }
00159                 return false;
00160         }
00161         
00162         function unresolveWhereClauseColumns(&$clause){
00163                 if ( !is_array($clause) ) return;
00164                 if ( isset($clause['type']) and $clause['type'] === 'ident' ){
00165                         $clause['value'] = $this->unresolveColumnName($clause['value']);
00166                 }
00167                 foreach ( array_keys($clause) as $key){
00168                         $this->unresolveWhereClauseColumns($clause[$key]);
00169                 }
00170                         
00171         
00172         }
00173         
00174         
00179         function removeColumn($columnname){
00180                 $columnNames =& $this->_data['column_names'];
00181                 $index = array_search($columnname, $columnNames);
00182                 if ( $index !== false ){
00183                         array_splice($columnNames, $index, 1);
00184                         if ( isset( $this->_data['column_aliases'] ) ){
00185                                 array_splice($this->_data['column_aliases'], $index, 1);
00186                         }
00187                         return true;
00188                 } else {
00189                         return false;
00190                 }
00191         }
00192         
00193         
00198         function removeColumnsFromTable($tablename){
00199                 $columnNames =& $this->_data['column_names'];
00200                 $count = 0;
00201                 foreach ( $columnNames as $name ){
00202                         if ( $this->getTableName($name) == $tablename){
00203                                 $res = $this->removeColumn($name);
00204                                 if ( $res ) $count++;
00205                         }
00206                 }
00207                 return $count;
00208         }
00209         
00213         function addColumn($columnname, $columnalias){
00214                 $this->_data['column_names'][] = $columnname;
00215                 $this->_data['column_aliases'][] = $columnalias;
00216         
00217         }
00218         
00222         function &appendClause($clause, $op='or'){
00223                 $data =& $this->_data;
00224                 if ( isset( $data['where_clause']) and $data['where_clause'] ) {
00225                         
00226                         if ( (isset( $data['where_clause']['type']) and $data['where_clause']['type'] == 'subclause') 
00227                                         or count($data['where_clause']) ===1 or 
00228                                         (isset($data['where_clause']['op']) and !in_array($data['where_clause']['op'], array('and','or')) )
00229                                 ){
00230                                 $arg1 = $data['where_clause'];
00231                         } else {
00232                                 $arg1 = array("value"=>$data['where_clause'], "type"=>"subclause");
00233                         }
00234                         
00235                         if ( (isset($clause['type']) and $clause['type'] == 'subclause') or
00236                                 count($clause) === 1 or
00237                                 (isset($clause['op']) and !in_array($clause['op'], array('and','or')))
00238                                 ){
00239                                 $arg2 = $clause;
00240                         } else {
00241                                 $arg2 = array("value"=>$clause, "type"=>"subclause");
00242                         }
00243                         
00244                         $data['where_clause'] = array( "arg_1"=> $arg1, 'op'=>$op, "arg_2"=> $arg2);
00245                 } else {
00246                         $data['where_clause'] = $clause;
00247                 }
00248                 return $data;
00249                 
00250         }
00251         
00252         
00253         function &addWhereClause($whereStr, $op='and'){
00254                 $sql = "SELECT * FROM foo WHERE $whereStr";
00255                 $parsed = $this->_parser->parse($sql);
00256 
00257                 
00258                 $this->unresolveWhereClauseColumns($parsed['where_clause']);
00259                 
00260                 $this->appendClause($parsed['where_clause'], $op);
00261                 return $this->_data;
00262         
00263         }
00264         
00265         
00266         function &setSortClause($sortStr){
00267         
00268                 $sql = "SELECT * FROM foo ORDER BY $sortStr";
00269                 $parsed = $this->_parser->parse($sql);
00270                 
00271                 $sort_order = array();
00272                 foreach (array_keys($parsed['sort_order']) as $sort_col){
00273                         $this->unresolveWhereClauseColumns($parsed['sort_order'][$sort_col]);
00274                         $sort_order[] =& $parsed['sort_order'][$sort_col];
00275                         
00276                 }
00277                 
00278                 $this->_data['sort_order'] = $sort_order;
00279                 return $this->_data;
00280         
00281         }
00282         
00283         
00284         function &addSortClause($sortStr){
00285         
00286                 $sql = "SELECT * FROM foo ORDER BY $sortStr";
00287                 $parsed = $this->_parser->parse($sql);
00288                 
00289                 $sort_order =& $this->_data['sort_order'];
00290                 foreach (array_keys($parsed['sort_order']) as $sort_col){
00291                         $this->unresolveWhereClauseColumns($parsed['sort_order'][$sort_col]);
00292                         $sort_order[] =& $parsed['sort_order'][$sort_col];
00293                         
00294                 }
00295                 
00296                 //$this->_data['sort_order'] = $sort_order;
00297                 return $this->_data;
00298         
00299         }
00300         
00301         
00302         
00303         
00304         
00305         function &removeWhereClause($clause){
00306                 $null = null;
00307                 $this->_data['where_clause'] = $this->_removeClause_rec($clause, $this->_data['where_clause']);
00308                 if ( $this->_data['where_clause'] == null ) {
00309                         unset($this->_data['where_clause']);
00310                         return $null;
00311                 }
00312                 return $this->_data['where_clause'];
00313         }
00314         
00315         function removeJoinClause($clause){
00316                 if ( is_array($this->_data['table_join_clause']) ){
00317                         $new_clauses = array();
00318                         $new_joins = array();
00319                         
00320                         foreach ( $this->_data['table_join_clause'] as $index=>$jc){
00321                                 $new_clause = $this->_removeClause_rec($clause, $jc);
00322                                 if ( $new_clause == null ) $new_clause = '';
00323                                 $new_clauses[] = $new_clause;
00324                                 if ( sizeof($new_clauses) > 1 ){
00325                                         if ( $new_clause == '' )  $new_joins[] = ',';
00326                                         else $new_joins[] = $this->_data['table_join'][$index-1];
00327                                 }
00328                         }
00329                         $this->_data['table_join_clause'] = $new_clauses;
00330                         $this->_data['table_join'] = $new_joins;
00331                 }
00332                 
00333         }
00334         
00335         
00336         function _removeClause_rec($clause, $root){
00337 
00338                 // Case 1: The current Node has "arg_1" and "arg_2" params
00339                 if ( isset( $root['arg_1'] ) and isset( $root['arg_2']) ){
00340 
00341                         if ( $clause == $root ){
00342                                 return null;
00343                         } else {
00344                                 $root['arg_1'] = $this->_removeClause_rec($clause, $root['arg_1']);
00345                                 $root['arg_2'] = $this->_removeClause_rec($clause, $root['arg_2']);
00346                                 
00347                                 if ( $root['arg_1'] == null and $root['arg_2'] == null ) return null;
00348                                 else if ( $root['arg_1'] != null and $root['arg_2'] == null ) return $root['arg_1'];
00349                                 else if ( $root['arg_2'] != null and $root['arg_1'] == null ) return $root['arg_2'];
00350                                 else return $root;
00351                         }
00352                 } 
00353                         
00354                 // There is only a single argument... this is kind of a lame case, but it exists.
00355                 else if ( isset( $root['arg_1'] ) ){
00356 
00357                         $root['arg_1'] = $this->_removeClause_rec($clause, $root['arg_1']);
00358                         return $root['arg_1'];
00359                 }
00360                 
00361                 // Case 2: The current Node has a "type" param
00362                 else if ( isset( $root['type']) and $root['type'] == 'subclause' ){
00363 
00364                         $root['value'] =  $this->_removeClause_rec($clause, $root['value']);
00365                         if ( $root['value'] == null ) return null;
00366                         return $root;
00367                 }
00368                 
00369                 // Case 3: Anything else... return the root unchanged
00370                 else {
00371 
00372                         return $root;
00373                 }
00374                 
00375         
00376         }
00377         
00378         function findWhereClausesWithTable($table){
00379                 
00380                 $clauses = array();
00381                 if ( isset($this->_data['where_clause']) and is_array($this->_data['where_clause']) ){
00382                         $this->_findClausesWithTable_rec($table, $this->_data['where_clause'], $clauses);
00383                 }
00384                 return $clauses;
00385                 
00386         }
00387         
00388         function findJoinClausesWithTable($table){
00389                 $clauses = array();
00390                 
00391                 if ( is_array($this->_data['table_join_clause']) ){
00392                         foreach ( $this->_data['table_join_clause'] as $index=>$jc){
00393                                 $this->_findClausesWithTable_rec($table, $jc, $clauses);
00394                                 
00395                         }
00396                 }
00397                 
00398                 return $clauses;
00399         
00400         }
00401         
00402         
00433         function addMetaDataColumn($columnName, $fullColumnNames=false){
00434                 if ( strpos($columnName, '.') !== false ){
00435                         list( $table, $shortName) = explode('.', $columnName);
00436                 } else {
00437                         $shortName = $columnName;
00438                 }
00439                 
00440                 $aliasName = str_replace('.','_',$columnName);
00441                 
00442                 // at this point $alias should hold the valid name of the column for which we want info.
00443                 $aliasColumnName = $this->unresolveColumnName($columnName);
00444                 if ( PEAR::isError($aliasColumnName) ){
00445                         return $aliasColumnName;
00446                 }
00447                 $func = array('name'=>'length', 'args'=>array(array('type'=>'ident', 'value'=>$aliasColumnName)), 'alias'=>'__'.($fullColumnNames ? $aliasName : $shortName).'_length');
00448                 
00449                 if ( !isset( $this->_data['set_function'] ) ){
00450                         $this->_data['set_function'] = array();
00451                 }
00452                 
00453                 /*
00454                  * Let's see if this function has already been added.
00455                  */
00456                 $index = array_search($func, $this->_data['set_function']);
00457                 if ( $index === false ){
00458                         $this->_data['set_function'][] = $func;
00459                 }
00460                 
00461         
00462         }
00463         
00468         function addMetaDataColumns($fullColumnNames = false){
00469                 if ( !isset( $this->_data['column_names']) ) return;
00470                 foreach ( $this->_data['column_names'] as $columnName){
00471                         $this->addMetaDataColumn($columnName, $fullColumnNames);
00472                 }
00473         }
00474          
00475          
00476         function _findClausesWithTable_rec($table, &$root, &$clauses){
00477                 
00478                 foreach ( array('arg_1','arg_2') as $arg){
00479                         if ( !isset( $root[$arg]) ) continue;
00480                         $type = (isset( $root[$arg]['type'] ) ? $root[$arg]['type'] : null);
00481                         switch ($type){
00482                                 case 'subclause':
00483                                         $this->_findClausesWithTable_rec($table, $root[$arg]['value'], $clauses);
00484                                         break;
00485                                         
00486                                 case 'ident':
00487                                         if ( $this->getTableName($root[$arg]['value']) == $table ) array_push($clauses, $root);
00488                                         break;
00489                                         
00490                                 default:
00491                                         $this->_findClausesWithTable_rec($table, $root[$arg], $clauses);
00492                                         
00493                                         
00494                         }
00495                 }       
00496         }
00497         
00498         function findWhereClausesWithPattern($regex){
00499                 $clauses = array();
00500                 if ( isset($this->_data['where_clause']) and is_array($this->_data['where_clause']) ){
00501                         $this->_findClausesWithPattern_rec($regex, $this->_data['where_clause'], $clauses);
00502                 }
00503                 return $clauses;
00504         }
00505         
00506         function findJoinClausesWithPattern($regex){
00507                 $clauses = array();
00508                 if ( is_array($this->_data['table_join_clause']) ){
00509                         foreach ( $this->_data['table_join_clause'] as $jc ){
00510                                 $this->_findClausesWithPattern_rec($regex, $jc, $clauses);
00511                         }
00512                 }
00513                 
00514                 return $clauses;
00515         
00516         }
00517         
00518         function _findClausesWithPattern_rec( $regex, &$root, &$clauses){
00519 
00520                 foreach ( array('arg_1','arg_2') as $arg){
00521 
00522                         if ( !isset( $root[$arg]) ) continue;
00523                         $type = (isset( $root[$arg]['type'] ) ? $root[$arg]['type'] : null);
00524 
00525                         switch ($type){
00526                                 case 'subclause':
00527 
00528                                         $this->_findClausesWithPattern_rec($regex, $root[$arg]['value'], $clauses);
00529                                         break;
00530                                         
00531                                 case 'text_val':
00532                                 case 'int_val':
00533                                 case 'real_val':
00534 
00535                                         if ( preg_match($regex, $root[$arg]['value']) ) array_push($clauses, $root);
00536                                         break;
00537                                         
00538                                 default:
00539 
00540                                         $this->_findClausesWithPattern_rec($regex, $root[$arg], $clauses);
00541                                         
00542                                         
00543                         }
00544                 }
00545         }
00546         
00547         function removeWhereClausesWithTable($table){
00548         
00549                 $clauses = $this->findWhereClausesWithTable($table);
00550                 foreach ($clauses as $clause){
00551                         $this->removeWhereClause($clause);
00552                 }
00553         
00554         }
00555         
00556         
00557         function removeJoinClausesWithTable($table){
00558                 $clauses = $this->findJoinClausesWithTable($table);
00559                 foreach ($clauses as $clause){
00560                         $this->removeJoinClause($clause);
00561                 }
00562         
00563         }
00564         
00565         function removeWhereClausesWithPattern($regex){
00566                 $clauses = $this->findWhereClausesWithPattern($regex);
00567                 foreach ($clauses as $clause){
00568                         $this->removeWhereClause($clause);
00569                 }
00570         
00571         }
00572         
00573         function removeJoinClausesWithPattern($regex){
00574                 $clauses = $this->findJoinClausesWithPattern($regex);
00575                 foreach ($clauses as $clause){
00576                         $this->removeJoinClause($clause);
00577                 }
00578         }
00579         
00585         function packTables($exempt=array()){
00586                 $selected_tables = array();
00587                 
00588                 // Find the tables that are selected -- these are absolutely necessary
00589                 foreach ($this->_data['column_names'] as $column){
00590                         $selected_tables[] = $this->getTableName($column);
00591                         $selected_tables = array_unique($selected_tables);
00592                 }
00593                 
00594                 $removed_tables = array();
00595                 foreach ( $this->_data['table_names'] as $index=>$table_name){
00596                         
00597                         // If this table is in the "exempt" list, we leave it alone
00598                         if ( in_array($table_name, $exempt) ) continue;
00599 
00600                         
00601                         // If this table is selected, it is needed -- so skip it
00602                         if ( in_array($table_name, $selected_tables) ) continue;
00603                         
00604                         
00605                         
00606                         // IF  this table is involved in a nontrivial join, it is exempt
00607                         $found = $this->findJoinClausesWithTable($table_name);
00608                         if ( count($found) > 0 ) continue;
00609                         
00610                         // If this table is involved in any where clauses, then it is needed
00611                         $found = $this->findWhereClausesWithTable( $table_name);
00612                         if ( count($found) > 0 ) continue;
00613                         
00614                         
00615                         
00616                         // At this point, the table appears to have no purpose in the query
00617                         $removed_tables[] = $table_name;
00618                 }
00619 
00620                 $table_names = array();
00621                 $table_join = array();
00622                 $table_join_clause = array();
00623                 $table_aliases = array();
00624                 $newIndex = 0;
00625                 foreach ( $this->_data['table_names'] as $index=>$table_name){
00626                         if ( !in_array($table_name, $removed_tables) ){
00627                                 $table_names[] = $table_name;
00628                                 if ( $index > 0 and $newIndex > 0 ){
00629                                         $table_join[] = $this->_data['table_join'][$index-1];
00630                                 }
00631                                 $table_join_clause[] = $this->_data['table_join_clause'][$index];
00632                                 $table_aliases[] = $this->_data['table_aliases'][$index];
00633                                 $newIndex++;
00634                         }
00635                 }
00636                 
00637                 $this->_data['table_names'] = $table_names;
00638                 $this->_data['table_aliases'] = $table_aliases;
00639                 $this->_data['table_join'] = $table_join;
00640                 $this->_data['table_join_clause'] = $table_join_clause;
00641                                         
00642         }
00643         
00644         
00645         function fixColumns(){
00646                 if ( PEAR::isError($this->_data) ){
00647                         throw new Exception($this->_data->toString(), E_USER_ERROR);
00648                 }
00649                 for ($i=0; $i<count($this->_data['column_names']); $i++){
00650                         $name =& $this->_data['column_names'][$i];
00651                         if ( strpos($name, '.') === strlen($name)-1 ) $name .= '*';
00652                         unset($name);
00653                 }
00654         
00655         }
00656         
00657         function makeEquivalenceLabels(&$labels, &$values){
00658                 
00659                 $roots = array();
00660                 if ( isset( $this->_data['where_clause'] ) and is_array( $this->_data['where_clause'] )){
00661                         $roots[] =& $this->_data['where_clause'];
00662                 }
00663                 if ( isset( $this->_data['table_join_clause'] ) and is_array( $this->_data['table_join_clause']) ){
00664                         foreach ( $this->_data['table_join_clause'] as $clause ){
00665                                 if ( is_array($clause) ){
00666                                         $roots[] = $clause;
00667                                 }
00668                         }
00669                 }
00670                 foreach ($roots as $root){
00671                         $this->_makeEquivalenceLabels($labels, $values, $root);
00672                 }
00673         
00674         
00675         }
00676         
00677         
00678         function _makeEquivalenceLabels_rec( &$labels, &$values, &$root){
00679         
00680         
00681         }
00682         
00683         
00684         function translate_select_query(){
00685         
00686         }
00687         
00688         function getTableNames(){
00689                 $tables = array();
00690                 $this->getTableNames_rec($this->_data, $tables);
00691                 return array_unique($tables);
00692         }
00693         
00694         function getTableNames_rec(&$root, &$tables){
00695                 if ( isset($root['table_names']) ){
00696                         foreach ($root['table_names'] as $table){
00697                                 $tables[] = $table;
00698                         }
00699                 }
00700                 foreach ( $root as $key=>$val ){
00701                         if ( is_array($val) ){
00702                                 $this->getTableNames_rec($val, $tables);
00703                         }
00704                 }
00705                 return true;
00706         }
00707         
00708         
00709         
00710         
00711 
00712 }
 All Data Structures Namespaces Files Functions Variables Enumerations