@strapi/database 5.0.0-beta.8 → 5.0.0-rc.0

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.
package/dist/index.mjs CHANGED
@@ -5,11 +5,11 @@ import _, { isNil, castArray, prop, omit, isInteger, snakeCase, partition, sumBy
5
5
  import crypto from "crypto";
6
6
  import crypto$1 from "node:crypto";
7
7
  import * as dateFns from "date-fns";
8
- import { isOperatorOfType, isOperator } from "@strapi/utils";
8
+ import { AsyncLocalStorage } from "node:async_hooks";
9
9
  import KnexBuilder from "knex/lib/query/querybuilder";
10
10
  import KnexRaw from "knex/lib/raw";
11
+ import { isOperatorOfType, isOperator } from "@strapi/utils";
11
12
  import { Readable } from "stream";
12
- import { AsyncLocalStorage } from "node:async_hooks";
13
13
  import _$1 from "lodash";
14
14
  import { Umzug } from "umzug";
15
15
  import { createId } from "@paralleldrive/cuid2";
@@ -1087,7 +1087,7 @@ const createHelpers = (db) => {
1087
1087
  }
1088
1088
  }
1089
1089
  };
1090
- const dropIndex = (tableBuilder, index2) => {
1090
+ const dropIndex2 = (tableBuilder, index2) => {
1091
1091
  if (!db.config.settings?.forceMigration) {
1092
1092
  return;
1093
1093
  }
@@ -1164,13 +1164,13 @@ const createHelpers = (db) => {
1164
1164
  for (const removedIndex of table.indexes.removed) {
1165
1165
  if (!ignoreForeignKeyNames.includes(removedIndex.name)) {
1166
1166
  debug$2(`Dropping index ${removedIndex.name} on ${table.name}`);
1167
- dropIndex(tableBuilder, removedIndex);
1167
+ dropIndex2(tableBuilder, removedIndex);
1168
1168
  }
1169
1169
  }
1170
1170
  for (const updatedIndex of table.indexes.updated) {
1171
1171
  if (!ignoreForeignKeyNames.includes(updatedIndex.name)) {
1172
1172
  debug$2(`Dropping updated index ${updatedIndex.name} on ${table.name}`);
1173
- dropIndex(tableBuilder, updatedIndex.object);
1173
+ dropIndex2(tableBuilder, updatedIndex.object);
1174
1174
  }
1175
1175
  }
1176
1176
  for (const updatedColumn of table.columns.updated) {
@@ -2895,6 +2895,68 @@ const createField = (attribute) => {
2895
2895
  }
2896
2896
  throw new Error(`Undefined field for type ${type}`);
2897
2897
  };
2898
+ const storage = new AsyncLocalStorage();
2899
+ const transactionCtx = {
2900
+ async run(trx, cb) {
2901
+ const store = storage.getStore();
2902
+ return storage.run(
2903
+ {
2904
+ trx,
2905
+ // Fill with existing callbacks if nesting transactions
2906
+ commitCallbacks: store?.commitCallbacks || [],
2907
+ rollbackCallbacks: store?.rollbackCallbacks || []
2908
+ },
2909
+ cb
2910
+ );
2911
+ },
2912
+ get() {
2913
+ const store = storage.getStore();
2914
+ return store?.trx;
2915
+ },
2916
+ async commit(trx) {
2917
+ const store = storage.getStore();
2918
+ if (store?.trx) {
2919
+ store.trx = null;
2920
+ }
2921
+ await trx.commit();
2922
+ if (!store?.commitCallbacks.length) {
2923
+ return;
2924
+ }
2925
+ store.commitCallbacks.forEach((cb) => cb());
2926
+ store.commitCallbacks = [];
2927
+ },
2928
+ async rollback(trx) {
2929
+ const store = storage.getStore();
2930
+ if (store?.trx) {
2931
+ store.trx = null;
2932
+ }
2933
+ await trx.rollback();
2934
+ if (!store?.rollbackCallbacks.length) {
2935
+ return;
2936
+ }
2937
+ store.rollbackCallbacks.forEach((cb) => cb());
2938
+ store.rollbackCallbacks = [];
2939
+ },
2940
+ onCommit(cb) {
2941
+ const store = storage.getStore();
2942
+ if (store?.commitCallbacks) {
2943
+ store.commitCallbacks.push(cb);
2944
+ }
2945
+ },
2946
+ onRollback(cb) {
2947
+ const store = storage.getStore();
2948
+ if (store?.rollbackCallbacks) {
2949
+ store.rollbackCallbacks.push(cb);
2950
+ }
2951
+ }
2952
+ };
2953
+ function isKnexQuery(value) {
2954
+ return value instanceof KnexBuilder || value instanceof KnexRaw;
2955
+ }
2956
+ const addSchema = (db, tableName) => {
2957
+ const schemaName = db.getSchemaName();
2958
+ return schemaName ? `${schemaName}.${tableName}` : tableName;
2959
+ };
2898
2960
  const fromSingleRow = (meta, row) => {
2899
2961
  const { attributes } = meta;
2900
2962
  if (_.isNil(row)) {
@@ -3017,7 +3079,7 @@ const escapeQuery = (query, charsToEscape, escapeChar = "\\") => {
3017
3079
  ""
3018
3080
  );
3019
3081
  };
3020
- const createPivotJoin = (ctx, { alias, refAlias, joinTable, targetMeta }) => {
3082
+ const createPivotJoin = (ctx, { alias: alias2, refAlias, joinTable, targetMeta }) => {
3021
3083
  const { qb } = ctx;
3022
3084
  const joinAlias = qb.getAlias();
3023
3085
  qb.join({
@@ -3025,7 +3087,7 @@ const createPivotJoin = (ctx, { alias, refAlias, joinTable, targetMeta }) => {
3025
3087
  referencedTable: joinTable.name,
3026
3088
  referencedColumn: joinTable.joinColumn.name,
3027
3089
  rootColumn: joinTable.joinColumn.referencedColumn,
3028
- rootTable: alias,
3090
+ rootTable: alias2,
3029
3091
  on: joinTable.on
3030
3092
  });
3031
3093
  const subAlias = refAlias || qb.getAlias();
@@ -3038,7 +3100,7 @@ const createPivotJoin = (ctx, { alias, refAlias, joinTable, targetMeta }) => {
3038
3100
  });
3039
3101
  return subAlias;
3040
3102
  };
3041
- const createJoin = (ctx, { alias, refAlias, attributeName, attribute }) => {
3103
+ const createJoin = (ctx, { alias: alias2, refAlias, attributeName, attribute }) => {
3042
3104
  const { db, qb, uid } = ctx;
3043
3105
  if (attribute.type !== "relation") {
3044
3106
  throw new Error(`Cannot join on non relational field ${attributeName}`);
@@ -3054,7 +3116,7 @@ const createJoin = (ctx, { alias, refAlias, attributeName, attribute }) => {
3054
3116
  referencedTable: targetMeta.tableName,
3055
3117
  referencedColumn: morphColumn.idColumn.name,
3056
3118
  rootColumn: morphColumn.idColumn.referencedColumn,
3057
- rootTable: alias,
3119
+ rootTable: alias2,
3058
3120
  on: {
3059
3121
  [morphColumn.typeColumn.name]: uid,
3060
3122
  ...morphColumn.on
@@ -3069,7 +3131,7 @@ const createJoin = (ctx, { alias, refAlias, attributeName, attribute }) => {
3069
3131
  referencedTable: joinTable2.name,
3070
3132
  referencedColumn: joinTable2.morphColumn.idColumn.name,
3071
3133
  rootColumn: joinTable2.morphColumn.idColumn.referencedColumn,
3072
- rootTable: alias,
3134
+ rootTable: alias2,
3073
3135
  on: {
3074
3136
  [joinTable2.morphColumn.typeColumn.name]: uid,
3075
3137
  field: attributeName
@@ -3085,7 +3147,7 @@ const createJoin = (ctx, { alias, refAlias, attributeName, attribute }) => {
3085
3147
  });
3086
3148
  return subAlias;
3087
3149
  }
3088
- return alias;
3150
+ return alias2;
3089
3151
  }
3090
3152
  const { joinColumn } = attribute;
3091
3153
  if (joinColumn) {
@@ -3095,20 +3157,20 @@ const createJoin = (ctx, { alias, refAlias, attributeName, attribute }) => {
3095
3157
  referencedTable: targetMeta.tableName,
3096
3158
  referencedColumn: joinColumn.referencedColumn,
3097
3159
  rootColumn: joinColumn.name,
3098
- rootTable: alias
3160
+ rootTable: alias2
3099
3161
  });
3100
3162
  return subAlias;
3101
3163
  }
3102
3164
  const { joinTable } = attribute;
3103
3165
  if (joinTable) {
3104
- return createPivotJoin(ctx, { alias, refAlias, joinTable, targetMeta });
3166
+ return createPivotJoin(ctx, { alias: alias2, refAlias, joinTable, targetMeta });
3105
3167
  }
3106
- return alias;
3168
+ return alias2;
3107
3169
  };
3108
3170
  const applyJoin = (qb, join) => {
3109
3171
  const {
3110
3172
  method = "leftJoin",
3111
- alias,
3173
+ alias: alias2,
3112
3174
  referencedTable,
3113
3175
  referencedColumn,
3114
3176
  rootColumn,
@@ -3118,26 +3180,28 @@ const applyJoin = (qb, join) => {
3118
3180
  on,
3119
3181
  orderBy
3120
3182
  } = join;
3121
- qb[method](`${referencedTable} as ${alias}`, (inner) => {
3122
- inner.on(`${rootTable}.${rootColumn}`, `${alias}.${referencedColumn}`);
3183
+ qb[method](`${referencedTable} as ${alias2}`, (inner) => {
3184
+ inner.on(`${rootTable}.${rootColumn}`, `${alias2}.${referencedColumn}`);
3123
3185
  if (on) {
3124
3186
  for (const key of Object.keys(on)) {
3125
- inner.onVal(`${alias}.${key}`, on[key]);
3187
+ inner.onVal(`${alias2}.${key}`, on[key]);
3126
3188
  }
3127
3189
  }
3128
3190
  });
3129
3191
  if (orderBy) {
3130
3192
  Object.keys(orderBy).forEach((column) => {
3131
3193
  const direction = orderBy[column];
3132
- qb.orderBy(`${alias}.${column}`, direction);
3194
+ qb.orderBy(`${alias2}.${column}`, direction);
3133
3195
  });
3134
3196
  }
3135
3197
  };
3136
3198
  const applyJoins = (qb, joins) => {
3137
3199
  return joins.forEach((join) => applyJoin(qb, join));
3138
3200
  };
3201
+ const COL_STRAPI_ROW_NUMBER = "__strapi_row_number";
3202
+ const COL_STRAPI_ORDER_BY_PREFIX = "__strapi_order_by";
3139
3203
  const processOrderBy = (orderBy, ctx) => {
3140
- const { db, uid, qb, alias } = ctx;
3204
+ const { db, uid, qb, alias: alias2 } = ctx;
3141
3205
  const meta = db.metadata.get(uid);
3142
3206
  const { attributes } = meta;
3143
3207
  if (typeof orderBy === "string") {
@@ -3146,7 +3210,7 @@ const processOrderBy = (orderBy, ctx) => {
3146
3210
  throw new Error(`Attribute ${orderBy} not found on model ${uid}`);
3147
3211
  }
3148
3212
  const columnName = toColumnName(meta, orderBy);
3149
- return [{ column: qb.aliasColumn(columnName, alias) }];
3213
+ return [{ column: qb.aliasColumn(columnName, alias2) }];
3150
3214
  }
3151
3215
  if (Array.isArray(orderBy)) {
3152
3216
  return orderBy.flatMap((value) => processOrderBy(value, ctx));
@@ -3160,11 +3224,11 @@ const processOrderBy = (orderBy, ctx) => {
3160
3224
  }
3161
3225
  if (isScalar(attribute.type)) {
3162
3226
  const columnName = toColumnName(meta, key);
3163
- return { column: qb.aliasColumn(columnName, alias), order: direction };
3227
+ return { column: qb.aliasColumn(columnName, alias2), order: direction };
3164
3228
  }
3165
3229
  if (attribute.type === "relation" && "target" in attribute) {
3166
3230
  const subAlias = createJoin(ctx, {
3167
- alias: alias || qb.alias,
3231
+ alias: alias2 || qb.alias,
3168
3232
  attributeName: key,
3169
3233
  attribute
3170
3234
  });
@@ -3180,6 +3244,76 @@ const processOrderBy = (orderBy, ctx) => {
3180
3244
  }
3181
3245
  throw new Error("Invalid orderBy syntax");
3182
3246
  };
3247
+ const getStrapiOrderColumnAlias = (column) => {
3248
+ const trimmedColumnName = column.replaceAll(".", "_");
3249
+ return `${COL_STRAPI_ORDER_BY_PREFIX}__${trimmedColumnName}`;
3250
+ };
3251
+ const wrapWithDeepSort = (originalQuery, ctx) => {
3252
+ const { db, qb, uid } = ctx;
3253
+ const { tableName } = db.metadata.get(uid);
3254
+ const orderBy = _.cloneDeep(qb.state.orderBy);
3255
+ const resultQueryAlias = qb.getAlias();
3256
+ const aliasedTableName = qb.mustUseAlias() ? alias(resultQueryAlias, tableName) : tableName;
3257
+ const resultQuery = db.getConnection(aliasedTableName);
3258
+ const baseQuery = originalQuery.clone();
3259
+ const baseQueryAlias = qb.getAlias();
3260
+ baseQuery.clear("select").clear("order").clear("limit").clear("offset");
3261
+ baseQuery.select(
3262
+ // Always select the row id for future manipulation
3263
+ prefix(qb.alias, "id"),
3264
+ ...orderBy.map(
3265
+ (orderByClause) => alias(getStrapiOrderColumnAlias(orderByClause.column), orderByClause.column)
3266
+ )
3267
+ );
3268
+ const partitionedQueryAlias = qb.getAlias();
3269
+ const selectRowsAsNumberedPartitions = (partitionedQuery) => {
3270
+ const prefixedOrderBy = orderBy.map((orderByClause) => ({
3271
+ column: prefix(baseQueryAlias, getStrapiOrderColumnAlias(orderByClause.column)),
3272
+ order: orderByClause.order
3273
+ }));
3274
+ const orderByColumns = prefixedOrderBy.map(_.prop("column"));
3275
+ partitionedQuery.select(
3276
+ // Always select baseQuery.id
3277
+ prefix(baseQueryAlias, "id"),
3278
+ ...orderByColumns
3279
+ ).rowNumber(COL_STRAPI_ROW_NUMBER, (subQuery) => {
3280
+ for (const orderByClause of prefixedOrderBy) {
3281
+ subQuery.orderBy(orderByClause.column, orderByClause.order, "last");
3282
+ }
3283
+ subQuery.partitionBy(`${baseQueryAlias}.id`);
3284
+ }).from(baseQuery.as(baseQueryAlias)).as(partitionedQueryAlias);
3285
+ };
3286
+ const originalSelect = _.difference(
3287
+ qb.state.select,
3288
+ // Remove order by columns from the initial select
3289
+ qb.state.orderBy.map(_.prop("column"))
3290
+ ).map(prefix(resultQueryAlias));
3291
+ resultQuery.select(originalSelect).innerJoin(selectRowsAsNumberedPartitions, function() {
3292
+ this.on(`${partitionedQueryAlias}.id`, `${resultQueryAlias}.id`).andOnVal(`${partitionedQueryAlias}.${COL_STRAPI_ROW_NUMBER}`, "=", 1);
3293
+ });
3294
+ if (qb.state.limit) {
3295
+ resultQuery.limit(qb.state.limit);
3296
+ }
3297
+ if (qb.state.offset) {
3298
+ resultQuery.offset(qb.state.offset);
3299
+ }
3300
+ if (qb.state.first) {
3301
+ resultQuery.first();
3302
+ }
3303
+ resultQuery.orderBy([
3304
+ // Transform "order by" clause to their T alias and prefix them with T alias
3305
+ ...orderBy.map((orderByClause) => ({
3306
+ column: prefix(partitionedQueryAlias, getStrapiOrderColumnAlias(orderByClause.column)),
3307
+ order: orderByClause.order
3308
+ })),
3309
+ // Add T.id to the order by clause to get consistent results in case several rows have the exact same order
3310
+ { column: `${partitionedQueryAlias}.id`, order: "asc" }
3311
+ ]);
3312
+ return resultQuery;
3313
+ };
3314
+ const alias = _.curry((alias2, value) => `${value} as ${alias2}`);
3315
+ const prefix = _.curry((prefix2, value) => `${prefix2}.${value}`);
3316
+ const joinColPrefix = "__strapi";
3183
3317
  const XtoOne = async (input, ctx) => {
3184
3318
  const { attribute, attributeName, results, populateValue, targetMeta, isCount } = input;
3185
3319
  const { db, qb } = ctx;
@@ -3206,8 +3340,10 @@ const XtoOne = async (input, ctx) => {
3206
3340
  const { joinTable } = attribute;
3207
3341
  const qb2 = db.entityManager.createQueryBuilder(targetMeta.uid);
3208
3342
  const { name: joinColumnName, referencedColumn: referencedColumnName } = joinTable.joinColumn;
3209
- const alias = qb2.getAlias();
3210
- const joinColAlias = `${alias}.${joinColumnName}`;
3343
+ const alias2 = qb2.getAlias();
3344
+ const joinColAlias = `${alias2}.${joinColumnName}`;
3345
+ const joinColRenameAs = `${joinColPrefix}${joinColumnName}`;
3346
+ const joinColSelect = `${joinColAlias} as ${joinColRenameAs}`;
3211
3347
  const referencedValues = _.uniq(
3212
3348
  results.map((r) => r[referencedColumnName]).filter((value) => !_.isNil(value))
3213
3349
  );
@@ -3219,7 +3355,7 @@ const XtoOne = async (input, ctx) => {
3219
3355
  return;
3220
3356
  }
3221
3357
  const rows2 = await qb2.init(populateValue).join({
3222
- alias,
3358
+ alias: alias2,
3223
3359
  referencedTable: joinTable.name,
3224
3360
  referencedColumn: joinTable.inverseJoinColumn.name,
3225
3361
  rootColumn: joinTable.inverseJoinColumn.referencedColumn,
@@ -3245,15 +3381,15 @@ const XtoOne = async (input, ctx) => {
3245
3381
  return;
3246
3382
  }
3247
3383
  const rows = await qb2.init(populateValue).join({
3248
- alias,
3384
+ alias: alias2,
3249
3385
  referencedTable: joinTable.name,
3250
3386
  referencedColumn: joinTable.inverseJoinColumn.name,
3251
3387
  rootColumn: joinTable.inverseJoinColumn.referencedColumn,
3252
3388
  rootTable: qb2.alias,
3253
3389
  on: joinTable.on,
3254
3390
  orderBy: joinTable.orderBy
3255
- }).addSelect(joinColAlias).where({ [joinColAlias]: referencedValues }).execute({ mapResults: false });
3256
- const map2 = _.groupBy(joinColumnName)(rows);
3391
+ }).addSelect(joinColSelect).where({ [joinColAlias]: referencedValues }).execute({ mapResults: false });
3392
+ const map2 = _.groupBy(joinColRenameAs)(rows);
3257
3393
  results.forEach((result) => {
3258
3394
  result[attributeName] = fromTargetRow(_.first(map2[result[referencedColumnName]]));
3259
3395
  });
@@ -3285,8 +3421,10 @@ const oneToMany = async (input, ctx) => {
3285
3421
  const { joinTable } = attribute;
3286
3422
  const qb2 = db.entityManager.createQueryBuilder(targetMeta.uid);
3287
3423
  const { name: joinColumnName, referencedColumn: referencedColumnName } = joinTable.joinColumn;
3288
- const alias = qb2.getAlias();
3289
- const joinColAlias = `${alias}.${joinColumnName}`;
3424
+ const alias2 = qb2.getAlias();
3425
+ const joinColAlias = `${alias2}.${joinColumnName}`;
3426
+ const joinColRenameAs = `${joinColPrefix}${joinColumnName}`;
3427
+ const joinColSelect = `${joinColAlias} as ${joinColRenameAs}`;
3290
3428
  const referencedValues = _.uniq(
3291
3429
  results.map((r) => r[referencedColumnName]).filter((value) => !_.isNil(value))
3292
3430
  );
@@ -3298,16 +3436,16 @@ const oneToMany = async (input, ctx) => {
3298
3436
  return;
3299
3437
  }
3300
3438
  const rows2 = await qb2.init(populateValue).join({
3301
- alias,
3439
+ alias: alias2,
3302
3440
  referencedTable: joinTable.name,
3303
3441
  referencedColumn: joinTable.inverseJoinColumn.name,
3304
3442
  rootColumn: joinTable.inverseJoinColumn.referencedColumn,
3305
3443
  rootTable: qb2.alias,
3306
3444
  on: joinTable.on
3307
- }).select([joinColAlias, qb2.raw("count(*) AS count")]).where({ [joinColAlias]: referencedValues }).groupBy(joinColAlias).execute({ mapResults: false });
3445
+ }).select([joinColSelect, qb2.raw("count(*) AS count")]).where({ [joinColAlias]: referencedValues }).groupBy(joinColAlias).execute({ mapResults: false });
3308
3446
  const map22 = rows2.reduce(
3309
3447
  (map3, row) => {
3310
- map3[row[joinColumnName]] = { count: Number(row.count) };
3448
+ map3[row[joinColRenameAs]] = { count: Number(row.count) };
3311
3449
  return map3;
3312
3450
  },
3313
3451
  {}
@@ -3324,15 +3462,15 @@ const oneToMany = async (input, ctx) => {
3324
3462
  return;
3325
3463
  }
3326
3464
  const rows = await qb2.init(populateValue).join({
3327
- alias,
3465
+ alias: alias2,
3328
3466
  referencedTable: joinTable.name,
3329
3467
  referencedColumn: joinTable.inverseJoinColumn.name,
3330
3468
  rootColumn: joinTable.inverseJoinColumn.referencedColumn,
3331
3469
  rootTable: qb2.alias,
3332
3470
  on: joinTable.on,
3333
3471
  orderBy: _.mapValues((v) => populateValue.ordering || v, joinTable.orderBy)
3334
- }).addSelect(joinColAlias).where({ [joinColAlias]: referencedValues }).execute({ mapResults: false });
3335
- const map2 = _.groupBy(joinColumnName)(rows);
3472
+ }).addSelect(joinColSelect).where({ [joinColAlias]: referencedValues }).execute({ mapResults: false });
3473
+ const map2 = _.groupBy(joinColRenameAs)(rows);
3336
3474
  results.forEach((r) => {
3337
3475
  r[attributeName] = fromTargetRow(map2[r[referencedColumnName]] || []);
3338
3476
  });
@@ -3345,8 +3483,10 @@ const manyToMany = async (input, ctx) => {
3345
3483
  const { joinTable } = attribute;
3346
3484
  const populateQb = db.entityManager.createQueryBuilder(targetMeta.uid);
3347
3485
  const { name: joinColumnName, referencedColumn: referencedColumnName } = joinTable.joinColumn;
3348
- const alias = populateQb.getAlias();
3349
- const joinColAlias = `${alias}.${joinColumnName}`;
3486
+ const alias2 = populateQb.getAlias();
3487
+ const joinColAlias = `${alias2}.${joinColumnName}`;
3488
+ const joinColRenameAs = `${joinColPrefix}${joinColumnName}`;
3489
+ const joinColSelect = `${joinColAlias} as ${joinColRenameAs}`;
3350
3490
  const referencedValues = _.uniq(
3351
3491
  results.map((r) => r[referencedColumnName]).filter((value) => !_.isNil(value))
3352
3492
  );
@@ -3358,7 +3498,7 @@ const manyToMany = async (input, ctx) => {
3358
3498
  return;
3359
3499
  }
3360
3500
  const rows2 = await populateQb.init(populateValue).join({
3361
- alias,
3501
+ alias: alias2,
3362
3502
  referencedTable: joinTable.name,
3363
3503
  referencedColumn: joinTable.inverseJoinColumn.name,
3364
3504
  rootColumn: joinTable.inverseJoinColumn.referencedColumn,
@@ -3384,15 +3524,15 @@ const manyToMany = async (input, ctx) => {
3384
3524
  return;
3385
3525
  }
3386
3526
  const rows = await populateQb.init(populateValue).join({
3387
- alias,
3527
+ alias: alias2,
3388
3528
  referencedTable: joinTable.name,
3389
3529
  referencedColumn: joinTable.inverseJoinColumn.name,
3390
3530
  rootColumn: joinTable.inverseJoinColumn.referencedColumn,
3391
3531
  rootTable: populateQb.alias,
3392
3532
  on: joinTable.on,
3393
3533
  orderBy: _.mapValues((v) => populateValue.ordering || v, joinTable.orderBy)
3394
- }).addSelect(joinColAlias).where({ [joinColAlias]: referencedValues }).execute({ mapResults: false });
3395
- const map2 = _.groupBy(joinColumnName)(rows);
3534
+ }).addSelect(joinColSelect).where({ [joinColAlias]: referencedValues }).execute({ mapResults: false });
3535
+ const map2 = _.groupBy(joinColRenameAs)(rows);
3396
3536
  results.forEach((result) => {
3397
3537
  result[attributeName] = fromTargetRow(map2[result[referencedColumnName]] || []);
3398
3538
  });
@@ -3435,9 +3575,9 @@ const morphX = async (input, ctx) => {
3435
3575
  return;
3436
3576
  }
3437
3577
  const qb = db.entityManager.createQueryBuilder(target);
3438
- const alias = qb.getAlias();
3578
+ const alias2 = qb.getAlias();
3439
3579
  const rows = await qb.init(populateValue).join({
3440
- alias,
3580
+ alias: alias2,
3441
3581
  referencedTable: joinTable.name,
3442
3582
  referencedColumn: joinColumn.name,
3443
3583
  rootColumn: joinColumn.referencedColumn,
@@ -3447,9 +3587,9 @@ const morphX = async (input, ctx) => {
3447
3587
  field: attributeName
3448
3588
  },
3449
3589
  orderBy: _.mapValues((v) => populateValue.ordering || v, joinTable.orderBy)
3450
- }).addSelect([`${alias}.${idColumn.name}`, `${alias}.${typeColumn.name}`]).where({
3451
- [`${alias}.${idColumn.name}`]: referencedValues,
3452
- [`${alias}.${typeColumn.name}`]: uid
3590
+ }).addSelect([`${alias2}.${idColumn.name}`, `${alias2}.${typeColumn.name}`]).where({
3591
+ [`${alias2}.${idColumn.name}`]: referencedValues,
3592
+ [`${alias2}.${typeColumn.name}`]: uid
3453
3593
  }).execute({ mapResults: false });
3454
3594
  const map2 = _.groupBy(idColumn.name)(rows);
3455
3595
  results.forEach((result) => {
@@ -3698,13 +3838,6 @@ const processPopulate = (populate, ctx) => {
3698
3838
  }
3699
3839
  return finalPopulate;
3700
3840
  };
3701
- function isKnexQuery(value) {
3702
- return value instanceof KnexBuilder || value instanceof KnexRaw;
3703
- }
3704
- const addSchema = (db, tableName) => {
3705
- const schemaName = db.getSchemaName();
3706
- return schemaName ? `${schemaName}.${tableName}` : tableName;
3707
- };
3708
3841
  const isRecord$1 = (value) => isPlainObject(value);
3709
3842
  const castValue = (value, attribute) => {
3710
3843
  if (!attribute) {
@@ -3746,8 +3879,8 @@ const processNested = (where, ctx) => {
3746
3879
  return processWhere(where, ctx);
3747
3880
  };
3748
3881
  const processRelationWhere = (where, ctx) => {
3749
- const { qb, alias } = ctx;
3750
- const idAlias = qb.aliasColumn("id", alias);
3882
+ const { qb, alias: alias2 } = ctx;
3883
+ const idAlias = qb.aliasColumn("id", alias2);
3751
3884
  if (!isRecord$1(where)) {
3752
3885
  return { [idAlias]: where };
3753
3886
  }
@@ -3763,6 +3896,9 @@ const processRelationWhere = (where, ctx) => {
3763
3896
  }
3764
3897
  if (operatorKeys.length === 1) {
3765
3898
  const operator = operatorKeys[0];
3899
+ if (isOperatorOfType("group", operator)) {
3900
+ return processWhere(where, ctx);
3901
+ }
3766
3902
  return { [idAlias]: { [operator]: processNested(where[operator], ctx) } };
3767
3903
  }
3768
3904
  return processWhere(where, ctx);
@@ -3774,7 +3910,7 @@ function processWhere(where, ctx) {
3774
3910
  if (isArray(where)) {
3775
3911
  return where.map((sub) => processWhere(sub, ctx));
3776
3912
  }
3777
- const { db, uid, qb, alias } = ctx;
3913
+ const { db, uid, qb, alias: alias2 } = ctx;
3778
3914
  const meta = db.metadata.get(uid);
3779
3915
  const filters = {};
3780
3916
  for (const key of Object.keys(where)) {
@@ -3797,12 +3933,12 @@ function processWhere(where, ctx) {
3797
3933
  }
3798
3934
  const attribute = meta.attributes[key];
3799
3935
  if (!attribute) {
3800
- filters[qb.aliasColumn(key, alias)] = processAttributeWhere(null, value);
3936
+ filters[qb.aliasColumn(key, alias2)] = processAttributeWhere(null, value);
3801
3937
  continue;
3802
3938
  }
3803
3939
  if (isRelation(attribute.type) && "target" in attribute) {
3804
3940
  const subAlias = createJoin(ctx, {
3805
- alias: alias || qb.alias,
3941
+ alias: alias2 || qb.alias,
3806
3942
  attributeName: key,
3807
3943
  attribute
3808
3944
  });
@@ -3817,7 +3953,7 @@ function processWhere(where, ctx) {
3817
3953
  }
3818
3954
  if (isScalar(attribute.type)) {
3819
3955
  const columnName = toColumnName(meta, key);
3820
- const aliasedColumnName = qb.aliasColumn(columnName, alias);
3956
+ const aliasedColumnName = qb.aliasColumn(columnName, alias2);
3821
3957
  filters[aliasedColumnName] = processAttributeWhere(attribute, value);
3822
3958
  continue;
3823
3959
  }
@@ -4045,7 +4181,7 @@ class ReadableStrapiQuery extends Readable {
4045
4181
  * Custom ._read() implementation
4046
4182
  *
4047
4183
  * NOTE: Here "size" means the number of entities to be read from the database.
4048
- * Not the actual byte size, as it would means that we need to return partial entities.
4184
+ * Not the actual byte size, as it would mean that we need to return partial entities.
4049
4185
  *
4050
4186
  */
4051
4187
  async _read(size) {
@@ -4108,61 +4244,6 @@ class ReadableStrapiQuery extends Readable {
4108
4244
  }
4109
4245
  }
4110
4246
  }
4111
- const storage = new AsyncLocalStorage();
4112
- const transactionCtx = {
4113
- async run(trx, cb) {
4114
- const store = storage.getStore();
4115
- return storage.run(
4116
- {
4117
- trx,
4118
- // Fill with existing callbacks if nesting transactions
4119
- commitCallbacks: store?.commitCallbacks || [],
4120
- rollbackCallbacks: store?.rollbackCallbacks || []
4121
- },
4122
- cb
4123
- );
4124
- },
4125
- get() {
4126
- const store = storage.getStore();
4127
- return store?.trx;
4128
- },
4129
- async commit(trx) {
4130
- const store = storage.getStore();
4131
- if (store?.trx) {
4132
- store.trx = null;
4133
- }
4134
- await trx.commit();
4135
- if (!store?.commitCallbacks.length) {
4136
- return;
4137
- }
4138
- store.commitCallbacks.forEach((cb) => cb());
4139
- store.commitCallbacks = [];
4140
- },
4141
- async rollback(trx) {
4142
- const store = storage.getStore();
4143
- if (store?.trx) {
4144
- store.trx = null;
4145
- }
4146
- await trx.rollback();
4147
- if (!store?.rollbackCallbacks.length) {
4148
- return;
4149
- }
4150
- store.rollbackCallbacks.forEach((cb) => cb());
4151
- store.rollbackCallbacks = [];
4152
- },
4153
- onCommit(cb) {
4154
- const store = storage.getStore();
4155
- if (store?.commitCallbacks) {
4156
- store.commitCallbacks.push(cb);
4157
- }
4158
- },
4159
- onRollback(cb) {
4160
- const store = storage.getStore();
4161
- if (store?.rollbackCallbacks) {
4162
- store.rollbackCallbacks.push(cb);
4163
- }
4164
- }
4165
- };
4166
4247
  const createQueryBuilder = (uid, db, initialState = {}) => {
4167
4248
  const meta = db.metadata.get(uid);
4168
4249
  const { tableName } = meta;
@@ -4195,9 +4276,9 @@ const createQueryBuilder = (uid, db, initialState = {}) => {
4195
4276
  initialState
4196
4277
  );
4197
4278
  const getAlias = () => {
4198
- const alias = `t${state.aliasCounter}`;
4279
+ const alias2 = `t${state.aliasCounter}`;
4199
4280
  state.aliasCounter += 1;
4200
- return alias;
4281
+ return alias2;
4201
4282
  };
4202
4283
  return {
4203
4284
  alias: getAlias(),
@@ -4364,15 +4445,15 @@ const createQueryBuilder = (uid, db, initialState = {}) => {
4364
4445
  mustUseAlias() {
4365
4446
  return ["select", "count"].includes(state.type);
4366
4447
  },
4367
- aliasColumn(key, alias) {
4448
+ aliasColumn(key, alias2) {
4368
4449
  if (typeof key !== "string") {
4369
4450
  return key;
4370
4451
  }
4371
4452
  if (key.indexOf(".") >= 0) {
4372
4453
  return key;
4373
4454
  }
4374
- if (!_.isNil(alias)) {
4375
- return `${alias}.${key}`;
4455
+ if (!_.isNil(alias2)) {
4456
+ return `${alias2}.${key}`;
4376
4457
  }
4377
4458
  return this.mustUseAlias() ? `${this.alias}.${key}` : key;
4378
4459
  },
@@ -4407,6 +4488,20 @@ const createQueryBuilder = (uid, db, initialState = {}) => {
4407
4488
  shouldUseDistinct() {
4408
4489
  return state.joins.length > 0 && _.isEmpty(state.groupBy);
4409
4490
  },
4491
+ shouldUseDeepSort() {
4492
+ return state.orderBy.filter(({ column }) => column.indexOf(".") >= 0).filter(({ column }) => {
4493
+ const col = column.split(".");
4494
+ for (let i = 0; i < col.length - 1; i += 1) {
4495
+ const el = col[i];
4496
+ const isRelationAttribute = meta.attributes[el]?.type === "relation";
4497
+ const isAliasedRelation = Object.values(state.joins).map((join) => join.alias).includes(el);
4498
+ if (isRelationAttribute || isAliasedRelation) {
4499
+ return true;
4500
+ }
4501
+ }
4502
+ return false;
4503
+ }).length > 0;
4504
+ },
4410
4505
  processSelect() {
4411
4506
  state.select = state.select.map((field) => {
4412
4507
  if (isKnexQuery(field)) {
@@ -4524,6 +4619,9 @@ const createQueryBuilder = (uid, db, initialState = {}) => {
4524
4619
  if (state.joins.length > 0) {
4525
4620
  applyJoins(qb, state.joins);
4526
4621
  }
4622
+ if (this.shouldUseDeepSort()) {
4623
+ return wrapWithDeepSort(qb, { qb: this, db, uid });
4624
+ }
4527
4625
  return qb;
4528
4626
  },
4529
4627
  async execute({ mapResults = true } = {}) {
@@ -4725,7 +4823,7 @@ const deleteRelatedMorphOneRelationsAfterMorphToManyUpdate = async (rows, {
4725
4823
  await createQueryBuilder(joinTable.name, db).delete().where({ $or: orWhere }).transacting(trx).execute();
4726
4824
  }
4727
4825
  };
4728
- const getDocumentSiblingIdsQuery = (con, tableName, id) => {
4826
+ const getDocumentSiblingIdsQuery = (tableName, id) => {
4729
4827
  const models = Array.from(strapi.db.metadata.values());
4730
4828
  const isContentType = models.find((model) => {
4731
4829
  return model.tableName === tableName && model.attributes.documentId;
@@ -4733,10 +4831,11 @@ const getDocumentSiblingIdsQuery = (con, tableName, id) => {
4733
4831
  if (!isContentType) {
4734
4832
  return [id];
4735
4833
  }
4736
- return con.from(tableName).select("id").where(
4737
- "document_id",
4738
- con.from(tableName).select("document_id").where("id", id)
4739
- );
4834
+ return function(query) {
4835
+ query.select("id").from(tableName).whereIn("document_id", (documentIDSubQuery) => {
4836
+ documentIDSubQuery.from(tableName).select("document_id").where("id", id);
4837
+ });
4838
+ };
4740
4839
  };
4741
4840
  const deletePreviousOneToAnyRelations = async ({
4742
4841
  id,
@@ -4753,7 +4852,7 @@ const deletePreviousOneToAnyRelations = async ({
4753
4852
  const { joinTable } = attribute;
4754
4853
  const { joinColumn, inverseJoinColumn } = joinTable;
4755
4854
  const con = db.getConnection();
4756
- await con.delete().from(joinTable.name).whereNotIn(joinColumn.name, getDocumentSiblingIdsQuery(con, joinColumn.referencedTable, id)).whereIn(inverseJoinColumn.name, relIdsToadd).where(joinTable.on || {}).transacting(trx);
4855
+ await con.delete().from(joinTable.name).whereNotIn(joinColumn.name, getDocumentSiblingIdsQuery(joinColumn.referencedTable, id)).whereIn(inverseJoinColumn.name, relIdsToadd).where(joinTable.on || {}).transacting(trx);
4757
4856
  await cleanOrderColumns({ attribute, db, inverseRelIds: relIdsToadd, transaction: trx });
4758
4857
  };
4759
4858
  const deletePreviousAnyToOneRelations = async ({
@@ -4772,7 +4871,7 @@ const deletePreviousAnyToOneRelations = async ({
4772
4871
  if (isManyToAny(attribute)) {
4773
4872
  const relsToDelete = await con.select(inverseJoinColumn.name).from(joinTable.name).where(joinColumn.name, id).whereNotIn(
4774
4873
  inverseJoinColumn.name,
4775
- getDocumentSiblingIdsQuery(con, inverseJoinColumn.referencedTable, relIdToadd)
4874
+ getDocumentSiblingIdsQuery(inverseJoinColumn.referencedTable, relIdToadd)
4776
4875
  ).where(joinTable.on || {}).transacting(trx);
4777
4876
  const relIdsToDelete = map(inverseJoinColumn.name, relsToDelete);
4778
4877
  await createQueryBuilder(joinTable.name, db).delete().where({
@@ -4783,7 +4882,7 @@ const deletePreviousAnyToOneRelations = async ({
4783
4882
  } else {
4784
4883
  await con.delete().from(joinTable.name).where(joinColumn.name, id).whereNotIn(
4785
4884
  inverseJoinColumn.name,
4786
- getDocumentSiblingIdsQuery(con, inverseJoinColumn.referencedTable, relIdToadd)
4885
+ getDocumentSiblingIdsQuery(inverseJoinColumn.referencedTable, relIdToadd)
4787
4886
  ).where(joinTable.on || {}).transacting(trx);
4788
4887
  }
4789
4888
  };
@@ -5867,6 +5966,21 @@ const createStorage = (opts) => {
5867
5966
  const wrapTransaction = (db) => (fn) => () => {
5868
5967
  return db.transaction(({ trx }) => Promise.resolve(fn(trx, db)));
5869
5968
  };
5969
+ const transformLogMessage = (level, message) => {
5970
+ if (typeof message === "string") {
5971
+ return { level, message };
5972
+ }
5973
+ if (typeof message === "object" && message !== null) {
5974
+ if ("event" in message && "name" in message) {
5975
+ return {
5976
+ level,
5977
+ message: `[internal migration]: ${message.event} ${message?.name}`,
5978
+ timestamp: Date.now()
5979
+ };
5980
+ }
5981
+ }
5982
+ return "";
5983
+ };
5870
5984
  const migrationResolver = ({ name, path: path2, context }) => {
5871
5985
  const { db } = context;
5872
5986
  if (!path2) {
@@ -5895,7 +6009,20 @@ const createUserMigrationProvider = (db) => {
5895
6009
  const context = { db };
5896
6010
  const umzugProvider = new Umzug({
5897
6011
  storage: createStorage({ db, tableName: "strapi_migrations" }),
5898
- logger: console,
6012
+ logger: {
6013
+ info(message) {
6014
+ db.logger.info(transformLogMessage("info", message));
6015
+ },
6016
+ warn(message) {
6017
+ db.logger.warn(transformLogMessage("warn", message));
6018
+ },
6019
+ error(message) {
6020
+ db.logger.error(transformLogMessage("error", message));
6021
+ },
6022
+ debug(message) {
6023
+ db.logger.debug(transformLogMessage("debug", message));
6024
+ }
6025
+ },
5899
6026
  context,
5900
6027
  migrations: {
5901
6028
  glob: ["*.{js,sql}", { cwd: dir }],
@@ -6183,16 +6310,115 @@ const findDiffs = (shortMap) => {
6183
6310
  });
6184
6311
  return diffs;
6185
6312
  };
6313
+ const createLocaleColumn = async (db, tableName) => {
6314
+ await db.schema.alterTable(tableName, (table) => {
6315
+ table.string("locale");
6316
+ });
6317
+ };
6318
+ const createdLocale = {
6319
+ name: "5.0.0-03-created-locale",
6320
+ async up(knex2, db) {
6321
+ for (const meta of db.metadata.values()) {
6322
+ const hasTable = await knex2.schema.hasTable(meta.tableName);
6323
+ if (!hasTable) {
6324
+ continue;
6325
+ }
6326
+ const uid = meta.uid;
6327
+ const model = strapi.getModel(uid);
6328
+ if (!model) {
6329
+ continue;
6330
+ }
6331
+ const hasLocaleColumn = await knex2.schema.hasColumn(meta.tableName, "locale");
6332
+ if (meta.attributes.locale && !hasLocaleColumn) {
6333
+ await createLocaleColumn(knex2, meta.tableName);
6334
+ }
6335
+ }
6336
+ },
6337
+ async down() {
6338
+ throw new Error("not implemented");
6339
+ }
6340
+ };
6341
+ const createPublishedAtColumn = async (db, tableName) => {
6342
+ await db.schema.alterTable(tableName, (table) => {
6343
+ table.string("published_at");
6344
+ });
6345
+ };
6346
+ const createdPublishedAt = {
6347
+ name: "5.0.0-04-created-published-at",
6348
+ async up(knex2, db) {
6349
+ for (const meta of db.metadata.values()) {
6350
+ const hasTable = await knex2.schema.hasTable(meta.tableName);
6351
+ if (!hasTable) {
6352
+ continue;
6353
+ }
6354
+ const uid = meta.uid;
6355
+ const model = strapi.getModel(uid);
6356
+ if (!model) {
6357
+ continue;
6358
+ }
6359
+ const hasPublishedAtColumn = await knex2.schema.hasColumn(meta.tableName, "published_at");
6360
+ if (meta.attributes.publishedAt && !hasPublishedAtColumn) {
6361
+ await createPublishedAtColumn(knex2, meta.tableName);
6362
+ }
6363
+ }
6364
+ },
6365
+ async down() {
6366
+ throw new Error("not implemented");
6367
+ }
6368
+ };
6369
+ const dropIndex = async (knex2, tableName, columnName) => {
6370
+ try {
6371
+ await knex2.schema.alterTable(tableName, (table) => {
6372
+ table.dropUnique([columnName], `${tableName}_${columnName}_unique`);
6373
+ });
6374
+ } catch (error) {
6375
+ }
6376
+ };
6377
+ const dropSlugFieldsIndex = {
6378
+ name: "5.0.0-05-drop-slug-fields-index",
6379
+ async up(knex2, db) {
6380
+ for (const meta of db.metadata.values()) {
6381
+ const hasTable = await knex2.schema.hasTable(meta.tableName);
6382
+ if (!hasTable) {
6383
+ continue;
6384
+ }
6385
+ for (const attribute of Object.values(meta.attributes)) {
6386
+ if (attribute.type === "uid" && attribute.columnName) {
6387
+ await dropIndex(knex2, meta.tableName, attribute.columnName);
6388
+ }
6389
+ }
6390
+ }
6391
+ },
6392
+ async down() {
6393
+ throw new Error("not implemented");
6394
+ }
6395
+ };
6186
6396
  const internalMigrations = [
6187
6397
  renameIdentifiersLongerThanMaxLength,
6188
- createdDocumentId
6398
+ createdDocumentId,
6399
+ createdLocale,
6400
+ createdPublishedAt,
6401
+ dropSlugFieldsIndex
6189
6402
  ];
6190
6403
  const createInternalMigrationProvider = (db) => {
6191
6404
  const context = { db };
6192
6405
  const migrations = [...internalMigrations];
6193
6406
  const umzugProvider = new Umzug({
6194
6407
  storage: createStorage({ db, tableName: "strapi_migrations_internal" }),
6195
- logger: console,
6408
+ logger: {
6409
+ info(message) {
6410
+ db.logger.debug(transformLogMessage("info", message));
6411
+ },
6412
+ warn(message) {
6413
+ db.logger.warn(transformLogMessage("warn", message));
6414
+ },
6415
+ error(message) {
6416
+ db.logger.error(transformLogMessage("error", message));
6417
+ },
6418
+ debug(message) {
6419
+ db.logger.debug(transformLogMessage("debug", message));
6420
+ }
6421
+ },
6196
6422
  context,
6197
6423
  migrations: () => migrations.map((migration) => {
6198
6424
  return {
@@ -6463,6 +6689,7 @@ class Database {
6463
6689
  migrations;
6464
6690
  lifecycles;
6465
6691
  entityManager;
6692
+ logger;
6466
6693
  constructor(config) {
6467
6694
  this.config = {
6468
6695
  ...config,
@@ -6482,6 +6709,7 @@ class Database {
6482
6709
  this.migrations = createMigrationsProvider(this);
6483
6710
  this.lifecycles = createLifecyclesProvider(this);
6484
6711
  this.entityManager = createEntityManager(this);
6712
+ this.logger = config.logger ?? console;
6485
6713
  }
6486
6714
  async init({ models }) {
6487
6715
  this.metadata.loadModels(models);