prototyping sql!() macro

This commit is contained in:
Austin Bonander 2019-10-31 22:01:42 -07:00 committed by Austin Bonander
parent 298421b45a
commit 3a76f9d207
14 changed files with 118 additions and 17 deletions

View file

@ -33,6 +33,7 @@ url = "2.1.0"
[dev-dependencies]
matches = "0.1.8"
tokio = { version = "0.2.0-alpha.4", default-features = false, features = [ "rt-full" ] }
sqlx-macros = { path = "sqlx-macros/" }
[profile.release]
lto = true

19
sqlx-macros/Cargo.toml Normal file
View file

@ -0,0 +1,19 @@
[package]
name = "sqlx-macros"
version = "0.1.0"
authors = ["Austin Bonander <austin.bonander@gmail.com>"]
edition = "2018"
[lib]
proc-macro = true
[dependencies]
futures-preview = "0.3.0-alpha.18"
hex = "0.4.0"
proc-macro2 = "1.0.6"
sqlx = { path = "../", features = ["postgres"] }
syn = "1.0"
quote = "1.0"
sha2 = "0.8.0"
tokio = { version = "0.2.0-alpha.4", default-features = false, features = [ "tcp" ] }

46
sqlx-macros/src/lib.rs Normal file
View file

@ -0,0 +1,46 @@
extern crate proc_macro;
use proc_macro::TokenStream;
use quote::quote;
use syn::parse_macro_input;
use sha2::{Sha256, Digest};
use sqlx::Postgres;
use tokio::runtime::Runtime;
type Error = Box<dyn std::error::Error>;
type Result<T> = std::result::Result<T, Error>;
#[proc_macro]
pub fn sql(input: TokenStream) -> TokenStream {
let string = parse_macro_input!(input as syn::LitStr).value();
eprintln!("expanding macro");
match Runtime::new().map_err(Error::from).and_then(|runtime| runtime.block_on(process_sql(&string))) {
Ok(ts) => ts,
Err(e) => {
let msg = e.to_string();
quote! ( compile_error!(#msg) ).into()
}
}
}
async fn process_sql(sql: &str) -> Result<TokenStream> {
let hash = dbg!(hex::encode(&Sha256::digest(sql.as_bytes())));
let conn = sqlx::Connection::<Postgres>::establish("postgresql://postgres@127.0.0.1/sqlx_test")
.await
.map_err(|e| format!("failed to connect to database: {}", e))?;
eprintln!("connection established");
let prepared = conn.prepare(&hash, sql).await?;
let msg = format!("{:?}", prepared);
Ok(quote! { compile_error!(#msg) }.into())
}

View file

@ -75,7 +75,7 @@ pub trait RawConnection: Send {
async fn prepare(&mut self, name: &str, body: &str) -> crate::Result<PreparedStatement> {
// TODO: implement for other backends
Err("connection does not support prepare() operation".into())
unimplemented!()
}
}
@ -130,7 +130,7 @@ where
/// Prepares a statement.
pub async fn prepare(&self, name: &str, body: &str) -> crate::Result<PreparedStatement> {
let mut live = self.0.acquire().await;
let ret = live.raw.prepare(name, body)?;
let ret = live.raw.prepare(name, body).await?;
self.0.release(live);
Ok(ret)
}

View file

@ -9,6 +9,8 @@ pub trait Buf {
fn get_u16<T: ByteOrder>(&mut self) -> io::Result<u16>;
fn get_i16<T: ByteOrder>(&mut self) -> io::Result<i16>;
fn get_u24<T: ByteOrder>(&mut self) -> io::Result<u32>;
fn get_i32<T: ByteOrder>(&mut self) -> io::Result<i32>;
@ -42,6 +44,13 @@ impl<'a> Buf for &'a [u8] {
Ok(val)
}
fn get_i16<T: ByteOrder>(&mut self) -> io::Result<i16> {
let val = T::read_i16(*self);
self.advance(2);
Ok(val)
}
fn get_i32<T: ByteOrder>(&mut self) -> io::Result<i32> {
let val = T::read_i32(*self);
self.advance(4);

View file

@ -1,8 +1,10 @@
use super::{Postgres, PostgresQueryParameters, PostgresRawConnection, PostgresRow};
use crate::{connection::RawConnection, postgres::raw::Step, url::Url, Error};
use crate::query::QueryParameters;
use async_trait::async_trait;
use futures_core::stream::BoxStream;
use crate::prepared::{PreparedStatement, Field};
use crate::postgres::error::ProtocolError;
#[async_trait]
impl RawConnection for PostgresRawConnection {
@ -94,21 +96,26 @@ impl RawConnection for PostgresRawConnection {
Ok(row)
}
fn prepare(&mut self, name: &str, body: &str) -> crate::Result<PreparedStatement> {
self.parse(name, body, &[]);
async fn prepare(&mut self, name: &str, body: &str) -> crate::Result<PreparedStatement> {
self.parse(name, body, &PostgresQueryParameters::new());
self.describe(name);
self.sync().await?;
let param_desc= loop {
if let Step::ParamDesc(desc) = self.step().await?
.ok_or("did not receive ParameterDescription")?
let step = self.step().await?
.ok_or(ProtocolError("did not receive ParameterDescription"));
if let Step::ParamDesc(desc) = dbg!(step)?
{
break desc;
}
};
let row_desc = loop {
if let Step::RowDesc(desc) = self.step().await?
.ok_or("did not receive RowDescription")?
let step = self.step().await?
.ok_or(ProtocolError("did not receive RowDescription"));
if let Step::RowDesc(desc) = dbg!(step)?
{
break desc;
}

View file

@ -1,11 +1,22 @@
use super::protocol::Response;
use crate::error::DatabaseError;
use std::borrow::Cow;
use std::fmt::Debug;
#[derive(Debug)]
pub struct PostgresDatabaseError(pub(super) Box<Response>);
#[derive(Debug)]
pub struct ProtocolError<T>(pub(super) T);
impl DatabaseError for PostgresDatabaseError {
fn message(&self) -> &str {
self.0.message()
}
}
impl<T: AsRef<str> + Debug + Send + Sync> DatabaseError for ProtocolError<T> {
fn message(&self) -> &str {
self.0.as_ref()
}
}

View file

@ -9,11 +9,11 @@ pub enum DescribeKind {
}
pub struct Describe<'a> {
kind: DescribeKind,
pub kind: DescribeKind,
/// The name of the prepared statement or portal to describe (an empty string selects the
/// unnamed prepared statement or portal).
name: &'a str,
pub name: &'a str,
}
impl Encode for Describe<'_> {

View file

@ -65,10 +65,10 @@ fn read_string(buf: &mut &[u8]) -> io::Result<String> {
let str_len = memchr::memchr(0u8, buf)
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "unterminated string"))?;
let string = str::from_utf8(&*buf[..str_len])
let string = str::from_utf8(&buf[..str_len])
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
*buf = &*buf[str_len + 1..];
*buf = &buf[str_len + 1..];
Ok(string.to_owned())
}

View file

@ -26,15 +26,15 @@ impl Decode for RowDescription {
let mut fields = Vec::with_capacity(cnt);
for _ in 0..cnt {
fields.push(RowField {
fields.push(dbg!(RowField {
name: super::read_string(&mut buf)?,
table_id: buf.get_u32::<NetworkEndian>()?,
attr_num: buf.get_i16::<NetworkEndian>()?,
type_id: buf.get_u32::<NetworkEndian>()?,
type_size: buf.get_16::<NetworkEndian>()?,
type_size: buf.get_i16::<NetworkEndian>()?,
type_mod: buf.get_i32::<NetworkEndian>()?,
format_code: buf.get_i16::<NetworkEndian>()?,
});
}));
}
Ok(Self {

View file

@ -204,7 +204,9 @@ impl PostgresRawConnection {
return Ok(Some(Step::ParamDesc(desc)));
},
Message::
Message::RowDescription(desc) => {
return Ok(Some(Step::RowDesc(desc)));
},
message => {
return Err(io::Error::new(
@ -296,6 +298,7 @@ impl PostgresRawConnection {
}
}
#[derive(Debug)]
pub(super) enum Step {
Command(u64),
Row(PostgresRow),

View file

@ -1,6 +1,7 @@
use super::{protocol::DataRow, Postgres};
use crate::row::Row;
#[derive(Debug)]
pub struct PostgresRow(pub(crate) DataRow);
impl Row for PostgresRow {

View file

@ -1,9 +1,11 @@
#[derive(Debug)]
pub struct PreparedStatement {
pub name: String,
pub param_types: Box<[u32]>,
pub fields: Vec<Field>,
}
#[derive(Debug)]
pub struct Field {
pub name: String,
pub table_id: u32,

View file

@ -1,3 +1,5 @@
#![feature(proc_macro_hygiene)]
fn main() {
sqlx::sql!("SELECT * from accounts");
sqlx_macros::sql!("SELECT * from accounts");
}