Changeset 1248

Show
Ignore:
Timestamp:
11/23/2007 02:38:14 PM (12 months ago)
Author:
zombor
Message:

Updates on getting a working PostgreSQL driver working.

Location:
trunk/system
Files:
2 modified

Legend:

Unmodified
Added
Removed
  • trunk/system/i18n/en_US/database.php

    r945 r1248  
    1010        'must_use_set'          => 'You must set a SET clause for your query.', 
    1111        'must_use_where'        => 'You must set a WHERE clause for your query.', 
    12         'must_use_table'        => 'You must set a datbase table for your query.' 
     12        'must_use_table'        => 'You must set a datbase table for your query.', 
     13        'not_implimented'       => 'The method you called, %s, is not supported by this driver.' 
    1314); 
  • trunk/system/libraries/drivers/Database_Pgsql.php

    </
    r1230 r1248  
    22/** 
    33 * Class: Database_Pgsql_Driver 
    4  *  Provides specific database items for PostgreSQL. 
     4 *  Provides specific database items for PostgreSQL 8.1+. 
    55 * 
    66 * Kohana Source Code: 
     
    99 *  license   - <http://kohanaphp.com/license.html> 
    1010 */ 
    11 class Database_Pgsql_Driver implements Database_Driver { 
     11class Database_Pgsql_Driver extends Database_Driver { 
    1212 
    1313        // Database connection link 
     
    4343                        } 
    4444 
    45                         return TRUE; 
     45                        return $this->link; 
    4646                } 
    4747 
     
    5151        public function query($sql) 
    5252        { 
    53                 return new Pgsql_Result(pg_query($this->link, $sql), $this->link, $this->db_config['object'], $sql); 
    54         } 
    55  
    56         public function delete($table, $where) 
    57         { 
    58         return 'DELETE FROM '.$this->escape_table($table).' WHERE '.implode(' ', $where); 
    59         } 
    60  
    61         public function update($table, $values, $where) 
    62         { 
    63                 foreach($values as $key => $val) 
    64                 { 
    65                         $valstr[] = $this->escape_column($key)." = ".$val; 
    66                 } 
    67                 return 'UPDATE '.$this->escape_table($table).' SET '.implode(', ', $valstr).' WHERE '.implode(' AND ',$this->where($where, NULL, 'AND', 0, TRUE)); 
     53                return new Pgsql_Result(pg_query($sql, $this->link), $this->link, $this->db_config['object'], $sql); 
    6854        } 
    6955 
     
    7561        public function escape_table($table) 
    7662        { 
    77                 return str_replace('.', '`.`', $table); 
    78         } 
    79  
    80         public function escape_column($column) 
    81         { 
    82                 return '\''.$column.'\''; 
    83         } 
    84  
    85         public function where($key, $value, $type, $num_wheres, $quote) 
    86         { 
    87                 if ( ! is_array($key)) 
    88                 { 
    89                         $key = array($key => $value); 
    90                 } 
    91  
    92                 $wheres = array(); 
    93                 $count = 1; 
    94                 foreach ($key as $k => $v) 
    95                 { 
    96  
    97                         $prefix = (($num_wheres > 0) OR ($count++ > 1)) ? $type : ''; 
    98  
    99                         if ($quote === -1) 
    100                         { 
    101                                 $v = ''; 
    102                         } 
    103                         else 
    104                         { 
    105                                 if ($v === NULL) 
    106                                 { 
    107                                         if ( ! $this->has_operator($k)) 
    108                                         { 
    109                                                 $k .= ' IS'; 
    110                                         } 
    111  
    112                                         $v = ' NULL'; 
    113                                 } 
    114                                 elseif (is_bool($v)) 
    115                                 { 
    116                                         if ( ! $this->has_operator($k)) 
    117                                         { 
    118                                                 $k .= ' ='; 
    119                                         } 
    120  
    121                                         $v = ($v == TRUE) ? ' 1' : ' 0'; 
    122                                 } 
    123                                 else 
    124                                 { 
    125                                         if ( ! $this->has_operator($k)) 
    126                                         { 
    127                                            $k .= ' ='; 
    128                                         } 
    129  
    130                                         $v = ' '.(($quote == TRUE) ? $this->escape($v) : $v); 
    131                                 } 
    132                         } 
    133                         $wheres[] = $prefix.$k.$v; 
    134                 } 
    135                 return $wheres; 
    136         } 
    137  
    138         public function like($field, $match = '', $type = 'AND ', $num_likes) 
    139         { 
    140                 if ( ! is_array($field)) 
    141                 { 
    142                         $field = array($field => $match); 
    143                 } 
    144  
    145                 $likes = array(); 
    146                 foreach ($field as $k => $v) 
    147                 { 
    148                         $prefix = (count($num_likes) == 0) ? '' : $type; 
    149  
    150                         $v = (substr($v, 0, 1) == '%' OR substr($v, (strlen($v)-1), 1) == '%') ? $this->escape_str($v) : '%'.$this->escape_str($v).'%'; 
    151  
    152                         $likes[] = $prefix." ".$k." LIKE '".$v . "'"; 
    153                 } 
    154                 return $likes; 
    155         } 
    156  
    157         public function insert($table, $keys, $values) 
    158         { 
    159                 return 'INSERT INTO '.$this->escape_table($table).' ('.implode(', ', $keys).') VALUES ('.implode(', ', $values).')'; 
     63                return '\''.str_replace('.', '\'.\'', $table).'\''; 
     64        } 
     65 
     66public function escape_column($column) 
     67        { 
     68                if (strtolower($column) == 'count(*)' OR $column == '*') 
     69                        return $column; 
     70 
     71                // This matches any modifiers we support to SELECT. 
     72                if ( ! preg_match('/\b(?:rand|all|distinct(?:row)?|high_priority|sql_(?:small_result|b(?:ig_result|uffer_result)|no_cache|ca(?:che|lc_found_rows)))\s/i', $column)) 
     73                { 
     74                        if (stripos($column, ' AS ') !== FALSE) 
     75                        { 
     76                                // Force 'AS' to uppercase 
     77                                $column = str_ireplace(' AS ', ' AS ', $column); 
     78 
     79                                // Runs escape_column on both sides of an AS statement 
     80                                $column = array_map(array($this, __FUNCTION__), explode(' AS ', $column)); 
     81 
     82                                // Re-create the AS statement 
     83                                return implode(' AS ', $column); 
     84                        } 
     85                 
     86                        return preg_replace('/[^.*]+/', '\'$0\'', $column); 
     87                } 
     88 
     89                $parts = explode(' ', $column); 
     90                $column = ''; 
     91 
     92                for ($i = 0, $c = count($parts); $i < $c; $i++) 
     93                { 
     94                        // The column is always last 
     95                        if ($i == ($c - 1)) 
     96                        { 
     97                                $column .= preg_replace('/[^.*]+/', '\'$0\'', $parts[$i]); 
     98                        } 
     99                        else // otherwise, it's a modifier 
     100                        { 
     101                                $column .= $parts[$i].' '; 
     102                        } 
     103                } 
     104                return $column; 
     105        } 
     106 
     107        public function regex($field, $match = '', $type = 'AND ', $num_regexs) 
     108        { 
     109                $prefix = ($num_regexs == 0) ? '' : $type; 
     110 
     111                return $prefix.' '.$this->escape_column($field).' REGEXP \''.$this->escape_str($match).'\''; 
     112        } 
     113 
     114        public function notregex($field, $match = '', $type = 'AND ', $num_regexs) 
     115        { 
     116                $prefix = $num_regexs == 0 ? '' : $type; 
     117 
     118                return $prefix.' '.$this->escape_column($field).' NOT REGEXP \''.$this->escape_str($match) . '\''; 
    160119        } 
    161120 
     
    167126        public function compile_select($database) 
    168127        { 
    169                 $sql  = ($database['distinct'] == TRUE) ? 'SELECT DISTINCT ' : 'SELECT '; 
     128                $sql = ($database['distinct'] == TRUE) ? 'SELECT DISTINCT ' : 'SELECT '; 
    170129                $sql .= (count($database['select']) > 0) ? implode(', ', $database['select']) : '*'; 
    171130 
     
    176135                } 
    177136 
    178                 if (count($database['where']) > 0 OR count($database['like']) > 0) 
     137                if (count($database['join']) > 0) 
     138                { 
     139                        $sql .= ' '.implode("\n", $database['join']); 
     140                } 
     141 
     142                if (count($database['where']) > 0) 
    179143                { 
    180144                        $sql .= "\nWHERE "; 
     
    182146 
    183147                $sql .= implode("\n", $database['where']); 
    184  
    185                 if (count($database['like']) > 0) 
    186                 { 
    187                         if (count($database['where']) > 0) 
    188                         { 
    189                                 $sql .= ' '; 
    190                         } 
    191  
    192                         $sql .= implode("\n", $database['like']); 
    193                 } 
    194148 
    195149                if (count($database['groupby']) > 0) 
     
    209163                        $sql .= "\nORDER BY "; 
    210164                        $sql .= implode(', ', $database['orderby']); 
    211  
    212                         if ($database['order'] !== FALSE) 
    213                         { 
    214                                 $sql .= ($database['order'] == 'desc') ? ' DESC' : ' ASC'; 
    215                         } 
    216165                } 
    217166 
     
    225174        } 
    226175 
    227         public function has_operator($str) 
    228         { 
    229                 return (bool) preg_match('/!?[=<>]|\sIS\s/i', trim($str)); 
    230         } 
    231  
    232         public function escape($str) 
    233         { 
    234                 switch (gettype($str)) 
    235                 { 
    236                         case 'string': 
    237                                 $str = "'".$this->escape_str($str)."'"; 
    238                         break; 
    239                         case 'boolean': 
    240                                 $str = (int) $str; 
    241                         break; 
    242                         default: 
    243                                 $str = ($str === NULL) ? 'NULL' : $str; 
    244                         break; 
    245                 } 
    246  
    247                 return (string) $str; 
    248         } 
    249  
    250176        public function escape_str($str) 
    251177        { 
    252                 if ( ! is_resource($this->link)) 
    253                 { 
    254                         $this->connect($this->db_config); 
    255                 } 
    256  
    257                 return mysql_real_escape_string($str, $this->link); 
    258         } 
    259   
     178                is_resource($this->link) or $this->connect($this->db_config); 
     179 
     180                return pg_escape_string($this->link, $str); 
     181        } 
     182 
    260183        public function list_tables() 
    261184        { 
    262                 $sql = 'SHOW TABLES FROM `'.$this->db_config['connection']['database'].'`'; 
    263                 $query = $this->query($sql); 
    264                 $query = $query->result(); 
    265                  
     185                $sql    = 'SELECT table_name FROM information_schema.tables WHERE table_schema = \'public\''; 
     186                $result = $this->query($sql)->result(FALSE, PGSQL_ASSOC); 
     187 
    266188                $retval = array(); 
    267                 foreach($query as $row) 
    268                 { 
    269                         $column = 'Tables_in_'.$this->db_config['connection']['database']; 
    270                         $retval[] = $row->$column; 
    271                 } 
    272                  
     189                foreach($result as $row) 
     190                { 
     191                        $retval[] = current($row); 
     192                } 
     193 
    273194                return $retval; 
    274195        } 
    275196 
    276         function show_error() 
     197        public function show_error() 
    277198        { 
    278199                return pg_last_error($this->link); 
    279200        } 
     201 
     202        public function list_fields($table, $query = FALSE) 
     203        { 
     204                static $tables; 
     205 
     206                if (is_object($query)) 
     207                { 
     208                        if (empty($tables[$table])) 
     209                        { 
     210                                $tables[$table] = array(); 
     211 
     212                                foreach($query as $row) 
     213                                { 
     214                                        $tables[$table][] = $row->Field; 
     215                                } 
     216                        } 
     217 
     218                        return $tables[$table]; 
     219                } 
     220 
     221                // WOW...REALLY?!? 
     222                // Taken from http://www.postgresql.org/docs/7.4/interactive/catalogs.html 
     223                return 'SELECT 
     224  -- Field 
     225  pg_attribute.attname AS "Field", 
     226  -- Type 
     227  CASE pg_type.typname 
     228    WHEN \'int2\' THEN \'smallint\' 
     229    WHEN \'int4\' THEN \'int\' 
     230    WHEN \'int8\' THEN \'bigint\' 
     231    WHEN \'varchar\' THEN \'varchar(\' || pg_attribute.atttypmod-4 || \')\' 
     232    ELSE pg_type.typname 
     233  END AS "Type", 
     234  -- Null 
     235  CASE WHEN pg_attribute.attnotnull THEN \'\' 
     236    ELSE \'YES\' 
     237  END AS "Null", 
     238  -- Default 
     239  CASE pg_type.typname 
     240    WHEN \'varchar\' THEN substring(pg_attrdef.adsrc from \'^\'(.*)\'.*$\') 
     241    ELSE pg_attrdef.adsrc 
     242  END AS "Default" 
     243FROM pg_class 
     244  INNER JOIN pg_attribute 
     245    ON (pg_class.oid=pg_attribute.attrelid) 
     246  INNER JOIN pg_type 
     247    ON (pg_attribute.atttypid=pg_type.oid) 
     248  LEFT JOIN pg_attrdef 
     249    ON (pg_class.oid=pg_attrdef.adrelid AND pg_attribute.attnum=pg_attrdef.adnum) 
     250WHERE pg_class.relname=\''.$this->escape_table($table).'\' AND pg_attribute.attnum>=1 AND NOT pg_attribute.attisdropped 
     251ORDER BY pg_attribute.attnum'; 
     252 
     253        } 
     254 
     255        public function field_data($table) 
     256        { 
     257                // TODO: This whole function needs to be debugged. 
     258                if ( ! in_array($table, $this->list_tables())) 
     259                        return FALSE; 
     260 
     261                $query  = pg_query('SELECT * FROM '.$this->escape_table($table).' LIMIT 1', $this->link); 
     262                $fields = pg_num_fields($query); 
     263                $table  = array(); 
     264 
     265                for ($i=0; $i < $fields; $i++) 
     266                { 
     267                        $table[$i]['type']  = pg_field_type($query, $i); 
     268                        $table[$i]['name']  = pg_field_name($query, $i); 
     269                        $table[$i]['len']   = pg_field_prtlen($query, $i); 
     270                } 
     271 
     272                return $table; 
     273        } 
     274 
    280275} // End Database_Pgsql_Driver Class 
    281276 
     
    289284 *  license   - <http://kohanaphp.com/license.html> 
    290285 */ 
    291 class Pgsql_Result implements Database_Result, Iterator 
    292 { 
    293         private $link      = FALSE; 
    294         private $result    = FALSE; 
    295         private $insert_id = NULL; 
    296         private $num_rows  = 0; 
    297         private $rows      = array(); 
    298         private $object    = TRUE; 
    299  
     286class Pgsql_Result implements Database_Result, ArrayAccess, Iterator, Countable { 
     287 
     288        // Result resource 
     289        protected $result = NULL; 
     290 
     291        // Total rows and current row 
     292        protected $total_rows  = FALSE; 
     293        protected $current_row = FALSE; 
     294 
     295        // Insert id 
     296        protected $insert_id = FALSE; 
     297 
     298        // Data fetching types 
     299        protected $fetch_type  = 'pgsql_fetch_object'; 
     300        protected $return_type = PGSQL_ASSOC; 
     301 
     302        /** 
     303         * Constructor: __construct 
     304         *  Sets up the class. 
     305         * 
     306         * Parameters: 
     307         *  result - result resource 
     308         *  link   - database resource link 
     309         *  object - return objects or arrays 
     310         *  sql    - sql query that was run 
     311         */ 
    300312        public function __construct($result, $link, $object = TRUE, $sql) 
    301313        { 
    302                 $this->object = (bool) $object; 
     314                $this->result = $result; 
    303315 
    304316                // If the query is a resource, it was a SELECT, SHOW, DESCRIBE, EXPLAIN query 
    305317                if (is_resource($result)) 
    306318                { 
    307                         $this->result   = $result; 
    308                         $this->num_rows = pg_num_rows($this->result); 
     319                        $this->current_row = 0; 
     320                        $this->total_rows  = pg_num_rows($this->result); 
     321                        $this->fetch_type = ($object === TRUE) ? 'pg_fetch_object' : 'pg_fetch_array'; 
     322                } 
     323                elseif (is_bool($result)) 
     324                { 
     325                        if ($result == FALSE) 
     326                        { 
     327                                // SQL error 
     328                                throw new Kohana_Database_Exception('database.error', pg_last_error().' - '.$sql); 
     329                        } 
     330                        else 
     331                        { 
     332                                // Its an DELETE, INSERT, REPLACE, or UPDATE query 
     333                                $this->insert_id  = $this->get_insert_id($link); 
     334                                $this->total_rows = pg_affected_rows($link); 
     335                        } 
     336                } 
     337 
     338                // Set result type 
     339                $this->result($object); 
     340        } 
     341 
     342        /** 
     343         * Destructor: __destruct 
     344         *  Magic __destruct function, frees the result. 
     345         */ 
     346        public function __destruct() 
     347        { 
     348                if (is_resource($this->result)) 
     349                { 
     350                        pg_free_result($this->result); 
     351                } 
     352        } 
     353 
     354        public function result($object = TRUE, $type = PGSQL_ASSOC) 
     355        { 
     356                $this->fetch_type = ((bool) $object) ? 'pg_fetch_object' : 'pg_fetch_array'; 
     357 
     358                // This check has to be outside the previous statement, because we do not 
     359                // know the state of fetch_type when $object = NULL 
     360                // NOTE - The class set by $type must be defined before fetching the result, 
     361                // autoloading is disabled to save a lot of stupid overhead. 
     362                if ($this->fetch_type == 'pg_fetch_object') 
     363                { 
     364                        $this->return_type = class_exists($type, FALSE) ? $type : 'stdClass'; 
    309365                } 
    310366                else 
    311367                { 
    312                         if ($result == FALSE) 
    313                         { 
    314                                 throw new Kohana_Exception('database.error', pg_last_error($this->link).' - '.$sql); 
    315                         } 
    316                         else if ($result == TRUE) // Its an DELETE, INSERT, REPLACE, or UPDATE query 
    317                         { 
    318                                 //$this->insert_id = mysql_insert_id($link); 
    319                                 $this->num_rows  = pg_affected_rows($link); 
    320                         } 
    321                 } 
    322         } 
    323  
    324         public function result($object = TRUE, $type = PGSQL_ASSOC) 
    325         { 
    326                 $fetch = ($object == TRUE) ? 'pg_fetch_object' : 'pg_fetch_array'; 
    327                 $type  = ($object == TRUE) ? 'stdClass' : $type; 
     368                        $this->return_type = $type; 
     369                } 
     370 
     371                return $this; 
     372        } 
     373 
     374        public function result_array($object = NULL, $type = PGSQL_ASSOC) 
     375        { 
     376                $rows = array(); 
     377 
     378                if (is_string($object)) 
     379                { 
     380                        $fetch = $object; 
     381                } 
     382                elseif (is_bool($object)) 
     383                { 
     384                        if ($object === TRUE) 
     385                        { 
     386                                $fetch = 'pg_fetch_object'; 
     387 
     388                                // NOTE - The class set by $type must be defined before fetching the result, 
     389                                // autoloading is disabled to save a lot of stupid overhead. 
     390                                $type = class_exists($type, FALSE) ? $type : 'stdClass'; 
     391                        } 
     392                        else 
     393                        { 
     394                                $fetch = 'pg_fetch_array'; 
     395                        } 
     396                } 
     397                else 
     398                { 
     399                        // Use the default config values 
     400                        $fetch = $this->fetch_type; 
     401 
     402                        if ($fetch == 'pg_fetch_object') 
     403                        { 
     404                                $type = class_exists($type, FALSE) ? $type : 'stdClass'; 
     405                        } 
     406                } 
    328407 
    329408                while ($row = $fetch($this->result, $type)) 
    330409                { 
    331                         $this->rows[] = $row; 
    332                 } 
    333  
    334                 return $this; 
    335         } 
    336  
    337         public function num_rows() 
    338         { 
    339                 return $this->num_rows; 
    340         } 
    341  
    342         public function get_rows() 
    343         { 
    344                 return $this->rows; 
     410                        $rows[] = $row; 
     411                } 
     412 
     413                return $rows; 
    345414        } 
    346415 
     
    350419        } 
    351420 
     421        public function list_fields() 
     422        { 
     423                throw new Kohana_Database_Exception('database.not_implimented', __FUNCTION__); 
     424        } 
     425        // End Interface 
     426 
     427        private function get_insert_id($link) 
     428        { 
     429                $query = 'SELECT LASTVAL() as insert_id'; 
     430 
     431                $result = pg_query($link, $query); 
     432                $insert_id = pg_fetch_array($result, NULL, PGSQL_ASSOC); 
     433 
     434                return $insert_id['insert_id']; 
     435        } 
     436 
     437        // Interface: Countable 
     438        /** 
     439         * Method: count 
     440         *  Counts the number of rows in the result set. 
     441         *  
     442         * Returns: 
     443         *  The number of rows in the result set 
     444         */ 
     445        public function count() 
     446        { 
     447                return $this->total_rows; 
     448        } 
     449        // End Interface 
     450 
     451        // Interface: ArrayAccess 
     452        /** 
     453         * Method: offsetExists 
     454         *  Determines if the requested offset of the result set exists. 
     455         * 
     456         * Parameters: 
     457         *  offset - offset id 
     458         *  
     459         * Returns: 
     460         *  TRUE if the offset exists, FALSE otherwise 
     461         */ 
     462        public function offsetExists($offset) 
     463        { 
     464                if ($this->total_rows > 0) 
     465                { 
     466                        $min = 0; 
     467                        $max = $this->total_rows - 1; 
     468 
     469                        return ($offset < $min OR $offset > $max) ? FALSE : TRUE; 
     470                } 
     471 
     472                return FALSE; 
     473        } 
     474 
     475        /** 
     476         * Method: offsetGet 
     477         *  Retreives the requested query result offset. 
     478         * 
     479         * Parameters: 
     480         *  offset - offset id 
     481         *  
     482         * Returns: 
     483         *  The query row 
     484         */ 
     485        public function offsetGet($offset) 
     486        { 
     487                // Check to see if the requested offset exists. 
     488                if (!$this->offsetExists($offset)) 
     489                        return FALSE; 
     490 
     491                // Go to the offset and return the row 
     492                $fetch = $this->fetch_type; 
     493                return $fetch($this->result, $offset, $this->return_type); 
     494        } 
     495 
     496        /**