diff --git a/lerna.json b/lerna.json index cec4ff97..22025c81 100644 --- a/lerna.json +++ b/lerna.json @@ -2,7 +2,7 @@ "packages": [ "packages/*" ], - "version": "0.2.71", + "version": "0.2.72", "npmClient": "yarn", "useWorkspaces": true, "command": { diff --git a/packages/cache/package.json b/packages/cache/package.json index ca82b145..e32dcfb2 100644 --- a/packages/cache/package.json +++ b/packages/cache/package.json @@ -1,6 +1,6 @@ { "name": "@cerc-io/cache", - "version": "0.2.71", + "version": "0.2.72", "description": "Generic object cache", "main": "dist/index.js", "scripts": { diff --git a/packages/cli/package.json b/packages/cli/package.json index 6060573d..d4a9b927 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@cerc-io/cli", - "version": "0.2.71", + "version": "0.2.72", "main": "dist/index.js", "license": "AGPL-3.0", "scripts": { @@ -12,13 +12,13 @@ }, "dependencies": { "@apollo/client": "^3.7.1", - "@cerc-io/cache": "^0.2.71", - "@cerc-io/ipld-eth-client": "^0.2.71", + "@cerc-io/cache": "^0.2.72", + "@cerc-io/ipld-eth-client": "^0.2.72", "@cerc-io/libp2p": "^0.42.2-laconic-0.1.4", "@cerc-io/nitro-node": "^0.1.15", - "@cerc-io/peer": "^0.2.71", - "@cerc-io/rpc-eth-client": "^0.2.71", - "@cerc-io/util": "^0.2.71", + "@cerc-io/peer": "^0.2.72", + "@cerc-io/rpc-eth-client": "^0.2.72", + "@cerc-io/util": "^0.2.72", "@ethersproject/providers": "^5.4.4", "@graphql-tools/utils": "^9.1.1", "@ipld/dag-cbor": "^8.0.0", diff --git a/packages/codegen/package.json b/packages/codegen/package.json index 5ed3116b..3bfc1f94 100644 --- a/packages/codegen/package.json +++ b/packages/codegen/package.json @@ -1,6 +1,6 @@ { "name": "@cerc-io/codegen", - "version": "0.2.71", + "version": "0.2.72", "description": "Code generator", "private": true, "main": "index.js", @@ -20,7 +20,7 @@ }, "homepage": "https://github.com/cerc-io/watcher-ts#readme", "dependencies": { - "@cerc-io/util": "^0.2.71", + "@cerc-io/util": "^0.2.72", "@graphql-tools/load-files": "^6.5.2", "@poanet/solidity-flattener": "https://github.com/vulcanize/solidity-flattener.git", "@solidity-parser/parser": "^0.13.2", diff --git a/packages/codegen/src/schema.ts b/packages/codegen/src/schema.ts index f5ca00ee..8d25fa6a 100644 --- a/packages/codegen/src/schema.ts +++ b/packages/codegen/src/schema.ts @@ -4,7 +4,7 @@ import assert from 'assert'; import { GraphQLSchema, parse, printSchema, print, GraphQLDirective, GraphQLInt, GraphQLBoolean, GraphQLEnumType, DefinitionNode, GraphQLString, GraphQLNonNull } from 'graphql'; -import { ObjectTypeComposer, NonNullComposer, ObjectTypeComposerDefinition, ObjectTypeComposerFieldConfigMapDefinition, SchemaComposer } from 'graphql-compose'; +import { ObjectTypeComposer, NonNullComposer, ObjectTypeComposerDefinition, ObjectTypeComposerFieldConfigMapDefinition, SchemaComposer, ListComposer, ComposeOutputType } from 'graphql-compose'; import { Writable } from 'stream'; import { utils } from 'ethers'; import { VariableDeclaration } from '@solidity-parser/parser/dist/src/ast-types'; @@ -164,6 +164,15 @@ export class Schema { }); this._composer.addSchemaMustHaveType(inputTypeComposer); + // Add the BlockChangedFilter input needed in subgraph queries. + inputTypeComposer = this._composer.createInputTC({ + name: BLOCK_CHANGED_FILTER, + fields: { + number_gte: 'Int!' + } + }); + this._composer.addSchemaMustHaveType(inputTypeComposer); + // Add the OrderDirection enum needed in subgraph plural queries. const orderDirectionEnum = new GraphQLEnumType({ name: ORDER_DIRECTION, @@ -175,15 +184,6 @@ export class Schema { const enumTypeComposer = this._composer.createEnumTC(orderDirectionEnum); this._composer.addSchemaMustHaveType(enumTypeComposer); - // Add the BlockChangedFilter input needed in subgraph queries. - inputTypeComposer = this._composer.createInputTC({ - name: BLOCK_CHANGED_FILTER, - fields: { - number_gte: 'Int!' - } - }); - this._composer.addSchemaMustHaveType(inputTypeComposer); - // Add subgraph-schema entity queries to the schema composer. this._addSubgraphSchemaQueries(subgraphTypeDefs); } @@ -212,11 +212,23 @@ export class Schema { // Add plural query + const subgraphTypeComposer = this._composer.getOTC(subgraphType); + const subgraphTypeFields = subgraphTypeComposer.getFields(); + // Create the subgraphType_orderBy enum type const subgraphTypeOrderByEnum = new GraphQLEnumType({ name: `${subgraphType}_orderBy`, values: (subgraphTypeDef.fields || []).reduce((acc: any, field) => { acc[field.name.value] = {}; + + const subgraphTypeField = subgraphTypeComposer.getField(field.name.value); + const { isArray, isRelation, entityType } = this._getDetailsForSubgraphField(subgraphTypeField.type); + + if (!isArray && isRelation) { + assert(entityType); + this._fillOrderByWithNestedFields(acc, entityType, field.name.value); + } + return acc; }, {}) }); @@ -225,10 +237,65 @@ export class Schema { // Create the subgraphType_filter input type const subgraphTypeFilterComposer = this._composer.createInputTC({ name: `${subgraphType}_filter`, - // TODO: Add fields to filter input based on entity properties - fields: {} + // Add fields to filter input based on entity properties + fields: Object.entries(subgraphTypeFields).reduce((acc: {[key: string]: string}, [fieldName, field]) => { + const { type: fieldType, isArray, isRelation, entityType } = this._getDetailsForSubgraphField(field.type); + acc[fieldName] = fieldType; + acc[`${fieldName}_not`] = acc[fieldName]; + + if (!isArray) { + acc[`${fieldName}_gt`] = acc[fieldName]; + acc[`${fieldName}_lt`] = acc[fieldName]; + acc[`${fieldName}_gte`] = acc[fieldName]; + acc[`${fieldName}_lte`] = acc[fieldName]; + acc[`${fieldName}_in`] = `[${acc[fieldName]}!]`; + acc[`${fieldName}_not_in`] = `[${acc[fieldName]}!]`; + + if (acc[fieldName] === 'String') { + acc[`${fieldName}_starts_with`] = acc[fieldName]; + acc[`${fieldName}_starts_with_nocase`] = acc[fieldName]; + acc[`${fieldName}_not_starts_with`] = acc[fieldName]; + acc[`${fieldName}_not_starts_with_nocase`] = acc[fieldName]; + acc[`${fieldName}_ends_with`] = acc[fieldName]; + acc[`${fieldName}_ends_with_nocase`] = acc[fieldName]; + acc[`${fieldName}_not_ends_with`] = acc[fieldName]; + acc[`${fieldName}_not_ends_with_nocase`] = acc[fieldName]; + } + } + + if (isArray || acc[fieldName].includes('String') || acc[fieldName].includes('Bytes')) { + acc[`${fieldName}_contains`] = acc[fieldName]; + acc[`${fieldName}_not_contains`] = acc[fieldName]; + } + + if (isArray || acc[fieldName].includes('String')) { + acc[`${fieldName}_contains_nocase`] = acc[fieldName]; + acc[`${fieldName}_not_contains_nocase`] = acc[fieldName]; + } + + // Check if field is a relation type + if (isRelation) { + // Nested filter for relation field + acc[`${fieldName}_`] = `${entityType}_filter`; + + // Remove filters if it is a derived field + if (field.directives && field.directives.some(directive => directive.name === 'derivedFrom')) { + delete acc[`${fieldName}`]; + delete acc[`${fieldName}_not`]; + delete acc[`${fieldName}_contains`]; + delete acc[`${fieldName}_contains_nocase`]; + delete acc[`${fieldName}_not_contains`]; + delete acc[`${fieldName}_not_contains_nocase`]; + } + } + + return acc; + }, {}) }); subgraphTypeFilterComposer.setField('_change_block', BLOCK_CHANGED_FILTER); + subgraphTypeFilterComposer.setField('and', `[${subgraphType}_filter]`); + subgraphTypeFilterComposer.setField('or', `[${subgraphType}_filter]`); + this._composer.addSchemaMustHaveType(subgraphTypeFilterComposer); // Create plural query name @@ -253,6 +320,62 @@ export class Schema { } } + _getDetailsForSubgraphField (fieldType: ComposeOutputType): { + type: string; + isArray: boolean; + isRelation: boolean; + entityType?: string; + } { + let type = fieldType.getTypeName(); + let isArray = false; + let isRelation = false; + let entityType: string | undefined; + + if (fieldType instanceof NonNullComposer) { + const unwrappedFieldType = fieldType.getUnwrappedTC() as ObjectTypeComposer; + + if (fieldType.ofType instanceof ListComposer) { + isArray = true; + } + + ({ type, isRelation, entityType } = this._getDetailsForSubgraphField(unwrappedFieldType)); + } + + if (fieldType instanceof ListComposer) { + const childFieldType = fieldType.getUnwrappedTC() as ObjectTypeComposer; + ({ type, isRelation, entityType } = this._getDetailsForSubgraphField(childFieldType)); + + isArray = true; + } + + if (fieldType instanceof ObjectTypeComposer) { + type = 'String'; + isRelation = true; + entityType = fieldType.getTypeName(); + } + + if (isArray) { + type = `[${type}!]`; + } + + return { type, isArray, isRelation, entityType }; + } + + _fillOrderByWithNestedFields (orderByFields: {[key: string]: any}, entityName: string, fieldName: string): void { + const subgraphTypeComposer = this._composer.getOTC(entityName); + const subgraphTypeFields = subgraphTypeComposer.getFields(); + + Object.entries(subgraphTypeFields) + .filter(([, field]) => { + // Avoid nested ordering on array / relational / derived type fields + const { isRelation, isArray } = this._getDetailsForSubgraphField(field.type); + return !isRelation && !isArray; + }) + .forEach(([name]) => { + orderByFields[`${fieldName}__${name}`] = {}; + }); + } + /** * Adds basic types to the schema and typemapping. */ diff --git a/packages/codegen/src/templates/package-template.handlebars b/packages/codegen/src/templates/package-template.handlebars index d79a01aa..f29018ce 100644 --- a/packages/codegen/src/templates/package-template.handlebars +++ b/packages/codegen/src/templates/package-template.handlebars @@ -41,12 +41,12 @@ "homepage": "https://github.com/cerc-io/watcher-ts#readme", "dependencies": { "@apollo/client": "^3.3.19", - "@cerc-io/cli": "^0.2.71", - "@cerc-io/ipld-eth-client": "^0.2.71", - "@cerc-io/solidity-mapper": "^0.2.71", - "@cerc-io/util": "^0.2.71", + "@cerc-io/cli": "^0.2.72", + "@cerc-io/ipld-eth-client": "^0.2.72", + "@cerc-io/solidity-mapper": "^0.2.72", + "@cerc-io/util": "^0.2.72", {{#if (subgraphPath)}} - "@cerc-io/graph-node": "^0.2.71", + "@cerc-io/graph-node": "^0.2.72", {{/if}} "@ethersproject/providers": "^5.4.4", "debug": "^4.3.1", diff --git a/packages/graph-node/package.json b/packages/graph-node/package.json index 76687a20..40e2f332 100644 --- a/packages/graph-node/package.json +++ b/packages/graph-node/package.json @@ -1,10 +1,10 @@ { "name": "@cerc-io/graph-node", - "version": "0.2.71", + "version": "0.2.72", "main": "dist/index.js", "license": "AGPL-3.0", "devDependencies": { - "@cerc-io/solidity-mapper": "^0.2.71", + "@cerc-io/solidity-mapper": "^0.2.72", "@ethersproject/providers": "^5.4.4", "@graphprotocol/graph-ts": "^0.22.0", "@nomiclabs/hardhat-ethers": "^2.0.2", @@ -51,9 +51,9 @@ "dependencies": { "@apollo/client": "^3.3.19", "@cerc-io/assemblyscript": "0.19.10-watcher-ts-0.1.2", - "@cerc-io/cache": "^0.2.71", - "@cerc-io/ipld-eth-client": "^0.2.71", - "@cerc-io/util": "^0.2.71", + "@cerc-io/cache": "^0.2.72", + "@cerc-io/ipld-eth-client": "^0.2.72", + "@cerc-io/util": "^0.2.72", "@types/json-diff": "^0.5.2", "@types/yargs": "^17.0.0", "bn.js": "^4.11.9", diff --git a/packages/ipld-eth-client/package.json b/packages/ipld-eth-client/package.json index 9043c0cc..63a888d9 100644 --- a/packages/ipld-eth-client/package.json +++ b/packages/ipld-eth-client/package.json @@ -1,6 +1,6 @@ { "name": "@cerc-io/ipld-eth-client", - "version": "0.2.71", + "version": "0.2.72", "description": "IPLD ETH Client", "main": "dist/index.js", "scripts": { @@ -20,8 +20,8 @@ "homepage": "https://github.com/cerc-io/watcher-ts#readme", "dependencies": { "@apollo/client": "^3.7.1", - "@cerc-io/cache": "^0.2.71", - "@cerc-io/util": "^0.2.71", + "@cerc-io/cache": "^0.2.72", + "@cerc-io/util": "^0.2.72", "cross-fetch": "^3.1.4", "debug": "^4.3.1", "ethers": "^5.4.4", diff --git a/packages/peer/package.json b/packages/peer/package.json index c3eb8c36..9ec6717d 100644 --- a/packages/peer/package.json +++ b/packages/peer/package.json @@ -1,6 +1,6 @@ { "name": "@cerc-io/peer", - "version": "0.2.71", + "version": "0.2.72", "description": "libp2p module", "main": "dist/index.js", "exports": "./dist/index.js", diff --git a/packages/rpc-eth-client/package.json b/packages/rpc-eth-client/package.json index 83d3f3c1..bbfe2856 100644 --- a/packages/rpc-eth-client/package.json +++ b/packages/rpc-eth-client/package.json @@ -1,6 +1,6 @@ { "name": "@cerc-io/rpc-eth-client", - "version": "0.2.71", + "version": "0.2.72", "description": "RPC ETH Client", "main": "dist/index.js", "scripts": { @@ -19,9 +19,9 @@ }, "homepage": "https://github.com/cerc-io/watcher-ts#readme", "dependencies": { - "@cerc-io/cache": "^0.2.71", - "@cerc-io/ipld-eth-client": "^0.2.71", - "@cerc-io/util": "^0.2.71", + "@cerc-io/cache": "^0.2.72", + "@cerc-io/ipld-eth-client": "^0.2.72", + "@cerc-io/util": "^0.2.72", "chai": "^4.3.4", "ethers": "^5.4.4", "left-pad": "^1.3.0", diff --git a/packages/solidity-mapper/package.json b/packages/solidity-mapper/package.json index 8ca26ef3..aa6444e5 100644 --- a/packages/solidity-mapper/package.json +++ b/packages/solidity-mapper/package.json @@ -1,6 +1,6 @@ { "name": "@cerc-io/solidity-mapper", - "version": "0.2.71", + "version": "0.2.72", "main": "dist/index.js", "license": "AGPL-3.0", "devDependencies": { diff --git a/packages/test/package.json b/packages/test/package.json index 00ae073f..91d01054 100644 --- a/packages/test/package.json +++ b/packages/test/package.json @@ -1,6 +1,6 @@ { "name": "@cerc-io/test", - "version": "0.2.71", + "version": "0.2.72", "main": "dist/index.js", "license": "AGPL-3.0", "private": true, diff --git a/packages/tracing-client/package.json b/packages/tracing-client/package.json index 73ac85f6..f89634c9 100644 --- a/packages/tracing-client/package.json +++ b/packages/tracing-client/package.json @@ -1,6 +1,6 @@ { "name": "@cerc-io/tracing-client", - "version": "0.2.71", + "version": "0.2.72", "description": "ETH VM tracing client", "main": "dist/index.js", "scripts": { diff --git a/packages/util/package.json b/packages/util/package.json index 5bcb2e5d..df8dc0ff 100644 --- a/packages/util/package.json +++ b/packages/util/package.json @@ -1,13 +1,13 @@ { "name": "@cerc-io/util", - "version": "0.2.71", + "version": "0.2.72", "main": "dist/index.js", "license": "AGPL-3.0", "dependencies": { "@apollo/utils.keyvaluecache": "^1.0.1", "@cerc-io/nitro-node": "^0.1.15", - "@cerc-io/peer": "^0.2.71", - "@cerc-io/solidity-mapper": "^0.2.71", + "@cerc-io/peer": "^0.2.72", + "@cerc-io/solidity-mapper": "^0.2.72", "@cerc-io/ts-channel": "1.0.3-ts-nitro-0.1.1", "@ethersproject/properties": "^5.7.0", "@ethersproject/providers": "^5.4.4", @@ -52,7 +52,7 @@ "yargs": "^17.0.1" }, "devDependencies": { - "@cerc-io/cache": "^0.2.71", + "@cerc-io/cache": "^0.2.72", "@nomiclabs/hardhat-waffle": "^2.0.1", "@types/bunyan": "^1.8.8", "@types/express": "^4.17.14", diff --git a/packages/util/src/database.ts b/packages/util/src/database.ts index c9596f36..30fd4c75 100644 --- a/packages/util/src/database.ts +++ b/packages/util/src/database.ts @@ -926,8 +926,9 @@ export class Database { assert(operator); let whereClause = ''; - // In case of array field having contains, NOT comes before the field name - // Disregards nocase + // In case of array field having contains: + // NOT comes before the field name + // Ignores nocase if (columnIsArray && operator.includes('contains')) { if (not) { whereClause += 'NOT '; @@ -1105,10 +1106,14 @@ export class Database { const [orderBy, suffix] = orderByWithSuffix.split('__'); const columnMetadata = repo.metadata.findColumnWithPropertyName(orderBy); - assert(columnMetadata); + const relation = relations[orderBy]; + + // Ordering by array / derived type fields not supported + if (columnMetadata?.isArray || relation?.isDerived) { + throw new Error(`Ordering by \`${orderBy}\` is not supported for type \`${repo.metadata.name}\``); + } // Handle nested entity sort - const relation = relations[orderBy]; if (suffix && relation) { return this.orderQueryNested( repo, @@ -1121,6 +1126,7 @@ export class Database { ); } + assert(columnMetadata); return selectQueryBuilder.addOrderBy( `"${alias}"."${columnPrefix}${columnMetadata.databaseName}"`, orderDirection === 'desc' ? 'DESC' : 'ASC'