Skip to content

Commit

Permalink
WIP [next]: implement generalized query placeholders
Browse files Browse the repository at this point in the history
Signed-off-by: Austin Bonander <austin@launchbadge.com>
  • Loading branch information
abonander committed Jul 16, 2021
1 parent 1cac286 commit 80fc14a
Show file tree
Hide file tree
Showing 24 changed files with 1,302 additions and 105 deletions.
20 changes: 20 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions sqlx-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,4 @@ memchr = "2.3"
conquer-once = { version = "0.3.2", optional = true }
parking_lot = { version = "0.11.1", optional = true }
crossbeam = { version = "0.8.0", optional = true }
combine = "4.5.2"
149 changes: 117 additions & 32 deletions sqlx-core/src/arguments.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use std::any;

use either::Either;

use crate::database::HasOutput;
use crate::{encode, Database, Error, Result, TypeEncode, TypeInfo};

use std::borrow::Cow;
use std::fmt::{self, Display, Formatter};

/// A collection of arguments to be applied to a prepared statement.
///
/// This container allows for a heterogeneous list of positional and named
Expand All @@ -17,10 +18,17 @@ pub struct Arguments<'a, Db: Database> {
positional: Vec<Argument<'a, Db>>,
}

/// The index for a given bind argument; either positional, or named.
#[derive(Debug, PartialEq, Eq)]
pub enum ArgumentIndex<'a> {
Positioned(usize),
Named(Cow<'a, str>),
}

/// A single argument to be applied to a prepared statement.
pub struct Argument<'a, Db: Database> {
unchecked: bool,
parameter: Either<usize, &'a str>,
index: ArgumentIndex<'a>,

// preserved from `T::type_id()`
type_id: Db::TypeId,
Expand All @@ -38,15 +46,15 @@ pub struct Argument<'a, Db: Database> {
}

impl<'a, Db: Database> Argument<'a, Db> {
fn new<T: 'a + TypeEncode<Db>>(
parameter: Either<usize, &'a str>,
fn new<'b: 'a, T: 'a + TypeEncode<Db>>(
parameter: impl Into<ArgumentIndex<'b>>,
value: &'a T,
unchecked: bool,
) -> Self {
Self {
value,
unchecked,
parameter,
index: parameter.into(),
type_id: T::type_id(),
type_compatible: T::compatible,
rust_type_name: any::type_name::<T>(),
Expand Down Expand Up @@ -77,7 +85,7 @@ impl<'a, Db: Database> Arguments<'a, Db> {
pub fn add<T: 'a + TypeEncode<Db>>(&mut self, value: &'a T) {
let index = self.positional.len();

self.positional.push(Argument::new(Either::Left(index), value, false));
self.positional.push(Argument::new(index, value, false));
}

/// Add an unchecked value to the end of the arguments collection.
Expand All @@ -89,17 +97,17 @@ impl<'a, Db: Database> Arguments<'a, Db> {
pub fn add_unchecked<T: 'a + TypeEncode<Db>>(&mut self, value: &'a T) {
let index = self.positional.len();

self.positional.push(Argument::new(Either::Left(index), value, true));
self.positional.push(Argument::new(index, value, true));
}

/// Add a named value to the argument collection.
pub fn add_as<T: 'a + TypeEncode<Db>>(&mut self, name: &'a str, value: &'a T) {
self.named.push(Argument::new(Either::Right(name), value, false));
self.named.push(Argument::new(name, value, false));
}

/// Add an unchecked, named value to the arguments collection.
pub fn add_unchecked_as<T: 'a + TypeEncode<Db>>(&mut self, name: &'a str, value: &'a T) {
self.named.push(Argument::new(Either::Right(name), value, true));
self.named.push(Argument::new(name, value, true));
}
}

Expand Down Expand Up @@ -155,20 +163,30 @@ impl<'a, Db: Database> Arguments<'a, Db> {
}

/// Returns a reference to the argument at the location, if present.
pub fn get<'x, I: ArgumentIndex<'a, Db>>(&'x self, index: &I) -> Option<&'x Argument<'a, Db>> {
index.get(self)
pub fn get<'x, 'i, I: Into<ArgumentIndex<'i>>>(
&'x self,
index: I,
) -> Option<&'x Argument<'a, Db>> {
let index = index.into();

match index {
ArgumentIndex::Named(_) => &self.named,
ArgumentIndex::Positioned(_) => &self.positional,
}
.iter()
.find(|arg| arg.index == index)
}
}

impl<'a, Db: Database> Argument<'a, Db> {
/// Gets the name of this argument, if it is a named argument, None otherwise
pub fn name<'b>(&'b self) -> Option<&'a str>{
self.parameter.right()
pub fn name(&self) -> Option<&str> {
self.index.name()
}

/// Gets the position of this argument, if it is a positional argument, None otherwise
pub fn position(&self) -> Option<usize>{
self.parameter.left()
pub fn position(&self) -> Option<usize> {
self.index.position()
}

/// Returns the SQL type identifier of the argument.
Expand Down Expand Up @@ -198,29 +216,96 @@ impl<'a, Db: Database> Argument<'a, Db> {
self.value.encode(ty, out)
};

res.map_err(|source| Error::ParameterEncode {
parameter: self.parameter.map_right(|name| name.to_string().into_boxed_str()),
source,
})
res.map_err(|source| Error::ParameterEncode { parameter: self.index.to_static(), source })
}

pub fn value(&self) -> &(dyn TypeEncode<Db> + 'a) {
self.value
}
}

impl<'a> From<&'a str> for ArgumentIndex<'a> {
fn from(name: &'a str) -> Self {
ArgumentIndex::Named(name.into())
}
}

impl<'a> From<&'a String> for ArgumentIndex<'a> {
fn from(name: &'a String) -> Self {
ArgumentIndex::Named(name.into())
}
}

impl From<usize> for ArgumentIndex<'static> {
fn from(position: usize) -> Self {
ArgumentIndex::Positioned(position)
}
}

impl<'a, 'b> From<&'a ArgumentIndex<'b>> for ArgumentIndex<'a> {
fn from(idx: &'a ArgumentIndex<'b>) -> Self {
match idx {
ArgumentIndex::Positioned(pos) => ArgumentIndex::Positioned(*pos),
ArgumentIndex::Named(name) => ArgumentIndex::Named(name.as_ref().into()),
}
}
}

impl<'a> ArgumentIndex<'a> {
pub(crate) fn into_static(self) -> ArgumentIndex<'static> {
match self {
Self::Positioned(pos) => ArgumentIndex::Positioned(pos),
Self::Named(named) => ArgumentIndex::Named(named.into_owned().into()),
}
}

pub(crate) fn to_static(&self) -> ArgumentIndex<'static> {
match self {
Self::Positioned(pos) => ArgumentIndex::Positioned(*pos),
Self::Named(named) => ArgumentIndex::Named((**named).to_owned().into()),
}
}

pub(crate) fn name(&self) -> Option<&str> {
if let Self::Named(s) = self {
Some(s)
} else {
None
}
}

pub(crate) fn position(&self) -> Option<usize> {
if let Self::Positioned(pos) = *self {
Some(pos)
} else {
None
}
}
}

impl Display for ArgumentIndex<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
Self::Positioned(pos) => Display::fmt(pos, f),
Self::Named(named) => Display::fmt(named, f),
}
}
}

/// A helper trait used for indexing into an [`Arguments`] collection.
pub trait ArgumentIndex<'a, Db: Database> {
/// Returns a reference to the argument at this location, if present.
fn get<'x>(&self, arguments: &'x Arguments<'a, Db>) -> Option<&'x Argument<'a, Db>>;
impl PartialEq<str> for ArgumentIndex<'_> {
fn eq(&self, other: &str) -> bool {
self == &ArgumentIndex::from(other)
}
}

// access a named argument by name
impl<'a, Db: Database> ArgumentIndex<'a, Db> for str {
fn get<'x>(&self, arguments: &'x Arguments<'a, Db>) -> Option<&'x Argument<'a, Db>> {
arguments.named.iter().find_map(|arg| (arg.parameter.right() == Some(self)).then(|| arg))
impl PartialEq<&'_ str> for ArgumentIndex<'_> {
fn eq(&self, other: &&str) -> bool {
self == &ArgumentIndex::from(*other)
}
}

// access a positional argument by index
impl<'a, Db: Database> ArgumentIndex<'a, Db> for usize {
fn get<'x>(&self, arguments: &'x Arguments<'a, Db>) -> Option<&'x Argument<'a, Db>> {
arguments.positional.get(*self)
impl PartialEq<usize> for ArgumentIndex<'_> {
fn eq(&self, other: &usize) -> bool {
self == &ArgumentIndex::from(*other)
}
}
9 changes: 9 additions & 0 deletions sqlx-core/src/database.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,15 @@ pub trait Database:

/// The concrete [`TypeId`] implementation for this database.
type TypeId: 'static + PartialEq + Hash + Clone + Copy + Send + Sync;

/// The character used to prefix bind parameter placeholders, e.g. `$` for Postgres, `?` for MySQL, etc.
const PLACEHOLDER_CHAR: char;

/// The indexing type for bind parameters.
///
/// E.g. `Implicit` for MySQL which just does `SELECT 1 FROM foo WHERE bar = ? AND baz = ?`
/// or `OneIndexed` for Postgres which does `SELECT 1 FROM foo WHERE bar = $1 AND baz = $2`
const PARAM_INDEXING: crate::placeholders::ParamIndexing;
}

/// Associates [`Database`] with an `Output` of a generic lifetime.
Expand Down
42 changes: 41 additions & 1 deletion sqlx-core/src/encode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ use std::error::Error as StdError;
use std::fmt::{self, Display, Formatter};

use crate::database::HasOutput;
use crate::Database;
use crate::{Arguments, Database, Type, TypeEncode};
use std::iter::FromIterator;
use std::ops::BitOr;

/// Type returned from [`Encode::encode`] that indicates if the value encoded is the SQL `null` or not.
pub enum IsNull {
Expand All @@ -20,17 +22,55 @@ pub enum IsNull {
No,
}

#[doc(hidden)]
impl BitOr<IsNull> for IsNull {
type Output = IsNull;

fn bitor(self, rhs: IsNull) -> Self::Output {
use IsNull::*;

match (self, rhs) {
(No, No) => No,
(Yes, No) | (No, Yes) | (Yes, Yes) => Yes,
}
}
}

/// Useful for encoding arrays
#[doc(hidden)]
impl FromIterator<IsNull> for IsNull {
fn from_iter<T: IntoIterator<Item = IsNull>>(iter: T) -> Self {
iter.into_iter().fold(IsNull::No, BitOr::bitor)
}
}

/// A type that can be encoded into a SQL value.
pub trait Encode<Db: Database>: Send + Sync {
/// Encode this value into the specified SQL type.
fn encode(&self, ty: &Db::TypeInfo, out: &mut <Db as HasOutput<'_>>::Output) -> Result;

/// If this type is a vector, get its length.
fn vector_len(&self) -> Option<usize> {
None
}

/// If this type is a vector, add its elements as positional arguments to `arguments`.
///
/// Panics if not a vector.
fn expand_vector<'a>(&'a self, _arguments: &mut Arguments<'a, Db>) {
panic!("not a vector!")
}
}

impl<T: Encode<Db>, Db: Database> Encode<Db> for &T {
#[inline]
fn encode(&self, ty: &Db::TypeInfo, out: &mut <Db as HasOutput<'_>>::Output) -> Result {
(*self).encode(ty, out)
}

fn vector_len(&self) -> Option<usize> {
(*self).vector_len()
}
}

/// Errors which can occur while encoding a SQL value.
Expand Down

0 comments on commit 80fc14a

Please sign in to comment.