fix: SIGSEGV in fillVgroupDataCxt when blob and non-blob tables share vgroup#35261
fix: SIGSEGV in fillVgroupDataCxt when blob and non-blob tables share vgroup#35261
Conversation
… vgroup Move aSubmitBlobData initialization out of the aSubmitTbData==NULL guard so it is lazily initialized when the first blob table is encountered, regardless of whether a non-blob table was processed first for the same vgroup. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Code Review
This pull request addresses a SIGSEGV in fillVgroupDataCxt by adjusting the initialization logic for aSubmitBlobData and adds a regression test. However, the current implementation introduces a critical logic error where aSubmitBlobData and aSubmitTbData can become misaligned. Feedback indicates that these arrays must maintain a 1-to-1 correspondence; therefore, aSubmitBlobData should be backfilled with NULL entries for existing tables upon lazy initialization, and the push logic must be updated to handle non-blob tables. Additionally, it is recommended that the new test case verify the actual content of blob columns to ensure data integrity.
| if (pTableCxt->hasBlob && NULL == pVgCxt->pData->aSubmitBlobData) { | ||
| pVgCxt->pData->aSubmitBlobData = taosArrayInit(128, sizeof(SBlobSet*)); | ||
| if (NULL == pVgCxt->pData->aSubmitBlobData) { | ||
| return terrno; | ||
| } | ||
| } |
There was a problem hiding this comment.
Moving the initialization of aSubmitBlobData out of the guard fixes the SIGSEGV, but it introduces a logic error regarding the synchronization between aSubmitTbData and aSubmitBlobData. These two arrays must maintain a 1-to-1 correspondence because they are iterated together using the same index in functions like insResetBlob (line 1132).
If aSubmitBlobData is initialized lazily, it will be missing entries for any non-blob tables already present in aSubmitTbData. Additionally, the current push logic (lines 561-567) only adds entries to aSubmitBlobData for tables where hasBlob is true. This means any non-blob tables added after aSubmitBlobData is initialized will also cause the arrays to go out of sync.
To fix this properly, you should:
- Fill
aSubmitBlobDatawithNULLpointers for existing tables when it is lazily initialized. - Update the push logic (lines 561-567) to push an entry to
aSubmitBlobDatafor every table if the array has been initialized, even if the table has no blob.
if (pTableCxt->hasBlob && NULL == pVgCxt->pData->aSubmitBlobData) {
pVgCxt->pData->aSubmitBlobData = taosArrayInit(128, sizeof(SBlobSet*));
if (NULL == pVgCxt->pData->aSubmitBlobData) {
return terrno;
}
int32_t n = taosArrayGetSize(pVgCxt->pData->aSubmitTbData);
for (int32_t i = 0; i < n; ++i) {
SBlobSet* pNull = NULL;
if (NULL == taosArrayPush(pVgCxt->pData->aSubmitBlobData, &pNull)) {
return terrno;
}
}
}| if (pTbCtx->hasBlob && NULL == pVgCxt->pData->aSubmitBlobData) { | ||
| pVgCxt->pData->aSubmitBlobData = taosArrayInit(128, sizeof(SBlobSet*)); | ||
| if (pVgCxt->pData->aSubmitBlobData == NULL) { | ||
| return terrno; | ||
| } | ||
| } |
There was a problem hiding this comment.
Similar to the issue in fillVgroupDataCxt, lazy initialization of aSubmitBlobData here will lead to index misalignment with aSubmitTbData. You must ensure that aSubmitBlobData contains an entry (even if NULL) for every entry in aSubmitTbData once it is initialized. The push logic at lines 771-777 also needs to be updated to handle non-blob tables when aSubmitBlobData is active.
if (pTbCtx->hasBlob && NULL == pVgCxt->pData->aSubmitBlobData) {
pVgCxt->pData->aSubmitBlobData = taosArrayInit(128, sizeof(SBlobSet*));
if (pVgCxt->pData->aSubmitBlobData == NULL) {
return terrno;
}
int32_t n = taosArrayGetSize(pVgCxt->pData->aSubmitTbData);
for (int32_t i = 0; i < n; ++i) {
SBlobSet* pNull = NULL;
if (NULL == taosArrayPush(pVgCxt->pData->aSubmitBlobData, &pNull)) {
return terrno;
}
}
}| "n4 using st_noblob tags(4) values ('2024-01-01 00:00:04', 5) " | ||
| "b2 using st_blob tags(2) values ('2024-01-01 00:00:05', 6, 'blob_2') " | ||
| ) | ||
| tdSql.execute(sql) |
There was a problem hiding this comment.
It is recommended to verify the actual content of the blob columns in the test case. Given the logic error in the current PR regarding array synchronization, blob data might be incorrectly associated with the wrong table or row. Verifying only the row count is insufficient to catch such data corruption issues.
There was a problem hiding this comment.
Pull request overview
Fixes a crash in the SQL insert path when a vgroup contains a mix of blob and non-blob tables, and adds CI coverage to prevent regressions.
Changes:
- Adjusts
parInsertUtil.cto lazily initializeaSubmitBlobDatawhen the first blob table is encountered. - Adds a new regression test covering mixed blob/non-blob multi-table INSERT scenarios in a single vgroup.
- Registers the new test in CI task list.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
source/libs/parser/src/parInsertUtil.c |
Moves aSubmitBlobData initialization to avoid NULL deref when a non-blob table is processed before a blob table in the same vgroup. |
test/cases/06-DataIngestion/01-SQL/test_write_blob_multitable.py |
New regression tests for mixed blob/non-blob tables sharing a vgroup during multi-table INSERT. |
test/ci/cases.task |
Adds the new regression test to CI execution list. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if (pTbCtx->hasBlob && NULL == pVgCxt->pData->aSubmitBlobData) { | ||
| pVgCxt->pData->aSubmitBlobData = taosArrayInit(128, sizeof(SBlobSet*)); | ||
| if (pVgCxt->pData->aSubmitBlobData == NULL) { | ||
| return terrno; | ||
| } | ||
| } |
| tdSql.execute(f"create database if not exists {dbname} vgroups 1") | ||
| tdSql.execute(f"use {dbname}") | ||
|
|
||
| # Supertable WITHOUT blob | ||
| tdSql.execute( | ||
| "create stable if not exists st_noblob (ts timestamp, val int) tags (t1 int)" | ||
| ) | ||
| # Supertable WITH blob | ||
| tdSql.execute( | ||
| "create stable if not exists st_blob (ts timestamp, val int, data blob) tags (t1 int)" | ||
| ) | ||
|
|
||
| # Multi-table INSERT: mix child tables from both supertables | ||
| # All land in vgId=1 (vgroups=1) | ||
| # Insert multiple non-blob tables to increase probability they are | ||
| # processed first in hash iteration | ||
| sql = ( | ||
| "insert into " | ||
| "n1 using st_noblob tags(1) values ('2024-01-01 00:00:00', 1) " | ||
| "n2 using st_noblob tags(2) values ('2024-01-01 00:00:01', 2) " | ||
| "n3 using st_noblob tags(3) values ('2024-01-01 00:00:02', 3) " | ||
| "b1 using st_blob tags(1) values ('2024-01-01 00:00:03', 4, 'blob_1') " | ||
| "n4 using st_noblob tags(4) values ('2024-01-01 00:00:04', 5) " | ||
| "b2 using st_blob tags(2) values ('2024-01-01 00:00:05', 6, 'blob_2') " | ||
| ) | ||
| tdSql.execute(sql) | ||
|
|
||
| # Verify data | ||
| tdSql.query("select count(*) from st_noblob") | ||
| tdSql.checkData(0, 0, 4) | ||
|
|
||
| tdSql.query("select count(*) from st_blob") | ||
| tdSql.checkData(0, 0, 2) | ||
|
|
||
| tdSql.query("select * from st_blob order by ts") | ||
| for i in range(tdSql.getRows()): | ||
| tdLog.info(f"row {i}: {tdSql.getData(i, 0)}, {tdSql.getData(i, 1)}, {tdSql.getData(i, 2)}") | ||
|
|
||
| tdSql.query("select * from st_noblob order by ts") | ||
| for i in range(tdSql.getRows()): | ||
| tdLog.info(f"row {i}: {tdSql.getData(i, 0)}, {tdSql.getData(i, 1)}") |
| tdSql.execute(f"create database if not exists {dbname} vgroups 1") | ||
| tdSql.execute(f"use {dbname}") | ||
|
|
||
| tdSql.execute( | ||
| "create stable if not exists st_noblob (ts timestamp, val int) tags (t1 int)" | ||
| ) | ||
| tdSql.execute( | ||
| "create stable if not exists st_blob (ts timestamp, val int, data blob) tags (t1 int)" | ||
| ) | ||
|
|
||
| # 50 non-blob child tables + 10 blob child tables | ||
| parts = [] | ||
| for i in range(50): | ||
| parts.append( | ||
| f"n{i} using st_noblob tags({i}) values " | ||
| f"({1700000000000 + i}, {i})" | ||
| ) | ||
| for i in range(10): | ||
| parts.append( | ||
| f"b{i} using st_blob tags({i}) values " | ||
| f"({1700000001000 + i}, {i}, 'blob_{i}')" | ||
| ) | ||
| sql = "insert into " + " ".join(parts) | ||
| tdSql.execute(sql) | ||
|
|
||
| tdSql.query("select count(*) from st_noblob") | ||
| tdSql.checkData(0, 0, 50) | ||
|
|
||
| tdSql.query("select count(*) from st_blob") | ||
| tdSql.checkData(0, 0, 10) | ||
|
|
| # Multi-table INSERT: mix child tables from both supertables | ||
| # All land in vgId=1 (vgroups=1) | ||
| # Insert multiple non-blob tables to increase probability they are | ||
| # processed first in hash iteration | ||
| sql = ( |
| if (pTableCxt->hasBlob && NULL == pVgCxt->pData->aSubmitBlobData) { | ||
| pVgCxt->pData->aSubmitBlobData = taosArrayInit(128, sizeof(SBlobSet*)); | ||
| if (NULL == pVgCxt->pData->aSubmitBlobData) { | ||
| return terrno; | ||
| } | ||
| } |
Move aSubmitBlobData initialization out of the aSubmitTbData==NULL guard
so it is lazily initialized when the first blob table is encountered,
regardless of whether a non-blob table was processed first for the same
vgroup.
Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com
Description
Issue(s)
Checklist
Please check the items in the checklist if applicable.