Skip to content

Commit 2bf08ac

Browse files
authored
Merge pull request #603 from M4xymm/RDBC-1126
RDBC-1126 Don't register existing included documents as missing
2 parents 0884cf4 + 3e43d24 commit 2bf08ac

4 files changed

Lines changed: 120 additions & 25 deletions

File tree

‎src/Documents/Session/InMemoryDocumentSessionOperations.ts‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -515,7 +515,7 @@ export abstract class InMemoryDocumentSessionOperations
515515

516516
IncludesUtil.include(documentInfo.document, include, (includeId: string) => {
517517
hasAll = hasAll && this.isLoaded(includeId);
518-
});
518+
}, this.conventions.identityPartsSeparator);
519519

520520
if (!hasAll) {
521521
return false;
@@ -748,6 +748,10 @@ export abstract class InMemoryDocumentSessionOperations
748748
return;
749749
}
750750

751+
if (this.conventions.serverToLocalFieldNameConverter) {
752+
return;
753+
}
754+
751755
for (const result of results) {
752756
for (const include of includePaths) {
753757
if (include === CONSTANTS.Documents.Indexing.Fields.DOCUMENT_ID_FIELD_NAME) {
@@ -769,7 +773,7 @@ export abstract class InMemoryDocumentSessionOperations
769773
}
770774

771775
this.registerMissing(id);
772-
});
776+
}, this.conventions.identityPartsSeparator);
773777
}
774778
}
775779
}

‎src/Documents/Session/IncludesUtil.ts‎

Lines changed: 31 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,17 @@ const COLLECTION_SEPARATOR = "[].";
1414
export class IncludesUtil {
1515

1616
public static include(
17-
document: object, include: string, loadId: (id: string) => void): void {
17+
document: object, include: string, loadId: (id: string) => void, identityPartsSeparator = "/"): void {
1818
if (!include || !document) {
1919
return;
2020
}
2121

22-
const { path, addition, isPrefix } = IncludesUtil._getIncludePath(include);
22+
const includePath = IncludesUtil._getIncludePath(include, identityPartsSeparator);
23+
if (!includePath) {
24+
return;
25+
}
26+
27+
const { path, addition, isPrefix } = includePath;
2328

2429
for (const token of IncludesUtil._selectTokens(document, path)) {
2530
IncludesUtil._executeInternal(token, addition, (value, valueAddition) => {
@@ -32,19 +37,25 @@ export class IncludesUtil {
3237
}
3338
}
3439

35-
private static _getIncludePath(include: string): IncludePath {
40+
private static _getIncludePath(include: string, identityPartsSeparator: string): IncludePath | null {
3641
const prefixMatch = INCLUDE_PREFIX_REGEX.exec(include);
3742
const match = prefixMatch ?? INCLUDE_SUFFIX_REGEX.exec(include);
3843
if (!match) {
3944
return { path: include, addition: null, isPrefix: false };
4045
}
4146

42-
const addition = match[1];
43-
return {
44-
path: include.split(addition).join(""),
45-
addition: addition.substring(1, addition.length - 1),
46-
isPrefix: !!prefixMatch
47-
};
47+
const path = include.slice(0, match.index);
48+
const addition = match[1].slice(1, -1);
49+
50+
if (prefixMatch) {
51+
return { path, addition: addition.endsWith(identityPartsSeparator) ? addition : null, isPrefix: true };
52+
}
53+
54+
if (!addition.startsWith("{0}" + identityPartsSeparator)) {
55+
return null;
56+
}
57+
58+
return { path, addition, isPrefix: false };
4859
}
4960

5061
private static _selectTokens(document: object, path: string): unknown[] {
@@ -62,10 +73,11 @@ export class IncludesUtil {
6273
TypeUtil.isObject(item) ? IncludesUtil._selectTokens(item, nestedPath) : [item]);
6374
}
6475

65-
if (TypeUtil.isObject(result)) {
76+
if (TypeUtil.isObject(result) && nestedPaths.length === 1) {
6677
return Object.values(result)
6778
.filter(value => TypeUtil.isObject(value))
68-
.flatMap(value => IncludesUtil._selectTokens(value, nestedPath));
79+
.map(value => IncludesUtil._readPath(value, nestedPath))
80+
.filter(value => !TypeUtil.isArray(value));
6981
}
7082

7183
return [];
@@ -87,18 +99,16 @@ export class IncludesUtil {
8799

88100
private static _executeInternal(
89101
token: unknown, addition: string, loadId: (value: string, addition: string) => void): void {
90-
if (TypeUtil.isArray(token)) {
91-
for (const item of token) {
92-
IncludesUtil._executeInternal(item, addition, loadId);
93-
}
94-
} else if (TypeUtil.isString(token) && token) {
95-
loadId(token, addition);
102+
for (const value of TypeUtil.isArray(token) ? token : [token]) {
103+
if (TypeUtil.isString(value) && value) {
104+
loadId(value, addition);
96105

97-
if (addition) {
98-
loadId(token, null);
106+
if (addition) {
107+
loadId(value, null);
108+
}
109+
} else if (Number.isInteger(value)) {
110+
loadId(String(value), addition);
99111
}
100-
} else if (Number.isInteger(token)) {
101-
loadId(String(token), addition);
102112
}
103113
}
104114

‎test/Ported/Documents/LoadTest.ts‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ import assert from "node:assert"
22
import { testContext, disposeTestDocumentStore } from "../../Utils/TestUtil.js";
33

44
import {
5+
DocumentStore,
56
IDocumentStore,
7+
ObjectUtil,
68
} from "../../../src/index.js";
79
import { Order, OrderLine } from "../../Assets/Entities.js";
810

@@ -114,6 +116,29 @@ describe("Load test", function () {
114116
assert.strictEqual(foo?.name, "Beginning");
115117
});
116118

119+
it("does not treat a document as missing when field names are converted", async () => {
120+
const convertingStore = new DocumentStore(store.urls, store.database);
121+
convertingStore.conventions.serverToLocalFieldNameConverter = ObjectUtil.camel;
122+
convertingStore.conventions.localToServerFieldNameConverter = ObjectUtil.pascal;
123+
convertingStore.initialize();
124+
125+
try {
126+
const session = convertingStore.openSession();
127+
await session.store(Object.assign(new Foo(), { name: "Beginning" }), "foos/1");
128+
await session.store(Object.assign(new Bar(), { name: "End", fooId: "foos/1" }), "bars/1");
129+
await session.saveChanges();
130+
131+
const newSession = convertingStore.openSession();
132+
await newSession.include("fooId").load<Bar>("bars/1", Bar);
133+
134+
const foo = await newSession.load<Foo>("foos/1", Foo);
135+
136+
assert.strictEqual(foo?.name, "Beginning");
137+
} finally {
138+
convertingStore.dispose();
139+
}
140+
});
141+
117142
it("does not request includes again when the included id is empty", async () => {
118143
const session = store.openSession();
119144
await session.store(Object.assign(new Bar(), { name: "End", fooId: "" }), "bars/1");

‎test/Ported/IncludesUtilTest.ts‎

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ import { IncludesUtil } from "../../src/index.js";
33

44
describe("IncludesUtil", function () {
55

6-
function includedIds(document: object, include: string): string[] {
6+
function includedIds(document: object, include: string, identityPartsSeparator?: string): string[] {
77
const ids: string[] = [];
8-
IncludesUtil.include(document, include, id => ids.push(id));
8+
IncludesUtil.include(document, include, id => ids.push(id), identityPartsSeparator);
99
return ids;
1010
}
1111

@@ -21,6 +21,32 @@ describe("IncludesUtil", function () {
2121
assert.deepStrictEqual(ids, ["1/customer", "1"]);
2222
});
2323

24+
it("include with prefix not ending with the identity parts separator yields only the raw id", () => {
25+
const ids = includedIds({ customerId: "1" }, "customerId(customer-)");
26+
27+
assert.deepStrictEqual(ids, ["1"]);
28+
});
29+
30+
it("include with prefix ending with a custom identity parts separator", () => {
31+
const ids = includedIds({ customerId: "1" }, "customerId(customer-)", "-");
32+
33+
assert.deepStrictEqual(ids, ["customer-1", "1"]);
34+
});
35+
36+
it("include with suffix not starting with the value and the identity parts separator yields no ids", () => {
37+
const document = { customerId: "1" };
38+
39+
assert.deepStrictEqual(includedIds(document, "customerId[{0}-customer]"), []);
40+
assert.deepStrictEqual(includedIds(document, "customerId[{0}]"), []);
41+
assert.deepStrictEqual(includedIds(document, "customerId[/customer]"), []);
42+
});
43+
44+
it("include with suffix using a custom identity parts separator", () => {
45+
const ids = includedIds({ customerId: "1" }, "customerId[{0}-customer]", "-");
46+
47+
assert.deepStrictEqual(ids, ["1-customer", "1"]);
48+
});
49+
2450
it("include with prefix of a numeric id", () => {
2551
const ids = includedIds({ supplierId: 5 }, "supplierId(suppliers/)");
2652

@@ -48,6 +74,20 @@ describe("IncludesUtil", function () {
4874
assert.deepStrictEqual(ids, ["suppliers/1", "suppliers/2"]);
4975
});
5076

77+
it("include array of ids skips nested arrays", () => {
78+
const ids = includedIds({ supplierIds: [["suppliers/1"], "suppliers/2"] }, "supplierIds");
79+
80+
assert.deepStrictEqual(ids, ["suppliers/2"]);
81+
});
82+
83+
it("include property of array items flattens one level of ids", () => {
84+
const ids = includedIds(
85+
{ lines: [{ products: ["products/1", ["products/2"]] }] },
86+
"lines[].products");
87+
88+
assert.deepStrictEqual(ids, ["products/1"]);
89+
});
90+
5191
it("include property of array items", () => {
5292
const ids = includedIds(
5393
{ lines: [{ product: "products/1" }, { product: "products/2" }] },
@@ -85,6 +125,22 @@ describe("IncludesUtil", function () {
85125
assert.deepStrictEqual(ids, ["products/1"]);
86126
});
87127

128+
it("include property of dictionary values skips arrays", () => {
129+
const ids = includedIds(
130+
{ lines: { first: { product: ["products/1"] }, second: { product: "products/2" } } },
131+
"lines[].product");
132+
133+
assert.deepStrictEqual(ids, ["products/2"]);
134+
});
135+
136+
it("include collection path below dictionary values yields no ids", () => {
137+
const ids = includedIds(
138+
{ lines: { first: { parts: [{ product: "products/1" }] } } },
139+
"lines[].parts[].product");
140+
141+
assert.deepStrictEqual(ids, []);
142+
});
143+
88144
it("include path syntax the server does not resolve yields no ids", () => {
89145
const lines = [{ product: "products/1" }, { product: "products/2" }];
90146

0 commit comments

Comments
 (0)