oak-db/lib/MySQL/store.js

431 lines
19 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.MysqlStore = void 0;
const tslib_1 = require("tslib");
const CascadeStore_1 = require("oak-domain/lib/store/CascadeStore");
const connector_1 = require("./connector");
const translator_1 = require("./translator");
const lodash_1 = require("lodash");
const assert_1 = tslib_1.__importDefault(require("assert"));
const relation_1 = require("oak-domain/lib/store/relation");
function convertGeoTextToObject(geoText) {
if (geoText.startsWith('POINT')) {
const coord = geoText.match((/(\d|\.)+(?=\)|\s)/g));
(0, assert_1.default)(coord.length === 2);
return {
type: 'point',
coordinate: coord.map(ele => parseFloat(ele)),
};
}
else {
throw new Error('only support Point now');
}
}
class MysqlStore extends CascadeStore_1.CascadeStore {
countAbjointRow(entity, selection, context, option) {
throw new Error('MySQL store不支持同步取数据不应该跑到这儿');
}
aggregateAbjointRowSync(entity, aggregation, context, option) {
throw new Error('MySQL store不支持同步取数据不应该跑到这儿');
}
selectAbjointRow(entity, selection, context, option) {
throw new Error('MySQL store不支持同步取数据不应该跑到这儿');
}
updateAbjointRow(entity, operation, context, option) {
throw new Error('MySQL store不支持同步更新数据不应该跑到这儿');
}
async exec(script, txnId) {
await this.connector.exec(script, txnId);
}
connector;
translator;
constructor(storageSchema, configuration) {
super(storageSchema);
this.connector = new connector_1.MySqlConnector(configuration);
this.translator = new translator_1.MySqlTranslator(storageSchema);
}
checkRelationAsync(entity, operation, context) {
throw new Error('Method not implemented.');
}
async aggregateAbjointRowAsync(entity, aggregation, context, option) {
const sql = this.translator.translateAggregate(entity, aggregation, option);
const result = await this.connector.exec(sql, context.getCurrentTxnId());
return this.formResult(entity, result[0]);
}
aggregate(entity, aggregation, context, option) {
return this.aggregateAsync(entity, aggregation, context, option);
}
supportManyToOneJoin() {
return true;
}
supportMultipleCreate() {
return true;
}
formResult(entity, result) {
const schema = this.getSchema();
/* function resolveObject(r: Record<string, any>, path: string, value: any) {
const i = path.indexOf(".");
const bs = path.indexOf('[');
const be = path.indexOf(']');
if (i === -1 && bs === -1) {
r[i] = value;
}
else if (i === -1) {
}
else if (bs === -1) {
const attrHead = path.slice(0, i);
const attrTail = path.slice(i + 1);
if (!r[attrHead]) {
r[attrHead] = {};
}
resolveObject(r[attrHead], attrTail, value);
}
} */
function resolveAttribute(entity2, r, attr, value) {
const { attributes, view } = schema[entity2];
if (!view) {
const i = attr.indexOf(".");
if (i !== -1) {
const attrHead = attr.slice(0, i);
const attrTail = attr.slice(i + 1);
const rel = (0, relation_1.judgeRelation)(schema, entity2, attrHead);
if (rel === 1) {
(0, lodash_1.set)(r, attr, value);
}
else {
if (!r[attrHead]) {
r[attrHead] = {};
}
if (rel === 0) {
resolveAttribute(entity2, r[attrHead], attrTail, value);
}
else if (rel === 2) {
resolveAttribute(attrHead, r[attrHead], attrTail, value);
}
else {
(0, assert_1.default)(typeof rel === 'string');
resolveAttribute(rel, r[attrHead], attrTail, value);
}
}
}
else if (attributes[attr]) {
const { type } = attributes[attr];
switch (type) {
case 'date':
case 'time': {
if (value instanceof Date) {
r[attr] = value.valueOf();
}
else {
r[attr] = value;
}
break;
}
case 'geometry': {
if (typeof value === 'string') {
r[attr] = convertGeoTextToObject(value);
}
else {
r[attr] = value;
}
break;
}
case 'object':
case 'array': {
if (typeof value === 'string') {
r[attr] = JSON.parse(value.replace(/[\r]/g, '\\r').replace(/[\n]/g, '\\n'));
}
else {
r[attr] = value;
}
break;
}
case 'function': {
if (typeof value === 'string') {
// 函数的执行环境需要的参数只有创建函数者知悉只能由上层再创建Function
r[attr] = `return ${Buffer.from(value, 'base64').toString()}`;
}
else {
r[attr] = value;
}
break;
}
case 'bool':
case 'boolean': {
if (value === 0) {
r[attr] = false;
}
else if (value === 1) {
r[attr] = true;
}
else {
r[attr] = value;
}
break;
}
case 'decimal': {
// mysql内部取回decimal是字符串
if (typeof value === 'string') {
r[attr] = parseFloat(value);
}
else {
(0, assert_1.default)(value === null || typeof value === 'number');
r[attr] = value;
}
break;
}
default: {
r[attr] = value;
}
}
}
else {
r[attr] = value;
}
}
else {
(0, lodash_1.assign)(r, {
[attr]: value,
});
}
}
function removeNullObjects(r, e) {
// assert(r.id && typeof r.id === 'string', `对象${<string>e}取数据时发现id为非法值${r.id},rowId是${r.id}`)
for (let attr in r) {
const rel = (0, relation_1.judgeRelation)(schema, e, attr);
if (rel === 2) {
// 边界如果是toModi的对象这里的外键确实有可能为空
// assert(schema[e].toModi || r.entity !== attr || r.entityId === r[attr].id, `对象${<string>e}取数据时发现entityId与连接的对象的主键不一致rowId是${r.id}其entityId值为${r.entityId},连接的对象的主键为${r[attr].id}`);
if (r[attr].id === null) {
(0, assert_1.default)(schema[e].toModi || r.entity !== attr);
delete r[attr];
continue;
}
// assert(r.entity === attr, `对象${<string>e}取数据时发现entity值与连接的外键对象不一致rowId是${r.id}其entity值为${r.entity},连接的对象为${attr}`);
removeNullObjects(r[attr], attr);
}
else if (typeof rel === 'string') {
// 边界如果是toModi的对象这里的外键确实有可能为空
// assert(schema[e].toModi || r[`${attr}Id`] === r[attr].id, `对象${<string>e}取数据时发现其外键与连接的对象的主键不一致rowId是${r.id},其${attr}Id值为${r[`${attr}Id`]},连接的对象的主键为${r[attr].id}`);
if (r[attr].id === null) {
(0, assert_1.default)(schema[e].toModi || r[`${attr}Id`] === null, `对象${String(e)}取数据时发现其外键找不到目标对象rowId是${r.id},其外键${attr}Id值为${r[`${attr}Id`]}`);
delete r[attr];
continue;
}
removeNullObjects(r[attr], rel);
}
}
}
function formSingleRow(r) {
let result2 = {};
for (let attr in r) {
const value = r[attr];
resolveAttribute(entity, result2, attr, value);
}
removeNullObjects(result2, entity);
return result2;
}
if (result instanceof Array) {
return result.map(r => formSingleRow(r));
}
return formSingleRow(result);
}
async selectAbjointRowAsync(entity, selection, context, option) {
const sql = this.translator.translateSelect(entity, selection, option);
const result = await this.connector.exec(sql, context.getCurrentTxnId());
return this.formResult(entity, result[0]);
}
async updateAbjointRowAsync(entity, operation, context, option) {
const { translator, connector } = this;
const { action } = operation;
const txn = context.getCurrentTxnId();
switch (action) {
case 'create': {
const { data } = operation;
const sql = translator.translateInsert(entity, data instanceof Array ? data : [data]);
const result = await connector.exec(sql, txn);
return result[0].affectedRows;
}
case 'remove': {
const sql = translator.translateRemove(entity, operation, option);
const result = await connector.exec(sql, txn);
// todo 这里对sorter和indexfrom/count的支持不完整
return result[0].changedRows;
}
default: {
(0, assert_1.default)(!['select', 'download', 'stat'].includes(action));
const sql = translator.translateUpdate(entity, operation, option);
const result = await connector.exec(sql, txn);
// todo 这里对sorter和indexfrom/count的支持不完整
return result[0].changedRows;
}
}
}
async operate(entity, operation, context, option) {
const { action } = operation;
(0, assert_1.default)(!['select', 'download', 'stat'].includes(action), '现在不支持使用select operation');
return await super.operateAsync(entity, operation, context, option);
}
async select(entity, selection, context, option) {
const result = await super.selectAsync(entity, selection, context, option);
return result;
}
async countAbjointRowAsync(entity, selection, context, option) {
const sql = this.translator.translateCount(entity, selection, option);
const result = await this.connector.exec(sql, context.getCurrentTxnId());
return result[0][0].cnt;
}
async count(entity, selection, context, option) {
return this.countAsync(entity, selection, context, option);
}
async begin(option) {
const txn = await this.connector.startTransaction(option);
return txn;
}
async commit(txnId) {
await this.connector.commitTransaction(txnId);
}
async rollback(txnId) {
await this.connector.rollbackTransaction(txnId);
}
async connect() {
await this.connector.connect();
}
async disconnect() {
await this.connector.disconnect();
}
async initialize(option) {
const schema = this.getSchema();
for (const entity in schema) {
const sqls = this.translator.translateCreateEntity(entity, option);
for (const sql of sqls) {
await this.connector.exec(sql);
}
}
}
// 从数据库中读取当前schema
readSchema() {
return this.translator.readSchema((sql) => this.connector.exec(sql));
}
/**
* 根据载入的dataSchema和数据库中原来的schema决定如何来upgrade
* 制订出来的plan分为两阶段增加阶段和削减阶段在两个阶段之间由用户来修正数据
*/
async makeUpgradePlan() {
const originSchema = await this.readSchema();
const plan = this.diffSchema(originSchema, this.translator.schema);
return plan;
}
/**
* 比较两个schema的不同这里计算的是new对old的增量
* @param schemaOld
* @param SchemaNew
*/
diffSchema(schemaOld, schemaNew) {
const plan = {
newTables: {},
newIndexes: {},
updatedIndexes: {},
updatedTables: {},
};
for (const table in schemaNew) {
// mysql数据字典不分大小写的
if (schemaOld[table] || schemaOld[table.toLowerCase()]) {
const { attributes, indexes } = schemaOld[table] || schemaOld[table.toLowerCase()];
const { attributes: attributesNew, indexes: indexesNew } = schemaNew[table];
const assignToUpdateTables = (attr, isNew) => {
if (!plan.updatedTables[table]) {
plan.updatedTables[table] = {
attributes: {
[attr]: {
...attributesNew[attr],
isNew,
}
}
};
}
else {
plan.updatedTables[table].attributes[attr] = {
...attributesNew[attr],
isNew,
};
}
};
for (const attr in attributesNew) {
if (attributes[attr]) {
// 因为反向无法复原原来定义的attribute类型这里就比较两次创建的sql是不是一致不是太好的设计。
const sql1 = this.translator.translateAttributeDef(attr, attributesNew[attr]);
const sql2 = this.translator.translateAttributeDef(attr, attributes[attr]);
if (!this.translator.compareSql(sql1, sql2)) {
assignToUpdateTables(attr, false);
}
}
else {
assignToUpdateTables(attr, true);
}
}
if (indexesNew) {
const assignToIndexes = (index, isNew) => {
if (isNew) {
if (plan.newIndexes[table]) {
plan.newIndexes[table].push(index);
}
else {
plan.newIndexes[table] = [index];
}
}
else {
if (plan.updatedIndexes[table]) {
plan.updatedIndexes[table].push(index);
}
else {
plan.updatedIndexes[table] = [index];
}
}
};
const compareConfig = (config1, config2) => {
const unique1 = config1?.unique || false;
const unique2 = config2?.unique || false;
if (unique1 !== unique2) {
return false;
}
const type1 = config1?.type || 'btree';
const type2 = config2?.type || 'btree';
// parser目前无法从mysql中读出来所以不比了
return type1 === type2;
};
for (const index of indexesNew) {
const { name, config, attributes } = index;
const origin = indexes?.find(ele => ele.name === name);
if (origin) {
if (JSON.stringify(attributes) !== JSON.stringify(origin.attributes)) {
// todo这里要细致比较不能用json.stringify
assignToIndexes(index, false);
}
else {
if (!compareConfig(config, origin.config)) {
assignToIndexes(index, false);
}
}
}
else {
assignToIndexes(index, true);
}
}
}
}
else {
plan.newTables[table] = {
attributes: schemaNew[table].attributes,
};
if (schemaNew[table].indexes) {
plan.newIndexes[table] = schemaNew[table].indexes;
}
}
}
return plan;
}
}
exports.MysqlStore = MysqlStore;