[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