From 9a31baa15a5448b3e0936fcc0596aaae845ca8db Mon Sep 17 00:00:00 2001 From: Ryan Leckey Date: Mon, 22 Feb 2021 21:22:30 -0800 Subject: [PATCH] feat(mysql): expand MySqlRow --- sqlx-mysql/src/connection/executor/columns.rs | 8 +- .../src/connection/executor/raw_prepare.rs | 4 +- .../src/connection/executor/raw_query.rs | 2 +- sqlx-mysql/src/raw_statement.rs | 24 ++++- sqlx-mysql/src/row.rs | 96 ++++++++++++++----- 5 files changed, 102 insertions(+), 32 deletions(-) diff --git a/sqlx-mysql/src/connection/executor/columns.rs b/sqlx-mysql/src/connection/executor/columns.rs index eda3535ce..e162274b8 100644 --- a/sqlx-mysql/src/connection/executor/columns.rs +++ b/sqlx-mysql/src/connection/executor/columns.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use sqlx_core::{Result, Runtime}; use crate::connection::command::QueryCommand; @@ -31,7 +33,7 @@ macro_rules! impl_recv_columns { // STATE: remember that we are now expecting a row or the end *$cmd = QueryCommand::QueryStep; - Ok(columns) + Ok(columns.into()) }}; } @@ -42,7 +44,7 @@ impl MySqlStream { store: bool, columns: u16, cmd: &mut QueryCommand, - ) -> Result> + ) -> Result> where Rt: sqlx_core::Async, { @@ -55,7 +57,7 @@ impl MySqlStream { store: bool, columns: u16, cmd: &mut QueryCommand, - ) -> Result> + ) -> Result> where Rt: sqlx_core::blocking::Runtime, { diff --git a/sqlx-mysql/src/connection/executor/raw_prepare.rs b/sqlx-mysql/src/connection/executor/raw_prepare.rs index 7fdbe7052..a4046dfe7 100644 --- a/sqlx-mysql/src/connection/executor/raw_prepare.rs +++ b/sqlx-mysql/src/connection/executor/raw_prepare.rs @@ -31,7 +31,7 @@ macro_rules! impl_raw_prepare { // extract the type only from the column definition // most other fields are useless - stmt.parameters.push(MySqlTypeInfo::new(&def)); + stmt.parameters_mut().push(MySqlTypeInfo::new(&def)); } // TODO: handle EOF for old MySQL @@ -42,7 +42,7 @@ macro_rules! impl_raw_prepare { let def = read_packet!($(@$blocking)? stream).deserialize()?; - stmt.columns.push(MySqlColumn::new(ordinal, def)); + stmt.columns_mut().push(MySqlColumn::new(ordinal, def)); } // TODO: handle EOF for old MySQL diff --git a/sqlx-mysql/src/connection/executor/raw_query.rs b/sqlx-mysql/src/connection/executor/raw_query.rs index b4cc981a2..f58a9db75 100644 --- a/sqlx-mysql/src/connection/executor/raw_query.rs +++ b/sqlx-mysql/src/connection/executor/raw_query.rs @@ -12,7 +12,7 @@ macro_rules! impl_raw_query { // execute the prepared statement $self.stream.write_packet(&protocol::Execute { statement: statement.id(), - parameters: &statement.parameters, + parameters: statement.parameters(), arguments: &arguments, })?; diff --git a/sqlx-mysql/src/raw_statement.rs b/sqlx-mysql/src/raw_statement.rs index 246bbdd44..32a21a7ca 100644 --- a/sqlx-mysql/src/raw_statement.rs +++ b/sqlx-mysql/src/raw_statement.rs @@ -1,11 +1,11 @@ use crate::protocol::PrepareOk; use crate::{MySqlColumn, MySqlTypeInfo}; -#[derive(Debug)] +#[derive(Debug, Clone)] pub(crate) struct RawStatement { id: u32, - pub(crate) columns: Vec, - pub(crate) parameters: Vec, + columns: Vec, + parameters: Vec, } impl RawStatement { @@ -17,7 +17,23 @@ impl RawStatement { } } - pub(crate) fn id(&self) -> u32 { + pub(crate) const fn id(&self) -> u32 { self.id } + + pub(crate) fn columns(&self) -> &[MySqlColumn] { + &self.columns + } + + pub(crate) fn columns_mut(&mut self) -> &mut Vec { + &mut self.columns + } + + pub(crate) fn parameters(&self) -> &[MySqlTypeInfo] { + &self.parameters + } + + pub(crate) fn parameters_mut(&mut self) -> &mut Vec { + &mut self.parameters + } } diff --git a/sqlx-mysql/src/row.rs b/sqlx-mysql/src/row.rs index 099ce0f2c..e1755293f 100644 --- a/sqlx-mysql/src/row.rs +++ b/sqlx-mysql/src/row.rs @@ -1,46 +1,97 @@ +use std::sync::Arc; + use bytes::Bytes; -use sqlx_core::{Decode, Error, Row}; +use sqlx_core::{ColumnIndex, Decode, Error, Result, Row}; use crate::{protocol, MySql, MySqlColumn, MySqlRawValue, MySqlRawValueFormat}; +/// A single row from a result set generated from MySQL. #[allow(clippy::module_name_repetitions)] pub struct MySqlRow { format: MySqlRawValueFormat, - columns: Vec, + columns: Arc<[MySqlColumn]>, values: Vec>, } impl MySqlRow { - // FIXME: Use Arc or some other way of sharing columns between rows - pub(crate) fn new(row: protocol::Row, columns: &Vec) -> Self { - Self { values: row.values, columns: columns.clone(), format: row.format } + pub(crate) fn new(row: protocol::Row, columns: &Arc<[MySqlColumn]>) -> Self { + Self { values: row.values, columns: Arc::clone(columns), format: row.format } } + /// Returns `true` if the row contains only `NULL` values. + fn is_null(&self) -> bool { + self.values.iter().all(Option::is_some) + } + + /// Returns the number of columns in the row. #[must_use] pub fn len(&self) -> usize { self.values.len() } + /// Returns `true` if there are no columns in the row. #[must_use] pub fn is_empty(&self) -> bool { self.len() == 0 } - pub fn try_get<'r, T>(&'r self, index: usize) -> sqlx_core::Result + /// Returns a reference to the columns in the row. + #[must_use] + pub fn columns(&self) -> &[MySqlColumn] { + &self.columns + } + + /// Returns the column name, given the ordinal (also known as index) of the column. + #[must_use] + pub fn column_name_of(&self, ordinal: usize) -> &str { + self.try_column_name_of(ordinal).unwrap() + } + + /// Returns the column name, given the ordinal (also known as index) of the column. + pub fn try_column_name_of(&self, ordinal: usize) -> Result<&str> { + self.columns + .get(ordinal) + .map(MySqlColumn::name) + .ok_or_else(|| Error::ColumnIndexOutOfBounds { index: ordinal, len: self.len() }) + } + + /// Returns the column ordinal, given the name of the column. + #[must_use] + pub fn ordinal_of(&self, name: &str) -> usize { + self.try_ordinal_of(name).unwrap() + } + + /// Returns the column ordinal, given the name of the column. + pub fn try_ordinal_of(&self, name: &str) -> Result { + self.columns + .iter() + .position(|col| col.name() == name) + .ok_or_else(|| Error::ColumnNotFound { name: name.to_owned().into_boxed_str() }) + } + + /// Returns the decoded value at the index. + pub fn try_get<'r, T, I>(&'r self, index: I) -> Result where + I: ColumnIndex, T: Decode<'r, MySql>, { Ok(self.try_get_raw(index)?.decode()?) } - // noinspection RsNeedlessLifetimes - pub fn try_get_raw<'r>(&'r self, index: usize) -> sqlx_core::Result> { + /// Returns the raw representation of the value at the index. + #[allow(clippy::needless_lifetimes)] + pub fn try_get_raw<'r, I>(&'r self, index: I) -> Result> + where + I: ColumnIndex, + { + let ordinal = index.get(self)?; + let value = self .values - .get(index) - .ok_or_else(|| Error::ColumnIndexOutOfBounds { len: self.len(), index })?; + .get(ordinal) + .ok_or_else(|| Error::ColumnIndexOutOfBounds { len: self.len(), index: ordinal })?; - let column = &self.columns[index]; + let column = &self.columns[ordinal]; Ok(MySqlRawValue::new(value, self.format, column.type_info())) } @@ -50,7 +101,7 @@ impl Row for MySqlRow { type Database = MySql; fn is_null(&self) -> bool { - todo!() + self.is_null() } fn len(&self) -> usize { @@ -58,34 +109,35 @@ impl Row for MySqlRow { } fn columns(&self) -> &[MySqlColumn] { - todo!() + self.columns() } fn column_name_of(&self, ordinal: usize) -> &str { - todo!() + self.column_name_of(ordinal) } - fn try_column_name_of(&self, ordinal: usize) -> sqlx_core::Result<&str> { - todo!() + fn try_column_name_of(&self, ordinal: usize) -> Result<&str> { + self.try_column_name_of(ordinal) } fn ordinal_of(&self, name: &str) -> usize { - todo!() + self.ordinal_of(name) } - fn try_ordinal_of(&self, name: &str) -> sqlx_core::Result { - todo!() + fn try_ordinal_of(&self, name: &str) -> Result { + self.try_ordinal_of(name) } - fn try_get<'r, T>(&'r self, index: usize) -> sqlx_core::Result + fn try_get<'r, T, I>(&'r self, index: I) -> Result where + I: ColumnIndex, T: Decode<'r, MySql>, { self.try_get(index) } - // noinspection RsNeedlessLifetimes - fn try_get_raw<'r>(&'r self, index: usize) -> sqlx_core::Result> { + #[allow(clippy::needless_lifetimes)] + fn try_get_raw<'r, I: ColumnIndex>(&'r self, index: I) -> Result> { self.try_get_raw(index) } }