Conflicting implementations- overwriting a trait method

I am trying to make a function that will be part of building up SQL-like queries. It gives the qfmt() method "Query Format" that can be used to help build a statement. Integers and Floats just need to be converted to a string "WHERE x = 5 " but strings need to be wrapped in (single) quotes "WHERE x = '5' "

Below is my attempt: Most generic types that implement ToString can simply use .to_string(), but strings themselves need the extra quotes.


use std::string::ToString;

pub trait QueryElement {
    fn qfmt(&self) -> String;
}

impl<T> QueryElement for T where T: ToString {
    fn qfmt(&self) -> String {
        self.to_string()
    }
}

impl QueryElement for String {
    fn qfmt(&self) -> String {
        format!("'{}'", &self)
    }
}

This returns the following error on compilation:

14 | impl QueryElement for String {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for std::string::String

I understand what the compiler is telling me: I am uncertain if there is a workaround. Any ideas are appreciated.

You should consider not using a blanket impl<T> QueryElement for T where T: ToString here—do you really need that level of generality or can you get away with just implementing QueryElement for some numeric types, one at a time? A simple macro can eliminate the boilerplate involved in the second approach.

If you definitely need arbitrary ToString types to work with QueryElement, I'd suggest modifying the blanket impl with a newtype, like this:

pub struct UnquotedLiteral<T>(pub T);

// no more overlap because `String` and `UnquotedLiteral<T>` are distinct,
// for every `T`
impl<T> QueryElement for UnquotedLiteral<T> where T: ToString {
    fn qfmt(&self) -> String {
        self.0.to_string()
    }
}

Then if x is a value of some ToString type, you can do UnquotedLiteral(x).qfmt() to use it for building a statement.

3 Likes

Thanks Cole. I think you are probably right in that selecting a few numeric types and implementing QueryElement for them is the more reasonable option. I will leave the trait as public so users can implement it on a custom or obscure type if they have a particular need.

1 Like

Note that you’ll want to do more processing than this if these strings are user-supplied. As written, they can break out of the quotes and add arbitrary SQL to the query. (cf XKCD)

3 Likes

lol! Thanks for the XKCD

As a quick follow-up: I have combined the recommendations from @cole-miller and @2e71828 into the below code.


pub trait QueryElement {
    fn qfmt(&self) -> String;
}

fn sanitize_input(string_raw: &str) -> String {
    // scrub a string to help prevent injection attacks
    let mut s: String = string_raw.clone().replace(" ", "_");
    s.retain(|c| r#"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890_-:.@"#.contains(c));
    return s
}

impl QueryElement for String {
    fn qfmt(&self) -> String {
        let sanitized =  sanitize_input(self);
        format!(r#""{}""#, &sanitized)
    }
}


impl QueryElement for i32 {
    fn qfmt(&self) -> String {
        self.to_string()
    }
}

fn main() {
    println!("SELECT * WHERE x = {}", 5i32.qfmt());
    println!("SELECT * WHERE x = {}", "fish".to_string().qfmt());
    println!("SELECT * WHERE x = {}", "fish'); DROP TABLE Users;".to_string().qfmt());
}

Output:

SELECT * WHERE x = 5
SELECT * WHERE x = "fish"
SELECT * WHERE x = "fish_DROP_TABLE_Users"