use mongodb::bson::Document;
use serde::{Deserialize, Serialize};
use crate::error::ParseError;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum QueryMode {
Interactive { batch_size: u32 },
Streaming { batch_size: u32 },
}
impl Default for QueryMode {
fn default() -> Self {
QueryMode::Interactive { batch_size: 20 }
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum Command {
Query(QueryCommand),
Admin(AdminCommand),
Utility(UtilityCommand),
Config(ConfigCommand),
Pipe(Box<Command>, PipeCommand),
Help(Option<String>),
Exit,
}
#[derive(Debug, Clone, PartialEq)]
pub enum QueryCommand {
Find {
collection: String,
filter: Document,
options: FindOptions,
},
FindOne {
collection: String,
filter: Document,
options: FindOptions,
},
InsertOne {
collection: String,
document: Document,
},
InsertMany {
collection: String,
documents: Vec<Document>,
},
UpdateOne {
collection: String,
filter: Document,
update: Document,
options: UpdateOptions,
},
UpdateMany {
collection: String,
filter: Document,
update: Document,
options: UpdateOptions,
},
ReplaceOne {
collection: String,
filter: Document,
replacement: Document,
options: UpdateOptions,
},
DeleteOne {
collection: String,
filter: Document,
},
DeleteMany {
collection: String,
filter: Document,
},
Aggregate {
collection: String,
pipeline: Vec<Document>,
options: AggregateOptions,
},
CountDocuments {
collection: String,
filter: Document,
},
EstimatedDocumentCount { collection: String },
FindOneAndDelete {
collection: String,
filter: Document,
options: FindAndModifyOptions,
},
FindOneAndUpdate {
collection: String,
filter: Document,
update: Document,
options: FindAndModifyOptions,
},
FindOneAndReplace {
collection: String,
filter: Document,
replacement: Document,
options: FindAndModifyOptions,
},
FindAndModify {
collection: String,
query: Document,
sort: Option<Document>,
remove: bool,
update: Option<Document>,
new: bool,
fields: Option<Document>,
upsert: bool,
array_filters: Option<Vec<Document>>,
max_time_ms: Option<u64>,
collation: Option<Document>,
},
Distinct {
collection: String,
field: String,
filter: Option<Document>,
},
BulkWrite {
collection: String,
operations: Vec<Document>,
ordered: bool,
},
Explain {
collection: String,
verbosity: ExplainVerbosity,
query: Box<QueryCommand>,
},
}
impl QueryCommand {
pub fn collection(&self) -> &str {
match self {
QueryCommand::Find { collection, .. }
| QueryCommand::FindOne { collection, .. }
| QueryCommand::InsertOne { collection, .. }
| QueryCommand::InsertMany { collection, .. }
| QueryCommand::UpdateOne { collection, .. }
| QueryCommand::UpdateMany { collection, .. }
| QueryCommand::ReplaceOne { collection, .. }
| QueryCommand::DeleteOne { collection, .. }
| QueryCommand::DeleteMany { collection, .. }
| QueryCommand::Aggregate { collection, .. }
| QueryCommand::CountDocuments { collection, .. }
| QueryCommand::EstimatedDocumentCount { collection }
| QueryCommand::FindOneAndDelete { collection, .. }
| QueryCommand::FindOneAndUpdate { collection, .. }
| QueryCommand::FindOneAndReplace { collection, .. }
| QueryCommand::FindAndModify { collection, .. }
| QueryCommand::Distinct { collection, .. }
| QueryCommand::BulkWrite { collection, .. }
| QueryCommand::Explain { collection, .. } => collection,
}
}
pub fn supports_explain(&self) -> bool {
matches!(
self,
QueryCommand::Find { .. }
| QueryCommand::FindOne { .. }
| QueryCommand::Aggregate { .. }
| QueryCommand::CountDocuments { .. }
| QueryCommand::Distinct { .. }
)
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum ExplainVerbosity {
QueryPlanner,
ExecutionStats,
AllPlansExecution,
}
impl ExplainVerbosity {
pub const QUERY_PLANNER: &'static str = "queryPlanner";
pub const EXECUTION_STATS: &'static str = "executionStats";
pub const ALL_PLANS_EXECUTION: &'static str = "allPlansExecution";
pub const COMPAT_TRUE: &'static str = "true";
pub const COMPAT_FALSE: &'static str = "false";
pub fn from_str(s: &str) -> Result<Self, ParseError> {
match s {
Self::QUERY_PLANNER => Ok(ExplainVerbosity::QueryPlanner),
Self::EXECUTION_STATS => Ok(ExplainVerbosity::ExecutionStats),
Self::ALL_PLANS_EXECUTION => Ok(ExplainVerbosity::AllPlansExecution),
Self::COMPAT_TRUE => Ok(ExplainVerbosity::AllPlansExecution), Self::COMPAT_FALSE => Ok(ExplainVerbosity::QueryPlanner), _ => Err(ParseError::InvalidCommand(format!(
"Invalid explain verbosity: '{}'. Valid options are: '{}', '{}', '{}' (or boolean true/false for compatibility)",
s, Self::QUERY_PLANNER, Self::EXECUTION_STATS, Self::ALL_PLANS_EXECUTION
))),
}
}
pub fn from_bool(value: bool) -> Self {
if value {
ExplainVerbosity::AllPlansExecution
} else {
ExplainVerbosity::QueryPlanner
}
}
pub fn as_str(&self) -> &'static str {
match self {
ExplainVerbosity::QueryPlanner => Self::QUERY_PLANNER,
ExplainVerbosity::ExecutionStats => Self::EXECUTION_STATS,
ExplainVerbosity::AllPlansExecution => Self::ALL_PLANS_EXECUTION,
}
}
pub fn parse_from_args(args: &[crate::parser::mongo_ast::Expr]) -> Result<Self, ParseError> {
use crate::parser::mongo_ast::Expr;
if args.is_empty() {
return Ok(Self::default());
}
if args.len() > 1 {
return Err(ParseError::InvalidCommand(
format!("explain() expects at most 1 argument, got {}", args.len())
));
}
match args.first().unwrap() {
Expr::String(verb_str) => Self::from_str(verb_str),
Expr::Boolean(b) => Ok(Self::from_bool(*b)),
_ => Err(ParseError::InvalidCommand(
"explain() expects a string verbosity ('queryPlanner', 'executionStats', 'allPlansExecution') or boolean".to_string()
)),
}
}
}
impl Default for ExplainVerbosity {
fn default() -> Self {
ExplainVerbosity::QueryPlanner
}
}
#[derive(Debug, Clone, PartialEq)]
#[allow(dead_code)]
pub enum AdminCommand {
ShowDatabases,
ShowCollections,
ShowUsers,
ShowRoles,
ShowProfile,
ShowLogs(Option<String>),
UseDatabase(String),
CreateIndex {
collection: String,
keys: Document,
options: Option<Document>,
},
CreateIndexes {
collection: String,
indexes: Vec<Document>,
},
ListIndexes(String),
DropIndex { collection: String, index: String },
DropIndexes {
collection: String,
indexes: Option<Vec<String>>,
},
DropCollection(String),
RenameCollection {
collection: String,
target: String,
drop_target: bool,
},
CollectionStats {
collection: String,
scale: Option<i32>,
},
}
#[derive(Debug, Clone, PartialEq)]
pub enum PipeCommand {
Export {
format: ExportFormat,
file: Option<String>,
},
Explain,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ExportFormat {
JsonL,
Csv,
}
#[derive(Debug, Clone, PartialEq)]
pub enum UtilityCommand {
#[allow(dead_code)]
Print(String),
Iterate,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ConfigCommand {
SetFormat(String),
GetFormat,
SetColor(bool),
GetColor,
ShowConfig,
ListNamedQueries,
ExecuteNamedQuery { name: String, args: Vec<String> },
SaveNamedQuery { name: String, query: String },
DeleteNamedQuery(String),
}
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
pub struct FindOptions {
pub limit: Option<i64>,
pub skip: Option<u64>,
pub sort: Option<Document>,
pub projection: Option<Document>,
pub batch_size: Option<u32>,
pub collation: Option<Document>,
pub hint: Option<Document>,
pub max_time_ms: Option<u64>,
pub read_concern: Option<Document>,
}
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
pub struct UpdateOptions {
pub upsert: bool,
pub array_filters: Option<Vec<Document>>,
pub collation: Option<Document>,
pub hint: Option<Document>,
pub write_concern: Option<Document>,
}
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
pub struct AggregateOptions {
pub allow_disk_use: bool,
pub batch_size: Option<u32>,
pub max_time_ms: Option<u64>,
pub collation: Option<Document>,
pub hint: Option<Document>,
pub read_concern: Option<Document>,
pub let_vars: Option<Document>,
}
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
pub struct FindAndModifyOptions {
pub return_new: bool,
pub upsert: bool,
pub sort: Option<Document>,
pub projection: Option<Document>,
pub collation: Option<Document>,
pub array_filters: Option<Vec<Document>>,
pub max_time_ms: Option<u64>,
pub hint: Option<Document>,
}