Skip to content

Commit

Permalink
Feat/Count queries (#329)
Browse files Browse the repository at this point in the history
* test: add init coverage for count

* feat: add count queries

* test: changes to existing for count queries

* docs: add count queries

* docs: remove misleading comment on count

* refactor: change to xCount over countX

* feat: add count to ogm
  • Loading branch information
danstarns authored Jul 22, 2021
1 parent b7708ec commit 63a0e62
Show file tree
Hide file tree
Showing 48 changed files with 749 additions and 11 deletions.
1 change: 1 addition & 0 deletions docs/antora/content-nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
*** xref:ogm/methods/index.adoc[]
**** xref:ogm/methods/create/index.adoc[]
**** xref:ogm/methods/find/index.adoc[]
**** xref:ogm/methods/count/index.adoc[]
**** xref:ogm/methods/update/index.adoc[]
**** xref:ogm/methods/delete/index.adoc[]
*** xref:ogm/private/index.adoc[]
Expand Down
3 changes: 3 additions & 0 deletions docs/asciidoc/ogm/api-reference.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ const model = ogm.model("name")
==== `find()`
Reference: <<ogm-methods-find>>

==== `count()`
Reference: <<ogm-methods-count>>

==== `create()`
Reference: <<ogm-methods-create>>

Expand Down
20 changes: 20 additions & 0 deletions docs/asciidoc/ogm/methods/count.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[[ogm-methods-count]]
= Count

Use to count nodes.

== Usage

=== Basic

[source, javascript]
----
const User = ogm.model("User");
const usersCount = await User.count();
----

== Args

=== `where`
JavaScript object representation of the GraphQL `where` input type, used for <<schema-queries>>.
1 change: 1 addition & 0 deletions docs/asciidoc/ogm/methods/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ You can call the following on a model;

. <<ogm-methods-create>>
. <<ogm-methods-find>>
. <<ogm-methods-count>>
. <<ogm-methods-update>>
. <<ogm-methods-delete>>

Expand Down
14 changes: 14 additions & 0 deletions docs/asciidoc/schema/queries.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ The following Query fields will be automatically generated:
type Query {
posts(where: PostWhere, options: PostOptions): [Post!]!
users(where: UserWhere, options: UserOptions): [User!]!
countPosts(where: PostWhere): Int!
usersCount(where: UserWhere): Int!
}
----

Expand Down Expand Up @@ -64,6 +66,18 @@ query {
}
----


=== Counting all Users

The following Query will count all users and return a number.

[source, graphql]
----
query {
usersCount
}
----

=== Filtering

See <<schema-filtering>> for details on how data can be filtered whilst querying.
5 changes: 4 additions & 1 deletion docs/docbook/content-map.xml
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,9 @@
<d:tocentry linkend="ogm-methods-find">
<?dbhtml filename="ogm/methods/find/index.html"?>
</d:tocentry>
<d:tocentry linkend="ogm-methods-count">
<?dbhtml filename="ogm/methods/count/index.html"?>
</d:tocentry>
<d:tocentry linkend="ogm-methods-update">
<?dbhtml filename="ogm/methods/update/index.html"?>
</d:tocentry>
Expand Down Expand Up @@ -163,4 +166,4 @@
<?dbhtml filename="troubleshooting/index.html"?>
</d:tocentry>
</d:tocentry>
</d:toc>
</d:toc>
13 changes: 12 additions & 1 deletion packages/graphql/src/schema/make-augmented-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,14 @@ import { Integer, isInt } from "neo4j-driver";
import { Node, Exclude } from "../classes";
import getAuth from "./get-auth";
import { PrimitiveField, Auth, CustomEnumField, ConnectionQueryArgs } from "../types";
import { findResolver, createResolver, deleteResolver, cypherResolver, updateResolver } from "./resolvers";
import {
findResolver,
createResolver,
deleteResolver,
cypherResolver,
updateResolver,
countResolver,
} from "./resolvers";
import checkNodeImplementsInterfaces from "./check-node-implements-interfaces";
import * as Scalars from "./scalars";
import parseExcludeDirective from "./parse-exclude-directive";
Expand Down Expand Up @@ -1153,6 +1160,10 @@ function makeAugmentedSchema(
composer.Query.addFields({
[pluralize(camelCase(node.name))]: findResolver({ node }),
});

composer.Query.addFields({
[`${pluralize(camelCase(node.name))}Count`]: countResolver({ node }),
});
}

if (!node.exclude?.operations.includes("create")) {
Expand Down
37 changes: 37 additions & 0 deletions packages/graphql/src/schema/resolvers/count.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright (c) "Neo4j"
* Neo4j Sweden AB [http://neo4j.com]
*
* This file is part of Neo4j.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { Node } from "../../classes";
import countResolver from "./count";

describe("Count resolver", () => {
test("should return the correct; type, args and resolve", () => {
// @ts-ignore
const node: Node = {
name: "Movie",
};

const result = countResolver({ node });
expect(result.type).toEqual("Int!");
expect(result.resolve).toBeInstanceOf(Function);
expect(result.args).toMatchObject({
where: "MovieWhere",
});
});
});
45 changes: 45 additions & 0 deletions packages/graphql/src/schema/resolvers/count.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright (c) "Neo4j"
* Neo4j Sweden AB [http://neo4j.com]
*
* This file is part of Neo4j.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { execute } from "../../utils";
import { translateCount } from "../../translate";
import { Node } from "../../classes";
import { Context } from "../../types";

export default function countResolver({ node }: { node: Node }) {
async function resolve(_root: any, _args: any, _context: unknown) {
const context = _context as Context;
const [cypher, params] = translateCount({ context, node });

const result = await execute({
cypher,
params,
defaultAccessMode: "READ",
context,
raw: true,
});

return result.records[0]._fields[0].toNumber();
}

return {
type: `Int!`,
resolve,
args: { where: `${node.name}Where` },
};
}
1 change: 1 addition & 0 deletions packages/graphql/src/schema/resolvers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ export { default as findResolver } from "./read";
export { default as updateResolver } from "./update";
export { default as deleteResolver } from "./delete";
export { default as cypherResolver } from "./cypher";
export { default as countResolver } from "./count";
1 change: 1 addition & 0 deletions packages/graphql/src/translate/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ export { default as translateCreate } from "./translate-create";
export { default as translateRead } from "./translate-read";
export { default as translateUpdate } from "./translate-update";
export { default as translateDelete } from "./translate-delete";
export { default as translateCount } from "./translate-count";
83 changes: 83 additions & 0 deletions packages/graphql/src/translate/translate-count.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
* Copyright (c) "Neo4j"
* Neo4j Sweden AB [http://neo4j.com]
*
* This file is part of Neo4j.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { Node } from "../classes";
import { AUTH_FORBIDDEN_ERROR } from "../constants";
import { Context, GraphQLWhereArg } from "../types";
import createAuthAndParams from "./create-auth-and-params";
import createWhereAndParams from "./create-where-and-params";

function translateCount({ node, context }: { node: Node; context: Context }): [string, any] {
const whereInput = context.resolveTree.args.where as GraphQLWhereArg;
const varName = "this";
let cypherParams: { [k: string]: any } = {};
const whereStrs: string[] = [];
const cypherStrs: string[] = [];

cypherStrs.push(`MATCH (${varName}:${node.name})`);

if (whereInput) {
const where = createWhereAndParams({
whereInput,
varName,
node,
context,
recursing: true,
});
if (where[0]) {
whereStrs.push(where[0]);
cypherParams = { ...cypherParams, ...where[1] };
}
}

const whereAuth = createAuthAndParams({
operation: "READ",
entity: node,
context,
where: { varName, node },
});
if (whereAuth[0]) {
whereStrs.push(whereAuth[0]);
cypherParams = { ...cypherParams, ...whereAuth[1] };
}

const allowAuth = createAuthAndParams({
operation: "READ",
entity: node,
context,
allow: {
parentNode: node,
varName,
},
});
if (allowAuth[0]) {
cypherParams = { ...cypherParams, ...allowAuth[1] };
cypherStrs.push(`CALL apoc.util.validate(NOT(${allowAuth[0]}), "${AUTH_FORBIDDEN_ERROR}", [0])`);
}

if (whereStrs.length) {
cypherStrs.push(`WHERE ${whereStrs.join(" AND ")}`);
}

cypherStrs.push(`RETURN count(${varName})`);

return [cypherStrs.filter(Boolean).join("\n"), cypherParams];
}

export default translateCount;
Loading

0 comments on commit 63a0e62

Please sign in to comment.