Skip to content

Commit 0bf4f41

Browse files
Fix flaky QCC integration test by mocking server response (#2866)
Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent f80224c commit 0bf4f41

2 files changed

Lines changed: 78 additions & 49 deletions

File tree

test/integ/test_connection.py

Lines changed: 0 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1280,55 +1280,6 @@ def test_disable_query_context_cache(conn_cnx) -> None:
12801280
assert conn.query_context_cache is None
12811281

12821282

1283-
@pytest.mark.aws
1284-
@pytest.mark.skipolddriver
1285-
def test_qcc_updated_on_failed_query_with_hybrid_table(conn_cnx) -> None:
1286-
"""SNOW-3010877: QCC must be merged even when a query fails.
1287-
1288-
Reproduces the exact hybrid table scenario from the bug report:
1289-
a duplicate-key INSERT inside a transaction fails, but the server
1290-
still sends queryContext in the error response. The client must
1291-
merge it so that subsequent queries on different GS nodes see the
1292-
updated sessionDPO / read snapshot watermark.
1293-
1294-
Note: full behavioural verification depends on the server returning
1295-
queryContext in error responses, which is gated behind a feature flag.
1296-
On accounts where the flag is off the QCC simply won't change, so the
1297-
>= assertion still passes. The unit tests with mocked responses are
1298-
the authoritative behavioural tests for this feature.
1299-
"""
1300-
with conn_cnx() as conn:
1301-
cur = conn.cursor()
1302-
table_name = f"test_qcc_hybrid_{random_string(5)}"
1303-
try:
1304-
cur.execute(
1305-
f"create or replace hybrid table {table_name} "
1306-
f"(pk text primary key, val text)"
1307-
)
1308-
1309-
# Q1 — successful insert
1310-
cur.execute(f"insert into {table_name} values ('k1', 'v1')")
1311-
qcc_after_success = len(conn.query_context_cache)
1312-
assert (
1313-
qcc_after_success > 0
1314-
), "QCC should have entries after successful query"
1315-
1316-
# Q2 — duplicate key insert, expected to fail
1317-
with pytest.raises(ProgrammingError):
1318-
cur.execute(f"insert into {table_name} values ('k1', 'v1')")
1319-
1320-
# The critical assertion: QCC must still have been updated
1321-
# despite the query failure. The entry count must not have
1322-
# decreased — it should stay the same or grow.
1323-
qcc_after_failure = len(conn.query_context_cache)
1324-
assert qcc_after_failure >= qcc_after_success, (
1325-
f"QCC should not shrink after a failed query: "
1326-
f"before={qcc_after_success}, after={qcc_after_failure}"
1327-
)
1328-
finally:
1329-
cur.execute(f"drop table if exists {table_name}")
1330-
1331-
13321283
@pytest.mark.aws
13331284
@pytest.mark.skipolddriver
13341285
def test_qcc_tracks_multi_database_hybrid_tables(conn_cnx) -> None:

test/unit/test_query_context_cache.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -516,3 +516,81 @@ def test_qcc_updated_regardless_of_query_success(success):
516516
entry_ids = {e.id for e in conn.query_context_cache._get_elements()}
517517
assert 1 in entry_ids, "Original entry should still be present"
518518
assert 2 in entry_ids, "New entry from server response should have been merged"
519+
520+
521+
def test_qcc_not_shrunk_after_failed_query_with_hybrid_table():
522+
"""SNOW-3010877: QCC must not shrink when a query on a hybrid table fails.
523+
524+
Simulates the hybrid table scenario: a successful INSERT populates
525+
the QCC with 2 entries (main + DPO), then a duplicate-key INSERT
526+
fails but the server still sends queryContext in the error response.
527+
The connector must merge it so the cache does not shrink.
528+
"""
529+
from snowflake.connector.connection import SnowflakeConnection
530+
531+
conn = MagicMock()
532+
conn._disable_query_context_cache = False
533+
conn.is_query_context_cache_disabled = False
534+
conn.query_context_cache = QueryContextCache(5)
535+
conn.query_context_cache_size = 5
536+
537+
conn.get_query_context = types.MethodType(
538+
SnowflakeConnection.get_query_context, conn
539+
)
540+
conn.set_query_context = types.MethodType(
541+
SnowflakeConnection.set_query_context, conn
542+
)
543+
544+
# Q1 — successful insert: server returns queryContext with 2 entries
545+
conn.rest.request.return_value = {
546+
"success": True,
547+
"data": {
548+
"queryId": "success-query-id",
549+
"queryContext": {
550+
"entries": [
551+
{"id": 0, "timestamp": 100, "priority": 0, "context": "main_ctx"},
552+
{"id": 1, "timestamp": 100, "priority": 1, "context": "ht_dpo_ctx"},
553+
]
554+
},
555+
},
556+
}
557+
SnowflakeConnection.cmd_query(
558+
conn, "INSERT INTO ht VALUES ('k1','v1')", 1, uuid.uuid4()
559+
)
560+
qcc_after_success = len(conn.query_context_cache)
561+
assert qcc_after_success == 2, "QCC should have 2 entries after successful query"
562+
563+
# Q2 — duplicate key insert fails, but server still sends queryContext
564+
conn.rest.request.return_value = {
565+
"success": False,
566+
"code": "100072",
567+
"message": "Duplicate key value violates unique constraint",
568+
"data": {
569+
"queryId": "failed-query-id",
570+
"queryContext": {
571+
"entries": [
572+
{
573+
"id": 0,
574+
"timestamp": 200,
575+
"priority": 0,
576+
"context": "main_ctx_v2",
577+
},
578+
{
579+
"id": 1,
580+
"timestamp": 200,
581+
"priority": 1,
582+
"context": "ht_dpo_ctx_v2",
583+
},
584+
]
585+
},
586+
},
587+
}
588+
SnowflakeConnection.cmd_query(
589+
conn, "INSERT INTO ht VALUES ('k1','v1')", 2, uuid.uuid4()
590+
)
591+
592+
qcc_after_failure = len(conn.query_context_cache)
593+
assert qcc_after_failure >= qcc_after_success, (
594+
f"QCC should not shrink after a failed query: "
595+
f"before={qcc_after_success}, after={qcc_after_failure}"
596+
)

0 commit comments

Comments
 (0)