@memberjunction/metadata-sync 6.2.0-edge.5 → 6.2.0-edge.6
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/README.md +5 -1
- package/dist/config.d.ts +13 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js.map +1 -1
- package/dist/lib/deletion-auditor.d.ts.map +1 -1
- package/dist/lib/deletion-auditor.js +1 -1
- package/dist/lib/deletion-auditor.js.map +1 -1
- package/dist/lib/env-sql-redactor.d.ts +1 -1
- package/dist/lib/env-sql-redactor.d.ts.map +1 -1
- package/dist/lib/env-sql-redactor.js +7 -72
- package/dist/lib/env-sql-redactor.js.map +1 -1
- package/dist/lib/lookup-sql.d.ts +108 -0
- package/dist/lib/lookup-sql.d.ts.map +1 -0
- package/dist/lib/lookup-sql.js +231 -0
- package/dist/lib/lookup-sql.js.map +1 -0
- package/dist/lib/natural-key-preflight.d.ts +86 -0
- package/dist/lib/natural-key-preflight.d.ts.map +1 -0
- package/dist/lib/natural-key-preflight.js +173 -0
- package/dist/lib/natural-key-preflight.js.map +1 -0
- package/dist/lib/record-dependency-analyzer.d.ts +9 -2
- package/dist/lib/record-dependency-analyzer.d.ts.map +1 -1
- package/dist/lib/record-dependency-analyzer.js +48 -71
- package/dist/lib/record-dependency-analyzer.js.map +1 -1
- package/dist/lib/schema-placeholders.d.ts +29 -0
- package/dist/lib/schema-placeholders.d.ts.map +1 -0
- package/dist/lib/schema-placeholders.js +72 -0
- package/dist/lib/schema-placeholders.js.map +1 -0
- package/dist/lib/sql-logger.d.ts +9 -1
- package/dist/lib/sql-logger.d.ts.map +1 -1
- package/dist/lib/sql-logger.js +12 -7
- package/dist/lib/sql-logger.js.map +1 -1
- package/dist/lib/sql-set-line.d.ts +26 -0
- package/dist/lib/sql-set-line.d.ts.map +1 -0
- package/dist/lib/sql-set-line.js +84 -0
- package/dist/lib/sql-set-line.js.map +1 -0
- package/dist/lib/sync-engine.d.ts +14 -2
- package/dist/lib/sync-engine.d.ts.map +1 -1
- package/dist/lib/sync-engine.js +33 -5
- package/dist/lib/sync-engine.js.map +1 -1
- package/dist/services/PushService.d.ts +21 -0
- package/dist/services/PushService.d.ts.map +1 -1
- package/dist/services/PushService.js +100 -20
- package/dist/services/PushService.js.map +1 -1
- package/dist/services/WatchService.d.ts +2 -0
- package/dist/services/WatchService.d.ts.map +1 -1
- package/dist/services/WatchService.js +19 -2
- package/dist/services/WatchService.js.map +1 -1
- package/package.json +15 -15
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { GenericDatabaseProvider } from '@memberjunction/generic-database-provider';
|
|
2
|
+
import { IsDateSQLType, IsUuidSQLType } from '@memberjunction/sql-dialect';
|
|
3
|
+
import { IsALevels, OwningLevel, SaveCallHashesIn, SetLinePattern, SqlLiteralsOf } from './sql-set-line.js';
|
|
4
|
+
/**
|
|
5
|
+
* Builds `(SELECT [PK] FROM [schema].[BaseTable] WHERE …)` for a lookup source. Each criterion is
|
|
6
|
+
* matched with `=` on the entity's own table (SQL Server's default collation is case-insensitive,
|
|
7
|
+
* as the push lookup is). `null` matches `IS NULL`. A criterion must be a column of the entity's
|
|
8
|
+
* own table: a virtual field or one an IS-A parent holds has no column there.
|
|
9
|
+
*/
|
|
10
|
+
export class LookupSubqueryBuilder {
|
|
11
|
+
constructor(entityInfo) {
|
|
12
|
+
this.entityInfo = entityInfo;
|
|
13
|
+
}
|
|
14
|
+
Build(source) {
|
|
15
|
+
if (source.EnvSourced) {
|
|
16
|
+
return { Problem: 'its @lookup criteria use an @env value, which the log never holds' };
|
|
17
|
+
}
|
|
18
|
+
const info = this.entityInfo(source.EntityName);
|
|
19
|
+
if (!info) {
|
|
20
|
+
return { Problem: `entity '${source.EntityName}' was not found` };
|
|
21
|
+
}
|
|
22
|
+
if (info.PrimaryKeys.length !== 1) {
|
|
23
|
+
return { Problem: `'${source.EntityName}' does not have a single-column primary key` };
|
|
24
|
+
}
|
|
25
|
+
if (source.Criteria.length === 0) {
|
|
26
|
+
return { Problem: 'the @lookup has no criteria' };
|
|
27
|
+
}
|
|
28
|
+
const conditions = [];
|
|
29
|
+
for (const criterion of source.Criteria) {
|
|
30
|
+
const condition = this.condition(info, criterion);
|
|
31
|
+
if (condition.Problem !== undefined) {
|
|
32
|
+
return condition;
|
|
33
|
+
}
|
|
34
|
+
conditions.push(condition.Sql);
|
|
35
|
+
}
|
|
36
|
+
const key = info.FirstPrimaryKey.Name; // first-pk-ok: a single-column key was checked above; a lookup yields one value
|
|
37
|
+
return { Sql: `(SELECT [${key}] FROM [${info.SchemaName}].[${info.BaseTable}] WHERE ${conditions.join(' AND ')})` };
|
|
38
|
+
}
|
|
39
|
+
condition(info, criterion) {
|
|
40
|
+
const wanted = criterion.FieldName.trim().toLowerCase();
|
|
41
|
+
const field = info.Fields.find((candidate) => candidate.Name.toLowerCase() === wanted);
|
|
42
|
+
if (!field) {
|
|
43
|
+
return { Problem: `'${info.Name}' has no field '${criterion.FieldName}'` };
|
|
44
|
+
}
|
|
45
|
+
if (field.IsVirtual || info.ParentEntityFieldNames?.has(field.Name)) {
|
|
46
|
+
return { Problem: `${info.Name}.${field.Name} is not a column of table ${info.BaseTable}` };
|
|
47
|
+
}
|
|
48
|
+
if (typeof criterion.Value !== 'string') {
|
|
49
|
+
const nested = this.Build(criterion.Value);
|
|
50
|
+
return nested.Problem !== undefined ? nested : { Sql: `[${field.Name}] = ${nested.Sql}` };
|
|
51
|
+
}
|
|
52
|
+
if (criterion.Value.trim().toLowerCase() === 'null') {
|
|
53
|
+
return { Sql: `[${field.Name}] IS NULL` };
|
|
54
|
+
}
|
|
55
|
+
const literal = criterionLiteral(field, criterion.Value);
|
|
56
|
+
if (literal === null) {
|
|
57
|
+
return { Problem: `the value of ${info.Name}.${field.Name} cannot be written as a ${field.Type} literal` };
|
|
58
|
+
}
|
|
59
|
+
return { Sql: `[${field.Name}] = ${literal}` };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/** The SQL Server literal for a criterion value, or null when the value does not fit the field type. */
|
|
63
|
+
function criterionLiteral(field, value) {
|
|
64
|
+
const escaped = value.replace(/'/g, "''");
|
|
65
|
+
if (!field.NeedsQuotes) {
|
|
66
|
+
const type = field.Type.trim().toLowerCase();
|
|
67
|
+
const lower = value.trim().toLowerCase();
|
|
68
|
+
if (type === 'bit' || type === 'boolean') {
|
|
69
|
+
if (lower === 'true' || lower === '1') {
|
|
70
|
+
return '1';
|
|
71
|
+
}
|
|
72
|
+
if (lower === 'false' || lower === '0') {
|
|
73
|
+
return '0';
|
|
74
|
+
}
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
return /^-?\d+(\.\d+)?$/.test(value.trim()) ? value.trim() : null;
|
|
78
|
+
}
|
|
79
|
+
if (IsUuidSQLType(field.Type) || IsDateSQLType(field.Type)) {
|
|
80
|
+
return `'${escaped}'`;
|
|
81
|
+
}
|
|
82
|
+
return `N'${escaped}'`;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Replaces the logged SET lines of lookup-sourced fields with subqueries, for one push. Registrations
|
|
86
|
+
* are keyed by the save-call hash of the record (`GenericDatabaseProvider.SaveCallVariableHash`), so
|
|
87
|
+
* only the SET line of that record's save is rewritten. A registration stays for the whole push.
|
|
88
|
+
*/
|
|
89
|
+
export class LookupSqlRewriter {
|
|
90
|
+
constructor(builder) {
|
|
91
|
+
this.linesByHash = new Map();
|
|
92
|
+
this.warnings = new Set();
|
|
93
|
+
/**
|
|
94
|
+
* The `SqlLoggingOptions.TransformStatement` function. It can run many times on the same text and
|
|
95
|
+
* on text that is never written. On an unexpected error it returns the text unchanged, so the log
|
|
96
|
+
* keeps the literal IDs, and keeps a warning.
|
|
97
|
+
*/
|
|
98
|
+
this.Transform = (sql) => {
|
|
99
|
+
try {
|
|
100
|
+
return this.replaceSetLines(sql);
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
this.warnings.add('SQL log: a statement kept its literal lookup IDs because the rewrite to subqueries failed.');
|
|
104
|
+
return sql;
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
this.builder = builder;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Registers one field of one save call. `value` is the resolved ID the statement holds. Returns
|
|
111
|
+
* false (and keeps a warning) when no subquery can be built, so the log keeps the literal ID.
|
|
112
|
+
*/
|
|
113
|
+
Register(entityName, fieldName, codeName, value, lookup, saveCallHash) {
|
|
114
|
+
const literals = SqlLiteralsOf(value);
|
|
115
|
+
if (literals.length === 0) {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
const subquery = this.builder.Build(lookup);
|
|
119
|
+
if (subquery.Problem !== undefined || subquery.Sql === undefined) {
|
|
120
|
+
this.warnings.add(`SQL log: ${entityName}.${fieldName} is written with this database's ID because ${subquery.Problem} (${lookup.Expression}). A database where that row has another ID will not match it.`);
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
const hash = saveCallHash.toLowerCase();
|
|
124
|
+
let lines = this.linesByHash.get(hash);
|
|
125
|
+
if (!lines) {
|
|
126
|
+
lines = new Map();
|
|
127
|
+
this.linesByHash.set(hash, lines);
|
|
128
|
+
}
|
|
129
|
+
const key = [codeName, ...literals].join('\u0000');
|
|
130
|
+
// Case-insensitive: SQL Server returns upper-case UUIDs where a lookup can return either case
|
|
131
|
+
lines.set(key, { Pattern: SetLinePattern(codeName, hash, literals, 'gmi'), Subquery: subquery.Sql });
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Registers each lookup-sourced field of the save statement of `entity`. A primary key is
|
|
136
|
+
* registered at every IS-A level, because each level's save sets it; another field at the level
|
|
137
|
+
* whose table holds it. Call it right before the save, so the hash uses the key values the
|
|
138
|
+
* provider hashes. `lookups` keys are matched without regard to case.
|
|
139
|
+
*/
|
|
140
|
+
RegisterEntityLookups(entity, lookups) {
|
|
141
|
+
if (!lookups || lookups.size === 0) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const keyNames = new Set(entity.EntityInfo.PrimaryKeys.map((pk) => pk.Name));
|
|
145
|
+
for (const [key, lookup] of lookups) {
|
|
146
|
+
const fieldName = entity.GetFieldByName(key)?.Name;
|
|
147
|
+
if (!fieldName) {
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
const levels = keyNames.has(fieldName) ? IsALevels(entity) : [OwningLevel(entity, fieldName)];
|
|
151
|
+
for (const level of levels) {
|
|
152
|
+
this.registerLevelField(level, fieldName, lookup);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Registers the primary key of a record that a `@lookup:…?create` made, keyed by the lookup that
|
|
158
|
+
* made it, and keeps a warning: where no row matches, the replay passes a NULL ID, so the
|
|
159
|
+
* `IF NOT EXISTS` guard inserts the row with an ID that database generates.
|
|
160
|
+
*/
|
|
161
|
+
RegisterLookupCreate(created, lookup) {
|
|
162
|
+
if (created.EntityInfo.PrimaryKeys.length !== 1) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
// Criteria that were lookups themselves are foreign keys the create sets: they nest too
|
|
166
|
+
const nested = new Map();
|
|
167
|
+
for (const criterion of lookup.Criteria) {
|
|
168
|
+
if (typeof criterion.Value !== 'string') {
|
|
169
|
+
nested.set(criterion.FieldName, criterion.Value);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
this.RegisterEntityLookups(created, nested);
|
|
173
|
+
const keyName = created.EntityInfo.FirstPrimaryKey.Name; // first-pk-ok: a single-column key was checked above
|
|
174
|
+
let registered = false;
|
|
175
|
+
for (const level of IsALevels(created)) {
|
|
176
|
+
registered = this.registerLevelField(level, keyName, lookup) || registered;
|
|
177
|
+
}
|
|
178
|
+
if (registered) {
|
|
179
|
+
this.warnings.add(`SQL log: the ${created.EntityInfo.Name} row that ${lookup.Expression} created is logged keyed by that lookup. On a database with no matching row the replay passes a NULL ID, so the IF NOT EXISTS guard inserts the row with an ID that database generates.`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
/** The warnings of this run, each once. */
|
|
183
|
+
Warnings() {
|
|
184
|
+
return [...this.warnings];
|
|
185
|
+
}
|
|
186
|
+
registerLevelField(level, fieldName, lookup) {
|
|
187
|
+
const field = level.GetFieldByName(fieldName);
|
|
188
|
+
if (!field) {
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
const levelInfo = level.EntityInfo;
|
|
192
|
+
const keyValues = level.PrimaryKey?.KeyValuePairs?.map((pair) => pair.Value) ?? [];
|
|
193
|
+
const saveCallHash = GenericDatabaseProvider.SaveCallVariableHash(levelInfo.SchemaName, levelInfo.BaseTable, keyValues);
|
|
194
|
+
return this.Register(levelInfo.Name, fieldName, field.EntityFieldInfo.CodeName, level.Get(fieldName), lookup, saveCallHash);
|
|
195
|
+
}
|
|
196
|
+
replaceSetLines(sql) {
|
|
197
|
+
if (this.linesByHash.size === 0) {
|
|
198
|
+
return sql;
|
|
199
|
+
}
|
|
200
|
+
let result = sql;
|
|
201
|
+
for (const hash of SaveCallHashesIn(sql)) {
|
|
202
|
+
for (const line of this.linesByHash.get(hash)?.values() ?? []) {
|
|
203
|
+
result = result.replace(line.Pattern, (_line, indent, variable) => `${indent}SET ${variable} = ${line.Subquery}`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return result;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* The lookup rewriter of one open SQL log, with the `SyncEngine.BeforeLookupCreateSaveSource` hook
|
|
211
|
+
* that registers each record a `?create` lookup makes.
|
|
212
|
+
*/
|
|
213
|
+
export class LookupSqlLog {
|
|
214
|
+
constructor(engine) {
|
|
215
|
+
this.engine = engine;
|
|
216
|
+
this.Rewriter = new LookupSqlRewriter(new LookupSubqueryBuilder((name) => engine.GetEntityInfo(name)));
|
|
217
|
+
this.hook = (created, lookup) => this.Rewriter.RegisterLookupCreate(created, lookup);
|
|
218
|
+
engine.BeforeLookupCreateSaveSource = this.hook;
|
|
219
|
+
}
|
|
220
|
+
/** Clears the engine's hook when it is still this log's hook. */
|
|
221
|
+
Close() {
|
|
222
|
+
if (this.engine.BeforeLookupCreateSaveSource === this.hook) {
|
|
223
|
+
this.engine.BeforeLookupCreateSaveSource = null;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
/** One `TransformStatement` that runs each transform in turn. */
|
|
228
|
+
export function ComposeStatementTransforms(...transforms) {
|
|
229
|
+
return (sql) => transforms.reduce((text, transform) => transform(text), sql);
|
|
230
|
+
}
|
|
231
|
+
//# sourceMappingURL=lookup-sql.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lookup-sql.js","sourceRoot":"","sources":["../../src/lib/lookup-sql.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Pre-flight for a create that a unique constraint would reject (#4400).
|
|
3
|
+
*
|
|
4
|
+
* A seed record with a literal primary key that is not in the database is created with that key.
|
|
5
|
+
* When the database already has a row with the same natural key under another ID (CodeGen and
|
|
6
|
+
* other server-side code create rows with database-generated IDs), the create violates the unique
|
|
7
|
+
* constraint and the push stops on a driver error. This check runs first: for each unique key of
|
|
8
|
+
* the table, it looks the record's values up and, on a hit, reports the existing row's key so the
|
|
9
|
+
* seed can be re-keyed with `@lookup`.
|
|
10
|
+
* @module natural-key-preflight
|
|
11
|
+
*/
|
|
12
|
+
import { type BaseEntity, type DatabaseProviderBase, type EntityFieldInfo, type EntityInfo, type IMetadataProvider, type UserInfo } from '@memberjunction/core';
|
|
13
|
+
/** One unique key of a table: its name and its columns in key order. */
|
|
14
|
+
export interface UniqueKeyColumns {
|
|
15
|
+
Name: string;
|
|
16
|
+
Columns: string[];
|
|
17
|
+
}
|
|
18
|
+
/** A row that already holds the natural key a create would add. */
|
|
19
|
+
export interface NaturalKeyConflict {
|
|
20
|
+
/** Unique constraint or index name. */
|
|
21
|
+
KeyName: string;
|
|
22
|
+
/** The entity's field names that make up the key. */
|
|
23
|
+
Fields: string[];
|
|
24
|
+
/** The primary key of the existing row. */
|
|
25
|
+
ExistingKey: Record<string, unknown>;
|
|
26
|
+
}
|
|
27
|
+
/** Reads unique keys and finds rows by them. */
|
|
28
|
+
export interface NaturalKeyReader {
|
|
29
|
+
/** The unique keys of the entity's table, other than the primary key. */
|
|
30
|
+
UniqueKeys(info: EntityInfo): Promise<UniqueKeyColumns[]>;
|
|
31
|
+
/** The primary key of a row whose fields equal `values`, or null when there is none. */
|
|
32
|
+
FindKey(info: EntityInfo, values: ReadonlyArray<{
|
|
33
|
+
Field: EntityFieldInfo;
|
|
34
|
+
Value: unknown;
|
|
35
|
+
}>): Promise<Record<string, unknown> | null>;
|
|
36
|
+
}
|
|
37
|
+
/** Finds the existing row, if any, that a create of `entity` would collide with on a unique key. */
|
|
38
|
+
export declare class NaturalKeyPreflight {
|
|
39
|
+
private readonly reader;
|
|
40
|
+
constructor(reader: NaturalKeyReader);
|
|
41
|
+
/**
|
|
42
|
+
* The first unique key whose values another row already holds, or null. A key is checked only
|
|
43
|
+
* when every one of its columns is a field of the entity with a non-null value and no column is
|
|
44
|
+
* part of the primary key.
|
|
45
|
+
*/
|
|
46
|
+
Check(entity: BaseEntity): Promise<NaturalKeyConflict | null>;
|
|
47
|
+
private keyValues;
|
|
48
|
+
}
|
|
49
|
+
/** Row shape of the unique-key catalog queries. */
|
|
50
|
+
interface UniqueKeyCatalogRow {
|
|
51
|
+
IndexName: string;
|
|
52
|
+
ColumnName: string;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Reads unique keys from the database catalog (unfiltered unique indexes and unique constraints,
|
|
56
|
+
* not the primary key) and finds rows with `RunView` on the record's provider, so the lookup sees
|
|
57
|
+
* rows the push transaction wrote. Catalog reads go to the pool and are never logged. A catalog
|
|
58
|
+
* read that fails gives no keys, so the check is skipped rather than failing the push.
|
|
59
|
+
*/
|
|
60
|
+
export declare class DatabaseNaturalKeyReader implements NaturalKeyReader {
|
|
61
|
+
private readonly provider;
|
|
62
|
+
private readonly contextUser;
|
|
63
|
+
private readonly rowProvider;
|
|
64
|
+
private readonly cache;
|
|
65
|
+
/**
|
|
66
|
+
* `rowProvider` is the provider rows are found on (the record's, so the read joins its
|
|
67
|
+
* transaction); `cache` (entity name → keys) can be shared by readers that find rows on
|
|
68
|
+
* different providers.
|
|
69
|
+
*/
|
|
70
|
+
constructor(provider: DatabaseProviderBase, contextUser: UserInfo, rowProvider?: IMetadataProvider, cache?: Map<string, Promise<UniqueKeyColumns[]>>);
|
|
71
|
+
UniqueKeys(info: EntityInfo): Promise<UniqueKeyColumns[]>;
|
|
72
|
+
FindKey(info: EntityInfo, values: ReadonlyArray<{
|
|
73
|
+
Field: EntityFieldInfo;
|
|
74
|
+
Value: unknown;
|
|
75
|
+
}>): Promise<Record<string, unknown> | null>;
|
|
76
|
+
private readUniqueKeys;
|
|
77
|
+
private literal;
|
|
78
|
+
}
|
|
79
|
+
/** The catalog query listing a table's unfiltered unique keys other than the primary key, one row per column in key order. */
|
|
80
|
+
export declare function UniqueKeyCatalogSQL(platformKey: string, schemaName: string, tableName: string): string;
|
|
81
|
+
/** Catalog rows grouped into keys, in row order. */
|
|
82
|
+
export declare function GroupUniqueKeyRows(rows: ReadonlyArray<UniqueKeyCatalogRow>): UniqueKeyColumns[];
|
|
83
|
+
/** The pre-flight error for a conflict. `recordKey` is the record's key as it may be shown. */
|
|
84
|
+
export declare function NaturalKeyConflictMessage(entityName: string, recordKey: string, conflict: NaturalKeyConflict): string;
|
|
85
|
+
export {};
|
|
86
|
+
//# sourceMappingURL=natural-key-preflight.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"natural-key-preflight.d.ts","sourceRoot":"","sources":["../../src/lib/natural-key-preflight.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAW,KAAK,UAAU,EAAE,KAAK,oBAAoB,EAAE,KAAK,eAAe,EAAE,KAAK,UAAU,EAAE,KAAK,iBAAiB,EAAE,KAAK,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAEzK,wEAAwE;AACxE,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,mEAAmE;AACnE,MAAM,WAAW,kBAAkB;IACjC,uCAAuC;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,qDAAqD;IACrD,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,2CAA2C;IAC3C,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC;AAED,gDAAgD;AAChD,MAAM,WAAW,gBAAgB;IAC/B,yEAAyE;IACzE,UAAU,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAAC;IAC1D,wFAAwF;IACxF,OAAO,CAAC,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,aAAa,CAAC;QAAE,KAAK,EAAE,eAAe,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC;CACvI;AAED,oGAAoG;AACpG,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAmB;gBAE9B,MAAM,EAAE,gBAAgB;IAIpC;;;;OAIG;IACG,KAAK,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC;IAoBnE,OAAO,CAAC,SAAS;CAkBlB;AASD,mDAAmD;AACnD,UAAU,mBAAmB;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;;;GAKG;AACH,qBAAa,wBAAyB,YAAW,gBAAgB;IAC/D,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAuB;IAChD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAW;IACvC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAgC;IAC5D,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA2C;IAEjE;;;;OAIG;gBAED,QAAQ,EAAE,oBAAoB,EAC9B,WAAW,EAAE,QAAQ,EACrB,WAAW,CAAC,EAAE,iBAAiB,EAC/B,KAAK,GAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAAa;IAQ7D,UAAU,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;IASnD,OAAO,CAAC,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,aAAa,CAAC;QAAE,KAAK,EAAE,eAAe,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;YAiB7H,cAAc;IAc5B,OAAO,CAAC,OAAO;CAahB;AAED,8HAA8H;AAC9H,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAsBtG;AAED,oDAAoD;AACpD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,aAAa,CAAC,mBAAmB,CAAC,GAAG,gBAAgB,EAAE,CAQ/F;AAED,+FAA+F;AAC/F,wBAAgB,yBAAyB,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,kBAAkB,GAAG,MAAM,CAYrH"}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Pre-flight for a create that a unique constraint would reject (#4400).
|
|
3
|
+
*
|
|
4
|
+
* A seed record with a literal primary key that is not in the database is created with that key.
|
|
5
|
+
* When the database already has a row with the same natural key under another ID (CodeGen and
|
|
6
|
+
* other server-side code create rows with database-generated IDs), the create violates the unique
|
|
7
|
+
* constraint and the push stops on a driver error. This check runs first: for each unique key of
|
|
8
|
+
* the table, it looks the record's values up and, on a hit, reports the existing row's key so the
|
|
9
|
+
* seed can be re-keyed with `@lookup`.
|
|
10
|
+
* @module natural-key-preflight
|
|
11
|
+
*/
|
|
12
|
+
import { RunView } from '@memberjunction/core';
|
|
13
|
+
/** Finds the existing row, if any, that a create of `entity` would collide with on a unique key. */
|
|
14
|
+
export class NaturalKeyPreflight {
|
|
15
|
+
constructor(reader) {
|
|
16
|
+
this.reader = reader;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* The first unique key whose values another row already holds, or null. A key is checked only
|
|
20
|
+
* when every one of its columns is a field of the entity with a non-null value and no column is
|
|
21
|
+
* part of the primary key.
|
|
22
|
+
*/
|
|
23
|
+
async Check(entity) {
|
|
24
|
+
const info = entity.EntityInfo;
|
|
25
|
+
const keys = await this.reader.UniqueKeys(info);
|
|
26
|
+
if (keys.length === 0) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
const keyNames = new Set(info.PrimaryKeys.map((pk) => pk.Name.toLowerCase()));
|
|
30
|
+
for (const key of keys) {
|
|
31
|
+
const values = this.keyValues(entity, key, keyNames);
|
|
32
|
+
if (!values) {
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
const existing = await this.reader.FindKey(info, values);
|
|
36
|
+
if (existing && !samePrimaryKey(entity, existing)) {
|
|
37
|
+
return { KeyName: key.Name, Fields: values.map((value) => value.Field.Name), ExistingKey: existing };
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
keyValues(entity, key, keyNames) {
|
|
43
|
+
if (key.Columns.length === 0) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
const values = [];
|
|
47
|
+
for (const column of key.Columns) {
|
|
48
|
+
if (keyNames.has(column.toLowerCase())) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
const field = entity.EntityInfo.Fields.find((candidate) => candidate.Name.toLowerCase() === column.toLowerCase());
|
|
52
|
+
const value = field ? entity.Get(field.Name) : undefined;
|
|
53
|
+
if (!field || value === null || value === undefined) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
values.push({ Field: field, Value: value });
|
|
57
|
+
}
|
|
58
|
+
return values;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/** True when `existing` is the entity's own key (compared without regard to case). */
|
|
62
|
+
function samePrimaryKey(entity, existing) {
|
|
63
|
+
return entity.EntityInfo.PrimaryKeys.every((pk) => String(entity.Get(pk.Name) ?? '').trim().toLowerCase() === String(existing[pk.Name] ?? '').trim().toLowerCase());
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Reads unique keys from the database catalog (unfiltered unique indexes and unique constraints,
|
|
67
|
+
* not the primary key) and finds rows with `RunView` on the record's provider, so the lookup sees
|
|
68
|
+
* rows the push transaction wrote. Catalog reads go to the pool and are never logged. A catalog
|
|
69
|
+
* read that fails gives no keys, so the check is skipped rather than failing the push.
|
|
70
|
+
*/
|
|
71
|
+
export class DatabaseNaturalKeyReader {
|
|
72
|
+
/**
|
|
73
|
+
* `rowProvider` is the provider rows are found on (the record's, so the read joins its
|
|
74
|
+
* transaction); `cache` (entity name → keys) can be shared by readers that find rows on
|
|
75
|
+
* different providers.
|
|
76
|
+
*/
|
|
77
|
+
constructor(provider, contextUser, rowProvider, cache = new Map()) {
|
|
78
|
+
this.provider = provider;
|
|
79
|
+
this.contextUser = contextUser;
|
|
80
|
+
this.rowProvider = rowProvider;
|
|
81
|
+
this.cache = cache;
|
|
82
|
+
}
|
|
83
|
+
UniqueKeys(info) {
|
|
84
|
+
let keys = this.cache.get(info.Name);
|
|
85
|
+
if (!keys) {
|
|
86
|
+
keys = this.readUniqueKeys(info);
|
|
87
|
+
this.cache.set(info.Name, keys);
|
|
88
|
+
}
|
|
89
|
+
return keys;
|
|
90
|
+
}
|
|
91
|
+
async FindKey(info, values) {
|
|
92
|
+
// Quoted, so a reserved-word or spaced column name still filters (rather than failing the read)
|
|
93
|
+
const filter = values.map(({ Field, Value }) => `${this.provider.QuoteIdentifier(Field.Name)} = ${this.literal(Field, Value)}`).join(' AND ');
|
|
94
|
+
const rv = this.rowProvider ? RunView.FromMetadataProvider(this.rowProvider) : new RunView();
|
|
95
|
+
const result = await rv.RunView({
|
|
96
|
+
EntityName: info.Name,
|
|
97
|
+
ExtraFilter: filter,
|
|
98
|
+
Fields: info.PrimaryKeys.map((pk) => pk.Name),
|
|
99
|
+
MaxRows: 1,
|
|
100
|
+
ResultType: 'simple',
|
|
101
|
+
}, this.contextUser);
|
|
102
|
+
return result.Success && result.Results.length > 0 ? result.Results[0] : null;
|
|
103
|
+
}
|
|
104
|
+
async readUniqueKeys(info) {
|
|
105
|
+
try {
|
|
106
|
+
const rows = await this.provider.ExecuteSQL(UniqueKeyCatalogSQL(this.provider.PlatformKey, info.SchemaName, info.BaseTable), undefined, { ignoreLogging: true, isMutation: false, ignoreAmbientTransaction: true }, this.contextUser);
|
|
107
|
+
return GroupUniqueKeyRows(rows);
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return [];
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
literal(field, value) {
|
|
114
|
+
if (value instanceof Date) {
|
|
115
|
+
return `'${value.toISOString()}'`;
|
|
116
|
+
}
|
|
117
|
+
if (typeof value === 'boolean') {
|
|
118
|
+
if (this.provider.PlatformKey === 'postgresql') {
|
|
119
|
+
return value ? 'true' : 'false';
|
|
120
|
+
}
|
|
121
|
+
return value ? '1' : '0';
|
|
122
|
+
}
|
|
123
|
+
const text = String(value).replace(/'/g, "''");
|
|
124
|
+
return field.NeedsQuotes ? `'${text}'` : text;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/** The catalog query listing a table's unfiltered unique keys other than the primary key, one row per column in key order. */
|
|
128
|
+
export function UniqueKeyCatalogSQL(platformKey, schemaName, tableName) {
|
|
129
|
+
const schema = schemaName.replace(/'/g, "''");
|
|
130
|
+
const table = tableName.replace(/'/g, "''");
|
|
131
|
+
if (platformKey === 'postgresql') {
|
|
132
|
+
return `SELECT ix.relname AS "IndexName", a.attname AS "ColumnName"
|
|
133
|
+
FROM pg_index x
|
|
134
|
+
JOIN pg_class t ON t.oid = x.indrelid
|
|
135
|
+
JOIN pg_namespace n ON n.oid = t.relnamespace
|
|
136
|
+
JOIN pg_class ix ON ix.oid = x.indexrelid
|
|
137
|
+
JOIN LATERAL unnest(x.indkey) WITH ORDINALITY AS k(attnum, ord) ON true
|
|
138
|
+
JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = k.attnum
|
|
139
|
+
WHERE x.indisunique AND NOT x.indisprimary AND x.indpred IS NULL
|
|
140
|
+
AND n.nspname = '${schema}' AND t.relname = '${table}'
|
|
141
|
+
ORDER BY ix.relname, k.ord`;
|
|
142
|
+
}
|
|
143
|
+
return `SELECT i.name AS IndexName, c.name AS ColumnName
|
|
144
|
+
FROM sys.indexes i
|
|
145
|
+
JOIN sys.index_columns ic ON ic.object_id = i.object_id AND ic.index_id = i.index_id
|
|
146
|
+
JOIN sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id
|
|
147
|
+
WHERE i.object_id = OBJECT_ID(N'[${schema.replace(/]/g, ']]')}].[${table.replace(/]/g, ']]')}]')
|
|
148
|
+
AND i.is_unique = 1 AND i.is_primary_key = 0 AND i.has_filter = 0 AND ic.is_included_column = 0
|
|
149
|
+
ORDER BY i.name, ic.key_ordinal`;
|
|
150
|
+
}
|
|
151
|
+
/** Catalog rows grouped into keys, in row order. */
|
|
152
|
+
export function GroupUniqueKeyRows(rows) {
|
|
153
|
+
const byName = new Map();
|
|
154
|
+
for (const row of rows) {
|
|
155
|
+
const columns = byName.get(row.IndexName) ?? [];
|
|
156
|
+
columns.push(row.ColumnName);
|
|
157
|
+
byName.set(row.IndexName, columns);
|
|
158
|
+
}
|
|
159
|
+
return [...byName].map(([Name, Columns]) => ({ Name, Columns }));
|
|
160
|
+
}
|
|
161
|
+
/** The pre-flight error for a conflict. `recordKey` is the record's key as it may be shown. */
|
|
162
|
+
export function NaturalKeyConflictMessage(entityName, recordKey, conflict) {
|
|
163
|
+
const existing = Object.entries(conflict.ExistingKey)
|
|
164
|
+
.map(([field, value]) => `${field}=${String(value)}`)
|
|
165
|
+
.join(', ');
|
|
166
|
+
const fields = conflict.Fields.join(', ');
|
|
167
|
+
const example = conflict.Fields.map((field) => `${field}=…`).join('&');
|
|
168
|
+
return (`${entityName}: no row has primary key {${recordKey}}, but row {${existing}} already has the same ${fields} ` +
|
|
169
|
+
`(unique key ${conflict.KeyName}), so creating this record would violate it. That row's ID was made by this ` +
|
|
170
|
+
`database, so a literal ID in metadata cannot match it. Re-key the record's primaryKey with @lookup on ` +
|
|
171
|
+
`${fields}, for example "@lookup:${entityName}.${example}".`);
|
|
172
|
+
}
|
|
173
|
+
//# sourceMappingURL=natural-key-preflight.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"natural-key-preflight.js","sourceRoot":"","sources":["../../src/lib/natural-key-preflight.ts"],"names":[],"mappings":"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"}
|
|
@@ -44,6 +44,13 @@ export interface DependencyAnalysisResult {
|
|
|
44
44
|
dependencyGraph: Map<string, Set<string>>;
|
|
45
45
|
dependencyLevels?: FlattenedRecord[][];
|
|
46
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* Depth-first topological order: each record comes after every record it depends on that is in
|
|
49
|
+
* `byId`. Records with no dependencies are started first, then the rest in input order. A cycle is
|
|
50
|
+
* broken where the walk meets it (cycles are reported separately); a dependency missing from `byId`
|
|
51
|
+
* is skipped.
|
|
52
|
+
*/
|
|
53
|
+
export declare function DependencyOrder(records: FlattenedRecord[], byId: ReadonlyMap<string, FlattenedRecord>): FlattenedRecord[];
|
|
47
54
|
/**
|
|
48
55
|
* Analyzes and sorts records based on their dependencies
|
|
49
56
|
*/
|
|
@@ -172,9 +179,9 @@ export declare class RecordDependencyAnalyzer {
|
|
|
172
179
|
*
|
|
173
180
|
* This is simply the reverse of the forward topological sort used for creates.
|
|
174
181
|
*/
|
|
175
|
-
ReverseTopologicalSort(records: FlattenedRecord[]
|
|
182
|
+
ReverseTopologicalSort(records: FlattenedRecord[]): FlattenedRecord[][];
|
|
176
183
|
/** @deprecated Use {@link ReverseTopologicalSort}. */
|
|
177
|
-
reverseTopologicalSort(records: FlattenedRecord[]
|
|
184
|
+
reverseTopologicalSort(records: FlattenedRecord[]): FlattenedRecord[][];
|
|
178
185
|
/**
|
|
179
186
|
* Find all transitive dependents of a set of records
|
|
180
187
|
* This is useful for finding all records that must be deleted when deleting a parent
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"record-dependency-analyzer.d.ts","sourceRoot":"","sources":["../../src/lib/record-dependency-analyzer.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAG3C;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,UAAU,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE;QACd,UAAU,EAAE,MAAM,CAAC;QACnB,MAAM,EAAE,UAAU,CAAC;QACnB,WAAW,EAAE,MAAM,CAAC;KACrB,CAAC;IACF,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,aAAa,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB,uFAAuF;IACvF,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC,aAAa,EAAE,eAAe,EAAE,CAAC;IACjC,oBAAoB,EAAE,MAAM,EAAE,EAAE,CAAC;IACjC,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;IAC1C,gBAAgB,CAAC,EAAE,eAAe,EAAE,EAAE,CAAC;CACxC;AAED;;GAEG;AACH,qEAAqE;AACrE,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,eAAe,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,eAAe,EAAE,CAAC,CAShG;AAED,qDAAqD;AACrD,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,eAAe,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,eAAe,EAAE,CAAC,CAEhG;AAED,qBAAa,wBAAwB;IACnC,OAAO,CAAC,QAAQ,CAAW;IAC3B,OAAO,CAAC,gBAAgB,CAAyB;IACjD,OAAO,CAAC,WAAW,CAA2C;IAC9D,OAAO,CAAC,eAAe,CAAsC;IAC7D,OAAO,CAAC,aAAa,CAAa;;IAMlC;;;;;;;;;;;;;OAaG;IACI,kBAAkB,CACvB,OAAO,EAAE,UAAU,EAAE,EACrB,UAAU,EAAE,MAAM,GACjB,eAAe,EAAE;IAWpB,kDAAkD;IAC3C,kBAAkB,CACvB,OAAO,EAAE,UAAU,EAAE,EACrB,UAAU,EAAE,MAAM,GACjB,eAAe,EAAE;IAIpB;;;;;;;;;OASG;IACI,sBAAsB,CAC3B,UAAU,EAAE,eAAe,EAAE,GAC5B,wBAAwB;IAkC3B,sDAAsD;IAC/C,sBAAsB,CAC3B,UAAU,EAAE,eAAe,EAAE,GAC5B,wBAAwB;IAI3B;;OAEG;IACI,KAAK,IAAI,IAAI;IAMpB,qCAAqC;IAC9B,KAAK,IAAI,IAAI;IAIpB;;;;;;;;;;OAUG;IACU,kBAAkB,CAC7B,OAAO,EAAE,UAAU,EAAE,EACrB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,wBAAwB,CAAC;IAiCpC;;OAEG;IACH,OAAO,CAAC,cAAc;IAoEtB;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAoC3B;;OAEG;IACH,OAAO,CAAC,6BAA6B;IAqBrC;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAqK5B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IA+B1B;;OAEG;IACH,OAAO,CAAC,sBAAsB;IAsB9B;;OAEG;IACH,OAAO,CAAC,aAAa;IAUrB;;OAEG;IACH,OAAO,CAAC,0BAA0B;IA0ClC;;OAEG;IACH,OAAO,CAAC,eAAe;
|
|
1
|
+
{"version":3,"file":"record-dependency-analyzer.d.ts","sourceRoot":"","sources":["../../src/lib/record-dependency-analyzer.ts"],"names":[],"mappings":"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"}
|