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,664 @@
1
+ use std::collections::HashMap;
2
+ use rustc_hash::FxHashMap;
3
+ use serde::Serialize;
4
+ use crate::types::{Row, FieldSchema, FieldType};
5
+ use std::fmt::Write;
6
+
7
+ #[derive(Debug, Clone, PartialEq, Default)]
8
+ pub enum FieldValue {
9
+ #[default]
10
+ Null,
11
+ Bool(bool),
12
+ Int(i64),
13
+ Float(f64),
14
+ String(String),
15
+ }
16
+
17
+ impl serde::Serialize for FieldValue {
18
+ fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
19
+ match self {
20
+ FieldValue::Null => s.serialize_unit(),
21
+ FieldValue::Bool(b) => s.serialize_bool(*b),
22
+ FieldValue::Int(n) => s.serialize_i64(*n),
23
+ FieldValue::Float(f) => s.serialize_f64(*f),
24
+ FieldValue::String(st) => s.serialize_str(st),
25
+ }
26
+ }
27
+ }
28
+
29
+ impl FieldValue {
30
+ pub fn from_json(jv: serde_json::Value) -> Self {
31
+ match jv {
32
+ serde_json::Value::Null => FieldValue::Null,
33
+ serde_json::Value::Bool(b) => FieldValue::Bool(b),
34
+ serde_json::Value::Number(n) => {
35
+ n.as_i64().map(FieldValue::Int)
36
+ .or_else(|| n.as_f64().map(FieldValue::Float))
37
+ .unwrap_or(FieldValue::Null)
38
+ }
39
+ serde_json::Value::String(s) => FieldValue::String(s),
40
+ _ => FieldValue::Null,
41
+ }
42
+ }
43
+
44
+ fn to_json(&self) -> serde_json::Value {
45
+ match self {
46
+ FieldValue::Null => serde_json::Value::Null,
47
+ FieldValue::Bool(b) => serde_json::Value::Bool(*b),
48
+ FieldValue::Int(n) => serde_json::Value::Number((*n).into()),
49
+ FieldValue::Float(f) => serde_json::Number::from_f64(*f)
50
+ .map(serde_json::Value::Number)
51
+ .unwrap_or(serde_json::Value::Null),
52
+ FieldValue::String(s) => serde_json::Value::String(s.clone()),
53
+ }
54
+ }
55
+
56
+ pub fn write_json(&self, out: &mut String) {
57
+ match self {
58
+ FieldValue::Null => out.push_str("null"),
59
+ FieldValue::Bool(true) => out.push_str("true"),
60
+ FieldValue::Bool(false) => out.push_str("false"),
61
+ FieldValue::Int(n) => {
62
+ out.push_str(itoa::Buffer::new().format(*n));
63
+ }
64
+ FieldValue::Float(f) => {
65
+ out.push_str(ryu::Buffer::new().format_finite(*f));
66
+ }
67
+ FieldValue::String(s) => {
68
+ write_json_string(out, s);
69
+ }
70
+ }
71
+ }
72
+
73
+ pub fn eq_json(&self, jv: &serde_json::Value) -> bool {
74
+ match (self, jv) {
75
+ (FieldValue::Null, serde_json::Value::Null) => true,
76
+ (FieldValue::Bool(a), serde_json::Value::Bool(b)) => a == b,
77
+ (FieldValue::Int(a), serde_json::Value::Number(n)) => n.as_i64() == Some(*a),
78
+ (FieldValue::Float(a), serde_json::Value::Number(n)) => n.as_f64() == Some(*a),
79
+ (FieldValue::String(a), serde_json::Value::String(b)) => a == b,
80
+ _ => false,
81
+ }
82
+ }
83
+ }
84
+
85
+ #[derive(Debug, Clone)]
86
+ pub struct RowStore {
87
+ pub id: u64,
88
+ pub values_start: usize,
89
+ pub num_values: usize,
90
+ pub created_at: u64,
91
+ pub updated_at: u64,
92
+ }
93
+
94
+ #[derive(Debug, Clone)]
95
+ pub struct Table {
96
+ pub name: String,
97
+ pub schema: HashMap<String, FieldSchema>,
98
+ pub field_order: Vec<String>,
99
+ pub field_index: HashMap<String, usize>,
100
+ pub rows: Vec<RowStore>,
101
+ pub values: Vec<FieldValue>,
102
+ pub next_id: u64,
103
+ pub pk_field: Option<String>,
104
+ pub pk_index: FxHashMap<u64, usize>,
105
+ }
106
+
107
+ fn default_value(ft: &FieldType) -> FieldValue {
108
+ match ft {
109
+ FieldType::Integer => FieldValue::Int(0),
110
+ FieldType::Float => FieldValue::Float(0.0),
111
+ FieldType::String => FieldValue::String(String::new()),
112
+ FieldType::Boolean => FieldValue::Bool(false),
113
+ }
114
+ }
115
+
116
+ fn now_millis() -> u64 {
117
+ #[cfg(feature = "wasm")]
118
+ {
119
+ js_sys::Date::now() as u64
120
+ }
121
+ #[cfg(not(feature = "wasm"))]
122
+ {
123
+ std::time::SystemTime::now()
124
+ .duration_since(std::time::UNIX_EPOCH)
125
+ .unwrap_or_default()
126
+ .as_millis() as u64
127
+ }
128
+ }
129
+
130
+ fn write_json_string(out: &mut String, s: &str) {
131
+ out.push('"');
132
+ for c in s.chars() {
133
+ match c {
134
+ '"' => out.push_str("\\\""),
135
+ '\\' => out.push_str("\\\\"),
136
+ '\n' => out.push_str("\\n"),
137
+ '\r' => out.push_str("\\r"),
138
+ '\t' => out.push_str("\\t"),
139
+ c if c.is_control() => {
140
+ write!(out, "\\u{:04x}", c as u32).unwrap();
141
+ }
142
+ c => out.push(c),
143
+ }
144
+ }
145
+ out.push('"');
146
+ }
147
+
148
+ fn millis_to_string(ms: u64) -> String {
149
+ #[cfg(feature = "wasm")]
150
+ {
151
+ let d = js_sys::Date::new(&wasm_bindgen::JsValue::from_f64(ms as f64));
152
+ d.to_iso_string().as_string().unwrap_or_else(|| "2026-01-01T00:00:00Z".into())
153
+ }
154
+ #[cfg(not(feature = "wasm"))]
155
+ {
156
+ let secs = ms / 1000;
157
+ let nanos = ((ms % 1000) * 1_000_000) as u32;
158
+ let dt = chrono::DateTime::from_timestamp(secs as i64, nanos)
159
+ .unwrap_or_default();
160
+ dt.to_rfc3339()
161
+ }
162
+ }
163
+
164
+ impl Table {
165
+ pub fn new(name: String, schema: &[(String, FieldSchema)]) -> Self {
166
+ let pk_field = schema.iter()
167
+ .find(|(_, f)| f.primary_key)
168
+ .map(|(name, _)| name.clone());
169
+
170
+ let field_order: Vec<String> = schema.iter().map(|(n, _)| n.clone()).collect();
171
+ let field_index: HashMap<String, usize> = field_order.iter()
172
+ .enumerate().map(|(i, n)| (n.clone(), i)).collect();
173
+ let schema: HashMap<String, FieldSchema> = schema.iter()
174
+ .map(|(k, v)| (k.clone(), v.clone())).collect();
175
+
176
+ Self {
177
+ name, schema, field_order, field_index,
178
+ rows: Vec::new(), values: Vec::new(), next_id: 1, pk_field,
179
+ pk_index: FxHashMap::default(),
180
+ }
181
+ }
182
+
183
+ pub fn to_row_map(&self, values: &[FieldValue]) -> HashMap<String, serde_json::Value> {
184
+ let mut map = HashMap::with_capacity(self.field_order.len());
185
+ for (i, name) in self.field_order.iter().enumerate() {
186
+ if let Some(v) = values.get(i) {
187
+ map.insert(name.clone(), v.to_json());
188
+ }
189
+ }
190
+ map
191
+ }
192
+
193
+ pub fn push_row_values(&mut self, id: u64, values: Vec<FieldValue>, now: u64) {
194
+ let num_values = values.len();
195
+ let values_start = self.values.len();
196
+ self.values.extend(values);
197
+ self.rows.push(RowStore { id, values_start, num_values, created_at: now, updated_at: now });
198
+ }
199
+
200
+ pub fn get_row_values(&self, rs: &RowStore) -> &[FieldValue] {
201
+ &self.values[rs.values_start..][..rs.num_values]
202
+ }
203
+
204
+ pub fn get_row_values_mut(&mut self, rs: &RowStore) -> &mut [FieldValue] {
205
+ let start = rs.values_start;
206
+ &mut self.values[start..][..rs.num_values]
207
+ }
208
+
209
+ fn to_row(&self, rs: &RowStore) -> Row {
210
+ Row {
211
+ id: rs.id,
212
+ fields: self.to_row_map(self.get_row_values(rs)),
213
+ created_at: Some(millis_to_string(rs.created_at)),
214
+ updated_at: Some(millis_to_string(rs.updated_at)),
215
+ }
216
+ }
217
+
218
+ pub fn add_rows(&mut self, fields_batch: Vec<HashMap<String, serde_json::Value>>) -> Vec<u64> {
219
+ let now = now_millis();
220
+ let batch_len = fields_batch.len();
221
+ let mut ids = Vec::with_capacity(batch_len);
222
+ self.pk_index.reserve(batch_len);
223
+ self.rows.reserve(batch_len);
224
+ self.values.reserve(batch_len * self.field_order.len());
225
+
226
+ let pk_auto_key = self.pk_field.as_deref().and_then(|pk| {
227
+ self.schema.get(pk).and_then(|fs| {
228
+ if fs.auto_increment { Some(pk.to_string()) } else { None }
229
+ })
230
+ });
231
+
232
+ for mut fields in fields_batch {
233
+ let id = self.next_id;
234
+ self.next_id += 1;
235
+
236
+ if let Some(ref pk_key) = pk_auto_key {
237
+ match fields.get(pk_key) {
238
+ Some(v) if v.is_null() => {
239
+ fields.insert(pk_key.clone(), serde_json::Value::Number(id.into()));
240
+ }
241
+ Some(v) => {
242
+ if let Some(n) = v.as_i64() {
243
+ if n > 0 { self.next_id = self.next_id.max((n + 1) as u64); }
244
+ }
245
+ }
246
+ None => {
247
+ fields.insert(pk_key.clone(), serde_json::Value::Number(id.into()));
248
+ }
249
+ }
250
+ }
251
+
252
+ let values_start = self.values.len();
253
+ if fields.len() < self.schema.len() {
254
+ for (name, fs) in &self.schema {
255
+ if let Some(v) = fields.remove(name) {
256
+ self.values.push(FieldValue::from_json(v));
257
+ } else {
258
+ self.values.push(default_value(&fs.field_type));
259
+ }
260
+ }
261
+ } else {
262
+ for name in &self.field_order {
263
+ self.values.push(fields.remove(name).map(FieldValue::from_json).unwrap_or(FieldValue::Null));
264
+ }
265
+ }
266
+
267
+ let idx = self.rows.len();
268
+ self.pk_index.insert(id, idx);
269
+ self.rows.push(RowStore { id, values_start, num_values: self.field_order.len(), created_at: now, updated_at: now });
270
+ ids.push(id);
271
+ }
272
+ ids
273
+ }
274
+
275
+ pub fn add_rows_vec(&mut self, field_names: &[String], batch: Vec<Vec<serde_json::Value>>) -> Vec<u64> {
276
+ let batch_fv: Vec<Vec<FieldValue>> = batch.into_iter()
277
+ .map(|row| row.into_iter().map(FieldValue::from_json).collect())
278
+ .collect();
279
+ self.add_rows_vec_fv(field_names, batch_fv)
280
+ }
281
+
282
+ pub fn add_rows_vec_fv(&mut self, field_names: &[String], batch: Vec<Vec<FieldValue>>) -> Vec<u64> {
283
+ let now = now_millis();
284
+ let batch_len = batch.len();
285
+ let mut ids = Vec::with_capacity(batch_len);
286
+ self.pk_index.reserve(batch_len);
287
+ self.rows.reserve(batch_len);
288
+ self.values.reserve(batch_len * self.field_order.len());
289
+
290
+ let pk_auto_key = self.pk_field.as_deref().and_then(|pk| {
291
+ self.schema.get(pk).and_then(|fs| {
292
+ if fs.auto_increment { Some(pk.to_string()) } else { None }
293
+ })
294
+ });
295
+
296
+ // fast path: field_names match field_order
297
+ if field_names.len() == self.field_order.len()
298
+ && field_names.iter().zip(&self.field_order).all(|(a, b)| a == b)
299
+ {
300
+ let pk_idx = pk_auto_key.as_ref().and_then(|pk| self.field_index.get(pk.as_str()).copied());
301
+ for mut row_vals in batch {
302
+ let id = self.next_id;
303
+ self.next_id += 1;
304
+ if row_vals.len() < self.field_order.len() {
305
+ row_vals.resize(self.field_order.len(), FieldValue::Null);
306
+ }
307
+ if let Some(pi) = pk_idx {
308
+ if pi < row_vals.len() {
309
+ match row_vals[pi] {
310
+ FieldValue::Null => row_vals[pi] = FieldValue::Int(id as i64),
311
+ FieldValue::Int(e) if e > 0 => {
312
+ self.next_id = self.next_id.max((e + 1) as u64);
313
+ }
314
+ _ => {}
315
+ }
316
+ }
317
+ }
318
+ let values_start = self.values.len();
319
+ self.values.append(&mut row_vals);
320
+ let idx = self.rows.len();
321
+ self.pk_index.insert(id, idx);
322
+ self.rows.push(RowStore { id, values_start, num_values: self.field_order.len(), created_at: now, updated_at: now });
323
+ ids.push(id);
324
+ }
325
+ return ids;
326
+ }
327
+
328
+ // slow path: remap field names
329
+ let remap: Vec<Option<usize>> = self.field_order.iter()
330
+ .map(|name| field_names.iter().position(|fname| fname == name))
331
+ .collect();
332
+ let explicit_pk_si = pk_auto_key.as_ref()
333
+ .and_then(|pk| field_names.iter().position(|n| n == pk));
334
+
335
+ for mut row_vals in batch {
336
+ let id = self.next_id;
337
+ self.next_id += 1;
338
+ if let Some(si) = explicit_pk_si {
339
+ if let Some(FieldValue::Int(e)) = row_vals.get(si) {
340
+ if *e > 0 { self.next_id = self.next_id.max((*e + 1) as u64); }
341
+ }
342
+ }
343
+ let values_start = self.values.len();
344
+ for (i, remapped_si) in remap.iter().enumerate() {
345
+ if let Some(si) = *remapped_si {
346
+ if si < row_vals.len() {
347
+ self.values.push(std::mem::take(&mut row_vals[si]));
348
+ continue;
349
+ }
350
+ }
351
+ if let Some(ref pk_key) = pk_auto_key {
352
+ if self.field_order[i] == *pk_key {
353
+ self.values.push(FieldValue::Int(id as i64));
354
+ continue;
355
+ }
356
+ }
357
+ if let Some(name) = self.field_order.get(i) {
358
+ if let Some(fs) = self.schema.get(name) {
359
+ if let Some(ref default) = fs.default {
360
+ self.values.push(FieldValue::from_json(default.clone()));
361
+ } else if !fs.nullable {
362
+ self.values.push(default_value(&fs.field_type));
363
+ } else {
364
+ self.values.push(FieldValue::Null);
365
+ }
366
+ } else {
367
+ self.values.push(FieldValue::Null);
368
+ }
369
+ }
370
+ }
371
+ let idx = self.rows.len();
372
+ self.pk_index.insert(id, idx);
373
+ self.rows.push(RowStore { id, values_start, num_values: self.field_order.len(), created_at: now, updated_at: now });
374
+ ids.push(id);
375
+ }
376
+ ids
377
+ }
378
+
379
+ pub fn row_matches(&self, r: &RowStore, filter: &HashMap<String, serde_json::Value>) -> bool {
380
+ let vals = self.get_row_values(r);
381
+ filter.iter().all(|(k, v)| {
382
+ self.field_index.get(k)
383
+ .and_then(|&pos| vals.get(pos))
384
+ .map_or(false, |fv| fv.eq_json(v))
385
+ })
386
+ }
387
+
388
+ pub fn get_row(&self, id: u64) -> Option<Row> {
389
+ let idx = *self.pk_index.get(&id)?;
390
+ self.rows.get(idx).map(|r| self.to_row(r))
391
+ }
392
+
393
+ pub fn get_row_json(&self, id: u64) -> Option<String> {
394
+ let idx = *self.pk_index.get(&id)?;
395
+ self.rows.get(idx).map(|r| self.to_row_json_string(r))
396
+ }
397
+
398
+ pub fn get_rows_json(&self, ids: &[u64]) -> String {
399
+ let mut out = String::with_capacity(ids.len() * 128);
400
+ out.push('[');
401
+ let mut first = true;
402
+ for &id in ids {
403
+ if let Some(&idx) = self.pk_index.get(&id) {
404
+ if let Some(rs) = self.rows.get(idx) {
405
+ if !first { out.push(','); }
406
+ first = false;
407
+ out.push_str(&self.to_row_json_string(rs));
408
+ }
409
+ }
410
+ }
411
+ out.push(']');
412
+ out
413
+ }
414
+
415
+ fn to_row_json_compact(&self, rs: &RowStore) -> String {
416
+ let vals = self.get_row_values(rs);
417
+ let mut out = String::with_capacity(64);
418
+ out.push('[');
419
+ out.push_str(itoa::Buffer::new().format(rs.id));
420
+ for v in vals {
421
+ out.push(',');
422
+ v.write_json(&mut out);
423
+ }
424
+ out.push(']');
425
+ out
426
+ }
427
+
428
+ fn to_row_json_string(&self, rs: &RowStore) -> String {
429
+ let vals = self.get_row_values(rs);
430
+ let mut out = String::with_capacity(96);
431
+ out.push_str(r#"{"id":"#);
432
+ out.push_str(itoa::Buffer::new().format(rs.id));
433
+ out.push_str(r#","fields":{"#);
434
+ for (i, name) in self.field_order.iter().enumerate() {
435
+ if i > 0 { out.push(','); }
436
+ write_json_string(&mut out, name);
437
+ out.push(':');
438
+ if let Some(v) = vals.get(i) {
439
+ v.write_json(&mut out);
440
+ } else {
441
+ out.push_str("null");
442
+ }
443
+ }
444
+ out.push_str("}}");
445
+ out
446
+ }
447
+
448
+ pub fn update_row(&mut self, id: u64, data: HashMap<String, serde_json::Value>) -> bool {
449
+ let idx = match self.pk_index.get(&id) {
450
+ Some(i) => *i,
451
+ None => return false,
452
+ };
453
+ // Use raw index access to update values without borrow conflicts
454
+ if idx < self.rows.len() {
455
+ let row = &mut self.rows[idx];
456
+ let nv = row.num_values;
457
+ let s = row.values_start;
458
+ for (k, v) in data {
459
+ if let Some(&pos) = self.field_index.get(&k) {
460
+ if pos < nv {
461
+ self.values[s + pos] = FieldValue::from_json(v);
462
+ }
463
+ }
464
+ }
465
+ self.rows[idx].updated_at = now_millis();
466
+ true
467
+ } else {
468
+ false
469
+ }
470
+ }
471
+
472
+ pub fn update_rows(&mut self, batch: Vec<(u64, HashMap<String, serde_json::Value>)>) -> usize {
473
+ let mut count = 0;
474
+ let now = now_millis();
475
+ for (id, data) in batch {
476
+ if let Some(&idx) = self.pk_index.get(&id) {
477
+ if idx < self.rows.len() {
478
+ let row = &mut self.rows[idx];
479
+ let nv = row.num_values;
480
+ let s = row.values_start;
481
+ for (k, v) in data {
482
+ if let Some(&pos) = self.field_index.get(&k) {
483
+ if pos < nv {
484
+ self.values[s + pos] = FieldValue::from_json(v);
485
+ }
486
+ }
487
+ }
488
+ self.rows[idx].updated_at = now;
489
+ count += 1;
490
+ }
491
+ }
492
+ }
493
+ count
494
+ }
495
+
496
+ pub fn remove_row(&mut self, id: u64) -> bool {
497
+ let idx = match self.pk_index.get(&id) {
498
+ Some(i) => *i,
499
+ None => return false,
500
+ };
501
+ self.pk_index.remove(&id);
502
+ let last = self.rows.len() - 1;
503
+ if idx != last {
504
+ self.pk_index.insert(self.rows[last].id, idx);
505
+ }
506
+ self.rows.swap_remove(idx);
507
+ true
508
+ }
509
+
510
+ pub fn remove_rows(&mut self, ids: &[u64]) -> usize {
511
+ let mut count = 0;
512
+ let mut indices: Vec<(usize, u64)> = Vec::with_capacity(ids.len());
513
+ for &id in ids {
514
+ if let Some(&idx) = self.pk_index.get(&id) {
515
+ indices.push((idx, id));
516
+ count += 1;
517
+ }
518
+ }
519
+ indices.sort_unstable_by(|a, b| b.0.cmp(&a.0));
520
+ for (idx, id) in indices {
521
+ self.pk_index.remove(&id);
522
+ let last = self.rows.len() - 1;
523
+ if idx != last {
524
+ self.pk_index.insert(self.rows[last].id, idx);
525
+ }
526
+ self.rows.swap_remove(idx);
527
+ }
528
+ count
529
+ }
530
+
531
+ pub fn remove_row_by_idx(&mut self, idx: usize) {
532
+ let last = self.rows.len() - 1;
533
+ if idx < last {
534
+ let swapped_id = self.rows[last].id;
535
+ self.pk_index.insert(swapped_id, idx);
536
+ }
537
+ self.pk_index.remove(&self.rows[idx].id);
538
+ self.rows.swap_remove(idx);
539
+ }
540
+
541
+ pub fn count(&self) -> usize {
542
+ self.rows.len()
543
+ }
544
+
545
+ // ============================================================
546
+ // 磁盘持久化导出 / 导入(供 HybridEngine 使用)
547
+ // ============================================================
548
+
549
+ pub fn export_all(&self) -> String {
550
+ let mut out = String::with_capacity(64 + self.rows.len() * 96);
551
+ out.push_str(r#"{"next_id":"#);
552
+ out.push_str(itoa::Buffer::new().format(self.next_id));
553
+ out.push_str(r#","rows":["#);
554
+ for (i, rs) in self.rows.iter().enumerate() {
555
+ if i > 0 { out.push(','); }
556
+ out.push_str(r#"{"id":"#);
557
+ out.push_str(itoa::Buffer::new().format(rs.id));
558
+ out.push_str(r#","created_at":"#);
559
+ out.push_str(itoa::Buffer::new().format(rs.created_at));
560
+ out.push_str(r#","updated_at":"#);
561
+ out.push_str(itoa::Buffer::new().format(rs.updated_at));
562
+ out.push_str(r#","fields":{"#);
563
+ let vals = self.get_row_values(rs);
564
+ for (fi, name) in self.field_order.iter().enumerate() {
565
+ if fi > 0 { out.push(','); }
566
+ write_json_string(&mut out, name);
567
+ out.push(':');
568
+ if let Some(v) = vals.get(fi) {
569
+ v.write_json(&mut out);
570
+ } else {
571
+ out.push_str("null");
572
+ }
573
+ }
574
+ out.push_str("}}");
575
+ }
576
+ out.push_str("]}");
577
+ out
578
+ }
579
+
580
+ pub fn import_from_json(&mut self, json: &str) -> Result<(), String> {
581
+ let parsed: serde_json::Value = serde_json::from_str(json)
582
+ .map_err(|e| format!("invalid table file: {}", e))?;
583
+ let next_id = parsed.get("next_id").and_then(|v| v.as_u64()).unwrap_or(1);
584
+ self.next_id = next_id;
585
+ let rows = parsed.get("rows").and_then(|v| v.as_array()).ok_or("table file missing rows")?;
586
+ let pk_auto_key = self.pk_field.as_deref().and_then(|pk| {
587
+ self.schema.get(pk).and_then(|fs| if fs.auto_increment { Some(pk.to_string()) } else { None })
588
+ });
589
+ self.rows.reserve(rows.len());
590
+ self.values.reserve(rows.len() * self.field_order.len());
591
+ self.pk_index.reserve(rows.len());
592
+ for rv in rows {
593
+ let id = rv.get("id").and_then(|v| v.as_u64()).ok_or("row missing id")?;
594
+ let created_at = rv.get("created_at").and_then(|v| v.as_u64()).unwrap_or_else(now_millis);
595
+ let updated_at = rv.get("updated_at").and_then(|v| v.as_u64()).unwrap_or(created_at);
596
+ let fields = rv.get("fields").and_then(|v| v.as_object()).ok_or("row missing fields")?;
597
+ let values_start = self.values.len();
598
+ for name in &self.field_order {
599
+ if let Some(jv) = fields.get(name) {
600
+ self.values.push(FieldValue::from_json(jv.clone()));
601
+ } else if let Some(ref pk_key) = pk_auto_key {
602
+ if *pk_key == *name {
603
+ self.values.push(FieldValue::Int(id as i64));
604
+ } else {
605
+ self.values.push(FieldValue::Null);
606
+ }
607
+ } else {
608
+ self.values.push(FieldValue::Null);
609
+ }
610
+ }
611
+ let idx = self.rows.len();
612
+ self.pk_index.insert(id, idx);
613
+ self.rows.push(RowStore { id, values_start, num_values: self.field_order.len(), created_at, updated_at });
614
+ }
615
+ Ok(())
616
+ }
617
+
618
+ pub fn find_rows(&self, filter: &Option<HashMap<String, serde_json::Value>>,
619
+ limit: Option<usize>, offset: Option<usize>,
620
+ order_by: &Option<String>, order: &Option<String>) -> Vec<Row> {
621
+ let mut results: Vec<&RowStore> = self.rows.iter().collect();
622
+
623
+ if let Some(ref f) = filter {
624
+ let filter_indices: Vec<(usize, &serde_json::Value)> = f.iter()
625
+ .filter_map(|(k, v)| self.field_index.get(k).map(|&pos| (pos, v)))
626
+ .collect();
627
+ results.retain(|r| {
628
+ let vals = self.get_row_values(r);
629
+ filter_indices.iter().all(|(pos, v)| {
630
+ vals.get(*pos).map_or(false, |fv| fv.eq_json(v))
631
+ })
632
+ });
633
+ }
634
+
635
+ if let Some(ref field) = order_by {
636
+ let desc = order.as_deref() == Some("desc");
637
+ if let Some(&pos) = self.field_index.get(field) {
638
+ results.sort_by(|a, b| {
639
+ let vals_a = self.get_row_values(a);
640
+ let vals_b = self.get_row_values(b);
641
+ let cmp = cmp_field_values(vals_a.get(pos), vals_b.get(pos));
642
+ if desc { cmp.reverse() } else { cmp }
643
+ });
644
+ }
645
+ }
646
+
647
+ let o = offset.unwrap_or(0);
648
+ let l = limit.unwrap_or(usize::MAX);
649
+ results.into_iter().skip(o).take(l).map(|r| self.to_row(r)).collect()
650
+ }
651
+ }
652
+
653
+ fn cmp_field_values(a: Option<&FieldValue>, b: Option<&FieldValue>) -> std::cmp::Ordering {
654
+ match (a, b) {
655
+ (Some(FieldValue::Int(na)), Some(FieldValue::Int(nb))) => na.cmp(nb),
656
+ (Some(FieldValue::Float(na)), Some(FieldValue::Float(nb))) => na.partial_cmp(nb).unwrap_or(std::cmp::Ordering::Equal),
657
+ (Some(FieldValue::String(sa)), Some(FieldValue::String(sb))) => sa.cmp(sb),
658
+ (Some(FieldValue::Bool(ba)), Some(FieldValue::Bool(bb))) => ba.cmp(bb),
659
+ (Some(a), Some(b)) => format!("{:?}", a).cmp(&format!("{:?}", b)),
660
+ (Some(_), None) => std::cmp::Ordering::Greater,
661
+ (None, Some(_)) => std::cmp::Ordering::Less,
662
+ (None, None) => std::cmp::Ordering::Equal,
663
+ }
664
+ }
@@ -0,0 +1,3 @@
1
+ pub mod engine;
2
+ pub mod storage;
3
+ pub mod types;
@@ -0,0 +1 @@
1
+ pub mod persistent;
@@ -0,0 +1,2 @@
1
+ // Persistent storage engine - WIP, not used by native addon
2
+ // The native addon uses MemoryEngine only