feat: mariadb support (#3052)
* feat: mariadb support * feat: mariadb test * chore: mariadb test * chore: test * fix: sort field test * fix: sort field test * fix: test * fix: test * fix(bi): chart query support mariadb * chore: test timeout * chore: test --------- Co-authored-by: xilesun <2013xile@gmail.com>
This commit is contained in:
parent
fbe3d2d9a5
commit
d7d2eb634e
50
.github/workflows/nocobase-test-backend.yml
vendored
50
.github/workflows/nocobase-test-backend.yml
vendored
@ -173,3 +173,53 @@ jobs:
|
|||||||
DB_TEST_DISTRIBUTOR_PORT: 23450
|
DB_TEST_DISTRIBUTOR_PORT: 23450
|
||||||
DB_TEST_PREFIX: test_
|
DB_TEST_PREFIX: test_
|
||||||
timeout-minutes: 40
|
timeout-minutes: 40
|
||||||
|
mariadb-test:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
node_version: ['18']
|
||||||
|
underscored: [true, false]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container: node:${{ matrix.node_version }}
|
||||||
|
services:
|
||||||
|
mariadb:
|
||||||
|
image: mariadb:10.9
|
||||||
|
env:
|
||||||
|
MARIADB_ROOT_PASSWORD: password
|
||||||
|
MARIADB_DATABASE: nocobase
|
||||||
|
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Use Node.js ${{ matrix.node_version }}
|
||||||
|
uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: ${{ matrix.node_version }}
|
||||||
|
- name: Get yarn cache directory path
|
||||||
|
id: yarn-cache-dir-path
|
||||||
|
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||||
|
|
||||||
|
- uses: actions/cache@v3
|
||||||
|
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
|
||||||
|
with:
|
||||||
|
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||||
|
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-yarn-
|
||||||
|
- name: Install project dependencies
|
||||||
|
run: yarn --prefer-offline
|
||||||
|
- name: Test with MariaDB
|
||||||
|
run: |
|
||||||
|
./node_modules/.bin/tsx packages/core/test/src/scripts/test-db-creator.ts &
|
||||||
|
sleep 1
|
||||||
|
node --max_old_space_size=4096 ./node_modules/.bin/jest --maxWorkers=100% --workerIdleMemoryLimit=3000MB
|
||||||
|
env:
|
||||||
|
LOGGER_LEVEL: error
|
||||||
|
DB_DIALECT: mariadb
|
||||||
|
DB_HOST: mariadb
|
||||||
|
DB_PORT: 3306
|
||||||
|
DB_USER: root
|
||||||
|
DB_PASSWORD: password
|
||||||
|
DB_DATABASE: nocobase
|
||||||
|
DB_UNDERSCORED: ${{ matrix.underscored }}
|
||||||
|
DB_TEST_DISTRIBUTOR_PORT: 23450
|
||||||
|
DB_TEST_PREFIX: test_
|
||||||
|
timeout-minutes: 40
|
||||||
|
@ -70,6 +70,7 @@ class AppGenerator extends Generator {
|
|||||||
|
|
||||||
if (allDbDialect) {
|
if (allDbDialect) {
|
||||||
dependencies.push(`"mysql2": "^2.3.3"`);
|
dependencies.push(`"mysql2": "^2.3.3"`);
|
||||||
|
dependencies.push(`"mariadb": "^2.5.6"`);
|
||||||
dependencies.push(`"pg": "^8.7.3"`);
|
dependencies.push(`"pg": "^8.7.3"`);
|
||||||
dependencies.push(`"pg-hstore": "^2.3.4"`);
|
dependencies.push(`"pg-hstore": "^2.3.4"`);
|
||||||
dependencies.push(`"sqlite3": "^5.0.8"`);
|
dependencies.push(`"sqlite3": "^5.0.8"`);
|
||||||
@ -92,6 +93,16 @@ class AppGenerator extends Generator {
|
|||||||
envs.push(`DB_USER=${env.DB_USER || ''}`);
|
envs.push(`DB_USER=${env.DB_USER || ''}`);
|
||||||
envs.push(`DB_PASSWORD=${env.DB_PASSWORD || ''}`);
|
envs.push(`DB_PASSWORD=${env.DB_PASSWORD || ''}`);
|
||||||
break;
|
break;
|
||||||
|
case 'mariadb':
|
||||||
|
if (!allDbDialect) {
|
||||||
|
dependencies.push(`"mariadb": "^2.5.6"`);
|
||||||
|
}
|
||||||
|
envs.push(`DB_HOST=${env.DB_HOST || 'localhost'}`);
|
||||||
|
envs.push(`DB_PORT=${env.DB_PORT || 3306}`);
|
||||||
|
envs.push(`DB_DATABASE=${env.DB_DATABASE || ''}`);
|
||||||
|
envs.push(`DB_USER=${env.DB_USER || ''}`);
|
||||||
|
envs.push(`DB_PASSWORD=${env.DB_PASSWORD || ''}`);
|
||||||
|
break;
|
||||||
case 'postgres':
|
case 'postgres':
|
||||||
if (!allDbDialect) {
|
if (!allDbDialect) {
|
||||||
dependencies.push(`"pg": "^8.7.3"`);
|
dependencies.push(`"pg": "^8.7.3"`);
|
||||||
|
@ -27,7 +27,11 @@ excludeSqlite()('collection', () => {
|
|||||||
await db.sync();
|
await db.sync();
|
||||||
const tableInfo = await db.sequelize.getQueryInterface().describeTable(collection.model.tableName);
|
const tableInfo = await db.sequelize.getQueryInterface().describeTable(collection.model.tableName);
|
||||||
|
|
||||||
expect(tableInfo['id'].type).toBe('BIGINT');
|
if (db.inDialect('mariadb')) {
|
||||||
|
expect(tableInfo['id'].type).toBe('BIGINT(20)');
|
||||||
|
} else {
|
||||||
|
expect(tableInfo['id'].type).toBe('BIGINT');
|
||||||
|
}
|
||||||
|
|
||||||
const profile = db.collection({
|
const profile = db.collection({
|
||||||
name: 'profiles',
|
name: 'profiles',
|
||||||
@ -43,6 +47,10 @@ excludeSqlite()('collection', () => {
|
|||||||
|
|
||||||
const profileTableInfo = await db.sequelize.getQueryInterface().describeTable(profile.model.tableName);
|
const profileTableInfo = await db.sequelize.getQueryInterface().describeTable(profile.model.tableName);
|
||||||
|
|
||||||
expect(profileTableInfo[profile.model.rawAttributes['userId'].field].type).toBe('BIGINT');
|
if (db.inDialect('mariadb')) {
|
||||||
|
expect(profileTableInfo[profile.model.rawAttributes['userId'].field].type).toBe('BIGINT(20)');
|
||||||
|
} else {
|
||||||
|
expect(profileTableInfo[profile.model.rawAttributes['userId'].field].type).toBe('BIGINT');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -6,6 +6,7 @@ describe('collection sortable options', () => {
|
|||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
db = mockDatabase();
|
db = mockDatabase();
|
||||||
|
await db.clean({ drop: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
|
@ -37,7 +37,7 @@ describe('collection', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should not throw error when create empty collection in sqlite and mysql', async () => {
|
it('should not throw error when create empty collection in sqlite and mysql', async () => {
|
||||||
if (!db.inDialect('sqlite', 'mysql')) {
|
if (!db.inDialect('sqlite', 'mysql', 'mariadb')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ describe('hidden field options', () => {
|
|||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
db = mockDatabase();
|
db = mockDatabase();
|
||||||
|
await db.clean({ drop: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
|
@ -7,6 +7,7 @@ describe('context field', () => {
|
|||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
db = mockDatabase();
|
db = mockDatabase();
|
||||||
|
await db.clean({ drop: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { Database } from '../../database';
|
import { Database } from '../../database';
|
||||||
import { mockDatabase } from '../';
|
import { mockDatabase } from '../';
|
||||||
import { makeWatchHost } from 'ts-loader/dist/servicesHost';
|
|
||||||
import { IdentifierError } from '../../errors/identifier-error';
|
import { IdentifierError } from '../../errors/identifier-error';
|
||||||
|
|
||||||
describe('has many field', () => {
|
describe('has many field', () => {
|
||||||
@ -8,6 +7,7 @@ describe('has many field', () => {
|
|||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
db = mockDatabase();
|
db = mockDatabase();
|
||||||
|
await db.clean({ drop: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
|
@ -8,6 +8,7 @@ describe('string field', () => {
|
|||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
db = mockDatabase();
|
db = mockDatabase();
|
||||||
await db.clean({ drop: true });
|
await db.clean({ drop: true });
|
||||||
|
|
||||||
db.registerFieldTypes({
|
db.registerFieldTypes({
|
||||||
sort: SortField,
|
sort: SortField,
|
||||||
});
|
});
|
||||||
|
@ -6,6 +6,7 @@ describe('string field', () => {
|
|||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
db = mockDatabase();
|
db = mockDatabase();
|
||||||
|
await db.clean({ drop: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
|
@ -142,10 +142,14 @@ describe('filter', () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const userCreatedAt = user.get('createdAt');
|
||||||
|
const year = userCreatedAt.getFullYear();
|
||||||
|
const month = userCreatedAt.getMonth() + 1; // 月份从0开始,因此加1
|
||||||
|
const date = userCreatedAt.getDate();
|
||||||
const count = await PostCollection.repository.count({
|
const count = await PostCollection.repository.count({
|
||||||
filter: {
|
filter: {
|
||||||
'user.createdAt': {
|
'user.createdAt': {
|
||||||
$dateOn: user.get('createdAt'),
|
$dateOn: `${year}-${month}-${date}`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -14,6 +14,8 @@ describe('array field operator', function () {
|
|||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
db = mockDatabase({});
|
db = mockDatabase({});
|
||||||
|
await db.clean({ drop: true });
|
||||||
|
|
||||||
Test = db.collection({
|
Test = db.collection({
|
||||||
name: 'test',
|
name: 'test',
|
||||||
fields: [
|
fields: [
|
||||||
|
@ -7,6 +7,7 @@ describe('eq operator', () => {
|
|||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
db = mockDatabase({});
|
db = mockDatabase({});
|
||||||
|
await db.clean({ drop: true });
|
||||||
|
|
||||||
Test = db.collection({
|
Test = db.collection({
|
||||||
name: 'tests',
|
name: 'tests',
|
||||||
|
@ -6,6 +6,7 @@ describe('ne operator', () => {
|
|||||||
let Test;
|
let Test;
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
db = mockDatabase({});
|
db = mockDatabase({});
|
||||||
|
await db.clean({ drop: true });
|
||||||
|
|
||||||
Test = db.collection({
|
Test = db.collection({
|
||||||
name: 'tests',
|
name: 'tests',
|
||||||
|
@ -84,7 +84,7 @@ describe('option parser', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('with sort option', () => {
|
test('with sort option', () => {
|
||||||
if (db.inDialect('mysql')) {
|
if (db.inDialect('mysql', 'mariadb')) {
|
||||||
expect(1).toBe(1);
|
expect(1).toBe(1);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -81,6 +81,7 @@ describe('repository.find', () => {
|
|||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
db = mockDatabase();
|
db = mockDatabase();
|
||||||
|
await db.clean({ drop: true });
|
||||||
User = db.collection({
|
User = db.collection({
|
||||||
name: 'users',
|
name: 'users',
|
||||||
fields: [
|
fields: [
|
||||||
@ -386,6 +387,8 @@ describe('repository.update', () => {
|
|||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
db = mockDatabase();
|
db = mockDatabase();
|
||||||
|
await db.clean({ drop: true });
|
||||||
|
|
||||||
User = db.collection({
|
User = db.collection({
|
||||||
name: 'users',
|
name: 'users',
|
||||||
fields: [
|
fields: [
|
||||||
@ -608,7 +611,7 @@ describe('repository.update', () => {
|
|||||||
filter: {
|
filter: {
|
||||||
user: {
|
user: {
|
||||||
id: {
|
id: {
|
||||||
$eq: u1.id
|
$eq: u1.id,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -623,9 +626,9 @@ describe('repository.update', () => {
|
|||||||
const updated = await Post.repository.find({
|
const updated = await Post.repository.find({
|
||||||
filter: {
|
filter: {
|
||||||
id: [p1.id, p2.id],
|
id: [p1.id, p2.id],
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
expect(updated.map(item => item.name)).toEqual(['p1_1', 'p1_1']);
|
expect(updated.map((item) => item.name)).toEqual(['p1_1', 'p1_1']);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -14,6 +14,8 @@ describe('count', () => {
|
|||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
db = mockDatabase();
|
db = mockDatabase();
|
||||||
|
await db.clean({ drop: true });
|
||||||
|
|
||||||
User = db.collection({
|
User = db.collection({
|
||||||
name: 'users',
|
name: 'users',
|
||||||
fields: [
|
fields: [
|
||||||
|
@ -336,6 +336,7 @@ describe('repository find', () => {
|
|||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
db = mockDatabase();
|
db = mockDatabase();
|
||||||
|
await db.clean({ drop: true });
|
||||||
User = db.collection<{ id: number; name: string }, { name: string }>({
|
User = db.collection<{ id: number; name: string }, { name: string }>({
|
||||||
name: 'users',
|
name: 'users',
|
||||||
fields: [
|
fields: [
|
||||||
|
@ -8,6 +8,8 @@ describe('update many', () => {
|
|||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
db = mockDatabase();
|
db = mockDatabase();
|
||||||
|
await db.clean({ drop: true });
|
||||||
|
|
||||||
db.collection({
|
db.collection({
|
||||||
name: 't1',
|
name: 't1',
|
||||||
fields: [{ type: 'string', name: 'title' }],
|
fields: [{ type: 'string', name: 'title' }],
|
||||||
|
@ -6,6 +6,7 @@ describe('sort', function () {
|
|||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
db = mockDatabase();
|
db = mockDatabase();
|
||||||
|
await db.clean({ drop: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
|
@ -11,7 +11,7 @@ describe('infer fields', () => {
|
|||||||
|
|
||||||
db.collection({
|
db.collection({
|
||||||
name: 'users',
|
name: 'users',
|
||||||
schema: 'public',
|
schema: db.inDialect('postgres') ? 'public' : undefined,
|
||||||
fields: [
|
fields: [
|
||||||
{ name: 'id', type: 'bigInt', interface: 'id' },
|
{ name: 'id', type: 'bigInt', interface: 'id' },
|
||||||
{ name: 'nickname', type: 'string', interface: 'input' },
|
{ name: 'nickname', type: 'string', interface: 'input' },
|
||||||
@ -19,7 +19,7 @@ describe('infer fields', () => {
|
|||||||
});
|
});
|
||||||
db.collection({
|
db.collection({
|
||||||
name: 'roles',
|
name: 'roles',
|
||||||
schema: 'public',
|
schema: db.inDialect('postgres') ? 'public' : undefined,
|
||||||
fields: [
|
fields: [
|
||||||
{ name: 'id', type: 'bigInt', interface: 'id' },
|
{ name: 'id', type: 'bigInt', interface: 'id' },
|
||||||
{ name: 'title', type: 'string', interface: 'input' },
|
{ name: 'title', type: 'string', interface: 'input' },
|
||||||
@ -28,7 +28,7 @@ describe('infer fields', () => {
|
|||||||
});
|
});
|
||||||
db.collection({
|
db.collection({
|
||||||
name: 'roles_users',
|
name: 'roles_users',
|
||||||
schema: 'public',
|
schema: db.inDialect('postgres') ? 'public' : undefined,
|
||||||
fields: [
|
fields: [
|
||||||
{ name: 'id', type: 'bigInt', interface: 'id' },
|
{ name: 'id', type: 'bigInt', interface: 'id' },
|
||||||
{ name: 'userId', type: 'bigInt', interface: 'id' },
|
{ name: 'userId', type: 'bigInt', interface: 'id' },
|
||||||
|
@ -359,6 +359,7 @@ describe('update associations', () => {
|
|||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
db = mockDatabase();
|
db = mockDatabase();
|
||||||
|
await db.clean({ drop: true });
|
||||||
User = db.collection({
|
User = db.collection({
|
||||||
name: 'users',
|
name: 'users',
|
||||||
fields: [
|
fields: [
|
||||||
|
@ -93,6 +93,7 @@ describe.only('china region', () => {
|
|||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
db = mockDatabase();
|
db = mockDatabase();
|
||||||
|
await db.clean({ drop: true });
|
||||||
db.collection({
|
db.collection({
|
||||||
name: 'users',
|
name: 'users',
|
||||||
fields: [
|
fields: [
|
||||||
|
@ -42,10 +42,6 @@ function EnsureAtomicity(target: any, propertyKey: string, descriptor: PropertyD
|
|||||||
|
|
||||||
const afterRawAttributes = Object.keys(model.rawAttributes);
|
const afterRawAttributes = Object.keys(model.rawAttributes);
|
||||||
const createdRawAttributes = lodash.difference(afterRawAttributes, beforeRawAttributes);
|
const createdRawAttributes = lodash.difference(afterRawAttributes, beforeRawAttributes);
|
||||||
console.log({
|
|
||||||
beforeRawAttributes,
|
|
||||||
afterRawAttributes,
|
|
||||||
});
|
|
||||||
for (const key of createdRawAttributes) {
|
for (const key of createdRawAttributes) {
|
||||||
delete this.model.rawAttributes[key];
|
delete this.model.rawAttributes[key];
|
||||||
}
|
}
|
||||||
|
@ -73,6 +73,8 @@ import { patchSequelizeQueryInterface, snakeCase } from './utils';
|
|||||||
import { BaseValueParser, registerFieldValueParsers } from './value-parsers';
|
import { BaseValueParser, registerFieldValueParsers } from './value-parsers';
|
||||||
import { ViewCollection } from './view-collection';
|
import { ViewCollection } from './view-collection';
|
||||||
import { CollectionFactory } from './collection-factory';
|
import { CollectionFactory } from './collection-factory';
|
||||||
|
import chalk from 'chalk';
|
||||||
|
import { checkDatabaseVersion } from './helpers';
|
||||||
|
|
||||||
export type MergeOptions = merge.Options;
|
export type MergeOptions = merge.Options;
|
||||||
|
|
||||||
@ -124,9 +126,13 @@ export const DialectVersionAccessors = {
|
|||||||
mysql: {
|
mysql: {
|
||||||
sql: 'select version() as version',
|
sql: 'select version() as version',
|
||||||
get: (v: string) => {
|
get: (v: string) => {
|
||||||
if (v.toLowerCase().includes('mariadb')) {
|
const m = /([\d+.]+)/.exec(v);
|
||||||
return '';
|
return m[0];
|
||||||
}
|
},
|
||||||
|
},
|
||||||
|
mariadb: {
|
||||||
|
sql: 'select version() as version',
|
||||||
|
get: (v: string) => {
|
||||||
const m = /([\d+.]+)/.exec(v);
|
const m = /([\d+.]+)/.exec(v);
|
||||||
return m[0];
|
return m[0];
|
||||||
},
|
},
|
||||||
@ -155,7 +161,13 @@ class DatabaseVersion {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const [result] = (await this.db.sequelize.query(accessors[dialect].sql)) as any;
|
const [result] = (await this.db.sequelize.query(accessors[dialect].sql)) as any;
|
||||||
return semver.satisfies(accessors[dialect].get(result?.[0]?.version), versions[dialect]);
|
const versionResult = accessors[dialect].get(result?.[0]?.version);
|
||||||
|
|
||||||
|
if (lodash.isPlainObject(versionResult) && versionResult.dialect) {
|
||||||
|
return semver.satisfies(versionResult.version, versions[versionResult.dialect]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return semver.satisfies(versionResult, versions[dialect]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@ -354,6 +366,7 @@ export class Database extends EventEmitter implements AsyncEmitter {
|
|||||||
await connection.query('SET search_path TO public;');
|
await connection.query('SET search_path TO public;');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -466,6 +479,10 @@ export class Database extends EventEmitter implements AsyncEmitter {
|
|||||||
return dialect.includes(this.sequelize.getDialect());
|
return dialect.includes(this.sequelize.getDialect());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isMySQLCompatibleDialect() {
|
||||||
|
return this.inDialect('mysql', 'mariadb');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add collection to database
|
* Add collection to database
|
||||||
* @param options
|
* @param options
|
||||||
@ -670,7 +687,7 @@ export class Database extends EventEmitter implements AsyncEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async sync(options?: SyncOptions) {
|
async sync(options?: SyncOptions) {
|
||||||
const isMySQL = this.sequelize.getDialect() === 'mysql';
|
const isMySQL = this.isMySQLCompatibleDialect();
|
||||||
if (isMySQL) {
|
if (isMySQL) {
|
||||||
await this.sequelize.query('SET FOREIGN_KEY_CHECKS = 0', null);
|
await this.sequelize.query('SET FOREIGN_KEY_CHECKS = 0', null);
|
||||||
}
|
}
|
||||||
@ -759,7 +776,23 @@ export class Database extends EventEmitter implements AsyncEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async checkVersion() {
|
||||||
|
return await checkDatabaseVersion(this);
|
||||||
|
}
|
||||||
|
|
||||||
async prepare() {
|
async prepare() {
|
||||||
|
if (this.isMySQLCompatibleDialect()) {
|
||||||
|
const result = await this.sequelize.query(`SHOW VARIABLES LIKE 'lower_case_table_names'`, { plain: true });
|
||||||
|
|
||||||
|
if (result?.Value === '1' && !this.options.underscored) {
|
||||||
|
console.log(
|
||||||
|
`Your database lower_case_table_names=1, please add ${chalk.yellow('DB_UNDERSCORED=true')} to the .env file`,
|
||||||
|
);
|
||||||
|
|
||||||
|
process.exit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (this.inDialect('postgres') && this.options.schema && this.options.schema != 'public') {
|
if (this.inDialect('postgres') && this.options.schema && this.options.schema != 'public') {
|
||||||
await this.sequelize.query(`CREATE SCHEMA IF NOT EXISTS "${this.options.schema}"`, null);
|
await this.sequelize.query(`CREATE SCHEMA IF NOT EXISTS "${this.options.schema}"`, null);
|
||||||
}
|
}
|
||||||
|
@ -189,7 +189,7 @@ export abstract class Field {
|
|||||||
sql = `SELECT *
|
sql = `SELECT *
|
||||||
from pragma_table_info('${this.collection.model.tableName}')
|
from pragma_table_info('${this.collection.model.tableName}')
|
||||||
WHERE name = '${this.columnName()}'`;
|
WHERE name = '${this.columnName()}'`;
|
||||||
} else if (this.database.inDialect('mysql')) {
|
} else if (this.database.inDialect('mysql', 'mariadb')) {
|
||||||
sql = `
|
sql = `
|
||||||
select column_name
|
select column_name
|
||||||
from INFORMATION_SCHEMA.COLUMNS
|
from INFORMATION_SCHEMA.COLUMNS
|
||||||
|
@ -93,47 +93,60 @@ export class SortField extends Field {
|
|||||||
|
|
||||||
const quotedOrderField = queryInterface.quoteIdentifier(orderField);
|
const quotedOrderField = queryInterface.quoteIdentifier(orderField);
|
||||||
|
|
||||||
const sortColumnName = this.collection.model.rawAttributes[this.name].field;
|
const sortColumnName = queryInterface.quoteIdentifier(this.collection.model.rawAttributes[this.name].field);
|
||||||
|
|
||||||
const sql = `
|
let sql: string;
|
||||||
WITH ordered_table AS (
|
|
||||||
SELECT *, ROW_NUMBER() OVER (${
|
|
||||||
scopeKey ? `PARTITION BY ${queryInterface.quoteIdentifier(scopeKey)}` : ''
|
|
||||||
} ORDER BY ${quotedOrderField}) AS new_sequence_number
|
|
||||||
FROM ${this.collection.quotedTableName()}
|
|
||||||
${(() => {
|
|
||||||
if (scopeKey && scopeValue) {
|
|
||||||
const hasNull = scopeValue.includes(null);
|
|
||||||
|
|
||||||
return `WHERE ${queryInterface.quoteIdentifier(scopeKey)} IN (${scopeValue
|
const whereClause =
|
||||||
.filter((v) => v !== null)
|
scopeKey && scopeValue
|
||||||
.map((v) => `'${v}'`)
|
? `
|
||||||
.join(',')}) ${hasNull ? `OR ${queryInterface.quoteIdentifier(scopeKey)} IS NULL` : ''} `;
|
WHERE ${queryInterface.quoteIdentifier(scopeKey)} IN (${scopeValue
|
||||||
}
|
.filter((v) => v !== null)
|
||||||
|
.map((v) => `'${v}'`)
|
||||||
return '';
|
.join(', ')})${scopeValue.includes(null) ? ` OR ${queryInterface.quoteIdentifier(scopeKey)} IS NULL` : ''}`
|
||||||
})()}
|
: '';
|
||||||
|
|
||||||
)
|
|
||||||
${
|
|
||||||
this.collection.db.inDialect('mysql')
|
|
||||||
? `
|
|
||||||
UPDATE ${this.collection.quotedTableName()}, ordered_table
|
|
||||||
SET ${this.collection.quotedTableName()}.${sortColumnName} = ordered_table.new_sequence_number
|
|
||||||
WHERE ${this.collection.quotedTableName()}.${quotedOrderField} = ordered_table.${quotedOrderField}
|
|
||||||
`
|
|
||||||
: `
|
|
||||||
UPDATE ${this.collection.quotedTableName()}
|
|
||||||
SET ${queryInterface.quoteIdentifier(sortColumnName)} = ordered_table.new_sequence_number
|
|
||||||
FROM ordered_table
|
|
||||||
WHERE ${this.collection.quotedTableName()}.${quotedOrderField} = ${queryInterface.quoteIdentifier(
|
|
||||||
'ordered_table',
|
|
||||||
)}.${quotedOrderField};
|
|
||||||
`
|
|
||||||
}
|
|
||||||
|
|
||||||
`;
|
|
||||||
|
|
||||||
|
if (this.collection.db.inDialect('postgres')) {
|
||||||
|
sql = `
|
||||||
|
UPDATE ${this.collection.quotedTableName()}
|
||||||
|
SET ${sortColumnName} = ordered_table.new_sequence_number
|
||||||
|
FROM (
|
||||||
|
SELECT *, ROW_NUMBER() OVER (${
|
||||||
|
scopeKey ? `PARTITION BY ${queryInterface.quoteIdentifier(scopeKey)}` : ''
|
||||||
|
} ORDER BY ${quotedOrderField}) AS new_sequence_number
|
||||||
|
FROM ${this.collection.quotedTableName()}
|
||||||
|
${whereClause}
|
||||||
|
) AS ordered_table
|
||||||
|
WHERE ${this.collection.quotedTableName()}.${quotedOrderField} = ordered_table.${quotedOrderField};
|
||||||
|
`;
|
||||||
|
} else if (this.collection.db.inDialect('sqlite')) {
|
||||||
|
sql = `
|
||||||
|
UPDATE ${this.collection.quotedTableName()}
|
||||||
|
SET ${sortColumnName} = (
|
||||||
|
SELECT new_sequence_number
|
||||||
|
FROM (
|
||||||
|
SELECT *, ROW_NUMBER() OVER (${
|
||||||
|
scopeKey ? `PARTITION BY ${queryInterface.quoteIdentifier(scopeKey)}` : ''
|
||||||
|
} ORDER BY ${quotedOrderField}) AS new_sequence_number
|
||||||
|
FROM ${this.collection.quotedTableName()}
|
||||||
|
${whereClause}
|
||||||
|
) AS ordered_table
|
||||||
|
WHERE ${this.collection.quotedTableName()}.${quotedOrderField} = ordered_table.${quotedOrderField}
|
||||||
|
);
|
||||||
|
`;
|
||||||
|
} else if (this.collection.db.inDialect('mysql') || this.collection.db.inDialect('mariadb')) {
|
||||||
|
sql = `
|
||||||
|
UPDATE ${this.collection.quotedTableName()}
|
||||||
|
JOIN (
|
||||||
|
SELECT *, ROW_NUMBER() OVER (${
|
||||||
|
scopeKey ? `PARTITION BY ${queryInterface.quoteIdentifier(scopeKey)}` : ''
|
||||||
|
} ORDER BY ${quotedOrderField}) AS new_sequence_number
|
||||||
|
FROM ${this.collection.quotedTableName()}
|
||||||
|
${whereClause}
|
||||||
|
) AS ordered_table ON ${this.collection.quotedTableName()}.${quotedOrderField} = ordered_table.${quotedOrderField}
|
||||||
|
SET ${this.collection.quotedTableName()}.${sortColumnName} = ordered_table.new_sequence_number;
|
||||||
|
`;
|
||||||
|
}
|
||||||
await this.collection.db.sequelize.query(sql, {
|
await this.collection.db.sequelize.query(sql, {
|
||||||
transaction,
|
transaction,
|
||||||
});
|
});
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { IDatabaseOptions } from './database';
|
import { Database, IDatabaseOptions } from './database';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
|
import semver from 'semver';
|
||||||
|
|
||||||
function getEnvValue(key, defaultValue?) {
|
function getEnvValue(key, defaultValue?) {
|
||||||
return process.env[key] || defaultValue;
|
return process.env[key] || defaultValue;
|
||||||
@ -90,3 +91,56 @@ function customLogger(queryString, queryObject) {
|
|||||||
console.log(queryObject.bind);
|
console.log(queryObject.bind);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const dialectVersionAccessors = {
|
||||||
|
sqlite: {
|
||||||
|
sql: 'select sqlite_version() as version',
|
||||||
|
get: (v: string) => v,
|
||||||
|
version: '3.x',
|
||||||
|
},
|
||||||
|
mysql: {
|
||||||
|
sql: 'select version() as version',
|
||||||
|
get: (v: string) => {
|
||||||
|
const m = /([\d+.]+)/.exec(v);
|
||||||
|
return m[0];
|
||||||
|
},
|
||||||
|
version: '>=8.0.17',
|
||||||
|
},
|
||||||
|
mariadb: {
|
||||||
|
sql: 'select version() as version',
|
||||||
|
get: (v: string) => {
|
||||||
|
const m = /([\d+.]+)/.exec(v);
|
||||||
|
return m[0];
|
||||||
|
},
|
||||||
|
version: '>=10.9',
|
||||||
|
},
|
||||||
|
postgres: {
|
||||||
|
sql: 'select version() as version',
|
||||||
|
get: (v: string) => {
|
||||||
|
const m = /([\d+.]+)/.exec(v);
|
||||||
|
return semver.minVersion(m[0]).version;
|
||||||
|
},
|
||||||
|
version: '>=10',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function checkDatabaseVersion(db: Database) {
|
||||||
|
const dialect = db.sequelize.getDialect();
|
||||||
|
const accessor = dialectVersionAccessors[dialect];
|
||||||
|
if (!accessor) {
|
||||||
|
throw new Error(`unsupported dialect ${dialect}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await db.sequelize.query(accessor.sql, {
|
||||||
|
type: 'SELECT',
|
||||||
|
});
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
const version = accessor.get(result?.[0]?.version);
|
||||||
|
const versionResult = semver.satisfies(version, accessor.version);
|
||||||
|
if (!versionResult) {
|
||||||
|
throw new Error(`to use ${dialect}, please ensure the version is ${accessor.version}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
@ -62,7 +62,7 @@ export class Model<TModelAttributes extends {} = any, TCreationAttributes extend
|
|||||||
|
|
||||||
// fix sequelize sync with model that not have any column
|
// fix sequelize sync with model that not have any column
|
||||||
if (Object.keys(model.tableAttributes).length === 0) {
|
if (Object.keys(model.tableAttributes).length === 0) {
|
||||||
if (this.database.inDialect('sqlite', 'mysql')) {
|
if (this.database.inDialect('sqlite', 'mysql', 'mariadb')) {
|
||||||
console.error(`Zero-column tables aren't supported in ${this.database.sequelize.getDialect()}`);
|
console.error(`Zero-column tables aren't supported in ${this.database.sequelize.getDialect()}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ const isPg = (ctx) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const isMySQL = (ctx) => {
|
const isMySQL = (ctx) => {
|
||||||
return getDialect(ctx) === 'mysql';
|
return getDialect(ctx) === 'mysql' || getDialect(ctx) === 'mariadb';
|
||||||
};
|
};
|
||||||
|
|
||||||
export { getDialect, isPg, isMySQL };
|
export { getDialect, isPg, isMySQL };
|
||||||
|
@ -125,7 +125,7 @@ export class OptionsParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sortField.push(direction);
|
sortField.push(direction);
|
||||||
if (this.database.inDialect('mysql')) {
|
if (this.database.isMySQLCompatibleDialect()) {
|
||||||
orderParams.push([Sequelize.fn('ISNULL', Sequelize.col(`${this.model.name}.${sortField[0]}`))]);
|
orderParams.push([Sequelize.fn('ISNULL', Sequelize.col(`${this.model.name}.${sortField[0]}`))]);
|
||||||
}
|
}
|
||||||
orderParams.push(sortField);
|
orderParams.push(sortField);
|
||||||
|
@ -6,6 +6,7 @@ import SqliteQueryInterface from './sqlite-query-interface';
|
|||||||
export default function buildQueryInterface(db: Database) {
|
export default function buildQueryInterface(db: Database) {
|
||||||
const map = {
|
const map = {
|
||||||
mysql: MysqlQueryInterface,
|
mysql: MysqlQueryInterface,
|
||||||
|
mariadb: MysqlQueryInterface,
|
||||||
postgres: PostgresQueryInterface,
|
postgres: PostgresQueryInterface,
|
||||||
sqlite: SqliteQueryInterface,
|
sqlite: SqliteQueryInterface,
|
||||||
};
|
};
|
||||||
|
@ -55,4 +55,4 @@ const sqlite = {
|
|||||||
json: ['json', 'array'],
|
json: ['json', 'array'],
|
||||||
};
|
};
|
||||||
|
|
||||||
export default { postgres, mysql, sqlite };
|
export default { postgres, mysql, sqlite, mariadb: mysql };
|
||||||
|
@ -366,7 +366,7 @@ export class Application<StateT = DefaultState, ContextT = DefaultContext> exten
|
|||||||
}
|
}
|
||||||
this._authenticated = true;
|
this._authenticated = true;
|
||||||
await this.db.auth();
|
await this.db.auth();
|
||||||
await this.dbVersionCheck({ exit: true });
|
await this.db.checkVersion();
|
||||||
await this.db.prepare();
|
await this.db.prepare();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -559,37 +559,6 @@ export class Application<StateT = DefaultState, ContextT = DefaultContext> exten
|
|||||||
this.logger.debug('finish destroy app');
|
this.logger.debug('finish destroy app');
|
||||||
}
|
}
|
||||||
|
|
||||||
async dbVersionCheck(options?: { exit?: boolean }) {
|
|
||||||
const r = await this.db.version.satisfies({
|
|
||||||
mysql: '>=8.0.17',
|
|
||||||
sqlite: '3.x',
|
|
||||||
postgres: '>=10',
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!r) {
|
|
||||||
console.log(chalk.red('The database only supports MySQL 8.0.17 and above, SQLite 3.x and PostgreSQL 10+'));
|
|
||||||
if (options?.exit) {
|
|
||||||
process.exit();
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.db.inDialect('mysql')) {
|
|
||||||
const result = await this.db.sequelize.query(`SHOW VARIABLES LIKE 'lower_case_table_names'`, { plain: true });
|
|
||||||
if (result?.Value === '1' && !this.db.options.underscored) {
|
|
||||||
console.log(
|
|
||||||
`Your database lower_case_table_names=1, please add ${chalk.yellow('DB_UNDERSCORED=true')} to the .env file`,
|
|
||||||
);
|
|
||||||
if (options?.exit) {
|
|
||||||
process.exit();
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
async isInstalled() {
|
async isInstalled() {
|
||||||
return (
|
return (
|
||||||
(await this.db.collectionExistsInDb('applicationVersion')) || (await this.db.collectionExistsInDb('collections'))
|
(await this.db.collectionExistsInDb('applicationVersion')) || (await this.db.collectionExistsInDb('collections'))
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
"pg": "^8.7.3",
|
"pg": "^8.7.3",
|
||||||
"pg-hstore": "^2.3.4",
|
"pg-hstore": "^2.3.4",
|
||||||
"sqlite3": "^5.0.8",
|
"sqlite3": "^5.0.8",
|
||||||
|
"mariadb": "^2.5.6",
|
||||||
"supertest": "^6.1.6",
|
"supertest": "^6.1.6",
|
||||||
"ws": "^8.13.0"
|
"ws": "^8.13.0"
|
||||||
},
|
},
|
||||||
|
@ -4,12 +4,13 @@ import pg from 'pg';
|
|||||||
import dotenv from 'dotenv';
|
import dotenv from 'dotenv';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import mysql from 'mysql2/promise';
|
import mysql from 'mysql2/promise';
|
||||||
|
import mariadb from 'mariadb';
|
||||||
|
|
||||||
dotenv.config({ path: path.resolve(process.cwd(), '.env.test') });
|
dotenv.config({ path: path.resolve(process.cwd(), '.env.test') });
|
||||||
|
|
||||||
abstract class BaseClient<Client> {
|
abstract class BaseClient<Client> {
|
||||||
private createdDBs: Set<string> = new Set();
|
|
||||||
protected _client: Client | null = null;
|
protected _client: Client | null = null;
|
||||||
|
private createdDBs: Set<string> = new Set();
|
||||||
|
|
||||||
abstract _createDB(name: string): Promise<void>;
|
abstract _createDB(name: string): Promise<void>;
|
||||||
abstract _createConnection(): Promise<Client>;
|
abstract _createConnection(): Promise<Client>;
|
||||||
@ -89,6 +90,28 @@ class MySQLClient extends BaseClient<any> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class MariaDBClient extends BaseClient<any> {
|
||||||
|
async _removeDB(name: string): Promise<void> {
|
||||||
|
await this._client.query(`DROP DATABASE IF EXISTS ${name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async _createDB(name: string): Promise<void> {
|
||||||
|
await this._client.query(`CREATE DATABASE IF NOT EXISTS ${name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async _createConnection(): Promise<mariadb.Connection> {
|
||||||
|
const connection = await mariadb.createConnection({
|
||||||
|
host: process.env['DB_HOST'],
|
||||||
|
port: Number(process.env['DB_PORT']),
|
||||||
|
user: process.env['DB_USER'],
|
||||||
|
password: process.env['DB_PASSWORD'],
|
||||||
|
database: process.env['DB_DATABASE'],
|
||||||
|
});
|
||||||
|
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const client = {
|
const client = {
|
||||||
postgres: () => {
|
postgres: () => {
|
||||||
return new PostgresClient();
|
return new PostgresClient();
|
||||||
@ -96,6 +119,9 @@ const client = {
|
|||||||
mysql: () => {
|
mysql: () => {
|
||||||
return new MySQLClient();
|
return new MySQLClient();
|
||||||
},
|
},
|
||||||
|
mariadb: () => {
|
||||||
|
return new MariaDBClient();
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const dialect = process.env['DB_DIALECT'];
|
const dialect = process.env['DB_DIALECT'];
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Database, Repository, Field, DataTypes } from '@nocobase/database';
|
import { Database, DataTypes, Field, Repository } from '@nocobase/database';
|
||||||
import { MockServer } from '@nocobase/test';
|
import { MockServer } from '@nocobase/test';
|
||||||
import { uid } from '@nocobase/utils';
|
import { uid } from '@nocobase/utils';
|
||||||
import { createApp } from '../index';
|
import { createApp } from '../index';
|
||||||
@ -56,10 +56,6 @@ SELECT * FROM numbers;
|
|||||||
|
|
||||||
it('should support preview field with getter', async () => {
|
it('should support preview field with getter', async () => {
|
||||||
class TestField extends Field {
|
class TestField extends Field {
|
||||||
get dataType() {
|
|
||||||
return DataTypes.STRING;
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(options: any, context: any) {
|
constructor(options: any, context: any) {
|
||||||
const { name } = options;
|
const { name } = options;
|
||||||
super(
|
super(
|
||||||
@ -72,6 +68,10 @@ SELECT * FROM numbers;
|
|||||||
context,
|
context,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get dataType() {
|
||||||
|
return DataTypes.STRING;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
db.registerFieldTypes({
|
db.registerFieldTypes({
|
||||||
@ -165,24 +165,18 @@ SELECT * FROM numbers;
|
|||||||
expect(response.status).toBe(200);
|
expect(response.status).toBe(200);
|
||||||
const data = response.body.data;
|
const data = response.body.data;
|
||||||
|
|
||||||
if (app.db.options.dialect === 'mysql') {
|
if (app.db.inDialect('mysql')) {
|
||||||
expect(data.fields.n.type).toBe('bigInt');
|
expect(data.fields.n.type).toBe('bigInt');
|
||||||
} else if (app.db.options.dialect == 'postgres') {
|
} else if (app.db.inDialect('postgres', 'mariadb')) {
|
||||||
expect(data.fields.n.type).toBe('integer');
|
expect(data.fields.n.type).toBe('integer');
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(
|
|
||||||
JSON.stringify(
|
|
||||||
{
|
|
||||||
nField: data.fields.n,
|
|
||||||
},
|
|
||||||
null,
|
|
||||||
2,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return possible types for json fields', async () => {
|
it('should return possible types for json fields', async () => {
|
||||||
|
if (app.db.inDialect('mariadb')) {
|
||||||
|
// can not get json type from mariadb
|
||||||
|
return;
|
||||||
|
}
|
||||||
const jsonViewName = 'json_view';
|
const jsonViewName = 'json_view';
|
||||||
const dropSql = `DROP VIEW IF EXISTS ${jsonViewName}`;
|
const dropSql = `DROP VIEW IF EXISTS ${jsonViewName}`;
|
||||||
await app.db.sequelize.query(dropSql);
|
await app.db.sequelize.query(dropSql);
|
||||||
@ -203,9 +197,11 @@ SELECT * FROM numbers;
|
|||||||
|
|
||||||
expect(response.status).toBe(200);
|
expect(response.status).toBe(200);
|
||||||
const data = response.body.data;
|
const data = response.body.data;
|
||||||
|
|
||||||
if (!app.db.inDialect('sqlite')) {
|
if (!app.db.inDialect('sqlite')) {
|
||||||
expect(data.fields.json_field.type).toBe('json');
|
expect(data.fields.json_field.type).toBe('json');
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(data.fields.json_field.possibleTypes).toBeTruthy();
|
expect(data.fields.json_field.possibleTypes).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ export default class DropForeignKeysMigration extends Migration {
|
|||||||
}
|
}
|
||||||
const transaction = await this.db.sequelize.transaction();
|
const transaction = await this.db.sequelize.transaction();
|
||||||
try {
|
try {
|
||||||
if (this.db.inDialect('mysql')) {
|
if (this.db.isMySQLCompatibleDialect()) {
|
||||||
const [results]: any = await this.db.sequelize.query(
|
const [results]: any = await this.db.sequelize.query(
|
||||||
`
|
`
|
||||||
SELECT CONCAT('ALTER TABLE ',TABLE_SCHEMA,'.',TABLE_NAME,' DROP FOREIGN KEY ',CONSTRAINT_NAME,' ;') as q
|
SELECT CONCAT('ALTER TABLE ',TABLE_SCHEMA,'.',TABLE_NAME,' DROP FOREIGN KEY ',CONSTRAINT_NAME,' ;') as q
|
||||||
|
@ -19,7 +19,7 @@ export default class UpdateIdToBigIntMigrator extends Migration {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!db.inDialect('mysql', 'postgres')) {
|
if (!db.inDialect('mysql', 'mariadb', 'postgres')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,7 +36,7 @@ export default class UpdateIdToBigIntMigrator extends Migration {
|
|||||||
if (model.rawAttributes[fieldName].type instanceof DataTypes.INTEGER) {
|
if (model.rawAttributes[fieldName].type instanceof DataTypes.INTEGER) {
|
||||||
if (db.inDialect('postgres')) {
|
if (db.inDialect('postgres')) {
|
||||||
sql = `ALTER TABLE "${tableName}" ALTER COLUMN "${fieldName}" SET DATA TYPE BIGINT;`;
|
sql = `ALTER TABLE "${tableName}" ALTER COLUMN "${fieldName}" SET DATA TYPE BIGINT;`;
|
||||||
} else if (db.inDialect('mysql')) {
|
} else if (db.inDialect('mysql', 'mariadb')) {
|
||||||
const dataTypeOrOptions = model.rawAttributes[fieldName];
|
const dataTypeOrOptions = model.rawAttributes[fieldName];
|
||||||
const attributeName = fieldName;
|
const attributeName = fieldName;
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ export default class UpdateIdToBigIntMigrator extends Migration {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!db.inDialect('mysql', 'postgres')) {
|
if (!db.inDialect('mysql', 'mariadb', 'postgres')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,7 +59,7 @@ export default class UpdateIdToBigIntMigrator extends Migration {
|
|||||||
if (model.rawAttributes[fieldName].type instanceof DataTypes.INTEGER) {
|
if (model.rawAttributes[fieldName].type instanceof DataTypes.INTEGER) {
|
||||||
if (db.inDialect('postgres')) {
|
if (db.inDialect('postgres')) {
|
||||||
sql = `ALTER TABLE ${quoteTableName} ALTER COLUMN "${columnName}" SET DATA TYPE BIGINT;`;
|
sql = `ALTER TABLE ${quoteTableName} ALTER COLUMN "${columnName}" SET DATA TYPE BIGINT;`;
|
||||||
} else if (db.inDialect('mysql')) {
|
} else if (db.inDialect('mysql', 'mariadb')) {
|
||||||
const dataTypeOrOptions = model.rawAttributes[fieldName];
|
const dataTypeOrOptions = model.rawAttributes[fieldName];
|
||||||
const attributeName = fieldName;
|
const attributeName = fieldName;
|
||||||
|
|
||||||
|
@ -90,6 +90,7 @@ export class CollectionRepository extends Repository {
|
|||||||
if (lodash.isArray(skipField) && skipField.length) {
|
if (lodash.isArray(skipField) && skipField.length) {
|
||||||
lazyCollectionFields[instanceName] = skipField;
|
lazyCollectionFields[instanceName] = skipField;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.database.logger.debug(`load ${instanceName} collection`);
|
this.database.logger.debug(`load ${instanceName} collection`);
|
||||||
this.app.setMaintainingMessage(`load ${instanceName} collection`);
|
this.app.setMaintainingMessage(`load ${instanceName} collection`);
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ export const dateFormatFn = (sequelize: any, dialect: string, field: string, for
|
|||||||
.replace(/ss/g, '%S');
|
.replace(/ss/g, '%S');
|
||||||
return sequelize.fn('strftime', format, sequelize.col(field));
|
return sequelize.fn('strftime', format, sequelize.col(field));
|
||||||
case 'mysql':
|
case 'mysql':
|
||||||
|
case 'mariadb':
|
||||||
format = format
|
format = format
|
||||||
.replace(/YYYY/g, '%Y')
|
.replace(/YYYY/g, '%Y')
|
||||||
.replace(/MM/g, '%m')
|
.replace(/MM/g, '%m')
|
||||||
@ -22,7 +23,7 @@ export const dateFormatFn = (sequelize: any, dialect: string, field: string, for
|
|||||||
format = format.replace(/hh/g, 'HH24').replace(/mm/g, 'MI').replace(/ss/g, 'SS');
|
format = format.replace(/hh/g, 'HH24').replace(/mm/g, 'MI').replace(/ss/g, 'SS');
|
||||||
return sequelize.fn('to_char', sequelize.col(field), format);
|
return sequelize.fn('to_char', sequelize.col(field), format);
|
||||||
default:
|
default:
|
||||||
return field;
|
return sequelize.col(field);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ import readline from 'readline';
|
|||||||
export const DUMPED_EXTENSION = 'nbdump';
|
export const DUMPED_EXTENSION = 'nbdump';
|
||||||
|
|
||||||
export function sqlAdapter(database: Database, sql: string) {
|
export function sqlAdapter(database: Database, sql: string) {
|
||||||
if (database.sequelize.getDialect() === 'mysql') {
|
if (database.isMySQLCompatibleDialect()) {
|
||||||
return lodash.replace(sql, /"/g, '`');
|
return lodash.replace(sql, /"/g, '`');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -306,7 +306,7 @@ describe('multiple apps', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await app.start();
|
await app.start();
|
||||||
await sleep(5000);
|
await sleep(10000);
|
||||||
expect(AppSupervisor.getInstance().hasApp(subAppName)).toBeTruthy();
|
expect(AppSupervisor.getInstance().hasApp(subAppName)).toBeTruthy();
|
||||||
const appStatus = AppSupervisor.getInstance().getAppStatus(subAppName);
|
const appStatus = AppSupervisor.getInstance().getAppStatus(subAppName);
|
||||||
expect(appStatus).toEqual('running');
|
expect(appStatus).toEqual('running');
|
||||||
|
@ -65,6 +65,13 @@ const defaultDbCreator = async (app: Application) => {
|
|||||||
await connection.close();
|
await connection.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (dialect === 'mariadb') {
|
||||||
|
const mariadb = require('mariadb');
|
||||||
|
const connection = await mariadb.createConnection({ host, port, user: username, password });
|
||||||
|
await connection.query(`CREATE DATABASE IF NOT EXISTS \`${database}\`;`);
|
||||||
|
await connection.end();
|
||||||
|
}
|
||||||
|
|
||||||
if (dialect === 'postgres') {
|
if (dialect === 'postgres') {
|
||||||
const { Client } = require('pg');
|
const { Client } = require('pg');
|
||||||
|
|
||||||
|
@ -72,41 +72,6 @@ function transaction(transactionAbleArgPosition?: number) {
|
|||||||
export class UiSchemaRepository extends Repository {
|
export class UiSchemaRepository extends Repository {
|
||||||
cache: Cache;
|
cache: Cache;
|
||||||
|
|
||||||
// if you need to handle cache in repo method, so you must set cache first
|
|
||||||
setCache(cache: Cache) {
|
|
||||||
this.cache = cache;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* clear cache with xUid which in uiSchemaTreePath's Path
|
|
||||||
* @param {string} xUid
|
|
||||||
* @param {Transaction} transaction
|
|
||||||
* @returns {Promise<void>}
|
|
||||||
*/
|
|
||||||
async clearXUidPathCache(xUid: string, transaction: Transaction) {
|
|
||||||
if (!this.cache || !xUid) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// find all xUid node's parent nodes
|
|
||||||
const uiSchemaNodes = await this.database.getRepository('uiSchemaTreePath').find({
|
|
||||||
filter: {
|
|
||||||
descendant: xUid,
|
|
||||||
},
|
|
||||||
transaction: transaction,
|
|
||||||
});
|
|
||||||
for (const uiSchemaNode of uiSchemaNodes) {
|
|
||||||
await this.cache.del(`p_${uiSchemaNode['ancestor']}`);
|
|
||||||
await this.cache.del(`s_${uiSchemaNode['ancestor']}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tableNameAdapter(tableName) {
|
|
||||||
if (this.database.sequelize.getDialect() === 'postgres') {
|
|
||||||
return `"${this.database.options.schema || 'public'}"."${tableName}"`;
|
|
||||||
}
|
|
||||||
return tableName;
|
|
||||||
}
|
|
||||||
|
|
||||||
get uiSchemasTableName() {
|
get uiSchemasTableName() {
|
||||||
return this.tableNameAdapter(this.model.tableName);
|
return this.tableNameAdapter(this.model.tableName);
|
||||||
}
|
}
|
||||||
@ -116,14 +81,6 @@ export class UiSchemaRepository extends Repository {
|
|||||||
return this.tableNameAdapter(model.tableName);
|
return this.tableNameAdapter(model.tableName);
|
||||||
}
|
}
|
||||||
|
|
||||||
sqlAdapter(sql: string) {
|
|
||||||
if (this.database.sequelize.getDialect() === 'mysql') {
|
|
||||||
return lodash.replace(sql, /"/g, '`');
|
|
||||||
}
|
|
||||||
|
|
||||||
return sql;
|
|
||||||
}
|
|
||||||
|
|
||||||
static schemaToSingleNodes(schema: any, carry: SchemaNode[] = [], childOptions: ChildOptions = null): SchemaNode[] {
|
static schemaToSingleNodes(schema: any, carry: SchemaNode[] = [], childOptions: ChildOptions = null): SchemaNode[] {
|
||||||
const node = lodash.cloneDeep(
|
const node = lodash.cloneDeep(
|
||||||
lodash.isString(schema)
|
lodash.isString(schema)
|
||||||
@ -180,6 +137,49 @@ export class UiSchemaRepository extends Repository {
|
|||||||
return carry;
|
return carry;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if you need to handle cache in repo method, so you must set cache first
|
||||||
|
setCache(cache: Cache) {
|
||||||
|
this.cache = cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* clear cache with xUid which in uiSchemaTreePath's Path
|
||||||
|
* @param {string} xUid
|
||||||
|
* @param {Transaction} transaction
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async clearXUidPathCache(xUid: string, transaction: Transaction) {
|
||||||
|
if (!this.cache || !xUid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// find all xUid node's parent nodes
|
||||||
|
const uiSchemaNodes = await this.database.getRepository('uiSchemaTreePath').find({
|
||||||
|
filter: {
|
||||||
|
descendant: xUid,
|
||||||
|
},
|
||||||
|
transaction: transaction,
|
||||||
|
});
|
||||||
|
for (const uiSchemaNode of uiSchemaNodes) {
|
||||||
|
await this.cache.del(`p_${uiSchemaNode['ancestor']}`);
|
||||||
|
await this.cache.del(`s_${uiSchemaNode['ancestor']}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tableNameAdapter(tableName) {
|
||||||
|
if (this.database.sequelize.getDialect() === 'postgres') {
|
||||||
|
return `"${this.database.options.schema || 'public'}"."${tableName}"`;
|
||||||
|
}
|
||||||
|
return tableName;
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlAdapter(sql: string) {
|
||||||
|
if (this.database.isMySQLCompatibleDialect()) {
|
||||||
|
return lodash.replace(sql, /"/g, '`');
|
||||||
|
}
|
||||||
|
|
||||||
|
return sql;
|
||||||
|
}
|
||||||
|
|
||||||
async getProperties(uid: string, options: GetPropertiesOptions = {}) {
|
async getProperties(uid: string, options: GetPropertiesOptions = {}) {
|
||||||
if (options?.readFromCache && this.cache) {
|
if (options?.readFromCache && this.cache) {
|
||||||
return this.cache.wrap(`p_${uid}`, () => {
|
return this.cache.wrap(`p_${uid}`, () => {
|
||||||
@ -189,66 +189,6 @@ export class UiSchemaRepository extends Repository {
|
|||||||
return this.doGetProperties(uid, options);
|
return this.doGetProperties(uid, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async doGetProperties(uid: string, options: GetPropertiesOptions = {}) {
|
|
||||||
const { transaction } = options;
|
|
||||||
|
|
||||||
const db = this.database;
|
|
||||||
|
|
||||||
const rawSql = `
|
|
||||||
SELECT "SchemaTable"."x-uid" as "x-uid", "SchemaTable"."name" as "name", "SchemaTable"."schema" as "schema",
|
|
||||||
TreePath.depth as depth,
|
|
||||||
NodeInfo.type as type, NodeInfo.async as async, ParentPath.ancestor as parent, ParentPath.sort as sort
|
|
||||||
FROM ${this.uiSchemaTreePathTableName} as TreePath
|
|
||||||
LEFT JOIN ${this.uiSchemasTableName} as "SchemaTable" ON "SchemaTable"."x-uid" = TreePath.descendant
|
|
||||||
LEFT JOIN ${this.uiSchemaTreePathTableName} as NodeInfo ON NodeInfo.descendant = "SchemaTable"."x-uid" and NodeInfo.descendant = NodeInfo.ancestor and NodeInfo.depth = 0
|
|
||||||
LEFT JOIN ${this.uiSchemaTreePathTableName} as ParentPath ON (ParentPath.descendant = "SchemaTable"."x-uid" AND ParentPath.depth = 1)
|
|
||||||
WHERE TreePath.ancestor = :ancestor AND (NodeInfo.async = false or TreePath.depth = 1)`;
|
|
||||||
|
|
||||||
const nodes = await db.sequelize.query(this.sqlAdapter(rawSql), {
|
|
||||||
replacements: {
|
|
||||||
ancestor: uid,
|
|
||||||
},
|
|
||||||
transaction,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (nodes[0].length == 0) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
const schema = this.nodesToSchema(nodes[0], uid);
|
|
||||||
return lodash.pick(schema, ['type', 'properties']);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async doGetJsonSchema(uid: string, options?: GetJsonSchemaOptions) {
|
|
||||||
const db = this.database;
|
|
||||||
|
|
||||||
const treeTable = this.uiSchemaTreePathTableName;
|
|
||||||
|
|
||||||
const rawSql = `
|
|
||||||
SELECT "SchemaTable"."x-uid" as "x-uid", "SchemaTable"."name" as name, "SchemaTable"."schema" as "schema" ,
|
|
||||||
TreePath.depth as depth,
|
|
||||||
NodeInfo.type as type, NodeInfo.async as async, ParentPath.ancestor as parent, ParentPath.sort as sort
|
|
||||||
FROM ${treeTable} as TreePath
|
|
||||||
LEFT JOIN ${this.uiSchemasTableName} as "SchemaTable" ON "SchemaTable"."x-uid" = TreePath.descendant
|
|
||||||
LEFT JOIN ${treeTable} as NodeInfo ON NodeInfo.descendant = "SchemaTable"."x-uid" and NodeInfo.descendant = NodeInfo.ancestor and NodeInfo.depth = 0
|
|
||||||
LEFT JOIN ${treeTable} as ParentPath ON (ParentPath.descendant = "SchemaTable"."x-uid" AND ParentPath.depth = 1)
|
|
||||||
WHERE TreePath.ancestor = :ancestor ${options?.includeAsyncNode ? '' : 'AND (NodeInfo.async != true )'}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const nodes = await db.sequelize.query(this.sqlAdapter(rawSql), {
|
|
||||||
replacements: {
|
|
||||||
ancestor: uid,
|
|
||||||
},
|
|
||||||
transaction: options?.transaction,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (nodes[0].length == 0) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.nodesToSchema(nodes[0], uid);
|
|
||||||
}
|
|
||||||
|
|
||||||
async getJsonSchema(uid: string, options?: GetJsonSchemaOptions): Promise<any> {
|
async getJsonSchema(uid: string, options?: GetJsonSchemaOptions): Promise<any> {
|
||||||
if (options?.readFromCache && this.cache) {
|
if (options?.readFromCache && this.cache) {
|
||||||
return this.cache.wrap(`s_${uid}`, () => {
|
return this.cache.wrap(`s_${uid}`, () => {
|
||||||
@ -258,10 +198,6 @@ export class UiSchemaRepository extends Repository {
|
|||||||
return this.doGetJsonSchema(uid, options);
|
return this.doGetJsonSchema(uid, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ignoreSchemaProperties(schemaProperties) {
|
|
||||||
return lodash.omit(schemaProperties, nodeKeys);
|
|
||||||
}
|
|
||||||
|
|
||||||
nodesToSchema(nodes, rootUid) {
|
nodesToSchema(nodes, rootUid) {
|
||||||
const nodeAttributeSanitize = (node) => {
|
const nodeAttributeSanitize = (node) => {
|
||||||
const schema = {
|
const schema = {
|
||||||
@ -374,104 +310,6 @@ export class UiSchemaRepository extends Repository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async updateNode(uid: string, schema: any, transaction?: Transaction) {
|
|
||||||
const nodeModel = await this.findOne({
|
|
||||||
filter: {
|
|
||||||
'x-uid': uid,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await nodeModel.update(
|
|
||||||
{
|
|
||||||
schema: {
|
|
||||||
...(nodeModel.get('schema') as any),
|
|
||||||
...lodash.omit(schema, ['x-async', 'name', 'x-uid', 'properties']),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
hooks: false,
|
|
||||||
transaction,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
if (schema['x-server-hooks']) {
|
|
||||||
await this.database.emitAsync(`${this.collection.name}.afterSave`, nodeModel, { transaction });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async childrenCount(uid, transaction) {
|
|
||||||
const db = this.database;
|
|
||||||
|
|
||||||
const countResult = await db.sequelize.query(
|
|
||||||
`SELECT COUNT(*) as count FROM ${this.uiSchemaTreePathTableName} where ancestor = :ancestor and depth = 1`,
|
|
||||||
{
|
|
||||||
replacements: {
|
|
||||||
ancestor: uid,
|
|
||||||
},
|
|
||||||
type: 'SELECT',
|
|
||||||
transaction,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
return parseInt(countResult[0]['count']);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async isLeafNode(uid, transaction) {
|
|
||||||
const childrenCount = await this.childrenCount(uid, transaction);
|
|
||||||
return childrenCount === 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async findParentUid(uid, transaction?) {
|
|
||||||
const parent = await this.database.getRepository('uiSchemaTreePath').findOne({
|
|
||||||
filter: {
|
|
||||||
descendant: uid,
|
|
||||||
depth: 1,
|
|
||||||
},
|
|
||||||
transaction,
|
|
||||||
});
|
|
||||||
|
|
||||||
return parent ? (parent.get('ancestor') as string) : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async findNodeSchemaWithParent(uid, transaction) {
|
|
||||||
const schema = await this.database.getRepository('uiSchemas').findOne({
|
|
||||||
filter: {
|
|
||||||
'x-uid': uid,
|
|
||||||
},
|
|
||||||
transaction,
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
parentUid: await this.findParentUid(uid, transaction),
|
|
||||||
schema,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async isSingleChild(uid, transaction) {
|
|
||||||
const db = this.database;
|
|
||||||
|
|
||||||
const parent = await this.findParentUid(uid, transaction);
|
|
||||||
|
|
||||||
if (!parent) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const parentChildrenCount = await this.childrenCount(parent, transaction);
|
|
||||||
|
|
||||||
if (parentChildrenCount == 1) {
|
|
||||||
const schema = await db.getRepository('uiSchemas').findOne({
|
|
||||||
filter: {
|
|
||||||
'x-uid': parent,
|
|
||||||
},
|
|
||||||
transaction,
|
|
||||||
});
|
|
||||||
|
|
||||||
return schema;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
async removeEmptyParents(options: Transactionable & { uid: string; breakRemoveOn?: BreakRemoveOnType }) {
|
async removeEmptyParents(options: Transactionable & { uid: string; breakRemoveOn?: BreakRemoveOnType }) {
|
||||||
const { transaction, uid, breakRemoveOn } = options;
|
const { transaction, uid, breakRemoveOn } = options;
|
||||||
|
|
||||||
@ -490,22 +328,6 @@ export class UiSchemaRepository extends Repository {
|
|||||||
await removeParent(uid);
|
await removeParent(uid);
|
||||||
}
|
}
|
||||||
|
|
||||||
private breakOnMatched(schemaInstance, breakRemoveOn: BreakRemoveOnType): boolean {
|
|
||||||
if (!breakRemoveOn) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const key of Object.keys(breakRemoveOn)) {
|
|
||||||
const instanceValue = schemaInstance.get(key);
|
|
||||||
const breakRemoveOnValue = breakRemoveOn[key];
|
|
||||||
if (instanceValue !== breakRemoveOnValue) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
async recursivelyRemoveIfNoChildren(options: Transactionable & { uid: string; breakRemoveOn?: BreakRemoveOnType }) {
|
async recursivelyRemoveIfNoChildren(options: Transactionable & { uid: string; breakRemoveOn?: BreakRemoveOnType }) {
|
||||||
const { uid, transaction, breakRemoveOn } = options;
|
const { uid, transaction, breakRemoveOn } = options;
|
||||||
|
|
||||||
@ -572,92 +394,6 @@ export class UiSchemaRepository extends Repository {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@transaction()
|
|
||||||
protected async insertBeside(
|
|
||||||
targetUid: string,
|
|
||||||
schema: any,
|
|
||||||
side: 'before' | 'after',
|
|
||||||
options?: InsertAdjacentOptions,
|
|
||||||
) {
|
|
||||||
const { transaction } = options;
|
|
||||||
const targetParent = await this.findParentUid(targetUid, transaction);
|
|
||||||
|
|
||||||
const db = this.database;
|
|
||||||
|
|
||||||
const treeTable = this.uiSchemaTreePathTableName;
|
|
||||||
|
|
||||||
const typeQuery = await db.sequelize.query(`SELECT type from ${treeTable} WHERE ancestor = :uid AND depth = 0;`, {
|
|
||||||
type: 'SELECT',
|
|
||||||
replacements: {
|
|
||||||
uid: targetUid,
|
|
||||||
},
|
|
||||||
transaction,
|
|
||||||
});
|
|
||||||
|
|
||||||
const nodes = UiSchemaRepository.schemaToSingleNodes(schema);
|
|
||||||
|
|
||||||
const rootNode = nodes[0];
|
|
||||||
|
|
||||||
rootNode.childOptions = {
|
|
||||||
parentUid: targetParent,
|
|
||||||
type: typeQuery[0]['type'],
|
|
||||||
position: {
|
|
||||||
type: side,
|
|
||||||
target: targetUid,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const insertedNodes = await this.insertNodes(nodes, options);
|
|
||||||
return await this.getJsonSchema(insertedNodes[0].get('x-uid'), {
|
|
||||||
transaction,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@transaction()
|
|
||||||
protected async insertInner(
|
|
||||||
targetUid: string,
|
|
||||||
schema: any,
|
|
||||||
position: 'first' | 'last',
|
|
||||||
options?: InsertAdjacentOptions,
|
|
||||||
) {
|
|
||||||
const { transaction } = options;
|
|
||||||
|
|
||||||
const nodes = UiSchemaRepository.schemaToSingleNodes(schema);
|
|
||||||
const rootNode = nodes[0];
|
|
||||||
|
|
||||||
rootNode.childOptions = {
|
|
||||||
parentUid: targetUid,
|
|
||||||
type: lodash.get(schema, 'x-node-type', 'properties'),
|
|
||||||
position,
|
|
||||||
};
|
|
||||||
|
|
||||||
const insertedNodes = await this.insertNodes(nodes, options);
|
|
||||||
|
|
||||||
return await this.getJsonSchema(insertedNodes[0].get('x-uid'), {
|
|
||||||
transaction,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private async schemaExists(schema: any, options?: Transactionable): Promise<boolean> {
|
|
||||||
if (lodash.isObject(schema) && !schema['x-uid']) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { transaction } = options;
|
|
||||||
const result = await this.database.sequelize.query(
|
|
||||||
this.sqlAdapter(`select "x-uid" from ${this.uiSchemasTableName} where "x-uid" = :uid`),
|
|
||||||
{
|
|
||||||
type: 'SELECT',
|
|
||||||
replacements: {
|
|
||||||
uid: lodash.isString(schema) ? schema : schema['x-uid'],
|
|
||||||
},
|
|
||||||
transaction,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
return result.length > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@transaction()
|
@transaction()
|
||||||
async insertAdjacent(
|
async insertAdjacent(
|
||||||
position: 'beforeBegin' | 'afterBegin' | 'beforeEnd' | 'afterEnd',
|
position: 'beforeBegin' | 'afterBegin' | 'beforeEnd' | 'afterEnd',
|
||||||
@ -706,51 +442,6 @@ export class UiSchemaRepository extends Repository {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@transaction()
|
|
||||||
protected async insertAfterBegin(targetUid: string, schema: any, options?: InsertAdjacentOptions) {
|
|
||||||
return await this.insertInner(targetUid, schema, 'first', options);
|
|
||||||
}
|
|
||||||
|
|
||||||
@transaction()
|
|
||||||
protected async insertBeforeEnd(targetUid: string, schema: any, options?: InsertAdjacentOptions) {
|
|
||||||
return await this.insertInner(targetUid, schema, 'last', options);
|
|
||||||
}
|
|
||||||
|
|
||||||
@transaction()
|
|
||||||
protected async insertBeforeBegin(targetUid: string, schema: any, options?: InsertAdjacentOptions) {
|
|
||||||
return await this.insertBeside(targetUid, schema, 'before', options);
|
|
||||||
}
|
|
||||||
|
|
||||||
@transaction()
|
|
||||||
protected async insertAfterEnd(targetUid: string, schema: any, options?: InsertAdjacentOptions) {
|
|
||||||
return await this.insertBeside(targetUid, schema, 'after', options);
|
|
||||||
}
|
|
||||||
|
|
||||||
@transaction()
|
|
||||||
protected async insertNodes(nodes: SchemaNode[], options?: Transactionable) {
|
|
||||||
const { transaction } = options;
|
|
||||||
|
|
||||||
const insertedNodes = [];
|
|
||||||
|
|
||||||
for (const node of nodes) {
|
|
||||||
insertedNodes.push(
|
|
||||||
await this.insertSingleNode(node, {
|
|
||||||
...options,
|
|
||||||
transaction,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return insertedNodes;
|
|
||||||
}
|
|
||||||
|
|
||||||
private regenerateUid(s: any) {
|
|
||||||
s['x-uid'] = uid();
|
|
||||||
Object.keys(s.properties || {}).forEach((key) => {
|
|
||||||
this.regenerateUid(s.properties[key]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@transaction()
|
@transaction()
|
||||||
async duplicate(uid: string, options?: Transactionable) {
|
async duplicate(uid: string, options?: Transactionable) {
|
||||||
const s = await this.getJsonSchema(uid, { ...options, includeAsyncNode: true });
|
const s = await this.getJsonSchema(uid, { ...options, includeAsyncNode: true });
|
||||||
@ -842,39 +533,6 @@ export class UiSchemaRepository extends Repository {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async insertSchemaRecord(name, uid, schema, transaction) {
|
|
||||||
const serverHooks = schema['x-server-hooks'] || [];
|
|
||||||
|
|
||||||
const node = await this.create({
|
|
||||||
values: {
|
|
||||||
name,
|
|
||||||
['x-uid']: uid,
|
|
||||||
schema,
|
|
||||||
serverHooks,
|
|
||||||
},
|
|
||||||
transaction,
|
|
||||||
context: {
|
|
||||||
disableInsertHook: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
private prepareSingleNodeForInsert(schema: SchemaNode) {
|
|
||||||
const uid = schema['x-uid'];
|
|
||||||
const name = schema['name'];
|
|
||||||
const async = lodash.get(schema, 'x-async', false);
|
|
||||||
const childOptions = schema['childOptions'];
|
|
||||||
|
|
||||||
delete schema['x-uid'];
|
|
||||||
delete schema['x-async'];
|
|
||||||
delete schema['name'];
|
|
||||||
delete schema['childOptions'];
|
|
||||||
|
|
||||||
return { uid, name, async, childOptions };
|
|
||||||
}
|
|
||||||
|
|
||||||
async insertSingleNode(schema: SchemaNode, options: Transactionable & removeParentOptions) {
|
async insertSingleNode(schema: SchemaNode, options: Transactionable & removeParentOptions) {
|
||||||
const { transaction } = options;
|
const { transaction } = options;
|
||||||
|
|
||||||
@ -1001,7 +659,7 @@ export class UiSchemaRepository extends Repository {
|
|||||||
AND TreeTable.depth = 1 AND TreeTable.ancestor = :ancestor and NodeInfo.type = :type`;
|
AND TreeTable.depth = 1 AND TreeTable.ancestor = :ancestor and NodeInfo.type = :type`;
|
||||||
|
|
||||||
// Compatible with mysql
|
// Compatible with mysql
|
||||||
if (this.database.sequelize.getDialect() === 'mysql') {
|
if (this.database.isMySQLCompatibleDialect()) {
|
||||||
updateSql = `UPDATE ${treeTable} as TreeTable
|
updateSql = `UPDATE ${treeTable} as TreeTable
|
||||||
JOIN ${treeTable} as NodeInfo ON (NodeInfo.descendant = TreeTable.descendant and NodeInfo.depth = 0)
|
JOIN ${treeTable} as NodeInfo ON (NodeInfo.descendant = TreeTable.descendant and NodeInfo.depth = 0)
|
||||||
SET TreeTable.sort = TreeTable.sort + 1
|
SET TreeTable.sort = TreeTable.sort + 1
|
||||||
@ -1074,7 +732,7 @@ export class UiSchemaRepository extends Repository {
|
|||||||
and TreeTable.sort >= :sort
|
and TreeTable.sort >= :sort
|
||||||
and NodeInfo.type = :type`;
|
and NodeInfo.type = :type`;
|
||||||
|
|
||||||
if (this.database.sequelize.getDialect() === 'mysql') {
|
if (this.database.isMySQLCompatibleDialect()) {
|
||||||
updateSql = `UPDATE ${treeTable} as TreeTable
|
updateSql = `UPDATE ${treeTable} as TreeTable
|
||||||
JOIN ${treeTable} as NodeInfo ON (NodeInfo.descendant = TreeTable.descendant and NodeInfo.depth = 0)
|
JOIN ${treeTable} as NodeInfo ON (NodeInfo.descendant = TreeTable.descendant and NodeInfo.depth = 0)
|
||||||
SET TreeTable.sort = TreeTable.sort + 1
|
SET TreeTable.sort = TreeTable.sort + 1
|
||||||
@ -1136,6 +794,348 @@ WHERE TreeTable.depth = 1 AND TreeTable.ancestor = :ancestor and TreeTable.sort
|
|||||||
await this.clearXUidPathCache(uid, transaction);
|
await this.clearXUidPathCache(uid, transaction);
|
||||||
return savedNode;
|
return savedNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected async updateNode(uid: string, schema: any, transaction?: Transaction) {
|
||||||
|
const nodeModel = await this.findOne({
|
||||||
|
filter: {
|
||||||
|
'x-uid': uid,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await nodeModel.update(
|
||||||
|
{
|
||||||
|
schema: {
|
||||||
|
...(nodeModel.get('schema') as any),
|
||||||
|
...lodash.omit(schema, ['x-async', 'name', 'x-uid', 'properties']),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
hooks: false,
|
||||||
|
transaction,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (schema['x-server-hooks']) {
|
||||||
|
await this.database.emitAsync(`${this.collection.name}.afterSave`, nodeModel, { transaction });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async childrenCount(uid, transaction) {
|
||||||
|
const db = this.database;
|
||||||
|
|
||||||
|
const countResult = await db.sequelize.query(
|
||||||
|
`SELECT COUNT(*) as count FROM ${this.uiSchemaTreePathTableName} where ancestor = :ancestor and depth = 1`,
|
||||||
|
{
|
||||||
|
replacements: {
|
||||||
|
ancestor: uid,
|
||||||
|
},
|
||||||
|
type: 'SELECT',
|
||||||
|
transaction,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return parseInt(countResult[0]['count']);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async isLeafNode(uid, transaction) {
|
||||||
|
const childrenCount = await this.childrenCount(uid, transaction);
|
||||||
|
return childrenCount === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async findParentUid(uid, transaction?) {
|
||||||
|
const parent = await this.database.getRepository('uiSchemaTreePath').findOne({
|
||||||
|
filter: {
|
||||||
|
descendant: uid,
|
||||||
|
depth: 1,
|
||||||
|
},
|
||||||
|
transaction,
|
||||||
|
});
|
||||||
|
|
||||||
|
return parent ? (parent.get('ancestor') as string) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async findNodeSchemaWithParent(uid, transaction) {
|
||||||
|
const schema = await this.database.getRepository('uiSchemas').findOne({
|
||||||
|
filter: {
|
||||||
|
'x-uid': uid,
|
||||||
|
},
|
||||||
|
transaction,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
parentUid: await this.findParentUid(uid, transaction),
|
||||||
|
schema,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async isSingleChild(uid, transaction) {
|
||||||
|
const db = this.database;
|
||||||
|
|
||||||
|
const parent = await this.findParentUid(uid, transaction);
|
||||||
|
|
||||||
|
if (!parent) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const parentChildrenCount = await this.childrenCount(parent, transaction);
|
||||||
|
|
||||||
|
if (parentChildrenCount == 1) {
|
||||||
|
const schema = await db.getRepository('uiSchemas').findOne({
|
||||||
|
filter: {
|
||||||
|
'x-uid': parent,
|
||||||
|
},
|
||||||
|
transaction,
|
||||||
|
});
|
||||||
|
|
||||||
|
return schema;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@transaction()
|
||||||
|
protected async insertBeside(
|
||||||
|
targetUid: string,
|
||||||
|
schema: any,
|
||||||
|
side: 'before' | 'after',
|
||||||
|
options?: InsertAdjacentOptions,
|
||||||
|
) {
|
||||||
|
const { transaction } = options;
|
||||||
|
const targetParent = await this.findParentUid(targetUid, transaction);
|
||||||
|
|
||||||
|
const db = this.database;
|
||||||
|
|
||||||
|
const treeTable = this.uiSchemaTreePathTableName;
|
||||||
|
|
||||||
|
const typeQuery = await db.sequelize.query(`SELECT type from ${treeTable} WHERE ancestor = :uid AND depth = 0;`, {
|
||||||
|
type: 'SELECT',
|
||||||
|
replacements: {
|
||||||
|
uid: targetUid,
|
||||||
|
},
|
||||||
|
transaction,
|
||||||
|
});
|
||||||
|
|
||||||
|
const nodes = UiSchemaRepository.schemaToSingleNodes(schema);
|
||||||
|
|
||||||
|
const rootNode = nodes[0];
|
||||||
|
|
||||||
|
rootNode.childOptions = {
|
||||||
|
parentUid: targetParent,
|
||||||
|
type: typeQuery[0]['type'],
|
||||||
|
position: {
|
||||||
|
type: side,
|
||||||
|
target: targetUid,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const insertedNodes = await this.insertNodes(nodes, options);
|
||||||
|
return await this.getJsonSchema(insertedNodes[0].get('x-uid'), {
|
||||||
|
transaction,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@transaction()
|
||||||
|
protected async insertInner(
|
||||||
|
targetUid: string,
|
||||||
|
schema: any,
|
||||||
|
position: 'first' | 'last',
|
||||||
|
options?: InsertAdjacentOptions,
|
||||||
|
) {
|
||||||
|
const { transaction } = options;
|
||||||
|
|
||||||
|
const nodes = UiSchemaRepository.schemaToSingleNodes(schema);
|
||||||
|
const rootNode = nodes[0];
|
||||||
|
|
||||||
|
rootNode.childOptions = {
|
||||||
|
parentUid: targetUid,
|
||||||
|
type: lodash.get(schema, 'x-node-type', 'properties'),
|
||||||
|
position,
|
||||||
|
};
|
||||||
|
|
||||||
|
const insertedNodes = await this.insertNodes(nodes, options);
|
||||||
|
|
||||||
|
return await this.getJsonSchema(insertedNodes[0].get('x-uid'), {
|
||||||
|
transaction,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@transaction()
|
||||||
|
protected async insertAfterBegin(targetUid: string, schema: any, options?: InsertAdjacentOptions) {
|
||||||
|
return await this.insertInner(targetUid, schema, 'first', options);
|
||||||
|
}
|
||||||
|
|
||||||
|
@transaction()
|
||||||
|
protected async insertBeforeEnd(targetUid: string, schema: any, options?: InsertAdjacentOptions) {
|
||||||
|
return await this.insertInner(targetUid, schema, 'last', options);
|
||||||
|
}
|
||||||
|
|
||||||
|
@transaction()
|
||||||
|
protected async insertBeforeBegin(targetUid: string, schema: any, options?: InsertAdjacentOptions) {
|
||||||
|
return await this.insertBeside(targetUid, schema, 'before', options);
|
||||||
|
}
|
||||||
|
|
||||||
|
@transaction()
|
||||||
|
protected async insertAfterEnd(targetUid: string, schema: any, options?: InsertAdjacentOptions) {
|
||||||
|
return await this.insertBeside(targetUid, schema, 'after', options);
|
||||||
|
}
|
||||||
|
|
||||||
|
@transaction()
|
||||||
|
protected async insertNodes(nodes: SchemaNode[], options?: Transactionable) {
|
||||||
|
const { transaction } = options;
|
||||||
|
|
||||||
|
const insertedNodes = [];
|
||||||
|
|
||||||
|
for (const node of nodes) {
|
||||||
|
insertedNodes.push(
|
||||||
|
await this.insertSingleNode(node, {
|
||||||
|
...options,
|
||||||
|
transaction,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return insertedNodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async doGetProperties(uid: string, options: GetPropertiesOptions = {}) {
|
||||||
|
const { transaction } = options;
|
||||||
|
|
||||||
|
const db = this.database;
|
||||||
|
|
||||||
|
const rawSql = `
|
||||||
|
SELECT "SchemaTable"."x-uid" as "x-uid", "SchemaTable"."name" as "name", "SchemaTable"."schema" as "schema",
|
||||||
|
TreePath.depth as depth,
|
||||||
|
NodeInfo.type as type, NodeInfo.async as async, ParentPath.ancestor as parent, ParentPath.sort as sort
|
||||||
|
FROM ${this.uiSchemaTreePathTableName} as TreePath
|
||||||
|
LEFT JOIN ${this.uiSchemasTableName} as "SchemaTable" ON "SchemaTable"."x-uid" = TreePath.descendant
|
||||||
|
LEFT JOIN ${this.uiSchemaTreePathTableName} as NodeInfo ON NodeInfo.descendant = "SchemaTable"."x-uid" and NodeInfo.descendant = NodeInfo.ancestor and NodeInfo.depth = 0
|
||||||
|
LEFT JOIN ${this.uiSchemaTreePathTableName} as ParentPath ON (ParentPath.descendant = "SchemaTable"."x-uid" AND ParentPath.depth = 1)
|
||||||
|
WHERE TreePath.ancestor = :ancestor AND (NodeInfo.async = false or TreePath.depth = 1)`;
|
||||||
|
|
||||||
|
const nodes = await db.sequelize.query(this.sqlAdapter(rawSql), {
|
||||||
|
replacements: {
|
||||||
|
ancestor: uid,
|
||||||
|
},
|
||||||
|
transaction,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (nodes[0].length == 0) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const schema = this.nodesToSchema(nodes[0], uid);
|
||||||
|
return lodash.pick(schema, ['type', 'properties']);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async doGetJsonSchema(uid: string, options?: GetJsonSchemaOptions) {
|
||||||
|
const db = this.database;
|
||||||
|
|
||||||
|
const treeTable = this.uiSchemaTreePathTableName;
|
||||||
|
|
||||||
|
const rawSql = `
|
||||||
|
SELECT "SchemaTable"."x-uid" as "x-uid", "SchemaTable"."name" as name, "SchemaTable"."schema" as "schema" ,
|
||||||
|
TreePath.depth as depth,
|
||||||
|
NodeInfo.type as type, NodeInfo.async as async, ParentPath.ancestor as parent, ParentPath.sort as sort
|
||||||
|
FROM ${treeTable} as TreePath
|
||||||
|
LEFT JOIN ${this.uiSchemasTableName} as "SchemaTable" ON "SchemaTable"."x-uid" = TreePath.descendant
|
||||||
|
LEFT JOIN ${treeTable} as NodeInfo ON NodeInfo.descendant = "SchemaTable"."x-uid" and NodeInfo.descendant = NodeInfo.ancestor and NodeInfo.depth = 0
|
||||||
|
LEFT JOIN ${treeTable} as ParentPath ON (ParentPath.descendant = "SchemaTable"."x-uid" AND ParentPath.depth = 1)
|
||||||
|
WHERE TreePath.ancestor = :ancestor ${options?.includeAsyncNode ? '' : 'AND (NodeInfo.async != true )'}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const nodes = await db.sequelize.query(this.sqlAdapter(rawSql), {
|
||||||
|
replacements: {
|
||||||
|
ancestor: uid,
|
||||||
|
},
|
||||||
|
transaction: options?.transaction,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (nodes[0].length == 0) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.nodesToSchema(nodes[0], uid);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ignoreSchemaProperties(schemaProperties) {
|
||||||
|
return lodash.omit(schemaProperties, nodeKeys);
|
||||||
|
}
|
||||||
|
|
||||||
|
private breakOnMatched(schemaInstance, breakRemoveOn: BreakRemoveOnType): boolean {
|
||||||
|
if (!breakRemoveOn) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const key of Object.keys(breakRemoveOn)) {
|
||||||
|
const instanceValue = schemaInstance.get(key);
|
||||||
|
const breakRemoveOnValue = breakRemoveOn[key];
|
||||||
|
if (instanceValue !== breakRemoveOnValue) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async schemaExists(schema: any, options?: Transactionable): Promise<boolean> {
|
||||||
|
if (lodash.isObject(schema) && !schema['x-uid']) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { transaction } = options;
|
||||||
|
const result = await this.database.sequelize.query(
|
||||||
|
this.sqlAdapter(`select "x-uid" from ${this.uiSchemasTableName} where "x-uid" = :uid`),
|
||||||
|
{
|
||||||
|
type: 'SELECT',
|
||||||
|
replacements: {
|
||||||
|
uid: lodash.isString(schema) ? schema : schema['x-uid'],
|
||||||
|
},
|
||||||
|
transaction,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return result.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private regenerateUid(s: any) {
|
||||||
|
s['x-uid'] = uid();
|
||||||
|
Object.keys(s.properties || {}).forEach((key) => {
|
||||||
|
this.regenerateUid(s.properties[key]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async insertSchemaRecord(name, uid, schema, transaction) {
|
||||||
|
const serverHooks = schema['x-server-hooks'] || [];
|
||||||
|
|
||||||
|
const node = await this.create({
|
||||||
|
values: {
|
||||||
|
name,
|
||||||
|
['x-uid']: uid,
|
||||||
|
schema,
|
||||||
|
serverHooks,
|
||||||
|
},
|
||||||
|
transaction,
|
||||||
|
context: {
|
||||||
|
disableInsertHook: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
private prepareSingleNodeForInsert(schema: SchemaNode) {
|
||||||
|
const uid = schema['x-uid'];
|
||||||
|
const name = schema['name'];
|
||||||
|
const async = lodash.get(schema, 'x-async', false);
|
||||||
|
const childOptions = schema['childOptions'];
|
||||||
|
|
||||||
|
delete schema['x-uid'];
|
||||||
|
delete schema['x-async'];
|
||||||
|
delete schema['name'];
|
||||||
|
delete schema['childOptions'];
|
||||||
|
|
||||||
|
return { uid, name, async, childOptions };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default UiSchemaRepository;
|
export default UiSchemaRepository;
|
||||||
|
39
yarn.lock
39
yarn.lock
@ -5805,6 +5805,11 @@
|
|||||||
version "7946.0.10"
|
version "7946.0.10"
|
||||||
resolved "https://registry.npmmirror.com/@types/geojson/-/geojson-7946.0.10.tgz#6dfbf5ea17142f7f9a043809f1cd4c448cb68249"
|
resolved "https://registry.npmmirror.com/@types/geojson/-/geojson-7946.0.10.tgz#6dfbf5ea17142f7f9a043809f1cd4c448cb68249"
|
||||||
|
|
||||||
|
"@types/geojson@^7946.0.8":
|
||||||
|
version "7946.0.13"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.13.tgz#e6e77ea9ecf36564980a861e24e62a095988775e"
|
||||||
|
integrity sha512-bmrNrgKMOhM3WsafmbGmC+6dsF2Z308vLFsQ3a/bT8X8Sv5clVYpPars/UPq+sAaJP+5OoLAYgwbkS5QEJdLUQ==
|
||||||
|
|
||||||
"@types/glob-stream@*":
|
"@types/glob-stream@*":
|
||||||
version "8.0.0"
|
version "8.0.0"
|
||||||
resolved "https://registry.npmmirror.com/@types/glob-stream/-/glob-stream-8.0.0.tgz#ffa679e43d896de883ffac408a32a78ca123db33"
|
resolved "https://registry.npmmirror.com/@types/glob-stream/-/glob-stream-8.0.0.tgz#ffa679e43d896de883ffac408a32a78ca123db33"
|
||||||
@ -6073,7 +6078,7 @@
|
|||||||
version "14.18.53"
|
version "14.18.53"
|
||||||
resolved "https://registry.npmmirror.com/@types/node/-/node-14.18.53.tgz#42855629b8773535ab868238718745bf56c56219"
|
resolved "https://registry.npmmirror.com/@types/node/-/node-14.18.53.tgz#42855629b8773535ab868238718745bf56c56219"
|
||||||
|
|
||||||
"@types/node@^17.0.5":
|
"@types/node@^17.0.10", "@types/node@^17.0.5":
|
||||||
version "17.0.45"
|
version "17.0.45"
|
||||||
resolved "https://registry.npmmirror.com/@types/node/-/node-17.0.45.tgz#2c0fafd78705e7a18b7906b5201a522719dc5190"
|
resolved "https://registry.npmmirror.com/@types/node/-/node-17.0.45.tgz#2c0fafd78705e7a18b7906b5201a522719dc5190"
|
||||||
|
|
||||||
@ -15945,6 +15950,11 @@ long@^4.0.0:
|
|||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://registry.npmmirror.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28"
|
resolved "https://registry.npmmirror.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28"
|
||||||
|
|
||||||
|
long@^5.2.0:
|
||||||
|
version "5.2.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/long/-/long-5.2.3.tgz#a3ba97f3877cf1d778eccbcb048525ebb77499e1"
|
||||||
|
integrity sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==
|
||||||
|
|
||||||
longest-streak@^3.0.0:
|
longest-streak@^3.0.0:
|
||||||
version "3.1.0"
|
version "3.1.0"
|
||||||
resolved "https://registry.npmmirror.com/longest-streak/-/longest-streak-3.1.0.tgz#62fa67cd958742a1574af9f39866364102d90cd4"
|
resolved "https://registry.npmmirror.com/longest-streak/-/longest-streak-3.1.0.tgz#62fa67cd958742a1574af9f39866364102d90cd4"
|
||||||
@ -16144,6 +16154,19 @@ map-visit@^1.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
object-visit "^1.0.0"
|
object-visit "^1.0.0"
|
||||||
|
|
||||||
|
mariadb@^2.5.6:
|
||||||
|
version "2.5.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/mariadb/-/mariadb-2.5.6.tgz#7314e9287cdba212831ebf16ef3b34dc6a1f0f06"
|
||||||
|
integrity sha512-zBx7loYY5GzLl8Y6AKxGXfY9DUYIIdGrmEORPOK9FEu0pg5ZLBKCGJuucHwKADxTBxKY7eM4rxndqxRcnMZKIw==
|
||||||
|
dependencies:
|
||||||
|
"@types/geojson" "^7946.0.8"
|
||||||
|
"@types/node" "^17.0.10"
|
||||||
|
denque "^2.0.1"
|
||||||
|
iconv-lite "^0.6.3"
|
||||||
|
long "^5.2.0"
|
||||||
|
moment-timezone "^0.5.34"
|
||||||
|
please-upgrade-node "^3.2.0"
|
||||||
|
|
||||||
markdown-it-highlightjs@3.3.1:
|
markdown-it-highlightjs@3.3.1:
|
||||||
version "3.3.1"
|
version "3.3.1"
|
||||||
resolved "https://registry.npmmirror.com/markdown-it-highlightjs/-/markdown-it-highlightjs-3.3.1.tgz#38403610487292b8a1ae2d1acc7bb66e4ede6be8"
|
resolved "https://registry.npmmirror.com/markdown-it-highlightjs/-/markdown-it-highlightjs-3.3.1.tgz#38403610487292b8a1ae2d1acc7bb66e4ede6be8"
|
||||||
@ -17028,7 +17051,7 @@ module-details-from-path@^1.0.3:
|
|||||||
version "1.0.3"
|
version "1.0.3"
|
||||||
resolved "https://registry.npmmirror.com/module-details-from-path/-/module-details-from-path-1.0.3.tgz#114c949673e2a8a35e9d35788527aa37b679da2b"
|
resolved "https://registry.npmmirror.com/module-details-from-path/-/module-details-from-path-1.0.3.tgz#114c949673e2a8a35e9d35788527aa37b679da2b"
|
||||||
|
|
||||||
moment-timezone@^0.5.40, moment-timezone@^0.5.43:
|
moment-timezone@^0.5.34, moment-timezone@^0.5.40, moment-timezone@^0.5.43:
|
||||||
version "0.5.43"
|
version "0.5.43"
|
||||||
resolved "https://registry.npmmirror.com/moment-timezone/-/moment-timezone-0.5.43.tgz#3dd7f3d0c67f78c23cd1906b9b2137a09b3c4790"
|
resolved "https://registry.npmmirror.com/moment-timezone/-/moment-timezone-0.5.43.tgz#3dd7f3d0c67f78c23cd1906b9b2137a09b3c4790"
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -18653,6 +18676,13 @@ playwright-core@1.37.1:
|
|||||||
resolved "https://registry.npmmirror.com/playwright-core/-/playwright-core-1.37.1.tgz#cb517d52e2e8cb4fa71957639f1cd105d1683126"
|
resolved "https://registry.npmmirror.com/playwright-core/-/playwright-core-1.37.1.tgz#cb517d52e2e8cb4fa71957639f1cd105d1683126"
|
||||||
integrity sha512-17EuQxlSIYCmEMwzMqusJ2ztDgJePjrbttaefgdsiqeLWidjYz9BxXaTaZWxH1J95SHGk6tjE+dwgWILJoUZfA==
|
integrity sha512-17EuQxlSIYCmEMwzMqusJ2ztDgJePjrbttaefgdsiqeLWidjYz9BxXaTaZWxH1J95SHGk6tjE+dwgWILJoUZfA==
|
||||||
|
|
||||||
|
please-upgrade-node@^3.2.0:
|
||||||
|
version "3.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz#aeddd3f994c933e4ad98b99d9a556efa0e2fe942"
|
||||||
|
integrity sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==
|
||||||
|
dependencies:
|
||||||
|
semver-compare "^1.0.0"
|
||||||
|
|
||||||
plugin-error@^1.0.1:
|
plugin-error@^1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.npmmirror.com/plugin-error/-/plugin-error-1.0.1.tgz#77016bd8919d0ac377fdcdd0322328953ca5781c"
|
resolved "https://registry.npmmirror.com/plugin-error/-/plugin-error-1.0.1.tgz#77016bd8919d0ac377fdcdd0322328953ca5781c"
|
||||||
@ -20993,6 +21023,11 @@ select-hose@^2.0.0:
|
|||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.npmmirror.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca"
|
resolved "https://registry.npmmirror.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca"
|
||||||
|
|
||||||
|
semver-compare@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc"
|
||||||
|
integrity sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==
|
||||||
|
|
||||||
semver-diff@^2.0.0:
|
semver-diff@^2.0.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.npmmirror.com/semver-diff/-/semver-diff-2.1.0.tgz#4bbb8437c8d37e4b0cf1a68fd726ec6d645d6d36"
|
resolved "https://registry.npmmirror.com/semver-diff/-/semver-diff-2.1.0.tgz#4bbb8437c8d37e4b0cf1a68fd726ec6d645d6d36"
|
||||||
|
Loading…
Reference in New Issue
Block a user