Nilorea Library
C utilities for networking, threading, graphics
Loading...
Searching...
No Matches
ex_query.c

N_QUERY language regression (headless): operators, AND/OR/NOT, errors.

N_QUERY language regression (headless): operators, AND/OR/NOT, errors.

Author
Castagnier Mickael
Version
1.0
/*
* Nilorea Library
* Copyright (C) 2005-2026 Castagnier Mickael
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "nilorea/n_log.h"
#include <stdio.h>
#include <string.h>
static int failures = 0;
/* one sample record exposed to the query engine through a field getter */
typedef struct {
const char* host;
const char* method;
const char* path;
const char* status;
const char* mime;
} REC;
static const char* rec_get(const char* field, void* ud) {
const REC* r = (const REC*)ud;
if (strcmp(field, "host") == 0)
return r->host;
if (strcmp(field, "method") == 0)
return r->method;
if (strcmp(field, "path") == 0)
return r->path;
if (strcmp(field, "status") == 0)
return r->status;
if (strcmp(field, "mime") == 0)
return r->mime;
return NULL; /* unknown field */
}
/* compile + evaluate; returns 1/0, or -1 on a compile error */
static int run(const char* expr, const REC* r) {
char err[160] = "";
N_QUERY* q = n_query_compile(expr, err, sizeof(err));
int m;
if (!q) {
n_log(LOG_ERR, "compile '%s' failed: %s", expr, err);
return -1;
}
m = n_query_eval(q, rec_get, (void*)r);
return m;
}
static void expect(const char* label, int got, int want) {
if (got != want) {
n_log(LOG_ERR, "%s: got %d, expected %d", label, got, want);
}
}
static void expect_parse_error(const char* expr) {
char err[160] = "";
N_QUERY* q = n_query_compile(expr, err, sizeof(err));
if (q) {
n_log(LOG_ERR, "expected '%s' to fail compiling", expr);
}
}
int main(void) {
REC r;
r.host = "api.example.com";
r.method = "GET";
r.path = "/admin/login";
r.status = "403";
r.mime = "text/html";
/* equality (case-insensitive) and contains */
expect("eq", run("host = api.example.com", &r), 1);
expect("eq ci", run("host = API.EXAMPLE.COM", &r), 1);
expect("eq no", run("host = other.test", &r), 0);
expect("contains", run("host ~ example", &r), 1);
expect("contains path", run("path ~ admin", &r), 1);
expect("not contains", run("path !~ secret", &r), 1);
expect("ne", run("mime != text/html", &r), 0);
/* numeric comparison */
expect("ge", run("status >= 400", &r), 1);
expect("gt no", run("status > 500", &r), 0);
expect("range", run("status >= 400 AND status < 500", &r), 1);
/* regex */
expect("regex", run("mime =~ ^text/", &r), 1);
expect("regex no", run("mime =~ ^json", &r), 0);
/* boolean composition and precedence (AND binds tighter than OR) */
expect("and", run("method = GET AND status >= 400", &r), 1);
expect("or", run("method = POST OR status = 403", &r), 1);
expect("not", run("NOT (status = 200)", &r), 1);
expect("paren", run("(method = POST OR method = GET) AND status = 403", &r), 1);
expect("prec", run("method = POST AND status = 200 OR host ~ example", &r), 1);
/* quoted value, unknown field, empty query */
expect("quoted", run("path = \"/admin/login\"", &r), 1);
expect("unknown field", run("bogus = x", &r), 0);
expect("empty match-all", run("", &r), 1);
expect("blank match-all", run(" ", &r), 1);
/* parse errors */
expect_parse_error("host ="); /* missing value */
expect_parse_error("host"); /* missing operator */
expect_parse_error("( host = a"); /* unbalanced paren */
expect_parse_error("host = a AND"); /* dangling AND */
expect_parse_error("mime =~ ("); /* invalid regex */
/* A field the caller does not know reads as the empty string at eval time, which
is indistinguishable from a field that is genuinely empty, so `status != 500`
on a MISSPELLED field used to match every record and say nothing. When the
caller states its vocabulary, that is caught at compile time instead. */
{
static const char* const KNOWN[] = {"host", "status", "mime"};
const size_t nknown = sizeof(KNOWN) / sizeof(KNOWN[0]);
char err[160] = "";
N_QUERY* q;
q = n_query_compile_ex("stauts != 500", KNOWN, nknown, err, sizeof(err));
if (q || !strstr(err, "stauts")) {
n_log(LOG_ERR, "FAIL: a misspelled field was accepted (err=%s)", err);
} else {
n_log(LOG_INFO, "PASS: misspelled field refused: %s", err);
}
/* a known field still compiles, case-insensitively, and so does a NOT form */
err[0] = '\0';
q = n_query_compile_ex("STATUS >= 400 AND host ~ example", KNOWN, nknown, err, sizeof(err));
if (!q) {
n_log(LOG_ERR, "FAIL: a known field was refused (err=%s)", err);
} else {
n_log(LOG_INFO, "PASS: known fields still compile");
}
/* passing no vocabulary keeps the original behaviour, so existing callers
and n_query_compile itself are unchanged */
err[0] = '\0';
q = n_query_compile_ex("stauts != 500", NULL, 0, err, sizeof(err));
if (!q) {
n_log(LOG_ERR, "FAIL: validation ran without a vocabulary (err=%s)", err);
} else {
n_log(LOG_INFO, "PASS: no vocabulary means no field validation");
}
}
if (failures) {
n_log(LOG_ERR, "ex_query: %d failure(s)", failures);
return 1;
}
n_log(LOG_NOTICE, "ex_query: all checks passed");
return 0;
}
static int expect(BITMAP *bmp, int x, int y, int r, int g, int b, const char *what)
static int failures
Definition ex_atlas.c:51
int main(void)
Definition ex_atlas.c:117
int run
Definition ex_kafka.c:56
const char * method
Definition ex_query.c:38
const char * status
Definition ex_query.c:40
const char * mime
Definition ex_query.c:41
const char * path
Definition ex_query.c:39
static const char * rec_get(const char *field, void *ud)
Definition ex_query.c:44
static void expect_parse_error(const char *expr)
Definition ex_query.c:80
const char * host
Definition ex_query.c:37
Definition ex_query.c:36
#define n_log(__LEVEL__,...)
Logging function wrapper to get line and func.
Definition n_log.h:113
#define LOG_ERR
error conditions
Definition n_log.h:100
void set_log_level(const int log_level)
Set the global log level value ( static int LOG_LEVEL ).
Definition n_log.c:123
#define LOG_NOTICE
normal but significant condition
Definition n_log.h:104
#define LOG_INFO
informational
Definition n_log.h:106
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
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
Generic log system.
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.