package core import ( "testing" "time" "git.ycl.pub/yl32940/TsDB2/DB/core/page" "git.ycl.pub/yl32940/TsDB2/DB/core/types" ) // TestUpdateNullPreserve 部分更新必须保留旧行的 NULL 列: // deserializeRow 不产出 NULL 列, 旧实现"未提供→零值兜底"会把旧行 NULL // 静默写成零值 (deleted_at 被污染 → 软删除作用域永久丢失该行, // 实测报告 #1/#3 根因)。 func TestUpdateNullPreserve(t *testing.T) { db := fixOpenDB(t) tbl, err := db.CreateTable("tp", []types.FieldInfo{ {FieldType: types.FieldType_Uint64, FieldName: []byte("id")}, {FieldType: types.FieldType_DateTime, FieldName: []byte("created_at")}, {FieldType: types.FieldType_DateTime, FieldName: []byte("updated_at")}, {FieldType: types.FieldType_DateTime, Flag: types.FlagNullable, FieldName: []byte("deleted_at")}, {FieldType: types.FieldType_String, Flag: types.WidthCodeFor(32) | types.FlagNullable, FieldName: []byte("title")}, {FieldType: types.FieldType_Int64, Flag: types.FlagNullable, FieldName: []byte("port")}, }) if err != nil { t.Fatal(err) } if _, err := tbl.CreateIndex("pk", page.IndexTypePrimary, []byte{0}); err != nil { t.Fatal(err) } if _, err := tbl.InsertRowWithID(1, []types.FieldValue{ {FieldName: []byte("id"), Value: uint64(1)}, {FieldName: []byte("created_at"), Value: time.Now()}, {FieldName: []byte("updated_at"), Value: time.Now()}, {FieldName: []byte("deleted_at"), Value: nil}, {FieldName: []byte("title"), Value: "原始"}, {FieldName: []byte("port"), Value: int64(11)}, }); err != nil { t.Fatal(err) } db.Commit() if err := tbl.UpdateRowByPK(1, []types.FieldValue{ {FieldName: []byte("title"), Value: "字面量A"}, {FieldName: []byte("updated_at"), Value: time.Now()}, }); err != nil { t.Fatal(err) } storeRow, _ := tbl.indexMeta.ResolveByPK(1) rowData := storeRow[8:] t.Logf("rowData=%x", rowData) layout, _ := tbl.parseRowLayout(rowData) bm, _ := types.RowNullBitmap(rowData, layout.ends, len(layout.sv.FieldIndices)) t.Logf("ends=%v bm=%x", layout.ends, bm) // deleted_at 的位 (fieldIndex 3 → pos 3) t.Logf("deleted_at NullBit=%v", types.NullBitSet(bm, 3)) }