@xuda.io/xuda-dbs-plugin-couchdb
Version:
Xuda Database Socket for CouchDB
575 lines (497 loc) • 14.6 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;
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);
}
};