[FEATURE] Execute native prepared queries
[Packages/TYPO3.CMS.git] / typo3 / sysext / dbal / Classes / Database / AdodbPreparedStatement.php
1 <?php
2 namespace TYPO3\CMS\Dbal\Database;
3
4 /***************************************************************
5 * Copyright notice
6 *
7 * (c) 2014 Xavier Perseguers <xavier@typo3.org>
8 * All rights reserved
9 *
10 * This script is part of the TYPO3 project. The TYPO3 project is
11 * free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
15 *
16 * The GNU General Public License can be found at
17 * http://www.gnu.org/copyleft/gpl.html.
18 * A copy is found in the text file GPL.txt and important notices to the license
19 * from the author is found in LICENSE.txt distributed with these scripts.
20 *
21 *
22 * This script is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 * GNU General Public License for more details.
26 *
27 * This copyright notice MUST APPEAR in all copies of the script!
28 ***************************************************************/
29
30 use TYPO3\CMS\Core\Utility\GeneralUtility;
31
32 /**
33 * MySQLi Prepared Statement-compatible implementation for ADOdb.
34 *
35 * Notice: Extends \TYPO3\CMS\Dbal\Database\DatabaseConnection to be able to access
36 * protected properties solely (thus would be a "friend" class in C++).
37 *
38 * @author Xavier Perseguers <xavier@typo3.org>
39 */
40 class AdodbPreparedStatement extends \TYPO3\CMS\Dbal\Database\DatabaseConnection {
41
42 /**
43 * @var \TYPO3\CMS\Dbal\Database\DatabaseConnection
44 */
45 protected $databaseConnection;
46
47 /**
48 * @var string
49 */
50 protected $query;
51
52 /**
53 * @var array
54 */
55 protected $queryComponents;
56
57 /**
58 * @var array
59 */
60 protected $parameters;
61
62 /**
63 * @var \ADORecordSet_array
64 */
65 protected $recordSet;
66
67 /**
68 * Default constructor.
69 *
70 * @param string $query
71 * @param array $queryComponents
72 * @param \TYPO3\CMS\Dbal\Database\DatabaseConnection $databaseConnection
73 */
74 public function __construct($query, array $queryComponents, \TYPO3\CMS\Dbal\Database\DatabaseConnection $databaseConnection) {
75 $this->databaseConnection = $databaseConnection;
76 $this->query = $query;
77 $this->queryComponents = $queryComponents;
78 $this->parameters = array();
79 }
80
81 /**
82 * Prepares an SQL statement for execution.
83 *
84 * @return boolean TRUE on success or FALSE on failure
85 */
86 public function prepare() {
87 // TODO: actually prepare the query with ADOdb, if supported by the underlying DBMS
88 // see: http://phplens.com/lens/adodb/docs-adodb.htm#prepare
89 return TRUE;
90 }
91
92 /**
93 * Transfers a result set from a prepared statement.
94 *
95 * @return TRUE on success or FALSE on failure
96 */
97 public function store_result() {
98 return TRUE;
99 }
100
101 /**
102 * Binds variables to a prepared statement as parameters.
103 *
104 * @param string $types
105 * @param mixed $var1 The number of variables and length of string types must match the parameters in the statement.
106 * @param mixed $_ [optional]
107 * @return boolean TRUE on success or FALSE on failure
108 * @see \mysqli_stmt::bind_param()
109 */
110 public function bind_param($types, $var1, $_ = NULL) {
111 $numberOfVariables = strlen($types);
112 if (func_num_args() !== $numberOfVariables + 1) {
113 return FALSE;
114 }
115
116 $this->parameters = array(
117 array(
118 'type' => $types{0},
119 'value' => $var1
120 ),
121 );
122 for ($i = 1; $i < $numberOfVariables; $i++) {
123 $this->parameters[] = array(
124 'type' => $types{$i},
125 'value' => func_get_arg($i + 1),
126 );
127 }
128
129 return TRUE;
130 }
131
132 /**
133 * Resets a prepared statement.
134 *
135 * @return boolean TRUE on success or FALSE on failure
136 */
137 public function reset() {
138 return TRUE;
139 }
140
141 /**
142 * Executes a prepared query.
143 *
144 * @return boolean TRUE on success or FALSE on failure
145 */
146 public function execute() {
147 $queryParts = $this->queryComponents['queryParts'];
148 $numberOfParameters = count($this->parameters);
149 for ($i = 0; $i < $numberOfParameters; $i++) {
150 $value = $this->parameters[$i]['value'];
151 switch ($this->parameters[$i]['type']) {
152 case 's':
153 if ($value !== NULL) {
154 $value = $this->databaseConnection->fullQuoteStr($value, $this->queryComponents['ORIG_tableName']);
155 }
156 break;
157 case 'i':
158 $value = (int)$value;
159 break;
160 default:
161 // Same error as in \TYPO3\CMS\Core\Database\PreparedStatement::execute()
162 throw new \InvalidArgumentException(sprintf('Unknown type %s used for parameter %s.', $this->parameters[$i]['type'], $i + 1), 1281859196);
163 }
164
165 $queryParts[$i * 2 + 1] = $value;
166 }
167
168 // Standard query from now on
169 $query = implode('', $queryParts);
170
171 $limit = $this->queryComponents['LIMIT'];
172 if ($this->databaseConnection->runningADOdbDriver('postgres')) {
173 // Possibly rewrite the LIMIT to be PostgreSQL-compatible
174 $splitLimit = GeneralUtility::intExplode(',', $limit);
175 // Splitting the limit values:
176 if ($splitLimit[1]) {
177 // If there are two parameters, do mapping differently than otherwise:
178 $numRows = $splitLimit[1];
179 $offset = $splitLimit[0];
180 $limit = $numrows . ' OFFSET ' . $offset;
181 }
182 }
183 if ($limit !== '') {
184 $splitLimit = GeneralUtility::intExplode(',', $limit);
185 // Splitting the limit values:
186 if ($splitLimit[1]) {
187 // If there are two parameters, do mapping differently than otherwise:
188 $numRows = $splitLimit[1];
189 $offset = $splitLimit[0];
190 } else {
191 $numRows = $splitLimit[0];
192 $offset = 0;
193 }
194 $this->recordSet = $this->databaseConnection->handlerInstance[$this->databaseConnection->lastHandlerKey]->SelectLimit($query, $numRows, $offset);
195 $this->databaseConnection->lastQuery = $this->recordSet->sql;
196 } else {
197 $this->databaseConnection->lastQuery = $query;
198 $this->recordSet = $this->databaseConnection->handlerInstance[$this->databaseConnection->lastHandlerKey]->_Execute($this->databaseConnection->lastQuery);
199 }
200
201 if ($this->recordSet !== FALSE) {
202 $success = TRUE;
203 $this->recordSet->TYPO3_DBAL_handlerType = 'adodb';
204 // Setting handler type in result object (for later recognition!)
205 //$this->recordSet->TYPO3_DBAL_tableList = $queryComponents['ORIG_tableName'];
206 } else {
207 $success = FALSE;
208 }
209
210 return $success;
211 }
212
213 /**
214 * Returns an array of objects representing the fields in a result set.
215 *
216 * @return array
217 */
218 public function fetch_fields() {
219 return $this->recordSet !== FALSE ? $this->recordSet->_fieldobjects : array();
220 }
221
222 /**
223 * Fetches a row from the underlying result set.
224 *
225 * @return array Array of rows or FALSE if there are no more rows.
226 */
227 public function fetch() {
228 $row = $this->databaseConnection->sql_fetch_assoc($this->recordSet);
229 return $row;
230 }
231
232 /**
233 * Seeks to an arbitrary row in statement result set.
234 *
235 * @param integer $offset Must be between zero and the total number of rows minus one
236 * @return boolean TRUE on success or FALSE on failure
237 */
238 public function data_seek($offset) {
239 return $this->databaseConnection->sql_data_seek($this->recordSet, $offset);
240 }
241
242 /**
243 * Closes a prepared statement.
244 *
245 * @return boolean TRUE on success or FALSE on failure
246 */
247 public function close() {
248 return $this->databaseConnection->sql_free_result($this->recordSet);
249 }
250
251 /**
252 * Magic getter for public properties of \mysqli_stmt access
253 * by \TYPO3\CMS\Core\Database\PreparedStatement.
254 *
255 * @param string $name
256 * @return mixed
257 */
258 public function __get($name) {
259 switch ($name) {
260 case 'errno':
261 $output = $this->databaseConnection->sql_errno();
262 break;
263 case 'error':
264 $output = $this->databaseConnection->sql_error();
265 break;
266 case 'num_rows':
267 $output = $this->databaseConnection->sql_num_rows($this->recordSet);
268 break;
269 default:
270 throw new \RuntimeException('Cannot access property ' . $name, 1394631927);
271 }
272 return $output;
273 }
274
275 }