use crate::connection::{SochConnection, to_field_type};
use crate::error::{ClientError, Result};
use sochdb_core::soch::{SochSchema, SochType};
pub struct SchemaBuilder {
name: String,
fields: Vec<FieldBuilder>,
primary_key: Option<String>,
indexes: Vec<IndexDef>,
}
#[derive(Debug, Clone)]
pub struct FieldBuilder {
pub name: String,
pub field_type: SochType,
pub nullable: bool,
pub unique: bool,
pub default: Option<String>,
}
#[derive(Debug, Clone)]
pub struct IndexDef {
pub name: String,
pub fields: Vec<String>,
pub unique: bool,
}
impl SchemaBuilder {
pub fn table(name: &str) -> Self {
Self {
name: name.to_string(),
fields: Vec::new(),
primary_key: None,
indexes: Vec::new(),
}
}
pub fn field(mut self, name: &str, field_type: SochType) -> FieldConfig {
self.fields.push(FieldBuilder {
name: name.to_string(),
field_type,
nullable: true,
unique: false,
default: None,
});
FieldConfig { builder: self }
}
pub fn primary_key(mut self, field: &str) -> Self {
self.primary_key = Some(field.to_string());
self
}
pub fn index(mut self, name: &str, fields: &[&str], unique: bool) -> Self {
self.indexes.push(IndexDef {
name: name.to_string(),
fields: fields.iter().map(|s| s.to_string()).collect(),
unique,
});
self
}
pub fn build(self) -> SochSchema {
let mut schema = SochSchema::new(&self.name);
for field in self.fields {
schema = schema.field(&field.name, field.field_type);
}
if let Some(pk) = self.primary_key {
schema = schema.primary_key(&pk);
}
schema
}
pub fn get_fields(&self) -> &[FieldBuilder] {
&self.fields
}
}
pub struct FieldConfig {
pub(crate) builder: SchemaBuilder,
}
impl FieldConfig {
pub fn not_null(mut self) -> Self {
if let Some(field) = self.builder.fields.last_mut() {
field.nullable = false;
}
self
}
pub fn unique(mut self) -> Self {
if let Some(field) = self.builder.fields.last_mut() {
field.unique = true;
}
self
}
pub fn default_value(mut self, value: &str) -> Self {
if let Some(field) = self.builder.fields.last_mut() {
field.default = Some(value.to_string());
}
self
}
pub fn field(self, name: &str, field_type: SochType) -> FieldConfig {
self.builder.field(name, field_type)
}
pub fn primary_key(self, field: &str) -> SchemaBuilder {
self.builder.primary_key(field)
}
pub fn index(self, name: &str, fields: &[&str], unique: bool) -> SchemaBuilder {
self.builder.index(name, fields, unique)
}
pub fn build(self) -> SochSchema {
self.builder.build()
}
}
#[derive(Debug, Clone)]
pub struct TableDescription {
pub name: String,
pub columns: Vec<ColumnDescription>,
pub row_count: u64,
pub indexes: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct ColumnDescription {
pub name: String,
pub field_type: SochType,
pub nullable: bool,
}
#[derive(Debug, Clone)]
pub struct CreateTableResult {
pub table_name: String,
pub column_count: usize,
}
#[derive(Debug, Clone)]
pub struct DropTableResult {
pub table_name: String,
pub rows_deleted: u64,
}
#[derive(Debug, Clone)]
pub struct CreateIndexResult {
pub index_name: String,
pub table_name: String,
pub rows_indexed: usize,
}
impl SochConnection {
pub fn create_table(&self, schema: SochSchema) -> Result<CreateTableResult> {
let name = schema.name.clone();
let column_count = schema.fields.len();
let fields: Vec<_> = schema
.fields
.iter()
.map(|f| (f.name.clone(), to_field_type(&f.field_type)))
.collect();
self.tch.write().register_table(&name, &fields);
{
let mut catalog = self.catalog.write();
let root_id = 0; catalog
.create_table(schema, root_id)
.map_err(ClientError::Schema)?;
}
Ok(CreateTableResult {
table_name: name,
column_count,
})
}
pub fn drop_table(&self, name: &str) -> Result<DropTableResult> {
let entry = {
let mut catalog = self.catalog.write();
catalog.drop_table(name).map_err(ClientError::Schema)?
};
Ok(DropTableResult {
table_name: name.to_string(),
rows_deleted: entry.row_count,
})
}
pub fn create_index(
&self,
name: &str,
table: &str,
fields: &[&str],
unique: bool,
) -> Result<CreateIndexResult> {
{
let mut catalog = self.catalog.write();
let root_id = 0; catalog
.create_index(
name,
table,
fields.iter().map(|s| s.to_string()).collect(),
unique,
root_id,
)
.map_err(ClientError::Schema)?;
}
Ok(CreateIndexResult {
index_name: name.to_string(),
table_name: table.to_string(),
rows_indexed: 0, })
}
pub fn drop_index(&self, name: &str) -> Result<()> {
let mut catalog = self.catalog.write();
catalog.drop_index(name).map_err(ClientError::Schema)?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_schema_builder() {
let schema = SchemaBuilder::table("users")
.field("id", SochType::Int)
.not_null()
.field("name", SochType::Text)
.not_null()
.field("email", SochType::Text)
.unique()
.primary_key("id")
.build();
assert_eq!(schema.name, "users");
assert_eq!(schema.fields.len(), 3);
}
#[test]
fn test_schema_with_index() {
let schema = SchemaBuilder::table("orders")
.field("id", SochType::Int)
.field("user_id", SochType::Int)
.field("amount", SochType::Float)
.primary_key("id")
.index("idx_user", &["user_id"], false)
.build();
assert_eq!(schema.name, "orders");
}
#[test]
fn test_create_table() {
let conn = SochConnection::open("./test").unwrap();
let schema = SchemaBuilder::table("test_table")
.field("id", SochType::Int)
.field("name", SochType::Text)
.build();
let result = conn.create_table(schema).unwrap();
assert_eq!(result.table_name, "test_table");
assert_eq!(result.column_count, 2);
}
#[test]
fn test_drop_table() {
let conn = SochConnection::open("./test").unwrap();
let schema = SchemaBuilder::table("to_drop")
.field("id", SochType::Int)
.build();
conn.create_table(schema).unwrap();
let result = conn.drop_table("to_drop").unwrap();
assert_eq!(result.table_name, "to_drop");
}
}