diff --git a/source/common/src/tdatablock.c b/source/common/src/tdatablock.c index 166d9777af05..ad5a8f010f94 100644 --- a/source/common/src/tdatablock.c +++ b/source/common/src/tdatablock.c @@ -2495,8 +2495,11 @@ static void colDataKeepFirstNRows(SColumnInfoData* pColInfoData, size_t n, size_ } } } - if (newLen <= -1) { - uFatal("colDataKeepFirstNRows: newLen:%d old:%d", newLen, pColInfoData->varmeta.length); + if (newLen < 0) { + // All kept rows are NULL — no valid var data belongs to them. + uDebug("colDataKeepFirstNRows: all kept rows are NULL, newLen:%d old:%u, reset length to 0", + newLen, pColInfoData->varmeta.length); + pColInfoData->varmeta.length = 0; } else { pColInfoData->varmeta.length = newLen; } diff --git a/source/libs/decimal/src/detail/intx/int128.hpp b/source/libs/decimal/src/detail/intx/int128.hpp index b351c1e4cf55..f5502dd20627 100644 --- a/source/libs/decimal/src/detail/intx/int128.hpp +++ b/source/libs/decimal/src/detail/intx/int128.hpp @@ -760,7 +760,9 @@ struct numeric_limits> static constexpr bool is_bounded = true; static constexpr bool is_modulo = true; static constexpr int digits = CHAR_BIT * sizeof(type); - static constexpr int digits10 = int(0.3010299956639812 * digits); + // Maximum number of base-10 digits that can be represented by this type. + // Computed as floor(digits * log10(2)) where log10(2) ≈ 0.30102999566398119. + static constexpr int digits10 = static_cast(0.30102999566398119521373889472449 * digits); static constexpr int max_digits10 = 0; static constexpr int radix = 2; static constexpr int min_exponent = 0; diff --git a/test/cases/01-DataTypes/test_null_column.py b/test/cases/01-DataTypes/test_null_column.py index 97dbdb4082ec..1c57b6fd54d7 100644 --- a/test/cases/01-DataTypes/test_null_column.py +++ b/test/cases/01-DataTypes/test_null_column.py @@ -30,6 +30,80 @@ def test_null_column(self): self.NullColSma() tdStream.dropAllStreamsAndDbs() + def test_null_varchar_limit(self): + """NULL: varchar column with LIMIT triggers blockDataKeepFirstNRows all-NULL path + + When the first N rows of a variable-length column are all NULL and a + LIMIT N query is executed, blockDataKeepFirstNRows must set + varmeta.length to 0 instead of keeping the stale value. + + Since: v3.0.0.0 + + Labels: common,ci + + Jira: TD-32818 + + History: + - 2026-4-3 Added to cover colDataKeepFirstNRows all-NULL var-data fix + + """ + + tdSql.prepare("db_null_limit", drop=True) + tdSql.execute("use db_null_limit") + + # Create a table with multiple var-length column types + tdSql.execute( + "create table t_var_null (ts timestamp, c1 varchar(64), c2 nchar(64), c3 int)" + ) + + # Insert rows: first 5 rows have NULL varchar/nchar, last 3 have values + tdSql.execute("insert into t_var_null values ('2024-01-01 00:00:01', NULL, NULL, 1)") + tdSql.execute("insert into t_var_null values ('2024-01-01 00:00:02', NULL, NULL, 2)") + tdSql.execute("insert into t_var_null values ('2024-01-01 00:00:03', NULL, NULL, 3)") + tdSql.execute("insert into t_var_null values ('2024-01-01 00:00:04', NULL, NULL, 4)") + tdSql.execute("insert into t_var_null values ('2024-01-01 00:00:05', NULL, NULL, 5)") + tdSql.execute("insert into t_var_null values ('2024-01-01 00:00:06', 'hello', 'world', 6)") + tdSql.execute("insert into t_var_null values ('2024-01-01 00:00:07', 'foo', 'bar', 7)") + tdSql.execute("insert into t_var_null values ('2024-01-01 00:00:08', 'test', 'data', 8)") + + # LIMIT 3: keep only the first 3 rows (all NULL in c1/c2) + # This triggers blockDataKeepFirstNRows where all kept var-data rows are NULL + tdSql.query("select * from t_var_null order by ts limit 3") + tdSql.checkRows(3) + tdSql.checkData(0, 1, None) + tdSql.checkData(1, 1, None) + tdSql.checkData(2, 1, None) + tdSql.checkData(0, 2, None) + tdSql.checkData(1, 2, None) + tdSql.checkData(2, 2, None) + tdSql.checkData(0, 3, 1) + tdSql.checkData(1, 3, 2) + tdSql.checkData(2, 3, 3) + + # LIMIT 5: all 5 NULL rows + tdSql.query("select * from t_var_null order by ts limit 5") + tdSql.checkRows(5) + for i in range(5): + tdSql.checkData(i, 1, None) + tdSql.checkData(i, 2, None) + + # LIMIT 6: 5 NULL + 1 non-NULL — boundary case + tdSql.query("select * from t_var_null order by ts limit 6") + tdSql.checkRows(6) + for i in range(5): + tdSql.checkData(i, 1, None) + tdSql.checkData(5, 1, "hello") + tdSql.checkData(5, 2, "world") + + # Also test with OFFSET to hit blockDataTrimFirstRows + blockDataKeepFirstNRows + tdSql.query("select * from t_var_null order by ts limit 2 offset 4") + tdSql.checkRows(2) + tdSql.checkData(0, 1, None) # row index 4 — still NULL + tdSql.checkData(1, 1, "hello") # row index 5 + + tdLog.info("test_null_varchar_limit passed") + tdStream.dropAllStreamsAndDbs() + def ComputeNull(self): dbPrefix = "db" tbPrefix = "tb"