tranfi 0.1.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 -0
- package/NOTICE +8 -0
- package/README.md +272 -40
- package/app/assets/{index-pDFMluyz.js → index-BIAIKnrp.js} +1 -1
- package/app/index.html +1 -1
- package/binding.gyp +55 -3
- package/csrc/arena.c +7 -5
- package/csrc/batch.c +818 -71
- package/csrc/buffer.c +84 -8
- package/csrc/cJSON.c +262 -19
- package/csrc/cJSON.h +17 -1
- package/csrc/codec_csv.c +1074 -181
- package/csrc/codec_jsonl.c +830 -118
- package/csrc/codec_table.c +108 -78
- package/csrc/codec_text.c +286 -68
- package/csrc/compiler.c +31 -3
- package/csrc/config.h +21 -0
- package/csrc/dsl.c +4722 -485
- package/csrc/expr.c +363 -55
- package/csrc/expr.h +2 -0
- package/csrc/internal.h +316 -27
- package/csrc/ir.c +65 -18
- package/csrc/ir.h +41 -0
- package/csrc/ir_schema.c +20 -5
- package/csrc/ir_serialize.c +68 -6
- package/csrc/ir_sql.c +796 -185
- package/csrc/ir_validate.c +462 -6
- package/csrc/json_path.c +210 -0
- package/csrc/main.c +879 -30
- package/csrc/memory_estimate.c +477 -0
- package/csrc/op_acf.c +171 -21
- package/csrc/op_across.c +477 -0
- package/csrc/op_anomaly.c +167 -32
- package/csrc/op_assert.c +761 -0
- package/csrc/op_bin.c +168 -29
- package/csrc/op_cast.c +383 -55
- package/csrc/op_clip.c +30 -19
- package/csrc/op_date_trunc.c +208 -34
- package/csrc/op_datetime.c +259 -77
- package/csrc/op_derive.c +65 -97
- package/csrc/op_diff.c +146 -30
- package/csrc/op_ewma.c +149 -30
- package/csrc/op_explode.c +124 -26
- package/csrc/op_fill_down.c +125 -53
- package/csrc/op_fill_null.c +176 -31
- package/csrc/op_filter.c +89 -40
- package/csrc/op_frequency.c +571 -43
- package/csrc/op_grep.c +36 -18
- package/csrc/op_group_agg.c +1790 -119
- package/csrc/op_hash.c +48 -15
- package/csrc/op_head.c +21 -86
- package/csrc/op_interpolate.c +268 -62
- package/csrc/op_join.c +2700 -182
- 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 +328 -53
- package/csrc/op_lag.c +181 -0
- package/csrc/op_lead.c +141 -89
- package/csrc/op_normalize.c +363 -79
- package/csrc/op_onehot.c +345 -73
- package/csrc/op_pivot.c +1546 -162
- package/csrc/op_quarantine.c +189 -0
- package/csrc/op_registry.c +2062 -166
- package/csrc/op_rename.c +41 -50
- package/csrc/op_replace.c +270 -118
- package/csrc/op_rleid.c +297 -0
- package/csrc/op_rowid.c +559 -0
- package/csrc/op_sample.c +80 -23
- package/csrc/op_schema.c +1341 -0
- package/csrc/op_schema_infer.c +252 -0
- package/csrc/op_select.c +265 -65
- package/csrc/op_set.c +3449 -0
- package/csrc/op_skip.c +30 -87
- package/csrc/op_sort.c +670 -124
- package/csrc/op_source_name.c +120 -0
- package/csrc/op_split.c +65 -28
- package/csrc/op_split_data.c +41 -9
- package/csrc/op_stack.c +178 -222
- package/csrc/op_stats.c +206 -110
- package/csrc/op_step.c +217 -55
- package/csrc/op_tail.c +21 -12
- package/csrc/op_tee.c +338 -0
- package/csrc/op_top.c +260 -53
- package/csrc/op_trim.c +48 -19
- package/csrc/op_unique.c +1193 -150
- package/csrc/op_unpivot.c +100 -66
- package/csrc/op_validate.c +601 -24
- package/csrc/op_window.c +492 -51
- package/csrc/path_policy.c +85 -0
- package/csrc/pipeline.c +872 -99
- package/csrc/recipes.c +3 -1
- package/csrc/report.c +73 -30
- 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 +169 -1
- 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 +70 -9
- package/napi_api.c +219 -11
- package/napi_transform.c +1648 -0
- package/napi_transform.h +8 -0
- package/package.json +27 -11
- package/scripts/install-native.js +76 -0
- package/scripts/prepack.js +64 -0
- package/scripts/sync-csrc.js +23 -0
- package/src/cli.js +8 -11
- package/src/engines/duckdb.js +45 -12
- package/src/index.js +661 -42
- package/src/memory_policy.js +411 -0
- package/src/native.js +1 -5
- package/src/pipeline.js +454 -31
- package/src/recipe_json.js +80 -0
- package/src/server.js +10 -8
- package/src/transform.js +403 -0
- package/src/transform_error.js +10 -0
- package/src/wasm.js +6 -4
- package/wasm/index.js +498 -10
- package/wasm/tranfi_core.js +0 -0
- package/wasm/transform.js +1156 -0
- package/wasm/worker.js +786 -0
- package/csrc/plan.c +0 -206
package/csrc/expr.c
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* add_expr = mul_expr (('+' | '-') mul_expr)*
|
|
12
12
|
* mul_expr = unary (('*' | '/') unary)*
|
|
13
13
|
* unary = '-' unary | atom
|
|
14
|
-
* atom = NUMBER | STRING | col_ref | '(' expr ')'
|
|
14
|
+
* atom = NUMBER | STRING | BOOL | col_ref | '(' expr ')'
|
|
15
15
|
* col_ref = 'col(' STRING ')'
|
|
16
16
|
* NUMBER = [0-9]+('.'[0-9]+)?
|
|
17
17
|
* STRING = '\'' [^']* '\'' | '"' [^"]* '"'
|
|
@@ -35,6 +35,7 @@ typedef struct {
|
|
|
35
35
|
const char *src;
|
|
36
36
|
size_t pos;
|
|
37
37
|
size_t len;
|
|
38
|
+
int depth_error;
|
|
38
39
|
} parser_state;
|
|
39
40
|
|
|
40
41
|
static void skip_ws(parser_state *p) {
|
|
@@ -91,7 +92,15 @@ static void consume_cmp_op(parser_state *p, cmp_op op) {
|
|
|
91
92
|
|
|
92
93
|
/* ---- Recursive descent parser ---- */
|
|
93
94
|
|
|
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
|
+
}
|
|
95
104
|
|
|
96
105
|
static tf_expr *make_expr(expr_kind kind) {
|
|
97
106
|
tf_expr *e = calloc(1, sizeof(tf_expr));
|
|
@@ -115,7 +124,16 @@ static tf_expr *parse_string_literal(parser_state *p) {
|
|
|
115
124
|
|
|
116
125
|
tf_expr *e = make_expr(EXPR_LIT_STR);
|
|
117
126
|
if (!e) return NULL;
|
|
118
|
-
|
|
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
|
+
}
|
|
119
137
|
memcpy(e->lit_str, p->src + start, slen);
|
|
120
138
|
e->lit_str[slen] = '\0';
|
|
121
139
|
return e;
|
|
@@ -124,10 +142,10 @@ static tf_expr *parse_string_literal(parser_state *p) {
|
|
|
124
142
|
/* Parse a comma-separated argument list inside parentheses.
|
|
125
143
|
* Assumes '(' has already been consumed. Consumes the closing ')'.
|
|
126
144
|
* Returns allocated array of expressions, sets *n_args. */
|
|
127
|
-
static tf_expr **parse_arg_list(parser_state *p, int *n_args) {
|
|
145
|
+
static tf_expr **parse_arg_list(parser_state *p, int *n_args, unsigned depth) {
|
|
128
146
|
*n_args = 0;
|
|
129
|
-
|
|
130
|
-
tf_expr **args =
|
|
147
|
+
size_t cap = 4;
|
|
148
|
+
tf_expr **args = tf_mallocarray_checked(cap, sizeof(tf_expr *));
|
|
131
149
|
if (!args) return NULL;
|
|
132
150
|
|
|
133
151
|
skip_ws(p);
|
|
@@ -138,13 +156,20 @@ static tf_expr **parse_arg_list(parser_state *p, int *n_args) {
|
|
|
138
156
|
}
|
|
139
157
|
|
|
140
158
|
for (;;) {
|
|
141
|
-
tf_expr *arg =
|
|
159
|
+
tf_expr *arg = parse_expr_d(p, depth);
|
|
142
160
|
if (!arg) goto fail;
|
|
143
|
-
if (*n_args >= cap) {
|
|
144
|
-
|
|
145
|
-
|
|
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 *));
|
|
146
170
|
if (!na) { tf_expr_free(arg); goto fail; }
|
|
147
171
|
args = na;
|
|
172
|
+
cap = new_cap;
|
|
148
173
|
}
|
|
149
174
|
args[(*n_args)++] = arg;
|
|
150
175
|
skip_ws(p);
|
|
@@ -164,14 +189,15 @@ fail:
|
|
|
164
189
|
return NULL;
|
|
165
190
|
}
|
|
166
191
|
|
|
167
|
-
static tf_expr *parse_atom(parser_state *p) {
|
|
192
|
+
static tf_expr *parse_atom(parser_state *p, unsigned depth) {
|
|
193
|
+
if (expr_depth_exceeded(p, depth)) return NULL;
|
|
168
194
|
skip_ws(p);
|
|
169
195
|
if (p->pos >= p->len) return NULL;
|
|
170
196
|
|
|
171
197
|
/* Parenthesized expression */
|
|
172
198
|
if (p->src[p->pos] == '(') {
|
|
173
199
|
p->pos++;
|
|
174
|
-
tf_expr *e =
|
|
200
|
+
tf_expr *e = parse_expr_d(p, depth + 1);
|
|
175
201
|
if (!e) return NULL;
|
|
176
202
|
if (!match_char(p, ')')) { tf_expr_free(e); return NULL; }
|
|
177
203
|
return e;
|
|
@@ -201,7 +227,10 @@ static tf_expr *parse_atom(parser_state *p) {
|
|
|
201
227
|
}
|
|
202
228
|
|
|
203
229
|
size_t numlen = p->pos - start;
|
|
204
|
-
|
|
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;
|
|
205
234
|
memcpy(numstr, p->src + start, numlen);
|
|
206
235
|
numstr[numlen] = '\0';
|
|
207
236
|
|
|
@@ -234,7 +263,9 @@ static tf_expr *parse_atom(parser_state *p) {
|
|
|
234
263
|
return NULL;
|
|
235
264
|
}
|
|
236
265
|
|
|
237
|
-
|
|
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));
|
|
238
269
|
if (!name) return NULL;
|
|
239
270
|
memcpy(name, p->src + start, id_len);
|
|
240
271
|
name[id_len] = '\0';
|
|
@@ -262,10 +293,13 @@ static tf_expr *parse_atom(parser_state *p) {
|
|
|
262
293
|
(isalnum((unsigned char)p->src[p->pos]) || p->src[p->pos] == '_'))
|
|
263
294
|
p->pos++;
|
|
264
295
|
if (p->pos == cstart) return NULL;
|
|
265
|
-
|
|
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));
|
|
266
300
|
if (!col_name) return NULL;
|
|
267
|
-
memcpy(col_name, p->src + cstart,
|
|
268
|
-
col_name[
|
|
301
|
+
memcpy(col_name, p->src + cstart, col_len);
|
|
302
|
+
col_name[col_len] = '\0';
|
|
269
303
|
}
|
|
270
304
|
|
|
271
305
|
if (!match_char(p, ')')) { free(col_name); return NULL; }
|
|
@@ -277,7 +311,7 @@ static tf_expr *parse_atom(parser_state *p) {
|
|
|
277
311
|
|
|
278
312
|
/* General function call: name(arg1, arg2, ...) */
|
|
279
313
|
int n_args = 0;
|
|
280
|
-
tf_expr **args = parse_arg_list(p, &n_args);
|
|
314
|
+
tf_expr **args = parse_arg_list(p, &n_args, depth + 1);
|
|
281
315
|
if (!args) { free(name); return NULL; }
|
|
282
316
|
|
|
283
317
|
tf_expr *e = make_expr(EXPR_FUNC_CALL);
|
|
@@ -293,6 +327,14 @@ static tf_expr *parse_atom(parser_state *p) {
|
|
|
293
327
|
return e;
|
|
294
328
|
}
|
|
295
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
|
+
|
|
296
338
|
/* Bare identifier — treat as column reference */
|
|
297
339
|
tf_expr *e = make_expr(EXPR_COL_REF);
|
|
298
340
|
if (!e) { free(name); return NULL; }
|
|
@@ -304,12 +346,13 @@ static tf_expr *parse_atom(parser_state *p) {
|
|
|
304
346
|
}
|
|
305
347
|
|
|
306
348
|
/* Unary minus: -expr */
|
|
307
|
-
static tf_expr *parse_unary(parser_state *p) {
|
|
349
|
+
static tf_expr *parse_unary(parser_state *p, unsigned depth) {
|
|
350
|
+
if (expr_depth_exceeded(p, depth)) return NULL;
|
|
308
351
|
skip_ws(p);
|
|
309
352
|
if (p->pos < p->len && p->src[p->pos] == '-') {
|
|
310
353
|
/* Check it's not a comparison op like != */
|
|
311
354
|
p->pos++;
|
|
312
|
-
tf_expr *child = parse_unary(p);
|
|
355
|
+
tf_expr *child = parse_unary(p, depth + 1);
|
|
313
356
|
if (!child) return NULL;
|
|
314
357
|
/* Optimize: fold literal negation */
|
|
315
358
|
if (child->kind == EXPR_LIT_INT) {
|
|
@@ -325,12 +368,12 @@ static tf_expr *parse_unary(parser_state *p) {
|
|
|
325
368
|
e->child = child;
|
|
326
369
|
return e;
|
|
327
370
|
}
|
|
328
|
-
return parse_atom(p);
|
|
371
|
+
return parse_atom(p, depth);
|
|
329
372
|
}
|
|
330
373
|
|
|
331
374
|
/* Multiplicative: unary (('*' | '/') unary)* */
|
|
332
|
-
static tf_expr *parse_mul(parser_state *p) {
|
|
333
|
-
tf_expr *left = parse_unary(p);
|
|
375
|
+
static tf_expr *parse_mul(parser_state *p, unsigned depth) {
|
|
376
|
+
tf_expr *left = parse_unary(p, depth);
|
|
334
377
|
if (!left) return NULL;
|
|
335
378
|
|
|
336
379
|
for (;;) {
|
|
@@ -340,7 +383,7 @@ static tf_expr *parse_mul(parser_state *p) {
|
|
|
340
383
|
if (op_char != '*' && op_char != '/') break;
|
|
341
384
|
p->pos++;
|
|
342
385
|
|
|
343
|
-
tf_expr *right = parse_unary(p);
|
|
386
|
+
tf_expr *right = parse_unary(p, depth);
|
|
344
387
|
if (!right) { tf_expr_free(left); return NULL; }
|
|
345
388
|
|
|
346
389
|
expr_kind kind = (op_char == '*') ? EXPR_MUL : EXPR_DIV;
|
|
@@ -354,8 +397,8 @@ static tf_expr *parse_mul(parser_state *p) {
|
|
|
354
397
|
}
|
|
355
398
|
|
|
356
399
|
/* Additive: mul_expr (('+' | '-') mul_expr)* */
|
|
357
|
-
static tf_expr *parse_add(parser_state *p) {
|
|
358
|
-
tf_expr *left = parse_mul(p);
|
|
400
|
+
static tf_expr *parse_add(parser_state *p, unsigned depth) {
|
|
401
|
+
tf_expr *left = parse_mul(p, depth);
|
|
359
402
|
if (!left) return NULL;
|
|
360
403
|
|
|
361
404
|
for (;;) {
|
|
@@ -368,7 +411,7 @@ static tf_expr *parse_add(parser_state *p) {
|
|
|
368
411
|
* Since we're at the add level, '+' and '-' are always arithmetic. */
|
|
369
412
|
p->pos++;
|
|
370
413
|
|
|
371
|
-
tf_expr *right = parse_mul(p);
|
|
414
|
+
tf_expr *right = parse_mul(p, depth);
|
|
372
415
|
if (!right) { tf_expr_free(left); return NULL; }
|
|
373
416
|
|
|
374
417
|
expr_kind kind = (op_char == '+') ? EXPR_ADD : EXPR_SUB;
|
|
@@ -381,14 +424,14 @@ static tf_expr *parse_add(parser_state *p) {
|
|
|
381
424
|
return left;
|
|
382
425
|
}
|
|
383
426
|
|
|
384
|
-
static tf_expr *parse_cmp(parser_state *p) {
|
|
385
|
-
tf_expr *left = parse_add(p);
|
|
427
|
+
static tf_expr *parse_cmp(parser_state *p, unsigned depth) {
|
|
428
|
+
tf_expr *left = parse_add(p, depth);
|
|
386
429
|
if (!left) return NULL;
|
|
387
430
|
|
|
388
431
|
cmp_op op;
|
|
389
432
|
if (peek_cmp_op(p, &op)) {
|
|
390
433
|
consume_cmp_op(p, op);
|
|
391
|
-
tf_expr *right = parse_add(p);
|
|
434
|
+
tf_expr *right = parse_add(p, depth);
|
|
392
435
|
if (!right) { tf_expr_free(left); return NULL; }
|
|
393
436
|
|
|
394
437
|
tf_expr *e = make_expr(EXPR_CMP);
|
|
@@ -402,23 +445,24 @@ static tf_expr *parse_cmp(parser_state *p) {
|
|
|
402
445
|
return left;
|
|
403
446
|
}
|
|
404
447
|
|
|
405
|
-
static tf_expr *parse_not(parser_state *p) {
|
|
448
|
+
static tf_expr *parse_not(parser_state *p, unsigned depth) {
|
|
449
|
+
if (expr_depth_exceeded(p, depth)) return NULL;
|
|
406
450
|
if (match_keyword(p, "not")) {
|
|
407
|
-
tf_expr *child = parse_not(p);
|
|
451
|
+
tf_expr *child = parse_not(p, depth + 1);
|
|
408
452
|
if (!child) return NULL;
|
|
409
453
|
tf_expr *e = make_expr(EXPR_NOT);
|
|
410
454
|
if (!e) { tf_expr_free(child); return NULL; }
|
|
411
455
|
e->child = child;
|
|
412
456
|
return e;
|
|
413
457
|
}
|
|
414
|
-
return parse_cmp(p);
|
|
458
|
+
return parse_cmp(p, depth);
|
|
415
459
|
}
|
|
416
460
|
|
|
417
|
-
static tf_expr *parse_and(parser_state *p) {
|
|
418
|
-
tf_expr *left = parse_not(p);
|
|
461
|
+
static tf_expr *parse_and(parser_state *p, unsigned depth) {
|
|
462
|
+
tf_expr *left = parse_not(p, depth);
|
|
419
463
|
if (!left) return NULL;
|
|
420
464
|
while (match_keyword(p, "and")) {
|
|
421
|
-
tf_expr *right = parse_not(p);
|
|
465
|
+
tf_expr *right = parse_not(p, depth);
|
|
422
466
|
if (!right) { tf_expr_free(left); return NULL; }
|
|
423
467
|
tf_expr *e = make_expr(EXPR_AND);
|
|
424
468
|
if (!e) { tf_expr_free(left); tf_expr_free(right); return NULL; }
|
|
@@ -429,11 +473,11 @@ static tf_expr *parse_and(parser_state *p) {
|
|
|
429
473
|
return left;
|
|
430
474
|
}
|
|
431
475
|
|
|
432
|
-
static tf_expr *parse_or(parser_state *p) {
|
|
433
|
-
tf_expr *left = parse_and(p);
|
|
476
|
+
static tf_expr *parse_or(parser_state *p, unsigned depth) {
|
|
477
|
+
tf_expr *left = parse_and(p, depth);
|
|
434
478
|
if (!left) return NULL;
|
|
435
479
|
while (match_keyword(p, "or")) {
|
|
436
|
-
tf_expr *right = parse_and(p);
|
|
480
|
+
tf_expr *right = parse_and(p, depth);
|
|
437
481
|
if (!right) { tf_expr_free(left); return NULL; }
|
|
438
482
|
tf_expr *e = make_expr(EXPR_OR);
|
|
439
483
|
if (!e) { tf_expr_free(left); tf_expr_free(right); return NULL; }
|
|
@@ -444,18 +488,28 @@ static tf_expr *parse_or(parser_state *p) {
|
|
|
444
488
|
return left;
|
|
445
489
|
}
|
|
446
490
|
|
|
447
|
-
static tf_expr *
|
|
448
|
-
|
|
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);
|
|
449
494
|
}
|
|
450
495
|
|
|
451
496
|
tf_expr *tf_expr_parse(const char *text) {
|
|
452
|
-
if (!text)
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
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
|
+
}
|
|
456
507
|
/* Ensure we consumed all input */
|
|
457
508
|
skip_ws(&p);
|
|
458
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);
|
|
459
513
|
tf_expr_free(e);
|
|
460
514
|
return NULL;
|
|
461
515
|
}
|
|
@@ -511,8 +565,8 @@ typedef struct {
|
|
|
511
565
|
* Uses a ring buffer approach: each call gets a slot, wrapping around. */
|
|
512
566
|
#define SCRATCH_SLOTS 8
|
|
513
567
|
#define SCRATCH_SLOT_SIZE 4096
|
|
514
|
-
static char scratch_buf[SCRATCH_SLOTS][SCRATCH_SLOT_SIZE];
|
|
515
|
-
static
|
|
568
|
+
static _Thread_local char scratch_buf[SCRATCH_SLOTS][SCRATCH_SLOT_SIZE];
|
|
569
|
+
static _Thread_local size_t scratch_idx = 0;
|
|
516
570
|
|
|
517
571
|
static char *scratch_alloc(void) {
|
|
518
572
|
char *slot = scratch_buf[scratch_idx % SCRATCH_SLOTS];
|
|
@@ -530,6 +584,146 @@ static eval_val val_bool(int v) { return (eval_val){ .tag = VAL_BOOL, .b = v };
|
|
|
530
584
|
static eval_val val_date(int32_t v) { return (eval_val){ .tag = VAL_DATE, .date = v }; }
|
|
531
585
|
static eval_val val_timestamp(int64_t v) { return (eval_val){ .tag = VAL_TIMESTAMP, .ts = v }; }
|
|
532
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
|
+
|
|
533
727
|
/* Convert to double for numeric comparison */
|
|
534
728
|
static double to_double(eval_val v) {
|
|
535
729
|
if (v.tag == VAL_INT) return (double)v.i;
|
|
@@ -541,6 +735,13 @@ static int is_numeric(eval_val v) {
|
|
|
541
735
|
return v.tag == VAL_INT || v.tag == VAL_FLOAT;
|
|
542
736
|
}
|
|
543
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
|
+
|
|
544
745
|
static eval_val eval_cmp(eval_val lv, eval_val rv, cmp_op op) {
|
|
545
746
|
/* Null comparisons: null != anything is true, null == null is true */
|
|
546
747
|
if (lv.tag == VAL_NULL || rv.tag == VAL_NULL) {
|
|
@@ -565,6 +766,20 @@ static eval_val eval_cmp(eval_val lv, eval_val rv, cmp_op op) {
|
|
|
565
766
|
}
|
|
566
767
|
}
|
|
567
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
|
+
|
|
568
783
|
/* Numeric comparison */
|
|
569
784
|
if (is_numeric(lv) && is_numeric(rv)) {
|
|
570
785
|
double l = to_double(lv);
|
|
@@ -688,7 +903,9 @@ static const char *val_to_str(eval_val v, char *buf, size_t buf_sz) {
|
|
|
688
903
|
switch (v.tag) {
|
|
689
904
|
case VAL_STR: return v.s;
|
|
690
905
|
case VAL_INT: snprintf(buf, buf_sz, "%lld", (long long)v.i); return buf;
|
|
691
|
-
case VAL_FLOAT:
|
|
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;
|
|
692
909
|
case VAL_BOOL: return v.b ? "true" : "false";
|
|
693
910
|
case VAL_DATE: tf_date_format(v.date, buf, buf_sz); return buf;
|
|
694
911
|
case VAL_TIMESTAMP: tf_timestamp_format(v.ts, buf, buf_sz); return buf;
|
|
@@ -870,13 +1087,43 @@ static eval_val eval_func(const char *name, eval_val *args, int n_args) {
|
|
|
870
1087
|
|
|
871
1088
|
/* if(cond, then, else) */
|
|
872
1089
|
if (strcmp(name, "if") == 0 && n_args == 3) {
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
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);
|
|
880
1127
|
}
|
|
881
1128
|
|
|
882
1129
|
/* coalesce(a, b, ...) — first non-null */
|
|
@@ -887,6 +1134,65 @@ static eval_val eval_func(const char *name, eval_val *args, int n_args) {
|
|
|
887
1134
|
return val_null();
|
|
888
1135
|
}
|
|
889
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
|
+
|
|
890
1196
|
/* ---- Math functions ---- */
|
|
891
1197
|
|
|
892
1198
|
/* abs(x) */
|
|
@@ -1137,6 +1443,8 @@ static eval_val eval_node(const tf_expr *e, const tf_batch *batch, size_t row) {
|
|
|
1137
1443
|
return val_float(e->lit_float);
|
|
1138
1444
|
case EXPR_LIT_STR:
|
|
1139
1445
|
return val_str(e->lit_str);
|
|
1446
|
+
case EXPR_LIT_BOOL:
|
|
1447
|
+
return val_bool(e->lit_bool);
|
|
1140
1448
|
case EXPR_COL_REF: {
|
|
1141
1449
|
int ci = tf_batch_col_index(batch, e->col_name);
|
|
1142
1450
|
if (ci < 0) return val_null();
|
package/csrc/expr.h
CHANGED
|
@@ -11,6 +11,7 @@ typedef enum {
|
|
|
11
11
|
EXPR_LIT_INT,
|
|
12
12
|
EXPR_LIT_FLOAT,
|
|
13
13
|
EXPR_LIT_STR,
|
|
14
|
+
EXPR_LIT_BOOL,
|
|
14
15
|
EXPR_COL_REF,
|
|
15
16
|
EXPR_CMP,
|
|
16
17
|
EXPR_AND,
|
|
@@ -34,6 +35,7 @@ struct tf_expr {
|
|
|
34
35
|
int64_t lit_int;
|
|
35
36
|
double lit_float;
|
|
36
37
|
char *lit_str;
|
|
38
|
+
int lit_bool;
|
|
37
39
|
char *col_name; /* for EXPR_COL_REF */
|
|
38
40
|
struct { /* for EXPR_CMP */
|
|
39
41
|
struct tf_expr *left;
|