blackbeam / rust-mysql-simple

Mysql client library implemented in rust.
Apache License 2.0
665 stars 146 forks source link

Failed using more than one argument on a MySQL function #273

Closed ApourtArtt closed 3 years ago

ApourtArtt commented 3 years ago

Hello,

I am using your crate on a project and successfully used it for all of my needs except one. I am not able to use a MySQL function that has more than one argument, if it does, I am receiving this error :

thread 'main' panicked at 'Unknown column type 243', C:\Users\Moi.cargo\registry\src\github.com-1ecc6299db9ec823\mysql_common-0.24.1\src\constants.rs:498:18 note: run with RUST_BACKTRACE=1 environment variable to display a backtrace error: process didn't exit successfully: target\debug\MySqlIssue.exe (exit code: 101)

Here is my dummy MySQL function :

CREATE DEFINER=`root`@`localhost` FUNCTION `f1`(p_arg INT, p_arg2 INT) RETURNS int
BEGIN

RETURN p_arg + p_arg2;

END

Executing it through SELECT works as intended on MySQL Workbench. But it does not with your crate, except if I remove p_arg2, in order to make it one arg only.

Here is the code I am using :

use mysql::{Pool, OptsBuilder};
use std::error::Error;
use mysql::{Row, FromRowError};
use mysql::prelude::{Queryable};

#[derive(Clone)]
pub struct Database {
    pub pool: mysql::Pool,
}

impl Database {
// Create a database object, owning a mysql::Pool
    pub fn new() -> Result<Self, Box<dyn Error>> {
        match Pool::new(
            OptsBuilder::new()
            .user(Some("user"))
            .pass(Some("pass"))
            .db_name(Some("db_name"))
            .tcp_port(3306))
        {
            Ok(pool) => {
                return Ok(Self {
                    pool,
                });
            },
            Err(e) => {
                return Err(Box::new(e));
            }
        }
    }
}

// Format a vec_u8 to a String
fn vec_u8_to_string(value: Vec<u8>) -> String {
    let mut result = String::new();
    result.reserve(value.len() * 3);

    for i in 0..value.len() {
        result.push_str(format!("\\{:#X}", value[i]).as_ref());
    }

    return result;
}

// Escape '"'
fn escape(value: String) -> String {
    value.replace("\"", "\\\"")
}

// Create a string from a row
fn handle_row(row_res: Result<Row, FromRowError>) -> String {
    let mut packet = String::new();
        packet.push('[');
        match row_res {
            Ok(mut row) => {
                for i in 0..row.len() {
                    match row.take_opt::<mysql::Value, usize>(i) {
                        Some(val_res) => {
                            match val_res {
                                Ok(val) => {
                                    if let Ok(as_string) = mysql::from_value_opt::<String>(val.clone()) {
                                        packet.push('"');
                                        packet.push_str(escape(as_string).as_ref());
                                        packet.push('"');
                                    } else {
                                        use mysql::chrono::NaiveDateTime;
                                        use std::time::Duration;
                                        match val {
                                            mysql::Value::NULL => packet.push_str(&String::from("null")),
                                            mysql::Value::Int(..) => packet.push_str(&mysql::from_value::<i64>(val).to_string()),
                                            mysql::Value::UInt(..) => packet.push_str(&mysql::from_value::<u64>(val).to_string()),
                                            mysql::Value::Float(..) => packet.push_str(&mysql::from_value::<f32>(val).to_string()),
                                            mysql::Value::Double(..) => packet.push_str(&mysql::from_value::<f64>(val).to_string()),
                                            mysql::Value::Time(..) => packet.push_str(&mysql::from_value::<Duration>(val).as_millis().to_string()),
                                            mysql::Value::Date(..) => {
                                                packet.push('"');
                                                packet.push_str(&mysql::from_value::<NaiveDateTime>(val).to_string());
                                                packet.push('"');
                                            },
                                            mysql::Value::Bytes(..) => {
                                                packet.push('"');
                                                packet.push_str(&vec_u8_to_string(mysql::from_value::<Vec<u8>>(val)));
                                                packet.push('"');
                                            },
                                        };
                                    }
                                },
                                Err(e) => { println!("Error2: {}", e); }
                            }
                        },
                        None => { println!("Empty"); }
                    }
                    if i < row.len() - 1 {
                        packet.push(',')
                    }
                }
            },
            Err(e) => { println!("Error1 : {}", e); }
        }
        packet.push(']');
        return packet;
}

fn main() {
    let db = Database::new().expect("Failed starting db");
    let mut conn = db.pool.get_conn().expect("Failed getting conn");

    let vec = vec!(
        String::from("100"),
        String::from("100"),
    );
    match conn.exec_map_opt(String::from("Select f1(?, ?);"), vec, handle_row) {
        Ok(v) => { println!("Success: {:?}", v); }, // Should print Success: [[200]]
        Err(e) => { println!("Error : {}", e); },
    }
}

Is there something I am not doing right ? I could not find any related issues, sorry if I missed them.

Edit : I am using mysql = "^20.0.1"

ApourtArtt commented 3 years ago

I'm leaving it as a second comment :

CREATE DEFINER=`root`@`localhost` PROCEDURE `p1`(
    IN p_a1 INT,
    IN p_a2 INT
)
BEGIN

    SELECT p_a1 + p_a2;

END

and using it through

fn main() {
    let db = Database::new().expect("Failed starting db");
    let mut conn = db.pool.get_conn().expect("Failed getting conn");

    let vec = vec!(
        String::from("100"),
        String::from("100"),
    );
    match conn.exec_map_opt(String::from("call p1(?, ?);"), vec, handle_row) {
        Ok(v) => { println!("Success: {:?}", v); },
        Err(e) => { println!("Error : {}", e); },
    }
}

works perfectly, so this is a valid workaround, but I'd like to be able to use return since it would be easier in my case.

blackbeam commented 3 years ago

Hi. Thanks for pointing this out.

blackbeam/rust_mysql_common@408effe fixes the issue. You need to wait for the next release.

blackbeam commented 3 years ago

Hi. This should be fixed in v21.0.0