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