Skip to content

Commit 9bad3d6

Browse files
aldrickdevclaudena-ji
authored
[sqlserver] Fix procedure_name missing from query metrics (DataDog#22731)
* [sqlserver] Fix procedure_name missing from query metrics when monitoring user lacks CONNECT on user database `OBJECT_NAME(sproc_object_id, dbid)` returns NULL when the Datadog monitoring user does not have a CONNECT grant on the user database, causing `is_proc` to evaluate to False and silently dropping the procedure_name tag from all query metrics rows. Fix by using `sproc_object_id` — which is populated by the LEFT JOIN to `sys.dm_exec_procedure_stats` regardless of DB-level permissions — as a permission-independent signal that a statement belongs to a stored procedure. When `OBJECT_NAME()` returns NULL, fall back to extracting the procedure name from the obfuscated procedure text via the obfuscator metadata. This restores the behaviour that existed prior to 7.66.0 and also fixes the same gap for the `NO_AGGREGATES` query path. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * [sqlserver] add unit test to prevent anymore regression on missing procedure name * add changelog entry for the fix --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Naji Astier <naji.astier@datadoghq.com>
1 parent cd0388c commit 9bad3d6

3 files changed

Lines changed: 97 additions & 3 deletions

File tree

sqlserver/changelog.d/22731.fixed

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix `procedure_name` tag missing from query metrics when the `CONNECT` permission hasn't been granted to the datadog user.

sqlserver/datadog_checks/sqlserver/statements.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -421,7 +421,8 @@ def _normalize_queries(self, rows):
421421
procedure_signature = None
422422
procedure_content = None
423423
row['is_proc'] = bool(row.get('procedure_name'))
424-
if (row['is_proc'] and row['text']) or self.disable_secondary_tags:
424+
has_sproc_context = row['is_proc'] or bool(row.get('sproc_object_id'))
425+
if (has_sproc_context and row['text']) or self.disable_secondary_tags:
425426
try:
426427
procedure_statement = obfuscate_sql_with_metadata(
427428
row['text'], self._config.obfuscator_options, replace_null_character=True
@@ -431,8 +432,7 @@ def _normalize_queries(self, rows):
431432
procedure_comments = procedure_statement['metadata'].get('comments', [])
432433
if procedure_comments:
433434
comments = list(set(comments + procedure_comments))
434-
if self.disable_secondary_tags and not row.get('procedure_name'):
435-
# Extract procedure name from the statement text when disable_secondary_tags is enabled
435+
if not row.get('procedure_name'):
436436
procedures = procedure_statement['metadata'].get('procedures')
437437
if procedures:
438438
row['procedure_name'] = procedures[0].lower()

sqlserver/tests/test_statements.py

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1139,6 +1139,99 @@ def test_metrics_lookback_window_config(instance_docker):
11391139
mock_cursor.execute.assert_called_with(ANY, (86400,))
11401140

11411141

1142+
@pytest.mark.unit
1143+
@pytest.mark.parametrize(
1144+
"procedure_name,sproc_object_id,expected_is_proc,expected_procedure_name",
1145+
[
1146+
pytest.param(
1147+
None,
1148+
12345,
1149+
True,
1150+
"myproc",
1151+
id="no_procedure_name_with_sproc_object_id_extracts_from_metadata",
1152+
),
1153+
pytest.param(
1154+
"myproc",
1155+
12345,
1156+
True,
1157+
"dbo.myproc",
1158+
id="procedure_name_present_with_sproc_object_id_uses_original",
1159+
),
1160+
pytest.param(
1161+
None,
1162+
None,
1163+
False,
1164+
None,
1165+
id="no_procedure_name_no_sproc_object_id_not_a_proc",
1166+
),
1167+
pytest.param(
1168+
None,
1169+
0,
1170+
False,
1171+
None,
1172+
id="no_procedure_name_zero_sproc_object_id_not_a_proc",
1173+
),
1174+
],
1175+
)
1176+
def test_normalize_queries_procedure_name_fallback(
1177+
instance_docker, datadog_agent, procedure_name, sproc_object_id, expected_is_proc, expected_procedure_name
1178+
):
1179+
"""Test that _normalize_queries extracts procedure_name from obfuscator metadata
1180+
when OBJECT_NAME() returns NULL (procedure_name is None) but sproc_object_id is set,
1181+
which happens when the monitoring user lacks CONNECT on the user database."""
1182+
instance_docker['dbm'] = True
1183+
instance_docker['query_metrics'] = {'enabled': True, 'run_sync': True, 'collection_interval': 0.1}
1184+
check = SQLServer(CHECK_NAME, {}, [instance_docker])
1185+
1186+
statement_text = "SELECT * FROM ϑings WHERE id = @P1"
1187+
procedure_text = "CREATE PROCEDURE dbo.myProc AS BEGIN SELECT * FROM ϑings WHERE id = @P1 END;"
1188+
1189+
def _obfuscate_sql(sql_query, options=None):
1190+
return json.dumps(
1191+
{
1192+
'query': sql_query,
1193+
'metadata': {
1194+
'tables_csv': 'ϑings',
1195+
'commands': ['SELECT'],
1196+
'comments': [],
1197+
'procedures': ['myProc'],
1198+
},
1199+
}
1200+
)
1201+
1202+
row = {
1203+
'statement_text': statement_text,
1204+
'text': procedure_text,
1205+
'procedure_name': procedure_name,
1206+
'schema_name': 'dbo' if procedure_name else None,
1207+
'sproc_object_id': sproc_object_id,
1208+
'query_hash': b'\x01\x02\x03\x04',
1209+
'query_plan_hash': b'\x05\x06\x07\x08',
1210+
'plan_handle': b'\x09\x0a\x0b\x0c',
1211+
'execution_count': 1,
1212+
'total_worker_time': 100,
1213+
}
1214+
1215+
with mock.patch.object(datadog_agent, 'obfuscate_sql', passthrough=True) as mock_agent:
1216+
mock_agent.side_effect = _obfuscate_sql
1217+
result = check.statement_metrics._normalize_queries([row])
1218+
1219+
assert len(result) == 1
1220+
result_row = result[0]
1221+
assert result_row['is_proc'] is expected_is_proc
1222+
if expected_procedure_name:
1223+
assert result_row['procedure_name'] == expected_procedure_name
1224+
else:
1225+
assert not result_row.get('procedure_name')
1226+
1227+
if expected_is_proc:
1228+
assert result_row.get('procedure_signature'), "should have a procedure signature"
1229+
assert result_row.get('procedure_text'), "should have obfuscated procedure text"
1230+
else:
1231+
assert not result_row.get('procedure_signature')
1232+
assert not result_row.get('procedure_text')
1233+
1234+
11421235
@pytest.mark.flaky
11431236
@pytest.mark.integration
11441237
@pytest.mark.usefixtures('dd_environment')

0 commit comments

Comments
 (0)