jsql-neo 5.5.0 → 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/LICENSE +202 -202
- package/README.md +8155 -8097
- package/bin/jsql +320 -320
- package/bin/jsql-server +90 -90
- package/bin/jsql-server-control.js +177 -177
- package/index.d.ts +386 -385
- package/index.js +128 -124
- package/lib/ast.js +214 -214
- package/lib/btree.js +413 -413
- package/lib/cache.js +328 -328
- package/lib/client.js +125 -125
- package/lib/database.js +1710 -1744
- package/lib/date-types.js +182 -182
- package/lib/errors.js +89 -89
- package/lib/jsql_format.js +497 -497
- package/lib/migrate.js +254 -254
- package/lib/mod.js +292 -292
- package/lib/mongo_server.js +607 -607
- package/lib/multiserver.js +140 -140
- package/lib/mysql_compat.js +413 -413
- package/lib/mysql_server.js +1237 -1237
- package/lib/native_client.js +709 -732
- package/lib/nedb_compat.js +506 -506
- package/lib/pg_server.js +864 -864
- package/lib/plugin.js +259 -35
- 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/query.js +815 -815
- package/lib/redis_server.js +571 -571
- package/lib/sql.js +4053 -3460
- package/lib/sqlite_compat.js +323 -323
- package/lib/sqlite_worker.js +264 -264
- package/lib/table.js +1304 -1304
- package/lib/tui.js +502 -502
- package/lib/wasm.js +1 -1
- package/lib/wasm_client.js +702 -718
- package/lib/web_ui.js +257 -257
- package/native/jsql-neo-native.node +0 -0
- package/nativesrc/jsql-neo-core/Cargo.lock +462 -455
- package/nativesrc/jsql-neo-core/Cargo.toml +26 -24
- package/nativesrc/jsql-neo-core/src/engine/hybrid.rs +492 -455
- package/nativesrc/jsql-neo-core/src/engine/memory.rs +191 -168
- package/nativesrc/jsql-neo-core/src/engine/mod.rs +74 -62
- package/nativesrc/jsql-neo-core/src/engine/table.rs +996 -700
- package/nativesrc/jsql-neo-core/src/lib.rs +2 -2
- package/nativesrc/jsql-neo-core/src/storage/mod.rs +2 -2
- package/nativesrc/jsql-neo-core/src/storage/persistent.rs +1 -1
- package/nativesrc/jsql-neo-core/src/storage/wal.rs +298 -85
- package/nativesrc/jsql-neo-core/src/types.rs +93 -93
- package/nativesrc/jsql-neo-native/Cargo.lock +613 -606
- package/nativesrc/jsql-neo-native/Cargo.toml +24 -16
- package/nativesrc/jsql-neo-native/build.rs +3 -3
- package/nativesrc/jsql-neo-native/package.json +7 -7
- package/nativesrc/jsql-neo-native/src/lib.rs +281 -269
- package/nativesrc/jsql-neo-wasm/Cargo.lock +472 -465
- package/nativesrc/jsql-neo-wasm/Cargo.toml +22 -15
- package/nativesrc/jsql-neo-wasm/src/lib.rs +463 -447
- package/package.json +87 -87
- package/postinstall.js +158 -158
- package/test/btree.test.js +128 -128
- package/test/coverage.js +187 -187
- package/test/join.test.js +110 -110
- package/test/native.test.js +158 -158
- package/test/regress-5.1.0.js +271 -271
- package/test/smoke.js +58 -58
- package/test/sql-parser.test.js +450 -273
- package/test/wasm.test.js +110 -110
- package/wasm/browser.d.ts +89 -89
- package/wasm/browser.mjs +581 -569
- package/wasm/browser_bg.mjs +618 -597
- package/wasm/jsql_neo_wasm.d.ts +44 -38
- package/wasm/jsql_neo_wasm.js +641 -520
- package/wasm/jsql_neo_wasm_bg.wasm +0 -0
- package/wasm/jsql_neo_wasm_bg.wasm.d.ts +26 -26
- package/wasm/package.json +11 -11
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
pub mod engine;
|
|
2
|
-
pub mod storage;
|
|
1
|
+
pub mod engine;
|
|
2
|
+
pub mod storage;
|
|
3
3
|
pub mod types;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
pub mod persistent;
|
|
2
|
-
pub mod wal;
|
|
1
|
+
pub mod persistent;
|
|
2
|
+
pub mod wal;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
// Persistent storage engine - WIP, not used by native addon
|
|
1
|
+
// Persistent storage engine - WIP, not used by native addon
|
|
2
2
|
// The native addon uses MemoryEngine only
|
|
@@ -1,86 +1,299 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
if
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
1
|
+
//! 预写日志(WAL)。
|
|
2
|
+
//!
|
|
3
|
+
//! 记录格式:`[len: u64 LE][crc32: u32 LE][bincode(WalEntry)]`,头部与载荷一次性写出,
|
|
4
|
+
//! 每条记录带 CRC 校验。重放时遇到截断或校验失败的记录即停止,把它视为「未完整落盘」,
|
|
5
|
+
//! 这样即使进程在写一半时被杀,也不会把半截数据当成有效记录。
|
|
6
|
+
|
|
7
|
+
use serde::{Deserialize, Serialize};
|
|
8
|
+
use std::io::{Read, Seek, SeekFrom, Write};
|
|
9
|
+
|
|
10
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
|
11
|
+
#[repr(u8)]
|
|
12
|
+
pub enum WalOp {
|
|
13
|
+
CreateTable = 0,
|
|
14
|
+
DropTable = 1,
|
|
15
|
+
Insert = 2,
|
|
16
|
+
Update = 3,
|
|
17
|
+
Remove = 4,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
21
|
+
pub struct WalEntry {
|
|
22
|
+
pub tx_id: u64,
|
|
23
|
+
pub op: WalOp,
|
|
24
|
+
pub data: Vec<u8>,
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/// 记录头长度:len(u64) + crc32(u32)
|
|
28
|
+
const HEADER_LEN: usize = 12;
|
|
29
|
+
/// 单条载荷上限,避免损坏的长度字段触发超大内存分配
|
|
30
|
+
const MAX_PAYLOAD: usize = 64 * 1024 * 1024;
|
|
31
|
+
|
|
32
|
+
fn io_err<E: std::fmt::Display>(e: E) -> std::io::Error {
|
|
33
|
+
std::io::Error::new(std::io::ErrorKind::Other, e.to_string())
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/// 顺序重放,回调所有 `tx_id > after_tx` 的完整记录。
|
|
37
|
+
/// 返回成功解析的字节数(尾部残缺部分不计入)。
|
|
38
|
+
fn replay<R: Read>(reader: &mut R, after_tx: u64, mut on_entry: impl FnMut(WalEntry)) -> std::io::Result<u64> {
|
|
39
|
+
let mut valid = 0u64;
|
|
40
|
+
let mut header = [0u8; HEADER_LEN];
|
|
41
|
+
loop {
|
|
42
|
+
if reader.read_exact(&mut header).is_err() {
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
let len = u64::from_le_bytes(header[..8].try_into().unwrap()) as usize;
|
|
46
|
+
let crc = u32::from_le_bytes(header[8..].try_into().unwrap());
|
|
47
|
+
if len == 0 || len > MAX_PAYLOAD {
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
let mut body = vec![0u8; len];
|
|
51
|
+
if reader.read_exact(&mut body).is_err() {
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
if crc32fast::hash(&body) != crc {
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
match bincode::deserialize::<WalEntry>(&body) {
|
|
58
|
+
Ok(entry) => {
|
|
59
|
+
valid += (HEADER_LEN + len) as u64;
|
|
60
|
+
if entry.tx_id > after_tx {
|
|
61
|
+
on_entry(entry);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
Err(_) => break,
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
Ok(valid)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
pub struct WalWriter {
|
|
71
|
+
file: std::fs::File,
|
|
72
|
+
next_tx: u64,
|
|
73
|
+
pending: u32,
|
|
74
|
+
sync_interval: u32,
|
|
75
|
+
buf: Vec<u8>,
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
impl WalWriter {
|
|
79
|
+
pub fn open(path: &str) -> std::io::Result<Self> {
|
|
80
|
+
let mut file = std::fs::OpenOptions::new()
|
|
81
|
+
.create(true)
|
|
82
|
+
.read(true)
|
|
83
|
+
.append(true)
|
|
84
|
+
.open(path)?;
|
|
85
|
+
// 从已有文件恢复 next_tx:否则重启后 tx_id 会从 1 重新开始,
|
|
86
|
+
// 与历史记录撞号,`replay_after` 的增量语义就废了。
|
|
87
|
+
let mut max_tx = 0u64;
|
|
88
|
+
replay(&mut file, 0, |e| {
|
|
89
|
+
if e.tx_id > max_tx {
|
|
90
|
+
max_tx = e.tx_id;
|
|
91
|
+
}
|
|
92
|
+
})?;
|
|
93
|
+
file.seek(SeekFrom::End(0))?;
|
|
94
|
+
Ok(Self {
|
|
95
|
+
file,
|
|
96
|
+
next_tx: max_tx + 1,
|
|
97
|
+
pending: 0,
|
|
98
|
+
sync_interval: 1,
|
|
99
|
+
buf: Vec::with_capacity(256),
|
|
100
|
+
})
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/// 每写多少条记录 fsync 一次。1 = 每条都落盘(默认,最安全);调大换取吞吐。
|
|
104
|
+
pub fn set_sync_interval(&mut self, n: u32) {
|
|
105
|
+
self.sync_interval = n.max(1);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
pub fn next_tx(&self) -> u64 {
|
|
109
|
+
self.next_tx
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
pub fn append(&mut self, op: WalOp, data: Vec<u8>) -> std::io::Result<u64> {
|
|
113
|
+
let tx_id = self.next_tx;
|
|
114
|
+
let body = bincode::serialize(&WalEntry { tx_id, op, data }).map_err(io_err)?;
|
|
115
|
+
if body.len() > MAX_PAYLOAD {
|
|
116
|
+
return Err(std::io::Error::new(
|
|
117
|
+
std::io::ErrorKind::InvalidInput,
|
|
118
|
+
"wal record too large",
|
|
119
|
+
));
|
|
120
|
+
}
|
|
121
|
+
// 头 + 载荷一次 write_all,避免两次写之间被打断留下半截头部
|
|
122
|
+
self.buf.clear();
|
|
123
|
+
self.buf.extend_from_slice(&(body.len() as u64).to_le_bytes());
|
|
124
|
+
self.buf.extend_from_slice(&crc32fast::hash(&body).to_le_bytes());
|
|
125
|
+
self.buf.extend_from_slice(&body);
|
|
126
|
+
self.file.write_all(&self.buf)?;
|
|
127
|
+
|
|
128
|
+
self.next_tx += 1;
|
|
129
|
+
self.pending += 1;
|
|
130
|
+
if self.pending >= self.sync_interval {
|
|
131
|
+
self.sync()?;
|
|
132
|
+
}
|
|
133
|
+
Ok(tx_id)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/// 真正落盘。`File::flush` 是空操作,WAL 必须走 `sync_data` 才算持久化。
|
|
137
|
+
pub fn sync(&mut self) -> std::io::Result<()> {
|
|
138
|
+
self.file.sync_data()?;
|
|
139
|
+
self.pending = 0;
|
|
140
|
+
Ok(())
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/// `sync` 的别名
|
|
144
|
+
pub fn flush(&mut self) -> std::io::Result<()> {
|
|
145
|
+
self.sync()
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/// 清空日志(快照落盘后调用),避免 WAL 无限增长
|
|
149
|
+
pub fn truncate(&mut self) -> std::io::Result<()> {
|
|
150
|
+
self.file.set_len(0)?;
|
|
151
|
+
self.file.seek(SeekFrom::Start(0))?;
|
|
152
|
+
self.file.sync_data()?;
|
|
153
|
+
self.pending = 0;
|
|
154
|
+
Ok(())
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
pub struct WalReader {
|
|
159
|
+
path: String,
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
impl WalReader {
|
|
163
|
+
pub fn open(path: &str) -> Self {
|
|
164
|
+
Self { path: path.to_string() }
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/// 增量重放:只返回 `tx_id > after_tx` 的记录。
|
|
168
|
+
pub fn replay_after(&self, after_tx: u64) -> std::io::Result<Vec<WalEntry>> {
|
|
169
|
+
let file = match std::fs::File::open(&self.path) {
|
|
170
|
+
Ok(f) => f,
|
|
171
|
+
Err(_) => return Ok(Vec::new()),
|
|
172
|
+
};
|
|
173
|
+
let mut reader = std::io::BufReader::new(file);
|
|
174
|
+
let mut entries = Vec::new();
|
|
175
|
+
replay(&mut reader, after_tx, |e| entries.push(e))?;
|
|
176
|
+
Ok(entries)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/// 重放全部记录
|
|
180
|
+
pub fn replay_all(&self) -> std::io::Result<Vec<WalEntry>> {
|
|
181
|
+
self.replay_after(0)
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
#[cfg(test)]
|
|
186
|
+
mod tests {
|
|
187
|
+
use super::*;
|
|
188
|
+
|
|
189
|
+
fn tmp_path(tag: &str) -> String {
|
|
190
|
+
let mut p = std::env::temp_dir();
|
|
191
|
+
p.push(format!("jsql-wal-{}-{}.log", tag, std::process::id()));
|
|
192
|
+
let s = p.to_string_lossy().to_string();
|
|
193
|
+
let _ = std::fs::remove_file(&s);
|
|
194
|
+
s
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
#[test]
|
|
198
|
+
fn append_replay_roundtrip() {
|
|
199
|
+
let path = tmp_path("rt");
|
|
200
|
+
{
|
|
201
|
+
let mut w = WalWriter::open(&path).unwrap();
|
|
202
|
+
for i in 0..5u8 {
|
|
203
|
+
assert_eq!(w.append(WalOp::Insert, vec![i]).unwrap(), i as u64 + 1);
|
|
204
|
+
}
|
|
205
|
+
w.sync().unwrap();
|
|
206
|
+
}
|
|
207
|
+
let entries = WalReader::open(&path).replay_all().unwrap();
|
|
208
|
+
assert_eq!(entries.len(), 5);
|
|
209
|
+
assert_eq!(entries[3].tx_id, 4);
|
|
210
|
+
assert_eq!(entries[3].op, WalOp::Insert);
|
|
211
|
+
assert_eq!(entries[3].data, vec![3u8]);
|
|
212
|
+
let _ = std::fs::remove_file(&path);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
#[test]
|
|
216
|
+
fn replay_after_is_incremental() {
|
|
217
|
+
let path = tmp_path("inc");
|
|
218
|
+
{
|
|
219
|
+
let mut w = WalWriter::open(&path).unwrap();
|
|
220
|
+
for i in 0..5u8 {
|
|
221
|
+
w.append(WalOp::Update, vec![i]).unwrap();
|
|
222
|
+
}
|
|
223
|
+
w.sync().unwrap();
|
|
224
|
+
}
|
|
225
|
+
let tail = WalReader::open(&path).replay_after(3).unwrap();
|
|
226
|
+
assert_eq!(tail.len(), 2);
|
|
227
|
+
assert!(tail.iter().all(|e| e.tx_id > 3));
|
|
228
|
+
let _ = std::fs::remove_file(&path);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
#[test]
|
|
232
|
+
fn next_tx_is_recovered_after_reopen() {
|
|
233
|
+
let path = tmp_path("recover");
|
|
234
|
+
{
|
|
235
|
+
let mut w = WalWriter::open(&path).unwrap();
|
|
236
|
+
w.append(WalOp::Insert, vec![1]).unwrap();
|
|
237
|
+
w.append(WalOp::Insert, vec![2]).unwrap();
|
|
238
|
+
w.sync().unwrap();
|
|
239
|
+
}
|
|
240
|
+
let mut w = WalWriter::open(&path).unwrap();
|
|
241
|
+
assert_eq!(w.next_tx(), 3, "tx_id 必须接着历史最大值,不能回退到 1");
|
|
242
|
+
assert_eq!(w.append(WalOp::Remove, vec![3]).unwrap(), 3);
|
|
243
|
+
assert_eq!(WalReader::open(&path).replay_all().unwrap().len(), 3);
|
|
244
|
+
let _ = std::fs::remove_file(&path);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
#[test]
|
|
248
|
+
fn truncated_tail_is_dropped() {
|
|
249
|
+
let path = tmp_path("torn");
|
|
250
|
+
{
|
|
251
|
+
let mut w = WalWriter::open(&path).unwrap();
|
|
252
|
+
w.append(WalOp::Insert, vec![7]).unwrap();
|
|
253
|
+
w.append(WalOp::Insert, vec![8]).unwrap();
|
|
254
|
+
w.sync().unwrap();
|
|
255
|
+
}
|
|
256
|
+
// 模拟写一半被杀:尾部只剩半截垃圾
|
|
257
|
+
{
|
|
258
|
+
use std::io::Write as _;
|
|
259
|
+
let mut f = std::fs::OpenOptions::new().append(true).open(&path).unwrap();
|
|
260
|
+
f.write_all(&[9u8, 9, 9]).unwrap();
|
|
261
|
+
f.sync_all().unwrap();
|
|
262
|
+
}
|
|
263
|
+
assert_eq!(WalReader::open(&path).replay_all().unwrap().len(), 2);
|
|
264
|
+
let _ = std::fs::remove_file(&path);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
#[test]
|
|
268
|
+
fn crc_mismatch_stops_replay() {
|
|
269
|
+
let path = tmp_path("crc");
|
|
270
|
+
{
|
|
271
|
+
let mut w = WalWriter::open(&path).unwrap();
|
|
272
|
+
w.append(WalOp::Insert, vec![1]).unwrap();
|
|
273
|
+
w.append(WalOp::Insert, vec![2]).unwrap();
|
|
274
|
+
w.sync().unwrap();
|
|
275
|
+
}
|
|
276
|
+
let len = std::fs::metadata(&path).unwrap().len();
|
|
277
|
+
{
|
|
278
|
+
use std::io::{Seek, SeekFrom, Write as _};
|
|
279
|
+
let mut f = std::fs::OpenOptions::new().write(true).open(&path).unwrap();
|
|
280
|
+
f.seek(SeekFrom::Start(len - 1)).unwrap();
|
|
281
|
+
f.write_all(&[0xFF]).unwrap();
|
|
282
|
+
f.sync_all().unwrap();
|
|
283
|
+
}
|
|
284
|
+
assert_eq!(WalReader::open(&path).replay_all().unwrap().len(), 1, "校验失败记录应丢弃");
|
|
285
|
+
let _ = std::fs::remove_file(&path);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
#[test]
|
|
289
|
+
fn truncate_resets_log_but_keeps_tx_monotonic() {
|
|
290
|
+
let path = tmp_path("trunc");
|
|
291
|
+
let mut w = WalWriter::open(&path).unwrap();
|
|
292
|
+
w.append(WalOp::Insert, vec![1]).unwrap();
|
|
293
|
+
w.truncate().unwrap();
|
|
294
|
+
assert_eq!(WalReader::open(&path).replay_all().unwrap().len(), 0);
|
|
295
|
+
// tx_id 不能回落:否则消费方持有的水位线(replay_after(T))会看不到新记录
|
|
296
|
+
assert_eq!(w.append(WalOp::DropTable, vec![]).unwrap(), 2);
|
|
297
|
+
let _ = std::fs::remove_file(&path);
|
|
298
|
+
}
|
|
86
299
|
}
|
|
@@ -1,94 +1,94 @@
|
|
|
1
|
-
use std::collections::HashMap;
|
|
2
|
-
use serde::{Deserialize, Serialize};
|
|
3
|
-
|
|
4
|
-
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
5
|
-
pub struct Row {
|
|
6
|
-
pub id: u64,
|
|
7
|
-
pub fields: HashMap<String, serde_json::Value>,
|
|
8
|
-
pub created_at: Option<String>,
|
|
9
|
-
pub updated_at: Option<String>,
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
13
|
-
pub struct TableDefinition {
|
|
14
|
-
pub name: String,
|
|
15
|
-
#[serde(with = "schema_order_preserving")]
|
|
16
|
-
pub schema: Vec<(String, FieldSchema)>,
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/// 保序的对象序列化:JSON 对象 ↔ Vec<(String, FieldSchema)>
|
|
20
|
-
pub mod schema_order_preserving {
|
|
21
|
-
use serde::{Deserialize, Deserializer, Serializer};
|
|
22
|
-
use super::FieldSchema;
|
|
23
|
-
|
|
24
|
-
pub fn serialize<S>(v: &[(String, FieldSchema)], s: S) -> Result<S::Ok, S::Error>
|
|
25
|
-
where
|
|
26
|
-
S: Serializer,
|
|
27
|
-
{
|
|
28
|
-
use serde::ser::SerializeMap;
|
|
29
|
-
let mut m = s.serialize_map(Some(v.len()))?;
|
|
30
|
-
for (k, f) in v {
|
|
31
|
-
m.serialize_entry(k, f)?;
|
|
32
|
-
}
|
|
33
|
-
m.end()
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
pub fn deserialize<'de, D>(d: D) -> Result<Vec<(String, FieldSchema)>, D::Error>
|
|
37
|
-
where
|
|
38
|
-
D: Deserializer<'de>,
|
|
39
|
-
{
|
|
40
|
-
struct V<'a>(&'a mut Vec<(String, FieldSchema)>);
|
|
41
|
-
impl<'de, 'a> serde::de::Visitor<'de> for V<'a> {
|
|
42
|
-
type Value = ();
|
|
43
|
-
fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
44
|
-
f.write_str("a schema object")
|
|
45
|
-
}
|
|
46
|
-
fn visit_map<A>(self, mut a: A) -> Result<(), A::Error>
|
|
47
|
-
where
|
|
48
|
-
A: serde::de::MapAccess<'de>,
|
|
49
|
-
{
|
|
50
|
-
while let Some((k, f)) = a.next_entry::<String, FieldSchema>()? {
|
|
51
|
-
self.0.push((k, f));
|
|
52
|
-
}
|
|
53
|
-
Ok(())
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
let mut out = Vec::new();
|
|
57
|
-
d.deserialize_map(V(&mut out))?;
|
|
58
|
-
Ok(out)
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
|
63
|
-
#[serde(rename_all = "camelCase")]
|
|
64
|
-
pub struct FieldSchema {
|
|
65
|
-
#[serde(rename = "type")]
|
|
66
|
-
pub field_type: FieldType,
|
|
67
|
-
#[serde(default)]
|
|
68
|
-
pub primary_key: bool,
|
|
69
|
-
#[serde(default)]
|
|
70
|
-
pub auto_increment: bool,
|
|
71
|
-
#[serde(default)]
|
|
72
|
-
pub nullable: bool,
|
|
73
|
-
#[serde(default)]
|
|
74
|
-
pub unique: bool,
|
|
75
|
-
#[serde(default)]
|
|
76
|
-
pub length: Option<usize>,
|
|
77
|
-
#[serde(default)]
|
|
78
|
-
pub default: Option<serde_json::Value>,
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
|
82
|
-
#[serde(rename_all = "lowercase")]
|
|
83
|
-
pub enum FieldType {
|
|
84
|
-
Integer,
|
|
85
|
-
Float,
|
|
86
|
-
String,
|
|
87
|
-
Boolean,
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
impl Default for FieldType {
|
|
91
|
-
fn default() -> Self {
|
|
92
|
-
FieldType::String
|
|
93
|
-
}
|
|
1
|
+
use std::collections::HashMap;
|
|
2
|
+
use serde::{Deserialize, Serialize};
|
|
3
|
+
|
|
4
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
5
|
+
pub struct Row {
|
|
6
|
+
pub id: u64,
|
|
7
|
+
pub fields: HashMap<String, serde_json::Value>,
|
|
8
|
+
pub created_at: Option<String>,
|
|
9
|
+
pub updated_at: Option<String>,
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
13
|
+
pub struct TableDefinition {
|
|
14
|
+
pub name: String,
|
|
15
|
+
#[serde(with = "schema_order_preserving")]
|
|
16
|
+
pub schema: Vec<(String, FieldSchema)>,
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/// 保序的对象序列化:JSON 对象 ↔ Vec<(String, FieldSchema)>
|
|
20
|
+
pub mod schema_order_preserving {
|
|
21
|
+
use serde::{Deserialize, Deserializer, Serializer};
|
|
22
|
+
use super::FieldSchema;
|
|
23
|
+
|
|
24
|
+
pub fn serialize<S>(v: &[(String, FieldSchema)], s: S) -> Result<S::Ok, S::Error>
|
|
25
|
+
where
|
|
26
|
+
S: Serializer,
|
|
27
|
+
{
|
|
28
|
+
use serde::ser::SerializeMap;
|
|
29
|
+
let mut m = s.serialize_map(Some(v.len()))?;
|
|
30
|
+
for (k, f) in v {
|
|
31
|
+
m.serialize_entry(k, f)?;
|
|
32
|
+
}
|
|
33
|
+
m.end()
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
pub fn deserialize<'de, D>(d: D) -> Result<Vec<(String, FieldSchema)>, D::Error>
|
|
37
|
+
where
|
|
38
|
+
D: Deserializer<'de>,
|
|
39
|
+
{
|
|
40
|
+
struct V<'a>(&'a mut Vec<(String, FieldSchema)>);
|
|
41
|
+
impl<'de, 'a> serde::de::Visitor<'de> for V<'a> {
|
|
42
|
+
type Value = ();
|
|
43
|
+
fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
44
|
+
f.write_str("a schema object")
|
|
45
|
+
}
|
|
46
|
+
fn visit_map<A>(self, mut a: A) -> Result<(), A::Error>
|
|
47
|
+
where
|
|
48
|
+
A: serde::de::MapAccess<'de>,
|
|
49
|
+
{
|
|
50
|
+
while let Some((k, f)) = a.next_entry::<String, FieldSchema>()? {
|
|
51
|
+
self.0.push((k, f));
|
|
52
|
+
}
|
|
53
|
+
Ok(())
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
let mut out = Vec::new();
|
|
57
|
+
d.deserialize_map(V(&mut out))?;
|
|
58
|
+
Ok(out)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
|
63
|
+
#[serde(rename_all = "camelCase")]
|
|
64
|
+
pub struct FieldSchema {
|
|
65
|
+
#[serde(rename = "type")]
|
|
66
|
+
pub field_type: FieldType,
|
|
67
|
+
#[serde(default)]
|
|
68
|
+
pub primary_key: bool,
|
|
69
|
+
#[serde(default)]
|
|
70
|
+
pub auto_increment: bool,
|
|
71
|
+
#[serde(default)]
|
|
72
|
+
pub nullable: bool,
|
|
73
|
+
#[serde(default)]
|
|
74
|
+
pub unique: bool,
|
|
75
|
+
#[serde(default)]
|
|
76
|
+
pub length: Option<usize>,
|
|
77
|
+
#[serde(default)]
|
|
78
|
+
pub default: Option<serde_json::Value>,
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
|
82
|
+
#[serde(rename_all = "lowercase")]
|
|
83
|
+
pub enum FieldType {
|
|
84
|
+
Integer,
|
|
85
|
+
Float,
|
|
86
|
+
String,
|
|
87
|
+
Boolean,
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
impl Default for FieldType {
|
|
91
|
+
fn default() -> Self {
|
|
92
|
+
FieldType::String
|
|
93
|
+
}
|
|
94
94
|
}
|