1use std::sync::Arc;
46
47use vibesql_ast::Statement;
48use vibesql_storage::{Database, Row};
49use vibesql_types::SqlValue;
50
51use crate::{
52 cache::{
53 ArenaParseError, ArenaPreparedStatement, CachedPlan, PkPointLookupPlan, PreparedStatement,
54 PreparedStatementCache, PreparedStatementError, ProjectionPlan, ResolvedProjection,
55 },
56 errors::ExecutorError,
57 DeleteExecutor, InsertExecutor, SelectExecutor, SelectResult, UpdateExecutor,
58};
59
60#[derive(Debug)]
62pub enum PreparedExecutionResult {
63 Select(SelectResult),
65 RowsAffected(usize),
67 Ok,
69}
70
71impl PreparedExecutionResult {
72 pub fn rows(&self) -> Option<&[Row]> {
74 match self {
75 PreparedExecutionResult::Select(result) => Some(&result.rows),
76 _ => None,
77 }
78 }
79
80 pub fn rows_affected(&self) -> Option<usize> {
82 match self {
83 PreparedExecutionResult::RowsAffected(n) => Some(*n),
84 _ => None,
85 }
86 }
87
88 pub fn into_select_result(self) -> Option<SelectResult> {
90 match self {
91 PreparedExecutionResult::Select(result) => Some(result),
92 _ => None,
93 }
94 }
95}
96
97#[derive(Debug)]
99pub enum SessionError {
100 PreparedStatement(PreparedStatementError),
102 Execution(ExecutorError),
104 UnsupportedStatement(String),
106}
107
108impl std::fmt::Display for SessionError {
109 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
110 match self {
111 SessionError::PreparedStatement(e) => write!(f, "Prepared statement error: {}", e),
112 SessionError::Execution(e) => write!(f, "Execution error: {:?}", e),
113 SessionError::UnsupportedStatement(msg) => write!(f, "Unsupported statement: {}", msg),
114 }
115 }
116}
117
118impl std::error::Error for SessionError {}
119
120impl From<PreparedStatementError> for SessionError {
121 fn from(e: PreparedStatementError) -> Self {
122 SessionError::PreparedStatement(e)
123 }
124}
125
126impl From<ExecutorError> for SessionError {
127 fn from(e: ExecutorError) -> Self {
128 SessionError::Execution(e)
129 }
130}
131
132pub struct Session<'a> {
137 db: &'a Database,
138 cache: Arc<PreparedStatementCache>,
139}
140
141impl<'a> Session<'a> {
142 pub fn new(db: &'a Database) -> Self {
146 Self { db, cache: Arc::new(PreparedStatementCache::default_cache()) }
147 }
148
149 pub fn with_cache_size(db: &'a Database, cache_size: usize) -> Self {
151 Self { db, cache: Arc::new(PreparedStatementCache::new(cache_size)) }
152 }
153
154 pub fn with_shared_cache(db: &'a Database, cache: Arc<PreparedStatementCache>) -> Self {
159 Self { db, cache }
160 }
161
162 pub fn database(&self) -> &Database {
164 self.db
165 }
166
167 pub fn cache(&self) -> &PreparedStatementCache {
169 &self.cache
170 }
171
172 pub fn shared_cache(&self) -> Arc<PreparedStatementCache> {
174 Arc::clone(&self.cache)
175 }
176
177 pub fn prepare(&self, sql: &str) -> Result<Arc<PreparedStatement>, SessionError> {
191 self.cache.get_or_prepare(sql).map_err(SessionError::from)
192 }
193
194 pub fn prepare_arena(&self, sql: &str) -> Result<Arc<ArenaPreparedStatement>, ArenaParseError> {
216 self.cache.get_or_prepare_arena(sql)
217 }
218
219 pub fn execute_prepared(
234 &self,
235 stmt: &PreparedStatement,
236 params: &[SqlValue],
237 ) -> Result<PreparedExecutionResult, SessionError> {
238 if params.len() != stmt.param_count() {
240 return Err(SessionError::PreparedStatement(
241 PreparedStatementError::ParameterCountMismatch {
242 expected: stmt.param_count(),
243 actual: params.len(),
244 },
245 ));
246 }
247
248 match stmt.cached_plan() {
250 CachedPlan::PkPointLookup(plan) => {
251 if let Some(result) = self.try_execute_pk_lookup(plan, params)? {
252 return Ok(result);
253 }
254 }
256 CachedPlan::SimpleFastPath(plan) => {
257 let bound_stmt = stmt.bind(params)?;
260 if let Statement::Select(select_stmt) = &bound_stmt {
261 let executor = SelectExecutor::new(self.db);
262
263 let columns = plan.get_or_resolve_columns(|| {
265 executor.derive_fast_path_column_names(select_stmt).ok()
266 });
267
268 match columns {
269 Some(cached_columns) => {
270 let rows = executor.execute_fast_path(select_stmt)?;
272 return Ok(PreparedExecutionResult::Select(SelectResult {
273 columns: cached_columns.iter().cloned().collect(),
274 rows,
275 }));
276 }
277 None => {
278 let result = executor.execute_fast_path_with_columns(select_stmt)?;
280 return Ok(PreparedExecutionResult::Select(result));
281 }
282 }
283 }
284 }
286 CachedPlan::PkDelete(_) => {
287 }
290 CachedPlan::Standard => {
291 }
293 }
294
295 let bound_stmt = stmt.bind(params)?;
297
298 self.execute_statement(&bound_stmt)
300 }
301
302 fn try_execute_pk_lookup(
308 &self,
309 plan: &PkPointLookupPlan,
310 params: &[SqlValue],
311 ) -> Result<Option<PreparedExecutionResult>, SessionError> {
312 let table = match self.db.get_table(&plan.table_name) {
314 Some(t) => t,
315 None => return Ok(None), };
317
318 let actual_pk_columns = match &table.schema.primary_key {
320 Some(cols) if cols.len() == plan.pk_columns.len() => cols,
321 _ => return Ok(None), };
323
324 for (param_idx, pk_col_idx) in &plan.param_to_pk_col {
327 if *param_idx >= params.len() || *pk_col_idx >= plan.pk_columns.len() {
328 return Ok(None); }
330
331 let expected_col = &plan.pk_columns[*pk_col_idx];
333 let actual_col = &actual_pk_columns[*pk_col_idx];
334 if !expected_col.eq_ignore_ascii_case(actual_col) {
335 return Ok(None); }
337 }
338
339 let resolved = match plan
341 .get_or_resolve(|proj| self.resolve_projection(proj, &table.schema.columns))
342 {
343 Some(r) => r,
344 None => return Ok(None), };
346
347 let row = if plan.param_to_pk_col.len() == 1 {
350 let (param_idx, _) = plan.param_to_pk_col[0];
351 self.db
352 .get_row_by_pk(&plan.table_name, ¶ms[param_idx])
353 .map_err(|e| SessionError::Execution(ExecutorError::StorageError(e.to_string())))?
354 } else {
355 let pk_values: Vec<SqlValue> = plan
357 .param_to_pk_col
358 .iter()
359 .map(|(param_idx, _)| params[*param_idx].clone())
360 .collect();
361 self.db
362 .get_row_by_composite_pk(&plan.table_name, &pk_values)
363 .map_err(|e| SessionError::Execution(ExecutorError::StorageError(e.to_string())))?
364 };
365
366 let columns: Vec<String> = resolved.column_names.iter().cloned().collect();
368
369 let rows = match row {
370 Some(r) => {
371 if resolved.column_indices.is_empty() {
373 vec![r.clone()]
375 } else {
376 let projected_values: Vec<SqlValue> =
378 resolved.column_indices.iter().map(|&i| r.values[i].clone()).collect();
379 vec![Row::new(projected_values)]
380 }
381 }
382 None => vec![],
383 };
384
385 Ok(Some(PreparedExecutionResult::Select(SelectResult { columns, rows })))
386 }
387
388 fn resolve_projection(
392 &self,
393 proj: &ProjectionPlan,
394 schema_columns: &[vibesql_catalog::ColumnSchema],
395 ) -> Option<ResolvedProjection> {
396 match proj {
397 ProjectionPlan::Wildcard => {
398 let column_names: Arc<[String]> =
401 schema_columns.iter().map(|c| c.name.clone()).collect();
402 Some(ResolvedProjection { column_indices: vec![], column_names })
403 }
404 ProjectionPlan::Columns(projections) => {
405 let mut col_indices = Vec::with_capacity(projections.len());
406 let mut column_names = Vec::with_capacity(projections.len());
407
408 for proj in projections {
409 let idx = schema_columns
410 .iter()
411 .position(|c| c.name.eq_ignore_ascii_case(&proj.column_name))?;
412
413 col_indices.push(idx);
414 column_names
415 .push(proj.alias.clone().unwrap_or_else(|| proj.column_name.clone()));
416 }
417
418 Some(ResolvedProjection {
419 column_indices: col_indices,
420 column_names: column_names.into(),
421 })
422 }
423 }
424 }
425
426 fn execute_statement(&self, stmt: &Statement) -> Result<PreparedExecutionResult, SessionError> {
428 match stmt {
429 Statement::Select(select_stmt) => {
430 let executor = SelectExecutor::new(self.db);
431 let result = executor.execute_with_columns(select_stmt)?;
432 Ok(PreparedExecutionResult::Select(result))
433 }
434 _ => Err(SessionError::UnsupportedStatement(
435 "Only SELECT is supported for read-only sessions. Use SessionMut for DML.".into(),
436 )),
437 }
438 }
439}
440
441pub struct SessionMut<'a> {
446 db: &'a mut Database,
447 cache: Arc<PreparedStatementCache>,
448}
449
450impl<'a> SessionMut<'a> {
451 pub fn new(db: &'a mut Database) -> Self {
453 Self { db, cache: Arc::new(PreparedStatementCache::default_cache()) }
454 }
455
456 pub fn with_cache_size(db: &'a mut Database, cache_size: usize) -> Self {
458 Self { db, cache: Arc::new(PreparedStatementCache::new(cache_size)) }
459 }
460
461 pub fn with_shared_cache(db: &'a mut Database, cache: Arc<PreparedStatementCache>) -> Self {
463 Self { db, cache }
464 }
465
466 pub fn database(&self) -> &Database {
468 self.db
469 }
470
471 pub fn database_mut(&mut self) -> &mut Database {
473 self.db
474 }
475
476 pub fn cache(&self) -> &PreparedStatementCache {
478 &self.cache
479 }
480
481 pub fn shared_cache(&self) -> Arc<PreparedStatementCache> {
483 Arc::clone(&self.cache)
484 }
485
486 pub fn prepare(&self, sql: &str) -> Result<Arc<PreparedStatement>, SessionError> {
488 self.cache.get_or_prepare(sql).map_err(SessionError::from)
489 }
490
491 pub fn prepare_arena(&self, sql: &str) -> Result<Arc<ArenaPreparedStatement>, ArenaParseError> {
495 self.cache.get_or_prepare_arena(sql)
496 }
497
498 pub fn execute_prepared(
502 &self,
503 stmt: &PreparedStatement,
504 params: &[SqlValue],
505 ) -> Result<PreparedExecutionResult, SessionError> {
506 let bound_stmt = stmt.bind(params)?;
507 self.execute_statement_readonly(&bound_stmt)
508 }
509
510 pub fn execute_prepared_mut(
514 &mut self,
515 stmt: &PreparedStatement,
516 params: &[SqlValue],
517 ) -> Result<PreparedExecutionResult, SessionError> {
518 if let CachedPlan::PkDelete(plan) = stmt.cached_plan() {
520 if let Some(result) = self.try_execute_pk_delete(plan, params)? {
521 return Ok(result);
522 }
523 }
525
526 let bound_stmt = stmt.bind(params)?;
527 self.execute_statement_mut(&bound_stmt)
528 }
529
530 fn try_execute_pk_delete(
535 &mut self,
536 plan: &crate::cache::PkDeletePlan,
537 params: &[SqlValue],
538 ) -> Result<Option<PreparedExecutionResult>, SessionError> {
539 if let Some(valid) = plan.is_fast_path_valid() {
541 if !valid {
542 return Ok(None); }
544 } else {
546 let valid = self.validate_delete_fast_path(plan);
548 plan.set_fast_path_valid(valid);
549 if !valid {
550 return Ok(None);
551 }
552 }
553
554 let pk_values = plan.build_pk_values(params);
556
557 match self.db.delete_by_pk_fast(&plan.table_name, &pk_values) {
559 Ok(deleted) => {
560 let rows_affected = if deleted { 1 } else { 0 };
561 self.db.set_last_changes_count(rows_affected);
563 self.db.increment_total_changes_count(rows_affected);
564 Ok(Some(PreparedExecutionResult::RowsAffected(rows_affected)))
565 }
566 Err(_) => Ok(None), }
568 }
569
570 fn validate_delete_fast_path(&self, plan: &crate::cache::PkDeletePlan) -> bool {
573 let has_triggers = self
575 .db
576 .catalog
577 .get_triggers_for_table(&plan.table_name, Some(vibesql_ast::TriggerEvent::Delete))
578 .next()
579 .is_some();
580
581 if has_triggers {
582 return false;
583 }
584
585 let schema = match self.db.catalog.get_table(&plan.table_name) {
587 Some(s) => s,
588 None => return false, };
590
591 let has_pk = schema.get_primary_key_indices().is_some();
592 if has_pk {
593 let has_referencing_fks = self.db.catalog.list_tables().iter().any(|t| {
594 self.db
595 .catalog
596 .get_table(t)
597 .map(|s| {
598 s.foreign_keys
599 .iter()
600 .any(|fk| fk.parent_table.eq_ignore_ascii_case(&plan.table_name))
601 })
602 .unwrap_or(false)
603 });
604
605 if has_referencing_fks {
606 return false;
607 }
608 }
609
610 if self.db.has_partial_indexes(&plan.table_name) {
613 return false;
614 }
615
616 true }
618
619 fn execute_statement_readonly(
621 &self,
622 stmt: &Statement,
623 ) -> Result<PreparedExecutionResult, SessionError> {
624 match stmt {
625 Statement::Select(select_stmt) => {
626 let executor = SelectExecutor::new(self.db);
627 let result = executor.execute_with_columns(select_stmt)?;
628 Ok(PreparedExecutionResult::Select(result))
629 }
630 _ => Err(SessionError::UnsupportedStatement(
631 "Use execute_prepared_mut for DML statements".into(),
632 )),
633 }
634 }
635
636 fn execute_statement_mut(
638 &mut self,
639 stmt: &Statement,
640 ) -> Result<PreparedExecutionResult, SessionError> {
641 match stmt {
642 Statement::Select(select_stmt) => {
643 let executor = SelectExecutor::new(self.db);
644 let result = executor.execute_with_columns(select_stmt)?;
645 Ok(PreparedExecutionResult::Select(result))
646 }
647 Statement::Insert(insert_stmt) => {
648 let rows_affected = InsertExecutor::execute(self.db, insert_stmt)?;
649 self.db.set_last_changes_count(rows_affected);
651 self.db.increment_total_changes_count(rows_affected);
652 Ok(PreparedExecutionResult::RowsAffected(rows_affected))
657 }
658 Statement::Update(update_stmt) => {
659 let rows_affected = UpdateExecutor::execute(update_stmt, self.db)?;
660 self.db.set_last_changes_count(rows_affected);
662 self.db.increment_total_changes_count(rows_affected);
663 Ok(PreparedExecutionResult::RowsAffected(rows_affected))
668 }
669 Statement::Delete(delete_stmt) => {
670 let rows_affected = DeleteExecutor::execute(delete_stmt, self.db)?;
671 self.db.set_last_changes_count(rows_affected);
673 self.db.increment_total_changes_count(rows_affected);
674 Ok(PreparedExecutionResult::RowsAffected(rows_affected))
679 }
680 _ => Err(SessionError::UnsupportedStatement(format!(
681 "Statement type {:?} not supported for prepared execution",
682 std::mem::discriminant(stmt)
683 ))),
684 }
685 }
686}
687
688#[cfg(test)]
689mod tests {
690 use vibesql_catalog::{ColumnSchema, TableSchema};
691 use vibesql_types::DataType;
692
693 use super::*;
694
695 fn create_test_db() -> Database {
696 let mut db = Database::new();
697 db.catalog.set_case_sensitive_identifiers(false);
699
700 let columns = vec![
702 ColumnSchema::new("id".to_string(), DataType::Integer, false),
703 ColumnSchema::new(
704 "name".to_string(),
705 DataType::Varchar { max_length: Some(100) },
706 true,
707 ),
708 ];
709 let schema =
710 TableSchema::with_primary_key("users".to_string(), columns, vec!["id".to_string()]);
711 db.create_table(schema).unwrap();
712
713 let row1 =
715 Row::new(vec![SqlValue::Integer(1), SqlValue::Varchar(arcstr::ArcStr::from("Alice"))]);
716 let row2 =
717 Row::new(vec![SqlValue::Integer(2), SqlValue::Varchar(arcstr::ArcStr::from("Bob"))]);
718 let row3 = Row::new(vec![
719 SqlValue::Integer(3),
720 SqlValue::Varchar(arcstr::ArcStr::from("Charlie")),
721 ]);
722
723 db.insert_row("users", row1).unwrap();
724 db.insert_row("users", row2).unwrap();
725 db.insert_row("users", row3).unwrap();
726
727 db
728 }
729
730 #[test]
731 fn test_session_prepare() {
732 let db = create_test_db();
733 let session = Session::new(&db);
734
735 let stmt = session.prepare("SELECT * FROM users WHERE id = ?").unwrap();
736 assert_eq!(stmt.param_count(), 1);
737 }
738
739 #[test]
740 fn test_session_execute_prepared() {
741 let db = create_test_db();
742 let session = Session::new(&db);
743
744 let stmt = session.prepare("SELECT * FROM users WHERE id = ?").unwrap();
745
746 let result = session.execute_prepared(&stmt, &[SqlValue::Integer(1)]).unwrap();
748
749 if let PreparedExecutionResult::Select(select_result) = result {
750 assert_eq!(select_result.rows.len(), 1);
751 assert_eq!(select_result.rows[0].values[0], SqlValue::Integer(1));
752 assert_eq!(
753 select_result.rows[0].values[1],
754 SqlValue::Varchar(arcstr::ArcStr::from("Alice"))
755 );
756 } else {
757 panic!("Expected Select result");
758 }
759 }
760
761 #[test]
762 fn test_session_reuse_prepared() {
763 let db = create_test_db();
764 let session = Session::new(&db);
765
766 let stmt = session.prepare("SELECT * FROM users WHERE id = ?").unwrap();
767
768 let result1 = session.execute_prepared(&stmt, &[SqlValue::Integer(1)]).unwrap();
770 let result2 = session.execute_prepared(&stmt, &[SqlValue::Integer(2)]).unwrap();
771 let result3 = session.execute_prepared(&stmt, &[SqlValue::Integer(3)]).unwrap();
772
773 assert_eq!(
775 result1.rows().unwrap()[0].values[1],
776 SqlValue::Varchar(arcstr::ArcStr::from("Alice"))
777 );
778 assert_eq!(
779 result2.rows().unwrap()[0].values[1],
780 SqlValue::Varchar(arcstr::ArcStr::from("Bob"))
781 );
782 assert_eq!(
783 result3.rows().unwrap()[0].values[1],
784 SqlValue::Varchar(arcstr::ArcStr::from("Charlie"))
785 );
786
787 let stats = session.cache().stats();
789 assert_eq!(stats.misses, 1);
790 let _hits = stats.hits;
792 }
793
794 #[test]
795 fn test_session_param_count_mismatch() {
796 let db = create_test_db();
797 let session = Session::new(&db);
798
799 let stmt = session.prepare("SELECT * FROM users WHERE id = ?").unwrap();
800
801 let result = session.execute_prepared(&stmt, &[]);
803 assert!(result.is_err());
804
805 let result = session.execute_prepared(&stmt, &[SqlValue::Integer(1), SqlValue::Integer(2)]);
806 assert!(result.is_err());
807 }
808
809 #[test]
810 fn test_session_mut_insert() {
811 let mut db = create_test_db();
812 let mut session = SessionMut::new(&mut db);
813
814 let stmt = session.prepare("INSERT INTO users (id, name) VALUES (?, ?)").unwrap();
815
816 let result = session
817 .execute_prepared_mut(
818 &stmt,
819 &[SqlValue::Integer(4), SqlValue::Varchar(arcstr::ArcStr::from("David"))],
820 )
821 .unwrap();
822
823 assert_eq!(result.rows_affected(), Some(1));
824
825 let select_stmt = session.prepare("SELECT * FROM users WHERE id = ?").unwrap();
827 let select_result =
828 session.execute_prepared(&select_stmt, &[SqlValue::Integer(4)]).unwrap();
829
830 assert_eq!(select_result.rows().unwrap().len(), 1);
831 assert_eq!(
832 select_result.rows().unwrap()[0].values[1],
833 SqlValue::Varchar(arcstr::ArcStr::from("David"))
834 );
835 }
836
837 #[test]
838 fn test_session_mut_update() {
839 let mut db = create_test_db();
840 let mut session = SessionMut::new(&mut db);
841
842 let stmt = session.prepare("UPDATE users SET name = ? WHERE id = ?").unwrap();
843
844 let result = session
845 .execute_prepared_mut(
846 &stmt,
847 &[SqlValue::Varchar(arcstr::ArcStr::from("Alicia")), SqlValue::Integer(1)],
848 )
849 .unwrap();
850
851 assert_eq!(result.rows_affected(), Some(1));
852
853 let select_stmt = session.prepare("SELECT * FROM users WHERE id = ?").unwrap();
855 let select_result =
856 session.execute_prepared(&select_stmt, &[SqlValue::Integer(1)]).unwrap();
857
858 assert_eq!(
859 select_result.rows().unwrap()[0].values[1],
860 SqlValue::Varchar(arcstr::ArcStr::from("Alicia"))
861 );
862 }
863
864 #[test]
865 fn test_session_mut_delete() {
866 let mut db = create_test_db();
867 let mut session = SessionMut::new(&mut db);
868
869 let stmt = session.prepare("DELETE FROM users WHERE id = ?").unwrap();
870
871 let result = session.execute_prepared_mut(&stmt, &[SqlValue::Integer(1)]).unwrap();
872
873 assert_eq!(result.rows_affected(), Some(1));
874
875 let select_stmt = session.prepare("SELECT * FROM users WHERE id = ?").unwrap();
877 let select_result =
878 session.execute_prepared(&select_stmt, &[SqlValue::Integer(1)]).unwrap();
879
880 assert_eq!(select_result.rows().unwrap().len(), 0);
881 }
882
883 #[test]
884 fn test_shared_cache() {
885 let db = create_test_db();
886
887 let session1 = Session::new(&db);
889 let stmt = session1.prepare("SELECT * FROM users WHERE id = ?").unwrap();
890
891 let shared_cache = session1.shared_cache();
893 let initial_misses = session1.cache().stats().misses;
894
895 let session2 = Session::with_shared_cache(&db, shared_cache);
897
898 let _stmt2 = session2.prepare("SELECT * FROM users WHERE id = ?").unwrap();
900
901 assert_eq!(session2.cache().stats().misses, initial_misses);
903
904 let result1 = session1.execute_prepared(&stmt, &[SqlValue::Integer(1)]).unwrap();
906 let result2 = session2.execute_prepared(&stmt, &[SqlValue::Integer(2)]).unwrap();
907
908 assert_eq!(
909 result1.rows().unwrap()[0].values[1],
910 SqlValue::Varchar(arcstr::ArcStr::from("Alice"))
911 );
912 assert_eq!(
913 result2.rows().unwrap()[0].values[1],
914 SqlValue::Varchar(arcstr::ArcStr::from("Bob"))
915 );
916 }
917
918 #[test]
919 fn test_no_params_statement() {
920 let db = create_test_db();
921 let session = Session::new(&db);
922
923 let stmt = session.prepare("SELECT * FROM users").unwrap();
924 assert_eq!(stmt.param_count(), 0);
925
926 let result = session.execute_prepared(&stmt, &[]).unwrap();
927 assert_eq!(result.rows().unwrap().len(), 3);
928 }
929
930 #[test]
931 fn test_multiple_placeholders() {
932 let db = create_test_db();
933 let session = Session::new(&db);
934
935 let stmt = session.prepare("SELECT * FROM users WHERE id >= ? AND id <= ?").unwrap();
936 assert_eq!(stmt.param_count(), 2);
937
938 let result =
939 session.execute_prepared(&stmt, &[SqlValue::Integer(1), SqlValue::Integer(2)]).unwrap();
940
941 assert_eq!(result.rows().unwrap().len(), 2);
942 }
943
944 #[test]
945 fn test_session_prepare_arena() {
946 let db = create_test_db();
947 let session = Session::new(&db);
948
949 let stmt = session.prepare_arena("SELECT * FROM users WHERE id = ?").unwrap();
951 assert_eq!(stmt.param_count(), 1);
952
953 assert!(stmt.tables().contains("users"));
955
956 let stmt2 = session.prepare_arena("SELECT * FROM users WHERE id = ?").unwrap();
958 assert_eq!(stmt2.param_count(), 1);
959
960 assert!(std::sync::Arc::ptr_eq(&stmt, &stmt2));
962 }
963
964 #[test]
965 fn test_session_prepare_arena_no_params() {
966 let db = create_test_db();
967 let session = Session::new(&db);
968
969 let stmt = session.prepare_arena("SELECT * FROM users").unwrap();
970 assert_eq!(stmt.param_count(), 0);
971 }
972
973 #[test]
974 fn test_session_prepare_arena_join() {
975 let db = create_test_db();
976 let session = Session::new(&db);
977
978 use vibesql_catalog::{ColumnSchema, TableSchema};
980 use vibesql_types::DataType;
981
982 let orders_columns = vec![
983 ColumnSchema::new("id".to_string(), DataType::Integer, false),
984 ColumnSchema::new("user_id".to_string(), DataType::Integer, false),
985 ];
986 let _orders_schema = TableSchema::with_primary_key(
987 "orders".to_string(),
988 orders_columns,
989 vec!["id".to_string()],
990 );
991
992 let stmt = session
995 .prepare_arena("SELECT u.id FROM users u JOIN orders o ON u.id = o.user_id")
996 .unwrap();
997
998 let tables = stmt.tables();
1000 assert!(tables.contains("users"), "Expected USERS in {:?}", tables);
1001 assert!(tables.contains("orders"), "Expected orders in {:?}", tables);
1002 }
1003
1004 #[test]
1005 fn test_session_mut_prepare_arena() {
1006 let mut db = create_test_db();
1007 let session = SessionMut::new(&mut db);
1008
1009 let stmt = session.prepare_arena("SELECT * FROM users WHERE id = ?").unwrap();
1011 assert_eq!(stmt.param_count(), 1);
1012 }
1013
1014 #[test]
1015 fn test_delete_fast_path_plan() {
1016 use crate::cache::CachedPlan;
1017
1018 let mut db = create_test_db();
1019 let mut session = SessionMut::new(&mut db);
1020
1021 let stmt = session.prepare("DELETE FROM users WHERE id = ?").unwrap();
1023
1024 match stmt.cached_plan() {
1026 CachedPlan::PkDelete(plan) => {
1027 assert_eq!(plan.table_name, "users");
1029 assert_eq!(plan.pk_columns, vec!["id"]);
1031 assert_eq!(plan.param_to_pk_col, vec![(0, 0)]);
1033 assert!(plan.is_fast_path_valid().is_none());
1035 }
1036 other => panic!("Expected PkDelete plan, got {:?}", other),
1037 }
1038
1039 let result = session.execute_prepared_mut(&stmt, &[SqlValue::Integer(1)]).unwrap();
1041 assert_eq!(result.rows_affected(), Some(1));
1042
1043 match stmt.cached_plan() {
1045 CachedPlan::PkDelete(plan) => {
1046 assert_eq!(
1047 plan.is_fast_path_valid(),
1048 Some(true),
1049 "Fast path should be valid after execution"
1050 );
1051 }
1052 _ => panic!("Plan should still be PkDelete"),
1053 }
1054
1055 let select_stmt = session.prepare("SELECT * FROM users WHERE id = ?").unwrap();
1057 let select_result =
1058 session.execute_prepared(&select_stmt, &[SqlValue::Integer(1)]).unwrap();
1059 assert_eq!(select_result.rows().unwrap().len(), 0);
1060 }
1061
1062 #[test]
1070 fn test_concurrent_sessions_coexist() {
1071 let db = create_test_db();
1072
1073 let session1 = Session::new(&db);
1075 let session2 = Session::new(&db);
1076 let session3 = Session::new(&db);
1077
1078 let stmt1 = session1.prepare("SELECT * FROM users WHERE id = ?").unwrap();
1080 let stmt2 = session2.prepare("SELECT * FROM users WHERE id = ?").unwrap();
1081 let stmt3 = session3.prepare("SELECT name FROM users WHERE id = ?").unwrap();
1082
1083 let result1 = session1.execute_prepared(&stmt1, &[SqlValue::Integer(1)]).unwrap();
1085 let result2 = session2.execute_prepared(&stmt2, &[SqlValue::Integer(2)]).unwrap();
1086 let result3 = session3.execute_prepared(&stmt3, &[SqlValue::Integer(3)]).unwrap();
1087
1088 assert_eq!(
1090 result1.rows().unwrap()[0].values[1],
1091 SqlValue::Varchar(arcstr::ArcStr::from("Alice"))
1092 );
1093 assert_eq!(
1094 result2.rows().unwrap()[0].values[1],
1095 SqlValue::Varchar(arcstr::ArcStr::from("Bob"))
1096 );
1097 assert_eq!(
1098 result3.rows().unwrap()[0].values[0],
1099 SqlValue::Varchar(arcstr::ArcStr::from("Charlie"))
1100 );
1101 }
1102
1103 #[test]
1104 fn test_concurrent_sessions_shared_cache() {
1105 let db = create_test_db();
1106
1107 let shared_cache = Arc::new(PreparedStatementCache::default_cache());
1109
1110 let session1 = Session::with_shared_cache(&db, Arc::clone(&shared_cache));
1112 let session2 = Session::with_shared_cache(&db, Arc::clone(&shared_cache));
1113
1114 let stmt = session1.prepare("SELECT * FROM users WHERE id = ?").unwrap();
1116 let stats_after_first = shared_cache.stats();
1117 assert_eq!(stats_after_first.misses, 1);
1118
1119 let _stmt2 = session2.prepare("SELECT * FROM users WHERE id = ?").unwrap();
1121 let stats_after_second = shared_cache.stats();
1122 assert_eq!(stats_after_second.misses, 1); assert!(stats_after_second.hits >= 1); let r1 = session1.execute_prepared(&stmt, &[SqlValue::Integer(1)]).unwrap();
1127 let r2 = session2.execute_prepared(&stmt, &[SqlValue::Integer(2)]).unwrap();
1128
1129 assert_eq!(r1.rows().unwrap().len(), 1);
1130 assert_eq!(r2.rows().unwrap().len(), 1);
1131 }
1132
1133 #[test]
1134 fn test_concurrent_sessions_different_queries() {
1135 let db = create_test_db();
1136
1137 let session1 = Session::new(&db);
1138 let session2 = Session::new(&db);
1139
1140 let point_query = session1.prepare("SELECT * FROM users WHERE id = ?").unwrap();
1142 let range_query =
1143 session2.prepare("SELECT * FROM users WHERE id >= ? AND id <= ?").unwrap();
1144 let all_query = session1.prepare("SELECT * FROM users").unwrap();
1145 let projection_query = session2.prepare("SELECT name FROM users WHERE id = ?").unwrap();
1146
1147 let r1 = session1.execute_prepared(&point_query, &[SqlValue::Integer(1)]).unwrap();
1149 let r2 = session2
1150 .execute_prepared(&range_query, &[SqlValue::Integer(1), SqlValue::Integer(2)])
1151 .unwrap();
1152 let r3 = session1.execute_prepared(&all_query, &[]).unwrap();
1153 let r4 = session2.execute_prepared(&projection_query, &[SqlValue::Integer(3)]).unwrap();
1154
1155 assert_eq!(r1.rows().unwrap().len(), 1);
1156 assert_eq!(r2.rows().unwrap().len(), 2);
1157 assert_eq!(r3.rows().unwrap().len(), 3);
1158 assert_eq!(r4.rows().unwrap().len(), 1);
1159 }
1160
1161 #[test]
1162 fn test_concurrent_sessions_interleaved_execution() {
1163 let db = create_test_db();
1164
1165 let session1 = Session::new(&db);
1166 let session2 = Session::new(&db);
1167
1168 let stmt = session1.prepare("SELECT * FROM users WHERE id = ?").unwrap();
1169
1170 let r1 = session1.execute_prepared(&stmt, &[SqlValue::Integer(1)]).unwrap();
1173 let r2 = session2.execute_prepared(&stmt, &[SqlValue::Integer(2)]).unwrap();
1174 let r3 = session1.execute_prepared(&stmt, &[SqlValue::Integer(3)]).unwrap();
1175 let r4 = session2.execute_prepared(&stmt, &[SqlValue::Integer(1)]).unwrap();
1176
1177 assert_eq!(r1.rows().unwrap()[0].values[0], SqlValue::Integer(1));
1179 assert_eq!(r2.rows().unwrap()[0].values[0], SqlValue::Integer(2));
1180 assert_eq!(r3.rows().unwrap()[0].values[0], SqlValue::Integer(3));
1181 assert_eq!(r4.rows().unwrap()[0].values[0], SqlValue::Integer(1));
1182 }
1183
1184 #[test]
1185 fn test_session_immutable_borrow_allows_multiple() {
1186 let db = create_test_db();
1187
1188 let session1 = Session::new(&db);
1191 let session2 = Session::new(&db);
1192
1193 let db_ref1 = session1.database();
1196 let db_ref2 = session2.database();
1197
1198 assert!(std::ptr::eq(db_ref1, db_ref2));
1200
1201 let stmt = session1.prepare("SELECT COUNT(*) FROM users").unwrap();
1203 let r1 = session1.execute_prepared(&stmt, &[]).unwrap();
1204 let r2 = session2.execute_prepared(&stmt, &[]).unwrap();
1205
1206 assert_eq!(r1.rows().unwrap().len(), 1);
1207 assert_eq!(r2.rows().unwrap().len(), 1);
1208 }
1209
1210 #[test]
1211 fn test_session_execute_uses_immutable_self() {
1212 let db = create_test_db();
1213 let session = Session::new(&db);
1214
1215 let stmt = session.prepare("SELECT * FROM users WHERE id = ?").unwrap();
1217
1218 let _ = session.execute_prepared(&stmt, &[SqlValue::Integer(1)]);
1221 let _ = session.execute_prepared(&stmt, &[SqlValue::Integer(2)]);
1222 let _ = session.execute_prepared(&stmt, &[SqlValue::Integer(3)]);
1223
1224 let cache = session.cache();
1226 let _stats = cache.stats();
1227 }
1229
1230 #[test]
1231 fn test_concurrent_sessions_with_aggregates() {
1232 let db = create_test_db();
1233
1234 let session1 = Session::new(&db);
1235 let session2 = Session::new(&db);
1236
1237 let count_stmt = session1.prepare("SELECT COUNT(*) FROM users").unwrap();
1239 let sum_stmt = session2.prepare("SELECT COUNT(*) FROM users WHERE id <= ?").unwrap();
1240
1241 let r1 = session1.execute_prepared(&count_stmt, &[]).unwrap();
1242 let r2 = session2.execute_prepared(&sum_stmt, &[SqlValue::Integer(2)]).unwrap();
1243
1244 assert_eq!(r1.rows().unwrap()[0].values[0], SqlValue::Integer(3));
1246 assert_eq!(r2.rows().unwrap()[0].values[0], SqlValue::Integer(2));
1247 }
1248
1249 #[test]
1250 fn test_concurrent_sessions_with_pk_fast_path() {
1251 let db = create_test_db();
1252
1253 let session1 = Session::new(&db);
1254 let session2 = Session::new(&db);
1255
1256 let stmt = session1.prepare("SELECT * FROM users WHERE id = ?").unwrap();
1258
1259 let r1 = session1.execute_prepared(&stmt, &[SqlValue::Integer(1)]).unwrap();
1261 let r2 = session2.execute_prepared(&stmt, &[SqlValue::Integer(2)]).unwrap();
1262
1263 assert_eq!(
1264 r1.rows().unwrap()[0].values[1],
1265 SqlValue::Varchar(arcstr::ArcStr::from("Alice"))
1266 );
1267 assert_eq!(r2.rows().unwrap()[0].values[1], SqlValue::Varchar(arcstr::ArcStr::from("Bob")));
1268 }
1269}