mod common;
use common::{check, check_shape_only, check_without_engine};
use keelson_mysql as mysql;
use keelson_mysql::{
Chain, Query, Value, arg, args, insert, quote, raw, replace, s, select, values_of,
};
#[test]
fn insert_into_with_a_column_list_and_one_row() {
let q = mysql::insert((
insert::into(quote("tags")).columns(["id", "name"]),
insert::values((arg(1i32), arg("rust"))),
));
let args = check(&q, "INSERT INTO `tags` (`id`, `name`) VALUES (?, ?)");
assert_eq!(args, vec![Value::I32(1), Value::Text("rust".into())]);
}
#[test]
fn several_values_mods_become_several_rows_under_one_keyword() {
let q = mysql::insert((
insert::into(quote("tags")).columns(["id", "name"]),
insert::values((arg(1i32), arg("rust"))),
insert::values((arg(2i32), arg("sql"))),
));
let args = check(
&q,
"INSERT INTO `tags` (`id`, `name`) VALUES (?, ?), (?, ?)",
);
assert_eq!(args.len(), 4);
assert_eq!(args[2], Value::I32(2));
}
#[test]
fn rows_adds_the_same_rows_in_one_mod() {
let q = mysql::insert((
insert::into(quote("tags")).columns(["id", "name"]),
insert::rows([(arg(1i32), arg("a")), (arg(2i32), arg("b"))]),
));
let args = check(
&q,
"INSERT INTO `tags` (`id`, `name`) VALUES (?, ?), (?, ?)",
);
assert_eq!(args.len(), 4);
}
#[test]
fn args_fills_a_whole_row_from_one_iterator() {
let q = mysql::insert((
insert::into(quote("tags")).columns(["id", "name"]),
insert::values(args([1i32, 2])),
));
let args = check(&q, "INSERT INTO `tags` (`id`, `name`) VALUES (?, ?)");
assert_eq!(args, vec![Value::I32(1), Value::I32(2)]);
}
#[test]
fn a_default_cell_is_raw_sql() {
let q = mysql::insert((
insert::into(quote("posts")).columns(["id", "user_id", "title", "views"]),
insert::values((arg(1i32), arg(2i32), arg("hello"), raw("DEFAULT"))),
));
let args = check(
&q,
"INSERT INTO `posts` (`id`, `user_id`, `title`, `views`) VALUES (?, ?, ?, DEFAULT)",
);
assert_eq!(args.len(), 3);
}
#[test]
fn an_insert_with_no_row_source_takes_every_default() {
let q = mysql::insert(insert::into(quote("users")));
assert!(check(&q, "INSERT INTO `users` VALUES ()").is_empty());
}
#[test]
fn the_set_row_source() {
let q = mysql::insert((
insert::into(quote("tags")),
insert::set_col("id").to_arg(1i32),
insert::set_col("name").to_arg("rust"),
));
let args = check(&q, "INSERT INTO `tags` SET `id` = ?, `name` = ?");
assert_eq!(args, vec![Value::I32(1), Value::Text("rust".into())]);
}
#[test]
fn set_wins_over_values_and_takes_the_column_list_with_it() {
let q = mysql::insert((
insert::into(quote("tags")).columns(["id"]),
insert::values(arg(1i32)),
insert::set_col("name").to_arg("rust"),
));
let args = check(&q, "INSERT INTO `tags` SET `name` = ?");
assert_eq!(args, vec![Value::Text("rust".into())]);
}
#[test]
fn insert_from_a_select() {
let source = mysql::select((
select::columns((quote("id"), quote("title"))),
select::from(quote("posts")),
select::where_(quote("views").gt(arg(100i32))),
));
let q = mysql::insert((
insert::into(quote("tags")).columns(["id", "name"]),
insert::query(source),
));
let args = check(
&q,
"INSERT INTO `tags` (`id`, `name`) \
SELECT `id`, `title` FROM `posts` WHERE (`views` > ?)",
);
assert_eq!(args, vec![Value::I32(100)]);
}
#[test]
fn a_cte_for_an_insert_select_belongs_to_the_select() {
let source = mysql::select((
select::with(
"popular",
mysql::select((
select::columns((quote("id"), quote("title"))),
select::from(quote("posts")),
select::where_(quote("views").gt(arg(100i32))),
)),
),
select::columns((quote("id"), quote("title"))),
select::from(quote("popular")),
));
let q = mysql::insert((
insert::into(quote("tags")).columns(["id", "name"]),
insert::query(source),
));
check(
&q,
"INSERT INTO `tags` (`id`, `name`) \
WITH `popular` AS (SELECT `id`, `title` FROM `posts` WHERE (`views` > ?)) \
SELECT `id`, `title` FROM `popular`",
);
}
#[test]
fn a_query_row_source_replaces_any_rows_already_added() {
let source = mysql::select((select::columns(quote("id")), select::from(quote("users"))));
let q = mysql::insert((
insert::into(quote("tags")).columns(["id"]),
insert::values(arg(1i32)),
insert::query(source),
));
let args = check(&q, "INSERT INTO `tags` (`id`) SELECT `id` FROM `users`");
assert!(args.is_empty(), "the replaced row's argument went with it");
}
#[test]
fn insert_ignore_follows_the_priority_keyword() {
let q = mysql::insert((
insert::ignore(),
insert::high_priority(),
insert::into(quote("tags")).columns(["id", "name"]),
insert::values((arg(1i32), arg("rust"))),
));
check(
&q,
"INSERT HIGH_PRIORITY IGNORE INTO `tags` (`id`, `name`) VALUES (?, ?)",
);
}
#[test]
fn insert_ignore_on_its_own() {
let q = mysql::insert((
insert::ignore(),
insert::into(quote("tags")).columns(["id", "name"]),
insert::values((arg(1i32), arg("rust"))),
));
check(&q, "INSERT IGNORE INTO `tags` (`id`, `name`) VALUES (?, ?)");
}
#[test]
fn insert_low_priority() {
let q = mysql::insert((
insert::low_priority(),
insert::into(quote("tags")).columns(["id"]),
insert::values(arg(1i32)),
));
check(&q, "INSERT LOW_PRIORITY INTO `tags` (`id`) VALUES (?)");
}
#[test]
fn insert_delayed_is_still_in_the_grammar() {
let q = mysql::insert((
insert::delayed(),
insert::into(quote("tags")).columns(["id"]),
insert::values(arg(1i32)),
));
check(&q, "INSERT DELAYED INTO `tags` (`id`) VALUES (?)");
}
#[test]
fn an_optimizer_hint_sits_between_insert_and_into() {
let q = mysql::insert((
insert::max_execution_time(1000),
insert::set_var("cte_max_recursion_depth = 1M"),
insert::into(quote("tags")).columns(["id"]),
insert::values(arg(1i32)),
));
check(
&q,
"INSERT /*+ MAX_EXECUTION_TIME(1000) SET_VAR(cte_max_recursion_depth = 1M) */ \
INTO `tags` (`id`) VALUES (?)",
);
}
#[test]
fn a_partition_list_follows_the_insert_target() {
let q = mysql::insert((
insert::into(quote("tags")).partition(["p0"]),
insert::values((arg(1i32), arg("rust"))),
));
check_without_engine(
&q,
"INSERT INTO `tags` PARTITION (`p0`) VALUES (?, ?)",
"the shared schema has no partitioned table and cannot have one",
);
}
#[test]
fn a_partition_list_precedes_the_insert_column_list() {
let q = mysql::insert((
insert::into(quote("tags"))
.partition(["p0"])
.columns(["id"]),
insert::values(arg(1i32)),
));
check_shape_only(
&q,
"INSERT INTO `tags` PARTITION (`p0`) (`id`) VALUES (?)",
"PARTITION followed by a column list on INSERT",
"the shared schema has no partitioned table and cannot have one",
);
}
#[test]
fn on_duplicate_key_update_with_the_values_function() {
let q = mysql::insert((
insert::into(quote("tags")).columns(["id", "name"]),
insert::values((arg(1i32), arg("rust"))),
insert::on_duplicate_key_update(insert::set_values(["name"])),
));
let args = check(
&q,
"INSERT INTO `tags` (`id`, `name`) VALUES (?, ?) \
ON DUPLICATE KEY UPDATE `name` = VALUES(`name`)",
);
assert_eq!(args.len(), 2);
}
#[test]
fn on_duplicate_key_update_through_a_row_alias() {
let q = mysql::insert((
insert::into(quote("tags")).columns(["id", "name"]),
insert::values((arg(1i32), arg("rust"))),
insert::as_("new"),
insert::on_duplicate_key_update(insert::set_row("new", ["name"])),
));
check(
&q,
"INSERT INTO `tags` (`id`, `name`) VALUES (?, ?) AS `new` \
ON DUPLICATE KEY UPDATE `name` = `new`.`name`",
);
}
#[test]
fn a_row_alias_may_rename_the_columns_too() {
let q = mysql::insert((
insert::into(quote("tags")).columns(["id", "name"]),
insert::values((arg(1i32), arg("rust"))),
insert::as_("new").columns(["new_id", "new_name"]),
insert::on_duplicate_key_update(insert::set_col("name").to(quote(("new", "new_name")))),
));
check(
&q,
"INSERT INTO `tags` (`id`, `name`) VALUES (?, ?) AS `new` (`new_id`, `new_name`) \
ON DUPLICATE KEY UPDATE `name` = `new`.`new_name`",
);
}
#[test]
fn an_upsert_assignment_may_be_any_expression() {
let q = mysql::insert((
insert::into(quote("posts")).columns(["id", "user_id", "title", "views"]),
insert::values((arg(1i32), arg(2i32), arg("hello"), arg(0i32))),
insert::on_duplicate_key_update((
insert::set_col("views").to(quote("views").plus(values_of("views"))),
insert::set_col("title").to_arg("updated"),
)),
));
let args = check(
&q,
"INSERT INTO `posts` (`id`, `user_id`, `title`, `views`) VALUES (?, ?, ?, ?) \
ON DUPLICATE KEY UPDATE `views` = (`views` + VALUES(`views`)), `title` = ?",
);
assert_eq!(args.len(), 5);
assert_eq!(args[4], Value::Text("updated".into()));
}
#[test]
fn an_upsert_on_top_of_an_insert_select() {
let source = mysql::select((
select::columns((quote("id"), quote("title"))),
select::from(quote("posts")),
));
let q = mysql::insert((
insert::into(quote("tags")).columns(["id", "name"]),
insert::query(source),
insert::on_duplicate_key_update(insert::set_col("name").to(s("clash"))),
));
check(
&q,
"INSERT INTO `tags` (`id`, `name`) SELECT `id`, `title` FROM `posts` \
ON DUPLICATE KEY UPDATE `name` = 'clash'",
);
}
#[test]
fn an_insert_with_no_table_is_a_recorded_failure() {
let err = mysql::insert(insert::values(arg(1i32)))
.build()
.unwrap_err();
assert!(
matches!(&err, mysql::Error::Incomplete(what) if what.contains("INSERT")),
"got: {err}"
);
}
#[test]
fn replace_into_with_a_column_list_and_rows() {
let q = mysql::replace((
replace::into(quote("tags")).columns(["id", "name"]),
replace::values((arg(1i32), arg("rust"))),
replace::values((arg(2i32), arg("sql"))),
));
let args = check(
&q,
"REPLACE INTO `tags` (`id`, `name`) VALUES (?, ?), (?, ?)",
);
assert_eq!(args.len(), 4);
}
#[test]
fn replace_the_set_row_source() {
let q = mysql::replace((
replace::into(quote("tags")),
replace::set_col("id").to_arg(1i32),
replace::set_col("name").to_arg("rust"),
));
let args = check(&q, "REPLACE INTO `tags` SET `id` = ?, `name` = ?");
assert_eq!(args, vec![Value::I32(1), Value::Text("rust".into())]);
}
#[test]
fn replace_set_wins_over_values_and_takes_the_column_list_with_it() {
let q = mysql::replace((
replace::into(quote("tags")).columns(["id"]),
replace::values(arg(9i32)),
replace::set_col("name").to_arg("rust"),
));
let args = check(&q, "REPLACE INTO `tags` SET `name` = ?");
assert_eq!(args, vec![Value::Text("rust".into())]);
}
#[test]
fn replace_low_priority_with_the_set_row_source() {
let q = mysql::replace((
replace::low_priority(),
replace::into(quote("tags")),
replace::set_col("id").to_arg(1i32),
replace::set_col("name").to_arg("rust"),
));
check(
&q,
"REPLACE LOW_PRIORITY INTO `tags` SET `id` = ?, `name` = ?",
);
}
#[test]
fn replace_delayed() {
let q = mysql::replace((
replace::delayed(),
replace::into(quote("tags")).columns(["id"]),
replace::values(arg(1i32)),
));
check(&q, "REPLACE DELAYED INTO `tags` (`id`) VALUES (?)");
}
#[test]
fn replace_from_a_select() {
let source = mysql::select((
select::columns((quote("id"), quote("title"))),
select::from(quote("posts")),
));
let q = mysql::replace((
replace::into(quote("tags")).columns(["id", "name"]),
replace::query(source),
));
check(
&q,
"REPLACE INTO `tags` (`id`, `name`) SELECT `id`, `title` FROM `posts`",
);
}
#[test]
fn replace_with_an_optimizer_hint() {
let q = mysql::replace((
replace::optimizer_hint("SET_VAR(foreign_key_checks = OFF)"),
replace::into(quote("tags")).columns(["id"]),
replace::values(arg(1i32)),
));
check(
&q,
"REPLACE /*+ SET_VAR(foreign_key_checks = OFF) */ INTO `tags` (`id`) VALUES (?)",
);
}
#[test]
fn a_replace_with_no_row_source_takes_every_default() {
let q = mysql::replace(replace::into(quote("users")));
check(&q, "REPLACE INTO `users` VALUES ()");
}
#[test]
fn a_replace_with_no_table_is_a_recorded_failure() {
let err = mysql::replace(replace::values(arg(1i32)))
.build()
.unwrap_err();
assert!(
matches!(&err, mysql::Error::Incomplete(what) if what.contains("REPLACE")),
"got: {err}"
);
}