I'm using golang and gorm to talk to a MySQL database.
I have a table with release metadata:
type OSType string
const (
Windows OSType = "windows"
Mac OSType = "mac"
)
type AgentMetadata struct {
Version string `gorm:"primary_key"`
OS OSType `gorm:"primary_key" sql:"type:ENUM('windows','mac')"`
Name string `sql:"not null"`
Description string `sql:"not null"`
ReleaseNotesUrl string `sql:"not null"`
UpdateTime time.Time `sql:"DEFAULT:current_timestamp"`
}
The releases are identified by a composite key - the OS and Version (number).
I have another table which defines the default version that clients should download (by OS):
type GlobalDefault struct {
OS OSType `gorm:"primary_key" sql:"type:ENUM('windows','mac')"`
Version string
AgentMetadata AgentMetadata
UpdateTime time.Time `sql:"DEFAULT:current_timestamp"`
}
What I want is to define two foreign keys from GlobalDefault to AgentMetadata (the pair OS and Version) and I want to be able to query the GlobalDefault table by its key OS and to get back a data structure which already contains the full AgentMetadata.
After a very long time and reading lots of documentatin, SO questions and code samples I tried to do the following:
func (repository *AgentRepository)GetGlobalDefault(os OSType) (error, AgentMetadata) {
gd := GlobalDefault{ OS:os }
result := AgentMetadata{}
return repository.connection.Find(&gd).Related(&result, "OS", "Version").Error, result
}
This "worked" in the sense that it really got the result filled up with AgentMetadata. However, it was not the correct metadata.
In my test I added two metadata records and one default:
And when I called err, queryResult := ar.GetGlobalDefault(defaultAgent.OS)
instead of getting the 1.2.3 version metadata, I got the 1.2.3.1 metadata.
Indeed, when I turned on the gorm logs I saw that it ran the query:
[2017-07-15 17:51:50] [276.74ms] SELECT * FROM
global_defaults
WHEREglobal_defaults
.os
= 'windows'[2017-07-15 17:51:50] [276.55ms] SELECT * FROM
agent_metadata
WHERE (os
= 'windows')
First, it ignored the fact that I have a composite key in the agent_metadata table, and second, instead of doing a single query with a join, it made two queries (which is really a waste of time).
Another thing that bothers me is that I had to explicitly specify the foreign key names, whereas according to the documentation it seems that specifiying them is not needed or at least can be achieved by adding a tag:
type GlobalDefault struct {
OS OSType `gorm:"primary_key" sql:"type:ENUM('windows','mac')"`
Version string
AgentMetadata AgentMetadata `gorm:"ForeignKey:OS;AssociationForeignKey:OS"`
UpdateTime time.Time `sql:"DEFAULT:current_timestamp"`
}
Here I only added a tag for the OS column, however, I tried concatenating the foreign key names and neither option seemed to have an effect on the result. Without explicitly specifying the foreign key names in the API, the related data would just not be read. Having the pass the names to the query means that my DB mapping is not consolidated in a single place and I don't like that.
Can my scenario be solved? Can I have a composite foreign key? Can I specify all ORM properties in a single place? How can I make gorm create foreign keys in the DB (I noticed that the schema is created without foreign keys from GlobalDefault to AgentMetadata)?