merged skunkworks into develop.

This commit is contained in:
Taylor Otwell
2012-01-16 13:59:24 -06:00
parent 610d8827c4
commit b5442c67fc
117 changed files with 7268 additions and 3999 deletions

View File

@@ -1,4 +1,10 @@
<?php namespace Laravel\Database; use Laravel\Paginator;
<?php namespace Laravel\Database;
use Closure;
use Laravel\Database;
use Laravel\Paginator;
use Laravel\Database\Query\Grammars\Grammar;
use Laravel\Database\Query\Grammars\SQLServer;
class Query {
@@ -12,7 +18,7 @@ class Query {
/**
* The query grammar instance.
*
* @var Grammars\Grammar
* @var Query\Grammars\Grammar
*/
public $grammar;
@@ -58,6 +64,13 @@ class Query {
*/
public $wheres;
/**
* The GROUP BY clauses.
*
* @var array
*/
public $groupings;
/**
* The ORDER BY clauses.
*
@@ -89,12 +102,12 @@ class Query {
/**
* Create a new query instance.
*
* @param Connection $connection
* @param Grammars\Grammar $grammar
* @param string $table
* @param Connection $connection
* @param Grammar $grammar
* @param string $table
* @return void
*/
public function __construct(Connection $connection, Grammars\Grammar $grammar, $table)
public function __construct(Connection $connection, Grammar $grammar, $table)
{
$this->from = $table;
$this->grammar = $grammar;
@@ -156,7 +169,7 @@ class Query {
}
/**
* Reset the where clause to its initial state. All bindings will be cleared.
* Reset the where clause to its initial state.
*
* @return void
*/
@@ -203,8 +216,16 @@ class Query {
* @param string $connector
* @return Query
*/
public function where($column, $operator, $value, $connector = 'AND')
public function where($column, $operator = null, $value = null, $connector = 'AND')
{
// If a CLosure is passed into the method, it means a nested where
// clause is being initiated, so we will take a different course
// of action than when the statement is just a simple where.
if ($column instanceof Closure)
{
return $this->where_nested($column, $connector);
}
$type = 'where';
$this->wheres[] = compact('type', 'column', 'operator', 'value', 'connector');
@@ -222,7 +243,7 @@ class Query {
* @param mixed $value
* @return Query
*/
public function or_where($column, $operator, $value)
public function or_where($column, $operator = null, $value = null)
{
return $this->where($column, $operator, $value, 'OR');
}
@@ -347,10 +368,35 @@ class Query {
}
/**
* Add dynamic where conditions to the query.
* Add a nested where condition to the query.
*
* Dynamic queries are caught by the __call magic method and are parsed here.
* They provide a convenient, expressive API for building simple conditions.
* @param Closure $callback
* @param string $connector
* @return Query
*/
protected function where_nested($callback, $connector)
{
$type = 'where_nested';
// To handle a nested where statement, we will actually instantiate a
// new Query instance and run the callback over that instance, which
// will allow the developer to have a fresh query to work with.
$query = new Query($this->connection, $this->grammar, $this->from);
// Once the callback has been run on the query, we will store the
// nested query instance on the where clause array so that it's
// passed to the query grammar.
call_user_func($callback, $query);
$this->wheres[] = compact('type', 'query', 'connector');
$this->bindings = array_merge($this->bindings, $query->bindings);
return $this;
}
/**
* Add dynamic where conditions to the query.
*
* @param string $method
* @param array $parameters
@@ -358,11 +404,11 @@ class Query {
*/
private function dynamic_where($method, $parameters)
{
// Strip the "where_" off of the method.
$finder = substr($method, 6);
// Split the column names from the connectors.
$segments = preg_split('/(_and_|_or_)/i', $finder, -1, PREG_SPLIT_DELIM_CAPTURE);
$flags = PREG_SPLIT_DELIM_CAPTURE;
$segments = preg_split('/(_and_|_or_)/i', $finder, -1, $flags);
// The connector variable will determine which connector will be
// used for the condition. We'll change it as we come across new
@@ -377,6 +423,13 @@ class Query {
foreach ($segments as $segment)
{
// If the segment is not a boolean connector, we can assume it
// it is a column name, and we'll add it to the query as a new
// where clause.
//
// Otherwise, we'll store the connector so that we know how to
// connection the next where clause we find to the query, as
// all connectors should precede a new where clause.
if ($segment != '_and_' and $segment != '_or_')
{
$this->where($segment, '=', $parameters[$index], $connector);
@@ -392,6 +445,18 @@ class Query {
return $this;
}
/**
* Add a grouping to the query.
*
* @param string $column
* @return Query
*/
public function group_by($column)
{
$this->groupings[] = $column;
return $this;
}
/**
* Add an ordering to the query.
*
@@ -430,7 +495,7 @@ class Query {
}
/**
* Set the query limit and offset for a given page and item per page count.
* Set the query limit and offset for a given page.
*
* @param int $page
* @param int $per_page
@@ -461,16 +526,14 @@ class Query {
*/
public function only($column)
{
$this->select(array($column));
$sql = $this->grammar->select($this->select(array($column)));
return $this->connection->only($this->grammar->select($this), $this->bindings);
return $this->connection->only($sql, $this->bindings);
}
/**
* Execute the query as a SELECT statement and return the first result.
*
* If a single column is selected from the database, only the value of that column will be returned.
*
* @param array $columns
* @return mixed
*/
@@ -478,7 +541,51 @@ class Query {
{
$columns = (array) $columns;
return (count($results = $this->take(1)->get($columns)) > 0) ? $results[0] : null;
// Since we only need the first result, we'll go ahead and set the
// limit clause to 1, since this will be much faster than getting
// all of the rows and then only returning the first.
$results = $this->take(1)->get($columns);
return (count($results) > 0) ? $results[0] : null;
}
/**
* Get an array with the values of a given column.
*
* @param string $column
* @param string $key
* @return array
*/
public function lists($column, $key = null)
{
$columns = (is_null($key)) ? array($column) : array($column, $key);
$results = $this->get($columns);
// First we will get the array of values for the requested column.
// Of course, this array will simply have numeric keys. After we
// have this array we will determine if we need to key the array
// by another column from the result set.
$values = array_map(function($row) use ($column)
{
return $row->$column;
}, $results);
// If a key was provided, we will extract an array of keys and
// set the keys on the array of values using the array_combine
// function provided by PHP, which should give us the proper
// array form to return from the method.
if ( ! is_null($key))
{
return array_combine(array_map(function($row) use ($key)
{
return $row->$key;
}, $results), $values);
}
return $values;
}
/**
@@ -491,11 +598,25 @@ class Query {
{
if (is_null($this->selects)) $this->select($columns);
$results = $this->connection->query($this->grammar->select($this), $this->bindings);
$sql = $this->grammar->select($this);
$results = $this->connection->query($sql, $this->bindings);
// If the query has an offset and we are using the SQL Server grammar,
// we need to spin through the results and remove the "rownum" from
// each of the objects. Unfortunately SQL Server does not have an
// offset keyword, so we have to use row numbers in the query.
if ($this->offset > 0 and $this->grammar instanceof SQLServer)
{
array_walk($results, function($result)
{
unset($result->rownum);
});
}
// Reset the SELECT clause so more queries can be performed using
// the same instance. This is helpful for getting aggregates and
// then getting actual results.
// then getting actual results from the query.
$this->selects = null;
return $results;
@@ -512,11 +633,13 @@ class Query {
{
$this->aggregate = compact('aggregator', 'column');
$result = $this->connection->only($this->grammar->select($this), $this->bindings);
$sql = $this->grammar->select($this);
$result = $this->connection->only($sql, $this->bindings);
// Reset the aggregate so more queries can be performed using
// the same instance. This is helpful for getting aggregates
// and then getting actual results.
// and then getting actual results from the query.
$this->aggregate = null;
return $result;
@@ -531,17 +654,23 @@ class Query {
*/
public function paginate($per_page = 20, $columns = array('*'))
{
// Because some database engines may throw errors if we leave
// orderings on the query when retrieving the total number
// of records, we will remove all of the ordreings and put
// them back on the query after we have the count.
// Because some database engines may throw errors if we leave orderings
// on the query when retrieving the total number of records, we will
// remove all of the ordreings and put them back on the query after
// we have the count.
list($orderings, $this->orderings) = array($this->orderings, null);
$page = Paginator::page($total = $this->count(), $per_page);
$this->orderings = $orderings;
return Paginator::make($this->for_page($page, $per_page)->get($columns), $total, $per_page);
// Now we're ready to get the actual pagination results from the
// database table. The "for_page" method provides a convenient
// way to set the limit and offset so we get the correct span
// of results from the table.
$results = $this->for_page($page, $per_page)->get($columns);
return Paginator::make($results, $total, $per_page);
}
/**
@@ -559,17 +688,21 @@ class Query {
$bindings = array();
// We need to merge the the insert values into the array of the query
// bindings so that they will be bound to the PDO statement when it
// is executed by the database connection.
foreach ($values as $value)
{
$bindings = array_merge($bindings, array_values($value));
}
return $this->connection->query($this->grammar->insert($this, $values), $bindings);
$sql = $this->grammar->insert($this, $values);
return $this->connection->statement($sql, $bindings);
}
/**
* Insert an array of values into the database table and
* return the value of the ID column.
* Insert an array of values into the database table and return the ID.
*
* @param array $values
* @param string $sequence
@@ -577,8 +710,13 @@ class Query {
*/
public function insert_get_id($values, $sequence = null)
{
$this->connection->query($this->grammar->insert($this, $values), array_values($values));
$sql = $this->grammar->insert($this, $values);
$this->connection->statement($sql, array_values($values));
// Some database systems (Postgres) require a sequence name to be
// given when retrieving the auto-incrementing ID, so we'll pass
// the given sequence into the method just in case.
return (int) $this->connection->pdo->lastInsertId($sequence);
}
@@ -616,7 +754,10 @@ class Query {
*/
protected function adjust($column, $amount, $operator)
{
$value = Manager::raw($this->grammar->wrap($column).$operator.$amount);
// To make the adjustment to the column, we'll wrap the expression
// in an Expression instance, which forces the adjustment to be
// injected into the query as a string instead of bound.
$value = Database::raw($this->grammar->wrap($column).$operator.$amount);
return $this->update(array($column => $value));
}
@@ -629,9 +770,15 @@ class Query {
*/
public function update($values)
{
// For update statements, we need to merge the bindings such that
// the update values occur before the where bindings in the array
// since the set statements will precede any of the where clauses
// in the SQL syntax that is generated.
$bindings = array_merge(array_values($values), $this->bindings);
return $this->connection->query($this->grammar->update($this, $values), $bindings);
$sql = $this->grammar->update($this, $values);
return $this->connection->update($sql, $bindings);
}
/**
@@ -644,16 +791,23 @@ class Query {
*/
public function delete($id = null)
{
if ( ! is_null($id)) $this->where('id', '=', $id);
// If an ID is given to the method, we'll set the where clause
// to match on the value of the ID. This allows the developer
// to quickly delete a row by its primary key value.
if ( ! is_null($id))
{
$this->where('id', '=', $id);
}
return $this->connection->query($this->grammar->delete($this), $this->bindings);
$sql = $this->grammar->delete($this);
return $this->connection->delete($sql, $this->bindings);
}
/**
* Magic Method for handling dynamic functions.
*
* This method handles all calls to aggregate functions as well
* as the construction of dynamic where clauses.
* This method handles calls to aggregates as well as dynamic where clauses.
*/
public function __call($method, $parameters)
{
@@ -662,7 +816,7 @@ class Query {
return $this->dynamic_where($method, $parameters, $this);
}
if (in_array($method, array('abs', 'count', 'min', 'max', 'avg', 'sum')))
if (in_array($method, array('count', 'min', 'max', 'avg', 'sum')))
{
if ($method == 'count')
{
@@ -674,7 +828,7 @@ class Query {
}
}
throw new \BadMethodCallException("Method [$method] is not defined on the Query class.");
throw new \Exception("Method [$method] is not defined on the Query class.");
}
}