1<?php
2/**
3 * This file contains the class XML_Query2XML_Driver_DB.
4 *
5 * PHP version 5
6 *
7 * @category  XML
8 * @package   XML_Query2XML
9 * @author    Lukas Feiler <lukas.feiler@lukasfeiler.com>
10 * @copyright 2006 Lukas Feiler
11 * @license   http://www.gnu.org/copyleft/lesser.html  LGPL Version 2.1
12 * @version   CVS: $Id: DB.php 276639 2009-03-01 13:17:08Z lukasfeiler $
13 * @link      http://pear.php.net/package/XML_Query2XML
14 */
15
16/**
17 * XML_Query2XML_Driver_DB extends XML_Query2XML_Driver.
18 */
19require_once 'XML/Query2XML.php';
20
21/**
22 * As the method PEAR::isError() is used within XML_Query2XML_Driver_DB
23 * we require PEAR.php.
24 */
25require_once 'PEAR.php';
26
27/**
28 * Driver for the database abstraction layer PEAR DB.
29 *
30 * usage:
31 * <code>
32 * $driver = XML_Query2XML_Driver::factory(DB::connect(...));
33 * </code>
34 *
35 * @category  XML
36 * @package   XML_Query2XML
37 * @author    Lukas Feiler <lukas.feiler@lukasfeiler.com>
38 * @copyright 2006 Lukas Feiler
39 * @license   http://www.gnu.org/copyleft/lesser.html  LGPL Version 2.1
40 * @version   Release: 1.7.2
41 * @link      http://pear.php.net/package/XML_Query2XML
42 * @since     Release 1.5.0RC1
43 */
44class XML_Query2XML_Driver_DB extends XML_Query2XML_Driver
45{
46    /**
47     * In instance of a class that extends DB_common.
48     * @var DB_common
49     */
50    private $_db = null;
51
52    /**
53     * Constructor
54     *
55     * @param DB_Common $db An instance of a class that extends DB_Common.
56     *
57     * @throws XML_Query2XML_DBException If the fetch mode cannot be set to
58     *                               DB_FETCHMODE_ASSOC.
59     */
60    public function __construct(DB_common $db)
61    {
62        $fetchModeError = $db->setFetchMode(DB_FETCHMODE_ASSOC);
63        if (PEAR::isError($fetchModeError)) {
64            // no unit tests for this one
65            throw new XML_Query2XML_DBException(
66                'Could not set fetch mode to DB_FETCHMODE_ASSOC: '
67                . $fetchModeError->toString()
68            );
69        }
70        $this->_db = $db;
71    }
72
73    /**
74     * Pre-processes a query specification and returns a string representation
75     * of the query.
76     * This method will call parent::preprocessQuery(). Additionally it will
77     * verify $query['limit'] and $query['offset'].
78     *
79     * @param mixed  &$query     A string or an array containing the element 'query'.
80     * @param string $configPath The config path; used for exception messages.
81     *
82     * @return string The query statement as a string.
83     * @throws XML_Query2XML_ConfigException If $query['limit'] or $query['offset']
84     *                                       is set but not numeric. This exception
85     *                                       might also bubble up from
86     *                                       parent::preprocessQuery().
87     */
88    public function preprocessQuery(&$query, $configPath)
89    {
90        /*
91         * This will make $query an array if it is not already.
92         * We'll ignore preprocessQuery()'s return value here.
93         */
94        parent::preprocessQuery($query, $configPath);
95
96        foreach (array('limit', 'offset') as $sqlOption) {
97            if (isset($query[$sqlOption])) {
98                if (!is_numeric($query[$sqlOption])) {
99                    /*
100                     * unit test: getXML/
101                     *  offsetlimit_throwConfigException_limit_not_numeric.phpt
102                     *  offsetlimit_throwConfigException_offset_not_numeric.phpt
103                     */
104                    throw new XML_Query2XML_ConfigException(
105                        $configPath . '[' . $sqlOption
106                        . ']: integer expected, '
107                        . gettype($query[$sqlOption]) . ' given.'
108                    );
109                }
110            }
111        }
112        $queryString = $query['query'];
113        if (isset($query['limit'])) {
114            if ($query['limit'] == 0) {
115                // setting limit to 0 is like not setting it at all
116                unset($query['limit']);
117            } else {
118                if (!isset($query['offset'])) {
119                    // offset defaults to 0
120                    $query['offset'] = 0;
121                }
122                $queryString .= '; LIMIT:' . $query['limit'];
123                $queryString .= '; OFFSET:' . $query['offset'];
124
125                $query['query'] = $this->_db->modifyLimitQuery(
126                    $query['query'],
127                    $query['offset'],
128                    $query['limit']
129                );
130            }
131        }
132        return $queryString;
133    }
134
135    /**
136     * Execute a SQL SELECT stement and fetch all records from the result set.
137     *
138     * @param mixed  $sql        The SQL query as an array containing the
139     *                           element 'query'.
140     * @param string $configPath The config path; used for exception messages.
141     *
142     * @return array An array of records.
143     * @throws XML_Query2XML_DBException If a database related error occures.
144     * @see XML_Query2XML_Driver::getAllRecords()
145     */
146    public function getAllRecords($sql, $configPath)
147    {
148        if (isset($sql['limit']) && $sql['limit'] < 0) {
149            return array();
150        }
151        $result  =& $this->_prepareAndExecute($sql, $configPath);
152        $records = array();
153        while ($record = $result->fetchRow()) {
154            if (PEAR::isError($record)) {
155                // no unit test for this exception as it cannot be produced easily
156                throw new XML_Query2XML_DBException(
157                    $configPath . ': Could not fetch rows for the following '
158                    . 'SQL query: ' . $sql['query'] . '; '
159                    . $record->toString()
160                );
161            }
162            $records[] = $record;
163        }
164        $result->free();
165        return $records;
166    }
167
168    /**
169     * Private method that will use DB_Common::prepare() & DB_Common::execute()
170     * to run an SQL query.
171     *
172     * @param mixed  $sql        An associative array with a 'query' element.
173     * @param string $configPath The config path used for exception messages.
174     *
175     * @return DB_result
176     * @throws XML_Query2XML_DBException If a database related error occures.
177     */
178    private function _prepareAndExecute($sql, $configPath)
179    {
180        $query = $sql['query'];
181
182        if (isset($this->_preparedQueries[$query])) {
183            $queryHandle = $this->_preparedQueries[$query];
184        } else {
185            // PREPARE
186            $queryHandle = $this->_db->prepare($query);
187
188            if (PEAR::isError($queryHandle)) {
189                /*
190                 * No unit test for this exception as DB's mysql and pgsql
191                 * drivers never return a PEAR error from prepare().
192                 */
193                throw new XML_Query2XML_DBException(
194                    $configPath . ': Could not prepare the following SQL query: '
195                    . $query . '; ' . $queryHandle->toString()
196                );
197            }
198
199            $this->_preparedQueries[$query] =& $queryHandle;
200        }
201
202        // EXECUTE
203        if (isset($sql['data'])) {
204            $result = $this->_db->execute($queryHandle, $sql['data']);
205        } else {
206            $result = $this->_db->execute($queryHandle);
207        }
208
209        if (PEAR::isError($result)) {
210            /*
211             * unit test: DB/_prepareAndExecute/
212             *  throwDBException_complexQuery.phpt
213             */
214            throw new XML_Query2XML_DBException(
215                $configPath . ': Could not execute the following SQL query: '
216                . $query . '; ' . $result->toString()
217            );
218        }
219        if (!($result instanceof DB_result)) {
220            /*
221             * unit tests: DB/getXML/
222             *  throwDBException_nullResultSet_complexQuery_multipleRecords.phpt
223             *  throwDBException_nullResultSet_complexQuery_singleRecord.phpt
224             */
225            throw new XML_Query2XML_DBException(
226                $configPath . ': the following SQL query returned no '
227                . 'result set: ' . $query
228            );
229        }
230        return $result;
231    }
232}
233?>