tachybase_todo/packages/plugins/@nocobase/plugin-collection-manager/src/server/resourcers/sql.ts
YANG QIA 89635982b4
feat(db): add sql collection (#2419)
* feat(db): add sql collection

* feat: frontend

* perf: issue of select

* fix: sql model

* fix: sql collection schema

* fix: implement sql collection

* fix: dependency

* fix: remove type declaration in actions

* fix: backend test

* chore: remove some ops of block using sql collection

* chore: remove sql collections from Form and Kanban

* feat: add execute button to sql input

* feat(backend): support infer fields by parsing sql

* feat(frontend): support infer interface by parsing sql

* fix: fix update issues and improve

* fix: update issue

* chore: update yarn.lock

* fix: fix T-1548

* fix: fix T-1544

* fix: fix T-1545

* fix: fix T-1549

* fix: test

* fix: fix T-1556

* fix: remove map action diviver

* chore: debug

* chore: remove schema of sql collection

* fix: sql collection schema

* chore: remove debug log & fix T-1555

* fix: fix T-1679

* fix: sql update issue

* fix: sql attribute issue

* fix: bug of star attribute

* fix: test

* fix: test

* fix: reset fields when updating sql collection

* fix(collection-manager): redundant fields after set collection fields

* fix: test

* fix: destory with individuals hook

* chore: save

* chore: test

* fix: fields sync issue

* fix: remove underscored option of sql collection

* chore: mutex in fields.afterDestroy

* fix: test

* chore: yarn.lock

* chore: update collections.setFields

* feat: improve sql input

* fix: fix T-1742 & improve

* chore: fix conflicts

* fix: workspace

* fix: build

* fix: test

* chore: add translations

* fix: reviewed issues

* chore: update yarn.lock

---------

Co-authored-by: ChengLei Shao <chareice@live.com>
2023-09-25 15:03:23 +08:00

97 lines
3.1 KiB
TypeScript

import { Context, Next } from '@nocobase/actions';
import { SQLModel, SqlCollection } from '@nocobase/database';
import { CollectionModel } from '../models';
const updateCollection = async (ctx: Context, transaction: any) => {
const { filterByTk, values } = ctx.action.params;
const repo = ctx.db.getRepository('collections');
const collection: CollectionModel = await repo.findOne({
filter: {
name: filterByTk,
},
transaction,
});
const existFields = await collection.getFields({ transaction });
const deletedFields = existFields.filter((field: any) => !values.fields?.find((f: any) => f.name === field.name));
for (const field of deletedFields) {
await field.destroy({ transaction });
}
const upRes = await repo.update({
filterByTk,
values,
updateAssociationValues: ['fields'],
transaction,
});
return { collection, upRes };
};
export default {
name: 'sqlCollection',
actions: {
execute: async (ctx: Context, next: Next) => {
let {
values: { sql },
} = ctx.action.params;
sql = sql.trim().split(';').shift();
if (!sql) {
ctx.throw(400, ctx.t('SQL is empty'));
}
if (!/^select/i.test(sql) && !/^with([\s\S]+)select([\s\S]+)/i.test(sql)) {
ctx.throw(400, ctx.t('Only select query allowed'));
}
const tmpCollection = new SqlCollection({ name: 'tmp', sql }, { database: ctx.db });
const model = tmpCollection.model as typeof SQLModel;
// The result is for preview only, add limit clause to avoid too many results
const data = await model.findAll({ attributes: ['*'], limit: 5, raw: true });
let fields: {
[field: string]: {
type: string;
source: string;
collection: string;
interface: string;
};
} = {};
try {
fields = model.inferFields();
} catch (err) {
ctx.logger.warn('resource: sql-collection, action: execute, error: ', err);
fields = {};
}
const sources = Array.from(new Set(Object.values(fields).map((field) => field.collection)));
ctx.body = { data, fields, sources };
await next();
},
setFields: async (ctx: Context, next: Next) => {
const transaction = await ctx.app.db.sequelize.transaction();
try {
const {
upRes: [collection],
} = await updateCollection(ctx, transaction);
await collection.loadFields({
transaction,
});
await transaction.commit();
} catch (e) {
await transaction.rollback();
throw e;
}
await next();
},
update: async (ctx: Context, next: Next) => {
const transaction = await ctx.app.db.sequelize.transaction();
try {
const { upRes } = await updateCollection(ctx, transaction);
const [collection] = upRes;
await (collection as CollectionModel).load({ transaction, resetFields: true });
await transaction.commit();
ctx.body = upRes;
} catch (e) {
await transaction.rollback();
throw e;
}
await next();
},
},
};