2020-08-21 03:31:43 +12:00
|
|
|
const TOKEN_MAP = {
|
|
|
|
EQUALS: "===",
|
2020-10-15 09:43:36 +13:00
|
|
|
NOT_EQUALS: "!==",
|
2020-08-21 03:31:43 +12:00
|
|
|
LT: "<",
|
|
|
|
LTE: "<=",
|
|
|
|
MT: ">",
|
|
|
|
MTE: ">=",
|
2020-08-22 04:05:26 +12:00
|
|
|
CONTAINS: "includes",
|
2020-08-21 03:31:43 +12:00
|
|
|
AND: "&&",
|
2020-08-22 04:05:26 +12:00
|
|
|
OR: "||",
|
2020-08-21 03:31:43 +12:00
|
|
|
}
|
|
|
|
|
2020-09-02 22:52:32 +12:00
|
|
|
const GROUP_PROPERTY = {
|
|
|
|
group: {
|
|
|
|
type: "string",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
const FIELD_PROPERTY = {
|
|
|
|
field: {
|
|
|
|
type: "string",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2020-08-24 22:46:28 +12:00
|
|
|
const SCHEMA_MAP = {
|
2020-10-15 22:48:57 +13:00
|
|
|
sum: {
|
|
|
|
field: "string",
|
|
|
|
value: "number",
|
|
|
|
},
|
|
|
|
count: {
|
|
|
|
field: "string",
|
|
|
|
value: "number",
|
|
|
|
},
|
2020-08-24 22:46:28 +12:00
|
|
|
stats: {
|
2020-08-25 02:48:34 +12:00
|
|
|
sum: {
|
|
|
|
type: "number",
|
2020-08-24 22:46:28 +12:00
|
|
|
},
|
2020-08-25 02:48:34 +12:00
|
|
|
min: {
|
|
|
|
type: "number",
|
2020-08-24 22:46:28 +12:00
|
|
|
},
|
2020-08-25 02:48:34 +12:00
|
|
|
max: {
|
|
|
|
type: "number",
|
2020-08-24 22:46:28 +12:00
|
|
|
},
|
2020-08-25 02:48:34 +12:00
|
|
|
count: {
|
|
|
|
type: "number",
|
2020-08-24 22:46:28 +12:00
|
|
|
},
|
2020-08-25 02:48:34 +12:00
|
|
|
sumsqr: {
|
|
|
|
type: "number",
|
2020-08-24 22:46:28 +12:00
|
|
|
},
|
2020-08-25 02:48:34 +12:00
|
|
|
avg: {
|
|
|
|
type: "number",
|
|
|
|
},
|
|
|
|
},
|
2020-08-24 22:46:28 +12:00
|
|
|
}
|
|
|
|
|
2020-08-22 04:05:26 +12:00
|
|
|
/**
|
|
|
|
* Iterates through the array of filters to create a JS
|
|
|
|
* expression that gets used in a CouchDB view.
|
|
|
|
* @param {Array} filters - an array of filter objects
|
|
|
|
* @returns {String} JS Expression
|
|
|
|
*/
|
|
|
|
function parseFilterExpression(filters) {
|
|
|
|
const expression = []
|
|
|
|
|
|
|
|
for (let filter of filters) {
|
2020-08-25 02:48:34 +12:00
|
|
|
if (filter.conjunction) expression.push(TOKEN_MAP[filter.conjunction])
|
2020-08-22 04:05:26 +12:00
|
|
|
|
|
|
|
if (filter.condition === "CONTAINS") {
|
|
|
|
expression.push(
|
2020-08-25 02:48:34 +12:00
|
|
|
`doc["${filter.key}"].${TOKEN_MAP[filter.condition]}("${filter.value}")`
|
|
|
|
)
|
|
|
|
} else {
|
2020-10-02 04:55:17 +13:00
|
|
|
const value =
|
|
|
|
typeof filter.value == "string" ? `"${filter.value}"` : filter.value
|
|
|
|
|
2020-08-25 02:48:34 +12:00
|
|
|
expression.push(
|
2020-10-02 04:55:17 +13:00
|
|
|
`doc["${filter.key}"] ${TOKEN_MAP[filter.condition]} ${value}`
|
2020-08-25 02:48:34 +12:00
|
|
|
)
|
2020-08-22 04:05:26 +12:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-21 03:31:43 +12:00
|
|
|
return expression.join(" ")
|
|
|
|
}
|
|
|
|
|
2020-08-25 02:48:34 +12:00
|
|
|
/**
|
|
|
|
* Returns a CouchDB compliant emit() expression that is used to emit the
|
|
|
|
* correct key/value pairs for custom views.
|
|
|
|
* @param {String?} field - field to use for calculations, if any
|
|
|
|
* @param {String?} groupBy - field to group calculation results on, if any
|
|
|
|
*/
|
2020-08-22 04:05:26 +12:00
|
|
|
function parseEmitExpression(field, groupBy) {
|
|
|
|
if (field) return `emit(doc["${groupBy || "_id"}"], doc["${field}"]);`
|
2020-10-15 22:48:57 +13:00
|
|
|
return `emit(doc._id, 1);`
|
2020-08-22 04:05:26 +12:00
|
|
|
}
|
|
|
|
|
2020-08-25 02:48:34 +12:00
|
|
|
/**
|
|
|
|
* Return a fully parsed CouchDB compliant view definition
|
|
|
|
* that will be stored in the design document in the database.
|
|
|
|
*
|
|
|
|
* @param {Object} viewDefinition - the JSON definition for a custom view.
|
|
|
|
* field: field that calculations will be performed on
|
2020-10-10 06:49:23 +13:00
|
|
|
* tableId: tableId of the table this view was created from
|
2020-08-25 02:48:34 +12:00
|
|
|
* groupBy: field that calculations will be grouped by. Field must be present for this to be useful
|
|
|
|
* filters: Array of filter objects containing predicates that are parsed into a JS expression
|
|
|
|
* calculation: an optional calculation to be performed over the view data.
|
|
|
|
*/
|
2020-10-10 06:49:23 +13:00
|
|
|
function viewTemplate({ field, tableId, groupBy, filters = [], calculation }) {
|
2020-08-25 02:48:34 +12:00
|
|
|
const parsedFilters = parseFilterExpression(filters)
|
2020-08-24 22:46:28 +12:00
|
|
|
const filterExpression = parsedFilters ? `&& ${parsedFilters}` : ""
|
2020-08-22 04:05:26 +12:00
|
|
|
|
|
|
|
const emitExpression = parseEmitExpression(field, groupBy)
|
|
|
|
|
2020-10-15 22:48:57 +13:00
|
|
|
const reduction = field ? { reduce: `_${calculation}` } : {}
|
2020-08-24 22:46:28 +12:00
|
|
|
|
2020-09-02 22:52:32 +12:00
|
|
|
let schema = null
|
|
|
|
|
|
|
|
if (calculation) {
|
2020-09-15 02:40:45 +12:00
|
|
|
schema = {
|
|
|
|
...(groupBy ? GROUP_PROPERTY : FIELD_PROPERTY),
|
2020-09-15 02:41:20 +12:00
|
|
|
...SCHEMA_MAP[calculation],
|
2020-09-15 02:40:45 +12:00
|
|
|
}
|
2020-09-02 22:52:32 +12:00
|
|
|
}
|
|
|
|
|
2020-08-15 03:31:53 +12:00
|
|
|
return {
|
|
|
|
meta: {
|
|
|
|
field,
|
2020-10-10 06:49:23 +13:00
|
|
|
tableId,
|
2020-08-18 08:01:43 +12:00
|
|
|
groupBy,
|
2020-08-22 04:05:26 +12:00
|
|
|
filters,
|
2020-09-02 22:52:32 +12:00
|
|
|
schema,
|
2020-08-25 02:48:34 +12:00
|
|
|
calculation,
|
2020-08-15 03:31:53 +12:00
|
|
|
},
|
|
|
|
map: `function (doc) {
|
2020-10-10 06:49:23 +13:00
|
|
|
if (doc.tableId === "${tableId}" ${filterExpression}) {
|
2020-08-22 04:05:26 +12:00
|
|
|
${emitExpression}
|
2020-08-15 03:31:53 +12:00
|
|
|
}
|
|
|
|
}`,
|
2020-08-25 02:48:34 +12:00
|
|
|
...reduction,
|
2020-08-15 03:31:53 +12:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-24 22:46:28 +12:00
|
|
|
module.exports = viewTemplate
|