Skip to content

Commit

Permalink
fix(tests & queries): added graphql handlers for object children, tes…
Browse files Browse the repository at this point in the history
…ts, and refactoring

Graphql:
  • Loading branch information
didimitrie committed May 13, 2020
1 parent 6376c79 commit 05afd4f
Show file tree
Hide file tree
Showing 11 changed files with 263 additions and 73 deletions.
16 changes: 12 additions & 4 deletions modules/core/graph/resolvers/objects.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const root = require( 'app-root-path' )
const { AuthorizationError, ApolloError } = require( 'apollo-server-express' )
const { validateScopes, authorizeResolver } = require( `${root}/modules/shared` )
const { getUser } = require( '../../users/services' )
const { createCommit, getCommitsByStreamId, createObject, createObjects, getObject, getObjects } = require( '../../objects/services' )
const { createCommit, getCommitsByStreamId, createObject, createObjects, getObject, getObjects, getObjectChildren, getObjectChildrenQuery } = require( '../../objects/services' )
const { createTag, updateTag, getTagById, deleteTagById, getTagsByStreamId, createBranch, updateBranch, getBranchById, getBranchCommits, deleteBranchById, getBranchesByStreamId, getStreamReferences } = require( '../../references/services' )

module.exports = {
Expand Down Expand Up @@ -41,11 +41,19 @@ module.exports = {
return await getUser( parent.author )
},
async children( parent, args, context, info ) {
console.log( parent.totalChildrenCount )
console.log( args )
// console.log( parent )
// console.log( args )

if ( !args.query && !args.orderBy ) {
// Simple query
let result = await getObjectChildren( { objectId: parent.id, limit: args.limit, depth: args.depth, select: args.select, cursor: args.cursor } )
return { totalCount: parent.totalChildrenCount, cursor: result.cursor, objects: result.objects }
}

throw new ApolloError( 'Not implemented' )
// Comlex query
let result = await getObjectChildrenQuery( { objectId: parent.id, limit: args.limit, depth: args.depth, select: args.select, query: args.query, orderBy: args.orderBy, cursor: args.cursor } )
return result
// throw new ApolloError( 'Not implemented' )
}
},
Tag: {
Expand Down
12 changes: 4 additions & 8 deletions modules/core/graph/schemas/objects.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ type Object {
speckleType: String!
applicationId: String
createdAt: DateTime
totalChildrenCount: Int
"""
The object's description. Valid only in the case of commit objects.
"""
Expand All @@ -49,7 +50,7 @@ type Object {
data: JSON

"""
Any objects that this object references.
Get any objects that this object references. In the case of commits, this will give you a commit's constituent objects.
**NOTE**: Providing any of the two last arguments ( `query`, `orderBy` ) will trigger a different code branch that executes a much more expensive SQL query. It is not recommended to do so for basic clients that are interested in purely getting all the objects of a given commit.
"""
Expand All @@ -58,13 +59,8 @@ type Object {
depth: Int! = 50,
select: [String],
cursor: String,
query: String,
orderBy: String ): ObjectCollection!

"""
Query the object's childern, so you can receive only the ones you want to.
"""
childernQuery(limit: Int! = 100, cursor:String, select: [String], depth: Int! = 1 ): ObjectCollection!
query: [JSONObject!],
orderBy: JSONObject ): ObjectCollection!
}

type ObjectCollection {
Expand Down
16 changes: 11 additions & 5 deletions modules/core/migrations/000-core.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,28 +61,34 @@ exports.up = async knex => {
table.specificType( 'role', 'speckle_acl_role_type' ).defaultTo( 'write' )
} )

// Objects Table
// Objects Table.
// First class citizen properties are:
// id - the object's hash
// totalChildrenCount - how many subchildren, regardless of depth, this object has
// data - the jsonb object
// author - commit specific field
// description - commit specific field
// createdAt - date of insertion
await knex.schema.createTable( 'objects', table => {
table.string( 'id' ).primary( )
table.string( 'speckle_type' ).defaultTo( 'Base' ).notNullable( )
table.string( 'applicationId' )
table.string( 'speckleType' ).defaultTo( 'Base' ).notNullable( )
table.integer( 'totalChildrenCount' )
table.jsonb( 'totalChildrenCountByDepth' )
table.jsonb( 'data' )
table.string( 'author', 10 ).references( 'id' ).inTable( 'users' )
table.string( 'description' )
table.timestamp( 'createdAt' ).defaultTo( knex.fn.now( ) )
table.index( [ 'speckle_type' ], 'type_index' )
} )

// Tree inheritance tracker
// Tree inheritance tracker (materialised path)
await knex.schema.createTable( 'object_tree_refs', table => {
table.increments( 'id' )
table.string( 'parent' ).index( null, 'HASH' )
table.specificType( 'path', 'ltree' )
} )
await knex.raw( `CREATE INDEX tree_path_idx ON object_tree_refs USING gist(path)` )

// Closure table for tracking the relationships we care about
await knex.schema.createTable( 'object_children_closure', table => {
table.string( 'parent' ).notNullable( ).index()
table.string( 'child' ).notNullable( ).index()
Expand Down
68 changes: 42 additions & 26 deletions modules/core/objects/services.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ module.exports = {
*/

async createCommit( streamId, userId, object ) {
object.speckle_type = 'commit'
object.speckleType = 'commit'
object.author = userId

let id = await module.exports.createObject( object )
Expand All @@ -48,15 +48,24 @@ module.exports = {
let insertionObject = prepInsertionObject( object )

let closures = [ ]
let totalChildrenCountByDepth = {}
if ( object.__closure !== null ) {
for ( const prop in object.__closure ) {
closures.push( { parent: insertionObject.id, child: prop, minDepth: object.__closure[ prop ] } )

if ( totalChildrenCountByDepth[ object.__closure[ prop ].toString( ) ] )
totalChildrenCountByDepth[ object.__closure[ prop ].toString( ) ]++
else
totalChildrenCountByDepth[ object.__closure[ prop ].toString( ) ] = 1
}
}

delete insertionObject.__tree
delete insertionObject.__closure

insertionObject.totalChildrenCount = closures.length
insertionObject.totalChildrenCountByDepth = JSON.stringify( totalChildrenCountByDepth )

let q1 = Objects( ).insert( insertionObject ).toString( ) + ' on conflict do nothing'
await knex.raw( q1 )

Expand Down Expand Up @@ -148,19 +157,22 @@ module.exports = {
let fullObjectSelect = false
let selectStatements = [ ]

if ( select && select.length > 0 ) {
selectStatements.push( `jsonb_path_query(data, '$.id') as id` )
select.forEach( f => {
selectStatements += `, jsonb_path_query(data, '$.${ f }') as "${f}"`
let q = Closures( )
q.select( 'id' )
q.select( 'createdAt' )
q.select( 'speckleType' )
q.select( 'totalChildrenCount' )

if ( Array.isArray( select ) ) {
select.forEach( ( field, index ) => {
q.select( knex.raw( 'jsonb_path_query(data, :path) as :name:', { path: "$." + field, name: '' + index } ) )
} )
} else {
selectStatements.push( '"data"' )
fullObjectSelect = true
q.select( 'data' )
}

let q = Closures( )
.select( knex.raw( selectStatements ) )
.rightJoin( 'objects', 'objects.id', 'object_children_closure.child' )
q.rightJoin( 'objects', 'objects.id', 'object_children_closure.child' )
.where( knex.raw( 'parent = ?', [ objectId ] ) )
.andWhere( knex.raw( '"minDepth" < ?', [ depth ] ) )
.andWhere( knex.raw( 'id > ?', [ cursor ? cursor : '0' ] ) )
Expand All @@ -169,15 +181,18 @@ module.exports = {

let rows = await q

if ( fullObjectSelect ) rows.forEach( ( o, i, arr ) => arr[ i ] = { ...o.data } )
else rows.forEach( ( o, i, arr ) => {
let no = {}
for ( let key in o ) set( no, key, o[ key ] )
arr[ i ] = no
} )
if ( !fullObjectSelect )
rows.forEach( ( o, i, arr ) => {
let no = { id: o.id, createdAt: o.createdAt, speckleType: o.speckleType, totalChildrenCount: o.totalChildrenCount, data: {} }
let k = 0
for ( let field of select ) {
set( no.data, field, o[ k++ ] )
}
arr[ i ] = no
} )

let lastId = rows[ rows.length - 1 ].id
return { rows, cursor: lastId }
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.
Expand All @@ -199,19 +214,22 @@ module.exports = {
if ( orderBy && select.indexOf( orderBy.field ) === -1 ) {
select.push( orderBy.field )
}
// always add the id!
if ( select.indexOf( 'id' ) === -1 ) select.unshift( 'id' )
// // always add the id!
// if ( select.indexOf( 'id' ) === -1 ) select.unshift( 'id' )
} else {
fullObjectSelect = true
}

let additionalIdOrderBy = orderBy.field !== 'id'

let operatorsWhitelist = [ '=', '>', '>=', '<', '<=', '!=' ]

let mainQuery = knex.with( 'objs', cteInnerQuery => {
// always select the id
cteInnerQuery.select( 'id' ).from( 'object_children_closure' )
cteInnerQuery.select( 'createdAt' )
cteInnerQuery.select( 'speckleType' )
cteInnerQuery.select( 'totalChildrenCount' )

// if there are any select fields, add them
if ( Array.isArray( select ) ) {
Expand Down Expand Up @@ -303,9 +321,7 @@ module.exports = {

mainQuery.limit( limit )

// console.log( mainQuery.toString( ) )
// console.log( '-----' )

// Finally, execute the query
let rows = await mainQuery
let totalCount = rows && rows.length > 0 ? parseInt( rows[ 0 ].total_count ) : 0

Expand All @@ -318,15 +334,16 @@ module.exports = {
// OR reconstruct the object based on the provided select paths.
else {
rows.forEach( ( o, i, arr ) => {
let no = {}
let no = { id: o.id, createdAt: o.createdAt, speckleType: o.speckleType, totalChildrenCount: o.totalChildrenCount, data: {} }
let k = 0
for ( let field of select ) {
set( no, field, o[ k++ ] )
set( no.data, field, o[ k++ ] )
}
arr[ i ] = no
} )
}

// Assemble the cursor for an eventual next call
cursor = cursor || {}
let cursorObj = {
field: cursor.field || orderBy.field,
Expand Down Expand Up @@ -370,8 +387,7 @@ function prepInsertionObject( obj ) {
return {
data: stringifiedObj, // stored in jsonb column
id: obj.id,
applicationId: obj.applicationId,
speckle_type: obj.speckle_type,
speckleType: obj.speckleType,
description: obj.description,
author: obj.author
}
Expand Down
Loading

0 comments on commit 05afd4f

Please sign in to comment.