예제 #1
0
 public function execute()
 {
     $formatter = new \CLIFramework\Formatter();
     $options = $this->options;
     $logger = $this->logger;
     $connectionManager = \LazyRecord\ConnectionManager::getInstance();
     $dsId = $this->getCurrentDataSourceId();
     $conn = $connectionManager->getConnection($dsId);
     $driver = $connectionManager->getQueryDriver($dsId);
     $this->logger->info('Performing Comparison...');
     $parser = TableParser::create($driver, $conn);
     $existingTables = $parser->getTables();
     $tableSchemas = $parser->getDeclareSchemaMap();
     $found = false;
     $comparator = new Comparator();
     foreach ($tableSchemas as $table => $currentSchema) {
         $this->logger->debug("Checking table {$table}");
         $ref = new ReflectionObject($currentSchema);
         $filepath = $ref->getFilename();
         $filepath = substr($filepath, strlen(getcwd()) + 1);
         if (in_array($table, $existingTables)) {
             $before = $parser->reverseTableSchema($table);
             $diffs = $comparator->compare($before, $currentSchema);
             if (count($diffs)) {
                 $found = true;
                 $printer = new ComparatorConsolePrinter($diffs);
                 $printer->beforeName = $table . ":data source [{$dsId}]";
                 $printer->afterName = $table . ':' . $filepath;
                 $printer->output();
             }
         } else {
             $msg = sprintf("+ table %-20s %s", "'" . $table . "'", $filepath);
             echo $formatter->format($msg, 'green'), "\n";
             $a = isset($tableSchemas[$table]) ? $tableSchemas[$table] : null;
             $diff = $comparator->compare(new DeclareSchema(), $currentSchema);
             foreach ($diff as $diffItem) {
                 echo "  ", $diffItem->toColumnAttrsString(), "\n";
             }
             $found = true;
         }
     }
     if (!$found) {
         $this->logger->info("No diff found");
     }
 }
예제 #2
0
 public function testBasicComparison()
 {
     $before = new DeclareSchema();
     $before->column('same');
     $before->column('changed')->varchar(20);
     $before->column('removed')->boolean();
     $after = new DeclareSchema();
     $after->column('same');
     $after->column('changed')->varchar(30);
     $after->column('added')->varchar(10);
     $comparator = new Comparator();
     $diffs = $comparator->compare($before, $after);
     foreach ($diffs as $diff) {
         $this->assertInstanceOf('LazyRecord\\Schema\\Comparator\\ColumnDiff', $diff);
     }
     $firstDiff = $diffs[0];
     $this->assertEquals('changed', $firstDiff->name);
     $this->assertEquals('M', $firstDiff->flag);
     $secondDiff = $diffs[1];
     $this->assertEquals('removed', $secondDiff->name);
     $this->assertEquals('D', $secondDiff->flag);
     $thirdDiff = $diffs[2];
     $this->assertEquals('added', $thirdDiff->name);
     $this->assertEquals('A', $thirdDiff->flag);
     /**
      * this can't work with posix (color output)
      */
     # $this->expectOutputRegex('/^= same/sm');
     # $this->expectOutputRegex('/^= changed/sm');
     # $this->expectOutputRegex('/^- removed/sm');
     /*
     ob_start();
     $printer->output();
     $content = ob_get_contents();
     ob_clean();
     like('#removed#',$content);
     like('#added#',$content);
     */
     return $diffs;
 }
예제 #3
0
 public function upgrade()
 {
     $parser = TableParser::create($this->driver, $this->connection);
     $tableSchemas = $parser->getDeclareSchemaMap();
     $comparator = new Comparator();
     $existingTables = $parser->getTables();
     // schema from runtime
     foreach ($tableSchemas as $table => $schema) {
         $this->logger->debug("Checking table {$table} for schema " . get_class($schema));
         if (!in_array($table, $existingTables)) {
             $this->logger->debug("Table {$table} does not exist, try importing...");
             // generate create table statement.
             // use sqlbuilder to build schema sql
             $this->importSchema($schema);
             continue;
         }
         $this->logger->debug("Found existing table {$table}");
         $before = $parser->reverseTableSchema($table);
         $this->logger->info("Comparing table `{$table}` with schema");
         $diffs = $comparator->compare($before, $schema);
         if (count($diffs) == 0) {
             $this->logger->info("Nothing changed.");
             continue;
         }
         $this->logger->debug("Found " . count($diffs) . ' differences');
         $alterTable = $this->alterTable($table);
         foreach ($diffs as $diff) {
             if ($this->options->{'separate-alter'}) {
                 $alterTable = $this->alterTable($table);
             }
             $column = $diff->getAfterColumn();
             switch ($diff->flag) {
                 case 'A':
                     $alterTable->addColumn($column);
                     break;
                 case 'D':
                     if ($this->options->{'no-drop-column'}) {
                         continue;
                     }
                     $alterTable->dropColumnByName($diff->name);
                     break;
                 case 'M':
                     $afterColumn = $diff->getAfterColumn();
                     $beforeColumn = $diff->getBeforeColumn();
                     if (!$afterColumn || !$beforeColumn) {
                         throw new LogicException("afterColumn or beforeColumn is undefined.");
                     }
                     // check foreign key change
                     if ($beforeColumn->primary != $afterColumn->primary) {
                         $alterTable->add()->primaryKey(['id']);
                     }
                     $alterTable->modifyColumn($afterColumn);
                     break;
                 default:
                     throw new LogicException("Unsupported flat: " . $diff->flag);
                     break;
             }
         }
         $this->executeQuery($alterTable);
     }
     $this->logger->info('Done!');
 }
예제 #4
0
 public function generateWithDiff($taskName, $dataSourceId, array $schemas, $time = null)
 {
     $connectionManager = \LazyRecord\ConnectionManager::getInstance();
     $connection = $connectionManager->getConnection($dataSourceId);
     $driver = $connectionManager->getQueryDriver($dataSourceId);
     $parser = TableParser::create($connection, $driver);
     $tableSchemas = $schemas;
     $existingTables = $parser->getTables();
     $this->logger->info('Found ' . count($schemas) . ' schemas to compare.');
     $template = $this->createClassTemplate($taskName, $time);
     $upgradeMethod = $template->addMethod('public', 'upgrade', array(), '');
     $downgradeMethod = $template->addMethod('public', 'downgrade', array(), '');
     $comparator = new Comparator($driver);
     // schema from runtime
     foreach ($tableSchemas as $key => $a) {
         $table = is_numeric($key) ? $a->getTable() : $key;
         if (!in_array($table, $existingTables)) {
             $this->logger->info(sprintf("Found schema '%s' to be imported to '%s'", $a, $table), 1);
             // generate create table statement.
             // use sqlbuilder to build schema sql
             $upcall = new MethodCallExpr('$this', 'importSchema', [new Raw('new ' . get_class($a))]);
             $upgradeMethod->getBlock()->appendLine(new Statement($upcall));
             $downcall = new MethodCallExpr('$this', 'dropTable', [$table]);
             $downgradeMethod->getBlock()->appendLine(new Statement($downcall));
             continue;
         }
         // revsersed schema
         $b = $parser->reverseTableSchema($table, $a);
         $diffs = $comparator->compare($b, $a);
         if (empty($diffs)) {
             continue;
         }
         // generate alter table statement.
         foreach ($diffs as $diff) {
             switch ($diff->flag) {
                 case 'A':
                     $alterTable = new AlterTableQuery($table);
                     $alterTable->addColumn($diff->getAfterColumn());
                     $this->appendQueryStatement($upgradeMethod, $driver, $alterTable, new ArgumentArray());
                     $alterTable = new AlterTableQuery($table);
                     $alterTable->dropColumn($diff->getAfterColumn());
                     $this->appendQueryStatement($downgradeMethod, $driver, $alterTable, new ArgumentArray());
                     break;
                 case 'M':
                     $alterTable = new AlterTableQuery($table);
                     $after = $diff->getAfterColumn();
                     $before = $diff->getBeforeColumn();
                     if (!$after || !$before) {
                         throw new LogicException('afterColumn or beforeColumn is undefined.');
                     }
                     // Check primary key
                     if ($before->primary != $after->primary) {
                         // primary key requires another sub-statement "ADD PRIMARY KEY .."
                         $alterTable->add()->primaryKey([$after->name]);
                     }
                     $alterTable->modifyColumn($after);
                     $this->appendQueryStatement($upgradeMethod, $driver, $alterTable, new ArgumentArray());
                     $alterTable = new AlterTableQuery($table);
                     $alterTable->modifyColumn($before);
                     $this->appendQueryStatement($downgradeMethod, $driver, $alterTable, new ArgumentArray());
                     break;
                 case 'D':
                     $alterTable = new AlterTableQuery($table);
                     $alterTable->dropColumnByName($diff->name);
                     $this->appendQueryStatement($upgradeMethod, $driver, $alterTable, new ArgumentArray());
                     $alterTable = new AlterTableQuery($table);
                     $alterTable->addColumn($diff->getBeforeColumn());
                     $this->appendQueryStatement($downgradeMethod, $driver, $alterTable, new ArgumentArray());
                     break;
                 default:
                     $this->logger->warn('** unsupported flag.');
                     continue;
             }
         }
     }
     $filename = $this->generateFilename($taskName, $time);
     $path = $this->migrationDir . DIRECTORY_SEPARATOR . $filename;
     if (false === file_put_contents($path, $template->render())) {
         throw new RuntimeException("Can't write migration script to {$path}.");
     }
     return array($template->class->name, $path);
 }
예제 #5
0
 /**
  * @param DeclareSchema[string tableName]
  */
 public function upgrade(array $tableSchemas)
 {
     $parser = TableParser::create($this->connection, $this->driver);
     $existingTables = $parser->getTables();
     $comparator = new Comparator($this->driver);
     // Schema from runtime
     foreach ($tableSchemas as $key => $a) {
         $table = is_numeric($key) ? $a->getTable() : $key;
         $this->logger->debug("Checking table {$table} for schema " . get_class($a));
         if (!in_array($table, $existingTables)) {
             $this->logger->debug("Table {$table} does not exist, try importing...");
             // generate create table statement.
             // use sqlbuilder to build schema sql
             $this->importSchema($a);
             continue;
         }
         $this->logger->debug("Found existing table {$table}");
         $b = $parser->reverseTableSchema($table, $a);
         $this->logger->debug("Comparing table `{$table}` with schema");
         $diffs = $comparator->compare($b, $a);
         do {
             if (count($diffs) == 0) {
                 $this->logger->debug("Nothing changed in `{$table}`.");
                 break;
             }
             $this->logger->debug('Found ' . count($diffs) . ' differences');
             $alterTable = $this->alterTable($table);
             foreach ($diffs as $diff) {
                 if ($this->options->{'separate-alter'}) {
                     $alterTable = $this->alterTable($table);
                 }
                 $column = $diff->getAfterColumn();
                 switch ($diff->flag) {
                     case 'A':
                         $alterTable->addColumn($column);
                         break;
                     case 'D':
                         if ($this->options->{'no-drop-column'}) {
                             continue;
                         }
                         $alterTable->dropColumnByName($diff->name);
                         break;
                     case 'M':
                         $afterColumn = $diff->getAfterColumn();
                         $beforeColumn = $diff->getBeforeColumn();
                         if (!$afterColumn || !$beforeColumn) {
                             throw new LogicException('afterColumn or beforeColumn is undefined.');
                         }
                         // Check primary key
                         if ($beforeColumn->primary != $afterColumn->primary) {
                             $alterTable->add()->primaryKey(['id']);
                         }
                         $alterTable->modifyColumn($afterColumn);
                         break;
                     default:
                         throw new LogicException('Unsupported flat: ' . $diff->flag);
                         break;
                 }
             }
             $this->executeQuery($alterTable);
         } while (0);
         // Compare references with relationships
         if ($parser instanceof ReferenceParser) {
             $references = $parser->queryReferences($table);
             $relationships = $a->getRelations();
             $relationshipColumns = [];
             foreach ($relationships as $accessor => $rel) {
                 if ($rel['type'] !== Relationship::BELONGS_TO) {
                     continue;
                 }
                 if ($rel['foreign_schema'] == $rel['self_schema']) {
                     continue;
                 }
                 if (isset($rel['self_column']) && $rel['self_column'] == 'id') {
                     continue;
                 }
                 if (!$rel->usingIndex) {
                     continue;
                 }
                 $class = $rel['foreign_schema'];
                 $foreignSchema = new $class();
                 $foreignColumn = $foreignSchema->getColumn($rel['foreign_column']);
                 $col = $rel['self_column'];
                 $relationshipColumns[$col] = $rel;
                 if (isset($references[$col]) && preg_match('/_ibfk_/i', $references[$col]->name)) {
                     $this->logger->debug("Column {$col} foreign key {$references[$col]->name} exists");
                     continue;
                 }
                 if ($constraint = $this->builder->buildForeignKeyConstraint($rel)) {
                     $alterTable = $this->alterTable($table);
                     $add = $alterTable->add();
                     $add->foreignKey($rel['self_column']);
                     $fSchema = new $rel['foreign_schema']();
                     $alterReferences = $add->references($fSchema->getTable(), (array) $rel['foreign_column']);
                     if ($this->driver instanceof MySQLDriver) {
                         if ($act = $rel->onUpdate) {
                             $alterReferences->onUpdate($act);
                         }
                         if ($act = $rel->onDelete) {
                             $alterReferences->onDelete($act);
                         }
                     }
                     $this->executeQuery($alterTable);
                 }
             }
             // Find foreign keys that are dropped (doesn't exist in relationship)
             foreach ($references as $col => $ref) {
                 if (!preg_match('/_ibfk_/i', $ref->name)) {
                     continue;
                 }
                 if (!isset($relationshipColumns[$col])) {
                     // echo "drop {$ref->name} for ({$ref->table}, {$ref->column}) from table $table\n";
                     $alterTable = $this->alterTable($table);
                     $alterTable->dropForeignKey($ref->name);
                     $this->executeQuery($alterTable);
                 }
             }
         }
     }
 }
예제 #6
0
 public function generateWithDiff($taskName, $dataSourceId, $schemas, $time = null)
 {
     $connectionManager = \LazyRecord\ConnectionManager::getInstance();
     $connection = $connectionManager->getConnection($dataSourceId);
     $driver = $connectionManager->getQueryDriver($dataSourceId);
     $parser = TableParser::create($driver, $connection);
     $tableSchemas = $parser->getTableSchemaMap();
     $this->logger->info('Found ' . count($schemas) . ' schemas to compare.');
     $template = $this->createClassTemplate($taskName, $time);
     $template->useClass('SQLBuilder\\Universal\\Syntax\\Column');
     $upgradeMethod = $template->addMethod('public', 'upgrade', array(), '');
     $downgradeMethod = $template->addMethod('public', 'downgrade', array(), '');
     $comparator = new Comparator();
     // schema from runtime
     foreach ($schemas as $b) {
         $tableName = $b->getTable();
         $foundTable = isset($tableSchemas[$tableName]);
         if ($foundTable) {
             $a = $tableSchemas[$tableName];
             // schema object, extracted from database.
             $diffs = $comparator->compare($a, $b);
             // generate alter table statement.
             foreach ($diffs as $diff) {
                 if ($diff->flag == 'A') {
                     $this->logger->info(sprintf("'%s': add column %s", $tableName, $diff->name), 1);
                     $column = $diff->getAfterColumn();
                     $upcall = new MethodCallExpr('$this', 'addColumn', [$tableName, $column]);
                     $upgradeMethod[] = new Statement($upcall);
                     $downcall = new MethodCallExpr('$this', 'dropColumnByName', [$tableName, $diff->name]);
                     $downgradeMethod[] = new Statement($downcall);
                 } else {
                     if ($diff->flag == 'D') {
                         $upcall = new MethodCallExpr('$this', 'dropColumnByName', [$tableName, $diff->name]);
                         $upgradeMethod->getBlock()->appendLine(new Statement($upcall));
                     } else {
                         if ($diff->flag == 'M') {
                             if ($afterColumn = $diff->getAfterColumn()) {
                                 $upcall = new MethodCallExpr('$this', 'modifyColumn', [$tableName, $afterColumn]);
                                 $upgradeMethod[] = new Statement($upcall);
                             } else {
                                 throw new \Exception("afterColumn is undefined.");
                             }
                             continue;
                         } else {
                             $this->logger->warn("** unsupported flag.");
                             continue;
                         }
                     }
                 }
             }
         } else {
             $this->logger->info(sprintf("Found schema '%s' to be imported to '%s'", $b, $tableName), 1);
             // generate create table statement.
             // use sqlbuilder to build schema sql
             $upcall = new MethodCallExpr('$this', 'importSchema', [new Raw('new ' . get_class($b))]);
             $upgradeMethod->getBlock()->appendLine(new Statement($upcall));
             $downcall = new MethodCallExpr('$this', 'dropTable', [$tableName]);
             $downgradeMethod->getBlock()->appendLine(new Statement($downcall));
         }
     }
     $filename = $this->generateFilename($taskName, $time);
     $path = $this->migrationDir . DIRECTORY_SEPARATOR . $filename;
     if (false === file_put_contents($path, $template->render())) {
         throw new RuntimeException("Can't write migration script to {$path}.");
     }
     return array($template->class->name, $path);
 }