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
24
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 const char* const* fields; /* optional known-field vocabulary, or NULL */
84 size_t nfields; /* its length */
85} PARSER;
86
87/* heap copy of the first n bytes of s as a NUL-terminated string */
88static char* dupn(const char* s, size_t n) {
89 char* out = malloc(n + 1);
90 if (!out)
91 return NULL;
92 if (n)
93 memcpy(out, s, n);
94 out[n] = '\0';
95 return out;
96}
97
98static int is_op_char(char c) {
99 return c == '=' || c == '!' || c == '~' || c == '>' || c == '<';
100}
101
102/* case-insensitive substring test */
103static int ci_contains(const char* hay, const char* needle) {
104 size_t nl = strlen(needle);
105 if (nl == 0)
106 return 1;
107 for (; *hay; hay++) {
108 size_t i = 0;
109 while (i < nl && hay[i] && tolower((unsigned char)hay[i]) == tolower((unsigned char)needle[i]))
110 i++;
111 if (i == nl)
112 return 1;
113 }
114 return 0;
115}
116
117/* advance to the next token */
118static void lex_next(PARSER* P) {
119 const char* s = P->p;
120 if (P->tval) {
121 free(P->tval);
122 P->tval = NULL;
123 }
124 while (*s == ' ' || *s == '\t' || *s == '\r' || *s == '\n')
125 s++;
126 if (*s == '\0') {
127 P->tok = TK_END;
128 P->p = s;
129 return;
130 }
131 if (*s == '(') {
132 P->tok = TK_LP;
133 P->p = s + 1;
134 return;
135 }
136 if (*s == ')') {
137 P->tok = TK_RP;
138 P->p = s + 1;
139 return;
140 }
141 /* operators, longest match first */
142 if (s[0] == '>' && s[1] == '=') {
143 P->tok = TK_OP;
144 P->top = OP_GE;
145 P->p = s + 2;
146 return;
147 }
148 if (s[0] == '<' && s[1] == '=') {
149 P->tok = TK_OP;
150 P->top = OP_LE;
151 P->p = s + 2;
152 return;
153 }
154 if (s[0] == '=' && s[1] == '~') {
155 P->tok = TK_OP;
156 P->top = OP_REGEX;
157 P->p = s + 2;
158 return;
159 }
160 if (s[0] == '!' && s[1] == '=') {
161 P->tok = TK_OP;
162 P->top = OP_NE;
163 P->p = s + 2;
164 return;
165 }
166 if (s[0] == '!' && s[1] == '~') {
167 P->tok = TK_OP;
168 P->top = OP_NCONT;
169 P->p = s + 2;
170 return;
171 }
172 if (s[0] == '=') {
173 P->tok = TK_OP;
174 P->top = OP_EQ;
175 P->p = s + 1;
176 return;
177 }
178 if (s[0] == '~') {
179 P->tok = TK_OP;
180 P->top = OP_CONT;
181 P->p = s + 1;
182 return;
183 }
184 if (s[0] == '>') {
185 P->tok = TK_OP;
186 P->top = OP_GT;
187 P->p = s + 1;
188 return;
189 }
190 if (s[0] == '<') {
191 P->tok = TK_OP;
192 P->top = OP_LT;
193 P->p = s + 1;
194 return;
195 }
196 if (*s == '"') {
197 const char* start = s + 1;
198 const char* e = start;
199 while (*e && *e != '"')
200 e++;
201 P->tval = dupn(start, (size_t)(e - start));
202 P->tok = TK_WORD;
203 P->p = (*e == '"') ? e + 1 : e;
204 return;
205 }
206 /* bareword: a run of non-space, non-paren, non-operator, non-quote bytes */
207 {
208 const char* start = s;
209 while (*s && !isspace((unsigned char)*s) && *s != '(' && *s != ')' && *s != '"' && !is_op_char(*s))
210 s++;
211 P->tval = dupn(start, (size_t)(s - start));
212 P->p = s;
213 if (P->tval && strcasecmp(P->tval, "and") == 0)
214 P->tok = TK_AND;
215 else if (P->tval && strcasecmp(P->tval, "or") == 0)
216 P->tok = TK_OR;
217 else if (P->tval && strcasecmp(P->tval, "not") == 0)
218 P->tok = TK_NOT;
219 else
220 P->tok = TK_WORD;
221 }
222}
223
224static QNODE* node_new(int kind) {
225 QNODE* n = calloc(1, sizeof(QNODE));
226 if (n)
227 n->kind = kind;
228 return n;
229}
230
231static void node_free(QNODE* n) {
232 if (!n)
233 return;
234 node_free(n->a);
235 node_free(n->b);
236 free(n->field);
237 free(n->value);
238 if (n->re)
239 npcre_delete(&n->re);
240 free(n);
241}
242
243static QNODE* parse_or(PARSER* P); /* forward */
244
246 if (P->tok == TK_LP) {
247 QNODE* inner;
248 lex_next(P);
249 inner = parse_or(P);
250 if (!inner)
251 return NULL;
252 if (P->tok != TK_RP) {
253 snprintf(P->err, sizeof(P->err), "expected ')'");
254 node_free(inner);
255 return NULL;
256 }
257 lex_next(P);
258 return inner;
259 }
260 if (P->tok != TK_WORD) {
261 snprintf(P->err, sizeof(P->err), "expected a field name");
262 return NULL;
263 }
264 {
265 char* field = P->tval ? dupn(P->tval, strlen(P->tval)) : NULL;
266 int op;
267 char* val;
268 QNODE* n;
269 lex_next(P);
270 if (P->tok != TK_OP) {
271 snprintf(P->err, sizeof(P->err), "expected an operator after field '%s'", field ? field : "");
272 free(field);
273 return NULL;
274 }
275 op = P->top;
276 lex_next(P);
277 if (P->tok != TK_WORD && P->tok != TK_AND && P->tok != TK_OR && P->tok != TK_NOT) {
278 snprintf(P->err, sizeof(P->err), "expected a value");
279 free(field);
280 return NULL;
281 }
282 val = P->tval ? dupn(P->tval, strlen(P->tval)) : dupn("", 0);
283 lex_next(P);
284 n = node_new(N_CMP);
285 if (!n || !field || !val) {
286 free(field);
287 free(val);
288 node_free(n);
289 return NULL;
290 }
291 if (P->fields && P->nfields > 0) {
292 /* A field the caller does not know reads as the empty string at eval
293 time, which is indistinguishable from a field that is genuinely
294 empty. So `status != 500` on a MISSPELLED field matched every record
295 instead of none, and the operator was told nothing. Caught here, where
296 they can still see it, when the caller states its vocabulary. */
297 size_t fi;
298 int known = 0;
299 for (fi = 0; fi < P->nfields; fi++) {
300 if (P->fields[fi] && strcasecmp(P->fields[fi], field) == 0) {
301 known = 1;
302 break;
303 }
304 }
305 if (!known) {
306 snprintf(P->err, sizeof(P->err), "unknown field '%s'", field);
307 free(field);
308 free(val);
309 node_free(n);
310 return NULL;
311 }
312 }
313 n->field = field;
314 n->op = op;
315 n->value = val;
316 if (op == OP_REGEX) {
317 n->re = npcre_new(val, 0);
318 if (!n->re) {
319 snprintf(P->err, sizeof(P->err), "invalid regex '%s'", val);
320 node_free(n);
321 return NULL;
322 }
323 }
324 return n;
325 }
326}
327
328static QNODE* parse_not(PARSER* P) {
329 if (P->tok == TK_NOT) {
330 QNODE* child;
331 QNODE* n;
332 lex_next(P);
333 child = parse_not(P);
334 if (!child)
335 return NULL;
336 n = node_new(N_NOT);
337 if (!n) {
338 node_free(child);
339 return NULL;
340 }
341 n->a = child;
342 return n;
343 }
344 return parse_primary(P);
345}
346
347static QNODE* parse_and(PARSER* P) {
348 QNODE* left = parse_not(P);
349 if (!left)
350 return NULL;
351 while (P->tok == TK_AND) {
352 QNODE* right;
353 QNODE* n;
354 lex_next(P);
355 right = parse_not(P);
356 if (!right) {
357 node_free(left);
358 return NULL;
359 }
360 n = node_new(N_AND);
361 if (!n) {
362 node_free(left);
364 return NULL;
365 }
366 n->a = left;
367 n->b = right;
368 left = n;
369 }
370 return left;
371}
372
373static QNODE* parse_or(PARSER* P) {
374 QNODE* left = parse_and(P);
375 if (!left)
376 return NULL;
377 while (P->tok == TK_OR) {
378 QNODE* right;
379 QNODE* n;
380 lex_next(P);
381 right = parse_and(P);
382 if (!right) {
383 node_free(left);
384 return NULL;
385 }
386 n = node_new(N_OR);
387 if (!n) {
388 node_free(left);
390 return NULL;
391 }
392 n->a = left;
393 n->b = right;
394 left = n;
395 }
396 return left;
397}
398
399N_QUERY* n_query_compile(const char* expr, char* errbuf, size_t errlen) {
400 return n_query_compile_ex(expr, NULL, 0, errbuf, errlen);
401}
402
403N_QUERY* n_query_compile_ex(const char* expr, const char* const* fields, size_t nfields, char* errbuf, size_t errlen) {
404 PARSER P;
405 N_QUERY* q;
406 QNODE* root;
407 memset(&P, 0, sizeof(P));
408 P.fields = fields;
409 P.nfields = nfields;
410 P.p = expr ? expr : "";
411 lex_next(&P);
412 if (P.tok == TK_END) {
413 /* empty expression: a match-all query */
414 if (P.tval)
415 free(P.tval);
416 q = calloc(1, sizeof(N_QUERY));
417 return q;
418 }
419 root = parse_or(&P);
420 if (!root) {
421 if (errbuf && errlen)
422 snprintf(errbuf, errlen, "%s", P.err[0] ? P.err : "parse error");
423 if (P.tval)
424 free(P.tval);
425 return NULL;
426 }
427 if (P.tok != TK_END) {
428 if (errbuf && errlen)
429 snprintf(errbuf, errlen, "unexpected trailing input");
430 node_free(root);
431 if (P.tval)
432 free(P.tval);
433 return NULL;
434 }
435 if (P.tval)
436 free(P.tval);
437 q = calloc(1, sizeof(N_QUERY));
438 if (!q) {
439 node_free(root);
440 return NULL;
441 }
442 q->root = root;
443 return q;
444}
445
446static int eval_node(const QNODE* n, N_QUERY_GET get, void* ud) {
447 if (!n)
448 return 1;
449 switch (n->kind) {
450 case N_AND:
451 return eval_node(n->a, get, ud) && eval_node(n->b, get, ud);
452 case N_OR:
453 return eval_node(n->a, get, ud) || eval_node(n->b, get, ud);
454 case N_NOT:
455 return !eval_node(n->a, get, ud);
456 case N_CMP: {
457 const char* v = get ? get(n->field, ud) : NULL;
458 if (!v)
459 v = "";
460 switch (n->op) {
461 case OP_EQ:
462 return strcasecmp(v, n->value) == 0;
463 case OP_NE:
464 return strcasecmp(v, n->value) != 0;
465 case OP_CONT:
466 return ci_contains(v, n->value);
467 case OP_NCONT:
468 return !ci_contains(v, n->value);
469 case OP_REGEX:
470 return (n->re && npcre_match((char*)v, n->re) == TRUE) ? 1 : 0;
471 case OP_GT:
472 case OP_LT:
473 case OP_GE:
474 case OP_LE: {
475 char* e1 = NULL;
476 char* e2 = NULL;
477 double a = strtod(v, &e1);
478 double b = strtod(n->value, &e2);
479 if (e1 == v || e2 == n->value)
480 return 0; /* a non-numeric side never matches */
481 if (n->op == OP_GT)
482 return a > b;
483 if (n->op == OP_LT)
484 return a < b;
485 if (n->op == OP_GE)
486 return a >= b;
487 return a <= b;
488 }
489 default:
490 return 0;
491 }
492 }
493 default:
494 return 0;
495 }
496}
497
498int n_query_eval(const N_QUERY* query, N_QUERY_GET get, void* user_data) {
499 if (!query)
500 return 1;
501 return eval_node(query->root, get, user_data) ? 1 : 0;
502}
503
504void n_query_free(N_QUERY** query) {
505 if (!query || !*query)
506 return;
507 node_free((*query)->root);
508 free(*query);
509 *query = NULL;
510}
static void right(N_GUI_CTX *gui, float cx, float cy)
right press
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:498
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:399
N_QUERY * n_query_compile_ex(const char *expr, const char *const *fields, size_t nfields, char *errbuf, size_t errlen)
Compile expr, rejecting any field not in fields.
Definition n_query.c:403
void n_query_free(N_QUERY **query)
Free a compiled query and set the pointer to NULL.
Definition n_query.c:504
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:88
struct QNODE * a
Definition n_query.c:54
@ 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
struct QNODE * b
Definition n_query.c:55
static int is_op_char(char c)
Definition n_query.c:98
static QNODE * node_new(int kind)
Definition n_query.c:224
static void lex_next(PARSER *P)
Definition n_query.c:118
const char * p
Definition n_query.c:78
static int ci_contains(const char *hay, const char *needle)
Definition n_query.c:103
int kind
Definition n_query.c:53
static int eval_node(const QNODE *n, N_QUERY_GET get, void *ud)
Definition n_query.c:446
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:347
const char *const * fields
Definition n_query.c:83
size_t nfields
Definition n_query.c:84
int top
Definition n_query.c:81
N_PCRE * re
Definition n_query.c:59
@ 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
static QNODE * parse_or(PARSER *P)
Definition n_query.c:373
char err[160]
Definition n_query.c:82
static QNODE * parse_not(PARSER *P)
Definition n_query.c:328
static void node_free(QNODE *n)
Definition n_query.c:231
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:245
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.