From 2aa911b217119e78aa9b5db0e8a9d6953687f3c7 Mon Sep 17 00:00:00 2001
From: mike12345567 <me@michaeldrury.co.uk>
Date: Wed, 19 Jun 2024 17:10:15 +0100
Subject: [PATCH] re-jigging things to get counting working properly again.

---
 packages/backend-core/src/sql/sql.ts | 66 ++++++++++++++--------------
 1 file changed, 33 insertions(+), 33 deletions(-)

diff --git a/packages/backend-core/src/sql/sql.ts b/packages/backend-core/src/sql/sql.ts
index a93f1276bb..4a8ba49d9e 100644
--- a/packages/backend-core/src/sql/sql.ts
+++ b/packages/backend-core/src/sql/sql.ts
@@ -114,7 +114,7 @@ function generateSelectStatement(
 ): (string | Knex.Raw)[] | "*" {
   const { resource, meta } = json
 
-  if (!resource) {
+  if (!resource || !resource.fields || resource.fields.length === 0) {
     return "*"
   }
 
@@ -410,14 +410,32 @@ class InternalBuilder {
     return query
   }
 
+  addDistinctCount(
+    query: Knex.QueryBuilder,
+    json: QueryJson
+  ): Knex.QueryBuilder {
+    const table = json.meta.table
+    const primary = table.primary
+    const aliases = json.tableAliases
+    const aliased =
+      table.name && aliases?.[table.name] ? aliases[table.name] : table.name
+    if (!primary) {
+      throw new Error("SQL counting requires primary key to be supplied")
+    }
+    return query.countDistinct(`${aliased}.${primary[0]} as total`)
+  }
+
   addSorting(query: Knex.QueryBuilder, json: QueryJson): Knex.QueryBuilder {
     let { sort } = json
     const table = json.meta.table
-    const mainPrimaryKey = table.primary![0]
+    const primaryKey = table.primary
     const tableName = getTableName(table)
     const aliases = json.tableAliases
     const aliased =
       tableName && aliases?.[tableName] ? aliases[tableName] : table?.name
+    if (!Array.isArray(primaryKey)) {
+      throw new Error("Sorting requires primary key to be specified for table")
+    }
     if (sort && Object.keys(sort || {}).length > 0) {
       for (let [key, value] of Object.entries(sort)) {
         const direction =
@@ -432,7 +450,7 @@ class InternalBuilder {
       }
     }
     // always add sorting by the primary key - make sure result is deterministic
-    query = query.orderBy(`${aliased}.${mainPrimaryKey}`)
+    query = query.orderBy(`${aliased}.${primaryKey[0]}`)
     return query
   }
 
@@ -600,25 +618,13 @@ class InternalBuilder {
     json: QueryJson,
     opts: {
       limits?: { base: number; query: number }
-      disableSorting?: boolean
     } = {}
   ): Knex.QueryBuilder {
-    let { endpoint, resource, filters, paginate, relationships, tableAliases } =
-      json
-    const { limits, disableSorting } = opts
+    let { endpoint, filters, paginate, relationships, tableAliases } = json
+    const { limits } = opts
+    const counting = endpoint.operation === Operation.COUNT
 
     const tableName = endpoint.entityId
-    // select all if not specified
-    if (!resource) {
-      resource = { fields: [] }
-    }
-    let selectStatement: string | (string | Knex.Raw)[] = "*"
-    // handle select
-    if (resource.fields && resource.fields.length > 0) {
-      // select the resources as the format "table.columnName" - this is what is provided
-      // by the resource builder further up
-      selectStatement = generateSelectStatement(json, knex)
-    }
     // start building the query
     let query = this.knexWithAlias(knex, endpoint, tableAliases)
     // handle pagination
@@ -650,7 +656,7 @@ class InternalBuilder {
     })
     // add sorting to pre-query
     // no point in sorting when counting
-    if (!disableSorting) {
+    if (!counting) {
       query = this.addSorting(query, json)
     }
 
@@ -662,9 +668,13 @@ class InternalBuilder {
       // be a table name, not a pre-query
       [alias]: query as any,
     })
-    preQuery = preQuery.select(selectStatement)
+    if (!counting) {
+      preQuery = preQuery.select(generateSelectStatement(json, knex))
+    } else {
+      preQuery = this.addDistinctCount(preQuery, json)
+    }
     // have to add after as well (this breaks MS-SQL)
-    if (this.client !== SqlClient.MS_SQL && !disableSorting) {
+    if (this.client !== SqlClient.MS_SQL && !counting) {
       preQuery = this.addSorting(preQuery, json)
     }
     // handle joins
@@ -688,17 +698,6 @@ class InternalBuilder {
     })
   }
 
-  count(knex: Knex, json: QueryJson) {
-    const readQuery = this.read(knex, json, {
-      disableSorting: true,
-    })
-    // have to alias the sub-query, this is a requirement for my-sql and ms-sql
-    // without this we get an error "Every derived table must have its own alias"
-    return knex({
-      subquery: readQuery as any,
-    }).count("* as total")
-  }
-
   update(knex: Knex, json: QueryJson, opts: QueryOptions): Knex.QueryBuilder {
     const { endpoint, body, filters, tableAliases } = json
     let query = this.knexWithAlias(knex, endpoint, tableAliases)
@@ -781,7 +780,8 @@ class SqlQueryBuilder extends SqlTableQueryBuilder {
         })
         break
       case Operation.COUNT:
-        query = builder.count(client, json)
+        // read without any limits to count
+        query = builder.read(client, json)
         break
       case Operation.UPDATE:
         query = builder.update(client, json, opts)