|
@@ -1,3 +1,5 @@
|
|
|
|
|
+// @ts-check
|
|
|
|
|
+
|
|
|
const { Chunk } = require('overleaf-editor-core')
|
|
const { Chunk } = require('overleaf-editor-core')
|
|
|
const assert = require('../assert')
|
|
const assert = require('../assert')
|
|
|
const knex = require('../knex')
|
|
const knex = require('../knex')
|
|
@@ -7,6 +9,10 @@ const { updateProjectRecord } = require('./mongo')
|
|
|
|
|
|
|
|
const DUPLICATE_KEY_ERROR_CODE = '23505'
|
|
const DUPLICATE_KEY_ERROR_CODE = '23505'
|
|
|
|
|
|
|
|
|
|
+/**
|
|
|
|
|
+ * @import { Knex } from 'knex'
|
|
|
|
|
+ */
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
* Get the latest chunk's metadata from the database
|
|
* Get the latest chunk's metadata from the database
|
|
|
* @param {string} projectId
|
|
* @param {string} projectId
|
|
@@ -15,11 +21,10 @@ const DUPLICATE_KEY_ERROR_CODE = '23505'
|
|
|
*/
|
|
*/
|
|
|
async function getLatestChunk(projectId, opts = {}) {
|
|
async function getLatestChunk(projectId, opts = {}) {
|
|
|
assert.postgresId(projectId, 'bad projectId')
|
|
assert.postgresId(projectId, 'bad projectId')
|
|
|
- projectId = parseInt(projectId, 10)
|
|
|
|
|
const { readOnly = false } = opts
|
|
const { readOnly = false } = opts
|
|
|
|
|
|
|
|
const record = await (readOnly ? knexReadOnly : knex)('chunks')
|
|
const record = await (readOnly ? knexReadOnly : knex)('chunks')
|
|
|
- .where('doc_id', projectId)
|
|
|
|
|
|
|
+ .where('doc_id', parseInt(projectId, 10))
|
|
|
.orderBy('end_version', 'desc')
|
|
.orderBy('end_version', 'desc')
|
|
|
.first()
|
|
.first()
|
|
|
if (record == null) {
|
|
if (record == null) {
|
|
@@ -30,13 +35,15 @@ async function getLatestChunk(projectId, opts = {}) {
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* Get the metadata for the chunk that contains the given version.
|
|
* Get the metadata for the chunk that contains the given version.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param {string} projectId
|
|
|
|
|
+ * @param {number} version
|
|
|
*/
|
|
*/
|
|
|
async function getChunkForVersion(projectId, version) {
|
|
async function getChunkForVersion(projectId, version) {
|
|
|
assert.postgresId(projectId, 'bad projectId')
|
|
assert.postgresId(projectId, 'bad projectId')
|
|
|
- projectId = parseInt(projectId, 10)
|
|
|
|
|
|
|
|
|
|
const record = await knex('chunks')
|
|
const record = await knex('chunks')
|
|
|
- .where('doc_id', projectId)
|
|
|
|
|
|
|
+ .where('doc_id', parseInt(projectId, 10))
|
|
|
.where('end_version', '>=', version)
|
|
.where('end_version', '>=', version)
|
|
|
.orderBy('end_version')
|
|
.orderBy('end_version')
|
|
|
.first()
|
|
.first()
|
|
@@ -48,20 +55,23 @@ async function getChunkForVersion(projectId, version) {
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* Get the metadata for the chunk that contains the given version.
|
|
* Get the metadata for the chunk that contains the given version.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param {string} projectId
|
|
|
|
|
+ * @param {Date} timestamp
|
|
|
*/
|
|
*/
|
|
|
async function getFirstChunkBeforeTimestamp(projectId, timestamp) {
|
|
async function getFirstChunkBeforeTimestamp(projectId, timestamp) {
|
|
|
assert.date(timestamp, 'bad timestamp')
|
|
assert.date(timestamp, 'bad timestamp')
|
|
|
|
|
|
|
|
const recordActive = await getChunkForVersion(projectId, 0)
|
|
const recordActive = await getChunkForVersion(projectId, 0)
|
|
|
|
|
+
|
|
|
// projectId must be valid if getChunkForVersion did not throw
|
|
// projectId must be valid if getChunkForVersion did not throw
|
|
|
- projectId = parseInt(projectId, 10)
|
|
|
|
|
if (recordActive && recordActive.endTimestamp <= timestamp) {
|
|
if (recordActive && recordActive.endTimestamp <= timestamp) {
|
|
|
return recordActive
|
|
return recordActive
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// fallback to deleted chunk
|
|
// fallback to deleted chunk
|
|
|
const recordDeleted = await knex('old_chunks')
|
|
const recordDeleted = await knex('old_chunks')
|
|
|
- .where('doc_id', projectId)
|
|
|
|
|
|
|
+ .where('doc_id', parseInt(projectId, 10))
|
|
|
.where('start_version', '=', 0)
|
|
.where('start_version', '=', 0)
|
|
|
.where('end_timestamp', '<=', timestamp)
|
|
.where('end_timestamp', '<=', timestamp)
|
|
|
.orderBy('end_version', 'desc')
|
|
.orderBy('end_version', 'desc')
|
|
@@ -75,14 +85,16 @@ async function getFirstChunkBeforeTimestamp(projectId, timestamp) {
|
|
|
/**
|
|
/**
|
|
|
* Get the metadata for the chunk that contains the version that was current at
|
|
* Get the metadata for the chunk that contains the version that was current at
|
|
|
* the given timestamp.
|
|
* the given timestamp.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param {string} projectId
|
|
|
|
|
+ * @param {Date} timestamp
|
|
|
*/
|
|
*/
|
|
|
async function getLastActiveChunkBeforeTimestamp(projectId, timestamp) {
|
|
async function getLastActiveChunkBeforeTimestamp(projectId, timestamp) {
|
|
|
assert.date(timestamp, 'bad timestamp')
|
|
assert.date(timestamp, 'bad timestamp')
|
|
|
assert.postgresId(projectId, 'bad projectId')
|
|
assert.postgresId(projectId, 'bad projectId')
|
|
|
- projectId = parseInt(projectId, 10)
|
|
|
|
|
|
|
|
|
|
const query = knex('chunks')
|
|
const query = knex('chunks')
|
|
|
- .where('doc_id', projectId)
|
|
|
|
|
|
|
+ .where('doc_id', parseInt(projectId, 10))
|
|
|
.where(function () {
|
|
.where(function () {
|
|
|
this.where('end_timestamp', '<=', timestamp).orWhere(
|
|
this.where('end_timestamp', '<=', timestamp).orWhere(
|
|
|
'end_timestamp',
|
|
'end_timestamp',
|
|
@@ -102,10 +114,12 @@ async function getLastActiveChunkBeforeTimestamp(projectId, timestamp) {
|
|
|
/**
|
|
/**
|
|
|
* Get the metadata for the chunk that contains the version that was current at
|
|
* Get the metadata for the chunk that contains the version that was current at
|
|
|
* the given timestamp.
|
|
* the given timestamp.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param {string} projectId
|
|
|
|
|
+ * @param {Date} timestamp
|
|
|
*/
|
|
*/
|
|
|
async function getChunkForTimestamp(projectId, timestamp) {
|
|
async function getChunkForTimestamp(projectId, timestamp) {
|
|
|
assert.postgresId(projectId, 'bad projectId')
|
|
assert.postgresId(projectId, 'bad projectId')
|
|
|
- projectId = parseInt(projectId, 10)
|
|
|
|
|
|
|
|
|
|
// This query will find the latest chunk after the timestamp (query orders
|
|
// This query will find the latest chunk after the timestamp (query orders
|
|
|
// in reverse chronological order), OR the latest chunk
|
|
// in reverse chronological order), OR the latest chunk
|
|
@@ -118,11 +132,11 @@ async function getChunkForTimestamp(projectId, timestamp) {
|
|
|
'WHERE doc_id = ? ' +
|
|
'WHERE doc_id = ? ' +
|
|
|
'ORDER BY end_version desc LIMIT 1' +
|
|
'ORDER BY end_version desc LIMIT 1' +
|
|
|
')',
|
|
')',
|
|
|
- [timestamp, projectId]
|
|
|
|
|
|
|
+ [timestamp, parseInt(projectId, 10)]
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
const record = await knex('chunks')
|
|
const record = await knex('chunks')
|
|
|
- .where('doc_id', projectId)
|
|
|
|
|
|
|
+ .where('doc_id', parseInt(projectId, 10))
|
|
|
.where(whereAfterEndTimestampOrLatestChunk)
|
|
.where(whereAfterEndTimestampOrLatestChunk)
|
|
|
.orderBy('end_version')
|
|
.orderBy('end_version')
|
|
|
.first()
|
|
.first()
|
|
@@ -137,7 +151,7 @@ async function getChunkForTimestamp(projectId, timestamp) {
|
|
|
*/
|
|
*/
|
|
|
function chunkFromRecord(record) {
|
|
function chunkFromRecord(record) {
|
|
|
return {
|
|
return {
|
|
|
- id: record.id,
|
|
|
|
|
|
|
+ id: record.id.toString(),
|
|
|
startVersion: record.start_version,
|
|
startVersion: record.start_version,
|
|
|
endVersion: record.end_version,
|
|
endVersion: record.end_version,
|
|
|
endTimestamp: record.end_timestamp,
|
|
endTimestamp: record.end_timestamp,
|
|
@@ -146,35 +160,41 @@ function chunkFromRecord(record) {
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* Get all of a project's chunk ids
|
|
* Get all of a project's chunk ids
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param {string} projectId
|
|
|
*/
|
|
*/
|
|
|
async function getProjectChunkIds(projectId) {
|
|
async function getProjectChunkIds(projectId) {
|
|
|
assert.postgresId(projectId, 'bad projectId')
|
|
assert.postgresId(projectId, 'bad projectId')
|
|
|
- projectId = parseInt(projectId, 10)
|
|
|
|
|
|
|
|
|
|
- const records = await knex('chunks').select('id').where('doc_id', projectId)
|
|
|
|
|
|
|
+ const records = await knex('chunks')
|
|
|
|
|
+ .select('id')
|
|
|
|
|
+ .where('doc_id', parseInt(projectId, 10))
|
|
|
return records.map(record => record.id)
|
|
return records.map(record => record.id)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* Get all of a projects chunks directly
|
|
* Get all of a projects chunks directly
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param {string} projectId
|
|
|
*/
|
|
*/
|
|
|
async function getProjectChunks(projectId) {
|
|
async function getProjectChunks(projectId) {
|
|
|
assert.postgresId(projectId, 'bad projectId')
|
|
assert.postgresId(projectId, 'bad projectId')
|
|
|
- projectId = parseInt(projectId, 10)
|
|
|
|
|
|
|
|
|
|
const records = await knex('chunks')
|
|
const records = await knex('chunks')
|
|
|
.select()
|
|
.select()
|
|
|
- .where('doc_id', projectId)
|
|
|
|
|
|
|
+ .where('doc_id', parseInt(projectId, 10))
|
|
|
.orderBy('end_version')
|
|
.orderBy('end_version')
|
|
|
return records.map(chunkFromRecord)
|
|
return records.map(chunkFromRecord)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* Insert a pending chunk before sending it to object storage.
|
|
* Insert a pending chunk before sending it to object storage.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param {string} projectId
|
|
|
|
|
+ * @param {Chunk} chunk
|
|
|
*/
|
|
*/
|
|
|
async function insertPendingChunk(projectId, chunk) {
|
|
async function insertPendingChunk(projectId, chunk) {
|
|
|
assert.postgresId(projectId, 'bad projectId')
|
|
assert.postgresId(projectId, 'bad projectId')
|
|
|
- projectId = parseInt(projectId, 10)
|
|
|
|
|
|
|
|
|
|
const result = await knex.first(
|
|
const result = await knex.first(
|
|
|
knex.raw("nextval('chunks_id_seq'::regclass)::integer as chunkid")
|
|
knex.raw("nextval('chunks_id_seq'::regclass)::integer as chunkid")
|
|
@@ -182,32 +202,52 @@ async function insertPendingChunk(projectId, chunk) {
|
|
|
const chunkId = result.chunkid
|
|
const chunkId = result.chunkid
|
|
|
await knex('pending_chunks').insert({
|
|
await knex('pending_chunks').insert({
|
|
|
id: chunkId,
|
|
id: chunkId,
|
|
|
- doc_id: projectId,
|
|
|
|
|
|
|
+ doc_id: parseInt(projectId, 10),
|
|
|
end_version: chunk.getEndVersion(),
|
|
end_version: chunk.getEndVersion(),
|
|
|
start_version: chunk.getStartVersion(),
|
|
start_version: chunk.getStartVersion(),
|
|
|
end_timestamp: chunk.getEndTimestamp(),
|
|
end_timestamp: chunk.getEndTimestamp(),
|
|
|
})
|
|
})
|
|
|
- return chunkId
|
|
|
|
|
|
|
+ return chunkId.toString()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* Record that a new chunk was created.
|
|
* Record that a new chunk was created.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param {string} projectId
|
|
|
|
|
+ * @param {Chunk} chunk
|
|
|
|
|
+ * @param {string} chunkId
|
|
|
|
|
+ * @param {object} opts
|
|
|
|
|
+ * @param {Date} [opts.earliestChangeTimestamp]
|
|
|
|
|
+ * @param {string} [opts.oldChunkId]
|
|
|
*/
|
|
*/
|
|
|
async function confirmCreate(projectId, chunk, chunkId, opts = {}) {
|
|
async function confirmCreate(projectId, chunk, chunkId, opts = {}) {
|
|
|
assert.postgresId(projectId, 'bad projectId')
|
|
assert.postgresId(projectId, 'bad projectId')
|
|
|
- projectId = parseInt(projectId, 10)
|
|
|
|
|
|
|
|
|
|
await knex.transaction(async tx => {
|
|
await knex.transaction(async tx => {
|
|
|
|
|
+ if (opts.oldChunkId != null) {
|
|
|
|
|
+ await _assertChunkIsNotClosed(tx, projectId, opts.oldChunkId)
|
|
|
|
|
+ await _closeChunk(tx, projectId, opts.oldChunkId)
|
|
|
|
|
+ }
|
|
|
await Promise.all([
|
|
await Promise.all([
|
|
|
_deletePendingChunk(tx, projectId, chunkId),
|
|
_deletePendingChunk(tx, projectId, chunkId),
|
|
|
_insertChunk(tx, projectId, chunk, chunkId),
|
|
_insertChunk(tx, projectId, chunk, chunkId),
|
|
|
])
|
|
])
|
|
|
- await updateProjectRecord(projectId, chunk, opts.earliestChangeTimestamp)
|
|
|
|
|
|
|
+ await updateProjectRecord(
|
|
|
|
|
+ // The history id in Mongo is an integer for Postgres projects
|
|
|
|
|
+ parseInt(projectId, 10),
|
|
|
|
|
+ chunk,
|
|
|
|
|
+ opts.earliestChangeTimestamp
|
|
|
|
|
+ )
|
|
|
})
|
|
})
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* Record that a chunk was replaced by a new one.
|
|
* Record that a chunk was replaced by a new one.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param {string} projectId
|
|
|
|
|
+ * @param {string} oldChunkId
|
|
|
|
|
+ * @param {Chunk} newChunk
|
|
|
|
|
+ * @param {string} newChunkId
|
|
|
*/
|
|
*/
|
|
|
async function confirmUpdate(
|
|
async function confirmUpdate(
|
|
|
projectId,
|
|
projectId,
|
|
@@ -217,40 +257,64 @@ async function confirmUpdate(
|
|
|
opts = {}
|
|
opts = {}
|
|
|
) {
|
|
) {
|
|
|
assert.postgresId(projectId, 'bad projectId')
|
|
assert.postgresId(projectId, 'bad projectId')
|
|
|
- projectId = parseInt(projectId, 10)
|
|
|
|
|
|
|
|
|
|
await knex.transaction(async tx => {
|
|
await knex.transaction(async tx => {
|
|
|
|
|
+ await _assertChunkIsNotClosed(tx, projectId, oldChunkId)
|
|
|
await _deleteChunks(tx, { doc_id: projectId, id: oldChunkId })
|
|
await _deleteChunks(tx, { doc_id: projectId, id: oldChunkId })
|
|
|
await Promise.all([
|
|
await Promise.all([
|
|
|
_deletePendingChunk(tx, projectId, newChunkId),
|
|
_deletePendingChunk(tx, projectId, newChunkId),
|
|
|
_insertChunk(tx, projectId, newChunk, newChunkId),
|
|
_insertChunk(tx, projectId, newChunk, newChunkId),
|
|
|
])
|
|
])
|
|
|
- await updateProjectRecord(projectId, newChunk, opts.earliestChangeTimestamp)
|
|
|
|
|
|
|
+ await updateProjectRecord(
|
|
|
|
|
+ // The history id in Mongo is an integer for Postgres projects
|
|
|
|
|
+ parseInt(projectId, 10),
|
|
|
|
|
+ newChunk,
|
|
|
|
|
+ opts.earliestChangeTimestamp
|
|
|
|
|
+ )
|
|
|
})
|
|
})
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/**
|
|
|
|
|
+ * Delete a pending chunk
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param {Knex} tx
|
|
|
|
|
+ * @param {string} projectId
|
|
|
|
|
+ * @param {string} chunkId
|
|
|
|
|
+ */
|
|
|
async function _deletePendingChunk(tx, projectId, chunkId) {
|
|
async function _deletePendingChunk(tx, projectId, chunkId) {
|
|
|
await tx('pending_chunks')
|
|
await tx('pending_chunks')
|
|
|
.where({
|
|
.where({
|
|
|
- doc_id: projectId,
|
|
|
|
|
- id: chunkId,
|
|
|
|
|
|
|
+ doc_id: parseInt(projectId, 10),
|
|
|
|
|
+ id: parseInt(chunkId, 10),
|
|
|
})
|
|
})
|
|
|
.del()
|
|
.del()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/**
|
|
|
|
|
+ * Adds an active chunk
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param {Knex} tx
|
|
|
|
|
+ * @param {string} projectId
|
|
|
|
|
+ * @param {Chunk} chunk
|
|
|
|
|
+ * @param {string} chunkId
|
|
|
|
|
+ */
|
|
|
async function _insertChunk(tx, projectId, chunk, chunkId) {
|
|
async function _insertChunk(tx, projectId, chunk, chunkId) {
|
|
|
const startVersion = chunk.getStartVersion()
|
|
const startVersion = chunk.getStartVersion()
|
|
|
const endVersion = chunk.getEndVersion()
|
|
const endVersion = chunk.getEndVersion()
|
|
|
try {
|
|
try {
|
|
|
await tx('chunks').insert({
|
|
await tx('chunks').insert({
|
|
|
- id: chunkId,
|
|
|
|
|
- doc_id: projectId,
|
|
|
|
|
|
|
+ id: parseInt(chunkId, 10),
|
|
|
|
|
+ doc_id: parseInt(projectId, 10),
|
|
|
start_version: startVersion,
|
|
start_version: startVersion,
|
|
|
end_version: endVersion,
|
|
end_version: endVersion,
|
|
|
end_timestamp: chunk.getEndTimestamp(),
|
|
end_timestamp: chunk.getEndTimestamp(),
|
|
|
})
|
|
})
|
|
|
} catch (err) {
|
|
} catch (err) {
|
|
|
- if (err.code === DUPLICATE_KEY_ERROR_CODE) {
|
|
|
|
|
|
|
+ if (
|
|
|
|
|
+ err instanceof Error &&
|
|
|
|
|
+ 'code' in err &&
|
|
|
|
|
+ err.code === DUPLICATE_KEY_ERROR_CODE
|
|
|
|
|
+ ) {
|
|
|
throw new ChunkVersionConflictError(
|
|
throw new ChunkVersionConflictError(
|
|
|
'chunk start or end version is not unique',
|
|
'chunk start or end version is not unique',
|
|
|
{ projectId, chunkId, startVersion, endVersion }
|
|
{ projectId, chunkId, startVersion, endVersion }
|
|
@@ -260,31 +324,92 @@ async function _insertChunk(tx, projectId, chunk, chunkId) {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/**
|
|
|
|
|
+ * Check that a chunk is not closed
|
|
|
|
|
+ *
|
|
|
|
|
+ * This is used to synchronize chunk creations and extensions.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param {Knex} tx
|
|
|
|
|
+ * @param {string} projectId
|
|
|
|
|
+ * @param {string} chunkId
|
|
|
|
|
+ */
|
|
|
|
|
+async function _assertChunkIsNotClosed(tx, projectId, chunkId) {
|
|
|
|
|
+ const record = await tx('chunks')
|
|
|
|
|
+ .forUpdate()
|
|
|
|
|
+ .select('closed')
|
|
|
|
|
+ .where('doc_id', parseInt(projectId, 10))
|
|
|
|
|
+ .where('id', parseInt(chunkId, 10))
|
|
|
|
|
+ .first()
|
|
|
|
|
+ if (!record) {
|
|
|
|
|
+ throw new ChunkVersionConflictError('unable to close chunk: not found', {
|
|
|
|
|
+ projectId,
|
|
|
|
|
+ chunkId,
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ if (record.closed) {
|
|
|
|
|
+ throw new ChunkVersionConflictError(
|
|
|
|
|
+ 'unable to close chunk: already closed',
|
|
|
|
|
+ {
|
|
|
|
|
+ projectId,
|
|
|
|
|
+ chunkId,
|
|
|
|
|
+ }
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Close a chunk
|
|
|
|
|
+ *
|
|
|
|
|
+ * A closed chunk can no longer be extended.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param {Knex} tx
|
|
|
|
|
+ * @param {string} projectId
|
|
|
|
|
+ * @param {string} chunkId
|
|
|
|
|
+ */
|
|
|
|
|
+async function _closeChunk(tx, projectId, chunkId) {
|
|
|
|
|
+ await tx('chunks')
|
|
|
|
|
+ .update({ closed: true })
|
|
|
|
|
+ .where('doc_id', parseInt(projectId, 10))
|
|
|
|
|
+ .where('id', parseInt(chunkId, 10))
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
* Delete a chunk.
|
|
* Delete a chunk.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param {string} projectId
|
|
|
|
|
+ * @param {string} chunkId
|
|
|
*/
|
|
*/
|
|
|
async function deleteChunk(projectId, chunkId) {
|
|
async function deleteChunk(projectId, chunkId) {
|
|
|
assert.postgresId(projectId, 'bad projectId')
|
|
assert.postgresId(projectId, 'bad projectId')
|
|
|
- projectId = parseInt(projectId, 10)
|
|
|
|
|
assert.integer(chunkId, 'bad chunkId')
|
|
assert.integer(chunkId, 'bad chunkId')
|
|
|
|
|
|
|
|
- await _deleteChunks(knex, { doc_id: projectId, id: chunkId })
|
|
|
|
|
|
|
+ await _deleteChunks(knex, {
|
|
|
|
|
+ doc_id: parseInt(projectId, 10),
|
|
|
|
|
+ id: parseInt(chunkId, 10),
|
|
|
|
|
+ })
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* Delete all of a project's chunks
|
|
* Delete all of a project's chunks
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param {string} projectId
|
|
|
*/
|
|
*/
|
|
|
async function deleteProjectChunks(projectId) {
|
|
async function deleteProjectChunks(projectId) {
|
|
|
assert.postgresId(projectId, 'bad projectId')
|
|
assert.postgresId(projectId, 'bad projectId')
|
|
|
- projectId = parseInt(projectId, 10)
|
|
|
|
|
|
|
|
|
|
await knex.transaction(async tx => {
|
|
await knex.transaction(async tx => {
|
|
|
- await _deleteChunks(knex, { doc_id: projectId })
|
|
|
|
|
|
|
+ await _deleteChunks(knex, { doc_id: parseInt(projectId, 10) })
|
|
|
})
|
|
})
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/**
|
|
|
|
|
+ * Delete many chunks
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param {Knex} tx
|
|
|
|
|
+ * @param {any} whereClause
|
|
|
|
|
+ */
|
|
|
async function _deleteChunks(tx, whereClause) {
|
|
async function _deleteChunks(tx, whereClause) {
|
|
|
- const rows = await tx('chunks').returning('*').where(whereClause).del()
|
|
|
|
|
|
|
+ const rows = await tx('chunks').where(whereClause).del().returning('*')
|
|
|
if (rows.length === 0) {
|
|
if (rows.length === 0) {
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
@@ -302,6 +427,9 @@ async function _deleteChunks(tx, whereClause) {
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* Get a batch of old chunks for deletion
|
|
* Get a batch of old chunks for deletion
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param {number} count
|
|
|
|
|
+ * @param {number} minAgeSecs
|
|
|
*/
|
|
*/
|
|
|
async function getOldChunksBatch(count, minAgeSecs) {
|
|
async function getOldChunksBatch(count, minAgeSecs) {
|
|
|
const maxDeletedAt = new Date(Date.now() - minAgeSecs * 1000)
|
|
const maxDeletedAt = new Date(Date.now() - minAgeSecs * 1000)
|
|
@@ -312,15 +440,22 @@ async function getOldChunksBatch(count, minAgeSecs) {
|
|
|
.limit(count)
|
|
.limit(count)
|
|
|
return records.map(oldChunk => ({
|
|
return records.map(oldChunk => ({
|
|
|
projectId: oldChunk.doc_id.toString(),
|
|
projectId: oldChunk.doc_id.toString(),
|
|
|
- chunkId: oldChunk.chunk_id,
|
|
|
|
|
|
|
+ chunkId: oldChunk.chunk_id.toString(),
|
|
|
}))
|
|
}))
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* Delete a batch of old chunks from the database
|
|
* Delete a batch of old chunks from the database
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param {string[]} chunkIds
|
|
|
*/
|
|
*/
|
|
|
async function deleteOldChunks(chunkIds) {
|
|
async function deleteOldChunks(chunkIds) {
|
|
|
- await knex('old_chunks').whereIn('chunk_id', chunkIds).del()
|
|
|
|
|
|
|
+ await knex('old_chunks')
|
|
|
|
|
+ .whereIn(
|
|
|
|
|
+ 'chunk_id',
|
|
|
|
|
+ chunkIds.map(id => parseInt(id, 10))
|
|
|
|
|
+ )
|
|
|
|
|
+ .del()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|