[lxc-devel] [lxd/master] Database logic cleanup part 2

freeekanayaka on Github lxc-bot at linuxcontainers.org
Tue May 19 12:10:40 UTC 2020


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 301 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20200519/241c363b/attachment.bin>
-------------- next part --------------
From 72b248177ce376d598037f99a485b0bfab919fd8 Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanayaka at canonical.com>
Date: Mon, 11 May 2020 11:19:21 +0100
Subject: [PATCH 1/8] lxd/db: Change UpdateCertificate to RenameCertificate
 (only renaming supported)

Signed-off-by: Free Ekanayaka <free.ekanayaka at canonical.com>
---
 lxd/certificates.go           |  2 +-
 lxd/db/certificates.go        |  9 +++++----
 lxd/db/certificates.mapper.go | 22 ++++++++++++++++++++++
 3 files changed, 28 insertions(+), 5 deletions(-)

diff --git a/lxd/certificates.go b/lxd/certificates.go
index fa817c1ee2..917f5d1178 100644
--- a/lxd/certificates.go
+++ b/lxd/certificates.go
@@ -322,7 +322,7 @@ func doCertificateUpdate(d *Daemon, fingerprint string, req api.CertificatePut)
 		return response.BadRequest(fmt.Errorf("Unknown request type %s", req.Type))
 	}
 
-	err := d.cluster.UpdateCertificate(fingerprint, req.Name, 1)
+	err := d.cluster.RenameCertificate(fingerprint, req.Name)
 	if err != nil {
 		return response.SmartError(err)
 	}
diff --git a/lxd/db/certificates.go b/lxd/db/certificates.go
index cfefaa773e..5176ceb29c 100644
--- a/lxd/db/certificates.go
+++ b/lxd/db/certificates.go
@@ -12,6 +12,7 @@ package db
 //go:generate mapper stmt -p db -e certificate id
 //go:generate mapper stmt -p db -e certificate create struct=Certificate
 //go:generate mapper stmt -p db -e certificate delete
+//go:generate mapper stmt -p db -e certificate rename
 //
 //go:generate mapper method -p db -e certificate List
 //go:generate mapper method -p db -e certificate Get
@@ -19,6 +20,7 @@ package db
 //go:generate mapper method -p db -e certificate Exists struct=Certificate
 //go:generate mapper method -p db -e certificate Create struct=Certificate
 //go:generate mapper method -p db -e certificate Delete
+//go:generate mapper method -p db -e certificate Rename
 
 // Certificate is here to pass the certificates content
 // from the database around
@@ -66,11 +68,10 @@ func (c *Cluster) DeleteCertificate(fingerprint string) error {
 	return err
 }
 
-// UpdateCertificate updates the certificate with the given fingerprint.
-func (c *Cluster) UpdateCertificate(fingerprint string, certName string, certType int) error {
+// RenameCertificate updates a certificate's name.
+func (c *Cluster) RenameCertificate(fingerprint string, name string) error {
 	err := c.Transaction(func(tx *ClusterTx) error {
-		_, err := tx.tx.Exec("UPDATE certificates SET name=?, type=? WHERE fingerprint=?", certName, certType, fingerprint)
-		return err
+		return c.RenameCertificate(fingerprint, name)
 	})
 	return err
 }
diff --git a/lxd/db/certificates.mapper.go b/lxd/db/certificates.mapper.go
index 4f6e718358..c682094754 100644
--- a/lxd/db/certificates.mapper.go
+++ b/lxd/db/certificates.mapper.go
@@ -41,6 +41,10 @@ var certificateDelete = cluster.RegisterStmt(`
 DELETE FROM certificates WHERE fingerprint = ?
 `)
 
+var certificateRename = cluster.RegisterStmt(`
+UPDATE certificates SET name = ? WHERE fingerprint = ?
+`)
+
 // GetCertificates returns all available certificates.
 func (c *ClusterTx) GetCertificates(filter CertificateFilter) ([]Certificate, error) {
 	// Result slice.
@@ -203,3 +207,21 @@ func (c *ClusterTx) DeleteCertificate(fingerprint string) error {
 
 	return nil
 }
+
+// RenameCertificate renames the certificate matching the given key parameters.
+func (c *ClusterTx) RenameCertificate(fingerprint string, to string) error {
+	stmt := c.stmt(certificateRename)
+	result, err := stmt.Exec(to, fingerprint)
+	if err != nil {
+		return errors.Wrap(err, "Rename certificate")
+	}
+
+	n, err := result.RowsAffected()
+	if err != nil {
+		return errors.Wrap(err, "Fetch affected rows")
+	}
+	if n != 1 {
+		return fmt.Errorf("Query affected %d rows instead of 1", n)
+	}
+	return nil
+}

From 1bb5dde03ee8174197bdea33dfe6446cb66e0f7e Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanayaka at canonical.com>
Date: Mon, 11 May 2020 11:23:28 +0100
Subject: [PATCH 2/8] lxd/db: Rename containers.go to instances.go

Signed-off-by: Free Ekanayaka <free.ekanayaka at canonical.com>
---
 lxd/db/{containers.go => instances.go}                         | 0
 lxd/db/{containers_export_test.go => instances_export_test.go} | 0
 lxd/db/{containers_test.go => instances_test.go}               | 0
 3 files changed, 0 insertions(+), 0 deletions(-)
 rename lxd/db/{containers.go => instances.go} (100%)
 rename lxd/db/{containers_export_test.go => instances_export_test.go} (100%)
 rename lxd/db/{containers_test.go => instances_test.go} (100%)

diff --git a/lxd/db/containers.go b/lxd/db/instances.go
similarity index 100%
rename from lxd/db/containers.go
rename to lxd/db/instances.go
diff --git a/lxd/db/containers_export_test.go b/lxd/db/instances_export_test.go
similarity index 100%
rename from lxd/db/containers_export_test.go
rename to lxd/db/instances_export_test.go
diff --git a/lxd/db/containers_test.go b/lxd/db/instances_test.go
similarity index 100%
rename from lxd/db/containers_test.go
rename to lxd/db/instances_test.go

From 3ebe673c97af596794cc0a3cfa206902576da160 Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanayaka at canonical.com>
Date: Mon, 11 May 2020 14:21:32 +0100
Subject: [PATCH 3/8] shared/generate/db: Statement for deleting references
 (config and devices)

Signed-off-by: Free Ekanayaka <free.ekanayaka at canonical.com>
---
 shared/generate/db/stmt.go | 102 ++++++++++++++++++++++++++-----------
 1 file changed, 72 insertions(+), 30 deletions(-)

diff --git a/shared/generate/db/stmt.go b/shared/generate/db/stmt.go
index 08f2d39adb..227733a655 100644
--- a/shared/generate/db/stmt.go
+++ b/shared/generate/db/stmt.go
@@ -50,6 +50,10 @@ func (s *Stmt) Generate(buf *file.Buffer) error {
 		return s.createRef(buf)
 	}
 
+	if strings.HasPrefix(s.kind, "delete") && strings.HasSuffix(s.kind, "-ref") {
+		return s.deleteRef(buf)
+	}
+
 	if strings.HasSuffix(s.kind, "-ref") || strings.Contains(s.kind, "-ref-by-") {
 		return s.ref(buf)
 	}
@@ -417,7 +421,6 @@ func (s *Stmt) createRef(buf *file.Buffer) error {
 
 		sql := fmt.Sprintf(stmts["create"], table, columns, params)
 		s.register(buf, sql)
-
 	} else if field.Type.Name == "map[string]map[string]string" {
 		// Assume this is a devices table
 		columns := fmt.Sprintf("%s_id, name, type", s.entity)
@@ -443,35 +446,7 @@ func (s *Stmt) id(buf *file.Buffer) error {
 	if err != nil {
 		return errors.Wrap(err, "Parse entity struct")
 	}
-	nk := mapping.NaturalKey()
-	criteria := ""
-	for i, field := range nk {
-		if i > 0 {
-			criteria += " AND "
-		}
-
-		var column string
-		if field.IsScalar() {
-			column = field.Config.Get("join")
-		} else {
-			column = mapping.FieldColumnName(field.Name)
-		}
-
-		criteria += fmt.Sprintf("%s = ?", column)
-	}
-
-	table := entityTable(s.entity)
-	for _, field := range mapping.ScalarFields() {
-		join := field.Config.Get("join")
-		right := strings.Split(join, ".")[0]
-		via := entityTable(s.entity)
-		if field.Config.Get("via") != "" {
-			via = entityTable(field.Config.Get("via"))
-		}
-		table += fmt.Sprintf(" JOIN %s ON %s.%s_id = %s.id", right, via, lex.Singular(right), right)
-	}
-
-	sql := fmt.Sprintf(stmts[s.kind], entityTable(s.entity), table, criteria)
+	sql := naturalKeySelect(s.entity, mapping)
 	s.register(buf, sql)
 
 	return nil
@@ -558,6 +533,38 @@ func (s *Stmt) delete(buf *file.Buffer) error {
 	return nil
 }
 
+func (s *Stmt) deleteRef(buf *file.Buffer) error {
+	// Base snake-case name of the references (e.g. "used-by-ref" -> "used_by")
+	name := strings.Replace(s.kind[len("create-"):strings.Index(s.kind, "-ref")], "-", "_", -1)
+
+	// Field name of the reference
+	fieldName := lex.Camel(name)
+
+	// Table name where reference objects can be fetched.
+	table := fmt.Sprintf("%s_%s", entityTable(s.entity), name)
+
+	mapping, err := Parse(s.packages[s.pkg], lex.Camel(s.entity))
+	if err != nil {
+		return err
+	}
+
+	field := mapping.FieldByName(fieldName)
+	if field == nil {
+		return fmt.Errorf("Entity %s has no field named %s", s.entity, fieldName)
+	}
+
+	where := fmt.Sprintf("%s_id = ?", s.entity)
+
+	if field.Type.Name == "map[string]string" || field.Type.Name == "map[string]map[string]string" {
+		// Assume this is a config or devices table
+		sql := fmt.Sprintf(stmts["delete"], table, where)
+		s.register(buf, sql)
+
+	}
+
+	return nil
+}
+
 // Return a where clause that filters an entity by natural key.
 func naturalKeyWhere(mapping *Mapping) string {
 	nk := mapping.NaturalKey()
@@ -605,6 +612,41 @@ func naturalKeyWhere(mapping *Mapping) string {
 	return strings.Join(where, " AND ")
 }
 
+// Return a select statement that returns the ID of an entity given its natural key.
+func naturalKeySelect(entity string, mapping *Mapping) string {
+	nk := mapping.NaturalKey()
+	criteria := ""
+	for i, field := range nk {
+		if i > 0 {
+			criteria += " AND "
+		}
+
+		var column string
+		if field.IsScalar() {
+			column = field.Config.Get("join")
+		} else {
+			column = mapping.FieldColumnName(field.Name)
+		}
+
+		criteria += fmt.Sprintf("%s = ?", column)
+	}
+
+	table := entityTable(entity)
+	for _, field := range mapping.ScalarFields() {
+		join := field.Config.Get("join")
+		right := strings.Split(join, ".")[0]
+		via := entityTable(entity)
+		if field.Config.Get("via") != "" {
+			via = entityTable(field.Config.Get("via"))
+		}
+		table += fmt.Sprintf(" JOIN %s ON %s.%s_id = %s.id", right, via, lex.Singular(right), right)
+	}
+
+	sql := fmt.Sprintf(stmts["id"], entityTable(entity), table, criteria)
+
+	return sql
+}
+
 // Output a line of code that registers the given statement and declares the
 // associated statement code global variable.
 func (s *Stmt) register(buf *file.Buffer, sql string, filters ...string) {

From 6451a292bd71d0739a565e95638b488e90c42d4e Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanayaka at canonical.com>
Date: Tue, 19 May 2020 12:04:28 +0100
Subject: [PATCH 4/8] lxd/db: Generate delete stements for profile config and
 devices

Signed-off-by: Free Ekanayaka <free.ekanayaka at canonical.com>
---
 lxd/db/profiles.go        | 2 ++
 lxd/db/profiles.mapper.go | 8 ++++++++
 2 files changed, 10 insertions(+)

diff --git a/lxd/db/profiles.go b/lxd/db/profiles.go
index 19e97787c2..8bd37c96b5 100644
--- a/lxd/db/profiles.go
+++ b/lxd/db/profiles.go
@@ -37,6 +37,8 @@ import (
 //go:generate mapper stmt -p db -e profile create-devices-ref
 //go:generate mapper stmt -p db -e profile rename
 //go:generate mapper stmt -p db -e profile delete
+//go:generate mapper stmt -p db -e profile delete-config-ref
+//go:generate mapper stmt -p db -e profile delete-devices-ref
 //
 //go:generate mapper method -p db -e profile URIs
 //go:generate mapper method -p db -e profile List
diff --git a/lxd/db/profiles.mapper.go b/lxd/db/profiles.mapper.go
index f71d742c42..df5c752a63 100644
--- a/lxd/db/profiles.mapper.go
+++ b/lxd/db/profiles.mapper.go
@@ -119,6 +119,14 @@ var profileDelete = cluster.RegisterStmt(`
 DELETE FROM profiles WHERE project_id = (SELECT projects.id FROM projects WHERE projects.name = ?) AND name = ?
 `)
 
+var profileDeleteConfigRef = cluster.RegisterStmt(`
+DELETE FROM profiles_config WHERE profile_id = ?
+`)
+
+var profileDeleteDevicesRef = cluster.RegisterStmt(`
+DELETE FROM profiles_devices WHERE profile_id = ?
+`)
+
 // GetProfileURIs returns all available profile URIs.
 func (c *ClusterTx) GetProfileURIs(filter ProfileFilter) ([]string, error) {
 	// Check which filter criteria are active.

From b5ee104c37eb5c692f30d3ab8a28cc321bdc7859 Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanayaka at canonical.com>
Date: Tue, 19 May 2020 12:24:33 +0100
Subject: [PATCH 5/8] shared/generate/db: update statement: take ID instead of
 natural key

Signed-off-by: Free Ekanayaka <free.ekanayaka at canonical.com>
---
 lxd/db/projects.go         | 12 ++++++------
 lxd/db/projects.mapper.go  |  2 +-
 shared/generate/db/stmt.go | 21 +--------------------
 3 files changed, 8 insertions(+), 27 deletions(-)

diff --git a/lxd/db/projects.go b/lxd/db/projects.go
index 49457c1533..295ec8fde1 100644
--- a/lxd/db/projects.go
+++ b/lxd/db/projects.go
@@ -132,8 +132,13 @@ func (c *ClusterTx) ProjectHasImages(name string) (bool, error) {
 
 // UpdateProject updates the project matching the given key parameters.
 func (c *ClusterTx) UpdateProject(name string, object api.ProjectPut) error {
+	id, err := c.GetProjectID(name)
+	if err != nil {
+		return errors.Wrap(err, "Fetch project ID")
+	}
+
 	stmt := c.stmt(projectUpdate)
-	result, err := stmt.Exec(object.Description, name)
+	result, err := stmt.Exec(object.Description, id)
 	if err != nil {
 		return errors.Wrap(err, "Update project")
 	}
@@ -146,11 +151,6 @@ func (c *ClusterTx) UpdateProject(name string, object api.ProjectPut) error {
 		return fmt.Errorf("Query updated %d rows instead of 1", n)
 	}
 
-	id, err := c.GetProjectID(name)
-	if err != nil {
-		return errors.Wrap(err, "Fetch project ID")
-	}
-
 	// Clear config.
 	_, err = c.tx.Exec(`
 DELETE FROM projects_config WHERE projects_config.project_id = ?
diff --git a/lxd/db/projects.mapper.go b/lxd/db/projects.mapper.go
index 2e8fd787e6..3b763721f3 100644
--- a/lxd/db/projects.mapper.go
+++ b/lxd/db/projects.mapper.go
@@ -77,7 +77,7 @@ UPDATE projects SET name = ? WHERE name = ?
 var projectUpdate = cluster.RegisterStmt(`
 UPDATE projects
   SET description = ?
- WHERE name = ?
+ WHERE id = ?
 `)
 
 var projectDelete = cluster.RegisterStmt(`
diff --git a/shared/generate/db/stmt.go b/shared/generate/db/stmt.go
index 227733a655..db28faad47 100644
--- a/shared/generate/db/stmt.go
+++ b/shared/generate/db/stmt.go
@@ -492,28 +492,9 @@ func (s *Stmt) update(buf *file.Buffer) error {
 		}
 	}
 
-	mapping, err = Parse(s.packages[s.pkg], lex.Capital(s.entity))
-	if err != nil {
-		return errors.Wrap(err, "Parse entity struct")
-	}
-
-	nk := mapping.NaturalKey()
-	where := make([]string, len(nk))
-
-	for i, field := range nk {
-		if field.IsScalar() {
-			ref := lex.Snake(field.Name)
-			where[i] = fmt.Sprintf("%s_id = (SELECT id FROM %s WHERE name = ?)", ref, lex.Plural(ref))
-		} else {
-
-			where[i] = fmt.Sprintf("%s = ?", field.Column())
-		}
-
-	}
-
 	sql := fmt.Sprintf(
 		stmts[s.kind], entityTable(s.entity),
-		strings.Join(updates, ", "), strings.Join(where, ", "))
+		strings.Join(updates, ", "), "id = ?")
 	s.register(buf, sql)
 
 	return nil

From 4f4e9c8773706d045a85496fa2425b07ee112f67 Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanayaka at canonical.com>
Date: Tue, 19 May 2020 12:49:12 +0100
Subject: [PATCH 6/8] shared/generate/db: Handle config and devices in Update
 method

Signed-off-by: Free Ekanayaka <free.ekanayaka at canonical.com>
---
 shared/generate/db/method.go | 69 +++++++++++++++++++++++++++++++++++-
 1 file changed, 68 insertions(+), 1 deletion(-)

diff --git a/shared/generate/db/method.go b/shared/generate/db/method.go
index 0ddc903cd9..c0f6859aa0 100644
--- a/shared/generate/db/method.go
+++ b/shared/generate/db/method.go
@@ -811,8 +811,14 @@ func (m *Method) update(buf *file.Buffer) error {
 		params[i] = fmt.Sprintf("object.%s", field.Name)
 	}
 
+	//buf.L("id, err := c.Get%s(%s)", lex.Camel(m.entity), FieldArgs(nk))
+	buf.L("id, err := c.Get%sID(%s)", lex.Camel(m.entity), FieldParams(nk))
+	buf.L("if err != nil {")
+	buf.L("        return errors.Wrap(err, \"Get %s\")", m.entity)
+	buf.L("}")
+	buf.N()
 	buf.L("stmt := c.stmt(%s)", stmtCodeVar(m.entity, "update"))
-	buf.L("result, err := stmt.Exec(%s)", strings.Join(params, ", ")+", "+FieldParams(nk))
+	buf.L("result, err := stmt.Exec(%s)", strings.Join(params, ", ")+", id")
 	buf.L("if err != nil {")
 	buf.L("        return errors.Wrap(err, \"Update %s\")", m.entity)
 	buf.L("}")
@@ -825,6 +831,67 @@ func (m *Method) update(buf *file.Buffer) error {
 	buf.L("        return fmt.Errorf(\"Query updated %%d rows instead of 1\", n)")
 	buf.L("}")
 	buf.N()
+
+	fields = mapping.RefFields()
+	for _, field := range fields {
+		switch field.Type.Name {
+		case "map[string]string":
+			buf.L("// Delete current config. ")
+			buf.L("stmt = c.stmt(%s)", stmtCodeVar(m.entity, "deleteConfigRef"))
+			buf.L("_, err = stmt.Exec(id)")
+			buf.L("if err != nil {")
+			buf.L("        return errors.Wrap(err, \"Delete current config\")")
+			buf.L("}")
+			buf.N()
+			buf.L("// Insert config reference. ")
+			buf.L("stmt = c.stmt(%s)", stmtCodeVar(m.entity, "createConfigRef"))
+			buf.L("for key, value := range object.%s {", field.Name)
+			buf.L("        _, err := stmt.Exec(id, key, value)")
+			buf.L("        if err != nil {")
+			buf.L("                return errors.Wrap(err, \"Insert config for %s\")", m.entity)
+			buf.L("        }")
+			buf.L("}")
+			buf.N()
+		case "map[string]map[string]string":
+			buf.L("// Delete current devices. ")
+			buf.L("stmt = c.stmt(%s)", stmtCodeVar(m.entity, "deleteDevicesRef"))
+			buf.L("_, err = stmt.Exec(id)")
+			buf.L("if err != nil {")
+			buf.L("        return errors.Wrap(err, \"Delete current devices\")")
+			buf.L("}")
+			buf.N()
+			buf.N()
+			buf.L("// Insert devices reference. ")
+			buf.L("for name, config := range object.%s {", field.Name)
+			buf.L("        typ, ok := config[\"type\"]")
+			buf.L("        if !ok {")
+			buf.L("                return fmt.Errorf(\"No type for device %%s\", name)")
+			buf.L("        }")
+			buf.L("        typCode, err := dbDeviceTypeToInt(typ)")
+			buf.L("        if err != nil {")
+			buf.L("                return errors.Wrapf(err, \"Device type code for %%s\", typ)")
+			buf.L("        }")
+			buf.L("        stmt = c.stmt(%s)", stmtCodeVar(m.entity, "createDevicesRef"))
+			buf.L("        result, err := stmt.Exec(id, name, typCode)")
+			buf.L("        if err != nil {")
+			buf.L("                return errors.Wrapf(err, \"Insert device %%s\", name)")
+			buf.L("        }")
+			buf.L("        deviceID, err := result.LastInsertId()")
+			buf.L("        if err != nil {")
+			buf.L("                return errors.Wrap(err, \"Failed to fetch device ID\")")
+			buf.L("        }")
+			buf.L("        stmt = c.stmt(%s)", stmtCodeVar(m.entity, "createDevicesConfigRef"))
+			buf.L("        for key, value := range config {")
+			buf.L("                _, err := stmt.Exec(deviceID, key, value)")
+			buf.L("                if err != nil {")
+			buf.L("                        return errors.Wrap(err, \"Insert config for %s\")", m.entity)
+			buf.L("                }")
+			buf.L("        }")
+			buf.L("}")
+			buf.N()
+		}
+	}
+
 	buf.L("return nil")
 
 	return nil

From 3215afdffe7d8ada49cf5057d8ef2ca6615a466f Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanayaka at canonical.com>
Date: Tue, 19 May 2020 12:49:16 +0100
Subject: [PATCH 7/8] lxd/db: Generate Update method for profiles

Signed-off-by: Free Ekanayaka <free.ekanayaka at canonical.com>
---
 lxd/db/profiles.go        |  2 +
 lxd/db/profiles.mapper.go | 81 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 83 insertions(+)

diff --git a/lxd/db/profiles.go b/lxd/db/profiles.go
index 8bd37c96b5..b83ad849f8 100644
--- a/lxd/db/profiles.go
+++ b/lxd/db/profiles.go
@@ -39,6 +39,7 @@ import (
 //go:generate mapper stmt -p db -e profile delete
 //go:generate mapper stmt -p db -e profile delete-config-ref
 //go:generate mapper stmt -p db -e profile delete-devices-ref
+//go:generate mapper stmt -p db -e profile update struct=Profile
 //
 //go:generate mapper method -p db -e profile URIs
 //go:generate mapper method -p db -e profile List
@@ -51,6 +52,7 @@ import (
 //go:generate mapper method -p db -e profile Create struct=Profile
 //go:generate mapper method -p db -e profile Rename
 //go:generate mapper method -p db -e profile Delete
+//go:generate mapper method -p db -e profile Update struct=Profile
 
 // Profile is a value object holding db-related details about a profile.
 type Profile struct {
diff --git a/lxd/db/profiles.mapper.go b/lxd/db/profiles.mapper.go
index df5c752a63..c9970eb9b4 100644
--- a/lxd/db/profiles.mapper.go
+++ b/lxd/db/profiles.mapper.go
@@ -127,6 +127,12 @@ var profileDeleteDevicesRef = cluster.RegisterStmt(`
 DELETE FROM profiles_devices WHERE profile_id = ?
 `)
 
+var profileUpdate = cluster.RegisterStmt(`
+UPDATE profiles
+  SET project_id = (SELECT id FROM projects WHERE name = ?), name = ?, description = ?
+ WHERE id = ?
+`)
+
 // GetProfileURIs returns all available profile URIs.
 func (c *ClusterTx) GetProfileURIs(filter ProfileFilter) ([]string, error) {
 	// Check which filter criteria are active.
@@ -715,3 +721,78 @@ func (c *ClusterTx) DeleteProfile(project string, name string) error {
 
 	return nil
 }
+
+// UpdateProfile updates the profile matching the given key parameters.
+func (c *ClusterTx) UpdateProfile(project string, name string, object Profile) error {
+	id, err := c.GetProfileID(project, name)
+	if err != nil {
+		return errors.Wrap(err, "Get profile")
+	}
+
+	stmt := c.stmt(profileUpdate)
+	result, err := stmt.Exec(object.Project, object.Name, object.Description, id)
+	if err != nil {
+		return errors.Wrap(err, "Update profile")
+	}
+
+	n, err := result.RowsAffected()
+	if err != nil {
+		return errors.Wrap(err, "Fetch affected rows")
+	}
+	if n != 1 {
+		return fmt.Errorf("Query updated %d rows instead of 1", n)
+	}
+
+	// Delete current config.
+	stmt = c.stmt(profileDeleteConfigRef)
+	_, err = stmt.Exec(id)
+	if err != nil {
+		return errors.Wrap(err, "Delete current config")
+	}
+
+	// Insert config reference.
+	stmt = c.stmt(profileCreateConfigRef)
+	for key, value := range object.Config {
+		_, err := stmt.Exec(id, key, value)
+		if err != nil {
+			return errors.Wrap(err, "Insert config for profile")
+		}
+	}
+
+	// Delete current devices.
+	stmt = c.stmt(profileDeleteDevicesRef)
+	_, err = stmt.Exec(id)
+	if err != nil {
+		return errors.Wrap(err, "Delete current devices")
+	}
+
+	// Insert devices reference.
+	for name, config := range object.Devices {
+		typ, ok := config["type"]
+		if !ok {
+			return fmt.Errorf("No type for device %s", name)
+		}
+		typCode, err := dbDeviceTypeToInt(typ)
+		if err != nil {
+			return errors.Wrapf(err, "Device type code for %s", typ)
+		}
+		stmt = c.stmt(profileCreateDevicesRef)
+		result, err := stmt.Exec(id, name, typCode)
+		if err != nil {
+			return errors.Wrapf(err, "Insert device %s", name)
+		}
+		deviceID, err := result.LastInsertId()
+		if err != nil {
+			return errors.Wrap(err, "Failed to fetch device ID")
+		}
+		stmt = c.stmt(profileCreateDevicesConfigRef)
+		for key, value := range config {
+			_, err := stmt.Exec(deviceID, key, value)
+			if err != nil {
+				return errors.Wrap(err, "Insert config for profile")
+			}
+		}
+	}
+
+	return nil
+}

From 94b74aa66d2010f5657621b5611566d071f159a4 Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanayaka at canonical.com>
Date: Tue, 19 May 2020 13:04:08 +0100
Subject: [PATCH 8/8] lxd: Plug new UpdateProfile() db method into
 doProfileUpdate

Signed-off-by: Free Ekanayaka <free.ekanayaka at canonical.com>
---
 lxd/profiles_utils.go | 57 ++++++-------------------------------------
 1 file changed, 8 insertions(+), 49 deletions(-)

diff --git a/lxd/profiles_utils.go b/lxd/profiles_utils.go
index 4728c55601..672d584d10 100644
--- a/lxd/profiles_utils.go
+++ b/lxd/profiles_utils.go
@@ -2,10 +2,8 @@ package main
 
 import (
 	"fmt"
-	"reflect"
 
 	"github.com/lxc/lxd/lxd/db"
-	"github.com/lxc/lxd/lxd/db/query"
 	deviceConfig "github.com/lxc/lxd/lxd/device/config"
 	"github.com/lxc/lxd/lxd/instance"
 	"github.com/lxc/lxd/lxd/instance/instancetype"
@@ -78,53 +76,14 @@ func doProfileUpdate(d *Daemon, project, name string, id int64, profile *api.Pro
 	}
 
 	// Update the database
-	err = query.Retry(func() error {
-		tx, err := d.cluster.Begin()
-		if err != nil {
-			return err
-		}
-
-		if profile.Description != req.Description {
-			err = db.UpdateProfileDescription(tx, id, req.Description)
-			if err != nil {
-				tx.Rollback()
-				return err
-			}
-		}
-
-		// Optimize for description-only changes
-		if reflect.DeepEqual(profile.Config, req.Config) && reflect.DeepEqual(profile.Devices, req.Devices) {
-			err = db.TxCommit(tx)
-			if err != nil {
-				return err
-			}
-
-			return nil
-		}
-
-		err = db.ClearProfileConfig(tx, id)
-		if err != nil {
-			tx.Rollback()
-			return err
-		}
-
-		err = db.CreateProfileConfig(tx, id, req.Config)
-		if err != nil {
-			tx.Rollback()
-			return err
-		}
-
-		err = db.AddDevicesToEntity(tx, "profile", id, deviceConfig.NewDevices(req.Devices))
-		if err != nil {
-			tx.Rollback()
-			return err
-		}
-
-		err = db.TxCommit(tx)
-		if err != nil {
-			return err
-		}
-		return nil
+	err = d.cluster.Transaction(func(tx *db.ClusterTx) error {
+		return tx.UpdateProfile(project, name, db.Profile{
+			Project:     project,
+			Name:        name,
+			Description: req.Description,
+			Config:      req.Config,
+			Devices:     req.Devices,
+		})
 	})
 	if err != nil {
 		return err


More information about the lxc-devel mailing list