tranfi 0.0.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +177 -21
- package/NOTICE +8 -0
- package/README.md +627 -0
- package/app/assets/index-6quYZ5Ap.css +5 -0
- package/app/assets/index-BIAIKnrp.js +160 -0
- package/app/assets/materialdesignicons-webfont-B7mPwVP_.ttf +0 -0
- package/app/assets/materialdesignicons-webfont-CSr8KVlo.eot +0 -0
- package/app/assets/materialdesignicons-webfont-Dp5v-WZN.woff2 +0 -0
- package/app/assets/materialdesignicons-webfont-PXm3-2wK.woff +0 -0
- package/app/index.html +13 -0
- package/binding.gyp +121 -0
- package/csrc/arena.c +93 -0
- package/csrc/batch.c +976 -0
- package/csrc/buffer.c +154 -0
- package/csrc/cJSON.c +3386 -0
- package/csrc/cJSON.h +316 -0
- package/csrc/codec_csv.c +1951 -0
- package/csrc/codec_jsonl.c +1086 -0
- package/csrc/codec_table.c +248 -0
- package/csrc/codec_text.c +447 -0
- package/csrc/compiler.c +130 -0
- package/csrc/config.h +21 -0
- package/csrc/date_utils.h +94 -0
- package/csrc/dsl.c +5417 -0
- package/csrc/dsl.h +22 -0
- package/csrc/expr.c +1553 -0
- package/csrc/expr.h +58 -0
- package/csrc/internal.h +539 -0
- package/csrc/ir.c +166 -0
- package/csrc/ir.h +208 -0
- package/csrc/ir_schema.c +75 -0
- package/csrc/ir_serialize.c +166 -0
- package/csrc/ir_sql.c +1822 -0
- package/csrc/ir_validate.c +576 -0
- package/csrc/json_path.c +210 -0
- package/csrc/main.c +1241 -0
- package/csrc/memory_estimate.c +477 -0
- package/csrc/op_acf.c +283 -0
- package/csrc/op_across.c +477 -0
- package/csrc/op_anomaly.c +255 -0
- package/csrc/op_assert.c +761 -0
- package/csrc/op_bin.c +248 -0
- package/csrc/op_cast.c +523 -0
- package/csrc/op_clip.c +99 -0
- package/csrc/op_date_trunc.c +355 -0
- package/csrc/op_datetime.c +394 -0
- package/csrc/op_derive.c +216 -0
- package/csrc/op_diff.c +250 -0
- package/csrc/op_ewma.c +222 -0
- package/csrc/op_explode.c +206 -0
- package/csrc/op_fill_down.c +235 -0
- package/csrc/op_fill_null.c +268 -0
- package/csrc/op_filter.c +181 -0
- package/csrc/op_frequency.c +721 -0
- package/csrc/op_grep.c +181 -0
- package/csrc/op_group_agg.c +1956 -0
- package/csrc/op_hash.c +159 -0
- package/csrc/op_head.c +84 -0
- package/csrc/op_interpolate.c +445 -0
- package/csrc/op_join.c +2902 -0
- package/csrc/op_json_extract.c +227 -0
- package/csrc/op_json_filter.c +384 -0
- package/csrc/op_json_flatten.c +293 -0
- package/csrc/op_json_schema.c +503 -0
- package/csrc/op_label_encode.c +419 -0
- package/csrc/op_lag.c +181 -0
- package/csrc/op_lead.c +242 -0
- package/csrc/op_normalize.c +510 -0
- package/csrc/op_onehot.c +457 -0
- package/csrc/op_pivot.c +1754 -0
- package/csrc/op_quarantine.c +189 -0
- package/csrc/op_registry.c +3044 -0
- package/csrc/op_rename.c +129 -0
- package/csrc/op_replace.c +354 -0
- package/csrc/op_rleid.c +297 -0
- package/csrc/op_rowid.c +559 -0
- package/csrc/op_sample.c +158 -0
- package/csrc/op_schema.c +1341 -0
- package/csrc/op_schema_infer.c +252 -0
- package/csrc/op_select.c +340 -0
- package/csrc/op_set.c +3449 -0
- package/csrc/op_skip.c +95 -0
- package/csrc/op_sort.c +819 -0
- package/csrc/op_source_name.c +120 -0
- package/csrc/op_split.c +151 -0
- package/csrc/op_split_data.c +119 -0
- package/csrc/op_stack.c +271 -0
- package/csrc/op_stats.c +875 -0
- package/csrc/op_step.c +333 -0
- package/csrc/op_tail.c +105 -0
- package/csrc/op_tee.c +338 -0
- package/csrc/op_top.c +357 -0
- package/csrc/op_trim.c +138 -0
- package/csrc/op_unique.c +1343 -0
- package/csrc/op_unpivot.c +193 -0
- package/csrc/op_validate.c +648 -0
- package/csrc/op_window.c +591 -0
- package/csrc/path_policy.c +85 -0
- package/csrc/pipeline.c +1088 -0
- package/csrc/recipes.c +104 -0
- package/csrc/recipes.h +27 -0
- package/csrc/report.c +506 -0
- package/csrc/report.h +22 -0
- package/csrc/selector.c +1097 -0
- package/csrc/size_utils.c +348 -0
- package/csrc/spill.c +317 -0
- package/csrc/spill.h +21 -0
- package/csrc/tranfi.h +291 -0
- package/csrc/transform.h +209 -0
- package/csrc/transform_api.c +2237 -0
- package/csrc/transform_categorical.c +923 -0
- package/csrc/transform_internal.h +472 -0
- package/csrc/transform_json.c +3812 -0
- package/csrc/transform_numeric.c +1966 -0
- package/csrc/transform_sha256.c +154 -0
- package/csrc/transform_wasm.h +162 -0
- package/csrc/transform_wasm_api.c +1373 -0
- package/csrc/wasm_api.c +218 -0
- package/napi_api.c +534 -0
- package/napi_transform.c +1648 -0
- package/napi_transform.h +8 -0
- package/package.json +64 -59
- package/scripts/install-native.js +76 -0
- package/scripts/prepack.js +64 -0
- package/scripts/sync-csrc.js +23 -0
- package/src/cli.js +190 -0
- package/src/engines/duckdb.js +142 -0
- package/src/index.js +925 -0
- package/src/memory_policy.js +411 -0
- package/src/native.js +18 -0
- package/src/pipeline.js +709 -0
- package/src/recipe_json.js +80 -0
- package/src/server.js +279 -0
- package/src/transform.js +403 -0
- package/src/transform_error.js +10 -0
- package/src/wasm.js +21 -0
- package/wasm/index.js +732 -0
- package/wasm/package.json +1 -0
- package/wasm/tranfi_core.js +0 -0
- package/wasm/transform.js +1156 -0
- package/wasm/worker.js +786 -0
- package/dist/bundle.js +0 -1
- package/index.html +0 -18
- package/src/app.css +0 -169
- package/src/app.js +0 -203
- package/src/app.vue +0 -250
- package/src/bulma-input.vue +0 -110
- package/src/common-inputs.js +0 -28
- package/src/main.js +0 -20
- package/src/transforms.js +0 -166
- package/webpack.config.js +0 -108
package/csrc/expr.c
ADDED
|
@@ -0,0 +1,1553 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* expr.c — Expression parser and evaluator for filter and derive operations.
|
|
3
|
+
*
|
|
4
|
+
* Grammar:
|
|
5
|
+
* expr = or_expr
|
|
6
|
+
* or_expr = and_expr ('or' and_expr)*
|
|
7
|
+
* and_expr = not_expr ('and' not_expr)*
|
|
8
|
+
* not_expr = 'not' not_expr | cmp_expr
|
|
9
|
+
* cmp_expr = add_expr (cmp_op add_expr)?
|
|
10
|
+
* cmp_op = '>' | '>=' | '<' | '<=' | '==' | '!='
|
|
11
|
+
* add_expr = mul_expr (('+' | '-') mul_expr)*
|
|
12
|
+
* mul_expr = unary (('*' | '/') unary)*
|
|
13
|
+
* unary = '-' unary | atom
|
|
14
|
+
* atom = NUMBER | STRING | BOOL | col_ref | '(' expr ')'
|
|
15
|
+
* col_ref = 'col(' STRING ')'
|
|
16
|
+
* NUMBER = [0-9]+('.'[0-9]+)?
|
|
17
|
+
* STRING = '\'' [^']* '\'' | '"' [^"]* '"'
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
#include "internal.h"
|
|
21
|
+
#include "date_utils.h"
|
|
22
|
+
#include <stdlib.h>
|
|
23
|
+
#include <string.h>
|
|
24
|
+
#include <ctype.h>
|
|
25
|
+
#include <stdio.h>
|
|
26
|
+
#include <math.h>
|
|
27
|
+
|
|
28
|
+
/* ---- AST node types (defined in expr.h) ---- */
|
|
29
|
+
|
|
30
|
+
#include "expr.h"
|
|
31
|
+
|
|
32
|
+
/* ---- Lexer ---- */
|
|
33
|
+
|
|
34
|
+
typedef struct {
|
|
35
|
+
const char *src;
|
|
36
|
+
size_t pos;
|
|
37
|
+
size_t len;
|
|
38
|
+
int depth_error;
|
|
39
|
+
} parser_state;
|
|
40
|
+
|
|
41
|
+
static void skip_ws(parser_state *p) {
|
|
42
|
+
while (p->pos < p->len && isspace((unsigned char)p->src[p->pos]))
|
|
43
|
+
p->pos++;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
static int match_char(parser_state *p, char c) {
|
|
47
|
+
skip_ws(p);
|
|
48
|
+
if (p->pos < p->len && p->src[p->pos] == c) {
|
|
49
|
+
p->pos++;
|
|
50
|
+
return 1;
|
|
51
|
+
}
|
|
52
|
+
return 0;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
static int match_keyword(parser_state *p, const char *kw) {
|
|
56
|
+
skip_ws(p);
|
|
57
|
+
size_t kwlen = strlen(kw);
|
|
58
|
+
if (p->pos + kwlen <= p->len &&
|
|
59
|
+
strncmp(p->src + p->pos, kw, kwlen) == 0 &&
|
|
60
|
+
(p->pos + kwlen == p->len || !isalnum((unsigned char)p->src[p->pos + kwlen]))) {
|
|
61
|
+
p->pos += kwlen;
|
|
62
|
+
return 1;
|
|
63
|
+
}
|
|
64
|
+
return 0;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
static int peek_cmp_op(parser_state *p, cmp_op *op) {
|
|
68
|
+
skip_ws(p);
|
|
69
|
+
size_t pos = p->pos;
|
|
70
|
+
if (pos >= p->len) return 0;
|
|
71
|
+
|
|
72
|
+
if (pos + 1 < p->len) {
|
|
73
|
+
if (p->src[pos] == '>' && p->src[pos + 1] == '=') { *op = CMP_GE; return 1; }
|
|
74
|
+
if (p->src[pos] == '<' && p->src[pos + 1] == '=') { *op = CMP_LE; return 1; }
|
|
75
|
+
if (p->src[pos] == '=' && p->src[pos + 1] == '=') { *op = CMP_EQ; return 1; }
|
|
76
|
+
if (p->src[pos] == '!' && p->src[pos + 1] == '=') { *op = CMP_NE; return 1; }
|
|
77
|
+
}
|
|
78
|
+
if (p->src[pos] == '>') { *op = CMP_GT; return 1; }
|
|
79
|
+
if (p->src[pos] == '<') { *op = CMP_LT; return 1; }
|
|
80
|
+
return 0;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
static void consume_cmp_op(parser_state *p, cmp_op op) {
|
|
84
|
+
skip_ws(p);
|
|
85
|
+
switch (op) {
|
|
86
|
+
case CMP_GE: case CMP_LE: case CMP_EQ: case CMP_NE:
|
|
87
|
+
p->pos += 2; break;
|
|
88
|
+
case CMP_GT: case CMP_LT:
|
|
89
|
+
p->pos += 1; break;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/* ---- Recursive descent parser ---- */
|
|
94
|
+
|
|
95
|
+
#define TF_EXPR_MAX_DEPTH 256
|
|
96
|
+
|
|
97
|
+
static tf_expr *parse_expr_d(parser_state *p, unsigned depth);
|
|
98
|
+
|
|
99
|
+
static int expr_depth_exceeded(parser_state *p, unsigned depth) {
|
|
100
|
+
if (depth <= TF_EXPR_MAX_DEPTH) return 0;
|
|
101
|
+
p->depth_error = 1;
|
|
102
|
+
return 1;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
static tf_expr *make_expr(expr_kind kind) {
|
|
106
|
+
tf_expr *e = calloc(1, sizeof(tf_expr));
|
|
107
|
+
if (e) e->kind = kind;
|
|
108
|
+
return e;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
static tf_expr *parse_string_literal(parser_state *p) {
|
|
112
|
+
skip_ws(p);
|
|
113
|
+
char quote = p->src[p->pos];
|
|
114
|
+
if (quote != '\'' && quote != '"') return NULL;
|
|
115
|
+
p->pos++; /* skip opening quote */
|
|
116
|
+
|
|
117
|
+
size_t start = p->pos;
|
|
118
|
+
while (p->pos < p->len && p->src[p->pos] != quote)
|
|
119
|
+
p->pos++;
|
|
120
|
+
if (p->pos >= p->len) return NULL; /* unterminated */
|
|
121
|
+
|
|
122
|
+
size_t slen = p->pos - start;
|
|
123
|
+
p->pos++; /* skip closing quote */
|
|
124
|
+
|
|
125
|
+
tf_expr *e = make_expr(EXPR_LIT_STR);
|
|
126
|
+
if (!e) return NULL;
|
|
127
|
+
size_t alloc_len = 0;
|
|
128
|
+
if (tf_size_add(slen, 1, &alloc_len) != TF_OK) {
|
|
129
|
+
tf_expr_free(e);
|
|
130
|
+
return NULL;
|
|
131
|
+
}
|
|
132
|
+
e->lit_str = tf_mallocarray_checked(alloc_len, sizeof(char));
|
|
133
|
+
if (!e->lit_str) {
|
|
134
|
+
tf_expr_free(e);
|
|
135
|
+
return NULL;
|
|
136
|
+
}
|
|
137
|
+
memcpy(e->lit_str, p->src + start, slen);
|
|
138
|
+
e->lit_str[slen] = '\0';
|
|
139
|
+
return e;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/* Parse a comma-separated argument list inside parentheses.
|
|
143
|
+
* Assumes '(' has already been consumed. Consumes the closing ')'.
|
|
144
|
+
* Returns allocated array of expressions, sets *n_args. */
|
|
145
|
+
static tf_expr **parse_arg_list(parser_state *p, int *n_args, unsigned depth) {
|
|
146
|
+
*n_args = 0;
|
|
147
|
+
size_t cap = 4;
|
|
148
|
+
tf_expr **args = tf_mallocarray_checked(cap, sizeof(tf_expr *));
|
|
149
|
+
if (!args) return NULL;
|
|
150
|
+
|
|
151
|
+
skip_ws(p);
|
|
152
|
+
/* Empty arg list: func() */
|
|
153
|
+
if (p->pos < p->len && p->src[p->pos] == ')') {
|
|
154
|
+
p->pos++;
|
|
155
|
+
return args;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
for (;;) {
|
|
159
|
+
tf_expr *arg = parse_expr_d(p, depth);
|
|
160
|
+
if (!arg) goto fail;
|
|
161
|
+
if ((size_t)*n_args >= cap) {
|
|
162
|
+
size_t need = 0;
|
|
163
|
+
size_t new_cap = 0;
|
|
164
|
+
if (tf_size_add((size_t)*n_args, 1, &need) != TF_OK ||
|
|
165
|
+
tf_size_grow_pow2(cap, need, 4, &new_cap) != TF_OK) {
|
|
166
|
+
tf_expr_free(arg);
|
|
167
|
+
goto fail;
|
|
168
|
+
}
|
|
169
|
+
tf_expr **na = tf_reallocarray_checked(args, new_cap, sizeof(tf_expr *));
|
|
170
|
+
if (!na) { tf_expr_free(arg); goto fail; }
|
|
171
|
+
args = na;
|
|
172
|
+
cap = new_cap;
|
|
173
|
+
}
|
|
174
|
+
args[(*n_args)++] = arg;
|
|
175
|
+
skip_ws(p);
|
|
176
|
+
if (p->pos < p->len && p->src[p->pos] == ',') {
|
|
177
|
+
p->pos++;
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
if (!match_char(p, ')')) goto fail;
|
|
183
|
+
return args;
|
|
184
|
+
|
|
185
|
+
fail:
|
|
186
|
+
for (int i = 0; i < *n_args; i++) tf_expr_free(args[i]);
|
|
187
|
+
free(args);
|
|
188
|
+
*n_args = 0;
|
|
189
|
+
return NULL;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
static tf_expr *parse_atom(parser_state *p, unsigned depth) {
|
|
193
|
+
if (expr_depth_exceeded(p, depth)) return NULL;
|
|
194
|
+
skip_ws(p);
|
|
195
|
+
if (p->pos >= p->len) return NULL;
|
|
196
|
+
|
|
197
|
+
/* Parenthesized expression */
|
|
198
|
+
if (p->src[p->pos] == '(') {
|
|
199
|
+
p->pos++;
|
|
200
|
+
tf_expr *e = parse_expr_d(p, depth + 1);
|
|
201
|
+
if (!e) return NULL;
|
|
202
|
+
if (!match_char(p, ')')) { tf_expr_free(e); return NULL; }
|
|
203
|
+
return e;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/* String literal */
|
|
207
|
+
if (p->src[p->pos] == '\'' || p->src[p->pos] == '"') {
|
|
208
|
+
return parse_string_literal(p);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/* Number (but NOT negative — that's handled in parse_unary) */
|
|
212
|
+
if (isdigit((unsigned char)p->src[p->pos])) {
|
|
213
|
+
size_t start = p->pos;
|
|
214
|
+
while (p->pos < p->len && isdigit((unsigned char)p->src[p->pos])) p->pos++;
|
|
215
|
+
int is_float = 0;
|
|
216
|
+
if (p->pos < p->len && p->src[p->pos] == '.') {
|
|
217
|
+
is_float = 1;
|
|
218
|
+
p->pos++;
|
|
219
|
+
while (p->pos < p->len && isdigit((unsigned char)p->src[p->pos])) p->pos++;
|
|
220
|
+
}
|
|
221
|
+
/* Also handle e/E notation */
|
|
222
|
+
if (p->pos < p->len && (p->src[p->pos] == 'e' || p->src[p->pos] == 'E')) {
|
|
223
|
+
is_float = 1;
|
|
224
|
+
p->pos++;
|
|
225
|
+
if (p->pos < p->len && (p->src[p->pos] == '+' || p->src[p->pos] == '-')) p->pos++;
|
|
226
|
+
while (p->pos < p->len && isdigit((unsigned char)p->src[p->pos])) p->pos++;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
size_t numlen = p->pos - start;
|
|
230
|
+
size_t alloc_len = 0;
|
|
231
|
+
if (tf_size_add(numlen, 1, &alloc_len) != TF_OK) return NULL;
|
|
232
|
+
char *numstr = tf_mallocarray_checked(alloc_len, sizeof(char));
|
|
233
|
+
if (!numstr) return NULL;
|
|
234
|
+
memcpy(numstr, p->src + start, numlen);
|
|
235
|
+
numstr[numlen] = '\0';
|
|
236
|
+
|
|
237
|
+
tf_expr *e;
|
|
238
|
+
if (is_float) {
|
|
239
|
+
e = make_expr(EXPR_LIT_FLOAT);
|
|
240
|
+
if (e) e->lit_float = strtod(numstr, NULL);
|
|
241
|
+
} else {
|
|
242
|
+
e = make_expr(EXPR_LIT_INT);
|
|
243
|
+
if (e) e->lit_int = strtoll(numstr, NULL, 10);
|
|
244
|
+
}
|
|
245
|
+
free(numstr);
|
|
246
|
+
return e;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/* Identifier: could be col(), function call, or keyword */
|
|
250
|
+
if (isalpha((unsigned char)p->src[p->pos]) || p->src[p->pos] == '_') {
|
|
251
|
+
size_t start = p->pos;
|
|
252
|
+
while (p->pos < p->len &&
|
|
253
|
+
(isalnum((unsigned char)p->src[p->pos]) || p->src[p->pos] == '_'))
|
|
254
|
+
p->pos++;
|
|
255
|
+
size_t id_len = p->pos - start;
|
|
256
|
+
|
|
257
|
+
/* Check for keywords — don't consume them here, let caller handle */
|
|
258
|
+
/* "and", "or", "not" are handled at higher precedence levels */
|
|
259
|
+
if ((id_len == 3 && strncmp(p->src + start, "and", 3) == 0) ||
|
|
260
|
+
(id_len == 2 && strncmp(p->src + start, "or", 2) == 0) ||
|
|
261
|
+
(id_len == 3 && strncmp(p->src + start, "not", 3) == 0)) {
|
|
262
|
+
p->pos = start; /* rewind */
|
|
263
|
+
return NULL;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
size_t name_len = 0;
|
|
267
|
+
if (tf_size_add(id_len, 1, &name_len) != TF_OK) return NULL;
|
|
268
|
+
char *name = tf_mallocarray_checked(name_len, sizeof(char));
|
|
269
|
+
if (!name) return NULL;
|
|
270
|
+
memcpy(name, p->src + start, id_len);
|
|
271
|
+
name[id_len] = '\0';
|
|
272
|
+
|
|
273
|
+
skip_ws(p);
|
|
274
|
+
if (p->pos < p->len && p->src[p->pos] == '(') {
|
|
275
|
+
p->pos++; /* consume '(' */
|
|
276
|
+
|
|
277
|
+
/* Special case: col() — column reference */
|
|
278
|
+
if (strcmp(name, "col") == 0) {
|
|
279
|
+
free(name);
|
|
280
|
+
skip_ws(p);
|
|
281
|
+
if (p->pos >= p->len) return NULL;
|
|
282
|
+
|
|
283
|
+
char *col_name = NULL;
|
|
284
|
+
if (p->src[p->pos] == '\'' || p->src[p->pos] == '"') {
|
|
285
|
+
tf_expr *str = parse_string_literal(p);
|
|
286
|
+
if (!str) return NULL;
|
|
287
|
+
col_name = str->lit_str;
|
|
288
|
+
str->lit_str = NULL;
|
|
289
|
+
tf_expr_free(str);
|
|
290
|
+
} else {
|
|
291
|
+
size_t cstart = p->pos;
|
|
292
|
+
while (p->pos < p->len &&
|
|
293
|
+
(isalnum((unsigned char)p->src[p->pos]) || p->src[p->pos] == '_'))
|
|
294
|
+
p->pos++;
|
|
295
|
+
if (p->pos == cstart) return NULL;
|
|
296
|
+
size_t col_len = p->pos - cstart;
|
|
297
|
+
size_t col_alloc_len = 0;
|
|
298
|
+
if (tf_size_add(col_len, 1, &col_alloc_len) != TF_OK) return NULL;
|
|
299
|
+
col_name = tf_mallocarray_checked(col_alloc_len, sizeof(char));
|
|
300
|
+
if (!col_name) return NULL;
|
|
301
|
+
memcpy(col_name, p->src + cstart, col_len);
|
|
302
|
+
col_name[col_len] = '\0';
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (!match_char(p, ')')) { free(col_name); return NULL; }
|
|
306
|
+
tf_expr *e = make_expr(EXPR_COL_REF);
|
|
307
|
+
if (!e) { free(col_name); return NULL; }
|
|
308
|
+
e->col_name = col_name;
|
|
309
|
+
return e;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/* General function call: name(arg1, arg2, ...) */
|
|
313
|
+
int n_args = 0;
|
|
314
|
+
tf_expr **args = parse_arg_list(p, &n_args, depth + 1);
|
|
315
|
+
if (!args) { free(name); return NULL; }
|
|
316
|
+
|
|
317
|
+
tf_expr *e = make_expr(EXPR_FUNC_CALL);
|
|
318
|
+
if (!e) {
|
|
319
|
+
for (int i = 0; i < n_args; i++) tf_expr_free(args[i]);
|
|
320
|
+
free(args);
|
|
321
|
+
free(name);
|
|
322
|
+
return NULL;
|
|
323
|
+
}
|
|
324
|
+
e->func.name = name;
|
|
325
|
+
e->func.args = args;
|
|
326
|
+
e->func.n_args = n_args;
|
|
327
|
+
return e;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (strcmp(name, "true") == 0 || strcmp(name, "false") == 0) {
|
|
331
|
+
tf_expr *e = make_expr(EXPR_LIT_BOOL);
|
|
332
|
+
if (!e) { free(name); return NULL; }
|
|
333
|
+
e->lit_bool = (strcmp(name, "true") == 0);
|
|
334
|
+
free(name);
|
|
335
|
+
return e;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/* Bare identifier — treat as column reference */
|
|
339
|
+
tf_expr *e = make_expr(EXPR_COL_REF);
|
|
340
|
+
if (!e) { free(name); return NULL; }
|
|
341
|
+
e->col_name = name;
|
|
342
|
+
return e;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return NULL; /* parse error */
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/* Unary minus: -expr */
|
|
349
|
+
static tf_expr *parse_unary(parser_state *p, unsigned depth) {
|
|
350
|
+
if (expr_depth_exceeded(p, depth)) return NULL;
|
|
351
|
+
skip_ws(p);
|
|
352
|
+
if (p->pos < p->len && p->src[p->pos] == '-') {
|
|
353
|
+
/* Check it's not a comparison op like != */
|
|
354
|
+
p->pos++;
|
|
355
|
+
tf_expr *child = parse_unary(p, depth + 1);
|
|
356
|
+
if (!child) return NULL;
|
|
357
|
+
/* Optimize: fold literal negation */
|
|
358
|
+
if (child->kind == EXPR_LIT_INT) {
|
|
359
|
+
child->lit_int = -child->lit_int;
|
|
360
|
+
return child;
|
|
361
|
+
}
|
|
362
|
+
if (child->kind == EXPR_LIT_FLOAT) {
|
|
363
|
+
child->lit_float = -child->lit_float;
|
|
364
|
+
return child;
|
|
365
|
+
}
|
|
366
|
+
tf_expr *e = make_expr(EXPR_NEG);
|
|
367
|
+
if (!e) { tf_expr_free(child); return NULL; }
|
|
368
|
+
e->child = child;
|
|
369
|
+
return e;
|
|
370
|
+
}
|
|
371
|
+
return parse_atom(p, depth);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/* Multiplicative: unary (('*' | '/') unary)* */
|
|
375
|
+
static tf_expr *parse_mul(parser_state *p, unsigned depth) {
|
|
376
|
+
tf_expr *left = parse_unary(p, depth);
|
|
377
|
+
if (!left) return NULL;
|
|
378
|
+
|
|
379
|
+
for (;;) {
|
|
380
|
+
skip_ws(p);
|
|
381
|
+
if (p->pos >= p->len) break;
|
|
382
|
+
char op_char = p->src[p->pos];
|
|
383
|
+
if (op_char != '*' && op_char != '/') break;
|
|
384
|
+
p->pos++;
|
|
385
|
+
|
|
386
|
+
tf_expr *right = parse_unary(p, depth);
|
|
387
|
+
if (!right) { tf_expr_free(left); return NULL; }
|
|
388
|
+
|
|
389
|
+
expr_kind kind = (op_char == '*') ? EXPR_MUL : EXPR_DIV;
|
|
390
|
+
tf_expr *e = make_expr(kind);
|
|
391
|
+
if (!e) { tf_expr_free(left); tf_expr_free(right); return NULL; }
|
|
392
|
+
e->binary.left = left;
|
|
393
|
+
e->binary.right = right;
|
|
394
|
+
left = e;
|
|
395
|
+
}
|
|
396
|
+
return left;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/* Additive: mul_expr (('+' | '-') mul_expr)* */
|
|
400
|
+
static tf_expr *parse_add(parser_state *p, unsigned depth) {
|
|
401
|
+
tf_expr *left = parse_mul(p, depth);
|
|
402
|
+
if (!left) return NULL;
|
|
403
|
+
|
|
404
|
+
for (;;) {
|
|
405
|
+
skip_ws(p);
|
|
406
|
+
if (p->pos >= p->len) break;
|
|
407
|
+
char op_char = p->src[p->pos];
|
|
408
|
+
if (op_char != '+' && op_char != '-') break;
|
|
409
|
+
/* Disambiguate: '-' followed by digit could be subtraction.
|
|
410
|
+
* But we need to avoid consuming '-' that's part of a comparison like '<=' etc.
|
|
411
|
+
* Since we're at the add level, '+' and '-' are always arithmetic. */
|
|
412
|
+
p->pos++;
|
|
413
|
+
|
|
414
|
+
tf_expr *right = parse_mul(p, depth);
|
|
415
|
+
if (!right) { tf_expr_free(left); return NULL; }
|
|
416
|
+
|
|
417
|
+
expr_kind kind = (op_char == '+') ? EXPR_ADD : EXPR_SUB;
|
|
418
|
+
tf_expr *e = make_expr(kind);
|
|
419
|
+
if (!e) { tf_expr_free(left); tf_expr_free(right); return NULL; }
|
|
420
|
+
e->binary.left = left;
|
|
421
|
+
e->binary.right = right;
|
|
422
|
+
left = e;
|
|
423
|
+
}
|
|
424
|
+
return left;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
static tf_expr *parse_cmp(parser_state *p, unsigned depth) {
|
|
428
|
+
tf_expr *left = parse_add(p, depth);
|
|
429
|
+
if (!left) return NULL;
|
|
430
|
+
|
|
431
|
+
cmp_op op;
|
|
432
|
+
if (peek_cmp_op(p, &op)) {
|
|
433
|
+
consume_cmp_op(p, op);
|
|
434
|
+
tf_expr *right = parse_add(p, depth);
|
|
435
|
+
if (!right) { tf_expr_free(left); return NULL; }
|
|
436
|
+
|
|
437
|
+
tf_expr *e = make_expr(EXPR_CMP);
|
|
438
|
+
if (!e) { tf_expr_free(left); tf_expr_free(right); return NULL; }
|
|
439
|
+
e->cmp.left = left;
|
|
440
|
+
e->cmp.right = right;
|
|
441
|
+
e->cmp.op = op;
|
|
442
|
+
return e;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
return left;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
static tf_expr *parse_not(parser_state *p, unsigned depth) {
|
|
449
|
+
if (expr_depth_exceeded(p, depth)) return NULL;
|
|
450
|
+
if (match_keyword(p, "not")) {
|
|
451
|
+
tf_expr *child = parse_not(p, depth + 1);
|
|
452
|
+
if (!child) return NULL;
|
|
453
|
+
tf_expr *e = make_expr(EXPR_NOT);
|
|
454
|
+
if (!e) { tf_expr_free(child); return NULL; }
|
|
455
|
+
e->child = child;
|
|
456
|
+
return e;
|
|
457
|
+
}
|
|
458
|
+
return parse_cmp(p, depth);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
static tf_expr *parse_and(parser_state *p, unsigned depth) {
|
|
462
|
+
tf_expr *left = parse_not(p, depth);
|
|
463
|
+
if (!left) return NULL;
|
|
464
|
+
while (match_keyword(p, "and")) {
|
|
465
|
+
tf_expr *right = parse_not(p, depth);
|
|
466
|
+
if (!right) { tf_expr_free(left); return NULL; }
|
|
467
|
+
tf_expr *e = make_expr(EXPR_AND);
|
|
468
|
+
if (!e) { tf_expr_free(left); tf_expr_free(right); return NULL; }
|
|
469
|
+
e->binary.left = left;
|
|
470
|
+
e->binary.right = right;
|
|
471
|
+
left = e;
|
|
472
|
+
}
|
|
473
|
+
return left;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
static tf_expr *parse_or(parser_state *p, unsigned depth) {
|
|
477
|
+
tf_expr *left = parse_and(p, depth);
|
|
478
|
+
if (!left) return NULL;
|
|
479
|
+
while (match_keyword(p, "or")) {
|
|
480
|
+
tf_expr *right = parse_and(p, depth);
|
|
481
|
+
if (!right) { tf_expr_free(left); return NULL; }
|
|
482
|
+
tf_expr *e = make_expr(EXPR_OR);
|
|
483
|
+
if (!e) { tf_expr_free(left); tf_expr_free(right); return NULL; }
|
|
484
|
+
e->binary.left = left;
|
|
485
|
+
e->binary.right = right;
|
|
486
|
+
left = e;
|
|
487
|
+
}
|
|
488
|
+
return left;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
static tf_expr *parse_expr_d(parser_state *p, unsigned depth) {
|
|
492
|
+
if (expr_depth_exceeded(p, depth)) return NULL;
|
|
493
|
+
return parse_or(p, depth);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
tf_expr *tf_expr_parse(const char *text) {
|
|
497
|
+
if (!text || !*text) {
|
|
498
|
+
tf_set_last_error("expression: empty expression");
|
|
499
|
+
return NULL;
|
|
500
|
+
}
|
|
501
|
+
parser_state p = { .src = text, .pos = 0, .len = strlen(text), .depth_error = 0 };
|
|
502
|
+
tf_expr *e = parse_expr_d(&p, 0);
|
|
503
|
+
if (!e) {
|
|
504
|
+
tf_set_last_error(p.depth_error ? "expression: nesting too deep" : "expression: invalid syntax");
|
|
505
|
+
return NULL;
|
|
506
|
+
}
|
|
507
|
+
/* Ensure we consumed all input */
|
|
508
|
+
skip_ws(&p);
|
|
509
|
+
if (p.pos < p.len) {
|
|
510
|
+
char buf[128];
|
|
511
|
+
snprintf(buf, sizeof(buf), "expression: unexpected input near byte %zu", p.pos);
|
|
512
|
+
tf_set_last_error(buf);
|
|
513
|
+
tf_expr_free(e);
|
|
514
|
+
return NULL;
|
|
515
|
+
}
|
|
516
|
+
return e;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
void tf_expr_free(tf_expr *e) {
|
|
520
|
+
if (!e) return;
|
|
521
|
+
switch (e->kind) {
|
|
522
|
+
case EXPR_LIT_STR: free(e->lit_str); break;
|
|
523
|
+
case EXPR_COL_REF: free(e->col_name); break;
|
|
524
|
+
case EXPR_CMP:
|
|
525
|
+
tf_expr_free(e->cmp.left);
|
|
526
|
+
tf_expr_free(e->cmp.right);
|
|
527
|
+
break;
|
|
528
|
+
case EXPR_AND: case EXPR_OR:
|
|
529
|
+
case EXPR_ADD: case EXPR_SUB: case EXPR_MUL: case EXPR_DIV:
|
|
530
|
+
tf_expr_free(e->binary.left);
|
|
531
|
+
tf_expr_free(e->binary.right);
|
|
532
|
+
break;
|
|
533
|
+
case EXPR_NOT: case EXPR_NEG:
|
|
534
|
+
tf_expr_free(e->child);
|
|
535
|
+
break;
|
|
536
|
+
case EXPR_FUNC_CALL:
|
|
537
|
+
free(e->func.name);
|
|
538
|
+
for (int i = 0; i < e->func.n_args; i++)
|
|
539
|
+
tf_expr_free(e->func.args[i]);
|
|
540
|
+
free(e->func.args);
|
|
541
|
+
break;
|
|
542
|
+
default: break;
|
|
543
|
+
}
|
|
544
|
+
free(e);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/* ---- Evaluator ---- */
|
|
548
|
+
|
|
549
|
+
/* A runtime value for expression evaluation.
|
|
550
|
+
* String values either point to batch/literal memory (owned=0)
|
|
551
|
+
* or to the scratch buffer (owned=0, in scratch). */
|
|
552
|
+
typedef struct {
|
|
553
|
+
enum { VAL_NULL, VAL_INT, VAL_FLOAT, VAL_STR, VAL_BOOL, VAL_DATE, VAL_TIMESTAMP } tag;
|
|
554
|
+
union {
|
|
555
|
+
int64_t i;
|
|
556
|
+
double f;
|
|
557
|
+
const char *s;
|
|
558
|
+
int b;
|
|
559
|
+
int32_t date; /* days since epoch */
|
|
560
|
+
int64_t ts; /* microseconds since epoch */
|
|
561
|
+
};
|
|
562
|
+
} eval_val;
|
|
563
|
+
|
|
564
|
+
/* Scratch buffer for string function results.
|
|
565
|
+
* Uses a ring buffer approach: each call gets a slot, wrapping around. */
|
|
566
|
+
#define SCRATCH_SLOTS 8
|
|
567
|
+
#define SCRATCH_SLOT_SIZE 4096
|
|
568
|
+
static _Thread_local char scratch_buf[SCRATCH_SLOTS][SCRATCH_SLOT_SIZE];
|
|
569
|
+
static _Thread_local size_t scratch_idx = 0;
|
|
570
|
+
|
|
571
|
+
static char *scratch_alloc(void) {
|
|
572
|
+
char *slot = scratch_buf[scratch_idx % SCRATCH_SLOTS];
|
|
573
|
+
scratch_idx++;
|
|
574
|
+
return slot;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
static eval_val eval_node(const tf_expr *e, const tf_batch *batch, size_t row);
|
|
578
|
+
|
|
579
|
+
static eval_val val_null(void) { return (eval_val){ .tag = VAL_NULL }; }
|
|
580
|
+
static eval_val val_int(int64_t v) { return (eval_val){ .tag = VAL_INT, .i = v }; }
|
|
581
|
+
static eval_val val_float(double v) { return (eval_val){ .tag = VAL_FLOAT, .f = v }; }
|
|
582
|
+
static eval_val val_str(const char *v) { return (eval_val){ .tag = VAL_STR, .s = v }; }
|
|
583
|
+
static eval_val val_bool(int v) { return (eval_val){ .tag = VAL_BOOL, .b = v }; }
|
|
584
|
+
static eval_val val_date(int32_t v) { return (eval_val){ .tag = VAL_DATE, .date = v }; }
|
|
585
|
+
static eval_val val_timestamp(int64_t v) { return (eval_val){ .tag = VAL_TIMESTAMP, .ts = v }; }
|
|
586
|
+
|
|
587
|
+
typedef enum {
|
|
588
|
+
TF_EXPR_TRUNC_YEAR,
|
|
589
|
+
TF_EXPR_TRUNC_MONTH,
|
|
590
|
+
TF_EXPR_TRUNC_DAY,
|
|
591
|
+
TF_EXPR_TRUNC_HOUR,
|
|
592
|
+
TF_EXPR_TRUNC_MINUTE,
|
|
593
|
+
TF_EXPR_TRUNC_SECOND,
|
|
594
|
+
} tf_expr_trunc_level;
|
|
595
|
+
|
|
596
|
+
static int expr_parse_trunc_level(const char *s, tf_expr_trunc_level *level) {
|
|
597
|
+
if (!s || !level) return 0;
|
|
598
|
+
if (strcmp(s, "year") == 0 || strcmp(s, "years") == 0) {
|
|
599
|
+
*level = TF_EXPR_TRUNC_YEAR; return 1;
|
|
600
|
+
}
|
|
601
|
+
if (strcmp(s, "month") == 0 || strcmp(s, "months") == 0) {
|
|
602
|
+
*level = TF_EXPR_TRUNC_MONTH; return 1;
|
|
603
|
+
}
|
|
604
|
+
if (strcmp(s, "day") == 0 || strcmp(s, "days") == 0) {
|
|
605
|
+
*level = TF_EXPR_TRUNC_DAY; return 1;
|
|
606
|
+
}
|
|
607
|
+
if (strcmp(s, "hour") == 0 || strcmp(s, "hours") == 0) {
|
|
608
|
+
*level = TF_EXPR_TRUNC_HOUR; return 1;
|
|
609
|
+
}
|
|
610
|
+
if (strcmp(s, "minute") == 0 || strcmp(s, "minutes") == 0) {
|
|
611
|
+
*level = TF_EXPR_TRUNC_MINUTE; return 1;
|
|
612
|
+
}
|
|
613
|
+
if (strcmp(s, "second") == 0 || strcmp(s, "seconds") == 0) {
|
|
614
|
+
*level = TF_EXPR_TRUNC_SECOND; return 1;
|
|
615
|
+
}
|
|
616
|
+
return 0;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
static int parse_temporal_string(const char *s, int *y, int *mo, int *d,
|
|
620
|
+
int *h, int *mi, int *se, int *frac_us,
|
|
621
|
+
int *has_time) {
|
|
622
|
+
if (!s) return 0;
|
|
623
|
+
*y = *mo = *d = *h = *mi = *se = *frac_us = 0;
|
|
624
|
+
*has_time = 0;
|
|
625
|
+
|
|
626
|
+
int n = sscanf(s, "%d-%d-%dT%d:%d:%d", y, mo, d, h, mi, se);
|
|
627
|
+
if (n >= 6) {
|
|
628
|
+
*has_time = 1;
|
|
629
|
+
const char *dot = strchr(s, '.');
|
|
630
|
+
if (dot) {
|
|
631
|
+
int frac = 0, digits = 0;
|
|
632
|
+
dot++;
|
|
633
|
+
while (digits < 6 && dot[digits] >= '0' && dot[digits] <= '9') {
|
|
634
|
+
frac = frac * 10 + (dot[digits] - '0');
|
|
635
|
+
digits++;
|
|
636
|
+
}
|
|
637
|
+
while (digits > 0 && digits < 6) { frac *= 10; digits++; }
|
|
638
|
+
*frac_us = frac;
|
|
639
|
+
}
|
|
640
|
+
return 1;
|
|
641
|
+
}
|
|
642
|
+
n = sscanf(s, "%d-%d-%d %d:%d:%d", y, mo, d, h, mi, se);
|
|
643
|
+
if (n >= 6) {
|
|
644
|
+
*has_time = 1;
|
|
645
|
+
const char *dot = strchr(s, '.');
|
|
646
|
+
if (dot) {
|
|
647
|
+
int frac = 0, digits = 0;
|
|
648
|
+
dot++;
|
|
649
|
+
while (digits < 6 && dot[digits] >= '0' && dot[digits] <= '9') {
|
|
650
|
+
frac = frac * 10 + (dot[digits] - '0');
|
|
651
|
+
digits++;
|
|
652
|
+
}
|
|
653
|
+
while (digits > 0 && digits < 6) { frac *= 10; digits++; }
|
|
654
|
+
*frac_us = frac;
|
|
655
|
+
}
|
|
656
|
+
return 1;
|
|
657
|
+
}
|
|
658
|
+
n = sscanf(s, "%d-%d-%d", y, mo, d);
|
|
659
|
+
return n >= 3;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
static int eval_temporal_parts(eval_val v, int *y, int *mo, int *d,
|
|
663
|
+
int *h, int *mi, int *se, int *frac_us,
|
|
664
|
+
int32_t *date_days, int64_t *timestamp_us,
|
|
665
|
+
int *is_timestamp, int *is_string) {
|
|
666
|
+
*y = *mo = *d = *h = *mi = *se = *frac_us = 0;
|
|
667
|
+
*date_days = 0;
|
|
668
|
+
*timestamp_us = 0;
|
|
669
|
+
*is_timestamp = 0;
|
|
670
|
+
*is_string = 0;
|
|
671
|
+
|
|
672
|
+
if (v.tag == VAL_DATE) {
|
|
673
|
+
tf_date_to_ymd(v.date, y, mo, d);
|
|
674
|
+
*date_days = v.date;
|
|
675
|
+
return 1;
|
|
676
|
+
}
|
|
677
|
+
if (v.tag == VAL_TIMESTAMP) {
|
|
678
|
+
tf_timestamp_to_parts(v.ts, y, mo, d, h, mi, se, frac_us);
|
|
679
|
+
*date_days = tf_date_from_ymd(*y, *mo, *d);
|
|
680
|
+
*timestamp_us = v.ts;
|
|
681
|
+
*is_timestamp = 1;
|
|
682
|
+
return 1;
|
|
683
|
+
}
|
|
684
|
+
if (v.tag == VAL_STR) {
|
|
685
|
+
int has_time = 0;
|
|
686
|
+
if (!parse_temporal_string(v.s, y, mo, d, h, mi, se, frac_us, &has_time)) return 0;
|
|
687
|
+
*date_days = tf_date_from_ymd(*y, *mo, *d);
|
|
688
|
+
if (has_time) {
|
|
689
|
+
*timestamp_us = tf_timestamp_from_parts(*y, *mo, *d, *h, *mi, *se, *frac_us);
|
|
690
|
+
*is_timestamp = 1;
|
|
691
|
+
}
|
|
692
|
+
*is_string = 1;
|
|
693
|
+
return 1;
|
|
694
|
+
}
|
|
695
|
+
return 0;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
static int32_t expr_trunc_date(int32_t days, tf_expr_trunc_level level) {
|
|
699
|
+
int y, mo, d;
|
|
700
|
+
tf_date_to_ymd(days, &y, &mo, &d);
|
|
701
|
+
switch (level) {
|
|
702
|
+
case TF_EXPR_TRUNC_YEAR: return tf_date_from_ymd(y, 1, 1);
|
|
703
|
+
case TF_EXPR_TRUNC_MONTH: return tf_date_from_ymd(y, mo, 1);
|
|
704
|
+
default: return days;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
static int64_t expr_trunc_timestamp(int64_t us, tf_expr_trunc_level level) {
|
|
709
|
+
int y, mo, d, h, mi, se, frac;
|
|
710
|
+
tf_timestamp_to_parts(us, &y, &mo, &d, &h, &mi, &se, &frac);
|
|
711
|
+
switch (level) {
|
|
712
|
+
case TF_EXPR_TRUNC_YEAR: return tf_timestamp_from_parts(y, 1, 1, 0, 0, 0, 0);
|
|
713
|
+
case TF_EXPR_TRUNC_MONTH: return tf_timestamp_from_parts(y, mo, 1, 0, 0, 0, 0);
|
|
714
|
+
case TF_EXPR_TRUNC_DAY: return tf_timestamp_from_parts(y, mo, d, 0, 0, 0, 0);
|
|
715
|
+
case TF_EXPR_TRUNC_HOUR: return tf_timestamp_from_parts(y, mo, d, h, 0, 0, 0);
|
|
716
|
+
case TF_EXPR_TRUNC_MINUTE: return tf_timestamp_from_parts(y, mo, d, h, mi, 0, 0);
|
|
717
|
+
case TF_EXPR_TRUNC_SECOND: return tf_timestamp_from_parts(y, mo, d, h, mi, se, 0);
|
|
718
|
+
}
|
|
719
|
+
return us;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
static int64_t timestamp_epoch_seconds(int64_t us) {
|
|
723
|
+
if (us >= 0) return us / 1000000LL;
|
|
724
|
+
return -(((-us) + 999999LL) / 1000000LL);
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
/* Convert to double for numeric comparison */
|
|
728
|
+
static double to_double(eval_val v) {
|
|
729
|
+
if (v.tag == VAL_INT) return (double)v.i;
|
|
730
|
+
if (v.tag == VAL_FLOAT) return v.f;
|
|
731
|
+
return 0.0;
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
static int is_numeric(eval_val v) {
|
|
735
|
+
return v.tag == VAL_INT || v.tag == VAL_FLOAT;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
static int eval_truthy(eval_val v) {
|
|
739
|
+
if (v.tag == VAL_BOOL) return v.b;
|
|
740
|
+
if (v.tag == VAL_INT) return v.i != 0;
|
|
741
|
+
if (v.tag == VAL_FLOAT) return v.f != 0.0;
|
|
742
|
+
return v.tag != VAL_NULL;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
static eval_val eval_cmp(eval_val lv, eval_val rv, cmp_op op) {
|
|
746
|
+
/* Null comparisons: null != anything is true, null == null is true */
|
|
747
|
+
if (lv.tag == VAL_NULL || rv.tag == VAL_NULL) {
|
|
748
|
+
int both_null = (lv.tag == VAL_NULL && rv.tag == VAL_NULL);
|
|
749
|
+
switch (op) {
|
|
750
|
+
case CMP_EQ: return val_bool(both_null);
|
|
751
|
+
case CMP_NE: return val_bool(!both_null);
|
|
752
|
+
default: return val_bool(0);
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
/* String comparison */
|
|
757
|
+
if (lv.tag == VAL_STR && rv.tag == VAL_STR) {
|
|
758
|
+
int cmp = strcmp(lv.s, rv.s);
|
|
759
|
+
switch (op) {
|
|
760
|
+
case CMP_GT: return val_bool(cmp > 0);
|
|
761
|
+
case CMP_GE: return val_bool(cmp >= 0);
|
|
762
|
+
case CMP_LT: return val_bool(cmp < 0);
|
|
763
|
+
case CMP_LE: return val_bool(cmp <= 0);
|
|
764
|
+
case CMP_EQ: return val_bool(cmp == 0);
|
|
765
|
+
case CMP_NE: return val_bool(cmp != 0);
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
/* Boolean comparison */
|
|
770
|
+
if (lv.tag == VAL_BOOL && rv.tag == VAL_BOOL) {
|
|
771
|
+
int l = lv.b ? 1 : 0;
|
|
772
|
+
int r = rv.b ? 1 : 0;
|
|
773
|
+
switch (op) {
|
|
774
|
+
case CMP_GT: return val_bool(l > r);
|
|
775
|
+
case CMP_GE: return val_bool(l >= r);
|
|
776
|
+
case CMP_LT: return val_bool(l < r);
|
|
777
|
+
case CMP_LE: return val_bool(l <= r);
|
|
778
|
+
case CMP_EQ: return val_bool(l == r);
|
|
779
|
+
case CMP_NE: return val_bool(l != r);
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
/* Numeric comparison */
|
|
784
|
+
if (is_numeric(lv) && is_numeric(rv)) {
|
|
785
|
+
double l = to_double(lv);
|
|
786
|
+
double r = to_double(rv);
|
|
787
|
+
switch (op) {
|
|
788
|
+
case CMP_GT: return val_bool(l > r);
|
|
789
|
+
case CMP_GE: return val_bool(l >= r);
|
|
790
|
+
case CMP_LT: return val_bool(l < r);
|
|
791
|
+
case CMP_LE: return val_bool(l <= r);
|
|
792
|
+
case CMP_EQ: return val_bool(l == r);
|
|
793
|
+
case CMP_NE: return val_bool(l != r);
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
/* Date/timestamp comparison — promote date to timestamp (midnight),
|
|
798
|
+
* and try parsing string literals as dates/timestamps */
|
|
799
|
+
if (lv.tag == VAL_DATE || lv.tag == VAL_TIMESTAMP ||
|
|
800
|
+
rv.tag == VAL_DATE || rv.tag == VAL_TIMESTAMP) {
|
|
801
|
+
/* Promote VAL_STR to date/timestamp if parseable */
|
|
802
|
+
for (int side = 0; side < 2; side++) {
|
|
803
|
+
eval_val *v = (side == 0) ? &lv : &rv;
|
|
804
|
+
if (v->tag == VAL_STR && v->s) {
|
|
805
|
+
size_t slen = strlen(v->s);
|
|
806
|
+
int y, m, d;
|
|
807
|
+
if (slen == 10 && sscanf(v->s, "%d-%d-%d", &y, &m, &d) == 3) {
|
|
808
|
+
*v = val_date(tf_date_from_ymd(y, m, d));
|
|
809
|
+
} else if (slen >= 19) {
|
|
810
|
+
int h = 0, mi2 = 0, s = 0;
|
|
811
|
+
if (sscanf(v->s, "%d-%d-%dT%d:%d:%d", &y, &m, &d, &h, &mi2, &s) >= 6 ||
|
|
812
|
+
sscanf(v->s, "%d-%d-%d %d:%d:%d", &y, &m, &d, &h, &mi2, &s) >= 6)
|
|
813
|
+
*v = val_timestamp(tf_timestamp_from_parts(y, m, d, h, mi2, s, 0));
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
int64_t l, r;
|
|
818
|
+
if (lv.tag == VAL_DATE) l = (int64_t)lv.date * 86400LL * 1000000LL;
|
|
819
|
+
else if (lv.tag == VAL_TIMESTAMP) l = lv.ts;
|
|
820
|
+
else { if (op == CMP_EQ) return val_bool(0); if (op == CMP_NE) return val_bool(1); return val_bool(0); }
|
|
821
|
+
if (rv.tag == VAL_DATE) r = (int64_t)rv.date * 86400LL * 1000000LL;
|
|
822
|
+
else if (rv.tag == VAL_TIMESTAMP) r = rv.ts;
|
|
823
|
+
else { if (op == CMP_EQ) return val_bool(0); if (op == CMP_NE) return val_bool(1); return val_bool(0); }
|
|
824
|
+
switch (op) {
|
|
825
|
+
case CMP_GT: return val_bool(l > r);
|
|
826
|
+
case CMP_GE: return val_bool(l >= r);
|
|
827
|
+
case CMP_LT: return val_bool(l < r);
|
|
828
|
+
case CMP_LE: return val_bool(l <= r);
|
|
829
|
+
case CMP_EQ: return val_bool(l == r);
|
|
830
|
+
case CMP_NE: return val_bool(l != r);
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
/* Mixed types: convert to string and compare */
|
|
835
|
+
/* For simplicity, treat as not equal for non-EQ/NE ops */
|
|
836
|
+
if (op == CMP_EQ) return val_bool(0);
|
|
837
|
+
if (op == CMP_NE) return val_bool(1);
|
|
838
|
+
return val_bool(0);
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
static eval_val eval_arith(eval_val lv, eval_val rv, expr_kind kind) {
|
|
842
|
+
/* If either is null, result is null */
|
|
843
|
+
if (lv.tag == VAL_NULL || rv.tag == VAL_NULL) return val_null();
|
|
844
|
+
|
|
845
|
+
/* Date/timestamp arithmetic */
|
|
846
|
+
if (lv.tag == VAL_DATE || lv.tag == VAL_TIMESTAMP ||
|
|
847
|
+
rv.tag == VAL_DATE || rv.tag == VAL_TIMESTAMP) {
|
|
848
|
+
/* date - date → int (days) */
|
|
849
|
+
if (lv.tag == VAL_DATE && rv.tag == VAL_DATE && kind == EXPR_SUB)
|
|
850
|
+
return val_int((int64_t)lv.date - (int64_t)rv.date);
|
|
851
|
+
/* timestamp - timestamp → int (microseconds) */
|
|
852
|
+
if (lv.tag == VAL_TIMESTAMP && rv.tag == VAL_TIMESTAMP && kind == EXPR_SUB)
|
|
853
|
+
return val_int(lv.ts - rv.ts);
|
|
854
|
+
/* date ± int → date */
|
|
855
|
+
if (lv.tag == VAL_DATE && rv.tag == VAL_INT) {
|
|
856
|
+
if (kind == EXPR_ADD) return val_date(lv.date + (int32_t)rv.i);
|
|
857
|
+
if (kind == EXPR_SUB) return val_date(lv.date - (int32_t)rv.i);
|
|
858
|
+
}
|
|
859
|
+
/* timestamp ± int → timestamp */
|
|
860
|
+
if (lv.tag == VAL_TIMESTAMP && rv.tag == VAL_INT) {
|
|
861
|
+
if (kind == EXPR_ADD) return val_timestamp(lv.ts + rv.i);
|
|
862
|
+
if (kind == EXPR_SUB) return val_timestamp(lv.ts - rv.i);
|
|
863
|
+
}
|
|
864
|
+
/* int + date → date */
|
|
865
|
+
if (lv.tag == VAL_INT && rv.tag == VAL_DATE && kind == EXPR_ADD)
|
|
866
|
+
return val_date((int32_t)lv.i + rv.date);
|
|
867
|
+
/* int + timestamp → timestamp */
|
|
868
|
+
if (lv.tag == VAL_INT && rv.tag == VAL_TIMESTAMP && kind == EXPR_ADD)
|
|
869
|
+
return val_timestamp(lv.i + rv.ts);
|
|
870
|
+
return val_null();
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
/* Both must be numeric */
|
|
874
|
+
if (!is_numeric(lv) || !is_numeric(rv)) return val_null();
|
|
875
|
+
|
|
876
|
+
/* If both are int and not dividing, stay in int domain */
|
|
877
|
+
if (lv.tag == VAL_INT && rv.tag == VAL_INT && kind != EXPR_DIV) {
|
|
878
|
+
int64_t l = lv.i, r = rv.i;
|
|
879
|
+
switch (kind) {
|
|
880
|
+
case EXPR_ADD: return val_int(l + r);
|
|
881
|
+
case EXPR_SUB: return val_int(l - r);
|
|
882
|
+
case EXPR_MUL: return val_int(l * r);
|
|
883
|
+
default: break;
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
/* Float domain */
|
|
888
|
+
double l = to_double(lv);
|
|
889
|
+
double r = to_double(rv);
|
|
890
|
+
switch (kind) {
|
|
891
|
+
case EXPR_ADD: return val_float(l + r);
|
|
892
|
+
case EXPR_SUB: return val_float(l - r);
|
|
893
|
+
case EXPR_MUL: return val_float(l * r);
|
|
894
|
+
case EXPR_DIV:
|
|
895
|
+
if (r == 0.0) return val_null();
|
|
896
|
+
return val_float(l / r);
|
|
897
|
+
default: return val_null();
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
/* Get string representation of a value for string functions */
|
|
902
|
+
static const char *val_to_str(eval_val v, char *buf, size_t buf_sz) {
|
|
903
|
+
switch (v.tag) {
|
|
904
|
+
case VAL_STR: return v.s;
|
|
905
|
+
case VAL_INT: snprintf(buf, buf_sz, "%lld", (long long)v.i); return buf;
|
|
906
|
+
case VAL_FLOAT:
|
|
907
|
+
if (tf_format_float64(buf, buf_sz, v.f) != TF_OK && buf_sz > 0) buf[0] = '\0';
|
|
908
|
+
return buf;
|
|
909
|
+
case VAL_BOOL: return v.b ? "true" : "false";
|
|
910
|
+
case VAL_DATE: tf_date_format(v.date, buf, buf_sz); return buf;
|
|
911
|
+
case VAL_TIMESTAMP: tf_timestamp_format(v.ts, buf, buf_sz); return buf;
|
|
912
|
+
default: return "";
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
static eval_val eval_func(const char *name, eval_val *args, int n_args) {
|
|
917
|
+
/* ---- String functions ---- */
|
|
918
|
+
|
|
919
|
+
/* upper(s) */
|
|
920
|
+
if (strcmp(name, "upper") == 0 && n_args == 1) {
|
|
921
|
+
if (args[0].tag == VAL_NULL) return val_null();
|
|
922
|
+
char tmp[64];
|
|
923
|
+
const char *s = val_to_str(args[0], tmp, sizeof(tmp));
|
|
924
|
+
char *out = scratch_alloc();
|
|
925
|
+
size_t i;
|
|
926
|
+
for (i = 0; s[i] && i < SCRATCH_SLOT_SIZE - 1; i++)
|
|
927
|
+
out[i] = (char)toupper((unsigned char)s[i]);
|
|
928
|
+
out[i] = '\0';
|
|
929
|
+
return val_str(out);
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
/* lower(s) */
|
|
933
|
+
if (strcmp(name, "lower") == 0 && n_args == 1) {
|
|
934
|
+
if (args[0].tag == VAL_NULL) return val_null();
|
|
935
|
+
char tmp[64];
|
|
936
|
+
const char *s = val_to_str(args[0], tmp, sizeof(tmp));
|
|
937
|
+
char *out = scratch_alloc();
|
|
938
|
+
size_t i;
|
|
939
|
+
for (i = 0; s[i] && i < SCRATCH_SLOT_SIZE - 1; i++)
|
|
940
|
+
out[i] = (char)tolower((unsigned char)s[i]);
|
|
941
|
+
out[i] = '\0';
|
|
942
|
+
return val_str(out);
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
/* len(s) — returns string length as int */
|
|
946
|
+
if (strcmp(name, "len") == 0 && n_args == 1) {
|
|
947
|
+
if (args[0].tag == VAL_NULL) return val_null();
|
|
948
|
+
if (args[0].tag == VAL_STR) return val_int((int64_t)strlen(args[0].s));
|
|
949
|
+
return val_null();
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
/* trim(s) */
|
|
953
|
+
if (strcmp(name, "trim") == 0 && n_args == 1) {
|
|
954
|
+
if (args[0].tag == VAL_NULL) return val_null();
|
|
955
|
+
if (args[0].tag != VAL_STR) return val_null();
|
|
956
|
+
const char *s = args[0].s;
|
|
957
|
+
while (*s && isspace((unsigned char)*s)) s++;
|
|
958
|
+
size_t len = strlen(s);
|
|
959
|
+
while (len > 0 && isspace((unsigned char)s[len - 1])) len--;
|
|
960
|
+
char *out = scratch_alloc();
|
|
961
|
+
if (len >= SCRATCH_SLOT_SIZE) len = SCRATCH_SLOT_SIZE - 1;
|
|
962
|
+
memcpy(out, s, len);
|
|
963
|
+
out[len] = '\0';
|
|
964
|
+
return val_str(out);
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
/* starts_with(s, prefix) */
|
|
968
|
+
if (strcmp(name, "starts_with") == 0 && n_args == 2) {
|
|
969
|
+
if (args[0].tag == VAL_NULL || args[1].tag == VAL_NULL) return val_null();
|
|
970
|
+
if (args[0].tag != VAL_STR || args[1].tag != VAL_STR) return val_bool(0);
|
|
971
|
+
size_t plen = strlen(args[1].s);
|
|
972
|
+
return val_bool(strncmp(args[0].s, args[1].s, plen) == 0);
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
/* ends_with(s, suffix) */
|
|
976
|
+
if (strcmp(name, "ends_with") == 0 && n_args == 2) {
|
|
977
|
+
if (args[0].tag == VAL_NULL || args[1].tag == VAL_NULL) return val_null();
|
|
978
|
+
if (args[0].tag != VAL_STR || args[1].tag != VAL_STR) return val_bool(0);
|
|
979
|
+
size_t slen = strlen(args[0].s);
|
|
980
|
+
size_t xlen = strlen(args[1].s);
|
|
981
|
+
if (xlen > slen) return val_bool(0);
|
|
982
|
+
return val_bool(strcmp(args[0].s + slen - xlen, args[1].s) == 0);
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
/* contains(s, substr) */
|
|
986
|
+
if (strcmp(name, "contains") == 0 && n_args == 2) {
|
|
987
|
+
if (args[0].tag == VAL_NULL || args[1].tag == VAL_NULL) return val_null();
|
|
988
|
+
if (args[0].tag != VAL_STR || args[1].tag != VAL_STR) return val_bool(0);
|
|
989
|
+
return val_bool(strstr(args[0].s, args[1].s) != NULL);
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
/* slice(s, start, len) — substring */
|
|
993
|
+
if (strcmp(name, "slice") == 0 && n_args >= 2) {
|
|
994
|
+
if (args[0].tag == VAL_NULL) return val_null();
|
|
995
|
+
if (args[0].tag != VAL_STR) return val_null();
|
|
996
|
+
int64_t start = 0, slice_len = -1;
|
|
997
|
+
if (args[1].tag == VAL_INT) start = args[1].i;
|
|
998
|
+
else if (args[1].tag == VAL_FLOAT) start = (int64_t)args[1].f;
|
|
999
|
+
if (n_args >= 3) {
|
|
1000
|
+
if (args[2].tag == VAL_INT) slice_len = args[2].i;
|
|
1001
|
+
else if (args[2].tag == VAL_FLOAT) slice_len = (int64_t)args[2].f;
|
|
1002
|
+
}
|
|
1003
|
+
size_t slen = strlen(args[0].s);
|
|
1004
|
+
if (start < 0) start = (int64_t)slen + start;
|
|
1005
|
+
if (start < 0) start = 0;
|
|
1006
|
+
if ((size_t)start >= slen) return val_str("");
|
|
1007
|
+
size_t avail = slen - (size_t)start;
|
|
1008
|
+
size_t take = (slice_len < 0) ? avail : ((size_t)slice_len < avail ? (size_t)slice_len : avail);
|
|
1009
|
+
char *out = scratch_alloc();
|
|
1010
|
+
if (take >= SCRATCH_SLOT_SIZE) take = SCRATCH_SLOT_SIZE - 1;
|
|
1011
|
+
memcpy(out, args[0].s + start, take);
|
|
1012
|
+
out[take] = '\0';
|
|
1013
|
+
return val_str(out);
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
/* concat(a, b, ...) — concatenate strings */
|
|
1017
|
+
if (strcmp(name, "concat") == 0 && n_args >= 1) {
|
|
1018
|
+
char *out = scratch_alloc();
|
|
1019
|
+
size_t pos = 0;
|
|
1020
|
+
char tmp[64];
|
|
1021
|
+
for (int i = 0; i < n_args && pos < SCRATCH_SLOT_SIZE - 1; i++) {
|
|
1022
|
+
if (args[i].tag == VAL_NULL) continue;
|
|
1023
|
+
const char *s = val_to_str(args[i], tmp, sizeof(tmp));
|
|
1024
|
+
size_t sl = strlen(s);
|
|
1025
|
+
if (pos + sl >= SCRATCH_SLOT_SIZE) sl = SCRATCH_SLOT_SIZE - 1 - pos;
|
|
1026
|
+
memcpy(out + pos, s, sl);
|
|
1027
|
+
pos += sl;
|
|
1028
|
+
}
|
|
1029
|
+
out[pos] = '\0';
|
|
1030
|
+
return val_str(out);
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
/* pad_left(s, width, char) */
|
|
1034
|
+
if (strcmp(name, "pad_left") == 0 && n_args >= 2) {
|
|
1035
|
+
if (args[0].tag == VAL_NULL) return val_null();
|
|
1036
|
+
char tmp[64];
|
|
1037
|
+
const char *s = val_to_str(args[0], tmp, sizeof(tmp));
|
|
1038
|
+
int64_t width = 0;
|
|
1039
|
+
if (args[1].tag == VAL_INT) width = args[1].i;
|
|
1040
|
+
else if (args[1].tag == VAL_FLOAT) width = (int64_t)args[1].f;
|
|
1041
|
+
char pad_ch = ' ';
|
|
1042
|
+
if (n_args >= 3 && args[2].tag == VAL_STR && args[2].s[0])
|
|
1043
|
+
pad_ch = args[2].s[0];
|
|
1044
|
+
char *out = scratch_alloc();
|
|
1045
|
+
size_t slen = strlen(s);
|
|
1046
|
+
if (width < 0) width = 0;
|
|
1047
|
+
if ((size_t)width >= SCRATCH_SLOT_SIZE) width = SCRATCH_SLOT_SIZE - 1;
|
|
1048
|
+
if (slen >= (size_t)width) {
|
|
1049
|
+
memcpy(out, s, slen < SCRATCH_SLOT_SIZE - 1 ? slen : SCRATCH_SLOT_SIZE - 1);
|
|
1050
|
+
out[slen < SCRATCH_SLOT_SIZE - 1 ? slen : SCRATCH_SLOT_SIZE - 1] = '\0';
|
|
1051
|
+
} else {
|
|
1052
|
+
size_t pad_n = (size_t)width - slen;
|
|
1053
|
+
memset(out, pad_ch, pad_n);
|
|
1054
|
+
memcpy(out + pad_n, s, slen);
|
|
1055
|
+
out[(size_t)width] = '\0';
|
|
1056
|
+
}
|
|
1057
|
+
return val_str(out);
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
/* pad_right(s, width, char) */
|
|
1061
|
+
if (strcmp(name, "pad_right") == 0 && n_args >= 2) {
|
|
1062
|
+
if (args[0].tag == VAL_NULL) return val_null();
|
|
1063
|
+
char tmp[64];
|
|
1064
|
+
const char *s = val_to_str(args[0], tmp, sizeof(tmp));
|
|
1065
|
+
int64_t width = 0;
|
|
1066
|
+
if (args[1].tag == VAL_INT) width = args[1].i;
|
|
1067
|
+
else if (args[1].tag == VAL_FLOAT) width = (int64_t)args[1].f;
|
|
1068
|
+
char pad_ch = ' ';
|
|
1069
|
+
if (n_args >= 3 && args[2].tag == VAL_STR && args[2].s[0])
|
|
1070
|
+
pad_ch = args[2].s[0];
|
|
1071
|
+
char *out = scratch_alloc();
|
|
1072
|
+
size_t slen = strlen(s);
|
|
1073
|
+
if (width < 0) width = 0;
|
|
1074
|
+
if ((size_t)width >= SCRATCH_SLOT_SIZE) width = SCRATCH_SLOT_SIZE - 1;
|
|
1075
|
+
if (slen >= (size_t)width) {
|
|
1076
|
+
memcpy(out, s, slen < SCRATCH_SLOT_SIZE - 1 ? slen : SCRATCH_SLOT_SIZE - 1);
|
|
1077
|
+
out[slen < SCRATCH_SLOT_SIZE - 1 ? slen : SCRATCH_SLOT_SIZE - 1] = '\0';
|
|
1078
|
+
} else {
|
|
1079
|
+
memcpy(out, s, slen);
|
|
1080
|
+
memset(out + slen, pad_ch, (size_t)width - slen);
|
|
1081
|
+
out[(size_t)width] = '\0';
|
|
1082
|
+
}
|
|
1083
|
+
return val_str(out);
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
/* ---- Conditional functions ---- */
|
|
1087
|
+
|
|
1088
|
+
/* if(cond, then, else) */
|
|
1089
|
+
if (strcmp(name, "if") == 0 && n_args == 3) {
|
|
1090
|
+
return eval_truthy(args[0]) ? args[1] : args[2];
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
/* case_when(cond, value, ..., default) — first matching condition */
|
|
1094
|
+
if (strcmp(name, "case_when") == 0 && n_args >= 2) {
|
|
1095
|
+
int pair_count = n_args / 2;
|
|
1096
|
+
int default_idx = (n_args % 2 == 1) ? n_args - 1 : -1;
|
|
1097
|
+
for (int i = 0; i < pair_count; i++) {
|
|
1098
|
+
if (eval_truthy(args[i * 2])) return args[i * 2 + 1];
|
|
1099
|
+
}
|
|
1100
|
+
return default_idx >= 0 ? args[default_idx] : val_null();
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
/* case_match(value, key, result, ..., default) — first equal key */
|
|
1104
|
+
if (strcmp(name, "case_match") == 0 && n_args >= 3) {
|
|
1105
|
+
int remaining = n_args - 1;
|
|
1106
|
+
int pair_count = remaining / 2;
|
|
1107
|
+
int default_idx = (remaining % 2 == 1) ? n_args - 1 : -1;
|
|
1108
|
+
for (int i = 0; i < pair_count; i++) {
|
|
1109
|
+
eval_val eq = eval_cmp(args[0], args[1 + i * 2], CMP_EQ);
|
|
1110
|
+
if (eq.tag == VAL_BOOL && eq.b) return args[2 + i * 2];
|
|
1111
|
+
}
|
|
1112
|
+
return default_idx >= 0 ? args[default_idx] : val_null();
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
/* if_any(pred, ...) / if_all(pred, ...) — row-local boolean reduction */
|
|
1116
|
+
if (strcmp(name, "if_any") == 0) {
|
|
1117
|
+
for (int i = 0; i < n_args; i++) {
|
|
1118
|
+
if (eval_truthy(args[i])) return val_bool(1);
|
|
1119
|
+
}
|
|
1120
|
+
return val_bool(0);
|
|
1121
|
+
}
|
|
1122
|
+
if (strcmp(name, "if_all") == 0) {
|
|
1123
|
+
for (int i = 0; i < n_args; i++) {
|
|
1124
|
+
if (!eval_truthy(args[i])) return val_bool(0);
|
|
1125
|
+
}
|
|
1126
|
+
return val_bool(1);
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
/* coalesce(a, b, ...) — first non-null */
|
|
1130
|
+
if (strcmp(name, "coalesce") == 0) {
|
|
1131
|
+
for (int i = 0; i < n_args; i++) {
|
|
1132
|
+
if (args[i].tag != VAL_NULL) return args[i];
|
|
1133
|
+
}
|
|
1134
|
+
return val_null();
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
/* between(x, lower, upper) / inrange(x, lower, upper) — inclusive range predicate */
|
|
1138
|
+
if ((strcmp(name, "between") == 0 || strcmp(name, "inrange") == 0) && n_args == 3) {
|
|
1139
|
+
if (args[0].tag == VAL_NULL || args[1].tag == VAL_NULL || args[2].tag == VAL_NULL)
|
|
1140
|
+
return val_null();
|
|
1141
|
+
eval_val ge = eval_cmp(args[0], args[1], CMP_GE);
|
|
1142
|
+
eval_val le = eval_cmp(args[0], args[2], CMP_LE);
|
|
1143
|
+
if (ge.tag != VAL_BOOL || le.tag != VAL_BOOL) return val_null();
|
|
1144
|
+
return val_bool(ge.b && le.b);
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
/* ---- Date/time functions ---- */
|
|
1148
|
+
|
|
1149
|
+
if ((strcmp(name, "year") == 0 || strcmp(name, "month") == 0 ||
|
|
1150
|
+
strcmp(name, "day") == 0 || strcmp(name, "hour") == 0 ||
|
|
1151
|
+
strcmp(name, "minute") == 0 || strcmp(name, "second") == 0 ||
|
|
1152
|
+
strcmp(name, "weekday") == 0 || strcmp(name, "epoch") == 0) && n_args == 1) {
|
|
1153
|
+
if (args[0].tag == VAL_NULL) return val_null();
|
|
1154
|
+
int y, mo, d, h, mi, se, frac, is_ts, is_str;
|
|
1155
|
+
int32_t days;
|
|
1156
|
+
int64_t us;
|
|
1157
|
+
if (!eval_temporal_parts(args[0], &y, &mo, &d, &h, &mi, &se, &frac,
|
|
1158
|
+
&days, &us, &is_ts, &is_str)) return val_null();
|
|
1159
|
+
(void)frac; (void)is_str;
|
|
1160
|
+
if (strcmp(name, "year") == 0) return val_int(y);
|
|
1161
|
+
if (strcmp(name, "month") == 0) return val_int(mo);
|
|
1162
|
+
if (strcmp(name, "day") == 0) return val_int(d);
|
|
1163
|
+
if (strcmp(name, "hour") == 0) return val_int(h);
|
|
1164
|
+
if (strcmp(name, "minute") == 0) return val_int(mi);
|
|
1165
|
+
if (strcmp(name, "second") == 0) return val_int(se);
|
|
1166
|
+
if (strcmp(name, "weekday") == 0) return val_int(tf_date_weekday(days));
|
|
1167
|
+
if (strcmp(name, "epoch") == 0) {
|
|
1168
|
+
if (is_ts) return val_int(timestamp_epoch_seconds(us));
|
|
1169
|
+
return val_int((int64_t)days * 86400LL);
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
if (strcmp(name, "date_trunc") == 0 && n_args == 2) {
|
|
1174
|
+
if (args[0].tag == VAL_NULL || args[1].tag != VAL_STR) return val_null();
|
|
1175
|
+
tf_expr_trunc_level level;
|
|
1176
|
+
if (!expr_parse_trunc_level(args[1].s, &level)) return val_null();
|
|
1177
|
+
int y, mo, d, h, mi, se, frac, is_ts, is_str;
|
|
1178
|
+
int32_t days;
|
|
1179
|
+
int64_t us;
|
|
1180
|
+
if (!eval_temporal_parts(args[0], &y, &mo, &d, &h, &mi, &se, &frac,
|
|
1181
|
+
&days, &us, &is_ts, &is_str)) return val_null();
|
|
1182
|
+
(void)y; (void)mo; (void)d; (void)h; (void)mi; (void)se; (void)frac;
|
|
1183
|
+
if (args[0].tag == VAL_DATE) return val_date(expr_trunc_date(days, level));
|
|
1184
|
+
if (args[0].tag == VAL_TIMESTAMP) return val_timestamp(expr_trunc_timestamp(us, level));
|
|
1185
|
+
if (is_str) {
|
|
1186
|
+
char *out = scratch_alloc();
|
|
1187
|
+
if (is_ts) {
|
|
1188
|
+
tf_timestamp_format(expr_trunc_timestamp(us, level), out, SCRATCH_SLOT_SIZE);
|
|
1189
|
+
} else {
|
|
1190
|
+
tf_date_format(expr_trunc_date(days, level), out, SCRATCH_SLOT_SIZE);
|
|
1191
|
+
}
|
|
1192
|
+
return val_str(out);
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
/* ---- Math functions ---- */
|
|
1197
|
+
|
|
1198
|
+
/* abs(x) */
|
|
1199
|
+
if (strcmp(name, "abs") == 0 && n_args == 1) {
|
|
1200
|
+
if (args[0].tag == VAL_INT) return val_int(args[0].i < 0 ? -args[0].i : args[0].i);
|
|
1201
|
+
if (args[0].tag == VAL_FLOAT) return val_float(fabs(args[0].f));
|
|
1202
|
+
return val_null();
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
/* round(x) */
|
|
1206
|
+
if (strcmp(name, "round") == 0 && n_args == 1) {
|
|
1207
|
+
if (args[0].tag == VAL_INT) return args[0];
|
|
1208
|
+
if (args[0].tag == VAL_FLOAT) return val_int((int64_t)round(args[0].f));
|
|
1209
|
+
return val_null();
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
/* floor(x) */
|
|
1213
|
+
if (strcmp(name, "floor") == 0 && n_args == 1) {
|
|
1214
|
+
if (args[0].tag == VAL_INT) return args[0];
|
|
1215
|
+
if (args[0].tag == VAL_FLOAT) return val_int((int64_t)floor(args[0].f));
|
|
1216
|
+
return val_null();
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
/* ceil(x) */
|
|
1220
|
+
if (strcmp(name, "ceil") == 0 && n_args == 1) {
|
|
1221
|
+
if (args[0].tag == VAL_INT) return args[0];
|
|
1222
|
+
if (args[0].tag == VAL_FLOAT) return val_int((int64_t)ceil(args[0].f));
|
|
1223
|
+
return val_null();
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
/* min(a, b, ...) / least(a, b, ...) — variadic minimum */
|
|
1227
|
+
if ((strcmp(name, "min") == 0 || strcmp(name, "least") == 0) && n_args >= 2) {
|
|
1228
|
+
int all_int = 1;
|
|
1229
|
+
for (int i = 0; i < n_args; i++) {
|
|
1230
|
+
if (!is_numeric(args[i])) return val_null();
|
|
1231
|
+
if (args[i].tag != VAL_INT) all_int = 0;
|
|
1232
|
+
}
|
|
1233
|
+
if (all_int) {
|
|
1234
|
+
int64_t result = args[0].i;
|
|
1235
|
+
for (int i = 1; i < n_args; i++)
|
|
1236
|
+
if (args[i].i < result) result = args[i].i;
|
|
1237
|
+
return val_int(result);
|
|
1238
|
+
}
|
|
1239
|
+
double result = to_double(args[0]);
|
|
1240
|
+
for (int i = 1; i < n_args; i++) {
|
|
1241
|
+
double v = to_double(args[i]);
|
|
1242
|
+
if (v < result) result = v;
|
|
1243
|
+
}
|
|
1244
|
+
return val_float(result);
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
/* max(a, b, ...) / greatest(a, b, ...) — variadic maximum */
|
|
1248
|
+
if ((strcmp(name, "max") == 0 || strcmp(name, "greatest") == 0) && n_args >= 2) {
|
|
1249
|
+
int all_int = 1;
|
|
1250
|
+
for (int i = 0; i < n_args; i++) {
|
|
1251
|
+
if (!is_numeric(args[i])) return val_null();
|
|
1252
|
+
if (args[i].tag != VAL_INT) all_int = 0;
|
|
1253
|
+
}
|
|
1254
|
+
if (all_int) {
|
|
1255
|
+
int64_t result = args[0].i;
|
|
1256
|
+
for (int i = 1; i < n_args; i++)
|
|
1257
|
+
if (args[i].i > result) result = args[i].i;
|
|
1258
|
+
return val_int(result);
|
|
1259
|
+
}
|
|
1260
|
+
double result = to_double(args[0]);
|
|
1261
|
+
for (int i = 1; i < n_args; i++) {
|
|
1262
|
+
double v = to_double(args[i]);
|
|
1263
|
+
if (v > result) result = v;
|
|
1264
|
+
}
|
|
1265
|
+
return val_float(result);
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
/* sign(x) — returns -1, 0, or 1 */
|
|
1269
|
+
if (strcmp(name, "sign") == 0 && n_args == 1) {
|
|
1270
|
+
if (args[0].tag == VAL_INT)
|
|
1271
|
+
return val_int(args[0].i > 0 ? 1 : (args[0].i < 0 ? -1 : 0));
|
|
1272
|
+
if (args[0].tag == VAL_FLOAT)
|
|
1273
|
+
return val_int(args[0].f > 0 ? 1 : (args[0].f < 0 ? -1 : 0));
|
|
1274
|
+
return val_null();
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
/* nullif(a, b) — returns NULL if a == b, else a */
|
|
1278
|
+
if (strcmp(name, "nullif") == 0 && n_args == 2) {
|
|
1279
|
+
if (args[0].tag == VAL_NULL && args[1].tag == VAL_NULL) return val_null();
|
|
1280
|
+
if (args[0].tag == VAL_INT && args[1].tag == VAL_INT)
|
|
1281
|
+
return args[0].i == args[1].i ? val_null() : args[0];
|
|
1282
|
+
if (is_numeric(args[0]) && is_numeric(args[1]))
|
|
1283
|
+
return to_double(args[0]) == to_double(args[1]) ? val_null() : args[0];
|
|
1284
|
+
if (args[0].tag == VAL_STR && args[1].tag == VAL_STR)
|
|
1285
|
+
return strcmp(args[0].s, args[1].s) == 0 ? val_null() : args[0];
|
|
1286
|
+
return args[0];
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
/* initcap(s) — title case */
|
|
1290
|
+
if (strcmp(name, "initcap") == 0 && n_args == 1) {
|
|
1291
|
+
if (args[0].tag == VAL_NULL) return val_null();
|
|
1292
|
+
char tmp[64];
|
|
1293
|
+
const char *s = val_to_str(args[0], tmp, sizeof(tmp));
|
|
1294
|
+
char *out = scratch_alloc();
|
|
1295
|
+
int word_start = 1;
|
|
1296
|
+
size_t i;
|
|
1297
|
+
for (i = 0; s[i] && i < SCRATCH_SLOT_SIZE - 1; i++) {
|
|
1298
|
+
if (isspace((unsigned char)s[i]) || s[i] == '_' || s[i] == '-') {
|
|
1299
|
+
out[i] = s[i];
|
|
1300
|
+
word_start = 1;
|
|
1301
|
+
} else if (word_start) {
|
|
1302
|
+
out[i] = (char)toupper((unsigned char)s[i]);
|
|
1303
|
+
word_start = 0;
|
|
1304
|
+
} else {
|
|
1305
|
+
out[i] = (char)tolower((unsigned char)s[i]);
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
out[i] = '\0';
|
|
1309
|
+
return val_str(out);
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
/* left(s, n) — first n characters */
|
|
1313
|
+
if (strcmp(name, "left") == 0 && n_args == 2) {
|
|
1314
|
+
if (args[0].tag == VAL_NULL) return val_null();
|
|
1315
|
+
if (args[0].tag != VAL_STR) return val_null();
|
|
1316
|
+
int64_t n = args[1].tag == VAL_INT ? args[1].i : (int64_t)args[1].f;
|
|
1317
|
+
if (n < 0) n = 0;
|
|
1318
|
+
size_t slen = strlen(args[0].s);
|
|
1319
|
+
size_t take = (size_t)n < slen ? (size_t)n : slen;
|
|
1320
|
+
char *out = scratch_alloc();
|
|
1321
|
+
if (take >= SCRATCH_SLOT_SIZE) take = SCRATCH_SLOT_SIZE - 1;
|
|
1322
|
+
memcpy(out, args[0].s, take);
|
|
1323
|
+
out[take] = '\0';
|
|
1324
|
+
return val_str(out);
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
/* right(s, n) — last n characters */
|
|
1328
|
+
if (strcmp(name, "right") == 0 && n_args == 2) {
|
|
1329
|
+
if (args[0].tag == VAL_NULL) return val_null();
|
|
1330
|
+
if (args[0].tag != VAL_STR) return val_null();
|
|
1331
|
+
int64_t n = args[1].tag == VAL_INT ? args[1].i : (int64_t)args[1].f;
|
|
1332
|
+
if (n < 0) n = 0;
|
|
1333
|
+
size_t slen = strlen(args[0].s);
|
|
1334
|
+
size_t take = (size_t)n < slen ? (size_t)n : slen;
|
|
1335
|
+
char *out = scratch_alloc();
|
|
1336
|
+
if (take >= SCRATCH_SLOT_SIZE) take = SCRATCH_SLOT_SIZE - 1;
|
|
1337
|
+
memcpy(out, args[0].s + slen - take, take);
|
|
1338
|
+
out[take] = '\0';
|
|
1339
|
+
return val_str(out);
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
/* replace(s, old, new) — string replace */
|
|
1343
|
+
if (strcmp(name, "replace") == 0 && n_args == 3) {
|
|
1344
|
+
if (args[0].tag == VAL_NULL) return val_null();
|
|
1345
|
+
if (args[0].tag != VAL_STR || args[1].tag != VAL_STR || args[2].tag != VAL_STR)
|
|
1346
|
+
return val_null();
|
|
1347
|
+
const char *s = args[0].s;
|
|
1348
|
+
const char *old_s = args[1].s;
|
|
1349
|
+
const char *new_s = args[2].s;
|
|
1350
|
+
size_t old_len = strlen(old_s);
|
|
1351
|
+
size_t new_len = strlen(new_s);
|
|
1352
|
+
if (old_len == 0) return args[0];
|
|
1353
|
+
char *out = scratch_alloc();
|
|
1354
|
+
size_t pos = 0;
|
|
1355
|
+
while (*s && pos < SCRATCH_SLOT_SIZE - 1) {
|
|
1356
|
+
const char *found = strstr(s, old_s);
|
|
1357
|
+
if (!found) {
|
|
1358
|
+
size_t rest = strlen(s);
|
|
1359
|
+
if (pos + rest >= SCRATCH_SLOT_SIZE) rest = SCRATCH_SLOT_SIZE - 1 - pos;
|
|
1360
|
+
memcpy(out + pos, s, rest);
|
|
1361
|
+
pos += rest;
|
|
1362
|
+
break;
|
|
1363
|
+
}
|
|
1364
|
+
size_t before = (size_t)(found - s);
|
|
1365
|
+
if (pos + before >= SCRATCH_SLOT_SIZE) before = SCRATCH_SLOT_SIZE - 1 - pos;
|
|
1366
|
+
memcpy(out + pos, s, before);
|
|
1367
|
+
pos += before;
|
|
1368
|
+
if (pos + new_len >= SCRATCH_SLOT_SIZE) break;
|
|
1369
|
+
memcpy(out + pos, new_s, new_len);
|
|
1370
|
+
pos += new_len;
|
|
1371
|
+
s = found + old_len;
|
|
1372
|
+
}
|
|
1373
|
+
out[pos] = '\0';
|
|
1374
|
+
return val_str(out);
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
/* pow(x, y) */
|
|
1378
|
+
if (strcmp(name, "pow") == 0 && n_args == 2) {
|
|
1379
|
+
if (!is_numeric(args[0]) || !is_numeric(args[1])) return val_null();
|
|
1380
|
+
return val_float(pow(to_double(args[0]), to_double(args[1])));
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
/* sqrt(x) */
|
|
1384
|
+
if (strcmp(name, "sqrt") == 0 && n_args == 1) {
|
|
1385
|
+
if (!is_numeric(args[0])) return val_null();
|
|
1386
|
+
double v = to_double(args[0]);
|
|
1387
|
+
if (v < 0) return val_null();
|
|
1388
|
+
return val_float(sqrt(v));
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
/* log(x) — natural log */
|
|
1392
|
+
if (strcmp(name, "log") == 0 && n_args == 1) {
|
|
1393
|
+
if (!is_numeric(args[0])) return val_null();
|
|
1394
|
+
double v = to_double(args[0]);
|
|
1395
|
+
if (v <= 0) return val_null();
|
|
1396
|
+
return val_float(log(v));
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
/* exp(x) */
|
|
1400
|
+
if (strcmp(name, "exp") == 0 && n_args == 1) {
|
|
1401
|
+
if (!is_numeric(args[0])) return val_null();
|
|
1402
|
+
return val_float(exp(to_double(args[0])));
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
/* mod(a, b) — modulo */
|
|
1406
|
+
if (strcmp(name, "mod") == 0 && n_args == 2) {
|
|
1407
|
+
if (!is_numeric(args[0]) || !is_numeric(args[1])) return val_null();
|
|
1408
|
+
if (args[0].tag == VAL_INT && args[1].tag == VAL_INT) {
|
|
1409
|
+
if (args[1].i == 0) return val_null();
|
|
1410
|
+
return val_int(args[0].i % args[1].i);
|
|
1411
|
+
}
|
|
1412
|
+
double b = to_double(args[1]);
|
|
1413
|
+
if (b == 0.0) return val_null();
|
|
1414
|
+
return val_float(fmod(to_double(args[0]), b));
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
/* ---- Aliases ---- */
|
|
1418
|
+
|
|
1419
|
+
/* substr → slice */
|
|
1420
|
+
if (strcmp(name, "substr") == 0 && n_args >= 2)
|
|
1421
|
+
return eval_func("slice", args, n_args);
|
|
1422
|
+
|
|
1423
|
+
/* length → len */
|
|
1424
|
+
if (strcmp(name, "length") == 0 && n_args == 1)
|
|
1425
|
+
return eval_func("len", args, n_args);
|
|
1426
|
+
|
|
1427
|
+
/* lpad → pad_left */
|
|
1428
|
+
if (strcmp(name, "lpad") == 0 && n_args >= 2)
|
|
1429
|
+
return eval_func("pad_left", args, n_args);
|
|
1430
|
+
|
|
1431
|
+
/* rpad → pad_right */
|
|
1432
|
+
if (strcmp(name, "rpad") == 0 && n_args >= 2)
|
|
1433
|
+
return eval_func("pad_right", args, n_args);
|
|
1434
|
+
|
|
1435
|
+
return val_null(); /* unknown function */
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
static eval_val eval_node(const tf_expr *e, const tf_batch *batch, size_t row) {
|
|
1439
|
+
switch (e->kind) {
|
|
1440
|
+
case EXPR_LIT_INT:
|
|
1441
|
+
return val_int(e->lit_int);
|
|
1442
|
+
case EXPR_LIT_FLOAT:
|
|
1443
|
+
return val_float(e->lit_float);
|
|
1444
|
+
case EXPR_LIT_STR:
|
|
1445
|
+
return val_str(e->lit_str);
|
|
1446
|
+
case EXPR_LIT_BOOL:
|
|
1447
|
+
return val_bool(e->lit_bool);
|
|
1448
|
+
case EXPR_COL_REF: {
|
|
1449
|
+
int ci = tf_batch_col_index(batch, e->col_name);
|
|
1450
|
+
if (ci < 0) return val_null();
|
|
1451
|
+
if (tf_batch_is_null(batch, row, ci)) return val_null();
|
|
1452
|
+
switch (batch->col_types[ci]) {
|
|
1453
|
+
case TF_TYPE_BOOL: return val_bool(tf_batch_get_bool(batch, row, ci));
|
|
1454
|
+
case TF_TYPE_INT64: return val_int(tf_batch_get_int64(batch, row, ci));
|
|
1455
|
+
case TF_TYPE_FLOAT64: return val_float(tf_batch_get_float64(batch, row, ci));
|
|
1456
|
+
case TF_TYPE_STRING: return val_str(tf_batch_get_string(batch, row, ci));
|
|
1457
|
+
case TF_TYPE_DATE: return val_date(tf_batch_get_date(batch, row, ci));
|
|
1458
|
+
case TF_TYPE_TIMESTAMP: return val_timestamp(tf_batch_get_timestamp(batch, row, ci));
|
|
1459
|
+
default: return val_null();
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
case EXPR_CMP: {
|
|
1463
|
+
eval_val lv = eval_node(e->cmp.left, batch, row);
|
|
1464
|
+
eval_val rv = eval_node(e->cmp.right, batch, row);
|
|
1465
|
+
return eval_cmp(lv, rv, e->cmp.op);
|
|
1466
|
+
}
|
|
1467
|
+
case EXPR_AND: {
|
|
1468
|
+
eval_val lv = eval_node(e->binary.left, batch, row);
|
|
1469
|
+
if (lv.tag == VAL_BOOL && !lv.b) return val_bool(0);
|
|
1470
|
+
eval_val rv = eval_node(e->binary.right, batch, row);
|
|
1471
|
+
return val_bool(lv.tag == VAL_BOOL && lv.b &&
|
|
1472
|
+
rv.tag == VAL_BOOL && rv.b);
|
|
1473
|
+
}
|
|
1474
|
+
case EXPR_OR: {
|
|
1475
|
+
eval_val lv = eval_node(e->binary.left, batch, row);
|
|
1476
|
+
if (lv.tag == VAL_BOOL && lv.b) return val_bool(1);
|
|
1477
|
+
eval_val rv = eval_node(e->binary.right, batch, row);
|
|
1478
|
+
return val_bool((lv.tag == VAL_BOOL && lv.b) ||
|
|
1479
|
+
(rv.tag == VAL_BOOL && rv.b));
|
|
1480
|
+
}
|
|
1481
|
+
case EXPR_NOT: {
|
|
1482
|
+
eval_val cv = eval_node(e->child, batch, row);
|
|
1483
|
+
return val_bool(!(cv.tag == VAL_BOOL && cv.b));
|
|
1484
|
+
}
|
|
1485
|
+
case EXPR_NEG: {
|
|
1486
|
+
eval_val cv = eval_node(e->child, batch, row);
|
|
1487
|
+
if (cv.tag == VAL_INT) return val_int(-cv.i);
|
|
1488
|
+
if (cv.tag == VAL_FLOAT) return val_float(-cv.f);
|
|
1489
|
+
return val_null();
|
|
1490
|
+
}
|
|
1491
|
+
case EXPR_ADD: case EXPR_SUB: case EXPR_MUL: case EXPR_DIV: {
|
|
1492
|
+
eval_val lv = eval_node(e->binary.left, batch, row);
|
|
1493
|
+
eval_val rv = eval_node(e->binary.right, batch, row);
|
|
1494
|
+
return eval_arith(lv, rv, e->kind);
|
|
1495
|
+
}
|
|
1496
|
+
case EXPR_FUNC_CALL: {
|
|
1497
|
+
/* Evaluate all arguments */
|
|
1498
|
+
eval_val argv[16]; /* max 16 args */
|
|
1499
|
+
int nargs = e->func.n_args;
|
|
1500
|
+
if (nargs > 16) nargs = 16;
|
|
1501
|
+
for (int i = 0; i < nargs; i++)
|
|
1502
|
+
argv[i] = eval_node(e->func.args[i], batch, row);
|
|
1503
|
+
return eval_func(e->func.name, argv, nargs);
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
return val_null();
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
int tf_expr_eval(const tf_expr *e, const tf_batch *batch, size_t row, bool *result) {
|
|
1510
|
+
eval_val v = eval_node(e, batch, row);
|
|
1511
|
+
if (v.tag == VAL_BOOL) {
|
|
1512
|
+
*result = v.b != 0;
|
|
1513
|
+
return TF_OK;
|
|
1514
|
+
}
|
|
1515
|
+
/* Non-boolean result: treat truthy (non-null, non-zero) */
|
|
1516
|
+
*result = (v.tag != VAL_NULL);
|
|
1517
|
+
return TF_OK;
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
int tf_expr_eval_val(const tf_expr *e, const tf_batch *batch, size_t row,
|
|
1521
|
+
tf_eval_result *result) {
|
|
1522
|
+
eval_val v = eval_node(e, batch, row);
|
|
1523
|
+
switch (v.tag) {
|
|
1524
|
+
case VAL_NULL:
|
|
1525
|
+
result->type = TF_TYPE_NULL;
|
|
1526
|
+
break;
|
|
1527
|
+
case VAL_INT:
|
|
1528
|
+
result->type = TF_TYPE_INT64;
|
|
1529
|
+
result->i = v.i;
|
|
1530
|
+
break;
|
|
1531
|
+
case VAL_FLOAT:
|
|
1532
|
+
result->type = TF_TYPE_FLOAT64;
|
|
1533
|
+
result->f = v.f;
|
|
1534
|
+
break;
|
|
1535
|
+
case VAL_STR:
|
|
1536
|
+
result->type = TF_TYPE_STRING;
|
|
1537
|
+
result->s = v.s;
|
|
1538
|
+
break;
|
|
1539
|
+
case VAL_BOOL:
|
|
1540
|
+
result->type = TF_TYPE_BOOL;
|
|
1541
|
+
result->b = v.b != 0;
|
|
1542
|
+
break;
|
|
1543
|
+
case VAL_DATE:
|
|
1544
|
+
result->type = TF_TYPE_DATE;
|
|
1545
|
+
result->date = v.date;
|
|
1546
|
+
break;
|
|
1547
|
+
case VAL_TIMESTAMP:
|
|
1548
|
+
result->type = TF_TYPE_TIMESTAMP;
|
|
1549
|
+
result->i = v.ts;
|
|
1550
|
+
break;
|
|
1551
|
+
}
|
|
1552
|
+
return TF_OK;
|
|
1553
|
+
}
|