Commit e3b40674 authored by aleclofabbro's avatar aleclofabbro
Browse files

build mongo pipeline with $unionWith

parent 204ae430
......@@ -89,7 +89,9 @@ input CreateUserInput {
username: String!
}
type Mutation {
createUser(user: CreateUserInput!): User!
createUser(user: CreateUserInput!): User
createKnows(from: ID!, to: ID!): Knows
createFollows(from: ID!, to: ID!): Follows
}
enum Role {
......
import { DocumentSelection, GraphQuery, GraphQueryObj, UnionLookupField, ValueField } from './types'
import {
DocumentSelection,
Field,
GraphQuery,
GraphQueryObj,
UnionLookupField,
ValueField,
} from './types'
export const buildQueryDocumentSelection = (
root?: GraphQuery
root: GraphQuery | null | undefined
): DocumentSelection | null | undefined => {
return root && buildDocumentSelection(root.qObj)
}
......@@ -55,3 +62,6 @@ const aliasConflictError = (selection: GraphQueryObj, par: GraphQueryObj) => {
console.error(`A ValueField can't have same alias[${selection.alias}] as a UnionLookupField`, par)
return new Error(`A ValueField can't have same alias[${selection.alias}] as a UnionLookupField`)
}
export const isValueField = (_: Field): _ is ValueField => !!(_ && 'fieldName' in _)
export const isUnionLookupField = (_: Field): _ is UnionLookupField => !!(_ && 'lookups' in _)
import { DocumentSelection } from '../types'
import { isValueField } from '../queryBuilder'
export const buildMongoPipeline = (docS: DocumentSelection) => {
const lookups = [] as any[]
const project = {} as Record<string, string>
Object.entries(docS).forEach(([alias, field]) => {
if (isValueField(field)) {
project[field.fieldName] = alias
} else {
const $match = { $and: [] as any[] }
const $unionWith = {
coll: 'Graph',
pipeline: [{ $match }] as any[],
}
const $lookup = {
as: alias,
from: 'Graph',
pipeline: [{ $unionWith }],
}
field.lookups.forEach((fieldLookup) => {
$match.$and.push({ __typename: fieldLookup.__typename })
fieldLookup.match && $match.$and.push(fieldLookup.match)
const pipeline = buildMongoPipeline(fieldLookup.select)
$unionWith.pipeline.push(...pipeline)
})
lookups.push({
$lookup,
})
}
})
const stages = [...lookups]
if (Object.keys(project).length) {
stages.push({ $project: project })
}
return stages
}
import { ObjectID } from 'mongodb'
import { GqlNode, GqlRelation, ShallowEntity } from '../types'
import { GqlNode, GqlRelation, ShallowEntity, ShallowRelation } from '../types'
export type MongoNode<T extends GqlNode> = Omit<T, 'id' | '_rel'> & { _id: ObjectID }
export type MongoRelation<T extends GqlRelation> = Omit<T, 'id' | '_subj' | '_obj'> & {
_id: ObjectID
......@@ -7,9 +7,9 @@ export type MongoRelation<T extends GqlRelation> = Omit<T, 'id' | '_subj' | '_ob
_subj: ObjectID
}
export const toMongoNode = <N extends GqlNode>(
gql_node: ShallowEntity<N> | Omit<ShallowEntity<N>, 'id'>
): MongoNode<N> => {
export const toMongoNode = <Node extends GqlNode>(
gql_node: ShallowEntity<Node> | Omit<ShallowEntity<Node>, 'id'>
): MongoNode<Node> => {
const _id = 'id' in gql_node ? new ObjectID(gql_node.id) : new ObjectID()
const mongo_node = Object.entries(gql_node).reduce(
......@@ -24,8 +24,35 @@ export const toMongoNode = <N extends GqlNode>(
},
{
_id,
} as MongoNode<N>
} as MongoNode<Node>
)
return mongo_node
}
export const toMongoRelation = <Rel extends GqlRelation>(
gql_node: ShallowRelation<Rel> | Omit<ShallowRelation<Rel>, 'id'>,
obj: ObjectID | string,
subj: ObjectID | string
): MongoRelation<Rel> => {
const _id = 'id' in gql_node ? new ObjectID(gql_node.id) : new ObjectID()
const mongo_rel = Object.entries(gql_node).reduce(
(constructing_mongo_rel, [key, val]) => {
if (['id'].includes(key)) {
return constructing_mongo_rel
}
return {
...constructing_mongo_rel,
[key]: val,
}
},
{
_id,
_obj: new ObjectID(obj),
_subj: new ObjectID(subj),
} as MongoRelation<Rel>
)
return mongo_rel
}
......@@ -35,6 +35,10 @@ export type Limit = any
// Graph DocumentSelection
export type DocumentSelection = {
[alias: string]: Field
}
export type Field = UnionLookupField | ValueField
export type ValueField = {
......@@ -52,9 +56,5 @@ export type UnionLookupField = {
export type FieldLookup = {
__typename: string
match?: any
select: null | DocumentSelection
}
export type DocumentSelection = {
[alias: string]: Field
select: DocumentSelection
}
......@@ -1045,13 +1045,91 @@
}
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "User",
"ofType": null
"kind": "OBJECT",
"name": "User",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "createKnows",
"description": null,
"args": [
{
"name": "from",
"description": null,
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
}
},
"defaultValue": null
},
{
"name": "to",
"description": null,
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
}
},
"defaultValue": null
}
],
"type": {
"kind": "OBJECT",
"name": "Knows",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "createFollows",
"description": null,
"args": [
{
"name": "from",
"description": null,
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
}
},
"defaultValue": null
},
{
"name": "to",
"description": null,
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
}
},
"defaultValue": null
}
],
"type": {
"kind": "OBJECT",
"name": "Follows",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
......
import { ObjectID } from 'mongodb'
import { toMongoRelation } from '../../../gql-graph/mongo/mappers'
import { collection } from '../../../mongo/collection'
import { Follows, MutationResolvers } from '../../types'
export const createFollows: MutationResolvers['createFollows'] = async (_parent, args) => {
const newFollows = toMongoRelation<Follows>(
{
__typename: 'Follows',
},
new ObjectID(args.to),
new ObjectID(args.from)
)
const c = await collection<Follows>()
await c.insertOne(newFollows)
return null //{ ...newFollows, id: newFollows._id.toHexString(), _obj: [], _subj: [] }
}
import { ObjectID } from 'mongodb'
import { toMongoRelation } from '../../../gql-graph/mongo/mappers'
import { collection } from '../../../mongo/collection'
import { Knows, MutationResolvers } from '../../types'
export const createKnows: MutationResolvers['createKnows'] = async (_parent, args) => {
const newKnows = toMongoRelation<Knows>(
{
__typename: 'Knows',
},
new ObjectID(args.to),
new ObjectID(args.from)
)
const c = await collection<Knows>()
await c.insertOne(newKnows)
return null //{ ...newKnows, id: newKnows._id.toHexString(), _obj: [], _subj: [] }
}
......@@ -10,7 +10,7 @@ export const createUser: MutationResolvers['createUser'] = async (
__typename: 'User',
username: args.user.username,
})
const c = await collection<User>('User')
const c = await collection<User>()
await c.insertOne(newUser)
return { ...newUser, id: newUser._id.toHexString(), _rel: [] }
return null //{ ...newUser, id: newUser._id.toHexString(), _rel: [] }
}
import { MutationResolvers } from '../../types'
import { createUser } from './createUser'
import { createFollows } from './createFollows'
import { createKnows } from './createKnows'
export const Mutation: MutationResolvers = {
createUser,
createFollows,
createKnows,
}
......@@ -152,7 +152,9 @@ export type CreateUserInput = {
export type Mutation = {
__typename: 'Mutation';
createUser: User;
createUser: Maybe<User>;
createKnows: Maybe<Knows>;
createFollows: Maybe<Follows>;
};
......@@ -160,6 +162,18 @@ export type MutationCreateUserArgs = {
user: CreateUserInput;
};
export type MutationCreateKnowsArgs = {
from: Scalars['ID'];
to: Scalars['ID'];
};
export type MutationCreateFollowsArgs = {
from: Scalars['ID'];
to: Scalars['ID'];
};
export enum Role {
Anonymous = 'Anonymous',
Admin = 'Admin',
......@@ -366,7 +380,9 @@ export type QueryResolvers<ContextType = Context, ParentType extends ResolversPa
};
export type MutationResolvers<ContextType = Context, ParentType extends ResolversParentTypes['Mutation'] = ResolversParentTypes['Mutation']> = {
createUser: Resolver<ResolversTypes['User'], ParentType, ContextType, RequireFields<MutationCreateUserArgs, 'user'>>;
createUser: Resolver<Maybe<ResolversTypes['User']>, ParentType, ContextType, RequireFields<MutationCreateUserArgs, 'user'>>;
createKnows: Resolver<Maybe<ResolversTypes['Knows']>, ParentType, ContextType, RequireFields<MutationCreateKnowsArgs, 'from' | 'to'>>;
createFollows: Resolver<Maybe<ResolversTypes['Follows']>, ParentType, ContextType, RequireFields<MutationCreateFollowsArgs, 'from' | 'to'>>;
};
export type AuthResolvers<ContextType = Context, ParentType extends ResolversParentTypes['Auth'] = ResolversParentTypes['Auth']> = {
......
......@@ -3,7 +3,9 @@ import { ApolloServer } from 'apollo-server-express'
import express from 'express'
import { graphql, GraphQLSchema } from 'graphql'
import { Context } from '../gql'
import { buildMongoPipeline } from '../gql-graph/mongo/buildMongoQuery'
import { buildQueryDocumentSelection } from '../gql-graph/queryBuilder'
import { collection } from '../mongo/collection'
type Cfg = {
httpPort: number
......@@ -24,5 +26,11 @@ const executor: GraphQLServerOptions['executor'] = async (requestContext) => {
console.dir({ 'executor $graph': ctx }, { depth: 15 })
const documentSelection = buildQueryDocumentSelection(ctx.$graph)
console.dir({ 'executor documentSelection': documentSelection }, { depth: 15 })
if (documentSelection) {
const pipeline = buildMongoPipeline(documentSelection)
console.dir({ 'executor pipeline': pipeline }, { depth: 15 })
const c = await collection<any>()
return { data: await c.aggregate(pipeline).toArray() }
}
return Promise.resolve(res)
}
......@@ -2,7 +2,12 @@ import { MongoNode, MongoRelation } from '../gql-graph/mongo/mappers'
import { GqlType, GqlNode, GqlRelation } from '../gql-graph/types'
import { DB } from './'
export const collection = async <E extends GqlType>(name: E['__typename']) =>
// export const collection = async <E extends GqlType>(name: E['__typename']) =>
// (await DB).collection<
// E extends GqlNode ? MongoNode<E> : E extends GqlRelation ? MongoRelation<E> : never
// >(name)
export const collection = async <E extends GqlType>() =>
(await DB).collection<
E extends GqlNode ? MongoNode<E> : E extends GqlRelation ? MongoRelation<E> : never
>(name)
>('Graph')
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment