![]() |
Xataface 2.0
Xataface Application Framework
|
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 }