Subversion-Projekte lars-tiefland.php_share

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
1 lars 1
<?php
2
/*
3
 *  $Id: RawSql.php 7490 2010-03-29 19:53:27Z jwage $
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.doctrine-project.org>.
20
 */
21
 
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.doctrine-project.org
35
 * @since       1.0
36
 * @version     $Revision: 7490 $
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
     * Constructor.
48
     *
49
     * @param Doctrine_Connection  The connection object the query will use.
50
     * @param Doctrine_Hydrator_Abstract  The hydrator that will be used for generating result sets.
51
     */
52
    function __construct(Doctrine_Connection $connection = null, Doctrine_Hydrator_Abstract $hydrator = null) {
53
        parent::__construct($connection, $hydrator);
54
 
55
        // Fix #1472. It's alid to disable QueryCache since there's no DQL for RawSql.
56
        // RawSql expects to be plain SQL + syntax for SELECT part. It is used as is in query execution.
57
        $this->useQueryCache(false);
58
    }
59
 
60
    protected function clear()
61
    {
62
        $this->_preQuery = false;
63
        $this->_pendingJoinConditions = array();
64
    }
65
 
66
    /**
67
     * parseDqlQueryPart
68
     * parses given DQL query part. Overrides Doctrine_Query_Abstract::parseDqlQueryPart().
69
     * This implementation does no parsing at all, except of the SELECT portion of the query
70
     * which is special in RawSql queries. The entire remaining parts are used "as is", so
71
     * the user of the RawSql query is responsible for writing SQL that is portable between
72
     * different DBMS.
73
     *
74
     * @param string $queryPartName     the name of the query part
75
     * @param string $queryPart         query part to be parsed
76
     * @param boolean $append           whether or not to append the query part to its stack
77
     *                                  if false is given, this method will overwrite
78
     *                                  the given query part stack with $queryPart
79
     * @return Doctrine_Query           this object
80
     */
81
 	public function parseDqlQueryPart($queryPartName, $queryPart, $append = false)
82
    {
83
        if ($queryPartName == 'select') {
84
     	    $this->_parseSelectFields($queryPart);
85
     	    return $this;
86
     	}
87
     	if ( ! isset($this->_sqlParts[$queryPartName])) {
88
     	    $this->_sqlParts[$queryPartName] = array();
89
     	}
90
 
91
     	if ( ! $append) {
92
     	    $this->_sqlParts[$queryPartName] = array($queryPart);
93
     	} else {
94
     	    $this->_sqlParts[$queryPartName][] = $queryPart;
95
     	}
96
     	return $this;
97
    }
98
 
99
    /**
100
     * Adds a DQL query part. Overrides Doctrine_Query_Abstract::_addDqlQueryPart().
101
     * This implementation for RawSql parses the new parts right away, generating the SQL.
102
     */
103
    protected function _addDqlQueryPart($queryPartName, $queryPart, $append = false)
104
    {
105
        return $this->parseDqlQueryPart($queryPartName, $queryPart, $append);
106
    }
107
 
108
    /**
109
     * Add select parts to fields.
110
     *
111
     * @param $queryPart sting The name of the querypart
112
     */
113
    private function _parseSelectFields($queryPart)
114
    {
115
        preg_match_all('/{([^}{]*)}/U', $queryPart, $m);
116
        $this->fields = $m[1];
117
        $this->_sqlParts['select'] = array();
118
    }
119
 
120
    /**
121
     * parseDqlQuery
122
     * parses an sql query and adds the parts to internal array.
123
     * Overrides Doctrine_Query_Abstract::parseDqlQuery().
124
     * This implementation simply tokenizes the provided query string and uses them
125
     * as SQL parts right away.
126
     *
127
     * @param string $query     query to be parsed
128
     * @return Doctrine_RawSql  this object
129
     */
130
    public function parseDqlQuery($query)
131
    {
132
        $this->_parseSelectFields($query);
133
        $this->clear();
134
 
135
        $tokens = $this->_tokenizer->sqlExplode($query, ' ');
136
 
137
        $parts = array();
138
        foreach ($tokens as $key => $part) {
139
            $partLowerCase = strtolower($part);
140
            switch ($partLowerCase) {
141
                case 'select':
142
                case 'from':
143
                case 'where':
144
                case 'limit':
145
                case 'offset':
146
                case 'having':
147
                    $type = $partLowerCase;
148
                    if ( ! isset($parts[$partLowerCase])) {
149
                        $parts[$partLowerCase] = array();
150
                    }
151
                    break;
152
                case 'order':
153
                case 'group':
154
                    $i = $key + 1;
155
                    if (isset($tokens[$i]) && strtolower($tokens[$i]) === 'by') {
156
                        $type = $partLowerCase . 'by';
157
                        $parts[$type] = array();
158
                    } else {
159
                        //not a keyword so we add it to the previous type
160
                        $parts[$type][] = $part;
161
                    }
162
                    break;
163
                case 'by':
164
                    continue;
165
                default:
166
                    //not a keyword so we add it to the previous type.
167
                    if ( ! isset($parts[$type][0])) {
168
                        $parts[$type][0] = $part;
169
                    } else {
170
                        // why does this add to index 0 and not append to the
171
                        // array. If it had done that one could have used
172
                        // parseQueryPart.
173
                        $parts[$type][0] .= ' '.$part;
174
                    }
175
            }
176
        }
177
 
178
        $this->_sqlParts = $parts;
179
        $this->_sqlParts['select'] = array();
180
 
181
        return $this;
182
    }
183
 
184
    /**
185
     * getSqlQuery
186
     * builds the sql query.
187
     *
188
     * @return string       the built sql query
189
     */
190
    public function getSqlQuery($params = array())
191
    {
192
        // Assign building/execution specific params
193
        $this->_params['exec'] = $params;
194
 
195
        // Initialize prepared parameters array
196
        $this->_execParams = $this->getFlattenedParams();
197
 
198
        // Initialize prepared parameters array
199
        $this->fixArrayParameterValues($this->_execParams);
200
 
201
        $select = array();
202
 
203
        $formatter = $this->getConnection()->formatter;
204
 
205
        foreach ($this->fields as $field) {
206
            $e = explode('.', $field);
207
            if ( ! isset($e[1])) {
208
                throw new Doctrine_RawSql_Exception('All selected fields in Sql query must be in format tableAlias.fieldName');
209
            }
210
            // try to auto-add component
211
            if ( ! $this->hasSqlTableAlias($e[0])) {
212
                try {
213
                    $this->addComponent($e[0], ucwords($e[0]));
214
                } catch (Doctrine_Exception $exception) {
215
                    throw new Doctrine_RawSql_Exception('The associated component for table alias ' . $e[0] . ' couldn\'t be found.');
216
                }
217
            }
218
 
219
            $componentAlias = $this->getComponentAlias($e[0]);
220
 
221
            if ($e[1] == '*') {
222
                foreach ($this->_queryComponents[$componentAlias]['table']->getColumnNames() as $name) {
223
                    $field = $formatter->quoteIdentifier($e[0]) . '.' . $formatter->quoteIdentifier($name);
224
 
225
                    $select[$componentAlias][$field] = $field . ' AS ' . $formatter->quoteIdentifier($e[0] . '__' . $name);
226
                }
227
            } else {
228
                $field = $formatter->quoteIdentifier($e[0]) . '.' . $formatter->quoteIdentifier($e[1]);
229
                $select[$componentAlias][$field] = $field . ' AS ' . $formatter->quoteIdentifier($e[0] . '__' . $e[1]);
230
            }
231
        }
232
 
233
        // force-add all primary key fields
234
        if ( ! isset($this->_sqlParts['distinct']) || $this->_sqlParts['distinct'] != true) {
235
            foreach ($this->getTableAliasMap() as $tableAlias => $componentAlias) {
236
                $map = $this->_queryComponents[$componentAlias];
237
 
238
                foreach ((array) $map['table']->getIdentifierColumnNames() as $key) {
239
                    $field = $formatter->quoteIdentifier($tableAlias) . '.' . $formatter->quoteIdentifier($key);
240
 
241
                    if ( ! isset($this->_sqlParts['select'][$field])) {
242
                        $select[$componentAlias][$field] = $field . ' AS ' . $formatter->quoteIdentifier($tableAlias . '__' . $key);
243
                    }
244
                }
245
            }
246
        }
247
 
248
        $q = 'SELECT ';
249
 
250
        if (isset($this->_sqlParts['distinct']) && $this->_sqlParts['distinct'] == true) {
251
            $q .= 'DISTINCT ';
252
        }
253
 
254
        // first add the fields of the root component
255
        reset($this->_queryComponents);
256
        $componentAlias = key($this->_queryComponents);
257
 
258
        $this->_rootAlias = $componentAlias;
259
 
260
        $q .= implode(', ', $select[$componentAlias]);
261
        unset($select[$componentAlias]);
262
 
263
        foreach ($select as $component => $fields) {
264
            if ( ! empty($fields)) {
265
                $q .= ', ' . implode(', ', $fields);
266
            }
267
        }
268
 
269
        $string = $this->getInheritanceCondition($this->getRootAlias());
270
 
271
        if ( ! empty($string)) {
272
            $this->_sqlParts['where'][] = $string;
273
        }
274
 
275
        $q .= ( ! empty($this->_sqlParts['from']))?    ' FROM '     . implode(' ', $this->_sqlParts['from']) : '';
276
        $q .= ( ! empty($this->_sqlParts['where']))?   ' WHERE '    . implode(' AND ', $this->_sqlParts['where']) : '';
277
        $q .= ( ! empty($this->_sqlParts['groupby']))? ' GROUP BY ' . implode(', ', $this->_sqlParts['groupby']) : '';
278
        $q .= ( ! empty($this->_sqlParts['having']))?  ' HAVING '   . implode(' AND ', $this->_sqlParts['having']) : '';
279
        $q .= ( ! empty($this->_sqlParts['orderby']))? ' ORDER BY ' . implode(', ', $this->_sqlParts['orderby']) : '';
280
        $q .= ( ! empty($this->_sqlParts['limit']))?   ' LIMIT ' . implode(' ', $this->_sqlParts['limit']) : '';
281
        $q .= ( ! empty($this->_sqlParts['offset']))?  ' OFFSET ' . implode(' ', $this->_sqlParts['offset']) : '';
282
 
283
        if ( ! empty($string)) {
284
            array_pop($this->_sqlParts['where']);
285
        }
286
        return $q;
287
    }
288
 
289
	/**
290
     * getCountQuery
291
     * builds the count query.
292
     *
293
     * @return string       the built sql query
294
     */
295
	public function getCountSqlQuery($params = array())
296
    {
297
        //Doing COUNT( DISTINCT rootComponent.id )
298
        //This is not correct, if the result is not hydrated by doctrine, but it mimics the behaviour of Doctrine_Query::getCountQuery
299
        reset($this->_queryComponents);
300
        $componentAlias = key($this->_queryComponents);
301
 
302
        $this->_rootAlias = $componentAlias;
303
 
304
        $tableAlias = $this->getSqlTableAlias($componentAlias);
305
        $fields = array();
306
 
307
        foreach ((array) $this->_queryComponents[$componentAlias]['table']->getIdentifierColumnNames() as $key) {
308
        	$fields[] = $tableAlias . '.' . $key;
309
        }
310
 
311
        $q = 'SELECT COUNT(*) as num_results FROM (SELECT DISTINCT '.implode(', ',$fields);
312
 
313
        $string = $this->getInheritanceCondition($this->getRootAlias());
314
        if ( ! empty($string)) {
315
            $this->_sqlParts['where'][] = $string;
316
        }
317
 
318
        $q .= ( ! empty($this->_sqlParts['from']))?    ' FROM '     . implode(' ', $this->_sqlParts['from']) : '';
319
        $q .= ( ! empty($this->_sqlParts['where']))?   ' WHERE '    . implode(' AND ', $this->_sqlParts['where']) : '';
320
        $q .= ( ! empty($this->_sqlParts['groupby']))? ' GROUP BY ' . implode(', ', $this->_sqlParts['groupby']) : '';
321
        $q .= ( ! empty($this->_sqlParts['having']))?  ' HAVING '   . implode(' AND ', $this->_sqlParts['having']) : '';
322
 
323
        $q .= ') as results';
324
 
325
        if ( ! empty($string)) {
326
            array_pop($this->_sqlParts['where']);
327
        }
328
 
329
        return $q;
330
    }
331
 
332
	/**
333
     * count
334
     * fetches the count of the query
335
     *
336
     * This method executes the main query without all the
337
     * selected fields, ORDER BY part, LIMIT part and OFFSET part.
338
     *
339
     * This is an exact copy of the Dql Version
340
     *
341
     * @see Doctrine_Query::count()
342
     * @param array $params        an array of prepared statement parameters
343
     * @return integer             the count of this query
344
     */
345
    public function count($params = array())
346
    {
347
        $sql = $this->getCountSqlQuery();
348
        $params = $this->getCountQueryParams($params);
349
        $results = $this->getConnection()->fetchAll($sql, $params);
350
 
351
        if (count($results) > 1) {
352
            $count = count($results);
353
        } else {
354
            if (isset($results[0])) {
355
                $results[0] = array_change_key_case($results[0], CASE_LOWER);
356
                $count = $results[0]['num_results'];
357
            } else {
358
                $count = 0;
359
            }
360
        }
361
 
362
        return (int) $count;
363
    }
364
 
365
    /**
366
     * getFields
367
     * returns the fields associated with this parser
368
     *
369
     * @return array    all the fields associated with this parser
370
     */
371
    public function getFields()
372
    {
373
        return $this->fields;
374
    }
375
 
376
    /**
377
     * addComponent
378
     *
379
     * @param string $tableAlias
380
     * @param string $componentName
381
     * @return Doctrine_RawSql
382
     */
383
    public function addComponent($tableAlias, $path)
384
    {
385
        $tmp           = explode(' ', $path);
386
        $originalAlias = (count($tmp) > 1) ? end($tmp) : null;
387
 
388
        $e = explode('.', $tmp[0]);
389
 
390
        $fullPath = $tmp[0];
391
        $fullLength = strlen($fullPath);
392
 
393
        $table = null;
394
 
395
        $currPath = '';
396
 
397
        if (isset($this->_queryComponents[$e[0]])) {
398
            $table = $this->_queryComponents[$e[0]]['table'];
399
 
400
            $currPath = $parent = array_shift($e);
401
        }
402
 
403
        foreach ($e as $k => $component) {
404
            // get length of the previous path
405
            $length = strlen($currPath);
406
 
407
            // build the current component path
408
            $currPath = ($currPath) ? $currPath . '.' . $component : $component;
409
 
410
            $delimeter = substr($fullPath, $length, 1);
411
 
412
            // if an alias is not given use the current path as an alias identifier
413
            if (strlen($currPath) === $fullLength && isset($originalAlias)) {
414
                $componentAlias = $originalAlias;
415
            } else {
416
                $componentAlias = $currPath;
417
            }
418
            if ( ! isset($table)) {
419
                $conn = Doctrine_Manager::getInstance()
420
                        ->getConnectionForComponent($component);
421
 
422
                $table = $conn->getTable($component);
423
                $this->_queryComponents[$componentAlias] = array('table' => $table);
424
            } else {
425
                $relation = $table->getRelation($component);
426
 
427
                $this->_queryComponents[$componentAlias] = array('table'    => $relation->getTable(),
428
                                                          'parent'   => $parent,
429
                                                          'relation' => $relation);
430
            }
431
            $this->addSqlTableAlias($tableAlias, $componentAlias);
432
 
433
            $parent = $currPath;
434
        }
435
 
436
        return $this;
437
    }
438
 
439
    /**
440
     * calculateResultCacheHash
441
     * calculate hash key for result cache
442
     *
443
     * @param array $params
444
     * @return string    the hash
445
     */
446
    public function calculateResultCacheHash($params = array())
447
    {
448
        $sql = $this->getSqlQuery();
449
        $conn = $this->getConnection();
450
        $params = $this->getFlattenedParams($params);
451
        $hash = md5($this->_hydrator->getHydrationMode() . $conn->getName() . $conn->getOption('dsn') . $sql . var_export($params, true));
452
        return $hash;
453
    }
454
}