jsql-neo 3.5.2 → 4.0.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.
Files changed (36) hide show
  1. package/LICENSE +202 -0
  2. package/bin/jsql +97 -0
  3. package/index.js +20 -0
  4. package/lib/database.js +484 -28
  5. package/lib/mod.js +316 -0
  6. package/lib/mysql_compat.js +232 -0
  7. package/lib/mysql_server.js +514 -0
  8. package/lib/native_client.js +470 -0
  9. package/lib/nedb_compat.js +506 -0
  10. package/lib/plugin.js +35 -0
  11. package/lib/sql.js +1160 -0
  12. package/lib/table.js +42 -21
  13. package/lib/wasm_client.js +176 -8
  14. package/native/jsql-neo-native.node +0 -0
  15. package/nativesrc/jsql-neo-core/Cargo.toml +24 -0
  16. package/nativesrc/jsql-neo-core/src/engine/hybrid.rs +449 -0
  17. package/nativesrc/jsql-neo-core/src/engine/memory.rs +147 -0
  18. package/nativesrc/jsql-neo-core/src/engine/mod.rs +41 -0
  19. package/nativesrc/jsql-neo-core/src/engine/table.rs +664 -0
  20. package/nativesrc/jsql-neo-core/src/lib.rs +3 -0
  21. package/nativesrc/jsql-neo-core/src/storage/mod.rs +1 -0
  22. package/nativesrc/jsql-neo-core/src/storage/persistent.rs +2 -0
  23. package/nativesrc/jsql-neo-core/src/storage/wal.rs +85 -0
  24. package/nativesrc/jsql-neo-core/src/types.rs +94 -0
  25. package/nativesrc/jsql-neo-native/Cargo.lock +606 -0
  26. package/nativesrc/jsql-neo-native/Cargo.toml +16 -0
  27. package/nativesrc/jsql-neo-native/jsql-neo-native.node +0 -0
  28. package/nativesrc/jsql-neo-native/package.json +7 -0
  29. package/nativesrc/jsql-neo-native/src/lib.rs +281 -0
  30. package/package.json +9 -1
  31. package/wasm/jsql_neo_wasm.d.ts +8 -0
  32. package/wasm/jsql_neo_wasm.js +455 -6
  33. package/wasm/jsql_neo_wasm_bg.js +22 -0
  34. package/wasm/jsql_neo_wasm_bg.wasm +0 -0
  35. package/wasm/jsql_neo_wasm_bg.wasm.d.ts +4 -0
  36. package/wasm/package.json +1 -8
@@ -0,0 +1,449 @@
1
+ // HybridEngine — Redis 式存储引擎(内存缓存 + 异步刷盘 + LRU 驱逐 + 惰性加载)
2
+ // 模式:
3
+ // - store_dir = None → 纯内存(等同 MemoryEngine)
4
+ // - store_dir = Some(..) → hybrid/disk:写内存并标记脏表,flush_dirty() 增量落盘;
5
+ // 表被 evict() 驱逐后,下一次访问自动从磁盘惰性加载
6
+
7
+ use std::cell::RefCell;
8
+ use std::collections::{HashMap, HashSet};
9
+ use std::fs;
10
+ use std::path::{Path, PathBuf};
11
+
12
+ use crate::engine::{Engine, MemoryEngine, Table};
13
+ use crate::types::{FieldSchema, TableDefinition};
14
+ use serde::{Deserialize, Serialize};
15
+
16
+ #[derive(Clone, Serialize, Deserialize)]
17
+ struct TableMeta {
18
+ file: String,
19
+ #[serde(with = "crate::types::schema_order_preserving")]
20
+ schema: Vec<(String, FieldSchema)>,
21
+ }
22
+
23
+ pub struct HybridEngine {
24
+ mem: RefCell<MemoryEngine>,
25
+ store_dir: Option<PathBuf>,
26
+ meta: RefCell<HashMap<String, TableMeta>>,
27
+ dirty: RefCell<HashSet<String>>,
28
+ last_access: RefCell<HashMap<String, u64>>,
29
+ }
30
+
31
+ fn now_millis() -> u64 {
32
+ std::time::SystemTime::now()
33
+ .duration_since(std::time::UNIX_EPOCH)
34
+ .unwrap_or_default()
35
+ .as_millis() as u64
36
+ }
37
+
38
+ fn table_file_name(name: &str) -> String {
39
+ let mut out = String::with_capacity(name.len() + 5);
40
+ for c in name.chars() {
41
+ if c.is_ascii_alphanumeric() || c == '_' || c == '-' || c == '.' {
42
+ out.push(c);
43
+ } else {
44
+ out.push('_');
45
+ }
46
+ }
47
+ out.push_str(".jsql.json");
48
+ out
49
+ }
50
+
51
+ impl HybridEngine {
52
+ pub fn new() -> Self {
53
+ Self {
54
+ mem: RefCell::new(MemoryEngine::new()),
55
+ store_dir: None,
56
+ meta: RefCell::new(HashMap::new()),
57
+ dirty: RefCell::new(HashSet::new()),
58
+ last_access: RefCell::new(HashMap::new()),
59
+ }
60
+ }
61
+
62
+ /// 打开磁盘存储目录(mode: 'hybrid' | 'disk' | 'memory';memory 或空目录忽略)
63
+ pub fn open(&mut self, dir: &str, mode: &str) -> Result<(), String> {
64
+ if dir.is_empty() || mode == "memory" {
65
+ self.store_dir = None;
66
+ return Ok(());
67
+ }
68
+ let p = PathBuf::from(dir);
69
+ fs::create_dir_all(&p).map_err(|e| format!("cannot create dir {}: {}", dir, e))?;
70
+ let meta_path = p.join("meta.json");
71
+ if meta_path.exists() {
72
+ if let Ok(json) = fs::read_to_string(&meta_path) {
73
+ if let Ok(parsed) = serde_json::from_str::<serde_json::Value>(&json) {
74
+ if let Some(tables) = parsed.get("tables").and_then(|v| v.as_object()) {
75
+ let mut meta = HashMap::new();
76
+ for (name, tv) in tables {
77
+ let tm: TableMeta = serde_json::from_value(tv.clone()).unwrap_or_else(|_| TableMeta {
78
+ file: table_file_name(name),
79
+ schema: Vec::new(),
80
+ });
81
+ meta.insert(name.clone(), tm);
82
+ }
83
+ self.meta = RefCell::new(meta);
84
+ }
85
+ }
86
+ }
87
+ }
88
+ self.store_dir = Some(p);
89
+ Ok(())
90
+ }
91
+
92
+ pub fn close(&mut self) {
93
+ if self.store_dir.is_none() { return; }
94
+ self.flush_dirty();
95
+ self.save_meta();
96
+ }
97
+
98
+ /// 磁盘上的表 schema(供 napi/Node 层获取已落盘表结构)
99
+ pub fn table_schema(&self, name: &str) -> Option<Vec<(String, FieldSchema)>> {
100
+ self.meta.borrow().get(name).map(|m| m.schema.clone())
101
+ }
102
+
103
+ /// 当前驻留内存的表数量(供 evict 剩余量统计)
104
+ pub fn mem_count(&self) -> usize {
105
+ self.mem.borrow().tables.len()
106
+ }
107
+
108
+ fn table_path(&self, name: &str, file: &str) -> PathBuf {
109
+ self.store_dir.as_ref().map(|d| d.join(file)).unwrap_or_else(|| PathBuf::from(file))
110
+ }
111
+
112
+ fn save_meta(&self) {
113
+ let dir = match &self.store_dir { Some(d) => d, None => return };
114
+ let mut tables = serde_json::Map::new();
115
+ for (name, tm) in self.meta.borrow().iter() {
116
+ let mut sv = serde_json::Map::new();
117
+ for (k, fs) in &tm.schema {
118
+ if let Ok(fv) = serde_json::to_value(fs) {
119
+ sv.insert(k.clone(), fv);
120
+ }
121
+ }
122
+ let mut tv = serde_json::Map::new();
123
+ tv.insert("file".into(), serde_json::Value::String(tm.file.clone()));
124
+ tv.insert("schema".into(), serde_json::Value::Object(sv));
125
+ tables.insert(name.clone(), serde_json::Value::Object(tv));
126
+ }
127
+ let doc = serde_json::json!({ "version": 1, "tables": tables });
128
+ let tmp = dir.join("meta.json.tmp");
129
+ if fs::write(&tmp, serde_json::to_string_pretty(&doc).unwrap_or_default()).is_ok() {
130
+ let _ = fs::rename(&tmp, dir.join("meta.json"));
131
+ }
132
+ }
133
+
134
+ fn touch(&self, name: &str) {
135
+ self.last_access.borrow_mut().insert(name.to_string(), now_millis());
136
+ }
137
+
138
+ /// 惰性加载:表不在内存但从磁盘存在 → 读文件重建
139
+ pub fn ensure_table(&self, name: &str) {
140
+ if self.mem.borrow().tables.contains_key(name) { return; }
141
+ if self.store_dir.is_none() { return; }
142
+ let tm = self.meta.borrow().get(name).cloned();
143
+ let Some(tm) = tm else { return };
144
+ let path = self.table_path(name, &tm.file);
145
+ let Ok(json) = fs::read_to_string(&path) else { return };
146
+ let mut t = Table::new(name.to_string(), &tm.schema);
147
+ if t.import_from_json(&json).is_err() { return; }
148
+ self.mem.borrow_mut().tables.insert(name.to_string(), t);
149
+ self.touch(name);
150
+ }
151
+
152
+ fn mark_dirty(&self, name: &str) {
153
+ self.touch(name);
154
+ self.dirty.borrow_mut().insert(name.to_string());
155
+ }
156
+
157
+ /// 把单个表写盘(原子:tmp + rename)
158
+ pub fn flush_table(&self, name: &str) -> Result<(), String> {
159
+ if self.store_dir.is_none() { return Ok(()); }
160
+ let json = {
161
+ let mem = self.mem.borrow();
162
+ let t = mem.tables.get(name).ok_or_else(|| format!("table '{}' not found", name))?;
163
+ t.export_all()
164
+ };
165
+ let file = {
166
+ let mut meta = self.meta.borrow_mut();
167
+ if let Some(tm) = meta.get_mut(name) {
168
+ tm.file.clone()
169
+ } else {
170
+ let f = table_file_name(name);
171
+ let schema = {
172
+ let mem = self.mem.borrow();
173
+ mem.tables.get(name)
174
+ .map(|t| t.field_order.iter()
175
+ .filter_map(|n| t.schema.get(n).map(|fs| (n.clone(), fs.clone())))
176
+ .collect())
177
+ .unwrap_or_default()
178
+ };
179
+ meta.insert(name.to_string(), TableMeta { file: f.clone(), schema });
180
+ f
181
+ }
182
+ };
183
+ let dir = self.store_dir.as_ref().unwrap();
184
+ let tmp = dir.join(format!("{}.tmp", file));
185
+ fs::write(&tmp, json).map_err(|e| format!("write {}: {}", tmp.display(), e))?;
186
+ let final_path = self.table_path(name, &file);
187
+ fs::rename(&tmp, &final_path).map_err(|e| format!("rename {}: {}", final_path.display(), e))?;
188
+ self.save_meta();
189
+ Ok(())
190
+ }
191
+
192
+ /// 增量落盘所有脏表
193
+ pub fn flush_dirty(&self) -> Result<usize, String> {
194
+ if self.store_dir.is_none() { return Ok(0); }
195
+ let names: Vec<String> = self.dirty.borrow().iter().cloned().collect();
196
+ if names.is_empty() { return Ok(0); }
197
+ let mut flushed = 0;
198
+ for name in &names {
199
+ if self.flush_table(name).is_ok() {
200
+ self.dirty.borrow_mut().remove(name);
201
+ flushed += 1;
202
+ }
203
+ }
204
+ self.save_meta();
205
+ Ok(flushed)
206
+ }
207
+
208
+ /// LRU 驱逐一个最冷已落盘表(返回被驱逐的表名)
209
+ pub fn evict_one(&self) -> Option<String> {
210
+ let names: Vec<String> = self.mem.borrow().tables.keys().cloned().collect();
211
+ let dirty = self.dirty.borrow();
212
+ let candidates: Vec<String> = names.into_iter()
213
+ .filter(|n| !dirty.contains(n) && self.meta.borrow().contains_key(n))
214
+ .collect();
215
+ if candidates.is_empty() { return None; }
216
+ let mut sorted = candidates;
217
+ sorted.sort_by_key(|n| self.last_access.borrow().get(n).copied().unwrap_or(0));
218
+ let victim = sorted.remove(0);
219
+ self.mem.borrow_mut().tables.remove(&victim);
220
+ Some(victim)
221
+ }
222
+
223
+ /// 批量二进制插入(native_client encodeBatch 格式),兼容原 jsql_insert_buf 性能路径
224
+ pub fn insert_buf(&self, table: &str, buf: &[u8]) -> Result<Vec<u64>, String> {
225
+ self.ensure_table(table);
226
+ let mut mem = self.mem.borrow_mut();
227
+ let t = mem.tables.get_mut(table).ok_or_else(|| format!("table '{}' not found", table))?;
228
+ if buf.is_empty() { return Err("empty data".to_string()); }
229
+
230
+ let mut off = 0usize;
231
+ let n_fields = buf[off] as usize; off += 1;
232
+ if n_fields == 0 || off + n_fields * 2 > buf.len() { return Err("invalid header".to_string()); }
233
+
234
+ let mut field_names = Vec::with_capacity(n_fields);
235
+ for _ in 0..n_fields {
236
+ let name_len = buf[off] as usize; off += 1;
237
+ if off + name_len > buf.len() { return Err("truncated header".to_string()); }
238
+ let name = std::str::from_utf8(&buf[off..off + name_len]).map_err(|_| "bad field name")?.to_string();
239
+ off += name_len;
240
+ field_names.push(name);
241
+ }
242
+
243
+ if off + 4 > buf.len() { return Err("truncated".to_string()); }
244
+ let row_count = u32::from_le_bytes([buf[off], buf[off+1], buf[off+2], buf[off+3]]) as usize;
245
+ off += 4;
246
+ if row_count == 0 { return Ok(Vec::new()); }
247
+
248
+ let mut parse_fv = |off: &mut usize| -> Result<crate::engine::FieldValue, String> {
249
+ if *off >= buf.len() { return Err("truncated row".into()); }
250
+ let tag = buf[*off]; *off += 1;
251
+ let fv = match tag {
252
+ 0 => crate::engine::FieldValue::Null,
253
+ 1 => {
254
+ if *off + 8 > buf.len() { return Err("truncated int".into()); }
255
+ let n = i64::from_le_bytes(buf[*off..*off+8].try_into().unwrap());
256
+ *off += 8;
257
+ crate::engine::FieldValue::Int(n)
258
+ }
259
+ 2 => {
260
+ if *off + 8 > buf.len() { return Err("truncated float".into()); }
261
+ let n = f64::from_le_bytes(buf[*off..*off+8].try_into().unwrap());
262
+ *off += 8;
263
+ crate::engine::FieldValue::Float(n)
264
+ }
265
+ 3 => {
266
+ if *off + 4 > buf.len() { return Err("truncated len".into()); }
267
+ let len = u32::from_le_bytes(buf[*off..*off+4].try_into().unwrap()) as usize;
268
+ *off += 4;
269
+ if *off + len > buf.len() { return Err("truncated str".into()); }
270
+ let s = std::str::from_utf8(&buf[*off..*off+len]).map_err(|_| "bad utf8")?.to_string();
271
+ *off += len;
272
+ crate::engine::FieldValue::String(s)
273
+ }
274
+ 4 => {
275
+ if *off >= buf.len() { return Err("truncated bool".into()); }
276
+ let b = buf[*off] != 0; *off += 1;
277
+ crate::engine::FieldValue::Bool(b)
278
+ }
279
+ 5 => {
280
+ if *off + 4 > buf.len() { return Err("truncated i32".into()); }
281
+ let n = i32::from_le_bytes(buf[*off..*off+4].try_into().unwrap()) as i64;
282
+ *off += 4;
283
+ crate::engine::FieldValue::Int(n)
284
+ }
285
+ _ => crate::engine::FieldValue::Null,
286
+ };
287
+ Ok(fv)
288
+ };
289
+
290
+ let mut row_batch: Vec<Vec<crate::engine::FieldValue>> = Vec::with_capacity(row_count);
291
+ for _ in 0..row_count {
292
+ let mut row = Vec::with_capacity(n_fields);
293
+ for _ in 0..n_fields { row.push(parse_fv(&mut off)?); }
294
+ row_batch.push(row);
295
+ }
296
+ let ids = t.add_rows_vec_fv(&field_names, row_batch);
297
+ drop(mem);
298
+ self.mark_dirty(table);
299
+ Ok(ids)
300
+ }
301
+ }
302
+
303
+ impl Default for HybridEngine {
304
+ fn default() -> Self { Self::new() }
305
+ }
306
+
307
+ impl Engine for HybridEngine {
308
+ fn create_table(&mut self, def: TableDefinition) -> Result<(), String> {
309
+ if self.mem.borrow().tables.contains_key(&def.name) || self.meta.borrow().contains_key(&def.name) {
310
+ return Err(format!("table '{}' already exists", def.name));
311
+ }
312
+ self.mem.borrow_mut().create_table(def.clone())?;
313
+ if self.store_dir.is_some() {
314
+ let file = table_file_name(&def.name);
315
+ self.meta.borrow_mut().insert(def.name.clone(), TableMeta { file, schema: def.schema });
316
+ self.save_meta();
317
+ }
318
+ Ok(())
319
+ }
320
+
321
+ fn drop_table(&mut self, name: &str) -> Result<(), String> {
322
+ let in_mem = self.mem.borrow().tables.contains_key(name);
323
+ let in_meta = self.meta.borrow().contains_key(name);
324
+ if !in_mem && !in_meta { return Err(format!("table '{}' not found", name)); }
325
+ if self.store_dir.is_some() {
326
+ if let Some(tm) = self.meta.borrow().get(name) {
327
+ let p = self.table_path(name, &tm.file);
328
+ let _ = fs::remove_file(&p);
329
+ }
330
+ self.meta.borrow_mut().remove(name);
331
+ self.dirty.borrow_mut().remove(name);
332
+ self.save_meta();
333
+ }
334
+ if in_mem { self.mem.borrow_mut().drop_table(name)?; }
335
+ Ok(())
336
+ }
337
+
338
+ fn has_table(&self, name: &str) -> bool {
339
+ self.mem.borrow().tables.contains_key(name) || self.meta.borrow().contains_key(name)
340
+ }
341
+
342
+ fn list_tables(&self) -> Vec<String> {
343
+ let mut names: HashSet<String> = self.mem.borrow().tables.keys().cloned().collect();
344
+ names.extend(self.meta.borrow().keys().cloned());
345
+ let mut v: Vec<String> = names.into_iter().collect();
346
+ v.sort();
347
+ v
348
+ }
349
+
350
+ fn insert(&mut self, table: &str, fields_batch: Vec<HashMap<String, serde_json::Value>>) -> Result<Vec<u64>, String> {
351
+ self.ensure_table(table);
352
+ let ids = self.mem.borrow_mut().insert(table, fields_batch)?;
353
+ self.mark_dirty(table);
354
+ Ok(ids)
355
+ }
356
+
357
+ fn find_by_id(&self, table: &str, id: u64) -> Result<Option<crate::types::Row>, String> {
358
+ self.ensure_table(table);
359
+ let r = self.mem.borrow().find_by_id(table, id);
360
+ if r.is_ok() { self.touch(table); }
361
+ r
362
+ }
363
+
364
+ fn find_by_id_json(&self, table: &str, id: u64) -> Result<Option<String>, String> {
365
+ self.ensure_table(table);
366
+ let r = self.mem.borrow().find_by_id_json(table, id);
367
+ if r.is_ok() { self.touch(table); }
368
+ r
369
+ }
370
+
371
+ fn find_by_ids_json(&self, table: &str, ids: &[u64]) -> Result<String, String> {
372
+ self.ensure_table(table);
373
+ let r = self.mem.borrow().find_by_ids_json(table, ids);
374
+ if r.is_ok() { self.touch(table); }
375
+ r
376
+ }
377
+
378
+ fn find(&self, table: &str, filter: &Option<HashMap<String, serde_json::Value>>,
379
+ limit: Option<usize>, offset: Option<usize>,
380
+ cursor: Option<String>,
381
+ order_by: &Option<String>, order: &Option<String>) -> Result<(Vec<crate::types::Row>, Option<String>, bool), String> {
382
+ self.ensure_table(table);
383
+ let r = self.mem.borrow().find(table, filter, limit, offset, cursor, order_by, order);
384
+ if r.is_ok() { self.touch(table); }
385
+ r
386
+ }
387
+
388
+ fn count(&self, table: &str) -> Result<usize, String> {
389
+ self.ensure_table(table);
390
+ let r = self.mem.borrow().count(table);
391
+ if r.is_ok() { self.touch(table); }
392
+ r
393
+ }
394
+
395
+ fn update_by_id(&mut self, table: &str, id: u64, data: HashMap<String, serde_json::Value>) -> Result<bool, String> {
396
+ self.ensure_table(table);
397
+ let r = self.mem.borrow_mut().update_by_id(table, id, data)?;
398
+ if r { self.mark_dirty(table); }
399
+ Ok(r)
400
+ }
401
+
402
+ fn update_by_ids(&mut self, table: &str, batch: Vec<(u64, HashMap<String, serde_json::Value>)>) -> Result<usize, String> {
403
+ self.ensure_table(table);
404
+ let n = self.mem.borrow_mut().update_by_ids(table, batch)?;
405
+ if n > 0 { self.mark_dirty(table); }
406
+ Ok(n)
407
+ }
408
+
409
+ fn remove_by_id(&mut self, table: &str, id: u64) -> Result<bool, String> {
410
+ self.ensure_table(table);
411
+ let r = self.mem.borrow_mut().remove_by_id(table, id)?;
412
+ if r { self.mark_dirty(table); }
413
+ Ok(r)
414
+ }
415
+
416
+ fn remove_by_ids(&mut self, table: &str, ids: &[u64]) -> Result<usize, String> {
417
+ self.ensure_table(table);
418
+ let n = self.mem.borrow_mut().remove_by_ids(table, ids)?;
419
+ if n > 0 { self.mark_dirty(table); }
420
+ Ok(n)
421
+ }
422
+
423
+ fn update_by_filter(&mut self, table: &str, filter: &Option<HashMap<String, serde_json::Value>>,
424
+ data: HashMap<String, serde_json::Value>) -> Result<usize, String> {
425
+ self.ensure_table(table);
426
+ let n = self.mem.borrow_mut().update_by_filter(table, filter, data)?;
427
+ if n > 0 { self.mark_dirty(table); }
428
+ Ok(n)
429
+ }
430
+
431
+ fn remove_by_filter(&mut self, table: &str, filter: &Option<HashMap<String, serde_json::Value>>) -> Result<usize, String> {
432
+ self.ensure_table(table);
433
+ let n = self.mem.borrow_mut().remove_by_filter(table, filter)?;
434
+ if n > 0 { self.mark_dirty(table); }
435
+ Ok(n)
436
+ }
437
+
438
+ fn begin_tx(&mut self) -> Result<String, String> {
439
+ self.mem.borrow_mut().begin_tx()
440
+ }
441
+
442
+ fn commit_tx(&mut self, tx_id: &str) -> Result<(), String> {
443
+ self.mem.borrow_mut().commit_tx(tx_id)
444
+ }
445
+
446
+ fn rollback_tx(&mut self, tx_id: &str) -> Result<(), String> {
447
+ self.mem.borrow_mut().rollback_tx(tx_id)
448
+ }
449
+ }
@@ -0,0 +1,147 @@
1
+ use std::collections::HashMap;
2
+ use crate::engine::table::Table;
3
+ use crate::engine::Engine;
4
+ use crate::types::{Row, TableDefinition};
5
+
6
+ pub struct MemoryEngine {
7
+ pub tables: HashMap<String, Table>,
8
+ }
9
+
10
+ impl MemoryEngine {
11
+ pub fn new() -> Self {
12
+ Self { tables: HashMap::new() }
13
+ }
14
+ }
15
+
16
+ impl Engine for MemoryEngine {
17
+ fn create_table(&mut self, def: TableDefinition) -> Result<(), String> {
18
+ if self.tables.contains_key(&def.name) {
19
+ return Err(format!("table '{}' already exists", def.name));
20
+ }
21
+ self.tables.insert(def.name.clone(), Table::new(def.name, &def.schema));
22
+ Ok(())
23
+ }
24
+
25
+ fn drop_table(&mut self, name: &str) -> Result<(), String> {
26
+ self.tables.remove(name).map(|_| ()).ok_or_else(|| format!("table '{}' not found", name))
27
+ }
28
+
29
+ fn has_table(&self, name: &str) -> bool {
30
+ self.tables.contains_key(name)
31
+ }
32
+
33
+ fn list_tables(&self) -> Vec<String> {
34
+ self.tables.keys().cloned().collect()
35
+ }
36
+
37
+ fn insert(&mut self, table: &str, fields_batch: Vec<HashMap<String, serde_json::Value>>) -> Result<Vec<u64>, String> {
38
+ let t = self.tables.get_mut(table).ok_or_else(|| format!("table '{}' not found", table))?;
39
+ Ok(t.add_rows(fields_batch))
40
+ }
41
+
42
+ fn find_by_id(&self, table: &str, id: u64) -> Result<Option<Row>, String> {
43
+ let t = self.tables.get(table).ok_or_else(|| format!("table '{}' not found", table))?;
44
+ Ok(t.get_row(id))
45
+ }
46
+
47
+ fn find_by_id_json(&self, table: &str, id: u64) -> Result<Option<String>, String> {
48
+ let t = self.tables.get(table).ok_or_else(|| format!("table '{}' not found", table))?;
49
+ Ok(t.get_row_json(id))
50
+ }
51
+
52
+ fn find_by_ids_json(&self, table: &str, ids: &[u64]) -> Result<String, String> {
53
+ let t = self.tables.get(table).ok_or_else(|| format!("table '{}' not found", table))?;
54
+ Ok(t.get_rows_json(ids))
55
+ }
56
+
57
+ fn find(&self, table: &str, filter: &Option<HashMap<String, serde_json::Value>>,
58
+ limit: Option<usize>, offset: Option<usize>,
59
+ cursor: Option<String>,
60
+ order_by: &Option<String>, order: &Option<String>) -> Result<(Vec<Row>, Option<String>, bool), String> {
61
+ let t = self.tables.get(table).ok_or_else(|| format!("table '{}' not found", table))?;
62
+ let off = cursor.as_deref().and_then(|c| c.strip_prefix('c')?.parse().ok()).or(offset);
63
+ let off_val = off.unwrap_or(0);
64
+ let probe_limit = limit.map(|l| l + 1);
65
+ let all = t.find_rows(filter, probe_limit, off, order_by, order);
66
+ let has_more = limit.map_or(false, |l| all.len() > l);
67
+ let rows: Vec<Row> = all.into_iter().take(limit.unwrap_or(usize::MAX)).collect();
68
+ let next_cursor = if has_more { Some(format!("c{}", off_val + limit.unwrap())) } else { None };
69
+ Ok((rows, next_cursor, has_more))
70
+ }
71
+
72
+ fn count(&self, table: &str) -> Result<usize, String> {
73
+ let t = self.tables.get(table).ok_or_else(|| format!("table '{}' not found", table))?;
74
+ Ok(t.count())
75
+ }
76
+
77
+ fn update_by_id(&mut self, table: &str, id: u64, data: HashMap<String, serde_json::Value>) -> Result<bool, String> {
78
+ let t = self.tables.get_mut(table).ok_or_else(|| format!("table '{}' not found", table))?;
79
+ Ok(t.update_row(id, data))
80
+ }
81
+
82
+ fn remove_by_id(&mut self, table: &str, id: u64) -> Result<bool, String> {
83
+ let t = self.tables.get_mut(table).ok_or_else(|| format!("table '{}' not found", table))?;
84
+ Ok(t.remove_row(id))
85
+ }
86
+
87
+ fn remove_by_ids(&mut self, table: &str, ids: &[u64]) -> Result<usize, String> {
88
+ let t = self.tables.get_mut(table).ok_or_else(|| format!("table '{}' not found", table))?;
89
+ Ok(t.remove_rows(ids))
90
+ }
91
+
92
+ fn update_by_ids(&mut self, table: &str, batch: Vec<(u64, HashMap<String, serde_json::Value>)>) -> Result<usize, String> {
93
+ let t = self.tables.get_mut(table).ok_or_else(|| format!("table '{}' not found", table))?;
94
+ Ok(t.update_rows(batch))
95
+ }
96
+
97
+ fn update_by_filter(&mut self, table: &str, filter: &Option<HashMap<String, serde_json::Value>>,
98
+ data: HashMap<String, serde_json::Value>) -> Result<usize, String> {
99
+ let ids: Vec<u64> = {
100
+ let t = self.tables.get(table).ok_or_else(|| format!("table '{}' not found", table))?;
101
+ t.rows.iter()
102
+ .filter(|r| filter.as_ref().map_or(true, |f| t.row_matches(r, f)))
103
+ .map(|r| r.id)
104
+ .collect()
105
+ };
106
+ let count = ids.len();
107
+ for id in &ids {
108
+ let t = self.tables.get_mut(table).ok_or_else(|| format!("table '{}' not found", table))?;
109
+ t.update_row(*id, data.clone());
110
+ }
111
+ Ok(count)
112
+ }
113
+
114
+ fn remove_by_filter(&mut self, table: &str, filter: &Option<HashMap<String, serde_json::Value>>) -> Result<usize, String> {
115
+ let t = self.tables.get_mut(table).ok_or_else(|| format!("table '{}' not found", table))?;
116
+ let len_before = t.rows.len();
117
+ if filter.is_none() {
118
+ t.rows.clear();
119
+ t.pk_index.clear();
120
+ return Ok(len_before);
121
+ }
122
+ let f = filter.as_ref().unwrap();
123
+ let mut to_remove = Vec::new();
124
+ for (i, r) in t.rows.iter().enumerate() {
125
+ let matches = f.iter().all(|(k, v)| {
126
+ t.field_index.get(k).and_then(|&pos| t.get_row_values(r).get(pos)).map_or(false, |fv| fv.eq_json(v))
127
+ });
128
+ if matches { to_remove.push(i); }
129
+ }
130
+ for i in to_remove.into_iter().rev() {
131
+ t.remove_row_by_idx(i);
132
+ }
133
+ Ok(len_before - t.rows.len())
134
+ }
135
+
136
+ fn begin_tx(&mut self) -> Result<String, String> {
137
+ Err("transactions not supported on MemoryEngine directly".into())
138
+ }
139
+
140
+ fn commit_tx(&mut self, _tx_id: &str) -> Result<(), String> {
141
+ Err("transactions not supported on MemoryEngine directly".into())
142
+ }
143
+
144
+ fn rollback_tx(&mut self, _tx_id: &str) -> Result<(), String> {
145
+ Err("transactions not supported on MemoryEngine directly".into())
146
+ }
147
+ }
@@ -0,0 +1,41 @@
1
+ pub mod table;
2
+ mod memory;
3
+ #[cfg(not(feature = "wasm"))]
4
+ pub mod hybrid;
5
+
6
+ pub use memory::MemoryEngine;
7
+ #[cfg(not(feature = "wasm"))]
8
+ pub use hybrid::HybridEngine;
9
+ pub use table::{Table, RowStore, FieldValue};
10
+
11
+ use std::collections::HashMap;
12
+ use crate::types::{Row, TableDefinition};
13
+
14
+ pub trait Engine {
15
+ fn create_table(&mut self, def: TableDefinition) -> Result<(), String>;
16
+ fn drop_table(&mut self, name: &str) -> Result<(), String>;
17
+ fn has_table(&self, name: &str) -> bool;
18
+ fn list_tables(&self) -> Vec<String>;
19
+
20
+ fn insert(&mut self, table: &str, fields: Vec<HashMap<String, serde_json::Value>>) -> Result<Vec<u64>, String>;
21
+ fn find_by_id(&self, table: &str, id: u64) -> Result<Option<Row>, String>;
22
+ fn find_by_id_json(&self, table: &str, id: u64) -> Result<Option<String>, String>;
23
+ fn find_by_ids_json(&self, table: &str, ids: &[u64]) -> Result<String, String>;
24
+ fn find(&self, table: &str, filter: &Option<HashMap<String, serde_json::Value>>,
25
+ limit: Option<usize>, offset: Option<usize>,
26
+ cursor: Option<String>,
27
+ order_by: &Option<String>, order: &Option<String>) -> Result<(Vec<Row>, Option<String>, bool), String>;
28
+ fn count(&self, table: &str) -> Result<usize, String>;
29
+ fn update_by_id(&mut self, table: &str, id: u64, data: HashMap<String, serde_json::Value>) -> Result<bool, String>;
30
+ fn update_by_ids(&mut self, table: &str, batch: Vec<(u64, HashMap<String, serde_json::Value>)>) -> Result<usize, String>;
31
+ fn remove_by_id(&mut self, table: &str, id: u64) -> Result<bool, String>;
32
+ fn remove_by_ids(&mut self, table: &str, ids: &[u64]) -> Result<usize, String>;
33
+
34
+ fn update_by_filter(&mut self, table: &str, filter: &Option<HashMap<String, serde_json::Value>>,
35
+ data: HashMap<String, serde_json::Value>) -> Result<usize, String>;
36
+ fn remove_by_filter(&mut self, table: &str, filter: &Option<HashMap<String, serde_json::Value>>) -> Result<usize, String>;
37
+
38
+ fn begin_tx(&mut self) -> Result<String, String>;
39
+ fn commit_tx(&mut self, tx_id: &str) -> Result<(), String>;
40
+ fn rollback_tx(&mut self, tx_id: &str) -> Result<(), String>;
41
+ }