* @package Mammut\DB\Adapter\PDO */ class PDO extends \Mammut\DB\DB { const _VERSION_ = '1.1.0.0'; /** * * @var \PDO */ private $db = false; private $type = false; private $affected = 0; private $supported = ['db2','sqlsrv','mysql','oracle','pgsql','sqlite']; private $driverVariant = NULL; private $utilClass; private $tableClass; public function __construct($cstring, $user, $password, array $options = array(), iCache $cache = NULL) { list($type, $dest) = explode(':', $cstring, 2); if(!in_array(strtolower($type), $this->supported)) throw new InvalidArgumentException('db type not supported:' . $type); $this->type = strtolower($type); $pdo_opt = array(); if(array_search(self::OPT_PERSISTENT, $options) !== false) $pdo_opt[PDO::ATTR_PERSISTENT] = true; $this->logDebug('connecting via pdo: ' . $cstring); try { $this->db = new \PDO($cstring, $user, $password, $pdo_opt); $this->db->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); } catch(\PDOException $pdoex) { throw new \Mammut\DB\DBException($pdoex->getMessage()); } switch($this->type) { case 'mssql': case 'sqlsrv': $this->db->query('SET DATEFORMAT ymd'); $this->db->query('SET IMPLICIT_TRANSACTIONS OFF'); $this->utilClass = '\\Mammut\\DB\\MSSQL\\MSSQLUtil'; $this->tableClass = '\\Mammut\\DB\\MSSQL\\Table'; $this->dialect = new \Mammut\DB\Sql\Dialect\SqlServer($this->db); break; case 'mysql': $this->db->query('SET SESSION sql_mode = \'ANSI\''); $this->utilClass = '\\Mammut\\DB\\MySQLi\\MySQLUtil'; $this->tableClass = '\\Mammut\\DB\\MySQLi\\Table'; $this->dialect = new \Mammut\DB\Sql\Dialect\Mysql($this->db); break; case 'pgsql': $this->utilClass = '\\Mammut\\DB\\PostGre\\PostGreUtil'; $this->tableClass = '\\Mammut\\DB\\PostGre\\Table'; $this->dialect = new \Mammut\DB\Sql\Dialect\Postgresql($this->db); break; } // TODO: add select database } public function setDatabase($name) { try { $name = $this->escapeValue($name); // todo: add return type support switch($this->type) { case 'sqlsrv': // TODO: implement break; case 'mysql': $this->query("USE DATABASE `{$name}`"); break; case 'pgsql': $result = $this->query('\connect ' . $name); break; } } catch(\PDOException $e) { throw new \Mammut\DB\DBException($e->getMessage); } } public function getDatabase() { try { switch($this->type) { case 'sqlsrv': $row = $this->getRow('SELECT db_name()'); if(is_array($row)) return $row[0]; else return false; break; case 'mysql': $row = $this->getRow("SELECT DATABASE()"); if(is_array($row)) return $row[0]; else return false; break; case 'pgsql': $row = $this->getRow('SELECT current_database() AS db'); if(is_array($row)) return $row[0]; else return false; break; } } catch(\PDOException $e) { throw new \Mammut\DB\DBException($e->getMessage()); } } public function getSchema() { if($this->type == 'pgsql') { $result = $this->getObject("SELECT current_schema() AS s"); return $result->s; } return false; } public function query($query, $limit = -1, $skip = 0) { $query = $this->checkQuery($query); $limit = intval($limit); $skip = intval($skip); if($skip < 0) $skip = 0; if($this->type == 'mysql') { if($limit >= 0) { if($skip > 0) $query .= ' LIMIT ' . $skip . ',' . $limit; else $query .= ' LIMIT ' . $limit; } elseif($skip > 0) // workaround, mysql dosen't support skip alone $query .= ' LIMIT 99999999999999,' . $skip; } elseif(($this->type == 'sqlsrv') && ($limit > 0)) { $query = preg_replace('#^\s*(SELECT)(\s*)#is', '\\1 TOP ' . ($limit + $skip) . '\\2', $query, 1); } elseif($this->type == 'pgsql') { if($limit >= 0) $query .= ' LIMIT ' . $limit; if($skip > 0) $query .= ' OFFSET ' . $skip; } $this->logTrace('sending new query: ' . $query); $this->affected = 0; $result = false; try { if(preg_match('/^\s*(SELECT|SHOW|DESCRIBE|\\d[tT])\s+/is', $query)) { // pgsql uses \d[a-Z] commands to fetch meta infos $result = $this->db->query($query); $this->qcount++; if(is_object($result)) { if(($this->type == 'sqlsrv') && ($skip > 0)) { for($i = 0; $i < $skip; $i++) // sqlsrv emulation of 'skip' $result->fetch(PDO::FETCH_NUM); } $this->affected = $result->rowCount() - $skip; return new Result($result, $this->dialect); } else return $result; } else { $this->affected = $this->db->exec($query); $this->qcount++; return $this->affected; } } catch(\PDOException $e) { throw new SQLException($e->getMessage(), $query); } } public function setAutocommit($doAutoCommit) { if($this->type == 'pgsql') { // not shure if there should be some code in here } elseif($this->type == 'sqlsrv') { if ($doAutoCommit) $this->query('SET IMPLICIT_TRANSACTIONS OFF'); else $this->query('SET IMPLICIT_TRANSACTIONS ON'); } else $this->db->setAttribute(\PDO::ATTR_AUTOCOMMIT, $doAutoCommit); } /** * creates a new statement which can be executed later * * @param string $query * @return \Mammut\DB\Statement */ public function prepareStatement($query, $limit = -1, $skip = 0) { $query = $this->checkQuery($query); try { $stmt = new Statement($this, $this->db->prepare($query), $this->dialect); } catch(\PDOException $e) { throw new SQLException($e->getMessage(), $query); } return $stmt; } public function getRow($sql) { $sql = $this->checkQuery($sql); $result = $this->db->query($sql); $this->qcount++; return $result->fetch(\PDO::FETCH_NUM); } public function getArray($sql) { $sql = $this->checkQuery($sql); $result = $this->db->query($sql); $this->qcount++; return $result->fetch(\PDO::FETCH_ASSOC); } public function getObject($sql, $class = false, $param = array()) { $sql = $this->checkQuery($sql); try { $result = $this->db->query($sql); $this->qcount++; if($class) return $result->fetchObject($class, $param); else return $result->fetchObject(); } catch(\PDOException $e) { throw new SQLException($e->getMessage(), $sql); } } public function getInsertId() { switch($this->type) { case 'sqlsrv': $this->qcount++; return (int) $this->getObject("SELECT @@IDENTITY AS 'identity'")->identity; break; default: return $this->db->lastInsertId(); } } /** * * @return int number of rows affected by the last query */ public function getAffectedRowCount() { return $this->affected; } public function tableList($removePrefix = true) { $result = false; if(!empty($this->tables)) return $this->tables; $this->qcount++; switch($this->type) { case 'sqlsrv': $result = $this->db->query('SELECT Table_name AS "Table name" FROM Information_schema.Tables WHERE Table_type = \'BASE TABLE\' AND (Objectproperty (Object_id(Table_name), \'IsMsShipped\') = 0 OR Objectproperty (Object_id(Table_name), \'IsMsShipped\') IS NULL)'); break; case 'mysql': $result = $this->db->query("SHOW TABLES"); break; case 'oracle': $result = $this->db->query("SELECT table_name FROM user_tables"); break; case 'pgsql': $result = $this->db->query("SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'"); break; default: throw new \InvalidArgumentException('unsupported type'); } if(is_object($result)) { $tables = array(); while($next = $result->fetch()) $tables[] = strtolower($next[0]); $this->tables = $tables; return $tables; } else return false; } public function table($table) { if($table instanceof \Mammut\DB\Model\TableInfo) $table = $table->getName(); if(array_search(strtolower($table), $this->tableList()) === false) throw new DBException('table "' . $table . '" dosen\'t exist'); $class = $this->tableClass; return new $class($this, $table); } // public function getTableInfo($tablename) { // return call_user_func(array($this->utilClass,'getTableInfo'), $this, $tablename); // } /** * converts a datetime column value to a unix timestap */ public function dateCol2uts($date) { switch($this->type) { default: if(preg_match('#([0-9]{1,4})[./-]([0-9]{1,2})[./-]([0-9]{1,2})#', $date, $match)) { $t = mktime(0, 0, 0, $match[2], $match[3], $match[1]); return $t; } else return false; break; } } /** * converts a uts integer to the datetime format of this database */ public function uts2dateCol($uts) { if(!is_numeric($uts)) throw new \InvalidArgumentException(gettype($uts) . ' is an invalid timestamp type'); switch($this->type) { default: return date('Y-m-d', $uts); } } /** * converts a datetime column */ public function datetimeCol2uts($datetime) { switch($this->type) { default: if(preg_match('#([0-9]{1,4})[./-]([0-9]{1,4})[./-]([0-9]{1,4})\s+([0-9]{1,2})[:.]([0-9]{1,2})[:.]([0-9]{1,2})#', $datetime, $match)) { $t = mktime($match[4], $match[5], $match[6], $match[2], $match[3], $match[1]); return $t; } else return false; break; } } /** * converts a uts integer to the datetime format of this database */ public function uts2datetimeCol($uts) { switch($this->type) { default: return date('Y-m-d H:i:s', $uts); } } /** * escapes a string to be used as a text value. * that means escaping all special chars and quoting the content * * @param string $name * name of the table * @return string escaped and quoted name */ public function escapeValue($value, $addQuotes = true) { if(is_null($value)) return 'NULL'; if(is_bool($value)) { switch($this->type) { case 'sqlsrv': return $value ? '1' : '0'; default: return $value ? 'TRUE' : 'FALSE'; } } if(is_int($value) || is_long($value)) return (int) $value; if($value instanceof SQLFunction) return $this->getDialect()->getFunctionSQL($value); $q = $addQuotes ? '\'' : ''; if($value instanceof \DateTime) return $q . $value->format('Y-m-d H:i:s') . $q; if($value instanceof \Mammut\IO\File) { $filename = $value->getPath(); $content = file_get_contents($filename); return $q . $this->db->quote($content) . $q; } if(is_int($value) || is_long($value) || is_float($value) || is_double($value)) return $value; return $addQuotes ? $this->db->quote($value) : $value; // ansi syntax } }