#[cfg(feature = "sqlite")] #[cfg(debug_assertions)] mod sqlite { use concatsql::prelude::*; use concatsql::prep; use concatsql::{Error, ErrorLevel}; macro_rules! err { () => { Err(Error::AnyError) }; ($msg:expr) => { Err(Error::Message($msg.to_string())) }; } pub fn prepare() -> concatsql::Connection { let conn = concatsql::sqlite::open(":memory:").unwrap(); conn.error_level(ErrorLevel::Debug); let stmt = prep!(stmt()); conn.execute(stmt).unwrap(); conn } fn stmt() -> &'static str { r#"CREATE TABLE users (name TEXT, age INTEGER); INSERT INTO users (name, age) VALUES ('Alice', 42); INSERT INTO users (name, age) VALUES ('Bob', 69); INSERT INTO users (name, age) VALUES ('Carol', 50);"# } #[test] fn open() { let _conn = concatsql::sqlite::open(":memory:").unwrap(); } #[test] fn static_strings() { macro_rules! static_strings {( $( $var:ident = $($expr:expr),* $(,)? ; )* ) => ( $( macro_rules! $var {() => ( concat!($( $expr, )* ) )} #[allow(dead_code, non_upper_case_globals)] const $var: &'static str = $var!(); )* )} let conn = concatsql::sqlite::open(":memory:").unwrap(); let stmt = prep!(stmt()); conn.execute(stmt).unwrap(); static_strings! { select = "SELECT "; cols = "name "; from = "FROM "; table = "users"; sql = select!(), cols!(), from!(), table!(); } assert_eq!(prep!(sql).simulate(), "SELECT name FROM users"); } #[test] fn execute() { let conn = concatsql::sqlite::open(":memory:").unwrap(); let stmt = prep!(stmt()); conn.execute(stmt).unwrap(); } #[test] fn iterate() { let conn = prepare(); let expects = ["Alice", "Bob", "Carol"]; let sql = prep!("SELECT name FROM users;"); let mut i = 0; conn.iterate(sql, |pairs| { for &(_, value) in pairs.iter() { assert_eq!(value.unwrap(), expects[i]); } i += 1; true }).unwrap(); } #[test] fn iterate_2sets() { let conn = prepare(); let expects = ["Alice", "Bob", "Carol", "Alice", "Bob", "Carol"]; let sql = prep!("SELECT name FROM users; SELECT name FROM users;"); let mut i = 0; conn.iterate(sql, |pairs| { for &(_, value) in pairs.iter() { assert_eq!(value.unwrap(), expects[i]); } i += 1; true }).unwrap(); } #[test] fn iterate_or() { let conn = prepare(); let expects = ["Alice", "Bob"]; let age = "50"; let sql = prep!("SELECT name FROM users WHERE ") + &prep!("age < ") + age + &prep!(" OR ") + age + &prep!(" < age"); let mut i = 0; conn.iterate(sql, |pairs| { for &(_, value) in pairs.iter() { assert_eq!(value.unwrap(), expects[i]); } i += 1; true }).unwrap(); } #[test] fn rows() { let conn = prepare(); let expects = [("Alice", 42), ("Bob", 69), ("Carol", 50)]; let sql = prep!("SELECT * FROM users;"); let mut cnt = 0; let rows = conn.rows(&sql).unwrap(); for (i, row) in rows.iter().enumerate() { cnt += 1; assert_eq!(row.get("name").unwrap(), expects[i].0); assert_eq!(row.get("age").unwrap(), expects[i].1.to_string()); } assert!(cnt == expects.len()); } #[test] fn rows_foreach() { let conn = prepare(); let expects = [("Alice", 42), ("Bob", 69), ("Carol", 50)]; let mut cnt = 0; conn.rows(&prep!("SELECT * FROM users;")).unwrap().iter().enumerate().for_each(|(i, row)| { cnt += 1; assert_eq!(row.get("name").unwrap(), expects[i].0); assert_eq!(row.get("age").unwrap(), expects[i].1.to_string()); }); assert!(cnt == expects.len()); } #[test] fn start_with_quotation_and_end_with_anything_else() { let conn = prepare(); let name = "'Alice'; DROP TABLE users; --"; let sql = prep!("select age from users where name = ") + name + &prep!(""); assert_eq!( sql.simulate(), "select age from users where name = '''Alice''; DROP TABLE users; --'" ); conn.iterate(&sql, |_| { unreachable!(); }).unwrap(); } #[test] fn whitespace() { let conn = prepare(); let sql = prep!("select\n*\rfrom\nusers;"); conn.iterate(sql, |_| { true }).unwrap(); } #[test] fn sqli_eq_nonquote() { let conn = prepare(); let name = "Alice' or '1'='1"; let sql = prep!("select age from users where name =") + name + &prep!(";"); // "select age from users where name = 'Alice'' or ''1''=''1';" conn.iterate(sql, |_| { unreachable!(); }).unwrap(); } #[test] fn sanitizing() { let conn = prepare(); let name = r#""#; let sql = prep!("INSERT INTO users VALUES(") + name + &prep!(", 12345);"); conn.execute(sql).unwrap(); conn.rows(prep!("SELECT name FROM users WHERE age = 12345;")).unwrap().iter() .all(|row| { assert_eq!( concatsql::html_special_chars(row.get("name").unwrap()), "<script>alert("&1");</script>" ); true }); } #[test] fn error_level() { let conn = concatsql::sqlite::open(":memory:").unwrap(); conn.error_level(ErrorLevel::AlwaysOk); conn.error_level(ErrorLevel::Release); conn.error_level(ErrorLevel::Develop); conn.error_level(ErrorLevel::Debug); } #[test] #[allow(non_snake_case)] fn error_level_AlwaysOk() { let conn = concatsql::sqlite::open(":memory:").unwrap(); conn.error_level(ErrorLevel::AlwaysOk); let invalid_sql = "INVALID_SQL"; assert_eq!(conn.execute(invalid_sql), Ok(())); assert_eq!(conn.iterate(invalid_sql, |_| unreachable!()), Ok(())); assert_eq!(conn.rows(invalid_sql), Ok(Vec::new())); } #[test] fn error_level_release() { let conn = concatsql::sqlite::open(":memory:").unwrap(); conn.error_level(ErrorLevel::Release); let invalid_sql = "INVALID_SQL"; assert_eq!(conn.execute(invalid_sql), err!()); assert_eq!(conn.iterate(invalid_sql, |_| unreachable!()), err!()); assert_eq!(conn.rows(invalid_sql), err!()); } #[test] fn error_level_develop() { let conn = concatsql::sqlite::open(":memory:").unwrap(); conn.error_level(ErrorLevel::Develop); let invalid_sql = "INVALID_SQL"; assert_eq!(conn.execute(invalid_sql), err!("exec error")); assert_eq!(conn.iterate(invalid_sql, |_| unreachable!()), err!("exec error")); assert_eq!(conn.rows(invalid_sql), err!("exec error")); } #[test] fn error_level_debug() { let conn = concatsql::sqlite::open(":memory:").unwrap(); conn.error_level(ErrorLevel::Debug); let invalid_sql = "INVALID_SQL"; assert_eq!(conn.execute(invalid_sql), err!("exec error: near \"INVALID_SQL\": syntax error")); assert_eq!(conn.iterate(invalid_sql, |_| unreachable!()), err!("exec error: near \"INVALID_SQL\": syntax error")); assert_eq!(conn.rows(invalid_sql), err!("exec error: near \"INVALID_SQL\": syntax error")); } #[test] fn prep_into_execute() { let conn = concatsql::sqlite::open(":memory:").unwrap(); conn.execute(prep!("SELECT ") + 1).unwrap(); } #[test] fn prep_into_iterate() { let conn = concatsql::sqlite::open(":memory:").unwrap(); conn.iterate(prep!("SELECT ") + 1, |_| true ).unwrap(); } #[test] fn prep_into_rows() { let conn = concatsql::sqlite::open(":memory:").unwrap(); let mut executed = false; for row in &conn.rows(prep!("SELECT ") + 1).unwrap() { executed = true; assert_eq!(row.get(0).unwrap(), "1"); } assert!(executed); } #[test] fn multi_thread() { use std::thread; use std::sync::{Arc, Mutex}; let conn = Arc::new(Mutex::new(concatsql::sqlite::open(":memory:").unwrap())); let stmt = prep!(stmt()); conn.lock().unwrap().execute(stmt).unwrap(); let mut handles = vec![]; for i in 0..10 { let conn_clone = conn.clone(); let handle = thread::spawn(move || { let conn = &*conn_clone.lock().unwrap(); let sql = prep!("INSERT INTO users VALUES ('Thread', ") + i + prep!(");"); conn.execute(sql).unwrap(); }); handles.push(handle); } for handle in handles { handle.join().unwrap(); } let conn = &*conn.lock().unwrap(); assert_eq!(90, (0..10).map(|mut i| { conn.iterate(prep!("SELECT age FROM users WHERE age = ") + i, |pairs| { pairs.iter().for_each(|(_, v)| { assert_eq!(i.to_string(), v.unwrap()); i*=2; }); true }).unwrap(); i }).sum::()); } #[test] fn like() { let conn = prepare(); let name = "A%"; let sql = prep!("SELECT * FROM users WHERE name LIKE ") + name + prep!(";"); let mut executed = false; conn.rows(&sql).unwrap().iter().all(|row| { assert_eq!(row.get("name").unwrap(), "Alice"); executed = true; true }); assert!(executed); let name = "A"; let sql = prep!("SELECT * FROM users WHERE name LIKE ") + ("%".to_owned() + name + "%"); assert_eq!(sql.simulate(), "SELECT * FROM users WHERE name LIKE '%A%'"); conn.execute(&sql).unwrap(); let name = "%A%"; let sql = prep!("SELECT * FROM users WHERE name LIKE ") + ("%".to_owned() + &sanitize_like!(name) + "%"); if cfg!(feature = "mysql") || cfg!(feature = "postgres") { assert_eq!(sql.simulate(), "SELECT * FROM users WHERE name LIKE '%\\\\%A\\\\%%'"); } else { assert_eq!(sql.simulate(), "SELECT * FROM users WHERE name LIKE '%\\%A\\%%'"); } conn.execute(&sql).unwrap(); let name = String::from("%A%"); let sql = prep!("SELECT * FROM users WHERE name LIKE ") + ("%".to_owned() + &sanitize_like!(name, '$') + "%"); assert_eq!(sql.simulate(), "SELECT * FROM users WHERE name LIKE '%$%A$%%'"); conn.execute(&sql).unwrap(); } #[test] fn glob() { let conn = prepare(); let name = "A?['i]*"; let sql = prep!("SELECT * FROM users WHERE name GLOB ") + name; let mut executed = false; conn.rows(&sql).unwrap().iter().all(|row| { assert_eq!(row.get("name").unwrap(), "Alice"); executed = true; true }); assert!(executed); } #[test] fn multiple_stmt() { let conn = prepare(); let mut cnt = 0; for (i, row) in conn.rows("SELECT 1; SELECT 2;").unwrap().iter().enumerate() { /*^^^^^^^^*/// <- only first statement cnt += 1; assert_eq!(row.get_into::<_, i32>(0).unwrap(), [ 1, 2 ][i]); }; for (i, row) in conn.rows("SELECT age FROM users;").unwrap().iter().enumerate() { cnt += 1; assert_eq!(row.get_into::<_, i32>(0).unwrap(), [ 42, 69, 50 ][i]); }; assert_eq!(cnt, 4); } #[test] #[ignore] fn mass_connection() { let capacity = 65536; let mut conns = Vec::with_capacity(capacity); for _ in 0..capacity { conns.push(concatsql::sqlite::open(":memory:").unwrap()); } for i in 1..capacity { assert_ne!(conns[0], conns[i]); } } #[test] fn blob() { let conn = concatsql::sqlite::open(":memory:").unwrap(); conn.execute("CREATE TABLE b (data blob)").unwrap(); let data = vec![0x1, 0xA, 0xFF, 0x00, 0x7F]; let sql = prep!("INSERT INTO b VALUES (") + &data + prep!(")"); conn.execute(sql).unwrap(); for row in conn.rows("SELECT data FROM b").unwrap() { assert_eq!(row.get_into::<_, Vec>(0).unwrap(), data); } } #[test] fn question() { let conn = prepare(); let sql = prep!("SELECT name FROM users WHERE name=") + "?"; for _ in conn.rows(&sql).unwrap() { unreachable!(); } } #[test] fn iterator() { let conn = prepare(); let sql = prep!("SELECT name FROM users WHERE name=") + "?"; for _ in conn.rows(&sql).unwrap() { unreachable!(); } for _ in conn.rows(&sql).unwrap().iter() { unreachable!(); } for _ in &conn.rows(&sql).unwrap() { unreachable!(); } } #[test] fn map_collect() { let conn = prepare(); let rows = conn.rows("SELECT * FROM users").unwrap(); let names = rows.iter().map(|row| row.get("name")).collect::>>(); let mut cnt = 0; for (i, name) in names.iter().enumerate() { cnt += 1; assert_eq!(name.unwrap(), ["Alice","Bob","Carol"][i]) } assert_eq!(cnt, 3); } #[test] fn without_escape() { unsafe { assert_eq!((prep!() + concatsql::without_escape(&String::from("42")) ).simulate(), "42"); assert_eq!((prep!() + concatsql::without_escape(&String::from("foo"))).simulate(), "foo"); assert_eq!((prep!() + concatsql::without_escape(&String::from("")) ).simulate(), ""); assert_eq!((prep!() + String::from("42") ).simulate(), "'42'"); assert_eq!((prep!() + String::from("foo") ).simulate(), "'foo'"); assert_eq!((prep!() + String::from("") ).simulate(), "''"); } } #[test] fn in_array() { let conn = prepare(); let sql = prep!("SELECT * FROM users WHERE name IN (") + vec![] as Vec<&str> + prep!(")"); conn.rows(&sql).unwrap(); let sql = prep!("SELECT * FROM users WHERE name IN (") + vec!["Adam"] + prep!(")"); conn.rows(&sql).unwrap(); let sql = prep!("SELECT * FROM users WHERE name IN (") + vec!["Adam","Eve"] + prep!(")"); conn.rows(&sql).unwrap(); } #[test] fn uuid() { use uuid::Uuid; let conn = prepare(); let sql = prep!("SELECT ") + Uuid::nil(); for row in conn.rows(&sql).unwrap() { assert_eq!(&row[0], "00000000000000000000000000000000"); } let sql = prep!("SELECT ") + Uuid::parse_str("936DA01F-9ABD-4D9D-80C7-02AF85C822A8").unwrap(); for row in conn.rows(&sql).unwrap() { assert_eq!(&row[0], "936DA01F9ABD4D9D80C702AF85C822A8"); } } #[test] fn sql_injection() { let conn = prepare(); let name = "' OR 1=2; SELECT 1; --"; let sql = prep!("SELECT age FROM users WHERE name = '") + name + &prep!("';"); // '?' is not placeholder assert_eq!( conn.rows(&sql), Err(Error::Message("bind error: column index out of range".to_string())) ); let name = "' OR 1=1; --"; let sql = prep!("SELECT age FROM users WHERE name = '") + name + &prep!("';"); // '?' is not placeholder assert_eq!( conn.rows(&sql), Err(Error::Message("bind error: column index out of range".to_string())) ); let name = "Alice"; let sql = prep!("SELECT age FROM users WHERE name = '") + name + &prep!("';"); // '?' is not placeholder assert_eq!( conn.rows(&sql), Err(Error::Message("bind error: column index out of range".to_string())) ); let name = "'' OR 1=1; --"; let sql = prep!("SELECT age FROM users WHERE name = ") + name; for _ in conn.rows(&sql).unwrap() { unreachable!(); } let name = "''; DROP TABLE users; --"; let sql = prep!("SELECT age FROM users WHERE name = ") + name; for _ in conn.rows(&sql).unwrap() { unreachable!(); } let sql = prep!("SELECT ") + "0x50 + 0x45"; for row in conn.rows(&sql).unwrap() { assert_eq!(row.get(0).unwrap(), "0x50 + 0x45"); } let sql = prep!("SELECT ") + "0x414243"; for row in conn.rows(&sql).unwrap() { assert_eq!(row.get(0).unwrap(), "0x414243"); } let sql = prep!("SELECT ") + "CHAR(0x66)"; for row in conn.rows(&sql).unwrap() { assert_eq!(row.get(0).unwrap(), "CHAR(0x66)"); } let sql = prep!("SELECT ") + "IF(1=1, 'true', 'false')"; for row in conn.rows(&sql).unwrap() { assert_eq!(row.get(0).unwrap(), "IF(1=1, 'true', 'false')"); } let sql = prep!("SELECT ") + "na + '-' + me FROM users"; for row in conn.rows(&sql).unwrap() { assert_eq!(row.get(0).unwrap(), "na + '-' + me FROM users"); } let sql = prep!("SELECT ") + "ASCII('a')"; for row in conn.rows(&sql).unwrap() { assert_eq!(row.get(0).unwrap(), "ASCII('a')"); } let sql = prep!("SELECT ") + "CHAR(64)"; for row in conn.rows(&sql).unwrap() { assert_eq!(row.get(0).unwrap(), "CHAR(64)"); } } } #[cfg(feature = "sqlite")] #[cfg(not(debug_assertions))] mod sqlite_release_build { use concatsql::prelude::*; use concatsql::prep; #[test] fn sqli_enable() { let conn = concatsql::sqlite::open(":memory:").unwrap(); conn.execute("CREATE TABLE users (name TEXT, age INTEGER);").unwrap(); let name = "OR 1=2; SELECT 1; --"; let sql = prep!("SELECT age FROM users WHERE name = '") + name + &prep!("';"); for row in conn.rows(&sql).unwrap() { assert_eq!(row.get(0).unwrap(), "1"); } } } #[cfg(feature = "sqlite")] mod anti_patterns { use concatsql::prelude::*; use concatsql::prep; // Although it becomes possible, I do not believe it is less useful // because its real advantage is that it still makes it harder to do the wrong thing. #[test] fn string_to_static_str() { let conn = sqlite::open(":memory:").unwrap(); let sql: &'static str = Box::leak(String::from("SELECT 1").into_boxed_str()); conn.execute(sql).unwrap(); unsafe { drop(Box::from_raw(sql.as_ptr() as *mut u8)); } } #[test] fn text_op_integer() { let conn = super::sqlite::prepare(); let mut cnt = 0; let sql = prep!("SELECT age FROM users WHERE name = ") + i32::MAX; for _ in conn.rows(&sql).unwrap() { unreachable!(); } let sql = prep!("SELECT age FROM users WHERE name < ") + i32::MAX; for _ in conn.rows(&sql).unwrap() { unreachable!(); } let sql = prep!("SELECT age FROM users WHERE name > ") + i32::MAX; for _ in conn.rows(&sql).unwrap() { cnt += 1; } let sql = prep!("SELECT age FROM users WHERE name = ") + i32::MIN; for _ in conn.rows(&sql).unwrap() { unreachable!(); } let sql = prep!("SELECT age FROM users WHERE name < ") + i32::MIN; for _ in conn.rows(&sql).unwrap() { unreachable!(); } let sql = prep!("SELECT age FROM users WHERE name > ") + i32::MIN; for _ in conn.rows(&sql).unwrap() { cnt += 1; } let sql = prep!("SELECT age FROM users WHERE name = ") + u32::MAX; for _ in conn.rows(&sql).unwrap() { unreachable!(); } let sql = prep!("SELECT age FROM users WHERE name < ") + u32::MAX; for _ in conn.rows(&sql).unwrap() { unreachable!(); } let sql = prep!("SELECT age FROM users WHERE name > ") + u32::MAX; for _ in conn.rows(&sql).unwrap() { cnt += 1; } let sql = prep!("SELECT age FROM users WHERE name = ") + u32::MIN; for _ in conn.rows(&sql).unwrap() { unreachable!(); } let sql = prep!("SELECT age FROM users WHERE name < ") + u32::MIN; for _ in conn.rows(&sql).unwrap() { unreachable!(); } let sql = prep!("SELECT age FROM users WHERE name > ") + u32::MIN; for _ in conn.rows(&sql).unwrap() { cnt += 1; } assert_eq!(cnt, 12); } }