@wowoengine/sawitdb 2.4.0 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +86 -25
- package/bin/sawit-server.js +12 -1
- package/cli/benchmark.js +318 -0
- package/cli/local.js +98 -20
- package/cli/remote.js +65 -11
- package/cli/test.js +203 -0
- package/cli/test_security.js +140 -0
- package/docs/DB Event.md +32 -0
- package/docs/index.html +699 -336
- package/package.json +10 -7
- package/src/SawitClient.js +122 -98
- package/src/SawitServer.js +78 -450
- package/src/SawitWorker.js +55 -0
- package/src/WowoEngine.js +360 -449
- package/src/modules/BTreeIndex.js +114 -43
- package/src/modules/ClusterManager.js +78 -0
- package/src/modules/Env.js +33 -0
- package/src/modules/Pager.js +215 -6
- package/src/modules/QueryParser.js +310 -82
- package/src/modules/ThreadManager.js +84 -0
- package/src/modules/ThreadPool.js +154 -0
- package/src/modules/WAL.js +340 -0
- package/src/server/DatabaseRegistry.js +92 -0
- package/src/server/auth/AuthManager.js +92 -0
- package/src/server/router/RequestRouter.js +278 -0
- package/src/server/session/ClientSession.js +19 -0
- package/src/services/IndexManager.js +183 -0
- package/src/services/QueryExecutor.js +11 -0
- package/src/services/TableManager.js +162 -0
- package/src/services/event/DBEvent.js +61 -0
- package/src/services/event/DBEventHandler.js +39 -0
- package/src/services/executors/AggregateExecutor.js +153 -0
- package/src/services/executors/DeleteExecutor.js +134 -0
- package/src/services/executors/InsertExecutor.js +113 -0
- package/src/services/executors/SelectExecutor.js +130 -0
- package/src/services/executors/UpdateExecutor.js +156 -0
- package/src/services/logic/ConditionEvaluator.js +75 -0
- package/src/services/logic/JoinProcessor.js +230 -0
package/package.json
CHANGED
|
@@ -3,16 +3,19 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "2.
|
|
7
|
-
"description": "A
|
|
6
|
+
"version": "2.6.0",
|
|
7
|
+
"description": "A high-performance hybrid paged database with Agricultural Query Language (AQL) and Generic SQL support. Features single-file storage (.sawit), object caching, worker-thread parallelism, and crash recovery.",
|
|
8
8
|
"main": "src/WowoEngine.js",
|
|
9
9
|
"scripts": {
|
|
10
10
|
"start": "node bin/sawit-server.js",
|
|
11
|
+
"start:cluster": "set SAWIT_CLUSTER_MODE=true && node bin/sawit-server.js",
|
|
11
12
|
"server": "node bin/sawit-server.js",
|
|
12
|
-
"test": "node
|
|
13
|
+
"test": "node cli/test.js",
|
|
13
14
|
"example": "node examples/example_client.js",
|
|
14
15
|
"cli": "node cli/remote.js sawitdb://localhost:7878/default",
|
|
15
|
-
"dev": "SAWIT_LOG_LEVEL=debug node bin/sawit-server.js"
|
|
16
|
+
"dev": "set SAWIT_LOG_LEVEL=debug && node bin/sawit-server.js",
|
|
17
|
+
"bench": "node cli/benchmark.js --spawn --workers=1 --clients=1",
|
|
18
|
+
"bench:cluster": "node cli/benchmark.js --spawn --cluster --workers=4 --clients=10"
|
|
16
19
|
},
|
|
17
20
|
"keywords": [
|
|
18
21
|
"database",
|
|
@@ -44,14 +47,14 @@
|
|
|
44
47
|
"license": "MIT",
|
|
45
48
|
"repository": {
|
|
46
49
|
"type": "git",
|
|
47
|
-
"url": "https://github.com/WowoEngine/SawitDB.git"
|
|
50
|
+
"url": "git+https://github.com/WowoEngine/SawitDB.git"
|
|
48
51
|
},
|
|
49
52
|
"engines": {
|
|
50
53
|
"node": ">=12.0.0"
|
|
51
54
|
},
|
|
52
55
|
"bin": {
|
|
53
|
-
"sawitdb-server": "
|
|
54
|
-
"sawitdb-cli": "
|
|
56
|
+
"sawitdb-server": "bin/sawit-server.js",
|
|
57
|
+
"sawitdb-cli": "cli/remote.js"
|
|
55
58
|
},
|
|
56
59
|
"files": [
|
|
57
60
|
"src/",
|
package/src/SawitClient.js
CHANGED
|
@@ -16,10 +16,10 @@ class SawitClient {
|
|
|
16
16
|
this.pendingRequests = [];
|
|
17
17
|
this.requestId = 0;
|
|
18
18
|
|
|
19
|
-
this
|
|
19
|
+
this.#parseConnectionString(connectionString);
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
#parseConnectionString(connStr) {
|
|
23
23
|
// Parse sawitdb://[user:pass@]host:port/database
|
|
24
24
|
const url = connStr.replace('sawitdb://', 'http://'); // Trick to use URL parser
|
|
25
25
|
const parsed = new URL(url);
|
|
@@ -33,13 +33,16 @@ class SawitClient {
|
|
|
33
33
|
|
|
34
34
|
async connect() {
|
|
35
35
|
return new Promise((resolve, reject) => {
|
|
36
|
-
this.socket = net.createConnection(
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
this.socket = net.createConnection(
|
|
37
|
+
{ host: this.host, port: this.port },
|
|
38
|
+
() => {
|
|
39
|
+
console.log(`[Client] Connected to ${this.host}:${this.port}`);
|
|
40
|
+
this.connected = true;
|
|
41
|
+
}
|
|
42
|
+
);
|
|
40
43
|
|
|
41
44
|
this.socket.on('data', (data) => {
|
|
42
|
-
this
|
|
45
|
+
this.#handleData(data);
|
|
43
46
|
});
|
|
44
47
|
|
|
45
48
|
this.socket.on('end', () => {
|
|
@@ -79,40 +82,46 @@ class SawitClient {
|
|
|
79
82
|
|
|
80
83
|
async _authenticate() {
|
|
81
84
|
return new Promise((resolve, reject) => {
|
|
82
|
-
this
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
85
|
+
this.#sendRequest(
|
|
86
|
+
{
|
|
87
|
+
type: 'auth',
|
|
88
|
+
payload: {
|
|
89
|
+
username: this.username,
|
|
90
|
+
password: this.password
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
(response) => {
|
|
94
|
+
if (response.type === 'auth_success') {
|
|
95
|
+
this.authenticated = true;
|
|
96
|
+
console.log('[Client] Authenticated successfully');
|
|
97
|
+
resolve();
|
|
98
|
+
} else if (response.type === 'error') {
|
|
99
|
+
reject(new Error(response.error));
|
|
100
|
+
}
|
|
95
101
|
}
|
|
96
|
-
|
|
102
|
+
);
|
|
97
103
|
});
|
|
98
104
|
}
|
|
99
105
|
|
|
100
106
|
async use(database) {
|
|
101
107
|
return new Promise((resolve, reject) => {
|
|
102
|
-
this
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
108
|
+
this.#sendRequest(
|
|
109
|
+
{
|
|
110
|
+
type: 'use',
|
|
111
|
+
payload: { database }
|
|
112
|
+
},
|
|
113
|
+
(response) => {
|
|
114
|
+
if (response.type === 'use_success') {
|
|
115
|
+
this.currentDatabase = database;
|
|
116
|
+
console.log(`[Client] Using database '${database}'`);
|
|
117
|
+
resolve(response);
|
|
118
|
+
} else if (response.type === 'error') {
|
|
119
|
+
reject(new Error(response.error));
|
|
120
|
+
} else {
|
|
121
|
+
resolve(response);
|
|
122
|
+
}
|
|
114
123
|
}
|
|
115
|
-
|
|
124
|
+
);
|
|
116
125
|
});
|
|
117
126
|
}
|
|
118
127
|
|
|
@@ -122,96 +131,111 @@ class SawitClient {
|
|
|
122
131
|
}
|
|
123
132
|
|
|
124
133
|
return new Promise((resolve, reject) => {
|
|
125
|
-
this
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
134
|
+
this.#sendRequest(
|
|
135
|
+
{
|
|
136
|
+
type: 'query',
|
|
137
|
+
payload: {
|
|
138
|
+
query: queryString,
|
|
139
|
+
params: params
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
(response) => {
|
|
143
|
+
if (response.type === 'query_result') {
|
|
144
|
+
resolve(response.result);
|
|
145
|
+
} else if (response.type === 'error') {
|
|
146
|
+
reject(new Error(response.error));
|
|
147
|
+
} else {
|
|
148
|
+
resolve(response);
|
|
149
|
+
}
|
|
138
150
|
}
|
|
139
|
-
|
|
151
|
+
);
|
|
140
152
|
});
|
|
141
153
|
}
|
|
142
154
|
|
|
143
155
|
async listDatabases() {
|
|
144
156
|
return new Promise((resolve, reject) => {
|
|
145
|
-
this
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
157
|
+
this.#sendRequest(
|
|
158
|
+
{
|
|
159
|
+
type: 'list_databases',
|
|
160
|
+
payload: {}
|
|
161
|
+
},
|
|
162
|
+
(response) => {
|
|
163
|
+
if (response.type === 'database_list') {
|
|
164
|
+
resolve(response.databases);
|
|
165
|
+
} else if (response.type === 'error') {
|
|
166
|
+
reject(new Error(response.error));
|
|
167
|
+
} else {
|
|
168
|
+
resolve(response);
|
|
169
|
+
}
|
|
155
170
|
}
|
|
156
|
-
|
|
171
|
+
);
|
|
157
172
|
});
|
|
158
173
|
}
|
|
159
174
|
|
|
160
175
|
async dropDatabase(database) {
|
|
161
176
|
return new Promise((resolve, reject) => {
|
|
162
|
-
this
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
177
|
+
this.#sendRequest(
|
|
178
|
+
{
|
|
179
|
+
type: 'drop_database',
|
|
180
|
+
payload: { database }
|
|
181
|
+
},
|
|
182
|
+
(response) => {
|
|
183
|
+
if (response.type === 'drop_success') {
|
|
184
|
+
if (this.currentDatabase === database) {
|
|
185
|
+
this.currentDatabase = null;
|
|
186
|
+
}
|
|
187
|
+
resolve(response.message);
|
|
188
|
+
} else if (response.type === 'error') {
|
|
189
|
+
reject(new Error(response.error));
|
|
190
|
+
} else {
|
|
191
|
+
resolve(response);
|
|
169
192
|
}
|
|
170
|
-
resolve(response.message);
|
|
171
|
-
} else if (response.type === 'error') {
|
|
172
|
-
reject(new Error(response.error));
|
|
173
|
-
} else {
|
|
174
|
-
resolve(response);
|
|
175
193
|
}
|
|
176
|
-
|
|
194
|
+
);
|
|
177
195
|
});
|
|
178
196
|
}
|
|
179
197
|
|
|
180
198
|
async ping() {
|
|
181
199
|
return new Promise((resolve, reject) => {
|
|
182
200
|
const start = Date.now();
|
|
183
|
-
this
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
201
|
+
this.#sendRequest(
|
|
202
|
+
{
|
|
203
|
+
type: 'ping',
|
|
204
|
+
payload: {}
|
|
205
|
+
},
|
|
206
|
+
(response) => {
|
|
207
|
+
if (response.type === 'pong') {
|
|
208
|
+
const latency = Date.now() - start;
|
|
209
|
+
resolve({ latency, serverTime: response.timestamp });
|
|
210
|
+
} else {
|
|
211
|
+
resolve(response);
|
|
212
|
+
}
|
|
192
213
|
}
|
|
193
|
-
|
|
214
|
+
);
|
|
194
215
|
});
|
|
195
216
|
}
|
|
196
217
|
|
|
197
218
|
async stats() {
|
|
198
219
|
return new Promise((resolve, reject) => {
|
|
199
|
-
this
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
220
|
+
this.#sendRequest(
|
|
221
|
+
{
|
|
222
|
+
type: 'stats',
|
|
223
|
+
payload: {}
|
|
224
|
+
},
|
|
225
|
+
(response) => {
|
|
226
|
+
if (response.type === 'stats') {
|
|
227
|
+
resolve(response.stats);
|
|
228
|
+
} else if (response.type === 'error') {
|
|
229
|
+
reject(new Error(response.error));
|
|
230
|
+
} else {
|
|
231
|
+
resolve(response);
|
|
232
|
+
}
|
|
209
233
|
}
|
|
210
|
-
|
|
234
|
+
);
|
|
211
235
|
});
|
|
212
236
|
}
|
|
213
237
|
|
|
214
|
-
|
|
238
|
+
#sendRequest(request, callback) {
|
|
215
239
|
const id = ++this.requestId;
|
|
216
240
|
this.pendingRequests.push({ id, handler: callback });
|
|
217
241
|
|
|
@@ -219,7 +243,7 @@ class SawitClient {
|
|
|
219
243
|
this.socket.write(JSON.stringify(request) + '\n');
|
|
220
244
|
} catch (err) {
|
|
221
245
|
console.error('[Client] Failed to send request:', err.message);
|
|
222
|
-
const idx = this.pendingRequests.findIndex(r => r.id === id);
|
|
246
|
+
const idx = this.pendingRequests.findIndex((r) => r.id === id);
|
|
223
247
|
if (idx !== -1) {
|
|
224
248
|
this.pendingRequests.splice(idx, 1);
|
|
225
249
|
}
|
|
@@ -227,7 +251,7 @@ class SawitClient {
|
|
|
227
251
|
}
|
|
228
252
|
}
|
|
229
253
|
|
|
230
|
-
|
|
254
|
+
#handleData(data) {
|
|
231
255
|
this.buffer += data.toString();
|
|
232
256
|
|
|
233
257
|
let newlineIndex;
|
|
@@ -237,14 +261,14 @@ class SawitClient {
|
|
|
237
261
|
|
|
238
262
|
try {
|
|
239
263
|
const response = JSON.parse(message);
|
|
240
|
-
this
|
|
264
|
+
this.#handleResponse(response);
|
|
241
265
|
} catch (err) {
|
|
242
266
|
console.error('[Client] Failed to parse response:', err.message);
|
|
243
267
|
}
|
|
244
268
|
}
|
|
245
269
|
}
|
|
246
270
|
|
|
247
|
-
|
|
271
|
+
#handleResponse(response) {
|
|
248
272
|
if (this.pendingRequests.length > 0) {
|
|
249
273
|
const request = this.pendingRequests.shift();
|
|
250
274
|
if (request.handler) {
|