Commit 2650bc00 authored by aleclofabbro's avatar aleclofabbro
Browse files

almost there !

parent e3b40674
...@@ -18,7 +18,7 @@ export const buildDocumentSelection = (qObj: GraphQueryObj): DocumentSelection | ...@@ -18,7 +18,7 @@ export const buildDocumentSelection = (qObj: GraphQueryObj): DocumentSelection |
return null return null
} }
const documentValueFieldsSelections = qObj.select.reduce((_documentSelection, selection) => { const documentValueFieldsSelections = qObj.select.reduce((_documentSelection, selection) => {
const valueField: ValueField = { fieldName: selection.field } const valueField: ValueField = selection
return { return {
..._documentSelection, ..._documentSelection,
[selection.alias]: valueField, [selection.alias]: valueField,
...@@ -27,7 +27,7 @@ export const buildDocumentSelection = (qObj: GraphQueryObj): DocumentSelection | ...@@ -27,7 +27,7 @@ export const buildDocumentSelection = (qObj: GraphQueryObj): DocumentSelection |
const documentUnionLookupFieldSelections = qObj.traverse.reduce( const documentUnionLookupFieldSelections = qObj.traverse.reduce(
(_documentSelection, qObjSelection) => { (_documentSelection, qObjSelection) => {
const _existingAlias = _documentSelection[qObjSelection.alias] const _existingAlias = _documentSelection[qObjSelection.alias]
if (_existingAlias && 'fieldName' in _existingAlias) { if (_existingAlias && !isUnionLookupField(_existingAlias)) {
throw aliasConflictError(qObjSelection, qObj) throw aliasConflictError(qObjSelection, qObj)
} }
const select = buildDocumentSelection(qObjSelection) const select = buildDocumentSelection(qObjSelection)
...@@ -37,7 +37,13 @@ export const buildDocumentSelection = (qObj: GraphQueryObj): DocumentSelection | ...@@ -37,7 +37,13 @@ export const buildDocumentSelection = (qObj: GraphQueryObj): DocumentSelection |
const unionLookupField: UnionLookupField = const unionLookupField: UnionLookupField =
_existingAlias || _existingAlias ||
(_documentSelection[qObjSelection.alias] = { page: qObjSelection.page, lookups: [] }) (_documentSelection[qObjSelection.alias] = {
page: qObjSelection.page,
fieldName: qObjSelection.fieldName,
alias: qObjSelection.alias,
lookups: [],
traverseRelation: qObjSelection.traverseRelation,
})
unionLookupField.lookups.push({ unionLookupField.lookups.push({
__typename: qObjSelection.__typename, __typename: qObjSelection.__typename,
...@@ -63,5 +69,4 @@ const aliasConflictError = (selection: GraphQueryObj, par: GraphQueryObj) => { ...@@ -63,5 +69,4 @@ const aliasConflictError = (selection: GraphQueryObj, par: GraphQueryObj) => {
return new Error(`A ValueField can't have same alias[${selection.alias}] as a UnionLookupField`) 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 _) export const isUnionLookupField = (_: Field): _ is UnionLookupField => !!(_ && 'lookups' in _)
...@@ -2,10 +2,11 @@ import { Context } from '../gql' ...@@ -2,10 +2,11 @@ import { Context } from '../gql'
import { ResolverFn } from '../gql/types' import { ResolverFn } from '../gql/types'
import { ShallowTypeMocks } from '../gql/shallowTypes' import { ShallowTypeMocks } from '../gql/shallowTypes'
import { typeInfo, getParent } from './graphQuery' import { typeInfo, getParent } from './graphQuery'
import { GraphQueryObj } from './types' import { GraphQueryObj, TraverseRelation } from './types'
export function defaultGraphFieldResolver(): ResolverFn<any, any, Context, any> { export function defaultGraphFieldResolver(): ResolverFn<any, any, Context, any> {
return (parent, args, context, info) => { return (parent, args, context, info) => {
const { fieldName, parentType, returnType, schema } = info
if (!context.$graph) { if (!context.$graph) {
context.$graph = { context.$graph = {
qObj: { qObj: {
...@@ -13,11 +14,12 @@ export function defaultGraphFieldResolver(): ResolverFn<any, any, Context, any> ...@@ -13,11 +14,12 @@ export function defaultGraphFieldResolver(): ResolverFn<any, any, Context, any>
alias: 'Query', alias: 'Query',
directives: {}, directives: {},
traverse: [], traverse: [],
traverseRelation: null,
fieldName: fieldName,
select: [], select: [],
}, },
} }
} }
const { fieldName, parentType, returnType, schema } = info
console.log(`defaultFieldResolver `, { console.log(`defaultFieldResolver `, {
parent, parent,
parentTypeName: parentType.name, parentTypeName: parentType.name,
...@@ -37,7 +39,7 @@ export function defaultGraphFieldResolver(): ResolverFn<any, any, Context, any> ...@@ -37,7 +39,7 @@ export function defaultGraphFieldResolver(): ResolverFn<any, any, Context, any>
if (!parentQ) { if (!parentQ) {
throw new Error('defaultFieldResolver no parentQ') throw new Error('defaultFieldResolver no parentQ')
} }
if (isTop || ['_rel', '_subj', '_obj'].includes(fieldName)) { if (isTop || isTraverseRelation(fieldName)) {
parentQ.traverse.push( parentQ.traverse.push(
...__typenames.map<GraphQueryObj>((__typename) => ({ ...__typenames.map<GraphQueryObj>((__typename) => ({
__typename, __typename,
...@@ -46,7 +48,9 @@ export function defaultGraphFieldResolver(): ResolverFn<any, any, Context, any> ...@@ -46,7 +48,9 @@ export function defaultGraphFieldResolver(): ResolverFn<any, any, Context, any>
page: args?.page, page: args?.page,
select: [], select: [],
traverse: [], traverse: [],
traverseRelation: isTraverseRelation(fieldName) ? fieldName : null,
directives: {}, directives: {},
fieldName: fieldName,
})) }))
) )
...@@ -62,9 +66,12 @@ export function defaultGraphFieldResolver(): ResolverFn<any, any, Context, any> ...@@ -62,9 +66,12 @@ export function defaultGraphFieldResolver(): ResolverFn<any, any, Context, any>
return fieldReturn return fieldReturn
} else { } else {
if ('string' == typeof info.path.key) { if ('string' == typeof info.path.key) {
parentQ.select.push({ field: fieldName, alias: info.path.key }) parentQ.select.push({ fieldName: fieldName, alias: info.path.key })
} }
return parent[fieldName] return parent[fieldName]
} }
} }
} }
const isTraverseRelation = (_: string): _ is TraverseRelation =>
['_subj', '_obj', '_rel'].includes(_)
import { DocumentSelection } from '../types' import { DocumentSelection } from '../types'
import { isValueField } from '../queryBuilder' import { isUnionLookupField } from '../buildDocumentSelection'
export const buildMongoPipeline = (docS: DocumentSelection) => { export const buildMongoPipeline = (docS: DocumentSelection, notTop?: boolean) => {
const lookups = [] as any[] const lookups = [] as any[]
const project = {} as Record<string, string> const project = {} as Record<string, string>
Object.entries(docS).forEach(([alias, field]) => { Object.entries(docS).forEach(([alias, field]) => {
if (isValueField(field)) { if (isUnionLookupField(field)) {
project[field.fieldName] = alias const $mainMatchUnionMatch = { $or: [] as any[] }
} else { const $mainMatch = { $and: [$mainMatchUnionMatch] }
const $match = { $and: [] as any[] } const pipeline = [{ $match: $mainMatch }] as any[]
const $unionWith = {
coll: 'Graph',
pipeline: [{ $match }] as any[],
}
const $lookup = { const $lookup = {
as: alias, as: field.alias,
let: {} as Record<string, string>,
from: 'Graph', from: 'Graph',
pipeline: [{ $unionWith }], pipeline,
}
if (field.traverseRelation) {
if (field.traverseRelation === '_rel') {
$lookup.let.nodeId = '$_id'
notTop &&
$mainMatch.$and.push({
// $expr: { $eq: [`$${field.traverseRelation}`, '$$nodeId'] },
$expr: { $eq: [`$_subj`, '$$nodeId'] },
} as any)
} else {
$lookup.let.edgeSide = `$${field.traverseRelation}` //=== '_obj' ? '$_subj' : '$_obj'
notTop && $mainMatch.$and.push({ $expr: { $eq: [`$_id`, '$$edgeSide'] } } as any)
}
} }
field.lookups.forEach((fieldLookup) => { field.lookups.forEach((fieldLookup) => {
$match.$and.push({ __typename: fieldLookup.__typename }) const $match = { $and: [{ __typename: fieldLookup.__typename }] as any[] }
$mainMatchUnionMatch.$or.push($match)
fieldLookup.match && $match.$and.push(fieldLookup.match) fieldLookup.match && $match.$and.push(fieldLookup.match)
const pipeline = buildMongoPipeline(fieldLookup.select) pipeline.push(...buildMongoPipeline(fieldLookup.select, true))
$unionWith.pipeline.push(...pipeline)
}) })
project[field.alias] = `$${field.alias}`
lookups.push({ lookups.push({
$lookup, $lookup,
}) })
} else {
project[alias] = `$${field.fieldName}`
} }
}) })
const stages = [...lookups] if (notTop) {
if (Object.keys(project).length) { const stages = [...lookups]
stages.push({ $project: project }) if (Object.keys(project).length) {
stages.push({ $project: project })
}
return stages
} else {
const stages = lookups[0].$lookup.pipeline
return stages
} }
return stages
} }
...@@ -7,14 +7,17 @@ export type GqlType = { id: string; __typename: string } ...@@ -7,14 +7,17 @@ export type GqlType = { id: string; __typename: string }
export type GqlNode = GqlType & { _rel: any[] } export type GqlNode = GqlType & { _rel: any[] }
export type GqlRelation = GqlType & { _obj: any; _subj: any } export type GqlRelation = GqlType & { _obj: any; _subj: any }
export type TraverseRelation = '_obj' | '_subj' | '_rel'
// GraphQuery // GraphQuery
export type GraphQuery = { export type GraphQuery = {
qObj: GraphQueryObj qObj: GraphQueryObj
} }
export type GraphQueryObj = { export type GraphQueryObj = Selection & {
__typename: string __typename: string
traverseRelation: TraverseRelation | null
match?: any match?: any
page?: { page?: {
limit?: number limit?: number
...@@ -23,11 +26,10 @@ export type GraphQueryObj = { ...@@ -23,11 +26,10 @@ export type GraphQueryObj = {
directives: Record<string, any> directives: Record<string, any>
select: Selection[] select: Selection[]
traverse: GraphQueryObj[] traverse: GraphQueryObj[]
alias: string
} }
export type Selection = { export type Selection = {
field: string fieldName: string
alias: string alias: string
} }
export type Match = any export type Match = any
...@@ -41,15 +43,14 @@ export type DocumentSelection = { ...@@ -41,15 +43,14 @@ export type DocumentSelection = {
export type Field = UnionLookupField | ValueField export type Field = UnionLookupField | ValueField
export type ValueField = { export type ValueField = Selection
fieldName: string
}
export type UnionLookupField = { export type UnionLookupField = ValueField & {
page?: { page?: {
limit?: number limit?: number
after?: string | ObjectID after?: string | ObjectID
} }
traverseRelation: TraverseRelation | null
lookups: FieldLookup[] lookups: FieldLookup[]
} }
......
...@@ -3,8 +3,8 @@ import { ApolloServer } from 'apollo-server-express' ...@@ -3,8 +3,8 @@ import { ApolloServer } from 'apollo-server-express'
import express from 'express' import express from 'express'
import { graphql, GraphQLSchema } from 'graphql' import { graphql, GraphQLSchema } from 'graphql'
import { Context } from '../gql' import { Context } from '../gql'
import { buildMongoPipeline } from '../gql-graph/mongo/buildMongoQuery' import { buildMongoPipeline } from '../gql-graph/mongo/buildMongoPipeline'
import { buildQueryDocumentSelection } from '../gql-graph/queryBuilder' import { buildQueryDocumentSelection } from '../gql-graph/buildDocumentSelection'
import { collection } from '../mongo/collection' import { collection } from '../mongo/collection'
type Cfg = { type Cfg = {
...@@ -28,7 +28,7 @@ const executor: GraphQLServerOptions['executor'] = async (requestContext) => { ...@@ -28,7 +28,7 @@ const executor: GraphQLServerOptions['executor'] = async (requestContext) => {
console.dir({ 'executor documentSelection': documentSelection }, { depth: 15 }) console.dir({ 'executor documentSelection': documentSelection }, { depth: 15 })
if (documentSelection) { if (documentSelection) {
const pipeline = buildMongoPipeline(documentSelection) const pipeline = buildMongoPipeline(documentSelection)
console.dir({ 'executor pipeline': pipeline }, { depth: 15 }) console.log('executor pipeline', JSON.stringify(pipeline, null, 4))
const c = await collection<any>() const c = await collection<any>()
return { data: await c.aggregate(pipeline).toArray() } return { data: await c.aggregate(pipeline).toArray() }
} }
......
Supports Markdown
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