From 64c25241de47df7c552425ae355ee8482628980a Mon Sep 17 00:00:00 2001
From: zetavg <mail@zeta.vg>
Date: Sun, 19 Nov 2023 03:39:24 +0800
Subject: [PATCH] data-storage-couchdb: test if getData will use index as
 expected

---
 Data/lib/types.ts                             |   1 +
 .../__tests__/data-storage-couchdb.test.ts    | 144 ++++++++++++++++++
 .../lib/functions/getGetData.ts               |  28 ++--
 3 files changed, 162 insertions(+), 11 deletions(-)

diff --git a/Data/lib/types.ts b/Data/lib/types.ts
index d24e16d9..030f4d58 100644
--- a/Data/lib/types.ts
+++ b/Data/lib/types.ts
@@ -85,6 +85,7 @@ export type GetData = <T extends DataTypeName>(
     skip?: number;
     limit?: number;
     sort?: SortOption<DataType<T>>;
+    debug?: boolean;
   },
 ) => Promise<Array<ValidDataTypeWithID<T> | InvalidDataTypeWithID<T>>>;
 
diff --git a/packages/data-storage-couchdb/lib/__tests__/data-storage-couchdb.test.ts b/packages/data-storage-couchdb/lib/__tests__/data-storage-couchdb.test.ts
index fa6dea73..c9399821 100644
--- a/packages/data-storage-couchdb/lib/__tests__/data-storage-couchdb.test.ts
+++ b/packages/data-storage-couchdb/lib/__tests__/data-storage-couchdb.test.ts
@@ -258,6 +258,45 @@ describe('getData', () => {
       });
     });
 
+    it('will use index with sort', async () => {
+      await withContext(async context => {
+        const d = new CouchDBData(context);
+        for (let i = 1; i <= 10; i++) {
+          const collection = await d.saveDatum({
+            __type: 'collection',
+            name: `Collection #${i}`,
+            icon_name: 'box',
+            icon_color: 'gray',
+            collection_reference_number: `${i}`,
+            __created_at: 11 - i,
+          });
+
+          await d.saveDatum({
+            __type: 'item',
+            collection_id: collection.__id,
+            name: `Item #${i}`,
+            icon_name: 'box',
+            icon_color: 'gray',
+            __created_at: 21 - i,
+          });
+        }
+
+        const collections = await d.getData(
+          'collection',
+          {},
+          { sort: [{ __created_at: 'desc' }], debug: true },
+        );
+        expect((collections as any).debug_info.explain.index.ddoc).toBeTruthy();
+
+        const items = await d.getData(
+          'item',
+          {},
+          { sort: [{ __created_at: 'asc' }], debug: true },
+        );
+        expect((items as any).debug_info.explain.index.ddoc).toBeTruthy();
+      });
+    });
+
     it('works with sort and limit and skip', async () => {
       await withContext(async context => {
         const d = new CouchDBData(context);
@@ -587,6 +626,66 @@ describe('getData', () => {
       });
     });
 
+    it('will use index', async () => {
+      await withContext(async context => {
+        const d = new CouchDBData(context);
+        for (let i = 1; i <= 10; i++) {
+          const collection = await d.saveDatum({
+            __type: 'collection',
+            name: `Collection #${i}`,
+            icon_name: 'box',
+            icon_color: 'gray',
+            collection_reference_number: `${i}`,
+          });
+
+          await d.saveDatum({
+            __type: 'item',
+            collection_id: collection.__id,
+            name: `Item #${i}`,
+            icon_name: 'box',
+            icon_color: 'gray',
+            model_name: `Model ${i <= 5 ? 'A' : 'B'}`,
+          });
+        }
+
+        const modelAItems = await d.getData(
+          'item',
+          { model_name: 'Model A' },
+          { debug: true },
+        );
+        expect((modelAItems as any).debug_info.explain.index.ddoc).toBeTruthy();
+
+        const modelBItems = await d.getData(
+          'item',
+          { model_name: 'Model B' },
+          { debug: true },
+        );
+        expect((modelBItems as any).debug_info.explain.index.ddoc).toBeTruthy();
+
+        const itemsCreatedAfter0 = await d.getData(
+          'item',
+          {
+            __created_at: { $gt: 0 },
+          },
+          { debug: true },
+        );
+        expect(
+          (itemsCreatedAfter0 as any).debug_info.explain.index.ddoc,
+        ).toBeTruthy();
+
+        const collectionsCreatedAfter0 = await d.getData(
+          'collection',
+          {
+            __created_at: { $gt: 0 },
+          },
+          { debug: true },
+        );
+        expect(
+          (collectionsCreatedAfter0 as any).debug_info.explain.index.ddoc,
+        ).toBeTruthy();
+      });
+    });
+
     it('works with limit and skip', async () => {
       await withContext(async context => {
         const d = new CouchDBData(context);
@@ -692,6 +791,51 @@ describe('getData', () => {
         );
       });
     });
+
+    it('will use index with sort', async () => {
+      await withContext(async context => {
+        const d = new CouchDBData(context);
+        for (let i = 1; i <= 10; i++) {
+          const collection = await d.saveDatum({
+            __type: 'collection',
+            name: `Collection #${i}`,
+            icon_name: 'box',
+            icon_color: 'gray',
+            collection_reference_number: `${i}`,
+          });
+
+          await d.saveDatum({
+            __type: 'item',
+            collection_id: collection.__id,
+            name: `Item #${i}`,
+            icon_name: 'box',
+            icon_color: 'gray',
+            model_name: `Model ${i <= 5 ? 'A' : 'B'}`,
+            purchase_price_x1000: Math.abs(6 - i) * 1000,
+          });
+        }
+
+        const items_a = await d.getData(
+          'item',
+          { model_name: 'Model A' },
+          { sort: [{ purchase_price_x1000: 'asc' }], debug: true },
+        );
+        expect((items_a as any).debug_info.explain.index.ddoc).toBeTruthy();
+
+        const items_b = await d.getData(
+          'item',
+          { model_name: 'Model B' },
+          {
+            sort: [
+              // { model_name: 'desc' }, // To prevent CouchDB error - { "error": "unsupported_mixed_sort", "reason": "Sorts currently only support a single direction for all fields.", "status": 400 } // This is now handled in getData
+              { purchase_price_x1000: 'desc' },
+            ],
+            debug: true,
+          },
+        );
+        expect((items_b as any).debug_info.explain.index.ddoc).toBeTruthy();
+      });
+    });
   });
 
   describe('with field exists conditions', () => {
diff --git a/packages/data-storage-couchdb/lib/functions/getGetData.ts b/packages/data-storage-couchdb/lib/functions/getGetData.ts
index 9b11f152..7216c70c 100644
--- a/packages/data-storage-couchdb/lib/functions/getGetData.ts
+++ b/packages/data-storage-couchdb/lib/functions/getGetData.ts
@@ -26,7 +26,7 @@ export default function getGetData({
   const getData: GetData = async function getData(
     type,
     conditions = {},
-    { skip = 0, limit = undefined, sort } = {},
+    { skip = 0, limit = undefined, sort, debug } = {},
   ) {
     const logDebug = logLevels && logLevels().includes('debug');
 
@@ -223,7 +223,7 @@ export default function getGetData({
     })();
 
     // Since remote CouchDB server may not throw error if index not found
-    if (alwaysCreateIndexFirst && ddocName && index) {
+    if ((alwaysCreateIndexFirst || debug) && ddocName && index) {
       try {
         await db.createIndex({
           ddoc: ddocName,
@@ -233,25 +233,25 @@ export default function getGetData({
       } catch (e) {}
     }
 
-    if (logger && logDebug) {
-      let explain = '';
+    let explain: string | object = '';
+    if (debug || (logger && logDebug)) {
       try {
-        explain = `, explain: ${JSON.stringify(
-          await ((db as any).explain as any)(query),
-          null,
-          2,
-        )}`;
+        explain = await ((db as any).explain as any)(query);
       } catch (e) {
-        explain = `, explain: error on getting explain - ${
+        explain = `error on getting explain - ${
           e instanceof Error ? e.message : JSON.stringify(e)
         }`;
       }
+    }
+    if (logger && logDebug) {
       logger.debug(
         `getData query: ${JSON.stringify(
           query,
           null,
           2,
-        )}, index: ${JSON.stringify(index, null, 2)}` + explain,
+        )}, index: ${JSON.stringify(index, null, 2)}, explain: ${
+          typeof explain === 'string' ? explain : JSON.stringify(explain)
+        }`,
       );
     }
 
@@ -318,6 +318,12 @@ export default function getGetData({
       );
     }
 
+    if (debug) {
+      data.debug_info = {
+        explain,
+      };
+    }
+
     return data;
   };