gen-jhipster
Version:
VHipster - Spring Boot + Angular/React/Vue in one handy generator
736 lines (735 loc) • 40.8 kB
JavaScript
/**
* Copyright 2013-2026 the original author or authors from the JHipster project.
*
* This file is part of the JHipster project, see https://www.jhipster.tech/
* for more information.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import fs from 'node:fs';
import { escape, min } from 'lodash-es';
import { fieldTypes } from "../../lib/jhipster/index.js";
import { mutateData } from "../../lib/utils/object.js";
import { mutateRelationship as mutateBaseApplicationRelationship, mutateRelationshipWithEntity as mutateBaseApplicationRelationshipWithEntity, } from "../base-application/entity.js";
import { loadRequiredConfigIntoEntity, prepareCommonFieldForTemplates, prepareEntity, prepareEntityPrimaryKeyForTemplates, prepareRelationship, } from "../base-application/support/index.js";
import BaseEntityChangesGenerator from "../base-entity-changes/index.js";
import { mutateField as commonMutateField } from "../common/entity.js";
import { prepareEntity as prepareEntityForServer } from "../java/support/index.js";
import { getFKConstraintName, getUXConstraintName, prepareField as prepareServerFieldForTemplates } from "../server/support/index.js";
import { prepareSqlApplicationProperties } from "../spring-boot/generators/data-relational/support/index.js";
import { addEntityFiles, fakeFiles, updateConstraintsFiles, updateEntityFiles, updateMigrateFiles } from "./changelog-files.js";
import { liquibaseFiles } from "./files.js";
import { addLiquibaseChangelogCallback, addLiquibaseConstraintsChangelogCallback, addLiquibaseIncrementalChangelogCallback, } from "./internal/needles.js";
import { checkAndReturnRelationshipOnValue } from "./internal/relationship-on-handler-options.js";
import { liquibaseComment, postPrepareEntity, prepareField as prepareFieldForLiquibase, prepareRelationshipForLiquibase, } from "./support/index.js";
import mavenPlugin from "./support/maven-plugin.js";
const { CommonDBTypes: { LONG: TYPE_LONG, INTEGER: TYPE_INTEGER }, } = fieldTypes;
export default class LiquibaseGenerator extends BaseEntityChangesGenerator {
numberOfRows;
databaseChangelogs = [];
injectBuildTool = true;
injectLogs = true;
async beforeQueue() {
if (!this.fromBlueprint) {
await this.composeWithBlueprints();
}
if (!this.delegateToBlueprint) {
await this.dependsOnBootstrap('java');
await this.dependsOnBootstrap('common');
}
}
get preparing() {
return this.asPreparingTaskGroup({
preparing({ application }) {
this.numberOfRows = application.clientFrameworkReact ? 10 : 30;
application.liquibaseDefaultSchemaName = '';
// Generate h2 properties at master.xml for blueprints that uses h2 for tests or others purposes.
application.liquibaseAddH2Properties ??= application.devDatabaseTypeH2Any;
},
liquibaseNeo4j({ application }) {
// TODO drop hardcoded version
if (application.databaseTypeNeo4j && application.javaManagedProperties['liquibase.version'] === '5.0.1') {
application.javaDependencies['liquibase-neo4j'] = '5.0.0';
}
},
checkDatabaseCompatibility({ application }) {
if (!application.databaseTypeSql && !application.databaseTypeNeo4j) {
throw new Error(`Database type ${application.databaseType} is not supported`);
}
if (!application.databaseTypeSql) {
// Add sql related derived properties
// TODO fix types
prepareSqlApplicationProperties({ application: application });
}
},
addNeedles({ source, application }) {
source.addLiquibaseChangelog = changelog => this.editFile(`${application.srcMainResources}config/liquibase/master.xml`, addLiquibaseChangelogCallback(changelog));
source.addLiquibaseIncrementalChangelog = changelog => this.editFile(`${application.srcMainResources}config/liquibase/master.xml`, addLiquibaseIncrementalChangelogCallback(changelog));
source.addLiquibaseConstraintsChangelog = changelog => this.editFile(`${application.srcMainResources}config/liquibase/master.xml`, addLiquibaseConstraintsChangelogCallback(changelog));
},
});
}
get [BaseEntityChangesGenerator.PREPARING]() {
return this.delegateTasksToBlueprint(() => this.preparing);
}
get preparingEachEntityField() {
return this.asPreparingEachEntityFieldTaskGroup({
prepareEntityField({ application, field }) {
if (!field.transient) {
prepareFieldForLiquibase(application, field);
}
},
validateConsistencyOfField({ entity, field }) {
if (field.columnRequired && field.liquibaseDefaultValueAttributeValue) {
this.handleCheckFailure(`The field ${field.fieldName} in entity ${entity.name} has both columnRequired and a defaultValue, this can lead to unexpected behaviors.`);
}
},
});
}
get [BaseEntityChangesGenerator.PREPARING_EACH_ENTITY_FIELD]() {
return this.delegateTasksToBlueprint(() => this.preparingEachEntityField);
}
get preparingEachEntityRelationship() {
return this.asPreparingEachEntityRelationshipTaskGroup({
prepareEntityRelationship({ application, entity, relationship }) {
prepareRelationshipForLiquibase({ application, entity, relationship });
relationship.onDelete = checkAndReturnRelationshipOnValue(relationship.onDelete, this);
relationship.onUpdate = checkAndReturnRelationshipOnValue(relationship.onUpdate, this);
},
});
}
get [BaseEntityChangesGenerator.PREPARING_EACH_ENTITY_RELATIONSHIP]() {
return this.delegateTasksToBlueprint(() => this.preparingEachEntityRelationship);
}
get postPreparingEachEntity() {
return this.asPostPreparingEachEntityTaskGroup({
postPrepareEntity,
});
}
get [BaseEntityChangesGenerator.POST_PREPARING_EACH_ENTITY]() {
return this.delegateTasksToBlueprint(() => this.postPreparingEachEntity);
}
get default() {
return this.asDefaultTaskGroup({
async calculateChangelogs({ application, entities, entityChanges }) {
if (!application.databaseTypeSql || this.options.skipDbChangelog || !entityChanges) {
return;
}
for (const databaseChangelog of entityChanges) {
if (!databaseChangelog.newEntity) {
// Previous entities are not prepared using default jhipster priorities.
// Prepare them.
const entity = databaseChangelog.previousEntity;
loadRequiredConfigIntoEntity(entity, this.jhipsterConfigWithDefaults);
// TODO fix types
prepareEntity(entity, this);
// TODO fix types
prepareEntityForServer(entity, application);
if (!entity.embedded && !entity.primaryKey) {
prepareEntityPrimaryKeyForTemplates.call(this, { entity: entity, application });
}
for (const field of entity.fields ?? []) {
prepareCommonFieldForTemplates(entity, field, this);
mutateData(field, commonMutateField);
prepareServerFieldForTemplates(application, entity, field, this);
prepareFieldForLiquibase(application, field);
}
}
}
for (const databaseChangelog of entityChanges) {
if (!databaseChangelog.newEntity) {
// Previous entities are not prepared using default jhipster priorities.
// Prepare them.
const entity = databaseChangelog.previousEntity;
for (const relationship of entity.relationships ?? []) {
mutateData(relationship, mutateBaseApplicationRelationship, mutateBaseApplicationRelationshipWithEntity);
prepareRelationship.call(this, entity, relationship, true);
prepareRelationshipForLiquibase({ application, entity, relationship });
}
postPrepareEntity.call(this, { application, entity });
}
}
const entitiesToWrite = this.options.entities ?? entities.filter(entity => !entity.builtIn && !entity.skipServer).map(entity => entity.name);
// Write only specified entities changelogs.
const changes = entityChanges.filter(databaseChangelog => entitiesToWrite.length === 0 || entitiesToWrite.includes(databaseChangelog.entityName));
for (const databaseChangelog of changes) {
if (databaseChangelog.newEntity) {
this.databaseChangelogs.push(this.prepareChangelog({
databaseChangelog: {
...databaseChangelog,
changelogData: { ...databaseChangelog.changelogData },
},
application,
}));
}
else if (databaseChangelog.addedFields.length > 0 || databaseChangelog.removedFields.length > 0) {
this.databaseChangelogs.push(this.prepareChangelog({
databaseChangelog: {
...databaseChangelog,
changelogData: { ...databaseChangelog.changelogData },
fieldChangelog: true,
addedRelationships: [],
removedRelationships: [],
removedDefaultValueFields: [],
addedDefaultValueFields: [],
relationshipsToRecreateForeignKeysOnly: [],
},
application,
}));
}
}
// Relationships needs to be added later to make sure every related field is already added.
for (const databaseChangelog of changes) {
if (databaseChangelog.incremental &&
(databaseChangelog.addedRelationships.length > 0 ||
databaseChangelog.removedRelationships.length > 0 ||
databaseChangelog.removedDefaultValueFields.length > 0 ||
databaseChangelog.addedDefaultValueFields.length > 0)) {
this.databaseChangelogs.push(this.prepareChangelog({
databaseChangelog: {
...databaseChangelog,
changelogData: { ...databaseChangelog.changelogData },
relationshipChangelog: true,
addedFields: [],
removedFields: [],
},
application,
}));
}
}
this.databaseChangelogs = this.databaseChangelogs.filter(Boolean);
},
});
}
get [BaseEntityChangesGenerator.DEFAULT]() {
return this.delegateTasksToBlueprint(() => this.default);
}
get writing() {
return this.asWritingTaskGroup({
async writing({ application }) {
await this.writeFiles({
sections: liquibaseFiles,
context: {
...application,
recreateInitialChangelog: this.recreateInitialChangelog,
},
});
},
});
}
get [BaseEntityChangesGenerator.WRITING]() {
return this.delegateTasksToBlueprint(() => this.writing);
}
get writingEntities() {
return this.asWritingEntitiesTaskGroup({
writeChangelogs() {
return Promise.all(this.databaseChangelogs.map(databaseChangelog => this.writeChangelog({ databaseChangelog })));
},
});
}
get [BaseEntityChangesGenerator.WRITING_ENTITIES]() {
return this.delegateTasksToBlueprint(() => this.writingEntities);
}
get postWriting() {
return this.asPostWritingTaskGroup({
customizeSpringLogs({ source }) {
if (!this.injectLogs)
return;
source.addMainLog?.({ name: 'liquibase', level: 'WARN' });
source.addMainLog?.({ name: 'LiquibaseSchemaResolver', level: 'INFO' });
source.addTestLog?.({ name: 'liquibase', level: 'WARN' });
source.addTestLog?.({ name: 'LiquibaseSchemaResolver', level: 'INFO' });
},
customizeMaven({ source, application }) {
if (!application.buildToolMaven || !this.injectBuildTool)
return;
if (!application.javaDependencies) {
throw new Error('Some application fields are be mandatory');
}
const { javaDependencies } = application;
const shouldAddProperty = (property, value) => {
return value && !source.hasJavaProperty?.(property) && application.javaManagedProperties[property] !== value;
};
const checkProperty = (property) => {
if (!source.hasJavaManagedProperty?.(property) && !source.hasJavaProperty?.(property)) {
const message = `${property} is required by maven-liquibase-plugin, make sure to add it to your pom.xml`;
if (this.skipChecks) {
this.log.warn(message);
}
else {
throw new Error(message);
}
}
};
const { 'jakarta-persistence': persistenceVersion, 'jakarta-validation': validationVersion, h2: h2Version, liquibase: liquibaseVersion, } = javaDependencies;
const relationalApplication = application;
const databaseTypeProfile = relationalApplication.devDatabaseTypeH2Any ? 'prod' : undefined;
let liquibasePluginHibernateDialect;
let liquibasePluginJdbcDriver;
const mavenProperties = [];
if (relationalApplication.devDatabaseTypeH2Any) {
// eslint-disable-next-line no-template-curly-in-string
liquibasePluginHibernateDialect = '${liquibase-plugin.hibernate-dialect}';
// eslint-disable-next-line no-template-curly-in-string
liquibasePluginJdbcDriver = '${liquibase-plugin.driver}';
if (shouldAddProperty('h2.version', h2Version)) {
mavenProperties.push({ property: 'h2.version', value: h2Version });
}
else {
checkProperty('h2.version');
}
mavenProperties.push({ property: 'liquibase-plugin.hibernate-dialect' }, { property: 'liquibase-plugin.driver' }, { inProfile: 'dev', property: 'liquibase-plugin.hibernate-dialect', value: relationalApplication.devHibernateDialect }, { inProfile: 'prod', property: 'liquibase-plugin.hibernate-dialect', value: relationalApplication.prodHibernateDialect }, { inProfile: 'dev', property: 'liquibase-plugin.driver', value: relationalApplication.devJdbcDriver }, { inProfile: 'prod', property: 'liquibase-plugin.driver', value: relationalApplication.prodJdbcDriver });
}
else {
liquibasePluginHibernateDialect = relationalApplication.prodHibernateDialect;
liquibasePluginJdbcDriver = relationalApplication.prodJdbcDriver;
}
if (shouldAddProperty('jakarta-validation.version', validationVersion)) {
mavenProperties.push({ property: 'jakarta-validation.version', value: validationVersion });
}
else {
checkProperty('jakarta-validation.version');
}
if (shouldAddProperty('jakarta-persistence.version', persistenceVersion)) {
mavenProperties.push({ property: 'jakarta-persistence.version', value: persistenceVersion });
}
else {
checkProperty('jakarta-persistence.version');
}
if (shouldAddProperty('liquibase.version', liquibaseVersion)) {
mavenProperties.push({ property: 'liquibase.version', value: liquibaseVersion });
}
else {
checkProperty('liquibase.version');
}
source.addMavenDefinition?.({
properties: [
...mavenProperties,
{ inProfile: 'no-liquibase', property: 'profile.no-liquibase', value: ',no-liquibase' },
{ property: 'profile.no-liquibase' },
{ property: 'liquibase-plugin.url' },
{ property: 'liquibase-plugin.username' },
{ property: 'liquibase-plugin.password' },
{ inProfile: 'dev', property: 'liquibase-plugin.url', value: relationalApplication.devLiquibaseUrl },
{ inProfile: 'dev', property: 'liquibase-plugin.username', value: relationalApplication.devDatabaseUsername },
{ inProfile: 'dev', property: 'liquibase-plugin.password', value: relationalApplication.devDatabasePassword },
{ inProfile: 'prod', property: 'liquibase-plugin.url', value: relationalApplication.prodLiquibaseUrl },
{ inProfile: 'prod', property: 'liquibase-plugin.username', value: relationalApplication.prodDatabaseUsername },
{ inProfile: 'prod', property: 'liquibase-plugin.password', value: relationalApplication.prodDatabasePassword },
],
pluginManagement: [
{
groupId: 'org.liquibase',
artifactId: 'liquibase-maven-plugin',
// eslint-disable-next-line no-template-curly-in-string
version: '${liquibase.version}',
additionalContent: mavenPlugin({
backendTypeSpringBoot: application.backendTypeSpringBoot,
hibernateNamingPhysicalStrategy: application.hibernateNamingPhysicalStrategy,
hibernateNamingImplicitStrategy: application.hibernateNamingImplicitStrategy,
packageName: application.packageName,
srcMainResources: application.srcMainResources,
authenticationTypeOauth2: application.authenticationTypeOauth2,
devDatabaseTypeH2Any: relationalApplication.devDatabaseTypeH2Any,
driver: liquibasePluginJdbcDriver,
hibernateDialect: liquibasePluginHibernateDialect,
defaultSchemaName: application.liquibaseDefaultSchemaName,
// eslint-disable-next-line no-template-curly-in-string
url: '${liquibase-plugin.url}',
// eslint-disable-next-line no-template-curly-in-string
username: '${liquibase-plugin.username}',
// eslint-disable-next-line no-template-curly-in-string
password: '${liquibase-plugin.password}',
}),
},
],
dependencies: [
{
groupId: 'org.liquibase',
artifactId: 'liquibase-core',
// eslint-disable-next-line no-template-curly-in-string
version: '${liquibase.version}',
},
],
});
if (relationalApplication.prodDatabaseTypeMssql) {
source.addMavenDependency?.({
inProfile: databaseTypeProfile,
groupId: 'org.liquibase.ext',
artifactId: 'liquibase-mssql',
// eslint-disable-next-line no-template-curly-in-string
version: '${liquibase.version}',
});
}
if (application.databaseTypeNeo4j) {
source.addMavenDependency?.([
{
groupId: 'org.liquibase.ext',
artifactId: 'liquibase-neo4j',
// eslint-disable-next-line no-template-curly-in-string
version: application.javaDependencies['liquibase-neo4j'] ?? '${liquibase.version}',
// Exclude current neo4j driver and use the one provided by spring-data
// See: https://github.com/jhipster/generator-jhipster/pull/24241
additionalContent: `
<exclusions>
<exclusion>
<groupId>org.neo4j.driver</groupId>
<artifactId>neo4j-java-driver</artifactId>
</exclusion>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>`,
},
]);
}
},
injectGradle({ source, application }) {
if (!application.buildToolGradle || !this.injectBuildTool)
return;
if (!application.javaDependencies) {
throw new Error('Some application fields are be mandatory');
}
const { liquibase: liquibaseVersion, 'gradle-liquibase': gradleLiquibaseVersion } = application.javaDependencies;
if (!liquibaseVersion) {
this.log.warn('liquibaseVersion is required by gradle-liquibase-plugin, make sure to add it to your dependencies');
}
else {
source.addGradleProperty?.({ property: 'liquibaseVersion', value: liquibaseVersion });
}
source.addGradleProperty?.({ property: 'liquibaseTaskPrefix', value: 'liquibase' });
source.addGradleProperty?.({ property: 'liquibasePluginVersion', value: gradleLiquibaseVersion });
source.applyFromGradle?.({ script: 'gradle/liquibase.gradle' });
source.addGradlePlugin?.({ id: 'org.liquibase.gradle' });
// eslint-disable-next-line no-template-curly-in-string
source.addGradlePluginManagement?.({ id: 'org.liquibase.gradle', version: '${liquibasePluginVersion}' });
if (application.databaseTypeSql && !application.reactive) {
source.addGradleDependency?.({
scope: 'liquibaseRuntime',
groupId: 'org.liquibase.ext',
artifactId: 'liquibase-hibernate6',
// eslint-disable-next-line no-template-curly-in-string
version: liquibaseVersion ? '${liquibaseVersion}' : "${dependencyManagement.importedProperties['liquibase.version']}",
}, { gradleFile: 'gradle/liquibase.gradle' });
}
if (application.databaseTypeNeo4j && application.javaDependencies['liquibase-neo4j']) {
source.addGradleDependencyCatalogLibrary({
libraryName: 'liquibase-neo4j',
module: 'org.liquibase.ext:liquibase-neo4j',
version: application.javaDependencies['liquibase-neo4j'],
});
}
},
});
}
get [BaseEntityChangesGenerator.POST_WRITING]() {
return this.delegateTasksToBlueprint(() => this.postWriting);
}
get postWritingEntities() {
return this.asPostWritingEntitiesTaskGroup({
postWriteChangelogs({ source }) {
return Promise.all(this.databaseChangelogs.map(databaseChangelog => {
const { entity, changelogData } = databaseChangelog;
if (entity.skipServer) {
return undefined;
}
if (databaseChangelog.newEntity) {
return this._addLiquibaseFilesReferences({ entity, databaseChangelog, source });
}
if (changelogData.requiresUpdateChangelogs) {
return this._addUpdateFilesReferences({ entity, databaseChangelog, changelogData, source });
}
return undefined;
}));
},
});
}
get [BaseEntityChangesGenerator.POST_WRITING_ENTITIES]() {
return this.delegateTasksToBlueprint(() => this.postWritingEntities);
}
/* ======================================================================== */
/* private methods use within generator */
/* ======================================================================== */
isChangelogNew({ entityName, changelogDate }) {
return !fs.existsSync(this.destinationPath(`src/main/resources/config/liquibase/changelog/${changelogDate}_added_entity_${entityName}.xml`));
}
/**
* Write files for new entities.
*/
_writeLiquibaseFiles({ context: writeContext, changelogData }) {
const promises = [];
const context = {
...writeContext,
skipFakeData: changelogData.skipFakeData,
fields: changelogData.allFields,
allFields: changelogData.allFields,
relationships: changelogData.relationships,
};
// Write initial liquibase files
promises.push(this.writeFiles({ sections: addEntityFiles, context }));
if (!changelogData.skipFakeData) {
promises.push(this.writeFiles({ sections: fakeFiles, context }));
}
return Promise.all(promises);
}
/**
* Write files for new entities.
*/
_addLiquibaseFilesReferences({ entity, databaseChangelog, source, }) {
const fileName = `${databaseChangelog.changelogDate}_added_entity_${entity.entityClass}`;
source.addLiquibaseChangelog({ changelogName: fileName, section: entity.incremental ? 'incremental' : 'base' });
if (entity.anyRelationshipIsOwnerSide) {
const constFileName = `${databaseChangelog.changelogDate}_added_entity_constraints_${entity.entityClass}`;
source.addLiquibaseChangelog({ changelogName: constFileName, section: entity.incremental ? 'incremental' : 'constraints' });
}
}
/**
* Write files for updated entities.
*/
_writeUpdateFiles({ context: writeContext, changelogData }) {
const { addedFields, allFields, removedFields, addedRelationships, removedRelationships, hasFieldConstraint, hasRelationshipConstraint, shouldWriteAnyRelationship, relationshipsToRecreateForeignKeysOnly, hasDefaultValueChange, removedDefaultValueFields, addedDefaultValueFields, } = changelogData;
const context = {
...writeContext,
skipFakeData: changelogData.skipFakeData,
addedFields,
removedFields,
fields: addedFields,
allFields,
hasFieldConstraint,
addedRelationships,
removedRelationships,
relationships: addedRelationships,
hasRelationshipConstraint,
shouldWriteAnyRelationship,
relationshipsToRecreateForeignKeysOnly,
hasDefaultValueChange,
removedDefaultValueFields,
addedDefaultValueFields,
};
const promises = [];
if (this._isBasicEntityUpdate(changelogData)) {
promises.push(this.writeFiles({ sections: updateEntityFiles, context }));
}
if (this._requiresWritingFakeData(changelogData)) {
promises.push(this.writeFiles({ sections: fakeFiles, context }));
promises.push(this.writeFiles({ sections: updateMigrateFiles, context }));
}
if (this._requiresConstraintUpdates(changelogData)) {
promises.push(this.writeFiles({ sections: updateConstraintsFiles, context }));
}
return Promise.all(promises);
}
_requiresConstraintUpdates(changelogData) {
return changelogData.hasFieldConstraint || changelogData.addedRelationships.length > 0 || changelogData.hasDefaultValueChange;
}
_isBasicEntityUpdate(changelogData) {
return changelogData.addedFields.length > 0 || changelogData.removedFields.length > 0 || changelogData.shouldWriteAnyRelationship;
}
_requiresWritingFakeData(changelogData) {
return !changelogData.skipFakeData && (changelogData.addedFields.length || changelogData.addedRelationships.length);
}
/**
* Write files for updated entities.
*/
_addUpdateFilesReferences({ entity, databaseChangelog, changelogData, source, }) {
if (this._isBasicEntityUpdate(changelogData)) {
source.addLiquibaseIncrementalChangelog({
changelogName: `${databaseChangelog.changelogDate}_updated_entity_${entity.entityClass}`,
});
}
if (this._requiresWritingFakeData(changelogData)) {
source.addLiquibaseIncrementalChangelog({
changelogName: `${databaseChangelog.changelogDate}_updated_entity_migrate_${entity.entityClass}`,
});
}
if (this._requiresConstraintUpdates(changelogData)) {
source.addLiquibaseIncrementalChangelog({
changelogName: `${databaseChangelog.changelogDate}_updated_entity_constraints_${entity.entityClass}`,
});
}
}
/**
* @private
* Format As Liquibase Remarks
*
* @param {string} text - text to format
* @param {boolean} addRemarksTag - add remarks tag
* @returns formatted liquibase remarks
*/
formatAsLiquibaseRemarks(text, addRemarksTag = false) {
return liquibaseComment(text, addRemarksTag);
}
/**
* @private
* Create the fitting liquibase default value attribute for a field.
* @param field
* @param leadingWhitespace
* @returns
*/
createDefaultValueLiquibaseAttribute(field, leadingWhitespace = false) {
if (field.liquibaseDefaultValueAttributeValue === undefined) {
return '';
}
return `${leadingWhitespace ? ' ' : ''}${field.liquibaseDefaultValueAttributeName}="${escape(field.liquibaseDefaultValueAttributeValue)}"`;
}
prepareChangelog({ databaseChangelog, application }) {
if (!databaseChangelog.changelogDate) {
databaseChangelog.changelogDate = this.nextTimestamp();
}
const entity = databaseChangelog.entity;
if (entity.skipServer) {
return undefined;
}
const entityChanges = databaseChangelog.changelogData;
entityChanges.skipFakeData = application.skipFakeData || entity.skipFakeData;
entityChanges.allFields = entity.fields.filter(field => !field.transient);
if (databaseChangelog.newEntity) {
entityChanges.fields = entityChanges.allFields;
}
else {
entityChanges.addedFields = databaseChangelog.addedFields.filter(field => !field.transient);
entityChanges.removedFields = databaseChangelog.removedFields.filter(field => !field.transient);
}
const seed = `${entity.entityClass}-liquibase`;
this.resetEntitiesFakeData(seed);
entity.liquibaseFakeData = [];
// fakeDataCount must be limited to the size of required unique relationships.
Object.defineProperty(entity, 'fakeDataCount', {
get: () => {
const uniqueRelationships = entity.relationships.filter(rel => rel.unique && (rel.columnRequired || rel.id));
return min([entity.liquibaseFakeData.length, ...uniqueRelationships.map(rel => rel.otherEntity.fakeDataCount)]);
},
configurable: true,
});
for (let rowNumber = 0; rowNumber < this.numberOfRows; rowNumber++) {
const rowData = {};
const fields = databaseChangelog.newEntity
? // generate id fields first to improve reproducibility
[...entityChanges.fields.filter(f => f.id), ...entityChanges.fields.filter(f => !f.id)]
: [...entityChanges.allFields.filter(f => f.id), ...entityChanges.addedFields.filter(f => !f.id)];
fields.forEach(field => {
if ('derived' in field && field.derived) {
const derivedField = field;
Object.defineProperty(rowData, field.fieldName, {
get: () => {
if (!derivedField.derivedEntity.liquibaseFakeData || rowNumber >= derivedField.derivedEntity.liquibaseFakeData.length) {
return undefined;
}
return derivedField.derivedEntity.liquibaseFakeData[rowNumber][field.fieldName];
},
});
return;
}
let data;
if (field.id && [TYPE_INTEGER, TYPE_LONG].includes(field.fieldType)) {
data = rowNumber + 1;
}
else {
data = field.generateFakeData();
}
// Ensure auditing timestamps are deterministic in Liquibase fake-data.
// - created_by / last_modified_by should come from fake-data generation.
// - created_date / last_modified_date should always use a fixed timestamp.
if (field.fieldName === 'created_date' || field.fieldName === 'last_modified_date') {
// Use ISO-8601 string, compatible with Liquibase CSV timestamp columns.
data = '2020-01-01T00:00:00Z';
}
rowData[field.fieldName] = data;
});
entity.liquibaseFakeData.push(rowData);
}
if (databaseChangelog.newEntity) {
entityChanges.relationships = entity.relationships;
}
else {
entityChanges.addedRelationships = databaseChangelog.addedRelationships;
entityChanges.removedRelationships = databaseChangelog.removedRelationships;
entityChanges.relationshipsToRecreateForeignKeysOnly = databaseChangelog.relationshipsToRecreateForeignKeysOnly;
entityChanges.removedDefaultValueFields = databaseChangelog.removedDefaultValueFields;
entityChanges.addedDefaultValueFields = databaseChangelog.addedDefaultValueFields;
}
const { prodDatabaseType, databaseType, authenticationType, jhiPrefix } = entity;
/* Required by the templates */
databaseChangelog.writeContext = {
entity,
databaseChangelog: databaseChangelog,
changelogDate: databaseChangelog.changelogDate,
databaseType,
prodDatabaseType,
authenticationType,
jhiPrefix,
reactive: application.reactive,
incrementalChangelog: application.incrementalChangelog,
recreateInitialChangelog: this.recreateInitialChangelog,
};
if (databaseChangelog.newEntity) {
return databaseChangelog;
}
entityChanges.requiresUpdateChangelogs =
entityChanges.addedFields.length > 0 ||
entityChanges.removedFields.length > 0 ||
entityChanges.addedRelationships.some(relationship => relationship.shouldWriteRelationship || relationship.shouldWriteJoinTable) ||
entityChanges.removedRelationships.some(relationship => relationship.shouldWriteRelationship || relationship.shouldWriteJoinTable) ||
entityChanges.addedDefaultValueFields.length > 0 ||
entityChanges.removedDefaultValueFields.length > 0;
if (entityChanges.requiresUpdateChangelogs) {
entityChanges.hasFieldConstraint = entityChanges.addedFields.some(field => field.unique || (field.columnRequired && !field.liquibaseDefaultValueAttributeValue) || field.shouldCreateContentType);
entityChanges.hasDefaultValueChange =
entityChanges.addedDefaultValueFields.length > 0 || entityChanges.removedDefaultValueFields.length > 0;
entityChanges.hasRelationshipConstraint = entityChanges.addedRelationships.some(relationship => (relationship.shouldWriteRelationship || relationship.shouldWriteJoinTable) && (relationship.unique || !relationship.nullable));
entityChanges.shouldWriteAnyRelationship =
entityChanges.addedRelationships.some(relationship => relationship.shouldWriteRelationship || relationship.shouldWriteJoinTable) ||
entityChanges.removedRelationships.some(relationship => relationship.shouldWriteRelationship || relationship.shouldWriteJoinTable);
}
return databaseChangelog;
}
writeChangelog({ databaseChangelog }) {
const { writeContext: context, changelogData } = databaseChangelog;
if (databaseChangelog.newEntity) {
return this._writeLiquibaseFiles({ context, changelogData });
}
if (changelogData.requiresUpdateChangelogs) {
return this._writeUpdateFiles({ context, changelogData });
}
return undefined;
}
/**
* @private
* get a foreign key constraint name for tables in JHipster preferred style.
*
* @param {string} entityName - name of the entity
* @param {string} relationshipName - name of the related entity
* @param {string} prodDatabaseType - database type
* @param {boolean} noSnakeCase - do not convert names to snakecase
*/
getFKConstraintName(entityName, relationshipName, prodDatabaseType, noSnakeCase) {
const result = getFKConstraintName(entityName, relationshipName, { prodDatabaseType, noSnakeCase });
this.validateResult(result);
return result.value;
}
/**
* @private
* get a unique constraint name for tables in JHipster preferred style.
*
* @param {string} entityName - name of the entity
* @param {string} columnName - name of the column
* @param {string} prodDatabaseType - database type
* @param {boolean} noSnakeCase - do not convert names to snakecase
*/
getUXConstraintName(entityName, columnName, prodDatabaseType, noSnakeCase) {
const result = getUXConstraintName(entityName, columnName, { prodDatabaseType, noSnakeCase });
this.validateResult(result);
return result.value;
}
}