Skip to content

Commit d7145ef

Browse files
authoredNov 13, 2024
Merge pull request #584 from metrico/fix/empty_labels_check
correct processing of {a=""} and {a!=""} cases
2 parents 42dd1e3 + 397b391 commit d7145ef

File tree

1 file changed

+78
-41
lines changed

1 file changed

+78
-41
lines changed
 

‎promql/index.js

+78-41
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,7 @@ module.exports.series = async (query, fromMs, toMs) => {
4545
const fromS = Math.floor(fromMs / 1000)
4646
const toS = Math.floor(toMs / 1000)
4747
const matchers = prometheus.pqlMatchers(query)
48-
const conds = getMatchersIdxCond(matchers[0])
49-
const idx = getIdxSubquery(conds, fromMs, toMs)
48+
const idx = getIdxSubqueryV2(matchers[0], fromMs, toMs)
5049
const withIdx = new Sql.With('idx', idx, !!clusterName)
5150
const req = (new Sql.Select())
5251
.with(withIdx)
@@ -72,51 +71,90 @@ module.exports.series = async (query, fromMs, toMs) => {
7271
}
7372
}
7473

74+
/**
75+
*
76+
* @param matcher {[string]}
77+
*/
78+
const getMatcherIdxCond = (matcher) => {
79+
const res = [
80+
Sql.Eq('key', matcher[0])
81+
]
82+
switch (matcher[1]) {
83+
case '=':
84+
res.push(Sql.Eq('val', matcher[2]))
85+
break
86+
case '!=':
87+
res.push(Sql.Ne('val', matcher[2]))
88+
break
89+
case '=~':
90+
res.push(Sql.Eq(new Sql.Raw(`match(val, ${Sql.quoteVal(matcher[2])})`), 1))
91+
break
92+
case '!~':
93+
res.push(Sql.Ne(new Sql.Raw(`match(val, ${Sql.quoteVal(matcher[2])})`), 1))
94+
}
95+
return res
96+
}
97+
7598
/**
7699
*
77100
* @param matchers {[[string]]}
78101
*/
79102
const getMatchersIdxCond = (matchers) => {
80-
const matchesCond = []
81-
for (const matcher of matchers) {
82-
const _matcher = [
83-
Sql.Eq('key', matcher[0])
84-
]
85-
switch (matcher[1]) {
86-
case '=':
87-
_matcher.push(Sql.Eq('val', matcher[2]))
88-
break
89-
case '!=':
90-
_matcher.push(Sql.Ne('val', matcher[2]))
91-
break
92-
case '=~':
93-
_matcher.push(Sql.Eq(new Sql.Raw(`match(val, ${Sql.quoteVal(matcher[2])})`), 1))
94-
break
95-
case '!~':
96-
_matcher.push(Sql.Ne(new Sql.Raw(`match(val, ${Sql.quoteVal(matcher[2])})`), 1))
97-
}
98-
matchesCond.push(Sql.And(..._matcher))
99-
}
100-
return matchesCond
103+
return matchers.map(matcher => Sql.And(...getMatcherIdxCond(matcher)))
101104
}
102105

103-
const getIdxSubquery = (conds, fromMs, toMs) => {
106+
const getIdxSubqueryV2 = (matchers, fromMs, toMs) => {
104107
const fromS = Math.floor(fromMs / 1000)
105108
const toS = Math.floor(toMs / 1000)
106-
return (new Sql.Select())
107-
.select('fingerprint')
108-
.from([DATABASE_NAME() + '.time_series_gin', 'time_series_gin'])
109-
.where(Sql.And(
110-
Sql.Or(...conds),
111-
Sql.Gte('date', new Sql.Raw(`toDate(fromUnixTimestamp(${fromS}))`)),
112-
Sql.Lte('date', new Sql.Raw(`toDate(fromUnixTimestamp(${toS}))`)),
113-
new Sql.In('type', 'in', [bothType, metricType])))
114-
.having(
115-
Sql.Eq(
116-
new Sql.Raw('groupBitOr(' + conds.map(
117-
(m, i) => new Sql.Raw(`bitShiftLeft((${m})::UInt64, ${i})`)
118-
).join('+') + ')'), (1 << conds.length) - 1)
119-
).groupBy('fingerprint')
109+
const nonEmptyMatchers = matchers.filter(m => m[2] !== '')
110+
const emptyMatchers = matchers.filter(m => m[2] === '' && ['=', '!='].includes(m[1]))
111+
let req = null
112+
if (nonEmptyMatchers.length) {
113+
const nonEmptyConds = getMatchersIdxCond(nonEmptyMatchers)
114+
req = (new Sql.Select())
115+
.select('fingerprint')
116+
.from([DATABASE_NAME() + '.time_series_gin', 'time_series_gin'])
117+
.where(Sql.And(
118+
Sql.Or(...nonEmptyConds),
119+
Sql.Gte('date', new Sql.Raw(`toDate(fromUnixTimestamp(${fromS}))`)),
120+
Sql.Lte('date', new Sql.Raw(`toDate(fromUnixTimestamp(${toS}))`)),
121+
new Sql.In('type', 'in', [bothType, metricType])))
122+
.having(
123+
Sql.Eq(
124+
new Sql.Raw('groupBitOr(' + nonEmptyConds.map(
125+
(m, i) => new Sql.Raw(`bitShiftLeft((${m})::UInt64, ${i})`)
126+
).join('+') + ')'), (1 << nonEmptyConds.length) - 1)
127+
).groupBy('fingerprint')
128+
}
129+
if (emptyMatchers.length) {
130+
const emptyConds = emptyMatchers.map(m => {
131+
const visitParamHas = new Sql.Raw('')
132+
visitParamHas.toString = function () {
133+
return `visitParamHas(labels, ${Sql.quoteVal(m[0])})`
134+
}
135+
switch (m[1]) {
136+
case '=':
137+
return Sql.Eq(visitParamHas, new Sql.Raw('0'))
138+
case '!=':
139+
return Sql.Ne(visitParamHas, new Sql.Raw('1'))
140+
default:
141+
return null
142+
}
143+
}).filter(m => !!m)
144+
const emptyReq = (new Sql.Select())
145+
.select('fingerprint')
146+
.from(`time_series${_dist}`)
147+
.where(Sql.And(...emptyConds))
148+
if (nonEmptyMatchers.length) {
149+
const withNonEmptyIdx = new Sql.With('nonEmptyIdx', req, !!clusterName)
150+
emptyReq.with(withNonEmptyIdx)
151+
.where(
152+
new Sql.In('fingerprint', 'in', new Sql.WithReference(withNonEmptyIdx))
153+
)
154+
}
155+
req = emptyReq
156+
}
157+
return req
120158
}
121159

122160
module.exports.getData = async (matchers, fromMs, toMs, subqueries) => {
@@ -128,8 +166,7 @@ module.exports.getData = async (matchers, fromMs, toMs, subqueries) => {
128166
null, db, { responseType: 'arraybuffer' })
129167
return new Uint8Array(data.data)
130168
}
131-
const matches = getMatchersIdxCond(matchers)
132-
const idx = getIdxSubquery(matches, fromMs, toMs)
169+
const idx = getIdxSubqueryV2(matchers, fromMs, toMs)
133170
const withIdx = new Sql.With('idx', idx, !!clusterName)
134171
const timeSeries = (new Sql.Select())
135172
.select(
@@ -138,7 +175,7 @@ module.exports.getData = async (matchers, fromMs, toMs, subqueries) => {
138175
).from(DATABASE_NAME() + '.time_series')
139176
.where(Sql.And(
140177
new Sql.In('fingerprint', 'in', new Sql.WithReference(withIdx)),
141-
new Sql.In('type', 'in', [bothType,metricType])))
178+
new Sql.In('type', 'in', [bothType, metricType])))
142179
const withTimeSeries = new Sql.With('timeSeries', timeSeries, !!clusterName)
143180
const raw = (new Sql.Select())
144181
.with(withIdx)

0 commit comments

Comments
 (0)