cloki
Version:
LogQL API with Clickhouse Backend
224 lines (215 loc) • 6.3 kB
JavaScript
const reg = require('./stream_selector_operator_registry/stream_selector_operator_registry')
const numreg = require('./number_operator_registry/compared_label_reg')
const { hasExtraLabels, hasStream, addStream } = require('./common')
const Sql = require('@cloki/clickhouse-sql')
/**
*
* @param token {Token}
* @param query {Select}
* @returns {Select}
*/
module.exports = (token, query) => {
if (hasStream(query)) {
const pred = processStreamExpression(token, query)
return addStream(query,
/**
*
* @param e {DataStream}
* @returns {DataStream}
*/
(e) => e.filter(pred))
}
const ex = processWhereExpression(token, query)
return hasExtraLabels(query)
? query.where(ex)
: reg.simpleAnd(query, ex)
}
/**
*
* @param andOr {string}
* @param cond {Conditions}
*/
const checkAndOrType = (andOr, cond) => {
return (andOr === 'and' && cond instanceof Sql.Conjunction) ||
(andOr === 'or' && cond instanceof Sql.Disjunction)
}
/**
*
* @param token {Token}
* @param query {Select}
* @returns {Conditions}
*/
const processWhereExpression = (token, query) => {
let where = null
let andOr = null
for (const t of token.tokens) {
if (t.name === 'and_or') {
andOr = t.value
continue
}
andOr = (andOr || 'and').toLowerCase()
let ex = null
if (t.name === 'label_filter_expression') {
ex = getLabelFilterWhereExpression(t, query)
} else if (t.name === 'bracketed_label_filter_expression' || t.name === 'complex_label_filter_expression') {
ex = processWhereExpression(t, query)
} else {
continue
}
if (!where) {
where = ex
} else if (checkAndOrType(andOr, where)) {
where.args.push(ex)
} else if (andOr === 'and') {
where = Sql.And(where, ex)
} else if (andOr === 'or') {
where = Sql.Or(where, ex)
}
andOr = null
}
return where
}
/**
*
* @param token {Token}
* @param query {Select}
* @returns {string | string[]}
*/
const getLabelFilterWhereExpression = (token, query) => {
// TODO:
let clauses = null
if (token.Child('string_label_filter_expression')) {
switch (token.Child('operator').value) {
case '=':
clauses = hasExtraLabels(query) ? reg.eqExtraLabels(token) : reg.eqSimple(token)
break
case '!=':
clauses = hasExtraLabels(query) ? reg.neqExtraLabels(token) : reg.neqSimple(token)
break
case '=~':
clauses = hasExtraLabels(query) ? reg.regExtraLabels(token) : reg.regSimple(token)
break
case '!~':
clauses = hasExtraLabels(query) ? reg.nregExtraLabels(token) : reg.nregSimple(token)
break
default:
throw new Error('Unsupported operator')
}
return clauses
}
if (token.Child('number_label_filter_expression')) {
const label = token.Child('label').value
if (token.Child('duration_value') || token.Child('bytes_value')) {
throw new Error('Not supported')
}
const val = token.Child('number_value').value
const idx = hasExtraLabels(query) ? 'extraLabelsWhere' : 'simpleWhere'
switch (token.Child('number_operator').value) {
case '==':
return numreg[idx].eq(label, val)
case '!=':
return numreg[idx].neq(label, val)
case '>':
return numreg[idx].gt(label, val)
case '>=':
return numreg[idx].ge(label, val)
case '<':
return numreg[idx].lt(label, val)
case '<=':
return numreg[idx].le(label, val)
}
}
}
/**
*
* @param fns {(function({labels: Object}): boolean)}
* @returns {function({labels: Object}): boolean}
*/
const genericAnd = (...fns) => {
return (e) => !fns.some(fn => !fn(e))
}
/**
*
* @param fns {(function({labels: Object}): boolean)}
* @returns {function({labels: Object}): boolean}
*/
const genericOr = (...fns) => {
return (e) => fns.some(fn => fn(e))
}
/**
*
* @param token {Token}
* @param query {registry_types.Request}
* @returns {function({labels: Object}): boolean}
*/
const processStreamExpression = (token, query) => {
let andOr = 'and'
let res = null
for (const t of token.tokens) {
if (t.name === 'label_filter_expression') {
if (!res) {
res = getLabelFilterStreamExpression(t, query)
continue
}
res = (andOr || 'and').toLowerCase() === 'and'
? genericAnd(res, getLabelFilterStreamExpression(t, query))
: genericOr(res, getLabelFilterStreamExpression(t, query))
}
if (t.name === 'bracketed_label_filter_expression' || t.name === 'complex_label_filter_expression') {
if (!res) {
res = processStreamExpression(t, query)
continue
}
res = (andOr || 'and').toLowerCase() === 'and'
? genericAnd(res, processStreamExpression(t, query))
: genericOr(res, processStreamExpression(t, query))
}
if (t.name === 'and_or') {
andOr = t.value
}
}
return res || (() => true)
}
/**
*
* @param token {Token}
* @param query {registry_types.Request}
* @returns {function({labels: Object}): boolean}
*/
const getLabelFilterStreamExpression = (token, query) => {
if (token.Child('string_label_filter_expression')) {
switch (token.Child('operator').value) {
case '=':
return reg.eqStream(token, query)
case '!=':
return reg.neqStream(token, query)
case '=~':
return reg.regStream(token, query)
case '!~':
return reg.nregStream(token, query)
default:
throw new Error('Unsupported operator')
}
}
if (token.Child('number_label_filter_expression')) {
const label = token.Child('label').value
if (token.Child('duration_value') || token.Child('bytes_value')) {
throw new Error('Not supported')
}
const val = token.Child('number_value').value
switch (token.Child('number_operator').value) {
case '==':
return numreg.streamWhere.eq(label, val)
case '!=':
return numreg.streamWhere.neq(label, val)
case '>':
return numreg.streamWhere.gt(label, val)
case '>=':
return numreg.streamWhere.ge(label, val)
case '<':
return numreg.streamWhere.lt(label, val)
case '<=':
return numreg.streamWhere.le(label, val)
}
}
}