root/trunk/system/libraries/Database.php

Revision 3700, 30.3 kB (checked in by Shadowhand, 12 days ago)

Updates to trunk:

  • Removed all SYSPATH file checks
  • Deleted some modules: code_coverage (3.0), shoutbox (defunct), user_guide (defunct), kobot (3.0), object_db (3.0)
  • Updated ORM and ORM_Iterator to match 3.0, enabling the new HABTM stuff, see r3636 and r3640
  • Property svn:eol-style set to LF
  • Property copyright set to Copyright (c) 2007 Kohana Team
  • Property svn:keywords set to Id
Line 
1<?php
2/**
3 * Provides database access in a platform agnostic way, using simple query building blocks.
4 *
5 * $Id$
6 *
7 * @package    Core
8 * @author     Kohana Team
9 * @copyright  (c) 2007-2008 Kohana Team
10 * @license    http://kohanaphp.com/license.html
11 */
12class Database_Core {
13
14    // Database instances
15    public static $instances = array();
16
17    // Global benchmark
18    public static $benchmarks = array();
19
20    // Configuration
21    protected $config = array
22    (
23        'benchmark'     => TRUE,
24        'persistent'    => FALSE,
25        'connection'    => '',
26        'character_set' => 'utf8',
27        'table_prefix'  => '',
28        'object'        => TRUE,
29        'cache'         => FALSE,
30        'escape'        => TRUE,
31    );
32
33    // Database driver object
34    protected $driver;
35    protected $link;
36
37    // Un-compiled parts of the SQL query
38    protected $select     = array();
39    protected $set        = array();
40    protected $from       = array();
41    protected $join       = array();
42    protected $where      = array();
43    protected $orderby    = array();
44    protected $order      = array();
45    protected $groupby    = array();
46    protected $having     = array();
47    protected $distinct   = FALSE;
48    protected $limit      = FALSE;
49    protected $offset     = FALSE;
50    protected $last_query = '';
51
52    /**
53     * Returns a singleton instance of Database.
54     *
55     * @param   mixed   configuration array or DSN
56     * @return  Database_Core
57     */
58    public static function & instance($name = 'default', $config = NULL)
59    {
60        if ( ! isset(Database::$instances[$name]))
61        {
62            // Create a new instance
63            Database::$instances[$name] = new Database($config === NULL ? $name : $config);
64        }
65
66        return Database::$instances[$name];
67    }
68
69    /**
70     * Returns the name of a given database instance.
71     *
72     * @param   Database  instance of Database
73     * @return  string
74     */
75    public static function instance_name(Database $db)
76    {
77        return array_search($db, Database::$instances, TRUE);
78    }
79
80    /**
81     * Sets up the database configuration, loads the Database_Driver.
82     *
83     * @throws  Kohana_Database_Exception
84     */
85    public function __construct($config = array())
86    {
87        if (empty($config))
88        {
89            // Load the default group
90            $config = Kohana::config('database.default');
91        }
92        elseif (is_array($config) AND count($config) > 0)
93        {
94            if ( ! array_key_exists('connection', $config))
95            {
96                $config = array('connection' => $config);
97            }
98        }
99        elseif (is_string($config))
100        {
101            // The config is a DSN string
102            if (strpos($config, '://') !== FALSE)
103            {
104                $config = array('connection' => $config);
105            }
106            // The config is a group name
107            else
108            {
109                $name = $config;
110
111                // Test the config group name
112                if (($config = Kohana::config('database.'.$config)) === NULL)
113                    throw new Kohana_Database_Exception('database.undefined_group', $name);
114            }
115        }
116
117        // Merge the default config with the passed config
118        $this->config = array_merge($this->config, $config);
119
120        if (is_string($this->config['connection']))
121        {
122            // Make sure the connection is valid
123            if (strpos($this->config['connection'], '://') === FALSE)
124                throw new Kohana_Database_Exception('database.invalid_dsn', $this->config['connection']);
125
126            // Parse the DSN, creating an array to hold the connection parameters
127            $db = array
128            (
129                'type'     => FALSE,
130                'user'     => FALSE,
131                'pass'     => FALSE,
132                'host'     => FALSE,
133                'port'     => FALSE,
134                'socket'   => FALSE,
135                'database' => FALSE
136            );
137
138            // Get the protocol and arguments
139            list ($db['type'], $connection) = explode('://', $this->config['connection'], 2);
140
141            if (strpos($connection, '@') !== FALSE)
142            {
143                // Get the username and password
144                list ($db['pass'], $connection) = explode('@', $connection, 2);
145                // Check if a password is supplied
146                $logindata = explode(':', $db['pass'], 2);
147                $db['pass'] = (count($logindata) > 1) ? $logindata[1] : '';
148                $db['user'] = $logindata[0];
149
150                // Prepare for finding the database
151                $connection = explode('/', $connection);
152
153                // Find the database name
154                $db['database'] = array_pop($connection);
155
156                // Reset connection string
157                $connection = implode('/', $connection);
158
159                // Find the socket
160                if (preg_match('/^unix\([^)]++\)/', $connection))
161                {
162                    // This one is a little hairy: we explode based on the end of
163                    // the socket, removing the 'unix(' from the connection string
164                    list ($db['socket'], $connection) = explode(')', substr($connection, 5), 2);
165                }
166                elseif (strpos($connection, ':') !== FALSE)
167                {
168                    // Fetch the host and port name
169                    list ($db['host'], $db['port']) = explode(':', $connection, 2);
170                }
171                else
172                {
173                    $db['host'] = $connection;
174                }
175            }
176            else
177            {
178                // File connection
179                $connection = explode('/', $connection);
180
181                // Find database file name
182                $db['database'] = array_pop($connection);
183
184                // Find database directory name
185                $db['socket'] = implode('/', $connection).'/';
186            }
187
188            // Reset the connection array to the database config
189            $this->config['connection'] = $db;
190        }
191        // Set driver name
192        $driver = 'Database_'.ucfirst($this->config['connection']['type']).'_Driver';
193
194        // Load the driver
195        if ( ! Kohana::auto_load($driver))
196            throw new Kohana_Database_Exception('core.driver_not_found', $this->config['connection']['type'], get_class($this));
197
198        // Initialize the driver
199        $this->driver = new $driver($this->config);
200
201        // Validate the driver
202        if ( ! ($this->driver instanceof Database_Driver))
203            throw new Kohana_Database_Exception('core.driver_implements', $this->config['connection']['type'], get_class($this), 'Database_Driver');
204
205        Kohana::log('debug', 'Database Library initialized');
206    }
207
208    /**
209     * Simple connect method to get the database queries up and running.
210     *
211     * @return  void
212     */
213    public function connect()
214    {
215        // A link can be a resource or an object
216        if ( ! is_resource($this->link) AND ! is_object($this->link))
217        {
218            $this->link = $this->driver->connect();
219            if ( ! is_resource($this->link) AND ! is_object($this->link))
220                throw new Kohana_Database_Exception('database.connection', $this->driver->show_error());
221
222            // Clear password after successful connect
223            $this->config['connection']['pass'] = NULL;
224        }
225    }
226
227    /**
228     * Runs a query into the driver and returns the result.
229     *
230     * @param   string  SQL query to execute
231     * @return  Database_Result
232     */
233    public function query($sql = '')
234    {
235        if ($sql == '') return FALSE;
236
237        // No link? Connect!
238        $this->link or $this->connect();
239
240        // Start the benchmark
241        $start = microtime(TRUE);
242
243        if (func_num_args() > 1) //if we have more than one argument ($sql)
244        {
245            $argv = func_get_args();
246            $binds = (is_array(next($argv))) ? current($argv) : array_slice($argv, 1);
247        }
248
249        // Compile binds if needed
250        if (isset($binds))
251        {
252            $sql = $this->compile_binds($sql, $binds);
253        }
254
255        // Fetch the result
256        $result = $this->driver->query($this->last_query = $sql);
257
258        // Stop the benchmark
259        $stop = microtime(TRUE);
260
261        if ($this->config['benchmark'] == TRUE)
262        {
263            // Benchmark the query
264            self::$benchmarks[] = array('query' => $sql, 'time' => $stop - $start, 'rows' => count($result));
265        }
266
267        return $result;
268    }
269
270    /**
271     * Selects the column names for a database query.
272     *
273     * @param   string  string or array of column names to select
274     * @return  Database_Core  This Database object.
275     */
276    public function select($sql = '*')
277    {
278        if (func_num_args() > 1)
279        {
280            $sql = func_get_args();
281        }
282        elseif (is_string($sql))
283        {
284            $sql = explode(',', $sql);
285        }
286        else
287        {
288            $sql = (array) $sql;
289        }
290
291        foreach ($sql as $val)
292        {
293            if (($val = trim($val)) === '') continue;
294
295            if (strpos($val, '(') === FALSE AND $val !== '*')
296            {
297                if (preg_match('/^DISTINCT\s++(.+)$/i', $val, $matches))
298                {
299                    $val            = $this->config['table_prefix'].$matches[1];
300                    $this->distinct = TRUE;
301                }
302                else
303                {
304                    $val = (strpos($val, '.') !== FALSE) ? $this->config['table_prefix'].$val : $val;
305                }
306
307                $val = $this->driver->escape_column($val);
308            }
309
310            $this->select[] = $val;
311        }
312
313        return $this;
314    }
315
316    /**
317     * Selects the from table(s) for a database query.
318     *
319     * @param   string  string or array of tables to select
320     * @return  Database_Core  This Database object.
321     */
322    public function from($sql)
323    {
324        if (func_num_args() > 1)
325        {
326            $sql = func_get_args();
327        }
328        elseif (is_string($sql))
329        {
330            $sql = explode(',', $sql);
331        }
332        else
333        {
334            $sql = (array) $sql;
335        }
336
337        foreach ($sql as $val)
338        {
339            if (($val = trim($val)) === '') continue;
340
341            $this->from[] = $this->config['table_prefix'].$val;
342        }
343
344        return $this;
345    }
346
347    /**
348     * Generates the JOIN portion of the query.
349     *
350     * @param   string        table name
351     * @param   string|array  where key or array of key => value pairs
352     * @param   string        where value
353     * @param   string        type of join
354     * @return  Database_Core        This Database object.
355     */
356    public function join($table, $key, $value = NULL, $type = '')
357    {
358        $join = array();
359
360        if ( ! empty($type))
361        {
362            $type = strtoupper(trim($type));
363
364            if ( ! in_array($type, array('LEFT', 'RIGHT', 'OUTER', 'INNER', 'LEFT OUTER', 'RIGHT OUTER'), TRUE))
365            {
366                $type = '';
367            }
368            else
369            {
370                $type .= ' ';
371            }
372        }
373
374        $cond = array();
375        $keys  = is_array($key) ? $key : array($key => $value);
376        foreach ($keys as $key => $value)
377        {
378            $key    = (strpos($key, '.') !== FALSE) ? $this->config['table_prefix'].$key : $key;
379            $cond[] = $this->driver->where($key, $this->driver->escape_column($this->config['table_prefix'].$value), 'AND ', count($cond), FALSE);
380        }
381
382        if( ! is_array($this->join)) { $this->join = array(); }
383
384        foreach ((array) $table as $t)
385        {
386            $join['tables'][] = $this->driver->escape_column($this->config['table_prefix'].$t);
387        }
388
389        $join['conditions'] = '('.trim(implode(' ', $cond)).')';
390        $join['type'] = $type;
391
392        $this->join[] = $join;
393
394        return $this;
395    }
396
397
398    /**
399     * Selects the where(s) for a database query.
400     *
401     * @param   string|array  key name or array of key => value pairs
402     * @param   string        value to match with key
403     * @param   boolean       disable quoting of WHERE clause
404     * @return  Database_Core        This Database object.
405     */
406    public function where($key, $value = NULL, $quote = TRUE)
407    {
408        $quote = (func_num_args() < 2 AND ! is_array($key)) ? -1 : $quote;
409        $keys  = is_array($key) ? $key : array($key => $value);
410
411        foreach ($keys as $key => $value)
412        {
413            $key           = (strpos($key, '.') !== FALSE) ? $this->config['table_prefix'].$key : $key;
414            $this->where[] = $this->driver->where($key, $value, 'AND ', count($this->where), $quote);
415        }
416
417        return $this;
418    }
419
420    /**
421     * Selects the or where(s) for a database query.
422     *
423     * @param   string|array  key name or array of key => value pairs
424     * @param   string        value to match with key
425     * @param   boolean       disable quoting of WHERE clause
426     * @return  Database_Core        This Database object.
427     */
428    public function orwhere($key, $value = NULL, $quote = TRUE)
429    {
430        $quote = (func_num_args() < 2 AND ! is_array($key)) ? -1 : $quote;
431        $keys  = is_array($key) ? $key : array($key => $value);
432
433        foreach ($keys as $key => $value)
434        {
435            $key           = (strpos($key, '.') !== FALSE) ? $this->config['table_prefix'].$key : $key;
436            $this->where[] = $this->driver->where($key, $value, 'OR ', count($this->where), $quote);
437        }
438
439        return $this;
440    }
441
442    /**
443     * Selects the like(s) for a database query.
444     *
445     * @param   string|array  field name or array of field => match pairs
446     * @param   string        like value to match with field
447     * @param   boolean       automatically add starting and ending wildcards
448     * @return  Database_Core        This Database object.
449     */
450    public function like($field, $match = '', $auto = TRUE)
451    {
452        $fields = is_array($field) ? $field : array($field => $match);
453
454        foreach ($fields as $field => $match)
455        {
456            $field         = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field;
457            $this->where[] = $this->driver->like($field, $match, $auto, 'AND ', count($this->where));
458        }
459
460        return $this;
461    }
462
463    /**
464     * Selects the or like(s) for a database query.
465     *
466     * @param   string|array  field name or array of field => match pairs
467     * @param   string        like value to match with field
468     * @param   boolean       automatically add starting and ending wildcards
469     * @return  Database_Core        This Database object.
470     */
471    public function orlike($field, $match = '', $auto = TRUE)
472    {
473        $fields = is_array($field) ? $field : array($field => $match);
474
475        foreach ($fields as $field => $match)
476        {
477            $field         = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field;
478            $this->where[] = $this->driver->like($field, $match, $auto, 'OR ', count($this->where));
479        }
480
481        return $this;
482    }
483
484    /**
485     * Selects the not like(s) for a database query.
486     *
487     * @param   string|array  field name or array of field => match pairs
488     * @param   string        like value to match with field
489     * @param   boolean       automatically add starting and ending wildcards
490     * @return  Database_Core        This Database object.
491     */
492    public function notlike($field, $match = '', $auto = TRUE)
493    {
494        $fields = is_array($field) ? $field : array($field => $match);
495
496        foreach ($fields as $field => $match)
497        {
498            $field         = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field;
499            $this->where[] = $this->driver->notlike($field, $match, $auto, 'AND ', count($this->where));
500        }
501
502        return $this;
503    }
504
505    /**
506     * Selects the or not like(s) for a database query.
507     *
508     * @param   string|array  field name or array of field => match pairs
509     * @param   string        like value to match with field
510     * @return  Database_Core        This Database object.
511     */
512    public function ornotlike($field, $match = '', $auto = TRUE)
513    {
514        $fields = is_array($field) ? $field : array($field => $match);
515
516        foreach ($fields as $field => $match)
517        {
518            $field         = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field;
519            $this->where[] = $this->driver->notlike($field, $match, $auto, 'OR ', count($this->where));
520        }
521
522        return $this;
523    }
524
525    /**
526     * Selects the like(s) for a database query.
527     *
528     * @param   string|array  field name or array of field => match pairs
529     * @param   string        like value to match with field
530     * @return  Database_Core        This Database object.
531     */
532    public function regex($field, $match = '')
533    {