diff --git a/src/filter.rs b/src/filter.rs index 8a7b561..054d9a9 100644 --- a/src/filter.rs +++ b/src/filter.rs @@ -1,199 +1,392 @@ use crate::Builder; +fn clean_param(param: S) -> String +where + S: Into, +{ + let param = param.into(); + if ",.:()".chars().any(|c| param.contains(c)) { + format!("\"{}\"", param) + } else { + param + } +} + impl Builder { - pub fn eq(mut self, column: S, param: T) -> Self + /// Finds all rows whose value on the stated `column` exactly matches the + /// specified `filter`. + /// ``` + /// # use postgrest::Postgrest; + /// let req = Postgrest::new("http://localhost:3000") + /// .from("table") + /// .select("*") + /// .eq("name", "New Zealand"); + /// ``` + pub fn eq(mut self, column: S, filter: T) -> Self where S: Into, T: Into, { self.queries - .push((column.into(), format!("eq.{}", param.into()))); + .push((clean_param(column), format!("eq.{}", clean_param(filter)))); self } - pub fn neq(mut self, column: S, param: T) -> Self + /// Finds all rows whose value on the stated `column` doesn't exactly match + /// the specified `filter`. + /// ``` + /// # use postgrest::Postgrest; + /// let req = Postgrest::new("http://localhost:3000") + /// .from("table") + /// .select("*") + /// .neq("name", "China"); + /// ``` + pub fn neq(mut self, column: S, filter: T) -> Self where S: Into, T: Into, { self.queries - .push((column.into(), format!("neq.{}", param.into()))); + .push((clean_param(column), format!("neq.{}", clean_param(filter)))); self } - pub fn gt(mut self, column: S, param: T) -> Self + /// Finds all rows whose value on the stated `column` is greater than the + /// specified `filter`. + /// ``` + /// # use postgrest::Postgrest; + /// let req = Postgrest::new("http://localhost:3000") + /// .from("table") + /// .select("*") + /// .gt("id", "20"); + /// ``` + pub fn gt(mut self, column: S, filter: T) -> Self where S: Into, T: Into, { self.queries - .push((column.into(), format!("gt.{}", param.into()))); + .push((clean_param(column), format!("gt.{}", clean_param(filter)))); self } - pub fn gte(mut self, column: S, param: T) -> Self + /// Finds all rows whose value on the stated `column` is greater than or + /// equal to the specified `filter`. + /// ``` + /// # use postgrest::Postgrest; + /// let req = Postgrest::new("http://localhost:3000") + /// .from("table") + /// .select("*") + /// .gte("id", "20"); + /// ``` + pub fn gte(mut self, column: S, filter: T) -> Self where S: Into, T: Into, { self.queries - .push((column.into(), format!("gte.{}", param.into()))); + .push((clean_param(column), format!("gte.{}", clean_param(filter)))); self } - pub fn lt(mut self, column: S, param: T) -> Self + /// Finds all rows whose value on the stated `column` is less than the + /// specified `filter`. + /// ``` + /// # use postgrest::Postgrest; + /// let req = Postgrest::new("http://localhost:3000") + /// .from("table") + /// .select("*") + /// .lt("id", "20"); + /// ``` + pub fn lt(mut self, column: S, filter: T) -> Self where S: Into, T: Into, { self.queries - .push((column.into(), format!("lt.{}", param.into()))); + .push((clean_param(column), format!("lt.{}", clean_param(filter)))); self } - pub fn lte(mut self, column: S, param: T) -> Self + /// Finds all rows whose value on the stated `column` is less than or equal + /// to the specified `filter`. + /// ``` + /// # use postgrest::Postgrest; + /// let req = Postgrest::new("http://localhost:3000") + /// .from("table") + /// .select("*") + /// .lte("id", "20"); + /// ``` + pub fn lte(mut self, column: S, filter: T) -> Self where S: Into, T: Into, { self.queries - .push((column.into(), format!("lte.{}", param.into()))); + .push((clean_param(column), format!("lte.{}", clean_param(filter)))); self } - pub fn like(mut self, column: S, param: &str) -> Self + /// Finds all rows whose value in the stated `column` matches the supplied + /// `pattern` (case sensitive). + /// ``` + /// # use postgrest::Postgrest; + /// let req = Postgrest::new("http://localhost:3000") + /// .from("table") + /// .select("*") + /// .like("name", "%United%"); + /// + /// let req = Postgrest::new("http://localhost:3000") + /// .from("table") + /// .select("*") + /// .like("name", "%United States%"); + /// ``` + pub fn like(mut self, column: S, pattern: T) -> Self where S: Into, + T: Into, { - let param = str::replace(param, '%', "*"); + let pattern = pattern.into().replace('%', "*"); self.queries - .push((column.into(), format!("like.{}", param))); + .push((clean_param(column), format!("like.{}", pattern))); self } - pub fn ilike(mut self, column: S, param: &str) -> Self + /// Finds all rows whose value in the stated `column` matches the supplied + /// `pattern` (case insensitive). + /// ``` + /// # use postgrest::Postgrest; + /// let req = Postgrest::new("http://localhost:3000") + /// .from("table") + /// .select("*") + /// .ilike("name", "%United%"); + /// + /// let req = Postgrest::new("http://localhost:3000") + /// .from("table") + /// .select("*") + /// .ilike("name", "%united states%"); + /// ``` + pub fn ilike(mut self, column: S, pattern: T) -> Self where S: Into, + T: Into, { - let param = str::replace(param, '%', "*"); + let pattern = pattern.into().replace('%', "*"); self.queries - .push((column.into(), format!("ilike.{}", param))); + .push((clean_param(column), format!("ilike.{}", pattern))); self } - pub fn is(mut self, column: S, param: T) -> Self + /// A check for exact equality (null, true, false), finds all rows whose + /// value on the stated `column` exactly match the specified `filter`. + /// ``` + /// # use postgrest::Postgrest; + /// let req = Postgrest::new("http://localhost:3000") + /// .from("table") + /// .select("*") + /// .is("name", "null"); + /// ``` + pub fn is(mut self, column: S, filter: T) -> Self where S: Into, T: Into, { self.queries - .push((column.into(), format!("is.{}", param.into()))); + .push((clean_param(column), format!("is.{}", clean_param(filter)))); self } - pub fn in_(mut self, column: S, param: T) -> Self + /// Finds all rows whose value on the stated `column` is found on the + /// specified `values`. + /// ``` + /// # use postgrest::Postgrest; + /// let req = Postgrest::new("http://localhost:3000") + /// .from("table") + /// .select("*") + /// .in_("name", vec!["China", "France"]); + /// + /// let req = Postgrest::new("http://localhost:3000") + /// .from("table") + /// .select("*") + /// .in_("capitals", vec!["Beijing,China", "Paris,France"]); + /// + /// let req = Postgrest::new("http://localhost:3000") + /// .from("table") + /// .select("*") + /// .in_("food_supplies", vec!["carrot (big)", "carrot (small)"]); + /// ``` + pub fn in_(mut self, column: S, values: T) -> Self where S: Into, - T: Into>, + T: IntoIterator, + U: Into, { - // As per PostgREST docs, `in` should allow quoted commas - let param: Vec = param - .into() - .iter() - .map(|s| { - if s.contains(',') { - format!("\"{}\"", s) - } else { - s.to_string() - } - }) - .collect(); + let values: Vec<_> = values.into_iter().map(clean_param).collect(); self.queries - .push((column.into(), format!("in.({})", param.join(",")))); + .push((clean_param(column), format!("in.({})", values.join(",")))); self } - pub fn cs(mut self, column: S, param: T) -> Self + // TODO: Sanitize input + /// Finds all rows whose json || array || range value on the stated `column` + /// contains the values specified in `filter`. + /// ``` + /// # use postgrest::Postgrest; + /// let req = Postgrest::new("http://localhost:3000") + /// .from("table") + /// .select("*") + /// .cs("age_range", "(10,20)"); + /// ``` + pub fn cs(mut self, column: S, filter: T) -> Self where S: Into, - T: Into>, + T: Into, { self.queries - .push((column.into(), format!("cs.{{{}}}", param.into().join(",")))); + .push((clean_param(column), format!("cs.{}", filter.into()))); self } - pub fn cd(mut self, column: S, param: T) -> Self + // TODO: Sanitize input + /// Finds all rows whose json || array || range value on the stated `column` + /// is contained by the specified `filter`. + /// ``` + /// # use postgrest::Postgrest; + /// let req = Postgrest::new("http://localhost:3000") + /// .from("table") + /// .select("*") + /// .cd("age_range", "(10,20)"); + /// ``` + pub fn cd(mut self, column: S, filter: T) -> Self where S: Into, - T: Into>, + T: Into, { self.queries - .push((column.into(), format!("cd.{{{}}}", param.into().join(",")))); + .push((column.into(), format!("cd.{}", filter.into()))); self } - pub fn sl(mut self, column: S, param: T) -> Self + /// Finds all rows whose range value on the stated `column` is strictly to + /// the left of the specified `range`. + /// ``` + /// # use postgrest::Postgrest; + /// let req = Postgrest::new("http://localhost:3000") + /// .from("table") + /// .select("*") + /// .sl("age_range", (10, 20)); + /// ``` + pub fn sl(mut self, column: S, range: (i64, i64)) -> Self where S: Into, - T: Into, { self.queries - .push((column.into(), format!("sl.{}", param.into()))); + .push((column.into(), format!("sl.({},{})", range.0, range.1))); self } - pub fn sr(mut self, column: S, param: T) -> Self + /// Finds all rows whose range value on the stated `column` is strictly to + /// the right of the specified `range`. + /// ``` + /// # use postgrest::Postgrest; + /// let req = Postgrest::new("http://localhost:3000") + /// .from("table") + /// .select("*") + /// .sr("age_range", (10, 20)); + /// ``` + pub fn sr(mut self, column: S, range: (i64, i64)) -> Self where S: Into, - T: Into, { self.queries - .push((column.into(), format!("sr.{}", param.into()))); + .push((column.into(), format!("sr.({},{})", range.0, range.1))); self } - pub fn nxl(mut self, column: S, param: T) -> Self + /// Finds all rows whose range value on the stated `column` does not extend + /// to the left of the specified `range`. + /// ``` + /// # use postgrest::Postgrest; + /// let req = Postgrest::new("http://localhost:3000") + /// .from("table") + /// .select("*") + /// .nxl("age_range", (10, 20)); + /// ``` + pub fn nxl(mut self, column: S, range: (i64, i64)) -> Self where S: Into, - T: Into, { self.queries - .push((column.into(), format!("nxl.{}", param.into()))); + .push((column.into(), format!("nxl.({},{})", range.0, range.1))); self } - pub fn nxr(mut self, column: S, param: T) -> Self + /// Finds all rows whose range value on the stated `column` does not extend + /// to the right of the specified `range`. + /// ``` + /// # use postgrest::Postgrest; + /// let req = Postgrest::new("http://localhost:3000") + /// .from("table") + /// .select("*") + /// .nxr("age_range", (10, 20)); + /// ``` + pub fn nxr(mut self, column: S, range: (i64, i64)) -> Self where S: Into, - T: Into, { self.queries - .push((column.into(), format!("nxr.{}", param.into()))); + .push((column.into(), format!("nxr.({},{})", range.0, range.1))); self } - pub fn adj(mut self, column: S, param: T) -> Self + /// Finds all rows whose range value on the stated `column` is adjacent to + /// the specified `range`. + /// ``` + /// # use postgrest::Postgrest; + /// let req = Postgrest::new("http://localhost:3000") + /// .from("table") + /// .select("*") + /// .adj("age_range", (10, 20)); + /// ``` + pub fn adj(mut self, column: S, range: (i64, i64)) -> Self where S: Into, - T: Into, { self.queries - .push((column.into(), format!("adj.{}", param.into()))); + .push((column.into(), format!("adj.({},{})", range.0, range.1))); self } - pub fn ov(mut self, column: S, param: T) -> Self + // TODO: Sanitize input + /// Finds all rows whose array || range value on the stated `column` is + /// contained by the specified `filter`. + /// ``` + /// # use postgrest::Postgrest; + /// let req = Postgrest::new("http://localhost:3000") + /// .from("table") + /// .select("*") + /// .ov("age_range", "(10,20)"); + /// ``` + pub fn ov(mut self, column: S, filter: T) -> Self where S: Into, T: Into, { self.queries - .push((column.into(), format!("ov.{}", param.into()))); + .push((column.into(), format!("cd.{}", filter.into()))); self } - pub fn fts(mut self, column: S, tsquery: T, config: Option) -> Self + /// Finds all rows whose tsvector value on the stated `column` matches + /// to_tsquery(`tsquery`). + /// ``` + /// # use postgrest::Postgrest; + /// let req = Postgrest::new("http://localhost:3000") + /// .from("table") + /// .select("*") + /// .fts("phrase", "The Fat Cats", Some("english")); + /// ``` + pub fn fts(mut self, column: S, tsquery: T, config: Option<&str>) -> Self where S: Into, T: Into, @@ -208,7 +401,16 @@ impl Builder { self } - pub fn plfts(mut self, column: S, tsquery: T, config: Option) -> Self + /// Finds all rows whose tsvector value on the stated `column` matches + /// plainto_tsquery(`tsquery`). + /// ``` + /// # use postgrest::Postgrest; + /// let req = Postgrest::new("http://localhost:3000") + /// .from("table") + /// .select("*") + /// .plfts("phrase", "The Fat Cats", None); + /// ``` + pub fn plfts(mut self, column: S, tsquery: T, config: Option<&str>) -> Self where S: Into, T: Into, @@ -223,7 +425,16 @@ impl Builder { self } - pub fn phfts(mut self, column: S, tsquery: T, config: Option) -> Self + /// Finds all rows whose tsvector value on the stated `column` matches + /// phraseto_tsquery(`tsquery`). + /// ``` + /// # use postgrest::Postgrest; + /// let req = Postgrest::new("http://localhost:3000") + /// .from("table") + /// .select("*") + /// .phfts("phrase", "The Fat Cats", Some("english")); + /// ``` + pub fn phfts(mut self, column: S, tsquery: T, config: Option<&str>) -> Self where S: Into, T: Into, @@ -238,7 +449,16 @@ impl Builder { self } - pub fn wfts(mut self, column: S, tsquery: T, config: Option) -> Self + /// Finds all rows whose tsvector value on the stated `column` matches + /// websearch_to_tsquery(`tsquery`). + /// ``` + /// # use postgrest::Postgrest; + /// let req = Postgrest::new("http://localhost:3000") + /// .from("table") + /// .select("*") + /// .wfts("phrase", "The Fat Cats", None); + /// ``` + pub fn wfts(mut self, column: S, tsquery: T, config: Option<&str>) -> Self where S: Into, T: Into, @@ -253,38 +473,3 @@ impl Builder { self } } - -#[cfg(test)] -mod tests { - use crate::Postgrest; - - const REST_URL: &str = "http://localhost:3000"; - - #[test] - fn simple_filters_assert_query() { - let client = Postgrest::new(REST_URL); - - let req = client.from("users").select("ignored").eq("column", "key"); - assert_eq!( - req.queries - .contains(&("column".to_string(), "eq.key".to_string())), - true - ); - - let req = client.from("users").select("ignored").neq("column", "key"); - assert_eq!( - req.queries - .contains(&("column".to_string(), "neq.key".to_string())), - true - ); - - let req = client.from("users").select("ignored").gt("column", "key"); - assert_eq!( - req.queries - .contains(&("column".to_string(), "gt.key".to_string())), - true - ); - - // ... - } -}