Skip to content

Relations

Derevtsov Konstantin edited this page Jul 28, 2020 · 15 revisions

Relations between entities are similar to those in regular GO code. Structures use references to other structures or collections of structures. In GO code for single relation, you use *someStruct or []*someStruct - if struct has many related structures. In d3 entities, you must use some not default containers, *Cell instead of a pointer to struct and *Collection instead of a slice of pointers. The reason for this explains below. Currently, d3 has three relation types.

  • one-to-one - use this relation if the field may contain only one dependent entity.
  • one-to-many - use this relation if the field may contain many dependent entities, and these entities belong to only one owner.
  • many-to-many - use this relation if field may contain many dependent entities, and dependent entities can be controlled not only one owner.

Eager and lazy relations

All relations can be two types:

  • eager - means that d3 fetch dependent entities together with the owner entity.
  • lazy - means that d3 fetch dependent entities when to call one of container (Cell or Collection) methods. In other words, the fetch will be made "upon request". To select a type, use the "type" setting in the mapping tag. Example
    Profile *entity.Cell  `d3:"one_to_one:<target_entity:mypkg/Profile,join_on:profile_id>,type:lazy"`

Fetch API

Eager relation has one problem - query for fetching dependent entities is not part of main query (it is separate select). If you want fetch dependent entities together with owner entity in one query (using Join) use With method of query builder. Example of usage:

query := profileRepository.MakeQuery()
if err := q.AndWhere("profile.id = ?", 1).With("myPkg/Photo"); err != nil {
    log.Fatal(err)
}
profileWithPhotos, err := profileRepository.FindOne(ctx, query)

Relation mapping

Owner and dependent side

Before explaining specific relations, you need to introduce the concept of the owner and dependent sides. The owner side is the entity whose field is a container with dependent entities. And dependent entity - mean an entity that join to owner entity by relation mapping.

One-to-one
import (
    "database/sql"
    "github.com/godzie44/d3/orm/entity"
)

//d3:entity
//d3_table:user
type User struct {
    ID      sql.NullInt32 `d3:"pk:auto"`
    Profile *entity.Cell  `d3:"one_to_one:<target_entity:Profile,join_on:profile_id,delete:cascade>,type:lazy"`
}

//d3:entity
//d3_table:profile
type Profile struct {
    ID           int32        `d3:"pk:auto"`
    Description  string
}

In this example, user entity has one profile entity. For creating one-to-one relation:

  • use *entity.Cell as field type.
  • use one_to_one D3 configuration subtag with parameters:
    • target_entity - required, full path to dependent entity, or, if owner and dependent entities in same package, name of dependent entity.
    • join_on - required, column name in an owner entity table for linking to dependent entity pk.
    • delete - optional, use "delete:cascade" for delete dependent entity when owner entity is deleting.
  • select relation type by setting tag "type". Can be lazy (by default) or eager.

Generated schema:

CREATE TABLE IF NOT EXISTS user(
    id SERIAL PRIMARY KEY,
    profile_id INTEGER NOT NULL
);

CREATE TABLE IF NOT EXISTS profile(
    id SERIAL PRIMARY KEY,
    description TEXT NOT NULL
);
One to many
import (
    "database/sql"
    "github.com/godzie44/d3/orm/entity"
)

//d3:entity
//d3_table:profile
type Profile struct {
    ID      int32        `d3:"pk:auto"`
    Photos  *entity.Collection `d3:"one_to_many:<target_entity:Photo,join_on:profile_id,delete:nullable>,type:eager"`
}

//d3:entity
//d3_table:photo
type Photo struct {
    ID   sql.NullInt32 `d3:"pk:auto"`
    link string
}

In this example, profile entity has many photo entities. In addition, each photo belongs to only one profile. For creating one-to-many relation:

  • use *entity.Collection as field type.
  • use one_to_many D3 configuration subtag with parameters:
    • target_entity - required, full path to dependent entity, or, if owner and dependent entities in same package, name of dependent entity.
    • join_on - required, column name in related entity table for linking to owner entity pk.
    • delete - optional, use "delete:cascade" for delete related entity when owner entity is deleting or use "delete:cascade" for set join_on field to null.
  • select relation type by setting tag "type". Can be lazy (by default) or eager.

Generated schema:

CREATE TABLE IF NOT EXISTS profile(
    id SERIAL PRIMARY KEY
);

CREATE TABLE IF NOT EXISTS photo(
    id SERIAL PRIMARY KEY,
    link TEXT NOT NULL,
    profile_id INTEGER
);
Many to many
import (
    "database/sql"
    "github.com/godzie44/d3/orm/entity"
)

//d3:entity
//d3_table:book
type Book struct {
    ID       int32        `d3:"pk:auto"`
    Authors  *entity.Collection `d3:"many_to_many:<target_entity:Author,join_on:book_id,reference_on:author_id,join_table:book_author_p>,type:lazy"`
}

//d3:entity
//d3_table:author
type Author struct {
    ID   sql.NullInt32 `d3:"pk:auto"`
}

In this example, book entity has many author entities. However, two different books can be written by the same author. For creating many-to-many relation:

  • use *entity.Collection as field type.
  • use many_to_many D3 configuration subtag with parameters:
    • target_entity - required, the full path to the dependent entity, or, if owner and dependent entities in same package, name of dependent entity.
    • join_on - required, column name where owner entity pk will be stored.
    • reference_on - required, column name where dependent entity pk will be stored.
    • join_table - required, name of the table for persists relation between entities (pk pairs).
    • delete - optional, use "delete:cascade" for delete dependent entity when owner entity is deleting. By default - deleted only record in join_table.
  • select relation type by setting tag "type". Can be lazy (by default) or eager.

Generated schema:

CREATE TABLE IF NOT EXISTS book(
    id SERIAL PRIMARY KEY
);

CREATE TABLE IF NOT EXISTS author(
    id SERIAL PRIMARY KEY
);

CREATE TABLE IF NOT EXISTS book_author_p(
    book_id INTEGER NOT NULL,
    author_id INTEGER NOT NULL,
    PRIMARY KEY (book_id,author_id)
);
Clone this wiki locally