Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add SurrealDB support #295

Closed
wants to merge 3 commits into from
Closed
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
feat: add initial SurrealDB support
  • Loading branch information
TECHNOFAB11 committed Dec 2, 2023
commit c12f1a7202670b95d780cc597b961586a638dd73
3 changes: 3 additions & 0 deletions refinery/Cargo.toml
Original file line number Diff line number Diff line change
@@ -22,10 +22,13 @@ tokio-postgres = ["refinery-core/tokio-postgres"]
mysql_async = ["refinery-core/mysql_async"]
tiberius = ["refinery-core/tiberius"]
tiberius-config = ["refinery-core/tiberius", "refinery-core/tiberius-config"]
surrealdb = ["refinery-core/surrealdb"]

[dependencies]
refinery-core = { version = "0.8.11", path = "../refinery_core" }
refinery-macros = { version = "0.8.11", path = "../refinery_macros" }
serde = { version = "1.0.193", features = ["derive"] }
serde_json = "1.0.108"

[dev-dependencies]
barrel = { git = "https://github.com/jxs/barrel", features = ["sqlite3", "pg", "mysql", "mssql"] }
8 changes: 8 additions & 0 deletions refinery/tests/migrations_surreal/V1__initial.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
pub fn migration() -> String {
r##"
DEFINE TABLE user SCHEMAFULL;

DEFINE FIELD first_name ON TABLE user TYPE string;
DEFINE FIELD last_name ON TABLE user TYPE string;
"##.to_string()
}
1 change: 1 addition & 0 deletions refinery/tests/migrations_surreal/V2__add_email.surql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
DEFINE FIELD email ON TABLE user TYPE string;
197 changes: 197 additions & 0 deletions refinery/tests/surreal.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
// needed to be able to embed the other migrations, the surrealdb migrations don't need that
use barrel::backend::Pg as Sql;

#[cfg(feature = "surrealdb")]
mod surreal {
use refinery_core::surrealdb::engine::local::{Db, Mem};
use refinery_core::Migration;
use refinery_core::{surrealdb, AsyncMigrate};
use refinery_macros::embed_migrations;
use serde::{Deserialize, Serialize};
use serde_json;
use time::OffsetDateTime;

const DEFAULT_TABLE_NAME: &str = "refinery_schema_history";

fn get_migrations() -> Vec<Migration> {
embed_migrations!("./tests/migrations_surreal");

let migration1 =
Migration::unapplied("V1__initial.rs", &migrations::V1__initial::migration()).unwrap();

let migration2 = Migration::unapplied(
"V2__add_email.surql",
include_str!("./migrations_surreal/V2__add_email.surql"),
)
.unwrap();

let migration3 = Migration::unapplied("V3__add_cars_table", "DEFINE TABLE cars;").unwrap();

vec![migration1, migration2, migration3]
}

async fn get_db() -> surrealdb::Surreal<Db> {
let db = surrealdb::Surreal::new::<Mem>(()).await.unwrap();
db.use_ns("refinery_test")
.use_db("refinery_test")
.await
.unwrap();

db
}

mod embedded {
use refinery::embed_migrations;
embed_migrations!("./tests/migrations_surreal");
}

#[tokio::test]
async fn report_contains_applied_migrations() {
let mut db = get_db().await;

let report = embedded::migrations::runner()
.run_async(&mut db)
.await
.unwrap();

let migrations = get_migrations();
let applied_migrations = report.applied_migrations();

assert_eq!(2, applied_migrations.len());

assert_eq!(migrations[0].version(), applied_migrations[0].version());
assert_eq!(migrations[1].version(), applied_migrations[1].version());

assert_eq!(migrations[0].name(), applied_migrations[0].name());
assert_eq!(migrations[1].name(), applied_migrations[1].name());

assert_eq!(migrations[0].checksum(), applied_migrations[0].checksum());
assert_eq!(migrations[1].checksum(), applied_migrations[1].checksum());
}

#[tokio::test]
async fn creates_migration_table() {
let mut db = get_db().await;

embedded::migrations::runner()
.run_async(&mut db)
.await
.unwrap();

#[derive(Deserialize)]
struct InfoResult {
fields: serde_json::Value,
}

let result: Option<InfoResult> = db
.query(format!("INFO FOR TABLE {};", DEFAULT_TABLE_NAME))
.await
.unwrap()
.take(0)
.unwrap();

// assert that 4 fields exist: applied_on, checksum, name, version
assert_eq!(4, result.unwrap().fields.as_object().unwrap().len());
}

#[tokio::test]
async fn creates_migration_table_grouped_migrations() {
let mut db = get_db().await;

embedded::migrations::runner()
.set_grouped(true)
.run_async(&mut db)
.await
.unwrap();

#[derive(Deserialize)]
struct InfoResult {
fields: serde_json::Value,
}

let result: Option<InfoResult> = db
.query(format!("INFO FOR TABLE {};", DEFAULT_TABLE_NAME))
.await
.unwrap()
.take(0)
.unwrap();

// assert that 4 fields exist: applied_on, checksum, name, version
assert_eq!(4, result.unwrap().fields.as_object().unwrap().len());
}

#[tokio::test]
async fn applies_migration_grouped() {
let mut db = get_db().await;

embedded::migrations::runner()
.set_grouped(true)
.run_async(&mut db)
.await
.unwrap();

#[derive(Debug, Serialize, Deserialize)]
struct User {
first_name: String,
last_name: String,
email: String,
}

let result: Option<User> = db
.create(("user", "john"))
.content(User {
first_name: "John".into(),
last_name: "Doe".into(),
email: "john@example.com".into(),
})
.await
.unwrap();

assert_eq!(result.unwrap().email, "john@example.com");
}

#[tokio::test]
async fn updates_schema_history() {
let mut db = get_db().await;

embedded::migrations::runner()
.run_async(&mut db)
.await
.unwrap();

let current = db
.get_last_applied_migration(DEFAULT_TABLE_NAME)
.await
.unwrap()
.unwrap();

assert_eq!(2, current.version());
assert_eq!(
OffsetDateTime::now_utc().date(),
current.applied_on().unwrap().date()
);
}

#[tokio::test]
async fn updates_schema_history_grouped() {
let mut db = get_db().await;

embedded::migrations::runner()
.set_grouped(true)
.run_async(&mut db)
.await
.unwrap();

let current = db
.get_last_applied_migration(DEFAULT_TABLE_NAME)
.await
.unwrap()
.unwrap();

assert_eq!(2, current.version());
assert_eq!(
OffsetDateTime::now_utc().date(),
current.applied_on().unwrap().date()
);
}
}
6 changes: 3 additions & 3 deletions refinery_cli/src/migrate.rs
Original file line number Diff line number Diff line change
@@ -60,10 +60,10 @@ fn run_migrations(
};

match config.db_type() {
ConfigDbType::Mssql => {
_db_type @ (ConfigDbType::Mssql | ConfigDbType::Surreal) => {
cfg_if::cfg_if! {
// tiberius is an async driver so we spawn tokio runtime and run the migrations
if #[cfg(feature = "mssql")] {
if #[cfg(any(feature = "mssql", feature = "surrealdb"))] {
use tokio::runtime::Builder;

let runtime = Builder::new_current_thread()
@@ -82,7 +82,7 @@ fn run_migrations(
.await
})?;
} else {
panic!("tried to migrate async from config for a mssql database, but mssql feature was not enabled!");
panic!("tried to migrate async from config for a {:?} database, but it's matching feature was not enabled!", _db_type);
}
}
}
20 changes: 19 additions & 1 deletion refinery_cli/src/setup.rs
Original file line number Diff line number Diff line change
@@ -29,7 +29,7 @@ pub fn handle_setup() -> Result<()> {
}

fn get_config_from_input() -> Result<Config> {
println!("Select database 1) Mysql 2) Postgresql 3) Sqlite 4) Mssql: ");
println!("Select database 1) Mysql 2) Postgresql 3) Sqlite 4) Mssql 5) SurrealDB: ");
print!("Enter a number: ");
io::stdout().flush()?;

@@ -40,6 +40,7 @@ fn get_config_from_input() -> Result<Config> {
"2" => ConfigDbType::Postgres,
"3" => ConfigDbType::Sqlite,
"4" => ConfigDbType::Mssql,
"5" => ConfigDbType::Surreal,
_ => return Err(anyhow!("invalid option")),
};
let mut config = Config::new(db_type);
@@ -89,6 +90,23 @@ fn get_config_from_input() -> Result<Config> {
db_pass.pop();
config = config.set_db_pass(&db_pass);

if config.db_type() == ConfigDbType::Surreal {
cfg_if::cfg_if! {
if #[cfg(feature = "surrealdb")] {
print!("Enter namespace: ");
io::stdout().flush()?;
let mut db_namespace = String::new();
io::stdin().read_line(&mut db_namespace)?;
//remove \n
db_namespace.pop();
config = config.set_db_namespace(db_namespace.trim());
return Ok(config);
} else {
panic!("tried to migrate async from config for a SurrealDB database, but surrealdb feature was not enabled!");
}
}
}

print!("Enter database name: ");
io::stdout().flush()?;
let mut db_name = String::new();
2 changes: 2 additions & 0 deletions refinery_core/Cargo.toml
Original file line number Diff line number Diff line change
@@ -15,6 +15,7 @@ tiberius = ["dep:tiberius", "futures", "tokio", "tokio/net"]
tiberius-config = ["tiberius", "tokio", "tokio-util"]
tokio-postgres = ["dep:tokio-postgres", "tokio", "tokio/rt"]
mysql_async = ["dep:mysql_async"]
surrealdb = ["dep:surrealdb", "surrealdb/kv-mem", "surrealdb/protocol-ws"]

[dependencies]
async-trait = "0.1"
@@ -36,6 +37,7 @@ tokio-postgres = { version = ">= 0.5, <= 0.7", optional = true }
mysql = { version = ">= 21.0.0, <= 24", optional = true, default-features = false, features = ["minimal"] }
mysql_async = { version = ">= 0.28, <= 0.33", optional = true, default-features = false, features = ["minimal"] }
tiberius = { version = ">= 0.7, <= 0.12", optional = true, default-features = false }
surrealdb = { version = "1.0.0", optional = true }
tokio = { version = "1.0", optional = true }
futures = { version = "0.3.16", optional = true, features = ["async-await"] }
tokio-util = { version = "0.7.7", features = ["compat"], optional = true }
36 changes: 36 additions & 0 deletions refinery_core/src/config.rs
Original file line number Diff line number Diff line change
@@ -20,6 +20,7 @@ pub enum ConfigDbType {
Postgres,
Sqlite,
Mssql,
Surreal,
}

impl Config {
@@ -34,6 +35,8 @@ impl Config {
db_user: None,
db_pass: None,
db_name: None,
#[cfg(feature = "surrealdb")]
db_namespace: None,
#[cfg(feature = "tiberius-config")]
trust_cert: false,
},
@@ -126,6 +129,35 @@ impl Config {
}
}

cfg_if::cfg_if! {
if #[cfg(feature = "surrealdb")] {
pub(crate) fn db_namespace(&self) -> Option<&str> {
self.main.db_namespace.as_deref()
}

pub(crate) fn db_name(&self) -> Option<&str> {
self.main.db_name.as_deref()
}

pub(crate) fn db_user(&self) -> Option<&str> {
self.main.db_user.as_deref()
}

pub(crate) fn db_pass(&self) -> Option<&str> {
self.main.db_pass.as_deref()
}

pub fn set_db_namespace(self, db_namespace: &str) -> Config {
Config {
main: Main {
db_namespace: Some(db_namespace.into()),
..self.main
},
}
}
}
}

pub fn db_type(&self) -> ConfigDbType {
self.main.db_type
}
@@ -237,6 +269,8 @@ impl TryFrom<Url> for Config {
db_user: Some(url.username().to_string()),
db_pass: url.password().map(|r| r.to_string()),
db_name: Some(url.path().trim_start_matches('/').to_string()),
#[cfg(feature = "surrealdb")]
db_namespace: None,
#[cfg(feature = "tiberius-config")]
trust_cert,
},
@@ -268,6 +302,8 @@ struct Main {
db_user: Option<String>,
db_pass: Option<String>,
db_name: Option<String>,
#[cfg(feature = "surrealdb")]
db_namespace: Option<String>,
#[cfg(feature = "tiberius-config")]
#[serde(default)]
trust_cert: bool,
Loading
Loading