1use crate::expr::Column;
6use crate::JsDataType;
7use alloc::string::{String, ToString};
8use alloc::vec::Vec;
9use cynos_core::schema::{Table, TableBuilder};
10use cynos_core::DataType;
11use wasm_bindgen::prelude::*;
12
13#[wasm_bindgen]
15#[derive(Clone, Debug, Default)]
16pub struct ColumnOptions {
17 pub primary_key: bool,
18 pub nullable: bool,
19 pub unique: bool,
20 pub auto_increment: bool,
21}
22
23#[wasm_bindgen]
24impl ColumnOptions {
25 #[wasm_bindgen(constructor)]
26 pub fn new() -> Self {
27 Self::default()
28 }
29
30 #[wasm_bindgen(js_name = primaryKey)]
31 pub fn set_primary_key(mut self, value: bool) -> Self {
32 self.primary_key = value;
33 self
34 }
35
36 #[wasm_bindgen(js_name = setNullable)]
37 pub fn set_nullable(mut self, value: bool) -> Self {
38 self.nullable = value;
39 self
40 }
41
42 #[wasm_bindgen(js_name = setUnique)]
43 pub fn set_unique(mut self, value: bool) -> Self {
44 self.unique = value;
45 self
46 }
47
48 #[wasm_bindgen(js_name = setAutoIncrement)]
49 pub fn set_auto_increment(mut self, value: bool) -> Self {
50 self.auto_increment = value;
51 self
52 }
53}
54
55#[wasm_bindgen]
57pub struct JsTableBuilder {
58 name: String,
59 columns: Vec<ColumnDef>,
60 primary_key: Option<Vec<String>>,
61 indices: Vec<IndexDef>,
62 auto_increment: bool,
63}
64
65#[derive(Clone, Debug)]
66#[allow(dead_code)]
67struct ColumnDef {
68 name: String,
69 data_type: DataType,
70 nullable: bool,
71 unique: bool,
72}
73
74#[derive(Clone, Debug)]
75struct IndexDef {
76 name: String,
77 columns: Vec<String>,
78 unique: bool,
79}
80
81#[wasm_bindgen]
82impl JsTableBuilder {
83 #[wasm_bindgen(constructor)]
85 pub fn new(name: &str) -> Self {
86 Self {
87 name: name.to_string(),
88 columns: Vec::new(),
89 primary_key: None,
90 indices: Vec::new(),
91 auto_increment: false,
92 }
93 }
94
95 pub fn build(&self) -> Result<JsTable, JsValue> {
97 let schema = self.build_internal()?;
98 Ok(JsTable::new(schema))
99 }
100
101 pub fn column(
103 mut self,
104 name: &str,
105 data_type: JsDataType,
106 options: Option<ColumnOptions>,
107 ) -> Self {
108 let opts = options.unwrap_or_default();
109
110 self.columns.push(ColumnDef {
111 name: name.to_string(),
112 data_type: data_type.into(),
113 nullable: opts.nullable,
114 unique: opts.unique || opts.primary_key,
115 });
116
117 if opts.primary_key {
118 self.primary_key = Some(alloc::vec![name.to_string()]);
119 self.auto_increment = opts.auto_increment;
120 }
121
122 self
123 }
124
125 #[wasm_bindgen(js_name = primaryKey)]
127 pub fn primary_key(mut self, columns: &JsValue) -> Self {
128 if let Some(arr) = columns.dyn_ref::<js_sys::Array>() {
129 let cols: Vec<String> = arr
130 .iter()
131 .filter_map(|v| v.as_string())
132 .collect();
133 self.primary_key = Some(cols);
134 } else if let Some(s) = columns.as_string() {
135 self.primary_key = Some(alloc::vec![s]);
136 }
137 self
138 }
139
140 pub fn index(mut self, name: &str, columns: &JsValue) -> Self {
142 let cols = if let Some(arr) = columns.dyn_ref::<js_sys::Array>() {
143 arr.iter().filter_map(|v| v.as_string()).collect()
144 } else if let Some(s) = columns.as_string() {
145 alloc::vec![s]
146 } else {
147 return self;
148 };
149
150 self.indices.push(IndexDef {
151 name: name.to_string(),
152 columns: cols,
153 unique: false,
154 });
155 self
156 }
157
158 #[wasm_bindgen(js_name = uniqueIndex)]
160 pub fn unique_index(mut self, name: &str, columns: &JsValue) -> Self {
161 let cols = if let Some(arr) = columns.dyn_ref::<js_sys::Array>() {
162 arr.iter().filter_map(|v| v.as_string()).collect()
163 } else if let Some(s) = columns.as_string() {
164 alloc::vec![s]
165 } else {
166 return self;
167 };
168
169 self.indices.push(IndexDef {
170 name: name.to_string(),
171 columns: cols,
172 unique: true,
173 });
174 self
175 }
176
177 #[wasm_bindgen(js_name = jsonbIndex)]
179 pub fn jsonb_index(mut self, column: &str, _paths: &JsValue) -> Self {
180 let name = alloc::format!("idx_jsonb_{}", column);
183 self.indices.push(IndexDef {
184 name,
185 columns: alloc::vec![column.to_string()],
186 unique: false,
187 });
188 self
189 }
190
191 pub(crate) fn build_internal(&self) -> Result<Table, JsValue> {
193 let mut builder = TableBuilder::new(&self.name)
194 .map_err(|e| JsValue::from_str(&alloc::format!("{:?}", e)))?;
195
196 for col in &self.columns {
198 builder = builder
199 .add_column(&col.name, col.data_type)
200 .map_err(|e| JsValue::from_str(&alloc::format!("{:?}", e)))?;
201
202 if col.nullable {
203 builder = builder.add_nullable(&[col.name.as_str()]);
204 }
205 }
206
207 if let Some(pk_cols) = &self.primary_key {
209 let pk_refs: Vec<&str> = pk_cols.iter().map(|s| s.as_str()).collect();
210 builder = builder
211 .add_primary_key(&pk_refs, self.auto_increment)
212 .map_err(|e| JsValue::from_str(&alloc::format!("{:?}", e)))?;
213 }
214
215 for idx in &self.indices {
217 let col_refs: Vec<&str> = idx.columns.iter().map(|s| s.as_str()).collect();
218 builder = builder
219 .add_index(&idx.name, &col_refs, idx.unique)
220 .map_err(|e| JsValue::from_str(&alloc::format!("{:?}", e)))?;
221 }
222
223 builder
224 .build()
225 .map_err(|e| JsValue::from_str(&alloc::format!("{:?}", e)))
226 }
227
228 #[wasm_bindgen(getter)]
230 pub fn name(&self) -> String {
231 self.name.clone()
232 }
233}
234
235#[wasm_bindgen]
237pub struct JsTable {
238 schema: Table,
239}
240
241impl JsTable {
242 pub fn new(schema: Table) -> Self {
243 Self { schema }
244 }
245
246 pub fn schema(&self) -> &Table {
247 &self.schema
248 }
249}
250
251#[wasm_bindgen]
252impl JsTable {
253 #[wasm_bindgen(getter)]
255 pub fn name(&self) -> String {
256 self.schema.name().to_string()
257 }
258
259 pub fn col(&self, name: &str) -> Option<Column> {
261 self.schema.get_column(name).map(|c| {
262 Column::new(self.schema.name(), c.name()).with_index(c.index())
263 })
264 }
265
266 #[wasm_bindgen(js_name = columnNames)]
268 pub fn column_names(&self) -> js_sys::Array {
269 let arr = js_sys::Array::new();
270 for col in self.schema.columns() {
271 arr.push(&JsValue::from_str(col.name()));
272 }
273 arr
274 }
275
276 #[wasm_bindgen(js_name = columnCount)]
278 pub fn column_count(&self) -> usize {
279 self.schema.columns().len()
280 }
281
282 #[wasm_bindgen(js_name = getColumnType)]
284 pub fn get_column_type(&self, name: &str) -> Option<JsDataType> {
285 self.schema.get_column(name).map(|c| c.data_type().into())
286 }
287
288 #[wasm_bindgen(js_name = isColumnNullable)]
290 pub fn is_column_nullable(&self, name: &str) -> bool {
291 self.schema
292 .get_column(name)
293 .map(|c| c.is_nullable())
294 .unwrap_or(false)
295 }
296
297 #[wasm_bindgen(js_name = primaryKeyColumns)]
299 pub fn primary_key_columns(&self) -> js_sys::Array {
300 let arr = js_sys::Array::new();
301 if let Some(pk) = self.schema.primary_key() {
302 for col in pk.columns() {
303 arr.push(&JsValue::from_str(&col.name));
304 }
305 }
306 arr
307 }
308}
309
310#[cfg(test)]
311mod tests {
312 use super::*;
313 use wasm_bindgen_test::*;
314
315 wasm_bindgen_test_configure!(run_in_browser);
316
317 #[wasm_bindgen_test]
318 fn test_table_builder_basic() {
319 let builder = JsTableBuilder::new("users")
320 .column("id", JsDataType::Int64, Some(ColumnOptions::new().set_primary_key(true)))
321 .column("name", JsDataType::String, None)
322 .column("age", JsDataType::Int32, None);
323
324 let table = builder.build_internal().unwrap();
325 assert_eq!(table.name(), "users");
326 assert_eq!(table.columns().len(), 3);
327 }
328
329 #[wasm_bindgen_test]
330 fn test_table_builder_with_index() {
331 let builder = JsTableBuilder::new("users")
332 .column("id", JsDataType::Int64, Some(ColumnOptions::new().set_primary_key(true)))
333 .column("email", JsDataType::String, None)
334 .unique_index("idx_email", &JsValue::from_str("email"));
335
336 let table = builder.build_internal().unwrap();
337 assert!(table.indices().iter().any(|i| i.name() == "idx_email"));
338 }
339
340 #[wasm_bindgen_test]
341 fn test_table_builder_nullable() {
342 let builder = JsTableBuilder::new("users")
343 .column("id", JsDataType::Int64, Some(ColumnOptions::new().set_primary_key(true)))
344 .column("bio", JsDataType::String, Some(ColumnOptions::new().set_nullable(true)));
345
346 let table = builder.build_internal().unwrap();
347 let bio_col = table.get_column("bio").unwrap();
348 assert!(bio_col.is_nullable());
349 }
350
351 #[wasm_bindgen_test]
352 fn test_js_table_col() {
353 let builder = JsTableBuilder::new("users")
354 .column("id", JsDataType::Int64, Some(ColumnOptions::new().set_primary_key(true)))
355 .column("name", JsDataType::String, None);
356
357 let schema = builder.build_internal().unwrap();
358 let table = JsTable::new(schema);
359
360 let col = table.col("name").unwrap();
361 assert_eq!(col.name(), "name");
362 }
363
364 #[wasm_bindgen_test]
365 fn test_js_table_column_names() {
366 let builder = JsTableBuilder::new("users")
367 .column("id", JsDataType::Int64, Some(ColumnOptions::new().set_primary_key(true)))
368 .column("name", JsDataType::String, None)
369 .column("age", JsDataType::Int32, None);
370
371 let schema = builder.build_internal().unwrap();
372 let table = JsTable::new(schema);
373
374 let names = table.column_names();
375 assert_eq!(names.length(), 3);
376 }
377}