@memberjunction/integration-engine 5.49.0 → 5.50.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.
@@ -1,5 +1,30 @@
1
- import { Metadata, RunView } from '@memberjunction/core';
1
+ import { DatabaseProviderBase, Metadata, RunView } from '@memberjunction/core';
2
2
  import { serializeKeyValue } from './KeySerialization.js';
3
+ import { quoteTextLiteral } from './prefetchFilter.js';
4
+ /**
5
+ * Folds an external ID the way a default SQL Server collation folds it for `=`
6
+ * (`SQL_Latin1_General_CP1_CI_AS`: case-insensitive, trailing blanks ignored).
7
+ *
8
+ * This exists because the batched prefetch and the per-record query compare differently. The
9
+ * per-record query sends `ExternalSystemRecordID = '<id>'` and lets the DATABASE decide equality;
10
+ * the batch reads a set of rows and then pairs them up in JavaScript with `===`, which is
11
+ * case- and whitespace-exact. So a row the database happily matched can miss in the JS map — and
12
+ * a miss on the record map means "no mapping", which turns an UPDATE into a duplicate CREATE.
13
+ * Normalizing gives the batch path a second, laxer attempt that mirrors the database's own rule.
14
+ *
15
+ * **Only applied on a platform that actually folds** — see {@link MatchEngine.platformFoldsStringEquality}.
16
+ * PostgreSQL's `=` on `text`/`varchar` is case-SENSITIVE and NOT blank-insensitive, so applying
17
+ * this fold there would make the index answer a match the database would never have returned:
18
+ * the record would be written onto a different row instead of created. On PG the exact map is the
19
+ * whole answer, and a miss is a real miss.
20
+ */
21
+ function normalizeExternalID(id) {
22
+ // Spaces only, NOT `\s`. SQL Server's trailing-blank insensitivity is ANSI padding on
23
+ // ASCII 0x20; a trailing tab or newline is significant to it. Folding those too would make
24
+ // the batch index equate `abc\t` with `abc`, which the database does not — a fold the
25
+ // per-record query would never confirm, and a wrong mapping is worse than a missed one.
26
+ return id.replace(/ +$/, '').toLowerCase();
27
+ }
3
28
  /**
4
29
  * Resolves mapped records against existing MJ data to determine
5
30
  * whether each record should be Created, Updated, Skipped, or Deleted.
@@ -24,21 +49,151 @@ export class MatchEngine {
24
49
  this._provider = provider;
25
50
  const keyFields = fieldMaps.filter(fm => fm.IsKeyField && fm.Status === 'Active');
26
51
  const conflictResolution = entityMap.ConflictResolution;
52
+ // Resolve the batch's lookups up front instead of one query per record (tasks.md PR 2
53
+ // item 5). Every record consults the record map — on the deleted path directly, on the
54
+ // live path as the fallback when identity/key matching finds nothing — so on a
55
+ // 500-record batch this replaces up to 500 single-row reads with a single `IN` read.
56
+ //
57
+ // All of it goes out as ONE batched RunViews: the record-map read plus one read per
58
+ // criteria-shape group. These are independent queries with no data dependency between
59
+ // them, which is exactly what RunViews is for — issuing them separately (or as a
60
+ // Promise.all of RunView calls) pays a round trip per leg for no reason.
61
+ const { MapIndex: mapIndex, KeyIndex: keyIndex } = await this.PrefetchBatchLookups(records, entityMap, keyFields, contextUser);
27
62
  const results = [];
28
63
  for (const record of records) {
29
- const resolved = await this.ResolveSingleRecord(record, entityMap, keyFields, conflictResolution, contextUser);
64
+ const resolved = await this.ResolveSingleRecord(record, entityMap, keyFields, conflictResolution, contextUser, mapIndex, keyIndex);
30
65
  results.push(resolved);
31
66
  }
32
67
  return results;
33
68
  }
69
+ /**
70
+ * Issues every lookup this batch needs as ONE batched read, and builds both indexes from it.
71
+ *
72
+ * The legs are independent — the record-map read and each criteria-shape group's read share
73
+ * no data dependency — so they go out together via {@link RunView.RunViews} rather than as
74
+ * separate awaits. Result order matches param order, which is how each leg is attributed back.
75
+ *
76
+ * A `null` index means "the read failed, fall back to the per-record query" and is NOT the
77
+ * same as an empty index, which means "asked, and there is nothing". Conflating them would
78
+ * treat every record as unmapped and turn an incremental sync into a batch of duplicate
79
+ * creates, so the two are kept distinct all the way down.
80
+ */
81
+ async PrefetchBatchLookups(records, entityMap, keyFields, contextUser) {
82
+ const mapParams = this.buildRecordMapViewParams(records, entityMap);
83
+ const keyGroups = this.buildKeyMatchGroups(records, keyFields);
84
+ const emptyMapIndex = { Exact: new Map(), Normalized: new Map(), Ambiguous: new Set() };
85
+ const emptyKeyIndex = { Matched: new Map(), Unmatched: new Set() };
86
+ if (!mapParams && keyGroups.length === 0)
87
+ return { MapIndex: emptyMapIndex, KeyIndex: emptyKeyIndex };
88
+ const params = [];
89
+ if (mapParams)
90
+ params.push(mapParams);
91
+ for (const group of keyGroups)
92
+ params.push(group.Params);
93
+ const rv = new RunView();
94
+ const results = await rv.RunViews(params, contextUser);
95
+ let next = 0;
96
+ const mapIndex = mapParams
97
+ ? this.buildRecordMapIndexFromResult(results[next++])
98
+ : emptyMapIndex;
99
+ return {
100
+ MapIndex: mapIndex,
101
+ KeyIndex: this.buildKeyMatchIndex(keyGroups, results.slice(next)),
102
+ };
103
+ }
104
+ /**
105
+ * The read that resolves an entire batch of external IDs against the record map, or null when
106
+ * the batch carries no usable external ID (nothing to ask, so no query is issued).
107
+ */
108
+ buildRecordMapViewParams(records, entityMap) {
109
+ const externalIDs = Array.from(new Set(records.map(r => r.ExternalRecord.ExternalID).filter(id => id != null && id !== '')));
110
+ if (externalIDs.length === 0)
111
+ return null;
112
+ const inList = externalIDs.map(id => this.quoteLiteral(id)).join(',');
113
+ return {
114
+ EntityName: 'MJ: Company Integration Record Maps',
115
+ ExtraFilter: `CompanyIntegrationID='${entityMap.CompanyIntegrationID}' ` +
116
+ `AND EntityID='${entityMap.EntityID}' ` +
117
+ `AND ExternalSystemRecordID IN (${inList})`,
118
+ Fields: ['ExternalSystemRecordID', 'EntityRecordID'],
119
+ IgnoreMaxRows: true, // a batch can exceed the entity's default row cap
120
+ ResultType: 'simple',
121
+ // Same reason the per-record lookup bypasses the cache: this decides CREATE vs UPDATE,
122
+ // and a stale miss re-creates a record that already exists.
123
+ BypassCache: true,
124
+ };
125
+ }
126
+ /**
127
+ * Builds the record-map index from its leg of the batched read.
128
+ *
129
+ * Returns an index containing only the IDs that HAVE a mapping, so a miss is represented by
130
+ * absence — exactly what the per-record lookup needed to distinguish. Returns null if the read
131
+ * failed, which makes callers fall back to their original per-record query rather than
132
+ * silently treating every record as unmapped.
133
+ *
134
+ * The index carries a second, normalized view of the same rows. The `IN (…)` read is evaluated
135
+ * by the database under its own collation, so a row stored as `abc` comes back for a requested
136
+ * `ABC`; pairing the result up in JavaScript with `===` would then miss it. See
137
+ * {@link normalizeExternalID}.
138
+ */
139
+ buildRecordMapIndexFromResult(result) {
140
+ if (!result?.Success)
141
+ return null;
142
+ return this.buildRecordMapIndex(result.Results);
143
+ }
144
+ /** Builds the exact + normalized views of a record-map read, separating out ambiguous folds. */
145
+ buildRecordMapIndex(rows) {
146
+ const index = { Exact: new Map(), Normalized: new Map(), Ambiguous: new Set() };
147
+ const ambiguous = index.Ambiguous;
148
+ const folds = this.platformFoldsStringEquality;
149
+ for (const row of rows) {
150
+ // A mapping with no external ID cannot be looked up by one. Skipping it keeps a bad
151
+ // row out of the index instead of taking the whole batch's matching down with it.
152
+ const externalID = row.ExternalSystemRecordID;
153
+ if (typeof externalID !== 'string' || externalID === '')
154
+ continue;
155
+ index.Exact.set(externalID, row.EntityRecordID);
156
+ // Left empty on an exact-comparison platform, so the folded lookup can never answer
157
+ // there — the exact map alone is what the database's own `IN (…)` decided.
158
+ if (!folds)
159
+ continue;
160
+ const norm = normalizeExternalID(externalID);
161
+ const seen = index.Normalized.get(norm);
162
+ if (seen === undefined)
163
+ index.Normalized.set(norm, row.EntityRecordID);
164
+ else if (seen !== row.EntityRecordID)
165
+ ambiguous.add(norm);
166
+ }
167
+ for (const key of ambiguous)
168
+ index.Normalized.delete(key);
169
+ return index;
170
+ }
171
+ /**
172
+ * True when the platform's `=` on string columns is case- and trailing-blank-insensitive, so
173
+ * the JS-side fold in {@link normalizeExternalID} reproduces what the database already did.
174
+ *
175
+ * SQL Server, under the default `SQL_Latin1_General_CP1_CI_AS` collation, is. PostgreSQL is
176
+ * not: `text`/`varchar` equality there is exact on both counts. Folding on PG would make the
177
+ * batch index return a mapping the per-record query would never return — the record would be
178
+ * UPDATED onto someone else's row instead of created, which is the one error direction this
179
+ * whole index is built to avoid. A provider with no dialect (client-side) gets `false`, the
180
+ * conservative answer.
181
+ */
182
+ get platformFoldsStringEquality() {
183
+ const provider = this.ProviderToUse;
184
+ // `PlatformKey` rather than `Dialect.PlatformKey`: the provider's `Dialect` getter
185
+ // memoizes the dialect object, so reading the platform through it answers from whatever
186
+ // was cached first. The platform is the property being asked about anyway.
187
+ return provider instanceof DatabaseProviderBase && provider.PlatformKey === 'sqlserver';
188
+ }
34
189
  /**
35
190
  * Resolves a single record by checking for an existing MJ match.
36
191
  */
37
- async ResolveSingleRecord(record, entityMap, keyFields, conflictResolution, contextUser) {
192
+ async ResolveSingleRecord(record, entityMap, keyFields, conflictResolution, contextUser, mapIndex, keyIndex) {
38
193
  if (record.ExternalRecord.IsDeleted) {
39
- return this.ResolveDeletedRecord(record, entityMap, contextUser);
194
+ return this.ResolveDeletedRecord(record, entityMap, contextUser, mapIndex);
40
195
  }
41
- const existingID = await this.FindExistingRecord(record, entityMap, keyFields, contextUser);
196
+ const existingID = await this.FindExistingRecord(record, entityMap, keyFields, contextUser, mapIndex, keyIndex);
42
197
  if (existingID) {
43
198
  return this.ResolveExistingRecord(record, existingID, conflictResolution);
44
199
  }
@@ -47,8 +202,8 @@ export class MatchEngine {
47
202
  /**
48
203
  * Handles records marked as deleted in the external system.
49
204
  */
50
- async ResolveDeletedRecord(record, entityMap, contextUser) {
51
- const existingID = await this.FindRecordMapEntry(entityMap.CompanyIntegrationID, record.ExternalRecord.ExternalID, entityMap.EntityID, contextUser);
205
+ async ResolveDeletedRecord(record, entityMap, contextUser, mapIndex) {
206
+ const existingID = await this.FindRecordMapEntry(entityMap.CompanyIntegrationID, record.ExternalRecord.ExternalID, entityMap.EntityID, contextUser, mapIndex);
52
207
  if (existingID) {
53
208
  return { ...record, ChangeType: 'Delete', MatchedMJRecordID: existingID };
54
209
  }
@@ -64,70 +219,91 @@ export class MatchEngine {
64
219
  return { ...record, ChangeType: 'Update', MatchedMJRecordID: existingID };
65
220
  }
66
221
  /**
67
- * Attempts to find an existing MJ record by key field matching, then falls back
68
- * to the CompanyIntegrationRecordMap.
222
+ * Attempts to find an existing MJ record by identity (PK) or, failing that, by the
223
+ * configured key fields — then falls back to the CompanyIntegrationRecordMap.
224
+ *
225
+ * The direct lookup is attempted whenever it CAN resolve something:
226
+ * - the mapped record carries a complete PK (single OR composite) — the identity case,
227
+ * and the same key the apply path will address the row by; or
228
+ * - key fields are configured — the fallback case, for mapping external records onto
229
+ * pre-existing MJ rows whose PK the external system does not know.
69
230
  *
70
- * For composite-PK entities (no auto-generated ID), key-field matching is always
71
- * attempted (even when no key fields are configured) so that PK fields themselves
72
- * serve as the unique match criteria. This handles entities like InsightTopics
73
- * where (person_id, topic) together form the natural key.
231
+ * Previously a single-PK entity with no configured key fields skipped this entirely and
232
+ * relied on the record map alone so a record whose map row was missing (or truncated;
233
+ * see `LoadAllRecordMaps`) re-CREATED a row whose PK already existed. Integration shadow
234
+ * tables are exactly this shape: a single soft PK holding the external ID.
74
235
  */
75
- async FindExistingRecord(record, entityMap, keyFields, contextUser) {
76
- const md = this.ProviderToUse;
77
- const entityInfo = md.EntityByName(record.MJEntityName);
78
- const isCompositePK = (entityInfo?.PrimaryKeys?.length ?? 0) > 1;
79
- if (keyFields.length > 0 || isCompositePK) {
80
- const idByKeys = await this.FindByKeyFields(record, keyFields, contextUser);
236
+ async FindExistingRecord(record, entityMap, keyFields, contextUser, mapIndex, keyIndex) {
237
+ if (keyFields.length > 0 || this.hasCompleteMappedPrimaryKey(record)) {
238
+ const idByKeys = await this.LookupByKeyFields(record, keyFields, contextUser, keyIndex);
81
239
  if (idByKeys)
82
240
  return idByKeys;
83
241
  }
84
- return this.FindRecordMapEntry(entityMap.CompanyIntegrationID, record.ExternalRecord.ExternalID, entityMap.EntityID, contextUser);
242
+ return this.FindRecordMapEntry(entityMap.CompanyIntegrationID, record.ExternalRecord.ExternalID, entityMap.EntityID, contextUser, mapIndex);
85
243
  }
86
244
  /**
87
- * Searches for an existing MJ record using key field values.
245
+ * Answers the identity/key lookup from the batch prefetch when it can, and only issues the
246
+ * per-record query when the prefetch neither found the record nor proved it absent.
247
+ */
248
+ async LookupByKeyFields(record, keyFields, contextUser, keyIndex) {
249
+ if (keyIndex) {
250
+ const criteria = this.BuildMatchCriteria(record, keyFields);
251
+ if (criteria) {
252
+ const key = this.CriteriaKey(criteria.Fields, criteria.Values);
253
+ const matched = keyIndex.Matched.get(key);
254
+ if (matched)
255
+ return matched;
256
+ if (keyIndex.Unmatched.has(key))
257
+ return null;
258
+ }
259
+ }
260
+ return this.FindByKeyFields(record, keyFields, contextUser);
261
+ }
262
+ /**
263
+ * Returns the entity's primary-key fields (soft PKs included — integration shadow tables
264
+ * carry no physical key, so `PrimaryKeys` is populated from `additionalSchemaInfo` and is
265
+ * still the natural identity).
266
+ */
267
+ primaryKeyFieldsFor(mjEntityName) {
268
+ const entityInfo = this.ProviderToUse.EntityByName(mjEntityName);
269
+ return entityInfo?.PrimaryKeys ?? (entityInfo?.FirstPrimaryKey ? [entityInfo.FirstPrimaryKey] : []);
270
+ }
271
+ /** True when the mapped data carries a value for EVERY PK field — i.e. it asserts an identity. */
272
+ hasCompleteMappedPrimaryKey(record) {
273
+ const pkFields = this.primaryKeyFieldsFor(record.MJEntityName);
274
+ if (pkFields.length === 0)
275
+ return false;
276
+ return pkFields.every(f => record.MappedFields[f.Name] != null);
277
+ }
278
+ /**
279
+ * Searches for an existing MJ record, using ONE definition of identity.
88
280
  *
89
- * For composite-PK entities, all PK fields are added to the WHERE filter to
90
- * guarantee a unique match, and the returned ID is a '|'-delimited composite
91
- * of all PK field values (matching the ExternalID format used by connectors).
281
+ * **The unified rule (tasks.md PR 2 item 3).** The entity's primary key — including a
282
+ * soft PK, which is what integration shadow tables carry — is the single definition of
283
+ * record identity, for matching AND for saving. Configured key fields (`IsKeyField`) are
284
+ * a *fallback lookup* for records whose PK the mapped data does not carry, never a
285
+ * competing identity.
92
286
  *
93
- * For single-PK entities, behaviour is unchanged: filter by configured key fields,
94
- * return the single PK value as a plain string.
287
+ * Concretely: when the mapped record carries a COMPLETE PK, match on the PK alone. The
288
+ * apply path already addresses the record by its PK (`CreateRecord.extractMappedPrimaryKey`
289
+ * → `InnerLoad`), so matching on anything else is exactly how a record could match one row
290
+ * and then be written to another — or match a row whose PK then collides on save. When the
291
+ * PK is absent or partial (e.g. mapping external records onto pre-existing MJ rows keyed by
292
+ * a server-assigned UUID), fall back to the configured key fields, plus whatever PK parts
293
+ * ARE present to narrow the result.
294
+ *
295
+ * The returned ID is always the PK values in `PrimaryKeys` order, '|'-joined — the same
296
+ * format `ExternalID`/`EntityRecordID` use — so the caller can load the row directly.
95
297
  */
96
298
  async FindByKeyFields(record, keyFields, contextUser) {
97
- const md = this.ProviderToUse;
98
- const entityInfo = md.EntityByName(record.MJEntityName);
99
- const pkFields = entityInfo?.PrimaryKeys ?? (entityInfo?.FirstPrimaryKey ? [entityInfo.FirstPrimaryKey] : []);
100
- if (pkFields.length === 0)
101
- return null;
102
- // Start with the configured key-field filter clauses
103
- const filterClauses = this.BuildKeyFieldFilter(record, keyFields);
104
- // For composite-PK entities, augment the filter with all PK field values
105
- // taken directly from the mapped record. This ensures uniqueness even when
106
- // no key fields are configured and prevents matching the wrong row when
107
- // the configured key fields alone are not unique (e.g. person_id matches
108
- // all topics for a given person).
109
- if (pkFields.length > 1) {
110
- for (const pkField of pkFields) {
111
- const value = record.MappedFields[pkField.Name];
112
- if (value == null)
113
- continue;
114
- const escaped = serializeKeyValue(value).replace(/'/g, "''");
115
- // ANSI double-quoted identifier — portable across SQL Server (QUOTED_IDENTIFIER ON,
116
- // the driver default) and Postgres (exact-case; integration columns are lowercase).
117
- // Plain identifiers break when a column name is a reserved word (e.g. a soft PK named
118
- // `open`/`order`); brackets would fix SQL Server but break Postgres, so double-quote.
119
- const clause = `"${pkField.Name}" = '${escaped}'`;
120
- if (!filterClauses.includes(clause)) {
121
- filterClauses.push(clause);
122
- }
123
- }
124
- }
125
- if (filterClauses.length === 0)
299
+ const pkFields = this.primaryKeyFieldsFor(record.MJEntityName);
300
+ const match = this.BuildMatchCriteria(record, keyFields);
301
+ if (!match)
126
302
  return null;
127
303
  const rv = new RunView();
128
304
  const result = await rv.RunView({
129
305
  EntityName: record.MJEntityName,
130
- ExtraFilter: filterClauses.join(' AND '),
306
+ ExtraFilter: this.CriteriaToSQL(match),
131
307
  Fields: pkFields.map(f => f.Name),
132
308
  MaxRows: 1,
133
309
  ResultType: 'simple',
@@ -139,32 +315,226 @@ export class MatchEngine {
139
315
  return pkFields.map(f => result.Results[0][f.Name] ?? '').join('|');
140
316
  }
141
317
  /**
142
- * Builds a SQL filter clause from key field values on a mapped record.
318
+ * Builds the field/value criteria that identify a record, applying the item-3 identity rule:
319
+ * a COMPLETE primary key matches on the PK alone; otherwise the configured key fields plus
320
+ * whatever PK parts are present. Returns null when nothing can be matched on.
321
+ *
322
+ * Field/value pairs (rather than pre-rendered SQL) so the same criteria can be rendered as a
323
+ * filter clause AND used as a local lookup key when a whole batch is resolved in one query.
143
324
  */
144
- BuildKeyFieldFilter(record, keyFields) {
145
- const clauses = [];
325
+ BuildMatchCriteria(record, keyFields) {
326
+ const pkFields = this.primaryKeyFieldsFor(record.MJEntityName);
327
+ if (pkFields.length === 0)
328
+ return null;
329
+ // serializeKeyValue mirrors the write-side coercion (objects → JSON, not "[object Object]")
330
+ // so an object-valued key filters against the value actually stored in the column.
331
+ const pk = { Fields: [], Values: [] };
332
+ for (const pkField of pkFields) {
333
+ const value = record.MappedFields[pkField.Name];
334
+ if (value == null)
335
+ continue;
336
+ pk.Fields.push(pkField.Name);
337
+ pk.Values.push(serializeKeyValue(value));
338
+ }
339
+ // Complete PK → identity match, on the PK alone. Configured key fields are deliberately
340
+ // NOT and-ed in here: a key field that disagrees with the PK would turn a correct
341
+ // identity match into a miss, and a miss becomes a duplicate INSERT downstream.
342
+ if (pk.Fields.length === pkFields.length)
343
+ return pk;
344
+ // Partial/absent PK → key fields first, then the PK parts we do have, to narrow.
345
+ // A field can be both a key field and a PK part; `A='x' AND A='x'` is noise, so dedup
346
+ // by name (both read the same MappedFields entry, so the values cannot disagree).
347
+ const merged = { Fields: [], Values: [] };
348
+ const add = (field, value) => {
349
+ if (merged.Fields.includes(field))
350
+ return;
351
+ merged.Fields.push(field);
352
+ merged.Values.push(value);
353
+ };
146
354
  for (const kf of keyFields) {
147
355
  const value = record.MappedFields[kf.DestinationFieldName];
148
356
  if (value == null)
149
357
  continue;
150
- // serializeKeyValue mirrors the write-side coercion (objects → JSON, not "[object Object]")
151
- // so an object-valued key filters against the value actually stored in the column.
152
- const escaped = serializeKeyValue(value).replace(/'/g, "''");
153
- // ANSI double-quoted identifier — reserved-word-safe + portable; see CompositePK note above.
154
- clauses.push(`"${kf.DestinationFieldName}" = '${escaped}'`);
358
+ add(kf.DestinationFieldName, serializeKeyValue(value));
155
359
  }
156
- return clauses;
360
+ for (let i = 0; i < pk.Fields.length; i++)
361
+ add(pk.Fields[i], pk.Values[i]);
362
+ return merged.Fields.length > 0 ? merged : null;
363
+ }
364
+ /**
365
+ * Quotes a value as a SQL string literal for a `RunView` `ExtraFilter`.
366
+ *
367
+ * `ExtraFilter` is SQL text, not a parameter list, so literals must be escaped here rather
368
+ * than bound. Where the provider is a database provider this defers to its dialect — the same
369
+ * rule `RecordMapBatch`'s read path uses, so the two cannot disagree about a value.
370
+ * `IMetadataProvider` in general carries no dialect (a client-side provider has none), so the
371
+ * fallback is ANSI quote-doubling, which is what both supported platforms do.
372
+ *
373
+ * Every value quoted here — external IDs and key-field values alike — is compared against an
374
+ * nvarchar column, so it goes through {@link quoteTextLiteral} rather than the dialect's bare
375
+ * `QuoteStringLiteral`: on SQL Server the bare form is a *varchar* literal and silently drops
376
+ * any character outside the database's collation codepage before the comparison.
377
+ */
378
+ quoteLiteral(value) {
379
+ const provider = this.ProviderToUse;
380
+ return provider instanceof DatabaseProviderBase
381
+ ? quoteTextLiteral(value, provider.Dialect)
382
+ : `'${value.replace(/'/g, "''")}'`;
383
+ }
384
+ /**
385
+ * Renders criteria as a filter clause.
386
+ *
387
+ * ANSI double-quoted identifiers — portable across SQL Server (QUOTED_IDENTIFIER ON, the
388
+ * driver default) and Postgres (exact-case; integration columns are lowercase). Plain
389
+ * identifiers break on a column named for a reserved word (e.g. a soft PK named
390
+ * `open`/`order`); brackets would fix SQL Server but break Postgres, so double-quote.
391
+ */
392
+ CriteriaToSQL(criteria) {
393
+ return criteria.Fields
394
+ .map((f, i) => `"${f}" = ${this.quoteLiteral(criteria.Values[i])}`)
395
+ .join(' AND ');
396
+ }
397
+ /**
398
+ * Order-independent local lookup key for a criteria set (and for a row read back for it).
399
+ *
400
+ * `\0` delimits both within and between pairs, because neither a field name nor a value can
401
+ * contain it and no printable delimiter can make that guarantee. With a space, field `A B`
402
+ * = `C` and field `A` = `B C` both render `A B C` — and since this key is what decides which
403
+ * row answers which criteria, a collision attributes a row to the wrong record.
404
+ */
405
+ CriteriaKey(fields, values) {
406
+ return fields
407
+ .map((f, i) => [f, values[i]])
408
+ .sort((a, b) => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0))
409
+ .map(([f, v]) => `${f}\0${v}`)
410
+ .join('\0');
411
+ }
412
+ /**
413
+ * Resolves the identity/key lookup for a WHOLE batch in one query per criteria shape,
414
+ * instead of one query per record (tasks.md PR 2 item 5).
415
+ *
416
+ * Records are grouped by entity + the exact set of fields they match on, so every record in
417
+ * a group produces a structurally identical AND-clause; the group's clauses are OR-ed into a
418
+ * single read and the rows attributed back locally by the same field/value pairs that built
419
+ * the filter. A 500-record batch of an integration shadow table (single soft PK) collapses
420
+ * from 500 reads to 1.
421
+ *
422
+ * Attribution is deliberately conservative. `Matched` holds records we found a row for;
423
+ * `Unmatched` holds records we can PROVE have no row — only populated when the group's read
424
+ * returned nothing at all, so a record whose row exists but whose local key comparison
425
+ * differs from the database's (case-insensitive collation, numeric/date formatting) is left
426
+ * in neither set and falls back to its own query. A false "unmatched" would create a
427
+ * duplicate, so it is never inferred.
428
+ *
429
+ * This half is pure: it builds the reads without issuing them, so every group goes out as one
430
+ * leg of the batch's single {@link RunView.RunViews} call. {@link MatchEngine.buildKeyMatchIndex}
431
+ * attributes the results back.
432
+ */
433
+ buildKeyMatchGroups(records, keyFields) {
434
+ const bySignature = new Map();
435
+ for (const record of records) {
436
+ if (record.ExternalRecord.IsDeleted)
437
+ continue;
438
+ if (keyFields.length === 0 && !this.hasCompleteMappedPrimaryKey(record))
439
+ continue;
440
+ const criteria = this.BuildMatchCriteria(record, keyFields);
441
+ if (!criteria)
442
+ continue;
443
+ // `\0`-delimited for the same reason CriteriaKey is: entity `AB` + field `C` and
444
+ // entity `A` + field `BC` would otherwise share a signature, merging two entities
445
+ // into one group that then reads whichever entity landed first.
446
+ const signature = `${record.MJEntityName}\0${[...criteria.Fields].sort().join('\0')}`;
447
+ const group = bySignature.get(signature) ?? { EntityName: record.MJEntityName, Criteria: [] };
448
+ group.Criteria.push(criteria);
449
+ if (!bySignature.has(signature))
450
+ bySignature.set(signature, group);
451
+ }
452
+ const groups = [];
453
+ for (const group of bySignature.values()) {
454
+ const pkFields = this.primaryKeyFieldsFor(group.EntityName);
455
+ if (pkFields.length === 0)
456
+ continue;
457
+ // Distinct clause sets only — a batch commonly repeats the same external record.
458
+ const clauses = new Map();
459
+ for (const c of group.Criteria)
460
+ clauses.set(this.CriteriaKey(c.Fields, c.Values), c);
461
+ const lookupFields = group.Criteria[0].Fields;
462
+ groups.push({
463
+ PKFields: pkFields,
464
+ LookupFields: lookupFields,
465
+ Clauses: clauses,
466
+ Params: {
467
+ EntityName: group.EntityName,
468
+ ExtraFilter: Array.from(clauses.values())
469
+ .map(c => `(${this.CriteriaToSQL(c)})`)
470
+ .join(' OR '),
471
+ Fields: Array.from(new Set([...pkFields.map(f => f.Name), ...lookupFields])),
472
+ IgnoreMaxRows: true, // a batch's matches can exceed the entity's default row cap
473
+ ResultType: 'simple',
474
+ },
475
+ });
476
+ }
477
+ return groups;
478
+ }
479
+ /**
480
+ * Attributes the batched group reads back to their criteria, positionally, in the order
481
+ * {@link MatchEngine.buildKeyMatchGroups} emitted them.
482
+ *
483
+ * Returns null if any group read failed — callers then use the original per-record path.
484
+ */
485
+ buildKeyMatchIndex(groups, results) {
486
+ const index = { Matched: new Map(), Unmatched: new Set() };
487
+ for (let i = 0; i < groups.length; i++) {
488
+ const group = groups[i];
489
+ const result = results[i];
490
+ if (!result?.Success)
491
+ return null;
492
+ const rows = result.Results;
493
+ for (const row of rows) {
494
+ const rowKey = this.CriteriaKey(group.LookupFields, group.LookupFields.map(f => serializeKeyValue(row[f])));
495
+ if (index.Matched.has(rowKey))
496
+ continue; // first row wins, as MaxRows:1 did
497
+ index.Matched.set(rowKey, group.PKFields.map(f => row[f.Name] ?? '').join('|'));
498
+ }
499
+ // Only a completely empty read proves absence for the whole group (see doc above).
500
+ if (rows.length === 0) {
501
+ for (const key of group.Clauses.keys())
502
+ index.Unmatched.add(key);
503
+ }
504
+ }
505
+ return index;
157
506
  }
158
507
  /**
159
508
  * Checks the CompanyIntegrationRecordMap for a previous external↔MJ mapping.
160
509
  */
161
- async FindRecordMapEntry(companyIntegrationID, externalID, entityID, contextUser) {
510
+ async FindRecordMapEntry(companyIntegrationID, externalID, entityID, contextUser, mapIndex) {
511
+ // The batch prefetch read every mapping for this batch's external IDs in one query, so
512
+ // a hit here is the same answer the per-record query would give.
513
+ //
514
+ // Concluding ABSENCE from the index is the delicate part, and it is only sound because the
515
+ // index is consulted the same two ways the database compared: exactly, then folded the way
516
+ // a default collation folds (normalizeExternalID). Two cases the index cannot speak for —
517
+ // an ambiguous fold, and an empty ID that was never in the `IN (…)` list — fall through to
518
+ // the per-record query below and let the database answer.
519
+ if (mapIndex && externalID) {
520
+ const exact = mapIndex.Exact.get(externalID);
521
+ if (exact !== undefined)
522
+ return exact;
523
+ const folded = normalizeExternalID(externalID);
524
+ const near = mapIndex.Normalized.get(folded);
525
+ if (near !== undefined)
526
+ return near;
527
+ // Ambiguous fold: several mappings collapse onto this key and the index cannot say
528
+ // which one the database would pick. Fall through and let it decide.
529
+ if (!mapIndex.Ambiguous.has(folded))
530
+ return null;
531
+ }
162
532
  const rv = new RunView();
163
- const escapedExternalID = externalID.replace(/'/g, "''");
533
+ const quotedExternalID = this.quoteLiteral(externalID);
164
534
  const result = await rv.RunView({
165
535
  EntityName: 'MJ: Company Integration Record Maps',
166
536
  ExtraFilter: `CompanyIntegrationID='${companyIntegrationID}' ` +
167
- `AND ExternalSystemRecordID='${escapedExternalID}' ` +
537
+ `AND ExternalSystemRecordID=${quotedExternalID} ` +
168
538
  `AND EntityID='${entityID}'`,
169
539
  Fields: ['EntityRecordID'],
170
540
  MaxRows: 1,
@@ -1 +1 @@
1
- {"version":3,"file":"MatchEngine.js","sourceRoot":"","sources":["../src/MatchEngine.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"MatchEngine.js","sourceRoot":"","sources":["../src/MatchEngine.ts"],"names":[],"mappings":"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"}