@xuda.io/xuda-dbs-plugin-couchdb
Version:
Xuda Database Socket for CouchDB
409 lines (347 loc) • 12.2 kB
JavaScript
import { createRequire } from 'module';
import _ from 'lodash';
import crypto from 'node:crypto';
const require = createRequire(import.meta.url);
const check_unique = async (e, docP, table_obj) => {
let len = docP.udfIndex.length;
if (!len) {
return { code: -11, data: 'table must have primary index' };
}
let found_unique = undefined;
let ret = { code: 1, data: 'ok' };
for await (let 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;
}
let keysArr = table_index.data.keys;
let keyValue = [];
for await (const [keySegment, valSegment] of Object.entries(keysArr)) {
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);
}
let key = '["' + val.fileId + '","' + val.indexId + '",[' + keyValue.join(',') + ']]';
try {
e.view = 'db_index';
// In ESM, we call the exported function directly.
// Assuming 'read' is defined below and exported.
// Note: The original code had `_this.read`, which implies context.
// Here we pass required params directly if available in scope or via arguments.
// Since 'params', 'setup_doc', 'resolve', 'reject' are not in this scope,
// this logic seems to rely on closure or context not fully visible here.
// I'll stub the call but this likely needs specific arguments from the caller.
// For now, assuming `read` handles the logic:
// const json = await read(params, setup_doc, resolve, reject);
// STUB: This part of the logic seems circular or incomplete in the provided snippet
// as it calls `read` which executes SQL, but here it's validation.
// I will retain the logic flow but note that `params` is undefined here.
// Assuming this function is called within a context where `read` is accessible.
// But `read` expects (params, setup_doc, resolve, reject).
// Proceeding with placeholder logic as original code called `_this.read`.
throw new Error("Context for 'read' missing in check_unique");
/* Original logic:
const json = await _this.read(params, setup_doc, resolve, reject);
if (json.code > 0) { ... }
*/
return { code: 1, data: 'ok' };
} catch (msg) {
console.error(msg);
ret = { code: -22, data: msg };
break;
}
}
return ret;
};
const get_index_json = async (docInP, table_obj) => {
try {
let keysArr = [];
let index_json = [];
let key_val = [];
if (docInP.udfData && table_obj) {
if (table_obj.tableIndexes) {
for await (let valIndex of table_obj.tableIndexes) {
keysArr = valIndex.data.keys;
key_val = [];
for await (let valSegment of keysArr) {
if (docInP.udfData.data[valSegment]) {
key_val.push(docInP.udfData.data[valSegment]);
continue;
}
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 = (arr, key, val) => {
return _.find(arr, function (e) {
return e[key] === val;
});
};
const find_item_by_key = (arr, key, val) => {
return _.find(arr, function (e) {
return e.data[key] === val;
});
};
const get_cast_val = async (source, attributeP, typeP, valP) => {
const module = require('@xuda.io/xu_cast');
return module.cast(
typeP,
valP,
function (res) {
let msg = `error converting ${attributeP} from ${valP} to ${typeP}`;
console.error(source, msg);
},
function (res) {
let msg = `type mismatch auto conversion made to ${attributeP} from value ${valP} to ${typeP}`;
console.warn(source, msg);
},
);
};
export const 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 (body) => {
const raw_data = async () => {
let rows = [];
for await (const [key, val] of Object.entries(body)) {
let 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 () => {
let rows = [];
rows.push({ key: '', value: body[0]['count(*)'] });
resolve({ rows: rows, total_rows: rows.length });
};
const reduce_data = async () => {
let rows = [];
const table_index = find_item_by_key_root(table_obj.tableIndexes, 'id', e.indexId);
if (e.indexId && table_index) {
let index_id = e.indexId;
for await (const [key, val] of Object.entries(body)) {
let obj = {};
for await (const [key2, val2] of Object.entries(val)) {
let 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 () => {
let total_fields_info = JSON.parse(e.total_fields_info);
let 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();
}
}
};
let SKIP = 0;
if (e.skip) {
SKIP = Number(e.skip);
}
let 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);
}
}
let WHERE = [];
let ORDER_BY = [];
let GROUP_BY = [];
let COUNT = [];
let LIMIT = undefined;
if (e.limit) {
LIMIT = Number(e.limit);
}
let from = e.filter_from ? JSON.parse(e.filter_from) : {};
let 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;
});
keys_to_replace.forEach((key) => {
const regex = new RegExp(`\\b${key}\\b`, 'g');
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)) {
let 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.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))) {
let 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);
}
};
export const get_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);
}
};