]> git.agnieray.net Git - galette.git/blob - galette/lib/Galette/Core/Db.php
405465ac47190ae37b1d6f5fcef3297255c38724
[galette.git] / galette / lib / Galette / Core / Db.php
1 <?php
2 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
3
4 /**
5 * Zend Db wrapper
6 *
7 * PHP version 5
8 *
9 * Copyright © 2011-2014 The Galette Team
10 *
11 * This file is part of Galette (http://galette.tuxfamily.org).
12 *
13 * Galette is free software: you can redistribute it and/or modify
14 * it under the terms of the GNU General Public License as published by
15 * the Free Software Foundation, either version 3 of the License, or
16 * (at your option) any later version.
17 *
18 * Galette is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
22 *
23 * You should have received a copy of the GNU General Public License
24 * along with Galette. If not, see <http://www.gnu.org/licenses/>.
25 *
26 * @category Core
27 * @package Galette
28 *
29 * @author Johan Cwiklinski <johan@x-tnd.be>
30 * @copyright 2011-2014 The Galette Team
31 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version
32 * @version SVN: $Id$
33 * @link http://galette.tuxfamily.org
34 * @since Available since 0.7dev - 2011-07-27
35 */
36
37 namespace Galette\Core;
38
39 use Analog\Analog;
40 use Laminas\Db\Adapter\Adapter;
41 use Laminas\Db\Sql\Sql;
42
43 /**
44 * Zend Db wrapper
45 *
46 * @category Core
47 * @name Db
48 * @package Galette
49 * @author Johan Cwiklinski <johan@x-tnd.be>
50 * @copyright 2011-2014 The Galette Team
51 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version
52 * @link http://framework.zend.com/apidoc/2.2/namespaces/Zend.Db.html
53 * @since Available since 0.7dev - 2011-07-27
54 */
55 class Db
56 {
57 private $db;
58 private $type_db;
59 private $sql;
60 private $options;
61
62 const MYSQL = 'mysql';
63 const PGSQL = 'pgsql';
64
65 const MYSQL_DEFAULT_PORT = 3306;
66 const PGSQL_DEFAULT_PORT = 5432;
67
68 /**
69 * Main constructor
70 *
71 * @param array $dsn Connection information
72 * If not set, database constants will be used.
73 */
74 public function __construct($dsn = null)
75 {
76 $_type = null;
77
78 if ($dsn !== null && is_array($dsn)) {
79 $_type_db = $dsn['TYPE_DB'];
80 $_host_db = $dsn['HOST_DB'];
81 $_port_db = $dsn['PORT_DB'];
82 $_user_db = $dsn['USER_DB'];
83 $_pwd_db = $dsn['PWD_DB'];
84 $_name_db = $dsn['NAME_DB'];
85 } else {
86 $_type_db = TYPE_DB;
87 $_host_db = HOST_DB;
88 $_port_db = PORT_DB;
89 $_user_db = USER_DB;
90 $_pwd_db = PWD_DB;
91 $_name_db = NAME_DB;
92 }
93
94 try {
95 if ($_type_db === self::MYSQL) {
96 $_type = 'Pdo_Mysql';
97 } elseif ($_type_db === self::PGSQL) {
98 $_type = 'Pdo_Pgsql';
99 } else {
100 throw new \Exception("Type $_type_db not known (dsn: $_user_db@$_host_db(:$_port_db)/$_name_db)");
101 }
102
103 $this->type_db = $_type_db;
104 $this->options = array(
105 'driver' => $_type,
106 'hostname' => $_host_db,
107 'port' => $_port_db,
108 'username' => $_user_db,
109 'password' => $_pwd_db,
110 'database' => $_name_db
111 );
112 if ($_type_db === self::MYSQL && !defined('NON_UTF_DBCONNECT')) {
113 $this->options['charset'] = 'utf8';
114 }
115
116 $this->doConnection();
117 } catch (\Exception $e) {
118 // perhaps factory() failed to load the specified Adapter class
119 Analog::log(
120 '[Db] Error (' . $e->getCode() . '|' .
121 $e->getMessage() . ')',
122 Analog::ALERT
123 );
124 throw $e;
125 }
126 }
127
128 /**
129 * Do database connection
130 *
131 * @return void
132 */
133 private function doConnection()
134 {
135 $this->db = new Adapter($this->options);
136 $this->db->getDriver()->getConnection()->connect();
137 $this->sql = new Sql($this->db);
138
139 if (!$this->isPostgres()) {
140 $this->db->query("SET GLOBAL sql_mode=(SELECT REPLACE(@@sql_mode,'ONLY_FULL_GROUP_BY',''));");
141 }
142
143 Analog::log(
144 '[Db] Database connection was successfull!',
145 Analog::DEBUG
146 );
147 }
148
149 /**
150 * To store Db in session
151 *
152 * @return array
153 */
154 public function __sleep()
155 {
156 return ['type_db', 'options'];
157 }
158
159 /**
160 * Connect again to the database on wakeup
161 *
162 * @return void
163 */
164 public function __wakeup()
165 {
166 $this->doConnection();
167 }
168
169 /**
170 * Retrieve current database version
171 *
172 * @param boolean $check_table Check if table exists, defaults to false
173 *
174 * @return float
175 *
176 * @throw LogicException
177 */
178 public function getDbVersion($check_table = false)
179 {
180 try {
181 if ($check_table === true) {
182 $exists = count($this->getTables(PREFIX_DB . 'database')) === 1;
183 } else {
184 $exists = true;
185 }
186
187 if ($exists === true) {
188 $select = $this->select('database');
189 $select->columns(
190 array('version')
191 )->limit(1);
192
193 $results = $this->execute($select);
194 $result = $results->current();
195 return number_format(
196 $result->version,
197 3,
198 '.',
199 ''
200 );
201 } else {
202 return 0.63;
203 }
204 } catch (\Exception $e) {
205 Analog::log(
206 'Cannot check database version: ' . $e->getMessage(),
207 Analog::ERROR
208 );
209 throw new \LogicException('Cannot check database version');
210 }
211 }
212
213 /**
214 * Check if database version suits our needs
215 *
216 * @return boolean
217 */
218 public function checkDbVersion()
219 {
220 if (GALETTE_MODE === 'DEV') {
221 Analog::log(
222 'Database version not checked in DEV mode.',
223 Analog::INFO
224 );
225 return true;
226 }
227
228 try {
229 return $this->getDbVersion() === GALETTE_DB_VERSION;
230 } catch (\LogicException $e) {
231 return false;
232 }
233 }
234
235 /**
236 * Peform a select query on the whole table
237 *
238 * @param string $table Table name
239 *
240 * @return array
241 */
242 public function selectAll($table)
243 {
244 return $this->db->query(
245 'SELECT * FROM ' . PREFIX_DB . $table,
246 Adapter::QUERY_MODE_EXECUTE
247 );
248 }
249
250 /**
251 * Test if database can be contacted. Mostly used for installation
252 *
253 * @param string $type db type
254 * @param string $user database's user
255 * @param string $pass password for the user
256 * @param string $host which host we want to connect to
257 * @param string $port which tcp port we want to connect to
258 * @param string $db database name
259 *
260 * @return true|array true if connection was successfull,
261 * an array with some infos otherwise
262 */
263 public static function testConnectivity(
264 $type,
265 $user = null,
266 $pass = null,
267 $host = null,
268 $port = null,
269 $db = null
270 ) {
271 $_type = null;
272 try {
273 if ($type === self::MYSQL) {
274 $_type = 'Pdo_Mysql';
275 } elseif ($type === self::PGSQL) {
276 $_type = 'Pdo_Pgsql';
277 } else {
278 throw new \Exception;
279 }
280
281 $_options = array(
282 'driver' => $_type,
283 'hostname' => $host,
284 'port' => $port,
285 'username' => $user,
286 'password' => $pass,
287 'database' => $db
288 );
289
290 $_db = new Adapter($_options);
291 $_db->getDriver()->getConnection()->connect();
292
293 Analog::log(
294 '[' . __METHOD__ . '] Database connection was successfull!',
295 Analog::DEBUG
296 );
297 return true;
298 } catch (\Exception $e) {
299 // perhaps failed to load the specified Adapter class
300 Analog::log(
301 '[' . __METHOD__ . '] Connection error (' . $e->getCode() . '|' .
302 $e->getMessage() . ')',
303 Analog::ALERT
304 );
305 return $e;
306 }
307 }
308
309 /**
310 * Drop test table if it exists, so we can make all checks.
311 *
312 * @return void
313 */
314 public function dropTestTable()
315 {
316 try {
317 $this->db->query('DROP TABLE IF EXISTS galette_test');
318 Analog::log('Test table successfully dropped.', Analog::DEBUG);
319 } catch (\Exception $e) {
320 Analog::log(
321 'Cannot drop test table! ' . $e->getMessage(),
322 Analog::WARNING
323 );
324 }
325 }
326
327 /**
328 * Checks GRANT access for install time
329 *
330 * @param char $mode are we at install time (i) or update time (u) ?
331 *
332 * @return array containing each test. Each array entry could
333 * be either true or contains an exception of false if test did not
334 * ran.
335 */
336 public function grantCheck($mode = 'i')
337 {
338 Analog::log(
339 'Check for database rights (mode ' . $mode . ')',
340 Analog::DEBUG
341 );
342 $stop = false;
343 $results = array(
344 'create' => false,
345 'insert' => false,
346 'select' => false,
347 'update' => false,
348 'delete' => false,
349 'drop' => false
350 );
351 if ($mode === 'u') {
352 $results['alter'] = false;
353 }
354
355 //can Galette CREATE tables?
356 try {
357 $sql = 'CREATE TABLE galette_test (
358 test_id INTEGER NOT NULL,
359 test_text VARCHAR(20)
360 )';
361 $this->db->query($sql, Adapter::QUERY_MODE_EXECUTE);
362 $results['create'] = true;
363 } catch (\Exception $e) {
364 Analog::log('Cannot CREATE TABLE', Analog::WARNING);
365 //if we cannot create tables, we cannot check other permissions
366 $stop = true;
367 $results['create'] = $e;
368 }
369
370 //all those tests need the table to exists
371 if (!$stop) {
372 if ($mode == 'u') {
373 //can Galette ALTER tables? (only for update mode)
374 try {
375 $sql = 'ALTER TABLE galette_test ALTER test_text SET DEFAULT \'nothing\'';
376 $this->db->query($sql, Adapter::QUERY_MODE_EXECUTE);
377 $results['alter'] = true;
378 } catch (\Exception $e) {
379 Analog::log(
380 'Cannot ALTER TABLE | ' . $e->getMessage(),
381 Analog::WARNING
382 );
383 $results['alter'] = $e;
384 }
385 }
386
387 //can Galette INSERT records ?
388 $values = array(
389 'test_id' => 1,
390 'test_text' => 'a simple text'
391 );
392 try {
393 $insert = $this->sql->insert('galette_test');
394 $insert->values($values);
395
396 $res = $this->execute($insert);
397
398 if ($res->count() === 1) {
399 $results['insert'] = true;
400 } else {
401 throw new \Exception('No row inserted!');
402 }
403 } catch (\Exception $e) {
404 Analog::log(
405 'Cannot INSERT records | ' . $e->getMessage(),
406 Analog::WARNING
407 );
408 //if we cannot insert records, some others tests cannot be done
409 $stop = true;
410 $results['insert'] = $e;
411 }
412
413 //all those tests need that the first record exists
414 if (!$stop) {
415 //can Galette UPDATE records ?
416 $values = array(
417 'test_text' => 'another simple text'
418 );
419 try {
420 $update = $this->sql->update('galette_test');
421 $update->set($values)->where(
422 array('test_id' => 1)
423 );
424 $res = $this->execute($update);
425 if ($res->count() === 1) {
426 $results['update'] = true;
427 } else {
428 throw new \Exception('No row updated!');
429 }
430 } catch (\Exception $e) {
431 Analog::log(
432 'Cannot UPDATE records | ' . $e->getMessage(),
433 Analog::WARNING
434 );
435 $results['update'] = $e;
436 }
437
438 //can Galette SELECT records ?
439 try {
440 $pass = false;
441 $select = $this->sql->select('galette_test');
442 $select->where('test_id = 1');
443 $res = $this->execute($select);
444 $pass = $res->count() === 1;
445
446 if ($pass) {
447 $results['select'] = true;
448 } else {
449 throw new \Exception('Select is empty!');
450 }
451 } catch (\Exception $e) {
452 Analog::log(
453 'Cannot SELECT records | ' . $e->getMessage(),
454 Analog::WARNING
455 );
456 $results['select'] = $e;
457 }
458
459 //can Galette DELETE records ?
460 try {
461 $delete = $this->sql->delete('galette_test');
462 $delete->where(array('test_id' => 1));
463 $this->execute($delete);
464 $results['delete'] = true;
465 } catch (\Exception $e) {
466 Analog::log(
467 'Cannot DELETE records | ' . $e->getMessage(),
468 Analog::WARNING
469 );
470 $results['delete'] = $e;
471 }
472 }
473
474 //can Galette DROP tables ?
475 try {
476 $sql = 'DROP TABLE galette_test';
477 $this->db->query($sql, Adapter::QUERY_MODE_EXECUTE);
478 $results['drop'] = true;
479 } catch (\Exception $e) {
480 Analog::log(
481 'Cannot DROP TABLE | ' . $e->getMessage(),
482 Analog::WARNING
483 );
484 $results['drop'] = $e;
485 }
486 }
487
488 return $results;
489 }
490
491 /**
492 * Get a list of Galette's tables
493 *
494 * @param string $prefix Specified table prefix, PREFIX_DB if null
495 *
496 * @return array
497 */
498 public function getTables($prefix = null)
499 {
500 $metadata = new \Laminas\Db\Metadata\Metadata($this->db);
501 $tmp_tables_list = $metadata->getTableNames();
502
503 if ($prefix === null) {
504 $prefix = PREFIX_DB;
505 }
506
507 $tables_list = array();
508 //filter table_list: we only want PREFIX_DB tables
509 foreach ($tmp_tables_list as $t) {
510 if (preg_match('/^' . $prefix . '/', $t)) {
511 $tables_list[] = $t;
512 }
513 }
514 return $tables_list;
515 }
516
517 /**
518 * Get columns for a specified table
519 *
520 * @param string $table Table name
521 *
522 * @return array
523 */
524 public function getColumns($table)
525 {
526 $metadata = new \Laminas\Db\Metadata\Metadata($this->db);
527 $table = $metadata->getTable(PREFIX_DB . $table);
528 return $table->getColumns();
529 }
530
531 /**
532 * Converts recursively database to UTF-8
533 *
534 * @param string $prefix Specified table prefix
535 * @param boolean $content_only Proceed only content (no table conversion)
536 *
537 * @return void
538 */
539 public function convertToUTF($prefix = null, $content_only = false)
540 {
541 if ($this->isPostgres()) {
542 Analog::log(
543 'Cannot change encoding on PostgreSQL database',
544 Analog::INFO
545 );
546 return;
547 }
548 if ($prefix === null) {
549 $prefix = PREFIX_DB;
550 }
551
552 try {
553 $this->connection->beginTransaction();
554
555 $tables = $this->getTables($prefix);
556
557 foreach ($tables as $table) {
558 if ($content_only === false) {
559 //Change whole table charset
560 //CONVERT TO instruction will take care of each fields,
561 //but converting data stay our problem.
562 $query = 'ALTER TABLE ' . $table .
563 ' CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci';
564
565 $this->db->query(
566 $query,
567 Adapter::QUERY_MODE_EXECUTE
568 );
569
570 Analog::log(
571 'Charset successfully changed for table `' . $table . '`',
572 Analog::DEBUG
573 );
574 }
575
576 //Data conversion
577 if ($table != $prefix . 'pictures') {
578 $this->convertContentToUTF($prefix, $table);
579 }
580 }
581 $this->connection->commit();
582 } catch (\Exception $e) {
583 $this->connection->rollBack();
584 Analog::log(
585 'An error occurred while converting to utf table ' .
586 $table . ' (' . $e->getMessage() . ')',
587 Analog::ERROR
588 );
589 }
590 }
591
592 /**
593 * Converts dtabase content to UTF-8
594 *
595 * @param string $prefix Specified table prefix
596 * @param string $table the table we want to convert datas from
597 *
598 * @return void
599 */
600 private function convertContentToUTF($prefix, $table)
601 {
602
603 try {
604 $query = 'SET NAMES latin1';
605 $this->db->query(
606 $query,
607 Adapter::QUERY_MODE_EXECUTE
608 );
609 } catch (\Exception $e) {
610 Analog::log(
611 'Cannot SET NAMES on table `' . $table . '`. ' .
612 $e->getMessage(),
613 Analog::ERROR
614 );
615 }
616
617 try {
618 $metadata = new \Laminas\Db\Metadata\Metadata($this->db);
619 $tbl = $metadata->getTable($table);
620 $columns = $tbl->getColumns();
621 $constraints = $tbl->getConstraints();
622 $pkeys = array();
623
624 foreach ($constraints as $constraint) {
625 if ($constraint->getType() === 'PRIMARY KEY') {
626 $pkeys = $constraint->getColumns();
627 }
628 }
629
630 if (count($pkeys) == 0) {
631 //no primary key! How to do an update without that?
632 //Prior to 0.7, l10n and dynamic_fields tables does not
633 //contains any primary key. Since encoding conversion is done
634 //_before_ the SQL upgrade, we'll have to manually
635 //check these ones
636 if (preg_match('/' . $prefix . 'dynamic_fields/', $table) !== 0) {
637 $pkeys = array(
638 'item_id',
639 'field_id',
640 'field_form',
641 'val_index'
642 );
643 } elseif (preg_match('/' . $prefix . 'l10n/', $table) !== 0) {
644 $pkeys = array(
645 'text_orig',
646 'text_locale'
647 );
648 } else {
649 //not a know case, we do not perform any update.
650 throw new \Exception(
651 'Cannot define primary key for table `' . $table .
652 '`, aborting'
653 );
654 }
655 }
656
657 $select = $this->sql->select($table);
658 $results = $this->execute($select);
659
660 foreach ($results as $row) {
661 $data = array();
662 $where = array();
663
664 //build where
665 foreach ($pkeys as $k) {
666 $where[] = $k . ' = "' . $row->$k . '"';
667 }
668
669 //build data
670 foreach ($row as $key => $value) {
671 $data[$key] = $value;
672 }
673
674 //finally, update data!
675 $update = $this->sql->update($table);
676 $update->set($data)->where($where);
677 $this->execute($update);
678 }
679 } catch (\Exception $e) {
680 Analog::log(
681 'An error occurred while converting contents to UTF-8 for table ' .
682 $table . ' (' . $e->getMessage() . ')',
683 Analog::ERROR
684 );
685 }
686 }
687
688 /**
689 * Is current database using Postgresql?
690 *
691 * @return boolean
692 */
693 public function isPostgres()
694 {
695 return $this->type_db === self::PGSQL;
696 }
697
698 /**
699 * Instanciate a select query
700 *
701 * @param string $table Table name, without prefix
702 * @param string $alias Tables alias, optionnal
703 *
704 * @return Select
705 */
706 public function select($table, $alias = null)
707 {
708 if ($alias === null) {
709 return $this->sql->select(
710 PREFIX_DB . $table
711 );
712 } else {
713 return $this->sql->select(
714 array(
715 $alias => PREFIX_DB . $table
716 )
717 );
718 }
719 }
720
721 /**
722 * Instanciate an insert query
723 *
724 * @param string $table Table name, without prefix
725 *
726 * @return Insert
727 */
728 public function insert($table)
729 {
730 return $this->sql->insert(
731 PREFIX_DB . $table
732 );
733 }
734
735 /**
736 * Instanciate an update query
737 *
738 * @param string $table Table name, without prefix
739 *
740 * @return Update
741 */
742 public function update($table)
743 {
744 return $this->sql->update(
745 PREFIX_DB . $table
746 );
747 }
748
749 /**
750 * Instanciate a delete query
751 *
752 * @param string $table Table name, without prefix
753 *
754 * @return Delete
755 */
756 public function delete($table)
757 {
758 return $this->sql->delete(
759 PREFIX_DB . $table
760 );
761 }
762
763 /**
764 * Execute query string
765 *
766 * @param SqlInterface $sql SQL object
767 *
768 * @return Stmt
769 */
770 public function execute($sql)
771 {
772 try {
773 $query_string = $this->sql->getSqlStringForSqlObject($sql);
774 $this->_last_query = $query_string;
775 Analog::log(
776 'Executing query: ' . $query_string,
777 Analog::DEBUG
778 );
779 return $this->db->query(
780 $query_string,
781 Adapter::QUERY_MODE_EXECUTE
782 );
783 } catch (\Exception $e) {
784 $msg = 'Query error: ';
785 if (isset($query_string)) {
786 $msg .= $query_string;
787 }
788 Analog::log(
789 $msg . ' ' . $e->__toString(),
790 Analog::ERROR
791 );
792 if ($this->isDuplicateException($e)) {
793 throw new \OverflowException('Duplicate entry', 0, $e);
794 }
795 throw $e;
796 }
797 }
798
799 /**
800 * Global getter method
801 *
802 * @param string $name name of the variable we want to retrieve
803 *
804 * @return mixed
805 */
806 public function __get($name)
807 {
808 switch ($name) {
809 case 'db':
810 return $this->db;
811 break;
812 case 'sql':
813 return $this->sql;
814 break;
815 case 'driver':
816 return $this->db->getDriver();
817 break;
818 case 'connection':
819 return $this->db->getDriver()->getConnection();
820 break;
821 case 'platform':
822 return $this->db->getPlatform();
823 break;
824 case 'query_string':
825 return $this->_last_query;
826 break;
827 case 'type_db':
828 return $this->type_db;
829 break;
830 }
831 }
832
833 /**
834 * Get database information
835 *
836 * @return array
837 */
838 public function getInfos()
839 {
840 $infos = [
841 'engine' => null,
842 'version' => null,
843 'size' => null,
844 'log_size' => null,
845 'sql_mode' => ''
846 ];
847
848 if ($this->isPostgres()) {
849 $infos['engine'] = 'PostgreSQL';
850 $sql = 'SHOW server_version';
851 $result = $this->db->query($sql, Adapter::QUERY_MODE_EXECUTE)
852 ->current();
853 $infos['version'] = $result['server_version'];
854
855 $sql = 'SELECT pg_database_size(\'' . NAME_DB . '\')';
856 $result = $this->db->query($sql, Adapter::QUERY_MODE_EXECUTE)
857 ->current();
858 $infos['size'] = (string)round($result['pg_database_size']/1024/1024);
859 } else {
860 $sql = 'SELECT @@sql_mode as mode, @@version AS version, @@version_comment AS version_comment';
861 $result = $this->db->query($sql, Adapter::QUERY_MODE_EXECUTE)
862 ->current();
863
864 $infos['engine'] = $result['version_comment'];
865 $infos['version'] = $result['version'];
866 $infos['sql_mode'] = $result['mode'];
867
868 $size_sql = 'SELECT ROUND(SUM(data_length + index_length) / 1024 / 1024, 1) AS dbsize' .
869 ' FROM information_schema.tables WHERE table_schema="' . NAME_DB . '"';
870 $result = $this->db->query($size_sql, Adapter::QUERY_MODE_EXECUTE)
871 ->current();
872
873 $infos['size'] = $result['dbsize'];
874 }
875
876 return $infos;
877 }
878
879 /**
880 * Handle sequence on PostgreSQL
881 *
882 * When inserting a value on a field with a sequence,
883 * this one is not incremented.
884 * This happens when installing system values (for status, titles, ...)
885 *
886 * @see https://bugs.galette.eu/issues/1158
887 * @see https://bugs.galette.eu/issues/1374
888 *
889 * @param sting $table Table name
890 * @param intger $expected Expected value
891 *
892 * @return void
893 */
894 public function handleSequence($table, $expected)
895 {
896 if ($this->isPostgres()) {
897 //check for Postgres sequence
898 //see https://bugs.galette.eu/issues/1158
899 //see https://bugs.galette.eu/issues/1374
900 $seq = $table . '_id_seq';
901
902 $select = $this->select($seq);
903 $select->columns(['last_value']);
904 $results = $this->execute($select);
905 $result = $results->current();
906 if ($result->last_value < $expected) {
907 $this->db->query(
908 'SELECT setval(\'' . PREFIX_DB . $seq . '\', ' . $expected . ')',
909 Adapter::QUERY_MODE_EXECUTE
910 );
911 }
912 }
913 }
914
915 /**
916 * Check if current exception is on a duplicate key
917 *
918 * @param Exception $exception Exception to check
919 *
920 * @return boolean
921 */
922 public function isDuplicateException($exception)
923 {
924 return $exception instanceof \PDOException
925 && (
926 (!$this->isPostgres() && $exception->getCode() == 23000)
927 || ($this->isPostgres() && $exception->getCode() == 23505)
928 )
929 ;
930 }
931 }