postgres: implement PgTypeInfo::with_name

- remove PgTypeInfo::with_oid
 - use a new PgRawBuffer type instead of Vec<u8> for encoding
This commit is contained in:
Ryan Leckey 2020-03-26 19:16:49 -07:00
parent c6db69574b
commit 3103d50be8
33 changed files with 667 additions and 532 deletions

View File

@ -3,16 +3,16 @@ use byteorder::{ByteOrder, NetworkEndian};
use crate::arguments::Arguments;
use crate::encode::{Encode, IsNull};
use crate::io::BufMut;
use crate::postgres::Postgres;
use crate::postgres::{PgRawBuffer, PgTypeInfo, Postgres};
use crate::types::Type;
#[derive(Default)]
pub struct PgArguments {
// OIDs of the bind parameters
pub(super) types: Vec<u32>,
// Types of the bind parameters
pub(super) types: Vec<PgTypeInfo>,
// Write buffer for serializing bind values
pub(super) values: Vec<u8>,
pub(super) buffer: PgRawBuffer,
}
impl Arguments for PgArguments {
@ -20,25 +20,24 @@ impl Arguments for PgArguments {
fn reserve(&mut self, len: usize, size: usize) {
self.types.reserve(len);
self.values.reserve(size);
self.buffer.reserve(size);
}
fn add<T>(&mut self, value: T)
where
T: Type<Self::Database>,
T: Encode<Self::Database>,
T: Type<Self::Database> + Encode<Self::Database>,
{
// TODO: When/if we receive types that do _not_ support BINARY, we need to check here
// TODO: There is no need to be explicit unless we are expecting mixed BINARY / TEXT
self.types.push(<T as Type<Postgres>>::type_info().id.0);
self.types.push(<T as Type<Postgres>>::type_info());
let pos = self.values.len();
// Reserves space for the length of the value
let pos = self.buffer.len();
self.buffer.put_i32::<NetworkEndian>(0);
self.values.put_i32::<NetworkEndian>(0);
let len = if let IsNull::No = value.encode_nullable(&mut self.values) {
(self.values.len() - pos - 4) as i32
let len = if let IsNull::No = value.encode_nullable(&mut self.buffer) {
(self.buffer.len() - pos - 4) as i32
} else {
// Write a -1 for the len to indicate NULL
// TODO: It is illegal for [encode] to write any data
@ -47,6 +46,6 @@ impl Arguments for PgArguments {
};
// Write-back the len to the beginning of this frame (not including the len of len)
NetworkEndian::write_i32(&mut self.values[pos..], len as i32);
NetworkEndian::write_i32(&mut self.buffer[pos..], len as i32);
}
}

View File

@ -0,0 +1,57 @@
use crate::postgres::type_info::SharedStr;
use crate::postgres::PgConnection;
use byteorder::{ByteOrder, NetworkEndian};
use core::ops::{Deref, DerefMut};
#[derive(Debug, Default, PartialEq)]
pub struct PgRawBuffer {
inner: Vec<u8>,
// Whenever an `Encode` impl encounters a `PgTypeInfo` object that does not have an OID
// It pushes a "hole" that must be patched later
// The hole is a `usize` offset into the buffer with the type name that should be resolved
// This is done for Records and Arrays as the OID is needed well before we are in an async
// function and can just ask postgres
type_holes: Vec<(usize, SharedStr)>,
}
impl PgRawBuffer {
// Extends the inner buffer by enough space to have an OID
// Remembers where the OID goes and type name for the OID
pub(crate) fn push_type_hole(&mut self, type_name: &SharedStr) {
let offset = self.len();
self.extend_from_slice(&0_u32.to_be_bytes());
self.type_holes.push((offset, type_name.clone()));
}
// Patch all remembered type holes
// This should only go out and ask postgres if we have not seen the type name yet
pub(crate) async fn patch_type_holes(
&mut self,
connection: &mut PgConnection,
) -> crate::Result<()> {
for (offset, name) in &self.type_holes {
let oid = connection.get_type_id_by_name(&*name).await?;
NetworkEndian::write_u32(&mut self.inner[*offset..], oid);
}
Ok(())
}
}
impl Deref for PgRawBuffer {
type Target = Vec<u8>;
#[inline]
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl DerefMut for PgRawBuffer {
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
}

View File

@ -15,6 +15,7 @@ use crate::postgres::protocol::{
};
use crate::postgres::row::Statement;
use crate::postgres::stream::PgStream;
use crate::postgres::type_info::SharedStr;
use crate::postgres::{sasl, tls};
use crate::url::Url;
@ -89,6 +90,12 @@ pub struct PgConnection {
// cache statement ID -> statement description
pub(super) cache_statement: HashMap<StatementId, Arc<Statement>>,
// cache type name -> type OID
pub(super) cache_type_oid: HashMap<SharedStr, u32>,
// cache type OID -> type name
pub(super) cache_type_name: HashMap<u32, SharedStr>,
// Work buffer for the value ranges of the current row
// This is used as the backing memory for each Row's value indexes
pub(super) current_row_values: Vec<Option<Range<u32>>>,
@ -251,6 +258,8 @@ impl PgConnection {
current_row_values: Vec::with_capacity(10),
next_statement_id: 1,
is_ready: true,
cache_type_oid: HashMap::new(),
cache_type_name: HashMap::new(),
cache_statement_id: HashMap::with_capacity(10),
cache_statement: HashMap::with_capacity(10),
process_id: key_data.process_id,

View File

@ -53,7 +53,7 @@ impl<'c, 'q> Cursor<'c, 'q> for PgCursor<'c, 'q> {
}
}
fn parse_row_description(rd: RowDescription) -> Statement {
fn parse_row_description(conn: &mut PgConnection, rd: RowDescription) -> Statement {
let mut names = HashMap::new();
let mut columns = Vec::new();
@ -65,8 +65,10 @@ fn parse_row_description(rd: RowDescription) -> Statement {
names.insert(name.clone(), index);
}
let type_info = conn.get_type_info_by_oid(field.type_id.0);
columns.push(Column {
type_id: field.type_id,
type_info,
format: field.type_format,
});
}
@ -100,7 +102,11 @@ async fn expect_desc(conn: &mut PgConnection) -> crate::Result<Statement> {
}
};
Ok(description.map(parse_row_description).unwrap_or_default())
if let Some(description) = description {
Ok(parse_row_description(conn, description))
} else {
Ok(Statement::default())
}
}
// A form of describe that uses the statement cache
@ -159,7 +165,7 @@ async fn next<'a, 'c: 'a, 'q: 'a>(
Message::RowDescription => {
let rd = RowDescription::read(conn.stream.buffer())?;
cursor.statement = Arc::new(parse_row_description(rd));
cursor.statement = Arc::new(parse_row_description(conn, rd));
}
Message::DataRow => {

View File

@ -2,7 +2,9 @@
use crate::cursor::HasCursor;
use crate::database::Database;
use crate::postgres::{PgArguments, PgConnection, PgCursor, PgError, PgRow, PgTypeInfo, PgValue};
use crate::postgres::{
PgArguments, PgConnection, PgCursor, PgError, PgRawBuffer, PgRow, PgTypeInfo, PgValue,
};
use crate::row::HasRow;
use crate::value::HasRawValue;
@ -19,7 +21,7 @@ impl Database for Postgres {
type TableId = u32;
type RawBuffer = Vec<u8>;
type RawBuffer = PgRawBuffer;
type Error = PgError;
}

View File

@ -12,8 +12,12 @@ use crate::postgres::protocol::{
self, CommandComplete, Field, Message, ParameterDescription, ReadyForQuery, RowDescription,
StatementId, TypeFormat, TypeId,
};
use crate::postgres::types::SharedStr;
use crate::postgres::{PgArguments, PgConnection, PgCursor, PgRow, PgTypeInfo, Postgres};
use crate::postgres::type_info::SharedStr;
use crate::postgres::types::try_resolve_type_name;
use crate::postgres::{
PgArguments, PgConnection, PgCursor, PgQueryAs, PgRow, PgTypeInfo, Postgres,
};
use crate::query_as::query_as;
use crate::row::Row;
impl PgConnection {
@ -21,23 +25,40 @@ impl PgConnection {
self.stream.write(protocol::Query(query));
}
pub(crate) fn write_prepare(&mut self, query: &str, args: &PgArguments) -> StatementId {
pub(crate) async fn write_prepare(
&mut self,
query: &str,
args: &PgArguments,
) -> crate::Result<StatementId> {
if let Some(&id) = self.cache_statement_id.get(query) {
id
Ok(id)
} else {
let id = StatementId(self.next_statement_id);
self.next_statement_id += 1;
// Build a list of type OIDs from the type info array provided by PgArguments
// This may need to query Postgres for an OID of a user-defined type
let mut types = Vec::with_capacity(args.types.len());
for ty in &args.types {
types.push(if let Some(oid) = ty.id {
oid.0
} else {
self.get_type_id_by_name(&*ty.name).await?
});
}
self.stream.write(protocol::Parse {
statement: id,
param_types: &*types,
query,
param_types: &*args.types,
});
self.cache_statement_id.insert(query.into(), id);
id
Ok(id)
}
}
@ -45,15 +66,24 @@ impl PgConnection {
self.stream.write(d);
}
pub(crate) fn write_bind(&mut self, portal: &str, statement: StatementId, args: &PgArguments) {
pub(crate) async fn write_bind(
&mut self,
portal: &str,
statement: StatementId,
args: &mut PgArguments,
) -> crate::Result<()> {
args.buffer.patch_type_holes(self).await?;
self.stream.write(protocol::Bind {
portal,
statement,
formats: &[TypeFormat::Binary],
values_len: args.types.len() as i16,
values: &*args.values,
values: &*args.buffer,
result_formats: &[TypeFormat::Binary],
});
Ok(())
}
pub(crate) fn write_execute(&mut self, portal: &str, limit: i32) {
@ -95,14 +125,14 @@ impl PgConnection {
query: &str,
arguments: Option<PgArguments>,
) -> crate::Result<Option<StatementId>> {
let statement = if let Some(arguments) = arguments {
let statement = if let Some(mut arguments) = arguments {
// Check the statement cache for a statement ID that matches the given query
// If it doesn't exist, we generate a new statement ID and write out [Parse] to the
// connection command buffer
let statement = self.write_prepare(query, &arguments);
let statement = self.write_prepare(query, &arguments).await?;
// Next, [Bind] attaches the arguments to the statement and creates a named portal
self.write_bind("", statement, &arguments);
self.write_bind("", statement, &mut arguments).await?;
// Next, [Describe] will return the expected result columns and types
// Conditionally run [Describe] only if the results have not been cached
@ -142,7 +172,7 @@ impl PgConnection {
) -> crate::Result<Describe<Postgres>> {
self.is_ready = false;
let statement = self.write_prepare(query, &Default::default());
let statement = self.write_prepare(query, &Default::default()).await?;
self.write_describe(protocol::Describe::Statement(statement));
self.write_sync();
@ -209,6 +239,42 @@ impl PgConnection {
})
}
pub(crate) async fn get_type_id_by_name(&mut self, name: &str) -> crate::Result<u32> {
if let Some(oid) = self.cache_type_oid.get(name) {
return Ok(*oid);
}
// language=SQL
let (oid,): (u32,) = query_as(
"
SElECT oid FROM pg_catalog.pg_type WHERE typname = $1
",
)
.bind(name)
.fetch_one(&mut *self)
.await?;
let shared = SharedStr::from(name.to_owned());
self.cache_type_oid.insert(shared.clone(), oid);
self.cache_type_name.insert(oid, shared.clone());
Ok(oid)
}
pub(crate) fn get_type_info_by_oid(&mut self, oid: u32) -> PgTypeInfo {
if let Some(name) = try_resolve_type_name(oid) {
return PgTypeInfo::new(TypeId(oid), name);
}
if let Some(name) = self.cache_type_name.get(&oid) {
return PgTypeInfo::new(TypeId(oid), name);
}
// NOTE: The name isn't too important for the decode lifecycle
return PgTypeInfo::new(TypeId(oid), "");
}
async fn get_type_names(
&mut self,
ids: impl IntoIterator<Item = TypeId>,

View File

@ -1,16 +1,18 @@
//! **Postgres** database and connection types.
pub use arguments::PgArguments;
pub use buffer::PgRawBuffer;
pub use connection::PgConnection;
pub use cursor::PgCursor;
pub use database::Postgres;
pub use error::PgError;
pub use listen::{PgListener, PgNotification};
pub use row::PgRow;
pub use types::PgTypeInfo;
pub use type_info::PgTypeInfo;
pub use value::{PgData, PgValue};
mod arguments;
mod buffer;
mod connection;
mod cursor;
mod database;
@ -22,6 +24,7 @@ mod row;
mod sasl;
mod stream;
mod tls;
mod type_info;
pub mod types;
mod value;

View File

@ -1,3 +1,4 @@
use crate::postgres::types::try_resolve_type_name;
use std::fmt::{self, Display};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@ -66,7 +67,7 @@ impl TypeId {
pub(crate) const ARRAY_BPCHAR: TypeId = TypeId(1014);
pub(crate) const ARRAY_NAME: TypeId = TypeId(1003);
pub(crate) const ARRAY_NUMERIC: TypeId = TypeId(1700);
pub(crate) const ARRAY_NUMERIC: TypeId = TypeId(1231);
pub(crate) const ARRAY_DATE: TypeId = TypeId(1182);
pub(crate) const ARRAY_TIME: TypeId = TypeId(1183);
@ -84,80 +85,19 @@ impl TypeId {
pub(crate) const JSON: TypeId = TypeId(114);
pub(crate) const JSONB: TypeId = TypeId(3802);
// Records
pub(crate) const RECORD: TypeId = TypeId(2249);
pub(crate) const ARRAY_RECORD: TypeId = TypeId(2287);
}
impl Display for TypeId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
TypeId::BOOL => f.write_str("BOOL"),
TypeId::CHAR => f.write_str("\"CHAR\""),
TypeId::INT2 => f.write_str("INT2"),
TypeId::INT4 => f.write_str("INT4"),
TypeId::INT8 => f.write_str("INT8"),
TypeId::OID => f.write_str("OID"),
TypeId::FLOAT4 => f.write_str("FLOAT4"),
TypeId::FLOAT8 => f.write_str("FLOAT8"),
TypeId::NUMERIC => f.write_str("NUMERIC"),
TypeId::TEXT => f.write_str("TEXT"),
TypeId::VARCHAR => f.write_str("VARCHAR"),
TypeId::BPCHAR => f.write_str("BPCHAR"),
TypeId::UNKNOWN => f.write_str("UNKNOWN"),
TypeId::NAME => f.write_str("NAME"),
TypeId::DATE => f.write_str("DATE"),
TypeId::TIME => f.write_str("TIME"),
TypeId::TIMESTAMP => f.write_str("TIMESTAMP"),
TypeId::TIMESTAMPTZ => f.write_str("TIMESTAMPTZ"),
TypeId::BYTEA => f.write_str("BYTEA"),
TypeId::UUID => f.write_str("UUID"),
TypeId::CIDR => f.write_str("CIDR"),
TypeId::INET => f.write_str("INET"),
TypeId::ARRAY_BOOL => f.write_str("BOOL[]"),
TypeId::ARRAY_CHAR => f.write_str("\"CHAR\"[]"),
TypeId::ARRAY_INT2 => f.write_str("INT2[]"),
TypeId::ARRAY_INT4 => f.write_str("INT4[]"),
TypeId::ARRAY_INT8 => f.write_str("INT8[]"),
TypeId::ARRAY_OID => f.write_str("OID[]"),
TypeId::ARRAY_FLOAT4 => f.write_str("FLOAT4[]"),
TypeId::ARRAY_FLOAT8 => f.write_str("FLOAT8[]"),
TypeId::ARRAY_TEXT => f.write_str("TEXT[]"),
TypeId::ARRAY_VARCHAR => f.write_str("VARCHAR[]"),
TypeId::ARRAY_BPCHAR => f.write_str("BPCHAR[]"),
TypeId::ARRAY_NAME => f.write_str("NAME[]"),
TypeId::ARRAY_NUMERIC => f.write_str("NUMERIC[]"),
TypeId::ARRAY_DATE => f.write_str("DATE[]"),
TypeId::ARRAY_TIME => f.write_str("TIME[]"),
TypeId::ARRAY_TIMESTAMP => f.write_str("TIMESTAMP[]"),
TypeId::ARRAY_TIMESTAMPTZ => f.write_str("TIMESTAMPTZ[]"),
TypeId::ARRAY_BYTEA => f.write_str("BYTEA[]"),
TypeId::ARRAY_UUID => f.write_str("UUID[]"),
TypeId::ARRAY_CIDR => f.write_str("CIDR[]"),
TypeId::ARRAY_INET => f.write_str("INET[]"),
TypeId::JSON => f.write_str("JSON"),
TypeId::JSONB => f.write_str("JSONB"),
_ => write!(f, "<{}>", self.0),
if let Some(name) = try_resolve_type_name(self.0) {
f.write_str(name)
} else {
write!(f, "<{}>", self.0)
}
}
}

View File

@ -1,9 +1,9 @@
use std::collections::HashMap;
use std::sync::Arc;
use crate::postgres::protocol::{DataRow, TypeFormat, TypeId};
use crate::postgres::protocol::{DataRow, TypeFormat};
use crate::postgres::value::PgValue;
use crate::postgres::Postgres;
use crate::postgres::{PgTypeInfo, Postgres};
use crate::row::{ColumnIndex, Row};
// A statement has 0 or more columns being returned from the database
@ -11,7 +11,7 @@ use crate::row::{ColumnIndex, Row};
// For simple (unprepared) queries, format will always be text
// For prepared queries, format will _almost_ always be binary
pub(crate) struct Column {
pub(crate) type_id: TypeId,
pub(crate) type_info: PgTypeInfo,
pub(crate) format: TypeFormat,
}
@ -53,9 +53,9 @@ impl<'c> Row<'c> for PgRow<'c> {
let column = &self.statement.columns[index];
let buffer = self.data.get(index);
let value = match (column.format, buffer) {
(_, None) => PgValue::null(column.type_id),
(TypeFormat::Binary, Some(buf)) => PgValue::bytes(column.type_id, buf),
(TypeFormat::Text, Some(buf)) => PgValue::utf8(column.type_id, buf)?,
(_, None) => PgValue::null(),
(TypeFormat::Binary, Some(buf)) => PgValue::bytes(column.type_info.clone(), buf),
(TypeFormat::Text, Some(buf)) => PgValue::utf8(column.type_info.clone(), buf)?,
};
Ok(value)

View File

@ -0,0 +1,184 @@
use crate::postgres::protocol::TypeId;
use crate::types::TypeInfo;
use std::borrow::Borrow;
use std::fmt;
use std::fmt::Display;
use std::ops::Deref;
use std::sync::Arc;
/// Type information for a Postgres SQL type.
#[derive(Debug, Clone)]
pub struct PgTypeInfo {
pub(crate) id: Option<TypeId>,
pub(crate) name: SharedStr,
}
impl PgTypeInfo {
pub(crate) fn new(id: TypeId, name: impl Into<SharedStr>) -> Self {
Self {
id: Some(id),
name: name.into(),
}
}
/// Create a `PgTypeInfo` from a type name.
///
/// The OID for the type will be fetched from Postgres on bind or decode of
/// a value of this type. The fetched OID will be cached per-connection.
pub const fn with_name(name: &'static str) -> Self {
Self {
id: None,
name: SharedStr::Static(name),
}
}
#[doc(hidden)]
pub fn type_feature_gate(&self) -> Option<&'static str> {
match self.id? {
TypeId::DATE | TypeId::TIME | TypeId::TIMESTAMP | TypeId::TIMESTAMPTZ => Some("chrono"),
TypeId::UUID => Some("uuid"),
TypeId::JSON | TypeId::JSONB => Some("json"),
// we can support decoding `PgNumeric` but it's decidedly less useful to the layman
TypeId::NUMERIC => Some("bigdecimal"),
TypeId::CIDR | TypeId::INET => Some("ipnetwork"),
_ => None,
}
}
}
impl Display for PgTypeInfo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.name)
}
}
impl PartialEq<PgTypeInfo> for PgTypeInfo {
fn eq(&self, other: &PgTypeInfo) -> bool {
// Postgres is strongly typed (mostly) so the rules that make sense here are equivalent
// to the rules that make sense in [compatible]
self.compatible(other)
}
}
impl TypeInfo for PgTypeInfo {
fn compatible(&self, other: &Self) -> bool {
if let (Some(self_id), Some(other_id)) = (self.id, other.id) {
return match (self_id, other_id) {
(TypeId::CIDR, TypeId::INET)
| (TypeId::INET, TypeId::CIDR)
| (TypeId::ARRAY_CIDR, TypeId::ARRAY_INET)
| (TypeId::ARRAY_INET, TypeId::ARRAY_CIDR) => true,
// the following text-like types are compatible
(TypeId::VARCHAR, other)
| (TypeId::TEXT, other)
| (TypeId::BPCHAR, other)
| (TypeId::NAME, other)
| (TypeId::UNKNOWN, other)
if match other {
TypeId::VARCHAR
| TypeId::TEXT
| TypeId::BPCHAR
| TypeId::NAME
| TypeId::UNKNOWN => true,
_ => false,
} =>
{
true
}
// the following text-like array types are compatible
(TypeId::ARRAY_VARCHAR, other)
| (TypeId::ARRAY_TEXT, other)
| (TypeId::ARRAY_BPCHAR, other)
| (TypeId::ARRAY_NAME, other)
if match other {
TypeId::ARRAY_VARCHAR
| TypeId::ARRAY_TEXT
| TypeId::ARRAY_BPCHAR
| TypeId::ARRAY_NAME => true,
_ => false,
} =>
{
true
}
// JSON <=> JSONB
(TypeId::JSON, other) | (TypeId::JSONB, other)
if match other {
TypeId::JSON | TypeId::JSONB => true,
_ => false,
} =>
{
true
}
_ => self_id.0 == other_id.0,
};
}
// If the type names match, the types are equivalent (and compatible)
// If the type names are the empty string, they are invalid type names
if (&*self.name == &*other.name) && !self.name.is_empty() {
return true;
}
// TODO: More efficient way to do case insensitive comparison
if !self.name.is_empty() && (&*self.name.to_lowercase() == &*other.name.to_lowercase()) {
return true;
}
false
}
}
/// Copy of `Cow` but for strings; clones guaranteed to be cheap.
#[derive(Clone, Debug, PartialEq, Hash, Eq)]
pub(crate) enum SharedStr {
Static(&'static str),
Arc(Arc<str>),
}
impl Deref for SharedStr {
type Target = str;
fn deref(&self) -> &str {
match self {
SharedStr::Static(s) => s,
SharedStr::Arc(s) => s,
}
}
}
impl Borrow<str> for SharedStr {
fn borrow(&self) -> &str {
&**self
}
}
impl<'a> From<&'a SharedStr> for SharedStr {
fn from(s: &'a SharedStr) -> Self {
s.clone()
}
}
impl From<&'static str> for SharedStr {
fn from(s: &'static str) -> Self {
SharedStr::Static(s)
}
}
impl From<String> for SharedStr {
#[inline]
fn from(s: String) -> Self {
SharedStr::Arc(s.into())
}
}
impl fmt::Display for SharedStr {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.pad(self)
}
}

View File

@ -3,9 +3,8 @@
use crate::database::Database;
use crate::decode::Decode;
use crate::encode::Encode;
use crate::postgres::database::Postgres;
use crate::postgres::types::raw::{PgArrayDecoder, PgArrayEncoder};
use crate::postgres::PgValue;
use crate::postgres::{PgRawBuffer, PgValue, Postgres};
use crate::types::Type;
impl<T> Encode<Postgres> for [T]
@ -13,7 +12,7 @@ where
T: Encode<Postgres>,
T: Type<Postgres>,
{
fn encode(&self, buf: &mut Vec<u8>) {
fn encode(&self, buf: &mut PgRawBuffer) {
let mut encoder = PgArrayEncoder::new(buf);
for item in self {
@ -29,7 +28,7 @@ where
T: Encode<Postgres>,
T: Type<Postgres>,
{
fn encode(&self, buf: &mut Vec<u8>) {
fn encode(&self, buf: &mut PgRawBuffer) {
self.as_slice().encode(buf)
}
}

View File

@ -6,7 +6,7 @@ use num_bigint::{BigInt, Sign};
use crate::decode::Decode;
use crate::encode::Encode;
use crate::postgres::{PgData, PgTypeInfo, PgValue, Postgres};
use crate::postgres::{PgData, PgRawBuffer, PgTypeInfo, PgValue, Postgres};
use crate::types::Type;
use super::raw::{PgNumeric, PgNumericSign};
@ -135,7 +135,7 @@ impl TryFrom<PgNumeric> for BigDecimal {
/// ### Panics
/// If this `BigDecimal` cannot be represented by [PgNumeric].
impl Encode<Postgres> for BigDecimal {
fn encode(&self, buf: &mut Vec<u8>) {
fn encode(&self, buf: &mut PgRawBuffer) {
// this copy is unfortunately necessary because we'd have to use `.to_bigint_and_exponent()`
// otherwise which does the exact same thing, so for the explicit impl might as well allow
// the user to skip one of the copies if they have an owned value

View File

@ -1,7 +1,7 @@
use crate::decode::Decode;
use crate::encode::Encode;
use crate::postgres::protocol::TypeId;
use crate::postgres::{PgData, PgTypeInfo, PgValue, Postgres};
use crate::postgres::{PgData, PgRawBuffer, PgTypeInfo, PgValue, Postgres};
use crate::types::Type;
impl Type<Postgres> for bool {
@ -22,7 +22,7 @@ impl Type<Postgres> for Vec<bool> {
}
impl Encode<Postgres> for bool {
fn encode(&self, buf: &mut Vec<u8>) {
fn encode(&self, buf: &mut PgRawBuffer) {
buf.push(*self as u8);
}
}

View File

@ -1,8 +1,7 @@
use crate::decode::Decode;
use crate::encode::Encode;
use crate::postgres::protocol::TypeId;
use crate::postgres::types::PgTypeInfo;
use crate::postgres::{PgData, PgValue, Postgres};
use crate::postgres::{PgData, PgRawBuffer, PgTypeInfo, PgValue, Postgres};
use crate::types::Type;
impl Type<Postgres> for [u8] {
@ -30,13 +29,13 @@ impl Type<Postgres> for Vec<u8> {
}
impl Encode<Postgres> for [u8] {
fn encode(&self, buf: &mut Vec<u8>) {
fn encode(&self, buf: &mut PgRawBuffer) {
buf.extend_from_slice(self);
}
}
impl Encode<Postgres> for Vec<u8> {
fn encode(&self, buf: &mut Vec<u8>) {
fn encode(&self, buf: &mut PgRawBuffer) {
<[u8] as Encode<Postgres>>::encode(self, buf);
}
}

View File

@ -7,8 +7,7 @@ use chrono::{DateTime, Duration, Local, NaiveDate, NaiveDateTime, NaiveTime, Tim
use crate::decode::Decode;
use crate::encode::Encode;
use crate::postgres::protocol::TypeId;
use crate::postgres::types::PgTypeInfo;
use crate::postgres::{PgData, PgValue, Postgres};
use crate::postgres::{PgData, PgRawBuffer, PgTypeInfo, PgValue, Postgres};
use crate::types::Type;
use crate::Error;
@ -108,7 +107,7 @@ impl<'de> Decode<'de, Postgres> for NaiveTime {
}
impl Encode<Postgres> for NaiveTime {
fn encode(&self, buf: &mut Vec<u8>) {
fn encode(&self, buf: &mut PgRawBuffer) {
let micros = (*self - NaiveTime::from_hms(0, 0, 0))
.num_microseconds()
.expect("shouldn't overflow");
@ -136,7 +135,7 @@ impl<'de> Decode<'de, Postgres> for NaiveDate {
}
impl Encode<Postgres> for NaiveDate {
fn encode(&self, buf: &mut Vec<u8>) {
fn encode(&self, buf: &mut PgRawBuffer) {
let days: i32 = self
.signed_duration_since(NaiveDate::from_ymd(2000, 1, 1))
.num_days()
@ -191,7 +190,7 @@ impl<'de> Decode<'de, Postgres> for NaiveDateTime {
}
impl Encode<Postgres> for NaiveDateTime {
fn encode(&self, buf: &mut Vec<u8>) {
fn encode(&self, buf: &mut PgRawBuffer) {
let micros = self
.signed_duration_since(postgres_epoch().naive_utc())
.num_microseconds()
@ -223,7 +222,7 @@ impl<Tz: TimeZone> Encode<Postgres> for DateTime<Tz>
where
Tz::Offset: Copy,
{
fn encode(&self, buf: &mut Vec<u8>) {
fn encode(&self, buf: &mut PgRawBuffer) {
Encode::<Postgres>::encode(&self.naive_utc(), buf);
}
@ -238,60 +237,60 @@ fn postgres_epoch() -> DateTime<Utc> {
#[test]
fn test_encode_time() {
let mut buf = Vec::new();
let mut buf = PgRawBuffer::default();
Encode::<Postgres>::encode(&NaiveTime::from_hms(0, 0, 0), &mut buf);
assert_eq!(buf, [0; 8]);
assert_eq!(&**buf, [0; 8]);
buf.clear();
// one second
Encode::<Postgres>::encode(&NaiveTime::from_hms(0, 0, 1), &mut buf);
assert_eq!(buf, 1_000_000i64.to_be_bytes());
assert_eq!(&**buf, 1_000_000i64.to_be_bytes());
buf.clear();
// two hours
Encode::<Postgres>::encode(&NaiveTime::from_hms(2, 0, 0), &mut buf);
let expected = 1_000_000i64 * 60 * 60 * 2;
assert_eq!(buf, expected.to_be_bytes());
assert_eq!(&**buf, expected.to_be_bytes());
buf.clear();
// 3:14:15.000001
Encode::<Postgres>::encode(&NaiveTime::from_hms_micro(3, 14, 15, 1), &mut buf);
let expected = 1_000_000i64 * 60 * 60 * 3 + 1_000_000i64 * 60 * 14 + 1_000_000i64 * 15 + 1;
assert_eq!(buf, expected.to_be_bytes());
assert_eq!(&**buf, expected.to_be_bytes());
buf.clear();
}
#[test]
fn test_decode_time() {
let buf = [0u8; 8];
let time: NaiveTime = Decode::<Postgres>::decode(PgValue::bytes(TypeId(0), &buf)).unwrap();
let time: NaiveTime = Decode::<Postgres>::decode(PgValue::from_bytes(&buf)).unwrap();
assert_eq!(time, NaiveTime::from_hms(0, 0, 0),);
// half an hour
let buf = (1_000_000i64 * 60 * 30).to_be_bytes();
let time: NaiveTime = Decode::<Postgres>::decode(PgValue::bytes(TypeId(0), &buf)).unwrap();
let time: NaiveTime = Decode::<Postgres>::decode(PgValue::from_bytes(&buf)).unwrap();
assert_eq!(time, NaiveTime::from_hms(0, 30, 0),);
// 12:53:05.125305
let buf = (1_000_000i64 * 60 * 60 * 12 + 1_000_000i64 * 60 * 53 + 1_000_000i64 * 5 + 125305)
.to_be_bytes();
let time: NaiveTime = Decode::<Postgres>::decode(PgValue::bytes(TypeId(0), &buf)).unwrap();
let time: NaiveTime = Decode::<Postgres>::decode(PgValue::from_bytes(&buf)).unwrap();
assert_eq!(time, NaiveTime::from_hms_micro(12, 53, 5, 125305),);
}
#[test]
fn test_encode_datetime() {
let mut buf = Vec::new();
let mut buf = PgRawBuffer::default();
let date = postgres_epoch();
Encode::<Postgres>::encode(&date, &mut buf);
assert_eq!(buf, [0; 8]);
assert_eq!(&**buf, [0; 8]);
buf.clear();
// one hour past epoch
let date2 = postgres_epoch() + Duration::hours(1);
Encode::<Postgres>::encode(&date2, &mut buf);
assert_eq!(buf, 3_600_000_000i64.to_be_bytes());
assert_eq!(&**buf, 3_600_000_000i64.to_be_bytes());
buf.clear();
// some random date
@ -300,57 +299,57 @@ fn test_encode_datetime() {
.num_microseconds()
.unwrap());
Encode::<Postgres>::encode(&date3, &mut buf);
assert_eq!(buf, expected.to_be_bytes());
assert_eq!(&**buf, expected.to_be_bytes());
buf.clear();
}
#[test]
fn test_decode_datetime() {
let buf = [0u8; 8];
let date: NaiveDateTime = Decode::<Postgres>::decode(PgValue::bytes(TypeId(0), &buf)).unwrap();
let date: NaiveDateTime = Decode::<Postgres>::decode(PgValue::from_bytes(&buf)).unwrap();
assert_eq!(date.to_string(), "2000-01-01 00:00:00");
let buf = 3_600_000_000i64.to_be_bytes();
let date: NaiveDateTime = Decode::<Postgres>::decode(PgValue::bytes(TypeId(0), &buf)).unwrap();
let date: NaiveDateTime = Decode::<Postgres>::decode(PgValue::from_bytes(&buf)).unwrap();
assert_eq!(date.to_string(), "2000-01-01 01:00:00");
let buf = 629_377_265_000_000i64.to_be_bytes();
let date: NaiveDateTime = Decode::<Postgres>::decode(PgValue::bytes(TypeId(0), &buf)).unwrap();
let date: NaiveDateTime = Decode::<Postgres>::decode(PgValue::from_bytes(&buf)).unwrap();
assert_eq!(date.to_string(), "2019-12-11 11:01:05");
}
#[test]
fn test_encode_date() {
let mut buf = Vec::new();
let mut buf = PgRawBuffer::default();
let date = NaiveDate::from_ymd(2000, 1, 1);
Encode::<Postgres>::encode(&date, &mut buf);
assert_eq!(buf, [0; 4]);
assert_eq!(&**buf, [0; 4]);
buf.clear();
let date2 = NaiveDate::from_ymd(2001, 1, 1);
Encode::<Postgres>::encode(&date2, &mut buf);
// 2000 was a leap year
assert_eq!(buf, 366i32.to_be_bytes());
assert_eq!(&**buf, 366i32.to_be_bytes());
buf.clear();
let date3 = NaiveDate::from_ymd(2019, 12, 11);
Encode::<Postgres>::encode(&date3, &mut buf);
assert_eq!(buf, 7284i32.to_be_bytes());
assert_eq!(&**buf, 7284i32.to_be_bytes());
buf.clear();
}
#[test]
fn test_decode_date() {
let buf = [0; 4];
let date: NaiveDate = Decode::<Postgres>::decode(PgValue::bytes(TypeId(0), &buf)).unwrap();
let date: NaiveDate = Decode::<Postgres>::decode(PgValue::from_bytes(&buf)).unwrap();
assert_eq!(date.to_string(), "2000-01-01");
let buf = 366i32.to_be_bytes();
let date: NaiveDate = Decode::<Postgres>::decode(PgValue::bytes(TypeId(0), &buf)).unwrap();
let date: NaiveDate = Decode::<Postgres>::decode(PgValue::from_bytes(&buf)).unwrap();
assert_eq!(date.to_string(), "2001-01-01");
let buf = 7284i32.to_be_bytes();
let date: NaiveDate = Decode::<Postgres>::decode(PgValue::bytes(TypeId(0), &buf)).unwrap();
let date: NaiveDate = Decode::<Postgres>::decode(PgValue::from_bytes(&buf)).unwrap();
assert_eq!(date.to_string(), "2019-12-11");
}

View File

@ -6,8 +6,7 @@ use crate::decode::Decode;
use crate::encode::Encode;
use crate::error::Error;
use crate::postgres::protocol::TypeId;
use crate::postgres::types::PgTypeInfo;
use crate::postgres::{PgData, PgValue, Postgres};
use crate::postgres::{PgData, PgRawBuffer, PgTypeInfo, PgValue, Postgres};
use crate::types::Type;
impl Type<Postgres> for f32 {
@ -28,7 +27,7 @@ impl Type<Postgres> for Vec<f32> {
}
impl Encode<Postgres> for f32 {
fn encode(&self, buf: &mut Vec<u8>) {
fn encode(&self, buf: &mut PgRawBuffer) {
<i32 as Encode<Postgres>>::encode(&(self.to_bits() as i32), buf)
}
}
@ -64,7 +63,7 @@ impl Type<Postgres> for Vec<f64> {
}
impl Encode<Postgres> for f64 {
fn encode(&self, buf: &mut Vec<u8>) {
fn encode(&self, buf: &mut PgRawBuffer) {
<i64 as Encode<Postgres>>::encode(&(self.to_bits() as i64), buf)
}
}

View File

@ -5,8 +5,7 @@ use byteorder::{NetworkEndian, ReadBytesExt};
use crate::decode::Decode;
use crate::encode::Encode;
use crate::postgres::protocol::TypeId;
use crate::postgres::types::PgTypeInfo;
use crate::postgres::{PgData, PgValue, Postgres};
use crate::postgres::{PgData, PgRawBuffer, PgTypeInfo, PgValue, Postgres};
use crate::types::Type;
use crate::Error;
@ -29,7 +28,7 @@ impl Type<Postgres> for Vec<i8> {
}
impl Encode<Postgres> for i8 {
fn encode(&self, buf: &mut Vec<u8>) {
fn encode(&self, buf: &mut PgRawBuffer) {
buf.extend_from_slice(&self.to_be_bytes());
}
}
@ -62,7 +61,7 @@ impl Type<Postgres> for Vec<i16> {
}
impl Encode<Postgres> for i16 {
fn encode(&self, buf: &mut Vec<u8>) {
fn encode(&self, buf: &mut PgRawBuffer) {
buf.extend_from_slice(&self.to_be_bytes());
}
}
@ -95,7 +94,7 @@ impl Type<Postgres> for Vec<i32> {
}
impl Encode<Postgres> for i32 {
fn encode(&self, buf: &mut Vec<u8>) {
fn encode(&self, buf: &mut PgRawBuffer) {
buf.extend_from_slice(&self.to_be_bytes());
}
}
@ -128,7 +127,7 @@ impl Type<Postgres> for Vec<u32> {
}
impl Encode<Postgres> for u32 {
fn encode(&self, buf: &mut Vec<u8>) {
fn encode(&self, buf: &mut PgRawBuffer) {
buf.extend_from_slice(&self.to_be_bytes());
}
}
@ -160,7 +159,7 @@ impl Type<Postgres> for Vec<i64> {
}
impl Encode<Postgres> for i64 {
fn encode(&self, buf: &mut Vec<u8>) {
fn encode(&self, buf: &mut PgRawBuffer) {
buf.extend_from_slice(&self.to_be_bytes());
}
}

View File

@ -5,9 +5,8 @@ use ipnetwork::{IpNetwork, Ipv4Network, Ipv6Network};
use crate::decode::Decode;
use crate::encode::Encode;
use crate::postgres::protocol::TypeId;
use crate::postgres::types::PgTypeInfo;
use crate::postgres::value::PgValue;
use crate::postgres::{PgData, Postgres};
use crate::postgres::{PgData, PgRawBuffer, PgTypeInfo, Postgres};
use crate::types::Type;
use crate::Error;
@ -38,7 +37,7 @@ impl Type<Postgres> for [IpNetwork] {
}
impl Encode<Postgres> for IpNetwork {
fn encode(&self, buf: &mut Vec<u8>) {
fn encode(&self, buf: &mut PgRawBuffer) {
match self {
IpNetwork::V4(net) => {
buf.push(PGSQL_AF_INET);

View File

@ -2,7 +2,7 @@ use crate::decode::Decode;
use crate::encode::Encode;
use crate::io::{Buf, BufMut};
use crate::postgres::protocol::TypeId;
use crate::postgres::{PgData, PgTypeInfo, PgValue, Postgres};
use crate::postgres::{PgData, PgRawBuffer, PgTypeInfo, PgValue, Postgres};
use crate::types::{Json, Type};
use crate::value::RawValue;
use serde::{Deserialize, Serialize};
@ -22,7 +22,7 @@ impl Type<Postgres> for JsonValue {
}
impl Encode<Postgres> for JsonValue {
fn encode(&self, buf: &mut Vec<u8>) {
fn encode(&self, buf: &mut PgRawBuffer) {
Json(self).encode(buf)
}
}
@ -40,7 +40,7 @@ impl Type<Postgres> for &'_ JsonRawValue {
}
impl Encode<Postgres> for &'_ JsonRawValue {
fn encode(&self, buf: &mut Vec<u8>) {
fn encode(&self, buf: &mut PgRawBuffer) {
Json(self).encode(buf)
}
}
@ -61,11 +61,11 @@ impl<T> Encode<Postgres> for Json<T>
where
T: Serialize,
{
fn encode(&self, buf: &mut Vec<u8>) {
// JSONB version (as of 2020-03-20 )
fn encode(&self, buf: &mut PgRawBuffer) {
// JSONB version (as of 2020-03-20)
buf.put_u8(1);
serde_json::to_writer(buf, &self.0)
serde_json::to_writer(&mut **buf, &self.0)
.expect("failed to serialize json for encoding to database");
}
}
@ -79,7 +79,7 @@ where
(match value.try_get()? {
PgData::Text(s) => serde_json::from_str(s),
PgData::Binary(mut buf) => {
if value.type_info().as_ref().map(|info| info.id) == Some(TypeId::JSONB) {
if value.type_info().as_ref().and_then(|info| info.id) == Some(TypeId::JSONB) {
let version = buf.get_u8()?;
assert_eq!(

View File

@ -128,14 +128,9 @@
//! a potentially `NULL` value from Postgres.
//!
use std::fmt::{self, Debug, Display};
use std::ops::Deref;
use std::sync::Arc;
use crate::decode::Decode;
use crate::postgres::protocol::TypeId;
use crate::postgres::{PgValue, Postgres};
use crate::types::TypeInfo;
mod array;
mod bool;
@ -167,125 +162,9 @@ mod json;
#[cfg(feature = "ipnetwork")]
mod ipnetwork;
/// Type information for a Postgres SQL type.
#[derive(Debug, Clone)]
pub struct PgTypeInfo {
pub(crate) id: TypeId,
pub(crate) name: Option<SharedStr>,
}
impl PgTypeInfo {
pub(crate) fn new(id: TypeId, name: impl Into<SharedStr>) -> Self {
Self {
id,
name: Some(name.into()),
}
}
/// Create a `PgTypeInfo` from a type's object identifier.
///
/// The object identifier of a type can be queried with
/// `SELECT oid FROM pg_type WHERE typname = <name>;`
pub fn with_oid(oid: u32) -> Self {
Self {
id: TypeId(oid),
name: None,
}
}
#[doc(hidden)]
pub fn type_feature_gate(&self) -> Option<&'static str> {
match self.id {
TypeId::DATE | TypeId::TIME | TypeId::TIMESTAMP | TypeId::TIMESTAMPTZ => Some("chrono"),
TypeId::UUID => Some("uuid"),
// we can support decoding `PgNumeric` but it's decidedly less useful to the layman
TypeId::NUMERIC => Some("bigdecimal"),
TypeId::CIDR | TypeId::INET => Some("ipnetwork"),
_ => None,
}
}
#[doc(hidden)]
pub fn oid(&self) -> u32 {
self.id.0
}
}
impl Display for PgTypeInfo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(ref name) = self.name {
write!(f, "{}", *name)
} else {
write!(f, "{}", self.id)
}
}
}
impl PartialEq<PgTypeInfo> for PgTypeInfo {
fn eq(&self, other: &PgTypeInfo) -> bool {
// Postgres is strongly typed (mostly) so the rules that make sense here are equivalent
// to the rules that make sense in [compatible]
self.compatible(other)
}
}
impl TypeInfo for PgTypeInfo {
fn compatible(&self, other: &Self) -> bool {
match (self.id, other.id) {
(TypeId::CIDR, TypeId::INET)
| (TypeId::INET, TypeId::CIDR)
| (TypeId::ARRAY_CIDR, TypeId::ARRAY_INET)
| (TypeId::ARRAY_INET, TypeId::ARRAY_CIDR) => true,
// the following text-like types are compatible
(TypeId::VARCHAR, other)
| (TypeId::TEXT, other)
| (TypeId::BPCHAR, other)
| (TypeId::NAME, other)
| (TypeId::UNKNOWN, other)
if match other {
TypeId::VARCHAR
| TypeId::TEXT
| TypeId::BPCHAR
| TypeId::NAME
| TypeId::UNKNOWN => true,
_ => false,
} =>
{
true
}
// the following text-like array types are compatible
(TypeId::ARRAY_VARCHAR, other)
| (TypeId::ARRAY_TEXT, other)
| (TypeId::ARRAY_BPCHAR, other)
| (TypeId::ARRAY_NAME, other)
if match other {
TypeId::ARRAY_VARCHAR
| TypeId::ARRAY_TEXT
| TypeId::ARRAY_BPCHAR
| TypeId::ARRAY_NAME => true,
_ => false,
} =>
{
true
}
// JSON <=> JSONB
(TypeId::JSON, other) | (TypeId::JSONB, other)
if match other {
TypeId::JSON | TypeId::JSONB => true,
_ => false,
} =>
{
true
}
_ => self.id.0 == other.id.0,
}
}
}
// Implement `Decode` for all postgres types
// The concept of a nullable `RawValue` is db-specific
// `Type` is implemented generically at src/types.rs
impl<'de, T> Decode<'de, Postgres> for Option<T>
where
T: Decode<'de, Postgres>,
@ -299,45 +178,82 @@ where
}
}
/// Copy of `Cow` but for strings; clones guaranteed to be cheap.
#[derive(Clone, Debug)]
pub(crate) enum SharedStr {
Static(&'static str),
Arc(Arc<str>),
}
// Try to resolve a _static_ type name from an OID
pub(crate) fn try_resolve_type_name(oid: u32) -> Option<&'static str> {
Some(match TypeId(oid) {
TypeId::BOOL => "BOOL",
impl Deref for SharedStr {
type Target = str;
TypeId::CHAR => "\"CHAR\"",
fn deref(&self) -> &str {
match self {
SharedStr::Static(s) => s,
SharedStr::Arc(s) => s,
TypeId::INT2 => "INT2",
TypeId::INT4 => "INT4",
TypeId::INT8 => "INT8",
TypeId::OID => "OID",
TypeId::FLOAT4 => "FLOAT4",
TypeId::FLOAT8 => "FLOAT8",
TypeId::NUMERIC => "NUMERIC",
TypeId::TEXT => "TEXT",
TypeId::VARCHAR => "VARCHAR",
TypeId::BPCHAR => "BPCHAR",
TypeId::UNKNOWN => "UNKNOWN",
TypeId::NAME => "NAME",
TypeId::DATE => "DATE",
TypeId::TIME => "TIME",
TypeId::TIMESTAMP => "TIMESTAMP",
TypeId::TIMESTAMPTZ => "TIMESTAMPTZ",
TypeId::BYTEA => "BYTEA",
TypeId::UUID => "UUID",
TypeId::CIDR => "CIDR",
TypeId::INET => "INET",
TypeId::ARRAY_BOOL => "BOOL[]",
TypeId::ARRAY_CHAR => "\"CHAR\"[]",
TypeId::ARRAY_INT2 => "INT2[]",
TypeId::ARRAY_INT4 => "INT4[]",
TypeId::ARRAY_INT8 => "INT8[]",
TypeId::ARRAY_OID => "OID[]",
TypeId::ARRAY_FLOAT4 => "FLOAT4[]",
TypeId::ARRAY_FLOAT8 => "FLOAT8[]",
TypeId::ARRAY_TEXT => "TEXT[]",
TypeId::ARRAY_VARCHAR => "VARCHAR[]",
TypeId::ARRAY_BPCHAR => "BPCHAR[]",
TypeId::ARRAY_NAME => "NAME[]",
TypeId::ARRAY_NUMERIC => "NUMERIC[]",
TypeId::ARRAY_DATE => "DATE[]",
TypeId::ARRAY_TIME => "TIME[]",
TypeId::ARRAY_TIMESTAMP => "TIMESTAMP[]",
TypeId::ARRAY_TIMESTAMPTZ => "TIMESTAMPTZ[]",
TypeId::ARRAY_BYTEA => "BYTEA[]",
TypeId::ARRAY_UUID => "UUID[]",
TypeId::ARRAY_CIDR => "CIDR[]",
TypeId::ARRAY_INET => "INET[]",
TypeId::JSON => "JSON",
TypeId::JSONB => "JSONB",
TypeId::RECORD => "RECORD",
TypeId::ARRAY_RECORD => "RECORD[]",
_ => {
return None;
}
}
}
impl<'a> From<&'a SharedStr> for SharedStr {
fn from(s: &'a SharedStr) -> Self {
s.clone()
}
}
impl From<&'static str> for SharedStr {
fn from(s: &'static str) -> Self {
SharedStr::Static(s)
}
}
impl From<String> for SharedStr {
#[inline]
fn from(s: String) -> Self {
SharedStr::Arc(s.into())
}
}
impl fmt::Display for SharedStr {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.pad(self)
}
})
}

View File

@ -2,7 +2,7 @@ use crate::decode::Decode;
use crate::encode::{Encode, IsNull};
use crate::io::{Buf, BufMut};
use crate::postgres::types::raw::sequence::PgSequenceDecoder;
use crate::postgres::{PgData, PgValue, Postgres};
use crate::postgres::{PgData, PgRawBuffer, PgValue, Postgres};
use crate::types::Type;
use byteorder::BE;
use std::marker::PhantomData;
@ -13,16 +13,15 @@ use std::marker::PhantomData;
pub(crate) struct PgArrayEncoder<'enc, T> {
count: usize,
len_start_index: usize,
buf: &'enc mut Vec<u8>,
buf: &'enc mut PgRawBuffer,
phantom: PhantomData<T>,
}
impl<'enc, T> PgArrayEncoder<'enc, T>
where
T: Encode<Postgres>,
T: Type<Postgres>,
T: Encode<Postgres> + Type<Postgres>,
{
pub(crate) fn new(buf: &'enc mut Vec<u8>) -> Self {
pub(crate) fn new(buf: &'enc mut PgRawBuffer) -> Self {
let ty = <T as Type<Postgres>>::type_info();
// ndim
@ -31,8 +30,15 @@ where
// dataoffset
buf.put_i32::<BE>(0);
// elemtype
buf.put_i32::<BE>(ty.id.0 as i32);
// [elemtype] element type OID
if let Some(oid) = ty.id {
// write oid
buf.extend(&oid.0.to_be_bytes());
} else {
// write hole for this oid
buf.push_type_hole(&ty.name);
}
let len_start_index = buf.len();
// dimensions
@ -96,14 +102,15 @@ where
pub(crate) fn new(value: PgValue<'de>) -> crate::Result<Self> {
let mut data = value.try_get()?;
match data {
let element_oid = match data {
PgData::Binary(ref mut buf) => {
// number of dimensions of the array
let ndim = buf.get_i32::<BE>()?;
if ndim == 0 {
// ndim of 0 is an empty array
return Ok(Self {
inner: PgSequenceDecoder::new(PgData::Binary(&[]), false),
inner: PgSequenceDecoder::new(PgData::Binary(&[]), None),
phantom: PhantomData,
});
}
@ -119,12 +126,8 @@ where
// this doesn't matter as the data is always at the end of the header
let _dataoffset = buf.get_i32::<BE>()?;
// TODO: Validate element type with whatever framework is put in place to do so
// As a reminder, we have no way to do this yet and still account for [compatible]
// types.
// element type OID
let _elemtype = buf.get_i32::<BE>()?;
let element_oid = buf.get_u32::<BE>()?;
// length of each array axis
let _dimensions = buf.get_i32::<BE>()?;
@ -138,13 +141,15 @@ where
lower_bnds
));
}
Some(element_oid)
}
PgData::Text(_) => {}
}
PgData::Text(_) => None,
};
Ok(Self {
inner: PgSequenceDecoder::new(data, false),
inner: PgSequenceDecoder::new(data, element_oid),
phantom: PhantomData,
})
}
@ -171,14 +176,13 @@ where
mod tests {
use super::PgArrayDecoder;
use super::PgArrayEncoder;
use crate::postgres::protocol::TypeId;
use crate::postgres::PgValue;
use crate::postgres::{PgRawBuffer, PgValue, Postgres};
const BUF_BINARY_I32: &[u8] = b"\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x17\x00\x00\x00\x04\x00\x00\x00\x01\x00\x00\x00\x04\x00\x00\x00\x01\x00\x00\x00\x04\x00\x00\x00\x02\x00\x00\x00\x04\x00\x00\x00\x03\x00\x00\x00\x04\x00\x00\x00\x04";
#[test]
fn it_encodes_i32() {
let mut buf = Vec::new();
let mut buf = PgRawBuffer::default();
let mut encoder = PgArrayEncoder::new(&mut buf);
for val in &[1_i32, 2, 3, 4] {
@ -187,13 +191,13 @@ mod tests {
encoder.finish();
assert_eq!(buf, BUF_BINARY_I32);
assert_eq!(&**buf, BUF_BINARY_I32);
}
#[test]
fn it_decodes_text_i32() -> crate::Result<()> {
let s = "{1,152,-12412}";
let mut decoder = PgArrayDecoder::<i32>::new(PgValue::str(TypeId(0), s))?;
let mut decoder = PgArrayDecoder::<i32>::new(PgValue::from_str(s))?;
assert_eq!(decoder.decode()?, Some(1));
assert_eq!(decoder.decode()?, Some(152));
@ -206,7 +210,7 @@ mod tests {
#[test]
fn it_decodes_text_str() -> crate::Result<()> {
let s = "{\"\",\"\\\"\"}";
let mut decoder = PgArrayDecoder::<String>::new(PgValue::str(TypeId(0), s))?;
let mut decoder = PgArrayDecoder::<String>::new(PgValue::from_str(s))?;
assert_eq!(decoder.decode()?, Some("".to_string()));
assert_eq!(decoder.decode()?, Some("\"".to_string()));
@ -217,7 +221,7 @@ mod tests {
#[test]
fn it_decodes_binary_nulls() -> crate::Result<()> {
let mut decoder = PgArrayDecoder::<Option<bool>>::new(PgValue::bytes(TypeId(0),
let mut decoder = PgArrayDecoder::<Option<bool>>::new(PgValue::from_bytes(
b"\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x10\x00\x00\x00\x04\x00\x00\x00\x01\xff\xff\xff\xff\x00\x00\x00\x01\x01\xff\xff\xff\xff\x00\x00\x00\x01\x00",
))?;
@ -231,7 +235,7 @@ mod tests {
#[test]
fn it_decodes_binary_i32() -> crate::Result<()> {
let mut decoder = PgArrayDecoder::<i32>::new(PgValue::bytes(TypeId(0), BUF_BINARY_I32))?;
let mut decoder = PgArrayDecoder::<i32>::new(PgValue::from_bytes(BUF_BINARY_I32))?;
let val_1 = decoder.decode()?;
let val_2 = decoder.decode()?;

View File

@ -6,7 +6,7 @@ use crate::decode::Decode;
use crate::encode::Encode;
use crate::io::{Buf, BufMut};
use crate::postgres::protocol::TypeId;
use crate::postgres::{PgData, PgTypeInfo, PgValue, Postgres};
use crate::postgres::{PgData, PgRawBuffer, PgTypeInfo, PgValue, Postgres};
use crate::types::Type;
use crate::Error;
@ -125,7 +125,7 @@ impl Decode<'_, Postgres> for PgNumeric {
/// * If `digits.len()` overflows `i16`
/// * If any element in `digits` is greater than or equal to 10000
impl Encode<Postgres> for PgNumeric {
fn encode(&self, buf: &mut Vec<u8>) {
fn encode(&self, buf: &mut PgRawBuffer) {
match *self {
PgNumeric::Number {
ref digits,

View File

@ -2,18 +2,18 @@ use crate::decode::Decode;
use crate::encode::{Encode, IsNull};
use crate::io::Buf;
use crate::postgres::types::raw::sequence::PgSequenceDecoder;
use crate::postgres::{PgData, PgValue, Postgres};
use crate::postgres::{PgData, PgRawBuffer, PgValue, Postgres};
use crate::types::Type;
use byteorder::BigEndian;
pub struct PgRecordEncoder<'a> {
buf: &'a mut Vec<u8>,
buf: &'a mut PgRawBuffer,
beg: usize,
num: u32,
}
impl<'a> PgRecordEncoder<'a> {
pub fn new(buf: &'a mut Vec<u8>) -> Self {
pub fn new(buf: &'a mut PgRawBuffer) -> Self {
// reserve space for a field count
buf.extend_from_slice(&(0_u32).to_be_bytes());
@ -33,9 +33,15 @@ impl<'a> PgRecordEncoder<'a> {
where
T: Type<Postgres> + Encode<Postgres>,
{
// write oid
let info = T::type_info();
self.buf.extend(&info.oid().to_be_bytes());
if let Some(oid) = info.id {
// write oid
self.buf.extend(&oid.0.to_be_bytes());
} else {
// write hole for this oid
self.buf.push_type_hole(&info.name);
}
// write zeros for length
self.buf.extend(&[0; 4]);
@ -71,7 +77,7 @@ impl<'de> PgRecordDecoder<'de> {
}
}
Ok(Self(PgSequenceDecoder::new(data, true)))
Ok(Self(PgSequenceDecoder::new(data, None)))
}
#[inline]
@ -91,15 +97,15 @@ fn test_encode_field() {
use std::convert::TryInto;
let value = "Foo Bar";
let mut raw_encoded = Vec::new();
let mut raw_encoded = PgRawBuffer::default();
<&str as Encode<Postgres>>::encode(&value, &mut raw_encoded);
let mut field_encoded = Vec::new();
let mut field_encoded = PgRawBuffer::default();
let mut encoder = PgRecordEncoder::new(&mut field_encoded);
encoder.encode(&value);
// check oid
let oid = <&str as Type<Postgres>>::type_info().oid();
let oid = <&str as Type<Postgres>>::type_info().id.unwrap().0;
let field_encoded_oid = u32::from_be_bytes(field_encoded[4..8].try_into().unwrap());
assert_eq!(oid, field_encoded_oid);
@ -108,21 +114,19 @@ fn test_encode_field() {
assert_eq!(raw_encoded.len(), field_encoded_length as usize);
// check data
assert_eq!(raw_encoded, &field_encoded[12..]);
assert_eq!(&**raw_encoded, &field_encoded[12..]);
}
#[test]
fn test_decode_field() {
use crate::postgres::protocol::TypeId;
let value = "Foo Bar".to_string();
let mut buf = Vec::new();
let mut buf = PgRawBuffer::default();
let mut encoder = PgRecordEncoder::new(&mut buf);
encoder.encode(&value);
let buf = buf.as_slice();
let mut decoder = PgRecordDecoder::new(PgValue::bytes(TypeId(0), buf)).unwrap();
let mut decoder = PgRecordDecoder::new(PgValue::from_bytes(buf)).unwrap();
let value_decoded: String = decoder.decode().unwrap();
assert_eq!(value_decoded, value);

View File

@ -8,11 +8,14 @@ use byteorder::BigEndian;
pub(crate) struct PgSequenceDecoder<'de> {
data: PgData<'de>,
len: usize,
mixed: bool,
is_text_record: bool,
element_oid: Option<u32>,
}
impl<'de> PgSequenceDecoder<'de> {
pub(crate) fn new(mut data: PgData<'de>, mixed: bool) -> Self {
pub(crate) fn new(mut data: PgData<'de>, element_oid: Option<u32>) -> Self {
let mut is_text_record = false;
match data {
PgData::Binary(_) => {
// assume that this has already gotten tweaked by the caller as
@ -20,14 +23,16 @@ impl<'de> PgSequenceDecoder<'de> {
}
PgData::Text(ref mut s) => {
is_text_record = s.as_bytes()[0] == b'(';
// remove the outer ( ... ) or { ... }
*s = &s[1..(s.len() - 1)];
}
}
Self {
is_text_record,
element_oid,
data,
mixed,
len: 0,
}
}
@ -47,34 +52,34 @@ impl<'de> PgSequenceDecoder<'de> {
return Ok(None);
}
// mixed sequences can contain values of many different types
// the OID of the type is encoded next to each value
let type_id = if self.mixed {
let oid = buf.get_u32::<BigEndian>()?;
let expected_ty = PgTypeInfo::with_oid(oid);
let type_info = if let Some(element_oid) = self.element_oid {
// NOTE: We don't validate the element type for non-mixed sequences because
// the outer type like `text[]` would have already ensured we are dealing
// with a Vec<String>
PgTypeInfo::new(TypeId(element_oid), "")
} else {
// mixed sequences can contain values of many different types
// the OID of the type is encoded next to each value
let element_oid = buf.get_u32::<BigEndian>()?;
let expected_ty = PgTypeInfo::new(TypeId(element_oid), "");
if !expected_ty.compatible(&T::type_info()) {
return Err(crate::Error::mismatched_types::<Postgres, T>(expected_ty));
}
TypeId(oid)
} else {
// NOTE: We don't validate the element type for non-mixed sequences because
// the outer type like `text[]` would have already ensured we are dealing
// with a Vec<String>
T::type_info().id
expected_ty
};
let len = buf.get_i32::<BigEndian>()? as isize;
let value = if len < 0 {
T::decode(PgValue::null(type_id))?
T::decode(PgValue::null())?
} else {
let value_buf = &buf[..(len as usize)];
*buf = &buf[(len as usize)..];
T::decode(PgValue::bytes(type_id, value_buf))?
T::decode(PgValue::bytes(type_info, value_buf))?
};
self.len += 1;
@ -146,12 +151,12 @@ impl<'de> PgSequenceDecoder<'de> {
// we could use. In TEXT mode, sequences aren't typed.
let value = T::decode(if end == Some(0) {
PgValue::null(TypeId(0))
} else if !self.mixed && value == "NULL" {
PgValue::null()
} else if !self.is_text_record && value == "NULL" {
// Yes, in arrays the text encoding of a NULL is just NULL
PgValue::null(TypeId(0))
PgValue::null()
} else {
PgValue::str(TypeId(0), &*value)
PgValue::from_str(&*value)
})?;
*s = if let Some(end) = end {
@ -168,9 +173,10 @@ impl<'de> PgSequenceDecoder<'de> {
}
}
#[cfg(test)]
impl<'de> From<&'de str> for PgSequenceDecoder<'de> {
fn from(s: &'de str) -> Self {
Self::new(PgData::Text(s), false)
Self::new(PgData::Text(s), None)
}
}

View File

@ -1,7 +1,7 @@
use crate::decode::Decode;
use crate::postgres::protocol::TypeId;
use crate::postgres::type_info::PgTypeInfo;
use crate::postgres::types::raw::PgRecordDecoder;
use crate::postgres::types::PgTypeInfo;
use crate::postgres::value::PgValue;
use crate::postgres::Postgres;
use crate::types::Type;
@ -11,20 +11,14 @@ macro_rules! impl_pg_record_for_tuple {
impl<$($T,)+> Type<Postgres> for ($($T,)+) {
#[inline]
fn type_info() -> PgTypeInfo {
PgTypeInfo {
id: TypeId(2249),
name: Some("RECORD".into()),
}
PgTypeInfo::new(TypeId::RECORD, "RECORD")
}
}
impl<$($T,)+> Type<Postgres> for [($($T,)+)] {
#[inline]
fn type_info() -> PgTypeInfo {
PgTypeInfo {
id: TypeId(2287),
name: Some("RECORD[]".into()),
}
PgTypeInfo::new(TypeId::ARRAY_RECORD, "RECORD[]")
}
}

View File

@ -3,9 +3,7 @@ use std::str::from_utf8;
use crate::decode::Decode;
use crate::encode::Encode;
use crate::postgres::protocol::TypeId;
use crate::postgres::types::PgTypeInfo;
use crate::postgres::value::{PgData, PgValue};
use crate::postgres::Postgres;
use crate::postgres::{PgData, PgRawBuffer, PgTypeInfo, PgValue, Postgres};
use crate::types::Type;
use crate::Error;
@ -46,7 +44,7 @@ impl Type<Postgres> for Vec<String> {
}
impl Encode<Postgres> for str {
fn encode(&self, buf: &mut Vec<u8>) {
fn encode(&self, buf: &mut PgRawBuffer) {
buf.extend_from_slice(self.as_bytes());
}
@ -56,7 +54,7 @@ impl Encode<Postgres> for str {
}
impl Encode<Postgres> for String {
fn encode(&self, buf: &mut Vec<u8>) {
fn encode(&self, buf: &mut PgRawBuffer) {
<str as Encode<Postgres>>::encode(self.as_str(), buf)
}

View File

@ -9,8 +9,7 @@ use crate::decode::Decode;
use crate::encode::Encode;
use crate::io::Buf;
use crate::postgres::protocol::TypeId;
use crate::postgres::types::PgTypeInfo;
use crate::postgres::{PgData, PgValue, Postgres};
use crate::postgres::{PgData, PgRawBuffer, PgTypeInfo, PgValue, Postgres};
use crate::types::Type;
const POSTGRES_EPOCH: PrimitiveDateTime = date!(2000 - 1 - 1).midnight();
@ -135,7 +134,7 @@ impl<'de> Decode<'de, Postgres> for Time {
}
impl Encode<Postgres> for Time {
fn encode(&self, buf: &mut Vec<u8>) {
fn encode(&self, buf: &mut PgRawBuffer) {
let micros = microseconds_since_midnight(*self);
Encode::<Postgres>::encode(&micros, buf);
@ -161,7 +160,7 @@ impl<'de> Decode<'de, Postgres> for Date {
}
impl Encode<Postgres> for Date {
fn encode(&self, buf: &mut Vec<u8>) {
fn encode(&self, buf: &mut PgRawBuffer) {
let days: i32 = (*self - date!(2000 - 1 - 1))
.whole_days()
.try_into()
@ -218,7 +217,7 @@ impl<'de> Decode<'de, Postgres> for PrimitiveDateTime {
}
impl Encode<Postgres> for PrimitiveDateTime {
fn encode(&self, buf: &mut Vec<u8>) {
fn encode(&self, buf: &mut PgRawBuffer) {
let micros: i64 = (*self - POSTGRES_EPOCH)
.whole_microseconds()
.try_into()
@ -241,7 +240,7 @@ impl<'de> Decode<'de, Postgres> for OffsetDateTime {
}
impl Encode<Postgres> for OffsetDateTime {
fn encode(&self, buf: &mut Vec<u8>) {
fn encode(&self, buf: &mut PgRawBuffer) {
let utc_dt = self.to_offset(offset!(UTC));
let primitive_dt = PrimitiveDateTime::new(utc_dt.date(), utc_dt.time());
@ -258,91 +257,88 @@ use time::time;
#[test]
fn test_encode_time() {
let mut buf = Vec::new();
let mut buf = PgRawBuffer::default();
Encode::<Postgres>::encode(&time!(0:00), &mut buf);
assert_eq!(buf, [0; 8]);
assert_eq!(&**buf, [0; 8]);
buf.clear();
// one second
Encode::<Postgres>::encode(&time!(0:00:01), &mut buf);
assert_eq!(buf, 1_000_000i64.to_be_bytes());
assert_eq!(&**buf, 1_000_000i64.to_be_bytes());
buf.clear();
// two hours
Encode::<Postgres>::encode(&time!(2:00), &mut buf);
let expected = 1_000_000i64 * 60 * 60 * 2;
assert_eq!(buf, expected.to_be_bytes());
assert_eq!(&**buf, expected.to_be_bytes());
buf.clear();
// 3:14:15.000001
Encode::<Postgres>::encode(&time!(3:14:15.000001), &mut buf);
let expected = 1_000_000i64 * 60 * 60 * 3 + 1_000_000i64 * 60 * 14 + 1_000_000i64 * 15 + 1;
assert_eq!(buf, expected.to_be_bytes());
assert_eq!(&**buf, expected.to_be_bytes());
buf.clear();
}
#[test]
fn test_decode_time() {
let buf = [0u8; 8];
let time: Time = Decode::<Postgres>::decode(PgValue::bytes(TypeId(0), &buf)).unwrap();
let time: Time = Decode::<Postgres>::decode(PgValue::from_bytes(&buf)).unwrap();
assert_eq!(time, time!(0:00));
// half an hour
let buf = (1_000_000i64 * 60 * 30).to_be_bytes();
let time: Time = Decode::<Postgres>::decode(PgValue::bytes(TypeId(0), &buf)).unwrap();
let time: Time = Decode::<Postgres>::decode(PgValue::from_bytes(&buf)).unwrap();
assert_eq!(time, time!(0:30));
// 12:53:05.125305
let buf = (1_000_000i64 * 60 * 60 * 12 + 1_000_000i64 * 60 * 53 + 1_000_000i64 * 5 + 125305)
.to_be_bytes();
let time: Time = Decode::<Postgres>::decode(PgValue::bytes(TypeId(0), &buf)).unwrap();
let time: Time = Decode::<Postgres>::decode(PgValue::from_bytes(&buf)).unwrap();
assert_eq!(time, time!(12:53:05.125305));
}
#[test]
fn test_encode_datetime() {
let mut buf = Vec::new();
let mut buf = PgRawBuffer::default();
Encode::<Postgres>::encode(&POSTGRES_EPOCH, &mut buf);
assert_eq!(buf, [0; 8]);
assert_eq!(&**buf, [0; 8]);
buf.clear();
// one hour past epoch
let date = POSTGRES_EPOCH + 1.hours();
Encode::<Postgres>::encode(&date, &mut buf);
assert_eq!(buf, 3_600_000_000i64.to_be_bytes());
assert_eq!(&**buf, 3_600_000_000i64.to_be_bytes());
buf.clear();
// some random date
let date = PrimitiveDateTime::new(date!(2019 - 12 - 11), time!(11:01:05));
let expected = (date - POSTGRES_EPOCH).whole_microseconds() as i64;
Encode::<Postgres>::encode(&date, &mut buf);
assert_eq!(buf, expected.to_be_bytes());
assert_eq!(&**buf, expected.to_be_bytes());
buf.clear();
}
#[test]
fn test_decode_datetime() {
let buf = [0u8; 8];
let date: PrimitiveDateTime =
Decode::<Postgres>::decode(PgValue::bytes(TypeId(0), &buf)).unwrap();
let date: PrimitiveDateTime = Decode::<Postgres>::decode(PgValue::from_bytes(&buf)).unwrap();
assert_eq!(
date,
PrimitiveDateTime::new(date!(2000 - 01 - 01), time!(00:00:00))
);
let buf = 3_600_000_000i64.to_be_bytes();
let date: PrimitiveDateTime =
Decode::<Postgres>::decode(PgValue::bytes(TypeId(0), &buf)).unwrap();
let date: PrimitiveDateTime = Decode::<Postgres>::decode(PgValue::from_bytes(&buf)).unwrap();
assert_eq!(
date,
PrimitiveDateTime::new(date!(2000 - 01 - 01), time!(01:00:00))
);
let buf = 629_377_265_000_000i64.to_be_bytes();
let date: PrimitiveDateTime =
Decode::<Postgres>::decode(PgValue::bytes(TypeId(0), &buf)).unwrap();
let date: PrimitiveDateTime = Decode::<Postgres>::decode(PgValue::from_bytes(&buf)).unwrap();
assert_eq!(
date,
PrimitiveDateTime::new(date!(2019 - 12 - 11), time!(11:01:05))
@ -351,16 +347,16 @@ fn test_decode_datetime() {
#[test]
fn test_encode_offsetdatetime() {
let mut buf = Vec::new();
let mut buf = PgRawBuffer::default();
Encode::<Postgres>::encode(&POSTGRES_EPOCH.assume_utc(), &mut buf);
assert_eq!(buf, [0; 8]);
assert_eq!(&**buf, [0; 8]);
buf.clear();
// one hour past epoch in MSK (2 hours before epoch in UTC)
let date = (POSTGRES_EPOCH + 1.hours()).assume_offset(offset!(+3));
Encode::<Postgres>::encode(&date, &mut buf);
assert_eq!(buf, (-7_200_000_000i64).to_be_bytes());
assert_eq!(&**buf, (-7_200_000_000i64).to_be_bytes());
buf.clear();
// some random date in MSK
@ -368,28 +364,28 @@ fn test_encode_offsetdatetime() {
PrimitiveDateTime::new(date!(2019 - 12 - 11), time!(11:01:05)).assume_offset(offset!(+3));
let expected = (date - POSTGRES_EPOCH.assume_utc()).whole_microseconds() as i64;
Encode::<Postgres>::encode(&date, &mut buf);
assert_eq!(buf, expected.to_be_bytes());
assert_eq!(&**buf, expected.to_be_bytes());
buf.clear();
}
#[test]
fn test_decode_offsetdatetime() {
let buf = [0u8; 8];
let date: OffsetDateTime = Decode::<Postgres>::decode(PgValue::bytes(TypeId(0), &buf)).unwrap();
let date: OffsetDateTime = Decode::<Postgres>::decode(PgValue::from_bytes(&buf)).unwrap();
assert_eq!(
date,
PrimitiveDateTime::new(date!(2000 - 01 - 01), time!(00:00:00)).assume_utc()
);
let buf = 3_600_000_000i64.to_be_bytes();
let date: OffsetDateTime = Decode::<Postgres>::decode(PgValue::bytes(TypeId(0), &buf)).unwrap();
let date: OffsetDateTime = Decode::<Postgres>::decode(PgValue::from_bytes(&buf)).unwrap();
assert_eq!(
date,
PrimitiveDateTime::new(date!(2000 - 01 - 01), time!(01:00:00)).assume_utc()
);
let buf = 629_377_265_000_000i64.to_be_bytes();
let date: OffsetDateTime = Decode::<Postgres>::decode(PgValue::bytes(TypeId(0), &buf)).unwrap();
let date: OffsetDateTime = Decode::<Postgres>::decode(PgValue::from_bytes(&buf)).unwrap();
assert_eq!(
date,
PrimitiveDateTime::new(date!(2019 - 12 - 11), time!(11:01:05)).assume_utc()
@ -398,36 +394,36 @@ fn test_decode_offsetdatetime() {
#[test]
fn test_encode_date() {
let mut buf = Vec::new();
let mut buf = PgRawBuffer::default();
let date = date!(2000 - 1 - 1);
Encode::<Postgres>::encode(&date, &mut buf);
assert_eq!(buf, [0; 4]);
assert_eq!(&**buf, [0; 4]);
buf.clear();
let date = date!(2001 - 1 - 1);
Encode::<Postgres>::encode(&date, &mut buf);
// 2000 was a leap year
assert_eq!(buf, 366i32.to_be_bytes());
assert_eq!(&**buf, 366i32.to_be_bytes());
buf.clear();
let date = date!(2019 - 12 - 11);
Encode::<Postgres>::encode(&date, &mut buf);
assert_eq!(buf, 7284i32.to_be_bytes());
assert_eq!(&**buf, 7284i32.to_be_bytes());
buf.clear();
}
#[test]
fn test_decode_date() {
let buf = [0; 4];
let date: Date = Decode::<Postgres>::decode(PgValue::bytes(TypeId(0), &buf)).unwrap();
let date: Date = Decode::<Postgres>::decode(PgValue::from_bytes(&buf)).unwrap();
assert_eq!(date, date!(2000 - 01 - 01));
let buf = 366i32.to_be_bytes();
let date: Date = Decode::<Postgres>::decode(PgValue::bytes(TypeId(0), &buf)).unwrap();
let date: Date = Decode::<Postgres>::decode(PgValue::from_bytes(&buf)).unwrap();
assert_eq!(date, date!(2001 - 01 - 01));
let buf = 7284i32.to_be_bytes();
let date: Date = Decode::<Postgres>::decode(PgValue::bytes(TypeId(0), &buf)).unwrap();
let date: Date = Decode::<Postgres>::decode(PgValue::from_bytes(&buf)).unwrap();
assert_eq!(date, date!(2019 - 12 - 11));
}

View File

@ -5,9 +5,8 @@ use uuid::Uuid;
use crate::decode::Decode;
use crate::encode::Encode;
use crate::postgres::protocol::TypeId;
use crate::postgres::types::PgTypeInfo;
use crate::postgres::value::{PgData, PgValue};
use crate::postgres::Postgres;
use crate::postgres::{PgRawBuffer, PgTypeInfo, Postgres};
use crate::types::Type;
impl Type<Postgres> for Uuid {
@ -29,7 +28,7 @@ impl Type<Postgres> for Vec<Uuid> {
}
impl Encode<Postgres> for Uuid {
fn encode(&self, buf: &mut Vec<u8>) {
fn encode(&self, buf: &mut PgRawBuffer) {
buf.extend_from_slice(self.as_bytes());
}
}

View File

@ -1,5 +1,4 @@
use crate::error::UnexpectedNullError;
use crate::postgres::protocol::TypeId;
use crate::postgres::{PgTypeInfo, Postgres};
use crate::value::RawValue;
use std::str::from_utf8;
@ -12,7 +11,7 @@ pub enum PgData<'c> {
#[derive(Debug)]
pub struct PgValue<'c> {
type_id: TypeId,
type_info: Option<PgTypeInfo>,
data: Option<PgData<'c>>,
}
@ -33,30 +32,38 @@ impl<'c> PgValue<'c> {
self.data
}
pub(crate) fn null(type_id: TypeId) -> Self {
pub(crate) fn null() -> Self {
Self {
type_id,
type_info: None,
data: None,
}
}
pub(crate) fn bytes(type_id: TypeId, buf: &'c [u8]) -> Self {
pub(crate) fn bytes(type_info: PgTypeInfo, buf: &'c [u8]) -> Self {
Self {
type_id,
type_info: Some(type_info),
data: Some(PgData::Binary(buf)),
}
}
pub(crate) fn utf8(type_id: TypeId, buf: &'c [u8]) -> crate::Result<Self> {
pub(crate) fn utf8(type_info: PgTypeInfo, buf: &'c [u8]) -> crate::Result<Self> {
Ok(Self {
type_id,
type_info: Some(type_info),
data: Some(PgData::Text(from_utf8(&buf).map_err(crate::Error::decode)?)),
})
}
pub(crate) fn str(type_id: TypeId, s: &'c str) -> Self {
#[cfg(test)]
pub(crate) fn from_bytes(buf: &'c [u8]) -> Self {
Self {
type_id,
type_info: None,
data: Some(PgData::Binary(buf)),
}
}
pub(crate) fn from_str(s: &'c str) -> Self {
Self {
type_info: None,
data: Some(PgData::Text(s)),
}
}
@ -66,8 +73,8 @@ impl<'c> RawValue<'c> for PgValue<'c> {
type Database = Postgres;
fn type_info(&self) -> Option<PgTypeInfo> {
if self.data.is_some() {
Some(PgTypeInfo::with_oid(self.type_id.0))
if let (Some(type_info), Some(_)) = (&self.type_info, &self.data) {
Some(type_info.clone())
} else {
None
}

View File

@ -33,7 +33,7 @@ pub enum RenameAll {
pub struct SqlxContainerAttributes {
pub transparent: bool,
pub postgres_oid: Option<u32>,
pub rename: Option<String>,
pub rename_all: Option<RenameAll>,
pub repr: Option<Ident>,
}
@ -44,8 +44,8 @@ pub struct SqlxChildAttributes {
pub fn parse_container_attributes(input: &[Attribute]) -> syn::Result<SqlxContainerAttributes> {
let mut transparent = None;
let mut postgres_oid = None;
let mut repr = None;
let mut rename = None;
let mut rename_all = None;
for attr in input {
@ -75,20 +75,11 @@ pub fn parse_container_attributes(input: &[Attribute]) -> syn::Result<SqlxContai
try_set!(rename_all, val, value)
}
Meta::List(list) if list.path.is_ident("postgres") => {
for value in list.nested.iter() {
match value {
NestedMeta::Meta(Meta::NameValue(MetaNameValue {
path,
lit: Lit::Int(val),
..
})) if path.is_ident("oid") => {
try_set!(postgres_oid, val.base10_parse()?, value);
}
u => fail!(u, "unexpected value"),
}
}
}
Meta::NameValue(MetaNameValue {
path,
lit: Lit::Str(val),
..
}) if path.is_ident("rename") => try_set!(rename, val.value(), value),
u => fail!(u, "unexpected attribute"),
},
@ -113,8 +104,8 @@ pub fn parse_container_attributes(input: &[Attribute]) -> syn::Result<SqlxContai
Ok(SqlxContainerAttributes {
transparent: transparent.unwrap_or(false),
postgres_oid,
repr,
rename,
rename_all,
})
}
@ -160,13 +151,6 @@ pub fn check_transparent_attributes(input: &DeriveInput, field: &Field) -> syn::
input
);
#[cfg(feature = "postgres")]
assert_attribute!(
attributes.postgres_oid.is_none(),
"unexpected #[sqlx(postgres(oid = ..))]",
input
);
assert_attribute!(
attributes.rename_all.is_none(),
"unexpected #[sqlx(rename_all = ..)]",
@ -204,13 +188,6 @@ pub fn check_weak_enum_attributes(
) -> syn::Result<SqlxContainerAttributes> {
let attributes = check_enum_attributes(input)?;
#[cfg(feature = "postgres")]
assert_attribute!(
attributes.postgres_oid.is_none(),
"unexpected #[sqlx(postgres(oid = ..))]",
input
);
assert_attribute!(attributes.repr.is_some(), "expected #[repr(..)]", input);
assert_attribute!(
@ -238,13 +215,6 @@ pub fn check_strong_enum_attributes(
) -> syn::Result<SqlxContainerAttributes> {
let attributes = check_enum_attributes(input)?;
#[cfg(feature = "postgres")]
assert_attribute!(
attributes.postgres_oid.is_some(),
"expected #[sqlx(postgres(oid = ..))]",
input
);
assert_attribute!(attributes.repr.is_none(), "unexpected #[repr(..)]", input);
Ok(attributes)
@ -262,13 +232,6 @@ pub fn check_struct_attributes<'a>(
input
);
#[cfg(feature = "postgres")]
assert_attribute!(
attributes.postgres_oid.is_some(),
"expected #[sqlx(postgres(oid = ..))]",
input
);
assert_attribute!(
attributes.rename_all.is_none(),
"unexpected #[sqlx(rename_all = ..)]",

View File

@ -117,11 +117,12 @@ fn expand_derive_has_sql_type_strong_enum(
}
if cfg!(feature = "postgres") {
let oid = attributes.postgres_oid.unwrap();
let ty_name = attributes.rename.unwrap_or_else(|| ident.to_string());
tts.extend(quote!(
impl sqlx::Type< sqlx::Postgres > for #ident {
fn type_info() -> sqlx::postgres::PgTypeInfo {
sqlx::postgres::PgTypeInfo::with_oid(#oid)
sqlx::postgres::PgTypeInfo::with_name(#ty_name)
}
}
));
@ -150,11 +151,12 @@ fn expand_derive_has_sql_type_struct(
let mut tts = proc_macro2::TokenStream::new();
if cfg!(feature = "postgres") {
let oid = attributes.postgres_oid.unwrap();
let ty_name = attributes.rename.unwrap_or_else(|| ident.to_string());
tts.extend(quote!(
impl sqlx::types::Type< sqlx::Postgres > for #ident {
fn type_info() -> sqlx::postgres::PgTypeInfo {
sqlx::postgres::PgTypeInfo::with_oid(#oid)
sqlx::postgres::PgTypeInfo::with_name(#ty_name)
}
}
));

View File

@ -18,7 +18,7 @@ enum Weak {
// "Strong" enums can map to TEXT (25) or a custom enum type
#[derive(PartialEq, Debug, sqlx::Type)]
#[sqlx(postgres(oid = 25))]
#[sqlx(rename = "text")]
#[sqlx(rename_all = "lowercase")]
enum Strong {
One,
@ -32,7 +32,7 @@ enum Strong {
// Records must map to a custom type
// Note that all types are types in Postgres
// #[derive(PartialEq, Debug, sqlx::Type)]
// #[sqlx(postgres(oid = ?))]
// #[sqlx(rename = "inventory_item")]
// struct InventoryItem {
// name: String,
// supplier_id: Option<i32>,

View File

@ -1,11 +1,9 @@
extern crate time_ as time;
use std::sync::atomic::{AtomicU32, Ordering};
use sqlx::decode::Decode;
use sqlx::encode::Encode;
use sqlx::postgres::types::raw::{PgNumeric, PgNumericSign, PgRecordDecoder, PgRecordEncoder};
use sqlx::postgres::{PgQueryAs, PgTypeInfo, PgValue};
use sqlx::postgres::{PgQueryAs, PgRawBuffer, PgTypeInfo, PgValue};
use sqlx::{Cursor, Executor, Postgres, Row, Type};
use sqlx_test::{new, test_prepared_type, test_type};
@ -440,9 +438,6 @@ async fn test_prepared_structs() -> anyhow::Result<()> {
// Setup custom types if needed
//
static OID_RECORD_EMPTY: AtomicU32 = AtomicU32::new(0);
static OID_RECORD_1: AtomicU32 = AtomicU32::new(0);
conn.execute(
r#"
DO $$ BEGIN
@ -455,15 +450,6 @@ END $$;
)
.await?;
let type_ids: Vec<(i32,)> = sqlx::query_as(
"SELECT oid::int4 FROM pg_type WHERE typname IN ('_sqlx_record_empty', '_sqlx_record_1')",
)
.fetch_all(&mut conn)
.await?;
OID_RECORD_EMPTY.store(type_ids[0].0 as u32, Ordering::SeqCst);
OID_RECORD_1.store(type_ids[1].0 as u32, Ordering::SeqCst);
//
// Record of no elements
//
@ -472,12 +458,12 @@ END $$;
impl Type<Postgres> for RecordEmpty {
fn type_info() -> PgTypeInfo {
PgTypeInfo::with_oid(OID_RECORD_EMPTY.load(Ordering::SeqCst))
PgTypeInfo::with_name("_sqlx_record_empty")
}
}
impl Encode<Postgres> for RecordEmpty {
fn encode(&self, buf: &mut Vec<u8>) {
fn encode(&self, buf: &mut PgRawBuffer) {
PgRecordEncoder::new(buf).finish();
}
}
@ -504,12 +490,12 @@ END $$;
impl Type<Postgres> for Record1 {
fn type_info() -> PgTypeInfo {
PgTypeInfo::with_oid(OID_RECORD_1.load(Ordering::SeqCst))
PgTypeInfo::with_name("_sqlx_record_1")
}
}
impl Encode<Postgres> for Record1 {
fn encode(&self, buf: &mut Vec<u8>) {
fn encode(&self, buf: &mut PgRawBuffer) {
PgRecordEncoder::new(buf).encode(self._1).finish();
}
}