[lxc-devel] [lxd/master] Make the db mapper code generator handle compound natural keys
freeekanayaka on Github
lxc-bot at linuxcontainers.org
Tue Oct 30 08:52:58 UTC 2018
A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 377 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20181030/1053d29d/attachment.bin>
-------------- next part --------------
From 6448fb7440d344e63a578541f0f1c95496b0e56d Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanayaka at canonical.com>
Date: Tue, 30 Oct 2018 09:49:58 +0100
Subject: [PATCH] Make the db mapper code generator handle compound natural
keys
Signed-off-by: Free Ekanayaka <free.ekanayaka at canonical.com>
---
lxd/db/containers.mapper.go | 49 +++++++++++++++++---------
lxd/db/containers_test.go | 67 ++++++++++++++++++++++++++++++++++++
lxd/db/profiles.mapper.go | 48 ++++++++++++++++++--------
shared/generate/db/lex.go | 11 ++++++
shared/generate/db/method.go | 40 ++++++++++++++++-----
5 files changed, 175 insertions(+), 40 deletions(-)
diff --git a/lxd/db/containers.mapper.go b/lxd/db/containers.mapper.go
index 9aec50d48f..10bcf523cf 100644
--- a/lxd/db/containers.mapper.go
+++ b/lxd/db/containers.mapper.go
@@ -5,7 +5,6 @@ package db
import (
"database/sql"
"fmt"
-
"github.com/lxc/lxd/lxd/db/cluster"
"github.com/lxc/lxd/lxd/db/query"
"github.com/lxc/lxd/shared/api"
@@ -245,7 +244,7 @@ func (c *ClusterTx) ContainerList(filter ContainerFilter) ([]Container, error) {
}
for i := range objects {
- value := configObjects[objects[i].Name]
+ value := configObjects[objects[i].Project][objects[i].Name]
if value == nil {
value = map[string]string{}
}
@@ -259,7 +258,7 @@ func (c *ClusterTx) ContainerList(filter ContainerFilter) ([]Container, error) {
}
for i := range objects {
- value := devicesObjects[objects[i].Name]
+ value := devicesObjects[objects[i].Project][objects[i].Name]
if value == nil {
value = map[string]map[string]string{}
}
@@ -273,7 +272,7 @@ func (c *ClusterTx) ContainerList(filter ContainerFilter) ([]Container, error) {
}
for i := range objects {
- value := profilesObjects[objects[i].Name]
+ value := profilesObjects[objects[i].Project][objects[i].Name]
if value == nil {
value = []string{}
}
@@ -432,7 +431,7 @@ func (c *ClusterTx) ContainerCreate(object Container) (int64, error) {
}
// ContainerProfilesRef returns entities used by containers.
-func (c *ClusterTx) ContainerProfilesRef(filter ContainerFilter) (map[string][]string, error) {
+func (c *ClusterTx) ContainerProfilesRef(filter ContainerFilter) (map[string]map[string][]string, error) {
// Result slice.
objects := make([]struct {
Project string
@@ -501,22 +500,28 @@ func (c *ClusterTx) ContainerProfilesRef(filter ContainerFilter) (map[string][]s
}
// Build index by primary name.
- index := map[string][]string{}
+ index := map[string]map[string][]string{}
for _, object := range objects {
- item, ok := index[object.Name]
+ _, ok := index[object.Project]
+ if !ok {
+ subIndex := map[string][]string{}
+ index[object.Project] = subIndex
+ }
+
+ item, ok := index[object.Project][object.Name]
if !ok {
item = []string{}
}
- index[object.Name] = append(item, object.Value)
+ index[object.Project][object.Name] = append(item, object.Value)
}
return index, nil
}
// ContainerConfigRef returns entities used by containers.
-func (c *ClusterTx) ContainerConfigRef(filter ContainerFilter) (map[string]map[string]string, error) {
+func (c *ClusterTx) ContainerConfigRef(filter ContainerFilter) (map[string]map[string]map[string]string, error) {
// Result slice.
objects := make([]struct {
Project string
@@ -588,15 +593,21 @@ func (c *ClusterTx) ContainerConfigRef(filter ContainerFilter) (map[string]map[s
}
// Build index by primary name.
- index := map[string]map[string]string{}
+ index := map[string]map[string]map[string]string{}
for _, object := range objects {
- item, ok := index[object.Name]
+ _, ok := index[object.Project]
+ if !ok {
+ subIndex := map[string]map[string]string{}
+ index[object.Project] = subIndex
+ }
+
+ item, ok := index[object.Project][object.Name]
if !ok {
item = map[string]string{}
}
- index[object.Name] = item
+ index[object.Project][object.Name] = item
item[object.Key] = object.Value
}
@@ -604,7 +615,7 @@ func (c *ClusterTx) ContainerConfigRef(filter ContainerFilter) (map[string]map[s
}
// ContainerDevicesRef returns entities used by containers.
-func (c *ClusterTx) ContainerDevicesRef(filter ContainerFilter) (map[string]map[string]map[string]string, error) {
+func (c *ClusterTx) ContainerDevicesRef(filter ContainerFilter) (map[string]map[string]map[string]map[string]string, error) {
// Result slice.
objects := make([]struct {
Project string
@@ -682,15 +693,21 @@ func (c *ClusterTx) ContainerDevicesRef(filter ContainerFilter) (map[string]map[
}
// Build index by primary name.
- index := map[string]map[string]map[string]string{}
+ index := map[string]map[string]map[string]map[string]string{}
for _, object := range objects {
- item, ok := index[object.Name]
+ _, ok := index[object.Project]
+ if !ok {
+ subIndex := map[string]map[string]map[string]string{}
+ index[object.Project] = subIndex
+ }
+
+ item, ok := index[object.Project][object.Name]
if !ok {
item = map[string]map[string]string{}
}
- index[object.Name] = item
+ index[object.Project][object.Name] = item
config, ok := item[object.Device]
if !ok {
// First time we see this device, let's int the config
diff --git a/lxd/db/containers_test.go b/lxd/db/containers_test.go
index 90ce6e5ab0..c10e08f2fb 100644
--- a/lxd/db/containers_test.go
+++ b/lxd/db/containers_test.go
@@ -6,6 +6,7 @@ import (
"github.com/lxc/lxd/lxd/db"
"github.com/lxc/lxd/lxd/types"
+ "github.com/lxc/lxd/shared/api"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -87,6 +88,72 @@ func TestContainerList_FilterByNode(t *testing.T) {
assert.Equal(t, "node2", containers[1].Node)
}
+func TestContainerList_ContainerWithSameNameInDifferentProjects(t *testing.T) {
+ tx, cleanup := db.NewTestClusterTx(t)
+ defer cleanup()
+
+ // Create a project with no features
+ project1 := api.ProjectsPost{}
+ project1.Name = "blah"
+ _, err := tx.ProjectCreate(project1)
+ require.NoError(t, err)
+
+ // Create a project with the profiles feature and a custom profile.
+ project2 := api.ProjectsPost{}
+ project2.Name = "test"
+ project2.Config = map[string]string{"features.profiles": "true"}
+ _, err = tx.ProjectCreate(project2)
+ require.NoError(t, err)
+
+ profile := db.Profile{
+ Project: "test",
+ Name: "intranet",
+ }
+ _, err = tx.ProfileCreate(profile)
+ require.NoError(t, err)
+
+ // Create a container in project1 using the default profile from the
+ // default project.
+ c1p1 := db.Container{
+ Project: "blah",
+ Name: "c1",
+ Node: "none",
+ Type: int(db.CTypeRegular),
+ Architecture: 1,
+ Ephemeral: false,
+ Stateful: true,
+ Profiles: []string{"default"},
+ }
+ _, err = tx.ContainerCreate(c1p1)
+ require.NoError(t, err)
+
+ // Create a container in project2 using the custom profile from the
+ // project.
+ c1p2 := db.Container{
+ Project: "test",
+ Name: "c1",
+ Node: "none",
+ Type: int(db.CTypeRegular),
+ Architecture: 1,
+ Ephemeral: false,
+ Stateful: true,
+ Profiles: []string{"intranet"},
+ }
+ _, err = tx.ContainerCreate(c1p2)
+ require.NoError(t, err)
+
+ containers, err := tx.ContainerList(db.ContainerFilter{})
+ require.NoError(t, err)
+
+ assert.Len(t, containers, 2)
+
+ assert.Equal(t, "blah", containers[0].Project)
+ assert.Equal(t, []string{"default"}, containers[0].Profiles)
+
+ assert.Equal(t, "test", containers[1].Project)
+ assert.Equal(t, []string{"intranet"}, containers[1].Profiles)
+}
+
func TestContainerListExpanded(t *testing.T) {
tx, cleanup := db.NewTestClusterTx(t)
defer cleanup()
diff --git a/lxd/db/profiles.mapper.go b/lxd/db/profiles.mapper.go
index 879731eaad..f9228cf81d 100644
--- a/lxd/db/profiles.mapper.go
+++ b/lxd/db/profiles.mapper.go
@@ -212,7 +212,7 @@ func (c *ClusterTx) ProfileList(filter ProfileFilter) ([]Profile, error) {
}
for i := range objects {
- value := configObjects[objects[i].Name]
+ value := configObjects[objects[i].Project][objects[i].Name]
if value == nil {
value = map[string]string{}
}
@@ -226,7 +226,7 @@ func (c *ClusterTx) ProfileList(filter ProfileFilter) ([]Profile, error) {
}
for i := range objects {
- value := devicesObjects[objects[i].Name]
+ value := devicesObjects[objects[i].Project][objects[i].Name]
if value == nil {
value = map[string]map[string]string{}
}
@@ -240,7 +240,7 @@ func (c *ClusterTx) ProfileList(filter ProfileFilter) ([]Profile, error) {
}
for i := range objects {
- value := usedByObjects[objects[i].Name]
+ value := usedByObjects[objects[i].Project][objects[i].Name]
if value == nil {
value = []string{}
}
@@ -314,7 +314,7 @@ func (c *ClusterTx) ProfileID(project string, name string) (int64, error) {
}
// ProfileConfigRef returns entities used by profiles.
-func (c *ClusterTx) ProfileConfigRef(filter ProfileFilter) (map[string]map[string]string, error) {
+func (c *ClusterTx) ProfileConfigRef(filter ProfileFilter) (map[string]map[string]map[string]string, error) {
// Result slice.
objects := make([]struct {
Project string
@@ -375,15 +375,21 @@ func (c *ClusterTx) ProfileConfigRef(filter ProfileFilter) (map[string]map[strin
}
// Build index by primary name.
- index := map[string]map[string]string{}
+ index := map[string]map[string]map[string]string{}
for _, object := range objects {
- item, ok := index[object.Name]
+ _, ok := index[object.Project]
+ if !ok {
+ subIndex := map[string]map[string]string{}
+ index[object.Project] = subIndex
+ }
+
+ item, ok := index[object.Project][object.Name]
if !ok {
item = map[string]string{}
}
- index[object.Name] = item
+ index[object.Project][object.Name] = item
item[object.Key] = object.Value
}
@@ -391,7 +397,7 @@ func (c *ClusterTx) ProfileConfigRef(filter ProfileFilter) (map[string]map[strin
}
// ProfileDevicesRef returns entities used by profiles.
-func (c *ClusterTx) ProfileDevicesRef(filter ProfileFilter) (map[string]map[string]map[string]string, error) {
+func (c *ClusterTx) ProfileDevicesRef(filter ProfileFilter) (map[string]map[string]map[string]map[string]string, error) {
// Result slice.
objects := make([]struct {
Project string
@@ -458,15 +464,21 @@ func (c *ClusterTx) ProfileDevicesRef(filter ProfileFilter) (map[string]map[stri
}
// Build index by primary name.
- index := map[string]map[string]map[string]string{}
+ index := map[string]map[string]map[string]map[string]string{}
for _, object := range objects {
- item, ok := index[object.Name]
+ _, ok := index[object.Project]
+ if !ok {
+ subIndex := map[string]map[string]map[string]string{}
+ index[object.Project] = subIndex
+ }
+
+ item, ok := index[object.Project][object.Name]
if !ok {
item = map[string]map[string]string{}
}
- index[object.Name] = item
+ index[object.Project][object.Name] = item
config, ok := item[object.Device]
if !ok {
// First time we see this device, let's int the config
@@ -489,7 +501,7 @@ func (c *ClusterTx) ProfileDevicesRef(filter ProfileFilter) (map[string]map[stri
}
// ProfileUsedByRef returns entities used by profiles.
-func (c *ClusterTx) ProfileUsedByRef(filter ProfileFilter) (map[string][]string, error) {
+func (c *ClusterTx) ProfileUsedByRef(filter ProfileFilter) (map[string]map[string][]string, error) {
// Result slice.
objects := make([]struct {
Project string
@@ -547,15 +559,21 @@ func (c *ClusterTx) ProfileUsedByRef(filter ProfileFilter) (map[string][]string,
}
// Build index by primary name.
- index := map[string][]string{}
+ index := map[string]map[string][]string{}
for _, object := range objects {
- item, ok := index[object.Name]
+ _, ok := index[object.Project]
+ if !ok {
+ subIndex := map[string][]string{}
+ index[object.Project] = subIndex
+ }
+
+ item, ok := index[object.Project][object.Name]
if !ok {
item = []string{}
}
- index[object.Name] = append(item, object.Value)
+ index[object.Project][object.Name] = append(item, object.Value)
}
return index, nil
diff --git a/shared/generate/db/lex.go b/shared/generate/db/lex.go
index 1e2314da21..f3868c9fbc 100644
--- a/shared/generate/db/lex.go
+++ b/shared/generate/db/lex.go
@@ -85,3 +85,14 @@ func destFunc(slice string, typ string, fields []*Field) string {
return f
}
+
+// Return an index type of the form "map[string]map[string]...<typ>", with one
+// level of indexing for each given field.
+func indexType(fields []*Field, typ string) string {
+ index := ""
+ for _ = range fields {
+ index += "map[string]"
+ }
+ index += typ
+ return index
+}
diff --git a/shared/generate/db/method.go b/shared/generate/db/method.go
index dc0ca3b368..f3e6530f94 100644
--- a/shared/generate/db/method.go
+++ b/shared/generate/db/method.go
@@ -348,8 +348,12 @@ func (m *Method) ref(buf *file.Buffer) error {
comment := fmt.Sprintf("returns entities used by %s.", lex.Plural(m.entity))
+ // The type of the returned index takes into account composite natural
+ // keys.
+ indexTyp := indexType(nk, retTyp)
+
args := fmt.Sprintf("filter %s", entityFilter(m.entity))
- rets := fmt.Sprintf("(map[string]%s, error)", retTyp)
+ rets := fmt.Sprintf("(%s, error)", indexTyp)
m.begin(buf, comment, args, rets)
defer m.end(buf)
@@ -419,24 +423,37 @@ func (m *Method) ref(buf *file.Buffer) error {
buf.L(" return nil, errors.Wrap(err, \"Failed to fetch %s ref for %s\")", typ, lex.Plural(m.entity))
buf.L("}")
buf.N()
- buf.L("// Build index by primary name.")
- // TODO: properly account for composite natural keys
- buf.L("index := map[string]%s{}", retTyp)
+ buf.L("// Build index by primary name.")
+ buf.L("index := %s{}", indexTyp)
buf.N()
buf.L("for _, object := range objects {")
- buf.L(" item, ok := index[object.%s]", nk[len(nk)-1].Name)
+ needle := ""
+ for i, key := range nk[:len(nk)-1] {
+ needle += fmt.Sprintf("[object.%s]", key.Name)
+
+ subIndexTyp := indexType(nk[i+1:], retTyp)
+ buf.L(" _, ok := index%s", needle)
+ buf.L(" if !ok {")
+ buf.L(" subIndex := %s{}", subIndexTyp)
+ buf.L(" index%s = subIndex", needle)
+ buf.L(" }")
+ buf.N()
+ }
+
+ needle += fmt.Sprintf("[object.%s]", nk[len(nk)-1].Name)
+ buf.L(" item, ok := index%s", needle)
buf.L(" if !ok {")
buf.L(" item = %s{}", retTyp)
buf.L(" }")
buf.N()
if field.Type.Code == TypeSlice && IsColumnType(typ) {
- buf.L(" index[object.%s] = append(item, object.Value)", nk[len(nk)-1].Name)
+ buf.L(" index%s = append(item, object.Value)", needle)
} else if field.Type.Code == TypeMap && field.Type.Name == "map[string]string" {
- buf.L(" index[object.%s] = item", nk[len(nk)-1].Name)
+ buf.L(" index%s = item", needle)
buf.L(" item[object.Key] = object.Value")
} else if field.Type.Code == TypeMap && field.Type.Name == "map[string]map[string]string" {
- buf.L(" index[object.%s] = item", nk[len(nk)-1].Name)
+ buf.L(" index%s = item", needle)
buf.L(" config, ok := item[object.Device]")
buf.L(" if !ok {")
buf.L(" // First time we see this device, let's int the config")
@@ -470,6 +487,11 @@ func (m *Method) fillSliceReferenceField(buf *file.Buffer, nk []*Field, field *F
objectsVar := fmt.Sprintf("%sObjects", lex.Minuscule(field.Name))
methodName := fmt.Sprintf("%s%sRef", lex.Capital(m.entity), field.Name)
+ needle := ""
+ for _, key := range nk {
+ needle += fmt.Sprintf("[objects[i].%s]", key.Name)
+ }
+
buf.L("// Fill field %s.", field.Name)
buf.L("%s, err := c.%s(filter)", objectsVar, methodName)
buf.L("if err != nil {")
@@ -477,7 +499,7 @@ func (m *Method) fillSliceReferenceField(buf *file.Buffer, nk []*Field, field *F
buf.L("}")
buf.N()
buf.L("for i := range objects {")
- buf.L(" value := %s[objects[i].%s]", objectsVar, nk[len(nk)-1].Name)
+ buf.L(" value := %s%s", objectsVar, needle)
buf.L(" if value == nil {")
buf.L(" value = %s{}", field.Type.Name)
buf.L(" }")
More information about the lxc-devel
mailing list