mirror: precompute some useful schema info (#857)
Summary: This is mostly useful not for computational efficiency, but for ease of implementation: there end up being multiple places where we want to find (say) the primitive fields on an object, and having to go through the whole iterate-and-switch-and-push process repeatedly is annoying. Test Plan: Unit tests included, with full coverage; run `yarn unit`. wchargin-branch: mirror-schema-info
This commit is contained in:
parent
1b1a1e4d46
commit
e69ff57c58
|
@ -12,6 +12,7 @@ import * as Schema from "./schema";
|
|||
export class Mirror {
|
||||
+_db: Database;
|
||||
+_schema: Schema.Schema;
|
||||
+_schemaInfo: SchemaInfo;
|
||||
|
||||
/**
|
||||
* Create a GraphQL mirror using the given database connection and
|
||||
|
@ -33,6 +34,7 @@ export class Mirror {
|
|||
if (schema == null) throw new Error("schema: " + String(schema));
|
||||
this._db = db;
|
||||
this._schema = schema;
|
||||
this._schemaInfo = _buildSchemaInfo(this._schema);
|
||||
this._initialize();
|
||||
}
|
||||
|
||||
|
@ -276,6 +278,98 @@ export class Mirror {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decomposition of a schema, grouping types by their kind (object vs.
|
||||
* union) and object fields by their kind (primitive vs. link vs.
|
||||
* connection).
|
||||
*
|
||||
* All arrays contain elements in arbitrary order.
|
||||
*/
|
||||
type SchemaInfo = {|
|
||||
+objectTypes: {|
|
||||
+[Schema.Typename]: {|
|
||||
+fields: {|+[Schema.Fieldname]: Schema.FieldType|},
|
||||
+primitiveFieldNames: $ReadOnlyArray<Schema.Fieldname>,
|
||||
+linkFieldNames: $ReadOnlyArray<Schema.Fieldname>,
|
||||
+connectionFieldNames: $ReadOnlyArray<Schema.Fieldname>,
|
||||
// There is always exactly one ID field, so it needs no
|
||||
// special representation. (It's still included in the `fields`
|
||||
// dictionary, though.)
|
||||
|},
|
||||
|},
|
||||
+unionTypes: {|
|
||||
+[Schema.Fieldname]: {|
|
||||
+clauses: $ReadOnlyArray<Schema.Typename>,
|
||||
|},
|
||||
|},
|
||||
|};
|
||||
|
||||
export function _buildSchemaInfo(schema: Schema.Schema): SchemaInfo {
|
||||
const result = {
|
||||
objectTypes: (({}: any): {|
|
||||
[Schema.Typename]: {|
|
||||
+fields: {|+[Schema.Fieldname]: Schema.FieldType|},
|
||||
+primitiveFieldNames: Array<Schema.Fieldname>,
|
||||
+linkFieldNames: Array<Schema.Fieldname>,
|
||||
+connectionFieldNames: Array<Schema.Fieldname>,
|
||||
|},
|
||||
|}),
|
||||
unionTypes: (({}: any): {|
|
||||
[Schema.Fieldname]: {|
|
||||
+clauses: $ReadOnlyArray<Schema.Typename>,
|
||||
|},
|
||||
|}),
|
||||
};
|
||||
for (const typename of Object.keys(schema)) {
|
||||
const type = schema[typename];
|
||||
switch (type.type) {
|
||||
case "OBJECT": {
|
||||
const entry: {|
|
||||
+fields: {|+[Schema.Fieldname]: Schema.FieldType|},
|
||||
+primitiveFieldNames: Array<Schema.Fieldname>,
|
||||
+linkFieldNames: Array<Schema.Fieldname>,
|
||||
+connectionFieldNames: Array<Schema.Fieldname>,
|
||||
|} = {
|
||||
fields: type.fields,
|
||||
primitiveFieldNames: [],
|
||||
linkFieldNames: [],
|
||||
connectionFieldNames: [],
|
||||
};
|
||||
result.objectTypes[typename] = entry;
|
||||
for (const fieldname of Object.keys(type.fields)) {
|
||||
const field = type.fields[fieldname];
|
||||
switch (field.type) {
|
||||
case "ID":
|
||||
break;
|
||||
case "PRIMITIVE":
|
||||
entry.primitiveFieldNames.push(fieldname);
|
||||
break;
|
||||
case "NODE":
|
||||
entry.linkFieldNames.push(fieldname);
|
||||
break;
|
||||
case "CONNECTION":
|
||||
entry.connectionFieldNames.push(fieldname);
|
||||
break;
|
||||
// istanbul ignore next
|
||||
default:
|
||||
throw new Error((field.type: empty));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "UNION": {
|
||||
const entry = {clauses: Object.keys(type.clauses)};
|
||||
result.unionTypes[typename] = entry;
|
||||
break;
|
||||
}
|
||||
// istanbul ignore next
|
||||
default:
|
||||
throw new Error((type.type: empty));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a function inside a database transaction.
|
||||
*
|
||||
|
|
|
@ -5,7 +5,7 @@ import fs from "fs";
|
|||
import tmp from "tmp";
|
||||
|
||||
import * as Schema from "./schema";
|
||||
import {_inTransaction, Mirror} from "./mirror";
|
||||
import {_buildSchemaInfo, _inTransaction, Mirror} from "./mirror";
|
||||
|
||||
describe("graphql/mirror", () => {
|
||||
function buildGithubSchema(): Schema.Schema {
|
||||
|
@ -175,6 +175,41 @@ describe("graphql/mirror", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("_buildSchemaInfo", () => {
|
||||
it("processes object types properly", () => {
|
||||
const result = _buildSchemaInfo(buildGithubSchema());
|
||||
expect(Object.keys(result.objectTypes).sort()).toEqual(
|
||||
[
|
||||
"Repository",
|
||||
"Issue",
|
||||
"IssueComment",
|
||||
"User",
|
||||
"Bot",
|
||||
"Organization",
|
||||
].sort()
|
||||
);
|
||||
expect(result.objectTypes["Issue"].fields).toEqual(
|
||||
(buildGithubSchema().Issue: any).fields
|
||||
);
|
||||
expect(
|
||||
result.objectTypes["Issue"].primitiveFieldNames.slice().sort()
|
||||
).toEqual(["url", "title"].sort());
|
||||
expect(result.objectTypes["Issue"].linkFieldNames.slice().sort()).toEqual(
|
||||
["author", "parent"].sort()
|
||||
);
|
||||
expect(
|
||||
result.objectTypes["Issue"].connectionFieldNames.slice().sort()
|
||||
).toEqual(["comments"].sort());
|
||||
});
|
||||
it("processes union types correctly", () => {
|
||||
const result = _buildSchemaInfo(buildGithubSchema());
|
||||
expect(Object.keys(result.unionTypes).sort()).toEqual(["Actor"].sort());
|
||||
expect(result.unionTypes["Actor"].clauses.slice().sort()).toEqual(
|
||||
["User", "Bot", "Organization"].sort()
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("_inTransaction", () => {
|
||||
it("runs its callback inside a transaction", () => {
|
||||
// We use an on-disk database file here because we need to open
|
||||
|
|
Loading…
Reference in New Issue