1use crate::connection::{ToonConnection, to_field_type};
20use crate::error::{ClientError, Result};
21
22use toondb_core::toon::{ToonSchema, ToonType};
23
24pub struct SchemaBuilder {
26 name: String,
27 fields: Vec<FieldBuilder>,
28 primary_key: Option<String>,
29 indexes: Vec<IndexDef>,
30}
31
32#[derive(Debug, Clone)]
34pub struct FieldBuilder {
35 pub name: String,
36 pub field_type: ToonType,
37 pub nullable: bool,
38 pub unique: bool,
39 pub default: Option<String>,
40}
41
42#[derive(Debug, Clone)]
44pub struct IndexDef {
45 pub name: String,
46 pub fields: Vec<String>,
47 pub unique: bool,
48}
49
50impl SchemaBuilder {
51 pub fn table(name: &str) -> Self {
53 Self {
54 name: name.to_string(),
55 fields: Vec::new(),
56 primary_key: None,
57 indexes: Vec::new(),
58 }
59 }
60
61 pub fn field(mut self, name: &str, field_type: ToonType) -> FieldConfig {
63 self.fields.push(FieldBuilder {
64 name: name.to_string(),
65 field_type,
66 nullable: true,
67 unique: false,
68 default: None,
69 });
70 FieldConfig { builder: self }
71 }
72
73 pub fn primary_key(mut self, field: &str) -> Self {
75 self.primary_key = Some(field.to_string());
76 self
77 }
78
79 pub fn index(mut self, name: &str, fields: &[&str], unique: bool) -> Self {
81 self.indexes.push(IndexDef {
82 name: name.to_string(),
83 fields: fields.iter().map(|s| s.to_string()).collect(),
84 unique,
85 });
86 self
87 }
88
89 pub fn build(self) -> ToonSchema {
91 let mut schema = ToonSchema::new(&self.name);
92 for field in self.fields {
93 schema = schema.field(&field.name, field.field_type);
94 }
95 if let Some(pk) = self.primary_key {
96 schema = schema.primary_key(&pk);
97 }
98 schema
99 }
100
101 pub fn get_fields(&self) -> &[FieldBuilder] {
103 &self.fields
104 }
105}
106
107pub struct FieldConfig {
109 pub(crate) builder: SchemaBuilder,
110}
111
112impl FieldConfig {
113 pub fn not_null(mut self) -> Self {
115 if let Some(field) = self.builder.fields.last_mut() {
116 field.nullable = false;
117 }
118 self
119 }
120
121 pub fn unique(mut self) -> Self {
123 if let Some(field) = self.builder.fields.last_mut() {
124 field.unique = true;
125 }
126 self
127 }
128
129 pub fn default_value(mut self, value: &str) -> Self {
131 if let Some(field) = self.builder.fields.last_mut() {
132 field.default = Some(value.to_string());
133 }
134 self
135 }
136
137 pub fn field(self, name: &str, field_type: ToonType) -> FieldConfig {
139 self.builder.field(name, field_type)
140 }
141
142 pub fn primary_key(self, field: &str) -> SchemaBuilder {
144 self.builder.primary_key(field)
145 }
146
147 pub fn index(self, name: &str, fields: &[&str], unique: bool) -> SchemaBuilder {
149 self.builder.index(name, fields, unique)
150 }
151
152 pub fn build(self) -> ToonSchema {
154 self.builder.build()
155 }
156}
157
158#[derive(Debug, Clone)]
160pub struct TableDescription {
161 pub name: String,
162 pub columns: Vec<ColumnDescription>,
163 pub row_count: u64,
164 pub indexes: Vec<String>,
165}
166
167#[derive(Debug, Clone)]
169pub struct ColumnDescription {
170 pub name: String,
171 pub field_type: ToonType,
172 pub nullable: bool,
173}
174
175#[derive(Debug, Clone)]
177pub struct CreateTableResult {
178 pub table_name: String,
179 pub column_count: usize,
180}
181
182#[derive(Debug, Clone)]
184pub struct DropTableResult {
185 pub table_name: String,
186 pub rows_deleted: u64,
187}
188
189#[derive(Debug, Clone)]
191pub struct CreateIndexResult {
192 pub index_name: String,
193 pub table_name: String,
194 pub rows_indexed: usize,
195}
196
197impl ToonConnection {
199 pub fn create_table(&self, schema: ToonSchema) -> Result<CreateTableResult> {
201 let name = schema.name.clone();
202 let column_count = schema.fields.len();
203
204 let fields: Vec<_> = schema
206 .fields
207 .iter()
208 .map(|f| (f.name.clone(), to_field_type(&f.field_type)))
209 .collect();
210 self.tch.write().register_table(&name, &fields);
211
212 {
214 let mut catalog = self.catalog.write();
215 let root_id = 0; catalog
217 .create_table(schema, root_id)
218 .map_err(ClientError::Schema)?;
219 }
220
221 Ok(CreateTableResult {
222 table_name: name,
223 column_count,
224 })
225 }
226
227 pub fn drop_table(&self, name: &str) -> Result<DropTableResult> {
229 let entry = {
230 let mut catalog = self.catalog.write();
231 catalog.drop_table(name).map_err(ClientError::Schema)?
232 };
233
234 Ok(DropTableResult {
235 table_name: name.to_string(),
236 rows_deleted: entry.row_count,
237 })
238 }
239
240 pub fn create_index(
242 &self,
243 name: &str,
244 table: &str,
245 fields: &[&str],
246 unique: bool,
247 ) -> Result<CreateIndexResult> {
248 {
249 let mut catalog = self.catalog.write();
250 let root_id = 0; catalog
252 .create_index(
253 name,
254 table,
255 fields.iter().map(|s| s.to_string()).collect(),
256 unique,
257 root_id,
258 )
259 .map_err(ClientError::Schema)?;
260 }
261
262 Ok(CreateIndexResult {
263 index_name: name.to_string(),
264 table_name: table.to_string(),
265 rows_indexed: 0, })
267 }
268
269 pub fn drop_index(&self, name: &str) -> Result<()> {
271 let mut catalog = self.catalog.write();
272 catalog.drop_index(name).map_err(ClientError::Schema)?;
273 Ok(())
274 }
275}
276
277#[cfg(test)]
278mod tests {
279 use super::*;
280
281 #[test]
282 fn test_schema_builder() {
283 let schema = SchemaBuilder::table("users")
284 .field("id", ToonType::Int)
285 .not_null()
286 .field("name", ToonType::Text)
287 .not_null()
288 .field("email", ToonType::Text)
289 .unique()
290 .primary_key("id")
291 .build();
292
293 assert_eq!(schema.name, "users");
294 assert_eq!(schema.fields.len(), 3);
295 }
296
297 #[test]
298 fn test_schema_with_index() {
299 let schema = SchemaBuilder::table("orders")
300 .field("id", ToonType::Int)
301 .field("user_id", ToonType::Int)
302 .field("amount", ToonType::Float)
303 .primary_key("id")
304 .index("idx_user", &["user_id"], false)
305 .build();
306
307 assert_eq!(schema.name, "orders");
308 }
309
310 #[test]
311 fn test_create_table() {
312 let conn = ToonConnection::open("./test").unwrap();
313
314 let schema = SchemaBuilder::table("test_table")
315 .field("id", ToonType::Int)
316 .field("name", ToonType::Text)
317 .build();
318
319 let result = conn.create_table(schema).unwrap();
320 assert_eq!(result.table_name, "test_table");
321 assert_eq!(result.column_count, 2);
322 }
323
324 #[test]
325 fn test_drop_table() {
326 let conn = ToonConnection::open("./test").unwrap();
327
328 let schema = SchemaBuilder::table("to_drop")
329 .field("id", ToonType::Int)
330 .build();
331
332 conn.create_table(schema).unwrap();
333 let result = conn.drop_table("to_drop").unwrap();
334 assert_eq!(result.table_name, "to_drop");
335 }
336}