@xuda.io/xuda-dbs-plugin-mysql
Version:
Xuda Database Socket for MySql
506 lines (445 loc) • 13.4 kB
JavaScript
const _ = require("lodash");
// const dot = require("dot-object");
const crypto = require("node:crypto");
const check_unique = async function (e, docP, table_obj) {
var len = docP.udfIndex.length;
if (!len) {
return { code: -11, data: "table must have primary index" };
}
var found_unique = undefined;
var ret = { code: 1, data: "ok" };
for await (var val of docP.udfIndex) {
if (!table_obj.tableIndexes) {
ret = {
code: -14,
data: "table definition missing for: " + val.fileId,
};
break;
}
const table_index = find_item_by_key_root(
table_obj.tableIndexes,
"id",
val.indexId
);
if (!table_index) {
ret = {
code: -13,
data: "keys definition missing for Table: " + val.fileId,
};
break;
}
if (!table_index.data.unique) {
continue;
}
if (!table_index?.data?.keys || _.isEmpty(table_index.data.keys)) {
ret = {
code: -14,
data: "keys definition missing for index: " + val.indexId,
};
break;
}
var keysArr = table_index.data.keys;
var keyValue = [];
for await (const [keySegment, valSegment] of Object.entries(keysArr)) {
// run on key segment
let _fieldType = find_item_by_key(
table_obj.tableFields,
"field_id",
valSegment
).props.fieldType;
let _val = await get_cast_val(
"check_unique",
valSegment,
_fieldType,
val.keyValue[Number(keySegment)]
);
keyValue.push(_val);
}
var key =
'["' +
val.fileId +
'","' +
val.indexId +
'",[' +
keyValue.join(",") +
"]]";
try {
e.view = "db_index";
const json = await _this.read(params, setup_doc, resolve, reject);
// var data;
if (json.code > 0) {
var data = json.data;
if (data?.rows?.length) {
for await (var val of data.rows) {
if (val.id !== docP._id) {
found_unique = true;
}
}
if (found_unique) {
throw "error checking unique:" + key;
}
}
}
// throw json.data;
return { code: 1, data: "ok" };
} catch (msg) {
console.error(msg);
ret = { code: -22, data: msg };
break;
}
}
return ret;
};
const get_index_json = async function (docInP, table_obj) {
try {
var keysArr = [];
var index_json = [];
var key_val = [];
if (docInP.udfData && table_obj) {
// check for udf data array
if (table_obj.tableIndexes) {
// console.log(udfDicIndexT.rows);
for await (var valIndex of table_obj.tableIndexes) {
// run on index rows
keysArr = valIndex.data.keys; // create array from keys string segment
key_val = [];
for await (var valSegment of keysArr) {
// run on key segment
// find keys values
if (docInP.udfData.data[valSegment]) {
key_val.push(docInP.udfData.data[valSegment]);
continue;
}
// put the value
// debugger;
const field_obj = find_item_by_key(
table_obj.tableFields,
"field_id",
valSegment
);
if (!field_obj) {
throw "field not found in key: " + valSegment;
}
let _val = await get_cast_val(
"get_index_json",
field_obj.id,
field_obj.props.fieldType,
null
);
key_val.push(_val);
}
index_json.push({
fileId: docInP.udfData.udffileid,
indexId: valIndex.id,
keySegments: keysArr,
keyValue: key_val,
});
}
// );
}
}
return { code: 1, data: index_json };
} catch (msg) {
return { code: -1, data: msg };
}
};
const find_item_by_key_root = function (arr, key, val) {
return _.find(arr, function (e) {
return e[key] === val;
});
};
const find_item_by_key = function (arr, key, val) {
return _.find(arr, function (e) {
return e.data[key] === val;
});
};
const get_cast_val = async function (source, attributeP, typeP, valP) {
const module = require("@xuda.io/xu_cast");
return module.cast(
typeP,
valP,
function (res) {
var msg = `error converting ${attributeP} from ${valP} to ${typeP}`;
console.error(source, msg);
},
function (res) {
var msg = `type mismatch auto conversion made to ${attributeP} from value ${valP} to ${typeP}`;
console.warn(source, msg);
}
);
};
exports.read = async (params, setup_doc, resolve, reject) => {
const e = params.e;
const db = params.db;
const app_id_reference = params.app_id_reference;
const table_obj = params.table_obj;
const done = async function (body) {
const raw_data = async function () {
var rows = [];
for await (const [key, val] of Object.entries(body)) {
var data = {};
for await (const val2 of table_obj.tableFields) {
if (val2.props.tableExternalFieldId) {
data[val2.data.field_id] = _.get(
val,
val2.props.tableExternalFieldId
);
} else {
data[val2.data.field_id] = val[val2.data.field_id];
}
if (typeof data[val2.data.field_id] === "undefined") {
let _val = await get_cast_val(
"get view",
val2.data.field_id,
val2.props.fieldType,
null
);
data[val2.data.field_id] = _val;
}
}
rows.push({
id: key,
value: data,
});
}
resolve({ rows, total_rows: rows.length });
};
const count_data = async function () {
var rows = [];
var keys_obj = {};
rows.push({ key: "", value: body[0]["count(*)"] });
resolve({ rows: rows, total_rows: rows.length });
};
const reduce_data = async function () {
var rows = [];
var keys_obj = {};
const table_index = find_item_by_key_root(
table_obj.tableIndexes,
"id",
e.indexId
);
if (e.indexId && table_index) {
var index_id = e.indexId;
for await (const [key, val] of Object.entries(body)) {
// rows.push({
// key: [e.key, index_id, [val[GROUP_BY[0]]]],
// value: val[`count(${GROUP_BY[0]})`],
// });
let obj = {};
for await (const [key2, val2] of Object.entries(val)) {
field_id = key2;
if (field_id.includes("COUNT")) {
field_id = "REDUCE_VALUE";
}
obj[field_id] = val2;
}
rows.push({ id: crypto.randomUUID(), value: obj });
}
resolve({ rows, total_rows: rows.length });
}
};
const totals = async function () {
var total_fields_info = JSON.parse(e.total_fields_info);
var totals_obj = {};
for (const [key, val] of Object.entries(total_fields_info)) {
totals_obj[val.field_id] = 0;
}
console.log(body);
for (const [row_key, row_data] of Object.entries(body)) {
for (const [key, val] of Object.entries(row_data)) {
totals_obj[key] = val;
}
}
resolve(totals_obj);
};
if (e.count === "true" || e.count === true) {
await count_data();
} else if (e.reduce === "true") {
await reduce_data();
} else {
if (e.total_fields_info) {
await totals();
} else {
await raw_data();
}
}
};
var SKIP = 0;
if (e.skip) {
SKIP = Number(e.skip);
}
var fields = [];
for (const [key, val] of Object.entries(table_obj.tableFields)) {
if (val.props.tableExternalFieldId) {
fields.push(val.props.tableExternalFieldId);
} else {
fields.push(val.data.field_id);
}
}
var WHERE = [];
var ORDER_BY = [];
var GROUP_BY = [];
var COUNT = [];
var LIMIT = undefined;
if (e.limit) {
LIMIT = Number(e.limit);
}
var from = e.filter_from ? JSON.parse(e.filter_from) : {};
var to = e.filter_to ? JSON.parse(e.filter_to) : {};
for (const [key, val] of Object.entries(from)) {
if (e.reduce === "true") {
COUNT.push(`${key},COUNT(${key})`);
GROUP_BY.push(key);
}
ORDER_BY.push(key);
const _tableFieldsObj = find_item_by_key(
table_obj.tableFields,
"field_id",
key
);
if (val === to[key]) {
if (_tableFieldsObj.props.fieldType === "string") {
WHERE.push(`${key} = '${val}'`);
} else {
WHERE.push(`${key} = ${val}`);
}
} else {
if (_tableFieldsObj.props.fieldType === "string") {
WHERE.push(`${key} BETWEEN '${val}' AND '${to[key]}'`);
} else {
WHERE.push(`${key} BETWEEN ${val} AND ${to[key]}`);
}
}
}
function replaceKeysInSQLQuery(sql_query) {
const keys_to_replace = table_obj.tableFields.map((e) => {
return e.id;
});
// Iterate over each key in the keys_to_replace array
keys_to_replace.forEach((key) => {
// Create a regex to find the key in the SQL query, considering word boundaries to avoid partial replacements
const regex = new RegExp(`\\b${key}\\b`, "g");
// Replace the key with the desired string
sql_query = sql_query.replace(regex, `udfData.data.${key}`);
});
return sql_query;
}
if (e.filterModelSql) {
WHERE.push(`AND ` + replaceKeysInSQLQuery(e.filterModelSql));
}
if (e.filterModelUserSql) {
WHERE.push(`AND ` + replaceKeysInSQLQuery(e.filterModelUserSql));
}
if (e?.sortModel?.length) {
let sortModel = JSON.parse(e.sortModel);
for (const [key, val] of Object.entries(sortModel)) {
var field_name = val;
const _tableFieldsObj = find_item_by_key(
table_obj.tableFields,
"field_id",
val
);
if (_tableFieldsObj.props.tableExternalFieldId) {
field_name = _tableFieldsObj.props.tableExternalFieldId;
}
sortModel[Number(key)] = field_name;
if (e.sort_dir === "des") {
e.desc = "true";
}
}
ORDER_BY = sortModel;
}
// if (e.grid_filter_info) {
// for (const [key, val] of Object.entries(JSON.parse(e.grid_filter_info))) {
// var field_name = val.field;
// var value = val.value;
// const _tableFieldsObj = find_item_by_key(
// table_obj.tableFields,
// "field_id",
// val.field
// );
// if (_tableFieldsObj.props.tableExternalFieldId) {
// field_name = _tableFieldsObj.props.tableExternalFieldId;
// }
// if (_tableFieldsObj.props.fieldType === "string") {
// value =
// val.type === "text_filter_strict"
// ? val.value + "%"
// : "%" + val.value + "%";
// WHERE.push(` ${field_name} LIKE '${value}'`);
// } else {
// WHERE.push(`${field_name} = ${value}`);
// }
// }
// }
if (e.count === "true" || e.count === true) {
COUNT.push("count(*)");
}
if (e.total_fields_info) {
COUNT = [];
for (const [key, val] of Object.entries(JSON.parse(e.total_fields_info))) {
var field_name = val.field_id;
const _tableFieldsObj = find_item_by_key(
table_obj.tableFields,
"field_id",
val.field_id
);
if (_tableFieldsObj.props.tableExternalFieldId) {
field_name = _tableFieldsObj.props.tableExternalFieldId;
}
if (val.isNumeric) {
COUNT.push(`SUM(${field_name}) as ${val.field_id}`);
} else {
COUNT.push(`COUNT(DISTINCT ${field_name}) as ${val.field_id}`);
}
}
}
const sql = `SELECT ${
COUNT.length ? `${COUNT.join(",")}` : fields.join(",")
} FROM ${table_obj.properties.menuTitle} ${
WHERE.length ? "WHERE " + WHERE.join(" AND ") : ""
} ${GROUP_BY.length ? "GROUP BY " + GROUP_BY.join(",") : ""} ${
ORDER_BY.length
? "ORDER BY " +
ORDER_BY.join(",") +
" " +
(e.desc === "true" ? "DESC" : "ASC")
: ""
} ${LIMIT ? ` LIMIT ${LIMIT}` : ""} ${SKIP ? ` OFFSET ${SKIP}` : ""}`;
console.log(sql);
try {
db.query(sql, async function (error, results, fields) {
if (error) throw error;
await done(results);
});
} catch (err) {
reject(err);
}
};
exports.test_connection = async (params, setup_doc, resolve, reject) => {
try {
if (!setup_doc.con_host) throw "con_host missing";
if (!setup_doc.con_user) throw "con_user missing";
if (!setup_doc.con_password) throw "con_password missing";
if (!setup_doc.con_database) throw "con_database missing";
if (!setup_doc.con_port) throw "con_port missing";
const mysql = require(`mysql-await`);
const connection = mysql.createConnection({
host: setup_doc.con_host,
user: setup_doc.con_user,
password: setup_doc.con_password,
database: setup_doc.con_database,
port: setup_doc.con_port,
});
connection.on(`error`, (err) => {
throw err;
});
let result = await connection.awaitQuery("SELECT 1 AS test");
if (params.test_only) {
connection.awaitEnd();
}
resolve(connection);
} catch (err) {
return reject(err);
}
};