Commit 1499b85d authored by aleclofabbro's avatar aleclofabbro
Browse files

unionType $lookup with $unionWith

parent 2650bc00
interface Glyph {
id: ID!
_id: ID!
}
input StringMatchInput {
_eq: String
......@@ -23,10 +23,10 @@ input KnowsObjectQueryInput {
input KnowsQueryInput {
_and: [KnowsQueryInput!]
_or: [KnowsQueryInput!]
id: [ID!]
_id: [ID!]
}
type Knows implements Glyph {
id: ID!
_id: ID!
_subj(query: KnowsSubjectQueryInput, page: PageInput): [Knower!]!
_obj(query: KnowsObjectQueryInput, page: PageInput): [Knowable!]!
}
......@@ -44,10 +44,10 @@ input FollowSubjectQueryInput {
input FollowsQueryInput {
_and: [FollowsQueryInput!]
_or: [FollowsQueryInput!]
id: [ID!]
_id: [ID!]
}
type Follows implements Glyph {
id: ID!
_id: ID!
_subj(query: FollowSubjectQueryInput, page: PageInput): [Follower!]!
_obj(query: FollowObjectQueryInput, page: PageInput): [Followable!]!
}
......@@ -56,7 +56,7 @@ type Follows implements Glyph {
input UserQueryInput {
_and: [UserQueryInput!]
_or: [UserQueryInput!]
id: [ID!]
_id: [ID!]
username: StringMatchInput
}
......@@ -68,7 +68,7 @@ input UserRelationQueryInput {
union UserRelation = Knows | Follows
type User implements Glyph {
id: ID!
_id: ID!
_rel(query: UserRelationQueryInput, page: PageInput): [UserRelation!]!
username: String!
}
......
......@@ -2,47 +2,62 @@ import { DocumentSelection } from '../types'
import { isUnionLookupField } from '../buildDocumentSelection'
export const buildMongoPipeline = (docS: DocumentSelection, notTop?: boolean) => {
const lookups = [] as any[]
const project = {} as Record<string, string>
const project = { __typename: true } as Record<string, any>
Object.entries(docS).forEach(([alias, field]) => {
Object.entries(docS).forEach(([_alias, field]) => {
if (isUnionLookupField(field)) {
const $mainMatchUnionMatch = { $or: [] as any[] }
const $mainMatch = { $and: [$mainMatchUnionMatch] }
const pipeline = [{ $match: $mainMatch }] as any[]
const $lookup = {
const unionLookupPipeline = [] as any[]
let _let: any = undefined
const mainLookup = {
as: field.alias,
let: {} as Record<string, string>,
from: 'Graph',
pipeline,
}
pipeline: [],
} as any
if (field.traverseRelation) {
if (field.traverseRelation === '_rel') {
$lookup.let.nodeId = '$_id'
_let = { nodeId: '$_id' }
notTop &&
$mainMatch.$and.push({
// $expr: { $eq: [`$${field.traverseRelation}`, '$$nodeId'] },
$expr: { $eq: [`$_subj`, '$$nodeId'] },
} as any)
unionLookupPipeline.push({
$match: {
// $expr: { $eq: [`$${field.traverseRelation}`, '$$nodeId'] },
$expr: { $eq: [`$_subj`, '$$nodeId'] },
},
})
} else {
$lookup.let.edgeSide = `$${field.traverseRelation}` //=== '_obj' ? '$_subj' : '$_obj'
notTop && $mainMatch.$and.push({ $expr: { $eq: [`$_id`, '$$edgeSide'] } } as any)
_let = { edgeSide: `$${field.traverseRelation}` } //=== '_obj' ? '$_subj' : '$_obj'
notTop && unionLookupPipeline.push({ $match: { $expr: { $eq: [`$_id`, '$$edgeSide'] } } })
}
}
field.lookups.forEach((fieldLookup) => {
field.lookups.forEach((fieldLookup, index) => {
const $match = { $and: [{ __typename: fieldLookup.__typename }] as any[] }
$mainMatchUnionMatch.$or.push($match)
fieldLookup.match && $match.$and.push(fieldLookup.match)
pipeline.push(...buildMongoPipeline(fieldLookup.select, true))
})
project[field.alias] = `$${field.alias}`
lookups.push({
$lookup,
if (!index) {
mainLookup.pipeline = [
...unionLookupPipeline,
{ $match },
...buildMongoPipeline(fieldLookup.select, true),
]
_let && (mainLookup.let = _let)
lookups.push({ $lookup: mainLookup })
} else {
mainLookup.pipeline.push({
$unionWith: {
coll: 'Graph',
pipeline: [
...unionLookupPipeline,
{ $match },
...buildMongoPipeline(fieldLookup.select, true) /* [0].$lookup.pipeline */,
],
},
})
}
})
project[field.alias] = true
} else {
project[alias] = `$${field.fieldName}`
project[field.alias] = field.alias === field.fieldName ? true : `$${field.fieldName}`
}
})
if (notTop) {
......
import { ObjectID } from 'mongodb'
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'> & {
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
_obj: ObjectID
_subj: ObjectID
}
export const toMongoNode = <Node extends GqlNode>(
gql_node: ShallowEntity<Node> | Omit<ShallowEntity<Node>, 'id'>
gql_node: ShallowEntity<Node> | Omit<ShallowEntity<Node>, '_id'>
): MongoNode<Node> => {
const _id = 'id' in gql_node ? new ObjectID(gql_node.id) : new ObjectID()
const _id = '_id' in gql_node ? new ObjectID(gql_node._id) : new ObjectID()
const mongo_node = Object.entries(gql_node).reduce(
(constructing_mongo_node, [key, val]) => {
if (['id', '_rel'].includes(key)) {
if (['_id', '_rel'].includes(key)) {
return constructing_mongo_node
}
return {
......@@ -31,15 +31,15 @@ export const toMongoNode = <Node extends GqlNode>(
}
export const toMongoRelation = <Rel extends GqlRelation>(
gql_node: ShallowRelation<Rel> | Omit<ShallowRelation<Rel>, 'id'>,
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 _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)) {
if (['_id'].includes(key)) {
return constructing_mongo_rel
}
return {
......
......@@ -3,7 +3,7 @@ import { ObjectID } from 'mongodb'
export type ShallowEntity<T> = Omit<T, '_rel'>
export type ShallowRelation<T> = Omit<T, '_obj' | '_subj'>
export type GqlType = { id: string; __typename: string }
export type GqlType = { _id: string; __typename: string }
export type GqlNode = GqlType & { _rel: any[] }
export type GqlRelation = GqlType & { _obj: any; _subj: any }
......
......@@ -14,7 +14,7 @@
"description": null,
"fields": [
{
"name": "id",
"name": "_id",
"description": null,
"args": [],
"type": {
......@@ -270,7 +270,7 @@
"defaultValue": null
},
{
"name": "id",
"name": "_id",
"description": null,
"type": {
"kind": "LIST",
......@@ -298,7 +298,7 @@
"description": null,
"fields": [
{
"name": "id",
"name": "_id",
"description": null,
"args": [],
"type": {
......@@ -532,7 +532,7 @@
"defaultValue": null
},
{
"name": "id",
"name": "_id",
"description": null,
"type": {
"kind": "LIST",
......@@ -560,7 +560,7 @@
"description": null,
"fields": [
{
"name": "id",
"name": "_id",
"description": null,
"args": [],
"type": {
......@@ -720,7 +720,7 @@
"defaultValue": null
},
{
"name": "id",
"name": "_id",
"description": null,
"type": {
"kind": "LIST",
......@@ -810,7 +810,7 @@
"description": null,
"fields": [
{
"name": "id",
"name": "_id",
"description": null,
"args": [],
"type": {
......
......@@ -13,5 +13,5 @@ export const createFollows: MutationResolvers['createFollows'] = async (_parent,
)
const c = await collection<Follows>()
await c.insertOne(newFollows)
return null //{ ...newFollows, id: newFollows._id.toHexString(), _obj: [], _subj: [] }
return null //{ ...newFollows, _id: newFollows._id.toHexString(), _obj: [], _subj: [] }
}
......@@ -13,5 +13,5 @@ export const createKnows: MutationResolvers['createKnows'] = async (_parent, arg
)
const c = await collection<Knows>()
await c.insertOne(newKnows)
return null //{ ...newKnows, id: newKnows._id.toHexString(), _obj: [], _subj: [] }
return null //{ ...newKnows, _id: newKnows._id.toHexString(), _obj: [], _subj: [] }
}
......@@ -12,5 +12,5 @@ export const createUser: MutationResolvers['createUser'] = async (
})
const c = await collection<User>()
await c.insertOne(newUser)
return null //{ ...newUser, id: newUser._id.toHexString(), _rel: [] }
return null //{ ...newUser, _id: newUser._id.toHexString(), _rel: [] }
}
import { defaultGraphFieldResolver } from '../../../gql-graph/defaultGraphFieldResolver'
export const Types = {
User: {
id: defaultGraphFieldResolver(),
_id: defaultGraphFieldResolver(),
_rel: defaultGraphFieldResolver(),
username: defaultGraphFieldResolver(),
},
Knows: {
id: defaultGraphFieldResolver(),
_id: defaultGraphFieldResolver(),
_subj: defaultGraphFieldResolver(),
_obj: defaultGraphFieldResolver(),
},
Follows: {
id: defaultGraphFieldResolver(),
_id: defaultGraphFieldResolver(),
_subj: defaultGraphFieldResolver(),
_obj: defaultGraphFieldResolver(),
},
......
......@@ -3,5 +3,5 @@ import * as Types from '../types'
export const Follows = (tag: string): ShallowRelation<Types.Follows> => ({
__typename: 'Follows',
id: `id[${tag}]`,
_id: `_id[${tag}]`,
})
......@@ -3,5 +3,5 @@ import * as Types from '../types'
export const Knows = (tag: string): ShallowRelation<Types.Knows> => ({
__typename: 'Knows',
id: `id[${tag}]`,
_id: `_id[${tag}]`,
})
......@@ -2,6 +2,6 @@ import { ShallowEntity } from '../../gql-graph/types'
import * as Types from '../types'
export const User = (tag: string): ShallowEntity<Types.User> => ({
__typename: 'User',
id: `id[${tag}]`,
_id: `_id[${tag}]`,
username: `username[${tag}]`,
})
......@@ -14,7 +14,7 @@ export type Scalars = {
};
export type Glyph = {
id: Scalars['ID'];
_id: Scalars['ID'];
};
export type StringMatchInput = {
......@@ -43,12 +43,12 @@ export type KnowsObjectQueryInput = {
export type KnowsQueryInput = {
_and: Maybe<Array<KnowsQueryInput>>;
_or: Maybe<Array<KnowsQueryInput>>;
id: Maybe<Array<Scalars['ID']>>;
_id: Maybe<Array<Scalars['ID']>>;
};
export type Knows = Glyph & {
__typename: 'Knows';
id: Scalars['ID'];
_id: Scalars['ID'];
_subj: Array<Knower>;
_obj: Array<Knowable>;
};
......@@ -80,12 +80,12 @@ export type FollowSubjectQueryInput = {
export type FollowsQueryInput = {
_and: Maybe<Array<FollowsQueryInput>>;
_or: Maybe<Array<FollowsQueryInput>>;
id: Maybe<Array<Scalars['ID']>>;
_id: Maybe<Array<Scalars['ID']>>;
};
export type Follows = Glyph & {
__typename: 'Follows';
id: Scalars['ID'];
_id: Scalars['ID'];
_subj: Array<Follower>;
_obj: Array<Followable>;
};
......@@ -105,7 +105,7 @@ export type Follows_ObjArgs = {
export type UserQueryInput = {
_and: Maybe<Array<UserQueryInput>>;
_or: Maybe<Array<UserQueryInput>>;
id: Maybe<Array<Scalars['ID']>>;
_id: Maybe<Array<Scalars['ID']>>;
username: Maybe<StringMatchInput>;
};
......@@ -118,7 +118,7 @@ export type UserRelation = Knows | Follows;
export type User = Glyph & {
__typename: 'User';
id: Scalars['ID'];
_id: Scalars['ID'];
_rel: Array<UserRelation>;
username: Scalars['String'];
};
......@@ -331,7 +331,7 @@ export type ResolversParentTypes = {
export type GlyphResolvers<ContextType = Context, ParentType extends ResolversParentTypes['Glyph'] = ResolversParentTypes['Glyph']> = {
__resolveType: TypeResolveFn<'Knows' | 'Follows' | 'User', ParentType, ContextType>;
id: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
_id: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
};
export type KnowerResolvers<ContextType = Context, ParentType extends ResolversParentTypes['Knower'] = ResolversParentTypes['Knower']> = {
......@@ -343,7 +343,7 @@ export type KnowableResolvers<ContextType = Context, ParentType extends Resolver
};
export type KnowsResolvers<ContextType = Context, ParentType extends ResolversParentTypes['Knows'] = ResolversParentTypes['Knows']> = {
id: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
_id: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
_subj: Resolver<Array<ResolversTypes['Knower']>, ParentType, ContextType, RequireFields<Knows_SubjArgs, never>>;
_obj: Resolver<Array<ResolversTypes['Knowable']>, ParentType, ContextType, RequireFields<Knows_ObjArgs, never>>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
......@@ -358,7 +358,7 @@ export type FollowableResolvers<ContextType = Context, ParentType extends Resolv
};
export type FollowsResolvers<ContextType = Context, ParentType extends ResolversParentTypes['Follows'] = ResolversParentTypes['Follows']> = {
id: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
_id: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
_subj: Resolver<Array<ResolversTypes['Follower']>, ParentType, ContextType, RequireFields<Follows_SubjArgs, never>>;
_obj: Resolver<Array<ResolversTypes['Followable']>, ParentType, ContextType, RequireFields<Follows_ObjArgs, never>>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
......@@ -369,7 +369,7 @@ export type UserRelationResolvers<ContextType = Context, ParentType extends Reso
};
export type UserResolvers<ContextType = Context, ParentType extends ResolversParentTypes['User'] = ResolversParentTypes['User']> = {
id: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
_id: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
_rel: Resolver<Array<ResolversTypes['UserRelation']>, ParentType, ContextType, RequireFields<User_RelArgs, never>>;
username: Resolver<ResolversTypes['String'], ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
......
......@@ -28,9 +28,10 @@ const executor: GraphQLServerOptions['executor'] = async (requestContext) => {
console.dir({ 'executor documentSelection': documentSelection }, { depth: 15 })
if (documentSelection) {
const pipeline = buildMongoPipeline(documentSelection)
console.log('executor pipeline', JSON.stringify(pipeline, null, 4))
const c = await collection<any>()
return { data: await c.aggregate(pipeline).toArray() }
console.log('executor pipeline', JSON.stringify(pipeline, null, 2))
const coll = await collection<any>()
const data = await coll.aggregate(pipeline).toArray()
return { data }
}
return Promise.resolve(res)
}
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