forked from launchbadge/sqlx
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial low-level connection interface
- Loading branch information
Showing
13 changed files
with
442 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
root = true | ||
|
||
[*] | ||
charset = utf-8 | ||
end_of_line = lf | ||
insert_final_newline = true | ||
indent_style = space | ||
indent_size = 4 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,3 @@ | ||
/target | ||
**/*.rs.bk | ||
Cargo.lock | ||
.idea/ | ||
target/ | ||
Cargo.lock |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,20 @@ | ||
[workspace] | ||
members = [ | ||
".", | ||
"mason-postgres" | ||
] | ||
|
||
[package] | ||
name = "dbx" | ||
version = "0.1.0" | ||
name = "mason" | ||
version = "0.0.0" | ||
authors = ["Ryan Leckey <[email protected]>"] | ||
license = "MIT OR Apache-2.0" | ||
description = "Asynchronous and expressive database client in pure Rust." | ||
edition = "2018" | ||
|
||
[dependencies] | ||
runtime = "=0.3.0-alpha.4" | ||
mason-postgres = { path = "mason-postgres" } | ||
failure = "0.1" | ||
env_logger = "0.6.1" | ||
bytes = "0.4.12" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
[package] | ||
name = "mason-postgres" | ||
version = "0.0.0" | ||
authors = ["Ryan Leckey <[email protected]>"] | ||
license = "MIT OR Apache-2.0" | ||
description = "PostgreSQL database driver for dbx." | ||
edition = "2018" | ||
|
||
[dependencies] | ||
runtime = "=0.3.0-alpha.4" | ||
futures-preview = "=0.3.0-alpha.16" | ||
failure = "0.1" | ||
byteorder = "1.3.1" | ||
log = "0.4" | ||
hex = "0.3.2" | ||
bytes = "0.4.12" | ||
memchr = "2.2.0" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
#![feature(non_exhaustive, async_await)] | ||
#![allow(clippy::needless_lifetimes)] | ||
|
||
use crate::protocol::{client, server}; | ||
use bytes::BytesMut; | ||
use futures::{ | ||
channel::mpsc, | ||
io::{AsyncRead, AsyncReadExt, AsyncWriteExt, WriteHalf}, | ||
SinkExt, StreamExt, | ||
}; | ||
use runtime::{net::TcpStream, task::JoinHandle}; | ||
use std::io; | ||
|
||
pub mod protocol; | ||
|
||
pub struct Connection { | ||
buf: Vec<u8>, | ||
writer: WriteHalf<TcpStream>, | ||
incoming: mpsc::Receiver<server::Message>, | ||
reader: Option<JoinHandle<io::Result<()>>>, | ||
} | ||
|
||
impl Connection { | ||
pub async fn open(address: &str) -> io::Result<Self> { | ||
let stream = TcpStream::connect(address).await?; | ||
let (mut reader, writer) = stream.split(); | ||
|
||
// FIXME: What's a good buffer size here? | ||
let (mut tx, rx) = mpsc::channel(1024); | ||
|
||
let reader = runtime::spawn(async move { | ||
let mut buf = BytesMut::with_capacity(0); | ||
let mut len = 0; | ||
|
||
'reader: loop { | ||
if len == buf.len() { | ||
unsafe { | ||
buf.reserve(32); | ||
buf.set_len(buf.capacity()); | ||
reader.initializer().initialize(&mut buf[len..]); | ||
} | ||
} | ||
|
||
let num = reader.read(&mut buf[len..]).await?; | ||
if num > 0 { | ||
len += num; | ||
} | ||
|
||
while len > 0 && !buf.is_empty() { | ||
let size = buf.len(); | ||
let msg = server::Message::deserialize(&mut buf)?; | ||
|
||
let removed = size - buf.len(); | ||
len -= removed; | ||
|
||
match msg { | ||
Some(server::Message::ParameterStatus(body)) => { | ||
// FIXME: Proper log | ||
log::info!("{:?}", body); | ||
} | ||
|
||
Some(msg) => { | ||
tx.send(msg).await.unwrap(); | ||
} | ||
|
||
None => { | ||
// We have _some_ amount of data but not enough to | ||
// deserialize anything | ||
break; | ||
} | ||
} | ||
} | ||
|
||
// FIXME: This probably doesn't make sense | ||
if len == 0 && !buf.is_empty() { | ||
// Hit end-of-stream | ||
break 'reader; | ||
} | ||
} | ||
|
||
Ok(()) | ||
}); | ||
|
||
Ok(Self { | ||
// FIXME: What's a good buffer size here? | ||
buf: Vec::with_capacity(1024), | ||
writer, | ||
reader: Some(reader), | ||
incoming: rx, | ||
}) | ||
} | ||
|
||
pub async fn startup<'a, 'b: 'a>( | ||
&'a mut self, | ||
user: &'b str, | ||
_password: &'b str, | ||
database: &'b str, | ||
) -> io::Result<()> { | ||
let params = [("user", user), ("database", database)]; | ||
|
||
let msg = client::StartupMessage { params: ¶ms }; | ||
msg.serialize(&mut self.buf); | ||
|
||
self.writer.write_all(&self.buf).await?; | ||
self.buf.clear(); | ||
|
||
self.writer.flush().await?; | ||
|
||
// FIXME: We _actually_ want to wait until ReadyForQuery, ErrorResponse, AuthX, etc. | ||
|
||
while let Some(message) = self.incoming.next().await { | ||
match message { | ||
server::Message::AuthenticationOk => { | ||
// Do nothing; server is just telling us "you're in" | ||
} | ||
|
||
server::Message::ReadyForQuery(_) => { | ||
// Good to go | ||
break; | ||
} | ||
|
||
_ => {} | ||
} | ||
} | ||
|
||
Ok(()) | ||
} | ||
|
||
pub async fn terminate(&mut self) -> io::Result<()> { | ||
let msg = client::Terminate {}; | ||
msg.serialize(&mut self.buf); | ||
|
||
self.writer.write_all(&self.buf).await?; | ||
self.buf.clear(); | ||
|
||
self.writer.flush().await?; | ||
self.writer.close().await?; | ||
|
||
if let Some(reader) = self.reader.take() { | ||
reader.await?; | ||
} | ||
|
||
Ok(()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
use byteorder::{BigEndian, ByteOrder}; | ||
|
||
// Reference | ||
// https://www.postgresql.org/docs/devel/protocol-message-formats.html | ||
// https://www.postgresql.org/docs/devel/protocol-message-types.html | ||
|
||
#[derive(Debug)] | ||
pub struct Terminate; | ||
|
||
impl Terminate { | ||
pub fn serialize(&self, buf: &mut Vec<u8>) { | ||
buf.push(b'X'); | ||
buf.push(4); | ||
} | ||
} | ||
|
||
#[derive(Debug)] | ||
pub struct StartupMessage<'a> { | ||
/// One or more pairs of parameter name and value strings. | ||
/// A zero byte is required as a terminator after the last name/value pair. | ||
/// Parameters can appear in any order. user is required, others are optional. | ||
pub params: &'a [(&'a str, &'a str)], | ||
} | ||
|
||
impl<'a> StartupMessage<'a> { | ||
pub fn serialize(&self, buf: &mut Vec<u8>) { | ||
with_length_prefix(buf, |buf| { | ||
// version: 3 = major, 0 = minor | ||
buf.extend_from_slice(&0x0003_0000_i32.to_be_bytes()); | ||
|
||
for (name, value) in self.params { | ||
buf.extend_from_slice(name.as_bytes()); | ||
buf.push(0); | ||
buf.extend_from_slice(value.as_bytes()); | ||
buf.push(0); | ||
} | ||
|
||
// A zero byte is required as a terminator after the last name/value pair. | ||
buf.push(0); | ||
}); | ||
} | ||
} | ||
|
||
// Write a variable amount of data into a buffer and then | ||
// prefix that data with the length of what was written | ||
fn with_length_prefix<F>(buf: &mut Vec<u8>, f: F) | ||
where | ||
F: FnOnce(&mut Vec<u8>), | ||
{ | ||
// Reserve space for length | ||
let base = buf.len(); | ||
buf.extend_from_slice(&[0; 4]); | ||
|
||
f(buf); | ||
|
||
// Write back the length | ||
// FIXME: Handle >= i32 | ||
let size = (buf.len() - base) as i32; | ||
BigEndian::write_i32(&mut buf[base..], size); | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
|
||
// TODO: Serialize test more messages | ||
|
||
#[test] | ||
fn ser_startup_message() { | ||
let msg = StartupMessage { params: &[("user", "postgres"), ("database", "postgres")] }; | ||
|
||
let mut buf = Vec::new(); | ||
msg.serialize(&mut buf); | ||
|
||
assert_eq!( | ||
"00000029000300007573657200706f73746772657\ | ||
300646174616261736500706f7374677265730000", | ||
hex::encode(buf) | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
pub mod client; | ||
pub mod server; |
Oops, something went wrong.