# Coverage for Doctrine_RawSql

Back to coverage report

1 | <?php
2 | /*
3 | * $Id: RawSql.php 3209 2007-11-24 18:11:09Z romanb $
4 | *
5 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
6 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
7 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
8 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
9 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
10 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
11 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
12 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
13 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
14 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
15 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
16 | *
17 | * This software consists of voluntary contributions made by many individuals
18 | * and is licensed under the LGPL. For more information, see
19 | * <http://www.phpdoctrine.org>.
20 | */
21 | Doctrine::autoload('Doctrine_Query_Abstract');
22 | /**
23 | * Doctrine_RawSql
24 | *
25 | * Doctrine_RawSql is an implementation of Doctrine_Query_Abstract that skips the entire
26 | * DQL parsing procedure. The "DQL" that is passed to a RawSql query object for execution
27 | * is considered to be plain SQL and will be used "as is". The only query part that is special
28 | * in a RawSql query is the SELECT part, which has a special syntax that provides Doctrine
29 | * with the necessary information to properly hydrate the query results.
30 | *
31 | * @package Doctrine
32 | * @subpackage RawSql
33 | * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
34 | * @link www.phpdoctrine.org
35 | * @since 1.0
36 | * @version $Revision: 3209 $
37 | * @author Konsta Vesterinen <kvesteri@cc.hut.fi>
38 | */
39 | class Doctrine_RawSql extends Doctrine_Query_Abstract
40 | {
41 | /**
42 | * @var array $fields
43 | */
44 | private $fields = array();
45 |
46 | /**
47 | * @deprecated
48 | */
49 | public function parseQueryPart($queryPartName, $queryPart, $append = false)
50 | {
51 | return $this->parseDqlQueryPart($queryPartName, $queryPart, $append);
52 | }
53 |
54 | /**
55 | * parseDqlQueryPart
56 | * parses given DQL query part. Overrides Doctrine_Query_Abstract::parseDqlQueryPart().
57 | * This implementation does no parsing at all, except of the SELECT portion of the query
58 | * which is special in RawSql queries. The entire remaining parts are used "as is", so
59 | * the user of the RawSql query is responsible for writing SQL that is portable between
60 | * different DBMS.
61 | *
62 | * @param string $queryPartName the name of the query part
63 | * @param string $queryPart query part to be parsed
64 | * @param boolean $append whether or not to append the query part to its stack
65 | * if false is given, this method will overwrite
66 | * the given query part stack with $queryPart
67 | * @return Doctrine_Query this object
68 | */
69 | public function parseDqlQueryPart($queryPartName, $queryPart, $append = false)
70 | {
71 | if ($queryPartName == 'select') {
72 | $this->_parseSelectFields($queryPart);
73 | return $this;
74 | }
75 | if ( ! isset($this->parts[$queryPartName])) {
76 | $this->_sqlParts[$queryPartName] = array();
77 | }
78 |
79 | if ( ! $append) {
80 | $this->_sqlParts[$queryPartName] = array($queryPart);
81 | } else {
82 | $this->_sqlParts[$queryPartName][] = $queryPart;
83 | }
84 | return $this;
85 | }
86 |
87 | /**
88 | * Adds a DQL query part. Overrides Doctrine_Query_Abstract::_addDqlQueryPart().
89 | * This implementation for RawSql parses the new parts right away, generating the SQL.
90 | */
91 | protected function _addDqlQueryPart($queryPartName, $queryPart, $append = false)
92 | {
93 | return $this->parseQueryPart($queryPartName, $queryPart, $append);
94 | }
95 |
96 | /**
97 | * Add select parts to fields.
98 | *
99 | * @param $queryPart sting The name of the querypart
100 | */
101 | private function _parseSelectFields($queryPart){
102 | preg_match_all('/{([^}{]*)}/U', $queryPart, $m);
103 | $this->fields = $m[1];
104 | $this->_sqlParts['select'] = array();
105 | }
106 |
107 | /**
108 | * parseDqlQuery
109 | * parses an sql query and adds the parts to internal array.
110 | * Overrides Doctrine_Query_Abstract::parseDqlQuery().
111 | * This implementation simply tokenizes the provided query string and uses them
112 | * as SQL parts right away.
113 | *
114 | * @param string $query query to be parsed
115 | * @return Doctrine_RawSql this object
116 | */
117 | public function parseDqlQuery($query)
118 | {
119 | $this->_parseSelectFields($query);
120 | $this->clear();
121 |
122 | $tokens = $this->_tokenizer->sqlExplode($query, ' ');
123 |
124 | $parts = array();
125 | foreach ($tokens as $key => $part) {
126 | $partLowerCase = strtolower($part);
127 | switch ($partLowerCase) {
128 | case 'select':
129 | case 'from':
130 | case 'where':
131 | case 'limit':
132 | case 'offset':
133 | case 'having':
134 | $type = $partLowerCase;
135 | if ( ! isset($parts[$partLowerCase])) {
136 | $parts[$partLowerCase] = array();
137 | }
138 | break;
139 | case 'order':
140 | case 'group':
141 | $i = $key + 1;
142 | if (isset($tokens[$i]) && strtolower($tokens[$i]) === 'by') {
143 | $type = $partLowerCase . 'by';
144 | $parts[$type] = array();
145 | } else {
146 | //not a keyword so we add it to the previous type
147 | $parts[$type][] = $part;
148 | }
149 | break;
150 | case 'by':
151 | continue;
152 | default:
153 | //not a keyword so we add it to the previous type.
154 | if ( ! isset($parts[$type][0])) {
155 | $parts[$type][0] = $part;
156 | } else {
157 | // why does this add to index 0 and not append to the
158 | // array. If it had done that one could have used
159 | // parseQueryPart.
160 | $parts[$type][0] .= ' '.$part;
161 | }
162 | }
163 | }
164 |
165 | $this->_sqlParts = $parts;
166 | $this->_sqlParts['select'] = array();
167 |
168 | return $this;
169 | }
170 |
171 | /**
172 | * getSqlQuery
173 | * builds the sql query.
174 | *
175 | * @return string the built sql query
176 | */
177 | public function getSqlQuery($params = array())
178 | {
179 | $select = array();
180 |
181 | foreach ($this->fields as $field) {
182 | $e = explode('.', $field);
183 | if ( ! isset($e[1])) {
184 | throw new Doctrine_RawSql_Exception('All selected fields in Sql query must be in format tableAlias.fieldName');
185 | }
186 | // try to auto-add component
187 | if ( ! $this->hasSqlTableAlias($e[0])) {
188 | try {
189 | $this->addComponent($e[0], ucwords($e[0]));
190 | } catch (Doctrine_Exception $exception) {
191 | throw new Doctrine_RawSql_Exception('The associated component for table alias ' . $e[0] . ' couldn\'t be found.');
192 | }
193 | }
194 |
195 | $componentAlias = $this->getComponentAlias($e[0]);
196 |
197 | if ($e[1] == '*') {
198 | foreach ($this->_queryComponents[$componentAlias]['table']->getColumnNames() as $name) {
199 | $field = $e[0] . '.' . $name;
200 |
201 | $select[$componentAlias][$field] = $field . ' AS ' . $e[0] . '__' . $name;
202 | }
203 | } else {
204 | $field = $e[0] . '.' . $e[1];
205 | $select[$componentAlias][$field] = $field . ' AS ' . $e[0] . '__' . $e[1];
206 | }
207 | }
208 |
209 | // force-add all primary key fields
210 |
211 | foreach ($this->getTableAliasMap() as $tableAlias => $componentAlias) {
212 | $map = $this->_queryComponents[$componentAlias];
213 |
214 | foreach ((array) $map['table']->getIdentifier() as $key) {
215 | $field = $tableAlias . '.' . $key;
216 |
217 | if ( ! isset($this->_sqlParts['select'][$field])) {
218 | $select[$componentAlias][$field] = $field . ' AS ' . $tableAlias . '__' . $key;
219 | }
220 | }
221 | }
222 |
223 | // first add the fields of the root component
224 | reset($this->_queryComponents);
225 | $componentAlias = key($this->_queryComponents);
226 |
227 | $q = 'SELECT ' . implode(', ', $select[$componentAlias]);
228 | unset($select[$componentAlias]);
229 |
230 | foreach ($select as $component => $fields) {
231 | if ( ! empty($fields)) {
232 | $q .= ', ' . implode(', ', $fields);
233 | }
234 | }
235 |
236 | $string = $this->applyInheritance();
237 | if ( ! empty($string)) {
238 | $this->_sqlParts['where'][] = $string;
239 | }
240 | $copy = $this->_sqlParts;
241 | unset($copy['select']);
242 |
243 | $q .= ( ! empty($this->_sqlParts['from']))? ' FROM ' . implode(' ', $this->_sqlParts['from']) : '';
244 | $q .= ( ! empty($this->_sqlParts['where']))? ' WHERE ' . implode(' AND ', $this->_sqlParts['where']) : '';
245 | $q .= ( ! empty($this->_sqlParts['groupby']))? ' GROUP BY ' . implode(', ', $this->_sqlParts['groupby']) : '';
246 | $q .= ( ! empty($this->_sqlParts['having']))? ' HAVING ' . implode(' AND ', $this->_sqlParts['having']) : '';
247 | $q .= ( ! empty($this->_sqlParts['orderby']))? ' ORDER BY ' . implode(', ', $this->_sqlParts['orderby']) : '';
248 | $q .= ( ! empty($this->_sqlParts['limit']))? ' LIMIT ' . implode(' ', $this->_sqlParts['limit']) : '';
249 | $q .= ( ! empty($this->_sqlParts['offset']))? ' OFFSET ' . implode(' ', $this->_sqlParts['offset']) : '';
250 |
251 | if ( ! empty($string)) {
252 | array_pop($this->_sqlParts['where']);
253 | }
254 | return $q;
255 | }
256 |
257 | /**
258 | * getFields
259 | * returns the fields associated with this parser
260 | *
261 | * @return array all the fields associated with this parser
262 | */
263 | public function getFields()
264 | {
265 | return $this->fields;
266 | }
267 |
268 | /**
269 | * addComponent
270 | *
271 | * @param string $tableAlias
272 | * @param string $componentName
273 | * @return Doctrine_RawSql
274 | */
275 | public function addComponent($tableAlias, $path)
276 | {
277 | $tmp = explode(' ', $path);
278 | $originalAlias = (count($tmp) > 1) ? end($tmp) : null;
279 |
280 | $e = explode('.', $tmp[0]);
281 |
282 | $fullPath = $tmp[0];
283 | $fullLength = strlen($fullPath);
284 |
285 | $table = null;
286 |
287 | $currPath = '';
288 |
289 | if (isset($this->_queryComponents[$e[0]])) {
290 | $table = $this->_queryComponents[$e[0]]['table'];
291 |
292 | $currPath = $parent = array_shift($e);
293 | }
294 |
295 | foreach ($e as $k => $component) {
296 | // get length of the previous path
297 | $length = strlen($currPath);
298 |
299 | // build the current component path
300 | $currPath = ($currPath) ? $currPath . '.' . $component : $component;
301 |
302 | $delimeter = substr($fullPath, $length, 1);
303 |
304 | // if an alias is not given use the current path as an alias identifier
305 | if (strlen($currPath) === $fullLength && isset($originalAlias)) {
306 | $componentAlias = $originalAlias;
307 | } else {
308 | $componentAlias = $currPath;
309 | }
310 | if ( ! isset($table)) {
311 | $conn = Doctrine_Manager::getInstance()
312 | ->getConnectionForComponent($component);
313 |
314 | $table = $conn->getTable($component);
315 | $this->_queryComponents[$componentAlias] = array('table' => $table);
316 | } else {
317 | $relation = $table->getRelation($component);
318 |
319 | $this->_queryComponents[$componentAlias] = array('table' => $relation->getTable(),
320 | 'parent' => $parent,
321 | 'relation' => $relation);
322 | }
323 | $this->addSqlTableAlias($tableAlias, $componentAlias);
324 |
325 | $parent = $currPath;
326 | }
327 |
328 | return $this;
329 | }
330 | }