@lenne.tech/nest-server 12.1.0 → 12.2.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/.claude/rules/architecture.md +1 -0
- package/.claude/rules/better-auth.md +18 -6
- package/.claude/rules/configurable-features.md +5 -4
- package/.claude/rules/package-management.md +16 -1
- package/.claude/rules/persistence.md +26 -0
- package/.claude/rules/testing.md +102 -28
- package/CLAUDE.md +3 -2
- package/FRAMEWORK-API.md +17 -2
- package/README.md +24 -3
- package/dist/core/common/helpers/file.helper.js +3 -4
- package/dist/core/common/helpers/file.helper.js.map +1 -1
- package/dist/core/common/helpers/graphql-ws-connection.helper.js.map +1 -1
- package/dist/core/common/interfaces/server-options.interface.d.ts +41 -0
- package/dist/core/common/interfaces/service-options.interface.d.ts +3 -0
- package/dist/core/common/persistence/core-persistence.module.js +12 -2
- package/dist/core/common/persistence/core-persistence.module.js.map +1 -1
- package/dist/core/common/persistence/data-source.runtime.js +14 -19
- package/dist/core/common/persistence/data-source.runtime.js.map +1 -1
- package/dist/core/common/persistence/data-store.interface.d.ts +24 -9
- package/dist/core/common/persistence/id-format.d.ts +2 -0
- package/dist/core/common/persistence/id-format.js +14 -0
- package/dist/core/common/persistence/id-format.js.map +1 -1
- package/dist/core/common/persistence/model-descriptor.interface.d.ts +6 -0
- package/dist/core/common/persistence/model-metadata.registry.d.ts +2 -1
- package/dist/core/common/persistence/model-metadata.registry.js +20 -2
- package/dist/core/common/persistence/model-metadata.registry.js.map +1 -1
- package/dist/core/common/persistence/mongo/sync-schema.plugin.d.ts +18 -0
- package/dist/core/common/persistence/mongo/sync-schema.plugin.js +368 -0
- package/dist/core/common/persistence/mongo/sync-schema.plugin.js.map +1 -0
- package/dist/core/common/persistence/mongoose-data-store.d.ts +19 -9
- package/dist/core/common/persistence/mongoose-data-store.js +107 -31
- package/dist/core/common/persistence/mongoose-data-store.js.map +1 -1
- package/dist/core/common/persistence/persisted-model.decorator.d.ts +3 -0
- package/dist/core/common/persistence/persisted-model.decorator.js +9 -0
- package/dist/core/common/persistence/persisted-model.decorator.js.map +1 -1
- package/dist/core/common/persistence/persistence-runtime.registry.d.ts +5 -1
- package/dist/core/common/persistence/persistence-runtime.registry.js.map +1 -1
- package/dist/core/common/persistence/policies/sync.policy.d.ts +17 -0
- package/dist/core/common/persistence/policies/sync.policy.js +77 -0
- package/dist/core/common/persistence/policies/sync.policy.js.map +1 -0
- package/dist/core/common/persistence/sql/mysql/mysql-data-store.d.ts +12 -8
- package/dist/core/common/persistence/sql/mysql/mysql-data-store.js +82 -29
- package/dist/core/common/persistence/sql/mysql/mysql-data-store.js.map +1 -1
- package/dist/core/common/persistence/sql/mysql/mysql-schema.compiler.d.ts +10 -0
- package/dist/core/common/persistence/sql/mysql/mysql-schema.compiler.js +61 -4
- package/dist/core/common/persistence/sql/mysql/mysql-schema.compiler.js.map +1 -1
- package/dist/core/common/persistence/sql/mysql/mysql-schema.migrator.d.ts +2 -0
- package/dist/core/common/persistence/sql/mysql/mysql-schema.migrator.js +145 -4
- package/dist/core/common/persistence/sql/mysql/mysql-schema.migrator.js.map +1 -1
- package/dist/core/common/persistence/sql/postgres/postgres-schema.compiler.d.ts +10 -0
- package/dist/core/common/persistence/sql/postgres/postgres-schema.compiler.js +41 -2
- package/dist/core/common/persistence/sql/postgres/postgres-schema.compiler.js.map +1 -1
- package/dist/core/common/persistence/sql/postgres/postgres-schema.migrator.js +98 -10
- package/dist/core/common/persistence/sql/postgres/postgres-schema.migrator.js.map +1 -1
- package/dist/core/common/persistence/sql/sql-data-store.d.ts +38 -14
- package/dist/core/common/persistence/sql/sql-data-store.js +212 -38
- package/dist/core/common/persistence/sql/sql-data-store.js.map +1 -1
- package/dist/core/common/persistence/sql/sql-dialect.d.ts +2 -0
- package/dist/core/common/persistence/sql/sql-dialect.js +14 -2
- package/dist/core/common/persistence/sql/sql-dialect.js.map +1 -1
- package/dist/core/common/persistence/sql/sql-errors.d.ts +2 -1
- package/dist/core/common/persistence/sql/sql-errors.js +3 -1
- package/dist/core/common/persistence/sql/sql-errors.js.map +1 -1
- package/dist/core/common/persistence/sync-access.registry.d.ts +17 -0
- package/dist/core/common/persistence/sync-access.registry.js +8 -0
- package/dist/core/common/persistence/sync-access.registry.js.map +1 -0
- package/dist/core/common/persistence/sync-model.d.ts +29 -0
- package/dist/core/common/persistence/sync-model.js +138 -0
- package/dist/core/common/persistence/sync-model.js.map +1 -0
- package/dist/core/common/services/core-cron-jobs.service.d.ts +27 -0
- package/dist/core/common/services/core-cron-jobs.service.js +269 -13
- package/dist/core/common/services/core-cron-jobs.service.js.map +1 -1
- package/dist/core/common/services/core-redis-pubsub.js +5 -5
- package/dist/core/common/services/core-redis-pubsub.js.map +1 -1
- package/dist/core/common/services/core-redis.service.d.ts +6 -0
- package/dist/core/common/services/core-redis.service.js +57 -0
- package/dist/core/common/services/core-redis.service.js.map +1 -1
- package/dist/core/common/services/crud.service.d.ts +13 -1
- package/dist/core/common/services/crud.service.js +174 -16
- package/dist/core/common/services/crud.service.js.map +1 -1
- package/dist/core/modules/better-auth/better-auth-sql.helper.d.ts +1 -0
- package/dist/core/modules/better-auth/better-auth-sql.helper.js +41 -14
- package/dist/core/modules/better-auth/better-auth-sql.helper.js.map +1 -1
- package/dist/core/modules/better-auth/better-auth.config.js.map +1 -1
- package/dist/core/modules/better-auth/core-better-auth-sql.store.d.ts +4 -7
- package/dist/core/modules/better-auth/core-better-auth-sql.store.js +23 -16
- package/dist/core/modules/better-auth/core-better-auth-sql.store.js.map +1 -1
- package/dist/core/modules/better-auth/core-better-auth-user.mapper.d.ts +12 -2
- package/dist/core/modules/better-auth/core-better-auth-user.mapper.js +82 -5
- package/dist/core/modules/better-auth/core-better-auth-user.mapper.js.map +1 -1
- package/dist/core/modules/better-auth/core-better-auth.module.js +1 -1
- package/dist/core/modules/better-auth/core-better-auth.module.js.map +1 -1
- package/dist/core/modules/error-code/error-codes.d.ts +54 -0
- package/dist/core/modules/error-code/error-codes.js +48 -0
- package/dist/core/modules/error-code/error-codes.js.map +1 -1
- package/dist/core/modules/hub/hub-buffer.js +3 -4
- package/dist/core/modules/hub/hub-buffer.js.map +1 -1
- package/dist/core/modules/migrate/index.js.map +1 -1
- package/dist/core/modules/migrate/migration-runner.d.ts +6 -0
- package/dist/core/modules/migrate/migration-runner.js +28 -2
- package/dist/core/modules/migrate/migration-runner.js.map +1 -1
- package/dist/core/modules/migrate/mongo-state-store.d.ts +0 -1
- package/dist/core/modules/migrate/mongo-state-store.js +0 -5
- package/dist/core/modules/migrate/mongo-state-store.js.map +1 -1
- package/dist/core/modules/migrate/sql-migration.helper.d.ts +15 -1
- package/dist/core/modules/migrate/sql-migration.helper.js +90 -5
- package/dist/core/modules/migrate/sql-migration.helper.js.map +1 -1
- package/dist/core/modules/sync/core-sync-base.controller.d.ts +7 -0
- package/dist/core/modules/sync/core-sync-base.controller.js +44 -0
- package/dist/core/modules/sync/core-sync-base.controller.js.map +1 -0
- package/dist/core/modules/sync/core-sync-cursor.d.ts +19 -0
- package/dist/core/modules/sync/core-sync-cursor.js +77 -0
- package/dist/core/modules/sync/core-sync-cursor.js.map +1 -0
- package/dist/core/modules/sync/core-sync-hints.controller.d.ts +4 -0
- package/dist/core/modules/sync/core-sync-hints.controller.js +55 -0
- package/dist/core/modules/sync/core-sync-hints.controller.js.map +1 -0
- package/dist/core/modules/sync/core-sync-hints.service.d.ts +28 -0
- package/dist/core/modules/sync/core-sync-hints.service.js +155 -0
- package/dist/core/modules/sync/core-sync-hints.service.js.map +1 -0
- package/dist/core/modules/sync/core-sync-infrastructure.models.d.ts +2 -0
- package/dist/core/modules/sync/core-sync-infrastructure.models.js +17 -0
- package/dist/core/modules/sync/core-sync-infrastructure.models.js.map +1 -0
- package/dist/core/modules/sync/core-sync-mutation.store.d.ts +43 -0
- package/dist/core/modules/sync/core-sync-mutation.store.js +88 -0
- package/dist/core/modules/sync/core-sync-mutation.store.js.map +1 -0
- package/dist/core/modules/sync/core-sync-state.store.d.ts +18 -0
- package/dist/core/modules/sync/core-sync-state.store.js +64 -0
- package/dist/core/modules/sync/core-sync-state.store.js.map +1 -0
- package/dist/core/modules/sync/core-sync.constants.d.ts +28 -0
- package/dist/core/modules/sync/core-sync.constants.js +23 -0
- package/dist/core/modules/sync/core-sync.constants.js.map +1 -0
- package/dist/core/modules/sync/core-sync.controller.d.ts +8 -0
- package/dist/core/modules/sync/core-sync.controller.js +79 -0
- package/dist/core/modules/sync/core-sync.controller.js.map +1 -0
- package/dist/core/modules/sync/core-sync.decorators.d.ts +12 -0
- package/dist/core/modules/sync/core-sync.decorators.js +28 -0
- package/dist/core/modules/sync/core-sync.decorators.js.map +1 -0
- package/dist/core/modules/sync/core-sync.module.d.ts +16 -0
- package/dist/core/modules/sync/core-sync.module.js +50 -0
- package/dist/core/modules/sync/core-sync.module.js.map +1 -0
- package/dist/core/modules/sync/core-sync.resolver.d.ts +9 -0
- package/dist/core/modules/sync/core-sync.resolver.js +46 -0
- package/dist/core/modules/sync/core-sync.resolver.js.map +1 -0
- package/dist/core/modules/sync/core-sync.service.d.ts +142 -0
- package/dist/core/modules/sync/core-sync.service.js +1208 -0
- package/dist/core/modules/sync/core-sync.service.js.map +1 -0
- package/dist/core/modules/sync/interfaces/core-sync.interfaces.d.ts +56 -0
- package/dist/core/modules/sync/interfaces/core-sync.interfaces.js +2 -0
- package/dist/core/modules/sync/interfaces/core-sync.interfaces.js.map +1 -0
- package/dist/core/modules/sync/models/core-sync-hint.model.d.ts +4 -0
- package/dist/core/modules/sync/models/core-sync-hint.model.js +30 -0
- package/dist/core/modules/sync/models/core-sync-hint.model.js.map +1 -0
- package/dist/core/modules/tenant/core-tenant.guard.js +8 -3
- package/dist/core/modules/tenant/core-tenant.guard.js.map +1 -1
- package/dist/core.module.js +14 -0
- package/dist/core.module.js.map +1 -1
- package/dist/index.d.ts +16 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -1
- package/dist/server/modules/error-code/error-codes.d.ts +6 -0
- package/dist/server/modules/sync-note/inputs/sync-note-create.input.d.ts +4 -0
- package/dist/server/modules/sync-note/inputs/sync-note-create.input.js +27 -0
- package/dist/server/modules/sync-note/inputs/sync-note-create.input.js.map +1 -0
- package/dist/server/modules/sync-note/inputs/sync-note.input.d.ts +6 -0
- package/dist/server/modules/sync-note/inputs/sync-note.input.js +37 -0
- package/dist/server/modules/sync-note/inputs/sync-note.input.js.map +1 -0
- package/dist/server/modules/sync-note/sync-note.model.d.ts +10 -0
- package/dist/server/modules/sync-note/sync-note.model.js +73 -0
- package/dist/server/modules/sync-note/sync-note.model.js.map +1 -0
- package/dist/server/modules/sync-note/sync-note.module.d.ts +2 -0
- package/dist/server/modules/sync-note/sync-note.module.js +21 -0
- package/dist/server/modules/sync-note/sync-note.module.js.map +1 -0
- package/dist/server/modules/sync-note/sync-note.service.d.ts +10 -0
- package/dist/server/modules/sync-note/sync-note.service.js +35 -0
- package/dist/server/modules/sync-note/sync-note.service.js.map +1 -0
- package/docs/REQUEST-LIFECYCLE.md +3 -2
- package/docs/choosing-a-database.md +7 -4
- package/docs/offline-sync-protocol.md +410 -0
- package/docs/security-overrides.md +14 -0
- package/migration-guides/12.0.x-to-12.1.x.md +9 -4
- package/migration-guides/12.1.0-to-12.1.1.md +125 -0
- package/migration-guides/12.1.x-to-12.2.x.md +154 -0
- package/package.json +10 -8
- package/src/core/common/helpers/file.helper.ts +7 -5
- package/src/core/common/helpers/graphql-ws-connection.helper.ts +3 -2
- package/src/core/common/interfaces/cron-job-config.interface.ts +7 -2
- package/src/core/common/interfaces/server-options.interface.ts +149 -0
- package/src/core/common/interfaces/service-options.interface.ts +15 -0
- package/src/core/common/persistence/core-persistence.module.ts +21 -2
- package/src/core/common/persistence/data-source.runtime.ts +22 -23
- package/src/core/common/persistence/data-store.interface.ts +65 -12
- package/src/core/common/persistence/id-format.ts +26 -0
- package/src/core/common/persistence/model-descriptor.interface.ts +12 -0
- package/src/core/common/persistence/model-metadata.registry.ts +31 -2
- package/src/core/common/persistence/mongo/sync-schema.plugin.ts +508 -0
- package/src/core/common/persistence/mongoose-data-store.ts +171 -35
- package/src/core/common/persistence/persisted-model.decorator.ts +26 -0
- package/src/core/common/persistence/persistence-runtime.registry.ts +16 -5
- package/src/core/common/persistence/policies/sync.policy.ts +121 -0
- package/src/core/common/persistence/sql/mysql/mysql-data-store.ts +108 -30
- package/src/core/common/persistence/sql/mysql/mysql-schema.compiler.ts +107 -4
- package/src/core/common/persistence/sql/mysql/mysql-schema.migrator.ts +194 -3
- package/src/core/common/persistence/sql/postgres/postgres-schema.compiler.ts +74 -2
- package/src/core/common/persistence/sql/postgres/postgres-schema.migrator.ts +139 -7
- package/src/core/common/persistence/sql/sql-data-store.ts +325 -44
- package/src/core/common/persistence/sql/sql-dialect.ts +38 -1
- package/src/core/common/persistence/sql/sql-errors.ts +5 -1
- package/src/core/common/persistence/sql/sql-executor.interface.ts +4 -1
- package/src/core/common/persistence/sync-access.registry.ts +50 -0
- package/src/core/common/persistence/sync-model.ts +270 -0
- package/src/core/common/services/core-cron-jobs.service.ts +441 -27
- package/src/core/common/services/core-redis-pubsub.ts +8 -6
- package/src/core/common/services/core-redis.service.ts +99 -0
- package/src/core/common/services/crud.service.ts +257 -19
- package/src/core/modules/better-auth/README.md +25 -1
- package/src/core/modules/better-auth/better-auth-sql.helper.ts +56 -24
- package/src/core/modules/better-auth/better-auth.config.ts +4 -3
- package/src/core/modules/better-auth/core-better-auth-sql.store.ts +28 -21
- package/src/core/modules/better-auth/core-better-auth-user.mapper.ts +122 -6
- package/src/core/modules/better-auth/core-better-auth.module.ts +2 -2
- package/src/core/modules/error-code/error-codes.ts +59 -0
- package/src/core/modules/hub/README.md +5 -2
- package/src/core/modules/hub/hub-buffer.ts +13 -7
- package/src/core/modules/migrate/README.md +20 -11
- package/src/core/modules/migrate/index.ts +1 -0
- package/src/core/modules/migrate/migration-runner.ts +68 -3
- package/src/core/modules/migrate/mongo-state-store.ts +7 -30
- package/src/core/modules/migrate/sql-migration.helper.ts +141 -7
- package/src/core/modules/sync/INTEGRATION-CHECKLIST.md +95 -0
- package/src/core/modules/sync/README.md +188 -0
- package/src/core/modules/sync/SECURITY.md +65 -0
- package/src/core/modules/sync/core-sync-base.controller.ts +43 -0
- package/src/core/modules/sync/core-sync-cursor.ts +117 -0
- package/src/core/modules/sync/core-sync-hints.controller.ts +52 -0
- package/src/core/modules/sync/core-sync-hints.service.ts +178 -0
- package/src/core/modules/sync/core-sync-infrastructure.models.ts +40 -0
- package/src/core/modules/sync/core-sync-mutation.store.ts +146 -0
- package/src/core/modules/sync/core-sync-state.store.ts +89 -0
- package/src/core/modules/sync/core-sync.constants.ts +51 -0
- package/src/core/modules/sync/core-sync.controller.ts +68 -0
- package/src/core/modules/sync/core-sync.decorators.ts +84 -0
- package/src/core/modules/sync/core-sync.module.ts +75 -0
- package/src/core/modules/sync/core-sync.resolver.ts +35 -0
- package/src/core/modules/sync/core-sync.service.ts +1728 -0
- package/src/core/modules/sync/interfaces/core-sync.interfaces.ts +85 -0
- package/src/core/modules/sync/models/core-sync-hint.model.ts +18 -0
- package/src/core/modules/tenant/README.md +1 -0
- package/src/core/modules/tenant/core-tenant.guard.ts +16 -3
- package/src/core.module.ts +25 -0
- package/src/index.ts +21 -0
- package/src/server/modules/sync-note/inputs/sync-note-create.input.ts +14 -0
- package/src/server/modules/sync-note/inputs/sync-note.input.ts +23 -0
- package/src/server/modules/sync-note/sync-note.model.ts +77 -0
- package/src/server/modules/sync-note/sync-note.module.ts +16 -0
- package/src/server/modules/sync-note/sync-note.service.ts +25 -0
|
@@ -2,8 +2,10 @@ import { BadRequestException, Logger } from '@nestjs/common';
|
|
|
2
2
|
|
|
3
3
|
import type {
|
|
4
4
|
DataStoreUpdate,
|
|
5
|
+
DeleteOptions,
|
|
5
6
|
FindOptions,
|
|
6
7
|
PushOptions,
|
|
8
|
+
ReadOptions,
|
|
7
9
|
StoreCapabilities,
|
|
8
10
|
StoredRecord,
|
|
9
11
|
UpdateOptions,
|
|
@@ -21,7 +23,9 @@ import {
|
|
|
21
23
|
assertSelectFields,
|
|
22
24
|
} from '../../store-guards.js';
|
|
23
25
|
import { castFilterValue } from '../sql-cast.js';
|
|
24
|
-
import { POSTGRES_STORE_CAPABILITIES, SqlDataStore, type SqlDataStoreOptions } from '../sql-data-store.js';
|
|
26
|
+
import { POSTGRES_STORE_CAPABILITIES, SqlDataStore, type SqlDataStoreOptions, type SqlRun } from '../sql-data-store.js';
|
|
27
|
+
import { tableName } from '../sql-schema.shared.js';
|
|
28
|
+
import { MYSQL_SYNC_COUNTERS } from './mysql-schema.compiler.js';
|
|
25
29
|
import { DuplicateKeyError, UnsupportedQueryError } from '../sql-errors.js';
|
|
26
30
|
import type { SqlQueryResult, SqlStatement } from '../sql-executor.interface.js';
|
|
27
31
|
import { columnName, quoteMysqlIdentifier, SqlParams } from '../sql-naming.js';
|
|
@@ -71,7 +75,6 @@ type Run = <R = Record<string, any>>(statement: SqlStatement) => Promise<SqlQuer
|
|
|
71
75
|
* gives MongoDB's findOneAndUpdate guarantee without a retry.
|
|
72
76
|
*/
|
|
73
77
|
export class MysqlDataStore<T = Record<string, any>> extends SqlDataStore<T> {
|
|
74
|
-
override readonly capabilities = MYSQL_STORE_CAPABILITIES;
|
|
75
78
|
override readonly driver: string = 'mysql';
|
|
76
79
|
protected override readonly logger = new Logger(MysqlDataStore.name);
|
|
77
80
|
private readonly variant: MysqlVariant;
|
|
@@ -82,13 +85,44 @@ export class MysqlDataStore<T = Record<string, any>> extends SqlDataStore<T> {
|
|
|
82
85
|
this.driver = executor.variant;
|
|
83
86
|
}
|
|
84
87
|
|
|
88
|
+
protected override baseCapabilities(): Readonly<StoreCapabilities> {
|
|
89
|
+
return MYSQL_STORE_CAPABILITIES;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Every store write of a synchronized model takes its change number last: `FOR UPDATE` locks index gaps here, and an
|
|
94
|
+
* insert waits for them after its BEFORE INSERT trigger ran — a trigger holding the counter meanwhile would deadlock
|
|
95
|
+
* with a multi-row write that locked those gaps and now asks for the counter (measured, DEV-3900 review)
|
|
96
|
+
*/
|
|
97
|
+
protected override numbersLast(): boolean {
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** A single-column write (pullFromArray, a one-stage push) — in a transaction, so a synchronized row is numbered */
|
|
102
|
+
protected override async updateColumn(id: string, column: string, value: string, params: SqlParams): Promise<void> {
|
|
103
|
+
if (!this.model.sync) {
|
|
104
|
+
return super.updateColumn(id, column, value, params);
|
|
105
|
+
}
|
|
106
|
+
const { $setOnInsert: _insertOnly, ...stamped } = await this.beforeUpdate({});
|
|
107
|
+
const assignments = [`${column} = ${value}`, ...this.compileUpdate(stamped, params)];
|
|
108
|
+
await this.transaction(async (run) => {
|
|
109
|
+
const { rowCount } = await run({
|
|
110
|
+
params: params.values,
|
|
111
|
+
text: `UPDATE ${this.table} SET ${assignments.join(', ')} WHERE ${this.where({ id } as Where<T>, params)}`,
|
|
112
|
+
});
|
|
113
|
+
if (rowCount) {
|
|
114
|
+
await this.numberPending(run, [id]);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
85
119
|
// -- reads ----------------------------------------------------------------------------------------------
|
|
86
120
|
|
|
87
|
-
override async count(where?: Where<T
|
|
121
|
+
override async count(where?: Where<T>, options: ReadOptions = {}): Promise<number> {
|
|
88
122
|
const params = new SqlParams();
|
|
89
123
|
const { rows } = await this.run<{ n: number | string }>({
|
|
90
124
|
params: params.values,
|
|
91
|
-
text: `SELECT COUNT(*) AS n FROM ${this.table} WHERE ${this.where(where, params)}`,
|
|
125
|
+
text: `SELECT COUNT(*) AS n FROM ${this.table} WHERE ${this.where(where, params, options)}`,
|
|
92
126
|
});
|
|
93
127
|
return Number(rows[0].n);
|
|
94
128
|
}
|
|
@@ -109,10 +143,10 @@ export class MysqlDataStore<T = Record<string, any>> extends SqlDataStore<T> {
|
|
|
109
143
|
return { count: Number(rows[0].n), sum: Number(rows[0].total) };
|
|
110
144
|
}
|
|
111
145
|
|
|
112
|
-
override async distinct(field: string, where?: Where<T
|
|
146
|
+
override async distinct(field: string, where?: Where<T>, options: ReadOptions = {}): Promise<unknown[]> {
|
|
113
147
|
const params = new SqlParams();
|
|
114
148
|
const path = compileMysqlPath(this.variant, this.model, field, params);
|
|
115
|
-
const condition = this.where(where, params);
|
|
149
|
+
const condition = this.where(where, params, options);
|
|
116
150
|
if (path.kind === 'array') {
|
|
117
151
|
const { rows } = await this.run<{ v: unknown }>({
|
|
118
152
|
params: params.values,
|
|
@@ -133,7 +167,7 @@ export class MysqlDataStore<T = Record<string, any>> extends SqlDataStore<T> {
|
|
|
133
167
|
override async find(where?: Where<T>, options: FindOptions = {}): Promise<StoredRecord<T>[]> {
|
|
134
168
|
const sample = options.sample === undefined ? undefined : assertSampleSize(options.sample);
|
|
135
169
|
const params = new SqlParams();
|
|
136
|
-
let text = `SELECT ${this.selectList(options.select, options.exclude)} FROM ${this.table} WHERE ${this.where(where, params)}`;
|
|
170
|
+
let text = `SELECT ${this.selectList(options.select, options.exclude)} FROM ${this.table} WHERE ${this.where(where, params, options)}`;
|
|
137
171
|
text += this.orderBy(options, params);
|
|
138
172
|
if (options.limit !== undefined) {
|
|
139
173
|
text += ` LIMIT ${params.add(assertNonNegativeInteger(options.limit, 'limit'))}`;
|
|
@@ -153,14 +187,17 @@ export class MysqlDataStore<T = Record<string, any>> extends SqlDataStore<T> {
|
|
|
153
187
|
|
|
154
188
|
override async insert(record: Partial<T>): Promise<StoredRecord<T>> {
|
|
155
189
|
const doc = await this.beforeInsert({ ...(record as Record<string, any>) });
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
190
|
+
return this.withServerId(doc, (prepared) => {
|
|
191
|
+
const params = new SqlParams();
|
|
192
|
+
const { columns, id, values } = this.insertValues(prepared, params);
|
|
193
|
+
return this.transaction(async (run) => {
|
|
194
|
+
await run({
|
|
195
|
+
params: params.values,
|
|
196
|
+
text: `INSERT INTO ${this.table} (${columns.join(', ')}) VALUES (${values.join(', ')})`,
|
|
197
|
+
});
|
|
198
|
+
await this.numberPending(run, [id]);
|
|
199
|
+
return (await this.readById(run, id))!;
|
|
162
200
|
});
|
|
163
|
-
return (await this.readById(run, id))!;
|
|
164
201
|
});
|
|
165
202
|
}
|
|
166
203
|
|
|
@@ -173,28 +210,36 @@ export class MysqlDataStore<T = Record<string, any>> extends SqlDataStore<T> {
|
|
|
173
210
|
const params = new SqlParams();
|
|
174
211
|
const assignments = this.compileUpdate(patch, params);
|
|
175
212
|
if (!assignments.length) {
|
|
176
|
-
return this.findById(id);
|
|
213
|
+
return this.findById(id, options);
|
|
177
214
|
}
|
|
178
|
-
const condition = this.where({ id } as Where<T>, params);
|
|
215
|
+
const condition = this.where({ id } as Where<T>, params, options);
|
|
179
216
|
return this.transaction(async (run) => {
|
|
180
217
|
const { rowCount } = await run({
|
|
181
218
|
params: params.values,
|
|
182
219
|
text: `UPDATE ${this.table} SET ${assignments.join(', ')} WHERE ${condition}`,
|
|
183
220
|
});
|
|
184
221
|
// The driver counts MATCHED rows (CLIENT_FOUND_ROWS), so an update that changes nothing still finds its record
|
|
185
|
-
|
|
222
|
+
if (!rowCount) {
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
await this.numberPending(run, [id]);
|
|
226
|
+
return this.readById(run, id);
|
|
186
227
|
});
|
|
187
228
|
}
|
|
188
229
|
|
|
189
|
-
override async updateOne(
|
|
190
|
-
|
|
230
|
+
override async updateOne(
|
|
231
|
+
where: Where<T>,
|
|
232
|
+
update: DataStoreUpdate<T>,
|
|
233
|
+
options: UpdateOptions = {},
|
|
234
|
+
): Promise<null | StoredRecord<T>> {
|
|
235
|
+
const patch = await this.preparePatch(update, options);
|
|
191
236
|
const params = new SqlParams();
|
|
192
237
|
const assignments = this.compileUpdate(patch, params);
|
|
193
238
|
if (!assignments.length) {
|
|
194
|
-
return this.findOne(where);
|
|
239
|
+
return this.findOne(where, options);
|
|
195
240
|
}
|
|
196
241
|
return this.transaction(async (run) => {
|
|
197
|
-
const id = await this.lockFirst(run, where);
|
|
242
|
+
const id = await this.lockFirst(run, where, options);
|
|
198
243
|
if (id === undefined) {
|
|
199
244
|
return null;
|
|
200
245
|
}
|
|
@@ -202,16 +247,19 @@ export class MysqlDataStore<T = Record<string, any>> extends SqlDataStore<T> {
|
|
|
202
247
|
params: params.values,
|
|
203
248
|
text: `UPDATE ${this.table} SET ${assignments.join(', ')} WHERE \`id\` = ${params.add(id)}`,
|
|
204
249
|
});
|
|
250
|
+
await this.numberPending(run, [id]);
|
|
205
251
|
return this.readById(run, id);
|
|
206
252
|
});
|
|
207
253
|
}
|
|
208
254
|
|
|
209
|
-
override async deleteById(id: string): Promise<null | StoredRecord<T>> {
|
|
210
|
-
return this.
|
|
255
|
+
override async deleteById(id: string, options: DeleteOptions = {}): Promise<null | StoredRecord<T>> {
|
|
256
|
+
return this.model.sync && !options.hardDelete
|
|
257
|
+
? super.deleteById(id, options)
|
|
258
|
+
: this.deleteFirst({ id } as Where<T>, options);
|
|
211
259
|
}
|
|
212
260
|
|
|
213
|
-
override async deleteOne(where: Where<T
|
|
214
|
-
return this.deleteFirst(where);
|
|
261
|
+
override async deleteOne(where: Where<T>, options: DeleteOptions = {}): Promise<null | StoredRecord<T>> {
|
|
262
|
+
return this.model.sync && !options.hardDelete ? super.deleteOne(where, options) : this.deleteFirst(where, options);
|
|
215
263
|
}
|
|
216
264
|
|
|
217
265
|
override async pushToArray(
|
|
@@ -307,6 +355,7 @@ export class MysqlDataStore<T = Record<string, any>> extends SqlDataStore<T> {
|
|
|
307
355
|
return;
|
|
308
356
|
}
|
|
309
357
|
}
|
|
358
|
+
await this.numberPending(run, [id]);
|
|
310
359
|
});
|
|
311
360
|
}
|
|
312
361
|
|
|
@@ -418,6 +467,30 @@ export class MysqlDataStore<T = Record<string, any>> extends SqlDataStore<T> {
|
|
|
418
467
|
return super.fromColumn(field, value);
|
|
419
468
|
}
|
|
420
469
|
|
|
470
|
+
/**
|
|
471
|
+
* The counter row of this table (plan §3.4, C1d — MySQL and MariaDB update it in place, so a long snapshot costs no
|
|
472
|
+
* versions): raised by `count` at once, its row lock held until the transaction ends
|
|
473
|
+
*/
|
|
474
|
+
protected override async takeChangeNumbers(run: SqlRun, count: number): Promise<string[]> {
|
|
475
|
+
const params = new SqlParams();
|
|
476
|
+
const key = tableName(this.model);
|
|
477
|
+
await run({
|
|
478
|
+
params: params.values,
|
|
479
|
+
text: `UPDATE ${this.quote(MYSQL_SYNC_COUNTERS)} SET \`seq\` = \`seq\` + ${params.add(count)} WHERE \`model\` = ${params.add(key)}`,
|
|
480
|
+
});
|
|
481
|
+
const read = new SqlParams();
|
|
482
|
+
const { rows } = await run<{ seq: number | string }>({
|
|
483
|
+
params: read.values,
|
|
484
|
+
text: `SELECT \`seq\` FROM ${this.quote(MYSQL_SYNC_COUNTERS)} WHERE \`model\` = ${read.add(key)}`,
|
|
485
|
+
});
|
|
486
|
+
const last = BigInt(rows[0].seq);
|
|
487
|
+
return Array.from({ length: count }, (_, index) => (last - BigInt(count - 1 - index)).toString());
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
protected override bigintParam(params: SqlParams, value: string): string {
|
|
491
|
+
return params.add(value);
|
|
492
|
+
}
|
|
493
|
+
|
|
421
494
|
/**
|
|
422
495
|
* MySQL / MariaDB errors as the answers the MongoDB path gives for the same situation. Matched by error NUMBER and
|
|
423
496
|
* server: the driver names numbers from MySQL's list, and MariaDB's 4025 (a failed CHECK) is another error there
|
|
@@ -425,7 +498,8 @@ export class MysqlDataStore<T = Record<string, any>> extends SqlDataStore<T> {
|
|
|
425
498
|
protected override mapError(error: unknown): unknown {
|
|
426
499
|
const errno = Number((error as { errno?: unknown })?.errno);
|
|
427
500
|
if (errno === MYSQL_ERRORS.duplicateEntry) {
|
|
428
|
-
|
|
501
|
+
const message = String((error as { message?: unknown })?.message ?? '');
|
|
502
|
+
return new DuplicateKeyError(this.modelName, /for key '(?:[^']*\.)?PRIMARY'/.test(message));
|
|
429
503
|
}
|
|
430
504
|
if (errno === MYSQL_ERRORS.dataTooLong) {
|
|
431
505
|
return new BadRequestException(`${this.modelName}: a value is longer than its column allows`);
|
|
@@ -460,21 +534,25 @@ export class MysqlDataStore<T = Record<string, any>> extends SqlDataStore<T> {
|
|
|
460
534
|
}
|
|
461
535
|
|
|
462
536
|
/** The id of the first match, its row locked until the transaction ends */
|
|
463
|
-
private async lockFirst(
|
|
537
|
+
private async lockFirst(
|
|
538
|
+
run: Run,
|
|
539
|
+
where: Where<T>,
|
|
540
|
+
options: { includeTombstones?: boolean } = {},
|
|
541
|
+
): Promise<string | undefined> {
|
|
464
542
|
const params = new SqlParams();
|
|
465
543
|
const { rows } = await run<{ id: string }>({
|
|
466
544
|
params: params.values,
|
|
467
|
-
text: `SELECT \`id\` FROM ${this.table} WHERE ${this.where(where, params)} LIMIT 1 FOR UPDATE`,
|
|
545
|
+
text: `SELECT \`id\` FROM ${this.table} WHERE ${this.where(where, params, options)} LIMIT 1 FOR UPDATE`,
|
|
468
546
|
});
|
|
469
547
|
return rows[0]?.id;
|
|
470
548
|
}
|
|
471
549
|
|
|
472
|
-
private async deleteFirst(where: Where<T
|
|
550
|
+
private async deleteFirst(where: Where<T>, options: DeleteOptions = {}): Promise<null | StoredRecord<T>> {
|
|
473
551
|
return this.transaction(async (run) => {
|
|
474
552
|
const params = new SqlParams();
|
|
475
553
|
const { rows } = await run({
|
|
476
554
|
params: params.values,
|
|
477
|
-
text: `SELECT ${this.selectList()} FROM ${this.table} WHERE ${this.where(where, params)} LIMIT 1 FOR UPDATE`,
|
|
555
|
+
text: `SELECT ${this.selectList()} FROM ${this.table} WHERE ${this.where(where, params, options)} LIMIT 1 FOR UPDATE`,
|
|
478
556
|
});
|
|
479
557
|
if (!rows.length) {
|
|
480
558
|
return null;
|
|
@@ -2,6 +2,7 @@ import type { FieldDescriptor, IndexDescriptor, ModelDescriptor } from '../../mo
|
|
|
2
2
|
import { UnsupportedQueryError } from '../sql-errors.js';
|
|
3
3
|
import { columnName, quoteMysqlIdentifier } from '../sql-naming.js';
|
|
4
4
|
import { schemaHash, shortName, tableName } from '../sql-schema.shared.js';
|
|
5
|
+
import { SYNC_DELETED_FIELD, SYNC_SEQUENCE_FIELD } from '../../sync-model.js';
|
|
5
6
|
import type { MysqlVariant } from './mysql-session.js';
|
|
6
7
|
|
|
7
8
|
/**
|
|
@@ -48,6 +49,12 @@ export interface MysqlDesiredCheck {
|
|
|
48
49
|
name: string;
|
|
49
50
|
}
|
|
50
51
|
|
|
52
|
+
/** The triggers that number a synchronized model's changes (offline-sync plan §3.4) */
|
|
53
|
+
export interface MysqlDesiredSync {
|
|
54
|
+
insertTrigger: string;
|
|
55
|
+
updateTrigger: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
51
58
|
export interface MysqlDesiredTable {
|
|
52
59
|
checks: MysqlDesiredCheck[];
|
|
53
60
|
columns: MysqlDesiredColumn[];
|
|
@@ -56,14 +63,29 @@ export interface MysqlDesiredTable {
|
|
|
56
63
|
name: string;
|
|
57
64
|
/** What the model declares but the table cannot carry, reported once */
|
|
58
65
|
notices: string[];
|
|
66
|
+
/** Present for a synchronized model: its change-order triggers */
|
|
67
|
+
sync?: MysqlDesiredSync;
|
|
59
68
|
}
|
|
60
69
|
|
|
70
|
+
/** The counter rows of every synchronized table (offline-sync plan §3.4, C1d): one row per table, updated in place */
|
|
71
|
+
export const MYSQL_SYNC_COUNTERS = 'lt_sync_counters';
|
|
72
|
+
/**
|
|
73
|
+
* Bumped whenever the change-order triggers change: their names carry the version, so the migrator finds an outdated
|
|
74
|
+
* trigger by name and replaces it
|
|
75
|
+
*/
|
|
76
|
+
export const MYSQL_SYNC_TRIGGER_VERSION = 'v1';
|
|
77
|
+
|
|
61
78
|
/** Text types whose collation is `utf8mb4_bin` */
|
|
62
79
|
const TEXT_TYPES = new Set(['char(36)', 'longtext', 'varchar(255)']);
|
|
63
80
|
/** Kinds stored as strings — the null sentinel of a unique index is a string */
|
|
64
81
|
const STRING_KINDS = new Set(['enum', 'id', 'ref', 'string', 'uuid']);
|
|
65
82
|
/** Prefix of the virtual columns that carry a unique index's null sentinel */
|
|
66
83
|
export const NULL_SENTINEL_PREFIX = 'lt_nn_';
|
|
84
|
+
/**
|
|
85
|
+
* Prefix of the virtual columns a synchronized model's unique index reads: the value while the record is live, NULL
|
|
86
|
+
* once it is a tombstone — MySQL and MariaDB have no partial index, and NULL may repeat in a unique one (plan §3.6)
|
|
87
|
+
*/
|
|
88
|
+
export const LIVE_KEY_PREFIX = 'lt_lk_';
|
|
67
89
|
/** InnoDB's limit for the columns of one index, in bytes (utf8mb4: four per character) */
|
|
68
90
|
const MAX_INDEX_BYTES = 3072;
|
|
69
91
|
const INDEX_BYTES: Record<string, number> = {
|
|
@@ -141,7 +163,14 @@ export function desiredMysqlTable(model: ModelDescriptor, variant: MysqlVariant)
|
|
|
141
163
|
tableIndexes.push(...result);
|
|
142
164
|
}
|
|
143
165
|
}
|
|
144
|
-
|
|
166
|
+
const sync =
|
|
167
|
+
model.sync && model.fields[SYNC_SEQUENCE_FIELD]
|
|
168
|
+
? {
|
|
169
|
+
insertTrigger: shortName(`lt_sync_${MYSQL_SYNC_TRIGGER_VERSION}`, name, ['bi']),
|
|
170
|
+
updateTrigger: shortName(`lt_sync_${MYSQL_SYNC_TRIGGER_VERSION}`, name, ['bu']),
|
|
171
|
+
}
|
|
172
|
+
: undefined;
|
|
173
|
+
return { checks, columns, indexes: tableIndexes, model, name, notices, ...(sync ? { sync } : {}) };
|
|
145
174
|
}
|
|
146
175
|
|
|
147
176
|
export function variantLabel(variant: MysqlVariant): string {
|
|
@@ -248,10 +277,14 @@ function desiredIndexes(
|
|
|
248
277
|
}
|
|
249
278
|
const unique = index.options.unique === true;
|
|
250
279
|
const partial = index.options.partialFilterExpression as Record<string, unknown> | undefined;
|
|
280
|
+
// A synchronized model's unique index applies to live records only: it reads virtual live-key columns
|
|
281
|
+
const live = unique && !!partial && livePartial(partial, entries);
|
|
282
|
+
const liveSparse = live && Object.keys(partial!).length > 1;
|
|
251
283
|
// A partial unique index whose filter only says "every indexed field exists" is a sparse one — MySQL's own NULL
|
|
252
284
|
// handling. Any other filter has no counterpart, and a uniqueness checked nowhere must not pass for one that holds
|
|
253
|
-
const sparse =
|
|
254
|
-
|
|
285
|
+
const sparse =
|
|
286
|
+
!live && (index.options.sparse === true || (unique && !!partial && requiresIndexedFields(partial, entries)));
|
|
287
|
+
if (unique && partial && !sparse && !live) {
|
|
255
288
|
throw new UnsupportedQueryError(
|
|
256
289
|
`${model.name}: the partial unique index ${JSON.stringify(index.fields)} has no MySQL / MariaDB counterpart — ` +
|
|
257
290
|
'uniqueness would be checked nowhere. Use a sparse unique index, or enforce it in the service',
|
|
@@ -273,7 +306,9 @@ function desiredIndexes(
|
|
|
273
306
|
return 'an index over an array, JSON or binary field';
|
|
274
307
|
}
|
|
275
308
|
let target = column.name;
|
|
276
|
-
if (
|
|
309
|
+
if (live && !column.primaryKey) {
|
|
310
|
+
target = liveKeyColumn(column, columns, !liveSparse && STRING_KINDS.has(field.kind) && !field.isArray);
|
|
311
|
+
} else if (unique && !sparse && !column.primaryKey && STRING_KINDS.has(field.kind) && !field.isArray) {
|
|
277
312
|
// A missing value is a value of its own, as in MongoDB: the index reads it from a virtual column
|
|
278
313
|
target = nullSentinelColumn(column, columns);
|
|
279
314
|
}
|
|
@@ -331,6 +366,36 @@ function desiredIndexes(
|
|
|
331
366
|
return result;
|
|
332
367
|
}
|
|
333
368
|
|
|
369
|
+
/**
|
|
370
|
+
* Whether a partial filter is a synchronized model's live-record condition (`{ deletedAt: null }`), alone or with
|
|
371
|
+
* "every indexed field exists" — the rewrite of a sparse unique index (`sync-model.ts` → `liveUniqueIndex()`)
|
|
372
|
+
*/
|
|
373
|
+
function livePartial(partial: Record<string, unknown>, entries: [string, unknown][]): boolean {
|
|
374
|
+
if (!(SYNC_DELETED_FIELD in partial) || partial[SYNC_DELETED_FIELD] !== null) {
|
|
375
|
+
return false;
|
|
376
|
+
}
|
|
377
|
+
const { [SYNC_DELETED_FIELD]: _live, ...rest } = partial;
|
|
378
|
+
return !Object.keys(rest).length || requiresIndexedFields(rest, entries);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* The virtual column carrying `column`'s value while the record is live — and NULL for a tombstone, which a unique
|
|
383
|
+
* index lets repeat. `sentinel`: a live NULL becomes the null sentinel, so it collides like a second live value
|
|
384
|
+
*/
|
|
385
|
+
function liveKeyColumn(column: MysqlDesiredColumn, columns: MysqlDesiredColumn[], sentinel: boolean): string {
|
|
386
|
+
const name = `${LIVE_KEY_PREFIX}${column.name}`.slice(0, 63);
|
|
387
|
+
if (!columns.some((candidate) => candidate.name === name)) {
|
|
388
|
+
const value = quoteMysqlIdentifier(column.name);
|
|
389
|
+
columns.push({
|
|
390
|
+
generated: `IF(${quoteMysqlIdentifier(columnName(SYNC_DELETED_FIELD))} IS NULL, ${sentinel ? `IFNULL(${value}, CHAR(0 USING utf8mb4))` : value}, NULL)`,
|
|
391
|
+
name,
|
|
392
|
+
primaryKey: false,
|
|
393
|
+
type: column.type,
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
return name;
|
|
397
|
+
}
|
|
398
|
+
|
|
334
399
|
/** Whether a partial filter says exactly "every indexed field exists" (`{ field: { $exists: true } }` each) */
|
|
335
400
|
function requiresIndexedFields(partial: Record<string, unknown>, entries: [string, unknown][]): boolean {
|
|
336
401
|
const keys = Object.keys(partial);
|
|
@@ -384,6 +449,44 @@ export function mysqlColumnDefinition(column: MysqlDesiredColumn): string {
|
|
|
384
449
|
return `${quoteMysqlIdentifier(column.name)} ${type}${collation}${key}${fallback}`;
|
|
385
450
|
}
|
|
386
451
|
|
|
452
|
+
/**
|
|
453
|
+
* The counter table, created once for every synchronized table of the database. `triggers` records the version of the
|
|
454
|
+
* table's triggers once they are in place: the application account may not read the triggers themselves (that needs
|
|
455
|
+
* the TRIGGER privilege it must never hold), so a boot without the owner checks this column instead
|
|
456
|
+
*/
|
|
457
|
+
export function mysqlSyncCountersStatement(): string {
|
|
458
|
+
return `CREATE TABLE IF NOT EXISTS ${quoteMysqlIdentifier(MYSQL_SYNC_COUNTERS)} (\`model\` varchar(64) COLLATE utf8mb4_bin NOT NULL PRIMARY KEY, \`seq\` bigint NOT NULL, \`triggers\` varchar(16) COLLATE utf8mb4_bin NULL) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin`;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* A synchronized table's counter row, its two triggers and the numbering of rows written before it was synchronized.
|
|
463
|
+
*
|
|
464
|
+
* A BEFORE ROW trigger runs after the row is locked, so a write takes its row first and the counter last; the counter
|
|
465
|
+
* row's lock is held until COMMIT, so sequence order is commit order (C1d). A value the statement sets itself is kept
|
|
466
|
+
* — the store's multi-row writes take their numbers and set them in their final statement. The numbering of existing
|
|
467
|
+
* rows runs in an explicit transaction holding the counter row: a DDL statement commits on its own, and a write firing
|
|
468
|
+
* the new trigger meanwhile must wait rather than draw a number twice.
|
|
469
|
+
*/
|
|
470
|
+
export function mysqlSyncTableStatements(table: string, sync: MysqlDesiredSync): string[] {
|
|
471
|
+
const quoted = quoteMysqlIdentifier(table);
|
|
472
|
+
const counters = quoteMysqlIdentifier(MYSQL_SYNC_COUNTERS);
|
|
473
|
+
const key = mysqlLiteral(table);
|
|
474
|
+
const seq = quoteMysqlIdentifier(columnName(SYNC_SEQUENCE_FIELD));
|
|
475
|
+
const take = `UPDATE ${counters} SET \`seq\` = \`seq\` + 1 WHERE \`model\` = ${key}; SET NEW.${seq} = (SELECT \`seq\` FROM ${counters} WHERE \`model\` = ${key});`;
|
|
476
|
+
return [
|
|
477
|
+
`INSERT IGNORE INTO ${counters} (\`model\`, \`seq\`) VALUES (${key}, 0)`,
|
|
478
|
+
`DROP TRIGGER IF EXISTS ${quoteMysqlIdentifier(sync.insertTrigger)}`,
|
|
479
|
+
`DROP TRIGGER IF EXISTS ${quoteMysqlIdentifier(sync.updateTrigger)}`,
|
|
480
|
+
`CREATE TRIGGER ${quoteMysqlIdentifier(sync.insertTrigger)} BEFORE INSERT ON ${quoted} FOR EACH ROW BEGIN IF NEW.${seq} IS NULL THEN ${take} END IF; END`,
|
|
481
|
+
`CREATE TRIGGER ${quoteMysqlIdentifier(sync.updateTrigger)} BEFORE UPDATE ON ${quoted} FOR EACH ROW BEGIN IF NEW.${seq} <=> OLD.${seq} THEN ${take} END IF; END`,
|
|
482
|
+
'START TRANSACTION',
|
|
483
|
+
`SELECT \`seq\` INTO @lt_sync_base FROM ${counters} WHERE \`model\` = ${key} FOR UPDATE`,
|
|
484
|
+
`UPDATE ${quoted} AS t JOIN (SELECT \`id\`, ROW_NUMBER() OVER (ORDER BY \`id\`) AS n FROM ${quoted} WHERE ${seq} IS NULL) AS o ON o.\`id\` = t.\`id\` SET t.${seq} = @lt_sync_base + o.n`,
|
|
485
|
+
`UPDATE ${counters} SET \`seq\` = GREATEST(\`seq\`, (SELECT COALESCE(MAX(${seq}), 0) FROM ${quoted})), \`triggers\` = ${mysqlLiteral(MYSQL_SYNC_TRIGGER_VERSION)} WHERE \`model\` = ${key}`,
|
|
486
|
+
'COMMIT',
|
|
487
|
+
];
|
|
488
|
+
}
|
|
489
|
+
|
|
387
490
|
export function addMysqlCheckStatement(table: string, check: MysqlDesiredCheck): string {
|
|
388
491
|
return `ALTER TABLE ${quoteMysqlIdentifier(table)} ADD CONSTRAINT ${quoteMysqlIdentifier(check.name)} CHECK (${check.expression})`;
|
|
389
492
|
}
|