Squiz Matrix  4.12.2
 All Data Structures Namespaces Functions Variables Pages
matrixsqlclient.php
1 #!/usr/bin/php
2 <?php
19 $rev = "version 1.01, r113";
20 
21 define('UP', chr(27).chr(91).chr(65));
22 define('DOWN', chr(27).chr(91).chr(66));
23 define('RIGHT', chr(27).chr(91).chr(67));
24 define('LEFT', chr(27).chr(91).chr(68));
25 
26 // Install signal handler (if possible)
27 if (function_exists("pcntl_signal")) {
28  declare(ticks = 1);
29 
30  function sig_handler($signal)
31  {
32  global $matrixSqlTerminal;
33  switch ($signal) {
34  case SIGINT:
35  // Tell SQL client to cancel what it's doing
36  $matrixSqlTerminal->cancel();
37  break;
38  case SIGCONT:
39  // Reset the terminal again when the process is unfrozen
40  $matrixSqlTerminal->resetTerminal();
41  break;
42  }
43  }
44  pcntl_signal(SIGCONT, "sig_handler");
45  pcntl_signal(SIGINT, "sig_handler");
46 }
47 
48 error_reporting(E_ALL);
49 mb_internal_encoding("UTF-8");
50 
51 // Run the terminal
52 try {
53  $matrixSqlTerminal = new InteractiveSqlTerminal('MatrixDAL');
54  $matrixSqlTerminal->connect((isset($_SERVER['argv'][1])) ? $_SERVER['argv'][1] : '');
55  $matrixSqlTerminal->setOption("HISTFILE", "~/.matrixsqlclient_history");
56  $matrixSqlTerminal->run();
57 }
58 catch (Exception $e) {
59  // If there is some kind of problem, try to ensure the terminal is reset
60  // for the user.
61  `stty sane`;
62  $matrixSqlTerminal->restoreTerminal();
63 }
64 
73 class Terminal
74 {
82  public static function left($c=1) {
83  for ($i=0; $i<$c; $i++) echo chr(8);
84  }
85 
93  public static function backspace($c=1) {
94  self::left($c);
95  for ($i=0; $i<$c; $i++) echo ' ';
96  self::left($c);
97  }
98 
105  public function getTtySize()
106  {
107  return explode("\n", `printf "lines\ncols" | tput -S`);
108  }
109 
115  public static function bell()
116  {
117  echo chr(7);
118  }
119 
120 }
121 
131 {
135  private $_db;
136 
140  private $_historyFile = '';
141 
145  private $_tty_saved = 'sane';
146 
150  private $_shell;
151 
155  private $_output_buffer = array();
156 
160  private $_options = array(
161  'HISTFILE' => '~/.phpsqlc_history',
162  'HISTSIZE' => 500,
163  'timing' => "off",
164  'disable-completion' => "off",
165  'rowlimit' => 500,
166  'pager' => 'on',
167  );
168 
173  private $_cancel = false;
174 
180  public function __construct($backend)
181  {
182  $this->resetTerminal(true);
183 
184  // Instantiate database backend plugin
185  $this->_db = new DbBackend($backend);
186 
187  // Instantiate/initialise stuff
188  $this->_shell = new SimpleReadline();
189 
190  // Parse options; set autocomplete on/off, etc.
191  $this->_parseOptions();
192  }
193 
197  public function __destruct()
198  {
199  $this->_shell->writeHistory($this->_historyFile);
200  $this->restoreTerminal();
201  }
202 
206  public function cancel() {
207  echo "Cancel request\n";
208  $this->_cancel = true;
209  }
210 
218  public function connect($dsn)
219  {
220  return $this->_db->connect($dsn);
221  }
222 
228  public function run()
229  {
230  $this->_shell->readHistory($this->_historyFile);
231 
232  $prompt = '=# ';
233  $sql = '';
234 
235  ob_start();
236  echo "Welcome to matrixsqlclient";
237  if (!empty($GLOBALS['rev'])) {
238  echo " (" . $GLOBALS['rev'] . ")";
239  }
240  echo ", the interactive database terminal in PHP.";
241  echo "\n\nYou are now connected.";
242  echo "\nDatabase type: " . $this->_db->getDbType() . $this->_db->getDbVersion() . ".\n\n";
243  ob_end_flush();
244 
245  while (1) {
246 
247  // Prompt for input
248  $line = $this->_shell->readline($this->_db->getDbName() . $prompt);
249 
250  if ($line === "") {
251  echo "\n";
252  continue;
253  }
254 
255  // Exits
256  if ((mb_substr(trim($line), 0, 4) == 'exit') || (mb_substr(trim($line), 0, 4) == 'quit') || (mb_substr(trim($line), 0, 2) == '\q')) {
257  echo "\n";
258  exit;
259  }
260  if (mb_substr($line, mb_strlen($line)-1, mb_strlen($line)) === chr(4)) {
261  echo "\q\n";
262  exit;
263  }
264 
265  // CTRL-C cancels any current query
266  if (ord(mb_substr($line, mb_strlen($line)-1, mb_strlen($line))) === 3) {
267  $sql = '';
268  $line = '';
269  $prompt = '=# ';
270  echo "\n";
271  continue;
272  }
273 
274  if (mb_strlen($line) > 0) {
275  // Add this command to the history
276  $this->_shell->addHistory(strtr($line, "\n", " "));
277  }
278 
279  if (mb_substr(trim($line), 0, 7) == "\\timing") {
280 
281  $this->setOption("timing", !$this->_getOptionValue("timing"));
282 
283  if ($this->_getOptionValue("timing")) {
284  echo "\nTiming is on.";
285  } else {
286  echo "\nTiming is off.";
287  }
288 
289  echo "\n";
290  continue;
291  }
292 
293  // "\set" command
294  if (strlen(trim($sql)) === 0 && mb_substr(trim($line), 0, 4) == "\set") {
295 
296  $params = explode(" ", $line, 3);
297 
298  // "\set" with no options - show existing options/values
299  if (count($params) === 1) {
300 
301  $options = $this->_getOptions();
302 
303  if (count($options) > 0) {
304 
305  foreach ($this->_getOptions() as $option => $value) {
306  $value = ($value === true) ? "on" : $value;
307  $value = ($value === false) ? "off" : $value;
308  echo "\n" . $option . " = '" . $value . "'";
309  }
310  }
311 
312  // "set" a particular value
313  } else {
314 
315  $params = array_pad($params, 3, "");
316  $this->setOption($params[1], $params[2]);
317  $this->_parseOptions();
318  }
319 
320  echo "\n";
321  continue;
322  }
323 
324  $sql .= "\n" . $line;
325 
326  // If the SQL string is terminated with a semicolon, or the DB module wants
327  // to accept it (eg. for a macro), then execute it
328  if ($this->_db->matchesMacro($sql) || mb_strpos($sql, ';')) {
329 
330  echo "\n";
331 
332  $sql = trim($sql);
333 
334  try {
335  // Run the SQL
336  $this->restoreTerminal();
337  $source_data = @$this->_db->execute($sql);
338  }
339  catch (Exception $e) {
340  echo "\n" . $e->getMessage() . "\n";
341 
342  $this->resetTerminal(true);
343 
344  // Reset the prompt cause its a new query
345  $prompt = '=# ';
346  $sql = '';
347 
348  echo "\n";
349  continue;
350  }
351 
352  $this->resetTerminal(true);
353 
354  // If cancel request was triggered then just discard anything that might
355  // come back
356  if ($this->_cancel) {
357  $source_data = null;
358  $this->_cancel = false;
359 
360  echo "Cancelled\n";
361 
362  $prompt = '=# ';
363  $sql = '';
364  continue;
365  }
366 
367  // If we get an array back, it's rows
368  if (is_array($source_data)) {
369 
370  $rowlimit = (int)$this->_getOptionValue("rowlimit");
371 
372  if (count($source_data) > $rowlimit) {
373  $this->_addToLinesBuffer(explode("\n", "\n\nWARNING: Number of rows returned exceeded rowlimit.\nOnly the first $rowlimit rows are being shown. Use \set rowlimit <num> to adjust.\n\n"));
374  $source_data = array_slice($source_data, 0, $rowlimit);
375  }
376 
377  // Only render the table if rows were returned
378  if (!empty($source_data)) {
379 
380  // Render the table
381  $table = new ArrayToTextTable($source_data);
382  $table->showHeaders(true);
383 
384  $data = explode("\n", $table->render(true));
385  array_pop($data);
386  $this->_addToLinesBuffer($data);
387  }
388 
389  // Build count summary (at end of table) and add to line buffer
390  $count_str = "(" . count($source_data) . " row";
391  if (count($source_data) !== 1) {
392  $count_str .= "s";
393  }
394  $count_str .= ")";
395 
396  $this->_addToLinesBuffer(array($count_str, ""));
397 
398  // Assuming it's a string...
399  } else {
400  $this->_addToLinesBuffer(array($source_data));
401  }
402 
403  if ($this->_getOptionValue("timing")) {
404  // Output amount of time this query took
405  $this->_addToLinesBuffer(array("Time: " . $this->_db->getQueryExecutionTime() . " ms"));
406  }
407 
408  // Output the data
409  $this->_printLines();
410 
411  // Reset the prompt cause its a new query
412  $prompt = '=# ';
413  $sql = '';
414 
415  } elseif (mb_strlen(trim($sql)) > 0) {
416  // We're in the middle of some SQL, so modify the prompt slightly to show that
417  // (like psql does)
418  if ((substr_count($sql, "(") > substr_count($sql, ")"))) {
419  $prompt = '(# ';
420  } else {
421  $prompt = '-# ';
422  }
423  echo "\n";
424  }
425  }
426  }
427 
433  public function restoreTerminal()
434  {
435  system("stty '" . trim($this->_tty_saved) . "'");
436  }
437 
445  public function autoComplete($hint)
446  {
447  $last_word = ltrim(mb_substr($hint, mb_strrpos($hint, ' ')));
448 
449  // Autocomplete table names after a FROM
450  if (preg_match('/SELECT\s+.+\s+FROM\s+\w*$/i', $hint)) {
451  $candidates = $this->_db->getTableNames();
452 
453  // Autocomplete table names after UPDATE
454  } elseif (preg_match('/UPDATE\s+\w*$/i', $hint)) {
455  $candidates = $this->_db->getTableNames();
456 
457  // Autocomplete table names at INSERT INTO
458  } elseif (preg_match('/INSERT INTO\s+\w*$/i', $hint)) {
459  $candidates = $this->_db->getTableNames();
460 
461  // Autocomplete column names after a WHERE
462  } elseif (preg_match('/SELECT\s+.+\s+FROM\s+(.+?)\s+WHERE\s+\w*$/i', $hint, $table_name_search)) {
463  $table_name = @$table_name_search[1];
464  $candidates = $this->_db->getColumnNames($table_name);
465 
466  // Autocomplete column names at UPDATE..SET
467  } elseif (preg_match('/UPDATE\s+(.+?)\s+SET\s+\w*$/i', $hint, $table_name_search)) {
468  $table_name = @$table_name_search[1];
469  $candidates = $this->_db->getColumnNames($table_name);
470  }
471 
472  // Nothing to autocomplete
473  if (empty($candidates)) {
474  return array();
475  }
476 
477  // Autocomplete has options, but user hasn't begun typing yet
478  if ($last_word === "") {
479  return $candidates;
480  }
481 
482  // Autocomplete has options, lets narrow it down based on what the user has
483  // typed already.
484  $matches = array();
485  foreach ($candidates as $candidate) {
486  if (mb_strpos($candidate, $last_word) === 0) {
487  $matches[] = $candidate;
488  }
489  }
490 
491  return $matches;
492  }
493 
502  public function resetTerminal($save_existing=true)
503  {
504  // Save existing settings
505  if ($save_existing) {
506  $this->_tty_saved = `stty -g`;
507  }
508 
509  // Reset terminal
510  system("stty raw opost -ocrnl onlcr -onocr -onlret icrnl -inlcr -echo isig intr undef");
511  }
512 
521  private function _printLines($n=0)
522  {
523 
524  $lines_printed = array();
525 
526  if ($n > 0) {
527 
528  // Print a specific number of lines
529  $line_buffer_len = count($this->_output_buffer);
530  for ($i=0; $i<$line_buffer_len && $i<$n; $i++) {
531  $line = array_shift($this->_output_buffer);
532  echo $line;
533  $lines_printed[] = $line;
534  }
535 
536  // Return the lines printed
537  return $lines_printed;
538 
539  } else {
540 
541  // Get current terminal size
542  $tty_size = $this->_getTtySize();
543 
544  if (!(bool)$this->_getOptionValue("pager") === true || count($this->_output_buffer) < $tty_size[0]) {
545 
546  // Print all lines, if it fits on the tty
547  $this->_printLines(count($this->_output_buffer));
548 
549  } else {
550 
551  // Otherwise, let's paginate...
552 
553  // Print first chunk
554  $last_lines = $this->_printLines($tty_size[0]-1);
555  if ($last_lines[count($last_lines)-1][mb_strlen($last_lines[count($last_lines)-1])-1] != "\n") {
556  echo "\n";
557  }
558  echo "\033[30;47m" . "--More--" . "\033[0m";
559 
560  // Print rest of the chunks
561  while (1) {
562 
563  // Stop printing chunks if the line buffer is empty
564  if (!count($this->_output_buffer) > 0) {
565  // Backspace the "--More--"
567  break;
568  }
569 
570  // Read user input
572 
573  switch ($c) {
574 
575  // 'G' -- print rest of all the output
576  case chr(71):
577  Terminal::backspace(8);
578  $this->_printLines(count($this->_output_buffer));
579  break;
580 
581  // User wants more lines, one at a time
582  case chr(10):
583 
584  // Backspace the "--More--"
585  Terminal::backspace(8);
586 
587  $last_lines = $this->_printLines(1);
588  if ($last_lines[count($last_lines)-1][mb_strlen($last_lines[count($last_lines)-1])-1] != "\n") {
589  echo "\n";
590  }
591  echo "\033[30;47m" . "--More--" . "\033[0m";
592 
593  break;
594 
595  // Page down
596  case chr(32):
597  case chr(122):
598 
599  // Backspace the "--More--"
600  Terminal::backspace(8);
601 
602  $last_lines = $this->_printLines($tty_size[0]-1);
603  if ($last_lines[count($last_lines)-1][mb_strlen($last_lines[count($last_lines)-1])-1] != "\n") {
604  echo "\n";
605  }
606  echo "\033[30;47m" . "--More--" . "\033[0m";
607 
608  break;
609 
610  // User wants to end output (ie. 'q', CTRL+C)
611  case chr(3):
612  case chr(113):
613 
614  // Backspace the "--More--"
615  Terminal::backspace(8);
616 
617  // Clear line buffer
618  $this->_clearLineBuffer();
619 
620  return;
621  break;
622 
623  default:
624  Terminal::bell();
625  continue;
626  }
627  }
628  }
629  }
630  }
631 
639  private function _addToLinesBuffer($data)
640  {
641  // Get current terminal size
642  $tty_size = $this->_getTtySize();
643 
644  // Loop through data so we can split lines at terminal size
645  for ($i=0; $i<count($data); $i++) {
646 
647  // Add newlines to the end of each proper line
648  $data[$i] .= "\n";
649 
650  // Split line at terminal width and add to output
651  foreach (str_split($data[$i], (int)$tty_size[1]) as $line) {
652  $this->_output_buffer[] = $line;
653  }
654  }
655  }
656 
662  private function _clearLineBuffer()
663  {
664  $this->_output_buffer = array();
665  }
666 
675  public function setOption($option, $value)
676  {
677  $this->_options[$option] = $value;
678  $this->_parseOptions();
679  }
680 
686  private function _getOptions()
687  {
688  return $this->_options;
689  }
690 
698  private function _getOptionValue($option)
699  {
700  $value = false;
701 
702  if (isset($this->_options[$option])) {
703 
704  $value = trim(strtolower($this->_options[$option]));
705 
706  switch ($value) {
707 
708  case "true":
709  case "yes":
710  case "on":
711  case "1":
712  $value = true;
713  break;
714 
715  case "false":
716  case "off":
717  case "no":
718  case "0":
719  $value = false;
720  break;
721  }
722  }
723 
724  return $value;
725  }
726 
733  private function _getTtySize()
734  {
735  return explode("\n", `printf "lines\ncols" | tput -S`);
736  }
737 
744  private function _parseOptions()
745  {
746  // Register autocomplete function
747  if (!$this->_getOptionValue("disable-completion")) {
748  $this->_shell->registerAutocompleteCallback(array($this, "autoComplete"));
749  } else {
750  $this->_shell->registerAutocompleteCallback(null);
751  }
752 
753  // Set maximum history size
754  $this->_shell->setHistorySize($this->_getOptionValue("HISTSIZE"));
755 
756  // Expand out tilde (~) in history filename
757  if (strpos($this->_getOptionValue("HISTFILE"), "~") !== false) {
758  if (isset($_ENV['HOME'])) {
759  $this->_historyFile = str_replace("~", $_ENV['HOME'], $this->_getOptionValue("HISTFILE"));
760  } else {
761  $this->_historyFile = str_replace("~", "/tmp", $this->_getOptionValue("HISTFILE"));
762  }
763  } else {
764  $this->_historyFile = $this->_getOptionValue("HISTFILE");
765  }
766  }
767 }
768 
778 function sortArrayByLength($a,$b)
779 {
780  return count($a)-count($b);
781 }
782 
783 
784 //system("stty raw opost -ocrnl onlcr -onocr -onlret icrnl -inlcr -echo isig intr undef");
785 
787 {
791  private $_history = array();
792 
796  private $_history_tmp = array();
797 
801  private $_history_position = -1;
802 
806  private $_history_storage = null;
807 
811  private $_buffer = '';
812 
816  private $_buffer_position = 0;
817 
821  private $_autocomplete_callback = null;
822 
826  private $_prompt = null;
827 
831  private $_autocompleteTabPressCount = 0;
832 
840  public function addHistory($line)
841  {
842  return ($this->_history[] = trim($line));
843  }
844 
852  public function readHistory($filename)
853  {
854  $this->_history_storage = null;
855  $this->_history_storage = new HistoryStorage($filename);
856 
857  if ($this->_history_storage->load()) {
858  $this->_history = $this->_history_storage->getData();
859  return true;
860  } else {
861  return false;
862  }
863  }
864 
872  public function setHistorySize($c)
873  {
874  if (is_integer($c)) {
875  $this->_history_storage->setMaxSize($c);
876  return true;
877  } else {
878  return false;
879  }
880  }
881 
889  public function readline($prompt=null)
890  {
891  $line = null;
892 
893  $this->_reset();
894 
895  // Output prompt
896  if ($prompt !== null) {
897  $this->_prompt = $prompt;
898  echo $prompt;
899  }
900 
901  while (1) {
902 
903  $c = self::readKey();
904 
905  switch ($c) {
906 
907  // Unrecognised character
908  case null:
909  Terminal::bell();
910  break;
911 
912  // TAB
913  case chr(9):
914 
915  // If autocompletion is registered, then do it
916  if ($this->_autocomplete_callback !== null) {
917 
918  $autocomplete_text = $this->_doAutocomplete($this->_buffer);
919 
920  if (!empty($autocomplete_text)) {
921  $this->_insert($autocomplete_text);
922  } else {
923  Terminal::bell();
924  }
925 
926  // Otherwise, TAB will insert spaces
927  } else {
928  $this->_insert(" ");
929  }
930 
931  break;
932 
933  // CTRL-A (Home) - move the cursor all the way to the left
934  case chr(1):
935  $this->_cursorLeft($this->_buffer_position);
936  break;
937 
938  // CTRL-E (End) - move cursor all the way to the end
939  case chr(5):
940  $this->_cursorRight(mb_strlen($this->_buffer) - $this->_buffer_position);
941  break;
942 
943  // Line-delete - backspace from current position to beginning of line
944  case chr(21):
945  $this->_backspace($this->_buffer_position);
946  break;
947 
948  // Word-delete (CTRL-W)
949  case chr(23):
950 
951  // Get previous word position
952  $prev_word_pos = $this->_buffer_position-$this->_getPreviousWordPos();
953 
954  // Delete word, unless we're at the start of the line, then bell
955  if ($prev_word_pos > 0) {
956  $this->_backspace($this->_buffer_position-$this->_getPreviousWordPos());
957  } else {
958  Terminal::bell();
959  }
960 
961  break;
962 
963  // CTRL-LEFT
964  case chr(27) . chr(91) . chr(53) . chr(68):
965  $this->_cursorLeft($this->_buffer_position-$this->_getPreviousWordPos());
966  break;
967 
968  // CTRL-RIGHT
969  case chr(27) . chr(91) . chr(53) . chr(67):
970  $this->_cursorRight($this->_getNextWordPos()-$this->_buffer_position);
971  break;
972 
973  // CTRL-C
974  case chr(3):
975  $line = $this->_buffer . $c;
976  break;
977 
978  // CTRL-D
979  case chr(4):
980 
981  // Return current line immediately on CTRL-D
982  if (mb_strlen($this->_buffer) === 0) {
983  $line = $this->_buffer . $c;
984  } else {
985  Terminal::bell();
986  }
987  break;
988 
989  case UP:
990  // Move backwards in the history
991  if (!$this->_historyMovePosition(-1)) {
992  Terminal::bell();
993  }
994  break;
995 
996  case DOWN:
997  // Move forward in the history
998  if (!$this->_historyMovePosition(1)) {
999  Terminal::bell();
1000  }
1001  break;
1002 
1003  case LEFT:
1004  // Move left, or beep if we're already at the beginning
1005  if (!$this->_cursorLeft()) {
1006  Terminal::bell();
1007  }
1008  break;
1009 
1010  case RIGHT:
1011  // Move right, or beep if we're already at the end
1012  if (!$this->_cursorRight()) {
1013  Terminal::bell();
1014  }
1015  break;
1016 
1017  // Backspace key
1018  case chr(8):
1019  // Delete
1020  case chr(127):
1021 
1022  if (!$this->_backspace()) {
1023  Terminal::bell();
1024  }
1025  break;
1026 
1027  // Enter key
1028  case chr(10):
1029 
1030  // Set the $line variable so we return below
1031  $line = $this->_buffer;
1032  break;
1033 
1034  // Normal character key
1035  default:
1036 
1037  // Ignore unknown control characters
1038  if (ord($c[0]) === 27) {
1039  Terminal::bell();
1040  continue;
1041  }
1042 
1043  // Insert this character into the buffer and move on
1044  $this->_insert($c);
1045  }
1046 
1047  // If line has been set, we're ready to do something with this command
1048  if ($line !== null) {
1049 
1050  // Firstly check for internal commands
1051  if ($this->_runInternalCommand(trim($line))) {
1052 
1053  // It it was an internal command, don't return, just reset and pretend
1054  // nothing happened...
1055  $this->addHistory($line);
1056  $line = null;
1057  $this->_reset();
1058  }
1059 
1060  // Remove temp history item
1061  array_pop($this->_history_tmp);
1062 
1063  return $line;
1064  }
1065  }
1066  }
1067 
1074  public static function readKey()
1075  {
1076  $buffer = null;
1077  $key = null;
1078 
1079  while (1) {
1080 
1081  $c = fgetc(STDIN);
1082 
1083  $buffer .= $c;
1084 
1085  // Handle control characters
1086  if (ord($buffer[0]) === 27) {
1087 
1088  if ((strlen($buffer) === 1) && (ord($c) === 27)) {
1089  continue;
1090  } elseif ((strlen($buffer) === 2) && (ord($c) === 91)) {
1091  continue;
1092  } elseif (strlen($buffer) === 3 && ord($c) >= 30 && ord($c) <= 57) {
1093  continue;
1094  } else {
1095  return $buffer;
1096  }
1097  }
1098 
1099  // Handle other characters and multibyte characters
1100  if (self::_isValidChar($buffer)) {
1101  return $buffer;
1102  }
1103 
1104  // Safeguard in case isValidChar() fails - UTF-8 characters will never be
1105  // more than 4 bytes. Something's gone wrong, so return null
1106  if (strlen($buffer) > 4) {
1107  return null;
1108  }
1109  }
1110  }
1111 
1121  public function registerAutocompleteCallback($f)
1122  {
1123  $this->_autocomplete_callback = $f;
1124  }
1125 
1133  public function writeHistory($filename)
1134  {
1135  if (get_class($this->_history_storage) !== "HistoryStorage") {
1136  return false;
1137  }
1138 
1139  $this->_history_storage->setData($this->_history);
1140 
1141  if ($this->_history_storage->save() !== false) {
1142  return true;
1143  } else {
1144  return false;
1145  }
1146  }
1147 
1155  private function _backspace($n=1)
1156  {
1157  if ($this->_buffer_position < $n) {
1158  // We can't backspace this far
1159  return false;
1160  }
1161 
1162  ob_start();
1163 
1164  for ($i=0; $i<$n; $i++) {
1165  if ($this->_buffer_position < mb_strlen($this->_buffer)) {
1166 
1167  $head = mb_substr($this->_buffer, 0, $this->_buffer_position);
1168  $tail = mb_substr($this->_buffer, $this->_buffer_position, mb_strlen($this->_buffer));
1169 
1171  echo $tail . ' ';
1172  Terminal::left(mb_strlen($tail)+1);
1173 
1174  // Update buffer
1175  $this->_buffer = mb_substr($head, 0, mb_strlen($head)-1) . $tail;
1176 
1177  } else {
1178 
1179  // Just backspace one char
1180  $this->_buffer = mb_substr($this->_buffer, 0, mb_strlen($this->_buffer)-1);
1182  }
1183 
1184  $this->_buffer_position--;
1185  }
1186 
1187  ob_end_flush();
1188 
1189  return true;
1190  }
1191 
1199  private function _historyMovePosition($n)
1200  {
1201  // Check we can actually move this far
1202  if (!array_key_exists($this->_history_position + $n, $this->_history_tmp)) {
1203 
1204  return false;
1205 
1206  } else {
1207 
1208  ob_start();
1209 
1210  // Clear current line
1211  $this->_cursorRight(mb_strlen($this->_buffer) - $this->_buffer_position);
1212  $this->_backspace($this->_buffer_position);
1213 
1214  // Move forward/back n number of positions
1215  $this->_history_position = $this->_history_position + $n;
1216 
1217  // Print history item and set buffer
1218  echo $this->_history_tmp[$this->_history_position];
1219  $this->_buffer = $this->_history_tmp[$this->_history_position];
1220  $this->_buffer_position = mb_strlen($this->_buffer);
1221 
1222  ob_end_flush();
1223 
1224  return true;
1225  }
1226 
1227  }
1228 
1236  private function _insert($c)
1237  {
1238  // If the cursor is in the middle of the line...
1239  if ($this->_buffer_position < mb_strlen($this->_buffer)) {
1240 
1241  $head = mb_substr($this->_buffer, 0, $this->_buffer_position);
1242  $tail = mb_substr($this->_buffer, $this->_buffer_position, mb_strlen($this->_buffer));
1243 
1244  ob_start();
1245  echo $c . $tail;
1246  Terminal::left(mb_strlen($tail));
1247  $this->_buffer = $head . $c . $tail;
1248  ob_end_flush();
1249 
1250  } else {
1251 
1252  // Otherwise just append/echo it don't worry about the other stuff
1253  $this->_buffer .= $c;
1254  echo $c; // User's terminal must take care of multibyte characters
1255  }
1256 
1257  $this->_buffer_position = $this->_buffer_position + mb_strlen($c);
1258  $this->_history_tmp[$this->_history_position] = $this->_buffer;
1259  }
1260 
1269  private static function _isValidChar($sequence)
1270  {
1271 
1272  $encoding = mb_internal_encoding();
1273 
1274  // Check for bad byte stream
1275  if (mb_check_encoding($sequence) === false) {
1276  return false;
1277  }
1278 
1279  // Check for bad byte sequence
1280  $fs = $encoding == 'UTF-8' ? 'UTF-32' : $encoding;
1281  $ts = $encoding == 'UTF-32' ? 'UTF-8' : $encoding;
1282 
1283  if ($sequence !== mb_convert_encoding(mb_convert_encoding($sequence, $fs, $ts), $ts, $fs)) {
1284  return false;
1285  }
1286 
1287  return true;
1288  }
1289 
1297  private function _cursorLeft($n=1)
1298  {
1299  // Move cursor left if we can
1300  if ($this->_buffer_position > 0) {
1301 
1302  $this->_buffer_position = $this->_buffer_position - $n;
1303  Terminal::left($n);
1304 
1305  return true;
1306 
1307  } else {
1308  return false;
1309  }
1310  }
1311 
1319  private function _cursorRight($n=1)
1320  {
1321  if ($this->_buffer_position < mb_strlen($this->_buffer)) {
1322 
1323  for ($i=0; $i<$n; $i++) {
1324  echo mb_substr($this->_buffer, $this->_buffer_position, 1);
1325  $this->_buffer_position++;
1326  }
1327 
1328  return true;
1329 
1330  } else {
1331 
1332  // Return false if the cursor is already at the end of the line
1333  return false;
1334  }
1335  }
1336 
1344  private function _doAutocomplete($hint)
1345  {
1346  if ($this->_autocomplete_callback === null) {
1347  return false;
1348  }
1349 
1350  $candidates = call_user_func($this->_autocomplete_callback, $hint);
1351 
1352  if (empty($candidates)) {
1353  return false;
1354  }
1355 
1356  $last_word = mb_substr($hint, mb_strrpos($hint, ' ')+1);
1357 
1358  /* If the last word is nothing '', then it means the user hasn't started off
1359  the autocomplete (given a hint) at all. We don't do inline autocomplete in
1360  this case. */
1361  if ($last_word === '') {
1362  $this->_showAutoCompleteOptions($candidates);
1363  return false;
1364  }
1365 
1366  /* Otherwise, the user has started typing a word, and we want to autocomplete
1367  it in-line as much as possible before showing possible options. */
1368  $matches = array();
1369  foreach ($candidates as $match) {
1370  if (mb_strpos($match, $last_word) === 0) {
1371  $matches[] = mb_substr($match, mb_strlen($last_word));
1372  }
1373  }
1374 
1375  if (empty($matches)) {
1376  return false;
1377  }
1378 
1379  // If there's only one match, return it, along with a space on the end
1380  if (count($matches) === 1) {
1381  return $matches[0] . " ";
1382  }
1383 
1384  // Otherwise, let's complete as many common letters as we can...
1385 
1386  $finalAutocompleteString = '';
1387 
1388  // Explode each character of each match into it's own array
1389  $candidate_map = array();
1390  foreach ($matches as $match) {
1391  $candidate_map[] = preg_split('/(?<!^)(?!$)/u', $match); // preg_split here for multibyte chars
1392  }
1393 
1394  // Sort matches by length, shortest first
1395  usort($candidate_map, 'sortArrayByLength');
1396 
1397  for ($i=0; $i<count($candidate_map[0]); $i++) { // "Best match" can't be longer than shortest candidate
1398 
1399  $chars = array();
1400 
1401  // Get all the letters at position $i from all candidates
1402  foreach ($candidate_map as &$candidate) {
1403  $chars[] = $candidate[$i];
1404  }
1405 
1406  // Check if they are all the same letter
1407  $chars_uniq = array_unique($chars);
1408  if (count($chars_uniq) === 1) {
1409  $finalAutocompleteString .= $chars_uniq[0];
1410  }
1411  }
1412 
1413  if ($finalAutocompleteString === '') {
1414  $this->_showAutoCompleteOptions($candidates);
1415  }
1416 
1417  return $finalAutocompleteString;
1418  }
1419 
1425  private function _getPreviousWordPos()
1426  {
1427  $temp_str = mb_substr($this->_buffer, 0, $this->_buffer_position);
1428 
1429  // Remove trailing spaces on the end
1430  $temp_str = rtrim($temp_str);
1431 
1432  // Get first reverse matching space
1433  if (mb_strlen($temp_str) === 0) {
1434  return 0;
1435  }
1436  $prev_word_pos = mb_strrpos($temp_str, ' ');
1437 
1438  // Add one, which is the beginning of the previous word (unless we're at the beginning of the line)
1439  if ($prev_word_pos > 0) {
1440  $prev_word_pos++;
1441  }
1442 
1443  return $prev_word_pos;
1444  }
1445 
1451  private function _getNextWordPos()
1452  {
1453  $temp_str = mb_substr($this->_buffer, $this->_buffer_position, mb_strlen($this->_buffer));
1454 
1455  // Store length, so we can calculate how many spaces are trimmed in the next step
1456  $temp_str_len = mb_strlen($temp_str);
1457 
1458  // Trim spaces from the beginning
1459  $temp_str = ltrim($temp_str);
1460 
1461  // Trimmed spaces
1462  $trimmed_spaces = $temp_str_len - mb_strlen($temp_str);
1463 
1464  // Get first matching space
1465  $next_word_pos = mb_strpos($temp_str, ' ');
1466 
1467  // If there is no matching space, we're at the end of the string
1468  if ($next_word_pos === false) {
1469  $next_word_pos = mb_strlen($this->_buffer);
1470  } else {
1471  $next_word_pos = $this->_buffer_position + $trimmed_spaces + $next_word_pos;
1472  }
1473 
1474  return $next_word_pos;
1475  }
1476 
1482  private function _reset()
1483  {
1484  // Reset buffer
1485  $this->_buffer = '';
1486  $this->_buffer_position = 0;
1487 
1488  // Reset working history
1489  $this->_history_tmp = $this->_history;
1490  $this->_history_tmp[] = '';
1491  $this->_history_position = count($this->_history);
1492  }
1493 
1501  private function _runInternalCommand($command)
1502  {
1503  // history command
1504  if (mb_substr($command, 0, 5) === "\hist") {
1505 
1506  echo "\n\n";
1507 
1508  // Print history
1509  for ($i=0; $i<count($this->_history); $i++) {
1510  $p = strlen((string)count($this->_history)) + 1;
1511  printf("%" . $p . "s %s\n", $i+1, $this->_history[$i]);
1512  }
1513 
1514  return true;
1515  } else {
1516  return false;
1517  }
1518  }
1519 
1527  private function _showAutoCompleteOptions($options)
1528  {
1529  // TAB must be pressed twice to show autocomplete options
1530  if (!$this->_autocompleteTabPressCount > 0) {
1531  $this->_autocompleteTabPressCount++;
1532  return;
1533  } else {
1534  $this->_autocompleteTabPressCount = 0;
1535  }
1536 
1537  $optionMaxChars = 0;
1538 
1539  // Get length of the longest match (for spacing)
1540  foreach ($options as $option) {
1541  if (mb_strlen($option)+2 > $optionMaxChars) {
1542  $optionMaxChars = mb_strlen($option) + 2; // +2 spaces to pad with
1543  }
1544  }
1545 
1546  // Get tty width
1547  $ttySize = Terminal::getTtySize();
1548  $ttyChars = $ttySize[1];
1549 
1550  // Calculate number of lines required
1551  $linesRequired = ceil((count($options)*$optionMaxChars) / $ttyChars);
1552 
1553  // Calculate number of items per line
1554  $itemsPerLine = floor($ttyChars / $optionMaxChars);
1555 
1556  for ($i=0; $i < count($options); $i++) {
1557  if ($i % $itemsPerLine === 0) {
1558  echo "\n";
1559  }
1560 
1561  printf("%-" . $optionMaxChars . "s", $options[$i]);
1562  }
1563  echo "\n";
1564  echo $this->_prompt . $this->_buffer;
1565  }
1566 }
1567 
1577 {
1581  private $rows;
1582 
1586  private $cs = array();
1587 
1591  private $rs = array();
1592 
1596  private $keys = array();
1597 
1601  private $mH = 2;
1602 
1606  private $mW = 10000;
1607 
1608  private $head = false;
1609  private $pcen = "+";
1610  private $prow = "-";
1611  private $pcol = "|";
1612 
1613 
1621  public function ArrayToTextTable($rows)
1622  {
1623  $this->rows =& $rows;
1624  $this->cs=array();
1625  $this->rs=array();
1626 
1627  if(!$xc = count($this->rows)) return false;
1628  $this->keys = array_keys($this->rows[0]);
1629  $columns = count($this->keys);
1630 
1631  for($x=0; $x<$xc; $x++)
1632  for($y=0; $y<$columns; $y++)
1633  $this->setMax($x, $y, $this->rows[$x][$this->keys[$y]]);
1634  }
1635 
1641  public function showHeaders($bool)
1642  {
1643  if($bool) $this->setHeading();
1644  }
1645 
1651  public function setMaxWidth($maxWidth)
1652  {
1653  $this->mW = (int) $maxWidth;
1654  }
1655 
1661  public function setMaxHeight($maxHeight)
1662  {
1663  $this->mH = (int) $maxHeight;
1664  }
1665 
1672  public function render($return=false)
1673  {
1674  if($return) ob_start(null, 0, true);
1675 
1676 // $this->printLine();
1677  $this->printHeading();
1678 
1679  $rc = count($this->rows);
1680  for($i=0; $i<$rc; $i++) $this->printRow($i);
1681 
1682  if($return) {
1683  $contents = ob_get_contents();
1684  ob_end_clean();
1685  return $contents;
1686  }
1687  }
1688 
1689  private function setHeading()
1690  {
1691  $data = array();
1692  foreach($this->keys as $colKey => $value)
1693  {
1694  $this->setMax(false, $colKey, $value);
1695  $data[$colKey] = $value;
1696  }
1697  if(!is_array($data)) return false;
1698  $this->head = $data;
1699  }
1700 
1701  private function printLine($nl=true)
1702  {
1703 // print ' ';
1704  $i = 0;
1705  foreach($this->cs as $key => $val) {
1706  print $this->prow .
1707  str_pad('', $val, $this->prow, STR_PAD_RIGHT) .
1708  $this->prow;
1709 
1710  if ($i < count($this->cs)-1) {
1711  print $this->pcen;
1712  } else {
1713  print ' ';
1714  }
1715  $i++;
1716  }
1717  if($nl) print "\n";
1718  }
1719 
1720  private function printHeading()
1721  {
1722  if(!is_array($this->head)) return false;
1723 
1724 // print ' ';
1725  $i = 0;
1726  foreach($this->cs as $key => $val) {
1727  print ' '.
1728  str_pad($this->head[$key], $val, ' ', STR_PAD_BOTH) .
1729  ' ';
1730  if ($i < count($this->cs)-1) {
1731  print $this->pcol;
1732  } else {
1733  print ' ';
1734  }
1735  $i++;
1736  }
1737 
1738  print "\n";
1739  $this->printLine();
1740  }
1741 
1742  private function printRow($rowKey)
1743  {
1744  // loop through each line
1745  for($line=1; $line <= $this->rs[$rowKey]; $line++)
1746  {
1747 // print ' ';
1748  for($colKey=0; $colKey < count($this->keys); $colKey++)
1749  {
1750  print " ";
1751  print str_pad(substr($this->rows[$rowKey][$this->keys[$colKey]], ($this->mW * ($line-1)), $this->mW), $this->cs[$colKey], ' ', STR_PAD_RIGHT);
1752  if ($colKey < count($this->keys)-1) {
1753  print " " . $this->pcol;
1754  } else {
1755  print " ";
1756  }
1757  }
1758  print "\n";
1759  }
1760  }
1761 
1762  private function setMax($rowKey, $colKey, &$colVal)
1763  {
1764  $w = mb_strlen($colVal);
1765  $h = 1;
1766  if($w > $this->mW)
1767  {
1768  $h = ceil($w % $this->mW);
1769  if($h > $this->mH) $h=$this->mH;
1770  $w = $this->mW;
1771  }
1772 
1773  if(!isset($this->cs[$colKey]) || $this->cs[$colKey] < $w)
1774  $this->cs[$colKey] = $w;
1775 
1776  if($rowKey !== false && (!isset($this->rs[$rowKey]) || $this->rs[$rowKey] < $h))
1777  $this->rs[$rowKey] = $h;
1778  }
1779 }
1780 
1790 {
1794  private $_file = '';
1795 
1799  private $_data = array();
1800 
1805  private $_autosave = false;
1806 
1810  private $_maxsize = 500;
1811 
1818  function __construct($file='', $autosave=true)
1819  {
1820  $this->_file = $file;
1821  $this->_autosave = $autosave;
1822  }
1823 
1827  function __destruct()
1828  {
1829  if ($this->_autosave) {
1830  $this->save();
1831  }
1832  }
1833 
1839  function load()
1840  {
1841  $data = @file($this->_file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
1842 
1843  if (is_array($data) === true) {
1844  $this->_data = $data;
1845  return true;
1846  } else {
1847  return false;
1848  }
1849  }
1850 
1857  function save()
1858  {
1859  while (count($this->_data) > $this->_maxsize) {
1860  array_shift($this->_data);
1861  }
1862 
1863  return @file_put_contents($this->_file, implode("\n", $this->_data));
1864  }
1865 
1871  function getData()
1872  {
1873  return $this->_data;
1874  }
1875 
1883  function setData($data)
1884  {
1885  if (is_array($data)) {
1886  $this->_data = $data;
1887  return true;
1888  } else {
1889  return false;
1890  }
1891  }
1892 
1900  function setMaxSize($n)
1901  {
1902  $this->_maxsize = (int)$n;
1903  }
1904 
1911  function getMaxSize()
1912  {
1913  return $this->_maxsize;
1914  }
1915 }
1916 
1926 {
1930  private $_executionTime;
1931 
1935  private $_backend;
1936 
1942  public function __construct($pluginName)
1943  {
1944  $backend = null;
1945  $pluginName = 'DbBackend_' . $pluginName;
1946 
1947  if (class_exists($pluginName)) {
1948  $backend = new $pluginName;
1949  }
1950 
1951  if (is_null($backend) || !get_parent_class($pluginName) == 'DbBackendPlugin') {
1952  echo("Cannot find valid DbBackendPlugin class \"" . $pluginName . "\".");
1953  exit(20);
1954  }
1955 
1956  $this->_backend = $backend;
1957  }
1958 
1966  public function connect($dsn)
1967  {
1968  $this->disconnect();
1969  return $this->_backend->connect($dsn);
1970  }
1971 
1977  public function getDbName()
1978  {
1979  return $this->_backend->getDbName();
1980  }
1981 
1987  public function getDbType()
1988  {
1989  return $this->_backend->getDbType();
1990  }
1991 
1997  public function getDbVersion()
1998  {
1999  return $this->_backend->getDbVersion();
2000  }
2001 
2007  public function disconnect()
2008  {
2009  return $this->_backend->disconnect();
2010  }
2011 
2019  public function execute($sql)
2020  {
2021  $query_start_time = microtime(true);
2022  $result = $this->_backend->execute($sql);
2023  $query_end_time = microtime(true);
2024 
2025  $this->_executionTime = $query_end_time - $query_start_time;
2026 
2027  return $result;
2028  }
2029 
2035  public function getQueryExecutionTime()
2036  {
2037  return round($this->_executionTime * 1000, 3);
2038  }
2039 
2045  public function getTableNames()
2046  {
2047  return $this->_backend->getTableNames();
2048  }
2049 
2057  public function getColumnNames($table)
2058  {
2059  return $this->_backend->getColumnNames($table);
2060  }
2061 
2069  public function matchesMacro($s)
2070  {
2071  return $this->_backend->matchesMacro($s);
2072  }
2073 }
2074 
2083 abstract class DbBackendPlugin
2084 {
2093  abstract public function connect($conn_string);
2094 
2100  abstract public function disconnect();
2101 
2109  abstract public function execute($sql);
2110 
2116  abstract public function getDbName();
2117 
2123  abstract public function getDbType();
2124 
2130  abstract public function getDbVersion();
2131 
2138  abstract public function getTableNames();
2139 
2148  abstract public function getColumnNames($table);
2149 
2157  abstract public function matchesMacro($s);
2158 }
2159 
2169 {
2173  private $_dsn = '';
2174 
2178  private $_db_type = '';
2179 
2183  private $_macros = array();
2184 
2188  public function __construct()
2189  {
2190  // Define macros
2191  $this->_macros = array(
2192 
2193  "pgsql" => array(
2194 
2195  "\dt" => "
2196  SELECT n.nspname as \"Schema\",
2197  c.relname as \"Name\",
2198  CASE c.relkind WHEN 'r' THEN 'table' WHEN 'v' THEN 'view' WHEN 'i' THEN 'index' WHEN 'S' THEN 'sequence' WHEN 's' THEN 'special' END as \"Type\",
2199  r.rolname as \"Owner\"
2200  FROM pg_catalog.pg_class c
2201  JOIN pg_catalog.pg_roles r ON r.oid = c.relowner
2202  LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
2203  WHERE c.relkind IN ('r','')
2204  AND n.nspname NOT IN ('pg_catalog', 'pg_toast')
2205  AND pg_catalog.pg_table_is_visible(c.oid)
2206  ORDER BY 1,2;",
2207  ),
2208 
2209  "oci" => array(
2210  "\dt" => "SELECT * FROM tab ORDER BY tname ASC;",
2211  ),
2212  );
2213  }
2214 
2222  public function connect($conn_string)
2223  {
2224  $SYSTEM_ROOT = $conn_string;
2225 
2226  if (empty($SYSTEM_ROOT) || !is_dir($SYSTEM_ROOT)) {
2227  echo "You need to supply the path to the System Root as the first argument\n";
2228  exit(1);
2229  }
2230 
2231  require_once $SYSTEM_ROOT.'/fudge/dev/dev.inc';
2232  require_once $SYSTEM_ROOT.'/core/include/general.inc';
2233  require_once $SYSTEM_ROOT.'/core/lib/DAL/DAL.inc';
2234  require_once $SYSTEM_ROOT.'/core/lib/MatrixDAL/MatrixDAL.inc';
2235  require_once $SYSTEM_ROOT.'/data/private/conf/db.inc';
2236 
2237  $this->_dsn = $db_conf['db2'];
2238  $this->_db_type = $db_conf['db2']['type'];
2239 
2240  // Attempt to connect
2241  MatrixDAL::dbConnect($this->_dsn, $this->_db_type);
2242  MatrixDAL::changeDb($this->_db_type);
2243 
2244  // Matrix will throw a FATAL error if it can't connect, so if we got here
2245  // we're all good
2246  return true;
2247  }
2248 
2254  public function getDbName()
2255  {
2256  // Shorten the DB name if Oracle is using the full specifier
2257  $dsn = trim($this->_dsn['DSN']);
2258  if (preg_match("/SERVICE_NAME/i", $dsn)) {
2259  return preg_replace("/\A.*HOST\s*=\s*(.*?)\).*SERVICE_NAME\s*=\s*(.*?)\).*\z/i", '$2 on $1', $dsn);
2260  } else {
2261  return $dsn;
2262  }
2263  }
2264 
2270  public function getDbType()
2271  {
2272  return $this->_db_type;
2273  }
2274 
2280  public function getDbVersion()
2281  {
2282  return '';
2283  }
2284 
2290  public function disconnect()
2291  {
2292  return true;
2293  }
2294 
2302  public function execute($sql)
2303  {
2304  $output = false;
2305 
2306  // Check/execute macros
2307  foreach ($this->_macros[$this->_db_type] as $pattern => $replacement) {
2308  $c = 0;
2309  $sql = str_replace($pattern, $replacement, $sql, $c);
2310  if ($c > 0) {
2311  break;
2312  }
2313  }
2314 
2315  // Strip semicolon from end if its Oracle
2316  if ($this->_db_type == 'oci') {
2317  $sql = mb_substr($sql, 0, mb_strlen($sql)-1);
2318  }
2319 
2320  // Check what kind of query it is
2321  $query_type = $this->_getQueryType($sql);
2322  switch ($query_type) {
2323 
2324  case "SELECT":
2325  $output = MatrixDAL::executeSqlAssoc($sql);
2326  break;
2327 
2328  case "UPDATE":
2329  case "INSERT":
2330  $rows_affected = MatrixDAL::executeSql($sql);
2331  $output = $query_type . " " . $rows_affected;
2332  break;
2333 
2334  case "BEGIN":
2335  /* There is no return bool code, but according to PHP docs an exception will
2336  be thrown if the DB doesn't support transactions */
2338  $output = $query_type;
2339  break;
2340 
2341  case "ROLLBACK":
2343  $output = $query_type;
2344  break;
2345 
2346  case "COMMIT":
2348  $output = $query_type;
2349  break;
2350 
2351  default:
2352  //echo "WARNING: Query type not recognised.\n";
2353  $output = MatrixDAL::executeSqlAssoc($sql);
2354  break;
2355  }
2356 
2357  return $output;
2358  }
2359 
2366  public function getTableNames()
2367  {
2368  $sql = '';
2369 
2370  switch ($this->_db_type) {
2371 
2372  case 'pgsql':
2373  $sql = <<<EOF
2374  -- phpsqlc: tab-completion: table-names
2375  SELECT
2376  c.relname as "Name"
2377  FROM pg_catalog.pg_class c
2378  JOIN pg_catalog.pg_roles r ON r.oid = c.relowner
2379  LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
2380  WHERE c.relkind IN ('r','')
2381  AND n.nspname NOT IN ('pg_catalog', 'pg_toast')
2382  AND pg_catalog.pg_table_is_visible(c.oid)
2383  ORDER BY 1;
2384 EOF;
2385  break;
2386 
2387  case 'oci':
2388  // Cheeky UNION here to allow tab completion to work for both all-upper OR
2389  // all-lowercase table names (only for MatrixDAL/oci, so users can be lazy)
2390  $sql = "SELECT tname FROM tab UNION SELECT LOWER(tname) FROM tab";
2391  break;
2392  }
2393 
2394  // We only know queries for pgsql and oci
2395  if ($sql === '') {
2396  $names = array();
2397 
2398  } else {
2399 
2400  try {
2401  $names = MatrixDAL::executeSqlAssoc($sql, 0);
2402  }
2403  catch (Exception $e) {
2404  $names = array();
2405  }
2406  }
2407 
2408  return $names;
2409  }
2410 
2419  public function getColumnNames($table)
2420  {
2421  $sql = '';
2422 
2423  switch ($this->_db_type) {
2424 
2425  case 'oci':
2426  // Cheeky UNION here to allow tab completion to work for both all-upper OR
2427  // all-lowercase table names (only for MatrixDAL/oci, so users can be lazy)
2428  $sql = "SELECT column_name FROM all_tab_columns WHERE table_name = " . mb_strtoupper(MatrixDAL::quote($table)) . " UNION " .
2429  "SELECT LOWER(column_name) FROM all_tab_columns WHERE table_name = " . mb_strtoupper(MatrixDAL::quote($table));
2430  break;
2431 
2432  case 'pgsql':
2433  $sql = <<<EOF
2434  -- phpsqlc: tab-completion: column-names
2435  SELECT a.attname FROM pg_catalog.pg_attribute a
2436  WHERE a.attrelid IN (
2437  SELECT c.oid
2438  FROM pg_catalog.pg_class c
2439  LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
2440  WHERE c.relname = '$table' AND pg_catalog.pg_table_is_visible(c.oid)
2441  ) AND a.attnum > 0 AND NOT a.attisdropped;
2442 EOF;
2443 
2444  }
2445 
2446  // We only know queries for pgsql and oci
2447  if ($sql === '') {
2448  return array();
2449  }
2450 
2451  try {
2452  $names = MatrixDAL::executeSqlAssoc($sql, 0);
2453  }
2454  catch (Exception $e) {
2455  $names = array();
2456  }
2457 
2458  return $names;
2459  }
2460 
2468  public function matchesMacro($s)
2469  {
2470  return array_key_exists(trim($s), $this->_macros[$this->_db_type]);
2471  }
2472 
2480  private function _getQueryType($input_query)
2481  {
2482  $input_query = mb_strtoupper($input_query);
2483 
2484  if (mb_strpos($input_query, "SELECT") === 0) {
2485  $type = "SELECT";
2486  } elseif (mb_strpos($input_query, "UPDATE") === 0) {
2487  $type = "UPDATE";
2488  } elseif (mb_strpos($input_query, "INSERT INTO") === 0) {
2489  $type = "INSERT";
2490  } elseif (mb_strpos($input_query, "BEGIN") === 0) {
2491  $type = "BEGIN";
2492  } elseif (mb_strpos($input_query, "START TRANSACTION") === 0) {
2493  $type = "BEGIN";
2494  } elseif (mb_strpos($input_query, "ABORT") === 0) {
2495  $type = "ROLLBACK";
2496  } elseif (mb_strpos($input_query, "ROLLBACK") === 0) {
2497  $type = "ROLLBACK";
2498  } elseif (mb_strpos($input_query, "COMMIT") === 0) {
2499  $type = "COMMIT";
2500  } else {
2501  $type = false;
2502  }
2503 
2504  return $type;
2505  }
2506 }
2507 
2508 ?>