mirror: remove legacy non-EAV `extract` (#1346)
Test Plan: Existing tests suffice, retaining full coverage. wchargin-branch: mirror-eav-prune-extract
This commit is contained in:
parent
f577ae7c1e
commit
003efdffa7
|
@ -145,8 +145,8 @@ export class Mirror {
|
|||
*
|
||||
* NOTE: A previous version of the schema used a separate
|
||||
* primitives table for each GraphQL object type. These
|
||||
* `primitives_*` tables are still written, but are no longer read
|
||||
* by default. They will be removed entirely in a future change.
|
||||
* `primitives_*` tables are still written, but are no longer
|
||||
* read. They will be removed entirely in a future change.
|
||||
*
|
||||
* We refer to node and primitive data together as "own data", because
|
||||
* this is the data that can be queried uniformly for all elements of
|
||||
|
@ -1640,14 +1640,7 @@ export class Mirror {
|
|||
* may be removed or changed at any time. For information about its
|
||||
* semantics, read the current source code.
|
||||
*/
|
||||
extract(
|
||||
rootId: Schema.ObjectId,
|
||||
options?: {|+useEavPrimitives?: boolean|}
|
||||
): mixed {
|
||||
const fullOptions = {
|
||||
...{useEavPrimitives: true},
|
||||
...(options || {}),
|
||||
};
|
||||
extract(rootId: Schema.ObjectId): mixed {
|
||||
const db = this._db;
|
||||
return _inTransaction(db, () => {
|
||||
// We'll compute the transitive dependencies and store them into a
|
||||
|
@ -1688,10 +1681,6 @@ export class Mirror {
|
|||
ON objects.id = transitive_dependencies.id
|
||||
`
|
||||
).run({rootId});
|
||||
const typenames: $ReadOnlyArray<Schema.Typename> = db
|
||||
.prepare(`SELECT DISTINCT typename FROM ${temporaryTableName}`)
|
||||
.pluck()
|
||||
.all();
|
||||
|
||||
// Check to make sure all required objects and connections have
|
||||
// been updated at least once.
|
||||
|
@ -1736,12 +1725,12 @@ export class Mirror {
|
|||
// Constructing the result set inherently requires mutation,
|
||||
// because the object graph can have cycles. We start by
|
||||
// creating a record for each object, with just that object's
|
||||
// typename and ID (and, in legacy non-EAV mode, all primitive
|
||||
// data). Then, we link in primitives (except in legacy non-EAV
|
||||
// mode), node references, and connection entries.
|
||||
// typename and ID. Then, we link in primitives, node
|
||||
// references, and connection entries.
|
||||
const allObjects: Map<Schema.ObjectId, Object> = new Map();
|
||||
if (fullOptions.useEavPrimitives) {
|
||||
// Initialize `allObjects`.
|
||||
|
||||
// Initialize `allObjects`.
|
||||
{
|
||||
const getObjects = db.prepare(
|
||||
`SELECT id AS id, typename AS typename FROM ${temporaryTableName}`
|
||||
);
|
||||
|
@ -1751,8 +1740,10 @@ export class Mirror {
|
|||
__typename: object.typename,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Fill in primitive data.
|
||||
// Fill in primitive data.
|
||||
{
|
||||
const getPrimitives = db.prepare(
|
||||
dedent`\
|
||||
SELECT
|
||||
|
@ -1801,99 +1792,6 @@ export class Mirror {
|
|||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Legacy non-EAV mode.
|
||||
for (const typename of typenames) {
|
||||
const objectType = this._schemaInfo.objectTypes[typename];
|
||||
// istanbul ignore if: should not be possible using the
|
||||
// publicly accessible APIs
|
||||
if (objectType == null) {
|
||||
throw new Error(
|
||||
`Corruption: unknown object type ${JSON.stringify(typename)}`
|
||||
);
|
||||
}
|
||||
const primitivesTableName = _primitivesTableName(typename);
|
||||
const selections: $ReadOnlyArray<string> = [].concat(
|
||||
[`${primitivesTableName}.id AS id`],
|
||||
objectType.primitiveFieldNames.map(
|
||||
(fieldname) =>
|
||||
`${primitivesTableName}."${fieldname}" AS "${fieldname}"`
|
||||
),
|
||||
objectType.nestedFieldNames.map(
|
||||
(fieldname) =>
|
||||
`${primitivesTableName}."${fieldname}" AS "${fieldname}"`
|
||||
),
|
||||
...objectType.nestedFieldNames.map((f1) =>
|
||||
Object.keys(objectType.nestedFields[f1].primitives).map(
|
||||
(f2) =>
|
||||
`${primitivesTableName}."${f1}.${f2}" AS "${f1}.${f2}"`
|
||||
)
|
||||
)
|
||||
);
|
||||
const rows: $ReadOnlyArray<{|
|
||||
+id: Schema.ObjectId,
|
||||
+[Schema.Fieldname]: string | 0 | 1,
|
||||
|}> = db
|
||||
.prepare(
|
||||
dedent`\
|
||||
SELECT ${selections.join(", ")}
|
||||
FROM ${temporaryTableName} JOIN ${primitivesTableName}
|
||||
USING (id)
|
||||
`
|
||||
)
|
||||
.all();
|
||||
for (const row of rows) {
|
||||
const object = {};
|
||||
object.id = row.id;
|
||||
object.__typename = typename;
|
||||
for (const fieldname of objectType.nestedFieldNames) {
|
||||
const isPresent: string | 0 | 1 = row[fieldname];
|
||||
// istanbul ignore if: should not be reachable
|
||||
if (isPresent !== 0 && isPresent !== 1) {
|
||||
const s = JSON.stringify;
|
||||
const id = object.id;
|
||||
throw new Error(
|
||||
`Corruption: nested field ${s(fieldname)} on ${s(id)} ` +
|
||||
`set to ${String(isPresent)}`
|
||||
);
|
||||
}
|
||||
if (isPresent) {
|
||||
// We'll add primitives and links onto this object.
|
||||
object[fieldname] = {};
|
||||
} else {
|
||||
object[fieldname] = null;
|
||||
}
|
||||
}
|
||||
for (const key of Object.keys(row)) {
|
||||
if (key === "id") continue;
|
||||
const rawValue = row[key];
|
||||
if (rawValue === 0 || rawValue === 1) {
|
||||
// Name of a nested field; already processed.
|
||||
continue;
|
||||
}
|
||||
const value = JSON.parse(rawValue);
|
||||
const parts = key.split(".");
|
||||
switch (parts.length) {
|
||||
case 1:
|
||||
object[key] = value;
|
||||
break;
|
||||
case 2: {
|
||||
const [nestName, eggName] = parts;
|
||||
if (object[nestName] !== null) {
|
||||
object[nestName][eggName] = value;
|
||||
}
|
||||
break;
|
||||
}
|
||||
// istanbul ignore next: should not be possible
|
||||
default:
|
||||
throw new Error(
|
||||
`Corruption: bad field name: ${JSON.stringify(key)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
allObjects.set(object.id, object);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add links.
|
||||
|
|
|
@ -3416,10 +3416,10 @@ describe("graphql/mirror", () => {
|
|||
}
|
||||
|
||||
describe("extract", () => {
|
||||
// We'll run under both legacy and EAV modes. In each case, hide
|
||||
// the tables corresponding to the other mode to catch any
|
||||
// accidental reads. (We hide and unhide rather than deleting
|
||||
// because some test cases call `extract` multiple times.)
|
||||
// Test in EAV mode, hiding the tables corresponding to the legacy
|
||||
// mode to catch any accidental reads. (We hide and unhide rather
|
||||
// than deleting because some test cases call `extract` multiple
|
||||
// times.)
|
||||
function hiddenName(name) {
|
||||
return `${name}_DO_NOT_READ`;
|
||||
}
|
||||
|
@ -3430,57 +3430,27 @@ describe("graphql/mirror", () => {
|
|||
db.prepare(`ALTER TABLE ${hiddenName(name)} RENAME TO ${name}`).run();
|
||||
}
|
||||
|
||||
describe("with legacy type-specific primitives", () => {
|
||||
testExtract((mirror, id) => {
|
||||
hideTable(mirror._db, "primitives");
|
||||
try {
|
||||
return mirror.extract(id, {useEavPrimitives: false});
|
||||
} finally {
|
||||
unhideTable(mirror._db, "primitives");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("with EAV primitives", () => {
|
||||
testExtract((mirror, id) => {
|
||||
const legacyTables = mirror._db
|
||||
.prepare(
|
||||
"SELECT name FROM sqlite_master " +
|
||||
"WHERE type = 'table' AND name LIKE 'primitives_%'"
|
||||
)
|
||||
.pluck()
|
||||
.all();
|
||||
if (legacyTables.length === 0) {
|
||||
throw new Error("Found no type-specific primitives tables?");
|
||||
}
|
||||
testExtract((mirror, id) => {
|
||||
const legacyTables = mirror._db
|
||||
.prepare(
|
||||
"SELECT name FROM sqlite_master " +
|
||||
"WHERE type = 'table' AND name LIKE 'primitives_%'"
|
||||
)
|
||||
.pluck()
|
||||
.all();
|
||||
if (legacyTables.length === 0) {
|
||||
throw new Error("Found no type-specific primitives tables?");
|
||||
}
|
||||
for (const table of legacyTables) {
|
||||
hideTable(mirror._db, table);
|
||||
}
|
||||
try {
|
||||
return mirror.extract(id);
|
||||
} finally {
|
||||
for (const table of legacyTables) {
|
||||
hideTable(mirror._db, table);
|
||||
unhideTable(mirror._db, table);
|
||||
}
|
||||
try {
|
||||
return mirror.extract(id, {useEavPrimitives: true});
|
||||
} finally {
|
||||
for (const table of legacyTables) {
|
||||
unhideTable(mirror._db, table);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("works with default options", () => {
|
||||
// Simple sanity check.
|
||||
const db = new Database(":memory:");
|
||||
const schema = buildGithubSchema();
|
||||
const mirror = new Mirror(db, schema);
|
||||
mirror.registerObject({typename: "SubscribedEvent", id: "sub"});
|
||||
const update = mirror._createUpdate(new Date(123));
|
||||
mirror._updateOwnData(update, [
|
||||
{__typename: "SubscribedEvent", id: "sub", actor: null},
|
||||
]);
|
||||
expect(mirror.extract("sub")).toEqual({
|
||||
__typename: "SubscribedEvent",
|
||||
id: "sub",
|
||||
actor: null,
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in New Issue