vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/PostgreSqlPlatform.php line 874

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