diff --git a/dbquery/pagination/README.md b/dbquery/pagination/README.md index 30a73ec..971d656 100644 --- a/dbquery/pagination/README.md +++ b/dbquery/pagination/README.md @@ -14,7 +14,6 @@ To run these examples, This example uses a demo database filled with mock data, you can inspect the `config.yaml` file to find the credentials for this database. - ## GraphQL pagination StepZen supports pagination through the standard [GraphQL Cursor Connection Specification](https://relay.dev/graphql/connections.htm). @@ -63,4 +62,51 @@ Fetch the next five, the value of `after` is taken from the `endCursor` field of stepzen request -f operations.graphql --operation-name=Customers --var first=5 --var after="eyJjIjoiTDpRdWVyeTpjdXN0b21lcnMiLCJvIjo0fQ==" ``` +## GraphQL filtering + +Filtering using a "MongoDB" inspired style for GraphQL is supported with `@dbquery` and is typically +used with pagination, though it can be used alone. [Reference](https://www.ibm.com/docs/en/api-connect-graphql/saas?topic=directives-directive-dbquery#filtering__title__1) + +The field `Query.lookup` in [`filter.graphql`](./filter.graphql) is similar to `Query.customers` but adds a `filter` argument. + +This filter value would return all names that start with "S": + +```json +{ "name": { "like": "S%" } } +``` + +This returns two customers using an OR clause: + +```json +{ + "or": { + "name": { "eq": "John Doe" }, + "email": { "eq": "jane.smith@example.com" } + } +} +``` + +### Examples + +With no filter all customers are returned (subject to pagination) + +``` +stepzen request -f operations.graphql --operation-name=Lookup +``` + +With a filter then only matching customers are returned. + +``` +stepzen request -f operations.graphql --operation-name=Lookup --var 'f={ "name": { "like": "S%" } }' +``` + +``` +stepzen request -f operations.graphql --operation-name=Lookup --var first=5 --var 'f={ + "or": { + "name": { "eq": "John Doe" }, + "email": { "eq": "jane.smith@example.com" } + } +}' +``` +Also note that `totalEdges` returns the number of customers (edges) in the connection subject to the filter. diff --git a/dbquery/pagination/filter.graphql b/dbquery/pagination/filter.graphql new file mode 100644 index 0000000..c9ce339 --- /dev/null +++ b/dbquery/pagination/filter.graphql @@ -0,0 +1,37 @@ +# This shows how filtering is supported with @dbquery + +input CustomerFilter { + id: ID + name: StringFilter + email: StringFilter + or: CustomerFilter +} + +input StringFilter { + eq: String + ne: String + like: String + ilike: String + gt: String + le: String +} + +extend type Query { + """ + `lookup` pages through mock `Customer` objects in a demo PostgreSQL database. + The data is exposed using standard GraphQL pagination using the connection model. + The filter argument provides a standard approach to filtering regardless of + the backend and in this case is dynamically translated to SQL predicates in the `WHERE` clause. + """ + lookup( + first: Int! = 10 + after: String! = "" + filter: CustomerFilter + ): CustomerConnection + @dbquery( + type: "postgresql" + table: "customer" + schema: "public" + configuration: "postgresql_config" + ) +} diff --git a/dbquery/pagination/index.graphql b/dbquery/pagination/index.graphql index 956ed98..4da5a12 100644 --- a/dbquery/pagination/index.graphql +++ b/dbquery/pagination/index.graphql @@ -1,3 +1,7 @@ -schema @sdl(files: ["api.graphql"]) { +schema @sdl(files: ["api.graphql", "filter.graphql"]) { query: Query } + +# this validates that the executable document is valid for the schema. +# If the schema and document become out of sync then deployment will fail. +extend schema @sdl(executables: { document: "operations.graphql" }) diff --git a/dbquery/pagination/operations.graphql b/dbquery/pagination/operations.graphql index 239c741..cf40259 100644 --- a/dbquery/pagination/operations.graphql +++ b/dbquery/pagination/operations.graphql @@ -14,3 +14,21 @@ query Customers($first: Int!, $after: String = "") { totalEdges } } + +# Lookup customers with filtering. +query Lookup($first: Int, $after: String = "", $f: CustomerFilter) { + lookup(first: $first, after: $after, filter: $f) { + edges { + node { + id + name + email + } + } + pageInfo { + endCursor + hasNextPage + } + totalEdges + } +} diff --git a/dbquery/pagination/tests/Test.js b/dbquery/pagination/tests/Test.js index 93002f3..3df9b29 100644 --- a/dbquery/pagination/tests/Test.js +++ b/dbquery/pagination/tests/Test.js @@ -12,38 +12,38 @@ const requestsFile = path.join(path.dirname(__dirname), "operations.graphql"); const requests = fs.readFileSync(requestsFile, "utf8").toString(); describe(testDescription, function () { - const CURSOR = "eyJjIjoiTDpRdWVyeTpjdXN0b21lcnMiLCJvIjoxfQ==" + const CURSOR = "eyJjIjoiTDpRdWVyeTpjdXN0b21lcnMiLCJvIjoxfQ=="; const tests = [ { label: "first set of results", query: requests, operationName: "Customers", - variables: { - first: 2 + variables: { + first: 2, }, expected: { customers: { edges: [ - { - node: { - id: "1", - name: "Lucas Bill " - } + { + node: { + id: "1", + name: "Lucas Bill ", + }, + }, + { + node: { + id: "2", + name: "Mandy Jones ", + }, }, - { - node: { - id: "2", - name: "Mandy Jones " - } - } ], pageInfo: { hasNextPage: true, - endCursor: CURSOR + endCursor: CURSOR, }, - totalEdges: 10 - } + totalEdges: 10, + }, }, }, { @@ -52,7 +52,7 @@ describe(testDescription, function () { operationName: "Customers", variables: { first: 2, - after: CURSOR + after: CURSOR, }, expected: { customers: { @@ -60,22 +60,61 @@ describe(testDescription, function () { { node: { id: "3", - name: "Salim Ali " - } + name: "Salim Ali ", + }, }, { node: { id: "4", - name: "Jane Xiu " - } - } + name: "Jane Xiu ", + }, + }, ], pageInfo: { endCursor: "eyJjIjoiTDpRdWVyeTpjdXN0b21lcnMiLCJvIjozfQ==", - hasNextPage: true + hasNextPage: true, + }, + totalEdges: 10, + }, + }, + }, + { + label: "lookup-filter", + query: requests, + operationName: "Lookup", + variables: { + first: 2, + f: { + or: { + name: { eq: "John Doe" }, + email: { eq: "jane.smith@example.com" }, + }, + }, + }, + expected: { + customers: { + edges: [ + { + node: { + id: "5", + name: "John Doe ", + email: "john.doe@example.com ", + }, + }, + { + node: { + id: "6", + name: "Jane Smith ", + email: "jane.smith@example.com ", + }, + }, + ], + pageInfo: { + hasNextPage: true, + endCursor: "eyJjIjoiTDpRdWVyeTpsb29rdXAiLCJvIjoxfQ==", }, - totalEdges: 10 - } + totalEdges: 2, + }, }, }, ];