Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,16 @@ keywords = ["diesel", "postgres", "hstore"]

categories = ["database"]

[features]
default = []
serde_derive = []

[dependencies]
diesel = { version = "~1.0.0-beta1", features = ["postgres"] }
byteorder = "~1.2"
diesel = { version = "~2.0.0", features = ["postgres"] }
byteorder = "~1.4"
fallible-iterator = "~0.1"
serde = { version = "~1.0", features = ["derive"] }

[dev-dependencies]
rstest = "0.16.0"
dotenv = "~0.10"
131 changes: 131 additions & 0 deletions src/dsl.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
use super::Hstore;
use diesel::expression::{AsExpression, Expression};
use diesel::sql_types::{Array, Text};

/// Operators on the hstore type
/// See [PostgreSQL hstore](https://www.postgresql.org/docs/current/hstore.html)
mod predicates {
use super::Hstore;
use diesel::pg::Pg;
use diesel::sql_types::{Array, Bool, Text};

type TextArray = Array<Text>;

diesel::infix_operator!(HstoreGet, "->", Text, backend: Pg);
diesel::infix_operator!(HstoreGetArray, "->", TextArray, backend: Pg);
diesel::infix_operator!(HstoreConcat, "||", Hstore, backend: Pg);
diesel::infix_operator!(HstoreHasKey, "?", Bool, backend: Pg);
diesel::infix_operator!(HstoreHasAll, "?&", Bool, backend: Pg);
diesel::infix_operator!(HstoreHasAny, "?|", Bool, backend: Pg);
diesel::infix_operator!(HstoreLeftSubset, "@>", Bool, backend: Pg);
diesel::infix_operator!(HstoreRightSubset, "<@", Bool, backend: Pg);
diesel::infix_operator!(HstoreRemove, "-", Hstore, backend: Pg);
diesel::prefix_operator!(HstoreFlatten, "%%", Array<Text>, backend: Pg);

// anyelement #= hstore → anyelement
// Replaces fields in the left operand (which must be a composite type) with matching values from hstore.
// Not sure how to implement this

// %# hstore → text[]
// Converts hstore to a two-dimensional key/value array.
// 2D arrays are not supported in diesel, this should translate to a vec of tuples
// but it seems hard to implement in practice
// diesel::prefix_operator!(HstoreRecords, "%#", Array<Array<Text>>, backend: Pg);
}

use self::predicates::*;

pub trait HstoreOpExtensions: Expression<SqlType = Hstore> + Sized {
/// Returns value associated with given key, or NULL if not present.
/// See [hstore -> text operator](https://www.postgresql.org/docs/current/hstore.html)
fn get_value<T: AsExpression<Text>>(self, other: T) -> HstoreGet<Self, T::Expression> {
HstoreGet::new(self, other.as_expression())
}

/// Returns values associated with given keys, or NULL if not present.
/// See [hstore -> text[] operator](https://www.postgresql.org/docs/current/hstore.html)
fn get_array<T: AsExpression<Array<Text>>>(
self,
other: T,
) -> HstoreGetArray<Self, T::Expression> {
HstoreGetArray::new(self, other.as_expression())
}

/// Concatenates two hstores.
/// See [hstore || hstore operator](https://www.postgresql.org/docs/current/hstore.html)
fn concat<T: AsExpression<Hstore>>(self, other: T) -> HstoreConcat<Self, T::Expression> {
HstoreConcat::new(self, other.as_expression())
}

/// Check whether the hstore contains a key
/// See [hstore ? text operator](https://www.postgresql.org/docs/current/hstore.html)
fn has_key<T: AsExpression<Text>>(self, other: T) -> HstoreHasKey<Self, T::Expression> {
HstoreHasKey::new(self, other.as_expression())
}

/// Does hstore contain all the specified keys?
/// See [hstore ?& text[] operator](https://www.postgresql.org/docs/current/hstore.html)
fn has_all_keys<T: AsExpression<Array<Text>>>(
self,
other: T,
) -> HstoreHasAll<Self, T::Expression> {
HstoreHasAll::new(self, other.as_expression())
}

/// Does hstore contain any of the specified keys?
/// See [hstore ?| text[] operator](https://www.postgresql.org/docs/current/hstore.html)
fn has_any_keys<T: AsExpression<Array<Text>>>(
self,
other: T,
) -> HstoreHasAny<Self, T::Expression> {
HstoreHasAny::new(self, other.as_expression())
}

/// Implements Expression.contains() for Hstore
/// Checks whether the left operand contains the right operand.
/// See [hstore @> hstore operator](https://www.postgresql.org/docs/current/hstore.html)
fn contains<T: AsExpression<Hstore>>(self, other: T) -> HstoreRightSubset<Self, T::Expression> {
HstoreRightSubset::new(self, other.as_expression())
}

/// Implements Expression.is_contained_by() for Hstore
/// Checks whether the left operand is contained by the right operand.
/// See [hstore <@ hstore operator](https://www.postgresql.org/docs/current/hstore.html)
fn is_contained_by<T: AsExpression<Hstore>>(
self,
other: T,
) -> HstoreLeftSubset<Self, T::Expression> {
HstoreLeftSubset::new(self, other.as_expression())
}

// There should be a way to merge these into a single generic remove()
// but my type-fu is too weak
/// Remove a single key from the hstore
/// See [hstore - text operator](https://www.postgresql.org/docs/current/hstore.html)
fn remove_key<T: AsExpression<Text>>(self, other: T) -> HstoreRemove<Self, T::Expression> {
HstoreRemove::new(self, other.as_expression())
}

/// Remove the keys in the rhs array from the hstore.
/// See [hstore - text[] operator](https://www.postgresql.org/docs/current/hstore.html)
fn remove_keys<T: AsExpression<Array<Text>>>(
self,
other: T,
) -> HstoreRemove<Self, T::Expression> {
HstoreRemove::new(self, other.as_expression())
}

/// Remove the entries in the left hstore that are present in the rhs operand.
/// See [hstore - hstore operator](https://www.postgresql.org/docs/current/hstore.html)
fn difference<T: AsExpression<Hstore>>(self, other: T) -> HstoreRemove<Self, T::Expression> {
HstoreRemove::new(self, other.as_expression())
}

/// Converts hstore to an array of alternating keys and values.
/// See [%% hstore operator](https://www.postgresql.org/docs/current/hstore.html)
fn to_flat_array(self) -> HstoreFlatten<Self> {
HstoreFlatten::new(self)
}
}

impl<T: Expression<SqlType = Hstore>> HstoreOpExtensions for T {}
102 changes: 102 additions & 0 deletions src/functions.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/// Functions on the hstore type
/// See [PostgreSQL hstore](https://www.postgresql.org/docs/current/hstore.html)
use super::Hstore;
use diesel::sql_types::*;

// hstore ( record ) → hstore
// Constructs an hstore from a record or row.
// hstore(ROW(1,2)) → "f1"=>"1", "f2"=>"2"
// Not sure how to implement this
// sql_function!(fn hstore(row: SqlType) -> Hstore);

sql_function! {
/// Constructs an hstore from an array, which may be either a key/value array, or a two-dimensional array.
/// The multi-dimensional variant is not yet supported.
/// This implements hstore(text[])
#[sql_name = "hstore"]
fn hstore_from_array(arr: Array<Text>) -> Hstore;
}

sql_function! {
/// Converts the hstore to an array of alternating key/value elements.
fn hstore_to_array(h: Hstore) -> Array<Text>;
}

// 2D array and JSON conversions not currently supported

sql_function! {
/// Constructs an hstore from separate key and value arrays.
/// This implements hstore(text[], text[]).
#[sql_name = "hstore"]
fn hstore_from_kv_array(keys: Array<Text>, values: Array<Text>) -> Hstore;
}

sql_function! {
/// Makes a single-item hstore.
/// This implements hstore(text, text).
#[sql_name = "hstore"]
fn hstore_from_kv(key: Text, value: Text) -> Hstore;
}

sql_function! {
/// Extracts an hstore's keys as an array.
/// This implements the akeys(hstore) -> text[] postgres function.
/// The set variant skeys is currently unsupported.
#[sql_name = "akeys"]
fn hstore_to_keys(h: Hstore) -> Array<Text>
}

sql_function! {
/// Extracts an hstore's values as an array.
/// This implements the avals(hstore) -> text[] postgres function.
/// The set variant svals is currently unsupported
#[sql_name = "avals"]
fn hstore_to_values(h: Hstore) -> Array<Text>;
}

sql_function! {
/// Extracts a subset of an hstore containing only the specified keys.
/// This implements the slice (hstore, text[]) -> hstore postgres function.
#[sql_name = "slice"]
fn hstore_slice(h: Hstore, keys: Array<Text>) -> Hstore;
}

sql_function! {
/// Check whether the hstore contains a key
/// This implements the exist(hstore, text) -> boolean postgres function.
#[sql_name = "exist"]
fn hstore_exist(h: Hstore, k: Text) -> Bool;
}

sql_function! {
/// Does hstore contain a non-NULL value for key?
/// This implements the defined(hstore, text) -> boolean postgres function.
#[sql_name = "defined"]
fn hstore_defined(h: Hstore, k: Text) -> Bool;
}

sql_function! {
/// Deletes pairs with matching keys.
/// This implements the delete(hstore, text) -> hstore postgres function.
#[sql_name = "delete"]
fn hstore_delete_key(h: Hstore, key: Text) -> Hstore;
}

sql_function! {
/// Deletes pairs with matching keys.
/// This implements delete(hstore, text[]) -> hstore postgres function.
#[sql_name = "delete"]
fn hstore_delete_array(h: Hstore, keys: Array<Text>) -> Hstore;
}

sql_function! {
/// Deletes pairs matching those in the second argument.
/// This implements the delete (hstore, hstore) -> hstore postgres function.
#[sql_name = "delete"]
fn hstore_delete_matching(h: Hstore, other: Hstore) -> Hstore;
}

// populate_record ( anyelement, hstore ) → anyelement
// Replaces fields in the left operand (which must be a composite type) with matching values from hstore.
// populate_record(ROW(1,2), 'f1=>42'::hstore) → (42,2)
// Not sure how to implement this
Loading