1use crate::binary_protocol::SchemaLayoutCache;
7use crate::query_builder::{DeleteBuilder, InsertBuilder, SelectBuilder, UpdateBuilder};
8use crate::reactive_bridge::QueryRegistry;
9use crate::table::{JsTable, JsTableBuilder};
10use crate::transaction::JsTransaction;
11use alloc::rc::Rc;
12use alloc::string::{String, ToString};
13#[cfg(feature = "benchmark")]
14use alloc::vec::Vec;
15#[cfg(feature = "benchmark")]
16use cynos_core::Row;
17#[allow(unused_imports)]
18use cynos_incremental::Delta;
19use cynos_query::plan_cache::PlanCache;
20use cynos_reactive::TableId;
21use cynos_storage::TableCache;
22use core::cell::RefCell;
23use wasm_bindgen::prelude::*;
24
25#[wasm_bindgen]
33pub struct Database {
34 name: String,
35 cache: Rc<RefCell<TableCache>>,
36 query_registry: Rc<RefCell<QueryRegistry>>,
37 table_id_map: Rc<RefCell<hashbrown::HashMap<String, TableId>>>,
38 next_table_id: Rc<RefCell<TableId>>,
39 schema_layout_cache: Rc<RefCell<SchemaLayoutCache>>,
40 plan_cache: Rc<RefCell<PlanCache>>,
41}
42
43#[wasm_bindgen]
44impl Database {
45 #[wasm_bindgen(constructor)]
47 pub fn new(name: &str) -> Self {
48 let query_registry = Rc::new(RefCell::new(QueryRegistry::new()));
49 query_registry.borrow_mut().set_self_ref(query_registry.clone());
51
52 Self {
53 name: name.to_string(),
54 cache: Rc::new(RefCell::new(TableCache::new())),
55 query_registry,
56 table_id_map: Rc::new(RefCell::new(hashbrown::HashMap::new())),
57 next_table_id: Rc::new(RefCell::new(1)),
58 schema_layout_cache: Rc::new(RefCell::new(SchemaLayoutCache::new())),
59 plan_cache: Rc::new(RefCell::new(PlanCache::default_size())),
60 }
61 }
62
63 #[wasm_bindgen(js_name = create)]
65 pub async fn create(name: &str) -> Result<Database, JsValue> {
66 Ok(Self::new(name))
67 }
68
69 #[wasm_bindgen(getter)]
71 pub fn name(&self) -> String {
72 self.name.clone()
73 }
74
75 #[wasm_bindgen(js_name = createTable)]
77 pub fn create_table(&self, name: &str) -> JsTableBuilder {
78 JsTableBuilder::new(name)
79 }
80
81 #[wasm_bindgen(js_name = registerTable)]
83 pub fn register_table(&self, builder: &JsTableBuilder) -> Result<(), JsValue> {
84 let schema = builder.build_internal()?;
85 let table_name = schema.name().to_string();
86
87 self.cache
88 .borrow_mut()
89 .create_table(schema)
90 .map_err(|e| JsValue::from_str(&alloc::format!("{:?}", e)))?;
91
92 let table_id = *self.next_table_id.borrow();
94 *self.next_table_id.borrow_mut() += 1;
95 self.table_id_map.borrow_mut().insert(table_name, table_id);
96
97 Ok(())
98 }
99
100 pub fn table(&self, name: &str) -> Option<JsTable> {
102 self.cache
103 .borrow()
104 .get_table(name)
105 .map(|store| JsTable::new(store.schema().clone()))
106 }
107
108 #[wasm_bindgen(js_name = dropTable)]
110 pub fn drop_table(&self, name: &str) -> Result<(), JsValue> {
111 self.cache
112 .borrow_mut()
113 .drop_table(name)
114 .map_err(|e| JsValue::from_str(&alloc::format!("{:?}", e)))?;
115
116 self.table_id_map.borrow_mut().remove(name);
117 Ok(())
118 }
119
120 #[wasm_bindgen(js_name = tableNames)]
122 pub fn table_names(&self) -> js_sys::Array {
123 let arr = js_sys::Array::new();
124 for name in self.cache.borrow().table_names() {
125 arr.push(&JsValue::from_str(name));
126 }
127 arr
128 }
129
130 #[wasm_bindgen(js_name = tableCount)]
132 pub fn table_count(&self) -> usize {
133 self.cache.borrow().table_count()
134 }
135
136 #[wasm_bindgen(variadic)]
141 pub fn select(&self, columns: &JsValue) -> SelectBuilder {
142 SelectBuilder::new(
143 self.cache.clone(),
144 self.query_registry.clone(),
145 self.table_id_map.clone(),
146 self.schema_layout_cache.clone(),
147 self.plan_cache.clone(),
148 columns.clone(),
149 )
150 }
151
152 pub fn insert(&self, table: &str) -> InsertBuilder {
154 InsertBuilder::new(
155 self.cache.clone(),
156 self.query_registry.clone(),
157 self.table_id_map.clone(),
158 table,
159 )
160 }
161
162 pub fn update(&self, table: &str) -> UpdateBuilder {
164 UpdateBuilder::new(
165 self.cache.clone(),
166 self.query_registry.clone(),
167 self.table_id_map.clone(),
168 table,
169 )
170 }
171
172 pub fn delete(&self, table: &str) -> DeleteBuilder {
174 DeleteBuilder::new(
175 self.cache.clone(),
176 self.query_registry.clone(),
177 self.table_id_map.clone(),
178 table,
179 )
180 }
181
182 pub fn transaction(&self) -> JsTransaction {
184 JsTransaction::new(
185 self.cache.clone(),
186 self.query_registry.clone(),
187 self.table_id_map.clone(),
188 )
189 }
190
191 pub fn clear(&self) {
193 self.cache.borrow_mut().clear();
194 }
195
196 #[wasm_bindgen(js_name = clearTable)]
198 pub fn clear_table(&self, name: &str) -> Result<(), JsValue> {
199 self.cache
200 .borrow_mut()
201 .clear_table(name)
202 .map_err(|e| JsValue::from_str(&alloc::format!("{:?}", e)))
203 }
204
205 #[wasm_bindgen(js_name = totalRowCount)]
207 pub fn total_row_count(&self) -> usize {
208 self.cache.borrow().total_row_count()
209 }
210
211 #[wasm_bindgen(js_name = hasTable)]
213 pub fn has_table(&self, name: &str) -> bool {
214 self.cache.borrow().has_table(name)
215 }
216
217 #[cfg(feature = "benchmark")]
226 #[wasm_bindgen(js_name = benchmarkInsert)]
227 pub fn benchmark_insert(&self, table: &str, count: u32) -> Result<JsValue, JsValue> {
228 use cynos_core::Value;
229
230 let mut cache = self.cache.borrow_mut();
231 let store = cache
232 .get_table_mut(table)
233 .ok_or_else(|| JsValue::from_str(&alloc::format!("Table not found: {}", table)))?;
234
235 let schema = store.schema().clone();
236 let columns = schema.columns();
237
238 let start = js_sys::Date::now();
240
241 for i in 0..count {
242 let row_id = cynos_core::next_row_id();
243 let mut values = Vec::with_capacity(columns.len());
244
245 for (col_idx, col) in columns.iter().enumerate() {
246 let value = match col.data_type() {
247 cynos_core::DataType::Int64 => {
248 if col_idx == 0 {
249 Value::Int64(i as i64 + 1)
251 } else {
252 Value::Int64((i % 1000) as i64)
253 }
254 }
255 cynos_core::DataType::Int32 => Value::Int32((i % 100) as i32),
256 cynos_core::DataType::String => Value::String(alloc::format!("value_{}", i)),
257 cynos_core::DataType::Boolean => Value::Boolean(i % 2 == 0),
258 cynos_core::DataType::Float64 => Value::Float64(i as f64 * 0.1),
259 cynos_core::DataType::DateTime => Value::DateTime(1700000000000 + i as i64 * 1000),
260 _ => Value::Null,
261 };
262 values.push(value);
263 }
264
265 let row = Row::new(row_id, values);
266 store.insert(row).map_err(|e| JsValue::from_str(&alloc::format!("{:?}", e)))?;
267 }
268
269 let end = js_sys::Date::now();
270 let duration_ms = end - start;
271 let rows_per_sec = if duration_ms > 0.0 {
272 (count as f64 / duration_ms) * 1000.0
273 } else {
274 f64::INFINITY
275 };
276
277 let result = js_sys::Object::new();
279 js_sys::Reflect::set(&result, &JsValue::from_str("duration_ms"), &JsValue::from_f64(duration_ms))?;
280 js_sys::Reflect::set(&result, &JsValue::from_str("rows_per_sec"), &JsValue::from_f64(rows_per_sec))?;
281 js_sys::Reflect::set(&result, &JsValue::from_str("count"), &JsValue::from_f64(count as f64))?;
282
283 Ok(result.into())
284 }
285
286 #[cfg(feature = "benchmark")]
303 #[wasm_bindgen(js_name = benchmarkRangeQuery)]
304 pub fn benchmark_range_query(
305 &self,
306 table: &str,
307 column: &str,
308 threshold: f64,
309 ) -> Result<JsValue, JsValue> {
310 use crate::query_engine::execute_plan;
311 use cynos_query::planner::LogicalPlan;
312 use cynos_query::ast::{Expr as AstExpr, BinaryOp};
313
314 let cache = self.cache.borrow();
315 let store = cache
316 .get_table(table)
317 .ok_or_else(|| JsValue::from_str(&alloc::format!("Table not found: {}", table)))?;
318
319 let schema = store.schema().clone();
320 let col = schema
321 .get_column(column)
322 .ok_or_else(|| JsValue::from_str(&alloc::format!("Column not found: {}", column)))?;
323 let col_idx = col.index();
324
325 let scan = LogicalPlan::Scan {
327 table: table.to_string(),
328 };
329
330 let predicate = AstExpr::BinaryOp {
331 left: Box::new(AstExpr::column(table, column, col_idx)),
332 op: BinaryOp::Gt,
333 right: Box::new(AstExpr::Literal(cynos_core::Value::Int64(threshold as i64))),
334 };
335
336 let plan = LogicalPlan::Filter {
337 input: Box::new(scan),
338 predicate,
339 };
340
341 let query_start = js_sys::Date::now();
343 let rows = execute_plan(&cache, table, plan)
344 .map_err(|e| JsValue::from_str(&alloc::format!("Query error: {:?}", e)))?;
345 let query_end = js_sys::Date::now();
346 let query_ms = query_end - query_start;
347
348 let row_count = rows.len();
349
350 let serialize_start = js_sys::Date::now();
352 let _js_result = crate::convert::rows_to_js_array(&rows, &schema);
353 let serialize_end = js_sys::Date::now();
354 let serialize_ms = serialize_end - serialize_start;
355
356 let total_ms = query_ms + serialize_ms;
357 let serialization_overhead_pct = if total_ms > 0.0 {
358 (serialize_ms / total_ms) * 100.0
359 } else {
360 0.0
361 };
362
363 let result = js_sys::Object::new();
365 js_sys::Reflect::set(&result, &JsValue::from_str("query_ms"), &JsValue::from_f64(query_ms))?;
366 js_sys::Reflect::set(&result, &JsValue::from_str("serialize_ms"), &JsValue::from_f64(serialize_ms))?;
367 js_sys::Reflect::set(&result, &JsValue::from_str("total_ms"), &JsValue::from_f64(total_ms))?;
368 js_sys::Reflect::set(&result, &JsValue::from_str("row_count"), &JsValue::from_f64(row_count as f64))?;
369 js_sys::Reflect::set(&result, &JsValue::from_str("serialization_overhead_pct"), &JsValue::from_f64(serialization_overhead_pct))?;
370
371 Ok(result.into())
372 }
373}
374
375#[allow(dead_code)]
376impl Database {
377 pub(crate) fn cache(&self) -> Rc<RefCell<TableCache>> {
379 self.cache.clone()
380 }
381
382 pub(crate) fn query_registry(&self) -> Rc<RefCell<QueryRegistry>> {
384 self.query_registry.clone()
385 }
386
387 pub(crate) fn get_table_id(&self, name: &str) -> Option<TableId> {
389 self.table_id_map.borrow().get(name).copied()
390 }
391
392 pub(crate) fn notify_table_change(&self, table_id: TableId, changed_ids: &hashbrown::HashSet<u64>) {
394 self.query_registry
395 .borrow_mut()
396 .on_table_change(table_id, changed_ids);
397 }
398}
399
400#[cfg(test)]
401mod tests {
402 use super::*;
403 use crate::table::ColumnOptions;
404 use crate::JsDataType;
405 use wasm_bindgen_test::*;
406
407 wasm_bindgen_test_configure!(run_in_browser);
408
409 #[wasm_bindgen_test]
410 fn test_database_new() {
411 let db = Database::new("test");
412 assert_eq!(db.name(), "test");
413 assert_eq!(db.table_count(), 0);
414 }
415
416 #[wasm_bindgen_test]
417 fn test_database_create_table() {
418 let db = Database::new("test");
419
420 let builder = db
421 .create_table("users")
422 .column(
423 "id",
424 JsDataType::Int64,
425 Some(ColumnOptions::new().set_primary_key(true)),
426 )
427 .column("name", JsDataType::String, None);
428
429 db.register_table(&builder).unwrap();
430
431 assert!(db.has_table("users"));
432 assert_eq!(db.table_count(), 1);
433 }
434
435 #[wasm_bindgen_test]
436 fn test_database_drop_table() {
437 let db = Database::new("test");
438
439 let builder = db
440 .create_table("users")
441 .column(
442 "id",
443 JsDataType::Int64,
444 Some(ColumnOptions::new().set_primary_key(true)),
445 );
446
447 db.register_table(&builder).unwrap();
448 assert!(db.has_table("users"));
449
450 db.drop_table("users").unwrap();
451 assert!(!db.has_table("users"));
452 }
453
454 #[wasm_bindgen_test]
455 fn test_database_table_names() {
456 let db = Database::new("test");
457
458 let builder1 = db
459 .create_table("users")
460 .column(
461 "id",
462 JsDataType::Int64,
463 Some(ColumnOptions::new().set_primary_key(true)),
464 );
465 db.register_table(&builder1).unwrap();
466
467 let builder2 = db
468 .create_table("orders")
469 .column(
470 "id",
471 JsDataType::Int64,
472 Some(ColumnOptions::new().set_primary_key(true)),
473 );
474 db.register_table(&builder2).unwrap();
475
476 let names = db.table_names();
477 assert_eq!(names.length(), 2);
478 }
479
480 #[wasm_bindgen_test]
481 fn test_database_get_table() {
482 let db = Database::new("test");
483
484 let builder = db
485 .create_table("users")
486 .column(
487 "id",
488 JsDataType::Int64,
489 Some(ColumnOptions::new().set_primary_key(true)),
490 )
491 .column("name", JsDataType::String, None);
492
493 db.register_table(&builder).unwrap();
494
495 let table = db.table("users").unwrap();
496 assert_eq!(table.name(), "users");
497 assert_eq!(table.column_count(), 2);
498 }
499
500 #[wasm_bindgen_test]
501 fn test_database_clear() {
502 let db = Database::new("test");
503
504 let builder = db
505 .create_table("users")
506 .column(
507 "id",
508 JsDataType::Int64,
509 Some(ColumnOptions::new().set_primary_key(true)),
510 );
511
512 db.register_table(&builder).unwrap();
513
514 db.clear();
515 assert_eq!(db.total_row_count(), 0);
516 assert!(db.has_table("users"));
518 }
519}