From d3a9c5d4bd931129e2c4578d2146c8faa2cce07e Mon Sep 17 00:00:00 2001 From: Jan Kapciar Date: Tue, 30 Oct 2018 15:47:00 +0100 Subject: [PATCH] sql replacement of the query part where empty group placeholder is present outdated installation check documentation (init command) example config file empty group dbchange sample --- README.md | 6 +- RELEASE_NOTES.md | 8 +++ config/config.neon | 11 +-- misc/config.local.neon.example | 22 +++--- misc/config.local.neon.example-filled | 71 +++++++++++++++++++ misc/sampledata/EMPTY_GROUP/01_central.sql | 1 + misc/sampledata/EMPTY_GROUP/02_central.sql | 1 + misc/sampledata/EMPTY_GROUP/03_empty.sql | 5 ++ misc/sampledata/EMPTY_GROUP/04_central.sql | 13 ++++ misc/sampledata/EMPTY_GROUP/_requirements.txt | 1 + src/Command/InstallCommand.php | 2 +- src/Model/Generator.php | 50 ++++++++----- src/Model/IGenerator.php | 3 +- src/Model/Manager.php | 68 +++++++++++++----- 14 files changed, 201 insertions(+), 61 deletions(-) create mode 100644 misc/config.local.neon.example-filled create mode 100644 misc/sampledata/EMPTY_GROUP/01_central.sql create mode 100644 misc/sampledata/EMPTY_GROUP/02_central.sql create mode 100644 misc/sampledata/EMPTY_GROUP/03_empty.sql create mode 100644 misc/sampledata/EMPTY_GROUP/04_central.sql create mode 100644 misc/sampledata/EMPTY_GROUP/_requirements.txt diff --git a/README.md b/README.md index b4fb9f9..abe9cf8 100644 --- a/README.md +++ b/README.md @@ -101,13 +101,13 @@ Usage ======================================== -2] Initialize the environment (e.g. DEV) with +2] Initialize the DbChange with ``` -php bin/console.php dbchanger:init DEV +php bin/console.php dbchanger:init ``` This command will load environment data specified in configuration file into internal -DbChanger database. Now, environment is ready for dbChange deployments. +DbChanger database. Now, DbChanger is aligned with your configuration and ready to serve. ======================================== diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 5810afc..e65f15e 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,3 +1,11 @@ +0.5.9 - 2018/10/30 +================= +* Fixed sql replacement of the query part where empty group placeholder is present +* Fixed outdated installation check +* Fixed documentation (init command) +* Added example config file +* Added empty group dbchange sample + 0.5.8 - 2018/10/12 ================= * Fixed the wrong order of loaded fragments into installation that could occur on some platforms. diff --git a/config/config.neon b/config/config.neon index 6ee7a49..a132d22 100644 --- a/config/config.neon +++ b/config/config.neon @@ -1,22 +1,13 @@ parameters: dbchanger: - database: - driver: oci8 - host: localhost - user: DBCHANGER_USER - password: '' - servicename: DBCHANGER_SERVICE - dbname: DBCHANGER_USER - port: 1521 inputDirectory: 'misc\sampledata\' outputDirectory: 'output' logDirectory: 'log' filePrefix: '' datamodel: [] - services: - - Doctrine\ORM\EntityManager::create(%dbchanger.database%, Doctrine\ORM\Tools\Setup::createAnnotationMetadataConfiguration(["/src/Entity"], true, null, null, false)) + - Doctrine\ORM\EntityManager::create(%database%, Doctrine\ORM\Tools\Setup::createAnnotationMetadataConfiguration(["/src/Entity"], true, null, null, false)) - Kapcus\DbChanger\Model\Manager(%dbchanger.outputDirectory%) - Kapcus\DbChanger\Model\Loader(%dbchanger.inputDirectory%, %dbchanger.filePrefix%) - Kapcus\DbChanger\Model\Generator(%dbchanger.outputDirectory%) diff --git a/misc/config.local.neon.example b/misc/config.local.neon.example index e498697..91b4d11 100644 --- a/misc/config.local.neon.example +++ b/misc/config.local.neon.example @@ -1,14 +1,14 @@ parameters: + # database connection data for dbchanger running instance, mandatory (individual database parameters by driver spec) + database: + driver: oci8 + host: localhost + user: DBCHANGERUSER + password: '' + servicename: DBCHANGERDBINSTANCE + dbname: DBCHANGESCHEMA + port: 1521 dbchanger: - # database connection data for dbchanger running instance, mandatory (individual database parameters by driver spec) - database: - driver: oci8 - host: localhost - user: DBCHANGERUSER - password: '' - servicename: DBCHANGERDBINSTANCE - dbname: DBCHANGESCHEMA - port: 1521 # folder where dbchange files are located inputDirectory: 'misc\sampledata\' @@ -63,12 +63,14 @@ parameters: # database user names (mandatory in case of option 2, omit in case of option 1) usernames: ['MAINUSER', 'SLAVEUSER1', 'SLAVEUSER2'] # database user passwords (mandatory in case of option 2, omit in case of option 1) - passwords: ['', '', ''] + passwords: ['', '', ''] # database groups with assigned users, see this example, mandatory groups: + all: [MAINUSER, SLAVEUSER1, SLAVEUSER2] central: [MAINUSER] region: [SLAVEUSER1, SLAVEUSER2] + empty: [] # placeholders used in dbchange fragment sql content, see this example, optional placeholders: diff --git a/misc/config.local.neon.example-filled b/misc/config.local.neon.example-filled new file mode 100644 index 0000000..d41f882 --- /dev/null +++ b/misc/config.local.neon.example-filled @@ -0,0 +1,71 @@ +parameters: + database: + driver: oci8 + host: localhost + user: ABC_GLOBAL + password: ABC123ABC + servicename: ABC + dbname: ABC + port: 1521 + dbchanger: + # database connection data for dbchanger running instance, mandatory (individual database parameters by driver spec) + + # folder where dbchange files are located + inputDirectory: 'misc\sampledata\' + + # folder into which dbchange content should be generated into + outputDirectory: 'output' + + # folder for logs and exceptions + logDirectory: 'log' + + # in case dbchange files have some prefix, e.g. when dbchange_01_group.sql = you need to define prefix dbchange_ + filePrefix: '' + + # in this section environments are defined + datamodel: + + # list of all defined groups, mandatory and must be filled + groups: [central, region, home, operations, all] + + # which groups should be treated as manual ones (sql content in such fragments is not executed during installation and require manual interaction), optional + manualGroups: [operations] + + # environment definitions, mandatory + environments: + + # each environment must have 3-letter code, mandatory + LOC: + + # environment name, mandatory + name: Local Development + + # environment description, optional + description: Local Development Environment + + # hostname where database is running, mandatory + hostname: %database.host% + + # database name (instance name, service name), mandatory + dbname: %database.dbname% + + # database port, mandatory + port: 1521 + + # or OPTION 2 = define usernames and passwords (order of values is important, length of arrays must equal) + # database user names (mandatory in case of option 2, omit in case of option 1) + usernames: ['%database.dbname%', '%database.dbname%au', '%database.dbname%us'] + # database user passwords (mandatory in case of option 2, omit in case of option 1) + passwords: ['%database.password%', '%database.password%au', '%database.password%us'] + + # database groups with assigned users, see this example, mandatory + groups: + all: ['%database.dbname%', '%database.dbname%au', '%database.dbname%us'] + central: ['%database.dbname%'] + region: ['%database.dbname%au', '%database.dbname%us'] + + # placeholders used in dbchange fragment sql content, see this example, optional + placeholders: + '': %database.dbname% + '': ABCUSER + '': ABCSYS diff --git a/misc/sampledata/EMPTY_GROUP/01_central.sql b/misc/sampledata/EMPTY_GROUP/01_central.sql new file mode 100644 index 0000000..ecca04f --- /dev/null +++ b/misc/sampledata/EMPTY_GROUP/01_central.sql @@ -0,0 +1 @@ +GRANT SELECT, INSERT, UPDATE, DELETE ON TESTTABLE to with grant option; diff --git a/misc/sampledata/EMPTY_GROUP/02_central.sql b/misc/sampledata/EMPTY_GROUP/02_central.sql new file mode 100644 index 0000000..d53e4d1 --- /dev/null +++ b/misc/sampledata/EMPTY_GROUP/02_central.sql @@ -0,0 +1 @@ +GRANT SELECT, INSERT, UPDATE, DELETE ON TESTTABLE to with grant option; diff --git a/misc/sampledata/EMPTY_GROUP/03_empty.sql b/misc/sampledata/EMPTY_GROUP/03_empty.sql new file mode 100644 index 0000000..53b2643 --- /dev/null +++ b/misc/sampledata/EMPTY_GROUP/03_empty.sql @@ -0,0 +1,5 @@ +CREATE TABLE DUMMYTABLE +( + VALUE1 INT, + VALUE2 VARCHAR2(20) +); diff --git a/misc/sampledata/EMPTY_GROUP/04_central.sql b/misc/sampledata/EMPTY_GROUP/04_central.sql new file mode 100644 index 0000000..e5fdbb9 --- /dev/null +++ b/misc/sampledata/EMPTY_GROUP/04_central.sql @@ -0,0 +1,13 @@ +CREATE OR REPLACE VIEW TESTEMPTYGLOBALVIEW AS +SELECT + v.VALUE11 as ONEVALUE1, v.VALUE12 as ONEVALUE2, v.VALUE21 as TWOVALUE1, v.VALUE22 as TWOVALUE2, v.REGION as REGION FROM ( +/*START*/ SELECT + tone.VALUE1 as VALUE11, tone.VALUE2 as VALUE12, ttwo.VALUE1 as VALUE21, ttwo.VALUE2 as VALUE22, '' as REGION + FROM + .DUMMYTABLE tone, .DUMMYTABLE ttwo + WHERE + tone.VALUE1 = 1 AND + ttwo.VALUE1 = 2 +/*END*/ +/*GLUE_START UNION ALL GLUE_END*/ +) v; diff --git a/misc/sampledata/EMPTY_GROUP/_requirements.txt b/misc/sampledata/EMPTY_GROUP/_requirements.txt new file mode 100644 index 0000000..d512f37 --- /dev/null +++ b/misc/sampledata/EMPTY_GROUP/_requirements.txt @@ -0,0 +1 @@ +CENTRAL_TABLE \ No newline at end of file diff --git a/src/Command/InstallCommand.php b/src/Command/InstallCommand.php index 97da4f7..3c411c6 100644 --- a/src/Command/InstallCommand.php +++ b/src/Command/InstallCommand.php @@ -50,7 +50,7 @@ protected function execute(InputInterface $input, OutputInterface $output) try { $environment = $this->manager->getEnvironmentByCode($environmentCode); $dbChange = $this->manager->getActiveDbChangeByCode($dbChangeCode); - $this->manager->installDbChange($environment, $this->configurator->getEnvironmentConnectionConfigurations($environment->getCode()), $dbChange, $input->getOption('force'), $input->getOption('stop')); + $this->manager->installDbChange($this->configurator->getGroups(), $environment, $this->configurator->getEnvironmentConnectionConfigurations($environment->getCode()), $dbChange, $input->getOption('force'), $input->getOption('stop')); $output->writeln('OK - DbChange installed successfully.'); exit(0); } catch (DbChangeException $e) { diff --git a/src/Model/Generator.php b/src/Model/Generator.php index 56d90db..d336ba1 100644 --- a/src/Model/Generator.php +++ b/src/Model/Generator.php @@ -160,18 +160,20 @@ private function prepareDbChangeOutputDirectory(Environment $environment, $dbCha } /** + * @param \Kapcus\DbChanger\Entity\Group[] $groups * @param \Kapcus\DbChanger\Entity\Environment $environment * @param \Kapcus\DbChanger\Entity\Fragment $dbChangeFragment * @param \Kapcus\DbChanger\Entity\UserGroup $userGroup * * @return string */ - public function getFragmentContent(Environment $environment, Fragment $dbChangeFragment, UserGroup $userGroup) + public function getFragmentContent(array $groups, Environment $environment, Fragment $dbChangeFragment, UserGroup $userGroup) { - return $this->doGenerateFragmentContent($environment, $dbChangeFragment, $userGroup, false); + return $this->doGenerateFragmentContent($groups, $environment, $dbChangeFragment, $userGroup, false); } /** + * @param \Kapcus\DbChanger\Entity\Group[] $groups * @param \Kapcus\DbChanger\Entity\Environment $environment * @param \Kapcus\DbChanger\Entity\Fragment $dbChangeFragment * @param \Kapcus\DbChanger\Entity\UserGroup $userGroup @@ -179,7 +181,7 @@ public function getFragmentContent(Environment $environment, Fragment $dbChangeF * * @return string */ - public function doGenerateFragmentContent(Environment $environment, Fragment $dbChangeFragment, UserGroup $userGroup, $loadFromFile = false) + public function doGenerateFragmentContent(array $groups, Environment $environment, Fragment $dbChangeFragment, UserGroup $userGroup, $loadFromFile = false) { $contentTemplate = $loadFromFile ? $dbChangeFragment->getTemplateContentFromFile() : $dbChangeFragment->getTemplateContent(); @@ -201,11 +203,12 @@ public function doGenerateFragmentContent(Environment $environment, Fragment $db $statements = $this->parser->parseContent($contentTemplate); foreach ($statements as $statement) { - $newChunks = $this->replaceGroupPlaceholder($environment, $statement->getContent()); + $newChunks = $this->replaceGroupPlaceholder($groups, $environment, $statement->getContent()); foreach ($newChunks as $newChunk) { $chunks[] = sprintf('%s%s', $newChunk, $statement->getDelimiter()); } } + $chunks[] = 'COMMIT' . $this->parser->getDelimiter(); $chunks[] = ''; @@ -231,33 +234,40 @@ private function replacePlaceholders(Environment $environment, $content) } /** + * @param \Kapcus\DbChanger\Entity\Group[] $groups * @param \Kapcus\DbChanger\Entity\Environment $environment * @param string $statementContent * * @return string[] */ - private function replaceGroupPlaceholder(Environment $environment, $statementContent) + private function replaceGroupPlaceholder(array $groups, Environment $environment, $statementContent) { if (strpos($statementContent, self::PLACEHOLDER_START) !== false) { - return $this->replaceGroupPlaceholderInSubstring($environment, $statementContent); + return $this->replaceGroupPlaceholderInSubstring($groups, $environment, $statementContent); } else { - return $this->replaceGroupPlaceholderGlobally($environment, $statementContent); + return $this->replaceGroupPlaceholderGlobally($groups, $environment, $statementContent); } } /** + * @param \Kapcus\DbChanger\Entity\Group[] $groups * @param \Kapcus\DbChanger\Entity\Environment $environment * @param $statementContent * * @return string[] */ - private function replaceGroupPlaceholderGlobally(Environment $environment, $statementContent) { + private function replaceGroupPlaceholderGlobally(array $groups, Environment $environment, $statementContent) { $newStatements = []; - foreach ($environment->getGroupNames() as $groupName) { - if (strpos($statementContent, $this->getGroupPlaceholder($groupName)) !== false) { - foreach (Util::getUserGroupUsersByGroupName($environment->getUserGroups(), $groupName) as $user) { + foreach ($groups as $group) { + if (strpos($statementContent, $this->getGroupPlaceholder($group->getName())) !== false) { + $users = Util::getUserGroupUsersByGroupName($environment->getUserGroups(), $group->getName()); + // empty group -> no sql is produced where group placeholder is present + if (empty($users)) { + return []; + } + foreach ($users as $user) { $newStatements[] = str_replace( - $this->getGroupPlaceholder($groupName), + $this->getGroupPlaceholder($group->getName()), $user->getName(), $statementContent ); @@ -269,12 +279,13 @@ private function replaceGroupPlaceholderGlobally(Environment $environment, $stat } /** + * @param \Kapcus\DbChanger\Entity\Group[] $groups * @param \Kapcus\DbChanger\Entity\Environment $environment * @param $statementContent * * @return string[] */ - private function replaceGroupPlaceholderInSubstring(Environment $environment, $statementContent) { + private function replaceGroupPlaceholderInSubstring(array $groups, Environment $environment, $statementContent) { $startPlaceholderIndex = strpos($statementContent, self::PLACEHOLDER_START); $newStatements = []; $subStringLength = null; @@ -285,11 +296,16 @@ private function replaceGroupPlaceholderInSubstring(Environment $environment, $s $subStringLength = $endPlaceholderIndex - $startIndex; } - foreach ($environment->getGroupNames() as $groupName) { - if (strpos($statementContent, $this->getGroupPlaceholder($groupName)) !== false) { - foreach (Util::getUserGroupUsersByGroupName($environment->getUserGroups(), $groupName) as $user) { + foreach ($groups as $group) { + if (strpos($statementContent, $this->getGroupPlaceholder($group->getName())) !== false) { + $users = Util::getUserGroupUsersByGroupName($environment->getUserGroups(), $group->getName()); + // empty group -> no sql is produced where group placeholder is present + if (empty($users)) { + return []; + } + foreach ($users as $user) { $newStatements[] = str_replace( - $this->getGroupPlaceholder($groupName), + $this->getGroupPlaceholder($group->getName()), $user->getName(), substr($statementContent, $startIndex, $subStringLength) ); diff --git a/src/Model/IGenerator.php b/src/Model/IGenerator.php index 1144c9c..1f53da0 100644 --- a/src/Model/IGenerator.php +++ b/src/Model/IGenerator.php @@ -36,13 +36,14 @@ public function generateDbChangeIntoFile(Environment $environment, DbChange $dbC public function generateFragmentIntoFile(Environment $environment, Fragment $fragment); /** + * @param \Kapcus\DbChanger\Entity\Group[] $groups * @param \Kapcus\DbChanger\Entity\Environment $environment * @param \Kapcus\DbChanger\Entity\Fragment $dbChangeFragment * @param \Kapcus\DbChanger\Entity\UserGroup $userGroup * * @return string fragment content */ - public function getFragmentContent(Environment $environment, Fragment $dbChangeFragment, UserGroup $userGroup); + public function getFragmentContent(array $groups, Environment $environment, Fragment $dbChangeFragment, UserGroup $userGroup); /** * @return string diff --git a/src/Model/Manager.php b/src/Model/Manager.php index c95e013..4a8b559 100644 --- a/src/Model/Manager.php +++ b/src/Model/Manager.php @@ -167,7 +167,6 @@ public function initializeEnvironments(array $environments) $existingEnvironment = $this->getEnvironmentByCodeIfExists($environment->getCode()); if ($existingEnvironment == null) { - //Debug::dump($environment); $this->entityManager->persist($environment); } else { if (!$this->isEqualEnvironments($environment, $existingEnvironment)) { @@ -246,20 +245,37 @@ public function getInstalledInstallation(Environment $environment, DbChange $dbC * @param \Kapcus\DbChanger\Entity\Environment $environment * @param \Kapcus\DbChanger\Entity\DbChange $dbChange * - * @param bool $activeOnly - * - * @return \Kapcus\DbChanger\Entity\Installation + * @return \Kapcus\DbChanger\Entity\Installation|null */ - public function getLatestInstallation(Environment $environment, DbChange $dbChange, $activeOnly = false) + public function getOutdatedInstallation(Environment $environment, DbChange $dbChange) { - $whereCondition = $this->getInstallationWhereCondition($dbChange, $environment->getId(), $activeOnly); - return $this->entityManager->getRepository(Installation::class)->findOneBy( - $whereCondition, - [ - 'id' => 'DESC', - ] + $query = $this->entityManager->createQuery( + 'select + i + from + Kapcus\DbChanger\Entity\Installation i, + Kapcus\DbChanger\Entity\DbChange d, + Kapcus\DbChanger\Entity\Environment e + WHERE + i.environment = e AND + i.dbChange = d AND + e.id = ?1 AND + d.code = ?2 AND + d.isActive = 0 AND + i.status = ?3 + ORDER BY + d.id ASC, i.id ASC' ); + $query->setParameter(1, $environment->getId()); + $query->setParameter(2, $dbChange->getCode()); + $query->setParameter(3, Installation::STATUS_INSTALLED); + + $result = $query->setMaxResults(1)->setFirstResult(1)->getResult(); + if (empty($result)) { + return null; + } + return $result[0]; } /** @@ -313,18 +329,19 @@ private function getInstallationWhereCondition(DbChange $dbChange, $environmentC } /** + * @param \Kapcus\DbChanger\Entity\Group[] $groups * @param \Kapcus\DbChanger\Entity\Environment $environment * @param \Kapcus\DbChanger\Entity\DbChange $dbChange * * @return \Kapcus\DbChanger\Entity\Installation * @throws \Doctrine\ORM\OptimisticLockException */ - public function prepareInstallation(Environment $environment, DbChange $dbChange) + public function prepareInstallation(array $groups, Environment $environment, DbChange $dbChange) { $installation = $this->createNewInstallation($environment, $dbChange, self::DEFAULT_USER); foreach ($dbChange->getFragments() as $fragment) { foreach ($environment->getUserGroupsByGroup($fragment->getGroup()) as $userGroup) { - $content = $this->generator->getFragmentContent($environment, $fragment, $userGroup); + $content = $this->generator->getFragmentContent($groups, $environment, $fragment, $userGroup); $installedFragment = new InstalledFragment(); $installedFragment->setInstallation($installation); $installedFragment->setFragment($fragment); @@ -339,11 +356,24 @@ public function prepareInstallation(Environment $environment, DbChange $dbChange return $installation; } - public function installDbChange(Environment $environment, array $connectionConfigurations, DbChange $dbChange, $isForce = false, $isStop = false) + /** + * @param \Kapcus\DbChanger\Entity\Group[] $groups + * @param \Kapcus\DbChanger\Entity\Environment $environment + * @param array $connectionConfigurations + * @param \Kapcus\DbChanger\Entity\DbChange $dbChange + * @param bool $isForce + * @param bool $isStop + * + * @throws \Doctrine\ORM\OptimisticLockException + * @throws \Kapcus\DbChanger\Model\Exception\ConnectionException + * @throws \Kapcus\DbChanger\Model\Exception\ExecutionException + * @throws \Kapcus\DbChanger\Model\Exception\InstallationException + */ + public function installDbChange(array $groups, Environment $environment, array $connectionConfigurations, DbChange $dbChange, $isForce = false, $isStop = false) { $installation = $this->getInstallation($environment, $dbChange, true); if ($installation == null) { - $installation = $this->prepareInstallation($environment, $dbChange); + $installation = $this->prepareInstallation($groups, $environment, $dbChange); } if ($isStop) { @@ -356,11 +386,11 @@ public function installDbChange(Environment $environment, array $connectionConfi foreach ($dbChange->getRequiredDbChanges() as $requiredDbChange) { $reqDbChangeInstallation = $this->getInstalledInstallation($environment, $requiredDbChange->getRequiredDbChange()); if ($reqDbChangeInstallation == null) { - $latestInstallation = $this->getLatestInstallation($environment, $dbChange); - if ($latestInstallation == null) { - $missingDbChanges[] = $requiredDbChange->getRequiredDbChange()->getCode(); + $outdatedInstallation = $this->getOutdatedInstallation($environment, $requiredDbChange->getRequiredDbChange()); + if (isset($outdatedInstallation)) { + $outdatedInstallations[] = $outdatedInstallation; } else { - $outdatedInstallations[] = $latestInstallation; + $missingDbChanges[] = $requiredDbChange->getRequiredDbChange()->getCode(); } } }