diff --git a/.github/workflows/nocobase-test.yml b/.github/workflows/nocobase-test.yml index ab3c1b1a7..ed1e221e7 100644 --- a/.github/workflows/nocobase-test.yml +++ b/.github/workflows/nocobase-test.yml @@ -7,19 +7,16 @@ on: - develop paths: - 'packages/**' - # paths-ignore: - # - 'docs/**' pull_request: paths: - 'packages/**' - # paths-ignore: - # - 'docs/**' jobs: sqlite-test: strategy: matrix: node_version: ['16', '18'] + underscored: [true, false] runs-on: ubuntu-latest container: node:${{ matrix.node_version }} steps: @@ -36,12 +33,14 @@ jobs: env: DB_DIALECT: sqlite DB_STORAGE: /tmp/db.sqlite + DB_UNDERSCORED: ${{ matrix.underscored }} postgres-test: strategy: matrix: node_version: ['16', '18'] - + underscored: [true, false] + schema: [public, nocobase] runs-on: ubuntu-latest container: node:${{ matrix.node_version }} services: @@ -77,11 +76,14 @@ jobs: DB_USER: nocobase DB_PASSWORD: password DB_DATABASE: nocobase + DB_UNDERSCORED: ${{ matrix.underscored }} + DB_SCHEMA: ${{ matrix.schema }} mysql-test: strategy: matrix: node_version: ['16', '18'] + underscored: [true, false] runs-on: ubuntu-latest container: node:${{ matrix.node_version }} services: @@ -109,97 +111,4 @@ jobs: DB_USER: root DB_PASSWORD: password DB_DATABASE: nocobase - - sqlite-underscored-test: - strategy: - matrix: - node_version: ['16'] - runs-on: ubuntu-latest - container: node:${{ matrix.node_version }} - steps: - - uses: actions/checkout@v2 - - name: Use Node.js ${{ matrix.node_version }} - uses: actions/setup-node@v2 - with: - node-version: ${{ matrix.node_version }} - cache: 'yarn' - - run: yarn install - - name: Test with Sqlite - run: yarn test - env: - DB_DIALECT: sqlite - DB_STORAGE: /tmp/db.sqlite - DB_UNDERSCORED: true - - postgres-underscored-test: - strategy: - matrix: - node_version: ['16'] - - runs-on: ubuntu-latest - container: node:${{ matrix.node_version }} - services: - # Label used to access the service container - postgres: - # Docker Hub image - image: postgres:10 - # Provide the password for postgres - env: - POSTGRES_USER: nocobase - POSTGRES_PASSWORD: password - # Set health checks to wait until postgres has started - options: >- - --health-cmd pg_isready - --health-interval 10s - --health-timeout 5s - --health-retries 5 - steps: - - uses: actions/checkout@v2 - - name: Use Node.js ${{ matrix.node_version }} - uses: actions/setup-node@v2 - with: - node-version: ${{ matrix.node_version }} - cache: 'yarn' - - run: yarn install - - name: Test with postgres - run: yarn test - env: - DB_DIALECT: postgres - DB_HOST: postgres - DB_PORT: 5432 - DB_USER: nocobase - DB_PASSWORD: password - DB_DATABASE: nocobase - DB_UNDERSCORED: true - - mysql-underscored-test: - strategy: - matrix: - node_version: ['16'] - runs-on: ubuntu-latest - container: node:${{ matrix.node_version }} - services: - mysql: - image: mysql:8 - env: - MYSQL_ROOT_PASSWORD: password - MYSQL_DATABASE: nocobase - options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 - steps: - - uses: actions/checkout@v2 - - name: Use Node.js ${{ matrix.node_version }} - uses: actions/setup-node@v2 - with: - node-version: ${{ matrix.node_version }} - cache: 'yarn' - - run: yarn install - - name: Test with MySQL - run: yarn test - env: - DB_DIALECT: mysql - DB_HOST: mysql - DB_PORT: 3306 - DB_USER: root - DB_PASSWORD: password - DB_DATABASE: nocobase - DB_UNDERSCORED: true + DB_UNDERSCORED: ${{ matrix.underscored }} diff --git a/packages/core/database/src/collection.ts b/packages/core/database/src/collection.ts index 3c43342bf..c4898c506 100644 --- a/packages/core/database/src/collection.ts +++ b/packages/core/database/src/collection.ts @@ -7,7 +7,7 @@ import { QueryInterfaceDropTableOptions, SyncOptions, Transactionable, - Utils + Utils, } from 'sequelize'; import { Database } from './database'; import { Field, FieldOptions } from './fields'; @@ -532,4 +532,18 @@ export class Collection< public isParent() { return this.context.database.inheritanceMap.isParentNode(this.name); } + + public addSchemaTableName() { + const tableName = this.model.tableName; + + if (this.options.schema) { + return this.db.utils.addSchema(tableName, this.options.schema); + } + + return tableName; + } + + public quotedTableName() { + return this.db.utils.quoteTable(this.addSchemaTableName()); + } } diff --git a/packages/core/database/src/database-utils/index.ts b/packages/core/database/src/database-utils/index.ts new file mode 100644 index 000000000..513ba1d3f --- /dev/null +++ b/packages/core/database/src/database-utils/index.ts @@ -0,0 +1,38 @@ +import Database from '../database'; +import lodash from 'lodash'; + +export default class DatabaseUtils { + constructor(public db: Database) {} + + addSchema(tableName, schema?) { + if (this.db.options.schema) { + schema = this.db.options.schema; + } + + if (schema) { + // @ts-ignore + tableName = this.db.sequelize.getQueryInterface().queryGenerator.addSchema({ + tableName, + _schema: this.db.options.schema, + }); + } + + return tableName; + } + + quoteTable(tableName) { + const queryGenerator = this.db.sequelize.getQueryInterface().queryGenerator; + // @ts-ignore + tableName = queryGenerator.quoteTable(lodash.isPlainObject(tableName) ? tableName : this.addSchema(tableName)); + + return tableName; + } + + schema() { + if (!this.db.inDialect('postgres')) { + return undefined; + } + + return this.db.options.schema || 'public'; + } +} diff --git a/packages/core/database/src/database.ts b/packages/core/database/src/database.ts index 90ffd7360..0ba129e3d 100644 --- a/packages/core/database/src/database.ts +++ b/packages/core/database/src/database.ts @@ -15,7 +15,7 @@ import { Sequelize, SyncOptions, Transactionable, - Utils + Utils, } from 'sequelize'; import { SequelizeStorage, Umzug } from 'umzug'; import { Collection, CollectionOptions, RepositoryType } from './collection'; @@ -58,9 +58,11 @@ import { SyncListener, UpdateListener, UpdateWithAssociationsListener, - ValidateListener + ValidateListener, } from './types'; -import { snakeCase } from './utils'; +import { patchSequelizeQueryInterface, snakeCase } from './utils'; + +import DatabaseUtils from './database-utils'; export interface MergeOptions extends merge.Options {} @@ -157,6 +159,7 @@ export class Database extends EventEmitter implements AsyncEmitter { modelCollection = new Map, Collection>(); tableNameCollectionMap = new Map(); + utils = new DatabaseUtils(this); referenceMap = new ReferencesMap(); inheritanceMap = new InheritanceMap(); @@ -197,9 +200,10 @@ export class Database extends EventEmitter implements AsyncEmitter { // https://github.com/sequelize/sequelize/issues/1774 require('pg').defaults.parseInt8 = true; } + this.options = opts; this.sequelize = new Sequelize(opts); - this.options = opts; + this.collections = new Map(); this.modelHook = new ModelHook(this); @@ -263,6 +267,7 @@ export class Database extends EventEmitter implements AsyncEmitter { }); this.initListener(); + patchSequelizeQueryInterface(this); } initListener() { @@ -321,6 +326,10 @@ export class Database extends EventEmitter implements AsyncEmitter { }); } } + + if (this.options.schema && !options.schema) { + options.schema = this.options.schema; + } }); } diff --git a/packages/core/database/src/mock-database.ts b/packages/core/database/src/mock-database.ts index 3e093d081..8ce980526 100644 --- a/packages/core/database/src/mock-database.ts +++ b/packages/core/database/src/mock-database.ts @@ -32,6 +32,7 @@ export function getConfigByEnv() { }, timezone: process.env.DB_TIMEZONE, underscored: process.env.DB_UNDERSCORED === 'true', + schema: process.env.DB_SCHEMA !== 'public' ? process.env.DB_SCHEMA : undefined, }; } diff --git a/packages/core/database/src/sync-runner.ts b/packages/core/database/src/sync-runner.ts index 7524a0f8e..0e158c26f 100644 --- a/packages/core/database/src/sync-runner.ts +++ b/packages/core/database/src/sync-runner.ts @@ -7,6 +7,8 @@ export class SyncRunner { const inheritedCollection = model.collection as InheritedCollection; const db = inheritedCollection.context.database; + const schemaName = db.options.schema || 'public'; + const dialect = db.sequelize.getDialect(); const queryInterface = db.sequelize.getQueryInterface(); @@ -27,7 +29,10 @@ export class SyncRunner { const parentTables = parents.map((parent) => parent.model.tableName); - const tableName = model.getTableName(); + const tableName = model.tableName; + + const schemaTableName = db.utils.addSchema(tableName); + const quoteTableName = db.utils.quoteTable(tableName); const attributes = model.tableAttributes; @@ -44,6 +49,7 @@ export class SyncRunner { `SELECT column_default FROM information_schema.columns WHERE table_name = '${parent}' + and table_schema = '${schemaName}' and "column_name" = 'id';`, { transaction, @@ -60,7 +66,7 @@ export class SyncRunner { throw new Error(`Can't find sequence name of ${parent}`); } - const regex = new RegExp(/nextval\('("?\w+"?)\'.*\)/); + const regex = new RegExp(/nextval\('(.*)'::regclass\)/); const match = regex.exec(columnDefault); const sequenceName = match[1]; @@ -82,25 +88,24 @@ export class SyncRunner { } } - await this.createTable(tableName, childAttributes, options, model, parentTables); + await this.createTable(schemaTableName, childAttributes, options, model, parentTables, db); if (maxSequenceName) { const parentsDeep = Array.from(db.inheritanceMap.getParents(inheritedCollection.name)).map( (parent) => db.getCollection(parent).model.tableName, ); - const sequenceTables = [...parentsDeep, tableName]; + const sequenceTables = [...parentsDeep, tableName.toString()]; for (const sequenceTable of sequenceTables) { - const queryName = Boolean(sequenceTable.match(/[A-Z]/)) ? `"${sequenceTable}"` : sequenceTable; + const queryName = + Boolean(sequenceTable.match(/[A-Z]/)) && !sequenceTable.includes(`"`) ? `"${sequenceTable}"` : sequenceTable; const idColumnQuery = await queryInterface.sequelize.query( ` - SELECT true - FROM pg_attribute - WHERE attrelid = '${queryName}'::regclass -- cast to a registered class (table) -AND attname = 'id' -AND NOT attisdropped + SELECT column_name +FROM information_schema.columns +WHERE table_name='${queryName}' and column_name='id' and table_schema = '${schemaName}'; `, { transaction, @@ -112,7 +117,7 @@ AND NOT attisdropped } await queryInterface.sequelize.query( - `alter table "${sequenceTable}" + `alter table "${schemaName}"."${sequenceTable}" alter column id set default nextval('${maxSequenceName}')`, { transaction, @@ -134,7 +139,7 @@ AND NOT attisdropped } } - static async createTable(tableName, attributes, options, model, parentTables) { + static async createTable(tableName, attributes, options, model, parentTables, db) { let sql = ''; options = { ...options }; @@ -159,7 +164,11 @@ AND NOT attisdropped sql = `${queryGenerator.createTableQuery(tableName, attributes, options)}`.replace( ';', - ` INHERITS (${parentTables.map((t) => `"${t}"`).join(', ')});`, + ` INHERITS (${parentTables + .map((t) => { + return db.utils.quoteTable(db.utils.addSchema(t, db.options.schema)); + }) + .join(', ')});`, ); return await model.sequelize.query(sql, options); diff --git a/packages/core/database/src/utils.ts b/packages/core/database/src/utils.ts index 173fc17b8..c11c98cf3 100644 --- a/packages/core/database/src/utils.ts +++ b/packages/core/database/src/utils.ts @@ -2,6 +2,7 @@ import crypto from 'crypto'; import { IdentifierError } from './errors/identifier-error'; import { Model } from './model'; import lodash from 'lodash'; +import Database from './database'; type HandleAppendsQueryOptions = { templateModel: any; @@ -82,3 +83,36 @@ export function getTableName(collectionName: string, options) { export function snakeCase(name: string) { return require('sequelize').Utils.underscore(name); } + +export function patchSequelizeQueryInterface(db: Database) { + if (db.inDialect('postgres')) { + //@ts-ignore + const queryGenerator = db.sequelize.dialect.queryGenerator; + + queryGenerator.showConstraintsQuery = (tableName, constraintName) => { + const lines = [ + 'SELECT constraint_catalog AS "constraintCatalog",', + 'constraint_schema AS "constraintSchema",', + 'constraint_name AS "constraintName",', + 'table_catalog AS "tableCatalog",', + 'table_schema AS "tableSchema",', + 'table_name AS "tableName",', + 'constraint_type AS "constraintType",', + 'is_deferrable AS "isDeferrable",', + 'initially_deferred AS "initiallyDeferred"', + 'from INFORMATION_SCHEMA.table_constraints', + `WHERE table_name='${tableName}'`, + ]; + + if (!constraintName) { + lines.push(`AND constraint_name='${constraintName}'`); + } + + if (db.options.schema && db.options.schema !== 'public') { + lines.push(`AND table_schema='${db.options.schema}'`); + } + + return lines.join(' '); + }; + } +} diff --git a/packages/core/test/src/mockServer.ts b/packages/core/test/src/mockServer.ts index 034fdffce..0272a287f 100644 --- a/packages/core/test/src/mockServer.ts +++ b/packages/core/test/src/mockServer.ts @@ -57,6 +57,7 @@ interface Resource { export class MockServer extends Application { async loadAndInstall(options: any = {}) { await this.load({ method: 'install' }); + await this.install({ ...options, sync: { @@ -69,7 +70,7 @@ export class MockServer extends Application { } async cleanDb() { - await this.db.sequelize.getQueryInterface().dropAllTables(); + await this.db.clean({ drop: true }); } agent(): SuperAgentTest & { resource: (name: string, resourceOf?: any) => Resource } { diff --git a/packages/plugins/collection-manager/src/__tests__/http-api/collections.test.ts b/packages/plugins/collection-manager/src/__tests__/http-api/collections.test.ts index 208c4faac..d3d3f61fa 100644 --- a/packages/plugins/collection-manager/src/__tests__/http-api/collections.test.ts +++ b/packages/plugins/collection-manager/src/__tests__/http-api/collections.test.ts @@ -97,7 +97,7 @@ describe('collections repository', () => { const testCollection = app.db.getCollection('test'); - const tableInfo = await app.db.sequelize.getQueryInterface().describeTable(testCollection.model.tableName); + const tableInfo = await app.db.sequelize.getQueryInterface().describeTable(testCollection.addSchemaTableName()); expect(tableInfo['field']).toBeDefined(); }); @@ -504,7 +504,7 @@ describe('collections repository', () => { const indexes = (await app.db.sequelize .getQueryInterface() - .showIndex(app.db.getCollection('test').model.tableName)) as any; + .showIndex(app.db.getCollection('test').addSchemaTableName())) as any; const columnName = app.db.getCollection('test').model.rawAttributes.testField.field; @@ -528,7 +528,7 @@ describe('collections repository', () => { const afterIndexes = (await app.db.sequelize .getQueryInterface() - .showIndex(app.db.getCollection('test').model.tableName)) as any; + .showIndex(app.db.getCollection('test').addSchemaTableName())) as any; expect( afterIndexes.find( diff --git a/packages/plugins/collection-manager/src/__tests__/index.ts b/packages/plugins/collection-manager/src/__tests__/index.ts index 6e9dcf4c3..a9b57aedd 100644 --- a/packages/plugins/collection-manager/src/__tests__/index.ts +++ b/packages/plugins/collection-manager/src/__tests__/index.ts @@ -9,6 +9,9 @@ export async function createApp(options = {}) { ...options, }); + await app.db.clean({ drop: true }); + await app.db.sync({}); + app.plugin(PluginErrorHandler, { name: 'error-handler' }); app.plugin(Plugin, { name: 'collection-manager' }); app.plugin(PluginUiSchema, { name: 'ui-schema-storage' }); diff --git a/packages/plugins/collection-manager/src/__tests__/inherits/inherited-collection.test.ts b/packages/plugins/collection-manager/src/__tests__/inherits/inherited-collection.test.ts index 60f954384..18a248110 100644 --- a/packages/plugins/collection-manager/src/__tests__/inherits/inherited-collection.test.ts +++ b/packages/plugins/collection-manager/src/__tests__/inherits/inherited-collection.test.ts @@ -309,7 +309,6 @@ pgOnly()('Inherited Collection', () => { const studentCollection = await db.getCollection('students'); - console.log(studentCollection.fields); await studentCollection.repository.create({ values: { name: 'foo', diff --git a/packages/plugins/collection-manager/src/__tests__/inherits/inhertied-collection-with-schema.test.ts b/packages/plugins/collection-manager/src/__tests__/inherits/inhertied-collection-with-schema.test.ts new file mode 100644 index 000000000..1b5406fde --- /dev/null +++ b/packages/plugins/collection-manager/src/__tests__/inherits/inhertied-collection-with-schema.test.ts @@ -0,0 +1,54 @@ +import Database, { Repository } from '@nocobase/database'; +import Application from '@nocobase/server'; +import { createApp } from '..'; +import { pgOnly } from '@nocobase/test'; + +pgOnly()('Inherited Collection with schema options', () => { + let db: Database; + let app: Application; + + let collectionRepository: Repository; + + let fieldsRepository: Repository; + + beforeEach(async () => { + app = await createApp({ + database: { + schema: 'testSchema', + }, + }); + + db = app.db; + + collectionRepository = db.getCollection('collections').repository; + fieldsRepository = db.getCollection('fields').repository; + }); + + afterEach(async () => { + await app.destroy(); + }); + + it('should create inherited collection in difference schema', async () => { + await collectionRepository.create({ + values: { + name: 'b', + fields: [ + { + name: 'name', + type: 'string', + }, + ], + }, + context: {}, + }); + + await collectionRepository.create({ + values: { + name: 'a', + inherits: ['b'], + fields: [{ type: 'string', name: 'bField' }], + }, + context: {}, + }); + }); +}); diff --git a/packages/plugins/collection-manager/src/__tests__/migrations/update-id-to-bigint.test.ts b/packages/plugins/collection-manager/src/__tests__/migrations/update-id-to-bigint.test.ts index d43e85dea..16caab4ee 100644 --- a/packages/plugins/collection-manager/src/__tests__/migrations/update-id-to-bigint.test.ts +++ b/packages/plugins/collection-manager/src/__tests__/migrations/update-id-to-bigint.test.ts @@ -68,17 +68,16 @@ excludeSqlite()('update id to bigint test', () => { await db.sync(); const assertBigInt = async (collectionName, fieldName) => { - const tableInfo = await db.sequelize - .getQueryInterface() - .describeTable( - db.getCollection(collectionName) ? db.getCollection(collectionName).model.tableName : collectionName, - ); + const tableName = db.getCollection(collectionName) + ? db.getCollection(collectionName).addSchemaTableName() + : collectionName; - if (process.env.DB_UNDERSCORED) { + const tableInfo = await db.sequelize.getQueryInterface().describeTable(tableName); + + if (db.options.underscored) { fieldName = lodash.snakeCase(fieldName); } - console.log(`${collectionName}, ${fieldName}`, tableInfo[fieldName].type); expect(tableInfo[fieldName].type).toBe('BIGINT'); }; @@ -92,7 +91,7 @@ excludeSqlite()('update id to bigint test', () => { let usersTableInfo = await db.sequelize .getQueryInterface() - .describeTable(db.getCollection('users').model.tableName); + .describeTable(db.getCollection('users').addSchemaTableName()); assertInteger(usersTableInfo.id.type); diff --git a/packages/plugins/collection-manager/src/migrations/20221121111113-update-id-to-bigint.ts b/packages/plugins/collection-manager/src/migrations/20221121111113-update-id-to-bigint.ts index 0247a30e6..c14be4c63 100644 --- a/packages/plugins/collection-manager/src/migrations/20221121111113-update-id-to-bigint.ts +++ b/packages/plugins/collection-manager/src/migrations/20221121111113-update-id-to-bigint.ts @@ -38,6 +38,8 @@ export default class UpdateIdToBigIntMigrator extends Migration { let sql; const tableName = model.tableName; + const addSchemaTableName = db.utils.addSchema(tableName); + const quoteTableName = db.utils.quoteTable(addSchemaTableName); const collection = db.modelCollection.get(model); @@ -56,7 +58,7 @@ export default class UpdateIdToBigIntMigrator extends Migration { if (model.rawAttributes[fieldName].type instanceof DataTypes.INTEGER) { if (db.inDialect('postgres')) { - sql = `ALTER TABLE "${tableName}" ALTER COLUMN "${columnName}" SET DATA TYPE BIGINT;`; + sql = `ALTER TABLE ${quoteTableName} ALTER COLUMN "${columnName}" SET DATA TYPE BIGINT;`; } else if (db.inDialect('mysql')) { const dataTypeOrOptions = model.rawAttributes[fieldName]; const attributeName = fieldName; @@ -74,7 +76,7 @@ export default class UpdateIdToBigIntMigrator extends Migration { }, ); - sql = queryGenerator.changeColumnQuery(tableName, query); + sql = queryGenerator.changeColumnQuery(addSchemaTableName, query); sql = sql.replace(' PRIMARY KEY;', ' ;'); } @@ -89,7 +91,7 @@ export default class UpdateIdToBigIntMigrator extends Migration { } if (db.inDialect('postgres')) { - const sequenceQuery = `SELECT pg_get_serial_sequence('"${model.tableName}"', '${columnName}');`; + const sequenceQuery = `SELECT pg_get_serial_sequence('${quoteTableName}', '${columnName}');`; const [result] = await this.sequelize.query(sequenceQuery, {}); const sequenceName = result[0]['pg_get_serial_sequence']; diff --git a/packages/plugins/collection-manager/src/models/field.ts b/packages/plugins/collection-manager/src/models/field.ts index 239a989b2..783ae522f 100644 --- a/packages/plugins/collection-manager/src/models/field.ts +++ b/packages/plugins/collection-manager/src/models/field.ts @@ -107,7 +107,7 @@ export class FieldModel extends MagicAttributeModel { const queryInterface = this.db.sequelize.getQueryInterface() as any; - const existsIndexes = await queryInterface.showIndex(collection.model.tableName, { + const existsIndexes = await queryInterface.showIndex(collection.addSchemaTableName(), { transaction: options.transaction, }); @@ -120,11 +120,7 @@ export class FieldModel extends MagicAttributeModel { let constraintName = `${tableName}_${field.name}_uk`; if (existUniqueIndex) { - const existsUniqueConstraints = await queryInterface.showConstraint( - collection.model.tableName, - constraintName, - {}, - ); + const existsUniqueConstraints = await queryInterface.showConstraint(tableName, constraintName, {}); existsUniqueConstraint = existsUniqueConstraints[0]; } @@ -133,7 +129,7 @@ export class FieldModel extends MagicAttributeModel { // @ts-ignore await collection.sync({ ...options, force: false, alter: { drop: false } }); - await queryInterface.addConstraint(tableName, { + await queryInterface.addConstraint(collection.addSchemaTableName(), { type: 'unique', fields: [columnName], name: constraintName, @@ -142,7 +138,7 @@ export class FieldModel extends MagicAttributeModel { } if (!unique && existsUniqueConstraint) { - await queryInterface.removeConstraint(collection.model.tableName, constraintName, { + await queryInterface.removeConstraint(collection.addSchemaTableName(), constraintName, { transaction: options.transaction, }); } @@ -164,7 +160,7 @@ export class FieldModel extends MagicAttributeModel { const queryInterface = collection.db.sequelize.getQueryInterface(); await queryInterface.changeColumn( - collection.model.tableName, + collection.addSchemaTableName(), collection.model.rawAttributes[this.get('name')].field, { type: field.dataType, diff --git a/packages/plugins/duplicator/src/server/__tests__/dump.test.ts b/packages/plugins/duplicator/src/server/__tests__/dump.test.ts index cb532bdb2..1f141fcbe 100644 --- a/packages/plugins/duplicator/src/server/__tests__/dump.test.ts +++ b/packages/plugins/duplicator/src/server/__tests__/dump.test.ts @@ -19,6 +19,7 @@ describe('dump', () => { app = mockServer(); db = app.db; + await app.cleanDb(); app.db.collection({ name: 'users', @@ -52,7 +53,6 @@ describe('dump', () => { fields: [], }); - await app.cleanDb(); await db.sync(); }); @@ -148,7 +148,7 @@ $$`); await db.sequelize.query(` CREATE TRIGGER last_name_changes BEFORE UPDATE - ON ${app.db.getCollection('users').model.tableName} + ON ${app.db.getCollection('users').quotedTableName()} FOR EACH ROW EXECUTE PROCEDURE trigger_function(); `); diff --git a/packages/plugins/duplicator/src/server/dumper.ts b/packages/plugins/duplicator/src/server/dumper.ts index 7762d142b..f820852a4 100644 --- a/packages/plugins/duplicator/src/server/dumper.ts +++ b/packages/plugins/duplicator/src/server/dumper.ts @@ -177,7 +177,7 @@ ORDER BY table_schema, table_name`, const dataStream = fs.createWriteStream(dataFilePath); const rows = await app.db.sequelize.query( - sqlAdapter(app.db, `SELECT * FROM ${collection.isParent() ? 'ONLY' : ''} "${collection.model.tableName}"`), + sqlAdapter(app.db, `SELECT * FROM ${collection.isParent() ? 'ONLY' : ''} ${collection.quotedTableName()}`), { type: 'SELECT', }, diff --git a/packages/plugins/duplicator/src/server/restorer.ts b/packages/plugins/duplicator/src/server/restorer.ts index 39a75ae8b..1414088b3 100644 --- a/packages/plugins/duplicator/src/server/restorer.ts +++ b/packages/plugins/duplicator/src/server/restorer.ts @@ -135,12 +135,12 @@ export class Restorer extends AppMigrator { const metaContent = await fsPromises.readFile(collectionMetaPath, 'utf8'); const meta = JSON.parse(metaContent); - const tableName = meta.tableName; + const tableName = this.app.db.utils.quoteTable(meta.tableName); try { // disable trigger if (this.app.db.inDialect('postgres')) { - await this.app.db.sequelize.query(`ALTER TABLE IF EXISTS "${tableName}" DISABLE TRIGGER ALL`); + await this.app.db.sequelize.query(`ALTER TABLE IF EXISTS ${tableName} DISABLE TRIGGER ALL`); } await this.importCollection({ @@ -152,7 +152,7 @@ export class Restorer extends AppMigrator { }); } finally { if (this.app.db.inDialect('postgres')) { - await this.app.db.sequelize.query(`ALTER TABLE IF EXISTS "${tableName}" ENABLE TRIGGER ALL`); + await this.app.db.sequelize.query(`ALTER TABLE IF EXISTS ${tableName} ENABLE TRIGGER ALL`); } } }; @@ -214,6 +214,8 @@ export class Restorer extends AppMigrator { rowCondition?: (row: any) => boolean; }) { const app = this.app; + const db = app.db; + const collectionName = options.name; const dir = this.workDir; const collection = app.db.getCollection(collectionName); @@ -224,15 +226,16 @@ export class Restorer extends AppMigrator { const meta = JSON.parse(metaContent); app.log.info(`collection meta ${metaContent}`); - const tableName = meta.tableName; + const addSchemaTableName = db.utils.addSchema(meta.tableName); + const tableName = db.utils.quoteTable(meta.tableName); if (options.clear !== false) { // truncate old data - let sql = `TRUNCATE TABLE "${tableName}"`; + let sql = `TRUNCATE TABLE ${tableName}`; if (app.db.inDialect('sqlite')) { sql = `DELETE - FROM "${tableName}"`; + FROM ${tableName}`; } await app.db.sequelize.query(sqlAdapter(app.db, sql)); @@ -290,7 +293,7 @@ export class Restorer extends AppMigrator { //@ts-ignore const sql = collection.model.queryInterface.queryGenerator.bulkInsertQuery( - tableName, + addSchemaTableName, rowsWithMeta, {}, fieldMappedAttributes, @@ -311,18 +314,20 @@ export class Restorer extends AppMigrator { if (this.app.db.inDialect('postgres')) { const sequenceNameResult = await app.db.sequelize.query( `SELECT column_default FROM information_schema.columns WHERE - table_name='${collection.model.tableName}' and "column_name" = 'id';`, + table_name='${collection.model.tableName}' and "column_name" = 'id' and table_schema = '${ + app.db.options.schema || 'public' + }';`, ); if (sequenceNameResult[0].length) { const columnDefault = sequenceNameResult[0][0]['column_default']; if (columnDefault.includes(`${collection.model.tableName}_id_seq`)) { - const regex = new RegExp(/nextval\('("?\w+"?)\'.*\)/); + const regex = new RegExp(/nextval\('(.*)'::regclass\)/); const match = regex.exec(columnDefault); const sequenceName = match[1]; const maxVal = await app.db.sequelize.query( - `SELECT MAX("${primaryKeyAttribute.field}") FROM "${collection.model.tableName}"`, + `SELECT MAX("${primaryKeyAttribute.field}") FROM ${tableName}`, { type: 'SELECT', }, diff --git a/packages/plugins/ui-schema-storage/src/__tests__/action.test.ts b/packages/plugins/ui-schema-storage/src/__tests__/action.test.ts index 0cdf4a9e2..40138c05f 100644 --- a/packages/plugins/ui-schema-storage/src/__tests__/action.test.ts +++ b/packages/plugins/ui-schema-storage/src/__tests__/action.test.ts @@ -13,8 +13,7 @@ describe('action test', () => { db = app.db; - const queryInterface = db.sequelize.getQueryInterface(); - await queryInterface.dropAllTables(); + await db.clean({ drop: true }); app.plugin(PluginUiSchema, { name: 'ui-schema-storage' }); diff --git a/packages/plugins/ui-schema-storage/src/__tests__/ui-schema-model.test.ts b/packages/plugins/ui-schema-storage/src/__tests__/ui-schema-model.test.ts index 8f6e6fc51..0ddf01011 100644 --- a/packages/plugins/ui-schema-storage/src/__tests__/ui-schema-model.test.ts +++ b/packages/plugins/ui-schema-storage/src/__tests__/ui-schema-model.test.ts @@ -19,9 +19,7 @@ describe('ui schema model', () => { db = app.db; - const queryInterface = db.sequelize.getQueryInterface(); - await queryInterface.dropAllTables(); - + await db.clean({ drop: true }); app.plugin(PluginUiSchema, { name: 'ui-schema-storage' }); await app.load(); diff --git a/packages/plugins/ui-schema-storage/src/__tests__/ui-schema-repository-with-cache.test.ts b/packages/plugins/ui-schema-storage/src/__tests__/ui-schema-repository-with-cache.test.ts index cfb3860d6..86b44cc97 100644 --- a/packages/plugins/ui-schema-storage/src/__tests__/ui-schema-repository-with-cache.test.ts +++ b/packages/plugins/ui-schema-storage/src/__tests__/ui-schema-repository-with-cache.test.ts @@ -23,7 +23,7 @@ describe('ui_schema repository with cache', () => { db = app.db; cache = app.cache; - await db.sequelize.getQueryInterface().dropAllTables(); + await db.clean({ drop: true }); app.plugin(PluginUiSchema, { name: 'ui-schema-storage' }); diff --git a/packages/plugins/ui-schema-storage/src/__tests__/ui-schema-repository.test.ts b/packages/plugins/ui-schema-storage/src/__tests__/ui-schema-repository.test.ts index 723d268a6..7004fea86 100644 --- a/packages/plugins/ui-schema-storage/src/__tests__/ui-schema-repository.test.ts +++ b/packages/plugins/ui-schema-storage/src/__tests__/ui-schema-repository.test.ts @@ -22,8 +22,7 @@ describe('ui_schema repository', () => { db = app.db; - const queryInterface = db.sequelize.getQueryInterface(); - await queryInterface.dropAllTables(); + await db.clean({ drop: true }); app.plugin(PluginUiSchema, { name: 'ui-schema-storage' }); diff --git a/packages/plugins/ui-schema-storage/src/__tests__/ui-schema.test.ts b/packages/plugins/ui-schema-storage/src/__tests__/ui-schema.test.ts index 0846ef451..fda699246 100644 --- a/packages/plugins/ui-schema-storage/src/__tests__/ui-schema.test.ts +++ b/packages/plugins/ui-schema-storage/src/__tests__/ui-schema.test.ts @@ -20,7 +20,7 @@ describe('ui-schema', () => { db = app.db; - await db.sequelize.getQueryInterface().dropAllTables(); + await db.clean({ drop: true }); app.plugin(PluginUiSchema, { name: 'ui-schema-storage' }); diff --git a/packages/plugins/ui-schema-storage/src/repository.ts b/packages/plugins/ui-schema-storage/src/repository.ts index 5c85b65dd..68bc02e17 100644 --- a/packages/plugins/ui-schema-storage/src/repository.ts +++ b/packages/plugins/ui-schema-storage/src/repository.ts @@ -103,7 +103,7 @@ export class UiSchemaRepository extends Repository { tableNameAdapter(tableName) { if (this.database.sequelize.getDialect() === 'postgres') { - return `"${tableName}"`; + return `"${this.database.options.schema || 'public'}"."${tableName}"`; } return tableName; }