Created
April 14, 2020 02:41
-
-
Save hwei/435650d044baa2fa5a4f46657d4e1f43 to your computer and use it in GitHub Desktop.
Cosmos DB stored procedures for general transactions
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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