mirror of
https://github.com/launchbadge/sqlx.git
synced 2026-03-27 13:46:32 +00:00
WIP [next]: implement generalized query placeholders
Signed-off-by: Austin Bonander <austin@launchbadge.com>
This commit is contained in:
@@ -42,9 +42,17 @@ bitflags = "1.2"
|
||||
base64 = "0.13.0"
|
||||
md-5 = "0.9.1"
|
||||
itoa = "0.4.7"
|
||||
paste = "1.0.5"
|
||||
|
||||
[dev-dependencies]
|
||||
sqlx-core = { version = "0.6.0-pre", path = "../sqlx-core", features = ["_mock"] }
|
||||
sqlx-test = { path = "../sqlx-test" }
|
||||
futures-executor = "0.3.8"
|
||||
anyhow = "1.0.37"
|
||||
conquer-once = "0.3.2"
|
||||
tokio = { version = "1.0", features = ["full"] }
|
||||
|
||||
[[test]]
|
||||
name = "postgres-connection"
|
||||
path = "tests/connection.rs"
|
||||
required-features = ["async", "sqlx-core/tokio"]
|
||||
|
||||
@@ -1,32 +1,39 @@
|
||||
use sqlx_core::{Result, Runtime};
|
||||
use sqlx_core::{placeholders, Result, Runtime};
|
||||
|
||||
use crate::protocol::backend::{
|
||||
BackendMessage, BackendMessageType, ParameterDescription, RowDescription,
|
||||
};
|
||||
use crate::protocol::frontend::{Describe, Parse, StatementRef, Sync, Target};
|
||||
use crate::protocol::frontend::{Describe, Parse, StatementId, Sync, Target};
|
||||
use crate::raw_statement::RawStatement;
|
||||
use crate::{PgArguments, PgClientError, PgConnection};
|
||||
use crate::{PgArguments, PgClientError, PgConnection, Postgres};
|
||||
use sqlx_core::arguments::ArgumentIndex;
|
||||
use sqlx_core::placeholders::{ArgumentKind, Placeholder};
|
||||
|
||||
impl<Rt: Runtime> PgConnection<Rt> {
|
||||
fn start_raw_prepare(
|
||||
&mut self,
|
||||
sql: &str,
|
||||
sql: &placeholders::ParsedQuery<'_>,
|
||||
arguments: &PgArguments<'_>,
|
||||
) -> Result<RawStatement> {
|
||||
let statement_id = self.next_statement_id;
|
||||
self.next_statement_id = self.next_statement_id.wrapping_add(1);
|
||||
let mut has_expansion = false;
|
||||
|
||||
let sql =
|
||||
sql.expand::<Postgres, _>(placeholder_get_argument(arguments, &mut has_expansion))?;
|
||||
|
||||
// if the query has a comma-expansion, we don't want to keep it as a named prepared statement
|
||||
let statement_id = if !has_expansion {
|
||||
let val = self.next_statement_id;
|
||||
self.next_statement_id = self.next_statement_id.wrapping_add(1);
|
||||
StatementId::Named(val)
|
||||
} else {
|
||||
StatementId::Unnamed
|
||||
};
|
||||
|
||||
let statement = RawStatement::new(statement_id);
|
||||
|
||||
self.stream.write_message(&Parse {
|
||||
statement: StatementRef::Named(statement.id),
|
||||
sql,
|
||||
arguments,
|
||||
})?;
|
||||
self.stream.write_message(&Parse { statement: statement.id, sql: &sql, arguments })?;
|
||||
|
||||
self.stream.write_message(&Describe {
|
||||
target: Target::Statement(StatementRef::Named(statement.id)),
|
||||
})?;
|
||||
self.stream.write_message(&Describe { target: Target::Statement(statement.id) })?;
|
||||
|
||||
self.stream.write_message(&Sync)?;
|
||||
|
||||
@@ -93,7 +100,7 @@ impl<Rt: Runtime> super::PgConnection<Rt> {
|
||||
#[cfg(feature = "async")]
|
||||
pub(crate) async fn raw_prepare_async(
|
||||
&mut self,
|
||||
sql: &str,
|
||||
sql: &placeholders::ParsedQuery<'_>,
|
||||
arguments: &PgArguments<'_>,
|
||||
) -> Result<RawStatement>
|
||||
where
|
||||
@@ -106,7 +113,7 @@ impl<Rt: Runtime> super::PgConnection<Rt> {
|
||||
#[cfg(feature = "blocking")]
|
||||
pub(crate) fn raw_prepare_blocking(
|
||||
&mut self,
|
||||
sql: &str,
|
||||
sql: &placeholders::ParsedQuery<'_>,
|
||||
arguments: &PgArguments<'_>,
|
||||
) -> Result<RawStatement>
|
||||
where
|
||||
@@ -126,3 +133,23 @@ macro_rules! raw_prepare {
|
||||
$self.raw_prepare_async($sql, $arguments).await?
|
||||
};
|
||||
}
|
||||
|
||||
fn placeholder_get_argument<'b, 'a: 'b>(
|
||||
arguments: &'b PgArguments<'_>,
|
||||
has_expansion: &'b mut bool,
|
||||
) -> impl FnMut(&ArgumentIndex<'_>, &Placeholder<'a>) -> Result<ArgumentKind, String> + 'b {
|
||||
move |idx, place| {
|
||||
// note: we don't need to print the argument cause it's included in the outer error
|
||||
let arg = arguments.get(idx).ok_or("unknown argument")?;
|
||||
|
||||
Ok(if place.kleene.is_some() {
|
||||
let len = arg.value().vector_len().ok_or("expected vector for argument")?;
|
||||
|
||||
*has_expansion = true;
|
||||
|
||||
ArgumentKind::Vector(len)
|
||||
} else {
|
||||
ArgumentKind::Scalar
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
use sqlx_core::{Execute, Result, Runtime};
|
||||
use sqlx_core::{placeholders, Arguments, Execute, Result, Runtime};
|
||||
|
||||
use crate::protocol::frontend::{self, Bind, PortalRef, Query, StatementRef, Sync};
|
||||
use crate::protocol::frontend::{self, Bind, PortalRef, Query, StatementId, Sync};
|
||||
use crate::raw_statement::RawStatement;
|
||||
use crate::{PgArguments, PgConnection, Postgres};
|
||||
use sqlx_core::arguments::ArgumentIndex;
|
||||
use sqlx_core::placeholders::{ArgumentKind, Placeholder};
|
||||
use std::borrow::Cow;
|
||||
|
||||
impl<Rt: Runtime> PgConnection<Rt> {
|
||||
fn write_raw_query_statement(
|
||||
@@ -13,7 +16,7 @@ impl<Rt: Runtime> PgConnection<Rt> {
|
||||
// bind values to the prepared statement
|
||||
self.stream.write_message(&Bind {
|
||||
portal: PortalRef::Unnamed,
|
||||
statement: StatementRef::Named(statement.id),
|
||||
statement: statement.id,
|
||||
arguments,
|
||||
parameters: &statement.parameters,
|
||||
})?;
|
||||
@@ -37,11 +40,17 @@ impl<Rt: Runtime> PgConnection<Rt> {
|
||||
|
||||
macro_rules! impl_raw_query {
|
||||
($(@$blocking:ident)? $self:ident, $query:ident) => {{
|
||||
let parsed = placeholders::parse_query($query.sql())?;
|
||||
|
||||
if let Some(arguments) = $query.arguments() {
|
||||
let statement = raw_prepare!($(@$blocking)? $self, $query.sql(), arguments);
|
||||
let statement = raw_prepare!($(@$blocking)? $self, &parsed, arguments);
|
||||
|
||||
$self.write_raw_query_statement(&statement, arguments)?;
|
||||
} else {
|
||||
if !parsed.placeholders().is_empty() {
|
||||
return Err(placeholders::Error::PreparedStatementsOnly.into());
|
||||
}
|
||||
|
||||
$self.stream.write_message(&Query { sql: $query.sql() })?;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use sqlx_core::database::{HasOutput, HasRawValue};
|
||||
use sqlx_core::placeholders;
|
||||
use sqlx_core::Database;
|
||||
|
||||
use super::{PgColumn, PgOutput, PgQueryResult, PgRawValue, PgRow, PgTypeId, PgTypeInfo};
|
||||
@@ -16,6 +17,9 @@ impl Database for Postgres {
|
||||
type TypeInfo = PgTypeInfo;
|
||||
|
||||
type TypeId = PgTypeId;
|
||||
|
||||
const PLACEHOLDER_CHAR: char = '$';
|
||||
const PARAM_INDEXING: placeholders::ParamIndexing = placeholders::ParamIndexing::OneIndexed;
|
||||
}
|
||||
|
||||
// 'x: execution
|
||||
|
||||
@@ -23,7 +23,7 @@ pub(crate) use password::{Password, PasswordMd5};
|
||||
pub(crate) use portal::PortalRef;
|
||||
pub(crate) use query::Query;
|
||||
pub(crate) use startup::Startup;
|
||||
pub(crate) use statement::StatementRef;
|
||||
pub(crate) use statement::StatementId;
|
||||
pub(crate) use sync::Sync;
|
||||
pub(crate) use target::Target;
|
||||
pub(crate) use terminate::Terminate;
|
||||
|
||||
@@ -4,13 +4,13 @@ use sqlx_core::io::Serialize;
|
||||
use sqlx_core::Result;
|
||||
|
||||
use crate::io::PgWriteExt;
|
||||
use crate::protocol::frontend::{PortalRef, StatementRef};
|
||||
use crate::protocol::frontend::{PortalRef, StatementId};
|
||||
use crate::{PgArguments, PgOutput, PgRawValueFormat, PgTypeInfo};
|
||||
use sqlx_core::encode::IsNull;
|
||||
|
||||
pub(crate) struct Bind<'a> {
|
||||
pub(crate) portal: PortalRef,
|
||||
pub(crate) statement: StatementRef,
|
||||
pub(crate) statement: StatementId,
|
||||
pub(crate) parameters: &'a [PgTypeInfo],
|
||||
pub(crate) arguments: &'a PgArguments<'a>,
|
||||
}
|
||||
|
||||
@@ -4,11 +4,11 @@ use sqlx_core::io::{Serialize, WriteExt};
|
||||
use sqlx_core::Result;
|
||||
|
||||
use crate::io::PgWriteExt;
|
||||
use crate::protocol::frontend::StatementRef;
|
||||
use crate::protocol::frontend::StatementId;
|
||||
use crate::{PgArguments, PgTypeId};
|
||||
|
||||
pub(crate) struct Parse<'a> {
|
||||
pub(crate) statement: StatementRef,
|
||||
pub(crate) statement: StatementId,
|
||||
pub(crate) sql: &'a str,
|
||||
pub(crate) arguments: &'a PgArguments<'a>,
|
||||
}
|
||||
|
||||
@@ -2,14 +2,14 @@ use sqlx_core::io::Serialize;
|
||||
use sqlx_core::Result;
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub(crate) enum StatementRef {
|
||||
pub(crate) enum StatementId {
|
||||
Unnamed,
|
||||
Named(u32),
|
||||
}
|
||||
|
||||
impl Serialize<'_> for StatementRef {
|
||||
impl Serialize<'_> for StatementId {
|
||||
fn serialize_with(&self, buf: &mut Vec<u8>, _: ()) -> Result<()> {
|
||||
if let StatementRef::Named(id) = self {
|
||||
if let StatementId::Named(id) = self {
|
||||
buf.extend_from_slice(b"_sqlx_s_");
|
||||
|
||||
itoa::write(&mut *buf, *id).unwrap();
|
||||
|
||||
@@ -2,14 +2,14 @@ use sqlx_core::io::Serialize;
|
||||
use sqlx_core::Result;
|
||||
|
||||
use crate::io::PgWriteExt;
|
||||
use crate::protocol::frontend::{PortalRef, StatementRef};
|
||||
use crate::protocol::frontend::{PortalRef, StatementId};
|
||||
|
||||
/// Target a command at a portal *or* statement.
|
||||
/// Used by [`Describe`] and [`Close`].
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum Target {
|
||||
Portal(PortalRef),
|
||||
Statement(StatementRef),
|
||||
Statement(StatementId),
|
||||
}
|
||||
|
||||
impl Serialize<'_> for Target {
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
use crate::protocol::frontend::StatementId;
|
||||
use crate::{PgColumn, PgTypeInfo};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct RawStatement {
|
||||
pub(crate) id: u32,
|
||||
pub(crate) id: StatementId,
|
||||
pub(crate) columns: Vec<PgColumn>,
|
||||
pub(crate) parameters: Vec<PgTypeInfo>,
|
||||
}
|
||||
|
||||
impl RawStatement {
|
||||
pub(crate) fn new(id: u32) -> Self {
|
||||
pub(crate) fn new(id: StatementId) -> Self {
|
||||
Self { id, columns: Vec::new(), parameters: Vec::new() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,10 +10,72 @@ pub enum PgTypeId {
|
||||
Name(&'static str),
|
||||
}
|
||||
|
||||
/// Macro to reduce boilerplate for defining constants for `PgTypeId`. See usage below for examples.
|
||||
macro_rules! type_id {
|
||||
($(
|
||||
$(#[$meta:meta])*
|
||||
$name:ident = $kind:ident ($val:literal) $(, [] = $array_kind:ident ($array_val:literal))?
|
||||
);* $(;)?) => {
|
||||
impl PgTypeId {
|
||||
$(
|
||||
$(#[$meta])*
|
||||
pub const $name: Self = Self::$kind($val);
|
||||
|
||||
$(
|
||||
paste::paste! {
|
||||
#[doc = "An array of [`" $name "`][Self::" $name "]."]
|
||||
///
|
||||
/// Maps to either a slice or a vector of the equivalent Rust type.
|
||||
pub const [<$name _ARRAY>]: Self = Self::$array_kind($array_val);
|
||||
}
|
||||
)?
|
||||
)*
|
||||
}
|
||||
|
||||
impl PgTypeId {
|
||||
/// Get the name of this type as a string.
|
||||
#[must_use]
|
||||
pub (crate) const fn name(self) -> &'static str {
|
||||
match self {
|
||||
$(
|
||||
Self::$name => stringify!($name),
|
||||
$(
|
||||
// just appends `[]` to the type name
|
||||
Self::$array_kind($array_val) => concat!(stringify!($name), "[]"),
|
||||
)?
|
||||
)*
|
||||
Self::Name(name) => name,
|
||||
_ => "UNKNOWN"
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the ID of the inner type if the current type is an array.
|
||||
#[allow(dead_code)]
|
||||
pub (crate) const fn elem_type(self) -> Option<Self> {
|
||||
match self {
|
||||
// only generates an arm if `$array_kind` and `$array_val` are provided
|
||||
$($(Self::$array_kind($array_val) => Some(Self::$kind($val)),)?)*
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the type ID for an array of this type, if we know it.
|
||||
#[allow(dead_code)]
|
||||
pub (crate) const fn array_type(self) -> Option<Self> {
|
||||
match self {
|
||||
// only generates an arm if `$array_kind` and `$array_val` are provided
|
||||
$($(Self::$name => Some(Self::$array_kind($array_val)),)?)*
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Data Types
|
||||
// https://www.postgresql.org/docs/current/datatype.html
|
||||
|
||||
impl PgTypeId {
|
||||
// for OIDs see: https://github.com/postgres/postgres/blob/master/src/include/catalog/pg_type.dat
|
||||
type_id! {
|
||||
// Boolean
|
||||
// https://www.postgresql.org/docs/current/datatype-boolean.html
|
||||
|
||||
@@ -21,7 +83,7 @@ impl PgTypeId {
|
||||
///
|
||||
/// Maps to `bool`.
|
||||
///
|
||||
pub const BOOLEAN: Self = Self::Oid(16);
|
||||
BOOLEAN = Oid(16), [] = Oid(1000); // also defines `BOOLEAN_ARRAY` for the `BOOLEAN[]` type
|
||||
|
||||
// Integers
|
||||
// https://www.postgresql.org/docs/current/datatype-numeric.html#DATATYPE-INT
|
||||
@@ -34,7 +96,7 @@ impl PgTypeId {
|
||||
///
|
||||
#[doc(alias = "INT2")]
|
||||
#[doc(alias = "SMALLSERIAL")]
|
||||
pub const SMALLINT: Self = Self::Oid(21);
|
||||
SMALLINT = Oid(21), [] = Oid(1005);
|
||||
|
||||
/// A 4-byte integer.
|
||||
///
|
||||
@@ -44,17 +106,17 @@ impl PgTypeId {
|
||||
///
|
||||
#[doc(alias = "INT4")]
|
||||
#[doc(alias = "SERIAL")]
|
||||
pub const INTEGER: Self = Self::Oid(23);
|
||||
INTEGER = Oid(23), [] = Oid(1007);
|
||||
|
||||
/// An 8-byte integer.
|
||||
///
|
||||
/// Compatible with any primitive integer type.
|
||||
///
|
||||
/// Maps to `i64`.
|
||||
/// Maps to `i64`
|
||||
///
|
||||
#[doc(alias = "INT8")]
|
||||
#[doc(alias = "BIGSERIAL")]
|
||||
pub const BIGINT: Self = Self::Oid(20);
|
||||
BIGINT = Oid(20), [] = Oid(1016);
|
||||
|
||||
// Arbitrary Precision Numbers
|
||||
// https://www.postgresql.org/docs/current/datatype-numeric.html#DATATYPE-NUMERIC-DECIMAL
|
||||
@@ -70,7 +132,7 @@ impl PgTypeId {
|
||||
/// enabled crate features).
|
||||
///
|
||||
#[doc(alias = "DECIMAL")]
|
||||
pub const NUMERIC: Self = Self::Oid(1700);
|
||||
NUMERIC = Oid(1700), [] = Oid(1231);
|
||||
|
||||
// Floating-Point
|
||||
// https://www.postgresql.org/docs/current/datatype-numeric.html#DATATYPE-FLOAT
|
||||
@@ -82,7 +144,7 @@ impl PgTypeId {
|
||||
/// Maps to `f32`.
|
||||
///
|
||||
#[doc(alias = "FLOAT4")]
|
||||
pub const REAL: Self = Self::Oid(700);
|
||||
REAL = Oid(700), [] = Oid(1021);
|
||||
|
||||
/// An 8-byte floating-point numeric type.
|
||||
///
|
||||
@@ -91,34 +153,24 @@ impl PgTypeId {
|
||||
/// Maps to `f64`.
|
||||
///
|
||||
#[doc(alias = "FLOAT8")]
|
||||
pub const DOUBLE: Self = Self::Oid(701);
|
||||
DOUBLE = Oid(701), [] = Oid(1022);
|
||||
|
||||
/// The `UNKNOWN` Postgres type. Returned for expressions that do not
|
||||
/// have a type (e.g., `SELECT $1` with no parameter type hint
|
||||
/// or `SELECT NULL`).
|
||||
pub const UNKNOWN: Self = Self::Oid(705);
|
||||
UNKNOWN = Oid(705);
|
||||
}
|
||||
|
||||
impl PgTypeId {
|
||||
#[must_use]
|
||||
pub(crate) const fn name(self) -> &'static str {
|
||||
match self {
|
||||
Self::BOOLEAN => "BOOLEAN",
|
||||
|
||||
Self::SMALLINT => "SMALLINT",
|
||||
Self::INTEGER => "INTEGER",
|
||||
Self::BIGINT => "BIGINT",
|
||||
|
||||
Self::NUMERIC => "NUMERIC",
|
||||
|
||||
Self::REAL => "REAL",
|
||||
Self::DOUBLE => "DOUBLE",
|
||||
|
||||
_ => "UNKNOWN",
|
||||
pub(crate) const fn oid(self) -> Option<u32> {
|
||||
if let Self::Oid(oid) = self {
|
||||
Some(oid)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) const fn is_integer(&self) -> bool {
|
||||
matches!(*self, Self::SMALLINT | Self::INTEGER | Self::BIGINT)
|
||||
pub(crate) const fn is_integer(self) -> bool {
|
||||
matches!(self, Self::SMALLINT | Self::INTEGER | Self::BIGINT)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
mod array;
|
||||
mod bool;
|
||||
mod int;
|
||||
mod null;
|
||||
|
||||
160
sqlx-postgres/src/types/array.rs
Normal file
160
sqlx-postgres/src/types/array.rs
Normal file
@@ -0,0 +1,160 @@
|
||||
use sqlx_core::{encode, Arguments, Database, Encode, Type, TypeEncode};
|
||||
|
||||
use crate::{PgOutput, PgTypeId, PgTypeInfo, Postgres};
|
||||
use sqlx_core::database::HasOutput;
|
||||
|
||||
/// Marker trait for types which support being wrapped in array in Postgres.
|
||||
pub trait PgHasArray {
|
||||
/// The type ID in Postgres of the array type which has this type as an element.
|
||||
const ARRAY_TYPE_ID: PgTypeId;
|
||||
}
|
||||
|
||||
impl<T: PgHasArray> Type<Postgres> for &'_ [T] {
|
||||
fn type_id() -> <Postgres as Database>::TypeId
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
T::ARRAY_TYPE_ID
|
||||
}
|
||||
|
||||
// TODO: check `PgTypeInfo` for array element type and check compatibility of that
|
||||
// fn compatible(ty: &<Postgres as Database>::TypeInfo) -> bool
|
||||
// where
|
||||
// Self: Sized,
|
||||
// {
|
||||
// }
|
||||
}
|
||||
|
||||
impl<T: Type<Postgres> + Encode<Postgres>> Encode<Postgres> for &'_ [T] {
|
||||
fn encode(
|
||||
&self,
|
||||
ty: &<Postgres as Database>::TypeInfo,
|
||||
out: &mut <Postgres as HasOutput<'_>>::Output,
|
||||
) -> encode::Result {
|
||||
encode_array(*self, ty, out)
|
||||
}
|
||||
|
||||
fn vector_len(&self) -> Option<usize> {
|
||||
Some(self.len())
|
||||
}
|
||||
|
||||
fn expand_vector<'a>(&'a self, arguments: &mut Arguments<'a, Postgres>) {
|
||||
for elem in *self {
|
||||
arguments.add(elem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Vector
|
||||
|
||||
impl<T: PgHasArray> Type<Postgres> for Vec<T> {
|
||||
fn type_id() -> <Postgres as Database>::TypeId
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
<&[T]>::type_id()
|
||||
}
|
||||
|
||||
fn compatible(ty: &<Postgres as Database>::TypeInfo) -> bool
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
<&[T]>::compatible(ty)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Type<Postgres> + Encode<Postgres>> Encode<Postgres> for Vec<T> {
|
||||
fn encode(
|
||||
&self,
|
||||
ty: &<Postgres as Database>::TypeInfo,
|
||||
out: &mut <Postgres as HasOutput<'_>>::Output,
|
||||
) -> encode::Result {
|
||||
encode_array(self.iter(), ty, out)
|
||||
}
|
||||
|
||||
fn vector_len(&self) -> Option<usize> {
|
||||
Some(self.len())
|
||||
}
|
||||
|
||||
fn expand_vector<'a>(&'a self, arguments: &mut Arguments<'a, Postgres>) {
|
||||
for elem in self {
|
||||
arguments.add(elem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// static-size arrays
|
||||
|
||||
impl<T: PgHasArray, const N: usize> Type<Postgres> for [T; N] {
|
||||
fn type_id() -> <Postgres as Database>::TypeId
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
<&[T]>::type_id()
|
||||
}
|
||||
|
||||
fn compatible(ty: &<Postgres as Database>::TypeInfo) -> bool
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
<&[T]>::compatible(ty)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Type<Postgres> + Encode<Postgres>, const N: usize> Encode<Postgres> for [T; N] {
|
||||
fn encode(
|
||||
&self,
|
||||
ty: &<Postgres as Database>::TypeInfo,
|
||||
out: &mut <Postgres as HasOutput<'_>>::Output,
|
||||
) -> encode::Result {
|
||||
encode_array(self.iter(), ty, out)
|
||||
}
|
||||
|
||||
fn vector_len(&self) -> Option<usize> {
|
||||
Some(self.len())
|
||||
}
|
||||
|
||||
fn expand_vector<'a>(&'a self, arguments: &mut Arguments<'a, Postgres>) {
|
||||
for elem in self {
|
||||
arguments.add(elem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn encode_array<T: TypeEncode<Postgres>, I: IntoIterator<Item = T>>(
|
||||
array: I,
|
||||
_ty: &PgTypeInfo,
|
||||
out: &mut PgOutput<'_>,
|
||||
) -> encode::Result {
|
||||
// number of dimensions (1 for now)
|
||||
out.buffer().extend_from_slice(&1i32.to_be_bytes());
|
||||
|
||||
let len_start = out.buffer().len();
|
||||
|
||||
// whether or not the array is null (fixup afterward)
|
||||
out.buffer().extend_from_slice(&[0; 4]);
|
||||
|
||||
// FIXME: better error message/avoid the error
|
||||
let elem_type = T::type_id().oid().ok_or_else(|| {
|
||||
encode::Error::msg("can only bind an array with elements with a known oid")
|
||||
})?;
|
||||
|
||||
out.buffer().extend_from_slice(&elem_type.to_be_bytes());
|
||||
|
||||
let mut count: i32 = 0;
|
||||
|
||||
let is_null = array
|
||||
.into_iter()
|
||||
.map(|elem| {
|
||||
count = count
|
||||
.checked_add(1)
|
||||
.ok_or_else(|| encode::Error::msg("array length overflows i32"))?;
|
||||
elem.encode(&PgTypeInfo(T::type_id()), out)
|
||||
})
|
||||
.collect::<encode::Result>()?;
|
||||
|
||||
// fixup the length
|
||||
out.buffer()[len_start..][..4].copy_from_slice(&count.to_be_bytes());
|
||||
|
||||
Ok(is_null)
|
||||
}
|
||||
@@ -86,7 +86,7 @@ where
|
||||
}
|
||||
|
||||
macro_rules! impl_type_int {
|
||||
($ty:ty $(: $real:ty)? => $sql:ident) => {
|
||||
($ty:ty $(: $real:ty)? => $sql:ident $(, [] => $array_sql:ident)?) => {
|
||||
impl Type<Postgres> for $ty {
|
||||
fn type_id() -> PgTypeId {
|
||||
PgTypeId::$sql
|
||||
@@ -97,6 +97,12 @@ macro_rules! impl_type_int {
|
||||
}
|
||||
}
|
||||
|
||||
$(
|
||||
impl super::array::PgHasArray for $ty {
|
||||
const ARRAY_TYPE_ID: PgTypeId = PgTypeId::$array_sql;
|
||||
}
|
||||
)?
|
||||
|
||||
impl Encode<Postgres> for $ty {
|
||||
fn encode(&self, ty: &PgTypeInfo, out: &mut PgOutput<'_>) -> encode::Result {
|
||||
ensure_not_too_large_or_too_small((*self $(as $real)?).into(), ty)?;
|
||||
@@ -115,9 +121,9 @@ macro_rules! impl_type_int {
|
||||
};
|
||||
}
|
||||
|
||||
impl_type_int! { i8 => SMALLINT }
|
||||
impl_type_int! { i16 => SMALLINT }
|
||||
impl_type_int! { i32 => INTEGER }
|
||||
impl_type_int! { i8 => SMALLINT, [] => SMALLINT_ARRAY }
|
||||
impl_type_int! { i16 => SMALLINT, [] => SMALLINT_ARRAY }
|
||||
impl_type_int! { i32 => INTEGER, [] => INTEGER_ARRAY }
|
||||
impl_type_int! { i64 => BIGINT }
|
||||
impl_type_int! { i128 => BIGINT }
|
||||
|
||||
|
||||
55
sqlx-postgres/tests/connection.rs
Normal file
55
sqlx-postgres/tests/connection.rs
Normal file
@@ -0,0 +1,55 @@
|
||||
use sqlx_core::{Connect, Connection, Executor, Tokio};
|
||||
use sqlx_postgres::PgArguments;
|
||||
use sqlx_postgres::PgConnection;
|
||||
use sqlx_test::assert_cancellation_safe;
|
||||
use std::env;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_connect() -> anyhow::Result<()> {
|
||||
let url = env::var("DATABASE_URL")?;
|
||||
let mut conn = PgConnection::<Tokio>::connect(&url).await?;
|
||||
|
||||
conn.ping().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_select_1() -> anyhow::Result<()> {
|
||||
let url = env::var("DATABASE_URL")?;
|
||||
let mut conn = PgConnection::<Tokio>::connect(&url).await?;
|
||||
|
||||
let row = conn.fetch_one("SELECT 1").await?;
|
||||
let col0: i32 = row.try_get(0)?;
|
||||
|
||||
assert_eq!(col0, 1);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_generic_placeholders() -> anyhow::Result<()> {
|
||||
let url = env::var("DATABASE_URL")?;
|
||||
let mut conn = PgConnection::<Tokio>::connect(&url).await?;
|
||||
|
||||
let mut args = PgArguments::new();
|
||||
args.add(&1i32);
|
||||
|
||||
let row = conn.fetch_one(("SELECT {}", args)).await?;
|
||||
let col0: i32 = row.try_get(0)?;
|
||||
|
||||
let mut args = PgArguments::new();
|
||||
args.add(&[1i32, 2, 3, 4, 5, 6]);
|
||||
|
||||
let row = conn
|
||||
.fetch_one((
|
||||
"SELECT val FROM generate_series(0, 9, 3) AS vals(val) WHERE val IN ({+})",
|
||||
args,
|
||||
))
|
||||
.await?;
|
||||
let col0: i32 = row.try_get(0)?;
|
||||
|
||||
assert_eq!(col0, 3);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user