mirror of
https://github.com/launchbadge/sqlx.git
synced 2025-10-02 15:25:32 +00:00
query_as: fully implement query_as, required a db-specific ext trait
This commit is contained in:
parent
f18ab2fecb
commit
47f3d77e59
36
Cargo.lock
generated
36
Cargo.lock
generated
@ -1061,6 +1061,26 @@ dependencies = [
|
||||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"paste-impl 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "paste-impl"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "paw"
|
||||
version = "1.0.0"
|
||||
@ -1466,8 +1486,10 @@ dependencies = [
|
||||
"dotenv 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"env_logger 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"futures 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"paste 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"sqlx-core 0.2.6",
|
||||
"sqlx-macros 0.2.5",
|
||||
"sqlx-test 0.1.0",
|
||||
"tokio 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"trybuild 1.0.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
@ -1580,6 +1602,18 @@ dependencies = [
|
||||
"url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-test"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"async-std 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"dotenv 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"env_logger 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"sqlx 0.2.5",
|
||||
"tokio 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "string"
|
||||
version = "0.2.1"
|
||||
@ -2173,6 +2207,8 @@ dependencies = [
|
||||
"checksum openssl-sys 0.9.54 (registry+https://github.com/rust-lang/crates.io-index)" = "1024c0a59774200a555087a6da3f253a9095a5f344e353b212ac4c8b8e450986"
|
||||
"checksum parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f842b1982eb6c2fe34036a4fbfb06dd185a3f5c8edfaacdf7d1ea10b07de6252"
|
||||
"checksum parking_lot_core 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b876b1b9e7ac6e1a74a6da34d25c42e17e8862aa409cbbbdcfc8d86c6f3bc62b"
|
||||
"checksum paste 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "63e1afe738d71b1ebab5f1207c055054015427dbfc7bbe9ee1266894156ec046"
|
||||
"checksum paste-impl 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "6d4dc4a7f6f743211c5aab239640a65091535d97d43d92a52bca435a640892bb"
|
||||
"checksum paw 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "09c0fc9b564dbc3dc2ed7c92c0c144f4de340aa94514ce2b446065417c4084e9"
|
||||
"checksum paw-attributes 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0f35583365be5d148e959284f42526841917b7bfa09e2d1a7ad5dde2cf0eaa39"
|
||||
"checksum paw-raw 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7f0b59668fe80c5afe998f0c0bf93322bf2cd66cafeeb80581f291716f3467f2"
|
||||
|
@ -3,6 +3,7 @@ members = [
|
||||
".",
|
||||
"sqlx-core",
|
||||
"sqlx-macros",
|
||||
"sqlx-test",
|
||||
"examples/realworld-postgres",
|
||||
"examples/todos-postgres",
|
||||
]
|
||||
@ -58,6 +59,8 @@ async-std = { version = "1.4.0", features = [ "attributes" ] }
|
||||
tokio = { version = "0.2.9", features = [ "full" ] }
|
||||
dotenv = "0.15.0"
|
||||
trybuild = "1.0"
|
||||
sqlx-test = { path = "./sqlx-test" }
|
||||
paste = "0.1"
|
||||
|
||||
[[test]]
|
||||
name = "postgres-macros"
|
||||
@ -76,7 +79,7 @@ name = "postgres"
|
||||
required-features = [ "postgres" ]
|
||||
|
||||
[[test]]
|
||||
name = "postgres-simple"
|
||||
name = "postgres-raw"
|
||||
required-features = [ "postgres" ]
|
||||
|
||||
[[test]]
|
||||
|
@ -60,10 +60,10 @@ impl<'c, C> MaybeOwnedConnection<'c, C>
|
||||
where
|
||||
C: Connect,
|
||||
{
|
||||
pub(crate) fn borrow(&mut self) -> MaybeOwnedConnection<'_, C> {
|
||||
pub(crate) fn borrow(&mut self) -> &'_ mut C {
|
||||
match self {
|
||||
MaybeOwnedConnection::Borrowed(conn) => MaybeOwnedConnection::Borrowed(&mut *conn),
|
||||
MaybeOwnedConnection::Owned(ref mut conn) => MaybeOwnedConnection::Borrowed(conn),
|
||||
MaybeOwnedConnection::Borrowed(conn) => &mut *conn,
|
||||
MaybeOwnedConnection::Owned(ref mut conn) => conn,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -72,7 +72,7 @@ impl<'c, C> ConnectionSource<'c, C>
|
||||
where
|
||||
C: Connect,
|
||||
{
|
||||
pub(crate) async fn resolve_by_ref(&mut self) -> crate::Result<MaybeOwnedConnection<'_, C>> {
|
||||
pub(crate) async fn resolve_by_ref(&mut self) -> crate::Result<&'_ mut C> {
|
||||
if let ConnectionSource::Pool(pool) = self {
|
||||
*self =
|
||||
ConnectionSource::Connection(MaybeOwnedConnection::Owned(pool.acquire().await?));
|
||||
|
@ -3,6 +3,7 @@ use std::fmt::Display;
|
||||
use crate::arguments::Arguments;
|
||||
use crate::connection::{Connect, Connection};
|
||||
use crate::cursor::Cursor;
|
||||
use crate::query_as::QueryAs;
|
||||
use crate::row::Row;
|
||||
use crate::types::TypeInfo;
|
||||
|
||||
|
@ -8,7 +8,7 @@ use crate::database::{Database, HasRawValue};
|
||||
/// Decode a single value from the database.
|
||||
pub trait Decode<'de, DB>
|
||||
where
|
||||
Self: Sized,
|
||||
Self: Sized + 'de,
|
||||
DB: HasRawValue<'de>,
|
||||
{
|
||||
fn decode(value: DB::RawValue) -> crate::Result<Self>;
|
||||
|
@ -36,7 +36,7 @@ where
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn buffer(&self) -> &[u8] {
|
||||
pub fn buffer<'c>(&'c self) -> &'c [u8] {
|
||||
&self.rbuf[self.rbuf_rindex..]
|
||||
}
|
||||
|
||||
|
@ -32,6 +32,10 @@ pub mod describe;
|
||||
pub mod encode;
|
||||
pub mod pool;
|
||||
pub mod query;
|
||||
|
||||
#[macro_use]
|
||||
pub mod query_as;
|
||||
|
||||
pub mod types;
|
||||
|
||||
#[macro_use]
|
||||
|
@ -183,10 +183,9 @@ async fn next<'a, 'c: 'a, 'q: 'a>(
|
||||
}
|
||||
|
||||
Message::DataRow => {
|
||||
let data = DataRow::read(&mut *conn)?;
|
||||
let data = DataRow::read(conn.stream.buffer(), &mut conn.current_row_values)?;
|
||||
|
||||
return Ok(Some(PgRow {
|
||||
connection: conn,
|
||||
columns: Arc::clone(&cursor.columns),
|
||||
formats: Arc::clone(&cursor.formats),
|
||||
data,
|
||||
|
@ -23,3 +23,8 @@ mod types;
|
||||
|
||||
/// An alias for [`Pool`][crate::Pool], specialized for **Postgres**.
|
||||
pub type PgPool = super::Pool<PgConnection>;
|
||||
|
||||
make_query_as!(PgQueryAs, Postgres, PgRow);
|
||||
impl_map_row_for_row!(Postgres, PgRow);
|
||||
impl_column_index_for_row!(Postgres);
|
||||
impl_from_row_for_tuples!(Postgres, PgRow);
|
||||
|
@ -5,31 +5,36 @@ use byteorder::NetworkEndian;
|
||||
use std::fmt::{self, Debug};
|
||||
use std::ops::Range;
|
||||
|
||||
pub struct DataRow {
|
||||
pub struct DataRow<'c> {
|
||||
len: u16,
|
||||
buffer: &'c [u8],
|
||||
values: &'c [Option<Range<u32>>],
|
||||
}
|
||||
|
||||
impl DataRow {
|
||||
impl<'c> DataRow<'c> {
|
||||
pub fn len(&self) -> usize {
|
||||
self.len as usize
|
||||
}
|
||||
|
||||
pub fn get<'a>(
|
||||
pub fn get(
|
||||
&self,
|
||||
buffer: &'a [u8],
|
||||
values: &[Option<Range<u32>>],
|
||||
// buffer: &'c [u8],
|
||||
// values: &[Option<Range<u32>>],
|
||||
index: usize,
|
||||
) -> Option<&'a [u8]> {
|
||||
let range = values[index].as_ref()?;
|
||||
) -> Option<&'c [u8]> {
|
||||
let range = self.values[index].as_ref()?;
|
||||
|
||||
Some(&buffer[(range.start as usize)..(range.end as usize)])
|
||||
Some(&self.buffer[(range.start as usize)..(range.end as usize)])
|
||||
}
|
||||
}
|
||||
|
||||
impl DataRow {
|
||||
pub(crate) fn read(connection: &mut PgConnection) -> crate::Result<Self> {
|
||||
let buffer = connection.stream.buffer();
|
||||
let values = &mut connection.current_row_values;
|
||||
impl<'c> DataRow<'c> {
|
||||
pub(crate) fn read(
|
||||
buffer: &'c [u8],
|
||||
values: &'c mut Vec<Option<Range<u32>>>,
|
||||
) -> crate::Result<Self> {
|
||||
// let buffer = connection.stream.buffer();
|
||||
// let values = &mut connection.current_row_values;
|
||||
|
||||
values.clear();
|
||||
|
||||
@ -57,6 +62,10 @@ impl DataRow {
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self { len })
|
||||
Ok(Self {
|
||||
len,
|
||||
buffer,
|
||||
values,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -33,8 +33,7 @@ impl<'c> TryFrom<Option<PgValue<'c>>> for PgValue<'c> {
|
||||
}
|
||||
|
||||
pub struct PgRow<'c> {
|
||||
pub(super) connection: MaybeOwnedConnection<'c, PgConnection>,
|
||||
pub(super) data: DataRow,
|
||||
pub(super) data: DataRow<'c>,
|
||||
pub(super) columns: Arc<HashMap<Box<str>, usize>>,
|
||||
pub(super) formats: Arc<[TypeFormat]>,
|
||||
}
|
||||
@ -46,18 +45,14 @@ impl<'c> Row<'c> for PgRow<'c> {
|
||||
self.data.len()
|
||||
}
|
||||
|
||||
fn get_raw<'i, I>(&'c self, index: I) -> crate::Result<Option<PgValue<'c>>>
|
||||
fn get_raw<'r, I>(&'r self, index: I) -> crate::Result<Option<PgValue<'c>>>
|
||||
where
|
||||
I: ColumnIndex<'c, Self> + 'i,
|
||||
I: ColumnIndex<Self::Database>,
|
||||
{
|
||||
let index = index.resolve(self)?;
|
||||
let buffer = self.data.get(index);
|
||||
|
||||
self.data
|
||||
.get(
|
||||
self.connection.stream.buffer(),
|
||||
&self.connection.current_row_values,
|
||||
index,
|
||||
)
|
||||
buffer
|
||||
.map(|buf| match self.formats[index] {
|
||||
TypeFormat::Binary => Ok(PgValue::Binary(buf)),
|
||||
TypeFormat::Text => Ok(PgValue::Text(from_utf8(buf)?)),
|
||||
@ -66,7 +61,3 @@ impl<'c> Row<'c> for PgRow<'c> {
|
||||
.map_err(|err: Utf8Error| crate::Error::Decode(Box::new(err)))
|
||||
}
|
||||
}
|
||||
|
||||
impl_map_row_for_row!(Postgres, PgRow);
|
||||
impl_column_index_for_row!(PgRow);
|
||||
impl_from_row_for_row!(PgRow);
|
||||
|
@ -9,12 +9,12 @@ use crate::postgres::PgError;
|
||||
use crate::url::Url;
|
||||
|
||||
pub struct PgStream {
|
||||
stream: BufStream<MaybeTlsStream>,
|
||||
pub(super) stream: BufStream<MaybeTlsStream>,
|
||||
|
||||
// Most recently received message
|
||||
// Is referenced by our buffered stream
|
||||
// Is initialized to ReadyForQuery/0 at the start
|
||||
message: (Message, u32),
|
||||
pub(super) message: (Message, u32),
|
||||
}
|
||||
|
||||
impl PgStream {
|
||||
|
@ -1,7 +1,7 @@
|
||||
use std::convert::TryInto;
|
||||
use std::str::FromStr;
|
||||
|
||||
use byteorder::{ReadBytesExt, NetworkEndian};
|
||||
use byteorder::{NetworkEndian, ReadBytesExt};
|
||||
|
||||
use crate::decode::Decode;
|
||||
use crate::encode::Encode;
|
||||
|
@ -134,29 +134,6 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<'q, DB, F> Map<'q, DB, F>
|
||||
where
|
||||
DB: Database,
|
||||
{
|
||||
/// Bind a value for use with this SQL query.
|
||||
pub fn bind<T>(mut self, value: T) -> Self
|
||||
where
|
||||
T: Type<DB>,
|
||||
T: Encode<DB>,
|
||||
{
|
||||
self.query.arguments.add(value);
|
||||
self
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn bind_all(self, arguments: DB::Arguments) -> Map<'q, DB, F, ImmutableArguments<DB>> {
|
||||
Map {
|
||||
query: self.query.bind_all(arguments),
|
||||
mapper: self.mapper,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'q, DB, F, A> Map<'q, DB, F, A>
|
||||
where
|
||||
DB: Database,
|
||||
@ -258,13 +235,3 @@ where
|
||||
query: sql,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn query_as<T, DB>(
|
||||
sql: &str,
|
||||
) -> Map<DB, for<'c> fn(<DB as HasRow<'c>>::Row) -> crate::Result<T>>
|
||||
where
|
||||
DB: Database,
|
||||
T: Unpin + for<'c> FromRow<'c, <DB as HasRow<'c>>::Row>,
|
||||
{
|
||||
query(sql).map(|row| T::from_row(row))
|
||||
}
|
||||
|
204
sqlx-core/src/query_as.rs
Normal file
204
sqlx-core/src/query_as.rs
Normal file
@ -0,0 +1,204 @@
|
||||
use core::marker::PhantomData;
|
||||
|
||||
use async_stream::try_stream;
|
||||
use futures_core::future::BoxFuture;
|
||||
use futures_core::stream::Stream;
|
||||
use futures_util::future::ready;
|
||||
use futures_util::future::TryFutureExt;
|
||||
|
||||
use crate::arguments::Arguments;
|
||||
use crate::cursor::Cursor;
|
||||
use crate::database::{Database, HasRow};
|
||||
use crate::encode::Encode;
|
||||
use crate::executor::{Execute, RefExecutor};
|
||||
use crate::row::FromRow;
|
||||
use crate::types::Type;
|
||||
|
||||
/// Raw SQL query with bind parameters, mapped to a concrete type
|
||||
/// using [`FromRow`](trait.FromRow.html). Returned
|
||||
/// by [`query_as`](fn.query_as.html).
|
||||
pub struct QueryAs<'q, DB, O>
|
||||
where
|
||||
DB: Database,
|
||||
{
|
||||
query: &'q str,
|
||||
arguments: <DB as Database>::Arguments,
|
||||
database: PhantomData<DB>,
|
||||
output: PhantomData<O>,
|
||||
}
|
||||
|
||||
impl<'q, DB, O> QueryAs<'q, DB, O>
|
||||
where
|
||||
DB: Database,
|
||||
{
|
||||
/// Bind a value for use with this SQL query.
|
||||
#[inline]
|
||||
pub fn bind<T>(mut self, value: T) -> Self
|
||||
where
|
||||
T: Type<DB>,
|
||||
T: Encode<DB>,
|
||||
{
|
||||
self.arguments.add(value);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'q, DB, O: Send> Execute<'q, DB> for QueryAs<'q, DB, O>
|
||||
where
|
||||
DB: Database,
|
||||
{
|
||||
#[inline]
|
||||
fn into_parts(self) -> (&'q str, Option<<DB as Database>::Arguments>) {
|
||||
(self.query, Some(self.arguments))
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct a raw SQL query that is mapped to a concrete type
|
||||
/// using [`FromRow`](crate::row::FromRow).
|
||||
///
|
||||
/// Returns [`QueryAs`].
|
||||
pub fn query_as<DB, O>(sql: &str) -> QueryAs<DB, O>
|
||||
where
|
||||
DB: Database,
|
||||
{
|
||||
QueryAs {
|
||||
query: sql,
|
||||
arguments: Default::default(),
|
||||
database: PhantomData,
|
||||
output: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
// We need database-specific QueryAs traits to work around:
|
||||
// https://github.com/rust-lang/rust/issues/62529
|
||||
|
||||
// If for some reason we miss that issue being resolved in a _stable_ edition of
|
||||
// rust, please open up a 100 issues and shout as loud as you can to remove
|
||||
// this unseemly hack.
|
||||
|
||||
macro_rules! make_query_as {
|
||||
($name:ident, $db:ident, $row:ident) => {
|
||||
pub trait $name<'q, O> {
|
||||
fn fetch<'e, E>(
|
||||
self,
|
||||
executor: E,
|
||||
) -> futures_core::stream::BoxStream<'e, crate::Result<O>>
|
||||
where
|
||||
E: 'e + Send + crate::executor::RefExecutor<'e, Database = $db>,
|
||||
O: 'e + Send + Unpin + for<'c> crate::row::FromRow<'c, $row<'c>>,
|
||||
'q: 'e;
|
||||
|
||||
fn fetch_all<'e, E>(
|
||||
self,
|
||||
executor: E,
|
||||
) -> futures_core::future::BoxFuture<'e, crate::Result<Vec<O>>>
|
||||
where
|
||||
E: 'e + Send + crate::executor::RefExecutor<'e, Database = $db>,
|
||||
O: 'e + Send + for<'c> crate::row::FromRow<'c, $row<'c>>,
|
||||
'q: 'e;
|
||||
|
||||
fn fetch_one<'e, E>(
|
||||
self,
|
||||
executor: E,
|
||||
) -> futures_core::future::BoxFuture<'e, crate::Result<O>>
|
||||
where
|
||||
E: 'e + Send + crate::executor::RefExecutor<'e, Database = $db>,
|
||||
O: 'e + Send + for<'c> crate::row::FromRow<'c, $row<'c>>,
|
||||
'q: 'e;
|
||||
|
||||
fn fetch_optional<'e, E>(
|
||||
self,
|
||||
executor: E,
|
||||
) -> futures_core::future::BoxFuture<'e, crate::Result<Option<O>>>
|
||||
where
|
||||
E: 'e + Send + crate::executor::RefExecutor<'e, Database = $db>,
|
||||
O: 'e + Send + for<'c> crate::row::FromRow<'c, $row<'c>>,
|
||||
'q: 'e;
|
||||
}
|
||||
|
||||
impl<'q, O> $name<'q, O> for crate::query_as::QueryAs<'q, $db, O> {
|
||||
fn fetch<'e, E>(
|
||||
mut self,
|
||||
executor: E,
|
||||
) -> futures_core::stream::BoxStream<'e, crate::Result<O>>
|
||||
where
|
||||
E: 'e + Send + crate::executor::RefExecutor<'e, Database = $db>,
|
||||
O: 'e + Send + Unpin + for<'c> crate::row::FromRow<'c, $row<'c>>,
|
||||
'q: 'e,
|
||||
{
|
||||
use crate::cursor::Cursor;
|
||||
|
||||
Box::pin(async_stream::try_stream! {
|
||||
let mut cursor = executor.fetch_by_ref(self);
|
||||
|
||||
while let Some(row) = cursor.next().await? {
|
||||
let obj = O::from_row(row)?;
|
||||
|
||||
yield obj;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn fetch_optional<'e, E>(
|
||||
mut self,
|
||||
executor: E,
|
||||
) -> futures_core::future::BoxFuture<'e, crate::Result<Option<O>>>
|
||||
where
|
||||
E: 'e + Send + crate::executor::RefExecutor<'e, Database = $db>,
|
||||
O: 'e + Send + for<'c> crate::row::FromRow<'c, $row<'c>>,
|
||||
'q: 'e,
|
||||
{
|
||||
use crate::cursor::Cursor;
|
||||
|
||||
Box::pin(async move {
|
||||
let mut cursor = executor.fetch_by_ref(self);
|
||||
let row = cursor.next().await?;
|
||||
|
||||
row.map(O::from_row).transpose()
|
||||
})
|
||||
}
|
||||
|
||||
fn fetch_one<'e, E>(
|
||||
mut self,
|
||||
executor: E,
|
||||
) -> futures_core::future::BoxFuture<'e, crate::Result<O>>
|
||||
where
|
||||
E: 'e + Send + crate::executor::RefExecutor<'e, Database = $db>,
|
||||
O: 'e + Send + for<'c> crate::row::FromRow<'c, $row<'c>>,
|
||||
'q: 'e,
|
||||
{
|
||||
use futures_util::TryFutureExt;
|
||||
|
||||
Box::pin(self.fetch_optional(executor).and_then(|row| match row {
|
||||
Some(row) => futures_util::future::ready(Ok(row)),
|
||||
None => futures_util::future::ready(Err(crate::Error::RowNotFound)),
|
||||
}))
|
||||
}
|
||||
|
||||
fn fetch_all<'e, E>(
|
||||
mut self,
|
||||
executor: E,
|
||||
) -> futures_core::future::BoxFuture<'e, crate::Result<Vec<O>>>
|
||||
where
|
||||
E: 'e + Send + crate::executor::RefExecutor<'e, Database = $db>,
|
||||
O: 'e + Send + for<'c> crate::row::FromRow<'c, $row<'c>>,
|
||||
'q: 'e,
|
||||
{
|
||||
use crate::cursor::Cursor;
|
||||
|
||||
Box::pin(async move {
|
||||
let mut cursor = executor.fetch_by_ref(self);
|
||||
let mut out = Vec::new();
|
||||
|
||||
while let Some(row) = cursor.next().await? {
|
||||
let obj = O::from_row(row)?;
|
||||
|
||||
out.push(obj);
|
||||
}
|
||||
|
||||
Ok(out)
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
@ -1,14 +1,15 @@
|
||||
//! Contains the Row and FromRow traits.
|
||||
|
||||
use crate::database::{Database, HasRawValue};
|
||||
use crate::database::{Database, HasRawValue, HasRow};
|
||||
use crate::decode::Decode;
|
||||
use crate::types::Type;
|
||||
|
||||
pub trait ColumnIndex<'c, R: ?Sized>
|
||||
pub trait ColumnIndex<DB>
|
||||
where
|
||||
R: Row<'c>,
|
||||
DB: Database,
|
||||
DB: for<'c> HasRow<'c, Database = DB>,
|
||||
{
|
||||
fn resolve(self, row: &'c R) -> crate::Result<usize>;
|
||||
fn resolve<'c>(self, row: &<DB as HasRow<'c>>::Row) -> crate::Result<usize>;
|
||||
}
|
||||
|
||||
/// Represents a single row of the result set.
|
||||
@ -23,28 +24,28 @@ pub trait Row<'c>: Unpin + Send {
|
||||
/// Returns the number of values in the row.
|
||||
fn len(&self) -> usize;
|
||||
|
||||
fn get<T, I>(&'c self, index: I) -> crate::Result<T>
|
||||
fn get<'r, T, I>(&'r self, index: I) -> crate::Result<T>
|
||||
where
|
||||
T: Type<Self::Database>,
|
||||
I: ColumnIndex<'c, Self>,
|
||||
I: ColumnIndex<Self::Database>,
|
||||
T: Decode<'c, Self::Database>,
|
||||
{
|
||||
Ok(Decode::decode(self.get_raw(index)?)?)
|
||||
}
|
||||
|
||||
fn get_raw<'i, I>(
|
||||
&'c self,
|
||||
fn get_raw<'r, I>(
|
||||
&self,
|
||||
index: I,
|
||||
) -> crate::Result<<Self::Database as HasRawValue<'c>>::RawValue>
|
||||
where
|
||||
I: ColumnIndex<'c, Self> + 'i;
|
||||
I: ColumnIndex<Self::Database>;
|
||||
}
|
||||
|
||||
/// A **record** that can be built from a row returned from by the database.
|
||||
pub trait FromRow<'a, R>
|
||||
pub trait FromRow<'c, R>
|
||||
where
|
||||
Self: Sized,
|
||||
R: Row<'a>,
|
||||
R: Row<'c>,
|
||||
{
|
||||
fn from_row(row: R) -> crate::Result<Self>;
|
||||
}
|
||||
@ -52,6 +53,101 @@ where
|
||||
// Macros to help unify the internal implementations as a good chunk
|
||||
// is very similar
|
||||
|
||||
#[allow(unused_macros)]
|
||||
macro_rules! impl_from_row_for_tuple {
|
||||
($db:ident, $r:ident; $( ($idx:tt) -> $T:ident );+;) => {
|
||||
impl<'c, $($T,)+> crate::row::FromRow<'c, $r<'c>> for ($($T,)+)
|
||||
where
|
||||
$($T: crate::types::Type<$db>,)+
|
||||
$($T: crate::decode::Decode<'c, $db>,)+
|
||||
{
|
||||
#[inline]
|
||||
fn from_row(row: $r<'c>) -> crate::Result<Self> {
|
||||
use crate::row::Row;
|
||||
|
||||
Ok(($(row.get($idx as usize)?,)+))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[allow(unused_macros)]
|
||||
macro_rules! impl_from_row_for_tuples {
|
||||
($db:ident, $r:ident) => {
|
||||
impl_from_row_for_tuple!($db, $r;
|
||||
(0) -> T1;
|
||||
);
|
||||
|
||||
impl_from_row_for_tuple!($db, $r;
|
||||
(0) -> T1;
|
||||
(1) -> T2;
|
||||
);
|
||||
|
||||
impl_from_row_for_tuple!($db, $r;
|
||||
(0) -> T1;
|
||||
(1) -> T2;
|
||||
(2) -> T3;
|
||||
);
|
||||
|
||||
impl_from_row_for_tuple!($db, $r;
|
||||
(0) -> T1;
|
||||
(1) -> T2;
|
||||
(2) -> T3;
|
||||
(3) -> T4;
|
||||
);
|
||||
|
||||
impl_from_row_for_tuple!($db, $r;
|
||||
(0) -> T1;
|
||||
(1) -> T2;
|
||||
(2) -> T3;
|
||||
(3) -> T4;
|
||||
(4) -> T5;
|
||||
);
|
||||
|
||||
impl_from_row_for_tuple!($db, $r;
|
||||
(0) -> T1;
|
||||
(1) -> T2;
|
||||
(2) -> T3;
|
||||
(3) -> T4;
|
||||
(4) -> T5;
|
||||
(5) -> T6;
|
||||
);
|
||||
|
||||
impl_from_row_for_tuple!($db, $r;
|
||||
(0) -> T1;
|
||||
(1) -> T2;
|
||||
(2) -> T3;
|
||||
(3) -> T4;
|
||||
(4) -> T5;
|
||||
(5) -> T6;
|
||||
(6) -> T7;
|
||||
);
|
||||
|
||||
impl_from_row_for_tuple!($db, $r;
|
||||
(0) -> T1;
|
||||
(1) -> T2;
|
||||
(2) -> T3;
|
||||
(3) -> T4;
|
||||
(4) -> T5;
|
||||
(5) -> T6;
|
||||
(6) -> T7;
|
||||
(7) -> T8;
|
||||
);
|
||||
|
||||
impl_from_row_for_tuple!($db, $r;
|
||||
(0) -> T1;
|
||||
(1) -> T2;
|
||||
(2) -> T3;
|
||||
(3) -> T4;
|
||||
(4) -> T5;
|
||||
(5) -> T6;
|
||||
(6) -> T7;
|
||||
(7) -> T8;
|
||||
(8) -> T9;
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
#[allow(unused_macros)]
|
||||
macro_rules! impl_map_row_for_row {
|
||||
($DB:ident, $R:ident) => {
|
||||
@ -70,22 +166,27 @@ macro_rules! impl_map_row_for_row {
|
||||
|
||||
#[allow(unused_macros)]
|
||||
macro_rules! impl_column_index_for_row {
|
||||
($R:ident) => {
|
||||
impl<'c> crate::row::ColumnIndex<'c, $R<'c>> for usize {
|
||||
fn resolve(self, row: &'c $R<'c>) -> crate::Result<usize> {
|
||||
if self >= row.len() {
|
||||
return Err(crate::Error::ColumnIndexOutOfBounds {
|
||||
len: row.len(),
|
||||
index: self,
|
||||
});
|
||||
($DB:ident) => {
|
||||
impl crate::row::ColumnIndex<$DB> for usize {
|
||||
fn resolve<'c>(
|
||||
self,
|
||||
row: &<$DB as crate::database::HasRow<'c>>::Row,
|
||||
) -> crate::Result<usize> {
|
||||
let len = crate::row::Row::len(row);
|
||||
|
||||
if self >= len {
|
||||
return Err(crate::Error::ColumnIndexOutOfBounds { len, index: self });
|
||||
}
|
||||
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'c> crate::row::ColumnIndex<'c, $R<'c>> for &'_ str {
|
||||
fn resolve(self, row: &'c $R<'c>) -> crate::Result<usize> {
|
||||
impl crate::row::ColumnIndex<$DB> for &'_ str {
|
||||
fn resolve<'c>(
|
||||
self,
|
||||
row: &<$DB as crate::database::HasRow<'c>>::Row,
|
||||
) -> crate::Result<usize> {
|
||||
row.columns
|
||||
.get(self)
|
||||
.ok_or_else(|| crate::Error::ColumnNotFound((*self).into()))
|
||||
|
12
sqlx-test/Cargo.toml
Normal file
12
sqlx-test/Cargo.toml
Normal file
@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "sqlx-test"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
sqlx = { default-features = false, path = ".." }
|
||||
env_logger = "0.7.1"
|
||||
dotenv = "0.15.0"
|
||||
anyhow = "1.0.26"
|
||||
async-std = { version = "1.4.0", features = [ "attributes" ] }
|
||||
tokio = { version = "0.2.9", features = [ "full" ] }
|
81
sqlx-test/src/lib.rs
Normal file
81
sqlx-test/src/lib.rs
Normal file
@ -0,0 +1,81 @@
|
||||
use sqlx::{Connect, Database};
|
||||
|
||||
fn setup_if_needed() {
|
||||
let _ = dotenv::dotenv();
|
||||
let _ = env_logger::try_init();
|
||||
}
|
||||
|
||||
// Make a new connection
|
||||
// Ensure [dotenv] and [env_logger] have been setup
|
||||
pub async fn new<DB>() -> anyhow::Result<DB::Connection>
|
||||
where
|
||||
DB: Database,
|
||||
{
|
||||
setup_if_needed();
|
||||
|
||||
Ok(DB::Connection::connect(dotenv::var("DATABASE_URL")?).await?)
|
||||
}
|
||||
|
||||
// Test type encoding and decoding
|
||||
#[macro_export]
|
||||
macro_rules! test_type {
|
||||
($name:ident($db:ident, $ty:ty, $($text:literal == $value:expr),+)) => {
|
||||
$crate::test_prepared_type!($name($db, $ty, $($text == $value),+));
|
||||
// $crate::test_unprepared_type!($name($db, $ty, $($text == $value),+));
|
||||
}
|
||||
}
|
||||
|
||||
// Test type decoding for the simple (unprepared) query API
|
||||
#[macro_export]
|
||||
macro_rules! test_unprepared_type {
|
||||
($name:ident($db:ident, $ty:ty, $($text:literal == $value:expr),+)) => {
|
||||
paste::item! {
|
||||
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
|
||||
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
|
||||
async fn [< test_unprepared_type_ $name >] () -> anyhow::Result<()> {
|
||||
let mut conn = sqlx_test::new::<$db>().await?;
|
||||
|
||||
$(
|
||||
let query = format!("SELECT {} as _1", $text);
|
||||
let mut cursor = conn.fetch(&*query);
|
||||
let row = cursor.next().await?.unwrap();
|
||||
let rec = row.get::<$ty, _>("_1")?;
|
||||
|
||||
assert!($value == rec);
|
||||
)+
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test type encoding and decoding for the prepared query API
|
||||
#[macro_export]
|
||||
macro_rules! test_prepared_type {
|
||||
($name:ident($db:ident, $ty:ty, $($text:literal == $value:expr),+)) => {
|
||||
paste::item! {
|
||||
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
|
||||
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
|
||||
async fn [< test_prepared_type_ $name >] () -> anyhow::Result<()> {
|
||||
use sqlx::prelude::*;
|
||||
|
||||
let mut conn = sqlx_test::new::<$db>().await?;
|
||||
|
||||
$(
|
||||
let query = format!("SELECT {} = $1, $1 as _1", $text);
|
||||
|
||||
let rec: (bool, $ty) = sqlx::query_as(&query)
|
||||
.bind($value)
|
||||
.fetch_one(&mut conn)
|
||||
.await?;
|
||||
|
||||
assert!(rec.0);
|
||||
assert!($value == rec.1);
|
||||
)+
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
14
src/lib.rs
14
src/lib.rs
@ -8,18 +8,17 @@ compile_error!("one of 'runtime-async-std' or 'runtime-tokio' features must be e
|
||||
compile_error!("only one of 'runtime-async-std' or 'runtime-tokio' features must be enabled");
|
||||
|
||||
// Modules
|
||||
pub use sqlx_core::{arguments, describe, error, pool, query, row, types};
|
||||
pub use sqlx_core::{arguments, describe, error, pool, row, types};
|
||||
|
||||
// Types
|
||||
pub use sqlx_core::{
|
||||
query::Query, Connect, Connection, Cursor, Error, Execute, Executor, FromRow, Pool, Result, Row,
|
||||
Connect, Connection, Cursor, Error, Execute, Executor, FromRow, Pool, Result, Row,
|
||||
Transaction
|
||||
};
|
||||
|
||||
pub use sqlx_core::database::{Database, HasCursor, HasRawValue, HasRow};
|
||||
|
||||
// Functions
|
||||
pub use query::{query, query_as};
|
||||
pub use sqlx_core::query::{self, query, Query};
|
||||
pub use sqlx_core::query_as::{query_as, QueryAs};
|
||||
|
||||
#[cfg(feature = "mysql")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "mysql")))]
|
||||
@ -58,3 +57,8 @@ pub mod decode {
|
||||
#[cfg(feature = "macros")]
|
||||
pub use sqlx_macros::Decode;
|
||||
}
|
||||
|
||||
pub mod prelude {
|
||||
#[cfg(feature = "postgres")]
|
||||
pub use super::postgres::PgQueryAs as _;
|
||||
}
|
||||
|
71
tests/postgres-raw.rs
Normal file
71
tests/postgres-raw.rs
Normal file
@ -0,0 +1,71 @@
|
||||
//! Tests for the raw (unprepared) query API for Postgres.
|
||||
|
||||
use sqlx::{Cursor, Executor, Postgres, Row};
|
||||
use sqlx_test::new;
|
||||
|
||||
/// Tests the edge case of executing a completely empty query string.
|
||||
///
|
||||
/// This gets flagged as an `EmptyQueryResponse` in Postgres. We currently
|
||||
/// catch this and just return no rows.
|
||||
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
|
||||
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
|
||||
async fn test_empty_query() -> anyhow::Result<()> {
|
||||
let mut conn = new::<Postgres>().await?;
|
||||
let affected = conn.execute("").await?;
|
||||
|
||||
assert_eq!(affected, 0);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Test a simple select expression. This should return the row.
|
||||
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
|
||||
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
|
||||
async fn test_select_expression() -> anyhow::Result<()> {
|
||||
let mut conn = new::<Postgres>().await?;
|
||||
|
||||
let mut cursor = conn.fetch("SELECT 5");
|
||||
let row = cursor.next().await?.unwrap();
|
||||
|
||||
assert!(5i32 == row.get::<i32, _>(0)?);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Test that we can interleave reads and writes to the database
|
||||
/// in one simple query. Using the `Cursor` API we should be
|
||||
/// able to fetch from both queries in sequence.
|
||||
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
|
||||
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
|
||||
async fn test_multi_read_write() -> anyhow::Result<()> {
|
||||
let mut conn = new::<Postgres>().await?;
|
||||
|
||||
let mut cursor = conn.fetch(
|
||||
"
|
||||
CREATE TABLE IF NOT EXISTS _sqlx_test_postgres_5112 (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
text TEXT NOT NULL
|
||||
);
|
||||
|
||||
SELECT 'Hello World' as _1;
|
||||
|
||||
INSERT INTO _sqlx_test_postgres_5112 (text) VALUES ('this is a test');
|
||||
|
||||
SELECT id, text FROM _sqlx_test_postgres_5112;
|
||||
",
|
||||
);
|
||||
|
||||
let row = cursor.next().await?.unwrap();
|
||||
|
||||
assert!("Hello World" == row.get::<&str, _>("_1")?);
|
||||
|
||||
let row = cursor.next().await?.unwrap();
|
||||
|
||||
let id: i64 = row.get("id")?;
|
||||
let text: &str = row.get("text")?;
|
||||
|
||||
assert_eq!(1_i64, id);
|
||||
assert_eq!("this is a test", text);
|
||||
|
||||
Ok(())
|
||||
}
|
@ -1,99 +0,0 @@
|
||||
use sqlx::{Connect, Executor, Cursor, Row, PgConnection};
|
||||
use sqlx::postgres::PgRow;
|
||||
|
||||
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
|
||||
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
|
||||
async fn test_empty_query() -> anyhow::Result<()> {
|
||||
let mut conn = connect().await?;
|
||||
let affected = conn.execute("").await?;
|
||||
|
||||
assert_eq!(affected, 0);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
|
||||
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
|
||||
async fn test_select_1() -> anyhow::Result<()> {
|
||||
let mut conn = connect().await?;
|
||||
|
||||
let mut cursor = conn.fetch("SELECT 5");
|
||||
let row = cursor.next().await?.unwrap();
|
||||
|
||||
assert!(5i32 == row.get::<i32, _>(0)?);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
|
||||
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
|
||||
async fn test_multi_create_insert() -> anyhow::Result<()> {
|
||||
let mut conn = connect().await?;
|
||||
|
||||
let mut cursor = conn.fetch("
|
||||
CREATE TABLE IF NOT EXISTS _sqlx_test_postgres_5112 (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
text TEXT NOT NULL
|
||||
);
|
||||
|
||||
SELECT 'Hello World';
|
||||
|
||||
INSERT INTO _sqlx_test_postgres_5112 (text) VALUES ('this is a test');
|
||||
|
||||
SELECT id, text FROM _sqlx_test_postgres_5112;
|
||||
");
|
||||
|
||||
let row = cursor.next().await?.unwrap();
|
||||
|
||||
assert!("Hello World" == row.get::<&str, _>(0)?);
|
||||
|
||||
let row = cursor.next().await?.unwrap();
|
||||
|
||||
let id: i64 = row.get(0)?;
|
||||
let text: &str = row.get(1)?;
|
||||
|
||||
assert!(1_i64 == id);
|
||||
assert!("this is a test" == text);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
macro_rules! test {
|
||||
($name:ident: $ty:ty: $($text:literal == $value:expr),+) => {
|
||||
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
|
||||
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
|
||||
async fn $name () -> anyhow::Result<()> {
|
||||
let mut conn = connect().await?;
|
||||
|
||||
$(
|
||||
let rec: $ty = sqlx::query(&format!("SELECT $1 as _1"))
|
||||
.bind($value)
|
||||
.map(|row: PgRow| row.get(0))
|
||||
.fetch_one(&mut conn)
|
||||
.await?;
|
||||
|
||||
assert!($value == rec);
|
||||
)+
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test!(postgres_simple_bool: bool: "false::boolean" == false, "true::boolean" == true);
|
||||
|
||||
test!(postgres_simple_smallint: i16: "821::smallint" == 821_i16);
|
||||
test!(postgres_simple_int: i32: "94101::int" == 94101_i32);
|
||||
test!(postgres_simple_bigint: i64: "9358295312::bigint" == 9358295312_i64);
|
||||
|
||||
test!(postgres_simple_real: f32: "9419.122::real" == 9419.122_f32);
|
||||
test!(postgres_simple_double: f64: "939399419.1225182::double precision" == 939399419.1225182_f64);
|
||||
|
||||
test!(postgres_simple_text: String: "'this is foo'" == "this is foo", "''" == "");
|
||||
|
||||
async fn connect() -> anyhow::Result<PgConnection> {
|
||||
let _ = dotenv::dotenv();
|
||||
let _ = env_logger::try_init();
|
||||
|
||||
Ok(PgConnection::connect(dotenv::var("DATABASE_URL")?).await?)
|
||||
}
|
@ -1,59 +1,50 @@
|
||||
use sqlx::postgres::PgRow;
|
||||
use sqlx::{Connect, PgConnection, Row};
|
||||
use sqlx::Postgres;
|
||||
use sqlx_test::test_type;
|
||||
|
||||
async fn connect() -> anyhow::Result<PgConnection> {
|
||||
Ok(PgConnection::connect(dotenv::var("DATABASE_URL")?).await?)
|
||||
}
|
||||
test_type!(bool(
|
||||
Postgres,
|
||||
bool,
|
||||
"false::boolean" == false,
|
||||
"true::boolean" == true
|
||||
));
|
||||
|
||||
macro_rules! test {
|
||||
($name:ident: $ty:ty: $($text:literal == $value:expr),+) => {
|
||||
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
|
||||
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
|
||||
async fn $name () -> anyhow::Result<()> {
|
||||
let mut conn = connect().await?;
|
||||
test_type!(i16(Postgres, i16, "821::smallint" == 821_i16));
|
||||
test_type!(i32(Postgres, i32, "94101::int" == 94101_i32));
|
||||
test_type!(i64(Postgres, i64, "9358295312::bigint" == 9358295312_i64));
|
||||
|
||||
$(
|
||||
let rec: (bool, $ty) = sqlx::query(&format!("SELECT {} = $1, $1 as _1", $text))
|
||||
.bind($value)
|
||||
.map(|row: PgRow| Ok((row.get(0)?, row.get(1)?)))
|
||||
.fetch_one(&mut conn)
|
||||
.await?;
|
||||
test_type!(f32(Postgres, f32, "9419.122::real" == 9419.122_f32));
|
||||
test_type!(f64(
|
||||
Postgres,
|
||||
f64,
|
||||
"939399419.1225182::double precision" == 939399419.1225182_f64
|
||||
));
|
||||
|
||||
assert!(rec.0);
|
||||
assert!($value == rec.1);
|
||||
)+
|
||||
test_type!(string(
|
||||
Postgres,
|
||||
String,
|
||||
"'this is foo'" == "this is foo",
|
||||
"''" == ""
|
||||
));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO: BYTEA
|
||||
// TODO: UUID
|
||||
// TODO: CHRONO
|
||||
|
||||
test!(postgres_bool: bool: "false::boolean" == false, "true::boolean" == true);
|
||||
|
||||
test!(postgres_smallint: i16: "821::smallint" == 821_i16);
|
||||
test!(postgres_int: i32: "94101::int" == 94101_i32);
|
||||
test!(postgres_bigint: i64: "9358295312::bigint" == 9358295312_i64);
|
||||
|
||||
test!(postgres_real: f32: "9419.122::real" == 9419.122_f32);
|
||||
test!(postgres_double: f64: "939399419.1225182::double precision" == 939399419.1225182_f64);
|
||||
|
||||
test!(postgres_text: String: "'this is foo'" == "this is foo", "''" == "");
|
||||
|
||||
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
|
||||
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
|
||||
async fn postgres_bytes() -> anyhow::Result<()> {
|
||||
let mut conn = connect().await?;
|
||||
|
||||
let value = b"Hello, World";
|
||||
|
||||
let rec: (bool, Vec<u8>) = sqlx::query("SELECT E'\\\\x48656c6c6f2c20576f726c64' = $1, $1")
|
||||
.bind(&value[..])
|
||||
.map(|row: PgRow| Ok((row.get(0)?, row.get(1)?)))
|
||||
.fetch_one(&mut conn)
|
||||
.await?;
|
||||
|
||||
assert!(rec.0);
|
||||
assert_eq!(&value[..], &*rec.1);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
// #[cfg_attr(feature = "runtime-async-std", async_std::test)]
|
||||
// #[cfg_attr(feature = "runtime-tokio", tokio::test)]
|
||||
// async fn postgres_bytes() -> anyhow::Result<()> {
|
||||
// let mut conn = connect().await?;
|
||||
//
|
||||
// let value = b"Hello, World";
|
||||
//
|
||||
// let rec: (bool, Vec<u8>) = sqlx::query("SELECT E'\\\\x48656c6c6f2c20576f726c64' = $1, $1")
|
||||
// .bind(&value[..])
|
||||
// .map(|row: PgRow| Ok((row.get(0)?, row.get(1)?)))
|
||||
// .fetch_one(&mut conn)
|
||||
// .await?;
|
||||
//
|
||||
// assert!(rec.0);
|
||||
// assert_eq!(&value[..], &*rec.1);
|
||||
//
|
||||
// Ok(())
|
||||
// }
|
||||
|
@ -4,4 +4,4 @@ error: "\'1" is not a valid Rust identifier
|
||||
2 | let query = sqlx::query!("select 1 as \"'1\"");
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)
|
||||
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
Loading…
x
Reference in New Issue
Block a user