Nilorea Library
C utilities for networking, threading, graphics
Loading...
Searching...
No Matches
n_query.c
Go to the documentation of this file.
1/*
2 * Nilorea Library
3 * Copyright (C) 2005-2026 Castagnier Mickael
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
14 * implied. See the License for the specific language governing
15 * permissions and limitations under the License.
16 *
17 * SPDX-License-Identifier: Apache-2.0
18 */
19
25#include "nilorea/n_query.h"
26#include "nilorea/n_log.h"
27#include "nilorea/n_pcre.h"
28
29#include <ctype.h>
30#include <stdlib.h>
31#include <string.h>
32#include <strings.h>
33
34/* comparison operators */
35enum { OP_EQ,
44
45/* AST node kinds */
46enum { N_AND,
50
51/* one AST node */
52typedef struct QNODE {
53 int kind;
54 struct QNODE* a; /* AND/OR left, NOT child */
55 struct QNODE* b; /* AND/OR right */
56 char* field; /* CMP: field name */
57 int op; /* CMP: operator */
58 char* value; /* CMP: comparison value */
59 N_PCRE* re; /* CMP: compiled regex for OP_REGEX, else NULL */
60} QNODE;
61
62struct N_QUERY {
63 QNODE* root; /* NULL = match all */
64};
65
66/* token kinds */
67enum { TK_END,
75
76/* parser/lexer state */
77typedef struct {
78 const char* p; /* cursor into the source */
79 int tok; /* current token kind */
80 char* tval; /* current token text (heap), for TK_WORD/keywords */
81 int top; /* current operator (when tok == TK_OP) */
82 char err[160]; /* error message on failure */
83} PARSER;
84
85/* heap copy of the first n bytes of s as a NUL-terminated string */
86static char* dupn(const char* s, size_t n) {
87 char* out = malloc(n + 1);
88 if (!out)
89 return NULL;
90 if (n)
91 memcpy(out, s, n);
92 out[n] = '\0';
93 return out;
94}
95
96static int is_op_char(char c) {
97 return c == '=' || c == '!' || c == '~' || c == '>' || c == '<';
98}
99
100/* case-insensitive substring test */
101static int ci_contains(const char* hay, const char* needle) {
102 size_t nl = strlen(needle);
103 if (nl == 0)
104 return 1;
105 for (; *hay; hay++) {
106 size_t i = 0;
107 while (i < nl && hay[i] && tolower((unsigned char)hay[i]) == tolower((unsigned char)needle[i]))
108 i++;
109 if (i == nl)
110 return 1;
111 }
112 return 0;
113}
114
115/* advance to the next token */
116static void lex_next(PARSER* P) {
117 const char* s = P->p;
118 if (P->tval) {
119 free(P->tval);
120 P->tval = NULL;
121 }
122 while (*s == ' ' || *s == '\t' || *s == '\r' || *s == '\n')
123 s++;
124 if (*s == '\0') {
125 P->tok = TK_END;
126 P->p = s;
127 return;
128 }
129 if (*s == '(') {
130 P->tok = TK_LP;
131 P->p = s + 1;
132 return;
133 }
134 if (*s == ')') {
135 P->tok = TK_RP;
136 P->p = s + 1;
137 return;
138 }
139 /* operators, longest match first */
140 if (s[0] == '>' && s[1] == '=') {
141 P->tok = TK_OP;
142 P->top = OP_GE;
143 P->p = s + 2;
144 return;
145 }
146 if (s[0] == '<' && s[1] == '=') {
147 P->tok = TK_OP;
148 P->top = OP_LE;
149 P->p = s + 2;
150 return;
151 }
152 if (s[0] == '=' && s[1] == '~') {
153 P->tok = TK_OP;
154 P->top = OP_REGEX;
155 P->p = s + 2;
156 return;
157 }
158 if (s[0] == '!' && s[1] == '=') {
159 P->tok = TK_OP;
160 P->top = OP_NE;
161 P->p = s + 2;
162 return;
163 }
164 if (s[0] == '!' && s[1] == '~') {
165 P->tok = TK_OP;
166 P->top = OP_NCONT;
167 P->p = s + 2;
168 return;
169 }
170 if (s[0] == '=') {
171 P->tok = TK_OP;
172 P->top = OP_EQ;
173 P->p = s + 1;
174 return;
175 }
176 if (s[0] == '~') {
177 P->tok = TK_OP;
178 P->top = OP_CONT;
179 P->p = s + 1;
180 return;
181 }
182 if (s[0] == '>') {
183 P->tok = TK_OP;
184 P->top = OP_GT;
185 P->p = s + 1;
186 return;
187 }
188 if (s[0] == '<') {
189 P->tok = TK_OP;
190 P->top = OP_LT;
191 P->p = s + 1;
192 return;
193 }
194 if (*s == '"') {
195 const char* start = s + 1;
196 const char* e = start;
197 while (*e && *e != '"')
198 e++;
199 P->tval = dupn(start, (size_t)(e - start));
200 P->tok = TK_WORD;
201 P->p = (*e == '"') ? e + 1 : e;
202 return;
203 }
204 /* bareword: a run of non-space, non-paren, non-operator, non-quote bytes */
205 {
206 const char* start = s;
207 while (*s && !isspace((unsigned char)*s) && *s != '(' && *s != ')' && *s != '"' && !is_op_char(*s))
208 s++;
209 P->tval = dupn(start, (size_t)(s - start));
210 P->p = s;
211 if (P->tval && strcasecmp(P->tval, "and") == 0)
212 P->tok = TK_AND;
213 else if (P->tval && strcasecmp(P->tval, "or") == 0)
214 P->tok = TK_OR;
215 else if (P->tval && strcasecmp(P->tval, "not") == 0)
216 P->tok = TK_NOT;
217 else
218 P->tok = TK_WORD;
219 }
220}
221
222static QNODE* node_new(int kind) {
223 QNODE* n = calloc(1, sizeof(QNODE));
224 if (n)
225 n->kind = kind;
226 return n;
227}
228
229static void node_free(QNODE* n) {
230 if (!n)
231 return;
232 node_free(n->a);
233 node_free(n->b);
234 free(n->field);
235 free(n->value);
236 if (n->re)
237 npcre_delete(&n->re);
238 free(n);
239}
240
241static QNODE* parse_or(PARSER* P); /* forward */
242
244 if (P->tok == TK_LP) {
245 QNODE* inner;
246 lex_next(P);
247 inner = parse_or(P);
248 if (!inner)
249 return NULL;
250 if (P->tok != TK_RP) {
251 snprintf(P->err, sizeof(P->err), "expected ')'");
252 node_free(inner);
253 return NULL;
254 }
255 lex_next(P);
256 return inner;
257 }
258 if (P->tok != TK_WORD) {
259 snprintf(P->err, sizeof(P->err), "expected a field name");
260 return NULL;
261 }
262 {
263 char* field = P->tval ? dupn(P->tval, strlen(P->tval)) : NULL;
264 int op;
265 char* val;
266 QNODE* n;
267 lex_next(P);
268 if (P->tok != TK_OP) {
269 snprintf(P->err, sizeof(P->err), "expected an operator after field '%s'", field ? field : "");
270 free(field);
271 return NULL;
272 }
273 op = P->top;
274 lex_next(P);
275 if (P->tok != TK_WORD && P->tok != TK_AND && P->tok != TK_OR && P->tok != TK_NOT) {
276 snprintf(P->err, sizeof(P->err), "expected a value");
277 free(field);
278 return NULL;
279 }
280 val = P->tval ? dupn(P->tval, strlen(P->tval)) : dupn("", 0);
281 lex_next(P);
282 n = node_new(N_CMP);
283 if (!n || !field || !val) {
284 free(field);
285 free(val);
286 node_free(n);
287 return NULL;
288 }
289 n->field = field;
290 n->op = op;
291 n->value = val;
292 if (op == OP_REGEX) {
293 n->re = npcre_new(val, 0);
294 if (!n->re) {
295 snprintf(P->err, sizeof(P->err), "invalid regex '%s'", val);
296 node_free(n);
297 return NULL;
298 }
299 }
300 return n;
301 }
302}
303
304static QNODE* parse_not(PARSER* P) {
305 if (P->tok == TK_NOT) {
306 QNODE* child;
307 QNODE* n;
308 lex_next(P);
309 child = parse_not(P);
310 if (!child)
311 return NULL;
312 n = node_new(N_NOT);
313 if (!n) {
314 node_free(child);
315 return NULL;
316 }
317 n->a = child;
318 return n;
319 }
320 return parse_primary(P);
321}
322
323static QNODE* parse_and(PARSER* P) {
324 QNODE* left = parse_not(P);
325 if (!left)
326 return NULL;
327 while (P->tok == TK_AND) {
328 QNODE* right;
329 QNODE* n;
330 lex_next(P);
331 right = parse_not(P);
332 if (!right) {
333 node_free(left);
334 return NULL;
335 }
336 n = node_new(N_AND);
337 if (!n) {
338 node_free(left);
339 node_free(right);
340 return NULL;
341 }
342 n->a = left;
343 n->b = right;
344 left = n;
345 }
346 return left;
347}
348
349static QNODE* parse_or(PARSER* P) {
350 QNODE* left = parse_and(P);
351 if (!left)
352 return NULL;
353 while (P->tok == TK_OR) {
354 QNODE* right;
355 QNODE* n;
356 lex_next(P);
357 right = parse_and(P);
358 if (!right) {
359 node_free(left);
360 return NULL;
361 }
362 n = node_new(N_OR);
363 if (!n) {
364 node_free(left);
365 node_free(right);
366 return NULL;
367 }
368 n->a = left;
369 n->b = right;
370 left = n;
371 }
372 return left;
373}
374
375N_QUERY* n_query_compile(const char* expr, char* errbuf, size_t errlen) {
376 PARSER P;
377 N_QUERY* q;
378 QNODE* root;
379 memset(&P, 0, sizeof(P));
380 P.p = expr ? expr : "";
381 lex_next(&P);
382 if (P.tok == TK_END) {
383 /* empty expression: a match-all query */
384 if (P.tval)
385 free(P.tval);
386 q = calloc(1, sizeof(N_QUERY));
387 return q;
388 }
389 root = parse_or(&P);
390 if (!root) {
391 if (errbuf && errlen)
392 snprintf(errbuf, errlen, "%s", P.err[0] ? P.err : "parse error");
393 if (P.tval)
394 free(P.tval);
395 return NULL;
396 }
397 if (P.tok != TK_END) {
398 if (errbuf && errlen)
399 snprintf(errbuf, errlen, "unexpected trailing input");
400 node_free(root);
401 if (P.tval)
402 free(P.tval);
403 return NULL;
404 }
405 if (P.tval)
406 free(P.tval);
407 q = calloc(1, sizeof(N_QUERY));
408 if (!q) {
409 node_free(root);
410 return NULL;
411 }
412 q->root = root;
413 return q;
414}
415
416static int eval_node(const QNODE* n, N_QUERY_GET get, void* ud) {
417 if (!n)
418 return 1;
419 switch (n->kind) {
420 case N_AND:
421 return eval_node(n->a, get, ud) && eval_node(n->b, get, ud);
422 case N_OR:
423 return eval_node(n->a, get, ud) || eval_node(n->b, get, ud);
424 case N_NOT:
425 return !eval_node(n->a, get, ud);
426 case N_CMP: {
427 const char* v = get ? get(n->field, ud) : NULL;
428 if (!v)
429 v = "";
430 switch (n->op) {
431 case OP_EQ:
432 return strcasecmp(v, n->value) == 0;
433 case OP_NE:
434 return strcasecmp(v, n->value) != 0;
435 case OP_CONT:
436 return ci_contains(v, n->value);
437 case OP_NCONT:
438 return !ci_contains(v, n->value);
439 case OP_REGEX:
440 return (n->re && npcre_match((char*)v, n->re) == TRUE) ? 1 : 0;
441 case OP_GT:
442 case OP_LT:
443 case OP_GE:
444 case OP_LE: {
445 char* e1 = NULL;
446 char* e2 = NULL;
447 double a = strtod(v, &e1);
448 double b = strtod(n->value, &e2);
449 if (e1 == v || e2 == n->value)
450 return 0; /* a non-numeric side never matches */
451 if (n->op == OP_GT)
452 return a > b;
453 if (n->op == OP_LT)
454 return a < b;
455 if (n->op == OP_GE)
456 return a >= b;
457 return a <= b;
458 }
459 default:
460 return 0;
461 }
462 }
463 default:
464 return 0;
465 }
466}
467
468int n_query_eval(const N_QUERY* query, N_QUERY_GET get, void* user_data) {
469 if (!query)
470 return 1;
471 return eval_node(query->root, get, user_data) ? 1 : 0;
472}
473
474void n_query_free(N_QUERY** query) {
475 if (!query || !*query)
476 return;
477 node_free((*query)->root);
478 free(*query);
479 *query = NULL;
480}
int n_query_eval(const N_QUERY *query, N_QUERY_GET get, void *user_data)
Evaluate a compiled query against one record via get.
Definition n_query.c:468
const char *(* N_QUERY_GET)(const char *field, void *user_data)
Field-value getter: return the value of field for the record being evaluated (NUL-terminated,...
Definition n_query.h:71
N_QUERY * n_query_compile(const char *expr, char *errbuf, size_t errlen)
Compile an expression into a query.
Definition n_query.c:375
void n_query_free(N_QUERY **query)
Free a compiled query and set the pointer to NULL.
Definition n_query.c:474
N_PCRE * npcre_new(char *str, int flags)
From pcre doc, the flag bits are: PCRE_ANCHORED Force pattern anchoring PCRE_AUTO_CALLOUT Compile aut...
Definition n_pcre.c:82
int npcre_match(char *str, N_PCRE *pcre)
Thread-safe match: returns TRUE/FALSE without modifying the N_PCRE struct.
Definition n_pcre.c:228
int npcre_delete(N_PCRE **pcre)
Free a N_PCRE pointer.
Definition n_pcre.c:136
N_PCRE structure.
Definition n_pcre.h:93
Generic log system.
PCRE helpers for regex matching.
static char * dupn(const char *s, size_t n)
Definition n_query.c:86
struct QNODE * a
Definition n_query.c:54
struct QNODE * b
Definition n_query.c:55
static int is_op_char(char c)
Definition n_query.c:96
static QNODE * node_new(int kind)
Definition n_query.c:222
static void lex_next(PARSER *P)
Definition n_query.c:116
const char * p
Definition n_query.c:78
static int ci_contains(const char *hay, const char *needle)
Definition n_query.c:101
int kind
Definition n_query.c:53
static int eval_node(const QNODE *n, N_QUERY_GET get, void *ud)
Definition n_query.c:416
int op
Definition n_query.c:57
char * field
Definition n_query.c:56
int tok
Definition n_query.c:79
static QNODE * parse_and(PARSER *P)
Definition n_query.c:323
int top
Definition n_query.c:81
@ N_AND
Definition n_query.c:46
@ N_CMP
Definition n_query.c:49
@ N_OR
Definition n_query.c:47
@ N_NOT
Definition n_query.c:48
N_PCRE * re
Definition n_query.c:59
static QNODE * parse_or(PARSER *P)
Definition n_query.c:349
char err[160]
Definition n_query.c:82
@ TK_WORD
Definition n_query.c:68
@ TK_RP
Definition n_query.c:73
@ TK_END
Definition n_query.c:67
@ TK_OP
Definition n_query.c:74
@ TK_NOT
Definition n_query.c:71
@ TK_OR
Definition n_query.c:70
@ TK_AND
Definition n_query.c:69
@ TK_LP
Definition n_query.c:72
static QNODE * parse_not(PARSER *P)
Definition n_query.c:304
static void node_free(QNODE *n)
Definition n_query.c:229
char * tval
Definition n_query.c:80
char * value
Definition n_query.c:58
@ OP_NE
Definition n_query.c:36
@ OP_EQ
Definition n_query.c:35
@ OP_CONT
Definition n_query.c:37
@ OP_GE
Definition n_query.c:42
@ OP_NCONT
Definition n_query.c:38
@ OP_LT
Definition n_query.c:41
@ OP_REGEX
Definition n_query.c:39
@ OP_GT
Definition n_query.c:40
@ OP_LE
Definition n_query.c:43
static QNODE * parse_primary(PARSER *P)
Definition n_query.c:243
QNODE * root
Definition n_query.c:63
Opaque compiled query (see n_query_compile / n_query_eval / n_query_free).
Definition n_query.c:62
Small boolean query language over caller-named fields.