jsql-neo 5.4.2 → 6.0.0-beta2
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/README.md +198 -14
- package/index.d.ts +29 -0
- package/index.js +12 -2
- package/lib/ast.js +214 -0
- package/lib/database.js +26 -60
- package/lib/mongo_server.js +1 -1
- package/lib/native_client.js +43 -66
- package/lib/plugin.js +227 -3
- package/lib/plugins/audit.js +37 -0
- package/lib/plugins/index.js +72 -0
- package/lib/plugins/logger.js +35 -0
- package/lib/plugins/metrics.js +38 -0
- package/lib/plugins/timestamps.js +43 -0
- package/lib/plugins/validation.js +128 -0
- package/lib/sql.js +1212 -84
- package/lib/wasm_client.js +41 -57
- package/native/jsql-neo-native.node +0 -0
- package/nativesrc/jsql-neo-core/Cargo.lock +8 -1
- package/nativesrc/jsql-neo-core/Cargo.toml +3 -1
- package/nativesrc/jsql-neo-core/src/engine/hybrid.rs +50 -13
- package/nativesrc/jsql-neo-core/src/engine/memory.rs +46 -23
- package/nativesrc/jsql-neo-core/src/engine/mod.rs +13 -1
- package/nativesrc/jsql-neo-core/src/engine/table.rs +338 -42
- package/nativesrc/jsql-neo-core/src/storage/wal.rs +245 -32
- package/nativesrc/jsql-neo-native/Cargo.lock +9 -2
- package/nativesrc/jsql-neo-native/Cargo.toml +9 -1
- package/nativesrc/jsql-neo-native/src/lib.rs +14 -2
- package/nativesrc/jsql-neo-wasm/Cargo.lock +9 -2
- package/nativesrc/jsql-neo-wasm/Cargo.toml +8 -1
- package/nativesrc/jsql-neo-wasm/src/lib.rs +18 -2
- package/package.json +4 -3
- package/test/sql-parser.test.js +450 -0
- package/wasm/browser.mjs +12 -0
- package/wasm/browser_bg.mjs +269 -248
- package/wasm/jsql_neo_wasm.d.ts +6 -0
- package/wasm/jsql_neo_wasm.js +248 -127
- package/wasm/jsql_neo_wasm_bg.wasm +0 -0
- package/wasm/jsql_neo_wasm_bg.wasm.d.ts +22 -22
- package/wasm/package.json +1 -1
|
@@ -161,6 +161,32 @@ fn millis_to_string(ms: u64) -> String {
|
|
|
161
161
|
}
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
+
/// 行内时间戳格式化缓存:批量写入的行共享同一个毫秒时间戳,
|
|
165
|
+
/// 命中缓存可避免每行都做一次 chrono/JS Date 格式化与字符串分配。
|
|
166
|
+
#[derive(Default)]
|
|
167
|
+
pub struct TimeCache {
|
|
168
|
+
last_ms: u64,
|
|
169
|
+
last_str: String,
|
|
170
|
+
valid: bool,
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
impl TimeCache {
|
|
174
|
+
pub fn new() -> Self {
|
|
175
|
+
Self { last_ms: 0, last_str: String::new(), valid: false }
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
#[inline]
|
|
179
|
+
fn write(&mut self, out: &mut String, ms: u64) {
|
|
180
|
+
if !self.valid || self.last_ms != ms {
|
|
181
|
+
self.last_str.clear();
|
|
182
|
+
self.last_str.push_str(&millis_to_string(ms));
|
|
183
|
+
self.last_ms = ms;
|
|
184
|
+
self.valid = true;
|
|
185
|
+
}
|
|
186
|
+
out.push_str(&self.last_str);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
164
190
|
impl Table {
|
|
165
191
|
pub fn new(name: String, schema: &[(String, FieldSchema)]) -> Self {
|
|
166
192
|
let pk_field = schema.iter()
|
|
@@ -215,6 +241,29 @@ impl Table {
|
|
|
215
241
|
}
|
|
216
242
|
}
|
|
217
243
|
|
|
244
|
+
/// 运行期加列(供 `db.addColumn` 使用):扩展 schema / field_order / field_index,
|
|
245
|
+
/// 并为已有行按「default → 非空类型默认值 → null」的既有语义回填新列的值。
|
|
246
|
+
/// 返回 true 表示新增成功;false 表示该列已存在(no-op)。
|
|
247
|
+
pub fn add_column(&mut self, name: &str, fs: &FieldSchema) -> bool {
|
|
248
|
+
if self.field_index.contains_key(name) {
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
let fill = match &fs.default {
|
|
252
|
+
Some(dv) => FieldValue::from_json(dv.clone()),
|
|
253
|
+
None => if !fs.nullable { default_value(&fs.field_type) } else { FieldValue::Null },
|
|
254
|
+
};
|
|
255
|
+
let pos = self.field_order.len();
|
|
256
|
+
self.schema.insert(name.to_string(), fs.clone());
|
|
257
|
+
self.field_order.push(name.to_string());
|
|
258
|
+
self.field_index.insert(name.to_string(), pos);
|
|
259
|
+
for i in 0..self.rows.len() {
|
|
260
|
+
let start = self.rows[i].values_start + self.rows[i].num_values;
|
|
261
|
+
self.values.insert(start, fill.clone());
|
|
262
|
+
self.rows[i].num_values += 1;
|
|
263
|
+
}
|
|
264
|
+
true
|
|
265
|
+
}
|
|
266
|
+
|
|
218
267
|
pub fn add_rows(&mut self, fields_batch: Vec<HashMap<String, serde_json::Value>>) -> Vec<u64> {
|
|
219
268
|
let now = now_millis();
|
|
220
269
|
let batch_len = fields_batch.len();
|
|
@@ -409,12 +458,32 @@ impl Table {
|
|
|
409
458
|
}
|
|
410
459
|
|
|
411
460
|
pub fn row_matches(&self, r: &RowStore, filter: &HashMap<String, serde_json::Value>) -> bool {
|
|
412
|
-
let
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
461
|
+
let conds = self.parse_filter(filter);
|
|
462
|
+
matches_filter(self.get_row_values(r), &conds)
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/// 预解析过滤条件:字段名 → 行内下标 + 条件,避免逐行重复做 HashMap 查找。
|
|
466
|
+
/// 支持字段级操作符($eq/$ne/$gt/$gte/$lt/$lte/$in/$nin/$between/$exists/$like/$regex),
|
|
467
|
+
/// 一个字段多个操作符(如 {$gte:18,$lte:65})会展开成多条,需同时满足。
|
|
468
|
+
pub(crate) fn parse_filter<'a>(&self, filter: &'a HashMap<String, serde_json::Value>)
|
|
469
|
+
-> Vec<(usize, Cond<'a>)>
|
|
470
|
+
{
|
|
471
|
+
let mut out = Vec::with_capacity(filter.len());
|
|
472
|
+
for (k, v) in filter {
|
|
473
|
+
// 未知字段用哨兵下标,按「字段不存在」参与匹配(不再静默放行)
|
|
474
|
+
let pos = self.field_index.get(k).copied().unwrap_or(ABSENT_FIELD);
|
|
475
|
+
match v {
|
|
476
|
+
serde_json::Value::Object(map)
|
|
477
|
+
if !map.is_empty() && map.keys().all(|kk| kk.starts_with('$')) =>
|
|
478
|
+
{
|
|
479
|
+
for (op, target) in map {
|
|
480
|
+
out.push((pos, parse_op(op, target)));
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
other => out.push((pos, Cond::Eq(other))),
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
out
|
|
418
487
|
}
|
|
419
488
|
|
|
420
489
|
pub fn get_row(&self, id: u64) -> Option<Row> {
|
|
@@ -429,6 +498,7 @@ impl Table {
|
|
|
429
498
|
|
|
430
499
|
pub fn get_rows_json(&self, ids: &[u64]) -> String {
|
|
431
500
|
let mut out = String::with_capacity(ids.len() * 128);
|
|
501
|
+
let mut tc = TimeCache::new();
|
|
432
502
|
out.push('[');
|
|
433
503
|
let mut first = true;
|
|
434
504
|
for &id in ids {
|
|
@@ -436,7 +506,7 @@ impl Table {
|
|
|
436
506
|
if let Some(rs) = self.rows.get(idx) {
|
|
437
507
|
if !first { out.push(','); }
|
|
438
508
|
first = false;
|
|
439
|
-
|
|
509
|
+
self.write_row_json_into(rs, &mut out, &mut tc);
|
|
440
510
|
}
|
|
441
511
|
}
|
|
442
512
|
}
|
|
@@ -458,30 +528,42 @@ impl Table {
|
|
|
458
528
|
}
|
|
459
529
|
|
|
460
530
|
fn to_row_json_string(&self, rs: &RowStore) -> String {
|
|
461
|
-
let vals = self.get_row_values(rs);
|
|
462
531
|
let mut out = String::with_capacity(160);
|
|
532
|
+
let mut tc = TimeCache::new();
|
|
533
|
+
self.write_row_json_into(rs, &mut out, &mut tc);
|
|
534
|
+
out
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/// 直接把一行写成 JSON 追加到 `out`。
|
|
538
|
+
/// 相比先构造 `Row`(HashMap<String, Value>)再 serde 序列化,
|
|
539
|
+
/// 省掉了每个字段一次的字符串分配与一次完整的二次序列化。
|
|
540
|
+
pub fn write_row_json_into(&self, rs: &RowStore, out: &mut String, tc: &mut TimeCache) {
|
|
541
|
+
let vals = self.get_row_values(rs);
|
|
463
542
|
out.push_str(r#"{"id":"#);
|
|
464
543
|
out.push_str(itoa::Buffer::new().format(rs.id));
|
|
465
544
|
out.push_str(r#","fields":{"#);
|
|
466
545
|
for (i, name) in self.field_order.iter().enumerate() {
|
|
467
546
|
if i > 0 { out.push(','); }
|
|
468
|
-
write_json_string(
|
|
547
|
+
write_json_string(out, name);
|
|
469
548
|
out.push(':');
|
|
470
|
-
|
|
471
|
-
v.write_json(
|
|
472
|
-
|
|
473
|
-
out.push_str("null");
|
|
549
|
+
match vals.get(i) {
|
|
550
|
+
Some(v) => v.write_json(out),
|
|
551
|
+
None => out.push_str("null"),
|
|
474
552
|
}
|
|
475
553
|
}
|
|
476
|
-
out.push_str(r#"},"created_at":"#);
|
|
477
|
-
|
|
478
|
-
out.push_str(r#","updated_at":"#);
|
|
479
|
-
|
|
480
|
-
out.push_str("}");
|
|
481
|
-
out
|
|
554
|
+
out.push_str(r#"},"created_at":""#);
|
|
555
|
+
tc.write(out, rs.created_at);
|
|
556
|
+
out.push_str(r#"","updated_at":""#);
|
|
557
|
+
tc.write(out, rs.updated_at);
|
|
558
|
+
out.push_str(r#""}"#);
|
|
482
559
|
}
|
|
483
560
|
|
|
484
561
|
pub fn update_row(&mut self, id: u64, data: HashMap<String, serde_json::Value>) -> bool {
|
|
562
|
+
self.update_row_ref(id, &data)
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/// 借用版本:批量按 filter 更新时复用同一份 data,避免每行都克隆整个 HashMap。
|
|
566
|
+
pub fn update_row_ref(&mut self, id: u64, data: &HashMap<String, serde_json::Value>) -> bool {
|
|
485
567
|
let idx = match self.pk_index.get(&id) {
|
|
486
568
|
Some(i) => *i,
|
|
487
569
|
None => return false,
|
|
@@ -492,9 +574,9 @@ impl Table {
|
|
|
492
574
|
let nv = row.num_values;
|
|
493
575
|
let s = row.values_start;
|
|
494
576
|
for (k, v) in data {
|
|
495
|
-
if let Some(&pos) = self.field_index.get(
|
|
577
|
+
if let Some(&pos) = self.field_index.get(k) {
|
|
496
578
|
if pos < nv {
|
|
497
|
-
self.values[s + pos] = FieldValue::from_json(v);
|
|
579
|
+
self.values[s + pos] = FieldValue::from_json(v.clone());
|
|
498
580
|
}
|
|
499
581
|
}
|
|
500
582
|
}
|
|
@@ -651,38 +733,252 @@ impl Table {
|
|
|
651
733
|
Ok(())
|
|
652
734
|
}
|
|
653
735
|
|
|
736
|
+
/// 与 `find_rows` 语义一致,但直接把结果写成 JSON 字符串。
|
|
737
|
+
/// - 无排序时流式扫描,凑够 limit 立刻停止,不再先收集全表行指针
|
|
738
|
+
/// - 命中 limit 后仍多探一行,用于判断是否还有下一页(has_more)
|
|
739
|
+
/// 返回 (json, has_more)
|
|
740
|
+
pub fn find_rows_json(&self,
|
|
741
|
+
filter: &Option<HashMap<String, serde_json::Value>>,
|
|
742
|
+
limit: Option<usize>, offset: Option<usize>,
|
|
743
|
+
order_by: &Option<String>, order: &Option<String>) -> (String, bool) {
|
|
744
|
+
let o = offset.unwrap_or(0);
|
|
745
|
+
let cap = limit.unwrap_or(usize::MAX);
|
|
746
|
+
let conds = filter.as_ref().map(|f| self.parse_filter(f)).unwrap_or_default();
|
|
747
|
+
|
|
748
|
+
let mut out = String::with_capacity(128);
|
|
749
|
+
out.push('[');
|
|
750
|
+
let mut written = 0usize;
|
|
751
|
+
let mut has_more = false;
|
|
752
|
+
let mut tc = TimeCache::new();
|
|
753
|
+
|
|
754
|
+
let ordered = order_by.as_ref().and_then(|f| {
|
|
755
|
+
self.field_index.get(f).map(|&p| (p, order.as_deref() == Some("desc")))
|
|
756
|
+
});
|
|
757
|
+
|
|
758
|
+
match ordered {
|
|
759
|
+
Some((pos, desc)) => {
|
|
760
|
+
// 排序路径必须先收集全部匹配行
|
|
761
|
+
let mut matched: Vec<&RowStore> = self.rows.iter()
|
|
762
|
+
.filter(|r| matches_filter(self.get_row_values(r), &conds))
|
|
763
|
+
.collect();
|
|
764
|
+
matched.sort_by(|a, b| {
|
|
765
|
+
let cmp = cmp_field_values(
|
|
766
|
+
self.get_row_values(a).get(pos),
|
|
767
|
+
self.get_row_values(b).get(pos),
|
|
768
|
+
);
|
|
769
|
+
if desc { cmp.reverse() } else { cmp }
|
|
770
|
+
});
|
|
771
|
+
has_more = limit.is_some() && matched.len().saturating_sub(o) > cap;
|
|
772
|
+
for r in matched.into_iter().skip(o).take(cap) {
|
|
773
|
+
if written > 0 { out.push(','); }
|
|
774
|
+
self.write_row_json_into(r, &mut out, &mut tc);
|
|
775
|
+
written += 1;
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
None => {
|
|
779
|
+
let mut skipped = 0usize;
|
|
780
|
+
for r in &self.rows {
|
|
781
|
+
if !matches_filter(self.get_row_values(r), &conds) { continue; }
|
|
782
|
+
if skipped < o { skipped += 1; continue; }
|
|
783
|
+
if written >= cap { has_more = true; break; }
|
|
784
|
+
if written > 0 { out.push(','); }
|
|
785
|
+
self.write_row_json_into(r, &mut out, &mut tc);
|
|
786
|
+
written += 1;
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
out.push(']');
|
|
792
|
+
(out, has_more)
|
|
793
|
+
}
|
|
794
|
+
|
|
654
795
|
pub fn find_rows(&self, filter: &Option<HashMap<String, serde_json::Value>>,
|
|
655
796
|
limit: Option<usize>, offset: Option<usize>,
|
|
656
797
|
order_by: &Option<String>, order: &Option<String>) -> Vec<Row> {
|
|
657
|
-
let
|
|
798
|
+
let o = offset.unwrap_or(0);
|
|
799
|
+
let l = limit.unwrap_or(usize::MAX);
|
|
800
|
+
let conds = filter.as_ref().map(|f| self.parse_filter(f)).unwrap_or_default();
|
|
658
801
|
|
|
659
|
-
if let Some(
|
|
660
|
-
let
|
|
661
|
-
|
|
802
|
+
if let Some(field) = order_by.as_ref().filter(|f| self.field_index.contains_key(*f)) {
|
|
803
|
+
let pos = self.field_index[field];
|
|
804
|
+
let desc = order.as_deref() == Some("desc");
|
|
805
|
+
let mut results: Vec<&RowStore> = self.rows.iter()
|
|
806
|
+
.filter(|r| matches_filter(self.get_row_values(r), &conds))
|
|
662
807
|
.collect();
|
|
663
|
-
results.
|
|
664
|
-
let
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
808
|
+
results.sort_by(|a, b| {
|
|
809
|
+
let cmp = cmp_field_values(
|
|
810
|
+
self.get_row_values(a).get(pos),
|
|
811
|
+
self.get_row_values(b).get(pos),
|
|
812
|
+
);
|
|
813
|
+
if desc { cmp.reverse() } else { cmp }
|
|
668
814
|
});
|
|
815
|
+
return results.into_iter().skip(o).take(l).map(|r| self.to_row(r)).collect();
|
|
669
816
|
}
|
|
670
817
|
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
818
|
+
// 无排序:流式扫描,凑够 limit 立刻停止,不先收集全表行指针
|
|
819
|
+
let mut out = Vec::with_capacity(l.min(self.rows.len()));
|
|
820
|
+
let mut skipped = 0usize;
|
|
821
|
+
for r in &self.rows {
|
|
822
|
+
if !matches_filter(self.get_row_values(r), &conds) { continue; }
|
|
823
|
+
if skipped < o { skipped += 1; continue; }
|
|
824
|
+
if out.len() >= l { break; }
|
|
825
|
+
out.push(self.to_row(r));
|
|
826
|
+
}
|
|
827
|
+
out
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
/// 过滤条件里出现 schema 之外的字段时用它作为哨兵下标:
|
|
832
|
+
/// `vals.get(SENTINEL)` 恒为 None,等价于「该字段不存在」,
|
|
833
|
+
/// 于是 {$exists:false}/{$ne:x} 等语义与 lib/table.js 的 `_matchRow` 一致,
|
|
834
|
+
/// 而 `{noSuchField: 5}` 会正确地匹配 0 行(旧实现会静默放行全部行)。
|
|
835
|
+
const ABSENT_FIELD: usize = usize::MAX;
|
|
836
|
+
|
|
837
|
+
/// 单字段查询条件(字段名已在解析阶段降为行内下标)
|
|
838
|
+
pub(crate) enum Cond<'a> {
|
|
839
|
+
Eq(&'a serde_json::Value),
|
|
840
|
+
Ne(&'a serde_json::Value),
|
|
841
|
+
Gt(&'a serde_json::Value),
|
|
842
|
+
Gte(&'a serde_json::Value),
|
|
843
|
+
Lt(&'a serde_json::Value),
|
|
844
|
+
Lte(&'a serde_json::Value),
|
|
845
|
+
In(&'a serde_json::Value),
|
|
846
|
+
Nin(&'a serde_json::Value),
|
|
847
|
+
/// 闭区间 [lo, hi]
|
|
848
|
+
Between(&'a serde_json::Value),
|
|
849
|
+
Exists(bool),
|
|
850
|
+
/// `$like`:SQL 风格通配(`%` = 任意多字符,`_` = 单字符),大小写不敏感。
|
|
851
|
+
/// 模式在解析阶段编译一次,逐行只做匹配。
|
|
852
|
+
Like(Box<regex_lite::Regex>),
|
|
853
|
+
/// `$regex`:JavaScript 风格正则,大小写敏感。
|
|
854
|
+
Regex(Box<regex_lite::Regex>),
|
|
855
|
+
/// 不认识的写法:永不匹配(与旧的等值语义一致,避免静默放行全部行)
|
|
856
|
+
Never,
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
/// 解析单个操作符。语义对齐 lib/table.js 的 `_matchOperator`。
|
|
860
|
+
pub(crate) fn parse_op<'a>(op: &str, target: &'a serde_json::Value) -> Cond<'a> {
|
|
861
|
+
match op {
|
|
862
|
+
"$eq" => Cond::Eq(target),
|
|
863
|
+
"$ne" => Cond::Ne(target),
|
|
864
|
+
"$gt" => Cond::Gt(target),
|
|
865
|
+
"$gte" => Cond::Gte(target),
|
|
866
|
+
"$lt" => Cond::Lt(target),
|
|
867
|
+
"$lte" => Cond::Lte(target),
|
|
868
|
+
"$in" => Cond::In(target),
|
|
869
|
+
"$nin" => Cond::Nin(target),
|
|
870
|
+
"$between" => Cond::Between(target),
|
|
871
|
+
"$exists" => Cond::Exists(target.as_bool().unwrap_or(true)),
|
|
872
|
+
// $like / $regex 目标必须是字符串;非法或编译失败的模式退化为「永不匹配」。
|
|
873
|
+
// regex-lite 为线性时间引擎,不存在灾难性回溯,无需 JS 侧 isSafeRegex 的长度拦截。
|
|
874
|
+
"$like" => match target.as_str() {
|
|
875
|
+
Some(p) => regex_lite::Regex::new(&like_to_regex(p))
|
|
876
|
+
.map(|re| Cond::Like(Box::new(re)))
|
|
877
|
+
.unwrap_or(Cond::Never),
|
|
878
|
+
None => Cond::Never,
|
|
879
|
+
},
|
|
880
|
+
"$regex" => match target.as_str() {
|
|
881
|
+
Some(p) => regex_lite::Regex::new(p)
|
|
882
|
+
.map(|re| Cond::Regex(Box::new(re)))
|
|
883
|
+
.unwrap_or(Cond::Never),
|
|
884
|
+
None => Cond::Never,
|
|
885
|
+
},
|
|
886
|
+
_ => Cond::Never,
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
/// 把 SQL LIKE 模式转成锚定的、大小写不敏感的正则:
|
|
891
|
+
/// 先转义正则元字符,再把 `%` 视作 `.*`、`_` 视作 `.`,
|
|
892
|
+
/// 与 lib/table.js 的 `$like` 实现逐字符对齐。
|
|
893
|
+
fn like_to_regex(pattern: &str) -> String {
|
|
894
|
+
let mut out = String::with_capacity(pattern.len() + 8);
|
|
895
|
+
out.push_str("(?i)^");
|
|
896
|
+
for ch in pattern.chars() {
|
|
897
|
+
match ch {
|
|
898
|
+
'%' => out.push_str(".*"),
|
|
899
|
+
'_' => out.push('.'),
|
|
900
|
+
'.' | '*' | '+' | '?' | '^' | '$' | '{' | '}' | '(' | ')' | '|' | '[' | ']' | '\\' => {
|
|
901
|
+
out.push('\\');
|
|
902
|
+
out.push(ch);
|
|
680
903
|
}
|
|
904
|
+
c => out.push(c),
|
|
681
905
|
}
|
|
906
|
+
}
|
|
907
|
+
out.push('$');
|
|
908
|
+
out
|
|
909
|
+
}
|
|
682
910
|
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
911
|
+
/// 把存储值转成 JS `String(value)` 的等价字符串(供 `$regex` 语义使用)。
|
|
912
|
+
fn field_value_to_string(v: &FieldValue) -> std::borrow::Cow<'_, str> {
|
|
913
|
+
use std::borrow::Cow;
|
|
914
|
+
match v {
|
|
915
|
+
FieldValue::Null => Cow::Borrowed("null"),
|
|
916
|
+
FieldValue::Bool(b) => Cow::Owned(b.to_string()),
|
|
917
|
+
FieldValue::Int(n) => Cow::Owned(n.to_string()),
|
|
918
|
+
FieldValue::Float(f) => Cow::Owned(f.to_string()),
|
|
919
|
+
FieldValue::String(s) => Cow::Borrowed(s),
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
#[inline]
|
|
924
|
+
pub(crate) fn matches_filter(vals: &[FieldValue], conds: &[(usize, Cond)]) -> bool {
|
|
925
|
+
// 最常见形态(单字段等值,如按主键点查)走直通,避免逐行做枚举分派
|
|
926
|
+
if let [(pos, Cond::Eq(t))] = conds {
|
|
927
|
+
return vals.get(*pos).map_or(false, |fv| fv.eq_json(t));
|
|
928
|
+
}
|
|
929
|
+
conds.iter().all(|(pos, c)| match_cond(vals.get(*pos), c))
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
#[inline]
|
|
933
|
+
fn match_cond(fv: Option<&FieldValue>, cond: &Cond) -> bool {
|
|
934
|
+
use std::cmp::Ordering;
|
|
935
|
+
let cmp = |t: &serde_json::Value, want: fn(Ordering) -> bool| {
|
|
936
|
+
fv.and_then(|v| cmp_to_json(v, t)).map_or(false, want)
|
|
937
|
+
};
|
|
938
|
+
match cond {
|
|
939
|
+
Cond::Never => false,
|
|
940
|
+
Cond::Exists(want) => fv.is_some() == *want,
|
|
941
|
+
Cond::Eq(t) => fv.map_or(false, |v| v.eq_json(t)),
|
|
942
|
+
Cond::Ne(t) => !fv.map_or(false, |v| v.eq_json(t)),
|
|
943
|
+
Cond::In(t) => match t.as_array() {
|
|
944
|
+
Some(arr) => fv.map_or(false, |v| arr.iter().any(|e| v.eq_json(e))),
|
|
945
|
+
None => false,
|
|
946
|
+
},
|
|
947
|
+
Cond::Nin(t) => match t.as_array() {
|
|
948
|
+
Some(arr) => !fv.map_or(false, |v| arr.iter().any(|e| v.eq_json(e))),
|
|
949
|
+
None => false,
|
|
950
|
+
},
|
|
951
|
+
Cond::Gt(t) => cmp(t, |o| o == Ordering::Greater),
|
|
952
|
+
Cond::Gte(t) => cmp(t, |o| o != Ordering::Less),
|
|
953
|
+
Cond::Lt(t) => cmp(t, |o| o == Ordering::Less),
|
|
954
|
+
Cond::Lte(t) => cmp(t, |o| o != Ordering::Greater),
|
|
955
|
+
Cond::Between(t) => match t.as_array() {
|
|
956
|
+
Some(arr) if arr.len() == 2 => {
|
|
957
|
+
cmp(&arr[0], |o| o != Ordering::Less) && cmp(&arr[1], |o| o != Ordering::Greater)
|
|
958
|
+
}
|
|
959
|
+
_ => false,
|
|
960
|
+
},
|
|
961
|
+
// $like 只对字符串字段生效(对齐 lib/table.js:非字符串直接 false)
|
|
962
|
+
Cond::Like(re) => matches!(fv, Some(FieldValue::String(s)) if re.is_match(s)),
|
|
963
|
+
// $regex 先把值字符串化再匹配(对齐 lib/table.js 的 String(value))
|
|
964
|
+
Cond::Regex(re) => fv.map_or(false, |v| re.is_match(&field_value_to_string(v))),
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
/// 存储值与 JSON 目标值比较;类型不可比或含 null/NaN 时返回 None(视为不匹配,
|
|
969
|
+
/// 对齐 JS 里 `value !== null && value > expected` 的写法)。
|
|
970
|
+
fn cmp_to_json(fv: &FieldValue, jv: &serde_json::Value) -> Option<std::cmp::Ordering> {
|
|
971
|
+
use serde_json::Value as J;
|
|
972
|
+
match (fv, jv) {
|
|
973
|
+
(FieldValue::Null, _) | (_, J::Null) => None,
|
|
974
|
+
(FieldValue::Int(a), J::Number(n)) => match n.as_i64() {
|
|
975
|
+
Some(b) => Some(a.cmp(&b)),
|
|
976
|
+
None => n.as_f64().and_then(|b| (*a as f64).partial_cmp(&b)),
|
|
977
|
+
},
|
|
978
|
+
(FieldValue::Float(a), J::Number(n)) => n.as_f64().and_then(|b| a.partial_cmp(&b)),
|
|
979
|
+
(FieldValue::Bool(a), J::Bool(b)) => Some(a.cmp(b)),
|
|
980
|
+
(FieldValue::String(a), J::String(b)) => Some(a.as_str().cmp(b.as_str())),
|
|
981
|
+
_ => None,
|
|
686
982
|
}
|
|
687
983
|
}
|
|
688
984
|
|