nu_command/database/query_plan/
mod.rs1use crate::database::values::sqlite::SQLiteQueryBuilder;
2use nu_protocol::{PipelineData, ShellError, Span, Value};
3
4pub enum QueryPlan {
14 Sqlite(SQLiteQueryBuilder),
16}
17
18impl QueryPlan {
19 pub fn try_from_any(val: &dyn std::any::Any) -> Option<Self> {
25 val.downcast_ref::<SQLiteQueryBuilder>()
26 .map(|b| Self::Sqlite(b.clone()))
27 }
28
29 pub fn with_limit(self, limit: i64) -> Self {
31 match self {
32 Self::Sqlite(b) => Self::Sqlite(b.with_limit(limit)),
33 }
34 }
35
36 pub fn with_offset(self, offset: i64) -> Self {
38 match self {
39 Self::Sqlite(b) => Self::Sqlite(b.with_offset(offset)),
40 }
41 }
42
43 pub fn with_distinct(self) -> Self {
45 match self {
46 Self::Sqlite(b) => Self::Sqlite(b.with_distinct()),
47 }
48 }
49
50 pub fn with_order_by(self, order_by: String) -> Self {
52 match self {
53 Self::Sqlite(b) => Self::Sqlite(b.with_order_by(order_by)),
54 }
55 }
56
57 pub fn project_output_columns(&self, columns: &[String]) -> Option<Self> {
63 match self {
64 Self::Sqlite(b) => b.project_output_columns(columns).map(Self::Sqlite),
65 }
66 }
67
68 pub fn execute(&self, call_span: Span) -> Result<PipelineData, ShellError> {
70 match self {
71 Self::Sqlite(b) => b.execute(call_span),
72 }
73 }
74
75 pub fn count(&self, call_span: Span) -> Result<i64, ShellError> {
77 match self {
78 Self::Sqlite(b) => b.count(call_span),
79 }
80 }
81
82 pub fn type_name(&self) -> &'static str {
84 match self {
85 Self::Sqlite(_) => "lazy query",
86 }
87 }
88
89 pub fn into_value(self, span: Span) -> Value {
91 match self {
92 Self::Sqlite(b) => Value::custom(Box::new(b), span),
93 }
94 }
95}
96
97#[cfg(test)]
98mod test {
99 use super::*;
100 use crate::database::values::sqlite::SQLiteDatabase;
101 use nu_protocol::Signals;
102 use std::path::Path;
103
104 fn sample_builder(table: &str) -> SQLiteQueryBuilder {
105 SQLiteQueryBuilder::new(
106 Path::new(":memory:").to_path_buf(),
107 table.to_string(),
108 Signals::empty(),
109 )
110 }
111
112 #[test]
113 fn try_from_any_accepts_sqlite_query_builder() {
114 let builder = sample_builder("test");
115 let plan = QueryPlan::try_from_any(&builder as &dyn std::any::Any);
116 assert!(matches!(plan, Some(QueryPlan::Sqlite(_))));
117 }
118
119 #[test]
120 fn try_from_any_rejects_other_custom_values() {
121 let db = SQLiteDatabase::new(Path::new(":memory:"), Signals::empty());
122 let plan = QueryPlan::try_from_any(&db as &dyn std::any::Any);
123 assert!(plan.is_none());
124 }
125
126 #[test]
127 fn try_from_any_rejects_non_custom_values() {
128 let val = 42i64;
129 let plan = QueryPlan::try_from_any(&val as &dyn std::any::Any);
130 assert!(plan.is_none());
131 }
132
133 #[test]
134 fn with_limit_delegates() {
135 let plan = QueryPlan::Sqlite(sample_builder("t"));
136 let limited = plan.with_limit(5);
137 assert!(matches!(limited, QueryPlan::Sqlite(_)));
138 let sql = match &limited {
140 QueryPlan::Sqlite(b) => b.build_sql(),
141 };
142 assert!(
143 sql.contains("LIMIT 5"),
144 "SQL should contain LIMIT 5, got: {sql}"
145 );
146 }
147
148 #[test]
149 fn with_offset_delegates() {
150 let plan = QueryPlan::Sqlite(sample_builder("t"));
151 let offset = plan.with_offset(10);
152 let sql = match &offset {
153 QueryPlan::Sqlite(b) => b.build_sql(),
154 };
155 assert!(
156 sql.contains("OFFSET 10"),
157 "SQL should contain OFFSET 10, got: {sql}"
158 );
159 }
160
161 #[test]
162 fn with_distinct_delegates() {
163 let plan = QueryPlan::Sqlite(sample_builder("t"));
164 let distinct = plan.with_distinct();
165 let sql = match &distinct {
166 QueryPlan::Sqlite(b) => b.build_sql(),
167 };
168 assert!(
169 sql.starts_with("SELECT DISTINCT"),
170 "SQL should start with SELECT DISTINCT, got: {sql}"
171 );
172 }
173
174 #[test]
175 fn with_order_by_delegates() {
176 let plan = QueryPlan::Sqlite(sample_builder("t"));
177 let ordered = plan.with_order_by("id DESC".to_string());
178 let sql = match &ordered {
179 QueryPlan::Sqlite(b) => b.build_sql(),
180 };
181 assert!(
182 sql.contains("ORDER BY id DESC"),
183 "SQL should contain ORDER BY id DESC, got: {sql}"
184 );
185 }
186
187 #[test]
188 fn type_name_returns_expected() {
189 let plan = QueryPlan::Sqlite(sample_builder("t"));
190 assert_eq!(plan.type_name(), "lazy query");
191 }
192
193 #[test]
194 fn into_value_round_trip_produces_sqlite_query_builder() {
195 let plan = QueryPlan::Sqlite(sample_builder("roundtrip"));
196 let span = Span::test_data();
197 let value = plan.into_value(span);
198
199 assert!(matches!(&value, Value::Custom { .. }));
201
202 if let Value::Custom { val, .. } = &value {
204 let retrieved = val.as_any().downcast_ref::<SQLiteQueryBuilder>();
205 assert!(retrieved.is_some());
206 assert_eq!(retrieved.unwrap().table_name, "roundtrip");
207 }
208 }
209}