mirror: update EAV primitives (#1342)

Summary:
This commit modifies `_updateOwnData` to write to both the old
type-specific primitives tables as well as the new EAV table. This
establishes the invariant that a node with non-null `last_update` will
always have primitive data (if its object type has primitive fields).

Test Plan:
Existing tests expanded. Commenting out each of the `updateEavPrimitive`
calls (independently) causes a test to fail. Note that every test that
queries an internal `primitives_*` table to inspect the database state
has been expanded to make an equivalent query against the `primitives`
table as well.

wchargin-branch: mirror-eav-update
This commit is contained in:
William Chargin 2019-09-14 17:28:09 -07:00 committed by GitHub
parent 463f3a073a
commit 3cb22565e5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 120 additions and 13 deletions

View File

@ -196,7 +196,7 @@ export class Mirror {
// it requires bumping the version, bump it: requiring some extra
// one-time cache resets is okay; doing the wrong thing is not.
const blob = stringify({
version: "MIRROR_v5",
version: "MIRROR_v6",
schema: this._schema,
options: {
blacklistedIds: this._blacklistedIds,
@ -1356,7 +1356,7 @@ export class Mirror {
].join("_"): string): any);
},
};
const updatePrimitives: ({|
const updateTypeSpecificPrimitives: ({|
+id: Schema.ObjectId,
// These keys can be top-level primitive fields or the primitive
// children of a nested field. The values are the JSON encodings
@ -1387,6 +1387,19 @@ export class Mirror {
);
return _makeSingleUpdateFunction(stmt);
})();
const updateEavPrimitive: ({|
+id: Schema.ObjectId,
+fieldname: string,
+value: string | 0 | 1,
|}) => void = _makeSingleUpdateFunction(
db.prepare(
dedent`\
UPDATE primitives
SET value = :value
WHERE object_id = :id AND fieldname = :fieldname
`
)
);
for (const entry of queryResult) {
const primitives: {|
@ -1406,9 +1419,9 @@ export class Mirror {
`of type ${s(typename)} (got ${(primitive: empty)})`
);
}
primitives[
parameterNameFor.topLevelField(fieldname)
] = JSON.stringify(primitive);
const jsonValue = JSON.stringify(primitive);
primitives[parameterNameFor.topLevelField(fieldname)] = jsonValue;
updateEavPrimitive({id: entry.id, fieldname, value: jsonValue});
}
// Add nested primitives.
@ -1428,6 +1441,11 @@ export class Mirror {
}
primitives[parameterNameFor.topLevelField(nestFieldname)] =
topLevelNested == null ? 0 : 1;
updateEavPrimitive({
id: entry.id,
fieldname: nestFieldname,
value: topLevelNested == null ? 0 : 1,
});
const eggFields = objectType.nestedFields[nestFieldname].primitives;
for (const eggFieldname of Object.keys(eggFields)) {
const eggValue: PrimitiveResult | NodeFieldResult =
@ -1442,13 +1460,19 @@ export class Mirror {
`of type ${s(typename)} (got ${(primitive: empty)})`
);
}
const jsonValue = JSON.stringify(primitive);
primitives[
parameterNameFor.nestedField(nestFieldname, eggFieldname)
] = JSON.stringify(primitive);
] = jsonValue;
updateEavPrimitive({
id: entry.id,
fieldname: `${nestFieldname}.${eggFieldname}`,
value: jsonValue,
});
}
}
updatePrimitives(primitives);
updateTypeSpecificPrimitives(primitives);
}
}

View File

@ -932,6 +932,30 @@ describe("graphql/mirror", () => {
// Check that some objects have the right primitives.
// (These poke at the internals of the storage format a bit.)
// Check primitives (EAV format).
expect(
db
.prepare(
"SELECT object_id AS id, fieldname, value " +
"FROM objects JOIN primitives ON objects.id = object_id " +
"WHERE " +
"typename IN ('Repository', 'Issue', 'User', 'ClosedEvent') " +
"ORDER BY id, fieldname ASC"
)
.all()
).toEqual([
{id: "issue:#1", fieldname: "title", value: '"something wicked"'},
{id: "issue:#1", fieldname: "url", value: '"url://foo/bar/issue/1"'},
{id: "issue:#2", fieldname: "title", value: '"this way comes"'},
{id: "issue:#2", fieldname: "url", value: '"url://foo/bar/issue/2"'},
{id: "repo:foo/bar", fieldname: "url", value: '"url://foo/bar"'},
{id: "user:alice", fieldname: "login", value: null},
{id: "user:alice", fieldname: "url", value: null},
// nothing for `ClosedEvent` (it has no primitive fields)
]);
// Check primitives (legacy format).
expect(
db
.prepare("SELECT * FROM primitives_Repository ORDER BY id ASC")
@ -942,13 +966,13 @@ describe("graphql/mirror", () => {
).toEqual([
{
id: "issue:#1",
url: JSON.stringify("url://foo/bar/issue/1"),
title: JSON.stringify("something wicked"),
url: JSON.stringify("url://foo/bar/issue/1"),
},
{
id: "issue:#2",
url: JSON.stringify("url://foo/bar/issue/2"),
title: JSON.stringify("this way comes"),
url: JSON.stringify("url://foo/bar/issue/2"),
},
]);
expect(
@ -2142,9 +2166,26 @@ describe("graphql/mirror", () => {
expect(
db.prepare("SELECT * FROM primitives_Issue ORDER BY id ASC").all()
).toEqual([
{id: "issue:#1", url: '"url://issue/1"', title: "13.75"},
{id: "issue:#2", url: "null", title: "false"},
{id: "issue:#3", url: null, title: null},
{id: "issue:#1", title: "13.75", url: '"url://issue/1"'},
{id: "issue:#2", title: "false", url: "null"},
{id: "issue:#3", title: null, url: null},
]);
expect(
db
.prepare(
"SELECT object_id AS id, fieldname, value " +
"FROM objects JOIN primitives ON objects.id = object_id " +
"WHERE typename = 'Issue' " +
"ORDER BY id, fieldname ASC"
)
.all()
).toEqual([
{id: "issue:#1", fieldname: "title", value: "13.75"},
{id: "issue:#1", fieldname: "url", value: '"url://issue/1"'},
{id: "issue:#2", fieldname: "title", value: "false"},
{id: "issue:#2", fieldname: "url", value: "null"},
{id: "issue:#3", fieldname: "title", value: null},
{id: "issue:#3", fieldname: "url", value: null},
]);
});
it("stores data with non-`null` nested fields", () => {
@ -2178,9 +2219,9 @@ describe("graphql/mirror", () => {
).toEqual([
{
id: "commit:oid",
oid: '"yes"',
author: +true,
"author.date": '"today"',
oid: '"yes"',
},
{
id: "commit:zzz",
@ -2189,6 +2230,23 @@ describe("graphql/mirror", () => {
"author.date": "null",
},
]);
expect(
db
.prepare(
"SELECT object_id AS id, fieldname, value " +
"FROM objects JOIN primitives ON objects.id = object_id " +
"WHERE typename = 'Commit' " +
"ORDER BY id, fieldname ASC"
)
.all()
).toEqual([
{id: "commit:oid", fieldname: "author", value: +true},
{id: "commit:oid", fieldname: "author.date", value: '"today"'},
{id: "commit:oid", fieldname: "oid", value: '"yes"'},
{id: "commit:zzz", fieldname: "author", value: +true},
{id: "commit:zzz", fieldname: "author.date", value: "null"},
{id: "commit:zzz", fieldname: "oid", value: '"zzz"'},
]);
expect(
db.prepare("SELECT * FROM links ORDER BY parent_id ASC").all()
).toEqual([
@ -2247,6 +2305,20 @@ describe("graphql/mirror", () => {
"author.date": "null",
},
]);
expect(
db
.prepare(
"SELECT object_id AS id, fieldname, value " +
"FROM objects JOIN primitives ON objects.id = object_id " +
"WHERE typename = 'Commit' " +
"ORDER BY id, fieldname ASC"
)
.all()
).toEqual([
{id: "commit:oid", fieldname: "author", value: +false},
{id: "commit:oid", fieldname: "author.date", value: "null"},
{id: "commit:oid", fieldname: "oid", value: '"mmm"'},
]);
expect(
db.prepare("SELECT * FROM links ORDER BY parent_id ASC").all()
).toEqual([
@ -2289,6 +2361,17 @@ describe("graphql/mirror", () => {
.prepare("SELECT * FROM primitives_LockedEvent ORDER BY id ASC")
.all()
).toEqual([{id: "dos"}, {id: "uno"}]);
expect(
db
.prepare(
"SELECT objects.id AS o_id, primitives.rowid AS p_rowid " +
"FROM objects LEFT OUTER JOIN primitives " +
"ON objects.id = object_id " +
"WHERE typename = 'LockedEvent' " +
"ORDER BY o_id ASC"
)
.all()
).toEqual([{o_id: "dos", p_rowid: null}, {o_id: "uno", p_rowid: null}]);
expect(
db
.prepare("SELECT * FROM links ORDER BY parent_id ASC")