Skip to content

Instantly share code, notes, and snippets.

@hwei
Created April 14, 2020 02:41
Show Gist options
  • Save hwei/435650d044baa2fa5a4f46657d4e1f43 to your computer and use it in GitHub Desktop.
Save hwei/435650d044baa2fa5a4f46657d4e1f43 to your computer and use it in GitHub Desktop.
Cosmos DB stored procedures for general transactions
function spRunOpList(opList) {
const ERROR_CODE = {
BAD_REQUEST: 400,
NOT_FOUND: 404,
CONFLICT: 409,
NOT_ACCEPTED: 499
}
const collection = getContext().getCollection()
const collectionLink = collection.getSelfLink()
const response = getContext().getResponse()
function queryApplyVar(query, varMap) {
for(const p of query.parameters) {
if (p.varName) {
p.value = varMap[p.varName]
}
}
return query
}
const opMap = {
upsert({ query, updateList, defaultDoc }, tailOpList, resultList, varMap) {
const isAccepted = collection.queryDocuments(collectionLink, queryApplyVar(query, varMap), {}, function(err, docs, options) {
if (err) throw err
if(docs.length == 0) {
const createResultList = []
createDocs([defaultDoc], createResultList, function() {
resultList.push(createResultList)
runOpList(tailOpList, resultList, varMap)
})
} else {
const updateResultList = []
updateDocs(docs, updateList, updateResultList, varMap, function() {
resultList.push(updateResultList)
runOpList(tailOpList, resultList, varMap)
})
}
})
checkAccepted(isAccepted)
},
create({ docs }, tailOpList, resultList, varMap) {
const createResultList = []
createDocs(docs, createResultList, function() {
resultList.push(createResultList)
runOpList(tailOpList, resultList, varMap)
})
},
update({ query, updateList, mustExist }, tailOpList, resultList, varMap) {
const isAccepted = collection.queryDocuments(collectionLink, queryApplyVar(query, varMap), {}, function(err, docs, options) {
if (err) throw err
if(mustExist && docs.length == 0)
throw new Error(ERROR_CODE.CONFLICT, `update set mustExist, but no docs found in op${resultList.length}`)
const updateResultList = []
updateDocs(docs, updateList, updateResultList, varMap, function() {
resultList.push(updateResultList)
runOpList(tailOpList, resultList, varMap)
})
})
checkAccepted(isAccepted)
},
query({ query, stopIf }, tailOpList, resultList, varMap) {
const isAccepted = collection.queryDocuments(collectionLink, queryApplyVar(query, varMap), {}, function(err, docs, options) {
if (err) throw err
resultList.push(docs)
if (stopIf == 'any') {
if (docs.length > 0) {
response.setBody(resultList)
return
}
} else if (stopIf == 'none') {
if (docs.length == 0) {
response.setBody(resultList)
return
}
}
runOpList(tailOpList, resultList, varMap)
})
checkAccepted(isAccepted)
},
notExists({ query }, tailOpList, resultList, varMap) {
const isAccepted = collection.queryDocuments(collectionLink, queryApplyVar(query, varMap), {}, function(err, docs, options) {
if (err) throw err
resultList.push(docs)
if(docs.length == 0)
runOpList(tailOpList, resultList, varMap)
else
response.setBody(resultList)
})
checkAccepted(isAccepted)
},
delete({ query, mustExist }, tailOpList, resultList, varMap) {
const isAccepted = collection.queryDocuments(collectionLink, queryApplyVar(query, varMap), {}, function(err, docs, options) {
if (err) throw err
if(mustExist && docs.length == 0) {
throw new Error(ERROR_CODE.CONFLICT, 'delete set mustExist, but no docs found')
}
deleteDocs(docs, function() {
resultList.push(docs)
runOpList(tailOpList, resultList, varMap)
})
})
checkAccepted(isAccepted)
}
}
function runOpList([op, ...tailOpList], resultList = [], varMap = {}) {
if(!op) {
response.setBody(resultList)
return
}
const opFunc = opMap[op.op]
if(!opFunc) {
throw new Error(ERROR_CODE.BAD_REQUEST, `op not supported: ${op.op}`)
}
opFunc(op, tailOpList, resultList, varMap)
}
function inc(doc, varMap, k, v, minV, maxV) {
const newValue = (doc[k] || 0) + v
if(typeof minV == 'number' && newValue < minV)
throw new Error(ERROR_CODE.CONFLICT, `"${doc[k]}" + ${v} < ${minV} in ${k}`)
if(typeof maxV == 'number' && newValue > maxV)
throw new Error(ERROR_CODE.CONFLICT, `"${doc[k]}" + ${v} > ${maxV} in ${k}`)
doc[k] = newValue
}
const funcMap = {
set(doc, varMap, k, v) {
doc[k] = v
},
inc,
assign(doc, varMap, k, obj) {
const target = k == null ? doc : doc[k]
Object.assign(target, obj)
},
delKeys(doc, varMap, k, subKeys) {
const target = k == null ? doc : doc[k]
for(const subKey of subKeys)
delete target[subKey]
},
push(doc, varMap, k, v, maxLen) {
let l = doc[k]
if (!l) {
l = []
doc[k] = l
}
l.push(v)
if (typeof maxLen == 'number' && l.length > maxLen) {
l.splice(0, l.length - maxLen)
}
},
var(doc, varMap, k, varName, defaultValue) {
let v = doc[k]
if (typeof v == 'undefined')
v = defaultValue
varMap[varName] = v
},
incVar(doc, varMap, k, varName, minV, maxV) {
inc(doc, varMap, k, varMap[varName], minV, maxV)
},
decVar(doc, varMap, k, varName, minV, maxV) {
inc(doc, varMap, k, -varMap[varName], minV, maxV)
},
}
function updateDocs([doc, ...tailDocs], updateList, resultList, varMap, callback) {
if(!doc) {
callback()
return
}
for(const [funcName, ...args] of updateList) {
const func = funcMap[funcName]
if(!func) {
throw new Error(ERROR_CODE.BAD_REQUEST, `update func not supported: ${funcName}`)
}
func(doc, varMap, ...args)
}
const isAccepted = collection.replaceDocument(doc._self, doc, {}, function (err, doc, options) {
if (err) throw err
resultList.push(doc)
updateDocs(tailDocs, updateList, resultList, varMap, callback)
})
checkAccepted(isAccepted)
}
function createDocs([doc, ...tailDocs], resultList, callback) {
if(!doc) {
callback()
return
}
const isAccepted = collection.createDocument(collectionLink, doc, {}, function (err, doc, options) {
if (err) throw err
resultList.push(doc)
createDocs(tailDocs, resultList, callback)
})
checkAccepted(isAccepted)
}
function deleteDocs([doc, ...tailDocs], callback) {
if(!doc) {
callback()
return
}
const isAccepted = collection.deleteDocument(doc._self, {}, function (err, doc, options) {
if (err) throw err
deleteDocs(tailDocs, callback)
})
checkAccepted(isAccepted)
}
function checkAccepted(isAccepted) {
if (!isAccepted) throw new Error(ERROR_CODE.NOT_ACCEPTED, "The request was not accepted. Retry from the client.");
}
runOpList(opList)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment