@xuda.io/xuda-dbs-plugin-xuda 1.0.318 → 1.0.320

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/package.json +1 -1
  2. package/server.mjs +86 -21
  3. package/studio.mjs +73 -50
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xuda.io/xuda-dbs-plugin-xuda",
3
- "version": "1.0.318",
3
+ "version": "1.0.320",
4
4
  "description": "Xuda Database Socket for Xuda's proprietary structure powered by CouchDB",
5
5
  "scripts": {
6
6
  "pub": "npm version patch --force && npm publish --access public"
package/server.mjs CHANGED
@@ -515,27 +515,68 @@ const query_db = async function (e, db, app_id_reference, table_obj) {
515
515
  return raw_data();
516
516
  };
517
517
 
518
+ const get_mango_index_fields = function (selector) {
519
+ const fields = new Set();
520
+ const add_selector_fields = function (obj) {
521
+ if (!obj || typeof obj !== 'object' || Array.isArray(obj)) {
522
+ return;
523
+ }
524
+ for (const [key, val] of Object.entries(obj)) {
525
+ if (['$and', '$or', '$nor'].includes(key)) {
526
+ if (Array.isArray(val)) {
527
+ val.forEach(add_selector_fields);
528
+ }
529
+ continue;
530
+ }
531
+ if (key.startsWith('$')) {
532
+ continue;
533
+ }
534
+ fields.add(key);
535
+ }
536
+ };
537
+ add_selector_fields(selector);
538
+ return [...fields];
539
+ };
540
+
541
+ const create_mango_index_for_selector = async function () {
542
+ const index = get_mango_index_fields(opt.selector);
543
+ if (!index.length) {
544
+ return;
545
+ }
546
+ const index_suffix = index.join('_').replace(/[^a-zA-Z0-9_]+/g, '_').slice(0, 80) || 'selector';
547
+ const mango_index_obj = {
548
+ index: {
549
+ fields: index,
550
+ },
551
+ name: `index_${e.table_id}_${index_suffix}`,
552
+ ddoc: `mango_index_table_${e.table_id}_${index_suffix}`,
553
+ };
554
+ try {
555
+ await db.createIndex(mango_index_obj);
556
+ } catch (err) {
557
+ // The read already succeeded; index creation is only an optimization.
558
+ }
559
+ };
560
+
561
+ // A COUNT must span the WHOLE result set, not the current page. `opt` carries
562
+ // the page limit/skip, so counting body.docs.length under them could never
563
+ // exceed one page. Ask for ids only (unless count_data's index-grouping
564
+ // branch needs the field data) and cap the scan so a pathological table
565
+ // cannot stall the request.
566
+ const COUNT_SCAN_CAP = 100000;
567
+ const find_opt = e.count
568
+ ? Object.assign({}, opt, {
569
+ limit: COUNT_SCAN_CAP,
570
+ skip: 0,
571
+ fields: e.indexId ? opt.fields : ['_id'],
572
+ })
573
+ : opt;
574
+
518
575
  try {
519
576
  try {
520
- const doc = await db.find(opt);
521
- var mango_index_obj;
577
+ const doc = await db.find(find_opt);
522
578
  if (doc?.warning?.includes('No matching index found')) {
523
- const index_name = `index_${e.table_id}_${new Date().valueOf().toString()}`;
524
- var index = [];
525
-
526
- for (const [key, val] of Object.entries(opt.selector)) {
527
- index.push(key);
528
- }
529
- mango_index_obj = {
530
- index: {
531
- fields: index,
532
- },
533
- name: index_name,
534
- ddoc: `mango_index_table_${e.table_id}`,
535
- };
536
- db.createIndex(mango_index_obj).then((result) => {
537
- console.log(result);
538
- });
579
+ await create_mango_index_for_selector();
539
580
  }
540
581
  return await done(doc);
541
582
  } catch (err) {
@@ -544,7 +585,7 @@ const query_db = async function (e, db, app_id_reference, table_obj) {
544
585
  const _selector = _.cloneDeep(opt.selector);
545
586
  delete _selector['$and'];
546
587
  const _sort = _.cloneDeep(opt.sort);
547
- mango_index_obj = {
588
+ const mango_index_obj = {
548
589
  index: {
549
590
  fields: Object.keys(
550
591
  _.reduce(
@@ -564,7 +605,7 @@ const query_db = async function (e, db, app_id_reference, table_obj) {
564
605
  try {
565
606
  const result = await db.createIndex(mango_index_obj);
566
607
 
567
- const doc = await db.find(opt);
608
+ const doc = await db.find(find_opt);
568
609
  return await done(doc);
569
610
  } catch (err) {
570
611
  return { code: -1, data: err.message };
@@ -692,7 +733,31 @@ const query_db = async function (e, db, app_id_reference, table_obj) {
692
733
  return await runtime_get_query_data();
693
734
  }
694
735
 
695
- if (e.count && !e.filter_from) {
736
+ // A COUNT must answer "how many rows match THIS query", not "how many rows are
737
+ // in the table". count_tables() reads the db_table_counts reduce view keyed by
738
+ // table_id alone, so it ignores the selector completely — and `filter_from` is
739
+ // only ever set for dataSourceFilterModelType==='index', so every MONGO-filtered
740
+ // count landed here and came back with the whole-table total. Live symptom: a
741
+ // filtered question list returned its correct rows while rows_found stayed 837,
742
+ // so the pager kept offering 168 pages over an empty/short result set.
743
+ // Keep the cheap view for a genuinely UNFILTERED count; anything carrying a
744
+ // selector is counted through Mango against that same selector.
745
+ const _has_filter_value = function (v) {
746
+ if (v === undefined || v === null || v === '') return false;
747
+ try {
748
+ const o = typeof v === 'string' ? JSON.parse(v) : v;
749
+ return !_.isEmpty(o);
750
+ } catch (err) {
751
+ return true; // present but unparseable — assume it filters; never over-count
752
+ }
753
+ };
754
+ const _count_is_filtered =
755
+ !!e.filter_from ||
756
+ _has_filter_value(e.filterModelMongo) ||
757
+ _has_filter_value(e.filterModelUserMongo) ||
758
+ _has_filter_value(e.filterModelNative);
759
+
760
+ if (e.count && !_count_is_filtered) {
696
761
  return await count_tables();
697
762
  }
698
763
 
package/studio.mjs CHANGED
@@ -180,6 +180,20 @@ const get_cast_val = async function (source, attributeP, typeP, valP) {
180
180
  return xu_cast(typeP, valP, success, fail);
181
181
  };
182
182
 
183
+ const get_json_value = function (value, fallback) {
184
+ if (typeof value === 'undefined' || value === null || value === '') {
185
+ return fallback;
186
+ }
187
+ if (typeof value === 'string') {
188
+ try {
189
+ return JSON.parse(value);
190
+ } catch (err) {
191
+ return fallback;
192
+ }
193
+ }
194
+ return value;
195
+ };
196
+
183
197
  const get_opt = function (e, table_obj) {
184
198
  var limit = 99999;
185
199
  var skip = 0;
@@ -244,9 +258,9 @@ const get_opt = function (e, table_obj) {
244
258
  opt.sort = sort;
245
259
  }
246
260
 
247
- let _sortModel = e?.sortModel || [];
248
- if (typeof _sortModel === 'string') {
249
- _sortModel = JSON.parse(e.sortModel);
261
+ let _sortModel = get_json_value(e?.sortModel, []);
262
+ if (!Array.isArray(_sortModel)) {
263
+ _sortModel = [];
250
264
  }
251
265
 
252
266
  if (_sortModel.length) {
@@ -288,7 +302,7 @@ const get_opt = function (e, table_obj) {
288
302
  opt.fields[key] = 'udfData.data.' + val;
289
303
  }
290
304
 
291
- if (!e?.sortModel || !JSON.parse(e.sortModel).length) {
305
+ if (!e?.sortModel || !_sortModel.length) {
292
306
  // added 2021 09 10
293
307
  if (opt.sort) {
294
308
  for (const [key, val] of Object.entries(opt.sort)) {
@@ -372,14 +386,16 @@ const get_opt = function (e, table_obj) {
372
386
  return recursiveReplace(query);
373
387
  }
374
388
 
375
- if (e.dataSourceFilterModelType === 'query' && e.filterModelMongo) {
376
- selector_new['$and'] = [replaceRegexOptions(replaceKeysInQuery(JSON.parse(e.filterModelMongo)))];
389
+ const filterModelMongo = get_json_value(e.filterModelMongo, {});
390
+ if (e.dataSourceFilterModelType === 'query' && !_.isEmpty(filterModelMongo)) {
391
+ selector_new['$and'] = [replaceRegexOptions(replaceKeysInQuery(filterModelMongo))];
377
392
  }
378
- if (e.filterModelUserMongo) {
393
+ const filterModelUserMongo = get_json_value(e.filterModelUserMongo, {});
394
+ if (!_.isEmpty(filterModelUserMongo)) {
379
395
  if (!selector_new['$and']) {
380
396
  selector_new['$and'] = [];
381
397
  }
382
- selector_new['$and'].push(replaceRegexOptions(replaceKeysInQuery(JSON.parse(e.filterModelUserMongo))));
398
+ selector_new['$and'].push(replaceRegexOptions(replaceKeysInQuery(filterModelUserMongo)));
383
399
  }
384
400
 
385
401
  if (e.dataSourceFilterModelType === 'index') {
@@ -587,44 +603,66 @@ const query_db = async function (e, db, table_obj) {
587
603
  };
588
604
 
589
605
  // xuda
606
+ const get_mango_index_fields = function (selector) {
607
+ const fields = new Set();
608
+ const add_selector_fields = function (obj) {
609
+ if (!obj || typeof obj !== 'object' || Array.isArray(obj)) {
610
+ return;
611
+ }
612
+ for (const [key, val] of Object.entries(obj)) {
613
+ if (['$and', '$or', '$nor'].includes(key)) {
614
+ if (Array.isArray(val)) {
615
+ val.forEach(add_selector_fields);
616
+ }
617
+ continue;
618
+ }
619
+ if (key.startsWith('$')) {
620
+ continue;
621
+ }
622
+ fields.add(key);
623
+ }
624
+ };
625
+ add_selector_fields(selector);
626
+ return [...fields];
627
+ };
628
+
629
+ const create_mango_index_for_selector = async function () {
630
+ const index = get_mango_index_fields(opt.selector);
631
+ if (!index.length) {
632
+ return;
633
+ }
634
+ const index_suffix = index.join('_').replace(/[^a-zA-Z0-9_]+/g, '_').slice(0, 80) || 'selector';
635
+ const mango_index_obj = {
636
+ index: {
637
+ fields: index,
638
+ },
639
+ name: `index_${e.table_id}_${index_suffix}`,
640
+ ddoc: `mango_index_table_${e.table_id}_${index_suffix}`,
641
+ };
642
+ try {
643
+ await db.createIndex(mango_index_obj);
644
+ } catch (err) {
645
+ // The read already succeeded; index creation is only an optimization.
646
+ }
647
+ };
590
648
 
591
649
  try {
592
650
  try {
593
651
  // console.log("opt", opt);
594
652
 
595
653
  const doc = await db.find(opt);
596
- var mango_index_obj;
597
654
  if (doc?.warning?.includes('No matching index found')) {
598
- try {
599
- const index_name = `index_${e.table_id}_${new Date().valueOf().toString()}`;
600
- var index = [];
601
-
602
- for (const [key, val] of Object.entries(opt.selector)) {
603
- index.push(key);
604
- }
605
- mango_index_obj = {
606
- index: {
607
- fields: index,
608
- },
609
- name: index_name,
610
- ddoc: `mango_index_table_${e.table_id}`,
611
- };
612
- const result = await db.createIndex(mango_index_obj);
613
- // console.log("createIndex", result);
614
- throw new Error('creating index in progress');
615
- } catch (err) {
616
- return { code: -1, data: err.message };
617
- }
655
+ await create_mango_index_for_selector();
618
656
  }
619
657
  return await done(doc);
620
658
  } catch (err) {
621
- if (err.message.includes('Cannot sort on field')) {
659
+ if (err?.message?.includes('Cannot sort on field')) {
622
660
  const index_name = `index_${e.table_id}_${new Date().valueOf().toString()}`;
623
661
 
624
662
  // const _selector = _.cloneDeep(opt.selector);
625
663
  let _sort = _.cloneDeep(opt.sort);
626
664
 
627
- mango_index_obj = {
665
+ const mango_index_obj = {
628
666
  index: {
629
667
  fields: [],
630
668
  },
@@ -655,32 +693,17 @@ const query_db = async function (e, db, table_obj) {
655
693
 
656
694
  try {
657
695
  const result = await db.createIndex(mango_index_obj);
658
- // const monitor_indexing = async function () {
659
- // return new Promise((resolve, reject) => {
660
- // db.on("indexing", function (event) {
661
- // // called when indexes are updated
662
- // console.log("indexes event", event);
663
- // // resolve();
664
- // });
665
- // setTimeout(() => {
666
- // resolve();
667
- // }, 5000);
668
- // });
669
- // };
670
- // await monitor_indexing();
671
- // throw new Error({ code: -88, data: "creating index" });
672
- throw new Error('creating index in progress');
673
- // const doc = await db.find(opt);
674
- // return await done(doc);
696
+ const doc = await db.find(opt);
697
+ return await done(doc);
675
698
  } catch (err) {
676
699
  return { code: -1, data: err.message };
677
700
  }
678
701
  } else {
679
- return { code: -1, data: err.message };
702
+ return { code: -1, data: err?.message || err };
680
703
  }
681
704
  }
682
705
  } catch (err) {
683
- return { code: -1, data: err.message };
706
+ return { code: -1, data: err?.message || err };
684
707
  }
685
708
  };
686
709