diff --git a/src/branch/current.rs b/src/branch/current.rs index c33a4ee39..4b5f45bd3 100644 --- a/src/branch/current.rs +++ b/src/branch/current.rs @@ -1,7 +1,7 @@ use crossterm::style::Stylize; use crate::branch::context::Context; use crate::branch::option::Current; -use crate::connect::Connection; + pub async fn main( options: &Current, diff --git a/src/branch/main.rs b/src/branch/main.rs index 09b1d2ae2..b2a384bff 100644 --- a/src/branch/main.rs +++ b/src/branch/main.rs @@ -1,3 +1,4 @@ + use crate::branch::context::Context; use crate::branch::option::{BranchCommand, Command}; use crate::branch::{create, current, drop, list, merge, rebase, rename, switch, wipe}; @@ -10,31 +11,43 @@ use edgedb_tokio::get_project_dir; pub async fn branch_main(options: &Options, cmd: &BranchCommand) -> anyhow::Result<()> { let context = create_context().await?; + run_branch_command(&cmd.subcommand, options, &context, None).await +} + +pub async fn run_branch_command(cmd: &Command, options: &Options, context: &Context, connection: Option<&mut Connection>) -> anyhow::Result<()> { let mut connector: Connector = options.conn_params.clone(); - // match commands that don't require a connection to run, then match the ones that do with a connection. - match &cmd.subcommand { + match &cmd { Command::Switch(switch) => switch::main(switch, &context, &mut connector).await, Command::Wipe(wipe) => wipe::main(wipe, &context, &mut connector).await, Command::Current(current) => current::main(current, &context).await, command => { - let mut connection = connector.connect().await?; - verify_server_can_use_branches(&mut connection).await?; - - match command { - Command::Create(create) => create::main(create, &context, &mut connection).await, - Command::Drop(drop) => drop::main(drop, &context, &mut connection).await, - Command::List(list) => list::main(list, &context, &mut connection).await, - Command::Rename(rename) => rename::main(rename, &context, &mut connection, &options).await, - Command::Rebase(rebase) => rebase::main(rebase, &context, &mut connection, &options).await, - Command::Merge(merge) => merge::main(merge, &context, &mut connection, &options).await, - unhandled => anyhow::bail!("unimplemented branch command '{:?}'", unhandled) + match connection { + Some(conn) => run_branch_command1(command, conn, context, options).await, + None => { + let mut conn = connector.connect().await?; + run_branch_command1(command, &mut conn, context, options).await + } } } } } -async fn create_context() -> anyhow::Result { +async fn run_branch_command1(command: &Command, connection: &mut Connection, context: &Context, options: &Options) -> anyhow::Result<()> { + verify_server_can_use_branches(connection).await?; + + match command { + Command::Create(create) => create::main(create, &context, connection).await, + Command::Drop(drop) => drop::main(drop, &context, connection).await, + Command::List(list) => list::main(list, &context, connection).await, + Command::Rename(rename) => rename::main(rename, &context, connection, &options).await, + Command::Rebase(rebase) => rebase::main(rebase, &context, connection, &options).await, + Command::Merge(merge) => merge::main(merge, &context, connection, &options).await, + unhandled => anyhow::bail!("unimplemented branch command '{:?}'", unhandled) + } +} + +pub async fn create_context() -> anyhow::Result { let project_dir = get_project_dir(None, true).await?; Context::new(project_dir.as_ref()).await } diff --git a/src/branch/mod.rs b/src/branch/mod.rs index 506ca0975..17bdc9e8b 100644 --- a/src/branch/mod.rs +++ b/src/branch/mod.rs @@ -1,4 +1,4 @@ -mod context; +pub mod context; mod create; mod drop; mod list; diff --git a/src/branch/option.rs b/src/branch/option.rs index 901f847a5..dc5e57b6b 100644 --- a/src/branch/option.rs +++ b/src/branch/option.rs @@ -1,5 +1,12 @@ + +use crate::commands::parser::BranchingCmd; +use crate::options::ConnectionOptions; + #[derive(clap::Args, Debug, Clone)] pub struct BranchCommand { + #[command(flatten)] + pub conn: ConnectionOptions, + #[command(subcommand)] pub subcommand: Command, } @@ -17,6 +24,16 @@ pub enum Command { Current(Current) } +impl From<&BranchingCmd> for Command { + fn from(cmd: &BranchingCmd) -> Self { + match cmd { + BranchingCmd::Create(args) => Command::Create(args.clone()), + BranchingCmd::Drop(args) => Command::Drop(args.clone()), + BranchingCmd::Wipe(args) => Command::Wipe(args.clone()), + } + } +} + /// Creates a new branch and switches to it. #[derive(clap::Args, Debug, Clone)] pub struct Create { diff --git a/src/commands/backslash.rs b/src/commands/backslash.rs index 145947e62..618b3ee84 100644 --- a/src/commands/backslash.rs +++ b/src/commands/backslash.rs @@ -41,7 +41,7 @@ Introspection \d [-v] NAME Describe schema object \ds Describe whole schema (alias: \describe schema) - \l List databases (alias: \list databases) + \l List databases/branches (alias: \list branches) \ls [-sc] [PATTERN] List scalar types (alias: \list scalars) \lt [-sc] [PATTERN] List object types (alias: \list types) \lr [-c] [PATTERN] List roles (alias: \list roles) @@ -63,7 +63,7 @@ Editing Defaults to vi (Notepad in Windows). Connection - \c, \connect [DBNAME] Connect to database DBNAME + \c, \connect [DBNAME] Connect to database/branch DBNAME Settings \set [OPTION [VALUE]] Show/change settings. Type \set to list @@ -306,7 +306,7 @@ impl CommandCache { let mut aliases = BTreeMap::new(); aliases.insert("d", &["describe", "object"][..]); aliases.insert("ds", &["describe", "schema"]); - aliases.insert("l", &["list", "databases"]); + aliases.insert("l", &["list", "branches"]); aliases.insert("ls", &["list", "scalars"]); aliases.insert("lt", &["list", "types"]); aliases.insert("lr", &["list", "roles"]); @@ -322,6 +322,7 @@ impl CommandCache { aliases.insert("quit", &["exit"]); aliases.insert("?", &["help"]); aliases.insert("h", &["help"]); + aliases.insert("branch", &["branching"]); let mut setting_cmd = None; let commands: BTreeMap<_,_> = clap.get_subcommands_mut() .map(|cmd| { diff --git a/src/commands/branching.rs b/src/commands/branching.rs new file mode 100644 index 000000000..6de3cd8bf --- /dev/null +++ b/src/commands/branching.rs @@ -0,0 +1,10 @@ +use crate::branch; +use crate::commands::parser::BranchingCmd; +use crate::connect::Connection; +use crate::commands::Options; + +pub async fn main(connection: &mut Connection, cmd: &BranchingCmd, options: &Options) -> anyhow::Result<()> { + let context = branch::main::create_context().await?; + + branch::main::run_branch_command(&cmd.into(), options, &context, Some(connection)).await +} \ No newline at end of file diff --git a/src/commands/execute.rs b/src/commands/execute.rs index ff49bcdc8..57dfe8541 100644 --- a/src/commands/execute.rs +++ b/src/commands/execute.rs @@ -1,9 +1,9 @@ use crate::connect::Connection; use edgedb_tokio::server_params::PostgresAddress; -use crate::analyze; +use crate::{analyze, options}; use crate::commands::parser::{Common, DatabaseCmd, ListCmd, DescribeCmd}; -use crate::commands::{self, Options}; +use crate::commands::{self, branching, Options}; use crate::migrations::options::{MigrationCmd}; use crate::migrations; use crate::print; @@ -29,6 +29,9 @@ pub async fn common(cli: &mut Connection, cmd: &Common, options: &Options) } ListCmd::Databases => { commands::list_databases(cli, &options).await?; + }, + ListCmd::Branches => { + commands::list_branches(cli, &options).await?; } ListCmd::Scalars(c) => { commands::list_scalar_types(cli, &options, @@ -91,6 +94,9 @@ pub async fn common(cli: &mut Connection, cmd: &Common, options: &Options) DatabaseCmd::Wipe(w) => { commands::database::wipe(cli, w, &options).await?; } + }, + Branching(branching) => { + branching::main(cli, &branching.subcommand, &options).await? } Migrate(params) => { migrations::migrate(cli, &options, params).await?; diff --git a/src/commands/list_branches.rs b/src/commands/list_branches.rs new file mode 100644 index 000000000..bf9bc92ef --- /dev/null +++ b/src/commands/list_branches.rs @@ -0,0 +1,27 @@ +use crate::commands::list_databases::get_databases; +use crate::commands::{list, list_databases, Options}; +use crate::connect::Connection; +use crate::print; + +pub async fn get_branches(cli: &mut Connection) -> anyhow::Result> { + get_databases(cli).await +} + +pub async fn list_branches(cli: &mut Connection, options: &Options) + -> Result<(), anyhow::Error> +{ + let version = cli.get_version().await?; + + if version.specific().major <= 4 { + print::warn(format!("Branches are not supported in EdgeDB {}, printing list of databases instead", version)); + return list_databases(cli, options).await; + } + + list_branches0(cli, options).await +} + +pub async fn list_branches0(cli: &mut Connection, options: &Options) -> Result<(), anyhow::Error> { + let databases = get_branches(cli).await?; + list::print(databases, "List of branches", options).await?; + Ok(()) +} \ No newline at end of file diff --git a/src/commands/list_databases.rs b/src/commands/list_databases.rs index f758aae1c..a36694094 100644 --- a/src/commands/list_databases.rs +++ b/src/commands/list_databases.rs @@ -1,6 +1,8 @@ use crate::commands::Options; use crate::commands::list; +use crate::commands::list_branches::{list_branches0}; use crate::connect::Connection; +use crate::print; pub async fn get_databases(cli: &mut Connection) -> anyhow::Result> @@ -15,6 +17,14 @@ pub async fn get_databases(cli: &mut Connection) -> anyhow::Result> pub async fn list_databases(cli: &mut Connection, options: &Options) -> Result<(), anyhow::Error> { + let version = cli.get_version().await?; + + + if version.specific().major >= 5 { + print::warn(format!("Databases are not supported in EdgeDB {}, printing list of branches instead", version)); + return list_branches0(cli, options).await; + } + let databases = get_databases(cli).await?; list::print(databases, "List of databases", options).await?; Ok(()) diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 15a2e1ea2..56c0d200d 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -24,6 +24,8 @@ pub mod cli; pub mod options; pub mod parser; mod ui; +mod list_branches; +mod branching; pub use self::configure::configure; pub use self::dump::{dump, dump_all}; @@ -32,6 +34,7 @@ pub use self::describe_schema::describe_schema; pub use self::list_aliases::list_aliases; pub use self::list_casts::list_casts; pub use self::list_databases::list_databases; +pub use self::list_branches::list_branches; pub use self::list_indexes::list_indexes; pub use self::list_modules::list_modules; pub use self::list_object_types::list_object_types; diff --git a/src/commands/parser.rs b/src/commands/parser.rs index c59ca0bc7..659383662 100644 --- a/src/commands/parser.rs +++ b/src/commands/parser.rs @@ -25,6 +25,7 @@ pub enum Common { /// Database commands Database(Database), + Branching(Branching), /// Describe database schema or object Describe(Describe), @@ -96,8 +97,10 @@ pub enum ListCmd { Aliases(ListAliases), /// Display list of casts defined in the schema Casts(ListCasts), - /// Display list of databases for an EdgeDB instance + /// On EdgeDB < 5.x: Display list of databases for an EdgeDB instance Databases, + /// On EdgeDB >= 5.x: Display list of branches for an EdgeDB instance + Branches, /// Display list of indexes defined in the schema Indexes(ListIndexes), /// Display list of modules defined in the schema @@ -110,6 +113,28 @@ pub enum ListCmd { Types(ListTypes), } +#[derive(clap::Args, Clone, Debug)] +#[command(version = "help_expand")] +#[command(disable_version_flag=true)] +pub struct Branching { + #[command(flatten)] + pub conn: ConnectionOptions, + + #[command(subcommand)] + pub subcommand: BranchingCmd, +} + +#[derive(clap::Subcommand, Clone, Debug)] +pub enum BranchingCmd { + /// Create a new branch + Create(crate::branch::option::Create), + /// Delete a branch along with its data + Drop(crate::branch::option::Drop), + /// Delete a branches data and reset its schema while + /// preserving the branch itself (its cfg::DatabaseConfig) + /// and existing migration scripts + Wipe(crate::branch::option::Wipe), +} #[derive(clap::Args, Clone, Debug)] #[command(version = "help_expand")] diff --git a/src/migrations/create.rs b/src/migrations/create.rs index 78d26ebc0..775d54a4a 100644 --- a/src/migrations/create.rs +++ b/src/migrations/create.rs @@ -1,6 +1,7 @@ use std::borrow::Cow; use std::collections::BTreeMap; use std::path::{Path, PathBuf}; +use std::slice::Iter; use anyhow::Context as _; use colorful::Colorful; @@ -106,12 +107,11 @@ pub enum MigrationKey { Fixup { target_revision: String }, } -pub trait MigrationToText { - type StatementsIter<'a>: Iterator where Self: 'a; +pub trait MigrationToText<'a, T: Iterator = std::iter::Once<&'a String>> { fn key(&self) -> &MigrationKey; fn parent(&self) -> anyhow::Result<&str>; fn id(&self) -> anyhow::Result<&str>; - fn statements<'a>(&'a self) -> Self::StatementsIter<'a>; + fn statements(&'a self) -> T; } #[derive(Debug)] @@ -165,9 +165,7 @@ impl FutureMigration { } } -impl MigrationToText for FutureMigration { - type StatementsIter<'a> = std::slice::Iter<'a, String>; - +impl<'a> MigrationToText<'a, Iter<'a, String> > for FutureMigration { fn key(&self) -> &MigrationKey { &self.key } @@ -188,7 +186,7 @@ impl MigrationToText for FutureMigration { }).map(|s| &s[..]) } - fn statements<'a>(&'a self) -> Self::StatementsIter<'a> { + fn statements(&'a self) -> Iter<'a, String> { self.statements.iter() } } @@ -699,9 +697,10 @@ async fn run_interactive(_ctx: &Context, cli: &mut Connection, Ok(FutureMigration::new(key, descr)) } -pub async fn write_migration(ctx: &Context, descr: &impl MigrationToText, +pub async fn write_migration<'a, T>(ctx: &Context, descr: &'a impl MigrationToText<'a, T>, verbose: bool) -> anyhow::Result<()> + where T : Iterator { let filename = match &descr.key() { MigrationKey::Index(idx) => { @@ -717,9 +716,10 @@ pub async fn write_migration(ctx: &Context, descr: &impl MigrationToText, } #[context("could not write migration file {}", filepath.display())] -async fn _write_migration(descr: &impl MigrationToText, filepath: &Path, +async fn _write_migration<'a, T>(descr: &'a impl MigrationToText<'a, T>, filepath: &Path, verbose: bool) -> anyhow::Result<()> + where T : Iterator { let id = descr.id()?; let dir = filepath.parent().unwrap(); diff --git a/src/migrations/extract.rs b/src/migrations/extract.rs index 5c332f802..59119c477 100644 --- a/src/migrations/extract.rs +++ b/src/migrations/extract.rs @@ -1,3 +1,4 @@ +use std::iter::Once; use fs_err as fs; use crate::commands::{ExitCode, Options}; @@ -14,9 +15,7 @@ pub struct DatabaseMigration { pub migration: db_migration::DBMigration, } -impl MigrationToText for DatabaseMigration { - type StatementsIter<'a> = std::iter::Once<&'a String>; - +impl<'a> MigrationToText<'a, Once<&'a String>> for DatabaseMigration { fn key(&self) -> &MigrationKey { &self.key } @@ -35,7 +34,7 @@ impl MigrationToText for DatabaseMigration { Ok(&self.migration.name) } - fn statements<'a>(&'a self) -> Self::StatementsIter<'a> { + fn statements(&'a self) -> Once<&'a String> { std::iter::once(&self.migration.script) } } diff --git a/src/migrations/merge.rs b/src/migrations/merge.rs index d39040622..b236d1fee 100644 --- a/src/migrations/merge.rs +++ b/src/migrations/merge.rs @@ -35,9 +35,7 @@ pub struct MergeMigration { migration: DBMigration } -impl MigrationToText for MergeMigration { - type StatementsIter<'a> = std::iter::Once<&'a String> where Self: 'a; - +impl<'a> MigrationToText<'a, std::iter::Once<&'a String>> for MergeMigration { fn key(&self) -> &MigrationKey { &self.key } @@ -50,7 +48,7 @@ impl MigrationToText for MergeMigration { Ok(self.migration.name.as_str()) } - fn statements<'a>(&'a self) -> Self::StatementsIter<'a> { + fn statements(&'a self) -> std::iter::Once<&'a String> { std::iter::once(&self.migration.script) } } diff --git a/src/migrations/rebase.rs b/src/migrations/rebase.rs index a43614440..0c5c8d5c9 100644 --- a/src/migrations/rebase.rs +++ b/src/migrations/rebase.rs @@ -27,9 +27,7 @@ struct RebaseMigration<'a> { kind: RebaseMigrationKind } -impl MigrationToText for RebaseMigration<'_> { - type StatementsIter<'a> = std::iter::Once<&'a String> where Self: 'a; - +impl<'a> MigrationToText<'a, std::iter::Once<&'a String>> for RebaseMigration<'_> { fn key(&self) -> &MigrationKey { &self.key } @@ -50,7 +48,7 @@ impl MigrationToText for RebaseMigration<'_> { Ok(&self.migration.name) } - fn statements<'a>(&'a self) -> Self::StatementsIter<'a> { + fn statements(&'a self) -> std::iter::Once<&'a String> { std::iter::once(&self.migration.script) } }