Skip to content

Commit 2e4495f

Browse files
committed
fix #11
1 parent 8785955 commit 2e4495f

File tree

2 files changed

+210
-1
lines changed

2 files changed

+210
-1
lines changed

sqlx-core/src/mssql/arguments.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use crate::mssql::database::Mssql;
44
use crate::mssql::io::MssqlBufMutExt;
55
use crate::mssql::protocol::rpc::StatusFlags;
66
use crate::types::Type;
7+
use std::fmt::{self, Write};
78

89
#[derive(Default, Clone)]
910
pub struct MssqlArguments {
@@ -115,4 +116,55 @@ impl<'q> Arguments<'q> for MssqlArguments {
115116
{
116117
self.add(value)
117118
}
119+
120+
fn format_placeholder<W: Write>(&self, writer: &mut W) -> fmt::Result {
121+
// self.ordinal is incremented by the `MssqlArguments::add` method (the inherent one)
122+
// *before* this `format_placeholder` method is called by QueryBuilder.
123+
// So, `self.ordinal` correctly represents the number of the current parameter (e.g., 1 for @p1).
124+
writer.write_str("@p")?;
125+
writer.write_str(itoa::Buffer::new().format(self.ordinal))
126+
}
127+
}
128+
129+
#[cfg(test)]
130+
mod tests {
131+
use super::*;
132+
use crate::query_builder::QueryBuilder;
133+
134+
#[test]
135+
fn test_format_placeholder_method() {
136+
let mut args = MssqlArguments::default(); // ordinal = 0 initially
137+
let mut buffer = String::new();
138+
139+
// Simulate first bind operation sequence as done by QueryBuilder:
140+
// 1. QueryBuilder calls MssqlArguments::add (via trait)
141+
// 2. QueryBuilder calls MssqlArguments::format_placeholder (via trait)
142+
143+
// First bind:
144+
args.add(123i32); // This calls the inherent `MssqlArguments::add`, which increments ordinal to 1.
145+
args.format_placeholder(&mut buffer).unwrap(); // This should now use ordinal = 1.
146+
assert_eq!(buffer, "@p1");
147+
148+
buffer.clear();
149+
150+
// Second bind:
151+
args.add("test_val".to_string()); // Inherent `add` increments ordinal to 2.
152+
args.format_placeholder(&mut buffer).unwrap(); // This should use ordinal = 2.
153+
assert_eq!(buffer, "@p2");
154+
}
155+
156+
#[test]
157+
fn test_query_builder_with_mssql_placeholders() {
158+
// This test replicates the scenario from GitHub issue #11
159+
let id = 100;
160+
let mut builder = QueryBuilder::<Mssql>::new("SELECT * FROM table ");
161+
builder
162+
.push("WHERE id=")
163+
.push_bind(id)
164+
.push(" AND name=")
165+
.push_bind("test");
166+
let sql = builder.sql(); // Get the generated SQL string
167+
168+
assert_eq!(sql, "SELECT * FROM table WHERE id=@p1 AND name=@p2");
169+
}
118170
}

tests/mssql/mssql.rs

Lines changed: 158 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
use futures::TryStreamExt;
22
use sqlx_core::mssql::MssqlRow;
33
use sqlx_oldapi::mssql::{Mssql, MssqlPoolOptions};
4-
use sqlx_oldapi::{Column, Connection, Executor, MssqlConnection, Row, Statement, TypeInfo};
4+
use sqlx_oldapi::{
5+
Column, Connection, Execute, Executor, MssqlConnection, Row, Statement, TypeInfo,
6+
};
57
use sqlx_test::new;
68
use std::sync::atomic::{AtomicI32, Ordering};
79
use std::time::Duration;
@@ -501,3 +503,158 @@ async fn it_can_decode_tinyint_as_i16() -> anyhow::Result<()> {
501503

502504
Ok(())
503505
}
506+
507+
#[sqlx_macros::test]
508+
async fn it_works_with_query_builder() -> anyhow::Result<()> {
509+
let mut conn = new::<Mssql>().await?;
510+
511+
// Create a temporary table
512+
conn.execute(
513+
r#"
514+
CREATE TABLE #qb_test (
515+
id INT PRIMARY KEY,
516+
name NVARCHAR(50)
517+
);
518+
"#,
519+
)
520+
.await?;
521+
522+
// Insert data using QueryBuilder
523+
let mut insert_builder = sqlx_oldapi::QueryBuilder::new("INSERT INTO #qb_test (id, name) ");
524+
525+
#[derive(sqlx_oldapi::FromRow, Debug, PartialEq)]
526+
struct TestItem {
527+
id: i32,
528+
name: String,
529+
}
530+
531+
let items_to_insert = vec![
532+
TestItem {
533+
id: 1,
534+
name: "Alice".to_string(),
535+
},
536+
TestItem {
537+
id: 2,
538+
name: "Bob".to_string(),
539+
},
540+
TestItem {
541+
id: 3,
542+
name: "Charlie".to_string(),
543+
},
544+
];
545+
546+
insert_builder.push_values(items_to_insert.iter(), |mut b, item| {
547+
b.push_bind(item.id).push_bind(&item.name);
548+
});
549+
550+
let insert_query = insert_builder.build();
551+
eprintln!("Generated INSERT SQL: {}", insert_query.sql()); // Debug print
552+
conn.execute(insert_query).await?;
553+
554+
// Select data using QueryBuilder
555+
let mut select_builder =
556+
sqlx_oldapi::QueryBuilder::<'_, Mssql>::new("SELECT id, name FROM #qb_test WHERE id = ");
557+
select_builder.push_bind(2i32);
558+
let select_query = select_builder.build_query_as::<TestItem>();
559+
560+
let selected_item: TestItem = select_query.fetch_one(&mut conn).await?;
561+
562+
assert_eq!(selected_item.id, 2);
563+
assert_eq!(selected_item.name, "Bob");
564+
565+
// Select multiple items
566+
let mut select_all_builder = sqlx_oldapi::QueryBuilder::<'_, Mssql>::new(
567+
"SELECT id, name FROM #qb_test WHERE name LIKE ",
568+
);
569+
select_all_builder.push_bind("B%"); // Names starting with B
570+
let select_all_query = select_all_builder.build_query_as::<TestItem>();
571+
572+
let all_b_items: Vec<TestItem> = select_all_query.fetch_all(&mut conn).await?;
573+
assert_eq!(all_b_items.len(), 1);
574+
assert_eq!(all_b_items[0].id, 2);
575+
assert_eq!(all_b_items[0].name, "Bob");
576+
577+
conn.close().await?;
578+
Ok(())
579+
}
580+
581+
#[sqlx_macros::test]
582+
async fn it_executes_query_from_issue_11() -> anyhow::Result<()> {
583+
// https://github.com/sqlpage/sqlx-oldapi/issues/11
584+
let mut conn = new::<Mssql>().await?;
585+
586+
// Create a temporary table similar to the one in the issue
587+
conn.execute(
588+
r#"
589+
CREATE TABLE #temp_issue_table (
590+
id INT PRIMARY KEY,
591+
name NVARCHAR(50)
592+
);
593+
"#,
594+
)
595+
.await?;
596+
597+
// Insert some data
598+
let insert_id1 = 100;
599+
let insert_name1 = "test_user_1";
600+
let insert_id2 = 200;
601+
let insert_name2 = "test_user_2";
602+
603+
sqlx_oldapi::query("INSERT INTO #temp_issue_table (id, name) VALUES (@p1, @p2), (@p3, @p4)")
604+
.bind(insert_id1)
605+
.bind(insert_name1)
606+
.bind(insert_id2)
607+
.bind(insert_name2)
608+
.execute(&mut conn)
609+
.await?;
610+
611+
// Define a struct to map the query results
612+
#[derive(sqlx_oldapi::FromRow, Debug, PartialEq)]
613+
struct TableRow {
614+
id: i32,
615+
name: String,
616+
}
617+
618+
// Use QueryBuilder as in the issue report
619+
let id_to_select = insert_id1;
620+
let name_to_select = insert_name1;
621+
622+
let mut builder = sqlx_oldapi::QueryBuilder::new("SELECT id, name FROM #temp_issue_table ");
623+
builder
624+
.push("WHERE id=")
625+
.push_bind(id_to_select) // Bind the specific id we want to find
626+
.push(" AND name=")
627+
.push_bind(name_to_select); // Bind the specific name
628+
629+
let query = builder.build_query_as::<TableRow>();
630+
let sql = query.sql();
631+
eprintln!("Generated SQL for issue report test: {}", sql);
632+
assert_eq!(
633+
sql,
634+
"SELECT id, name FROM #temp_issue_table WHERE id=@p1 AND name=@p2"
635+
);
636+
637+
let selected_row: TableRow = query.fetch_one(&mut conn).await?;
638+
639+
assert_eq!(selected_row.id, id_to_select);
640+
assert_eq!(selected_row.name, name_to_select);
641+
642+
// Test selecting a non-existent row to ensure a different id/name fails as expected
643+
let mut builder_no_match =
644+
sqlx_oldapi::QueryBuilder::new("SELECT id, name FROM #temp_issue_table ");
645+
builder_no_match
646+
.push("WHERE id=")
647+
.push_bind(999) // Non-existent ID
648+
.push(" AND name=")
649+
.push_bind("no_such_user");
650+
651+
let query_no_match = builder_no_match.build_query_as::<TableRow>();
652+
let result_no_match = query_no_match.fetch_optional(&mut conn).await?;
653+
assert!(
654+
result_no_match.is_none(),
655+
"Query should not have found a match for non-existent data"
656+
);
657+
658+
conn.close().await?;
659+
Ok(())
660+
}

0 commit comments

Comments
 (0)