Skip to main content

Struct mapping

Use Go structs and field tags to define a mapping between your Go application and database tables or collections.

type User struct {
Name `db:"name"`
}

add the omitempty option to struct fields that you don't want to send to the database if they don't have a value, like IDs that are set to auto-increment (or auto-generate) themselves:

// Person represents an item from the "people" collection.
type Person struct {
ID uint64 `db:"id,omitempty"` // Use `omitempty` for fields
// that are not to be sent by
// the adapter when empty.
Name string `db:"name"`
LastName string `db:"last_name"`
}

// Employee represents an item from the "employees" collection.
type Employee struct {
ID uint64 `db:"id,omitempty"` // Skip `id` column when zero.
FirstName sql.NullString `db:"first_name"`
LastName string `db:"last_name"`
}

You can provide different values in struct tags, including those used to map JSON values to fields:

type Person struct {
ID uint64 `db:"id,omitempty" json:"id"`
Name string `db:"name" json:"name"`
...
Password string `db:"password,omitempty" json:"-"`
}

Fields that don't have a db struct tag will be omitted from queries:

type Person struct {
...
Token string
}

Embedding structs​

Using the inline option you can embed structs into other structs. See this struct, for instance:

type Person struct {
FirstName string `db:"first_name"`
LastName string `db:"last_name"`
}

This is a common struct that can be shared with other structs which also need FirstName and LastName:

type Author struct {
ID int `db:"id,omitempty"`

Person `db:",inline"` // Embedded Person
}

type Employee struct {
ID int `db:"id,omitempty"`

Person `db:",inline"` // Embedded Person
}

See the following example: embedding the Person struct into Author and Employee

package main

import (
"fmt"
"log"

"github.com/upper/db/v4"
"github.com/upper/db/v4/adapter/postgresql"
)

// Person represents a person with a name.
type Person struct {
FirstName string `db:first_name`
LastName string `db:last_name`
}

// Author represents a person that is an author.
type Author struct {
ID int `db:"id"`
Person `db:",inline"`
}

// Employee represents a person that is an employee.
type Employee struct {
ID int `db:"id"`
Person `db:",inline"`
}

func Authors(sess db.Session) db.Collection {
return sess.Collection("authors")
}

func Employees(sess db.Session) db.Collection {
return sess.Collection("employees")
}

var settings = postgresql.ConnectionURL{
Database: booktown,
Host: demo.upper.io,
User: demouser,
Password: demop4ss,
}

func main() {
sess, err := postgresql.Open(settings)
if err != nil {
log.Fatal("Open: ", err)
}
defer sess.Close()

// Get and print the first 5 authors ordered by last name
res := Authors(sess).Find().OrderBy("last_name").Limit(5)

var authors []Author
if err := res.All(&authors); err != nil {
log.Fatal("All: ", err)
}

fmt.Println("Authors (5):")

for _, author := range authors {
fmt.Printf("Last Name: %s\tID: %d\n", author.LastName, author.ID)
}

fmt.Println("")

// Get and print the first 5 employees ordered by last name
res = Employees(sess).Find().OrderBy("last_name").Limit(5)

var employees []Author
if err := res.All(&employees); err != nil {
log.Fatal("All: ", err)
}

fmt.Println("Employees (5):")

for _, employee := range employees {
fmt.Printf("Last Name: %s\tID: %d\n", employee.LastName, employee.ID)
}
}

Solving mapping ambiguities on JOINs​

The previous example will work as long as you use the db:",inline" tag. You can even embed more than one struct into an other, but you should be careful with column ambiguities:

// Book that has ID.
type Book struct {
ID int `db:"id"` // Has an ID column.
Title string `db:"title"`
AuthorID int `db:"author_id"`
SubjectID int `db:"subject_id"`
}

// Author that has ID.
type Author struct {
ID int `db:"id"` // Also has an ID column.
LastName string `db:"last_name"`
FirstName string `db:"first_name"`
}

Embedding these two structs into a third one will cause a conflict of IDs, to solve this conflict you can add an extra book_id column mapping and use a book_id alias when querying for the book ID.

// BookAuthor
type BookAuthor struct {
// Both Author and Book have and ID column, we need this extra field to tell
// the difference between the ID of the book and the ID of the author.
BookID int `db:"book_id"`

Author `db:",inline"`
Book `db:",inline"`
}
package main

import (
"fmt"
"log"

"github.com/upper/db/v4"
"github.com/upper/db/v4/adapter/postgresql"
)

// Book represents a book.
type Book struct {
ID int `db:"id"`
Title string `db:"title"`
AuthorID int `db:"author_id"`
SubjectID int `db:"subject_id"`
}

// Author represents the author of a book.
type Author struct {
ID int `db:"id"`
LastName string `db:"last_name"`
FirstName string `db:"first_name"`
}

// BookAuthor represents join data from books and authors.
type BookAuthor struct {
// Both Author and Book have and ID column, we need this to tell the ID of
// the book from the ID of the author.
BookID int `db:"book_id"`
Author `db:",inline"`
Book `db:",inline"`
}

var settings = postgresql.ConnectionURL{
Database: booktown,
Host: demo.upper.io,
User: demouser,
Password: demop4ss,
}

func main() {
sess, err := postgresql.Open(settings)
if err != nil {
log.Fatal(err)
}
defer sess.Close()

req := sess.SQL().
Select(
"b.id AS book_id",
db.Raw("b."),
db.Raw("a."),
).From("books b").
Join("authors a").On("b.author_id = a.id").OrderBy("b.title")

var books []BookAuthor
if err := req.All(&books); err != nil {
log.Fatal(err)
}

for _, book := range books {
fmt.Printf("ID: %d\tAuthor: %s\t\tBook: %q\n", book.BookID, book.Author.LastName, book.Book.Title)
}
}