Skip to content

Commit e374c4a

Browse files
authored
Merge pull request #29 from microsoft/dev/tkotian/sync-ado-main-to-github-main
2 parents ffa9d6a + 9db5d23 commit e374c4a

22 files changed

Lines changed: 1615 additions & 148 deletions
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# Pipeline to create a PR from development to main after a PR merges into development.
2+
# Can also be triggered manually. Skips PR creation if:
3+
# - A PR from development to main already exists
4+
# - There are no changes between development and main
5+
6+
trigger: none
7+
8+
pr: none
9+
10+
resources:
11+
repositories:
12+
- repository: self
13+
type: git
14+
ref: development
15+
16+
pool:
17+
vmImage: ubuntu-latest
18+
19+
steps:
20+
- checkout: self
21+
fetchDepth: 0
22+
persistCredentials: true
23+
24+
- script: |
25+
git fetch origin main development
26+
if git diff --quiet origin/main..origin/development; then
27+
echo "No changes between development and main. Skipping."
28+
echo "##vso[task.setvariable variable=hasChanges]false"
29+
else
30+
echo "Changes detected between development and main."
31+
echo "##vso[task.setvariable variable=hasChanges]true"
32+
fi
33+
displayName: Check for changes
34+
35+
- script: |
36+
PR_EXISTS=$(az repos pr list \
37+
--organization "$(System.TeamFoundationCollectionUri)" \
38+
--project "$(System.TeamProject)" \
39+
--repository "$(Build.Repository.Name)" \
40+
--source-branch development \
41+
--target-branch main \
42+
--status active \
43+
--query "[0].pullRequestId" \
44+
--output tsv)
45+
46+
if [ -n "$PR_EXISTS" ]; then
47+
echo "Active PR #$PR_EXISTS already exists. Skipping."
48+
echo "##vso[task.setvariable variable=prExists]true"
49+
else
50+
echo "No active PR found."
51+
echo "##vso[task.setvariable variable=prExists]false"
52+
fi
53+
displayName: Check for existing PR
54+
condition: eq(variables['hasChanges'], 'true')
55+
env:
56+
AZURE_DEVOPS_EXT_PAT: $(System.AccessToken)
57+
58+
- script: |
59+
az repos pr create \
60+
--organization "$(System.TeamFoundationCollectionUri)" \
61+
--project "$(System.TeamProject)" \
62+
--repository "$(Build.Repository.Name)" \
63+
--source-branch development \
64+
--target-branch main \
65+
--title "Sync development to main" \
66+
--description $'Sync of development to main branch.\n\nBefore merging this PR, ensure that the CI has had a good run on the development branch.'
67+
displayName: Create PR
68+
condition: and(eq(variables['hasChanges'], 'true'), eq(variables['prExists'], 'false'))
69+
env:
70+
AZURE_DEVOPS_EXT_PAT: $(System.AccessToken)

.pipeline/templates/build-template.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ steps:
121121
env:
122122
SQL_PASSWORD: $(SQL_PASSWORD)
123123
RUST_BACKTRACE: full
124+
SSPI_TEST: '1'
124125
125126
- task: PublishTestResults@2
126127
condition: and(succeededOrFailed(), eq(variables['Build.Reason'], 'PullRequest'))

.pipeline/templates/sql-setup-template.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,19 @@ steps:
3131
Restart-Service -Name 'MSSQLSERVER' -Force
3232
sqlcmd -Q "ALTER SERVER ROLE sysadmin ADD MEMBER testuser;"
3333
sqlcmd -S localhost,1433 -U sa -P $(SQL_PASSWORD) -Q "SELECT @@VERSION"
34+
35+
# Setup the same logins on the SQLDEV named instance (used by SSRP tests)
36+
$svc = Get-Service -Name 'MSSQL$SQLDEV' -ErrorAction SilentlyContinue
37+
if ($svc -and $svc.Status -eq 'Running') {
38+
echo "Configuring logins on SQLDEV instance..."
39+
sqlcmd -S "localhost\SQLDEV" -C -Q "EXEC xp_instance_regwrite N'HKEY_LOCAL_MACHINE', N'Software\Microsoft\MSSQLServer\MSSQLServer', N'LoginMode', REG_DWORD, 2"
40+
sqlcmd -S "localhost\SQLDEV" -C -Q "ALTER LOGIN sa ENABLE; ALTER LOGIN sa WITH CHECK_POLICY = OFF, CHECK_EXPIRATION = OFF; ALTER LOGIN sa WITH PASSWORD = '$(SQL_PASSWORD)'; IF NOT EXISTS (SELECT 1 FROM sys.server_principals WHERE name = 'testuser') CREATE LOGIN testuser WITH PASSWORD = '$(SQL_PASSWORD)', CHECK_POLICY = OFF, CHECK_EXPIRATION = OFF; ALTER SERVER ROLE sysadmin ADD MEMBER testuser;"
41+
Restart-Service -Name 'MSSQL$SQLDEV' -Force
42+
sqlcmd -S "localhost\SQLDEV" -C -U sa -P "$(SQL_PASSWORD)" -Q "SELECT @@VERSION"
43+
echo "SQLDEV instance configured."
44+
} else {
45+
echo "SQLDEV instance not found or not running — skipping."
46+
}
3447
env:
3548
SQL_PASSWORD: $(SQL_PASSWORD)
3649

mssql-mock-tds/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ pub mod tds_tls_wrapper;
2626
pub mod tls_helper;
2727

2828
pub use query_response::{
29-
ColumnDefinition, ColumnValue, QueryRegistry, QueryResponse, Row, SqlDataType,
29+
ColumnDefinition, ColumnValue, InfoMessage, QueryRegistry, QueryResponse, Row, SqlDataType,
3030
};
3131
pub use server::{ConnectionInfo, ConnectionStore, MockTdsServer, RedirectionConfig};
3232
pub use tds_tls_wrapper::TdsTlsWrapper;

mssql-mock-tds/src/protocol.rs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -614,6 +614,54 @@ pub fn build_done_token(row_count: u64) -> BytesMut {
614614
token_data
615615
}
616616

617+
/// Build an INFO token (0xAB) matching the TDS wire format.
618+
///
619+
/// Wire layout (all integers little-endian):
620+
/// 0xAB | u16 length | u32 number | u8 state | u8 severity
621+
/// | US_VARCHAR message | B_VARCHAR server_name | B_VARCHAR proc_name | u32 line_number
622+
///
623+
/// US_VARCHAR: u16 char-count prefix + UTF-16LE data
624+
/// B_VARCHAR: u8 char-count prefix + UTF-16LE data
625+
pub fn build_info_token(info: &crate::query_response::InfoMessage) -> BytesMut {
626+
let mut buf = BytesMut::new();
627+
628+
buf.put_u8(TokenType::Info as u8);
629+
630+
let msg_units: Vec<u16> = info.message.encode_utf16().collect();
631+
let srv_units: Vec<u16> = info.server_name.encode_utf16().collect();
632+
let proc_units: Vec<u16> = info.proc_name.encode_utf16().collect();
633+
634+
// token_len covers everything after the 2-byte length field
635+
let token_len: u16 = 4 + 1 + 1 // number, state, severity
636+
+ 2 + (msg_units.len() * 2) as u16 // US_VARCHAR
637+
+ 1 + (srv_units.len() * 2) as u16 // B_VARCHAR
638+
+ 1 + (proc_units.len() * 2) as u16 // B_VARCHAR
639+
+ 4; // line_number
640+
buf.put_u16_le(token_len);
641+
642+
buf.put_u32_le(info.number);
643+
buf.put_u8(info.state);
644+
buf.put_u8(info.severity);
645+
646+
buf.put_u16_le(msg_units.len() as u16);
647+
for u in &msg_units {
648+
buf.put_u16_le(*u);
649+
}
650+
651+
buf.put_u8(srv_units.len() as u8);
652+
for u in &srv_units {
653+
buf.put_u16_le(*u);
654+
}
655+
656+
buf.put_u8(proc_units.len() as u8);
657+
for u in &proc_units {
658+
buf.put_u16_le(*u);
659+
}
660+
661+
buf.put_u32_le(info.line_number);
662+
buf
663+
}
664+
617665
/// Build a query result from a QueryResponse
618666
pub fn build_query_result(response: &crate::query_response::QueryResponse) -> BytesMut {
619667
let mut result = BytesMut::new();
@@ -644,6 +692,11 @@ pub fn build_query_result(response: &crate::query_response::QueryResponse) -> By
644692
}
645693
}
646694

695+
// Inject Info tokens between ColMetadata and Rows (Fabric DW behavior)
696+
for info in &response.info_tokens {
697+
result.extend_from_slice(&build_info_token(info));
698+
}
699+
647700
// Serialize each row
648701
for row in &response.rows {
649702
result.put_u8(TokenType::Row as u8);

mssql-mock-tds/src/query_response.rs

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,24 +133,64 @@ impl Row {
133133
}
134134
}
135135

136+
/// An informational message injected into the result stream.
137+
///
138+
/// Fabric DW injects these between ColMetadata and Row tokens with tracing
139+
/// metadata (Statement ID, Query hash, Distributed request ID).
140+
#[derive(Debug, Clone)]
141+
pub struct InfoMessage {
142+
pub number: u32,
143+
pub state: u8,
144+
pub severity: u8,
145+
pub message: String,
146+
pub server_name: String,
147+
pub proc_name: String,
148+
pub line_number: u32,
149+
}
150+
151+
impl InfoMessage {
152+
pub fn new(number: u32, severity: u8, message: impl Into<String>) -> Self {
153+
Self {
154+
number,
155+
state: 1,
156+
severity,
157+
message: message.into(),
158+
server_name: String::new(),
159+
proc_name: String::new(),
160+
line_number: 0,
161+
}
162+
}
163+
}
164+
136165
/// A complete query response definition
137166
#[derive(Debug, Clone)]
138167
pub struct QueryResponse {
139168
pub columns: Vec<ColumnDefinition>,
140169
pub rows: Vec<Row>,
170+
pub info_tokens: Vec<InfoMessage>,
141171
}
142172

143173
impl QueryResponse {
144174
/// Create a new query response
145175
pub fn new(columns: Vec<ColumnDefinition>, rows: Vec<Row>) -> Self {
146-
Self { columns, rows }
176+
Self {
177+
columns,
178+
rows,
179+
info_tokens: Vec::new(),
180+
}
181+
}
182+
183+
pub fn with_info_tokens(mut self, info_tokens: Vec<InfoMessage>) -> Self {
184+
self.info_tokens = info_tokens;
185+
self
147186
}
148187

149188
/// Helper to create a response for SELECT 1
150189
pub fn select_one() -> Self {
151190
Self {
152191
columns: vec![ColumnDefinition::new("", SqlDataType::Int)],
153192
rows: vec![Row::new(vec![ColumnValue::Int(1)])],
193+
info_tokens: Vec::new(),
154194
}
155195
}
156196

@@ -167,6 +207,7 @@ impl QueryResponse {
167207
ColumnValue::Int(2),
168208
ColumnValue::Int(3),
169209
])],
210+
info_tokens: Vec::new(),
170211
}
171212
}
172213
}

mssql-mock-tds/src/server.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ pub struct ConnectionProcessor {
5555
received_token: Option<Vec<u8>>,
5656
/// User Agent received during authentication (if any)
5757
pub user_agent: Option<String>,
58+
/// ServerName received in the Login7 packet
59+
received_server_name: Option<String>,
5860
/// Reference to the shared query registry
5961
query_registry: Arc<Mutex<QueryRegistry>>,
6062
/// Packet buffer for this connection
@@ -71,6 +73,7 @@ impl ConnectionProcessor {
7173
is_authenticated: false,
7274
received_token: None,
7375
user_agent: None,
76+
received_server_name: None,
7477
query_registry,
7578
buffer: BytesMut::with_capacity(4096),
7679
redirection: None,
@@ -88,6 +91,7 @@ impl ConnectionProcessor {
8891
is_authenticated: false,
8992
received_token: None,
9093
user_agent: None,
94+
received_server_name: None,
9195
query_registry,
9296
buffer: BytesMut::with_capacity(4096),
9397
redirection,
@@ -109,6 +113,11 @@ impl ConnectionProcessor {
109113
self.received_token.as_deref()
110114
}
111115

116+
/// Get the ServerName received in the Login7 packet
117+
pub fn received_server_name(&self) -> Option<&str> {
118+
self.received_server_name.as_deref()
119+
}
120+
112121
/// Get the received access token as a UTF-16LE decoded string
113122
pub fn received_token_as_string(&self) -> Option<String> {
114123
self.received_token.as_ref().and_then(|bytes| {
@@ -175,13 +184,16 @@ impl ConnectionProcessor {
175184
// Log the server name sent by client (important for verifying redirection behavior)
176185
if let Some(ref server_name) = auth_info.server_name {
177186
info!(
178-
"Login7 from {}: client sent ServerName='{}'",
187+
"Login7 from {}: client sent ServerName={:?}",
179188
self.addr, server_name
180189
);
181190
} else {
182191
info!("Login7 from {}: no ServerName in packet", self.addr);
183192
}
184193

194+
// Store the server name for test verification
195+
self.received_server_name = auth_info.server_name.clone();
196+
185197
// FedAuth is always supported - check if client used it
186198
if auth_info.has_fedauth {
187199
let token_len = auth_info
@@ -336,6 +348,8 @@ pub struct ConnectionInfo {
336348
pub authenticated: bool,
337349
/// User Agent received during authentication (if any)
338350
pub user_agent: Option<String>,
351+
/// ServerName received in the Login7 packet
352+
pub received_server_name: Option<String>,
339353
}
340354

341355
impl ConnectionInfo {
@@ -373,6 +387,7 @@ impl ConnectionStore {
373387
received_token: processor.received_token().map(|t| t.to_vec()),
374388
authenticated: processor.is_authenticated(),
375389
user_agent: processor.user_agent.clone(),
390+
received_server_name: processor.received_server_name().map(|s| s.to_string()),
376391
};
377392
self.connections.insert(processor.addr(), info);
378393
}

mssql-py-core/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "mssql-py-core"
3-
version = "0.1.3"
3+
version = "0.1.4"
44
edition = "2024"
55
publish = false
66

mssql-tds/src/connection.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ pub mod client_context;
1515
pub(crate) mod connection_actions;
1616
pub(crate) mod datasource_parser;
1717
pub(crate) mod execution_context;
18+
pub(crate) mod instance_cache;
1819
pub(crate) mod metadata_retriever;
1920
/// ODBC-style authentication keyword transform.
2021
pub mod odbc_authentication_transformer;

0 commit comments

Comments
 (0)