UNPKG

@xuda.io/xuda-dbs-plugin-couchdb

Version:

Xuda Database Socket for CouchDB

409 lines (347 loc) 12.2 kB
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); } };