diff --git a/cmfx/relationship/models.go b/cmfx/relationship/models.go new file mode 100644 index 0000000..06a40f8 --- /dev/null +++ b/cmfx/relationship/models.go @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: 2025 caixw +// +// SPDX-License-Identifier: MIT + +package relationship + +type relationshipPO[T1, T2 T] struct { + V1 T1 `orm:"name(v1);unique(v12)"` + V2 T2 `orm:"name(v2);unique(v12)"` +} + +func (*relationshipPO[T1, T2]) TableName() string { return "_relationships" } diff --git a/cmfx/relationship/module.go b/cmfx/relationship/module.go new file mode 100644 index 0000000..5427c9f --- /dev/null +++ b/cmfx/relationship/module.go @@ -0,0 +1,87 @@ +// SPDX-FileCopyrightText: 2025 caixw +// +// SPDX-License-Identifier: MIT + +package relationship + +import ( + "github.com/issue9/orm/v6" + "github.com/issue9/orm/v6/fetch" + "github.com/issue9/web" + + "github.com/issue9/cmfx/cmfx" +) + +type Module[T1, T2 T] struct { + db *orm.DB + mod *cmfx.Module +} + +func Load[T1, T2 T](mod *cmfx.Module, prefix string) *Module[T1, T2] { + return &Module[T1, T2]{ + db: buildDB(mod, prefix), + mod: mod, + } +} + +func Install[T1, T2 T](mod *cmfx.Module, prefix string) *Module[T1, T2] { + db := buildDB(mod, prefix) + if err := db.Create(&relationshipPO[T1, T2]{}); err != nil { + panic(web.SprintError(mod.Server().Locale().Printer(), true, err)) + } + + return Load[T1, T2](mod, prefix) +} + +func (m *Module[T1, T2]) engine(tx *orm.Tx) orm.Engine { + if tx == nil { + return m.db + } + return tx.NewEngine(m.db.TablePrefix()) +} + +func (m *Module[T1, T2]) Add(tx *orm.Tx, v1 T1, v2 T2) error { + _, err := m.engine(tx).Insert(&relationshipPO[T1, T2]{V1: v1, V2: v2}) + return err +} + +func (m *Module[T1, T2]) Delete(tx *orm.Tx, v1 T1, v2 T2) error { + _, err := m.engine(tx).Delete(&relationshipPO[T1, T2]{V1: v1, V2: v2}) + return err +} + +func (m *Module[T1, T2]) DeleteByV1(tx *orm.Tx, v1 T1) error { + _, err := m.engine(tx).Where("v1=?", v1).Delete(&relationshipPO[T1, T2]{}) + return err +} + +func (m *Module[T1, T2]) DeleteByV2(tx *orm.Tx, v2 T2) error { + _, err := m.engine(tx).Where("v2=?", v2).Delete(&relationshipPO[T1, T2]{}) + return err +} + +func (m *Module[T1, T2]) CountByV1(v1 T1) (int64, error) { + return m.db.Where("v1=?", v1).Count(&relationshipPO[T1, T2]{}) +} + +func (m *Module[T1, T2]) CountByV2(v2 T2) (int64, error) { + return m.db.Where("v2=?", v2).Count(&relationshipPO[T1, T2]{}) +} + +// ListV1 列出所有与 v2 关联的 v1 列表 +func (m *Module[T1, T2]) ListV1(v2 T2) ([]T1, error) { + rows, err := m.db.SQLBuilder().Select().From(orm.TableName(&relationshipPO[T1, T2]{})).Where("v2=?", v2).Query() + if err != nil { + return nil, err + } + return fetch.Column[T1](false, "v1", rows) +} + +// ListV2 列出所有与 v1 关联的 v2 列表 +func (m *Module[T1, T2]) ListV2(v1 T1) ([]T2, error) { + rows, err := m.db.SQLBuilder().Select().From(orm.TableName(&relationshipPO[T1, T2]{})).Where("v1=?", v1).Query() + if err != nil { + return nil, err + } + return fetch.Column[T2](false, "v2", rows) +} diff --git a/cmfx/relationship/module_test.go b/cmfx/relationship/module_test.go new file mode 100644 index 0000000..81aa728 --- /dev/null +++ b/cmfx/relationship/module_test.go @@ -0,0 +1,79 @@ +// SPDX-FileCopyrightText: 2025 caixw +// +// SPDX-License-Identifier: MIT + +package relationship + +import ( + "testing" + + "github.com/issue9/assert/v4" + + "github.com/issue9/cmfx/cmfx/initial/test" +) + +func TestInstall(t *testing.T) { + a := assert.New(t, false) + suite := test.NewSuite(a) + defer suite.Close() + + mod := suite.NewModule("rbac") + Install[string, int](mod, "t1_t2") + + suite.TableExists(mod.ID() + "_t1_t2_relationships") +} + +func TestModule(t *testing.T) { + a := assert.New(t, false) + s := test.NewSuite(a) + defer s.Close() + + mod := s.NewModule("rbac") + m := Install[string, int](mod, "t1_t2") + + // Add + + a.NotError(m.Add(nil, "v", 1)) + a.NotError(m.Add(nil, "v", 2)) + a.NotError(m.Add(nil, "v", 3)) + + // Count + + cnt, err := m.CountByV1("v") + a.NotError(err).Equal(cnt, 3) + + cnt, err = m.CountByV2(3) + a.NotError(err).Equal(cnt, 1) + + cnt, err = m.CountByV2(100) + a.NotError(err).Equal(cnt, 0) + + // List + + list1, err := m.ListV1(2) + a.NotError(err).Equal(list1, []string{"v"}) + + list2, err := m.ListV2("v") + a.NotError(err).Equal(list2, []int{1, 2, 3}) + + list2, err = m.ListV2("vv") // 不存在的数据 + a.NotError(err).Equal(list2, []int{}) + + // Delete + + a.NotError(m.Delete(nil, "v", 1)) + cnt, err = m.CountByV1("v") + a.NotError(err).Equal(cnt, 2) // 删除了一条 + + a.NotError(m.Delete(nil, "vv", 2)) // 不存在 + cnt, err = m.CountByV1("v") + a.NotError(err).Equal(cnt, 2) + + a.NotError(m.DeleteByV2(nil, 2)) + cnt, err = m.CountByV1("v") + a.NotError(err).Equal(cnt, 1) + + a.NotError(m.DeleteByV1(nil, "v")) + cnt, err = m.CountByV1("v") + a.NotError(err).Equal(cnt, 0) +} diff --git a/cmfx/relationship/relationship.go b/cmfx/relationship/relationship.go new file mode 100644 index 0000000..5318402 --- /dev/null +++ b/cmfx/relationship/relationship.go @@ -0,0 +1,21 @@ +// SPDX-FileCopyrightText: 2025 caixw +// +// SPDX-License-Identifier: MIT + +// Package relationship 用于处理多对多的数据表关系 +package relationship + +import ( + "github.com/issue9/orm/v6" + + "github.com/issue9/cmfx/cmfx" +) + +// T 限制了可用的字段类型 +type T interface { + ~string | ~int | ~int64 +} + +func buildDB(mod *cmfx.Module, tableName string) *orm.DB { + return mod.DB().New(mod.DB().TablePrefix() + "_" + tableName) +}