Split out ltree,query,txtquery protocol parsers, add tests, rust fmt

This commit is contained in:
Matt Oliver 2022-03-05 14:55:07 -06:00
parent 6ae60d6d09
commit d9d283e131
4 changed files with 273 additions and 70 deletions

View File

@ -1060,18 +1060,59 @@ impl Inet {
} }
} }
/// Serializes a Postgres l{tree,query,txtquery} string /// Serializes a Postgres ltree string
#[inline] #[inline]
pub fn ltree_to_sql(v: &str, buf: &mut BytesMut) { pub fn ltree_to_sql(v: &str, buf: &mut BytesMut) {
// A version number is prepended to an Ltree string per spec // A version number is prepended to an ltree string per spec
buf.put_u8(1); buf.put_u8(1);
// Append the rest of the query // Append the rest of the query
buf.put_slice(v.as_bytes()); buf.put_slice(v.as_bytes());
} }
/// Deserialize a Postgres l{tree,query,txtquery} string /// Deserialize a Postgres ltree string
#[inline] #[inline]
pub fn ltree_from_sql(buf: &[u8]) -> Result<&str, StdBox<dyn Error + Sync + Send>> { pub fn ltree_from_sql(buf: &[u8]) -> Result<&str, StdBox<dyn Error + Sync + Send>> {
// Remove the version number from the front of the string per spec match buf {
Ok(str::from_utf8(&buf[1..])?) // Remove the version number from the front of the ltree per spec
[1u8, rest @ ..] => Ok(str::from_utf8(rest)?),
_ => Err("ltree version 1 only supported".into()),
}
}
/// Serializes a Postgres lquery string
#[inline]
pub fn lquery_to_sql(v: &str, buf: &mut BytesMut) {
// A version number is prepended to an lquery string per spec
buf.put_u8(1);
// Append the rest of the query
buf.put_slice(v.as_bytes());
}
/// Deserialize a Postgres lquery string
#[inline]
pub fn lquery_from_sql(buf: &[u8]) -> Result<&str, StdBox<dyn Error + Sync + Send>> {
match buf {
// Remove the version number from the front of the lquery per spec
[1u8, rest @ ..] => Ok(str::from_utf8(rest)?),
_ => Err("lquery version 1 only supported".into()),
}
}
/// Serializes a Postgres ltxtquery string
#[inline]
pub fn ltxtquery_to_sql(v: &str, buf: &mut BytesMut) {
// A version number is prepended to an ltxtquery string per spec
buf.put_u8(1);
// Append the rest of the query
buf.put_slice(v.as_bytes());
}
/// Deserialize a Postgres ltxtquery string
#[inline]
pub fn ltxtquery_from_sql(buf: &[u8]) -> Result<&str, StdBox<dyn Error + Sync + Send>> {
match buf {
// Remove the version number from the front of the ltxtquery per spec
[1u8, rest @ ..] => Ok(str::from_utf8(rest)?),
_ => Err("ltxtquery version 1 only supported".into()),
}
} }

View File

@ -1,4 +1,4 @@
use bytes::BytesMut; use bytes::{Buf, BytesMut};
use fallible_iterator::FallibleIterator; use fallible_iterator::FallibleIterator;
use std::collections::HashMap; use std::collections::HashMap;
@ -156,3 +156,117 @@ fn non_null_array() {
assert_eq!(array.dimensions().collect::<Vec<_>>().unwrap(), dimensions); assert_eq!(array.dimensions().collect::<Vec<_>>().unwrap(), dimensions);
assert_eq!(array.values().collect::<Vec<_>>().unwrap(), values); assert_eq!(array.values().collect::<Vec<_>>().unwrap(), values);
} }
#[test]
fn ltree_sql() {
let mut query = vec![1u8];
query.extend_from_slice("A.B.C".as_bytes());
let mut buf = BytesMut::new();
ltree_to_sql("A.B.C", &mut buf);
assert_eq!(query.as_slice(), buf.chunk());
}
#[test]
fn ltree_str() {
let mut query = vec![1u8];
query.extend_from_slice("A.B.C".as_bytes());
let success = match ltree_from_sql(query.as_slice()) {
Ok(_) => true,
_ => false,
};
assert!(success)
}
#[test]
fn ltree_wrong_version() {
let mut query = vec![2u8];
query.extend_from_slice("A.B.C".as_bytes());
let success = match ltree_from_sql(query.as_slice()) {
Err(_) => true,
_ => false,
};
assert!(success)
}
#[test]
fn lquery_sql() {
let mut query = vec![1u8];
query.extend_from_slice("A.B.C".as_bytes());
let mut buf = BytesMut::new();
lquery_to_sql("A.B.C", &mut buf);
assert_eq!(query.as_slice(), buf.chunk());
}
#[test]
fn lquery_str() {
let mut query = vec![1u8];
query.extend_from_slice("A.B.C".as_bytes());
let success = match lquery_from_sql(query.as_slice()) {
Ok(_) => true,
_ => false,
};
assert!(success)
}
#[test]
fn lquery_wrong_version() {
let mut query = vec![2u8];
query.extend_from_slice("A.B.C".as_bytes());
let success = match lquery_from_sql(query.as_slice()) {
Err(_) => true,
_ => false,
};
assert!(success)
}
#[test]
fn ltxtquery_sql() {
let mut query = vec![1u8];
query.extend_from_slice("a & b*".as_bytes());
let mut buf = BytesMut::new();
ltree_to_sql("a & b*", &mut buf);
assert_eq!(query.as_slice(), buf.chunk());
}
#[test]
fn ltxtquery_str() {
let mut query = vec![1u8];
query.extend_from_slice("a & b*".as_bytes());
let success = match ltree_from_sql(query.as_slice()) {
Ok(_) => true,
_ => false,
};
assert!(success)
}
#[test]
fn ltxtquery_wrong_version() {
let mut query = vec![2u8];
query.extend_from_slice("a & b*".as_bytes());
let success = match ltree_from_sql(query.as_slice()) {
Err(_) => true,
_ => false,
};
assert!(success)
}

View File

@ -619,24 +619,24 @@ impl<'a> FromSql<'a> for Box<str> {
impl<'a> FromSql<'a> for &'a str { impl<'a> FromSql<'a> for &'a str {
fn from_sql(ty: &Type, raw: &'a [u8]) -> Result<&'a str, Box<dyn Error + Sync + Send>> { fn from_sql(ty: &Type, raw: &'a [u8]) -> Result<&'a str, Box<dyn Error + Sync + Send>> {
match *ty { match *ty {
ref ty if ( ref ty if ty.name() == "ltree" => types::ltree_from_sql(raw),
ty.name() == "ltree" || ref ty if ty.name() == "lquery" => types::lquery_from_sql(raw),
ty.name() == "lquery" || ref ty if ty.name() == "ltxtquery" => types::ltxtquery_from_sql(raw),
ty.name() == "ltxtquery" _ => types::text_from_sql(raw),
) => types::ltree_from_sql(raw),
_ => types::text_from_sql(raw)
} }
} }
fn accepts(ty: &Type) -> bool { fn accepts(ty: &Type) -> bool {
match *ty { match *ty {
Type::VARCHAR | Type::TEXT | Type::BPCHAR | Type::NAME | Type::UNKNOWN => true, Type::VARCHAR | Type::TEXT | Type::BPCHAR | Type::NAME | Type::UNKNOWN => true,
ref ty if ( ref ty
ty.name() == "citext" || if (ty.name() == "citext"
ty.name() == "ltree" || || ty.name() == "ltree"
ty.name() == "lquery" || || ty.name() == "lquery"
ty.name() == "ltxtquery" || ty.name() == "ltxtquery") =>
) => true, {
true
}
_ => false, _ => false,
} }
} }
@ -939,13 +939,11 @@ impl ToSql for Vec<u8> {
impl<'a> ToSql for &'a str { impl<'a> ToSql for &'a str {
fn to_sql(&self, ty: &Type, w: &mut BytesMut) -> Result<IsNull, Box<dyn Error + Sync + Send>> { fn to_sql(&self, ty: &Type, w: &mut BytesMut) -> Result<IsNull, Box<dyn Error + Sync + Send>> {
match ty { match *ty {
ref ty if ( ref ty if ty.name() == "ltree" => types::ltree_to_sql(*self, w),
ty.name() == "ltree" || ref ty if ty.name() == "lquery" => types::lquery_to_sql(*self, w),
ty.name() == "lquery" || ref ty if ty.name() == "ltxtquery" => types::ltxtquery_to_sql(*self, w),
ty.name() == "ltxtquery" _ => types::text_to_sql(*self, w),
) => types::ltree_to_sql(*self, w),
_ => types::text_to_sql(*self, w)
} }
Ok(IsNull::No) Ok(IsNull::No)
} }
@ -953,12 +951,14 @@ impl<'a> ToSql for &'a str {
fn accepts(ty: &Type) -> bool { fn accepts(ty: &Type) -> bool {
match *ty { match *ty {
Type::VARCHAR | Type::TEXT | Type::BPCHAR | Type::NAME | Type::UNKNOWN => true, Type::VARCHAR | Type::TEXT | Type::BPCHAR | Type::NAME | Type::UNKNOWN => true,
ref ty if ( ref ty
ty.name() == "citext" || if (ty.name() == "citext"
ty.name() == "ltree" || || ty.name() == "ltree"
ty.name() == "lquery" || || ty.name() == "lquery"
ty.name() == "ltxtquery" || ty.name() == "ltxtquery") =>
) => true, {
true
}
_ => false, _ => false,
} }
} }

View File

@ -652,74 +652,122 @@ async fn inet() {
#[tokio::test] #[tokio::test]
async fn ltree() { async fn ltree() {
let client = connect("user=postgres").await; let client = connect("user=postgres").await;
client.execute("CREATE EXTENSION IF NOT EXISTS ltree;", &[]).await.unwrap(); client
.execute("CREATE EXTENSION IF NOT EXISTS ltree;", &[])
.await
.unwrap();
test_type("ltree", &[ test_type(
(Some("b.c.d".to_owned()), "'b.c.d'"), "ltree",
(None, "NULL"), &[(Some("b.c.d".to_owned()), "'b.c.d'"), (None, "NULL")],
]).await; )
.await;
} }
#[tokio::test] #[tokio::test]
async fn ltree_any() { async fn ltree_any() {
let client = connect("user=postgres").await; let client = connect("user=postgres").await;
client.execute("CREATE EXTENSION IF NOT EXISTS ltree;", &[]).await.unwrap(); client
.execute("CREATE EXTENSION IF NOT EXISTS ltree;", &[])
.await
.unwrap();
test_type("ltree[]", &[ test_type(
"ltree[]",
&[
(Some(vec![]), "ARRAY[]"), (Some(vec![]), "ARRAY[]"),
(Some(vec!["a.b.c".to_string()]), "ARRAY['a.b.c']"), (Some(vec!["a.b.c".to_string()]), "ARRAY['a.b.c']"),
(Some(vec!["a.b.c".to_string(), "e.f.g".to_string()]), "ARRAY['a.b.c','e.f.g']"), (
Some(vec!["a.b.c".to_string(), "e.f.g".to_string()]),
"ARRAY['a.b.c','e.f.g']",
),
(None, "NULL"), (None, "NULL"),
]).await; ],
)
.await;
} }
#[tokio::test] #[tokio::test]
async fn lquery() { async fn lquery() {
let client = connect("user=postgres").await; let client = connect("user=postgres").await;
client.execute("CREATE EXTENSION IF NOT EXISTS ltree;", &[]).await.unwrap(); client
.execute("CREATE EXTENSION IF NOT EXISTS ltree;", &[])
.await
.unwrap();
test_type("lquery", &[ test_type(
"lquery",
&[
(Some("b.c.d".to_owned()), "'b.c.d'"), (Some("b.c.d".to_owned()), "'b.c.d'"),
(Some("b.c.*".to_owned()), "'b.c.*'"), (Some("b.c.*".to_owned()), "'b.c.*'"),
(Some("b.*{1,2}.d|e".to_owned()), "'b.*{1,2}.d|e'"), (Some("b.*{1,2}.d|e".to_owned()), "'b.*{1,2}.d|e'"),
(None, "NULL"), (None, "NULL"),
]).await; ],
)
.await;
} }
#[tokio::test] #[tokio::test]
async fn lquery_any() { async fn lquery_any() {
let client = connect("user=postgres").await; let client = connect("user=postgres").await;
client.execute("CREATE EXTENSION IF NOT EXISTS ltree;", &[]).await.unwrap(); client
.execute("CREATE EXTENSION IF NOT EXISTS ltree;", &[])
.await
.unwrap();
test_type("lquery[]", &[ test_type(
"lquery[]",
&[
(Some(vec![]), "ARRAY[]"), (Some(vec![]), "ARRAY[]"),
(Some(vec!["b.c.*".to_string()]), "ARRAY['b.c.*']"), (Some(vec!["b.c.*".to_string()]), "ARRAY['b.c.*']"),
(Some(vec!["b.c.*".to_string(), "b.*{1,2}.d|e".to_string()]), "ARRAY['b.c.*','b.*{1,2}.d|e']"), (
Some(vec!["b.c.*".to_string(), "b.*{1,2}.d|e".to_string()]),
"ARRAY['b.c.*','b.*{1,2}.d|e']",
),
(None, "NULL"), (None, "NULL"),
]).await; ],
)
.await;
} }
#[tokio::test] #[tokio::test]
async fn ltxtquery() { async fn ltxtquery() {
let client = connect("user=postgres").await; let client = connect("user=postgres").await;
client.execute("CREATE EXTENSION IF NOT EXISTS ltree;", &[]).await.unwrap(); client
.execute("CREATE EXTENSION IF NOT EXISTS ltree;", &[])
.await
.unwrap();
test_type("ltxtquery", &[ test_type(
"ltxtquery",
&[
(Some("b & c & d".to_owned()), "'b & c & d'"), (Some("b & c & d".to_owned()), "'b & c & d'"),
(Some("b@* & !c".to_owned()), "'b@* & !c'"), (Some("b@* & !c".to_owned()), "'b@* & !c'"),
(None, "NULL"), (None, "NULL"),
]).await; ],
)
.await;
} }
#[tokio::test] #[tokio::test]
async fn ltxtquery_any() { async fn ltxtquery_any() {
let client = connect("user=postgres").await; let client = connect("user=postgres").await;
client.execute("CREATE EXTENSION IF NOT EXISTS ltree;", &[]).await.unwrap(); client
.execute("CREATE EXTENSION IF NOT EXISTS ltree;", &[])
.await
.unwrap();
test_type("ltxtquery[]", &[ test_type(
"ltxtquery[]",
&[
(Some(vec![]), "ARRAY[]"), (Some(vec![]), "ARRAY[]"),
(Some(vec!["b & c & d".to_string()]), "ARRAY['b & c & d']"), (Some(vec!["b & c & d".to_string()]), "ARRAY['b & c & d']"),
(Some(vec!["b & c & d".to_string(), "b@* & !c".to_string()]), "ARRAY['b & c & d','b@* & !c']"), (
Some(vec!["b & c & d".to_string(), "b@* & !c".to_string()]),
"ARRAY['b & c & d','b@* & !c']",
),
(None, "NULL"), (None, "NULL"),
]).await; ],
)
.await;
} }