77// Copyright 2024 MonetDB Foundation
88#![ allow( dead_code) ]
99
10- pub mod delayed;
11- pub mod replies;
12- pub mod rowset;
10+ pub ( crate ) mod delayed;
11+ pub ( crate ) mod replies;
12+ pub ( crate ) mod rowset;
1313
1414use std:: mem;
1515use std:: { io, sync:: Arc } ;
@@ -21,27 +21,40 @@ use rowset::RowSet;
2121use crate :: conn:: Conn ;
2222use crate :: framing:: reading:: MapiReader ;
2323use crate :: framing:: writing:: MapiBuf ;
24+ use crate :: framing:: FramingError ;
2425use crate :: framing:: { ServerSock , ServerState } ;
25- use crate :: { framing :: FramingError , IoError } ;
26+ use crate :: util :: ioerror :: IoError ;
2627
28+ /// An error that occurs while accessing data with a [`Cursor`].
2729#[ derive( Debug , PartialEq , Eq , Clone , thiserror:: Error ) ]
2830pub enum CursorError {
31+ /// The server returned an error.
2932 #[ error( "{0}" ) ]
3033 Server ( String ) ,
34+ /// The connection has been closed.
3135 #[ error( "connection has been closed" ) ]
3236 Closed ,
37+ /// An IO Error occurred.
3338 #[ error( transparent) ]
3439 IO ( #[ from] IoError ) ,
3540 #[ error( transparent) ]
41+ /// Something went wrong in the communication with the server.
3642 Framing ( #[ from] FramingError ) ,
43+ /// The server sent a response that we do not understand.
3744 #[ error( transparent) ]
3845 BadReply ( #[ from] BadReply ) ,
46+ /// [`next_row()`](`Cursor::next_row`) or [`next_reply()`](`Cursor::next_reply`)
47+ /// was called but the server did not send a result set.
3948 #[ error( "there is no result set" ) ]
4049 NoResultSet ,
41- #[ error( "could not convert column {0} to {1}: {2}" ) ]
42- Conversion ( usize , & ' static str , String ) ,
43- #[ error( "server unexpectedly returned no rows" ) ]
44- NoRows ,
50+ /// The user called the wrong typed getter, for example
51+ /// [`get_bool()`](`Cursor::get_bool`) on an INT column.
52+ #[ error( "could not convert column {colnr} to {expected_type}: {message}" ) ]
53+ Conversion {
54+ colnr : usize ,
55+ expected_type : & ' static str ,
56+ message : String ,
57+ } ,
4558}
4659
4760pub type CursorResult < T > = Result < T , CursorError > ;
@@ -52,6 +65,54 @@ impl From<io::Error> for CursorError {
5265 }
5366}
5467
68+ /// Executes queries on a connection and manages retrieval of the
69+ /// results. It can be obtained using the
70+ /// [`cursor()`](`super::conn::Connection::cursor`) method on the connection.
71+ ///
72+ /// The method [`execute()`][`Cursor::execute`] can be used to send SQL
73+ /// statements to the server. The server will return zero or more replies,
74+ /// usually one per statement. A reply may be an error, an acknowledgement such
75+ /// as "your UPDATE statement affected 1001 rows", or a result set. This method
76+ /// will immediately abort with `Err(CursorError::Server(_))` if *any* of the
77+ /// replies is an error message, not just the first reply.
78+ ///
79+ /// Most retrieval methods on a cursor operate on the *current reply*. To move
80+ /// on to the next reply, call [`next_reply()`][`Cursor::next_reply`]. The only
81+ /// exception is [`next_row()`][`Cursor::next_row`], which will automatically
82+ /// try to skip to the next result set reply if the current reply is not a
83+ /// result set. This is useful because people often write things like
84+ /// ```sql
85+ /// CREATE TABLE foo(..);
86+ /// INSERT INTO foo SELECT .. FROM other_table;
87+ /// INSERT INTO foo SELECT .. FROM yet_another_table;
88+ /// SELECT COUNT(*) FROM foo;
89+ /// ```
90+ /// and they expect to be able to directly retrieve the count, not get an error
91+ /// message "CREATE TABLE did not return a result set". Note that
92+ /// [`next_row()`][`Cursor::next_row`] will *not* automatically skip to the next
93+ /// result set if the current result set is exhausted.
94+ ///
95+ /// To retrieve data from a result set, first call
96+ /// [`next_row()`][`Cursor::next_row`]. This tries to move the cursor to the
97+ /// next row and returns a boolean indicating if a new row was found. if so,
98+ /// methods like [`get_str(colnr)`][`Cursor::get_str`] and
99+ /// [`get_i32(colnr)`][`Cursor::get_i32`] can be used to retrieve individual
100+ /// fields from this row.
101+ /// Note that you **must** call [`next_row()`][`Cursor::next_row`] before you
102+ /// call a getter. Before the first call to [`next_row()`][`Cursor::next_row`],
103+ /// the cursor is *before* the first row, not *at* the first row. This behaviour
104+ /// is convenient because it allows to write things like
105+ /// ```no_run
106+ /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
107+ /// # let mut cursor: monetdb::Cursor = todo!();
108+ /// cursor.execute("SELECT * FROM mytable")?;
109+ /// while cursor.next_row()? {
110+ /// let value: Option<&str> = cursor.get_str(0)?;
111+ /// println!("{}", value.unwrap());
112+ /// }
113+ /// # Ok(())
114+ /// # }
115+ /// ```
55116pub struct Cursor {
56117 conn : Arc < Conn > ,
57118 buf : MapiBuf ,
@@ -69,6 +130,9 @@ impl Cursor {
69130 }
70131 }
71132
133+ /// Execute the given SQL statements and place the cursor at the first
134+ /// reply. The results of any earlier queries on this cursor are discarded.
135+
72136 pub fn execute ( & mut self , statements : & str ) -> CursorResult < ( ) > {
73137 self . exhaust ( ) ?;
74138
@@ -107,14 +171,23 @@ impl Cursor {
107171 Ok ( ( ) )
108172 }
109173
174+ /// Retrieve the number of affected rows from the current reply. INSERT,
175+ /// UPDATE and SELECT statements provide the number of affected rows, but
176+ /// for example CREATE TABLE doesn't. Returns a signed value because we're
177+ /// not entirely sure whether the server ever sends negative values to indicate
178+ /// exceptional conditions.
179+ ///
180+ /// TODO figure this out and deal with it.
110181 pub fn affected_rows ( & self ) -> Option < i64 > {
111182 self . replies . affected_rows ( )
112183 }
113184
185+ /// Return `true` if the current reply is a result set.
114186 pub fn has_result_set ( & self ) -> bool {
115187 self . replies . at_result_set ( )
116188 }
117189
190+ /// Try to move the cursor to the next reply.
118191 pub fn next_reply ( & mut self ) -> CursorResult < bool > {
119192 // todo: close server side result set if necessary
120193 let old = mem:: take ( & mut self . replies ) ;
@@ -148,6 +221,8 @@ impl Cursor {
148221 }
149222 }
150223
224+ /// Destroy the cursor, discarding all results. This may need to communicate with the server
225+ /// to release resources there.
151226 pub fn close ( mut self ) -> CursorResult < ( ) > {
152227 self . do_close ( ) ?;
153228 Ok ( ( ) )
@@ -165,6 +240,7 @@ impl Cursor {
165240 } )
166241 }
167242
243+ /// Return information about the columns of the current result set.
168244 pub fn column_metadata ( & self ) -> & [ ResultColumn ] {
169245 if let ReplyParser :: Data ( ResultSet { columns, .. } ) = & self . replies {
170246 & columns[ ..]
@@ -173,6 +249,15 @@ impl Cursor {
173249 }
174250 }
175251
252+ /// Advance the cursor to the next available row in the result set,
253+ /// returning a boolean that indicates whether such a row was present.
254+ ///
255+ /// When the cursor enters a new result set after
256+ /// [`execute()`][`Cursor::execute`] or
257+ /// [`next_reply()`][`Cursor::next_reply`], it is initially positioned
258+ /// *before* the first row, and the first call to this method will advance
259+ /// it to be *at* the first row. This means you always have to call this method
260+ /// before calling getters.
176261 pub fn next_row ( & mut self ) -> CursorResult < bool > {
177262 self . skip_to_result_set ( ) ?;
178263
@@ -284,6 +369,9 @@ macro_rules! getter {
284369 } ;
285370}
286371
372+ /// These getters can be called to retrieve values from the current row, after
373+ /// [`next_row()`][`Cursor::next_row`] has confirmed that that row exists.
374+ /// They return None if the value is NULL.
287375impl Cursor {
288376 getter ! ( get_str, & str ) ;
289377 getter ! ( get_bool, bool ) ;
0 commit comments