query.php 18.7 KB
Newer Older
1 2 3 4 5 6 7
<?php namespace Laravel\Database;

use Closure;
use Laravel\Database;
use Laravel\Paginator;
use Laravel\Database\Query\Grammars\Grammar;
use Laravel\Database\Query\Grammars\SQLServer;
8 9 10 11

class Query {

	/**
Taylor Otwell committed
12
	 * The database connection.
13
	 *
14
	 * @var Connection
15
	 */
Taylor Otwell committed
16
	public $connection;
17 18

	/**
19
	 * The query grammar instance.
20
	 *
21
	 * @var Query\Grammars\Grammar
22
	 */
23
	public $grammar;
24 25

	/**
26 27
	 * The SELECT clause.
	 *
Taylor Otwell committed
28
	 * @var array
29
	 */
Taylor Otwell committed
30
	public $selects;
31 32

	/**
33
	 * The aggregating column and function.
34
	 *
Taylor Otwell committed
35
	 * @var array
36
	 */
Taylor Otwell committed
37
	public $aggregate;
38 39

	/**
Taylor Otwell committed
40
	 * Indicates if the query should return distinct results.
41
	 *
Taylor Otwell committed
42
	 * @var bool
43
	 */
Taylor Otwell committed
44
	public $distinct = false;
45 46 47 48 49 50

	/**
	 * The table name.
	 *
	 * @var string
	 */
Taylor Otwell committed
51
	public $from;
52 53

	/**
Taylor Otwell committed
54
	 * The table joins.
55
	 *
Taylor Otwell committed
56
	 * @var array
57
	 */
Taylor Otwell committed
58
	public $joins;
59 60

	/**
Taylor Otwell committed
61
	 * The WHERE clauses.
62
	 *
Taylor Otwell committed
63
	 * @var array
64
	 */
Taylor Otwell committed
65
	public $wheres;
66 67

	/**
68 69 70 71 72 73 74
	 * The GROUP BY clauses.
	 *
	 * @var array
	 */
	public $groupings;

	/**
Taylor Otwell committed
75
	 * The ORDER BY clauses.
76 77 78
	 *
	 * @var array
	 */
Taylor Otwell committed
79
	public $orderings;
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104

	/**
	 * The LIMIT value.
	 *
	 * @var int
	 */
	public $limit;

	/**
	 * The OFFSET value.
	 *
	 * @var int
	 */
	public $offset;

	/**
	 * The query value bindings.
	 *
	 * @var array
	 */
	public $bindings = array();

	/**
	 * Create a new query instance.
	 *
105 106 107
	 * @param  Connection  $connection
	 * @param  Grammar     $grammar
	 * @param  string      $table
108 109
	 * @return void
	 */
110
	public function __construct(Connection $connection, Grammar $grammar, $table)
111
	{
Taylor Otwell committed
112
		$this->from = $table;
113
		$this->grammar = $grammar;
114
		$this->connection = $connection;
115 116 117 118 119 120 121 122 123 124 125 126 127 128
	}

	/**
	 * Force the query to return distinct results.
	 *
	 * @return Query
	 */
	public function distinct()
	{
		$this->distinct = true;
		return $this;
	}

	/**
Taylor Otwell committed
129 130
	 * Add an array of columns to the SELECT clause.
	 *
131
	 * @param  array  $columns
132 133
	 * @return Query
	 */
134
	public function select($columns = array('*'))
135
	{
Taylor Otwell committed
136
		$this->selects = (array) $columns;
137 138 139 140
		return $this;
	}

	/**
Taylor Otwell committed
141
	 * Add a join clause to the query.
142
	 *
143 144 145 146 147 148 149 150 151
	 * @param  string  $table
	 * @param  string  $column1
	 * @param  string  $operator
	 * @param  string  $column2
	 * @param  string  $type
	 * @return Query
	 */
	public function join($table, $column1, $operator, $column2, $type = 'INNER')
	{
Taylor Otwell committed
152
		$this->joins[] = compact('type', 'table', 'column1', 'operator', 'column2');
Taylor Otwell committed
153

154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171
		return $this;
	}

	/**
	 * Add a left join to the query.
	 *
	 * @param  string  $table
	 * @param  string  $column1
	 * @param  string  $operator
	 * @param  string  $column2
	 * @return Query
	 */
	public function left_join($table, $column1, $operator, $column2)
	{
		return $this->join($table, $column1, $operator, $column2, 'LEFT');
	}

	/**
172
	 * Reset the where clause to its initial state.
Taylor Otwell committed
173 174 175 176 177
	 *
	 * @return void
	 */
	public function reset_where()
	{
178
		list($this->wheres, $this->bindings) = array(array(), array());
Taylor Otwell committed
179 180 181
	}

	/**
182 183 184 185 186 187 188 189 190
	 * Add a raw where condition to the query.
	 *
	 * @param  string  $where
	 * @param  array   $bindings
	 * @param  string  $connector
	 * @return Query
	 */
	public function raw_where($where, $bindings = array(), $connector = 'AND')
	{
191
		$this->wheres[] = array('type' => 'where_raw', 'connector' => $connector, 'sql' => $where);
Taylor Otwell committed
192

193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218
		$this->bindings = array_merge($this->bindings, $bindings);

		return $this;
	}

	/**
	 * Add a raw or where condition to the query.
	 *
	 * @param  string  $where
	 * @param  array   $bindings
	 * @return Query
	 */
	public function raw_or_where($where, $bindings = array())
	{
		return $this->raw_where($where, $bindings, 'OR');
	}

	/**
	 * Add a where condition to the query.
	 *
	 * @param  string  $column
	 * @param  string  $operator
	 * @param  mixed   $value
	 * @param  string  $connector
	 * @return Query
	 */
219
	public function where($column, $operator = null, $value = null, $connector = 'AND')
220
	{
221 222 223 224 225 226 227 228
		// 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);
		}

229 230 231
		$type = 'where';

		$this->wheres[] = compact('type', 'column', 'operator', 'value', 'connector');
Taylor Otwell committed
232

233 234 235 236 237 238 239 240 241 242 243 244 245
		$this->bindings[] = $value;

		return $this;
	}

	/**
	 * Add an or where condition to the query.
	 *
	 * @param  string  $column
	 * @param  string  $operator
	 * @param  mixed   $value
	 * @return Query
	 */
246
	public function or_where($column, $operator = null, $value = null)
247 248 249 250 251
	{
		return $this->where($column, $operator, $value, 'OR');
	}

	/**
252
	 * Add an or where condition for the primary key to the query.
Taylor Otwell committed
253
	 *
254 255 256 257 258 259 260 261 262
	 * @param  mixed  $value
	 * @return Query
	 */
	public function or_where_id($value)
	{
		return $this->or_where('id', '=', $value);		
	}

	/**
263 264 265 266 267
	 * Add a where in condition to the query.
	 *
	 * @param  string  $column
	 * @param  array   $values
	 * @param  string  $connector
Taylor Otwell committed
268
	 * @param  bool    $not
269 270
	 * @return Query
	 */
Taylor Otwell committed
271
	public function where_in($column, $values, $connector = 'AND', $not = false)
272
	{
273 274 275
		$type = ($not) ? 'where_not_in' : 'where_in';

		$this->wheres[] = compact('type', 'column', 'values', 'connector');
Taylor Otwell committed
276

277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303
		$this->bindings = array_merge($this->bindings, $values);

		return $this;
	}

	/**
	 * Add an or where in condition to the query.
	 *
	 * @param  string  $column
	 * @param  array   $values
	 * @return Query
	 */
	public function or_where_in($column, $values)
	{
		return $this->where_in($column, $values, 'OR');
	}

	/**
	 * Add a where not in condition to the query.
	 *
	 * @param  string  $column
	 * @param  array   $values
	 * @param  string  $connector
	 * @return Query
	 */
	public function where_not_in($column, $values, $connector = 'AND')
	{
Taylor Otwell committed
304
		return $this->where_in($column, $values, $connector, true);
305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323
	}

	/**
	 * Add an or where not in condition to the query.
	 *
	 * @param  string  $column
	 * @param  array   $values
	 * @return Query
	 */
	public function or_where_not_in($column, $values)
	{
		return $this->where_not_in($column, $values, 'OR');
	}

	/**
	 * Add a where null condition to the query.
	 *
	 * @param  string  $column
	 * @param  string  $connector
Taylor Otwell committed
324
	 * @param  bool    $not
325 326
	 * @return Query
	 */
Taylor Otwell committed
327
	public function where_null($column, $connector = 'AND', $not = false)
328
	{
329 330 331
		$type = ($not) ? 'where_not_null' : 'where_null';

		$this->wheres[] = compact('type', 'column', 'connector');
Taylor Otwell committed
332

333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355
		return $this;
	}

	/**
	 * Add an or where null condition to the query.
	 *
	 * @param  string  $column
	 * @return Query
	 */
	public function or_where_null($column)
	{
		return $this->where_null($column, 'OR');
	}

	/**
	 * Add a where not null condition to the query.
	 *
	 * @param  string  $column
	 * @param  string  $connector
	 * @return Query
	 */
	public function where_not_null($column, $connector = 'AND')
	{
Taylor Otwell committed
356
		return $this->where_null($column, $connector, true);
357 358 359 360 361 362 363 364 365 366 367 368 369 370
	}

	/**
	 * Add an or where not null condition to the query.
	 *
	 * @param  string  $column
	 * @return Query
	 */
	public function or_where_not_null($column)
	{
		return $this->where_not_null($column, 'OR');
	}

	/**
371
	 * Add a nested where condition to the query.
Taylor Otwell committed
372
	 *
373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399
	 * @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.
Taylor Otwell committed
400
	 *
Taylor Otwell committed
401 402 403 404 405 406 407 408
	 * @param  string  $method
	 * @param  array   $parameters
	 * @return Query
	 */
	private function dynamic_where($method, $parameters)
	{
		$finder = substr($method, 6);

409 410 411
		$flags = PREG_SPLIT_DELIM_CAPTURE;

		$segments = preg_split('/(_and_|_or_)/i', $finder, -1, $flags);
Taylor Otwell committed
412

413 414 415
		// The connector variable will determine which connector will be
		// used for the condition. We'll change it as we come across new
		// connectors in the dynamic method string.
Taylor Otwell committed
416
		//
417 418 419
		// The index variable helps us get the correct parameter value
		// for the where condition. We increment it each time we add
		// a condition to the query.
Taylor Otwell committed
420 421 422 423 424 425
		$connector = 'AND';

		$index = 0;

		foreach ($segments as $segment)
		{
426 427 428 429 430 431 432
			// 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.
Taylor Otwell committed
433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448
			if ($segment != '_and_' and $segment != '_or_')
			{
				$this->where($segment, '=', $parameters[$index], $connector);

				$index++;
			}
			else
			{
				$connector = trim(strtoupper($segment), '_');
			}
		}

		return $this;
	}

	/**
449 450 451 452 453 454 455 456 457 458 459 460
	 * Add a grouping to the query.
	 *
	 * @param  string  $column
	 * @return Query
	 */
	public function group_by($column)
	{
		$this->groupings[] = $column;
		return $this;
	}

	/**
461 462 463 464 465 466
	 * Add an ordering to the query.
	 *
	 * @param  string  $column
	 * @param  string  $direction
	 * @return Query
	 */
467
	public function order_by($column, $direction = 'asc')
468
	{
Taylor Otwell committed
469
		$this->orderings[] = compact('column', 'direction');
470 471 472 473 474 475 476 477 478 479 480
		return $this;
	}

	/**
	 * Set the query offset.
	 *
	 * @param  int  $value
	 * @return Query
	 */
	public function skip($value)
	{
Taylor Otwell committed
481
		$this->offset = $value;
482 483 484 485 486 487 488 489 490 491 492
		return $this;
	}

	/**
	 * Set the query limit.
	 *
	 * @param  int  $value
	 * @return Query
	 */
	public function take($value)
	{
Taylor Otwell committed
493
		$this->limit = $value;
494 495 496 497
		return $this;
	}

	/**
498
	 * Set the query limit and offset for a given page.
499 500 501 502 503
	 *
	 * @param  int    $page
	 * @param  int    $per_page
	 * @return Query
	 */
Taylor Otwell committed
504
	public function for_page($page, $per_page)
505
	{
Taylor Otwell committed
506
		return $this->skip(($page - 1) * $per_page)->take($per_page);
507 508 509
	}

	/**
Taylor Otwell committed
510
	 * Find a record by the primary key.
511
	 *
Taylor Otwell committed
512
	 * @param  int     $id
513
	 * @param  array   $columns
514 515
	 * @return object
	 */
Taylor Otwell committed
516
	public function find($id, $columns = array('*'))
517
	{
Taylor Otwell committed
518
		return $this->where('id', '=', $id)->first($columns);
519 520 521
	}

	/**
522
	 * Execute the query as a SELECT statement and return a single column.
523 524 525 526
	 *
	 * @param  string  $column
	 * @return mixed
	 */
527
	public function only($column)
528
	{
529
		$sql = $this->grammar->select($this->select(array($column)));
Taylor Otwell committed
530

531
		return $this->connection->only($sql, $this->bindings);
532 533 534
	}

	/**
Taylor Otwell committed
535
	 * Execute the query as a SELECT statement and return the first result.
536
	 *
537 538
	 * @param  array  $columns
	 * @return mixed
539
	 */
Taylor Otwell committed
540
	public function first($columns = array('*'))
541
	{
542 543
		$columns = (array) $columns;

544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588
		// 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;
Taylor Otwell committed
589 590 591 592 593 594 595 596 597 598
	}

	/**
	 * Execute the query as a SELECT statement.
	 *
	 * @param  array  $columns
	 * @return array
	 */
	public function get($columns = array('*'))
	{
Taylor Otwell committed
599
		if (is_null($this->selects)) $this->select($columns);
Taylor Otwell committed
600

601 602 603 604 605 606 607 608 609 610 611 612 613 614 615
		$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);
			});
		}
616

617 618
		// Reset the SELECT clause so more queries can be performed using
		// the same instance. This is helpful for getting aggregates and
619
		// then getting actual results from the query.
Taylor Otwell committed
620
		$this->selects = null;
621 622 623 624 625

		return $results;
	}

	/**
626 627 628 629 630 631 632 633 634 635
	 * Get an aggregate value.
	 *
	 * @param  string  $aggregate
	 * @param  string  $column
	 * @return mixed
	 */
	private function aggregate($aggregator, $column)
	{
		$this->aggregate = compact('aggregator', 'column');

636 637 638
		$sql = $this->grammar->select($this);

		$result = $this->connection->only($sql, $this->bindings);
639

640 641
		// Reset the aggregate so more queries can be performed using
		// the same instance. This is helpful for getting aggregates
642
		// and then getting actual results from the query.
643 644 645 646 647 648
		$this->aggregate = null;

		return $result;
	}

	/**
649 650 651 652 653 654
	 * Get the paginated query results as a Paginator instance.
	 *
	 * @param  int        $per_page
	 * @param  array      $columns
	 * @return Paginator
	 */
655
	public function paginate($per_page = 20, $columns = array('*'))
656
	{
657 658 659 660
		// 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.
661 662
		list($orderings, $this->orderings) = array($this->orderings, null);

663 664
		$page = Paginator::page($total = $this->count(), $per_page);

665 666
		$this->orderings = $orderings;

667 668 669 670 671 672 673
		// 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);
674 675 676
	}

	/**
Taylor Otwell committed
677 678
	 * Insert an array of values into the database table.
	 *
679 680 681 682 683
	 * @param  array  $values
	 * @return bool
	 */
	public function insert($values)
	{
684 685 686
		// Force every insert to be treated like a batch insert to make creating
		// the binding array simpler since we can just spin through the inserted
		// rows as if there/ was more than one every time.
687 688 689 690
		if ( ! is_array(reset($values))) $values = array($values);

		$bindings = array();

691 692 693
		// 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.
694 695 696 697 698
		foreach ($values as $value)
		{
			$bindings = array_merge($bindings, array_values($value));
		}

699 700 701
		$sql = $this->grammar->insert($this, $values);

		return $this->connection->statement($sql, $bindings);
702 703 704
	}

	/**
705
	 * Insert an array of values into the database table and return the ID.
Taylor Otwell committed
706
	 *
707 708
	 * @param  array   $values
	 * @param  string  $sequence
709 710
	 * @return int
	 */
711
	public function insert_get_id($values, $sequence = null)
712
	{
713 714 715
		$sql = $this->grammar->insert($this, $values);

		$this->connection->statement($sql, array_values($values));
716

717 718 719
		// 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.
720
		return (int) $this->connection->pdo->lastInsertId($sequence);
Taylor Otwell committed
721 722 723
	}

	/**
724 725 726 727 728 729 730 731
	 * Increment the value of a column by a given amount.
	 *
	 * @param  string  $column
	 * @param  int     $amount
	 * @return int
	 */
	public function increment($column, $amount = 1)
	{
732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756
		return $this->adjust($column, $amount, ' + ');
	}

	/**
	 * Decrement the value of a column by a given amount.
	 *
	 * @param  string  $column
	 * @param  int     $amount
	 * @return int
	 */
	public function decrement($column, $amount = 1)
	{
		return $this->adjust($column, $amount, ' - ');
	}

	/**
	 * Adjust the value of a column up or down by a given amount.
	 *
	 * @param  string  $column
	 * @param  int     $amount
	 * @param  string  $operator
	 * @return int
	 */
	protected function adjust($column, $amount, $operator)
	{
757 758 759 760
		// 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);
761 762

		return $this->update(array($column => $value));
763 764 765
	}

	/**
Taylor Otwell committed
766 767
	 * Update an array of values in the database table.
	 *
768
	 * @param  array  $values
Taylor Otwell committed
769
	 * @return int
770 771 772
	 */
	public function update($values)
	{
773 774 775 776
		// 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.
777 778
		$bindings =  array_merge(array_values($values), $this->bindings);

779 780 781
		$sql = $this->grammar->update($this, $values);

		return $this->connection->update($sql, $bindings);
782 783 784 785 786
	}

	/**
	 * Execute the query as a DELETE statement.
	 *
Taylor Otwell committed
787 788
	 * Optionally, an ID may be passed to the method do delete a specific row.
	 *
789
	 * @param  int   $id
Taylor Otwell committed
790
	 * @return int
791 792 793
	 */
	public function delete($id = null)
	{
794 795 796 797 798 799 800 801 802
		// 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);
		}

		$sql = $this->grammar->delete($this);
803

804
		return $this->connection->delete($sql, $this->bindings);		
805 806 807
	}

	/**
Taylor Otwell committed
808
	 * Magic Method for handling dynamic functions.
Taylor Otwell committed
809
	 *
810
	 * This method handles calls to aggregates as well as dynamic where clauses.
811 812 813
	 */
	public function __call($method, $parameters)
	{
814 815
		if (strpos($method, 'where_') === 0)
		{
Taylor Otwell committed
816
			return $this->dynamic_where($method, $parameters, $this);
817 818
		}

819
		if (in_array($method, array('count', 'min', 'max', 'avg', 'sum')))
820
		{
821 822 823 824 825 826 827 828
			if ($method == 'count')
			{
				return $this->aggregate(strtoupper($method), '*');
			}
			else
			{
				return $this->aggregate(strtoupper($method), $parameters[0]);
			}
829
		}
830

831
		throw new \Exception("Method [$method] is not defined on the Query class.");
832 833
	}

Phill Sparks committed
834
}