Tests using BTreeMap end up in stack overflow while similar implementation works seamless


#1

I’m currently working on a library project for creating SQL-Queries and encountered a stack overflow when running a test with my InsertQuery struct.
The struct is defined as follows:

pub struct InsertQuery<'a> {
    into: &'a str,
    pub values: BTreeMap<&'a str, Value<'a>>,
}

‘Value’ is just an Enum that represents some basic MySQL data types (Value::Varchar(&'a str)…)

The struct has a method that returns a string representing the struct as a Query for use with databases called ‘as_string’ which is defined like this:

pub fn as_string(&self) -> String {
        let mut res = String::new();
        let (mut vals, mut vals_list) = (String::new(), String::new());

        res = format!("INSERT INTO {}", self.into);

        if !self.values.is_empty() {
            let mut keys = self.values.keys();
            let key = keys.next().unwrap();
            vals = format!("{}", key);
            vals_list = format!("{}", self.values[key]);

            for k in keys {
                vals = format!("{}, {}", vals, k);
                vals_list = format!("{}, {}", vals_list, self.values[k]);
            }
        }

        format!("{}({}) VALUES({})", res, vals, vals_list)
    }

and the struct’s constructor looks like this:

pub fn into(table: &'a str) -> InsertQuery<'a> {
        InsertQuery {
            into: table,
            values: BTreeMap::new(),
        }
    }

And when running my test function

#[test]
fn insert_simple() {
    let mut q = InsertQuery::into("users");
    q.values.insert("name", Value::Varchar("greg"));

    assert_eq!(q.as_string(), "INSERT INTO users(name) VALUES('greg')")
}

with ‘cargo test’ I end up with this error:

thread 'tests::insert_simple' has overflowed its stack fatal runtime error: stack overflow
error: process didn't exit successfully: `/home/elliot/Rust/libs/query_builder/target/debug /deps/query_builder-0ad36dfbc570aadb` (signal: 6, SIGABRT: process abort signal)

However, I already got an older implementation that works similar (also using BTreeMap<&'a str, Value<'a>>) which did not end up like this. The only differences are, that the older implementation had an internal String that was updated on every other call to the struct (the field ‘values’ was private and data was added via an extra method that just called the insert function on the map followed a call to update the string)

I do not understand why this one does overflow while the other one did not. Please help me to understand and fix this problem.

Thank you in advance


#2

What does the Display impl for Value<'a> look like?


#3

The Display Trait is implemented like this:

impl<'c> Display for Value<'c> {
    fn fmt(&self, f: &mut Formatter) -> FormatResult {
        write!(f, "{}", self.to_string())
    }
}

while the to_string function looks like this:

pub fn as_string(&self) -> String {
        match *self {
            Value::Varchar(v) => format!("'{}'", v),
            Value::Bool(b) => if b {
                "TRUE".to_string()
            } else {
                "FALSE".to_string()
            },
            Value::Tinyint(t) => format!("{}", t),
            Value::UnsignedTinyint(ut) => format!("{}", ut),
            Value::Smallint(s) => format!("{}", s),
            Value::UnsignedSmallint(us) => format!("{}", us),
            Value::Int(i) => format!("{}", i),
            Value::UnsignedInt(ui) => format!("{}", ui),
            Value::Bigint(bi) => format!("{}", bi),
            Value::UnsignedBigint(ubi) => format!("{}", ubi),
        }
    }

The older implementation was different in the Display part, by just using the ‘raw’(?) values contained in the enum while the to_string method is pretty much the same


#4

Is it just a typo that Display is calling self.to_string() but you’re showing as_string()? to_string() might be part of the ToString impl that you have (I don’t know, just guessing). My hunch is that you end up having inf recursion because you end up calling some formatting code in a loop.

Can you paste all the relevant code of these types?


#5

Value does not implement ToString, however you were right about the typo. I did not notice that one and after changing it does compile and all tests complete successful.

So finally, this is all the current code for Value

#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub enum Value<'c> {
    Varchar(&'c str),
    Bool(bool),
    Tinyint(i8),
    UnsignedTinyint(u8),
    Smallint(i16),
    UnsignedSmallint(u16),
    Int(i32),
    UnsignedInt(u32),
    Bigint(i64),
    UnsignedBigint(u64),
}

impl<'c> Value<'c> {
    pub fn as_string(&self) -> String {
        match *self {
            Value::Varchar(v) => format!("'{}'", v),
            Value::Bool(b) => if b {
                "TRUE".to_string()
            } else {
                "FALSE".to_string()
            },
            Value::Tinyint(t) => format!("{}", t),
            Value::UnsignedTinyint(ut) => format!("{}", ut),
            Value::Smallint(s) => format!("{}", s),
            Value::UnsignedSmallint(us) => format!("{}", us),
            Value::Int(i) => format!("{}", i),
            Value::UnsignedInt(ui) => format!("{}", ui),
            Value::Bigint(bi) => format!("{}", bi),
            Value::UnsignedBigint(ubi) => format!("{}", ubi),
        }
    }
}

impl<'c> Display for Value<'c> {
    fn fmt(&self, f: &mut Formatter) -> FormatResult {
        write!(f, "{}", self.as_string())
    }
}

And this is all the current code for the InsertQuery:

pub struct InsertQuery<'a> {
    into: &'a str,
    pub values: BTreeMap<&'a str, Value<'a>>,
}

impl<'a> InsertQuery<'a> {
    pub fn into(table: &'a str) -> InsertQuery<'a> {
        InsertQuery {
            into: table,
            values: BTreeMap::new(),
        }
    }

    pub fn as_string(&self) -> String {
        let mut res = String::new();
        let (mut vals, mut vals_list) = (String::new(), String::new());

        res = format!("INSERT INTO {}", self.into);

        if !self.values.is_empty() {
            let mut keys = self.values.keys();
            let key = keys.next().unwrap();
            vals = format!("{}", key);
            vals_list = format!("{}", self.values[key]);

            for k in keys {
                vals = format!("{}, {}", vals, k);
                vals_list = format!("{}, {}", vals_list, self.values[k]);
            }
        }

        format!("{}({}) VALUES({})", res, vals, vals_list)
    }
}

and this is the current test function using both of the above:

#[test]
fn insert_simple() {
    let mut q = InsertQuery::into("users");
    q.values.insert("name", Value::Varchar("greg"));

    assert_eq!(q.as_string(), "INSERT INTO users(name) VALUES('greg')")
}

EDIT:
Now that I think about I don’t even know why the code even compiled, I had the ToString imported for some reason, but it wasn’t really implemented anywhere…


#6

It’s implemented automatically for anything that implements Display :slight_smile:


#7

Oh ok… Thank you for clarifying that to me ^-^