Skip to content

Commit

Permalink
Merge pull request #123 from pshenmic/fix/doc-count-by-identifier
Browse files Browse the repository at this point in the history
Fix sorting by documents count implementation
  • Loading branch information
pshenmic authored May 6, 2024
2 parents 3482771 + 2d9cc43 commit 7b0d84f
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 57 deletions.
20 changes: 16 additions & 4 deletions packages/api/src/dao/DataContractsDAO.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,24 @@ module.exports = class DataContractsDAO {
acc + ` ${value.column} ${value.order}${index === arr.length - 1 ? '' : ','}`, 'order by')
}

const sumDocuments = this.knex('documents')
.select('documents.id', 'data_contracts.identifier as dc_identifier', 'documents.data_contract_id')
.leftJoin('data_contracts', 'data_contracts.id', 'documents.data_contract_id')
.as('sum_documents')

const subquery = this.knex('data_contracts')
.select('data_contracts.id as id', 'data_contracts.identifier as identifier', 'data_contracts.owner as owner',
.select('data_contracts.id as id', 'data_contracts.identifier as identifier', 'data_contracts.identifier as my_identifier', 'data_contracts.owner as owner',
'data_contracts.is_system as is_system', 'data_contracts.version as version',
'data_contracts.state_transition_hash as tx_hash')
.select(this.knex('documents').count('*').whereRaw('documents.data_contract_id = id').as('documents_count'))
.select(this.knex(sumDocuments)
.count('*')
.whereRaw('data_contracts.identifier = sum_documents.dc_identifier')
.as('documents_count'))
.select(this.knex.raw('rank() over (partition by identifier order by version desc) rank'))

const filteredContracts = this.knex.with('filtered_data_contracts', subquery)
.select('id', 'owner', 'identifier', 'version', 'tx_hash', 'rank', 'is_system', 'documents_count',
.select(this.knex.raw('COALESCE(documents_count, 0) as documents_count'))
.select('id', 'owner', 'identifier', 'version', 'tx_hash', 'rank', 'is_system',
this.knex('filtered_data_contracts').count('*').as('total_count').where('rank', '1'))
.select(this.knex.raw(`rank() over (${getRankString()}) row_number`))
.from('filtered_data_contracts')
Expand All @@ -56,7 +65,10 @@ module.exports = class DataContractsDAO {
.select('data_contracts.identifier as identifier', 'data_contracts.owner as owner',
'data_contracts.schema as schema', 'data_contracts.is_system as is_system',
'data_contracts.version as version', 'state_transitions.hash as tx_hash', 'blocks.timestamp as timestamp')
.select(this.knex('documents').count('*').whereRaw('documents.data_contract_id = id').as('documents_count'))
.select(this.knex('documents').count('*')
.leftJoin('data_contracts', 'data_contracts.id', 'documents.data_contract_id')
.whereRaw(`data_contracts.identifier = '${identifier}'`)
.as('documents_count'))
.leftJoin('state_transitions', 'data_contracts.state_transition_hash', 'state_transitions.hash')
.leftJoin('blocks', 'blocks.hash', 'state_transitions.block_hash')
.where('data_contracts.identifier', identifier)
Expand Down
15 changes: 12 additions & 3 deletions packages/api/src/dao/IdentitiesDAO.js
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,11 @@ module.exports = class IdentitiesDAO {
const fromRank = (page - 1) * limit + 1
const toRank = fromRank + limit - 1

const sumDocuments = this.knex('documents')
.select('documents.id', 'data_contracts.identifier as dc_identifier', 'documents.data_contract_id')
.leftJoin('data_contracts', 'data_contracts.id', 'documents.data_contract_id')
.as('sum_documents')

const subquery = this.knex('data_contracts')
.select('data_contracts.id', 'data_contracts.identifier as identifier', 'data_contracts.owner as data_contract_owner',
'data_contracts.version as version', 'data_contracts.state_transition_hash as tx_hash',
Expand All @@ -149,11 +154,15 @@ module.exports = class IdentitiesDAO {
.select(this.knex.raw(`rank() over (order by id ${order}) row_number`))
.from('with_alias')
.where('rank', 1)
.as('data_contracts')
.as('data_contractz')

const rows = await this.knex(filteredDataContracts)
.select('data_contracts.id as id', 'identifier', 'data_contract_owner', 'version', 'tx_hash', 'rank', 'total_count', 'row_number', 'is_system', 'blocks.timestamp as timestamp')
.select(this.knex('documents').count('*').whereRaw('documents.data_contract_id = id').as('documents_count'))
.select('data_contractz.id as id', 'identifier', 'data_contract_owner', 'version', 'tx_hash', 'rank', 'total_count', 'row_number', 'is_system', 'blocks.timestamp as timestamp')
.select(this.knex(sumDocuments)
.count('*')
.whereRaw('sum_documents.dc_identifier = data_contractz.identifier')
.as('documents_count')
)
.leftJoin('state_transitions', 'state_transitions.hash', 'tx_hash')
.leftJoin('blocks', 'blocks.hash', 'state_transitions.block_hash')
.whereBetween('row_number', [fromRank, toRank])
Expand Down
119 changes: 72 additions & 47 deletions packages/api/test/integration/data.contracts.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ describe('DataContracts routes', () => {
dataContracts.push({ transaction: null, block: null, dataContract })
}

for (let i = 5; i < 35; i++) {
for (let i = 5; i < 29; i++) {
const block = await fixtures.block(knex, { height: i + 1 })
const transaction = await fixtures.transaction(knex, {
block_hash: block.hash,
Expand Down Expand Up @@ -69,15 +69,31 @@ describe('DataContracts routes', () => {
owner: identity.identifier,
schema: '{}'
})
dataContract.documents = []
dataContracts.push({ transaction: contractCreateTransaction, block: block2, dataContract })

// create random amount of documents
// create some documents in different data contract revisions
for (let i = 0; i < 5; i++) {
const contractCreateTransaction = await fixtures.transaction(knex, {
block_hash: block2.hash,
type: StateTransitionEnum.DATA_CONTRACT_UPDATE,
owner: identity.identifier
})
const dataContract = await fixtures.dataContract(knex, {
state_transition_hash: contractCreateTransaction.hash,
owner: identity.identifier,
identifier: dataContracts[dataContracts.length - 1].dataContract.identifier,
version: dataContracts[dataContracts.length - 1].dataContract.version + 1,
schema: '{}',
documents: dataContracts[dataContracts.length - 1].dataContract.documents
})
const document = await fixtures.document(knex, {
data_contract_id: dataContract.id, owner: identity.identifier, is_system: true
})
dataContract.documents.push(document)
documents.push({ transaction: null, block: null, dataContract, document })
dataContracts[dataContracts.length - 1].transaction = contractCreateTransaction
dataContracts[dataContracts.length - 1].dataContract = dataContract
}
})

Expand All @@ -102,7 +118,7 @@ describe('DataContracts routes', () => {
txHash: dataContract.is_system ? null : transaction.hash,
timestamp: dataContract.is_system ? null : block.timestamp.toISOString(),
isSystem: dataContract.is_system,
documentsCount: 0
documentsCount: dataContract.documents.length
}))

assert.equal(body.resultSet.length, 10)
Expand All @@ -125,11 +141,11 @@ describe('DataContracts routes', () => {
identifier: dataContract.identifier,
owner: identity.identifier,
schema: null,
version: 0,
version: dataContract.version,
txHash: dataContract.is_system ? null : transaction.hash,
timestamp: dataContract.is_system ? null : block.timestamp.toISOString(),
isSystem: dataContract.is_system,
documentsCount: 0
documentsCount: dataContract.documents.length
}))

assert.equal(body.resultSet.length, 10)
Expand All @@ -156,7 +172,7 @@ describe('DataContracts routes', () => {
txHash: dataContract.is_system ? null : transaction.hash,
timestamp: dataContract.is_system ? null : block.timestamp.toISOString(),
isSystem: dataContract.is_system,
documentsCount: 0
documentsCount: dataContract.documents.length
}))

assert.equal(body.resultSet.length, 6)
Expand All @@ -183,7 +199,7 @@ describe('DataContracts routes', () => {
txHash: dataContract.is_system ? null : transaction.hash,
timestamp: dataContract.is_system ? null : block.timestamp.toISOString(),
isSystem: dataContract.is_system,
documentsCount: 0
documentsCount: dataContract.documents.length
}))

assert.equal(body.resultSet.length, 6)
Expand All @@ -193,46 +209,46 @@ describe('DataContracts routes', () => {

assert.deepEqual(body.resultSet, expectedDataContracts)
})
})

it('should return set sort by doc count (desc)', async () => {
const { body } = await client.get('/dataContracts?order=desc&order_by=documents_count')
.expect(200)
.expect('Content-Type', 'application/json; charset=utf-8')

const expectedDataContracts = dataContracts
.sort((a, b) => (b.dataContract.documents.length - a.dataContract.documents.length ||
b.dataContract.id - a.dataContract.id))
.slice(0, 10)
.map(({ transaction, dataContract, block }) => ({
identifier: dataContract.identifier,
owner: identity.identifier,
schema: null,
version: 0,
txHash: dataContract.is_system ? null : transaction.hash,
timestamp: dataContract.is_system ? null : block.timestamp.toISOString(),
isSystem: dataContract.is_system,
documentsCount: 0
}))
it('should return set sort by doc count (desc)', async () => {
const { body } = await client.get('/dataContracts?order=desc&order_by=documents_count')
.expect(200)
.expect('Content-Type', 'application/json; charset=utf-8')

const expectedDataContracts = dataContracts
.sort((a, b) => (b.dataContract.documents.length - a.dataContract.documents.length ||
b.dataContract.id - a.dataContract.id))
.slice(0, 10)
.map(({ transaction, dataContract, block }) => ({
identifier: dataContract.identifier,
owner: identity.identifier,
schema: null,
version: dataContract.version,
txHash: dataContract.is_system ? null : transaction.hash,
timestamp: dataContract.is_system ? null : block.timestamp.toISOString(),
isSystem: dataContract.is_system,
documentsCount: dataContract.documents.length
}))

assert.equal(body.resultSet.length, 10)
assert.equal(body.pagination.total, dataContracts.length)
assert.equal(body.pagination.page, 1)
assert.equal(body.pagination.limit, 10)
assert.equal(body.resultSet.length, 10)
assert.equal(body.pagination.total, dataContracts.length)
assert.equal(body.pagination.page, 1)
assert.equal(body.pagination.limit, 10)

assert.deepEqual(body.resultSet, expectedDataContracts)
assert.deepEqual(body.resultSet, expectedDataContracts)
})
})

describe('getDataContractByIdentifier()', async () => {
it('should return system data contract by identifier', async () => {
dataContracts.sort((a, b) => a.dataContract.id - b.dataContract.id)
const [dataContract] = dataContracts.sort((a, b) => a.dataContract.id - b.dataContract.id)

const { body } = await client.get(`/dataContract/${dataContracts[0].dataContract.identifier}`)
const { body } = await client.get(`/dataContract/${dataContract.dataContract.identifier}`)
.expect(200)
.expect('Content-Type', 'application/json; charset=utf-8')

const expectedDataContract = {
identifier: dataContracts[0].dataContract.identifier,
identifier: dataContract.dataContract.identifier,
owner: identity.identifier,
schema: '{}',
version: 0,
Expand All @@ -245,18 +261,27 @@ describe('DataContracts routes', () => {
assert.deepEqual(body, expectedDataContract)
})

// it('should return system data contract by identifier', async () => {
// const {body} = await client.get('/dataContract/GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec')
// .expect(200)
// .expect('Content-Type', 'application/json; charset=utf-8');
//
// assert.equal(body.identifier, 'GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec')
// assert.equal(body.txHash, null)
// assert.equal(body.owner, '4EfA9Jrvv3nnCFdSf7fad59851iiTRZ6Wcu6YVJ4iSeF')
// assert.equal(body.version, 0)
// assert.equal(body.timestamp, null)
// assert.equal(body.isSystem, true)
// });
it('should return data contract by identifier', async () => {
const [dataContract] = dataContracts.sort((a, b) => b.dataContract.id - a.dataContract.id)

const { body } = await client.get(`/dataContract/${dataContract.dataContract.identifier}`)
.expect(200)
.expect('Content-Type', 'application/json; charset=utf-8')

const expectedDataContract = {
identifier: dataContract.dataContract.identifier,
owner: identity.identifier,
schema: '{}',
version: dataContract.dataContract.version,
txHash: dataContract.transaction.hash,
timestamp: dataContract.block.timestamp.toISOString(),
isSystem: false,
documentsCount: dataContract.dataContract.documents.length
}

assert.deepEqual(body, expectedDataContract)
})

//
// it('should return last revision of data contract by identifier', async () => {
// const {body} = await client.get('/dataContract/Gc7HqRGqmA4ZSafQ6zXeKH8Rh4AjNjjWsztotJDLpMXa')
Expand Down
2 changes: 1 addition & 1 deletion packages/api/test/integration/main.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ describe('Other routes', () => {
txHash: dataContractTransaction.hash,
timestamp: block.timestamp.toISOString(),
isSystem: false,
documentsCount: 0
documentsCount: 1
}

assert.deepEqual({ dataContract: expectedDataContract }, body)
Expand Down
4 changes: 2 additions & 2 deletions packages/api/test/utils/fixtures.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ const fixtures = {

return { ...row, txHash: state_transition_hash ?? transaction.hash, id: result[0].id, transaction }
},
dataContract: async (knex, { identifier, schema, version, state_transition_hash, owner, is_system } = {}) => {
dataContract: async (knex, { identifier, schema, version, state_transition_hash, owner, is_system, documents = [] } = {}) => {
if (!identifier) {
identifier = generateIdentifier()
}
Expand All @@ -101,7 +101,7 @@ const fixtures = {

const result = await knex('data_contracts').insert(row).returning('id')

return { ...row, id: result[0].id, documents: [] }
return { ...row, id: result[0].id, documents }
},
document: async (knex, {
identifier,
Expand Down

0 comments on commit 7b0d84f

Please sign in to comment.