Merge pull request #3341 from specklesystems/fabians/core-ioc-94

chore(server): core IoC #94 - getObjectChildrenFactory
This commit is contained in:
Alessandro Magionami 2024-10-21 10:45:45 +02:00 коммит произвёл GitHub
Родитель 17ec3b21cd 70897b0b72
Коммит 73decee881
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
6 изменённых файлов: 121 добавлений и 100 удалений

Просмотреть файл

@ -6,7 +6,7 @@ import {
SpeckleObjectClosureEntry
} from '@/modules/core/domain/objects/types'
import { BatchedSelectOptions } from '@/modules/shared/helpers/dbHelper'
import { Nullable, Optional } from '@speckle/shared'
import { MaybeNullOrUndefined, Nullable, Optional } from '@speckle/shared'
import { Knex } from 'knex'
import type stream from 'node:stream'
@ -54,6 +54,18 @@ export type GetObjectChildrenStream = (params: {
objectId: string
}) => Promise<stream.PassThrough & AsyncIterable<{ dataText: string; id: string }>>
export type GetObjectChildren = (params: {
streamId: string
objectId: string
limit?: MaybeNullOrUndefined<number | string>
depth?: MaybeNullOrUndefined<number | string>
select?: MaybeNullOrUndefined<string[]>
cursor?: MaybeNullOrUndefined<string>
}) => Promise<{
objects: Omit<SpeckleObject, 'totalChildrenCountByDepth' | 'streamId'>[]
cursor: string | null
}>
export type CreateObject = (params: {
streamId: string
object: RawSpeckleObject

Просмотреть файл

@ -1,13 +1,11 @@
import { authorizeResolver } from '@/modules/shared'
import {
getObjectChildren,
getObjectChildrenQuery
} from '@/modules/core/services/objects'
import { getObjectChildrenQuery } from '@/modules/core/services/objects'
import { isNonNullable, Roles } from '@speckle/shared'
import { Resolvers } from '@/modules/core/graph/generated/graphql'
import {
getObjectChildrenFactory,
getObjectFactory,
storeClosuresIfNotFoundFactory,
storeObjectsIfNotFoundFactory
@ -20,6 +18,7 @@ const createObjects = createObjectsFactory({
storeObjectsIfNotFoundFactory: storeObjectsIfNotFoundFactory({ db }),
storeClosuresIfNotFound: storeClosuresIfNotFoundFactory({ db })
})
const getObjectChildren = getObjectChildrenFactory({ db })
const getStreamObject: NonNullable<Resolvers['Stream']>['object'] =
async function object(parent, args) {
@ -42,14 +41,22 @@ export = {
objectId: parent.id,
limit: args.limit,
depth: args.depth,
select: args.select,
select: args.select?.filter(isNonNullable),
cursor: args.cursor
})
result.objects.forEach((x) => (x.streamId = parent.streamId))
// Hacky typing here, but I want to avoid filling up memory with a new array of new objects w/ .map()
const objects = result.objects as Array<
(typeof result)['objects'][number] & {
streamId: string
}
>
objects.forEach((x) => (x.streamId = parent.streamId))
return {
totalCount: parent.totalChildrenCount || 0,
cursor: result.cursor,
objects: result.objects
objects
}
}

Просмотреть файл

@ -62,7 +62,10 @@ export type ModelsTreeItemGraphQLReturn = Omit<ModelsTreeItem, 'model' | 'childr
projectId: string
}
export type ObjectGraphQLReturn = ObjectRecord
export type ObjectGraphQLReturn = Omit<
ObjectRecord,
'createdAt' | 'totalChildrenCountByDepth'
>
/**
* Return type for top-level mutations groupings like `projectMutations`, `activeUserMutations` etc.

Просмотреть файл

@ -10,6 +10,7 @@ import {
GetBatchedStreamObjects,
GetFormattedObject,
GetObject,
GetObjectChildren,
GetObjectChildrenStream,
GetStreamObjects,
StoreClosuresIfNotFound,
@ -19,6 +20,7 @@ import {
} from '@/modules/core/domain/objects/operations'
import { SpeckleObject } from '@/modules/core/domain/objects/types'
import { SetOptional } from 'type-fest'
import { set, toNumber } from 'lodash'
const ObjectChildrenClosure = buildTableHelper('object_children_closure', [
'parent',
@ -163,3 +165,87 @@ export const getObjectChildrenStreamFactory =
.orderBy('objects.id')
return q.stream({ highWaterMark: 500 })
}
export const getObjectChildrenFactory =
(deps: { db: Knex }): GetObjectChildren =>
async ({ streamId, objectId, limit, depth, select, cursor }) => {
limit = toNumber(limit || 0) || 50
depth = toNumber(depth || 0) || 1000
let fullObjectSelect = false
const q = deps.db.with(
'object_children_closure',
knex.raw(
`SELECT objects.id as parent, d.key as child, d.value as mindepth, ? as "streamId"
FROM objects
JOIN jsonb_each_text(objects.data->'__closure') d ON true
where objects.id = ?`,
[streamId, objectId]
)
)
if (Array.isArray(select)) {
select.forEach((field, index) => {
q.select(
knex.raw('jsonb_path_query(data, :path) as :name:', {
path: '$.' + field,
name: '' + index
})
)
})
} else {
fullObjectSelect = true
q.select('data')
}
q.select('id')
q.select('createdAt')
q.select('speckleType')
q.select('totalChildrenCount')
q.from('object_children_closure')
q.rightJoin('objects', function () {
this.on('objects.streamId', '=', 'object_children_closure.streamId').andOn(
'objects.id',
'=',
'object_children_closure.child'
)
})
.where(
knex.raw('object_children_closure."streamId" = ? AND parent = ?', [
streamId,
objectId
])
)
.andWhere(knex.raw('object_children_closure.mindepth < ?', [depth]))
.andWhere(knex.raw('id > ?', [cursor ? cursor : '0']))
.orderBy('objects.id')
.limit(limit)
const rows = await q
if (rows.length === 0) {
return { objects: rows, cursor: null }
}
if (!fullObjectSelect)
rows.forEach((o, i, arr) => {
const no = {
id: o.id,
createdAt: o.createdAt,
speckleType: o.speckleType,
totalChildrenCount: o.totalChildrenCount,
data: {}
}
let k = 0
for (const field of select || []) {
set(no.data, field, o[k++])
}
arr[i] = no
})
const lastId = rows[rows.length - 1].id
return { objects: rows, cursor: lastId }
}

Просмотреть файл

@ -5,91 +5,6 @@ const knex = require(`@/db/knex`)
const Objects = () => knex('objects')
module.exports = {
/**
* @returns {Promise<{objects: import('@/modules/core/helpers/types').ObjectRecord[], cursor: string | null}>}
*/
async getObjectChildren({ streamId, objectId, limit, depth, select, cursor }) {
limit = parseInt(limit) || 50
depth = parseInt(depth) || 1000
let fullObjectSelect = false
const q = knex.with(
'object_children_closure',
knex.raw(
`SELECT objects.id as parent, d.key as child, d.value as mindepth, ? as "streamId"
FROM objects
JOIN jsonb_each_text(objects.data->'__closure') d ON true
where objects.id = ?`,
[streamId, objectId]
)
)
if (Array.isArray(select)) {
select.forEach((field, index) => {
q.select(
knex.raw('jsonb_path_query(data, :path) as :name:', {
path: '$.' + field,
name: '' + index
})
)
})
} else {
fullObjectSelect = true
q.select('data')
}
q.select('id')
q.select('createdAt')
q.select('speckleType')
q.select('totalChildrenCount')
q.from('object_children_closure')
q.rightJoin('objects', function () {
this.on('objects.streamId', '=', 'object_children_closure.streamId').andOn(
'objects.id',
'=',
'object_children_closure.child'
)
})
.where(
knex.raw('object_children_closure."streamId" = ? AND parent = ?', [
streamId,
objectId
])
)
.andWhere(knex.raw('object_children_closure.mindepth < ?', [depth]))
.andWhere(knex.raw('id > ?', [cursor ? cursor : '0']))
.orderBy('objects.id')
.limit(limit)
const rows = await q
if (rows.length === 0) {
return { objects: rows, cursor: null }
}
if (!fullObjectSelect)
rows.forEach((o, i, arr) => {
const no = {
id: o.id,
createdAt: o.createdAt,
speckleType: o.speckleType,
totalChildrenCount: o.totalChildrenCount,
data: {}
}
let k = 0
for (const field of select) {
set(no.data, field, o[k++])
}
arr[i] = no
})
const lastId = rows[rows.length - 1].id
return { objects: rows, cursor: lastId }
},
/**
*
* This query is inefficient on larger sets (n * 10k objects) as we need to return the total count on an arbitrarily (user) defined selection of objects.

Просмотреть файл

@ -7,11 +7,7 @@ const { cloneDeep, times, random, padStart } = require('lodash')
const { beforeEachContext } = require('@/test/hooks')
const { getAnIdForThisOnePlease } = require('@/test/helpers')
const {
getObjects,
getObjectChildren,
getObjectChildrenQuery
} = require('../services/objects')
const { getObjects, getObjectChildrenQuery } = require('../services/objects')
const {
getStreamFactory,
createStreamFactory
@ -86,7 +82,8 @@ const {
storeClosuresIfNotFoundFactory,
storeObjectsIfNotFoundFactory,
getFormattedObjectFactory,
getObjectChildrenStreamFactory
getObjectChildrenStreamFactory,
getObjectChildrenFactory
} = require('@/modules/core/repositories/objects')
const sampleCommit = JSON.parse(`{
@ -190,6 +187,7 @@ const createObjects = createObjectsFactory({
})
const getObject = getFormattedObjectFactory({ db })
const getObjectChildrenStream = getObjectChildrenStreamFactory({ db })
const getObjectChildren = getObjectChildrenFactory({ db })
describe('Objects @core-objects', () => {
const userOne = {