UNPKG

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

Version:

Xuda Database Socket for CouchDB

575 lines (497 loc) 14.6 kB
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; var limit = 99999; var skip = 0; if (e.limit) { limit = Number(e.limit); } if (e.skip) { skip = JSON.parse(e.skip); } var fields = []; for await (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 data = {}; var from = e.filter_from ? JSON.parse(e.filter_from) : {}; var to = e.filter_to ? JSON.parse(e.filter_to) : {}; var sort = []; for await (const [key, val] of Object.entries(from)) { var field_name = key; const _tableFieldsObj = find_item_by_key( table_obj.tableFields, "field_id", key ); if (_tableFieldsObj.props.tableExternalFieldId) { field_name = _tableFieldsObj.props.tableExternalFieldId; } sort.push(field_name); if (val === to[key]) { data[field_name] = val; } else { data[field_name] = { $gte: val }; } } for await (const [key, val] of Object.entries(to)) { var field_name = key; const _tableFieldsObj = find_item_by_key( table_obj.tableFields, "field_id", key ); if (_tableFieldsObj.props.tableExternalFieldId) { field_name = _tableFieldsObj.props.tableExternalFieldId; } if (val !== from[key]) { data[field_name].$lte = val; } } var selector = { _id: { $gt: null }, }; if (from && !_.isEmpty(from)) { selector = data; } var opt = { selector, limit, skip, fields, }; if (sort.length) { opt.sort = sort; } if (e.sortModel && JSON.parse(e.sortModel).length) { opt.sort = JSON.parse(e.sortModel); for await (const [key, val] of Object.entries(opt.sort)) { var field_name = val; const _tableFieldsObj = find_item_by_key( table_obj.tableFields, "field_id", Number(key) ); if (_tableFieldsObj.props.tableExternalFieldId) { field_name = _tableFieldsObj.props.tableExternalFieldId; } opt.sort[Number(key)] = { [field_name]: e.sort_dir === "des" ? "desc" : "asc", }; } } if (e.grid_filter_info) { for await (const [key, val] of Object.entries( JSON.parse(e.grid_filter_info) )) { var field_name = val.field; var value = (val.type === "text_filter_strict" ? "^" : "") + val.value; const _tableFieldsObj = find_item_by_key( table_obj.tableFields, "field_id", val.field ); if (_tableFieldsObj.props.tableExternalFieldId) { field_name = _tableFieldsObj.props.tableExternalFieldId; } var operator = "$regex"; if (val.type === "numeric_filter") { operator = "$eq"; value = Number(val.value); } opt.selector[field_name] = { [operator]: value, }; } } if (e.total_fields_info) { fields = []; for await (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 ); if (_tableFieldsObj.props.tableExternalFieldId) { field_name = _tableFieldsObj.props.tableExternalFieldId; } fields.push(field_name); } opt.fields = fields; } try { const body = await db.find(opt); const raw_data = async function () { var rows = []; for await (const [key, val] of Object.entries(body.docs)) { // if (key > 50) return; 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: val._id, value: data, // value: { // udfData: { data: data }, // _id: val._id, // _rev: val._rev, // }, }); } // ); resolve({ rows: rows, total_rows: rows.length }); }; const count_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_keys = table_index.data.keys; var index_id = e.indexId; for await (const [key, val] of Object.entries(body.docs)) { var data = {}; for await (const [key2, val2] of Object.entries( 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.props.dEID]; } } var key_arr = []; for (const [key_idx, key_name] of Object.entries(index_keys)) { key_arr.push(data[key_name]); } keys_obj[key_arr]++; } for (const [key_arr, key_count] of Object.entries(keys_obj)) { rows.push({ key: [e.key, index_id, key_arr], value: body.docs.length, }); } } else { // no index rows.push({ key: "", value: body.docs.length }); } resolve({ rows: rows, total_rows: rows.length }); }; const totals = async function () { var total_fields_info = JSON.parse(e.total_fields_info); var totals_obj = {}; var totals_distinct_obj_arr = {}; for (const [key, val] of Object.entries(total_fields_info)) { totals_obj[val.field_id] = 0; totals_distinct_obj_arr[val.field_id] = []; } for (const [row_key, row_data] of Object.entries(body.docs)) { for (const [key, val] of Object.entries(row_data)) { var field_id = key; var value = undefined; const _tableFieldsObj = find_item_by_key( table_obj.tableFields, "field_id", key ); if (_tableFieldsObj?.props.tableExternalFieldId) { field_id = _tableFieldsObj.props.tableExternalFieldId; value = _.get(val, field_id); } else { value = val; } if (typeof value === "number") { totals_obj[key] += value; } else { if (totals_distinct_obj_arr[key]) { if (totals_distinct_obj_arr[key].indexOf(value) === -1) { totals_distinct_obj_arr[key].push(value); } totals_obj[key] = totals_distinct_obj_arr[key].length; } else { totals_obj[key] = 0; } } } } resolve(totals_obj); }; const reduce = async function () { const reduce_data = body.docs.reduce( (params, data) => { const fx = (val, idx) => { const group_by_field = data[params.sort[idx]]; if (params.sort[idx + 1]) { val[group_by_field] = val[group_by_field] ?? {}; val[group_by_field][data[params.sort[idx + 1]]] = 0; idx++; fx(val[group_by_field], idx); return; } val[group_by_field] = val[group_by_field] ?? 0; val[group_by_field] += 1; }; fx(params.group, 0); return params; }, { sort: opt.sort, group: {} } ); var rows = []; const udfData_maker = async (node, i, obj) => { for await (const [key, val] of Object.entries(node)) { let field_id = sort[i]; const _tableFieldsObj = find_item_by_key( table_obj.tableFields, "field_id", field_id ); let field_value = await get_cast_val( "dbs_update", e.field_id, _tableFieldsObj.props.fieldType, key ); var obj2 = { ...obj, ...{ [field_id]: field_value } }; if (typeof val === "object") { i++; await udfData_maker(val, i, obj2); return; } obj2["REDUCE_VALUE"] = val; rows.push({ id: crypto.randomUUID(), value: obj2 }); } }; await udfData_maker(reduce_data.group, 0, {}); resolve({ rows, total_rows: body.docs.length }); }; if (e.count === "true" || e.count === true) { return await count_data(); } if (e.total_fields_info) { return await totals(); } if (e.reduce === "true") { return await reduce(); } return await raw_data(); } catch (err) { reject(err.message); } }; exports.test_connection = async (params, setup_doc, resolve, reject) => { try { if (!setup_doc.db_connection_string) throw "db_connection_string missing"; const nano = require("nano")(setup_doc.db_connection_string); if (!nano) { return reject("connection error"); } const info = await nano.info(); resolve(nano); } catch (err) { return reject(err.message || err); } };