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 }