# Coverage for Doctrine_Export_Sqlite

Back to coverage report

1 | <?php
2 | /*
3 | * $Id: Sqlite.php 3048 2007-11-01 19:45:36Z Jonathan.Wage $
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_Export');
22 | /**
23 | * Doctrine_Export_Sqlite
24 | *
25 | * @package Doctrine
26 | * @subpackage Export
27 | * @author Konsta Vesterinen <kvesteri@cc.hut.fi>
28 | * @author Lukas Smith <smith@pooteeweet.org> (PEAR MDB2 library)
29 | * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
30 | * @link www.phpdoctrine.org
31 | * @since 1.0
32 | * @version $Revision: 3048 $
33 | */
34 | class Doctrine_Export_Sqlite extends Doctrine_Export
35 | {
36 | /**
37 | * drop an existing database
38 | *
39 | * @param string $name name of the database that should be dropped
40 | * @throws Doctrine_Export_Exception if the database file does not exist
41 | * @throws Doctrine_Export_Exception if something failed during the removal of the database file
42 | * @return void
43 | */
44 | public function dropDatabase($name)
45 | {
46 | $databaseFile = $this->conn->getDatabaseFile($name);
47 | if ( ! @file_exists($databaseFile)) {
48 | throw new Doctrine_Export_Exception('database does not exist');
49 | }
50 | $result = @unlink($databaseFile);
51 | if ( ! $result) {
52 | throw new Doctrine_Export_Exception('could not remove the database file');
53 | }
54 | }
55 |
56 | /**
57 | * Get the stucture of a field into an array
58 | *
59 | * @param string $table name of the table on which the index is to be created
60 | * @param string $name name of the index to be created
61 | * @param array $definition associative array that defines properties of the index to be created.
62 | * Currently, only one property named FIELDS is supported. This property
63 | * is also an associative with the names of the index fields as array
64 | * indexes. Each entry of this array is set to another type of associative
65 | * array that specifies properties of the index that are specific to
66 | * each field.
67 | *
68 | * Currently, only the sorting property is supported. It should be used
69 | * to define the sorting direction of the index. It may be set to either
70 | * ascending or descending.
71 | *
72 | * Not all DBMS support index sorting direction configuration. The DBMS
73 | * drivers of those that do not support it ignore this property. Use the
74 | * function support() to determine whether the DBMS driver can manage indexes.
75 |
76 | * Example
77 | * array(
78 | * 'fields' => array(
79 | * 'user_name' => array(
80 | * 'sorting' => 'ascending'
81 | * ),
82 | * 'last_login' => array()
83 | * )
84 | * )
85 | * @throws PDOException
86 | * @return void
87 | */
88 | public function createIndexSql($table, $name, array $definition)
89 | {
90 | $name = $this->conn->formatter->getIndexName($name);
91 | $name = $this->conn->quoteIdentifier($name);
92 | $query = 'CREATE INDEX ' . $name . ' ON ' . $table;
93 | $query .= ' (' . $this->getIndexFieldDeclarationList($definition['fields']) . ')';
94 |
95 | return $query;
96 | }
97 |
98 | /**
99 | * getIndexFieldDeclarationList
100 | * Obtain DBMS specific SQL code portion needed to set an index
101 | * declaration to be used in statements like CREATE TABLE.
102 | *
103 | * @return string
104 | */
105 | public function getIndexFieldDeclarationList(array $fields)
106 | {
107 | $declFields = array();
108 |
109 | foreach ($fields as $fieldName => $field) {
110 | $fieldString = $this->conn->quoteIdentifier($fieldName);
111 |
112 | if (is_array($field)) {
113 | if (isset($field['sorting'])) {
114 | $sort = strtoupper($field['sorting']);
115 | switch ($sort) {
116 | case 'ASC':
117 | case 'DESC':
118 | $fieldString .= ' ' . $sort;
119 | break;
120 | default:
121 | throw new Doctrine_Export_Exception('Unknown index sorting option given.');
122 | }
123 | }
124 | } else {
125 | $fieldString = $this->conn->quoteIdentifier($field);
126 | }
127 | $declFields[] = $fieldString;
128 | }
129 | return implode(', ', $declFields);
130 | }
131 |
132 | /**
133 | * create a new table
134 | *
135 | * @param string $name Name of the database that should be created
136 | * @param array $fields Associative array that contains the definition of each field of the new table
137 | * The indexes of the array entries are the names of the fields of the table an
138 | * the array entry values are associative arrays like those that are meant to be
139 | * passed with the field definitions to get[Type]Declaration() functions.
140 | * array(
141 | * 'id' => array(
142 | * 'type' => 'integer',
143 | * 'unsigned' => 1
144 | * 'notnull' => 1
145 | * 'default' => 0
146 | * ),
147 | * 'name' => array(
148 | * 'type' => 'text',
149 | * 'length' => 12
150 | * ),
151 | * 'password' => array(
152 | * 'type' => 'text',
153 | * 'length' => 12
154 | * )
155 | * );
156 | * @param array $options An associative array of table options:
157 | *
158 | * @return void
159 | */
160 | public function createTableSql($name, array $fields, array $options = array())
161 | {
162 | if ( ! $name) {
163 | throw new Doctrine_Export_Exception('no valid table name specified');
164 | }
165 |
166 | if (empty($fields)) {
167 | throw new Doctrine_Export_Exception('no fields specified for table '.$name);
168 | }
169 | $queryFields = $this->getFieldDeclarationList($fields);
170 |
171 | $autoinc = false;
172 | foreach($fields as $field) {
173 | if (isset($field['autoincrement']) && $field['autoincrement'] ||
174 | (isset($field['autoinc']) && $field['autoinc'])) {
175 | $autoinc = true;
176 | break;
177 | }
178 | }
179 |
180 | if ( ! $autoinc && isset($options['primary']) && ! empty($options['primary'])) {
181 | $keyColumns = array_values($options['primary']);
182 | $keyColumns = array_map(array($this->conn, 'quoteIdentifier'), $keyColumns);
183 | $queryFields.= ', PRIMARY KEY('.implode(', ', $keyColumns).')';
184 | }
185 |
186 | $name = $this->conn->quoteIdentifier($name, true);
187 | $sql = 'CREATE TABLE ' . $name . ' (' . $queryFields;
188 |
189 | if ($check = $this->getCheckDeclaration($fields)) {
190 | $sql .= ', ' . $check;
191 | }
192 |
193 | if (isset($options['checks']) && $check = $this->getCheckDeclaration($options['checks'])) {
194 | $sql .= ', ' . $check;
195 | }
196 |
197 | $sql .= ')';
198 |
199 | $query[] = $sql;
200 |
201 | if (isset($options['indexes']) && ! empty($options['indexes'])) {
202 | foreach ($options['indexes'] as $index => $definition) {
203 | $query[] = $this->createIndexSql($name, $index, $definition);
204 | }
205 | }
206 | return $query;
207 |
208 |
209 | /**
210 | try {
211 |
212 | if ( ! empty($fk)) {
213 | $this->conn->beginTransaction();
214 | }
215 |
216 | $ret = $this->conn->exec($query);
217 |
218 | if ( ! empty($fk)) {
219 | foreach ($fk as $definition) {
220 |
221 | $query = 'CREATE TRIGGER doctrine_' . $name . '_cscd_delete '
222 | . 'AFTER DELETE ON ' . $name . ' FOR EACH ROW '
223 | . 'BEGIN '
224 | . 'DELETE FROM ' . $definition['foreignTable'] . ' WHERE ';
225 |
226 | $local = (array) $definition['local'];
227 | foreach((array) $definition['foreign'] as $k => $field) {
228 | $query .= $field . ' = old.' . $local[$k] . ';';
229 | }
230 |
231 | $query .= 'END;';
232 |
233 | $this->conn->exec($query);
234 | }
235 |
236 | $this->conn->commit();
237 | }
238 |
239 |
240 | } catch(Doctrine_Exception $e) {
241 |
242 | $this->conn->rollback();
243 |
244 | throw $e;
245 | }
246 | */
247 | }
248 |
249 | /**
250 | * getAdvancedForeignKeyOptions
251 | * Return the FOREIGN KEY query section dealing with non-standard options
252 | * as MATCH, INITIALLY DEFERRED, ON UPDATE, ...
253 | *
254 | * @param array $definition foreign key definition
255 | * @return string
256 | * @access protected
257 | */
258 | public function getAdvancedForeignKeyOptions(array $definition)
259 | {
260 | $query = '';
261 | if (isset($definition['match'])) {
262 | $query .= ' MATCH ' . $definition['match'];
263 | }
264 | if (isset($definition['onUpdate'])) {
265 | $query .= ' ON UPDATE ' . $definition['onUpdate'];
266 | }
267 | if (isset($definition['onDelete'])) {
268 | $query .= ' ON DELETE ' . $definition['onDelete'];
269 | }
270 | if (isset($definition['deferrable'])) {
271 | $query .= ' DEFERRABLE';
272 | } else {
273 | $query .= ' NOT DEFERRABLE';
274 | }
275 | if (isset($definition['feferred'])) {
276 | $query .= ' INITIALLY DEFERRED';
277 | } else {
278 | $query .= ' INITIALLY IMMEDIATE';
279 | }
280 | return $query;
281 | }
282 |
283 | /**
284 | * create sequence
285 | *
286 | * @param string $seqName name of the sequence to be created
287 | * @param string $start start value of the sequence; default is 1
288 | * @param array $options An associative array of table options:
289 | * array(
290 | * 'comment' => 'Foo',
291 | * 'charset' => 'utf8',
292 | * 'collate' => 'utf8_unicode_ci',
293 | * );
294 | * @return boolean
295 | */
296 | public function createSequence($seqName, $start = 1, array $options = array())
297 | {
298 | $sequenceName = $this->conn->quoteIdentifier($this->conn->getSequenceName($seqName), true);
299 | $seqcolName = $this->conn->quoteIdentifier($this->conn->getAttribute(Doctrine::ATTR_SEQCOL_NAME), true);
300 | $query = 'CREATE TABLE ' . $sequenceName . ' (' . $seqcolName . ' INTEGER PRIMARY KEY DEFAULT 0 NOT NULL)';
301 |
302 | $this->conn->exec($query);
303 |
304 | if ($start == 1) {
305 | return true;
306 | }
307 |
308 | try {
309 | $this->conn->exec('INSERT INTO ' . $sequenceName . ' (' . $seqcolName . ') VALUES (' . ($start-1) . ')');
310 | return true;
311 | } catch(Doctrine_Connection_Exception $e) {
312 | // Handle error
313 |
314 | try {
315 | $result = $db->exec('DROP TABLE ' . $sequenceName);
316 | } catch(Doctrine_Connection_Exception $e) {
317 | throw new Doctrine_Export_Exception('could not drop inconsistent sequence table');
318 | }
319 | }
320 | throw new Doctrine_Export_Exception('could not create sequence table');
321 | }
322 |
323 | /**
324 | * drop existing sequence
325 | *
326 | * @param string $sequenceName name of the sequence to be dropped
327 | * @return string
328 | */
329 | public function dropSequenceSql($sequenceName)
330 | {
331 | $sequenceName = $this->conn->quoteIdentifier($this->conn->getSequenceName($sequenceName), true);
332 |
333 | return 'DROP TABLE ' . $sequenceName;
334 | }
335 |
336 | public function alterTableSql($name, array $changes, $check = false)
337 | {
338 | if ( ! $name) {
339 | throw new Doctrine_Export_Exception('no valid table name specified');
340 | }
341 | foreach ($changes as $changeName => $change) {
342 | switch ($changeName) {
343 | case 'add':
344 | case 'change':
345 | case 'rename':
346 | case 'name':
347 | break;
348 | default:
349 | throw new Doctrine_Export_Exception('change type "' . $changeName . '" not yet supported');
350 | }
351 | }
352 |
353 | if ($check) {
354 | return true;
355 | }
356 |
357 | $query = '';
358 | if ( ! empty($changes['name'])) {
359 | $change_name = $this->conn->quoteIdentifier($changes['name']);
360 | $query .= 'RENAME TO ' . $change_name;
361 | }
362 |
363 | if ( ! empty($changes['add']) && is_array($changes['add'])) {
364 | foreach ($changes['add'] as $fieldName => $field) {
365 | if ($query) {
366 | $query.= ', ';
367 | }
368 | $query.= 'ADD ' . $this->getDeclaration($fieldName, $field);
369 | }
370 | }
371 |
372 | $rename = array();
373 | if ( ! empty($changes['rename']) && is_array($changes['rename'])) {
374 | foreach ($changes['rename'] as $fieldName => $field) {
375 | $rename[$field['name']] = $fieldName;
376 | }
377 | }
378 |
379 | if ( ! empty($changes['change']) && is_array($changes['change'])) {
380 | foreach ($changes['change'] as $fieldName => $field) {
381 | if ($query) {
382 | $query.= ', ';
383 | }
384 | if (isset($rename[$fieldName])) {
385 | $oldFieldName = $rename[$fieldName];
386 | unset($rename[$fieldName]);
387 | } else {
388 | $oldFieldName = $fieldName;
389 | }
390 | $oldFieldName = $this->conn->quoteIdentifier($oldFieldName, true);
391 | $query .= 'CHANGE ' . $oldFieldName . ' '
392 | . $this->getDeclaration($fieldName, $field['definition']);
393 | }
394 | }
395 |
396 | if ( ! empty($rename) && is_array($rename)) {
397 | foreach ($rename as $renameName => $renamedField) {
398 | if ($query) {
399 | $query.= ', ';
400 | }
401 | $field = $changes['rename'][$renamedField];
402 | $renamedField = $this->conn->quoteIdentifier($renamedField, true);
403 | $query .= 'CHANGE ' . $renamedField . ' '
404 | . $this->getDeclaration($field['name'], $field['definition']);
405 | }
406 | }
407 |
408 | if ( ! $query) {
409 | return false;
410 | }
411 |
412 | $name = $this->conn->quoteIdentifier($name, true);
413 |
414 | return 'ALTER TABLE ' . $name . ' ' . $query;
415 | }
416 | }