Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 2 additions & 6 deletions lib/query-builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,7 @@ function addDeferredQueryField (query, fieldName, queryFieldNode) {
function toArgsAdapterInput (result, path) {
if (!result) { return [] }

if (!Array.isArray(result)) {
return [result]
}

let r = result.filter(r => !!r)
let r = Array.isArray(result) ? result.filter(r => !!r) : [result]

if (!path) {
return r.flat()
Expand All @@ -86,7 +82,7 @@ function toArgsAdapterInput (result, path) {
i++
}

return r.flat()
return r.flat(Infinity)
}

function buildQuery (query, parentResult) {
Expand Down
84 changes: 31 additions & 53 deletions lib/result.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,27 +28,16 @@ function mergeResult (mainResult, fullPath, queryNode, parentResult) {
return
}

// traverse result till bottom
let r = mainResult[path[0]]
let i = 1
while (i < path.length) {
const t = traverseResult(r, path[i])
if (!t) { break }
r = t
i++
}
const containerPath = (parentResult.path?.length ? parentResult.path : path).slice(0, -1)
const fillPath = path.slice(containerPath.length)

// fill the missing result path
const fillPath = []
for (let j = i; j < path.length; j++) {
fillPath.push(path[j])
let r = resolveResultPath(mainResult, containerPath)
while (!r && containerPath.length) {
fillPath.unshift(containerPath.pop())
r = resolveResultPath(mainResult, containerPath)
}

if (!r) {
// copy reference
r = mergingResult
return
}
r ??= mainResult

const many = parentResult.as && parentResult.many
let key, parentKey, index
Expand Down Expand Up @@ -132,28 +121,18 @@ function copyResultRowList (dst, src, srcIndex, parentKey, keyPath, fillPath) {

if (!traverseDst?.[parentKey]) { return } // TODO !undefined !null

const keys = Array.isArray(traverseDst[parentKey]) ? traverseDst[parentKey] : [traverseDst[parentKey]]
let rowIndexes = []
// TODO more performant code
// design a different struct to avoid loops
if (Array.isArray(traverseDst[parentKey])) {
for (let i = 0; i < traverseDst[parentKey].length; i++) {
const indexes = srcIndex.map.get(traverseDst[parentKey][i])
if (indexes) { rowIndexes = rowIndexes.concat(indexes) }
}
} else {
const indexes = srcIndex.map.get(traverseDst[parentKey])
if (indexes) { rowIndexes = indexes }
for (const key of keys) {
const indexes = srcIndex.map.get(key)
if (indexes) rowIndexes = rowIndexes.concat(indexes)
}

for (; fillIndex < fillPath.length; fillIndex++) {
if (!traverseDst[fillPath[fillIndex]]) {
// TODO get result type from types
if (fillIndex === fillPath.length - 1) {
// TODO more performant code
traverseDst[fillPath[fillIndex]] = []
for (let i = 0; i < rowIndexes.length; i++) {
traverseDst[fillPath[fillIndex]].push(src[rowIndexes[i]])
}
traverseDst[fillPath[fillIndex]] = rowIndexes.map(i => src[i])
return
}
traverseDst[fillPath[fillIndex]] = {}
Expand All @@ -167,37 +146,36 @@ function copyResultRowList (dst, src, srcIndex, parentKey, keyPath, fillPath) {
}

function resultIndex (result, key) {
if (result.length < 1) {
if (!result.length) {
return { list: false, map: new Map() }
}

const list = Array.isArray(result[0][key])
const index = new Map()

if (list) {
for (let i = 0; i < result.length; i++) {
for (let j = 0; j < result[i][key].length; j++) {
const s = index.get(result[i][key][j])
if (s) {
index.set(result[i][key][j], s.concat(i))
continue
}
index.set(result[i][key][j], [i])
}
}
} else {
for (let i = 0; i < result.length; i++) {
const s = index.get(result[i][key])
if (s) {
index.set(result[i][key], s.concat(i))
continue
}
index.set(result[i][key], [i])
for (let i = 0; i < result.length; i++) {
const keys = list ? result[i][key] : [result[i][key]]
for (const k of keys) {
const existing = index.get(k)
index.set(k, existing ? existing.concat(i) : [i])
}
}

return { list, map: index }
}

function resolveResultPath (result, segments) {
if (!segments?.length) return result

let current = result
for (const segment of segments) {
if (current == null) return current
current = traverseResult(current, segment)
}

return current
}

module.exports = {
traverseResult,
mergeResult,
Expand Down
2 changes: 1 addition & 1 deletion lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ function objectDeepClone (object) {
function copyObjectByKeys (to, src) {
const keys = Object.keys(src)
for (let i = 0; i < keys.length; i++) {
if (typeof to[keys[i]] === 'object') {
if (isObject(to[keys[i]]) && isObject(src[keys[i]])) {
copyObjectByKeys(to[keys[i]], src[keys[i]])
} else {
to[keys[i]] ??= src[keys[i]]
Expand Down
158 changes: 156 additions & 2 deletions test/entities/on-subgraphs-3.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ function artistsSubgraph () {
firstName: 'Brian',
lastName: 'Molko',
profession: 'Singer'
},
105: {
id: 105,
firstName: 'Luciano',
lastName: 'Pavarotti',
profession: 'Singer'
}
}
}
Expand All @@ -60,7 +66,22 @@ function artistsSubgraph () {
const entities = {
Artist: {
resolver: { name: 'artists' },
pkey: 'id'
pkey: 'id',
many: [
{
type: 'Song',
as: 'songs',
fkey: 'singerId',
pkey: 'id',
subgraph: 'songs-subgraph',
resolver: {
name: 'artistsSongs',
argsAdapter: (partialResults) => {
return { artistIds: partialResults.map(r => r?.id) }
}
}
}
]
}
}

Expand Down Expand Up @@ -106,6 +127,11 @@ function songsSubgraphs () {
id: 3,
title: 'Vieni via con me',
singerId: 102
},
4: {
id: 4,
title: 'Nessun dorma',
singerId: 105
}
}
}
Expand Down Expand Up @@ -389,11 +415,139 @@ test('entities on subgraph, scenario #3: entities with 1-1, 1-2-m, m-2-m relatio
name: 'should run a query with insane nested results',
query: '{ artists (ids: ["103"]) { songs { singer { songs { singer { songs { title } }} } } } }',
result: { artists: [{ songs: [{ singer: { songs: [{ singer: { songs: [{ title: 'Every you every me' }, { title: 'The bitter end' }] } }, { singer: { songs: [{ title: 'Every you every me' }, { title: 'The bitter end' }] } }] } }, { singer: { songs: [{ singer: { songs: [{ title: 'Every you every me' }, { title: 'The bitter end' }] } }, { singer: { songs: [{ title: 'Every you every me' }, { title: 'The bitter end' }] } }] } }] }] }
},

{
name: 'should handle deeply nested queries without returning null',
query: '{ artists (ids: ["105"]) { firstName, songs { singer { firstName, songs { title } } } } }',
result: {
artists: [{
firstName: 'Luciano',
songs: [{
singer: {
firstName: 'Luciano',
songs: [{ title: 'Nessun dorma' }]
}
}]
}]
}
},

{
name: 'should keep singer loops populated alongside sibling fields',
query: '{ artists (ids: ["103","105"]) { songs { title singer { firstName profession songs { title } } } } }',
result: {
artists: [
{
songs: [
{
title: 'Every you every me',
singer: {
firstName: 'Brian',
profession: 'Singer',
songs: [
{ title: 'Every you every me' },
{ title: 'The bitter end' }
]
}
},
{
title: 'The bitter end',
singer: {
firstName: 'Brian',
profession: 'Singer',
songs: [
{ title: 'Every you every me' },
{ title: 'The bitter end' }
]
}
}
]
},
{
songs: [
{
title: 'Nessun dorma',
singer: {
firstName: 'Luciano',
profession: 'Singer',
songs: [
{ title: 'Nessun dorma' }
]
}
}
]
}
]
}
},

{
name: 'should preserve first names across alternating singer chains',
query: '{ artists (ids: ["103"]) { songs { singer { firstName songs { singer { firstName songs { title } } } } } } }',
result: {
artists: [
{
songs: [
{
singer: {
firstName: 'Brian',
songs: [
{
singer: {
firstName: 'Brian',
songs: [
{ title: 'Every you every me' },
{ title: 'The bitter end' }
]
}
},
{
singer: {
firstName: 'Brian',
songs: [
{ title: 'Every you every me' },
{ title: 'The bitter end' }
]
}
}
]
}
},
{
singer: {
firstName: 'Brian',
songs: [
{
singer: {
firstName: 'Brian',
songs: [
{ title: 'Every you every me' },
{ title: 'The bitter end' }
]
}
},
{
singer: {
firstName: 'Brian',
songs: [
{ title: 'Every you every me' },
{ title: 'The bitter end' }
]
}
}
]
}
}
]
}
]
}
}
]

for (const c of requests) {
await t.test(c.name, async (t) => {
await t.test(c.name, { skip: c.skip }, async (t) => {
const result = await graphqlRequest(service, c.query, c.variables)

assert.deepStrictEqual(result, c.result)
Expand Down
Loading
Loading