root/trunk/classes/database.php

Revision trunk,181, 15.1 kB (checked in by Suren A. Chilingaryan <csa@dside.dyndns.org>, 7 months ago)

Tables escaping fix

Line 
1 <?php
2
3 class DBRes implements Iterator {
4  var $arr;
5  
6  public function __construct() {
7     $this->arr = array();
8  }
9  
10  public function push(&$obj) {
11     array_push($this->arr, $obj);
12  }
13  
14  public function fetch($flags = 0) {
15     return array_shift($this->arr);
16  }
17
18  function rewind() {
19     reset($this->arr);
20  }
21  function current() {
22     return current($this->arr);
23  }
24  function key() {
25     return key($this->arr);
26  }
27  function next() {
28     each($this->arr);   
29  }
30  function valid() {
31     // We don't expect 'false' elements in array (all are arrays)
32     return current($this->arr)?true:false;
33  }
34
35 }
36
37 class DATABASE {
38  var $dbh, $connected;
39  var $driver;
40  var $odbc;
41  
42  var $server;
43  var $dbname;
44  
45  var $text_quote = "\'";
46  var $col_quote = "\"";
47  var $tbl_quote = "";
48
49  const GLOBAL_QUERY = 0x0001;
50  const SINGLE_RESULT = 0x0002;
51  const FETCH_NUM = 0x0100;
52
53  
54  function __construct(&$server) {
55     $this->server = $server;
56
57     if (($server['charset'])&&(preg_match("/UTF.*8/i", $server['charset']))) {
58     unset($this->server['charset']);
59     }
60
61     $this->ReConnect();
62  }
63  
64  function ReConnect() {
65     $server = &$this->server;
66
67     if (($this->connected)||($this->dbh)) {
68     $this->connected = false;
69     $this->dbh = false;
70     }
71     
72     $pdo_opts = array();
73     if ($server['persistent']) {
74     $pdo_opts[PDO::ATTR_PERSISTENT] = true;
75     }
76
77      try {
78         $this->odbc = false;
79     
80     switch($server['driver']) {
81       case "odbc":
82         if ($server['source']) {
83         $this->dbh = new PDO ("odbc:" . $server['source'], $server['user'], $server['password'], $pdo_opts);
84         
85         $this->dbname = $server['source'];
86         $this->connected = true;
87         } else {
88         if ($server['database']) {
89             $dbtext = ";DATABASE=" . $server['database'];
90             $this->dbname = $server['database'];
91             $this->connected = true;
92         } else $dbtext = "";
93         
94         if ($server['timeout']) {
95             if ($server['ping']) {
96             if ((!is_array($server['ping']))&&((!$server['host'])||(!$server['port'])))
97                 throw new ADEIException(translate("The ping-before-connection feature is requiring the database's host and port to be excplicitly specified."));
98                 
99             $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
100             if ($socket) {
101                 socket_set_nonblock($socket);
102             
103                 if (is_array($ping))
104                 $res = @socket_connect($socket, $server['ping']['host'], $server['ping']['port']);
105                 else
106                 $res = @socket_connect($socket, $server['host'], $server['port']);
107             
108                 if (!$res) {
109                 $res = socket_select($r = array($socket), $w = array($socket), $e = array($socket), floor($server['timeout']/1000000), $server['timeout']%1000000);
110                 if ($res == 1) $res = true;
111                 else $res = false;
112                 }
113                 socket_close($socket);
114                 
115                 if (!$res) {
116                 throw new ADEIException(translate("Error connecting to the database (timeout expired)"));
117                 }
118             }
119             }
120         
121             $timeout = floor($server['timeout'] / 1000000);
122             if (!$timeout) $timeout = 1;
123             
124             $pdo_opts[PDO::ATTR_TIMEOUT] = $timeout;
125             $this->dbh = @new PDO ("odbc:DRIVER=" . $server['subdrv'] . ";SERVER=" . $server['host'] . ";PORT=" . ($server['port']?$server['port']:"1433") . $dbtext . ";PROTOCOL=TCPIP;UID=" . $server['user'] . ";PWD=" . $server['password'], NULL, NULL, $pdo_opts);
126         } else {
127             $this->dbh = new PDO ("odbc:DRIVER=" . $server['subdrv'] . ";SERVER=" . $server['host'] . ";PORT=" . ($server['port']?$server['port']:"1433") . $dbtext . ";PROTOCOL=TCPIP;UID=" . $server['user'] . ";PWD=" . $server['password'], NULL, NULL, $pdo_opts);
128         }
129         }
130
131         $this->odbc = true;
132       break;
133       default:   
134 #        echo "connecting\n";
135 #        print_r($server);
136         if ($server['database']) {
137         $this->dbh = new PDO (
138             $server['driver'] . ":host=" . $server['host'] . ($server['port']?(";port=" . $server['port']):"") . ";dbname=" . $server['database'],
139             $server['user'],
140             $server['password'],
141             $pdo_opts
142         );
143         $this->dbname = $server['database'];       
144         $this->connected = true;
145         } else {
146             $this->dbh = new PDO (
147             $server['driver'] . ":host=" . $server['host'] . ($server['port']?(";port=" . $server['port']):""),
148             $server['user'],
149             $server['password'],
150             $pdo_opts
151         );
152         $this->connected = false;
153         }
154 #        echo "connected\n";
155 #        exit;
156     }
157         
158     if ($server['sqldrv']) $this->driver = $server['sqldrv'];
159     else $this->driver = $server['driver'];
160     
161         /* Single check for single scheme */
162     switch ($this->driver) {
163         case "dblib":
164         $this->driver="mssql";
165         break;
166         case "mysql":
167         $this->col_quote = "`";
168         $this->tbl_quote = "`";
169         break;
170     }
171     
172      $this->dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
173     } catch(PDOException $e) {
174     $this->dbh = NULL;
175     $this->connected = false;
176
177     $errmsg = $this->RecodeMessage($e->getMessage());
178     if ($this->dbname)
179         throw new ADEIException(translate("Error connecting to the server \"%s\" database \"%s\": %s", $server['title'], $this->dbname, $errmsg), $e->getCode());
180     else
181         throw new ADEIException(translate("Error connecting to the server \"%s\": %s", $server['title'], $errmsg), $e->getCode());
182     }
183  }
184  
185  function RecodeMessage($msg) {
186     if (($msg)&&($this->server['charset'])) {
187     $rec = iconv($this->server['charset'], "UTF-8", $msg);
188     if (!$rec) {
189         throw new ADEIException(translate("The message received from server (%s) could not be translated from source charset (%s) to UTF-8. Please, check the server charset in configuration.", $this->server['title'], $this->server['charset']));
190     }
191
192     return $rec;
193     }
194
195     return $msg;   
196  }
197
198 /*
199  function RecodeData($data) {
200  }
201 */
202
203  static function GetConnectionString(array &$server, array &$options = NULL, &$application = NULL) {
204     if (strtolower($server['driver']) == 'mysql') {
205     if ((!$application)||($application == 'mysqldump')) {
206         return "-h" . $server['host'] . ($server['port']?(":" . $server['port']):"") . " -u" . $server['user'] . " -p" . $server['password'] . " " . $server['database'];
207     } else throw new ADEIException(translate("The required application (\"%s\") is not supported at the moment", $application));
208     } else throw new ADEIException(translate("The supplied connection (\"%s\") is not supported at the moment", $server['driver']));
209  }
210  
211
212  function Query($sql, $flags = 0) {
213 //    echo $sql . "\n\n";
214
215     if ((!$this->connected)&&(($flags&DATABASE::GLOBAL_QUERY)==0))
216      throw new ADEIException(translate("The database is not specified"));
217
218     try {
219     if ($this->odbc) {
220         //    This is to prevent spurious odbc errors:
221         $stmt = $this->Prepare($sql, $flags);
222         $stmt->execute();
223     
224         $resp = new DBRes();
225         if ($flags&DATABASE::SINGLE_RESULT) {
226         $row = $stmt->fetch(($flags&DATABASE::FETCH_NUM)?PDO::FETCH_NUM:PDO::FETCH_ASSOC);
227         if ($row) $resp->push($row);
228         } else {
229             while ($row = $stmt->fetch(($flags&DATABASE::FETCH_NUM)?PDO::FETCH_NUM:PDO::FETCH_ASSOC)) {
230             $resp->push($row);
231         }
232         }
233     } else {
234         //echo $sql . "\n\n";
235         $resp = $this->dbh->query($sql);
236     }
237     } catch (PDOException $e) {
238         $errmsg = $this->RecodeMessage($e->getMessage());
239     throw new ADEIException(translate("SQL Query is failed with error: %s", $errmsg));
240     }
241 //    echo "done\n";
242     
243     if (!$resp) {
244     $e = $this->dbh->errorInfo();
245         $errmsg = $this->RecodeMessage($e[2]);
246     throw new ADEIException(translate("SQL Query is failed. SQL Error: %u, Driver Error: %u, Message: %s ", $e[0], $e[1], $errmsg) . "[$sql]");
247     }
248
249     return $resp;
250  }
251
252  function Prepare($sql, $flags = 0) {
253     if ((!$this->connected)&&(($flags&DATABASE::GLOBAL_QUERY)==0))
254      throw new ADEIException(translate("The database is not specified"));
255
256     try {
257     $stmt = $this->dbh->prepare($sql);
258     } catch (PDOException $e) {
259     $throw = true;
260     
261         // Link fault, retrying
262     if ($e->getCode() == "08S01") {
263         try {
264 //        echo "Reconnecting\n";
265         $this->Reconnect();
266 //        echo "Preparing New STMT\n";
267         $stmt = $this->dbh->prepare($sql);
268 //        echo "Done\n";
269         $throw = false;
270         } catch (PDOException $e2) {
271         }
272     }
273     if ($throw) {
274         $errmsg = $this->RecodeMessage($e->getMessage());
275         throw new ADEIException(translate("Can not prepare SQL query for execution, Server: %s, Database: %s, Error: %s [%s]", $this->server['title'], $this->dbname, $errmsg, $sql), $e->getCode());
276     }
277     }
278
279     if (!$stmt) {
280     $e = $this->dbh->errorInfo();
281     $errmsg = $this->RecodeMessage($e[2]);
282     throw new ADEIException(translate("Preparation of the SQL Query is failed. Server: %s, Database: %s, SQL Error: %u, Driver Error: %u, Message: %s [%s]", $this->server['title'], $this->dbname, $e[0], $e[1], $errmsg, $sql));
283     }
284
285     return $stmt;
286  }
287
288 /*
289  function statementBindColumn($stmt, $column, &$var, $type = PDO::PARAM_LOB) {
290     $stmt->bindColumn($column, $var, $type);
291  }
292
293  function statementBindParam($stmt, $param, &$var, $type = PDO::PARAM_LOB) {
294     $stmt->bindColumn($param, $var, $type);
295  }
296 */
297
298  function SelectRequest($table, $columns="*", array $req = NULL) {
299     $res = "SELECT ";
300     if ($req['limit']) {
301     switch ($this->driver) {
302         case "mssql":
303         $res .= "TOP " . $req['limit'] . " ";
304         break;
305         case "mysql":
306         $suffix = " LIMIT " . $req['limit'];
307         break;
308         default:
309         throw new ADEIException(translate("Don't know how to handle LIMIT for '%s'", $this->driver));
310     }
311     }
312     
313     if (is_array($columns)) {
314     $last = array_pop($columns);
315     if (!$last)
316         throw new ADEIException(translate("Columns list is empty"));
317     
318     foreach ($columns as $item) {
319         $res .=  "\"" . $item/*['column']*/ . "\", ";
320     }
321     $res .= "\"" . $last . "\"";
322     
323     } else $res .= $columns;
324     
325     if (preg_match("/^[(\s]*SELECT/i", $table)) {
326     $res .= " FROM " . $table;
327     } else {
328     $res .= " FROM " . $this->tbl_quote . $table . $this->tbl_quote;
329     }
330
331     if ($req['condition']) $cond = " WHERE " . $req['condition'];
332     else $cond = "";
333     
334     if ($req['sampling']) {
335     if (is_array($req['sampling'])) {
336         $slicer = $req['sampling']['slicer'];
337         $selector = $req['sampling']['selector'];
338     } else {
339         $slicer = $req['sampling']['slicer'];
340         $selector = $req['sampling']['slicer'];
341     }
342     
343     switch ($this->driver) {
344         case "mysql":
345         $res .= ", (SELECT MAX($selector) AS tmptbl_sel FROM {$this->tbl_quote}$table{$this->tbl_quote} $cond GROUP BY FLOOR($slicer)) AS tmptbl";
346         $res .= " WHERE  tmptbl.tmptbl_sel = $selector";
347         break;
348         default:
349         $res .= " WHERE $selector IN (SELECT MAX($selector) FROM {$this->tbl_quote}$table{$this->tbl_quote} $cond GROUP BY FLOOR($slicer))";
350     }
351     } else {
352     $res .= $cond;
353     }
354     if ($req['group']) $res .= " GROUP BY " . $req['group'];
355     if ($req['order']) $res .= " ORDER BY " . $req['order'];
356     
357     return $res . $suffix;
358  }
359
360  function GetTimeFormat() {
361 //    if ($this->time_format) return $this->time_format;
362     
363     switch ($this->driver) {
364     case "mysql":
365         return "YmdHis";
366     case "mssql":
367         return "Ymd H:i:s";
368         /* Hm. Didn't work from Linux
369         return "Y-d-m H:i:s";*/
370         /* We can't use '.u' since it is formated with 6 digits after the
371         decimal point, while the MSSQL complains if there are more than 3
372         return "Y-d-m H:i:s.u";*/
373     default:
374         throw new ADEIException(translate("The date format for \"%s\" is not known", $this->driver));
375     }
376  }
377  
378  function GetTimeRequest($column_name) {
379     switch ($this->driver) {
380     case "mssql":
381         return "CONVERT(CHAR(24), {$this->col_quote}$column_name{$this->col_quote}, 21)";
382     default:
383         return "{$this->col_quote}column_name{$this->col_quote}";
384     }
385  }
386  
387  function ShowDatabases() {
388     switch ($this->driver) {
389     case "mysql":
390         return $this->Query("SHOW DATABASES", DATABASE::GLOBAL_QUERY|DATABASE::FETCH_NUM);
391     case "mssql":
392         return $this->Query("SELECT name FROM master..sysdatabases", DATABASE::GLOBAL_QUERY|DATABASE::FETCH_NUM);
393     default:
394         throw new ADEIException(translate("The ShowDatabases for \"%s\" is not implemented", $this->driver));
395     }
396  }
397  
398  function ShowTables() {
399     switch ($this->driver) {
400     case "mysql":
401         return $this->Query("SHOW TABLES", DATABASE::FETCH_NUM);
402     case "mssql":
403         /* Bugs in ODBC/MySQL (with segm. in some cases,
404         possibly http://bugs.php.net/bug.php?id=33533&edit=1
405         */
406 /*
407         $db = new DATABASE($this->server);
408         return $db->Query(" SELECT name AS gid FROM sysobjects WHERE type = 'U'");
409 */
410
411         return $this->Query("SELECT name AS gid FROM sysobjects WHERE (type = 'U' OR type = 'V')", DATABASE::FETCH_NUM);
412     default:
413         throw new ADEIException(translate("The ShowTables for \"%s\" is not implemented", $this->driver));
414     }
415  }
416
417  function ShowColumns($table) {
418     switch ($this->driver) {
419     case "mysql":
420         return $this->Query("SHOW COLUMNS FROM `$table`", DATABASE::FETCH_NUM);
421     case "mssql":
422         /* we could get here problems if several non-equal tables with
423         different prefixes (mda,dbo) are present */
424         $table = preg_replace("/^\[?mda\]?\./", "", $table);
425         $table = preg_replace("/(^\[|\]$)/", "", $table);
426         return $this->Query("SELECT name FROM (SELECT DISTINCT TOP 65535 name=syscolumns.name, type=systypes.name, length=syscolumns.length, objname=sysobjects.name, colid=syscolumns.colid
427         FROM sysobjects
428         JOIN syscolumns ON sysobjects.id = syscolumns.id
429         JOIN systypes ON syscolumns.xtype=systypes.xtype
430         WHERE (sysobjects.xtype='U' OR sysobjects.xtype='V')  AND sysobjects.name='$table'
431         ORDER BY sysobjects.name,syscolumns.colid) AS tmptable", DATABASE::FETCH_NUM);
432     default:
433         throw new ADEIException(translate("SHOW COLUMNS not implemented for %s", $this->driver));
434     }
435  }
436  
437  function CreateDatabase($dbname) {
438     $this->Query("CREATE DATABASE `$dbname`", DATABASE::GLOBAL_QUERY);
439  }
440  
441  
442  function GetDatabaseList($filter = false) {
443     $resp = $this->ShowDatabases();
444
445     $dblist = array();
446
447     foreach ($resp as $row) {
448     $name = $row[0];
449     if ((!$filter)||(preg_match($filter, $name))) {
450         $dblist[$name] = array(
451         'name' => $name
452         );
453     }
454     }
455     
456     return $dblist;
457  }
458  
459  function __sleep() {
460     return array('server', 'dbname', 'driver', 'odbc', 'connected');
461  }
462  
463  function __wakeup() {
464     $this->dbh = NULL;
465     $this->connected = false;
466  }
467 }
468
469
470 /* expecting rows in a form: chan1,chan2,...,chan#,time  */
471 class DATABASEData implements Iterator {
472  var $reader;
473  var $stmt;
474  var $row;
475  var $time;
476  
477  function __construct(READER &$reader, PDOStatement &$stmt) {
478     $this->reader = &$reader;
479     $this->stmt = &$stmt;
480  }
481  
482  function rewind() {
483     try {
484     $this->stmt->execute();
485     $this->next();
486     } catch(PDOException $e) {
487     throw new ADEIException(translate("SQL request is failed with error") . ": " . $e->getMessage(), $e->getCode());
488     }
489
490  }
491  
492     // current element
493  function current() {
494     return $this->row;
495  }
496  
497     // current key (PHP rounding to integer :()
498  function key() {
499 /*    echo $this->time;
500     echo "  -  ";
501     echo date('Y-m-d', $this->reader->ExportUnixTime($this->time));
502     echo "\n";*/
503     return $this->reader->ExportUnixTime($this->time);
504  }
505  
506     // advvance to next (and returns it or false)
507  function next() {
508     try {
509     $this->row = $this->stmt->fetch(PDO::FETCH_NUM);
510     if ($this->row) {
511         $last = sizeof($this->row) - 1;
512         $this->time = $this->row[$last];
513         unset($this->row[$last]);
514     }
515     } catch(PDOException $e) {
516 //    $err = $this->RecodeMessage($e->getMessage());
517     $err = $e->getMessage();
518     throw new ADEIException(translate("SQL error: %s", $err));
519     }
520  }
521  
522     // checks if there is current element
523  function valid() {
524     return $this->row?true:false;
525  }
526 }
527
528 ?>
Note: See TracBrowser for help on using the browser.