Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/Count queries #329

Merged
merged 8 commits into from
Jul 22, 2021
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/antora/content-nav.adoc
Original file line number Diff line number Diff line change
@@ -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[]
3 changes: 3 additions & 0 deletions docs/asciidoc/ogm/api-reference.adoc
Original file line number Diff line number Diff line change
@@ -48,6 +48,9 @@ const model = ogm.model("name")
==== `find()`
Reference: <<ogm-methods-find>>

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

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

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
@@ -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>>

14 changes: 14 additions & 0 deletions docs/asciidoc/schema/queries.adoc
Original file line number Diff line number Diff line change
@@ -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!
}
----

@@ -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
@@ -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>
@@ -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
@@ -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";
@@ -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")) {
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
@@ -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
@@ -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