Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor store.service Add support for relations, enums, subscription on Schema Migration #2251

Merged
merged 23 commits into from
Feb 15, 2024
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
f39f985
update schema migration with raw query execution, instead of sequeliz…
bz888 Feb 5, 2024
629b57c
fix tests
bz888 Feb 5, 2024
6fdcc36
update changelog
bz888 Feb 5, 2024
ba27ace
tidy up test for historical db sync, update statement generators
bz888 Feb 7, 2024
cc23277
update topo logic, add tests, add sync logic with cyclic support
bz888 Feb 8, 2024
23658a1
revert bad rebase on migeration class
bz888 Feb 8, 2024
225d464
revert bad rebase on migeration class
bz888 Feb 8, 2024
cdeeec9
refacotr toposort, updated sync-helper tests
bz888 Feb 8, 2024
ab0785f
update schema migration with raw query execution, instead of sequeliz…
bz888 Feb 5, 2024
2b1e969
add relational support on schema migration, fix table comments
bz888 Feb 1, 2024
76d1946
fix tableComments, refactor createTable and createModel, update creat…
bz888 Feb 5, 2024
c3cd09b
add drop relations, add check for fkey on creation relations, add tests
bz888 Feb 8, 2024
8c20565
refactor drop table and column with relational foreign keys, fix test…
bz888 Feb 10, 2024
d63efae
update syncSchema logic with migration.run()
bz888 Feb 11, 2024
4c5fee5
added test for enum creation, and enum support on drop and creation
bz888 Feb 12, 2024
7dbb468
clean up comments and logs
bz888 Feb 12, 2024
d0b6c36
update based on review, add idempotency to db queries
bz888 Feb 13, 2024
3a8c55a
check matching ddl, fixed relation comments, benchmarked init and res…
bz888 Feb 13, 2024
fc0fa74
update logic for get exisitng f key and enumMap, refactor based on re…
bz888 Feb 14, 2024
026a794
update logger
bz888 Feb 14, 2024
1ef14fc
update tests, clean up todo
bz888 Feb 15, 2024
4f47d7a
update tests, clean up todo, update improves
bz888 Feb 15, 2024
a7cb813
fix dropEnum and dropEnum test
bz888 Feb 15, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion packages/node-core/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).


## [Unreleased]
### Added
- Schema Migration support for Enums, Relations, Subscription (#2251)
-
### Fixed
- Fixed non-atomic schema migration execution (#2244)

## [7.2.1] - 2024-02-07
### Added
Expand Down
1 change: 1 addition & 0 deletions packages/node-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"prom-client": "^14.0.1",
"source-map": "^0.7.4",
"tar": "^6.1.11",
"toposort-class": "^1.0.1",
"vm2": "^3.9.19",
"yargs": "^16.2.0"
},
Expand Down
1 change: 0 additions & 1 deletion packages/node-core/src/configure/ProjectUpgrade.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,6 @@ export class ProjectUpgradeSevice<P extends ISubqueryProject = ISubqueryProject>
assert(this.migrationService, 'MigrationService is undefined');
if (this.config.allowSchemaMigration) {
const modifiedModels = await this.migrationService.run(project.schema, newProject.schema, transaction);

if (modifiedModels) {
this.#storeCache?.updateModels(modifiedModels);
}
Expand Down
29 changes: 28 additions & 1 deletion packages/node-core/src/configure/SchemaMigration.service.spec.ts
bz888 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
// SPDX-License-Identifier: GPL-3.0

import path from 'path';
import {buildSchemaFromFile} from '@subql/utils';
import {compareEnums} from '@subql/node-core/configure/migration-service/migration-helpers';
import {buildSchemaFromFile, GraphQLEnumsType} from '@subql/utils';
import {SchemaMigrationService} from './migration-service';
import {ProjectUpgradeSevice} from './ProjectUpgrade.service';

Expand Down Expand Up @@ -57,4 +58,30 @@ describe('SchemaMigration', () => {
expect(v).toBe(false);
});
});
it('compare enums on modified enums', () => {
const currentEnums = [
{
name: 'TestEnum',
values: ['GOOD', 'BAD', 'NEUTRAL', 'CHAOS'],
} as GraphQLEnumsType,
];
const nextEnum = [
{
name: 'TestEnum',
values: ['GOOD', 'BAD', 'NEUTRAL'],
} as GraphQLEnumsType,
];

const changes: any = {
addedEnums: [],
removedEnums: [],
modifiedEnums: [],
};
compareEnums(currentEnums, nextEnum, changes);
expect(changes).toStrictEqual({
addedEnums: [],
removedEnums: [],
modifiedEnums: [{name: 'TestEnum', values: ['GOOD', 'BAD', 'NEUTRAL']}],
});
});
});
Original file line number Diff line number Diff line change
@@ -1,31 +1,38 @@
// Copyright 2020-2024 SubQuery Pte Ltd authors & contributors
// SPDX-License-Identifier: GPL-3.0

import {getAllEntitiesRelations} from '@subql/utils';
import {SUPPORT_DB} from '@subql/common';
import {getAllEntitiesRelations, GraphQLModelsType, GraphQLRelationsType} from '@subql/utils';
import {ModelStatic, Sequelize, Transaction} from '@subql/x-sequelize';
import {GraphQLSchema} from 'graphql';
import {StoreService} from '../../indexer';
import {getLogger} from '../../logger';
import {sortModels} from '../../utils';
import {NodeConfig} from '../NodeConfig';
import {Migration} from './migration';
import {
alignModelOrder,
compareEnums,
compareModels,
compareRelations,
hasChanged,
ModifiedModels,
schemaChangesLoggerMessage,
SchemaChangesType,
} from './migration-helpers';

const logger = getLogger('SchemaMigrationService');

export class SchemaMigrationService {
private withoutForeignKeys = false;

constructor(
private sequelize: Sequelize,
private storeService: StoreService,
private flushCache: (flushAll?: boolean) => Promise<void>,
private dbSchema: string,
private config: NodeConfig
private config: NodeConfig,
private dbType: SUPPORT_DB = SUPPORT_DB.postgres
) {}

static validateSchemaChanges(currentSchema: GraphQLSchema, nextSchema: GraphQLSchema): boolean {
Expand All @@ -41,7 +48,7 @@ export class SchemaMigrationService {
return true;
}

static schemaComparator(currentSchema: GraphQLSchema, nextSchema: GraphQLSchema): SchemaChangesType {
static schemaComparator(currentSchema: GraphQLSchema | null, nextSchema: GraphQLSchema): SchemaChangesType {
const currentData = getAllEntitiesRelations(currentSchema);
const nextData = getAllEntitiesRelations(nextSchema);

Expand All @@ -53,7 +60,7 @@ export class SchemaMigrationService {
removedRelations: [],
addedEnums: [],
removedEnums: [],
allEnums: currentData.enums, // TODO support for Enum migration
modifiedEnums: [],
};

compareEnums(currentData.enums, nextData.enums, changes);
Expand All @@ -63,84 +70,113 @@ export class SchemaMigrationService {
return changes;
}

private orderModelsByRelations(models: GraphQLModelsType[], relations: GraphQLRelationsType[]): GraphQLModelsType[] {
const sortedModels = sortModels(relations, models);
if (sortedModels === null) {
this.withoutForeignKeys = true;
return models;
} else {
this.withoutForeignKeys = false;
return sortedModels.reverse();
}
}

async run(
currentSchema: GraphQLSchema,
currentSchema: GraphQLSchema | null,
nextSchema: GraphQLSchema,
transaction: Transaction | undefined
): Promise<ModelStatic<any>[] | void> {
const schemaDifference = SchemaMigrationService.schemaComparator(currentSchema, nextSchema);

const {
addedEnums,
addedModels,
addedRelations,
allEnums, // TODO enum support
modifiedEnums,
modifiedModels,
removedEnums,
removedModels,
removedRelations,
} = schemaDifference;

if (!hasChanged(schemaDifference)) {
logger.info('No Schema changes');
return;
}

if (addedEnums.length > 0 || removedEnums.length > 0) {
throw new Error('Schema Migration currently does not support Enum removal and creation');
if (modifiedEnums.length > 0) {
throw new Error(
`Modifying enums is currently not supported. Please revert the changes to the following enums: ${modifiedEnums
.map((e) => e.name)
.join(', ')}`
);
}

if (removedRelations.length > 0 || addedRelations.length > 0) {
throw new Error('Schema Migration currently does not support Relational removal or creation');
}
const sortedSchemaModels = this.orderModelsByRelations(
getAllEntitiesRelations(nextSchema).models,
getAllEntitiesRelations(nextSchema).relations
);

const sortedAddedModels = alignModelOrder<GraphQLModelsType[]>(sortedSchemaModels, addedModels);
const sortedModifiedModels = alignModelOrder<ModifiedModels>(sortedSchemaModels, modifiedModels);

await this.flushCache(true);
const migrationAction = await Migration.create(
this.sequelize,
this.storeService,
this.dbSchema,
nextSchema,
currentSchema,
this.config,
logger
this.dbType
);

// TODO this should only be printed if schema migration is enabled and not store.service sync schema
logger.info(`${schemaChangesLoggerMessage(schemaDifference)}`);

try {
if (removedModels.length) {
for (const model of removedModels) {
migrationAction.dropTable(model);
}
for (const enumValue of addedEnums) {
await migrationAction.createEnum(enumValue);
}

if (addedModels.length) {
for (const model of addedModels) {
await migrationAction.createTable(model);
}
for (const model of removedModels) {
migrationAction.dropTable(model);
}

for (const model of sortedAddedModels) {
await migrationAction.createTable(model, this.withoutForeignKeys);
}

if (Object.keys(modifiedModels).length) {
const entities = Object.keys(modifiedModels);
for (const model of entities) {
const modelValue = modifiedModels[model];
const entities = Object.keys(sortedModifiedModels);
for (const model of entities) {
const modelValue = sortedModifiedModels[model];

for (const index of modelValue.removedIndexes) {
migrationAction.dropIndex(modelValue.model, index);
}
for (const index of modelValue.removedIndexes) {
migrationAction.dropIndex(modelValue.model, index);
}

for (const field of modelValue.removedFields) {
migrationAction.dropColumn(modelValue.model, field);
}
for (const field of modelValue.removedFields) {
migrationAction.dropColumn(modelValue.model, field);
}

for (const field of modelValue.addedFields) {
migrationAction.createColumn(modelValue.model, field);
}
for (const field of modelValue.addedFields) {
migrationAction.createColumn(modelValue.model, field);
}

for (const index of modelValue.addedIndexes) {
migrationAction.createIndex(modelValue.model, index);
}
for (const index of modelValue.addedIndexes) {
migrationAction.createIndex(modelValue.model, index);
}
}

if (addedRelations.length) {
for (const relationModel of addedRelations) {
migrationAction.createRelation(relationModel);
}
}
bz888 marked this conversation as resolved.
Show resolved Hide resolved

for (const relationModel of removedRelations) {
migrationAction.dropRelation(relationModel);
}
for (const enumValue of removedEnums) {
migrationAction.dropEnum(enumValue);
}
return migrationAction.run(transaction);
} catch (e: any) {
logger.error(e, 'Failed to execute Schema Migration');
Expand Down
Loading
Loading