toolu-orm-core 0.1.2

Schema definitions, migrations and driver traits for toolu-orm
Documentation
use std::collections::BTreeMap;

use toolu_orm_core::column::{ColumnDef, ColumnType, ForeignKeyAction};
use toolu_orm_core::diff::{diff_foreign_keys, Operation};
use toolu_orm_core::snapshot::{ForeignKeyDef, Snapshot, SnapshotMeta, SnapshotTable};

use super::col;

fn snapshot_with_fks(
  table_name: &str,
  columns: &[ColumnDef],
  fks: BTreeMap<String, ForeignKeyDef>,
) -> Snapshot {
  let mut cols_map = BTreeMap::new();
  for c in columns {
    cols_map.insert(c.name.clone(), c.clone());
  }
  let column_order: Vec<String> = columns.iter().map(|c| c.name.clone()).collect();
  let mut tables = BTreeMap::new();
  tables.insert(
    table_name.to_owned(),
    SnapshotTable {
      column_order,
      columns: cols_map,
      indexes: BTreeMap::new(),
      foreign_keys: fks,
      check_constraints: BTreeMap::new(),
      strict: false,
    },
  );
  Snapshot {
    version: 1,
    dialect: "postgres".to_owned(),
    id: "test".to_owned(),
    prev_id: "prev".to_owned(),
    tables,
    enums: BTreeMap::new(),
    meta: SnapshotMeta::default(),
  }
}

#[test]
fn diff_add_foreign_key() {
  let columns = vec![
    col("id", ColumnType::Text, true, true),
    col("author_id", ColumnType::Text, false, true),
  ];

  let old = snapshot_with_fks("posts", &columns, BTreeMap::new());

  let mut new_fks = BTreeMap::new();
  new_fks.insert(
    "fk_posts_author".to_owned(),
    ForeignKeyDef {
      name: "fk_posts_author".to_owned(),
      columns: vec!["author_id".to_owned()],
      references_table: "users".to_owned(),
      references_columns: vec!["id".to_owned()],
      on_delete: Some(ForeignKeyAction::Cascade),
      on_update: None,
    },
  );

  let new = snapshot_with_fks("posts", &columns, new_fks);
  let ops = diff_foreign_keys("posts", &old, &new);

  match ops.as_slice() {
    [Operation::AddForeignKey { table, fk }] => {
      assert_eq!(table, "posts");
      assert_eq!(fk.name, "fk_posts_author");
    },
    _ => assert_eq!(ops.len(), 1, "expected exactly one AddForeignKey operation"),
  }
}

#[test]
fn diff_drop_foreign_key() {
  let columns = vec![
    col("id", ColumnType::Text, true, true),
    col("author_id", ColumnType::Text, false, true),
  ];

  let mut old_fks = BTreeMap::new();
  old_fks.insert(
    "fk_posts_author".to_owned(),
    ForeignKeyDef {
      name: "fk_posts_author".to_owned(),
      columns: vec!["author_id".to_owned()],
      references_table: "users".to_owned(),
      references_columns: vec!["id".to_owned()],
      on_delete: Some(ForeignKeyAction::Cascade),
      on_update: None,
    },
  );

  let old = snapshot_with_fks("posts", &columns, old_fks);
  let new = snapshot_with_fks("posts", &columns, BTreeMap::new());
  let ops = diff_foreign_keys("posts", &old, &new);

  match ops.as_slice() {
    [Operation::DropForeignKey { table, name }] => {
      assert_eq!(table, "posts");
      assert_eq!(name, "fk_posts_author");
    },
    _ => assert_eq!(
      ops.len(),
      1,
      "expected exactly one DropForeignKey operation"
    ),
  }
}

#[test]
fn diff_foreign_key_modified_emits_drop_and_add() {
  let columns = vec![
    col("id", ColumnType::Text, true, true),
    col("author_id", ColumnType::Text, false, true),
  ];

  let mut old_fks = BTreeMap::new();
  old_fks.insert(
    "fk_posts_author".to_owned(),
    ForeignKeyDef {
      name: "fk_posts_author".to_owned(),
      columns: vec!["author_id".to_owned()],
      references_table: "users".to_owned(),
      references_columns: vec!["id".to_owned()],
      on_delete: Some(ForeignKeyAction::SetNull),
      on_update: None,
    },
  );

  let mut new_fks = BTreeMap::new();
  new_fks.insert(
    "fk_posts_author".to_owned(),
    ForeignKeyDef {
      name: "fk_posts_author".to_owned(),
      columns: vec!["author_id".to_owned()],
      references_table: "users".to_owned(),
      references_columns: vec!["id".to_owned()],
      on_delete: Some(ForeignKeyAction::Cascade),
      on_update: None,
    },
  );

  let old = snapshot_with_fks("posts", &columns, old_fks);
  let new = snapshot_with_fks("posts", &columns, new_fks);
  let ops = diff_foreign_keys("posts", &old, &new);

  assert_eq!(ops.len(), 2);
  assert!(ops
    .iter()
    .any(|op| matches!(op, Operation::DropForeignKey { .. })));
  assert!(ops
    .iter()
    .any(|op| matches!(op, Operation::AddForeignKey { .. })));
}

#[test]
fn diff_foreign_key_unchanged() {
  let columns = vec![
    col("id", ColumnType::Text, true, true),
    col("author_id", ColumnType::Text, false, true),
  ];

  let mut fks = BTreeMap::new();
  fks.insert(
    "fk_posts_author".to_owned(),
    ForeignKeyDef {
      name: "fk_posts_author".to_owned(),
      columns: vec!["author_id".to_owned()],
      references_table: "users".to_owned(),
      references_columns: vec!["id".to_owned()],
      on_delete: Some(ForeignKeyAction::Cascade),
      on_update: None,
    },
  );

  let old = snapshot_with_fks("posts", &columns, fks.clone());
  let new = snapshot_with_fks("posts", &columns, fks);
  let ops = diff_foreign_keys("posts", &old, &new);

  assert!(ops.is_empty());
}