2017-06-01 17:56:34 +03:00
< ? php
/**
2017-06-02 15:45:34 +03:00
* @ copyright Copyright ( c ) 2017 Joas Schilling < coding @ schilljs . com >
2017-06-01 17:56:34 +03:00
* @ copyright Copyright ( c ) 2017 , ownCloud GmbH
2017-06-02 15:45:34 +03:00
*
2020-04-29 12:57:22 +03:00
* @ author Christoph Wurst < christoph @ winzerhof - wurst . at >
2020-08-24 15:54:25 +03:00
* @ author Daniel Kesselberg < mail @ danielkesselberg . de >
2017-11-06 17:56:42 +03:00
* @ author Joas Schilling < coding @ schilljs . com >
2020-12-16 16:54:15 +03:00
* @ author Julius Härtl < jus @ bitgrid . net >
2019-12-03 21:57:53 +03:00
* @ author Morris Jobke < hey @ morrisjobke . de >
* @ author Robin Appelman < robin @ icewind . nl >
2017-11-06 17:56:42 +03:00
*
2017-06-01 17:56:34 +03:00
* @ license AGPL - 3.0
*
* This code is free software : you can redistribute it and / or modify
* it under the terms of the GNU Affero General Public License , version 3 ,
* as published by the Free Software Foundation .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU Affero General Public License for more details .
*
* You should have received a copy of the GNU Affero General Public License , version 3 ,
2019-12-03 21:57:53 +03:00
* along with this program . If not , see < http :// www . gnu . org / licenses />
2017-06-01 17:56:34 +03:00
*
*/
namespace OC\DB ;
2020-12-07 21:35:01 +03:00
use Doctrine\DBAL\Exception\DriverException ;
2018-07-19 11:28:52 +03:00
use Doctrine\DBAL\Platforms\OraclePlatform ;
2021-01-03 17:28:31 +03:00
use Doctrine\DBAL\Platforms\PostgreSQL94Platform ;
2018-07-19 11:28:52 +03:00
use Doctrine\DBAL\Schema\Index ;
2018-07-12 17:52:08 +03:00
use Doctrine\DBAL\Schema\Schema ;
2018-01-04 16:58:01 +03:00
use Doctrine\DBAL\Schema\SchemaException ;
2018-07-20 13:31:52 +03:00
use Doctrine\DBAL\Schema\Sequence ;
2018-08-06 19:25:09 +03:00
use Doctrine\DBAL\Schema\Table ;
2020-06-30 23:12:06 +03:00
use Doctrine\DBAL\Types\Types ;
2018-08-06 19:36:38 +03:00
use OC\App\InfoParser ;
2017-06-01 17:56:34 +03:00
use OC\IntegrityCheck\Helpers\AppLocator ;
use OC\Migration\SimpleOutput ;
2017-06-02 15:30:02 +03:00
use OCP\AppFramework\App ;
2017-06-01 17:56:34 +03:00
use OCP\AppFramework\QueryException ;
2017-06-02 14:54:09 +03:00
use OCP\Migration\IMigrationStep ;
2017-06-01 17:56:34 +03:00
use OCP\Migration\IOutput ;
class MigrationService {
/** @var boolean */
private $migrationTableCreated ;
/** @var array */
private $migrations ;
/** @var IOutput */
private $output ;
/** @var Connection */
private $connection ;
/** @var string */
private $appName ;
2018-08-06 19:36:38 +03:00
/** @var bool */
private $checkOracle ;
2017-06-01 17:56:34 +03:00
/**
* MigrationService constructor .
*
* @ param $appName
2021-01-03 17:28:31 +03:00
* @ param Connection $connection
2017-06-01 17:56:34 +03:00
* @ param AppLocator $appLocator
* @ param IOutput | null $output
* @ throws \Exception
*/
2021-01-03 17:28:31 +03:00
public function __construct ( $appName , Connection $connection , IOutput $output = null , AppLocator $appLocator = null ) {
2017-06-01 17:56:34 +03:00
$this -> appName = $appName ;
$this -> connection = $connection ;
$this -> output = $output ;
2017-06-02 15:30:02 +03:00
if ( null === $this -> output ) {
2017-06-01 17:56:34 +03:00
$this -> output = new SimpleOutput ( \OC :: $server -> getLogger (), $appName );
}
if ( $appName === 'core' ) {
$this -> migrationsPath = \OC :: $SERVERROOT . '/core/Migrations' ;
2017-07-05 15:44:24 +03:00
$this -> migrationsNamespace = 'OC\\Core\\Migrations' ;
2018-08-06 19:36:38 +03:00
$this -> checkOracle = true ;
2017-06-01 17:56:34 +03:00
} else {
2017-06-02 15:30:02 +03:00
if ( null === $appLocator ) {
2017-06-01 17:56:34 +03:00
$appLocator = new AppLocator ();
}
$appPath = $appLocator -> getAppPath ( $appName );
2017-06-02 15:30:02 +03:00
$namespace = App :: buildAppNamespace ( $appName );
2017-06-02 15:22:04 +03:00
$this -> migrationsPath = " $appPath /lib/Migration " ;
$this -> migrationsNamespace = $namespace . '\\Migration' ;
2018-08-06 19:36:38 +03:00
$infoParser = new InfoParser ();
$info = $infoParser -> parse ( $appPath . '/appinfo/info.xml' );
if ( ! isset ( $info [ 'dependencies' ][ 'database' ])) {
$this -> checkOracle = true ;
} else {
$this -> checkOracle = false ;
foreach ( $info [ 'dependencies' ][ 'database' ] as $database ) {
if ( \is_string ( $database ) && $database === 'oci' ) {
$this -> checkOracle = true ;
2020-04-10 11:35:09 +03:00
} elseif ( \is_array ( $database ) && isset ( $database [ '@value' ]) && $database [ '@value' ] === 'oci' ) {
2018-08-06 19:36:38 +03:00
$this -> checkOracle = true ;
}
}
}
2017-06-01 17:56:34 +03:00
}
}
/**
* Returns the name of the app for which this migration is executed
*
* @ return string
*/
public function getApp () {
return $this -> appName ;
}
/**
* @ return bool
* @ codeCoverageIgnore - this will implicitly tested on installation
*/
private function createMigrationTable () {
if ( $this -> migrationTableCreated ) {
return false ;
}
2020-12-09 12:10:51 +03:00
if ( $this -> connection -> tableExists ( 'migrations' ) && \OC :: $server -> getConfig () -> getAppValue ( 'core' , 'vendor' , '' ) !== 'owncloud' ) {
2020-11-11 22:12:13 +03:00
$this -> migrationTableCreated = true ;
return false ;
}
2018-01-04 16:58:01 +03:00
$schema = new SchemaWrapper ( $this -> connection );
/**
* We drop the table when it has different columns or the definition does not
* match . E . g . ownCloud uses a length of 177 for app and 14 for version .
*/
try {
$table = $schema -> getTable ( 'migrations' );
$columns = $table -> getColumns ();
if ( count ( $columns ) === 2 ) {
try {
$column = $table -> getColumn ( 'app' );
$schemaMismatch = $column -> getLength () !== 255 ;
if ( ! $schemaMismatch ) {
$column = $table -> getColumn ( 'version' );
$schemaMismatch = $column -> getLength () !== 255 ;
}
} catch ( SchemaException $e ) {
// One of the columns is missing
$schemaMismatch = true ;
}
if ( ! $schemaMismatch ) {
// Table exists and schema matches: return back!
$this -> migrationTableCreated = true ;
return false ;
}
}
// Drop the table, when it didn't match our expectations.
2018-01-17 14:17:41 +03:00
$this -> connection -> dropTable ( 'migrations' );
2018-01-31 15:13:14 +03:00
// Recreate the schema after the table was dropped.
$schema = new SchemaWrapper ( $this -> connection );
2018-01-04 16:58:01 +03:00
} catch ( SchemaException $e ) {
// Table not found, no need to panic, we will create it.
2017-06-01 17:56:34 +03:00
}
2018-01-31 15:13:14 +03:00
$table = $schema -> createTable ( 'migrations' );
2020-06-30 23:12:06 +03:00
$table -> addColumn ( 'app' , Types :: STRING , [ 'length' => 255 ]);
$table -> addColumn ( 'version' , Types :: STRING , [ 'length' => 255 ]);
2018-01-31 15:13:14 +03:00
$table -> setPrimaryKey ([ 'app' , 'version' ]);
$this -> connection -> migrateToSchema ( $schema -> getWrappedSchema ());
2017-06-01 17:56:34 +03:00
$this -> migrationTableCreated = true ;
return true ;
}
/**
* Returns all versions which have already been applied
*
* @ return string []
* @ codeCoverageIgnore - no need to test this
*/
public function getMigratedVersions () {
$this -> createMigrationTable ();
$qb = $this -> connection -> getQueryBuilder ();
$qb -> select ( 'version' )
-> from ( 'migrations' )
-> where ( $qb -> expr () -> eq ( 'app' , $qb -> createNamedParameter ( $this -> getApp ())))
-> orderBy ( 'version' );
$result = $qb -> execute ();
$rows = $result -> fetchAll ( \PDO :: FETCH_COLUMN );
$result -> closeCursor ();
return $rows ;
}
/**
* Returns all versions which are available in the migration folder
*
* @ return array
*/
public function getAvailableVersions () {
$this -> ensureMigrationsAreLoaded ();
2017-06-09 17:45:12 +03:00
return array_map ( 'strval' , array_keys ( $this -> migrations ));
2017-06-01 17:56:34 +03:00
}
protected function findMigrations () {
$directory = realpath ( $this -> migrationsPath );
2018-01-12 17:45:51 +03:00
if ( $directory === false || ! file_exists ( $directory ) || ! is_dir ( $directory )) {
2017-07-06 10:58:39 +03:00
return [];
}
2017-06-01 17:56:34 +03:00
$iterator = new \RegexIterator (
new \RecursiveIteratorIterator (
new \RecursiveDirectoryIterator ( $directory , \FilesystemIterator :: SKIP_DOTS ),
\RecursiveIteratorIterator :: LEAVES_ONLY
),
'#^.+\\/Version[^\\/]{1,255}\\.php$#i' ,
\RegexIterator :: GET_MATCH );
$files = array_keys ( iterator_to_array ( $iterator ));
uasort ( $files , function ( $a , $b ) {
2017-06-07 13:27:16 +03:00
preg_match ( '/^Version(\d+)Date(\d+)\\.php$/' , basename ( $a ), $matchA );
preg_match ( '/^Version(\d+)Date(\d+)\\.php$/' , basename ( $b ), $matchB );
if ( ! empty ( $matchA ) && ! empty ( $matchB )) {
if ( $matchA [ 1 ] !== $matchB [ 1 ]) {
return ( $matchA [ 1 ] < $matchB [ 1 ]) ? - 1 : 1 ;
}
return ( $matchA [ 2 ] < $matchB [ 2 ]) ? - 1 : 1 ;
}
2017-06-01 17:56:34 +03:00
return ( basename ( $a ) < basename ( $b )) ? - 1 : 1 ;
});
$migrations = [];
foreach ( $files as $file ) {
$className = basename ( $file , '.php' );
$version = ( string ) substr ( $className , 7 );
if ( $version === '0' ) {
throw new \InvalidArgumentException (
" Cannot load a migrations with the name ' $version ' because it is a reserved number "
);
}
$migrations [ $version ] = sprintf ( '%s\\%s' , $this -> migrationsNamespace , $className );
}
return $migrations ;
}
/**
* @ param string $to
2017-06-02 15:30:02 +03:00
* @ return string []
2017-06-01 17:56:34 +03:00
*/
private function getMigrationsToExecute ( $to ) {
$knownMigrations = $this -> getMigratedVersions ();
$availableMigrations = $this -> getAvailableVersions ();
$toBeExecuted = [];
foreach ( $availableMigrations as $v ) {
if ( $to !== 'latest' && $v > $to ) {
continue ;
}
if ( $this -> shallBeExecuted ( $v , $knownMigrations )) {
$toBeExecuted [] = $v ;
}
}
return $toBeExecuted ;
}
/**
2017-06-02 15:30:02 +03:00
* @ param string $m
2017-06-01 17:56:34 +03:00
* @ param string [] $knownMigrations
2017-06-02 15:30:02 +03:00
* @ return bool
2017-06-01 17:56:34 +03:00
*/
private function shallBeExecuted ( $m , $knownMigrations ) {
if ( in_array ( $m , $knownMigrations )) {
return false ;
}
return true ;
}
/**
* @ param string $version
*/
private function markAsExecuted ( $version ) {
$this -> connection -> insertIfNotExist ( '*PREFIX*migrations' , [
'app' => $this -> appName ,
'version' => $version
]);
}
/**
* Returns the name of the table which holds the already applied versions
*
* @ return string
*/
public function getMigrationsTableName () {
return $this -> connection -> getPrefix () . 'migrations' ;
}
/**
* Returns the namespace of the version classes
*
* @ return string
*/
public function getMigrationsNamespace () {
return $this -> migrationsNamespace ;
}
/**
* Returns the directory which holds the versions
*
* @ return string
*/
public function getMigrationsDirectory () {
return $this -> migrationsPath ;
}
/**
* Return the explicit version for the aliases ; current , next , prev , latest
*
* @ param string $alias
* @ return mixed | null | string
*/
public function getMigration ( $alias ) {
2020-04-10 15:19:56 +03:00
switch ( $alias ) {
2017-06-01 17:56:34 +03:00
case 'current' :
return $this -> getCurrentVersion ();
case 'next' :
return $this -> getRelativeVersion ( $this -> getCurrentVersion (), 1 );
case 'prev' :
return $this -> getRelativeVersion ( $this -> getCurrentVersion (), - 1 );
case 'latest' :
$this -> ensureMigrationsAreLoaded ();
2017-07-19 17:18:11 +03:00
$migrations = $this -> getAvailableVersions ();
return @ end ( $migrations );
2017-06-01 17:56:34 +03:00
}
return '0' ;
}
/**
* @ param string $version
* @ param int $delta
* @ return null | string
*/
private function getRelativeVersion ( $version , $delta ) {
$this -> ensureMigrationsAreLoaded ();
$versions = $this -> getAvailableVersions ();
array_unshift ( $versions , 0 );
2017-06-02 15:30:02 +03:00
$offset = array_search ( $version , $versions , true );
2017-06-01 17:56:34 +03:00
if ( $offset === false || ! isset ( $versions [ $offset + $delta ])) {
// Unknown version or delta out of bounds.
return null ;
}
return ( string ) $versions [ $offset + $delta ];
}
/**
* @ return string
*/
private function getCurrentVersion () {
$m = $this -> getMigratedVersions ();
if ( count ( $m ) === 0 ) {
return '0' ;
}
2017-07-19 17:18:11 +03:00
$migrations = array_values ( $m );
return @ end ( $migrations );
2017-06-01 17:56:34 +03:00
}
/**
2017-06-02 15:30:02 +03:00
* @ param string $version
2017-06-01 17:56:34 +03:00
* @ return string
2017-06-02 15:30:02 +03:00
* @ throws \InvalidArgumentException
2017-06-01 17:56:34 +03:00
*/
private function getClass ( $version ) {
$this -> ensureMigrationsAreLoaded ();
if ( isset ( $this -> migrations [ $version ])) {
return $this -> migrations [ $version ];
}
throw new \InvalidArgumentException ( " Version $version is unknown. " );
}
/**
* Allows to set an IOutput implementation which is used for logging progress and messages
*
* @ param IOutput $output
*/
public function setOutput ( IOutput $output ) {
$this -> output = $output ;
}
/**
* Applies all not yet applied versions up to $to
*
* @ param string $to
2018-07-19 16:32:36 +03:00
* @ param bool $schemaOnly
2017-06-02 15:30:02 +03:00
* @ throws \InvalidArgumentException
2017-06-01 17:56:34 +03:00
*/
2018-07-19 16:32:36 +03:00
public function migrate ( $to = 'latest' , $schemaOnly = false ) {
2020-11-11 22:12:13 +03:00
if ( $schemaOnly ) {
$this -> migrateSchemaOnly ( $to );
return ;
}
2017-06-01 17:56:34 +03:00
// read known migrations
$toBeExecuted = $this -> getMigrationsToExecute ( $to );
foreach ( $toBeExecuted as $version ) {
2020-12-07 21:35:01 +03:00
try {
$this -> executeStep ( $version , $schemaOnly );
} catch ( DriverException $e ) {
// The exception itself does not contain the name of the migration,
// so we wrap it here, to make debugging easier.
throw new \Exception ( 'Database error when running migration ' . $to . ' for app ' . $this -> getApp (), 0 , $e );
}
2017-06-01 17:56:34 +03:00
}
}
2020-11-11 22:12:13 +03:00
/**
* Applies all not yet applied versions up to $to
*
* @ param string $to
* @ throws \InvalidArgumentException
*/
public function migrateSchemaOnly ( $to = 'latest' ) {
// read known migrations
$toBeExecuted = $this -> getMigrationsToExecute ( $to );
if ( empty ( $toBeExecuted )) {
return ;
}
$toSchema = null ;
foreach ( $toBeExecuted as $version ) {
$instance = $this -> createInstance ( $version );
$toSchema = $instance -> changeSchema ( $this -> output , function () use ( $toSchema ) {
return $toSchema ? : new SchemaWrapper ( $this -> connection );
}, [ 'tablePrefix' => $this -> connection -> getPrefix ()]) ? : $toSchema ;
}
if ( $toSchema instanceof SchemaWrapper ) {
$targetSchema = $toSchema -> getWrappedSchema ();
if ( $this -> checkOracle ) {
$beforeSchema = $this -> connection -> createSchema ();
2020-11-11 16:40:26 +03:00
$this -> ensureOracleConstraints ( $beforeSchema , $targetSchema , strlen ( $this -> connection -> getPrefix ()));
2020-11-11 22:12:13 +03:00
}
$this -> connection -> migrateToSchema ( $targetSchema );
$toSchema -> performDropTableCalls ();
}
2021-03-04 10:49:42 +03:00
foreach ( $toBeExecuted as $version ) {
$this -> markAsExecuted ( $version );
}
2020-11-11 22:12:13 +03:00
}
2018-04-12 18:47:40 +03:00
/**
* Get the human readable descriptions for the migration steps to run
*
* @ param string $to
* @ return string [] [ $name => $description ]
*/
public function describeMigrationStep ( $to = 'latest' ) {
$toBeExecuted = $this -> getMigrationsToExecute ( $to );
$description = [];
foreach ( $toBeExecuted as $version ) {
$migration = $this -> createInstance ( $version );
if ( $migration -> name ()) {
$description [ $migration -> name ()] = $migration -> description ();
}
}
return $description ;
}
2017-06-01 17:56:34 +03:00
/**
* @ param string $version
2018-04-12 18:47:40 +03:00
* @ return IMigrationStep
2017-06-02 15:30:02 +03:00
* @ throws \InvalidArgumentException
2017-06-01 17:56:34 +03:00
*/
protected function createInstance ( $version ) {
$class = $this -> getClass ( $version );
try {
$s = \OC :: $server -> query ( $class );
2018-04-12 18:47:40 +03:00
if ( ! $s instanceof IMigrationStep ) {
throw new \InvalidArgumentException ( 'Not a valid migration' );
}
2017-06-01 17:56:34 +03:00
} catch ( QueryException $e ) {
if ( class_exists ( $class )) {
$s = new $class ();
} else {
2017-06-02 15:30:02 +03:00
throw new \InvalidArgumentException ( " Migration step ' $class ' is unknown " );
2017-06-01 17:56:34 +03:00
}
}
return $s ;
}
/**
* Executes one explicit version
*
* @ param string $version
2018-07-19 16:32:36 +03:00
* @ param bool $schemaOnly
2017-06-02 15:30:02 +03:00
* @ throws \InvalidArgumentException
2017-06-01 17:56:34 +03:00
*/
2018-07-19 16:32:36 +03:00
public function executeStep ( $version , $schemaOnly = false ) {
2017-06-01 17:56:34 +03:00
$instance = $this -> createInstance ( $version );
2017-06-02 14:54:09 +03:00
2018-07-19 16:32:36 +03:00
if ( ! $schemaOnly ) {
2020-04-09 14:53:40 +03:00
$instance -> preSchemaChange ( $this -> output , function () {
2018-07-19 16:32:36 +03:00
return new SchemaWrapper ( $this -> connection );
}, [ 'tablePrefix' => $this -> connection -> getPrefix ()]);
}
2017-06-02 14:54:09 +03:00
2020-04-09 14:53:40 +03:00
$toSchema = $instance -> changeSchema ( $this -> output , function () {
2017-06-07 16:15:53 +03:00
return new SchemaWrapper ( $this -> connection );
2017-06-02 14:54:09 +03:00
}, [ 'tablePrefix' => $this -> connection -> getPrefix ()]);
2017-06-07 16:15:53 +03:00
if ( $toSchema instanceof SchemaWrapper ) {
2018-07-12 17:52:08 +03:00
$targetSchema = $toSchema -> getWrappedSchema ();
2018-08-06 19:36:38 +03:00
if ( $this -> checkOracle ) {
$sourceSchema = $this -> connection -> createSchema ();
2020-11-11 16:40:26 +03:00
$this -> ensureOracleConstraints ( $sourceSchema , $targetSchema , strlen ( $this -> connection -> getPrefix ()));
2018-08-06 19:36:38 +03:00
}
2018-07-12 17:52:08 +03:00
$this -> connection -> migrateToSchema ( $targetSchema );
2017-06-07 16:15:53 +03:00
$toSchema -> performDropTableCalls ();
2017-06-01 17:56:34 +03:00
}
2017-06-02 14:54:09 +03:00
2018-07-19 16:32:36 +03:00
if ( ! $schemaOnly ) {
2020-04-09 14:53:40 +03:00
$instance -> postSchemaChange ( $this -> output , function () {
2018-07-19 16:32:36 +03:00
return new SchemaWrapper ( $this -> connection );
}, [ 'tablePrefix' => $this -> connection -> getPrefix ()]);
}
2017-06-02 14:54:09 +03:00
2017-06-01 17:56:34 +03:00
$this -> markAsExecuted ( $version );
}
2020-11-11 16:33:47 +03:00
/**
* Naming constraints :
* - Tables names must be 30 chars or shorter ( 27 + oc_ prefix )
* - Column names must be 30 chars or shorter
* - Index names must be 30 chars or shorter
* - Sequence names must be 30 chars or shorter
* - Primary key names must be set or the table name 23 chars or shorter
*
* Data constraints :
* - Columns with " NotNull " can not have empty string as default value
* - Columns with " NotNull " can not have number 0 as default value
2020-11-11 16:34:24 +03:00
* - Columns with type " bool " ( which is in fact integer of length 1 ) can not be " NotNull " as it can not store 0 / false
2020-11-11 16:33:47 +03:00
*
* @ param Schema $sourceSchema
* @ param Schema $targetSchema
* @ param int $prefixLength
* @ throws \Doctrine\DBAL\Exception
*/
2020-11-11 16:40:26 +03:00
public function ensureOracleConstraints ( Schema $sourceSchema , Schema $targetSchema , int $prefixLength ) {
2018-08-06 19:25:09 +03:00
$sequences = $targetSchema -> getSequences ();
2018-07-20 13:31:52 +03:00
2018-08-06 19:25:09 +03:00
foreach ( $targetSchema -> getTables () as $table ) {
try {
$sourceTable = $sourceSchema -> getTable ( $table -> getName ());
} catch ( SchemaException $e ) {
if ( \strlen ( $table -> getName ()) - $prefixLength > 27 ) {
2021-02-18 12:14:12 +03:00
throw new \InvalidArgumentException ( 'Table name "' . $table -> getName () . '" is too long.' );
2018-08-06 19:25:09 +03:00
}
$sourceTable = null ;
2018-07-12 17:52:08 +03:00
}
foreach ( $table -> getColumns () as $thing ) {
2019-03-28 16:51:11 +03:00
if (( ! $sourceTable instanceof Table || ! $sourceTable -> hasColumn ( $thing -> getName ())) && \strlen ( $thing -> getName ()) > 30 ) {
2021-02-18 12:14:12 +03:00
throw new \InvalidArgumentException ( 'Column name "' . $table -> getName () . '"."' . $thing -> getName () . '" is too long.' );
2018-07-12 17:52:08 +03:00
}
2020-09-07 14:14:49 +03:00
if ( $thing -> getNotnull () && $thing -> getDefault () === ''
&& $sourceTable instanceof Table && ! $sourceTable -> hasColumn ( $thing -> getName ())) {
2020-11-11 16:33:29 +03:00
throw new \InvalidArgumentException ( 'Column "' . $table -> getName () . '"."' . $thing -> getName () . '" is NotNull, but has empty string or null as default.' );
2020-09-07 14:14:49 +03:00
}
2020-11-11 16:34:24 +03:00
if ( $thing -> getNotnull () && $thing -> getType () -> getName () === Types :: BOOLEAN ) {
throw new \InvalidArgumentException ( 'Column "' . $table -> getName () . '"."' . $thing -> getName () . '" is type Bool and also NotNull, so it can not store "false".' );
}
2018-07-12 17:52:08 +03:00
}
foreach ( $table -> getIndexes () as $thing ) {
2019-03-28 16:51:11 +03:00
if (( ! $sourceTable instanceof Table || ! $sourceTable -> hasIndex ( $thing -> getName ())) && \strlen ( $thing -> getName ()) > 30 ) {
2021-02-18 12:14:12 +03:00
throw new \InvalidArgumentException ( 'Index name "' . $table -> getName () . '"."' . $thing -> getName () . '" is too long.' );
2018-07-12 17:52:08 +03:00
}
}
foreach ( $table -> getForeignKeys () as $thing ) {
2019-03-28 16:51:11 +03:00
if (( ! $sourceTable instanceof Table || ! $sourceTable -> hasForeignKey ( $thing -> getName ())) && \strlen ( $thing -> getName ()) > 30 ) {
2020-11-11 16:33:29 +03:00
throw new \InvalidArgumentException ( 'Foreign key name "' . $table -> getName () . '"."' . $thing -> getName () . '" is too long.' );
2018-07-12 17:52:08 +03:00
}
}
2018-07-19 11:28:52 +03:00
$primaryKey = $table -> getPrimaryKey ();
2018-08-06 19:25:09 +03:00
if ( $primaryKey instanceof Index && ( ! $sourceTable instanceof Table || ! $sourceTable -> hasPrimaryKey ())) {
2018-07-19 11:28:52 +03:00
$indexName = strtolower ( $primaryKey -> getName ());
$isUsingDefaultName = $indexName === 'primary' ;
2021-01-03 17:28:31 +03:00
if ( $this -> connection -> getDatabasePlatform () instanceof PostgreSQL94Platform ) {
2018-07-27 15:31:19 +03:00
$defaultName = $table -> getName () . '_pkey' ;
2018-07-19 11:28:52 +03:00
$isUsingDefaultName = strtolower ( $defaultName ) === $indexName ;
2018-07-20 13:31:52 +03:00
if ( $isUsingDefaultName ) {
2018-07-27 15:31:19 +03:00
$sequenceName = $table -> getName () . '_' . implode ( '_' , $primaryKey -> getColumns ()) . '_seq' ;
2020-04-09 14:53:40 +03:00
$sequences = array_filter ( $sequences , function ( Sequence $sequence ) use ( $sequenceName ) {
2018-07-27 15:31:19 +03:00
return $sequence -> getName () !== $sequenceName ;
2018-07-20 13:31:52 +03:00
});
}
2020-04-10 11:35:09 +03:00
} elseif ( $this -> connection -> getDatabasePlatform () instanceof OraclePlatform ) {
2018-07-19 11:28:52 +03:00
$defaultName = $table -> getName () . '_seq' ;
$isUsingDefaultName = strtolower ( $defaultName ) === $indexName ;
}
2019-03-28 16:51:11 +03:00
if ( ! $isUsingDefaultName && \strlen ( $indexName ) > 30 ) {
2021-02-18 12:14:12 +03:00
throw new \InvalidArgumentException ( 'Primary index name on "' . $table -> getName () . '" is too long.' );
2018-07-19 11:28:52 +03:00
}
2019-12-05 16:38:28 +03:00
if ( $isUsingDefaultName && \strlen ( $table -> getName ()) - $prefixLength >= 23 ) {
2021-02-18 12:14:12 +03:00
throw new \InvalidArgumentException ( 'Primary index name on "' . $table -> getName () . '" is too long.' );
2018-07-19 11:28:52 +03:00
}
2018-07-12 17:52:08 +03:00
}
}
2018-07-20 13:31:52 +03:00
foreach ( $sequences as $sequence ) {
2019-03-28 16:51:11 +03:00
if ( ! $sourceSchema -> hasSequence ( $sequence -> getName ()) && \strlen ( $sequence -> getName ()) > 30 ) {
2021-02-18 12:14:12 +03:00
throw new \InvalidArgumentException ( 'Sequence name "' . $sequence -> getName () . '" is too long.' );
2018-07-12 17:52:08 +03:00
}
}
}
2017-06-01 17:56:34 +03:00
private function ensureMigrationsAreLoaded () {
if ( empty ( $this -> migrations )) {
$this -> migrations = $this -> findMigrations ();
}
}
}