Xataface  2.0alpha2
Xataface Application Framework
 All Data Structures Namespaces Files Functions Variables Groups Pages
QueryTranslator.php
Go to the documentation of this file.
1 <?php
2 /*-------------------------------------------------------------------------------
3  * Xataface Web Application Framework
4  * Copyright (C) 2005-2008 Web Lite Solutions Corp (shannah@sfu.ca)
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19  *-------------------------------------------------------------------------------
20  */
31  var $app;
32  var $_tableNames = array(); // [Alias -> Name]
33  var $_tableAliases = array();// [Name -> Alias]
34  var $_tables = array(); // [Dataface_Table]
35 
36  var $_tableNames_tr = array(); // translated table names : [Alias -> Name]
37  var $_tableAliases_tr = array(); // translated table aliases : [Name -> Alias]
38  var $_tableAliasTranslationMap = array(); // maps translated table alias to original table alias
39 
40  var $_columnTranslationMap = array();
41 
42  var $_tableTranslations = null;
43 
44  var $_lang = null;
45  var $_data; // the parsed data after SQL parser has parsed the query.
46  var $_data_translated; // the translated data.
47 
48  var $_parser;
50  var $_query;
51 
53 
54 
68  function Dataface_QueryTranslator($lang=null){
69  $this->app =& Dataface_Application::getInstance();
70  if ( !isset($lang) ) $lang = $this->app->_conf['lang'];
71  $this->_lang = $lang;
72 
73  //if ( !@$this->app->_conf['default_language_no_fallback'] or $this->app->_conf['default_language'] != $lang ){
74  // In Dataface 0.6.10 the default behavior of the query translator was
75  // changed so that default language queries are not changed. This
76  // behavior can be reversed by adding the default_language_no_fallback=1
77  // flag to the conf.ini file.
78 
79  import('SQL/Parser.php');
80  $this->_parser = new SQL_Parser( null, 'MySQL');
81  import('SQL/Compiler.php');
82  $this->_compiler =& SQL_Compiler::newInstance('mysql');
83  $this->_compiler->version = 2;
84  //}
85 
86  }
87 
99  function setParentContext(&$translator){
100  $this->parentContext =& $translator;
101  }
102 
103  function translateQuery($query, $lang=null, $compile = true){
104  //echo "Translating query: ".$query;
105  if ( !is_array($query) ){
106 
107 
108  $query = trim($query);
109  $this->_query = $query;
110  $command = strtolower(substr($query, 0, strpos($query, ' ')));
111  } else {
112  $command = $query['command'];
113  }
114 
115  // Reset all of the private variables that are used for temp storage
116  // while parsing.
117  unset($this->_tableNames);
118  unset($this->_tableAliases);
119  unset($this->_tableAliasTranslationMap);
120  unset($this->_columnTranslationMap);
121  unset($this->_data);
122  unset($this->_talbeNames_tr);
123  unset($this->_tableAliases_tr);
124  $this->_tableNames = array();
125  $this->_tableAliases = array();
126  $this->_tableAliasTranslationMap = array();
127  $this->_columnTranslationMap = array();
128  if ( isset($lang) ) $this->_lang = $lang;
129 
130  // In Dataface 0.6.10 we changed the default behavior of the translations so
131  // that the translation table is not used for the default language.
132  // This can be reversed by adding the default_language_no_fallback=1 flag
133  // to the conf.ini file.
134  // If the flag has not been added, then queries in the default language
135  // will be returned unchanged.
136  if ( !@$this->app->_conf['default_language_no_fallback'] and ($this->_lang == $this->app->_conf['default_language']) ){
137 
138  return array($query);
139  }
140  if ( is_array($query) ){
141  $this->_data = $query;
142  } else {
143  $this->_data = $this->_parser->parse($query);
144  }
145  if ( PEAR::isError($this->_data) ) return $this->_data;
146  $this->_tableNames_tr = array();
147  $this->_tableAliases_tr = array();
148 
149 
150 
151 
152 
153  switch ($command){
154  case 'select': return $this->translateSelectQuery($query,$lang, $compile);
155  case 'update': return $this->translateUpdateQuery($query,$lang, $compile);
156  case 'insert': return $this->translateInsertQuery($query,$lang, $compile);
157  case 'delete': return $this->translateDeleteQuery($query,$lang, $compile);
158  default: return PEAR::raiseError(
159  df_translate(
160  'scripts.Dataface.QueryTranslator.translateQuery.ERROR_INVALID_QUERY',
161  "Invalid query attempted to be translated. Expected select, update, or insert query but received: ".$query,
162  array('query'=>$query)
163  )
164  );
165  }
166  }
167 
168 
169 
170  function translateSelectQuery($query, $lang = null, $compile = true){
171  //echo "Translating $query";
172  // Make a short ref to the parsed data structure of the query
173  $d =& $this->_data;
174  // fill in the tableNames:
175  $numTables = count($d['tables']); // number of tables in the query
176  for ($i=0; $i<$numTables; $i++){
177  if ( $d['tables'][$i]['type'] == 'ident' ){
178  $tname = $d['tables'][$i]['value'];
179  $talias = $d['tables'][$i]['alias'];
180  if ( !$talias ) $talias = $tname;
181  } else {
182  // This table is a subselect - we won't keep this in the tables array
183  $translator = new Dataface_QueryTranslator($this->_lang);
184  $d['tables'][$i]['value'] = $translator->translateQuery($d['tables'][$i]['value'], null, false);
185  continue;
186  }
187 
188  $this->_tableNames[$talias] = $tname;
189  $this->_tableAliases[$tname] = $talias;
190  if (!isset( $this->_tables[$tname] ) ){
191  $this->_tables[$tname] =& Dataface_Table::loadTable($tname);
192  }
193  }
194 
195  if ( isset( $this->parentContext) ){
196  foreach ( array_keys($this->parentContext->_tables) as $tablename ){
197  if ( !isset($this->_tables[$tablename]) ){
198  $this->_tables[$tablename] =& $this->parentContext->_tables[$tablename];
199  $this->_tableAliases[$tablename] = $this->parentContext->_tablesAliases[$tablename];
200  $this->_tableNames[$this->_tableAliases[$tablename]] = $tablename;
201  }
202  }
203  }
204 
205 
206 
207  // Prepare the translated data array:
208  $this->_data_translated = $d;
209  // Placeholder for the data structure for the translated query
210  $this->_data_translated['columns'] = array();
211  if ( !isset( $d['table_join'] ) ) $this->_data_translated['table_join'] = array();
212  foreach ($this->_data_translated['table_join'] as $k=>$v){
213  if ( $v == "," ){
214  // For some reason the comma causes problems when we add joins at the end
215  $this->_data_translated['table_join'][$k] = "inner join";
216  }
217  }
218  // If there were no joins, we initialize it.
219 
220  // Translate the column names
221  if ( isset( $d['columns'] ) and is_array($d['columns']) ) {
222  $numCols = count($d['columns']);
223  for ($i=0; $i<$numCols; $i++){
224  $currColumn = $d['columns'][$i];
225  //$this->_data_translated['columns'][] =& $currColumn;
226  $this->translateColumn($currColumn);
227  unset($currColumn);
228  }
229 
230  }
231 
232 
233  // Translate the where clause
234  if ( isset($d['where_clause']) ){
235  $this->translateClause($this->_data_translated['where_clause']);
236  }
237 
238  // Translate the join clause
239  if ( isset($d['table_join_clause']) ){
240  $numClauses = count($d['table_join_clause']);
241  for ($i=0; $i<$numClauses; $i++){
242 
243  $this->translateClause($this->_data_translated['table_join_clause'][$i]);
244  }
245  }
246 
247  // Translate order by clause
248  if ( isset($d['sort_order']) ){
249  $numClauses = count($d['sort_order']);
250  for ( $i=0; $i<$numClauses;$i++){
251  $this->translateClause($this->_data_translated['sort_order'][$i]);
252  }
253  }
254 
255  if ( $compile ){
256  $out = array($this->_compiler->compile( $this->_data_translated));
257  //echo "Translated as: ";print_r($out);
258  return $out;
259  } else {
261  }
262 
263 
264  }
265 
266 
267 
277  function translateColumn(&$col, $column_alias=null, $update=true){
278  if ( is_array($col) ){
279  // For backwards compatability it is possible for $col to simply
280  // be the name of a column. Fron now on, however, $col will be
281  // an associative array of column information of the form:
282  // 'type' => ident|func
283  // 'table' => ..
284  // 'value' => ..
285  // 'alias' => ..
286  $columnInfo =& $col;
287  unset($col);
288  switch ( $columnInfo['type'] ){
289  case 'glob':
290  case 'ident':
291  $col = $columnInfo['value'];
292  if ( $columnInfo['table'] ) $col = $columnInfo['table'].'.'.$col;
293  $column_alias = $columnInfo['alias'];
294  break;
295 
296  case 'func':
297  //print_r($columnInfo['value']);
298  $this->translateFunction($columnInfo['value'], $update);
299  if ( $update ){
300  $this->_data_translated['columns'][] =& $columnInfo;
301  return true;
302  } else {
303  // This shouldn't happen.
304  }
305  break;
306  }
307  }
308  if ( $update && preg_match('/\.{0,1}\*$/', $col) ){
309  // this is a glob column.
310  $expandedGlob = $this->expandGlob($col);
311  foreach ( $expandedGlob as $globCol){
312  $currColumnTable = null;
313  $currColumnName = null;
314  $currColumnArr = explode('.', $globCol);
315  if ( count($currColumnArr) > 1){
316  $currColumnName = $currColumnArr[1];
317  $currColumnTable = $currColumnArr[0];
318  } else {
319  $currColumnName = $currColumnArr[0];
320  $currColumnTable = '';
321  }
322  $currColumn = array('type'=>'ident','table'=>$currColumnTable,'value'=>$currColumnName, 'alias'=>'');
323  //$this->_data_translated['columns'][] =& $currColumn;
324  $this->translateColumn($currColumn);
325  unset($currColumn);
326  }
327  return true;
328  }
329 
330  $originalColumnName = $col;
331  $tablename = null;
332  if ( strpos($col, '.') !== false ) {
333  // this column has table portion
334  list($alias,$col) = explode('.', $col);
335 
336  if ( isset($this->_tableNames[$alias]) ){
337  $tablename = $this->_tableNames[$alias];
338  }
339  } else {
340  $alias = '';
341  foreach ( array_keys($this->_tables) as $tableKey){
342  if ( isset($this->_tables[$tableKey]->_fields[$col]) ){
343  $tablename = $this->_tables[$tableKey]->tablename;
344  break;
345  }
346  }
347 
348  }
349 
350  if ( !isset($tablename) ){
351 
352  // This column is not associated with a table. This means
353  // that it could be referencing a subselect - which would
354  // already have been translated, so we don't need to do any
355  // translations - or it could be that the column doesn't exist.
356  // In either case we just leave it alone and MySQL can return
357  // an error in the latter case.
358  if ( !$update ){
359 
360  return array('column_names'=>array(($alias ? $alias.'.' : '').$col), 'column_aliases'=>array($column_alias));
361  } else {
362  if ( isset( $columnInfo ) ){
363  $this->_data_translated['columns'][] = $columnInfo;
364  }
365  return false;
366  }
367  } else if ( isset( $this->_tableAliases[$tablename]) and !$alias ){
368  $alias = $this->_tableAliases[$tablename];
369  }
370 
371  $table =& $this->_tables[$tablename];
372  $translation = $table->getTranslation($this->_lang);
373  if ( !isset($translation) or !in_array($col, $translation) or in_array($col, array_keys($table->keys()) ) ){
374  // there is no translation for this field
375  // so we do nothing here
376  if ( $update ){
377  //$this->_data_translated['column_names'][] = ($alias ? $alias : $tablename).'.'. $col;
378  //$this->_data_translated['column_aliases'][] = $column_alias;
379  if ( isset($columnInfo) ){
380  if ( !$columnInfo['table'] ) $columnInfo['table'] = $tablename;
381  if ( $alias ) $columnInfo['table'] = $alias;
382  $this->_data_translated['columns'][] = $columnInfo;
383  }
384  return false;
385  } else {
386  return array('column_names'=>array(($alias ? $alias : $tablename).'.'.$col), 'column_aliases'=>array($column_alias));
387  }
388  } else {
389  // the table has a translation
390 
391  // the column has a translation
392  $old_alias = $alias;
393  if ( !$alias ){
394  $alias = $tablename.'__'.$this->_lang;
395 
396  } else {
397  $alias = $alias.'__'.$this->_lang;
398  }
399  if ( !isset($this->_tableNames_tr[$alias] ) ){
400  // If the translation table hasn't been recorded yet, let's do that now
401  $this->_tableNames_tr[$alias] = $tablename.'_'.$this->_lang;
402  }
403  if ( !isset($this->_tableAliases_tr[$tablename.'_'.$this->_lang]) ){
404  $this->_tableAliases_tr[$tablename.'_'.$this->_lang] = $tablename;
405  }
406  if ( !isset($this->_tableAliasTranslationMap[$alias]) ){
407  $this->_tableAliasTranslationMap[$alias] = $old_alias;
408  }
409 
410  // Now we add the join clause for the translation table (if necessary)
411  if ( !in_array( $alias, $this->_data_translated['table_aliases'] ) ){
412  $this->_data_translated['table_names'][] = $tablename.'_'.$this->_lang;
413  $this->_data_translated['table_aliases'][] = $alias;
414  $this->_data_translated['tables'][] = array('type'=>'ident','value'=>$tablename.'_'.$this->_lang, 'alias'=>$alias);
415  $this->_data_translated['table_join'][] = 'left join';
416  $join_clause = null;
417  foreach ( array_keys($this->_tables[$tablename]->keys()) as $keyName){
418  $temp = array(
419  'arg_1'=>array(
420  'value'=> ( $old_alias ? $old_alias : $tablename).'.'.$keyName,
421  'type'=> 'ident'
422  ),
423  'op'=>'=',
424  'arg_2'=>array(
425  'value'=> $alias.'.'.$keyName,
426  'type'=>'ident'
427  )
428  );
429  if ( !isset($join_clause) ){
430  $join_clause =& $temp;
431  } else {
432  $temp2 =& $join_clause;
433  unset($join_clause);
434  $join_clause = array(
435  'arg_1'=>&$temp2,
436  'op'=>'and',
437  'arg_2'=>&$temp
438  );
439  }
440 
441  unset($temp);
442  unset($temp2);
443 
444 
445  }
446  $this->_data_translated['table_join_clause'][] = $join_clause;
447  }
448 
449 
450 
451 
452  // Now adjust the column name
453 
454  $func_struct =
455  array(
456  'name'=>'ifnull',
457  'args'=>array(
458  array(
459  'type'=>'ident',
460  'value'=>$alias.'.'.$col
461  ),
462  array(
463  'type'=>'ident',
464  'value'=>($old_alias ? $old_alias : $tablename).'.'.$col
465  ),
466  ),
467  'alias'=>($column_alias ? $column_alias : $col)
468  );
469  if ( $update && !isset( $this->_columnTranslationMap[$originalColumnName] ) ) {
470  $this->_columnTranslationMap[$originalColumnName] = $func_struct['alias'];
471  }
472  if ( $update){
473  if ( isset($columnInfo) ){
474  $columnInfo['type'] = 'func';
475  $columnInfo['table'] = '';
476  $columnInfo['value'] = $func_struct;
477  $columnInfo['alias'] = $func_struct['alias'];
478  $this->_data_translated['columns'][] = $columnInfo;
479  return true;
480  //print_r($columnInfo);
481  } else {
482  $this->_data_translated['set_function'][] = $func_struct;
483  return true;
484  }
485  } else {
486  return array('set_function'=>array($func_struct));
487  }
488 
489  }
490  return true;
491  }
492 
499  function translateFunction(&$func, $update=true){
500  if ( !isset( $func['args'] ) ) return false;
501  if ( !$update ) $new_func = $func;
502  foreach ( array_keys($func['args']) as $key){
503  $arg =& $func['args'][$key];
504  switch( $arg['type'] ){
505  case 'ident':
506  $new_value = $this->translateColumn($func['args'][$key]['value'], null, false);
507 
508  if ( isset($new_value['set_function']) ) {
509  $new_arg = array('type'=>'function', 'value'=>$new_value['set_function'][0]);
510  } else if ( isset( $new_value['column_names'] ) ){
511  $new_arg = array('type'=>'ident', 'value'=>$new_value['column_names'][0]);
512  }
513  if ( !$update ){
514  $new_func['args'][$key] = $new_arg;
515  } else {
516  $func['args'][$key] = $new_arg;
517  }
518  break;
519 
520  case 'function':
521  if ( $update ) {
522  $this->translateFunction($func['args'][$key]['value'], true);
523  } else {
524  $this->translateFunction($new_func['args'][$key]['value'], true);
525  }
526  break;
527 
528  }
529  unset($arg);
530 
531  }
532 
533  if ( !$update ){
534 
535  return $new_func;
536  } else {
537  return true;
538  }
539 
540  }
541 
546  function translateClause(&$clause){
547  if ( isset($clause['type']) ) $this->translateUnaryClause($clause);
548  else $this->translateBinaryClause($clause);
549 
550  }
551 
552  function translateBinaryClause(&$clause){
553  if ( !is_array($clause) ) return;
554  foreach ( array('arg_1','arg_2') as $arg ){
555  if ( !isset($clause[$arg]) ) continue;
556  $this->translateUnaryClause($clause[$arg]);
557  }
558  }
559 
560  function translateUnaryClause(&$clause){
561  if ( !isset($clause['type']) ){
562  $this->translateClause($clause);
563  } else {
564  switch( $clause['type'] ){
565  case 'ident':
566  $new_value = $this->translateColumn($clause['value'], null, false);
567  if ( is_array($new_value) and isset($new_value['set_function']) ) {
568  // the translation is a function
569  $clause['type'] = 'function';
570  $clause['value'] = $new_value['set_function'][0];
571  } else {
572  $clause['value'] = $new_value['column_names'][0];
573  }
574 
575  break;
576 
577  case 'function':
578  $this->translateFunction($clause['value']);
579  break;
580 
581  case 'subclause':
582  $this->translateClause($clause['value']);
583  break;
584 
585  case 'command':
586  case 'subquery':
587  $translator = new Dataface_QueryTranslator($this->_lang);
588  $translator->setParentContext($this);
589  $clause['value'] = $translator->translateQuery($clause['value'], $this->_lang, false);
590 
591  break;
592 
593  //case 'match':
594  // if ( isset($clause['value']) and is_array($clause['value']) ){
595  // $numClauses = count($clause['value']);
596  // for ($i=0; $i<$numClauses; $i++){
597  //
598  //
599  //
600  // }
601  // }
602  // break;
603 
604 
605 
606  }
607  }
608 
609 
610  }
611 
612 
613 
618  function expandGlob($glob){
619  $numTables = count($this->_tableNames);
620  if ( strpos($glob,'.') !== false ){
621  // This is a glob of only a single table or alias
622  list($alias, $glob) = explode('.', $glob);
623  if ( isset( $this->_tableNames[$alias]) ){
624  $out = array_keys($this->_tables[ $this->_tableNames[$alias] ]->fields() );
625  $out2=array();
626  foreach ($out as $col){
627  $out2[] = $alias.'.'.$col;
628  }
629  return $out2;
630  } else {
631  throw new Exception(
632  df_translate(
633  'scripts.Dataface.QueryTranslator.expandGlob.ERROR_NONEXISTENT_TABLE',
634  "Attempt to expand glob for non-existent table '$alias'",
635  array('table'=>$alias)
636  ), E_USER_ERROR);
637  }
638  } else {
639  $fields = array();
640  foreach ( array_keys($this->_tableNames) as $alias ){
641  $newfields = array_keys($this->_tables[ $this->_tableNames[ $alias ] ]->fields());
642  foreach ( $newfields as $newfield ){
643  //if ( $numTables > 1 ){
644  $fields[] = $alias.'.'.$newfield;
645  //} else {
646  // $fields[] = $newfield;
647  //}
648  }
649  }
650  return $fields;
651  }
652 
653  }
654 
662  function translateUpdateQuery($query){
663  /*
664  * This method translates an update query to be multilingual.
665  * It tries to be non-obtrusive in that only column names in the
666  * update list are converted. The where clause is left alone.
667  * As a consequence, this does not support multi-table updates.
668  */
669 
670  $d =& $this->_data;
671  $tableName = $d['table_names'][0];
672  if ( count($d['table_names']) > 1 ){
673  return PEAR::raiseError(
674  df_translate(
675  'scripts.Dataface.QueryTranslator.translateUpdateQuery.ERROR_MULTI_TABLE_UPDATE',
676  'Failed to translate update query because the translator does not support multiple-table update syntax.'
677  ), E_USER_ERROR);
678  }
679 
680  $table = Dataface_Table::loadTable($tableName);
681  $keys = array_keys($table->keys());
682  // Array of the names of fields the form the primary key of this table.
683 
684  $translation = $table->getTranslation($this->_lang);
685  // Array of column names that have a translation in this language.
686 
687 
688  if ( !isset( $translation ) ) $translation = array();
689  // If there is no translation we will just set the translation to
690  // and empty array. Even if there is no translation for the current
691  // language, we should still go through and parse the query in case
692  // a key is being changed and other translations will need to be modified
693  // to maintaing foreign key integrity.
694 
695  // We initialize the data structure to store the update to the translation
696  // table.
697  $this->_data_translated = $d; // to store update query to translation table
698  $this->_data_translated['column_names'] = array();
699  $this->_data_translated['values'] = array();
700  $this->_data_translated['table_names'] = array($tableName.($translation?'_'.$this->_lang:''));
701 
702  // Initialize the data structure to store the update to the original table
703  // after translated columns are removed.
704  $new_data = $d; // to store update query to base table
705  $new_data['column_names'] = array();
706  $new_data['values'] = array();
707 
708  // Initialize the data structure to store the update to the other translation
709  // tables in case the keys are changed. This update is just to synchronize
710  // the keys.
711  $keyChange_data = $d;
712  $keyChange_data['column_names'] = array();
713  $keyChange_data['values'] = array();
714  // if keys are updated, then they should be updated in all translation tables
715  // to remain consistent.
716 
717  $numCols = count($d['column_names']);
718  $translationRequired = false;
719  $originalRequired = false;
720  $keysChanged = false;
721 
722  for ($i=0; $i<$numCols; $i++ ){
723  $col = $d['column_names'][$i];
724  $value = $d['values'][$i];
725  if ( in_array($col, $keys) ){
726  $originalRequired = true;
727  $keysChanged = true;
728  $this->_data_translated['column_names'][] = $col;
729  $this->_data_translated['values'][] = $value;
730  $new_data['column_names'][] = $col;
731  $new_data['values'][] = $value;
732  $keyChange_data['column_names'][] = $col;
733  $keyChange_data['values'][] = $value;
734  } else if ( in_array($col, $translation) ){
735  $translationRequired = true;
736  $this->_data_translated['column_names'][] = $col;
737  $this->_data_translated['values'][] = $value;
738  } else {
739  $originalRequired = true;
740  $new_data['column_names'][] = $col;
741  $new_data['values'][] = $value;
742  }
743  }
744 
745  if (!$translationRequired and !$keysChanged){
746  return array($query);
747  } else {
748  $queryKeys = $this->extractQuery($d);
749  if ( $translationRequired ){
750  $out = array('insert ignore into `'.$tableName.'_'.$this->_lang.'` (`'.implode('`,`', array_keys($queryKeys)).'`) values (\''.implode('\',\'', array_values($queryKeys)).'\')');
751  } else {
752  $out = array();
753  }
754  if ( $originalRequired ) $out[] = $this->_compiler->compile($new_data);
755  $out[] = $this->_compiler->compile($this->_data_translated);
756 
757  if ( $keysChanged ){
758  $translations = array_keys($table->getTranslations());
759  foreach ( $translations as $tr ){
760  if ( $tr == $this->_lang ) continue;
761  $keyChange_data['table_names'] = array($tableName.'_'.$tr);
762  $out[] = $this->_compiler->compile($keyChange_data);
763  }
764  }
765 
766  return $out;
767  }
768 
769 
770 
771 
772 
773  }
774 
779  function extractQuery(&$data){
780  $w =& $data['where_clause'];
781  $out = array();
782  $this->extractQuery_rec($w, $out);
783  return $out;
784  }
785 
786  function extractQuery_rec($clause, &$out){
787  if ( !is_array($clause)) return;
788 
789  if ( isset($clause['arg_1'] ) ){
790  switch ($clause['arg_1']['type']){
791  case 'subclause':
792  $this->extractQuery_rec($clause['arg_1'], $out);
793  break;
794 
795  case 'ident':
796  if ( in_array($clause['arg_2']['type'], array('int_val','real_val','text_val','null') ) and $clause['op'] == '='){
797  $out[$clause['arg_1']['value']] = $clause['arg_2']['value'];
798  }
799  break;
800 
801  }
802  }
803 
804 
805  }
806 
816  function translateInsertQuery($query){
817 
818  /*
819  * This method translates an update query to be multilingual.
820  * It tries to be non-obtrusive in that only column names in the
821  * update list are converted. The where clause is left alone.
822  * As a consequence, this does not support multi-table updates.
823  */
824  $d =& $this->_data;
825  $tableName = $d['table_names'][0];
826  if ( count($d['table_names']) > 1 ){
827  return PEAR::raiseError(
828  df_translate(
829  'scripts.Dataface.QueryTranslator.translateUpdateQuery.ERROR_MULTI_TABLE_UPDATE',
830  'Failed to translate update query because the translator does not support multiple-table update syntax.'
831  ), E_USER_ERROR);
832  }
833 
834  $table = Dataface_Table::loadTable($tableName, null, false, true);
835  if ( PEAR::isError($table) ){
836  return array($query);
837  }
838 
839  $translation = $table->getTranslation($this->_lang);
840  // Array of column names that have a translation in this language.
841 
842  if ( !isset( $translation ) ) return array($query);
843  // there are no translations for this table, so we just return the query.
844 
845  // We initialize the data structure to store the update to the translation
846  // table.
847  $this->_data_translated = $d; // to store update query to translation table
848  $this->_data_translated['column_names'] = array();
849  $this->_data_translated['values'] = array();
850  $this->_data_translated['table_names'] = array($tableName.'_'.$this->_lang);
851 
852  // Initialize the data structure to store the update to the original table
853  // after translated columns are removed.
854  $new_data = $d; // to store update query to base table
855  $new_data['column_names'] = array();
856  $new_data['values'] = array();
857 
858 
859 
860  $numCols = count($d['column_names']);
861  $translationRequired = false;
862  $originalRequired = true; // for inserts the original is always required!
863 
864  if ( ($aif = $table->getAutoIncrementField()) ){
865  $new_data['column_names'][] = $this->_data_translated['column_names'][] = $aif;
866  $new_data['values'][] = $this->_data_translated['values'][] = array('type'=>'text_val', 'value'=>'%%%%%__MYSQL_INSERT_ID__%%%%%');
867 
868  }
869 
870  for ($i=0; $i<$numCols; $i++ ){
871  $col = $d['column_names'][$i];
872  $value = $d['values'][$i];
873  if ( in_array($col, $translation)){
874  $translationRequired = true;
875  $this->_data_translated['column_names'][] = $col;
876  $this->_data_translated['values'][] = $value;
877  }
878 
879  $new_data['column_names'][] = $col;
880  $new_data['values'][] = $value;
881 
882  }
883 
884  if (!$translationRequired){
885  $out = array($query);
886  } else {
887  $out = array();
888 
889  if ( $originalRequired ) $out[] = $this->_compiler->compile($new_data);
890  $out[] = $this->_compiler->compile($this->_data_translated);
891 
892  }
893 
894  return $out;
895 
896  }
897 
903  function translateDeleteQuery($query){
904  $d =& $this->_data;
905  $out = array($this->_compiler->compile($d));
906  $table = Dataface_Table::loadTable($d['table_names'][0]);
907  foreach ( array_keys($table->getTranslations()) as $lang){
908  $d['table_names'][0] = $table->tablename.'_'.$lang;
909  $out[] = $this->_compiler->compile($d);
910  }
911  return $out;
912  }
913 }