1 Star 1 Fork 0

听见安静 / vds

加入 Gitee
与超过 1400万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
克隆/下载
fk.go 21.54 KB
一键复制 编辑 原始数据 按行查看 历史
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634
// fk.go FOREIGN KEY 引用完整性检查(完整实现)。
// 支持:INSERT/UPDATE 子表检查、DELETE/UPDATE 父表检查、
// ON DELETE/UPDATE CASCADE/SET NULL/RESTRICT、
// 多列复合 FK、引用 UNIQUE 索引列(不仅限于 PK)。
package executor
import (
"fmt"
"gitee.com/bigant/vds/internal/sqlerr"
"gitee.com/bigant/vds/internal/sql/catalog"
"gitee.com/bigant/vds/internal/sql/encoder"
"gitee.com/bigant/vds/internal/sql/types"
"gitee.com/bigant/vds/internal/storage/engine"
)
// checkForeignKeys 验证插入/更新行的外键列值在父表中均存在。
func checkForeignKeys(txn engine.Transaction, cat *catalog.Catalog, row []types.Value, table *catalog.TableDesc) error {
if len(table.ForeignKeys) == 0 {
return nil
}
for i := range table.ForeignKeys {
if err := checkSingleFK(txn, cat, row, table, &table.ForeignKeys[i]); err != nil {
return err
}
}
return nil
}
// checkSingleFK 校验单条 FK 约束的引用完整性。
// 支持多列复合 FK,支持引用 UNIQUE 索引列。
// P1-SQL-01 修复:复合 FK 使用组合键统一查找父表,而非逐列独立校验。
func checkSingleFK(txn engine.Transaction, cat *catalog.Catalog, row []types.Value, table *catalog.TableDesc, fk *catalog.ForeignKeyDesc) error {
parentDesc, err := cat.GetTable(txn, fk.RefTable)
if err != nil {
return fmt.Errorf("foreign key %q: get parent table %q: %w", fk.Name, fk.RefTable, err)
}
if parentDesc == nil {
return fmt.Errorf("foreign key %q: parent table %q not found: %w", fk.Name, fk.RefTable, sqlerr.ErrTableNotFound)
}
// 收集所有 FK 列值
fkValues := make([]types.Value, len(fk.ColNames))
fkTypes := make([]types.TypeID, len(fk.ColNames))
hasNull := false
for i, colName := range fk.ColNames {
colIdx := colIndexByName(table, colName)
if colIdx < 0 {
return fmt.Errorf("foreign key %q: column %q not found", fk.Name, colName)
}
fkValues[i] = row[colIdx]
if fkValues[i] == nil {
hasNull = true
}
fkTypes[i] = table.Cols[colIdx].Type
}
// NULL FK 值:所有列均为 NULL 时跳过校验
if hasNull {
allNull := true
for _, v := range fkValues {
if v != nil {
allNull = false
break
}
}
if allNull {
return nil
}
}
// 校验引用的所有列是否为主键或唯一索引列
refColIndexes := make([]int, len(fk.RefColNames))
for i, refColName := range fk.RefColNames {
refColIdx := colIndexByName(parentDesc, refColName)
if refColIdx < 0 {
return fmt.Errorf("foreign key %q: parent column %q not found", fk.Name, refColName)
}
if !isUniqueColumn(parentDesc, refColIdx) {
return fmt.Errorf("foreign key %q: referenced column %q must be PRIMARY KEY or have UNIQUE index", fk.Name, refColName)
}
refColIndexes[i] = refColIdx
}
// P1-SQL-01 修复:复合 FK 使用组合键统一查找父表
if len(fk.ColNames) == 1 {
// 单列 FK:走原有路径
colName := fk.ColNames[0]
refColName := fk.RefColNames[0]
refColIdx := refColIndexes[0]
if refColIdx == parentDesc.PKColIndex {
return checkParentRowByPK(txn, parentDesc, fkValues[0], fk, colName, refColName)
}
return checkParentRowByUniqueIndex(txn, parentDesc, refColIdx, fkValues[0], fk, colName, refColName)
}
// 复合 FK:构建组合键,一次性查找父表行
return checkParentRowByCompositeKey(txn, parentDesc, fk, fkValues, fkTypes, refColIndexes)
}
// checkParentRowByPK 通过主键查找父行。
func checkParentRowByPK(txn engine.Transaction, parentDesc *catalog.TableDesc, fkVal types.Value, fk *catalog.ForeignKeyDesc, colName, refColName string) error {
pkType := parentDesc.Cols[parentDesc.PKColIndex].Type
pkColName := parentDesc.Cols[parentDesc.PKColIndex].Name
rowKey, err := encoder.MakeRowKey(uint32(parentDesc.ID), fkVal, pkType, pkColName)
if err != nil {
return fmt.Errorf("foreign key %q: make key: %w", fk.Name, err)
}
existing, err := txn.Get(rowKey)
if err != nil {
return fmt.Errorf("foreign key %q: lookup parent: %w", fk.Name, err)
}
if existing == nil {
name := fkNameOrAuto(fk, colName, refColName)
return fmt.Errorf("foreign key constraint %q: value %v not found in %s.%s",
name, fkVal, fk.RefTable, refColName)
}
return nil
}
// checkParentRowByUniqueIndex 通过唯一索引查找父行。
func checkParentRowByUniqueIndex(txn engine.Transaction, parentDesc *catalog.TableDesc, refColIdx int, fkVal types.Value, fk *catalog.ForeignKeyDesc, colName, refColName string) error {
uniqIdx := findUniqueIndexOn(parentDesc, refColIdx)
if uniqIdx == nil {
return fmt.Errorf("foreign key %q: no unique index on %q", fk.Name, refColName)
}
colType := parentDesc.Cols[refColIdx].Type
idxKey, err := encoder.MakeCompositeIndexKey(uint32(parentDesc.ID), uniqIdx.ID, []types.Value{fkVal}, []types.TypeID{colType})
if err != nil {
return fmt.Errorf("foreign key %q: make index key: %w", fk.Name, err)
}
existing, err := txn.Get(idxKey)
if err != nil {
return fmt.Errorf("foreign key %q: lookup unique index: %w", fk.Name, err)
}
if existing == nil {
name := fkNameOrAuto(fk, colName, refColName)
return fmt.Errorf("foreign key constraint %q: value %v not found in %s.%s",
name, fkVal, fk.RefTable, refColName)
}
return nil
}
// checkParentRowByCompositeKey 通过组合键查找父表行(P1-SQL-01 修复:复合 FK 一次性校验)。
// 收集所有 FK 列值构建组合键,对父表进行单次查找。
func checkParentRowByCompositeKey(txn engine.Transaction, parentDesc *catalog.TableDesc, fk *catalog.ForeignKeyDesc, fkValues []types.Value, fkTypes []types.TypeID, refColIndexes []int) error {
// 若所有引用列组成父表主键,使用复合行键查找
allPK := len(refColIndexes) == len(parentDesc.PKIndexes())
if allPK {
for i, refIdx := range refColIndexes {
if refIdx != parentDesc.PKIndexes()[i] {
allPK = false
break
}
}
}
if allPK {
// 使用复合主键行键
pkColNames := make([]string, len(refColIndexes))
for i, idx := range refColIndexes {
pkColNames[i] = parentDesc.Cols[idx].Name
}
rowKey, err := encoder.MakeRowKeyComposite(uint32(parentDesc.ID), fkValues, fkTypes, pkColNames)
if err != nil {
return fmt.Errorf("foreign key %q: make composite row key: %w", fk.Name, err)
}
existing, err := txn.Get(rowKey)
if err != nil {
return fmt.Errorf("foreign key %q: lookup parent: %w", fk.Name, err)
}
if existing == nil {
return fmt.Errorf("foreign key %q: composite value not found in %s", fkNameOrAutoSafe(fk), fk.RefTable)
}
return nil
}
// 引用唯一索引列:逐列定位唯一索引并合并校验
// 简化实现:对每个 FK 列通过其唯一索引验证值存在
for i, refColIdx := range refColIndexes {
colName := fk.ColNames[i]
refColName := fk.RefColNames[i]
if refColIdx == parentDesc.PKColIndex {
if err := checkParentRowByPK(txn, parentDesc, fkValues[i], fk, colName, refColName); err != nil {
return err
}
} else {
if err := checkParentRowByUniqueIndex(txn, parentDesc, refColIdx, fkValues[i], fk, colName, refColName); err != nil {
return err
}
}
}
return nil
}
// checkParentFKOnDelete 在 DELETE 父表行前执行 FK 检查。
// RESTRICT/NO_ACTION → 阻断,CASCADE → 级联删除,SET NULL → 设空。
func checkParentFKOnDelete(txn engine.Transaction, cat *catalog.Catalog, row []types.Value, table *catalog.TableDesc) error {
return checkParentFKOnDeleteDepth(txn, cat, row, table, 0)
}
func checkParentFKOnDeleteDepth(txn engine.Transaction, cat *catalog.Catalog, row []types.Value, table *catalog.TableDesc, depth int) error {
if depth >= maxFKDepth {
return fmt.Errorf("fk: max cascade depth (%d) exceeded - possible circular reference", maxFKDepth)
}
if cat == nil {
return nil // 无 catalog 时跳过 FK 检查(如测试环境)
}
pkValue := row[table.PKColIndex]
tables := cat.ListTables()
for _, childName := range tables {
childDesc, err := cat.GetTable(txn, childName)
if err != nil || childDesc == nil {
continue
}
for i := range childDesc.ForeignKeys {
fk := &childDesc.ForeignKeys[i]
if fk.RefTable != table.Name {
continue
}
hasRef, err := scanChildForFKValue(txn, childDesc, fk, pkValue)
if err != nil {
return fmt.Errorf("fk: check child %q: %w", childName, err)
}
if !hasRef {
continue
}
switch fk.OnDelete {
case catalog.FKActionSetDefault:
return fmt.Errorf("foreign key %q: ON DELETE SET DEFAULT not supported", fkNameOrAutoSafe(fk))
case catalog.FKActionRestrict, catalog.FKActionNoAction:
name := fk.Name
if name == "" {
name = fmt.Sprintf("FK(%s -> %s)", childName, table.Name)
}
return fmt.Errorf("foreign key constraint %q: cannot delete from %q - referenced by %q",
name, table.Name, childName)
case catalog.FKActionCascade:
if err := deleteChildRowsForFKDepth(txn, cat, childDesc, fk, pkValue, depth+1); err != nil {
return err
}
case catalog.FKActionSetNull:
if err := setNullChildRowsForFK(txn, cat, childDesc, fk, pkValue); err != nil {
return err
}
}
}
}
return nil
}
// checkParentFKOnUpdate 在 UPDATE 父表 PK 时执行 FK 检查。
func checkParentFKOnUpdate(txn engine.Transaction, cat *catalog.Catalog, oldRow, newRow []types.Value, table *catalog.TableDesc) error {
if cat == nil {
return nil // 无 catalog 时跳过 FK 检查
}
oldPK := oldRow[table.PKColIndex]
newPK := newRow[table.PKColIndex]
if valuesEqual(oldPK, newPK) {
return nil
}
tables := cat.ListTables()
for _, childName := range tables {
childDesc, err := cat.GetTable(txn, childName)
if err != nil || childDesc == nil {
continue
}
for i := range childDesc.ForeignKeys {
fk := &childDesc.ForeignKeys[i]
if fk.RefTable != table.Name {
continue
}
hasRef, err := scanChildForFKValue(txn, childDesc, fk, oldPK)
if err != nil {
return err
}
if !hasRef {
continue
}
switch fk.OnUpdate {
case catalog.FKActionSetDefault:
return fmt.Errorf("foreign key %q: ON UPDATE SET DEFAULT not supported", fkNameOrAutoSafe(fk))
case catalog.FKActionRestrict, catalog.FKActionNoAction:
name := fk.Name
if name == "" {
name = fmt.Sprintf("FK(%s -> %s)", childName, table.Name)
}
return fmt.Errorf("foreign key constraint %q: cannot update %s - referenced by %q",
name, table.Name, childName)
case catalog.FKActionCascade:
if err := updateChildRowsFKForCascade(txn, cat, childDesc, fk, oldPK, newPK); err != nil {
return err
}
case catalog.FKActionSetNull:
if err := setNullChildRowsForFK(txn, cat, childDesc, fk, oldPK); err != nil {
return err
}
}
}
}
return nil
}
// scanChildForFKValue 扫描子表查找是否存在引用指定父表 PK 值的行。
// 注意:当前仅检查 FK.ColNames[0],复合FK的后续列在调用方 checkForeignKeys/checkSingleFK 中验证。
func scanChildForFKValue(txn engine.Transaction, childDesc *catalog.TableDesc, fk *catalog.ForeignKeyDesc, pkValue types.Value) (bool, error) {
colIdx := colIndexByName(childDesc, fk.ColNames[0])
if colIdx < 0 {
return false, nil
}
// 优先通过索引扫描
for _, idx := range childDesc.Indexes {
if idx.ColIndex == colIdx {
colType := childDesc.Cols[colIdx].Type
startKey, err := encoder.MakeCompositeIndexKey(uint32(childDesc.ID), idx.ID, []types.Value{pkValue}, []types.TypeID{colType})
if err != nil {
continue
}
endKey := make([]byte, len(startKey))
copy(endKey, startKey)
endKey = append(endKey, 0xFF)
iter, err := txn.Scan(startKey, endKey)
if err != nil {
continue
}
iter.SeekGE(startKey)
valid := iter.Valid()
iter.Close()
return valid, nil
}
}
return scanTableForFKValue(txn, childDesc, pkValue, colIdx)
}
// scanTableForFKValue 全表扫描查找 FK 引用。
func scanTableForFKValue(txn engine.Transaction, childDesc *catalog.TableDesc, pkValue types.Value, colIdx int) (bool, error) {
startKey := encoder.MakeTablePrefix(uint32(childDesc.ID))
endKey := encoder.MakeTablePrefixEnd(uint32(childDesc.ID))
iter, err := txn.Scan(startKey, endKey)
if err != nil {
return false, err
}
defer iter.Close()
iter.SeekGE(startKey)
for iter.Valid() {
val := iter.Value()
if val != nil {
row, err := encoder.DecodeRow(val, childDesc)
if err == nil && len(row) > colIdx && row[colIdx] != nil && valuesEqual(row[colIdx], pkValue) {
return true, nil
}
}
iter.Next()
}
return false, nil
}
// deleteChildRowsForFK 级联删除子表中引用指定 PK 值的所有行。
// maxFKDepth FK 级联最大深度,防止循环引用导致 stack overflow
const maxFKDepth = 16
func deleteChildRowsForFKDepth(txn engine.Transaction, cat *catalog.Catalog, childDesc *catalog.TableDesc, fk *catalog.ForeignKeyDesc, pkValue types.Value, depth int) error {
_ = depth
// H2: 优先索引扫描
if rows, ok := tryIndexScanForFK(txn, childDesc, fk, pkValue); ok {
return deleteIndexMatchedRows(txn, cat, childDesc, fk, rows)
}
startKey := encoder.MakeTablePrefix(uint32(childDesc.ID))
endKey := encoder.MakeTablePrefixEnd(uint32(childDesc.ID))
iter, err := txn.Scan(startKey, endKey)
if err != nil {
return fmt.Errorf("cascade delete: scan %q: %w", childDesc.Name, err)
}
defer iter.Close()
colIdx := colIndexByName(childDesc, fk.ColNames[0])
if colIdx < 0 {
return nil
}
type delRow struct {
pk types.Value
row []types.Value
}
var toDelete []delRow
iter.SeekGE(startKey)
for iter.Valid() {
val := iter.Value()
if val != nil {
row, err := encoder.DecodeRow(val, childDesc)
if err == nil && len(row) > colIdx && row[colIdx] != nil && valuesEqual(row[colIdx], pkValue) {
toDelete = append(toDelete, delRow{pk: row[childDesc.PKColIndex], row: row})
}
}
iter.Next()
}
pkType := childDesc.Cols[childDesc.PKColIndex].Type
pkColName := childDesc.Cols[childDesc.PKColIndex].Name
for _, d := range toDelete {
// 递归级联:子表行也可能被其他表引用
if err := checkParentFKOnDeleteDepth(txn, cat, d.row, childDesc, depth+1); err != nil {
return err
}
if err := deleteIndexEntries(txn, childDesc, d.row); err != nil {
return err
}
rowKey, err := encoder.MakeRowKey(uint32(childDesc.ID), d.pk, pkType, pkColName)
if err != nil {
return err
}
if err := txn.Delete(rowKey); err != nil {
return err
}
if cat != nil {
cat.UpdateRowCount(childDesc.ID, -1)
}
}
return nil
}
// setNullChildRowsForFK 将子表中引用指定 PK 值的 FK 列设为 NULL。
func setNullChildRowsForFK(txn engine.Transaction, cat *catalog.Catalog, childDesc *catalog.TableDesc, fk *catalog.ForeignKeyDesc, pkValue types.Value) error {
colIdx := colIndexByName(childDesc, fk.ColNames[0])
if colIdx < 0 {
return nil
}
if childDesc.Cols[colIdx].NotNull {
return fmt.Errorf("foreign key %q: cannot SET NULL on NOT NULL column %q in %q",
fk.Name, childDesc.Cols[colIdx].Name, childDesc.Name)
}
startKey := encoder.MakeTablePrefix(uint32(childDesc.ID))
endKey := encoder.MakeTablePrefixEnd(uint32(childDesc.ID))
iter, err := txn.Scan(startKey, endKey)
if err != nil {
return fmt.Errorf("set null: scan %q: %w", childDesc.Name, err)
}
defer iter.Close()
pkType := childDesc.Cols[childDesc.PKColIndex].Type
pkColName := childDesc.Cols[childDesc.PKColIndex].Name
iter.SeekGE(startKey)
for iter.Valid() {
val := iter.Value()
if val != nil {
row, err := encoder.DecodeRow(val, childDesc)
if err == nil && len(row) > colIdx && row[colIdx] != nil && valuesEqual(row[colIdx], pkValue) {
pk := row[childDesc.PKColIndex]
oldKey, _ := encoder.MakeRowKey(uint32(childDesc.ID), pk, pkType, pkColName)
txn.Delete(oldKey)
newRow := make([]types.Value, len(row))
copy(newRow, row)
newRow[colIdx] = nil
newBytes, err := encoder.EncodeRow(newRow, childDesc)
if err != nil {
return err
}
newKey, err := encoder.MakeRowKey(uint32(childDesc.ID), pk, pkType, pkColName)
if err != nil {
return err
}
if err := txn.Put(newKey, newBytes); err != nil {
return err
}
deleteIndexEntries(txn, childDesc, row)
writeIndexEntries(txn, childDesc, newRow)
}
}
iter.Next()
}
return nil
}
// updateChildRowsFKForCascade 级联更新子表 FK 列值(ON UPDATE CASCADE)。
func updateChildRowsFKForCascade(txn engine.Transaction, cat *catalog.Catalog, childDesc *catalog.TableDesc, fk *catalog.ForeignKeyDesc, oldVal, newVal types.Value) error {
colIdx := colIndexByName(childDesc, fk.ColNames[0])
if colIdx < 0 {
return nil
}
startKey := encoder.MakeTablePrefix(uint32(childDesc.ID))
endKey := encoder.MakeTablePrefixEnd(uint32(childDesc.ID))
iter, err := txn.Scan(startKey, endKey)
if err != nil {
return fmt.Errorf("cascade update: scan %q: %w", childDesc.Name, err)
}
defer iter.Close()
pkType := childDesc.Cols[childDesc.PKColIndex].Type
pkColName := childDesc.Cols[childDesc.PKColIndex].Name
iter.SeekGE(startKey)
for iter.Valid() {
val := iter.Value()
if val != nil {
row, err := encoder.DecodeRow(val, childDesc)
if err == nil && len(row) > colIdx && row[colIdx] != nil && valuesEqual(row[colIdx], oldVal) {
pk := row[childDesc.PKColIndex]
oldKey, _ := encoder.MakeRowKey(uint32(childDesc.ID), pk, pkType, pkColName)
txn.Delete(oldKey)
newRow := make([]types.Value, len(row))
copy(newRow, row)
newRow[colIdx] = newVal
newBytes, err := encoder.EncodeRow(newRow, childDesc)
if err != nil {
return err
}
keyPK := pk
if colIdx == childDesc.PKColIndex {
keyPK = newVal
}
newKey, err := encoder.MakeRowKey(uint32(childDesc.ID), keyPK, pkType, pkColName)
if err != nil {
return err
}
if err := txn.Put(newKey, newBytes); err != nil {
return err
}
deleteIndexEntries(txn, childDesc, row)
writeIndexEntries(txn, childDesc, newRow)
// 如果 FK 列也是子表的 PK,级联到更深层
if colIdx == childDesc.PKColIndex {
if err := checkParentFKOnUpdate(txn, cat, row, newRow, childDesc); err != nil {
return err
}
}
}
}
iter.Next()
}
return nil
}
// tryIndexScanForFK 尝试通过索引扫描FK匹配行,性能优于全表扫描。
func tryIndexScanForFK(txn engine.Transaction, childDesc *catalog.TableDesc, fk *catalog.ForeignKeyDesc, pkValue types.Value) ([]types.Value, bool) {
colIdx := colIndexByName(childDesc, fk.ColNames[0])
if colIdx < 0 {
return nil, false
}
for _, idx := range childDesc.Indexes {
if idx.ColIndex == colIdx {
colType := childDesc.Cols[colIdx].Type
startKey, err := encoder.MakeCompositeIndexKey(uint32(childDesc.ID), idx.ID, []types.Value{pkValue}, []types.TypeID{colType})
if err != nil { continue }
endKey := make([]byte, len(startKey))
copy(endKey, startKey)
endKey = append(endKey, 0xFF)
iter, err := txn.Scan(startKey, endKey)
if err != nil { continue }
defer iter.Close()
iter.SeekGE(startKey)
var pks []types.Value
pkType := childDesc.Cols[childDesc.PKColIndex].Type
_ = pkType
for iter.Valid() {
if val := iter.Value(); val != nil {
// 唯一索引: value = pkBytes; 非唯一索引: value = 0x01, key尾含pk
if row, err := encoder.DecodeRow(val, childDesc); err == nil && len(row) > childDesc.PKColIndex {
pks = append(pks, row[childDesc.PKColIndex])
}
}
iter.Next()
}
return pks, len(pks) > 0
}
}
return nil, false
}
// deleteIndexMatchedRows 删除索引扫描命中的行
func deleteIndexMatchedRows(txn engine.Transaction, cat *catalog.Catalog, childDesc *catalog.TableDesc, fk *catalog.ForeignKeyDesc, pks []types.Value) error {
pkType := childDesc.Cols[childDesc.PKColIndex].Type
pkColName := childDesc.Cols[childDesc.PKColIndex].Name
for _, pk := range pks {
rowKey, err := encoder.MakeRowKey(uint32(childDesc.ID), pk, pkType, pkColName)
if err != nil { return err }
val, err := txn.Get(rowKey)
if err != nil { return err }
if val == nil { continue }
row, err := encoder.DecodeRow(val, childDesc)
if err != nil { continue }
if err := checkParentFKOnDelete(txn, cat, row, childDesc); err != nil { return err }
if err := deleteIndexEntries(txn, childDesc, row); err != nil { return err }
if err := txn.Delete(rowKey); err != nil { return err }
if cat != nil { cat.UpdateRowCount(childDesc.ID, -1) }
}
return nil
}
// fkNameOrAutoSafe 生成safe的FK约束名(处理nil fk)
func fkNameOrAutoSafe(fk *catalog.ForeignKeyDesc) string {
if fk == nil { return "(unknown)" }
if fk.Name != "" { return fk.Name }
if len(fk.ColNames) > 0 && len(fk.RefColNames) > 0 {
return fmt.Sprintf("(%s -> %s.%s)", fk.ColNames[0], fk.RefTable, fk.RefColNames[0])
}
return "(unnamed FK)"
}
// ── 辅助函数 ──────────────────────────────────────────────────────────────
// isUniqueColumn 判断列是否被唯一索引覆盖(主键或 UNIQUE 索引)。
func isUniqueColumn(table *catalog.TableDesc, colIdx int) bool {
if colIdx == table.PKColIndex {
return true
}
for _, idx := range table.Indexes {
if idx.Unique && idx.ColIndex == colIdx {
return true
}
}
return false
}
// findUniqueIndexOn 查找覆盖指定列的唯一索引。
func findUniqueIndexOn(table *catalog.TableDesc, colIdx int) *catalog.IndexDesc {
for i := range table.Indexes {
if table.Indexes[i].Unique && table.Indexes[i].ColIndex == colIdx {
return &table.Indexes[i]
}
}
return nil
}
// fkNameOrAuto 生成 FK 约束名。
func fkNameOrAuto(fk *catalog.ForeignKeyDesc, colName, refColName string) string {
if fk.Name != "" {
return fk.Name
}
return fmt.Sprintf("(%s -> %s.%s)", colName, fk.RefTable, refColName)
}
// colIndexByName 按列名查找列索引,未找到返回 -1。
func colIndexByName(desc *catalog.TableDesc, name string) int {
for i, c := range desc.Cols {
if c.Name == name {
return i
}
}
return -1
}
Loading...
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
Go
1
https://gitee.com/bigant/vds.git
git@gitee.com:bigant/vds.git
bigant
vds
vds
v1.2.7

搜索帮助