|
@@ -107,71 +107,74 @@ func (table *Table) dynamicFieldData(masterIdx int, f types.FieldInfo, value int
|
|
|
//
|
|
//
|
|
|
// checkOnly 是唯一性试算行 (serializeRowForCheck): 绝不能落盘, 返回零引用即可 ——
|
|
// checkOnly 是唯一性试算行 (serializeRowForCheck): 绝不能落盘, 返回零引用即可 ——
|
|
|
// Blob 列不能当索引键 (见 CreateIndex), 试算行的键字节与它无关。
|
|
// Blob 列不能当索引键 (见 CreateIndex), 试算行的键字节与它无关。
|
|
|
-func (table *Table) blobFieldRef(f types.FieldInfo, value interface{}, checkOnly bool) (BlobRef, error) {
|
|
|
|
|
|
|
+func (table *Table) blobFieldRef(f types.FieldInfo, value interface{}, checkOnly bool) (BlobRef, bool, error) {
|
|
|
switch v := value.(type) {
|
|
switch v := value.(type) {
|
|
|
case BlobRef: // == types.BlobRef (类型别名), 引用原样透传
|
|
case BlobRef: // == types.BlobRef (类型别名), 引用原样透传
|
|
|
- return v, nil
|
|
|
|
|
|
|
+ return v, false, nil
|
|
|
case types.Blob:
|
|
case types.Blob:
|
|
|
return table.blobRefFromValue(f, v, checkOnly)
|
|
return table.blobRefFromValue(f, v, checkOnly)
|
|
|
case *types.Blob:
|
|
case *types.Blob:
|
|
|
if v == nil {
|
|
if v == nil {
|
|
|
- return BlobRef{}, nil
|
|
|
|
|
|
|
+ return BlobRef{}, false, nil
|
|
|
}
|
|
}
|
|
|
return table.blobRefFromValue(f, *v, checkOnly)
|
|
return table.blobRefFromValue(f, *v, checkOnly)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if checkOnly {
|
|
if checkOnly {
|
|
|
- return BlobRef{}, nil
|
|
|
|
|
|
|
+ return BlobRef{}, false, nil
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
var src io.Reader
|
|
var src io.Reader
|
|
|
switch v := value.(type) {
|
|
switch v := value.(type) {
|
|
|
case []byte:
|
|
case []byte:
|
|
|
if len(v) == 0 {
|
|
if len(v) == 0 {
|
|
|
- return BlobRef{}, nil
|
|
|
|
|
|
|
+ return BlobRef{}, false, nil
|
|
|
}
|
|
}
|
|
|
src = bytes.NewReader(v)
|
|
src = bytes.NewReader(v)
|
|
|
case string:
|
|
case string:
|
|
|
if v == "" {
|
|
if v == "" {
|
|
|
- return BlobRef{}, nil
|
|
|
|
|
|
|
+ return BlobRef{}, false, nil
|
|
|
}
|
|
}
|
|
|
src = strings.NewReader(v)
|
|
src = strings.NewReader(v)
|
|
|
case io.Reader:
|
|
case io.Reader:
|
|
|
src = v
|
|
src = v
|
|
|
default:
|
|
default:
|
|
|
- return BlobRef{}, fmt.Errorf("字段 %q (%s): blob 列只接受 []byte / string / io.Reader / types.Blob / types.BlobRef, 收到 %T",
|
|
|
|
|
|
|
+ return BlobRef{}, false, fmt.Errorf("字段 %q (%s): blob 列只接受 []byte / string / io.Reader / types.Blob / types.BlobRef, 收到 %T",
|
|
|
f.FieldName, types.TypeNameOf(f.FieldType), value)
|
|
f.FieldName, types.TypeNameOf(f.FieldType), value)
|
|
|
}
|
|
}
|
|
|
ref, err := table.db.blobCommit(src, types.CompressNone)
|
|
ref, err := table.db.blobCommit(src, types.CompressNone)
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
- return BlobRef{}, fmt.Errorf("字段 %q (%s): blob 落链失败: %w",
|
|
|
|
|
|
|
+ return BlobRef{}, false, fmt.Errorf("字段 %q (%s): blob 落链失败: %w",
|
|
|
f.FieldName, types.TypeNameOf(f.FieldType), err)
|
|
f.FieldName, types.TypeNameOf(f.FieldType), err)
|
|
|
}
|
|
}
|
|
|
- return ref, nil
|
|
|
|
|
|
|
+ return ref, true, nil
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// blobRefFromValue 处理 types.Blob 载体: 既有"内容"形态 (Source / Bytes),
|
|
// blobRefFromValue 处理 types.Blob 载体: 既有"内容"形态 (Source / Bytes),
|
|
|
// 也有"读回镜像"形态 (FirstPageID / TotalLen 已由引擎填好).
|
|
// 也有"读回镜像"形态 (FirstPageID / TotalLen 已由引擎填好).
|
|
|
-func (table *Table) blobRefFromValue(f types.FieldInfo, b types.Blob, checkOnly bool) (BlobRef, error) {
|
|
|
|
|
|
|
+//
|
|
|
|
|
+// 返回值 owned 标注该引用是否本次新落的链: 调用方在【整行写入失败】时只能还
|
|
|
|
|
+// 自己新落的链 —— 透传引用 (调用方持有的) 还掉会把别的行/别的表的链误回收。
|
|
|
|
|
+func (table *Table) blobRefFromValue(f types.FieldInfo, b types.Blob, checkOnly bool) (BlobRef, bool, error) {
|
|
|
hasRef := b.FirstPageID != 0 || b.TotalLen != 0
|
|
hasRef := b.FirstPageID != 0 || b.TotalLen != 0
|
|
|
hasContent := b.Source != nil || len(b.Bytes) > 0
|
|
hasContent := b.Source != nil || len(b.Bytes) > 0
|
|
|
if hasRef {
|
|
if hasRef {
|
|
|
if hasContent {
|
|
if hasContent {
|
|
|
- return BlobRef{}, fmt.Errorf("字段 %q: types.Blob 同时带了引用与内容 (Source/Bytes), 无法判定该用哪个", f.FieldName)
|
|
|
|
|
|
|
+ return BlobRef{}, false, fmt.Errorf("字段 %q: types.Blob 同时带了引用与内容 (Source/Bytes), 无法判定该用哪个", f.FieldName)
|
|
|
}
|
|
}
|
|
|
- return b.Ref(), nil // 读回镜像: 原样透传
|
|
|
|
|
|
|
+ return b.Ref(), false, nil // 读回镜像: 原样透传
|
|
|
}
|
|
}
|
|
|
if !hasContent {
|
|
if !hasContent {
|
|
|
- return BlobRef{}, nil // 空 blob: 不分配链页
|
|
|
|
|
|
|
+ return BlobRef{}, false, nil // 空 blob: 不分配链页
|
|
|
}
|
|
}
|
|
|
if checkOnly {
|
|
if checkOnly {
|
|
|
- return BlobRef{}, nil
|
|
|
|
|
|
|
+ return BlobRef{}, false, nil
|
|
|
}
|
|
}
|
|
|
if b.Source != nil && len(b.Bytes) > 0 {
|
|
if b.Source != nil && len(b.Bytes) > 0 {
|
|
|
- return BlobRef{}, fmt.Errorf("字段 %q: types.Blob 的 Source 与 Bytes 都非空, 引擎无法选择", f.FieldName)
|
|
|
|
|
|
|
+ return BlobRef{}, false, fmt.Errorf("字段 %q: types.Blob 的 Source 与 Bytes 都非空, 引擎无法选择", f.FieldName)
|
|
|
}
|
|
}
|
|
|
if !b.Compress.IsValid() {
|
|
if !b.Compress.IsValid() {
|
|
|
- return BlobRef{}, fmt.Errorf("字段 %q: types.Blob.Compress 非法", f.FieldName)
|
|
|
|
|
|
|
+ return BlobRef{}, false, fmt.Errorf("字段 %q: types.Blob.Compress 非法", f.FieldName)
|
|
|
}
|
|
}
|
|
|
var src io.Reader
|
|
var src io.Reader
|
|
|
if b.Source != nil {
|
|
if b.Source != nil {
|
|
@@ -181,10 +184,10 @@ func (table *Table) blobRefFromValue(f types.FieldInfo, b types.Blob, checkOnly
|
|
|
}
|
|
}
|
|
|
ref, err := table.db.blobCommit(src, b.Compress)
|
|
ref, err := table.db.blobCommit(src, b.Compress)
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
- return BlobRef{}, fmt.Errorf("字段 %q (%s): blob 落链失败: %w",
|
|
|
|
|
|
|
+ return BlobRef{}, false, fmt.Errorf("字段 %q (%s): blob 落链失败: %w",
|
|
|
f.FieldName, types.TypeNameOf(f.FieldType), err)
|
|
f.FieldName, types.TypeNameOf(f.FieldType), err)
|
|
|
}
|
|
}
|
|
|
- return ref, nil
|
|
|
|
|
|
|
+ return ref, true, nil
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// serializeRow 将 FieldValue 切片序列化为行数据字节 (不含 pkID 前缀)
|
|
// serializeRow 将 FieldValue 切片序列化为行数据字节 (不含 pkID 前缀)
|
|
@@ -222,7 +225,22 @@ func nullFieldError(name string, ft types.FieldType) error {
|
|
|
types.ErrValueRange, name, types.TypeNameOf(ft))
|
|
types.ErrValueRange, name, types.TypeNameOf(ft))
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-func (table *Table) serializeRowInternal(values []types.FieldValue, oldRowData []byte, checkOnly bool) ([]byte, error) {
|
|
|
|
|
|
|
+func (table *Table) serializeRowInternal(values []types.FieldValue, oldRowData []byte, checkOnly bool) (_ []byte, err error) {
|
|
|
|
|
+ // 失败回收: blob 列在 Pass 1 就落链 (行内只有 8B 引用), 一旦后续列编码失败 /
|
|
|
|
|
+ // 布局自检失败, 这批新链页还不掉就成为孤儿页 (无事务写入的既有泄漏路径,
|
|
|
|
|
+ // 见 blobCommit 的注释)。只还"本次新落的链" (owned) —— 透传引用
|
|
|
|
|
+ // (调用方持有的 types.BlobRef / 读回镜像) 不属于本行, 绝不能碰。
|
|
|
|
|
+ // 这些链此刻尚未被任何行/索引引用, 无条件归还安全; 更新路径 Pass 3 已释放的
|
|
|
|
|
+ // 旧链不会出现在这里 (owned 只记新链)。
|
|
|
|
|
+ var newBlobRefs []BlobRef
|
|
|
|
|
+ defer func() {
|
|
|
|
|
+ if err != nil && !checkOnly {
|
|
|
|
|
+ for _, r := range newBlobRefs {
|
|
|
|
|
+ _ = table.BlobFree(r)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }()
|
|
|
|
|
+
|
|
|
ver := table.pickVersionForUpdate(oldRowData, values)
|
|
ver := table.pickVersionForUpdate(oldRowData, values)
|
|
|
|
|
|
|
|
// 写行闸门 (第三处行宽校验)。
|
|
// 写行闸门 (第三处行宽校验)。
|
|
@@ -391,10 +409,13 @@ func (table *Table) serializeRowInternal(values []types.FieldValue, oldRowData [
|
|
|
// Blob 列行内只有 8B BlobRef, 但用户手上多半是"内容本身" ([]byte / io.Reader),
|
|
// Blob 列行内只有 8B BlobRef, 但用户手上多半是"内容本身" ([]byte / io.Reader),
|
|
|
// 由引擎在这里把内容落进链页、换成引用 (见 blobFieldRef). 已经是引用的原样用。
|
|
// 由引擎在这里把内容落进链页、换成引用 (见 blobFieldRef). 已经是引用的原样用。
|
|
|
// 试算行 (checkOnly) 不落盘: Blob 列不能当索引键 (见 CreateIndex), 键字节与它无关。
|
|
// 试算行 (checkOnly) 不落盘: Blob 列不能当索引键 (见 CreateIndex), 键字节与它无关。
|
|
|
- ref, err := table.blobFieldRef(f, value, checkOnly)
|
|
|
|
|
|
|
+ ref, owned, err := table.blobFieldRef(f, value, checkOnly)
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
return nil, err
|
|
return nil, err
|
|
|
}
|
|
}
|
|
|
|
|
+ if owned {
|
|
|
|
|
+ newBlobRefs = append(newBlobRefs, ref)
|
|
|
|
|
+ }
|
|
|
enc, err := table.encodeFieldValue(int(masterIdx), f, ref)
|
|
enc, err := table.encodeFieldValue(int(masterIdx), f, ref)
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
return nil, err
|
|
return nil, err
|