vendor/doctrine/dbal/src/Platforms/PostgreSQLPlatform.php line 806

Open in your IDE?
  1. <?php
  2. namespace Doctrine\DBAL\Platforms;
  3. use Doctrine\DBAL\Schema\Column;
  4. use Doctrine\DBAL\Schema\ColumnDiff;
  5. use Doctrine\DBAL\Schema\ForeignKeyConstraint;
  6. use Doctrine\DBAL\Schema\Identifier;
  7. use Doctrine\DBAL\Schema\Index;
  8. use Doctrine\DBAL\Schema\Sequence;
  9. use Doctrine\DBAL\Schema\TableDiff;
  10. use Doctrine\DBAL\Types\BigIntType;
  11. use Doctrine\DBAL\Types\BinaryType;
  12. use Doctrine\DBAL\Types\BlobType;
  13. use Doctrine\DBAL\Types\IntegerType;
  14. use Doctrine\DBAL\Types\Type;
  15. use Doctrine\Deprecations\Deprecation;
  16. use UnexpectedValueException;
  17. use function array_diff;
  18. use function array_merge;
  19. use function array_unique;
  20. use function array_values;
  21. use function count;
  22. use function explode;
  23. use function implode;
  24. use function in_array;
  25. use function is_array;
  26. use function is_bool;
  27. use function is_numeric;
  28. use function is_string;
  29. use function sprintf;
  30. use function strpos;
  31. use function strtolower;
  32. use function trim;
  33. /**
  34.  * Provides the behavior, features and SQL dialect of the PostgreSQL database platform of the oldest supported version.
  35.  */
  36. class PostgreSQLPlatform extends AbstractPlatform
  37. {
  38.     /** @var bool */
  39.     private $useBooleanTrueFalseStrings true;
  40.     /** @var string[][] PostgreSQL booleans literals */
  41.     private $booleanLiterals = [
  42.         'true' => [
  43.             't',
  44.             'true',
  45.             'y',
  46.             'yes',
  47.             'on',
  48.             '1',
  49.         ],
  50.         'false' => [
  51.             'f',
  52.             'false',
  53.             'n',
  54.             'no',
  55.             'off',
  56.             '0',
  57.         ],
  58.     ];
  59.     /**
  60.      * PostgreSQL has different behavior with some drivers
  61.      * with regard to how booleans have to be handled.
  62.      *
  63.      * Enables use of 'true'/'false' or otherwise 1 and 0 instead.
  64.      *
  65.      * @param bool $flag
  66.      *
  67.      * @return void
  68.      */
  69.     public function setUseBooleanTrueFalseStrings($flag)
  70.     {
  71.         $this->useBooleanTrueFalseStrings = (bool) $flag;
  72.     }
  73.     /**
  74.      * {@inheritDoc}
  75.      */
  76.     public function getSubstringExpression($string$start$length null)
  77.     {
  78.         if ($length === null) {
  79.             return 'SUBSTRING(' $string ' FROM ' $start ')';
  80.         }
  81.         return 'SUBSTRING(' $string ' FROM ' $start ' FOR ' $length ')';
  82.     }
  83.     /**
  84.      * {@inheritDoc}
  85.      *
  86.      * @deprecated Generate dates within the application.
  87.      */
  88.     public function getNowExpression()
  89.     {
  90.         Deprecation::trigger(
  91.             'doctrine/dbal',
  92.             'https://github.com/doctrine/dbal/pull/4753',
  93.             'PostgreSQLPlatform::getNowExpression() is deprecated. Generate dates within the application.'
  94.         );
  95.         return 'LOCALTIMESTAMP(0)';
  96.     }
  97.     /**
  98.      * {@inheritDoc}
  99.      */
  100.     public function getRegexpExpression()
  101.     {
  102.         return 'SIMILAR TO';
  103.     }
  104.     /**
  105.      * {@inheritDoc}
  106.      */
  107.     public function getLocateExpression($str$substr$startPos false)
  108.     {
  109.         if ($startPos !== false) {
  110.             $str $this->getSubstringExpression($str$startPos);
  111.             return 'CASE WHEN (POSITION(' $substr ' IN ' $str ') = 0) THEN 0'
  112.                 ' ELSE (POSITION(' $substr ' IN ' $str ') + ' $startPos ' - 1) END';
  113.         }
  114.         return 'POSITION(' $substr ' IN ' $str ')';
  115.     }
  116.     /**
  117.      * {@inheritdoc}
  118.      */
  119.     protected function getDateArithmeticIntervalExpression($date$operator$interval$unit)
  120.     {
  121.         if ($unit === DateIntervalUnit::QUARTER) {
  122.             $interval *= 3;
  123.             $unit      DateIntervalUnit::MONTH;
  124.         }
  125.         return '(' $date ' ' $operator ' (' $interval " || ' " $unit "')::interval)";
  126.     }
  127.     /**
  128.      * {@inheritDoc}
  129.      */
  130.     public function getDateDiffExpression($date1$date2)
  131.     {
  132.         return '(DATE(' $date1 ')-DATE(' $date2 '))';
  133.     }
  134.     public function getCurrentDatabaseExpression(): string
  135.     {
  136.         return 'CURRENT_DATABASE()';
  137.     }
  138.     /**
  139.      * {@inheritDoc}
  140.      */
  141.     public function supportsSequences()
  142.     {
  143.         return true;
  144.     }
  145.     /**
  146.      * {@inheritDoc}
  147.      */
  148.     public function supportsSchemas()
  149.     {
  150.         return true;
  151.     }
  152.     /**
  153.      * {@inheritdoc}
  154.      */
  155.     public function getDefaultSchemaName()
  156.     {
  157.         return 'public';
  158.     }
  159.     /**
  160.      * {@inheritDoc}
  161.      */
  162.     public function supportsIdentityColumns()
  163.     {
  164.         return true;
  165.     }
  166.     /**
  167.      * {@inheritdoc}
  168.      */
  169.     public function supportsPartialIndexes()
  170.     {
  171.         return true;
  172.     }
  173.     /**
  174.      * {@inheritdoc}
  175.      */
  176.     public function usesSequenceEmulatedIdentityColumns()
  177.     {
  178.         return true;
  179.     }
  180.     /**
  181.      * {@inheritdoc}
  182.      */
  183.     public function getIdentitySequenceName($tableName$columnName)
  184.     {
  185.         return $tableName '_' $columnName '_seq';
  186.     }
  187.     /**
  188.      * {@inheritDoc}
  189.      */
  190.     public function supportsCommentOnStatement()
  191.     {
  192.         return true;
  193.     }
  194.     /**
  195.      * {@inheritDoc}
  196.      */
  197.     public function hasNativeGuidType()
  198.     {
  199.         return true;
  200.     }
  201.     /**
  202.      * {@inheritDoc}
  203.      */
  204.     public function getListDatabasesSQL()
  205.     {
  206.         return 'SELECT datname FROM pg_database';
  207.     }
  208.     /**
  209.      * {@inheritDoc}
  210.      *
  211.      * @deprecated Use {@see PostgreSQLSchemaManager::listSchemaNames()} instead.
  212.      */
  213.     public function getListNamespacesSQL()
  214.     {
  215.         Deprecation::triggerIfCalledFromOutside(
  216.             'doctrine/dbal',
  217.             'https://github.com/doctrine/dbal/issues/4503',
  218.             'PostgreSQLPlatform::getListNamespacesSQL() is deprecated,'
  219.                 ' use PostgreSQLSchemaManager::listSchemaNames() instead.'
  220.         );
  221.         return "SELECT schema_name AS nspname
  222.                 FROM   information_schema.schemata
  223.                 WHERE  schema_name NOT LIKE 'pg\_%'
  224.                 AND    schema_name != 'information_schema'";
  225.     }
  226.     /**
  227.      * {@inheritDoc}
  228.      */
  229.     public function getListSequencesSQL($database)
  230.     {
  231.         return "SELECT sequence_name AS relname,
  232.                        sequence_schema AS schemaname
  233.                 FROM   information_schema.sequences
  234.                 WHERE  sequence_schema NOT LIKE 'pg\_%'
  235.                 AND    sequence_schema != 'information_schema'";
  236.     }
  237.     /**
  238.      * {@inheritDoc}
  239.      */
  240.     public function getListTablesSQL()
  241.     {
  242.         return "SELECT quote_ident(table_name) AS table_name,
  243.                        table_schema AS schema_name
  244.                 FROM   information_schema.tables
  245.                 WHERE  table_schema NOT LIKE 'pg\_%'
  246.                 AND    table_schema != 'information_schema'
  247.                 AND    table_name != 'geometry_columns'
  248.                 AND    table_name != 'spatial_ref_sys'
  249.                 AND    table_type != 'VIEW'";
  250.     }
  251.     /**
  252.      * {@inheritDoc}
  253.      */
  254.     public function getListViewsSQL($database)
  255.     {
  256.         return 'SELECT quote_ident(table_name) AS viewname,
  257.                        table_schema AS schemaname,
  258.                        view_definition AS definition
  259.                 FROM   information_schema.views
  260.                 WHERE  view_definition IS NOT NULL';
  261.     }
  262.     /**
  263.      * @param string      $table
  264.      * @param string|null $database
  265.      *
  266.      * @return string
  267.      */
  268.     public function getListTableForeignKeysSQL($table$database null)
  269.     {
  270.         return 'SELECT quote_ident(r.conname) as conname, pg_catalog.pg_get_constraintdef(r.oid, true) as condef
  271.                   FROM pg_catalog.pg_constraint r
  272.                   WHERE r.conrelid =
  273.                   (
  274.                       SELECT c.oid
  275.                       FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n
  276.                       WHERE ' $this->getTableWhereClause($table) . " AND n.oid = c.relnamespace
  277.                   )
  278.                   AND r.contype = 'f'";
  279.     }
  280.     /**
  281.      * {@inheritDoc}
  282.      */
  283.     public function getListTableConstraintsSQL($table)
  284.     {
  285.         $table = new Identifier($table);
  286.         $table $this->quoteStringLiteral($table->getName());
  287.         return sprintf(
  288.             <<<'SQL'
  289. SELECT
  290.     quote_ident(relname) as relname
  291. FROM
  292.     pg_class
  293. WHERE oid IN (
  294.     SELECT indexrelid
  295.     FROM pg_index, pg_class
  296.     WHERE pg_class.relname = %s
  297.         AND pg_class.oid = pg_index.indrelid
  298.         AND (indisunique = 't' OR indisprimary = 't')
  299.     )
  300. SQL
  301.             ,
  302.             $table
  303.         );
  304.     }
  305.     /**
  306.      * {@inheritDoc}
  307.      *
  308.      * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaPgsqlReader.html
  309.      */
  310.     public function getListTableIndexesSQL($table$database null)
  311.     {
  312.         return 'SELECT quote_ident(relname) as relname, pg_index.indisunique, pg_index.indisprimary,
  313.                        pg_index.indkey, pg_index.indrelid,
  314.                        pg_get_expr(indpred, indrelid) AS where
  315.                  FROM pg_class, pg_index
  316.                  WHERE oid IN (
  317.                     SELECT indexrelid
  318.                     FROM pg_index si, pg_class sc, pg_namespace sn
  319.                     WHERE ' $this->getTableWhereClause($table'sc''sn') . '
  320.                     AND sc.oid=si.indrelid AND sc.relnamespace = sn.oid
  321.                  ) AND pg_index.indexrelid = oid';
  322.     }
  323.     /**
  324.      * @param string $table
  325.      * @param string $classAlias
  326.      * @param string $namespaceAlias
  327.      */
  328.     private function getTableWhereClause($table$classAlias 'c'$namespaceAlias 'n'): string
  329.     {
  330.         $whereClause $namespaceAlias ".nspname NOT IN ('pg_catalog', 'information_schema', 'pg_toast') AND ";
  331.         if (strpos($table'.') !== false) {
  332.             [$schema$table] = explode('.'$table);
  333.             $schema           $this->quoteStringLiteral($schema);
  334.         } else {
  335.             $schema 'ANY(current_schemas(false))';
  336.         }
  337.         $table = new Identifier($table);
  338.         $table $this->quoteStringLiteral($table->getName());
  339.         return $whereClause sprintf(
  340.             '%s.relname = %s AND %s.nspname = %s',
  341.             $classAlias,
  342.             $table,
  343.             $namespaceAlias,
  344.             $schema
  345.         );
  346.     }
  347.     /**
  348.      * {@inheritDoc}
  349.      */
  350.     public function getListTableColumnsSQL($table$database null)
  351.     {
  352.         return "SELECT
  353.                     a.attnum,
  354.                     quote_ident(a.attname) AS field,
  355.                     t.typname AS type,
  356.                     format_type(a.atttypid, a.atttypmod) AS complete_type,
  357.                     (SELECT tc.collcollate FROM pg_catalog.pg_collation tc WHERE tc.oid = a.attcollation) AS collation,
  358.                     (SELECT t1.typname FROM pg_catalog.pg_type t1 WHERE t1.oid = t.typbasetype) AS domain_type,
  359.                     (SELECT format_type(t2.typbasetype, t2.typtypmod) FROM
  360.                       pg_catalog.pg_type t2 WHERE t2.typtype = 'd' AND t2.oid = a.atttypid) AS domain_complete_type,
  361.                     a.attnotnull AS isnotnull,
  362.                     (SELECT 't'
  363.                      FROM pg_index
  364.                      WHERE c.oid = pg_index.indrelid
  365.                         AND pg_index.indkey[0] = a.attnum
  366.                         AND pg_index.indisprimary = 't'
  367.                     ) AS pri,
  368.                     (SELECT pg_get_expr(adbin, adrelid)
  369.                      FROM pg_attrdef
  370.                      WHERE c.oid = pg_attrdef.adrelid
  371.                         AND pg_attrdef.adnum=a.attnum
  372.                     ) AS default,
  373.                     (SELECT pg_description.description
  374.                         FROM pg_description WHERE pg_description.objoid = c.oid AND a.attnum = pg_description.objsubid
  375.                     ) AS comment
  376.                     FROM pg_attribute a, pg_class c, pg_type t, pg_namespace n
  377.                     WHERE " $this->getTableWhereClause($table'c''n') . '
  378.                         AND a.attnum > 0
  379.                         AND a.attrelid = c.oid
  380.                         AND a.atttypid = t.oid
  381.                         AND n.oid = c.relnamespace
  382.                     ORDER BY a.attnum';
  383.     }
  384.     /**
  385.      * {@inheritDoc}
  386.      */
  387.     public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey)
  388.     {
  389.         $query '';
  390.         if ($foreignKey->hasOption('match')) {
  391.             $query .= ' MATCH ' $foreignKey->getOption('match');
  392.         }
  393.         $query .= parent::getAdvancedForeignKeyOptionsSQL($foreignKey);
  394.         if ($foreignKey->hasOption('deferrable') && $foreignKey->getOption('deferrable') !== false) {
  395.             $query .= ' DEFERRABLE';
  396.         } else {
  397.             $query .= ' NOT DEFERRABLE';
  398.         }
  399.         if (
  400.             ($foreignKey->hasOption('feferred') && $foreignKey->getOption('feferred') !== false)
  401.             || ($foreignKey->hasOption('deferred') && $foreignKey->getOption('deferred') !== false)
  402.         ) {
  403.             $query .= ' INITIALLY DEFERRED';
  404.         } else {
  405.             $query .= ' INITIALLY IMMEDIATE';
  406.         }
  407.         return $query;
  408.     }
  409.     /**
  410.      * {@inheritDoc}
  411.      */
  412.     public function getAlterTableSQL(TableDiff $diff)
  413.     {
  414.         $sql         = [];
  415.         $commentsSQL = [];
  416.         $columnSql   = [];
  417.         foreach ($diff->addedColumns as $column) {
  418.             if ($this->onSchemaAlterTableAddColumn($column$diff$columnSql)) {
  419.                 continue;
  420.             }
  421.             $query 'ADD ' $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray());
  422.             $sql[] = 'ALTER TABLE ' $diff->getName($this)->getQuotedName($this) . ' ' $query;
  423.             $comment $this->getColumnComment($column);
  424.             if ($comment === null || $comment === '') {
  425.                 continue;
  426.             }
  427.             $commentsSQL[] = $this->getCommentOnColumnSQL(
  428.                 $diff->getName($this)->getQuotedName($this),
  429.                 $column->getQuotedName($this),
  430.                 $comment
  431.             );
  432.         }
  433.         foreach ($diff->removedColumns as $column) {
  434.             if ($this->onSchemaAlterTableRemoveColumn($column$diff$columnSql)) {
  435.                 continue;
  436.             }
  437.             $query 'DROP ' $column->getQuotedName($this);
  438.             $sql[] = 'ALTER TABLE ' $diff->getName($this)->getQuotedName($this) . ' ' $query;
  439.         }
  440.         foreach ($diff->changedColumns as $columnDiff) {
  441.             if ($this->onSchemaAlterTableChangeColumn($columnDiff$diff$columnSql)) {
  442.                 continue;
  443.             }
  444.             if ($this->isUnchangedBinaryColumn($columnDiff)) {
  445.                 continue;
  446.             }
  447.             $oldColumnName $columnDiff->getOldColumnName()->getQuotedName($this);
  448.             $column        $columnDiff->column;
  449.             if (
  450.                 $columnDiff->hasChanged('type')
  451.                 || $columnDiff->hasChanged('precision')
  452.                 || $columnDiff->hasChanged('scale')
  453.                 || $columnDiff->hasChanged('fixed')
  454.             ) {
  455.                 $type $column->getType();
  456.                 // SERIAL/BIGSERIAL are not "real" types and we can't alter a column to that type
  457.                 $columnDefinition                  $column->toArray();
  458.                 $columnDefinition['autoincrement'] = false;
  459.                 // here was a server version check before, but DBAL API does not support this anymore.
  460.                 $query 'ALTER ' $oldColumnName ' TYPE ' $type->getSQLDeclaration($columnDefinition$this);
  461.                 $sql[] = 'ALTER TABLE ' $diff->getName($this)->getQuotedName($this) . ' ' $query;
  462.             }
  463.             if ($columnDiff->hasChanged('default') || $this->typeChangeBreaksDefaultValue($columnDiff)) {
  464.                 $defaultClause $column->getDefault() === null
  465.                     ' DROP DEFAULT'
  466.                     ' SET' $this->getDefaultValueDeclarationSQL($column->toArray());
  467.                 $query         'ALTER ' $oldColumnName $defaultClause;
  468.                 $sql[]         = 'ALTER TABLE ' $diff->getName($this)->getQuotedName($this) . ' ' $query;
  469.             }
  470.             if ($columnDiff->hasChanged('notnull')) {
  471.                 $query 'ALTER ' $oldColumnName ' ' . ($column->getNotnull() ? 'SET' 'DROP') . ' NOT NULL';
  472.                 $sql[] = 'ALTER TABLE ' $diff->getName($this)->getQuotedName($this) . ' ' $query;
  473.             }
  474.             if ($columnDiff->hasChanged('autoincrement')) {
  475.                 if ($column->getAutoincrement()) {
  476.                     // add autoincrement
  477.                     $seqName $this->getIdentitySequenceName($diff->name$oldColumnName);
  478.                     $sql[] = 'CREATE SEQUENCE ' $seqName;
  479.                     $sql[] = "SELECT setval('" $seqName "', (SELECT MAX(" $oldColumnName ') FROM '
  480.                         $diff->getName($this)->getQuotedName($this) . '))';
  481.                     $query 'ALTER ' $oldColumnName " SET DEFAULT nextval('" $seqName "')";
  482.                     $sql[] = 'ALTER TABLE ' $diff->getName($this)->getQuotedName($this) . ' ' $query;
  483.                 } else {
  484.                     // Drop autoincrement, but do NOT drop the sequence. It might be re-used by other tables or have
  485.                     $query 'ALTER ' $oldColumnName ' DROP DEFAULT';
  486.                     $sql[] = 'ALTER TABLE ' $diff->getName($this)->getQuotedName($this) . ' ' $query;
  487.                 }
  488.             }
  489.             $newComment $this->getColumnComment($column);
  490.             $oldComment $this->getOldColumnComment($columnDiff);
  491.             if (
  492.                 $columnDiff->hasChanged('comment')
  493.                 || ($columnDiff->fromColumn !== null && $oldComment !== $newComment)
  494.             ) {
  495.                 $commentsSQL[] = $this->getCommentOnColumnSQL(
  496.                     $diff->getName($this)->getQuotedName($this),
  497.                     $column->getQuotedName($this),
  498.                     $newComment
  499.                 );
  500.             }
  501.             if (! $columnDiff->hasChanged('length')) {
  502.                 continue;
  503.             }
  504.             $query 'ALTER ' $oldColumnName ' TYPE '
  505.                 $column->getType()->getSQLDeclaration($column->toArray(), $this);
  506.             $sql[] = 'ALTER TABLE ' $diff->getName($this)->getQuotedName($this) . ' ' $query;
  507.         }
  508.         foreach ($diff->renamedColumns as $oldColumnName => $column) {
  509.             if ($this->onSchemaAlterTableRenameColumn($oldColumnName$column$diff$columnSql)) {
  510.                 continue;
  511.             }
  512.             $oldColumnName = new Identifier($oldColumnName);
  513.             $sql[] = 'ALTER TABLE ' $diff->getName($this)->getQuotedName($this) .
  514.                 ' RENAME COLUMN ' $oldColumnName->getQuotedName($this) . ' TO ' $column->getQuotedName($this);
  515.         }
  516.         $tableSql = [];
  517.         if (! $this->onSchemaAlterTable($diff$tableSql)) {
  518.             $sql array_merge($sql$commentsSQL);
  519.             $newName $diff->getNewName();
  520.             if ($newName !== false) {
  521.                 $sql[] = sprintf(
  522.                     'ALTER TABLE %s RENAME TO %s',
  523.                     $diff->getName($this)->getQuotedName($this),
  524.                     $newName->getQuotedName($this)
  525.                 );
  526.             }
  527.             $sql array_merge(
  528.                 $this->getPreAlterTableIndexForeignKeySQL($diff),
  529.                 $sql,
  530.                 $this->getPostAlterTableIndexForeignKeySQL($diff)
  531.             );
  532.         }
  533.         return array_merge($sql$tableSql$columnSql);
  534.     }
  535.     /**
  536.      * Checks whether a given column diff is a logically unchanged binary type column.
  537.      *
  538.      * Used to determine whether a column alteration for a binary type column can be skipped.
  539.      * Doctrine's {@see BinaryType} and {@see BlobType} are mapped to the same database column type on this platform
  540.      * as this platform does not have a native VARBINARY/BINARY column type. Therefore the comparator
  541.      * might detect differences for binary type columns which do not have to be propagated
  542.      * to database as there actually is no difference at database level.
  543.      */
  544.     private function isUnchangedBinaryColumn(ColumnDiff $columnDiff): bool
  545.     {
  546.         $columnType $columnDiff->column->getType();
  547.         if (! $columnType instanceof BinaryType && ! $columnType instanceof BlobType) {
  548.             return false;
  549.         }
  550.         $fromColumn $columnDiff->fromColumn instanceof Column $columnDiff->fromColumn null;
  551.         if ($fromColumn !== null) {
  552.             $fromColumnType $fromColumn->getType();
  553.             if (! $fromColumnType instanceof BinaryType && ! $fromColumnType instanceof BlobType) {
  554.                 return false;
  555.             }
  556.             return count(array_diff($columnDiff->changedProperties, ['type''length''fixed'])) === 0;
  557.         }
  558.         if ($columnDiff->hasChanged('type')) {
  559.             return false;
  560.         }
  561.         return count(array_diff($columnDiff->changedProperties, ['length''fixed'])) === 0;
  562.     }
  563.     /**
  564.      * {@inheritdoc}
  565.      */
  566.     protected function getRenameIndexSQL($oldIndexNameIndex $index$tableName)
  567.     {
  568.         if (strpos($tableName'.') !== false) {
  569.             [$schema]     = explode('.'$tableName);
  570.             $oldIndexName $schema '.' $oldIndexName;
  571.         }
  572.         return ['ALTER INDEX ' $oldIndexName ' RENAME TO ' $index->getQuotedName($this)];
  573.     }
  574.     /**
  575.      * {@inheritdoc}
  576.      */
  577.     public function getCommentOnColumnSQL($tableName$columnName$comment)
  578.     {
  579.         $tableName  = new Identifier($tableName);
  580.         $columnName = new Identifier($columnName);
  581.         $comment    $comment === null 'NULL' $this->quoteStringLiteral($comment);
  582.         return sprintf(
  583.             'COMMENT ON COLUMN %s.%s IS %s',
  584.             $tableName->getQuotedName($this),
  585.             $columnName->getQuotedName($this),
  586.             $comment
  587.         );
  588.     }
  589.     /**
  590.      * {@inheritDoc}
  591.      */
  592.     public function getCreateSequenceSQL(Sequence $sequence)
  593.     {
  594.         return 'CREATE SEQUENCE ' $sequence->getQuotedName($this) .
  595.             ' INCREMENT BY ' $sequence->getAllocationSize() .
  596.             ' MINVALUE ' $sequence->getInitialValue() .
  597.             ' START ' $sequence->getInitialValue() .
  598.             $this->getSequenceCacheSQL($sequence);
  599.     }
  600.     /**
  601.      * {@inheritDoc}
  602.      */
  603.     public function getAlterSequenceSQL(Sequence $sequence)
  604.     {
  605.         return 'ALTER SEQUENCE ' $sequence->getQuotedName($this) .
  606.             ' INCREMENT BY ' $sequence->getAllocationSize() .
  607.             $this->getSequenceCacheSQL($sequence);
  608.     }
  609.     /**
  610.      * Cache definition for sequences
  611.      */
  612.     private function getSequenceCacheSQL(Sequence $sequence): string
  613.     {
  614.         if ($sequence->getCache() > 1) {
  615.             return ' CACHE ' $sequence->getCache();
  616.         }
  617.         return '';
  618.     }
  619.     /**
  620.      * {@inheritDoc}
  621.      */
  622.     public function getDropSequenceSQL($sequence)
  623.     {
  624.         return parent::getDropSequenceSQL($sequence) . ' CASCADE';
  625.     }
  626.     /**
  627.      * {@inheritDoc}
  628.      */
  629.     public function getDropForeignKeySQL($foreignKey$table)
  630.     {
  631.         return $this->getDropConstraintSQL($foreignKey$table);
  632.     }
  633.     /**
  634.      * {@inheritDoc}
  635.      */
  636.     protected function _getCreateTableSQL($name, array $columns, array $options = [])
  637.     {
  638.         $queryFields $this->getColumnDeclarationListSQL($columns);
  639.         if (isset($options['primary']) && ! empty($options['primary'])) {
  640.             $keyColumns   array_unique(array_values($options['primary']));
  641.             $queryFields .= ', PRIMARY KEY(' implode(', '$keyColumns) . ')';
  642.         }
  643.         $query 'CREATE TABLE ' $name ' (' $queryFields ')';
  644.         $sql = [$query];
  645.         if (isset($options['indexes']) && ! empty($options['indexes'])) {
  646.             foreach ($options['indexes'] as $index) {
  647.                 $sql[] = $this->getCreateIndexSQL($index$name);
  648.             }
  649.         }
  650.         if (isset($options['uniqueConstraints'])) {
  651.             foreach ($options['uniqueConstraints'] as $uniqueConstraint) {
  652.                 $sql[] = $this->getCreateConstraintSQL($uniqueConstraint$name);
  653.             }
  654.         }
  655.         if (isset($options['foreignKeys'])) {
  656.             foreach ((array) $options['foreignKeys'] as $definition) {
  657.                 $sql[] = $this->getCreateForeignKeySQL($definition$name);
  658.             }
  659.         }
  660.         return $sql;
  661.     }
  662.     /**
  663.      * Converts a single boolean value.
  664.      *
  665.      * First converts the value to its native PHP boolean type
  666.      * and passes it to the given callback function to be reconverted
  667.      * into any custom representation.
  668.      *
  669.      * @param mixed    $value    The value to convert.
  670.      * @param callable $callback The callback function to use for converting the real boolean value.
  671.      *
  672.      * @return mixed
  673.      *
  674.      * @throws UnexpectedValueException
  675.      */
  676.     private function convertSingleBooleanValue($value$callback)
  677.     {
  678.         if ($value === null) {
  679.             return $callback(null);
  680.         }
  681.         if (is_bool($value) || is_numeric($value)) {
  682.             return $callback((bool) $value);
  683.         }
  684.         if (! is_string($value)) {
  685.             return $callback(true);
  686.         }
  687.         /**
  688.          * Better safe than sorry: http://php.net/in_array#106319
  689.          */
  690.         if (in_array(strtolower(trim($value)), $this->booleanLiterals['false'], true)) {
  691.             return $callback(false);
  692.         }
  693.         if (in_array(strtolower(trim($value)), $this->booleanLiterals['true'], true)) {
  694.             return $callback(true);
  695.         }
  696.         throw new UnexpectedValueException("Unrecognized boolean literal '${value}'");
  697.     }
  698.     /**
  699.      * Converts one or multiple boolean values.
  700.      *
  701.      * First converts the value(s) to their native PHP boolean type
  702.      * and passes them to the given callback function to be reconverted
  703.      * into any custom representation.
  704.      *
  705.      * @param mixed    $item     The value(s) to convert.
  706.      * @param callable $callback The callback function to use for converting the real boolean value(s).
  707.      *
  708.      * @return mixed
  709.      */
  710.     private function doConvertBooleans($item$callback)
  711.     {
  712.         if (is_array($item)) {
  713.             foreach ($item as $key => $value) {
  714.                 $item[$key] = $this->convertSingleBooleanValue($value$callback);
  715.             }
  716.             return $item;
  717.         }
  718.         return $this->convertSingleBooleanValue($item$callback);
  719.     }
  720.     /**
  721.      * {@inheritDoc}
  722.      *
  723.      * Postgres wants boolean values converted to the strings 'true'/'false'.
  724.      */
  725.     public function convertBooleans($item)
  726.     {
  727.         if (! $this->useBooleanTrueFalseStrings) {
  728.             return parent::convertBooleans($item);
  729.         }
  730.         return $this->doConvertBooleans(
  731.             $item,
  732.             /**
  733.              * @param mixed $value
  734.              */
  735.             static function ($value) {
  736.                 if ($value === null) {
  737.                     return 'NULL';
  738.                 }
  739.                 return $value === true 'true' 'false';
  740.             }
  741.         );
  742.     }
  743.     /**
  744.      * {@inheritDoc}
  745.      */
  746.     public function convertBooleansToDatabaseValue($item)
  747.     {
  748.         if (! $this->useBooleanTrueFalseStrings) {
  749.             return parent::convertBooleansToDatabaseValue($item);
  750.         }
  751.         return $this->doConvertBooleans(
  752.             $item,
  753.             /**
  754.              * @param mixed $value
  755.              */
  756.             static function ($value): ?int {
  757.                 return $value === null null : (int) $value;
  758.             }
  759.         );
  760.     }
  761.     /**
  762.      * {@inheritDoc}
  763.      */
  764.     public function convertFromBoolean($item)
  765.     {
  766.         if ($item !== null && in_array(strtolower($item), $this->booleanLiterals['false'], true)) {
  767.             return false;
  768.         }
  769.         return parent::convertFromBoolean($item);
  770.     }
  771.     /**
  772.      * {@inheritDoc}
  773.      */
  774.     public function getSequenceNextValSQL($sequence)
  775.     {
  776.         return "SELECT NEXTVAL('" $sequence "')";
  777.     }
  778.     /**
  779.      * {@inheritDoc}
  780.      */
  781.     public function getSetTransactionIsolationSQL($level)
  782.     {
  783.         return 'SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL '
  784.             $this->_getTransactionIsolationLevelSQL($level);
  785.     }
  786.     /**
  787.      * {@inheritDoc}
  788.      */
  789.     public function getBooleanTypeDeclarationSQL(array $column)
  790.     {
  791.         return 'BOOLEAN';
  792.     }
  793.     /**
  794.      * {@inheritDoc}
  795.      */
  796.     public function getIntegerTypeDeclarationSQL(array $column)
  797.     {
  798.         if (! empty($column['autoincrement'])) {
  799.             return 'SERIAL';
  800.         }
  801.         return 'INT';
  802.     }
  803.     /**
  804.      * {@inheritDoc}
  805.      */
  806.     public function getBigIntTypeDeclarationSQL(array $column)
  807.     {
  808.         if (! empty($column['autoincrement'])) {
  809.             return 'BIGSERIAL';
  810.         }
  811.         return 'BIGINT';
  812.     }
  813.     /**
  814.      * {@inheritDoc}
  815.      */
  816.     public function getSmallIntTypeDeclarationSQL(array $column)
  817.     {
  818.         if (! empty($column['autoincrement'])) {
  819.             return 'SMALLSERIAL';
  820.         }
  821.         return 'SMALLINT';
  822.     }
  823.     /**
  824.      * {@inheritDoc}
  825.      */
  826.     public function getGuidTypeDeclarationSQL(array $column)
  827.     {
  828.         return 'UUID';
  829.     }
  830.     /**
  831.      * {@inheritDoc}
  832.      */
  833.     public function getDateTimeTypeDeclarationSQL(array $column)
  834.     {
  835.         return 'TIMESTAMP(0) WITHOUT TIME ZONE';
  836.     }
  837.     /**
  838.      * {@inheritDoc}
  839.      */
  840.     public function getDateTimeTzTypeDeclarationSQL(array $column)
  841.     {
  842.         return 'TIMESTAMP(0) WITH TIME ZONE';
  843.     }
  844.     /**
  845.      * {@inheritDoc}
  846.      */
  847.     public function getDateTypeDeclarationSQL(array $column)
  848.     {
  849.         return 'DATE';
  850.     }
  851.     /**
  852.      * {@inheritDoc}
  853.      */
  854.     public function getTimeTypeDeclarationSQL(array $column)
  855.     {
  856.         return 'TIME(0) WITHOUT TIME ZONE';
  857.     }
  858.     /**
  859.      * {@inheritDoc}
  860.      */
  861.     protected function _getCommonIntegerTypeDeclarationSQL(array $column)
  862.     {
  863.         return '';
  864.     }
  865.     /**
  866.      * {@inheritDoc}
  867.      */
  868.     protected function getVarcharTypeDeclarationSQLSnippet($length$fixed)
  869.     {
  870.         return $fixed ? ($length 'CHAR(' $length ')' 'CHAR(255)')
  871.             : ($length 'VARCHAR(' $length ')' 'VARCHAR(255)');
  872.     }
  873.     /**
  874.      * {@inheritdoc}
  875.      */
  876.     protected function getBinaryTypeDeclarationSQLSnippet($length$fixed)
  877.     {
  878.         return 'BYTEA';
  879.     }
  880.     /**
  881.      * {@inheritDoc}
  882.      */
  883.     public function getClobTypeDeclarationSQL(array $column)
  884.     {
  885.         return 'TEXT';
  886.     }
  887.     /**
  888.      * {@inheritDoc}
  889.      */
  890.     public function getName()
  891.     {
  892.         Deprecation::triggerIfCalledFromOutside(
  893.             'doctrine/dbal',
  894.             'https://github.com/doctrine/dbal/issues/4749',
  895.             'PostgreSQLPlatform::getName() is deprecated. Identify platforms by their class.'
  896.         );
  897.         return 'postgresql';
  898.     }
  899.     /**
  900.      * {@inheritDoc}
  901.      */
  902.     public function getDateTimeTzFormatString()
  903.     {
  904.         return 'Y-m-d H:i:sO';
  905.     }
  906.     /**
  907.      * {@inheritDoc}
  908.      */
  909.     public function getEmptyIdentityInsertSQL($quotedTableName$quotedIdentifierColumnName)
  910.     {
  911.         return 'INSERT INTO ' $quotedTableName ' (' $quotedIdentifierColumnName ') VALUES (DEFAULT)';
  912.     }
  913.     /**
  914.      * {@inheritDoc}
  915.      */
  916.     public function getTruncateTableSQL($tableName$cascade false)
  917.     {
  918.         $tableIdentifier = new Identifier($tableName);
  919.         $sql             'TRUNCATE ' $tableIdentifier->getQuotedName($this);
  920.         if ($cascade) {
  921.             $sql .= ' CASCADE';
  922.         }
  923.         return $sql;
  924.     }
  925.     /**
  926.      * {@inheritDoc}
  927.      */
  928.     public function getReadLockSQL()
  929.     {
  930.         return 'FOR SHARE';
  931.     }
  932.     /**
  933.      * {@inheritDoc}
  934.      */
  935.     protected function initializeDoctrineTypeMappings()
  936.     {
  937.         $this->doctrineTypeMapping = [
  938.             'bigint'           => 'bigint',
  939.             'bigserial'        => 'bigint',
  940.             'bool'             => 'boolean',
  941.             'boolean'          => 'boolean',
  942.             'bpchar'           => 'string',
  943.             'bytea'            => 'blob',
  944.             'char'             => 'string',
  945.             'date'             => 'date',
  946.             'datetime'         => 'datetime',
  947.             'decimal'          => 'decimal',
  948.             'double'           => 'float',
  949.             'double precision' => 'float',
  950.             'float'            => 'float',
  951.             'float4'           => 'float',
  952.             'float8'           => 'float',
  953.             'inet'             => 'string',
  954.             'int'              => 'integer',
  955.             'int2'             => 'smallint',
  956.             'int4'             => 'integer',
  957.             'int8'             => 'bigint',
  958.             'integer'          => 'integer',
  959.             'interval'         => 'string',
  960.             'json'             => 'json',
  961.             'jsonb'            => 'json',
  962.             'money'            => 'decimal',
  963.             'numeric'          => 'decimal',
  964.             'serial'           => 'integer',
  965.             'serial4'          => 'integer',
  966.             'serial8'          => 'bigint',
  967.             'real'             => 'float',
  968.             'smallint'         => 'smallint',
  969.             'text'             => 'text',
  970.             'time'             => 'time',
  971.             'timestamp'        => 'datetime',
  972.             'timestamptz'      => 'datetimetz',
  973.             'timetz'           => 'time',
  974.             'tsvector'         => 'text',
  975.             'uuid'             => 'guid',
  976.             'varchar'          => 'string',
  977.             'year'             => 'date',
  978.             '_varchar'         => 'string',
  979.         ];
  980.     }
  981.     /**
  982.      * {@inheritDoc}
  983.      */
  984.     public function getVarcharMaxLength()
  985.     {
  986.         return 65535;
  987.     }
  988.     /**
  989.      * {@inheritdoc}
  990.      */
  991.     public function getBinaryMaxLength()
  992.     {
  993.         return 0;
  994.     }
  995.     /**
  996.      * {@inheritdoc}
  997.      */
  998.     public function getBinaryDefaultLength()
  999.     {
  1000.         return 0;
  1001.     }
  1002.     /**
  1003.      * {@inheritdoc}
  1004.      */
  1005.     public function hasNativeJsonType()
  1006.     {
  1007.         return true;
  1008.     }
  1009.     /**
  1010.      * {@inheritDoc}
  1011.      *
  1012.      * @deprecated Implement {@see createReservedKeywordsList()} instead.
  1013.      */
  1014.     protected function getReservedKeywordsClass()
  1015.     {
  1016.         Deprecation::triggerIfCalledFromOutside(
  1017.             'doctrine/dbal',
  1018.             'https://github.com/doctrine/dbal/issues/4510',
  1019.             'PostgreSQLPlatform::getReservedKeywordsClass() is deprecated,'
  1020.                 ' use PostgreSQLPlatform::createReservedKeywordsList() instead.'
  1021.         );
  1022.         return Keywords\PostgreSQL94Keywords::class;
  1023.     }
  1024.     /**
  1025.      * {@inheritDoc}
  1026.      */
  1027.     public function getBlobTypeDeclarationSQL(array $column)
  1028.     {
  1029.         return 'BYTEA';
  1030.     }
  1031.     /**
  1032.      * {@inheritdoc}
  1033.      */
  1034.     public function getDefaultValueDeclarationSQL($column)
  1035.     {
  1036.         if ($this->isSerialColumn($column)) {
  1037.             return '';
  1038.         }
  1039.         return parent::getDefaultValueDeclarationSQL($column);
  1040.     }
  1041.     /**
  1042.      * {@inheritdoc}
  1043.      */
  1044.     public function supportsColumnCollation()
  1045.     {
  1046.         return true;
  1047.     }
  1048.     /**
  1049.      * {@inheritdoc}
  1050.      */
  1051.     public function getColumnCollationDeclarationSQL($collation)
  1052.     {
  1053.         return 'COLLATE ' $this->quoteSingleIdentifier($collation);
  1054.     }
  1055.     /**
  1056.      * {@inheritdoc}
  1057.      */
  1058.     public function getJsonTypeDeclarationSQL(array $column)
  1059.     {
  1060.         if (! empty($column['jsonb'])) {
  1061.             return 'JSONB';
  1062.         }
  1063.         return 'JSON';
  1064.     }
  1065.     /**
  1066.      * @param mixed[] $column
  1067.      */
  1068.     private function isSerialColumn(array $column): bool
  1069.     {
  1070.         return isset($column['type'], $column['autoincrement'])
  1071.             && $column['autoincrement'] === true
  1072.             && $this->isNumericType($column['type']);
  1073.     }
  1074.     /**
  1075.      * Check whether the type of a column is changed in a way that invalidates the default value for the column
  1076.      */
  1077.     private function typeChangeBreaksDefaultValue(ColumnDiff $columnDiff): bool
  1078.     {
  1079.         if ($columnDiff->fromColumn === null) {
  1080.             return $columnDiff->hasChanged('type');
  1081.         }
  1082.         $oldTypeIsNumeric $this->isNumericType($columnDiff->fromColumn->getType());
  1083.         $newTypeIsNumeric $this->isNumericType($columnDiff->column->getType());
  1084.         // default should not be changed when switching between numeric types and the default comes from a sequence
  1085.         return $columnDiff->hasChanged('type')
  1086.             && ! ($oldTypeIsNumeric && $newTypeIsNumeric && $columnDiff->column->getAutoincrement());
  1087.     }
  1088.     private function isNumericType(Type $type): bool
  1089.     {
  1090.         return $type instanceof IntegerType || $type instanceof BigIntType;
  1091.     }
  1092.     private function getOldColumnComment(ColumnDiff $columnDiff): ?string
  1093.     {
  1094.         return $columnDiff->fromColumn !== null $this->getColumnComment($columnDiff->fromColumn) : null;
  1095.     }
  1096.     public function getListTableMetadataSQL(string $table, ?string $schema null): string
  1097.     {
  1098.         if ($schema !== null) {
  1099.             $table $schema '.' $table;
  1100.         }
  1101.         return sprintf(
  1102.             <<<'SQL'
  1103. SELECT obj_description(%s::regclass) AS table_comment;
  1104. SQL
  1105.             ,
  1106.             $this->quoteStringLiteral($table)
  1107.         );
  1108.     }
  1109. }