use vibesql_catalog::{ColumnSchema, TableSchema};
use vibesql_types::{DataType, SqlValue};
use crate::Database;
#[test]
fn test_json_roundtrip_basic() {
let mut db = Database::new();
let schema = TableSchema::new(
"test_json".to_string(),
vec![
ColumnSchema::new("id".to_string(), DataType::Integer, false),
ColumnSchema::new(
"name".to_string(),
DataType::Varchar { max_length: Some(50) },
false,
),
ColumnSchema::new("active".to_string(), DataType::Boolean, true),
],
);
db.create_table(schema).unwrap();
let table = db.get_table_mut("test_json").unwrap();
table
.insert(crate::Row::new(vec![
SqlValue::Integer(1),
SqlValue::Varchar(arcstr::ArcStr::from("Alice")),
SqlValue::Boolean(true),
]))
.unwrap();
table
.insert(crate::Row::new(vec![
SqlValue::Integer(2),
SqlValue::Varchar(arcstr::ArcStr::from("Bob")),
SqlValue::Boolean(false),
]))
.unwrap();
table
.insert(crate::Row::new(vec![
SqlValue::Integer(3),
SqlValue::Varchar(arcstr::ArcStr::from("Charlie")),
SqlValue::Null,
]))
.unwrap();
let path = "/tmp/test_roundtrip.json";
db.save_json(path).unwrap();
let content = std::fs::read_to_string(path).unwrap();
assert!(content.contains("vibesql"));
assert!(content.contains("test_json"));
assert!(content.contains("Alice"));
assert!(content.contains("Bob"));
assert!(content.contains("Charlie"));
let loaded_db = Database::load_json(path).unwrap();
let loaded_table = loaded_db.get_table("test_json").unwrap();
let rows = loaded_table.scan();
assert_eq!(rows.len(), 3);
assert_eq!(rows[0].values[0], SqlValue::Integer(1));
assert_eq!(rows[0].values[1], SqlValue::Varchar(arcstr::ArcStr::from("Alice")));
assert_eq!(rows[0].values[2], SqlValue::Boolean(true));
assert_eq!(rows[1].values[0], SqlValue::Integer(2));
assert_eq!(rows[1].values[1], SqlValue::Varchar(arcstr::ArcStr::from("Bob")));
assert_eq!(rows[1].values[2], SqlValue::Boolean(false));
assert_eq!(rows[2].values[0], SqlValue::Integer(3));
assert_eq!(rows[2].values[1], SqlValue::Varchar(arcstr::ArcStr::from("Charlie")));
assert_eq!(rows[2].values[2], SqlValue::Null);
std::fs::remove_file(path).ok();
}
#[test]
fn test_json_roundtrip_all_types() {
let mut db = Database::new();
let schema = TableSchema::new(
"all_types_json".to_string(),
vec![
ColumnSchema::new("col_int".to_string(), DataType::Integer, true),
ColumnSchema::new("col_bigint".to_string(), DataType::Bigint, true),
ColumnSchema::new("col_float".to_string(), DataType::Float { precision: 24 }, true),
ColumnSchema::new(
"col_varchar".to_string(),
DataType::Varchar { max_length: Some(100) },
true,
),
ColumnSchema::new("col_bool".to_string(), DataType::Boolean, true),
ColumnSchema::new(
"col_numeric".to_string(),
DataType::Numeric { precision: 10, scale: 2 },
true,
),
],
);
db.create_table(schema).unwrap();
let table = db.get_table_mut("all_types_json").unwrap();
table
.insert(crate::Row::new(vec![
SqlValue::Integer(42),
SqlValue::Bigint(999999),
SqlValue::Float(3.14),
SqlValue::Varchar(arcstr::ArcStr::from("test")),
SqlValue::Boolean(true),
SqlValue::Numeric(123.45),
]))
.unwrap();
table
.insert(crate::Row::new(vec![
SqlValue::Null,
SqlValue::Null,
SqlValue::Null,
SqlValue::Varchar(arcstr::ArcStr::from("not_null")),
SqlValue::Boolean(false),
SqlValue::Null,
]))
.unwrap();
let path = "/tmp/test_all_types.json";
db.save_json(path).unwrap();
let loaded_db = Database::load_json(path).unwrap();
let loaded_table = loaded_db.get_table("all_types_json").unwrap();
let rows = loaded_table.scan();
assert_eq!(rows.len(), 2);
assert_eq!(rows[0].values[0], SqlValue::Integer(42));
assert_eq!(rows[0].values[1], SqlValue::Bigint(999999));
assert_eq!(rows[0].values[3], SqlValue::Varchar(arcstr::ArcStr::from("test")));
assert_eq!(rows[0].values[4], SqlValue::Boolean(true));
assert_eq!(rows[0].values[5], SqlValue::Numeric(123.45));
assert_eq!(rows[1].values[0], SqlValue::Null);
assert_eq!(rows[1].values[3], SqlValue::Varchar(arcstr::ArcStr::from("not_null")));
assert_eq!(rows[1].values[4], SqlValue::Boolean(false));
std::fs::remove_file(path).ok();
}
#[test]
fn test_json_empty_database() {
let db = Database::new();
let path = "/tmp/test_empty.json";
db.save_json(path).unwrap();
let content = std::fs::read_to_string(path).unwrap();
assert!(content.contains("vibesql"));
assert!(content.contains("\"tables\": []"));
let loaded_db = Database::load_json(path).unwrap();
assert_eq!(loaded_db.catalog.list_tables().len(), 0);
std::fs::remove_file(path).ok();
}
#[test]
fn test_json_roundtrip_temporal_types() {
use vibesql_types::{Date, Time, Timestamp};
let mut db = Database::new();
let schema = TableSchema::new(
"temporal_test".to_string(),
vec![
ColumnSchema::new("id".to_string(), DataType::Integer, false),
ColumnSchema::new("col_date".to_string(), DataType::Date, true),
ColumnSchema::new(
"col_time".to_string(),
DataType::Time { with_timezone: false },
true,
),
ColumnSchema::new(
"col_timestamp".to_string(),
DataType::Timestamp { with_timezone: false },
true,
),
ColumnSchema::new(
"col_timestamp_tz".to_string(),
DataType::Timestamp { with_timezone: true },
true,
),
],
);
db.create_table(schema).unwrap();
let table = db.get_table_mut("temporal_test").unwrap();
let date = Date::new(2025, 1, 15).unwrap();
let time = Time::new(14, 30, 45, 0).unwrap();
let timestamp = Timestamp::new(date, time);
table
.insert(crate::Row::new(vec![
SqlValue::Integer(1),
SqlValue::Date(Date::new(2025, 1, 15).unwrap()),
SqlValue::Time(Time::new(14, 30, 45, 0).unwrap()),
SqlValue::Timestamp(timestamp),
SqlValue::Timestamp(timestamp),
]))
.unwrap();
table
.insert(crate::Row::new(vec![
SqlValue::Integer(2),
SqlValue::Null,
SqlValue::Null,
SqlValue::Null,
SqlValue::Null,
]))
.unwrap();
let path = "/tmp/test_temporal.json";
db.save_json(path).unwrap();
let content = std::fs::read_to_string(path).unwrap();
assert!(content.contains("2025-01-15"));
assert!(content.contains("14:30:45"));
let loaded_db = Database::load_json(path).unwrap();
let loaded_table = loaded_db.get_table("temporal_test").unwrap();
let rows = loaded_table.scan();
assert_eq!(rows.len(), 2);
assert_eq!(rows[0].values[0], SqlValue::Integer(1));
assert_eq!(rows[0].values[1], SqlValue::Date(Date::new(2025, 1, 15).unwrap()));
assert_eq!(rows[0].values[2], SqlValue::Time(Time::new(14, 30, 45, 0).unwrap()));
assert_eq!(rows[1].values[1], SqlValue::Null);
assert_eq!(rows[1].values[2], SqlValue::Null);
std::fs::remove_file(path).ok();
}
#[test]
fn test_json_datetime_alias_behavior() {
use vibesql_types::{Date, Time, Timestamp};
let mut db = Database::new();
let schema = TableSchema::new(
"datetime_test".to_string(),
vec![
ColumnSchema::new("id".to_string(), DataType::Integer, false),
ColumnSchema::new(
"created_at".to_string(),
DataType::Timestamp { with_timezone: false },
true,
),
ColumnSchema::new(
"updated_at".to_string(),
DataType::Timestamp { with_timezone: true },
true,
),
],
);
db.create_table(schema).unwrap();
let table = db.get_table_mut("datetime_test").unwrap();
let date = Date::new(2025, 11, 13).unwrap();
let time = Time::new(10, 30, 0, 0).unwrap();
let timestamp = Timestamp::new(date, time);
table
.insert(crate::Row::new(vec![
SqlValue::Integer(1),
SqlValue::Timestamp(timestamp),
SqlValue::Timestamp(timestamp),
]))
.unwrap();
let path = "/tmp/test_datetime_alias.json";
db.save_json(path).unwrap();
let content = std::fs::read_to_string(path).unwrap();
assert!(content.contains("TIMESTAMP"));
assert!(content.contains("2025-11-13"));
let loaded_db = Database::load_json(path).unwrap();
let loaded_table = loaded_db.get_table("datetime_test").unwrap();
let rows = loaded_table.scan();
assert_eq!(rows.len(), 1);
assert_eq!(rows[0].values[0], SqlValue::Integer(1));
assert_eq!(rows[0].values[1], SqlValue::Timestamp(Timestamp::new(date, time)));
assert_eq!(
loaded_table.schema.columns[1].data_type,
DataType::Timestamp { with_timezone: false }
);
assert_eq!(
loaded_table.schema.columns[2].data_type,
DataType::Timestamp { with_timezone: true }
);
std::fs::write(
path,
r#"{
"vibesql": {
"version": "1.0",
"format": "json",
"timestamp": 0
},
"schemas": [],
"roles": [],
"tables": [
{
"name": "datetime_compat",
"schema": "public",
"columns": [
{"name": "id", "type": "INTEGER", "nullable": false},
{"name": "dt1", "type": "DATETIME", "nullable": true},
{"name": "dt2", "type": "DATETIME WITH TIME ZONE", "nullable": true},
{"name": "ts1", "type": "TIMESTAMP", "nullable": true},
{"name": "ts2", "type": "TIMESTAMP WITH TIME ZONE", "nullable": true}
],
"rows": [
{
"id": 1,
"dt1": "2025-11-13 10:30:00",
"dt2": "2025-11-13 10:30:00",
"ts1": "2025-11-13 10:30:00",
"ts2": "2025-11-13 10:30:00"
}
]
}
],
"indexes": [],
"views": []
}"#,
)
.unwrap();
let compat_db = Database::load_json(path).unwrap();
let compat_table = compat_db.get_table("datetime_compat").unwrap();
assert_eq!(
compat_table.schema.columns[1].data_type,
DataType::Timestamp { with_timezone: false }
);
assert_eq!(
compat_table.schema.columns[2].data_type,
DataType::Timestamp { with_timezone: true }
);
assert_eq!(
compat_table.schema.columns[3].data_type,
DataType::Timestamp { with_timezone: false }
);
assert_eq!(
compat_table.schema.columns[4].data_type,
DataType::Timestamp { with_timezone: true }
);
let compat_rows = compat_table.scan();
assert_eq!(compat_rows.len(), 1);
assert_eq!(compat_rows[0].values[0], SqlValue::Integer(1));
let expected_ts = SqlValue::Timestamp(Timestamp::new(date, time));
assert_eq!(compat_rows[0].values[1], expected_ts);
assert_eq!(compat_rows[0].values[2], expected_ts);
assert_eq!(compat_rows[0].values[3], expected_ts);
assert_eq!(compat_rows[0].values[4], expected_ts);
std::fs::remove_file(path).ok();
}
#[test]
fn test_json_numeric_precision() {
let mut db = Database::new();
let schema = TableSchema::new(
"numeric_test".to_string(),
vec![
ColumnSchema::new("id".to_string(), DataType::Integer, false),
ColumnSchema::new(
"col_numeric".to_string(),
DataType::Numeric { precision: 10, scale: 2 },
true,
),
ColumnSchema::new(
"col_decimal".to_string(),
DataType::Decimal { precision: 5, scale: 0 },
true,
),
ColumnSchema::new(
"col_numeric_large".to_string(),
DataType::Numeric { precision: 20, scale: 5 },
true,
),
],
);
db.create_table(schema).unwrap();
let table = db.get_table_mut("numeric_test").unwrap();
table
.insert(crate::Row::new(vec![
SqlValue::Integer(1),
SqlValue::Numeric(123.45),
SqlValue::Numeric(12345.0),
SqlValue::Numeric(123456789.12345),
]))
.unwrap();
table
.insert(crate::Row::new(vec![
SqlValue::Integer(2),
SqlValue::Numeric(0.01),
SqlValue::Numeric(99999.0),
SqlValue::Numeric(0.00001),
]))
.unwrap();
table
.insert(crate::Row::new(vec![
SqlValue::Integer(3),
SqlValue::Null,
SqlValue::Null,
SqlValue::Null,
]))
.unwrap();
let path = "/tmp/test_numeric_precision.json";
db.save_json(path).unwrap();
let loaded_db = Database::load_json(path).unwrap();
let loaded_table = loaded_db.get_table("numeric_test").unwrap();
let rows = loaded_table.scan();
assert_eq!(rows.len(), 3);
assert_eq!(rows[0].values[1], SqlValue::Numeric(123.45));
assert_eq!(rows[0].values[2], SqlValue::Numeric(12345.0));
assert_eq!(rows[1].values[1], SqlValue::Numeric(0.01));
assert_eq!(rows[2].values[1], SqlValue::Null);
assert_eq!(
loaded_table.schema.columns[1].data_type,
DataType::Numeric { precision: 10, scale: 2 }
);
assert_eq!(
loaded_table.schema.columns[2].data_type,
DataType::Decimal { precision: 5, scale: 0 }
);
std::fs::remove_file(path).ok();
}
#[test]
fn test_json_integer_types() {
let mut db = Database::new();
let schema = TableSchema::new(
"integer_test".to_string(),
vec![
ColumnSchema::new("col_smallint".to_string(), DataType::Smallint, true),
ColumnSchema::new("col_integer".to_string(), DataType::Integer, true),
ColumnSchema::new("col_bigint".to_string(), DataType::Bigint, true),
ColumnSchema::new("col_unsigned".to_string(), DataType::Unsigned, true),
],
);
db.create_table(schema).unwrap();
let table = db.get_table_mut("integer_test").unwrap();
table
.insert(crate::Row::new(vec![
SqlValue::Smallint(i16::MAX),
SqlValue::Integer(i64::MAX),
SqlValue::Bigint(i64::MAX),
SqlValue::Unsigned(u64::MAX),
]))
.unwrap();
table
.insert(crate::Row::new(vec![
SqlValue::Smallint(i16::MIN),
SqlValue::Integer(i64::MIN),
SqlValue::Bigint(i64::MIN),
SqlValue::Unsigned(0),
]))
.unwrap();
table
.insert(crate::Row::new(vec![
SqlValue::Smallint(0),
SqlValue::Integer(42),
SqlValue::Bigint(-999999),
SqlValue::Unsigned(123456789),
]))
.unwrap();
let path = "/tmp/test_integer_types.json";
db.save_json(path).unwrap();
let loaded_db = Database::load_json(path).unwrap();
let loaded_table = loaded_db.get_table("integer_test").unwrap();
let rows = loaded_table.scan();
assert_eq!(rows.len(), 3);
assert_eq!(rows[0].values[0], SqlValue::Smallint(i16::MAX));
assert_eq!(rows[0].values[1], SqlValue::Integer(i64::MAX));
assert_eq!(rows[0].values[3], SqlValue::Unsigned(u64::MAX));
assert_eq!(rows[1].values[0], SqlValue::Smallint(i16::MIN));
assert_eq!(rows[1].values[1], SqlValue::Integer(i64::MIN));
assert_eq!(rows[1].values[3], SqlValue::Unsigned(0));
std::fs::remove_file(path).ok();
}
#[test]
fn test_json_special_floats() {
let mut db = Database::new();
let schema = TableSchema::new(
"float_test".to_string(),
vec![
ColumnSchema::new("id".to_string(), DataType::Integer, false),
ColumnSchema::new("col_float".to_string(), DataType::Float { precision: 24 }, true),
ColumnSchema::new("col_real".to_string(), DataType::Real, true),
ColumnSchema::new("col_double".to_string(), DataType::DoublePrecision, true),
],
);
db.create_table(schema).unwrap();
let table = db.get_table_mut("float_test").unwrap();
table
.insert(crate::Row::new(vec![
SqlValue::Integer(1),
SqlValue::Float(f32::NAN),
SqlValue::Real(f64::INFINITY),
SqlValue::Double(f64::NEG_INFINITY),
]))
.unwrap();
table
.insert(crate::Row::new(vec![
SqlValue::Integer(2),
SqlValue::Float(3.14159),
SqlValue::Real(2.71828),
SqlValue::Double(1.41421),
]))
.unwrap();
table
.insert(crate::Row::new(vec![
SqlValue::Integer(3),
SqlValue::Float(0.0),
SqlValue::Real(-0.0),
SqlValue::Double(123.456789),
]))
.unwrap();
let path = "/tmp/test_special_floats.json";
db.save_json(path).unwrap();
let _content = std::fs::read_to_string(path).unwrap();
let loaded_db = Database::load_json(path).unwrap();
let loaded_table = loaded_db.get_table("float_test").unwrap();
let rows = loaded_table.scan();
assert_eq!(rows.len(), 3);
assert_eq!(rows[1].values[0], SqlValue::Integer(2));
std::fs::remove_file(path).ok();
}
#[test]
fn test_json_schemas_and_roles() {
let mut db = Database::new();
let _ = &mut db.catalog.create_schema("analytics".to_string()).unwrap();
let _ = &mut db.catalog.create_schema("staging".to_string()).unwrap();
let _ = &mut db.catalog.create_role("admin".to_string()).unwrap();
let _ = &mut db.catalog.create_role("readonly".to_string()).unwrap();
let schema1 = TableSchema::new(
"users".to_string(),
vec![ColumnSchema::new("id".to_string(), DataType::Integer, false)],
);
db.create_table(schema1).unwrap();
let path = "/tmp/test_schemas_roles.json";
db.save_json(path).unwrap();
let content = std::fs::read_to_string(path).unwrap();
assert!(content.contains("analytics"));
assert!(content.contains("staging"));
assert!(content.contains("admin"));
assert!(content.contains("readonly"));
let loaded_db = Database::load_json(path).unwrap();
let schemas = &loaded_db.catalog.list_schemas();
assert!(schemas.contains(&"analytics".to_string()));
assert!(schemas.contains(&"staging".to_string()));
let roles = &loaded_db.catalog.list_roles();
assert!(roles.contains(&"admin".to_string()));
assert!(roles.contains(&"readonly".to_string()));
assert!(loaded_db.get_table("users").is_some());
std::fs::remove_file(path).ok();
}
#[test]
fn test_json_large_dataset() {
let mut db = Database::new();
let schema = TableSchema::new(
"large_table".to_string(),
vec![
ColumnSchema::new("id".to_string(), DataType::Integer, false),
ColumnSchema::new(
"data".to_string(),
DataType::Varchar { max_length: Some(100) },
false,
),
ColumnSchema::new("value".to_string(), DataType::Float { precision: 24 }, true),
],
);
db.create_table(schema).unwrap();
let table = db.get_table_mut("large_table").unwrap();
for i in 0..10000 {
table
.insert(crate::Row::new(vec![
SqlValue::Integer(i as i64),
SqlValue::Varchar(arcstr::ArcStr::from(format!("data_value_{}", i))),
SqlValue::Float((i as f32) * 1.5),
]))
.unwrap();
}
let path = "/tmp/test_large_dataset.json";
let start = std::time::Instant::now();
db.save_json(path).unwrap();
let save_duration = start.elapsed();
assert!(save_duration.as_secs() < 10, "JSON save took too long: {:?}", save_duration);
let metadata = std::fs::metadata(path).unwrap();
assert!(metadata.len() > 0, "File should have content");
let start = std::time::Instant::now();
let loaded_db = Database::load_json(path).unwrap();
let load_duration = start.elapsed();
assert!(load_duration.as_secs() < 10, "JSON load took too long: {:?}", load_duration);
let loaded_table = loaded_db.get_table("large_table").unwrap();
let rows = loaded_table.scan();
assert_eq!(rows.len(), 10000);
assert_eq!(rows[0].values[0], SqlValue::Integer(0));
assert_eq!(rows[0].values[1], SqlValue::Varchar(arcstr::ArcStr::from("data_value_0")));
assert_eq!(rows[9999].values[0], SqlValue::Integer(9999));
assert_eq!(rows[9999].values[1], SqlValue::Varchar(arcstr::ArcStr::from("data_value_9999")));
std::fs::remove_file(path).ok();
}
#[test]
fn test_json_load_nonexistent_file() {
let result = Database::load_json("/tmp/nonexistent_file_12345.json");
assert!(result.is_err());
let err_msg = result.unwrap_err().to_string();
assert!(err_msg.contains("Failed to open file") || err_msg.contains("No such file"));
}
#[test]
fn test_json_load_malformed() {
let path = "/tmp/test_malformed.json";
std::fs::write(path, "{invalid json content}").unwrap();
let result = Database::load_json(path);
assert!(result.is_err());
let err_msg = result.unwrap_err().to_string();
assert!(err_msg.contains("JSON deserialization failed") || err_msg.contains("expected"));
std::fs::remove_file(path).ok();
}
#[test]
fn test_json_load_empty_file() {
let path = "/tmp/test_empty_json.json";
std::fs::write(path, "").unwrap();
let result = Database::load_json(path);
assert!(result.is_err());
std::fs::remove_file(path).ok();
}
#[test]
fn test_json_index_roundtrip() {
let mut db = Database::new();
let schema = TableSchema::new(
"test_indexes".to_string(),
vec![
ColumnSchema::new("id".to_string(), DataType::Integer, false),
ColumnSchema::new(
"name".to_string(),
DataType::Varchar { max_length: Some(50) },
false,
),
],
);
db.create_table(schema).unwrap();
let idx = vibesql_ast::IndexColumn::Column {
column_name: "name".to_string(),
direction: vibesql_ast::OrderDirection::Asc,
prefix_length: None,
};
db.create_index("idx_name".to_string(), "test_indexes".to_string(), false, vec![idx]).unwrap();
let path = "/tmp/test_index_roundtrip.json";
db.save_json(path).unwrap();
let content = std::fs::read_to_string(path).unwrap();
assert!(content.contains("idx_name") || content.contains("IDX_NAME"));
assert!(content.contains("indexes"));
let loaded_db = Database::load_json(path).unwrap();
let tables = &loaded_db.catalog.list_tables();
eprintln!("Available tables after load: {:?}", tables);
let indexes = loaded_db.list_indexes();
assert!(
indexes.iter().any(|idx| idx.to_lowercase() == "idx_name"),
"Index not found in loaded database. Available indexes: {:?}, Available tables: {:?}",
indexes,
tables
);
std::fs::remove_file(path).ok();
}
#[test]
fn test_json_view_preservation() {
use vibesql_ast::{Expression, FromClause, SelectItem, SelectStmt};
use vibesql_catalog::ViewDefinition;
let mut db = Database::new();
let schema = TableSchema::new(
"users".to_string(),
vec![
ColumnSchema::new("id".to_string(), DataType::Integer, false),
ColumnSchema::new(
"name".to_string(),
DataType::Varchar { max_length: Some(50) },
false,
),
ColumnSchema::new("active".to_string(), DataType::Boolean, false),
],
);
db.create_table(schema).unwrap();
let select_stmt = SelectStmt {
with_clause: None,
distinct: false,
select_list: vec![
SelectItem::Expression {
expr: Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple("id", false)),
alias: None,
source_text: None,
},
SelectItem::Expression {
expr: Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple("name", false)),
alias: None,
source_text: None,
},
],
into_table: None,
into_variables: None,
from: Some(FromClause::Table {
index_hint: None,
name: "users".to_string(),
alias: None,
column_aliases: None,
quoted: false,
}),
where_clause: Some(Expression::BinaryOp {
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"active", false,
))),
op: vibesql_ast::BinaryOperator::Equal,
right: Box::new(Expression::Literal(SqlValue::Boolean(true))),
}),
group_by: None,
having: None,
window_definitions: None,
order_by: None,
limit: None,
offset: None,
set_operation: None,
values: None,
};
let view = ViewDefinition::new_with_sql(
"active_users".to_string(),
Some(vec!["id".to_string(), "name".to_string()]),
select_stmt,
false,
"SELECT id, name FROM users WHERE active = true".to_string(),
);
db.catalog.create_view(view).unwrap();
let path = "/tmp/test_view_preservation.json";
db.save_json(path).unwrap();
let content = std::fs::read_to_string(path).unwrap();
assert!(content.contains("active_users"), "View name should be in JSON");
assert!(
content.contains("SELECT id, name FROM users WHERE active = true"),
"View definition should be in JSON"
);
let loaded_db = Database::load_json(path).unwrap();
assert!(
loaded_db.catalog.get_view("active_users").is_none(),
"Views should not be automatically recreated during deserialization"
);
std::fs::remove_file(path).ok();
}
#[test]
fn test_json_view_preservation_without_sql_definition() {
use vibesql_ast::{FromClause, SelectItem, SelectStmt};
use vibesql_catalog::ViewDefinition;
let mut db = Database::new();
let schema = TableSchema::new(
"products".to_string(),
vec![
ColumnSchema::new("id".to_string(), DataType::Integer, false),
ColumnSchema::new(
"name".to_string(),
DataType::Varchar { max_length: Some(50) },
false,
),
],
);
db.create_table(schema).unwrap();
let select_stmt = SelectStmt {
with_clause: None,
distinct: false,
select_list: vec![SelectItem::Wildcard { alias: None }],
into_table: None,
into_variables: None,
from: Some(FromClause::Table {
index_hint: None,
name: "products".to_string(),
alias: None,
column_aliases: None,
quoted: false,
}),
where_clause: None,
group_by: None,
having: None,
window_definitions: None,
order_by: None,
limit: None,
offset: None,
set_operation: None,
values: None,
};
let view = ViewDefinition::new("all_products".to_string(), None, select_stmt, false);
db.catalog.create_view(view).unwrap();
let path = "/tmp/test_view_no_sql.json";
db.save_json(path).unwrap();
let content = std::fs::read_to_string(path).unwrap();
assert!(content.contains("all_products"), "View name should be in JSON");
assert!(content.contains("SelectStmt"), "View should have Debug format definition");
std::fs::remove_file(path).ok();
}