/** * Perform migration * * @param string $base Absolute base path (parent of assets folder). Will default to BASE_PATH * @return int Number of files successfully migrated */ public function run($base = null) { if (empty($base)) { $base = BASE_PATH; } // Check if the File dataobject has a "Filename" field. // If not, cannot migrate /** @skipUpgrade */ if (!DB::get_schema()->hasField('File', 'Filename')) { return 0; } // Set max time and memory limit increase_time_limit_to(); increase_memory_limit_to(); // Loop over all files $count = 0; $originalState = Versioned::get_reading_mode(); Versioned::set_stage(Versioned::DRAFT); $filenameMap = $this->getFilenameArray(); foreach ($this->getFileQuery() as $file) { // Get the name of the file to import $filename = $filenameMap[$file->ID]; $success = $this->migrateFile($base, $file, $filename); if ($success) { $count++; } } Versioned::set_reading_mode($originalState); return $count; }
/** * Returns the manifest of all classes which are present in the database. * * @param string $class Class name to check enum values for ClassName field * @param boolean $includeUnbacked Flag indicating whether or not to include * types that don't exist as implemented classes. By default these are excluded. * @return array List of subclasses */ public static function getValidSubClasses($class = 'SilverStripe\\CMS\\Model\\SiteTree', $includeUnbacked = false) { if (is_string($class) && !class_exists($class)) { return array(); } $class = self::class_name($class); if ($includeUnbacked) { $table = DataObject::getSchema()->tableName($class); $classes = DB::get_schema()->enumValuesForField($table, 'ClassName'); } else { $classes = static::subclassesFor($class); } return $classes; }
public function testUniqueIndexes() { $tableExpectations = array('VersionedTest_WithIndexes' => array('value' => true, 'message' => 'Unique indexes are unique in main table'), 'VersionedTest_WithIndexes_versions' => array('value' => false, 'message' => 'Unique indexes are no longer unique in _versions table'), 'VersionedTest_WithIndexes_Live' => array('value' => true, 'message' => 'Unique indexes are unique in _Live table')); // Test each table's performance foreach ($tableExpectations as $tableName => $expectation) { $indexes = DB::get_schema()->indexList($tableName); // Check for presence of all unique indexes $indexColumns = array_map(function ($index) { return $index['value']; }, $indexes); sort($indexColumns); $expectedColumns = array('"UniqA"', '"UniqS"'); $this->assertEquals(array_values($expectedColumns), array_values(array_intersect($indexColumns, $expectedColumns)), "{$tableName} has both indexes"); // Check unique -> non-unique conversion foreach ($indexes as $indexKey => $indexSpec) { if (in_array($indexSpec['value'], $expectedColumns)) { $isUnique = $indexSpec['type'] === 'unique'; $this->assertEquals($isUnique, $expectation['value'], $expectation['message']); } } } }
/** * Get the list of classnames, including obsolete classes. * * If table or name are not set, or if it is not a valid field on the given table, * then only known classnames are returned. * * Values cached in this method can be cleared via `DBClassName::clear_classname_cache();` * * @return array */ public function getEnumObsolete() { // Without a table or field specified, we can only retrieve known classes $table = $this->getTable(); $name = $this->getName(); if (empty($table) || empty($name)) { return $this->getEnum(); } // Ensure the table level cache exists if (empty(self::$classname_cache[$table])) { self::$classname_cache[$table] = array(); } // Check existing cache if (!empty(self::$classname_cache[$table][$name])) { return self::$classname_cache[$table][$name]; } // Get all class names $classNames = $this->getEnum(); if (DB::get_schema()->hasField($table, $name)) { $existing = DB::query("SELECT DISTINCT \"{$name}\" FROM \"{$table}\"")->column(); $classNames = array_unique(array_merge($classNames, $existing)); } // Cache and return self::$classname_cache[$table][$name] = $classNames; return $classNames; }
public function testHasTable() { $this->assertTrue(DB::get_schema()->hasTable('DatabaseTest_MyObject')); $this->assertFalse(DB::get_schema()->hasTable('asdfasdfasdf')); }
/** * Reset the testing database's schema. * @param $includeExtraDataObjects If true, the extraDataObjects tables will also be included */ public function resetDBSchema($includeExtraDataObjects = false) { if (self::using_temp_db()) { DataObject::reset(); // clear singletons, they're caching old extension info which is used in DatabaseAdmin->doBuild() Injector::inst()->unregisterAllObjects(); $dataClasses = ClassInfo::subclassesFor('SilverStripe\\ORM\\DataObject'); array_shift($dataClasses); DB::quiet(); $schema = DB::get_schema(); $extraDataObjects = $includeExtraDataObjects ? $this->extraDataObjects : null; $schema->schemaUpdate(function () use($dataClasses, $extraDataObjects) { foreach ($dataClasses as $dataClass) { // Check if class exists before trying to instantiate - this sidesteps any manifest weirdness if (class_exists($dataClass)) { $SNG = singleton($dataClass); if (!$SNG instanceof TestOnly) { $SNG->requireTable(); } } } // If we have additional dataobjects which need schema, do so here: if ($extraDataObjects) { foreach ($extraDataObjects as $dataClass) { $SNG = singleton($dataClass); if (singleton($dataClass) instanceof DataObject) { $SNG->requireTable(); } } } }); ClassInfo::reset_db_cache(); singleton('SilverStripe\\ORM\\DataObject')->flushCache(); } }
/** * Updates the database schema, creating tables & fields as necessary. * * @param boolean $quiet Don't show messages * @param boolean $populate Populate the database, as well as setting up its schema * @param bool $testMode */ public function doBuild($quiet = false, $populate = true, $testMode = false) { if ($quiet) { DB::quiet(); } else { $conn = DB::get_conn(); // Assumes database class is like "MySQLDatabase" or "MSSQLDatabase" (suffixed with "Database") $dbType = substr(get_class($conn), 0, -8); $dbVersion = $conn->getVersion(); $databaseName = method_exists($conn, 'currentDatabase') ? $conn->getSelectedDatabase() : ""; if (Director::is_cli()) { echo sprintf("\n\nBuilding database %s using %s %s\n\n", $databaseName, $dbType, $dbVersion); } else { echo sprintf("<h2>Building database %s using %s %s</h2>", $databaseName, $dbType, $dbVersion); } } // Set up the initial database if (!DB::is_active()) { if (!$quiet) { echo '<p><b>Creating database</b></p>'; } // Load parameters from existing configuration global $databaseConfig; if (empty($databaseConfig) && empty($_REQUEST['db'])) { user_error("No database configuration available", E_USER_ERROR); } $parameters = !empty($databaseConfig) ? $databaseConfig : $_REQUEST['db']; // Check database name is given if (empty($parameters['database'])) { user_error("No database name given; please give a value for \$databaseConfig['database']", E_USER_ERROR); } $database = $parameters['database']; // Establish connection and create database in two steps unset($parameters['database']); DB::connect($parameters); DB::create_database($database); } // Build the database. Most of the hard work is handled by DataObject $dataClasses = ClassInfo::subclassesFor('SilverStripe\\ORM\\DataObject'); array_shift($dataClasses); if (!$quiet) { if (Director::is_cli()) { echo "\nCREATING DATABASE TABLES\n\n"; } else { echo "\n<p><b>Creating database tables</b></p>\n\n"; } } // Initiate schema update $dbSchema = DB::get_schema(); $dbSchema->schemaUpdate(function () use($dataClasses, $testMode, $quiet) { foreach ($dataClasses as $dataClass) { // Check if class exists before trying to instantiate - this sidesteps any manifest weirdness if (!class_exists($dataClass)) { continue; } // Check if this class should be excluded as per testing conventions $SNG = singleton($dataClass); if (!$testMode && $SNG instanceof TestOnly) { continue; } // Log data if (!$quiet) { if (Director::is_cli()) { echo " * {$dataClass}\n"; } else { echo "<li>{$dataClass}</li>\n"; } } // Instruct the class to apply its schema to the database $SNG->requireTable(); } }); ClassInfo::reset_db_cache(); if ($populate) { if (!$quiet) { if (Director::is_cli()) { echo "\nCREATING DATABASE RECORDS\n\n"; } else { echo "\n<p><b>Creating database records</b></p>\n\n"; } } foreach ($dataClasses as $dataClass) { // Check if class exists before trying to instantiate - this sidesteps any manifest weirdness // Test_ indicates that it's the data class is part of testing system if (strpos($dataClass, 'Test_') === false && class_exists($dataClass)) { if (!$quiet) { if (Director::is_cli()) { echo " * {$dataClass}\n"; } else { echo "<li>{$dataClass}</li>\n"; } } singleton($dataClass)->requireDefaultRecords(); } } // Remap obsolete class names $schema = DataObject::getSchema(); foreach ($this->config()->classname_value_remapping as $oldClassName => $newClassName) { $badRecordCount = $newClassName::get()->filter(["ClassName" => $oldClassName])->count(); if ($badRecordCount > 0) { if (Director::is_cli()) { echo " * Correcting {$badRecordCount} obsolete classname values for {$newClassName}\n"; } else { echo "<li>Correcting {$badRecordCount} obsolete classname values for {$newClassName}</li>\n"; } $table = $schema->baseDataTable($newClassName); DB::prepared_query("UPDATE \"{$table}\" SET \"ClassName\" = ? WHERE \"ClassName\" = ?", [$newClassName, $oldClassName]); } } } touch(TEMP_FOLDER . '/database-last-generated-' . str_replace(array('\\', '/', ':'), '.', Director::baseFolder())); if (isset($_REQUEST['from_installer'])) { echo "OK"; } if (!$quiet) { echo Director::is_cli() ? "\n Database build completed!\n\n" : "<p>Database build completed!</p>"; } ClassInfo::reset_db_cache(); }
public function testDatabaseIsReadyWithInsufficientMemberColumns() { $old = Security::$force_database_is_ready; Security::$force_database_is_ready = null; Security::$database_is_ready = false; DBClassName::clear_classname_cache(); // Assumption: The database has been built correctly by the test runner, // and has all columns present in the ORM /** @skipUpgrade */ DB::get_schema()->renameField('Member', 'Email', 'Email_renamed'); // Email column is now missing, which means we're not ready to do permission checks $this->assertFalse(Security::database_is_ready()); // Rebuild the database (which re-adds the Email column), and try again $this->resetDBSchema(true); $this->assertTrue(Security::database_is_ready()); Security::$force_database_is_ready = $old; }
public function requireField() { $spec = DB::get_schema()->IdColumn(false, $this->getAutoIncrement()); DB::require_field($this->getTable(), $this->getName(), $spec); }
/** * Check that updates to a dataobject's indexes are reflected in DDL */ public function testIndexesRerequestChanges() { $schema = DB::get_schema(); $test = $this; DB::quiet(); // Table will have been initially created by the $extraDataObjects setting // Update the SearchFields index here Config::inst()->update('DataObjectSchemaGenerationTest_IndexDO', 'indexes', array('SearchFields' => array('value' => 'Title'))); // Verify that the above index change triggered a schema update $schema->schemaUpdate(function () use($test, $schema) { $obj = new DataObjectSchemaGenerationTest_IndexDO(); $obj->requireTable(); $needsUpdating = $schema->doesSchemaNeedUpdating(); $schema->cancelSchemaUpdate(); $test->assertTrue($needsUpdating); }); }
/** * Cleanup orphaned records in the _versions table * * @param string $baseTable base table to use as authoritative source of records * @param string $childTable Sub-table to clean orphans from */ protected function cleanupVersionedOrphans($baseTable, $childTable) { // Skip if child table doesn't exist if (!DB::get_schema()->hasTable($childTable)) { return; } // Skip if tables are the same if ($childTable === $baseTable) { return; } // Select all orphaned version records $orphanedQuery = SQLSelect::create()->selectField("\"{$childTable}\".\"ID\"")->setFrom("\"{$childTable}\""); // If we have a parent table limit orphaned records // to only those that exist in this if (DB::get_schema()->hasTable($baseTable)) { $orphanedQuery->addLeftJoin($baseTable, "\"{$childTable}\".\"RecordID\" = \"{$baseTable}\".\"RecordID\"\n\t\t\t\t\tAND \"{$childTable}\".\"Version\" = \"{$baseTable}\".\"Version\"")->addWhere("\"{$baseTable}\".\"ID\" IS NULL"); } $count = $orphanedQuery->count(); if ($count > 0) { DB::alteration_message("Removing {$count} orphaned versioned records", "deleted"); $ids = $orphanedQuery->execute()->column(); foreach ($ids as $id) { DB::prepared_query("DELETE FROM \"{$childTable}\" WHERE \"ID\" = ?", array($id)); } } }