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
|
* NOTE: A previous version of the schema used a separate
|
||||||
* primitives table for each GraphQL object type. These
|
* primitives table for each GraphQL object type. These
|
||||||
* `primitives_*` tables are still written, but are no longer read
|
* `primitives_*` tables are still written, but are no longer
|
||||||
* by default. They will be removed entirely in a future change.
|
* read. They will be removed entirely in a future change.
|
||||||
*
|
*
|
||||||
* We refer to node and primitive data together as "own data", because
|
* 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
|
* 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
|
* may be removed or changed at any time. For information about its
|
||||||
* semantics, read the current source code.
|
* semantics, read the current source code.
|
||||||
*/
|
*/
|
||||||
extract(
|
extract(rootId: Schema.ObjectId): mixed {
|
||||||
rootId: Schema.ObjectId,
|
|
||||||
options?: {|+useEavPrimitives?: boolean|}
|
|
||||||
): mixed {
|
|
||||||
const fullOptions = {
|
|
||||||
...{useEavPrimitives: true},
|
|
||||||
...(options || {}),
|
|
||||||
};
|
|
||||||
const db = this._db;
|
const db = this._db;
|
||||||
return _inTransaction(db, () => {
|
return _inTransaction(db, () => {
|
||||||
// We'll compute the transitive dependencies and store them into a
|
// We'll compute the transitive dependencies and store them into a
|
||||||
|
@ -1688,10 +1681,6 @@ export class Mirror {
|
||||||
ON objects.id = transitive_dependencies.id
|
ON objects.id = transitive_dependencies.id
|
||||||
`
|
`
|
||||||
).run({rootId});
|
).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
|
// Check to make sure all required objects and connections have
|
||||||
// been updated at least once.
|
// been updated at least once.
|
||||||
|
@ -1736,12 +1725,12 @@ export class Mirror {
|
||||||
// Constructing the result set inherently requires mutation,
|
// Constructing the result set inherently requires mutation,
|
||||||
// because the object graph can have cycles. We start by
|
// because the object graph can have cycles. We start by
|
||||||
// creating a record for each object, with just that object's
|
// creating a record for each object, with just that object's
|
||||||
// typename and ID (and, in legacy non-EAV mode, all primitive
|
// typename and ID. Then, we link in primitives, node
|
||||||
// data). Then, we link in primitives (except in legacy non-EAV
|
// references, and connection entries.
|
||||||
// mode), node references, and connection entries.
|
|
||||||
const allObjects: Map<Schema.ObjectId, Object> = new Map();
|
const allObjects: Map<Schema.ObjectId, Object> = new Map();
|
||||||
if (fullOptions.useEavPrimitives) {
|
|
||||||
// Initialize `allObjects`.
|
// Initialize `allObjects`.
|
||||||
|
{
|
||||||
const getObjects = db.prepare(
|
const getObjects = db.prepare(
|
||||||
`SELECT id AS id, typename AS typename FROM ${temporaryTableName}`
|
`SELECT id AS id, typename AS typename FROM ${temporaryTableName}`
|
||||||
);
|
);
|
||||||
|
@ -1751,8 +1740,10 @@ export class Mirror {
|
||||||
__typename: object.typename,
|
__typename: object.typename,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Fill in primitive data.
|
// Fill in primitive data.
|
||||||
|
{
|
||||||
const getPrimitives = db.prepare(
|
const getPrimitives = db.prepare(
|
||||||
dedent`\
|
dedent`\
|
||||||
SELECT
|
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.
|
// Add links.
|
||||||
|
|
|
@ -3416,10 +3416,10 @@ describe("graphql/mirror", () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
describe("extract", () => {
|
describe("extract", () => {
|
||||||
// We'll run under both legacy and EAV modes. In each case, hide
|
// Test in EAV mode, hiding the tables corresponding to the legacy
|
||||||
// the tables corresponding to the other mode to catch any
|
// mode to catch any accidental reads. (We hide and unhide rather
|
||||||
// accidental reads. (We hide and unhide rather than deleting
|
// than deleting because some test cases call `extract` multiple
|
||||||
// because some test cases call `extract` multiple times.)
|
// times.)
|
||||||
function hiddenName(name) {
|
function hiddenName(name) {
|
||||||
return `${name}_DO_NOT_READ`;
|
return `${name}_DO_NOT_READ`;
|
||||||
}
|
}
|
||||||
|
@ -3430,57 +3430,27 @@ describe("graphql/mirror", () => {
|
||||||
db.prepare(`ALTER TABLE ${hiddenName(name)} RENAME TO ${name}`).run();
|
db.prepare(`ALTER TABLE ${hiddenName(name)} RENAME TO ${name}`).run();
|
||||||
}
|
}
|
||||||
|
|
||||||
describe("with legacy type-specific primitives", () => {
|
testExtract((mirror, id) => {
|
||||||
testExtract((mirror, id) => {
|
const legacyTables = mirror._db
|
||||||
hideTable(mirror._db, "primitives");
|
.prepare(
|
||||||
try {
|
"SELECT name FROM sqlite_master " +
|
||||||
return mirror.extract(id, {useEavPrimitives: false});
|
"WHERE type = 'table' AND name LIKE 'primitives_%'"
|
||||||
} finally {
|
)
|
||||||
unhideTable(mirror._db, "primitives");
|
.pluck()
|
||||||
}
|
.all();
|
||||||
});
|
if (legacyTables.length === 0) {
|
||||||
});
|
throw new Error("Found no type-specific primitives tables?");
|
||||||
|
}
|
||||||
describe("with EAV primitives", () => {
|
for (const table of legacyTables) {
|
||||||
testExtract((mirror, id) => {
|
hideTable(mirror._db, table);
|
||||||
const legacyTables = mirror._db
|
}
|
||||||
.prepare(
|
try {
|
||||||
"SELECT name FROM sqlite_master " +
|
return mirror.extract(id);
|
||||||
"WHERE type = 'table' AND name LIKE 'primitives_%'"
|
} finally {
|
||||||
)
|
|
||||||
.pluck()
|
|
||||||
.all();
|
|
||||||
if (legacyTables.length === 0) {
|
|
||||||
throw new Error("Found no type-specific primitives tables?");
|
|
||||||
}
|
|
||||||
for (const table of legacyTables) {
|
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