├── classes ├── Database │ ├── PostgreSQL.php │ └── PostgreSQL │ │ └── Result.php └── Kohana │ └── Database │ ├── PostgreSQL │ └── Result.php │ └── PostgreSQL.php ├── README.md └── config └── database.php /classes/Database/PostgreSQL.php: -------------------------------------------------------------------------------- 1 | array 6 | ( 7 | 'type' => 'PostgreSQL', 8 | 'connection' => array( 9 | /** 10 | * There are two ways to define a connection for PostgreSQL: 11 | * 12 | * 1. Full connection string passed directly to pg_connect() 13 | * 14 | * string info 15 | * 16 | * 2. Connection parameters: 17 | * 18 | * string hostname NULL to use default domain socket 19 | * integer port NULL to use the default port 20 | * string username 21 | * string password 22 | * string database 23 | * boolean persistent 24 | * mixed ssl TRUE to require, FALSE to disable, or 'prefer' to negotiate 25 | * 26 | * @link http://www.postgresql.org/docs/current/static/libpq-connect.html 27 | */ 28 | 'hostname' => 'localhost', 29 | 'username' => NULL, 30 | 'password' => NULL, 31 | 'persistent' => FALSE, 32 | 'database' => 'kohana', 33 | ), 34 | 'primary_key' => '', // Column to return from INSERT queries, see #2188 and #2273 35 | 'schema' => '', 36 | 'table_prefix' => '', 37 | 'charset' => 'utf8', 38 | 'caching' => FALSE, 39 | ), 40 | ); 41 | -------------------------------------------------------------------------------- /classes/Kohana/Database/PostgreSQL/Result.php: -------------------------------------------------------------------------------- 1 | _as_object = 'stdClass'; 20 | } 21 | 22 | if ($total_rows !== NULL) 23 | { 24 | $this->_total_rows = $total_rows; 25 | } 26 | else 27 | { 28 | switch (pg_result_status($result)) 29 | { 30 | case PGSQL_TUPLES_OK: 31 | $this->_total_rows = pg_num_rows($result); 32 | break; 33 | case PGSQL_COMMAND_OK: 34 | $this->_total_rows = pg_affected_rows($result); 35 | break; 36 | case PGSQL_BAD_RESPONSE: 37 | case PGSQL_NONFATAL_ERROR: 38 | case PGSQL_FATAL_ERROR: 39 | throw new Database_Exception(':error [ :query ]', 40 | array(':error' => pg_result_error($result), ':query' => $sql)); 41 | case PGSQL_COPY_OUT: 42 | case PGSQL_COPY_IN: 43 | throw new Database_Exception('PostgreSQL COPY operations not supported [ :query ]', 44 | array(':query' => $sql)); 45 | default: 46 | $this->_total_rows = 0; 47 | } 48 | } 49 | } 50 | 51 | public function __destruct() 52 | { 53 | if (is_resource($this->_result)) 54 | { 55 | pg_free_result($this->_result); 56 | } 57 | } 58 | 59 | public function as_array($key = NULL, $value = NULL) 60 | { 61 | if ($this->_total_rows === 0) 62 | return array(); 63 | 64 | if ( ! $this->_as_object AND $key === NULL) 65 | { 66 | // Rewind 67 | $this->_current_row = 0; 68 | 69 | if ($value === NULL) 70 | { 71 | // Indexed rows 72 | return pg_fetch_all($this->_result); 73 | } 74 | 75 | // Indexed columns 76 | return pg_fetch_all_columns($this->_result, pg_field_num($this->_result, $value)); 77 | } 78 | 79 | return parent::as_array($key, $value); 80 | } 81 | 82 | /** 83 | * SeekableIterator: seek 84 | */ 85 | public function seek($offset) 86 | { 87 | if ( ! $this->offsetExists($offset)) 88 | return FALSE; 89 | 90 | $this->_current_row = $offset; 91 | 92 | return TRUE; 93 | } 94 | 95 | /** 96 | * Iterator: current 97 | */ 98 | public function current() 99 | { 100 | if ( ! $this->offsetExists($this->_current_row)) 101 | return FALSE; 102 | 103 | if ( ! $this->_as_object) 104 | return pg_fetch_assoc($this->_result, $this->_current_row); 105 | 106 | if ( ! $this->_object_params) 107 | return pg_fetch_object($this->_result, $this->_current_row, $this->_as_object); 108 | 109 | return pg_fetch_object($this->_result, $this->_current_row, $this->_as_object, $this->_object_params); 110 | } 111 | 112 | } 113 | -------------------------------------------------------------------------------- /classes/Kohana/Database/PostgreSQL.php: -------------------------------------------------------------------------------- 1 | _config['connection']['info'])) 20 | { 21 | // Build connection string 22 | $this->_config['connection']['info'] = ''; 23 | 24 | extract($this->_config['connection']); 25 | 26 | if ( ! empty($hostname)) 27 | { 28 | $info .= "host='$hostname'"; 29 | } 30 | 31 | if ( ! empty($port)) 32 | { 33 | $info .= " port='$port'"; 34 | } 35 | 36 | if ( ! empty($username)) 37 | { 38 | $info .= " user='$username'"; 39 | } 40 | 41 | if ( ! empty($password)) 42 | { 43 | $info .= " password='$password'"; 44 | } 45 | 46 | if ( ! empty($database)) 47 | { 48 | $info .= " dbname='$database'"; 49 | } 50 | 51 | if (isset($ssl)) 52 | { 53 | if ($ssl === TRUE) 54 | { 55 | $info .= " sslmode='require'"; 56 | } 57 | elseif ($ssl === FALSE) 58 | { 59 | $info .= " sslmode='disable'"; 60 | } 61 | else 62 | { 63 | $info .= " sslmode='$ssl'"; 64 | } 65 | } 66 | 67 | $this->_config['connection']['info'] = $info; 68 | } 69 | } 70 | 71 | public function connect() 72 | { 73 | if ($this->_connection) 74 | return; 75 | 76 | try 77 | { 78 | $this->_connection = empty($this->_config['connection']['persistent']) 79 | ? pg_connect($this->_config['connection']['info'], PGSQL_CONNECT_FORCE_NEW) 80 | : pg_pconnect($this->_config['connection']['info'], PGSQL_CONNECT_FORCE_NEW); 81 | } 82 | catch (ErrorException $e) 83 | { 84 | throw new Database_Exception(':error', array(':error' => $e->getMessage())); 85 | } 86 | 87 | if ( ! is_resource($this->_connection)) 88 | throw new Database_Exception('Unable to connect to PostgreSQL ":name"', array(':name' => $this->_instance)); 89 | 90 | $this->_version = pg_parameter_status($this->_connection, 'server_version'); 91 | 92 | if ( ! empty($this->_config['charset'])) 93 | { 94 | $this->set_charset($this->_config['charset']); 95 | } 96 | 97 | if (empty($this->_config['schema'])) 98 | { 99 | // Assume the default schema without changing the search path 100 | $this->_config['schema'] = 'public'; 101 | } 102 | else 103 | { 104 | if ( ! pg_send_query($this->_connection, 'SET search_path = '.$this->_config['schema'].', pg_catalog')) 105 | throw new Database_Exception(pg_last_error($this->_connection)); 106 | 107 | if ( ! $result = pg_get_result($this->_connection)) 108 | throw new Database_Exception(pg_last_error($this->_connection)); 109 | 110 | if (pg_result_status($result) !== PGSQL_COMMAND_OK) 111 | throw new Database_Exception(pg_result_error($result)); 112 | } 113 | } 114 | 115 | public function disconnect() 116 | { 117 | if ( ! $status = ! is_resource($this->_connection)) 118 | { 119 | if ($status = pg_close($this->_connection)) 120 | { 121 | $this->_connection = NULL; 122 | } 123 | } 124 | 125 | return $status; 126 | } 127 | 128 | public function set_charset($charset) 129 | { 130 | $this->_connection OR $this->connect(); 131 | 132 | if (pg_set_client_encoding($this->_connection, $charset) !== 0) 133 | throw new Database_Exception(pg_last_error($this->_connection)); 134 | } 135 | 136 | /** 137 | * Execute a PostgreSQL command 138 | * 139 | * @param string $sql SQL command 140 | * @return boolean 141 | */ 142 | protected function _command($sql) 143 | { 144 | $this->_connection OR $this->connect(); 145 | 146 | if ( ! pg_send_query($this->_connection, $sql)) 147 | throw new Database_Exception(pg_last_error($this->_connection)); 148 | 149 | if ( ! $result = pg_get_result($this->_connection)) 150 | throw new Database_Exception(pg_last_error($this->_connection)); 151 | 152 | return (pg_result_status($result) === PGSQL_COMMAND_OK); 153 | } 154 | 155 | public function query($type, $sql, $as_object = FALSE, array $params = NULL) 156 | { 157 | $this->_connection OR $this->connect(); 158 | 159 | if (Kohana::$profiling) 160 | { 161 | // Benchmark this query for the current instance 162 | $benchmark = Profiler::start("Database ({$this->_instance})", $sql); 163 | } 164 | 165 | try 166 | { 167 | if ($type === Database::INSERT AND $this->_config['primary_key']) 168 | { 169 | $sql .= ' RETURNING '.$this->quote_identifier($this->_config['primary_key']); 170 | } 171 | 172 | try 173 | { 174 | $result = pg_query($this->_connection, $sql); 175 | } 176 | catch (Exception $e) 177 | { 178 | throw new Database_Exception(':error [ :query ]', 179 | array(':error' => pg_last_error($this->_connection), ':query' => $sql)); 180 | } 181 | 182 | if ( ! $result) 183 | throw new Database_Exception(':error [ :query ]', 184 | array(':error' => pg_last_error($this->_connection), ':query' => $sql)); 185 | 186 | // Check the result for errors 187 | switch (pg_result_status($result)) 188 | { 189 | case PGSQL_COMMAND_OK: 190 | $rows = pg_affected_rows($result); 191 | break; 192 | case PGSQL_TUPLES_OK: 193 | $rows = pg_num_rows($result); 194 | break; 195 | case PGSQL_BAD_RESPONSE: 196 | case PGSQL_NONFATAL_ERROR: 197 | case PGSQL_FATAL_ERROR: 198 | throw new Database_Exception(':error [ :query ]', 199 | array(':error' => pg_result_error($result), ':query' => $sql)); 200 | case PGSQL_COPY_OUT: 201 | case PGSQL_COPY_IN: 202 | pg_end_copy($this->_connection); 203 | 204 | throw new Database_Exception('PostgreSQL COPY operations not supported [ :query ]', 205 | array(':query' => $sql)); 206 | default: 207 | $rows = 0; 208 | } 209 | 210 | if (isset($benchmark)) 211 | { 212 | Profiler::stop($benchmark); 213 | } 214 | 215 | $this->last_query = $sql; 216 | 217 | if ($type === Database::SELECT) 218 | return new Database_PostgreSQL_Result($result, $sql, $as_object, $params, $rows); 219 | 220 | if ($type === Database::INSERT) 221 | { 222 | if ($this->_config['primary_key']) 223 | { 224 | // Fetch the first column of the last row 225 | $insert_id = pg_fetch_result($result, $rows - 1, 0); 226 | } 227 | elseif ($insert_id = pg_send_query($this->_connection, 'SELECT LASTVAL()')) 228 | { 229 | if ($result = pg_get_result($this->_connection) AND pg_result_status($result) === PGSQL_TUPLES_OK) 230 | { 231 | $insert_id = pg_fetch_result($result, 0); 232 | } 233 | } 234 | 235 | return array($insert_id, $rows); 236 | } 237 | 238 | return $rows; 239 | } 240 | catch (Exception $e) 241 | { 242 | if (isset($benchmark)) 243 | { 244 | Profiler::delete($benchmark); 245 | } 246 | 247 | throw $e; 248 | } 249 | } 250 | 251 | /** 252 | * Start a SQL transaction 253 | * 254 | * @link http://www.postgresql.org/docs/current/static/sql-set-transaction.html 255 | * 256 | * @param string $mode Transaction mode 257 | * @return boolean 258 | */ 259 | public function begin($mode = NULL) 260 | { 261 | return $this->_command("BEGIN $mode"); 262 | } 263 | 264 | public function commit() 265 | { 266 | return $this->_command('COMMIT'); 267 | } 268 | 269 | /** 270 | * Abort the current transaction or roll back to a savepoint 271 | * 272 | * @param string $savepoint Savepoint name 273 | * @return boolean 274 | */ 275 | public function rollback($savepoint = NULL) 276 | { 277 | return $this->_command($savepoint ? "ROLLBACK TO $savepoint" : 'ROLLBACK'); 278 | } 279 | 280 | /** 281 | * Define a new savepoint in the current transaction 282 | * 283 | * @param string $name Savepoint name 284 | * @return boolean 285 | */ 286 | public function savepoint($name) 287 | { 288 | return $this->_command("SAVEPOINT $name"); 289 | } 290 | 291 | /** 292 | * @link http://www.postgresql.org/docs/current/static/datatype.html#DATATYPE-TABLE 293 | */ 294 | public function datatype($type) 295 | { 296 | static $types = array 297 | ( 298 | // PostgreSQL >= 7.4 299 | 'box' => array('type' => 'string'), 300 | 'bytea' => array('type' => 'string', 'binary' => TRUE), 301 | 'cidr' => array('type' => 'string'), 302 | 'circle' => array('type' => 'string'), 303 | 'inet' => array('type' => 'string'), 304 | 'int2' => array('type' => 'int', 'min' => '-32768', 'max' => '32767'), 305 | 'int4' => array('type' => 'int', 'min' => '-2147483648', 'max' => '2147483647'), 306 | 'int8' => array('type' => 'int', 'min' => '-9223372036854775808', 'max' => '9223372036854775807'), 307 | 'line' => array('type' => 'string'), 308 | 'lseg' => array('type' => 'string'), 309 | 'macaddr' => array('type' => 'string'), 310 | 'money' => array('type' => 'float', 'exact' => TRUE, 'min' => '-92233720368547758.08', 'max' => '92233720368547758.07'), 311 | 'path' => array('type' => 'string'), 312 | 'point' => array('type' => 'string'), 313 | 'polygon' => array('type' => 'string'), 314 | 'text' => array('type' => 'string'), 315 | 316 | // PostgreSQL >= 8.3 317 | 'tsquery' => array('type' => 'string'), 318 | 'tsvector' => array('type' => 'string'), 319 | 'uuid' => array('type' => 'string'), 320 | 'xml' => array('type' => 'string'), 321 | ); 322 | 323 | if (isset($types[$type])) 324 | return $types[$type]; 325 | 326 | return parent::datatype($type); 327 | } 328 | 329 | public function list_tables($like = NULL) 330 | { 331 | $this->_connection OR $this->connect(); 332 | 333 | $sql = 'SELECT table_name FROM information_schema.tables WHERE table_schema = '.$this->quote($this->schema()); 334 | 335 | if (is_string($like)) 336 | { 337 | $sql .= ' AND table_name LIKE '.$this->quote($like); 338 | } 339 | 340 | return $this->query(Database::SELECT, $sql, FALSE)->as_array(NULL, 'table_name'); 341 | } 342 | 343 | public function list_columns($table, $like = NULL, $add_prefix = TRUE) 344 | { 345 | $this->_connection OR $this->connect(); 346 | 347 | $sql = 'SELECT column_name, column_default, is_nullable, data_type, character_maximum_length, numeric_precision, numeric_scale, datetime_precision' 348 | .' FROM information_schema.columns' 349 | .' WHERE table_schema = '.$this->quote($this->schema()) 350 | .' AND table_name = '.$this->quote($add_prefix ? ($this->table_prefix().$table) : $table); 351 | 352 | if (is_string($like)) 353 | { 354 | $sql .= ' AND column_name LIKE '.$this->quote($like); 355 | } 356 | 357 | $sql .= ' ORDER BY ordinal_position'; 358 | 359 | $result = array(); 360 | 361 | foreach ($this->query(Database::SELECT, $sql, FALSE) as $column) 362 | { 363 | $column = array_merge($this->datatype($column['data_type']), $column); 364 | 365 | $column['is_nullable'] = ($column['is_nullable'] === 'YES'); 366 | 367 | $result[$column['column_name']] = $column; 368 | } 369 | 370 | return $result; 371 | } 372 | 373 | public function schema() 374 | { 375 | return $this->_config['schema']; 376 | } 377 | 378 | public function escape($value) 379 | { 380 | $this->_connection OR $this->connect(); 381 | 382 | $value = pg_escape_string($this->_connection, $value); 383 | 384 | return "'$value'"; 385 | } 386 | } 387 | --------------------------------------------------------------------------------