use std::fmt;
use std::hash::Hash;
use std::marker::PhantomData;
use crate::postgres::{
PostgresDDL, PostgresSnapshot, ddl::PostgresEntity, statements::PostgresGenerator,
};
use crate::sqlite::{SQLiteDDL, SQLiteSnapshot, ddl::SqliteEntity, statements::SqliteGenerator};
pub trait Version: Copy + Clone + Default + 'static {
const NUMBER: u32;
}
#[must_use]
pub fn version_str<V: Version>() -> String {
V::NUMBER.to_string()
}
#[derive(Copy, Clone, Default, Debug)]
pub struct V5;
impl Version for V5 {
const NUMBER: u32 = 5;
}
#[derive(Copy, Clone, Default, Debug)]
pub struct V6;
impl Version for V6 {
const NUMBER: u32 = 6;
}
#[derive(Copy, Clone, Default, Debug)]
pub struct V7;
impl Version for V7 {
const NUMBER: u32 = 7;
}
#[derive(Copy, Clone, Default, Debug)]
pub struct V8;
impl Version for V8 {
const NUMBER: u32 = 8;
}
pub type SqliteLatest = V7;
pub type PostgresLatest = V8;
pub type MysqlLatest = V5;
pub trait Upgradable<From: Version, To: Version> {
type Output;
type Error;
fn upgrade(self) -> Result<Self::Output, Self::Error>;
}
#[inline]
pub const fn assert_can_upgrade<D, From, To>()
where
D: CanUpgrade<From, To>,
From: Version,
To: Version,
{
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum EntityKind {
Schema = 0,
Enum = 1,
Sequence = 2,
Role = 3,
Table = 10,
Column = 11,
Index = 12,
ForeignKey = 13,
PrimaryKey = 14,
UniqueConstraint = 15,
CheckConstraint = 16,
Policy = 20,
View = 21,
}
impl EntityKind {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Schema => "schemas",
Self::Enum => "enums",
Self::Sequence => "sequences",
Self::Role => "roles",
Self::Table => "tables",
Self::Column => "columns",
Self::Index => "indexes",
Self::ForeignKey => "fks",
Self::PrimaryKey => "pks",
Self::UniqueConstraint => "uniques",
Self::CheckConstraint => "checks",
Self::Policy => "policies",
Self::View => "views",
}
}
#[must_use]
pub fn parse(s: &str) -> Option<Self> {
match s {
"schemas" => Some(Self::Schema),
"enums" => Some(Self::Enum),
"sequences" => Some(Self::Sequence),
"roles" => Some(Self::Role),
"tables" => Some(Self::Table),
"columns" => Some(Self::Column),
"indexes" => Some(Self::Index),
"fks" => Some(Self::ForeignKey),
"pks" => Some(Self::PrimaryKey),
"uniques" => Some(Self::UniqueConstraint),
"checks" => Some(Self::CheckConstraint),
"policies" => Some(Self::Policy),
"views" => Some(Self::View),
_ => None,
}
}
}
impl std::str::FromStr for EntityKind {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::parse(s).ok_or(())
}
}
impl fmt::Display for EntityKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum EntityKey {
Simple(String),
Composite2(String, String),
Composite3(String, String, String),
}
impl EntityKey {
pub fn simple(name: impl Into<String>) -> Self {
Self::Simple(name.into())
}
pub fn composite2(a: impl Into<String>, b: impl Into<String>) -> Self {
Self::Composite2(a.into(), b.into())
}
pub fn composite3(a: impl Into<String>, b: impl Into<String>, c: impl Into<String>) -> Self {
Self::Composite3(a.into(), b.into(), c.into())
}
}
pub trait Entity: Clone + PartialEq {
const KIND: EntityKind;
fn key(&self) -> EntityKey;
fn parent_key(&self) -> Option<EntityKey> {
None
}
}
#[derive(Clone, Debug)]
pub struct Versioned<Data, V: Version> {
pub data: Data,
_version: PhantomData<V>,
}
impl<Data, V: Version> Versioned<Data, V> {
pub const fn new(data: Data) -> Self {
Self {
data,
_version: PhantomData,
}
}
#[must_use]
pub const fn version() -> u32 {
V::NUMBER
}
#[must_use]
pub fn version_str() -> String {
version_str::<V>()
}
pub fn into_inner(self) -> Data {
self.data
}
}
pub trait Dialect: Sized + 'static {
const NAME: &'static str;
type MinVersion: Version;
type LatestVersion: Version;
type Snapshot: Clone + Default + std::fmt::Debug;
type DDL: Clone + Default + std::fmt::Debug;
type Entity: Clone + std::fmt::Debug + PartialEq;
type Generator: Default;
#[inline]
#[must_use]
fn is_supported_version(version: u32) -> bool {
version >= Self::MinVersion::NUMBER && version <= Self::LatestVersion::NUMBER
}
#[inline]
#[must_use]
fn is_latest_version(version: u32) -> bool {
version == Self::LatestVersion::NUMBER
}
#[inline]
#[must_use]
fn needs_upgrade_from(version: u32) -> bool {
version < Self::LatestVersion::NUMBER && version >= Self::MinVersion::NUMBER
}
fn diff_and_generate(
prev: &Self::Snapshot,
cur: &Self::Snapshot,
breakpoints: bool,
) -> MigrationResult;
}
pub trait CanUpgrade<From: Version, To: Version>: Dialect {}
#[derive(Debug, Clone)]
pub struct MigrationResult {
pub sql_statements: Vec<String>,
pub has_changes: bool,
}
impl MigrationResult {
#[must_use]
pub const fn empty() -> Self {
Self {
sql_statements: Vec::new(),
has_changes: false,
}
}
#[must_use]
pub const fn with_changes(sql_statements: Vec<String>) -> Self {
let has_changes = !sql_statements.is_empty();
Self {
sql_statements,
has_changes,
}
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct Sqlite;
impl Sqlite {
pub const MIN_VERSION: u32 = V5::NUMBER;
pub const LATEST_VERSION: u32 = V7::NUMBER;
}
impl Dialect for Sqlite {
const NAME: &'static str = "sqlite";
type MinVersion = V5;
type LatestVersion = V7;
type Snapshot = SQLiteSnapshot;
type DDL = SQLiteDDL;
type Entity = SqliteEntity;
type Generator = SqliteGenerator;
fn diff_and_generate(
prev: &Self::Snapshot,
cur: &Self::Snapshot,
breakpoints: bool,
) -> MigrationResult {
let diff = crate::sqlite::diff_snapshots(prev, cur);
if !diff.has_changes() {
return MigrationResult::empty();
}
let generator = SqliteGenerator::new().with_breakpoints(breakpoints);
let sql = generator.generate_migration(&diff);
MigrationResult::with_changes(sql)
}
}
impl CanUpgrade<V5, V6> for Sqlite {}
impl CanUpgrade<V6, V7> for Sqlite {}
impl CanUpgrade<V5, V7> for Sqlite {}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct Postgres;
impl Postgres {
pub const MIN_VERSION: u32 = V5::NUMBER;
pub const LATEST_VERSION: u32 = V8::NUMBER;
}
impl Dialect for Postgres {
const NAME: &'static str = "postgresql";
type MinVersion = V5;
type LatestVersion = V8;
type Snapshot = PostgresSnapshot;
type DDL = PostgresDDL;
type Entity = PostgresEntity;
type Generator = PostgresGenerator;
fn diff_and_generate(
prev: &Self::Snapshot,
cur: &Self::Snapshot,
breakpoints: bool,
) -> MigrationResult {
let diff = crate::postgres::diff_snapshots(&prev.ddl, &cur.ddl);
if !diff.has_changes() {
return MigrationResult::empty();
}
let generator = PostgresGenerator::new().with_breakpoints(breakpoints);
let sql = generator.generate(&diff.diffs);
MigrationResult::with_changes(sql)
}
}
impl CanUpgrade<V5, V6> for Postgres {}
impl CanUpgrade<V6, V7> for Postgres {}
impl CanUpgrade<V7, V8> for Postgres {}
impl CanUpgrade<V5, V7> for Postgres {}
impl CanUpgrade<V5, V8> for Postgres {}
impl CanUpgrade<V6, V8> for Postgres {}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct Mysql;
impl Mysql {
pub const MIN_VERSION: u32 = V5::NUMBER;
pub const LATEST_VERSION: u32 = V5::NUMBER;
}
impl Dialect for Mysql {
const NAME: &'static str = "mysql";
type MinVersion = V5;
type LatestVersion = V5;
type Snapshot = ();
type DDL = ();
type Entity = ();
type Generator = ();
fn diff_and_generate(
_prev: &Self::Snapshot,
_cur: &Self::Snapshot,
_breakpoints: bool,
) -> MigrationResult {
unimplemented!("MySQL migrations not yet supported")
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum DiffType {
Create,
Drop,
Alter,
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use super::*;
#[test]
fn test_version_numbers() {
assert_eq!(V5::NUMBER, 5);
assert_eq!(V6::NUMBER, 6);
assert_eq!(V7::NUMBER, 7);
assert_eq!(V8::NUMBER, 8);
}
#[test]
fn test_version_str() {
assert_eq!(version_str::<V5>(), "5");
assert_eq!(version_str::<V7>(), "7");
}
#[test]
fn test_entity_kind_str() {
assert_eq!(EntityKind::Table.as_str(), "tables");
assert_eq!(EntityKind::Column.as_str(), "columns");
assert_eq!(EntityKind::ForeignKey.as_str(), "fks");
}
#[test]
fn test_entity_kind_parse() {
assert_eq!(EntityKind::from_str("tables"), Ok(EntityKind::Table));
assert_eq!(EntityKind::from_str("columns"), Ok(EntityKind::Column));
assert_eq!(EntityKind::from_str("invalid"), Err(()));
}
#[test]
fn test_versioned_snapshot() {
#[derive(Clone, Debug)]
struct TestData {
value: i32,
}
let versioned: Versioned<TestData, V7> = Versioned::new(TestData { value: 42 });
assert_eq!(Versioned::<TestData, V7>::version(), 7);
assert_eq!(versioned.data.value, 42);
}
#[test]
fn test_dialect_version_info() {
assert_eq!(Sqlite::MIN_VERSION, 5);
assert_eq!(Sqlite::LATEST_VERSION, 7);
assert_eq!(Postgres::MIN_VERSION, 5);
assert_eq!(Postgres::LATEST_VERSION, 8);
assert_eq!(Mysql::MIN_VERSION, 5);
assert_eq!(Mysql::LATEST_VERSION, 5);
}
#[test]
fn test_dialect_version_checks() {
assert!(Sqlite::is_supported_version(5));
assert!(Sqlite::is_supported_version(6));
assert!(Sqlite::is_supported_version(7));
assert!(!Sqlite::is_supported_version(4));
assert!(!Sqlite::is_supported_version(8));
assert!(Sqlite::needs_upgrade_from(5));
assert!(Sqlite::needs_upgrade_from(6));
assert!(!Sqlite::needs_upgrade_from(7));
assert!(!Sqlite::is_latest_version(5));
assert!(Sqlite::is_latest_version(7));
}
#[test]
fn test_can_upgrade_compiles() {
assert_can_upgrade::<Sqlite, V5, V6>();
assert_can_upgrade::<Sqlite, V6, V7>();
assert_can_upgrade::<Sqlite, V5, V7>();
assert_can_upgrade::<Postgres, V5, V6>();
assert_can_upgrade::<Postgres, V6, V7>();
assert_can_upgrade::<Postgres, V7, V8>();
assert_can_upgrade::<Postgres, V5, V8>(); }
}