@strapi/database 5.0.0-beta.9 → 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,75 @@ 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}`);
3183
3316
  const joinColPrefix = "__strapi";
3184
3317
  const XtoOne = async (input, ctx) => {
3185
3318
  const { attribute, attributeName, results, populateValue, targetMeta, isCount } = input;
@@ -3207,8 +3340,8 @@ const XtoOne = async (input, ctx) => {
3207
3340
  const { joinTable } = attribute;
3208
3341
  const qb2 = db.entityManager.createQueryBuilder(targetMeta.uid);
3209
3342
  const { name: joinColumnName, referencedColumn: referencedColumnName } = joinTable.joinColumn;
3210
- const alias = qb2.getAlias();
3211
- const joinColAlias = `${alias}.${joinColumnName}`;
3343
+ const alias2 = qb2.getAlias();
3344
+ const joinColAlias = `${alias2}.${joinColumnName}`;
3212
3345
  const joinColRenameAs = `${joinColPrefix}${joinColumnName}`;
3213
3346
  const joinColSelect = `${joinColAlias} as ${joinColRenameAs}`;
3214
3347
  const referencedValues = _.uniq(
@@ -3222,7 +3355,7 @@ const XtoOne = async (input, ctx) => {
3222
3355
  return;
3223
3356
  }
3224
3357
  const rows2 = await qb2.init(populateValue).join({
3225
- alias,
3358
+ alias: alias2,
3226
3359
  referencedTable: joinTable.name,
3227
3360
  referencedColumn: joinTable.inverseJoinColumn.name,
3228
3361
  rootColumn: joinTable.inverseJoinColumn.referencedColumn,
@@ -3248,7 +3381,7 @@ const XtoOne = async (input, ctx) => {
3248
3381
  return;
3249
3382
  }
3250
3383
  const rows = await qb2.init(populateValue).join({
3251
- alias,
3384
+ alias: alias2,
3252
3385
  referencedTable: joinTable.name,
3253
3386
  referencedColumn: joinTable.inverseJoinColumn.name,
3254
3387
  rootColumn: joinTable.inverseJoinColumn.referencedColumn,
@@ -3288,8 +3421,8 @@ const oneToMany = async (input, ctx) => {
3288
3421
  const { joinTable } = attribute;
3289
3422
  const qb2 = db.entityManager.createQueryBuilder(targetMeta.uid);
3290
3423
  const { name: joinColumnName, referencedColumn: referencedColumnName } = joinTable.joinColumn;
3291
- const alias = qb2.getAlias();
3292
- const joinColAlias = `${alias}.${joinColumnName}`;
3424
+ const alias2 = qb2.getAlias();
3425
+ const joinColAlias = `${alias2}.${joinColumnName}`;
3293
3426
  const joinColRenameAs = `${joinColPrefix}${joinColumnName}`;
3294
3427
  const joinColSelect = `${joinColAlias} as ${joinColRenameAs}`;
3295
3428
  const referencedValues = _.uniq(
@@ -3303,7 +3436,7 @@ const oneToMany = async (input, ctx) => {
3303
3436
  return;
3304
3437
  }
3305
3438
  const rows2 = await qb2.init(populateValue).join({
3306
- alias,
3439
+ alias: alias2,
3307
3440
  referencedTable: joinTable.name,
3308
3441
  referencedColumn: joinTable.inverseJoinColumn.name,
3309
3442
  rootColumn: joinTable.inverseJoinColumn.referencedColumn,
@@ -3329,7 +3462,7 @@ const oneToMany = async (input, ctx) => {
3329
3462
  return;
3330
3463
  }
3331
3464
  const rows = await qb2.init(populateValue).join({
3332
- alias,
3465
+ alias: alias2,
3333
3466
  referencedTable: joinTable.name,
3334
3467
  referencedColumn: joinTable.inverseJoinColumn.name,
3335
3468
  rootColumn: joinTable.inverseJoinColumn.referencedColumn,
@@ -3350,8 +3483,8 @@ const manyToMany = async (input, ctx) => {
3350
3483
  const { joinTable } = attribute;
3351
3484
  const populateQb = db.entityManager.createQueryBuilder(targetMeta.uid);
3352
3485
  const { name: joinColumnName, referencedColumn: referencedColumnName } = joinTable.joinColumn;
3353
- const alias = populateQb.getAlias();
3354
- const joinColAlias = `${alias}.${joinColumnName}`;
3486
+ const alias2 = populateQb.getAlias();
3487
+ const joinColAlias = `${alias2}.${joinColumnName}`;
3355
3488
  const joinColRenameAs = `${joinColPrefix}${joinColumnName}`;
3356
3489
  const joinColSelect = `${joinColAlias} as ${joinColRenameAs}`;
3357
3490
  const referencedValues = _.uniq(
@@ -3365,7 +3498,7 @@ const manyToMany = async (input, ctx) => {
3365
3498
  return;
3366
3499
  }
3367
3500
  const rows2 = await populateQb.init(populateValue).join({
3368
- alias,
3501
+ alias: alias2,
3369
3502
  referencedTable: joinTable.name,
3370
3503
  referencedColumn: joinTable.inverseJoinColumn.name,
3371
3504
  rootColumn: joinTable.inverseJoinColumn.referencedColumn,
@@ -3391,7 +3524,7 @@ const manyToMany = async (input, ctx) => {
3391
3524
  return;
3392
3525
  }
3393
3526
  const rows = await populateQb.init(populateValue).join({
3394
- alias,
3527
+ alias: alias2,
3395
3528
  referencedTable: joinTable.name,
3396
3529
  referencedColumn: joinTable.inverseJoinColumn.name,
3397
3530
  rootColumn: joinTable.inverseJoinColumn.referencedColumn,
@@ -3442,9 +3575,9 @@ const morphX = async (input, ctx) => {
3442
3575
  return;
3443
3576
  }
3444
3577
  const qb = db.entityManager.createQueryBuilder(target);
3445
- const alias = qb.getAlias();
3578
+ const alias2 = qb.getAlias();
3446
3579
  const rows = await qb.init(populateValue).join({
3447
- alias,
3580
+ alias: alias2,
3448
3581
  referencedTable: joinTable.name,
3449
3582
  referencedColumn: joinColumn.name,
3450
3583
  rootColumn: joinColumn.referencedColumn,
@@ -3454,9 +3587,9 @@ const morphX = async (input, ctx) => {
3454
3587
  field: attributeName
3455
3588
  },
3456
3589
  orderBy: _.mapValues((v) => populateValue.ordering || v, joinTable.orderBy)
3457
- }).addSelect([`${alias}.${idColumn.name}`, `${alias}.${typeColumn.name}`]).where({
3458
- [`${alias}.${idColumn.name}`]: referencedValues,
3459
- [`${alias}.${typeColumn.name}`]: uid
3590
+ }).addSelect([`${alias2}.${idColumn.name}`, `${alias2}.${typeColumn.name}`]).where({
3591
+ [`${alias2}.${idColumn.name}`]: referencedValues,
3592
+ [`${alias2}.${typeColumn.name}`]: uid
3460
3593
  }).execute({ mapResults: false });
3461
3594
  const map2 = _.groupBy(idColumn.name)(rows);
3462
3595
  results.forEach((result) => {
@@ -3705,13 +3838,6 @@ const processPopulate = (populate, ctx) => {
3705
3838
  }
3706
3839
  return finalPopulate;
3707
3840
  };
3708
- function isKnexQuery(value) {
3709
- return value instanceof KnexBuilder || value instanceof KnexRaw;
3710
- }
3711
- const addSchema = (db, tableName) => {
3712
- const schemaName = db.getSchemaName();
3713
- return schemaName ? `${schemaName}.${tableName}` : tableName;
3714
- };
3715
3841
  const isRecord$1 = (value) => isPlainObject(value);
3716
3842
  const castValue = (value, attribute) => {
3717
3843
  if (!attribute) {
@@ -3753,8 +3879,8 @@ const processNested = (where, ctx) => {
3753
3879
  return processWhere(where, ctx);
3754
3880
  };
3755
3881
  const processRelationWhere = (where, ctx) => {
3756
- const { qb, alias } = ctx;
3757
- const idAlias = qb.aliasColumn("id", alias);
3882
+ const { qb, alias: alias2 } = ctx;
3883
+ const idAlias = qb.aliasColumn("id", alias2);
3758
3884
  if (!isRecord$1(where)) {
3759
3885
  return { [idAlias]: where };
3760
3886
  }
@@ -3784,7 +3910,7 @@ function processWhere(where, ctx) {
3784
3910
  if (isArray(where)) {
3785
3911
  return where.map((sub) => processWhere(sub, ctx));
3786
3912
  }
3787
- const { db, uid, qb, alias } = ctx;
3913
+ const { db, uid, qb, alias: alias2 } = ctx;
3788
3914
  const meta = db.metadata.get(uid);
3789
3915
  const filters = {};
3790
3916
  for (const key of Object.keys(where)) {
@@ -3807,12 +3933,12 @@ function processWhere(where, ctx) {
3807
3933
  }
3808
3934
  const attribute = meta.attributes[key];
3809
3935
  if (!attribute) {
3810
- filters[qb.aliasColumn(key, alias)] = processAttributeWhere(null, value);
3936
+ filters[qb.aliasColumn(key, alias2)] = processAttributeWhere(null, value);
3811
3937
  continue;
3812
3938
  }
3813
3939
  if (isRelation(attribute.type) && "target" in attribute) {
3814
3940
  const subAlias = createJoin(ctx, {
3815
- alias: alias || qb.alias,
3941
+ alias: alias2 || qb.alias,
3816
3942
  attributeName: key,
3817
3943
  attribute
3818
3944
  });
@@ -3827,7 +3953,7 @@ function processWhere(where, ctx) {
3827
3953
  }
3828
3954
  if (isScalar(attribute.type)) {
3829
3955
  const columnName = toColumnName(meta, key);
3830
- const aliasedColumnName = qb.aliasColumn(columnName, alias);
3956
+ const aliasedColumnName = qb.aliasColumn(columnName, alias2);
3831
3957
  filters[aliasedColumnName] = processAttributeWhere(attribute, value);
3832
3958
  continue;
3833
3959
  }
@@ -4055,7 +4181,7 @@ class ReadableStrapiQuery extends Readable {
4055
4181
  * Custom ._read() implementation
4056
4182
  *
4057
4183
  * NOTE: Here "size" means the number of entities to be read from the database.
4058
- * 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.
4059
4185
  *
4060
4186
  */
4061
4187
  async _read(size) {
@@ -4118,61 +4244,6 @@ class ReadableStrapiQuery extends Readable {
4118
4244
  }
4119
4245
  }
4120
4246
  }
4121
- const storage = new AsyncLocalStorage();
4122
- const transactionCtx = {
4123
- async run(trx, cb) {
4124
- const store = storage.getStore();
4125
- return storage.run(
4126
- {
4127
- trx,
4128
- // Fill with existing callbacks if nesting transactions
4129
- commitCallbacks: store?.commitCallbacks || [],
4130
- rollbackCallbacks: store?.rollbackCallbacks || []
4131
- },
4132
- cb
4133
- );
4134
- },
4135
- get() {
4136
- const store = storage.getStore();
4137
- return store?.trx;
4138
- },
4139
- async commit(trx) {
4140
- const store = storage.getStore();
4141
- if (store?.trx) {
4142
- store.trx = null;
4143
- }
4144
- await trx.commit();
4145
- if (!store?.commitCallbacks.length) {
4146
- return;
4147
- }
4148
- store.commitCallbacks.forEach((cb) => cb());
4149
- store.commitCallbacks = [];
4150
- },
4151
- async rollback(trx) {
4152
- const store = storage.getStore();
4153
- if (store?.trx) {
4154
- store.trx = null;
4155
- }
4156
- await trx.rollback();
4157
- if (!store?.rollbackCallbacks.length) {
4158
- return;
4159
- }
4160
- store.rollbackCallbacks.forEach((cb) => cb());
4161
- store.rollbackCallbacks = [];
4162
- },
4163
- onCommit(cb) {
4164
- const store = storage.getStore();
4165
- if (store?.commitCallbacks) {
4166
- store.commitCallbacks.push(cb);
4167
- }
4168
- },
4169
- onRollback(cb) {
4170
- const store = storage.getStore();
4171
- if (store?.rollbackCallbacks) {
4172
- store.rollbackCallbacks.push(cb);
4173
- }
4174
- }
4175
- };
4176
4247
  const createQueryBuilder = (uid, db, initialState = {}) => {
4177
4248
  const meta = db.metadata.get(uid);
4178
4249
  const { tableName } = meta;
@@ -4205,9 +4276,9 @@ const createQueryBuilder = (uid, db, initialState = {}) => {
4205
4276
  initialState
4206
4277
  );
4207
4278
  const getAlias = () => {
4208
- const alias = `t${state.aliasCounter}`;
4279
+ const alias2 = `t${state.aliasCounter}`;
4209
4280
  state.aliasCounter += 1;
4210
- return alias;
4281
+ return alias2;
4211
4282
  };
4212
4283
  return {
4213
4284
  alias: getAlias(),
@@ -4374,15 +4445,15 @@ const createQueryBuilder = (uid, db, initialState = {}) => {
4374
4445
  mustUseAlias() {
4375
4446
  return ["select", "count"].includes(state.type);
4376
4447
  },
4377
- aliasColumn(key, alias) {
4448
+ aliasColumn(key, alias2) {
4378
4449
  if (typeof key !== "string") {
4379
4450
  return key;
4380
4451
  }
4381
4452
  if (key.indexOf(".") >= 0) {
4382
4453
  return key;
4383
4454
  }
4384
- if (!_.isNil(alias)) {
4385
- return `${alias}.${key}`;
4455
+ if (!_.isNil(alias2)) {
4456
+ return `${alias2}.${key}`;
4386
4457
  }
4387
4458
  return this.mustUseAlias() ? `${this.alias}.${key}` : key;
4388
4459
  },
@@ -4417,6 +4488,20 @@ const createQueryBuilder = (uid, db, initialState = {}) => {
4417
4488
  shouldUseDistinct() {
4418
4489
  return state.joins.length > 0 && _.isEmpty(state.groupBy);
4419
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
+ },
4420
4505
  processSelect() {
4421
4506
  state.select = state.select.map((field) => {
4422
4507
  if (isKnexQuery(field)) {
@@ -4534,6 +4619,9 @@ const createQueryBuilder = (uid, db, initialState = {}) => {
4534
4619
  if (state.joins.length > 0) {
4535
4620
  applyJoins(qb, state.joins);
4536
4621
  }
4622
+ if (this.shouldUseDeepSort()) {
4623
+ return wrapWithDeepSort(qb, { qb: this, db, uid });
4624
+ }
4537
4625
  return qb;
4538
4626
  },
4539
4627
  async execute({ mapResults = true } = {}) {
@@ -4735,7 +4823,7 @@ const deleteRelatedMorphOneRelationsAfterMorphToManyUpdate = async (rows, {
4735
4823
  await createQueryBuilder(joinTable.name, db).delete().where({ $or: orWhere }).transacting(trx).execute();
4736
4824
  }
4737
4825
  };
4738
- const getDocumentSiblingIdsQuery = (con, tableName, id) => {
4826
+ const getDocumentSiblingIdsQuery = (tableName, id) => {
4739
4827
  const models = Array.from(strapi.db.metadata.values());
4740
4828
  const isContentType = models.find((model) => {
4741
4829
  return model.tableName === tableName && model.attributes.documentId;
@@ -4743,10 +4831,11 @@ const getDocumentSiblingIdsQuery = (con, tableName, id) => {
4743
4831
  if (!isContentType) {
4744
4832
  return [id];
4745
4833
  }
4746
- return con.from(tableName).select("id").where(
4747
- "document_id",
4748
- con.from(tableName).select("document_id").where("id", id)
4749
- );
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
+ };
4750
4839
  };
4751
4840
  const deletePreviousOneToAnyRelations = async ({
4752
4841
  id,
@@ -4763,7 +4852,7 @@ const deletePreviousOneToAnyRelations = async ({
4763
4852
  const { joinTable } = attribute;
4764
4853
  const { joinColumn, inverseJoinColumn } = joinTable;
4765
4854
  const con = db.getConnection();
4766
- 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);
4767
4856
  await cleanOrderColumns({ attribute, db, inverseRelIds: relIdsToadd, transaction: trx });
4768
4857
  };
4769
4858
  const deletePreviousAnyToOneRelations = async ({
@@ -4782,7 +4871,7 @@ const deletePreviousAnyToOneRelations = async ({
4782
4871
  if (isManyToAny(attribute)) {
4783
4872
  const relsToDelete = await con.select(inverseJoinColumn.name).from(joinTable.name).where(joinColumn.name, id).whereNotIn(
4784
4873
  inverseJoinColumn.name,
4785
- getDocumentSiblingIdsQuery(con, inverseJoinColumn.referencedTable, relIdToadd)
4874
+ getDocumentSiblingIdsQuery(inverseJoinColumn.referencedTable, relIdToadd)
4786
4875
  ).where(joinTable.on || {}).transacting(trx);
4787
4876
  const relIdsToDelete = map(inverseJoinColumn.name, relsToDelete);
4788
4877
  await createQueryBuilder(joinTable.name, db).delete().where({
@@ -4793,7 +4882,7 @@ const deletePreviousAnyToOneRelations = async ({
4793
4882
  } else {
4794
4883
  await con.delete().from(joinTable.name).where(joinColumn.name, id).whereNotIn(
4795
4884
  inverseJoinColumn.name,
4796
- getDocumentSiblingIdsQuery(con, inverseJoinColumn.referencedTable, relIdToadd)
4885
+ getDocumentSiblingIdsQuery(inverseJoinColumn.referencedTable, relIdToadd)
4797
4886
  ).where(joinTable.on || {}).transacting(trx);
4798
4887
  }
4799
4888
  };
@@ -5877,6 +5966,21 @@ const createStorage = (opts) => {
5877
5966
  const wrapTransaction = (db) => (fn) => () => {
5878
5967
  return db.transaction(({ trx }) => Promise.resolve(fn(trx, db)));
5879
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
+ };
5880
5984
  const migrationResolver = ({ name, path: path2, context }) => {
5881
5985
  const { db } = context;
5882
5986
  if (!path2) {
@@ -5905,7 +6009,20 @@ const createUserMigrationProvider = (db) => {
5905
6009
  const context = { db };
5906
6010
  const umzugProvider = new Umzug({
5907
6011
  storage: createStorage({ db, tableName: "strapi_migrations" }),
5908
- 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
+ },
5909
6026
  context,
5910
6027
  migrations: {
5911
6028
  glob: ["*.{js,sql}", { cwd: dir }],
@@ -6211,7 +6328,8 @@ const createdLocale = {
6211
6328
  if (!model) {
6212
6329
  continue;
6213
6330
  }
6214
- if (isNil(meta.attributes.locale)) {
6331
+ const hasLocaleColumn = await knex2.schema.hasColumn(meta.tableName, "locale");
6332
+ if (meta.attributes.locale && !hasLocaleColumn) {
6215
6333
  await createLocaleColumn(knex2, meta.tableName);
6216
6334
  }
6217
6335
  }
@@ -6220,17 +6338,87 @@ const createdLocale = {
6220
6338
  throw new Error("not implemented");
6221
6339
  }
6222
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
+ };
6223
6396
  const internalMigrations = [
6224
6397
  renameIdentifiersLongerThanMaxLength,
6225
6398
  createdDocumentId,
6226
- createdLocale
6399
+ createdLocale,
6400
+ createdPublishedAt,
6401
+ dropSlugFieldsIndex
6227
6402
  ];
6228
6403
  const createInternalMigrationProvider = (db) => {
6229
6404
  const context = { db };
6230
6405
  const migrations = [...internalMigrations];
6231
6406
  const umzugProvider = new Umzug({
6232
6407
  storage: createStorage({ db, tableName: "strapi_migrations_internal" }),
6233
- 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
+ },
6234
6422
  context,
6235
6423
  migrations: () => migrations.map((migration) => {
6236
6424
  return {
@@ -6501,6 +6689,7 @@ class Database {
6501
6689
  migrations;
6502
6690
  lifecycles;
6503
6691
  entityManager;
6692
+ logger;
6504
6693
  constructor(config) {
6505
6694
  this.config = {
6506
6695
  ...config,
@@ -6520,6 +6709,7 @@ class Database {
6520
6709
  this.migrations = createMigrationsProvider(this);
6521
6710
  this.lifecycles = createLifecyclesProvider(this);
6522
6711
  this.entityManager = createEntityManager(this);
6712
+ this.logger = config.logger ?? console;
6523
6713
  }
6524
6714
  async init({ models }) {
6525
6715
  this.metadata.loadModels(models);