model

package
v0.5.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 12, 2022 License: MIT Imports: 12 Imported by: 2

Documentation

Overview

Package model allows Go structs to behave as database models.

While this package exports several types the only one you currently need to be concerned with is type Models. All of the examples in this package use a global instance of Models defined in the examples subpackage; you may refer to that global instance for an instantiation example.

Note that in the examples for this package when you see examples.Models or examples.Connect() it is referring the examples subdirectory for this package and NOT the subdirectory for sqlh (i.e. both sqlh and sqlh/model have an examples subdirectory.)

Index

Examples

Constants

This section is empty.

Variables

View Source
var ErrUnsupported error = errors.New("unsupported")

Functions

This section is empty.

Types

type Model

type Model struct {
	// Table is the related database table.
	Table schema.Table

	// Statements are the SQL database statements.
	Statements statements.Table

	// SaveMode is set during model registration and inspected during Models.Save
	// to determine which of Insert, Update, or Upsert operations to use.
	//
	// SaveMode=InsertOrUpdate means InsertUpdatePaths is a non-empty slice of
	// key field traversal information.  The key fields are examined and if
	// any are non-zero values then Update will be used otherwise Insert.
	SaveMode          SaveMode
	InsertUpdatePaths []path.ReflectPath

	// Mapping is the column to struct field mapping.
	Mapping set.Mapping
}

Model relates a Go type to its Table.

func (*Model) BindQuery

func (me *Model) BindQuery(mapper *set.Mapper, query *statements.Query) QueryBinding

BindQuery returns a QueryBinding that facilitates running queries against instaces of the model.

type Models

type Models struct {
	//
	// Mapper defines how SQL column names map to fields in Go structs.
	Mapper *set.Mapper
	//
	// Grammar defines the SQL grammar to use for SQL generation.
	Grammar grammar.Grammar
	//
	// Models is a map of Go types to Model instances.  This member is automatically
	// instantiated during calls to Register().
	Models map[reflect.Type]*Model
	//
	// StructTag specifies the struct tag name to use when inspecting types
	// during register.  If not set will default to "model".
	StructTag string
}

Models is a registry of Models and methods to manipulate them.

Example (Relationship)
package main

import (
	"fmt"

	"github.com/nofeaturesonlybugs/sqlh/model/examples"
)

func main() {
	// This single example shows the common cases of INSERT|UPDATE|UPSERT as distinct code blocks.
	// examples.Relationship has a composite key and no auto-updating fields.
	{
		// Demonstrates INSERT of a single model.
		db, err := examples.Connect(examples.ExRelationshipInsert)
		if err != nil {
			fmt.Println(err.Error())
			return
		}
		// A "value" model.
		value := examples.Relationship{
			LeftId:  1,
			RightId: 10,
			Toggle:  false,
		}
		// Pass "value" model by address.
		if err = examples.Models.Insert(db, &value); err != nil {
			fmt.Println(err)
			return
		}
		fmt.Println("Insert success.")
	}
	{
		// Demonstrates UPDATE of a single model.
		db, err := examples.Connect(examples.ExRelationshipUpdate)
		if err != nil {
			fmt.Println(err.Error())
			return
		}
		// A pointer model.
		relate := &examples.Relationship{
			LeftId:  1,
			RightId: 10,
			Toggle:  true,
		}
		// Pass pointer model directly.
		if err = examples.Models.Update(db, relate); err != nil {
			fmt.Println(err)
			return
		}
		fmt.Println("Update success.")
	}
	{
		// Demonstrates UPSERT of a single model.
		db, err := examples.Connect(examples.ExRelationshipUpsert)
		if err != nil {
			fmt.Println(err.Error())
			return
		}
		//
		relate := &examples.Relationship{
			LeftId:  1,
			RightId: 10,
			Toggle:  false,
		}
		if err = examples.Models.Upsert(db, relate); err != nil {
			fmt.Println(err)
			return
		}
		fmt.Println("Upsert success.")
	}

}
Output:

Insert success.
Update success.
Upsert success.
Example (RelationshipSlice)
package main

import (
	"fmt"

	"github.com/nofeaturesonlybugs/sqlh/model/examples"
)

func main() {
	// This single example shows the common cases of INSERT|UPDATE|UPSERT as distinct code blocks.
	// examples.Relationship has a composite key and no auto-updating fields.
	{
		// Demonstrates INSERT of a slice of models.
		db, err := examples.Connect(examples.ExRelationshipInsertSlice)
		if err != nil {
			fmt.Println(err.Error())
			return
		}
		// Slice of "values".
		relate := []examples.Relationship{
			{
				LeftId:  1,
				RightId: 10,
				Toggle:  false,
			},
			{
				LeftId:  2,
				RightId: 20,
				Toggle:  true,
			},
			{
				LeftId:  3,
				RightId: 30,
				Toggle:  false,
			},
		}
		// Pass slice of "values" directly.
		if err = examples.Models.Insert(db, relate); err != nil {
			fmt.Println(err)
			return
		}
		fmt.Println("Insert success.")
	}
	{
		// Demonstrates UPDATE of a slice of models.
		db, err := examples.Connect(examples.ExRelationshipUpdateSlice)
		if err != nil {
			fmt.Println(err.Error())
			return
		}
		// Slice of pointers.
		relate := []*examples.Relationship{
			{
				LeftId:  1,
				RightId: 10,
				Toggle:  true,
			},
			{
				LeftId:  2,
				RightId: 20,
				Toggle:  false,
			},
			{
				LeftId:  3,
				RightId: 30,
				Toggle:  true,
			},
		}
		// Pass slice of pointers directly.
		if err = examples.Models.Update(db, relate); err != nil {
			fmt.Println(err)
			return
		}
		fmt.Println("Update success.")
	}
	{
		// Demonstrates UPSERT of a slice of models.
		db, err := examples.Connect(examples.ExRelationshipUpsertSlice)
		if err != nil {
			fmt.Println(err.Error())
			return
		}
		//
		relate := []*examples.Relationship{
			{
				LeftId:  1,
				RightId: 10,
				Toggle:  false,
			},
			{
				LeftId:  2,
				RightId: 20,
				Toggle:  true,
			},
			{
				LeftId:  3,
				RightId: 30,
				Toggle:  false,
			},
		}
		if err = examples.Models.Upsert(db, relate); err != nil {
			fmt.Println(err)
			return
		}
		fmt.Println("Upsert success.")
	}

}
Output:

Insert success.
Update success.
Upsert success.

func (*Models) Insert

func (me *Models) Insert(Q sqlh.IQueries, value interface{}) error

Insert attempts to persist values via INSERTs.

Example
package main

import (
	"fmt"
	"time"

	"github.com/nofeaturesonlybugs/sqlh/model/examples"
)

func main() {
	var zero time.Time
	//
	// Create a mock database.
	db, err := examples.Connect(examples.ExAddressInsert)
	if err != nil {
		fmt.Println("err", err.Error())
		return
	}
	WasInserted := func(id int, created time.Time, modified time.Time) error {
		if id == 0 || zero.Equal(created) || zero.Equal(modified) {
			return fmt.Errorf("Record not inserted.")
		}
		return nil
	}
	// A "value" record.
	byVal := examples.Address{
		// Id, CreatedTime, ModifiedTime are updated by the database.
		Street: "1234 The Street",
		City:   "Small City",
		State:  "ST",
		Zip:    "98765",
	}
	// A pointer record.
	byPtr := &examples.Address{
		// Id, CreatedTime, ModifiedTime are updated by the database.
		Street: "4321 The Street",
		City:   "Big City",
		State:  "TS",
		Zip:    "56789",
	}

	// Pass the address of the "value" record.
	if err := examples.Models.Insert(db, &byVal); err != nil {
		fmt.Println("err", err.Error())
		return
	}
	if err := WasInserted(byVal.Id, byVal.CreatedTime, byVal.ModifiedTime); err != nil {
		fmt.Println("err", err.Error())
		return
	}
	// The pointer record can be passed directly.
	if err := examples.Models.Insert(db, byPtr); err != nil {
		fmt.Println("err", err.Error())
		return
	}
	if err := WasInserted(byPtr.Id, byPtr.CreatedTime, byPtr.ModifiedTime); err != nil {
		fmt.Println("err", err.Error())
		return
	}
	fmt.Println("Models inserted.")

}
Output:

Models inserted.
Example (Slice)
package main

import (
	"fmt"
	"time"

	"github.com/nofeaturesonlybugs/sqlh/model/examples"
)

func main() {
	var zero time.Time
	//
	// Create a mock database.
	db, err := examples.Connect(examples.ExAddressInsertSlice)
	if err != nil {
		fmt.Println("err", err.Error())
		return
	}
	WasInserted := func(id int, created time.Time, modified time.Time) error {
		if id == 0 || zero.Equal(created) || zero.Equal(modified) {
			return fmt.Errorf("Record not inserted.")
		}
		return nil
	}
	// A slice of values.
	values := []examples.Address{
		// Id, CreatedTime, ModifiedTime are updated by the database.
		{
			Street: "1234 The Street",
			City:   "Small City",
			State:  "ST",
			Zip:    "98765",
		},
		{
			Street: "55 Here We Are",
			City:   "Big City",
			State:  "TS",
			Zip:    "56789",
		},
	}
	// A slice of pointers.
	pointers := []*examples.Address{
		// Id, CreatedTime, ModifiedTime are updated by the database.
		{
			Street: "1234 The Street",
			City:   "Small City",
			State:  "ST",
			Zip:    "98765",
		},
		{
			Street: "55 Here We Are",
			City:   "Big City",
			State:  "TS",
			Zip:    "56789",
		},
	}

	// Slices of values can be passed directly.
	if err := examples.Models.Insert(db, values); err != nil {
		fmt.Println("err", err.Error())
		return
	}
	for _, model := range values {
		if err := WasInserted(model.Id, model.CreatedTime, model.ModifiedTime); err != nil {
			fmt.Println("err", err.Error())
			return
		}
	}
	// Slices of pointers can be passed directly.
	if err := examples.Models.Insert(db, pointers); err != nil {
		fmt.Println("err", err.Error())
		return
	}
	for _, model := range pointers {
		if err := WasInserted(model.Id, model.CreatedTime, model.ModifiedTime); err != nil {
			fmt.Println("err", err.Error())
			return
		}
	}

	fmt.Println("Models inserted.")

}
Output:

Models inserted.

func (*Models) Lookup

func (me *Models) Lookup(value interface{}) (m *Model, err error)

Lookup returns the model associated with the value.

func (*Models) Register

func (me *Models) Register(value interface{}, opts ...interface{})

Register adds a Go type to the Models instance.

Register is not goroutine safe; implement locking in the store or application level if required.

When Register is called with a type T the following registrations are made:

T, *T, []T, & []*T

As a convenience register can be called with a reflect.Type as the value.

Example
package main

import (
	"fmt"
	"time"

	"github.com/nofeaturesonlybugs/set"

	"github.com/nofeaturesonlybugs/sqlh/grammar"
	"github.com/nofeaturesonlybugs/sqlh/model"
)

func main() {
	// This example demonstrates model registration.

	var Models *model.Models = &model.Models{
		// Mapper and its fields control how Go structs are traversed and mapped to
		// database column names.
		Mapper: &set.Mapper{
			Join: "_",
			Tags: []string{"db", "json"},
		},
		Grammar: grammar.Postgres,

		// StructTag defines the tag name to use when inspecting models.
		// StructTag: "", // Blank defaults to "model"
	}

	//
	// Simple model examples
	//

	// tablename=strings
	//	strings.pk    ↣ auto incrementing key
	//	strings.value
	type StringModel struct {
		// This field specifies the table name in the database.
		//   json:"-" tells encoding/json to ignore this field when marshalling
		//   model:"strings" means the table name is "strings" in the database.
		model.TableName `json:"-" model:"strings"`

		// An auto incrementing primary key field.
		//
		// The mapper is configured to use `db` tag before `json` tag;
		// therefore this maps to strings.pk in the database but json
		// marshals as id
		//
		//  `json:"id" db:"pk" model:"key,auto"`
		//                                 ^-- auto incrementing
		//                              ^-- field is the key or part of composite key
		//                 ^-- maps to strings.pk column
		//          ^-- json marshals to id
		Id int `json:"id" db:"pk" model:"key,auto"`

		// json marshals as value
		// maps to database column strings.value
		Value string `json:"value"`
	}

	// tablename=numbers
	//  numbers.pk    ↣ auto incrementing key
	//  numbers.value
	type NumberModel struct {
		// This model does not include the model.TableName embed; the table name
		// must be specified during registration (see below).
		// model.TableName `json:"-" model:"numbers"`

		Id    int `json:"id" db:"pk" model:"key,auto"`
		Value int `json:"value"`
	}

	// tablename=companies
	//  companies.pk        ↣ auto incrementing key
	//  companies.created   ↣ updates on INSERT
	//  companies.modified  ↣ updates on INSERT and UPDATE
	//  companies.name
	type CompanyModel struct {
		Id int `json:"id" db:"pk" model:"key,auto"`

		// Models can have fields that update during INSERT or UPDATE statements.
		//  `json:"created" model:"inserted"`
		//                           ^-- this column updates on insert
		//  `json:"modified" model:"inserted,updated"`
		//                           ^-- this column updates on insert and updates
		CreatedTime  time.Time `json:"created" model:"inserted"`
		ModifiedTime time.Time `json:"modified" model:"inserted,updated"`

		Name int `json:"name"`
	}

	//
	// Model registration
	//  + Models that embed model.TableName do not need to specify the tablename during registration.
	Models.Register(StringModel{})
	Models.Register(NumberModel{}, model.TableName("numbers"))
	Models.Register(CompanyModel{}, model.TableName("companies"))

	fmt.Println("all done")

}
Output:

all done

func (*Models) Save added in v0.5.0

func (me *Models) Save(Q sqlh.IQueries, value interface{}) error

Save inspects the incoming model and delegates to Insert, Update, or Upsert method according to the model's SaveMode value, which is determined during registration.

Models with at least one key field defined as "key" (i.e. not "key,auto") use Upsert.

Models with zero "key" and "key,auto" fields use Insert.

Otherwise the model has only "key,auto" fields and will use Update if any such field is a non-zero value and Insert otherwise.

If value is a slice []M then the first element is inspected to determine which of Insert, Update, or Upsert is applied to the entire slice.

Example
package main

import (
	"fmt"
	"time"

	"github.com/nofeaturesonlybugs/sqlh/model/examples"
)

func main() {
	// This example demonstrates using Models.Save when the models have only "key,auto" fields.
	// This means when models are first created and passed to Save an INSERT is performed.
	// Subsequent calls to Save with the model instances results in UPDATE queries.

	// Similar to other examples this example uses a "value" model and a pointer model.  Note
	// the "value" model needs to be passed by address.

	var zero time.Time
	//
	// Create a mock database.
	db, err := examples.Connect(examples.ExAddressSave)
	if err != nil {
		fmt.Println("err", err.Error())
		return
	}
	WasInserted := func(id int, created time.Time, modified time.Time) error {
		if id == 0 || zero.Equal(created) || !created.Equal(modified) {
			return fmt.Errorf("Record not inserted.")
		}
		return nil
	}
	WasUpdated := func(created time.Time, modified time.Time) error {
		if created.Equal(modified) {
			return fmt.Errorf("Record not updated.")
		}
		return nil
	}
	// A "value" instance.
	byVal := examples.Address{
		Street: "1234 The Street",
		City:   "Small City",
		State:  "ST",
		Zip:    "98765",
	}
	// A pointer instance.
	byPtr := &examples.Address{
		Street: "55 Here We Are",
		City:   "Big City",
		State:  "TS",
		Zip:    "56789",
	}
	//

	// Save the models; since these models only have "key,auto" fields they will first INSERT.
	if err := examples.Models.Save(db, &byVal); err != nil {
		fmt.Println("err", err.Error())
		return
	}
	if err := WasInserted(byVal.Id, byVal.CreatedTime, byVal.ModifiedTime); err != nil {
		fmt.Println("err", err.Error())
		return
	}
	if err := examples.Models.Save(db, byPtr); err != nil {
		fmt.Println("err", err.Error())
		return
	}
	if err := WasInserted(byPtr.Id, byPtr.CreatedTime, byPtr.ModifiedTime); err != nil {
		fmt.Println("err", err.Error())
		return
	}

	// Edit the model fields.
	byVal.Street = "1 New Street"
	byVal.Zip = "99111"

	byPtr.Street = "2 New Street"
	byPtr.Zip = "99222"

	// Save the models; since the key fields are no longer zero values they will UPDATE.
	if err := examples.Models.Save(db, &byVal); err != nil {
		fmt.Println("err", err.Error())
		return
	}
	if err := WasUpdated(byVal.CreatedTime, byVal.ModifiedTime); err != nil {
		fmt.Println("err", err.Error())
		return
	}
	if err := examples.Models.Save(db, byPtr); err != nil {
		fmt.Println("err", err.Error())
		return
	}
	if err := WasUpdated(byPtr.CreatedTime, byPtr.ModifiedTime); err != nil {
		fmt.Println("err", err.Error())
		return
	}

	fmt.Println("Models saved.")

}
Output:

Models saved.
Example (CompositeKeyUpserts)
package main

import (
	"fmt"

	"github.com/nofeaturesonlybugs/sqlh/model/examples"
)

func main() {
	// This example demonstrates using Models.Save when the models have only "key" key fields
	// and zero "key,auto" fields.  Such models are saved with UPSERT.

	//
	// Create a mock database.
	db, err := examples.Connect(examples.ExRelationshipSave)
	if err != nil {
		fmt.Println("err", err.Error())
		return
	}
	values := []examples.Relationship{
		{
			LeftId:  1, // LeftId and RightId are the composite key
			RightId: 2,
			Toggle:  false,
		},
		{
			LeftId:  10,
			RightId: 20,
			Toggle:  false,
		},
	}

	// Save the models; since these models only have "key,auto" fields they will first INSERT.
	if err := examples.Models.Save(db, values); err != nil {
		fmt.Println("err", err.Error())
		return
	}

	// Edit the model fields.
	values[0].Toggle = true
	values[1].Toggle = true

	// Save the models; since the key fields are no longer zero values they will UPDATE.
	if err := examples.Models.Save(db, values); err != nil {
		fmt.Println("err", err.Error())
		return
	}

	fmt.Println("Models saved.")

}
Output:

Models saved.
Example (NoKeyFieldsInserts)
package main

import (
	"fmt"

	"github.com/nofeaturesonlybugs/sqlh/model/examples"
)

func main() {
	// This example demonstrates using Models.Save when the models do not have any
	// "key" or "key,auto" fields.  Such models must INSERT.
	//
	// Note also that such a model could be a partial model of the actual database table
	// that does have key fields defined in the table schema.

	//
	// Create a mock database.
	db, err := examples.Connect(examples.ExLogEntrySave)
	if err != nil {
		fmt.Println("err", err.Error())
		return
	}
	values := []examples.LogEntry{
		{Message: "Hello, World!"},
		{Message: "Foo, Bar!"},
		{Message: "The llamas are escaping!"},
	}

	// Save the models; since these models have no "key" or "key,auto" fields use INSERT.
	if err := examples.Models.Save(db, values); err != nil {
		fmt.Println("err", err.Error())
		return
	}

	fmt.Println("Models saved.")

}
Output:

Models saved.
Example (Slice)
package main

import (
	"fmt"
	"time"

	"github.com/nofeaturesonlybugs/sqlh/model/examples"
)

func main() {
	// This example demonstrates using Models.Save when the models have only "key,auto" fields.
	// This means when models are first created and passed to Save an INSERT is performed.
	// Subsequent calls to Save with the model instances results in UPDATE queries.

	var zero time.Time
	//
	// Create a mock database.
	db, err := examples.Connect(examples.ExAddressSaveSlice)
	if err != nil {
		fmt.Println("err", err.Error())
		return
	}
	WasInserted := func(id int, created time.Time, modified time.Time) error {
		if id == 0 || zero.Equal(created) || !created.Equal(modified) {
			return fmt.Errorf("Record not inserted.")
		}
		return nil
	}
	WasUpdated := func(created time.Time, modified time.Time) error {
		if created.Equal(modified) {
			return fmt.Errorf("Record not updated.")
		}
		return nil
	}
	values := []examples.Address{
		{
			Street: "1234 The Street",
			City:   "Small City",
			State:  "ST",
			Zip:    "98765",
		},
		{
			Street: "55 Here We Are",
			City:   "Big City",
			State:  "TS",
			Zip:    "56789",
		},
	}

	// Save the models; since these models only have "key,auto" fields they will first INSERT.
	if err := examples.Models.Save(db, values); err != nil {
		fmt.Println("err", err.Error())
		return
	}
	for _, value := range values {
		if err := WasInserted(value.Id, value.CreatedTime, value.ModifiedTime); err != nil {
			fmt.Println("err", err.Error())
			return
		}
	}

	// Edit the model fields.
	values[0].Street = "1 New Street"
	values[0].Zip = "99111"

	values[1].Street = "2 New Street"
	values[1].Zip = "99222"

	// Save the models; since the key fields are no longer zero values they will UPDATE.
	if err := examples.Models.Save(db, values); err != nil {
		fmt.Println("err", err.Error())
		return
	}
	for _, value := range values {
		if err := WasUpdated(value.CreatedTime, value.ModifiedTime); err != nil {
			fmt.Println("err", err.Error())
			return
		}
	}

	fmt.Println("Models saved.")

}
Output:

Models saved.

func (*Models) Update

func (me *Models) Update(Q sqlh.IQueries, value interface{}) error

Update attempts to persist values via UPDATESs.

Example
package main

import (
	"fmt"
	"time"

	"github.com/nofeaturesonlybugs/sqlh/model/examples"
)

func main() {
	var zero time.Time
	//
	// Create a mock database.
	db, err := examples.Connect(examples.ExAddressUpdate)
	if err != nil {
		fmt.Println("err", err.Error())
		return
	}
	WasUpdated := func(modified time.Time) error {
		if zero.Equal(modified) {
			return fmt.Errorf("Record not updated.")
		}
		return nil
	}
	// A "value" record.
	byVal := examples.Address{
		Id:          42,
		CreatedTime: time.Now().Add(-1 * time.Hour),
		// ModifiedTime is zero value; will be updated by database.
		Street: "1234 The Street",
		City:   "Small City",
		State:  "ST",
		Zip:    "98765",
	}
	// A pointer record.
	byPtr := &examples.Address{
		Id:          42,
		CreatedTime: time.Now().Add(-1 * time.Hour),
		// ModifiedTime is zero value; will be updated by database.
		Street: "4321 The Street",
		City:   "Big City",
		State:  "TS",
		Zip:    "56789",
	}

	// Pass "value" record by address.
	if err := examples.Models.Update(db, &byVal); err != nil {
		fmt.Println("err", err.Error())
		return
	}
	if err := WasUpdated(byVal.ModifiedTime); err != nil {
		fmt.Println("err", err.Error())
		return
	}
	// Pass pointer record directly.
	if err := examples.Models.Update(db, byPtr); err != nil {
		fmt.Println("err", err.Error())
		return
	}
	if err := WasUpdated(byPtr.ModifiedTime); err != nil {
		fmt.Println("err", err.Error())
		return
	}

	fmt.Printf("Models updated.")

}
Output:

Models updated.
Example (Slice)
package main

import (
	"fmt"
	"time"

	"github.com/nofeaturesonlybugs/sqlh/model/examples"
)

func main() {
	var zero time.Time
	//
	// Create a mock database.
	db, err := examples.Connect(examples.ExAddressUpdateSlice)
	if err != nil {
		fmt.Println("err", err.Error())
		return
	}
	WasUpdated := func(modified time.Time) error {
		if zero.Equal(modified) {
			return fmt.Errorf("Record not updated.")
		}
		return nil
	}
	// Slice of values.
	values := []examples.Address{
		// ModifiedTime is not set and updated by the database.
		{
			Id:          42,
			CreatedTime: time.Now().Add(-2 * time.Hour),
			Street:      "1234 The Street",
			City:        "Small City",
			State:       "ST",
			Zip:         "98765",
		},
		{
			Id:          62,
			CreatedTime: time.Now().Add(-1 * time.Hour),
			Street:      "55 Here We Are",
			City:        "Big City",
			State:       "TS",
			Zip:         "56789",
		},
	}
	// Slice of pointers.
	pointers := []*examples.Address{
		// ModifiedTime is not set and updated by the database.
		{
			Id:          42,
			CreatedTime: time.Now().Add(-2 * time.Hour),
			Street:      "1234 The Street",
			City:        "Small City",
			State:       "ST",
			Zip:         "98765",
		},
		{
			Id:          62,
			CreatedTime: time.Now().Add(-1 * time.Hour),
			Street:      "55 Here We Are",
			City:        "Big City",
			State:       "TS",
			Zip:         "56789",
		},
	}

	// Slice of values can be passed directly.
	if err := examples.Models.Update(db, values); err != nil {
		fmt.Println("err", err.Error())
		return
	}
	for _, model := range values {
		if err := WasUpdated(model.ModifiedTime); err != nil {
			fmt.Println("err", err.Error())
			return
		}
	}
	// Slice of pointers can be passed directly.
	if err := examples.Models.Update(db, pointers); err != nil {
		fmt.Println("err", err.Error())
		return
	}
	for _, model := range pointers {
		if err := WasUpdated(model.ModifiedTime); err != nil {
			fmt.Println("err", err.Error())
			return
		}
	}

	fmt.Println("Models updated.")

}
Output:

Models updated.

func (*Models) Upsert added in v0.3.0

func (me *Models) Upsert(Q sqlh.IQueries, value interface{}) error

Upsert attempts to persist values via UPSERTs.

Upsert only works on primary keys that are defined as "key"; in other words columns tagged with "key,auto" are not used in the generated query.

Upsert only supports primary keys; currently there is no support for upsert on UNIQUE indexes that are not primary keys.

Example
package main

import (
	"fmt"
	"time"

	"github.com/nofeaturesonlybugs/sqlh/model/examples"
)

func main() {
	var zero time.Time
	//
	// Create a mock database.
	db, err := examples.Connect(examples.ExUpsert)
	if err != nil {
		fmt.Println("err", err.Error())
		return
	}
	WasUpserted := func(created time.Time, modified time.Time) error {
		if zero.Equal(created) || zero.Equal(modified) {
			return fmt.Errorf("Record not upserted.")
		}
		return nil
	}
	// A "value" record.
	byVal := examples.Upsertable{
		Id:     "some-unique-string",
		String: "Hello, World!",
		Number: 42,
	}
	// A pointer record.
	byPtr := &examples.Upsertable{
		Id:     "other-unique-string",
		String: "Foo, Bar!",
		Number: 100,
	}

	// Pass "value" record by address.
	if err := examples.Models.Upsert(db, &byVal); err != nil {
		fmt.Println("err", err.Error())
		return
	}
	if err := WasUpserted(byVal.CreatedTime, byVal.ModifiedTime); err != nil {
		fmt.Println("err", err.Error())
		return
	}
	// Pass pointer record directly.
	if err := examples.Models.Upsert(db, byPtr); err != nil {
		fmt.Println("err", err.Error())
		return
	}
	if err := WasUpserted(byPtr.CreatedTime, byPtr.ModifiedTime); err != nil {
		fmt.Println("err", err.Error())
		return
	}

	fmt.Printf("Models upserted.")

}
Output:

Models upserted.
Example (Slice)
package main

import (
	"fmt"
	"time"

	"github.com/nofeaturesonlybugs/sqlh/model/examples"
)

func main() {
	var zero time.Time
	//
	// Create a mock database.
	db, err := examples.Connect(examples.ExUpsertSlice)
	if err != nil {
		fmt.Println("err", err.Error())
		return
	}
	WasUpserted := func(created time.Time, modified time.Time) error {
		if zero.Equal(created) || zero.Equal(modified) {
			return fmt.Errorf("Record not upserted.")
		}
		return nil
	}
	// Slice of values.
	values := []examples.Upsertable{
		{
			Id:     "some-unique-string",
			String: "Hello, World!",
			Number: 42,
		},
		{
			Id:     "other-unique-string",
			String: "Goodbye, World!",
			Number: 10,
		},
	}
	// Slice of pointers.
	pointers := []*examples.Upsertable{
		{
			Id:     "some-unique-string",
			String: "Hello, World!",
			Number: 42,
		},
		{
			Id:     "other-unique-string",
			String: "Goodbye, World!",
			Number: 10,
		},
	}

	// Pass "values" directly.
	if err := examples.Models.Upsert(db, values); err != nil {
		fmt.Println("err", err.Error())
		return
	}
	for _, model := range values {
		if err := WasUpserted(model.CreatedTime, model.ModifiedTime); err != nil {
			fmt.Println("err", err.Error())
			return
		}
	}
	// Pass pointers directly.
	if err := examples.Models.Upsert(db, pointers); err != nil {
		fmt.Println("err", err.Error())
		return
	}
	for _, model := range pointers {
		if err := WasUpserted(model.CreatedTime, model.ModifiedTime); err != nil {
			fmt.Println("err", err.Error())
			return
		}
	}

	fmt.Println("Models upserted.")

}
Output:

Models upserted.

type QueryBinding

type QueryBinding struct {
	// contains filtered or unexported fields
}

QueryBinding binds a model together with a specific query.

func (QueryBinding) Query

func (me QueryBinding) Query(q sqlh.IQueries, value interface{}) error

Query accepts either a single model M or a slice of models []M. It then runs and returns the result of QueryOne or QuerySlice.

func (QueryBinding) QueryOne

func (me QueryBinding) QueryOne(q sqlh.IQueries, value interface{}) error

QueryOne runs the query against a single instance of the model.

As a special case value can be an instance of reflect.Value.

func (QueryBinding) QuerySlice

func (me QueryBinding) QuerySlice(q sqlh.IQueries, values interface{}) error

QuerySlice runs the query against a slice of model instances.

type SaveMode added in v0.5.0

type SaveMode int

SaveMode describes how a model should be saved when passed to Models.Save method.

const (

	// Models with zero key fields can only be inserted.
	Insert SaveMode

	// Models with only key,auto fields use insert or update depending
	// on the current values of the key fields.  If any key,auto field
	// is not the zero value then update otherwise insert.
	InsertOrUpdate

	// Models with at least one key field that is not auto must use upsert.
	Upsert
)

type TableName

type TableName string

TableName represents a database table name. Embed the TableName type into a struct and set the appropriate struct tag to configure the table name.

Directories

Path Synopsis
Package examples provides types and functions to facilitate the examples and test code in the model package.
Package examples provides types and functions to facilitate the examples and test code in the model package.
Package statements builds uses a grammar to build SQL statements scoped to entities within the database.
Package statements builds uses a grammar to build SQL statements scoped to entities within the database.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL