• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

joaoh82 / rust_sqlite / 25190382873

30 Apr 2026 09:32PM UTC coverage: 58.9% (-8.9%) from 67.839%
25190382873

push

github

web-flow
Phase 7g.2: REPL .ask + dep-direction flip (#60)

Wires the REPL's `.ask <question>` meta-command — the smallest
per-product wrapper sub-phase, and the natural validation that
sqlrite-ask 0.1.18's foundational shape fits the consumer side.
Required a structural refactor 7g.1 didn't anticipate; this commit
lands both pieces together because they're inseparable.

## What ships

REPL UX (sqlrite> prompt):

    sqlrite> .ask How many users are over 30?
    Generated SQL:
      SELECT COUNT(*) FROM users WHERE age > 30
    Rationale: Counts users older than thirty.
    Run? [Y/n] y
    +-------+
    | count |
    +-------+
    | 47    |
    +-------+
    SELECT Statement executed. 1 row returned.

`.ask` parses verbatim (rest of line after `.ask `, preserving
quotes/punctuation/multiple spaces — natural-language questions
need that). Confirm-and-run via rustyline; empty/y/yes runs through
the same `process_command` pipeline as a typed statement. Ctrl-C /
EOF / `n` map to skip — paranoid default for LLM-generated SQL.
Empty SQL response (model declined) surfaces the model's
explanation rather than silently doing nothing.

`.help` updated. New tests in `src/meta_command/`: `parse_ask_*`
(verbatim capture, separator handling), `ask_meta_command_displays_*`.

## Why the dep-direction flip

The REPL binary needed to import `sqlrite-ask` to call ask().
But `sqlrite-ask` 0.1.18 imported `sqlrite-engine` (for
Connection/Database integration and the ConnectionAskExt trait).
That's a cargo cycle:

    sqlrite-engine[bin] → sqlrite-ask → sqlrite-engine[lib]

Cargo's static cycle detection counts every potential edge in
the dep graph regardless of features — `optional = true` on
either side doesn't escape it. Verified empirically:
`cargo check -p sqlrite-engine --no-default-features` errors
with `cyclic package dependency` even when the optional dep
isn't enabled.

The fix flipped the dep direction structurally:

  - `sqlrite-ask` 0.1.19 dropped `sqlrite-engine` entir... (continued)

43 of 80 new or added lines in 5 files covered. (53.75%)

1458 existing lines in 5 files now uncovered.

5496 of 9331 relevant lines covered (58.9%)

1.21 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

8.94
/src/error.rs
1
use thiserror::Error;
2

3
use std::result;
4

5
use sqlparser::parser::ParserError;
6

7
/// This is a type that encapsulated the `std::result` with the enum `SQLRiteError`
8
/// and makes function signatures easier to read.
UNCOV
9
pub type Result<T> = result::Result<T, SQLRiteError>;
×
10

11
/// SQLRiteError is an enum with all the standardized errors available for returning
12
///
13
#[derive(Error, Debug)]
14
pub enum SQLRiteError {
15
    #[error("Not Implemented error: {0}")]
16
    NotImplemented(String),
17
    #[error("General error: {0}")]
18
    General(String),
19
    #[error("Internal error: {0}")]
20
    Internal(String),
21
    #[error("Unknown command error: {0}")]
22
    UnknownCommand(String),
23
    #[error("SQL error: {0:?}")]
24
    SqlError(#[from] ParserError),
25
    #[error("IO error: {0}")]
26
    Io(#[from] std::io::Error),
27
}
28

29
// `std::io::Error` has no `PartialEq`, so we implement one by value-of-message.
×
30
// Used by existing tests that compare error variants.
×
31
impl PartialEq for SQLRiteError {
×
32
    fn eq(&self, other: &Self) -> bool {
1✔
33
        use SQLRiteError::*;
34
        match (self, other) {
1✔
35
            (NotImplemented(a), NotImplemented(b)) => a == b,
×
36
            (General(a), General(b)) => a == b,
1✔
37
            (Internal(a), Internal(b)) => a == b,
×
38
            (UnknownCommand(a), UnknownCommand(b)) => a == b,
×
39
            (SqlError(a), SqlError(b)) => format!("{a:?}") == format!("{b:?}"),
×
40
            (Io(a), Io(b)) => a.kind() == b.kind() && a.to_string() == b.to_string(),
×
41
            _ => false,
×
42
        }
43
    }
44
}
45

46
/// Returns SQLRiteError::General error from String
UNCOV
47
#[allow(dead_code)]
×
48
pub fn sqlrite_error(message: &str) -> SQLRiteError {
1✔
49
    SQLRiteError::General(message.to_owned())
1✔
50
}
51

52
#[cfg(test)]
×
53
mod tests {
54
    use super::*;
55

56
    #[test]
×
57
    fn sqlrite_error_test() {
3✔
58
        let input = String::from("test error");
1✔
59
        let expected = SQLRiteError::General("test error".to_string());
2✔
60

61
        let result = sqlrite_error(&input);
2✔
62
        assert_eq!(result, expected);
2✔
63
    }
64

65
    #[test]
×
66
    fn sqlrite_display_not_implemented_test() {
3✔
67
        let error_string = String::from("Feature not implemented.");
1✔
68
        let input = SQLRiteError::NotImplemented(error_string.clone());
2✔
69

70
        let expected = format!("Not Implemented error: {}", error_string);
2✔
71
        let result = format!("{}", input);
2✔
72
        assert_eq!(result, expected);
2✔
73
    }
74

75
    #[test]
×
76
    fn sqlrite_display_general_test() {
3✔
77
        let error_string = String::from("General error.");
1✔
78
        let input = SQLRiteError::General(error_string.clone());
2✔
79

80
        let expected = format!("General error: {}", error_string);
2✔
81
        let result = format!("{}", input);
2✔
82
        assert_eq!(result, expected);
2✔
83
    }
84

85
    #[test]
×
86
    fn sqlrite_display_internal_test() {
3✔
87
        let error_string = String::from("Internet error.");
1✔
88
        let input = SQLRiteError::Internal(error_string.clone());
2✔
89

90
        let expected = format!("Internal error: {}", error_string);
2✔
91
        let result = format!("{}", input);
2✔
92
        assert_eq!(result, expected);
2✔
93
    }
94

95
    #[test]
×
96
    fn sqlrite_display_sqlrite_test() {
3✔
97
        let error_string = String::from("SQL error.");
1✔
98
        let input = SQLRiteError::SqlError(ParserError::ParserError(error_string.clone()));
2✔
99

100
        let expected = format!("SQL error: ParserError(\"{}\")", error_string);
2✔
101
        let result = format!("{}", input);
2✔
102
        assert_eq!(result, expected);
2✔
103
    }
104

105
    #[test]
×
106
    fn sqlrite_unknown_test() {
3✔
107
        let error_string = String::from("Unknown error.");
1✔
108
        let input = SQLRiteError::UnknownCommand(error_string.clone());
2✔
109

110
        let expected = format!("Unknown command error: {}", error_string);
2✔
111
        let result = format!("{}", input);
2✔
112
        assert_eq!(result, expected);
2✔
113
    }
114
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc