grammar.php 10.4 KB
Newer Older
1
<?php namespace Laravel\Database\Query\Grammars;
2 3 4

use Laravel\Database\Query;
use Laravel\Database\Expression;
Taylor Otwell committed
5

6
class Grammar extends \Laravel\Database\Grammar {
Taylor Otwell committed
7 8

	/**
9
	 * All of the query componenets in the order they should be built.
Taylor Otwell committed
10
	 *
11
	 * @var array
Taylor Otwell committed
12
	 */
13
	protected $components = array(
14 15
		'aggregate', 'selects', 'from', 'joins', 'wheres',
		'groupings', 'orderings', 'limit', 'offset',
16
	);
Taylor Otwell committed
17 18 19 20

	/**
	 * Compile a SQL SELECT statement from a Query instance.
	 *
Taylor Otwell committed
21 22 23
	 * @param  Query   $query
	 * @return string
	 */
24
	public function select(Query $query)
Taylor Otwell committed
25
	{
Taylor Otwell committed
26
		return $this->concatenate($this->components($query));
27
	}
Taylor Otwell committed
28

29 30 31 32 33 34 35 36 37 38
	/**
	 * Generate the SQL for every component of the query.
	 *
	 * @param  Query  $query
	 * @return array
	 */
	final protected function components($query)
	{
		// Each portion of the statement is compiled by a function corresponding
		// to an item in the components array. This lets us to keep the creation
39
		// of the query very granular and very flexible.
Taylor Otwell committed
40
		foreach ($this->components as $component)
Taylor Otwell committed
41
		{
42 43
			if ( ! is_null($query->$component))
			{
44
				$sql[$component] = call_user_func(array($this, $component), $query);
45
			}
Taylor Otwell committed
46 47
		}

48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
		return (array) $sql;
	}

	/**
	 * Concatenate an array of SQL segments, removing those that are empty.
	 *
	 * @param  array   $components
	 * @return string
	 */
	final protected function concatenate($components)
	{
		return implode(' ', array_filter($components, function($value)
		{
			return (string) $value !== '';
		}));
Taylor Otwell committed
63 64 65
	}

	/**
Taylor Otwell committed
66
	 * Compile the SELECT clause for a query.
Taylor Otwell committed
67 68 69 70
	 *
	 * @param  Query   $query
	 * @return string
	 */
Taylor Otwell committed
71
	protected function selects(Query $query)
Taylor Otwell committed
72
	{
73 74
		if ( ! is_null($query->aggregate)) return;

75 76 77
		$select = ($query->distinct) ? 'SELECT DISTINCT ' : 'SELECT ';

		return $select.$this->columnize($query->selects);
Taylor Otwell committed
78 79 80
	}

	/**
Taylor Otwell committed
81
	 * Compile an aggregating SELECT clause for a query.
Taylor Otwell committed
82
	 *
Taylor Otwell committed
83
	 * @param  Query   $query
Taylor Otwell committed
84 85
	 * @return string
	 */
Taylor Otwell committed
86
	protected function aggregate(Query $query)
Taylor Otwell committed
87
	{
88 89
		$column = $this->columnize($query->aggregate['columns']);

90 91 92 93 94 95 96
		// If the "distinct" flag is set and we're not aggregating everything
		// we'll set the distinct clause on the query, since this is used
		// to count all of the distinct values in a column, etc.
		if ($query->distinct and $column !== '*')
		{
			$column = 'DISTINCT '.$column;
		}
Taylor Otwell committed
97

98
		return 'SELECT '.$query->aggregate['aggregator'].'('.$column.') AS '.$this->wrap('aggregate');
Taylor Otwell committed
99 100 101
	}

	/**
Taylor Otwell committed
102
	 * Compile the FROM clause for a query.
Taylor Otwell committed
103
	 *
Taylor Otwell committed
104
	 * @param  Query   $query
Taylor Otwell committed
105 106
	 * @return string
	 */
Taylor Otwell committed
107
	protected function from(Query $query)
Taylor Otwell committed
108
	{
109
		return 'FROM '.$this->wrap_table($query->from);
Taylor Otwell committed
110 111 112
	}

	/**
Taylor Otwell committed
113
	 * Compile the JOIN clauses for a query.
Taylor Otwell committed
114
	 *
Taylor Otwell committed
115
	 * @param  Query   $query
Taylor Otwell committed
116 117
	 * @return string
	 */
Taylor Otwell committed
118
	protected function joins(Query $query)
Taylor Otwell committed
119
	{
120 121 122
		// We need to iterate through each JOIN clause that is attached to the
		// query an translate it into SQL. The table and the columns will be
		// wrapped in identifiers to avoid naming collisions.
Taylor Otwell committed
123
		foreach ($query->joins as $join)
Taylor Otwell committed
124
		{
125
			$table = $this->wrap_table($join->table);
Taylor Otwell committed
126

127
			$clauses = array();
128

129 130 131
			// Each JOIN statement may have multiple clauses, so we will iterate
			// through each clause creating the conditions then we'll join all
			// of the together at the end to build the clause.
132 133 134 135 136 137 138 139 140 141 142
			foreach ($join->clauses as $clause)
			{
				extract($clause);

				$column1 = $this->wrap($column1);

				$column2 = $this->wrap($column2);

				$clauses[] = "{$connector} {$column1} {$operator} {$column2}";
			}

143 144 145
			// The first clause will have a connector on the front, but it is
			// not needed on the first condition, so we will strip it off of
			// the condition before adding it to the arrya of joins.
146 147 148 149 150
			$search = array('AND ', 'OR ');

			$clauses[0] = str_replace($search, '', $clauses[0]);

			$clauses = implode(' ', $clauses);
151

152
			$sql[] = "{$join->type} JOIN {$table} ON {$clauses}";
Taylor Otwell committed
153 154
		}

155 156 157
		// Finally, we should have an array of JOIN clauses that we can
		// implode together and return as the complete SQL for the
		// join clause of the query under construction.
Taylor Otwell committed
158 159 160 161
		return implode(' ', $sql);
	}

	/**
Taylor Otwell committed
162
	 * Compile the WHERE clause for a query.
Taylor Otwell committed
163
	 *
Taylor Otwell committed
164
	 * @param  Query   $query
Taylor Otwell committed
165 166
	 * @return string
	 */
167
	final protected function wheres(Query $query)
Taylor Otwell committed
168
	{
169 170
		if (is_null($query->wheres)) return '';

171
		// Each WHERE clause array has a "type" that is assigned by the query
172
		// builder, and each type has its own compiler function. We will call
173
		// the appropriate compiler for each where clause.
174
		foreach ($query->wheres as $where)
Taylor Otwell committed
175
		{
176
			$sql[] = $where['connector'].' '.$this->{$where['type']}($where);
Taylor Otwell committed
177 178
		}

179 180 181 182
		if  (isset($sql))
		{
			// We attach the boolean connector to every where segment just
			// for convenience. Once we have built the entire clause we'll
183
			// remove the first instance of a connector.
184 185
			return 'WHERE '.preg_replace('/AND |OR /', '', implode(' ', $sql), 1);
		}
Taylor Otwell committed
186 187 188
	}

	/**
189
	 * Compile a nested WHERE clause.
Taylor Otwell committed
190
	 *
191 192 193 194 195 196 197 198 199 200
	 * @param  array   $where
	 * @return string
	 */
	protected function where_nested($where)
	{
		return '('.substr($this->wheres($where['query']), 6).')';
	}

	/**
	 * Compile a simple WHERE clause.
201
	 *
Taylor Otwell committed
202 203 204
	 * @param  array   $where
	 * @return string
	 */
Taylor Otwell committed
205
	protected function where($where)
Taylor Otwell committed
206
	{
207 208 209
		$parameter = $this->parameter($where['value']);

		return $this->wrap($where['column']).' '.$where['operator'].' '.$parameter;
Taylor Otwell committed
210 211 212 213 214 215 216 217
	}

	/**
	 * Compile a WHERE IN clause.
	 *
	 * @param  array   $where
	 * @return string
	 */
Taylor Otwell committed
218
	protected function where_in($where)
Taylor Otwell committed
219
	{
220 221 222
		$parameters = $this->parameterize($where['values']);

		return $this->wrap($where['column']).' IN ('.$parameters.')';
223
	}
Taylor Otwell committed
224

225 226 227 228 229 230 231 232
	/**
	 * Compile a WHERE NOT IN clause.
	 *
	 * @param  array   $where
	 * @return string
	 */
	protected function where_not_in($where)
	{
233 234 235
		$parameters = $this->parameterize($where['values']);

		return $this->wrap($where['column']).' NOT IN ('.$parameters.')';
Taylor Otwell committed
236 237 238 239 240 241 242 243
	}

	/**
	 * Compile a WHERE NULL clause.
	 *
	 * @param  array   $where
	 * @return string
	 */
Taylor Otwell committed
244
	protected function where_null($where)
Taylor Otwell committed
245
	{
246 247
		return $this->wrap($where['column']).' IS NULL';
	}
Taylor Otwell committed
248

249 250 251 252 253 254 255 256 257 258 259 260 261 262
	/**
	 * Compile a WHERE NULL clause.
	 *
	 * @param  array   $where
	 * @return string
	 */
	protected function where_not_null($where)
	{
		return $this->wrap($where['column']).' IS NOT NULL';
	}

	/**
	 * Compile a raw WHERE clause.
	 *
263
	 * @param  array   $where
264 265
	 * @return string
	 */
266
	final protected function where_raw($where)
267
	{
268
		return $where['sql'];
Taylor Otwell committed
269 270 271
	}

	/**
272 273 274 275 276 277 278 279 280 281 282
	 * Compile the GROUP BY clause for a query.
	 *
	 * @param  Query   $query
	 * @return string
	 */
	protected function groupings(Query $query)
	{
		return 'GROUP BY '.$this->columnize($query->groupings);
	}

	/**
283
	 * Compile the ORDER BY clause for a query.
Taylor Otwell committed
284
	 *
Taylor Otwell committed
285
	 * @param  Query   $query
Taylor Otwell committed
286 287
	 * @return string
	 */
Taylor Otwell committed
288
	protected function orderings(Query $query)
Taylor Otwell committed
289
	{
Taylor Otwell committed
290
		foreach ($query->orderings as $ordering)
Taylor Otwell committed
291
		{
292
			$sql[] = $this->wrap($ordering['column']).' '.strtoupper($ordering['direction']);
Taylor Otwell committed
293 294 295 296 297 298
		}

		return 'ORDER BY '.implode(', ', $sql);
	}

	/**
Taylor Otwell committed
299
	 * Compile the LIMIT clause for a query.
Taylor Otwell committed
300
	 *
Taylor Otwell committed
301
	 * @param  Query   $query
Taylor Otwell committed
302 303
	 * @return string
	 */
Taylor Otwell committed
304
	protected function limit(Query $query)
Taylor Otwell committed
305
	{
Taylor Otwell committed
306
		return 'LIMIT '.$query->limit;
Taylor Otwell committed
307 308 309
	}

	/**
Taylor Otwell committed
310
	 * Compile the OFFSET clause for a query.
Taylor Otwell committed
311
	 *
Taylor Otwell committed
312
	 * @param  Query   $query
Taylor Otwell committed
313 314
	 * @return string
	 */
Taylor Otwell committed
315
	protected function offset(Query $query)
Taylor Otwell committed
316
	{
Taylor Otwell committed
317
		return 'OFFSET '.$query->offset;
Taylor Otwell committed
318 319 320 321 322
	}

	/**
	 * Compile a SQL INSERT statment from a Query instance.
	 *
323
	 * This method handles the compilation of single row inserts and batch inserts.
324
	 *
Taylor Otwell committed
325 326 327 328 329 330
	 * @param  Query   $query
	 * @param  array   $values
	 * @return string
	 */
	public function insert(Query $query, $values)
	{
331
		$table = $this->wrap_table($query->from);
332 333

		// Force every insert to be treated like a batch insert. This simply makes
334
		// creating the SQL syntax a little easier on us since we can always treat
335
		// the values as if it contains multiple inserts.
336 337
		if ( ! is_array(reset($values))) $values = array($values);

338
		// Since we only care about the column names, we can pass any of the insert
339
		// arrays into the "columnize" method. The columns should be the same for
340
		// every record inserted into the table.
341 342
		$columns = $this->columnize(array_keys(reset($values)));

343 344 345
		// Build the list of parameter place-holders of values bound to the query.
		// Each insert should have the same number of bound paramters, so we can
		// just use the first array of values.
346 347
		$parameters = $this->parameterize(reset($values));

348
		$parameters = implode(', ', array_fill(0, count($values), "($parameters)"));
Taylor Otwell committed
349

350
		return "INSERT INTO {$table} ({$columns}) VALUES {$parameters}";
Taylor Otwell committed
351 352 353
	}

	/**
Taylor Otwell committed
354
	 * Compile a SQL UPDATE statment from a Query instance.
Taylor Otwell committed
355 356 357 358 359
	 *
	 * @param  Query   $query
	 * @param  array   $values
	 * @return string
	 */
Taylor Otwell committed
360
	public function update(Query $query, $values)
Taylor Otwell committed
361
	{
362
		$table = $this->wrap_table($query->from);
363

364 365 366
		// Each column in the UPDATE statement needs to be wrapped in the keyword
		// identifiers, and a place-holder needs to be created for each value in
		// the array of bindings, so we'll build the sets first.
367 368
		foreach ($values as $column => $value)
		{
369
			$columns[] = $this->wrap($column).' = '.$this->parameter($value);
370
		}
Taylor Otwell committed
371

372 373
		$columns = implode(', ', $columns);

374 375 376
		// UPDATE statements may be constrained by a WHERE clause, so we'll run
		// the entire where compilation process for those contraints. This is
		// easily achieved by passing it to the "wheres" method.
377
		return trim("UPDATE {$table} SET {$columns} ".$this->wheres($query));
Taylor Otwell committed
378 379 380
	}

	/**
Taylor Otwell committed
381
	 * Compile a SQL DELETE statment from a Query instance.
Taylor Otwell committed
382 383 384 385
	 *
	 * @param  Query   $query
	 * @return string
	 */
Taylor Otwell committed
386
	public function delete(Query $query)
Taylor Otwell committed
387
	{
388
		$table = $this->wrap_table($query->from);
Taylor Otwell committed
389

390
		return trim("DELETE FROM {$table} ".$this->wheres($query));
Taylor Otwell committed
391
	}
Taylor Otwell committed
392

393 394 395 396 397 398 399 400 401
	/**
	 * Transform an SQL short-cuts into real SQL for PDO.
	 *
	 * @param  string  $sql
	 * @param  array   $bindings
	 * @return string
	 */
	public function shortcut($sql, $bindings)
	{
402 403 404
		// Laravel provides an easy short-cut notation for writing raw WHERE IN
		// statements. If (...) is in the query, it will be replaced with the
		// correct number of parameters based on the bindings.
405 406 407 408
		if (strpos($sql, '(...)') !== false)
		{
			for ($i = 0; $i < count($bindings); $i++)
			{
409 410 411
				// If the binding is an array, we can just assume it's used to
				// fill a "where in" condition, so we will just replace the
				// next place-holder in the query with the constraint.
412 413 414 415 416 417 418 419 420 421 422 423
				if (is_array($bindings[$i]))
				{
					$parameters = $this->parameterize($bindings[$i]);

					$sql = preg_replace('~\(\.\.\.\)~', "({$parameters})", $sql, 1);
				}
			}			
		}

		return trim($sql);
	}

Taylor Otwell committed
424
}