{"version":3,"file":"order-by.mjs","sources":["../../../src/query/helpers/order-by.ts"],"sourcesContent":["import _ from 'lodash/fp';\nimport knex from 'knex';\n\nimport * as types from '../../utils/types';\nimport { createJoin } from './join';\nimport { toColumnName } from './transform';\n\nimport type { Ctx } from '../types';\n\ntype OrderByCtx = Ctx & { alias?: string };\ntype OrderBy = string | { [key: string]: 'asc' | 'desc' } | OrderBy[];\ntype OrderByValue = { column: string; order?: 'asc' | 'desc' };\n\nconst COL_STRAPI_ROW_NUMBER = '__strapi_row_number';\nconst COL_STRAPI_ORDER_BY_PREFIX = '__strapi_order_by';\n\nexport const processOrderBy = (orderBy: OrderBy, ctx: OrderByCtx): OrderByValue[] => {\n  const { db, uid, qb, alias } = ctx;\n  const meta = db.metadata.get(uid);\n  const { attributes } = meta;\n\n  if (typeof orderBy === 'string') {\n    const attribute = attributes[orderBy];\n\n    if (!attribute) {\n      throw new Error(`Attribute ${orderBy} not found on model ${uid}`);\n    }\n\n    const columnName = toColumnName(meta, orderBy);\n\n    return [{ column: qb.aliasColumn(columnName, alias) }];\n  }\n\n  if (Array.isArray(orderBy)) {\n    return orderBy.flatMap((value) => processOrderBy(value, ctx));\n  }\n\n  if (_.isPlainObject(orderBy)) {\n    return Object.entries(orderBy).flatMap(([key, direction]) => {\n      const value = orderBy[key];\n      const attribute = attributes[key];\n\n      if (!attribute) {\n        throw new Error(`Attribute ${key} not found on model ${uid}`);\n      }\n\n      if (types.isScalar(attribute.type)) {\n        const columnName = toColumnName(meta, key);\n\n        return { column: qb.aliasColumn(columnName, alias), order: direction };\n      }\n\n      if (attribute.type === 'relation' && 'target' in attribute) {\n        const subAlias = createJoin(ctx, {\n          alias: alias || qb.alias,\n          attributeName: key,\n          attribute,\n        });\n\n        return processOrderBy(value, {\n          db,\n          qb,\n          alias: subAlias,\n          uid: attribute.target,\n        });\n      }\n\n      throw new Error(`You cannot order on ${attribute.type} types`);\n    });\n  }\n\n  throw new Error('Invalid orderBy syntax');\n};\n\nexport const getStrapiOrderColumnAlias = (column: string) => {\n  const trimmedColumnName = column.replaceAll('.', '_');\n\n  return `${COL_STRAPI_ORDER_BY_PREFIX}__${trimmedColumnName}`;\n};\n\n/**\n * Wraps the original Knex query with deep sorting functionality.\n *\n * The function takes an original query and an OrderByCtx object as parameters and returns a new Knex query with deep sorting applied.\n */\nexport const wrapWithDeepSort = (originalQuery: knex.Knex.QueryBuilder, ctx: OrderByCtx) => {\n  /**\n   * Notes:\n   * - The generated query has the following flow: baseQuery (filtered unsorted data) -> T (partitioned/sorted data) --> resultQuery (distinct, paginated, sorted data)\n   * - Pagination and selection are transferred from the original query to the outer one to avoid pruning rows too early\n   * - Filtering (where) has to be done in the deepest sub query possible to avoid processing invalid rows and corrupting the final results\n   * - We assume that all necessary joins are done in the original query (`originalQuery`), and every needed column is available with the right name and alias.\n   */\n\n  const { db, qb, uid } = ctx;\n\n  const { tableName } = db.metadata.get(uid);\n\n  // The orderBy is cloned to avoid unwanted mutations of the original object\n  const orderBy = _.cloneDeep<OrderByValue[]>(qb.state.orderBy);\n\n  // 0. Init a new Knex query instance (referenced as resultQuery) using the DB connection\n  //    The connection reuse the original table name (aliased if needed)\n  const resultQueryAlias = qb.getAlias();\n  const aliasedTableName = qb.mustUseAlias() ? alias(resultQueryAlias, tableName) : tableName;\n\n  const resultQuery = db.getConnection(aliasedTableName);\n\n  // 1. Clone the original query to create the sub-query (referenced as baseQuery) and avoid any mutation on the initial object\n  const baseQuery = originalQuery.clone();\n  const baseQueryAlias = qb.getAlias();\n\n  // Clear unwanted statements from the sub-query 'baseQuery'\n  // Note: `first()` is cleared through the combination of `baseQuery.clear('limit')` and calling `baseQuery.select(...)` again\n  // Note: Those statements will be re-applied when duplicates are removed from the final selection\n  baseQuery\n    // Columns selection\n    .clear('select')\n    // Pagination and sorting\n    .clear('order')\n    .clear('limit')\n    .clear('offset');\n\n  // Override the initial select and return only the columns needed for the partitioning.\n  baseQuery.select(\n    // Always select the row id for future manipulation\n    prefix(qb.alias, 'id'),\n    // Select every column used in an order by clause, but alias it for future reference\n    // i.e. if t2.name is present in an order by clause:\n    //      Then, \"t2.name\" will become \"t2.name as __strapi_order_by__t2_name\"\n    ...orderBy.map((orderByClause) =>\n      alias(getStrapiOrderColumnAlias(orderByClause.column), orderByClause.column)\n    )\n  );\n\n  // 2. Create a sub-query callback to extract and sort the partitions using row number\n  const partitionedQueryAlias = qb.getAlias();\n\n  const selectRowsAsNumberedPartitions = (partitionedQuery: knex.Knex.QueryBuilder) => {\n    // Transform order by clause to their alias to reference them from baseQuery\n    const prefixedOrderBy = orderBy.map((orderByClause) => ({\n      column: prefix(baseQueryAlias, getStrapiOrderColumnAlias(orderByClause.column)),\n      order: orderByClause.order,\n    }));\n\n    // partitionedQuery select must contain every column used for sorting\n    const orderByColumns = prefixedOrderBy.map<string>(_.prop('column'));\n\n    partitionedQuery\n      .select(\n        // Always select baseQuery.id\n        prefix(baseQueryAlias, 'id'),\n        // Sort columns\n        ...orderByColumns\n      )\n      // The row number is used to assign an index to every row in every partition\n      .rowNumber(COL_STRAPI_ROW_NUMBER, (subQuery) => {\n        for (const orderByClause of prefixedOrderBy) {\n          subQuery.orderBy(orderByClause.column, orderByClause.order, 'last');\n        }\n\n        // And each partition/group is created based on baseQuery.id\n        subQuery.partitionBy(`${baseQueryAlias}.id`);\n      })\n      .from(baseQuery.as(baseQueryAlias))\n      .as(partitionedQueryAlias);\n  };\n\n  // 3. Create the final resultQuery query, that select and sort the wanted data using T\n\n  const originalSelect = _.difference(\n    qb.state.select,\n    // Remove order by columns from the initial select\n    qb.state.orderBy.map(_.prop('column'))\n  )\n    // Alias everything in resultQuery\n    .map(prefix(resultQueryAlias));\n\n  resultQuery\n    .select(originalSelect)\n    // Join T to resultQuery to access sorted data\n    // Notes:\n    // - Only select the first row for each partition\n    // - Since we're applying the \"where\" statement directly on baseQuery (and not on resultQuery), we're using an inner join to avoid unwanted rows\n    .innerJoin(selectRowsAsNumberedPartitions, function () {\n      this\n        // Only select rows that are returned by T\n        .on(`${partitionedQueryAlias}.id`, `${resultQueryAlias}.id`)\n        // By only selecting the rows number equal to 1, we make sure we don't have duplicate, and that\n        // we're selecting rows in the correct order amongst the groups created by the \"partition by\"\n        .andOnVal(`${partitionedQueryAlias}.${COL_STRAPI_ROW_NUMBER}`, '=', 1);\n    });\n\n  // Re-apply pagination params\n\n  if (qb.state.limit) {\n    resultQuery.limit(qb.state.limit);\n  }\n\n  if (qb.state.offset) {\n    resultQuery.offset(qb.state.offset);\n  }\n\n  if (qb.state.first) {\n    resultQuery.first();\n  }\n\n  // Re-apply the sort using T values\n  resultQuery.orderBy([\n    // Transform \"order by\" clause to their T alias and prefix them with T alias\n    ...orderBy.map((orderByClause) => ({\n      column: prefix(partitionedQueryAlias, getStrapiOrderColumnAlias(orderByClause.column)),\n      order: orderByClause.order,\n    })),\n    // Add T.id to the order by clause to get consistent results in case several rows have the exact same order\n    { column: `${partitionedQueryAlias}.id`, order: 'asc' },\n  ]);\n\n  return resultQuery;\n};\n\n// Utils\nconst alias = _.curry((alias: string, value: string) => `${value} as ${alias}`);\nconst prefix = _.curry((prefix: string, value: string) => `${prefix}.${value}`);\n"],"names":["COL_STRAPI_ROW_NUMBER","COL_STRAPI_ORDER_BY_PREFIX","processOrderBy","orderBy","ctx","db","uid","qb","alias","meta","metadata","get","attributes","attribute","Error","columnName","toColumnName","column","aliasColumn","Array","isArray","flatMap","value","_","isPlainObject","Object","entries","key","direction","types","type","order","subAlias","createJoin","attributeName","target","getStrapiOrderColumnAlias","trimmedColumnName","replaceAll","wrapWithDeepSort","originalQuery","tableName","cloneDeep","state","resultQueryAlias","getAlias","aliasedTableName","mustUseAlias","resultQuery","getConnection","baseQuery","clone","baseQueryAlias","clear","select","prefix","map","orderByClause","partitionedQueryAlias","selectRowsAsNumberedPartitions","partitionedQuery","prefixedOrderBy","orderByColumns","prop","rowNumber","subQuery","partitionBy","from","as","originalSelect","difference","innerJoin","on","andOnVal","limit","offset","first","curry"],"mappings":";;;;;AAaA,MAAMA,qBAAwB,GAAA,qBAAA;AAC9B,MAAMC,0BAA6B,GAAA,mBAAA;AAE5B,MAAMC,cAAiB,GAAA,CAACC,OAAkBC,EAAAA,GAAAA,GAAAA;IAC/C,MAAM,EAAEC,EAAE,EAAEC,GAAG,EAAEC,EAAE,EAAEC,KAAK,EAAE,GAAGJ,GAAAA;AAC/B,IAAA,MAAMK,IAAOJ,GAAAA,EAAAA,CAAGK,QAAQ,CAACC,GAAG,CAACL,GAAAA,CAAAA;IAC7B,MAAM,EAAEM,UAAU,EAAE,GAAGH,IAAAA;IAEvB,IAAI,OAAON,YAAY,QAAU,EAAA;QAC/B,MAAMU,SAAAA,GAAYD,UAAU,CAACT,OAAQ,CAAA;AAErC,QAAA,IAAI,CAACU,SAAW,EAAA;YACd,MAAM,IAAIC,MAAM,CAAC,UAAU,EAAEX,OAAQ,CAAA,oBAAoB,EAAEG,GAAK,CAAA,CAAA,CAAA;AAClE;QAEA,MAAMS,UAAAA,GAAaC,aAAaP,IAAMN,EAAAA,OAAAA,CAAAA;QAEtC,OAAO;AAAC,YAAA;gBAAEc,MAAQV,EAAAA,EAAAA,CAAGW,WAAW,CAACH,UAAYP,EAAAA,KAAAA;AAAO;AAAE,SAAA;AACxD;IAEA,IAAIW,KAAAA,CAAMC,OAAO,CAACjB,OAAU,CAAA,EAAA;AAC1B,QAAA,OAAOA,QAAQkB,OAAO,CAAC,CAACC,KAAAA,GAAUpB,eAAeoB,KAAOlB,EAAAA,GAAAA,CAAAA,CAAAA;AAC1D;IAEA,IAAImB,CAAAA,CAAEC,aAAa,CAACrB,OAAU,CAAA,EAAA;QAC5B,OAAOsB,MAAAA,CAAOC,OAAO,CAACvB,OAAAA,CAAAA,CAASkB,OAAO,CAAC,CAAC,CAACM,GAAAA,EAAKC,SAAU,CAAA,GAAA;YACtD,MAAMN,KAAAA,GAAQnB,OAAO,CAACwB,GAAI,CAAA;YAC1B,MAAMd,SAAAA,GAAYD,UAAU,CAACe,GAAI,CAAA;AAEjC,YAAA,IAAI,CAACd,SAAW,EAAA;gBACd,MAAM,IAAIC,MAAM,CAAC,UAAU,EAAEa,GAAI,CAAA,oBAAoB,EAAErB,GAAK,CAAA,CAAA,CAAA;AAC9D;AAEA,YAAA,IAAIuB,QAAc,CAAChB,SAAAA,CAAUiB,IAAI,CAAG,EAAA;gBAClC,MAAMf,UAAAA,GAAaC,aAAaP,IAAMkB,EAAAA,GAAAA,CAAAA;gBAEtC,OAAO;oBAAEV,MAAQV,EAAAA,EAAAA,CAAGW,WAAW,CAACH,UAAYP,EAAAA,KAAAA,CAAAA;oBAAQuB,KAAOH,EAAAA;AAAU,iBAAA;AACvE;AAEA,YAAA,IAAIf,SAAUiB,CAAAA,IAAI,KAAK,UAAA,IAAc,YAAYjB,SAAW,EAAA;gBAC1D,MAAMmB,QAAAA,GAAWC,WAAW7B,GAAK,EAAA;oBAC/BI,KAAOA,EAAAA,KAAAA,IAASD,GAAGC,KAAK;oBACxB0B,aAAeP,EAAAA,GAAAA;AACfd,oBAAAA;AACF,iBAAA,CAAA;AAEA,gBAAA,OAAOX,eAAeoB,KAAO,EAAA;AAC3BjB,oBAAAA,EAAAA;AACAE,oBAAAA,EAAAA;oBACAC,KAAOwB,EAAAA,QAAAA;AACP1B,oBAAAA,GAAAA,EAAKO,UAAUsB;AACjB,iBAAA,CAAA;AACF;YAEA,MAAM,IAAIrB,MAAM,CAAC,oBAAoB,EAAED,SAAUiB,CAAAA,IAAI,CAAC,MAAM,CAAC,CAAA;AAC/D,SAAA,CAAA;AACF;AAEA,IAAA,MAAM,IAAIhB,KAAM,CAAA,wBAAA,CAAA;AAClB;AAEO,MAAMsB,4BAA4B,CAACnB,MAAAA,GAAAA;AACxC,IAAA,MAAMoB,iBAAoBpB,GAAAA,MAAAA,CAAOqB,UAAU,CAAC,GAAK,EAAA,GAAA,CAAA;AAEjD,IAAA,OAAO,CAAGrC,EAAAA,0BAAAA,CAA2B,EAAE,EAAEoC,iBAAmB,CAAA,CAAA;AAC9D;AAEA;;;;AAIC,IACM,MAAME,gBAAmB,GAAA,CAACC,aAAuCpC,EAAAA,GAAAA,GAAAA;AACtE;;;;;;MAQA,MAAM,EAAEC,EAAE,EAAEE,EAAE,EAAED,GAAG,EAAE,GAAGF,GAAAA;IAExB,MAAM,EAAEqC,SAAS,EAAE,GAAGpC,GAAGK,QAAQ,CAACC,GAAG,CAACL,GAAAA,CAAAA;;AAGtC,IAAA,MAAMH,UAAUoB,CAAEmB,CAAAA,SAAS,CAAiBnC,EAAGoC,CAAAA,KAAK,CAACxC,OAAO,CAAA;;;IAI5D,MAAMyC,gBAAAA,GAAmBrC,GAAGsC,QAAQ,EAAA;AACpC,IAAA,MAAMC,mBAAmBvC,EAAGwC,CAAAA,YAAY,EAAKvC,GAAAA,KAAAA,CAAMoC,kBAAkBH,SAAaA,CAAAA,GAAAA,SAAAA;IAElF,MAAMO,WAAAA,GAAc3C,EAAG4C,CAAAA,aAAa,CAACH,gBAAAA,CAAAA;;IAGrC,MAAMI,SAAAA,GAAYV,cAAcW,KAAK,EAAA;IACrC,MAAMC,cAAAA,GAAiB7C,GAAGsC,QAAQ,EAAA;;;;AAKlCK,IAAAA,SACE;KACCG,KAAK,CAAC,SACP;AACCA,KAAAA,KAAK,CAAC,OACNA,CAAAA,CAAAA,KAAK,CAAC,OAAA,CAAA,CACNA,KAAK,CAAC,QAAA,CAAA;;IAGTH,SAAUI,CAAAA,MAAM;AAEdC,IAAAA,MAAAA,CAAOhD,EAAGC,CAAAA,KAAK,EAAE,IAAA,CAAA;;;OAIdL,OAAQqD,CAAAA,GAAG,CAAC,CAACC,aACdjD,GAAAA,KAAAA,CAAM4B,0BAA0BqB,aAAcxC,CAAAA,MAAM,CAAGwC,EAAAA,aAAAA,CAAcxC,MAAM,CAAA,CAAA,CAAA;;IAK/E,MAAMyC,qBAAAA,GAAwBnD,GAAGsC,QAAQ,EAAA;AAEzC,IAAA,MAAMc,iCAAiC,CAACC,gBAAAA,GAAAA;;AAEtC,QAAA,MAAMC,kBAAkB1D,OAAQqD,CAAAA,GAAG,CAAC,CAACC,iBAAmB;AACtDxC,gBAAAA,MAAAA,EAAQsC,MAAOH,CAAAA,cAAAA,EAAgBhB,yBAA0BqB,CAAAA,aAAAA,CAAcxC,MAAM,CAAA,CAAA;AAC7Ec,gBAAAA,KAAAA,EAAO0B,cAAc1B;aACvB,CAAA,CAAA;;AAGA,QAAA,MAAM+B,iBAAiBD,eAAgBL,CAAAA,GAAG,CAASjC,CAAAA,CAAEwC,IAAI,CAAC,QAAA,CAAA,CAAA;QAE1DH,gBACGN,CAAAA,MAAM;QAELC,MAAOH,CAAAA,cAAAA,EAAgB;AAEpBU,QAAAA,GAAAA,cAAAA,CAEL;SACCE,SAAS,CAAChE,uBAAuB,CAACiE,QAAAA,GAAAA;YACjC,KAAK,MAAMR,iBAAiBI,eAAiB,CAAA;AAC3CI,gBAAAA,QAAAA,CAAS9D,OAAO,CAACsD,aAAAA,CAAcxC,MAAM,EAAEwC,aAAAA,CAAc1B,KAAK,EAAE,MAAA,CAAA;AAC9D;;AAGAkC,YAAAA,QAAAA,CAASC,WAAW,CAAC,CAAGd,EAAAA,cAAAA,CAAe,GAAG,CAAC,CAAA;AAC7C,SAAA,CAAA,CACCe,IAAI,CAACjB,SAAAA,CAAUkB,EAAE,CAAChB,cAAAA,CAAAA,CAAAA,CAClBgB,EAAE,CAACV,qBAAAA,CAAAA;AACR,KAAA;;IAIA,MAAMW,cAAAA,GAAiB9C,EAAE+C,UAAU,CACjC/D,GAAGoC,KAAK,CAACW,MAAM;IAEf/C,EAAGoC,CAAAA,KAAK,CAACxC,OAAO,CAACqD,GAAG,CAACjC,CAAEwC,CAAAA,IAAI,CAAC,QAAA,CAAA,CAAA,CAE5B;AACCP,KAAAA,GAAG,CAACD,MAAOX,CAAAA,gBAAAA,CAAAA,CAAAA;IAEdI,WACGM,CAAAA,MAAM,CAACe,cAAAA,CACR;;;;AAICE,KAAAA,SAAS,CAACZ,8BAAgC,EAAA,WAAA;AACzC,QAAA,IAAI;SAEDa,EAAE,CAAC,CAAGd,EAAAA,qBAAAA,CAAsB,GAAG,CAAC,EAAE,CAAA,EAAGd,gBAAiB,CAAA,GAAG,CAAC,CAC3D;;AAEC6B,SAAAA,QAAQ,CAAC,CAAGf,EAAAA,qBAAAA,CAAsB,CAAC,EAAE1D,qBAAAA,CAAAA,CAAuB,EAAE,GAAK,EAAA,CAAA,CAAA;AACxE,KAAA,CAAA;;AAIF,IAAA,IAAIO,EAAGoC,CAAAA,KAAK,CAAC+B,KAAK,EAAE;AAClB1B,QAAAA,WAAAA,CAAY0B,KAAK,CAACnE,EAAGoC,CAAAA,KAAK,CAAC+B,KAAK,CAAA;AAClC;AAEA,IAAA,IAAInE,EAAGoC,CAAAA,KAAK,CAACgC,MAAM,EAAE;AACnB3B,QAAAA,WAAAA,CAAY2B,MAAM,CAACpE,EAAGoC,CAAAA,KAAK,CAACgC,MAAM,CAAA;AACpC;AAEA,IAAA,IAAIpE,EAAGoC,CAAAA,KAAK,CAACiC,KAAK,EAAE;AAClB5B,QAAAA,WAAAA,CAAY4B,KAAK,EAAA;AACnB;;AAGA5B,IAAAA,WAAAA,CAAY7C,OAAO,CAAC;;AAEfA,QAAAA,GAAAA,OAAAA,CAAQqD,GAAG,CAAC,CAACC,aAAAA,IAAmB;AACjCxC,gBAAAA,MAAAA,EAAQsC,MAAOG,CAAAA,qBAAAA,EAAuBtB,yBAA0BqB,CAAAA,aAAAA,CAAcxC,MAAM,CAAA,CAAA;AACpFc,gBAAAA,KAAAA,EAAO0B,cAAc1B;aACvB,CAAA,CAAA;;AAEA,QAAA;YAAEd,MAAQ,EAAA,CAAA,EAAGyC,qBAAsB,CAAA,GAAG,CAAC;YAAE3B,KAAO,EAAA;AAAM;AACvD,KAAA,CAAA;IAED,OAAOiB,WAAAA;AACT;AAEA;AACA,MAAMxC,KAAAA,GAAQe,CAAEsD,CAAAA,KAAK,CAAC,CAACrE,KAAec,EAAAA,KAAAA,GAAkB,CAAGA,EAAAA,KAAAA,CAAM,IAAI,EAAEd,KAAO,CAAA,CAAA,CAAA;AAC9E,MAAM+C,MAAAA,GAAShC,CAAEsD,CAAAA,KAAK,CAAC,CAACtB,MAAgBjC,EAAAA,KAAAA,GAAkB,CAAGiC,EAAAA,MAAAA,CAAO,CAAC,EAAEjC,KAAO,CAAA,CAAA,CAAA;;;;"}