{"id":37113387,"url":"https://github.com/bigpigeon/toyorm","last_synced_at":"2026-01-14T13:22:49.022Z","repository":{"id":57497453,"uuid":"116920797","full_name":"bigpigeon/toyorm","owner":"bigpigeon","description":"golang orm ","archived":false,"fork":false,"pushed_at":"2019-06-30T11:58:11.000Z","size":751,"stargazers_count":226,"open_issues_count":2,"forks_count":9,"subscribers_count":6,"default_branch":"master","last_synced_at":"2024-06-20T06:56:29.138Z","etag":null,"topics":["database","golang"],"latest_commit_sha":null,"homepage":"https://bigpigeon.org/toyorm","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bigpigeon.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"License","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-01-10T07:01:38.000Z","updated_at":"2024-06-20T06:56:29.138Z","dependencies_parsed_at":"2022-09-03T23:52:19.041Z","dependency_job_id":null,"html_url":"https://github.com/bigpigeon/toyorm","commit_stats":null,"previous_names":[],"tags_count":13,"template":false,"template_full_name":null,"purl":"pkg:github/bigpigeon/toyorm","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bigpigeon%2Ftoyorm","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bigpigeon%2Ftoyorm/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bigpigeon%2Ftoyorm/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bigpigeon%2Ftoyorm/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bigpigeon","download_url":"https://codeload.github.com/bigpigeon/toyorm/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bigpigeon%2Ftoyorm/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28421135,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-14T10:47:48.104Z","status":"ssl_error","status_checked_at":"2026-01-14T10:46:19.031Z","response_time":107,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["database","golang"],"created_at":"2026-01-14T13:22:48.312Z","updated_at":"2026-01-14T13:22:49.008Z","avatar_url":"https://github.com/bigpigeon.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Toyorm\n\nthis is powerful sql orm library for Golang, have some funny features\n\n[![Build Status](https://travis-ci.org/bigpigeon/toyorm.svg)](https://travis-ci.org/bigpigeon/toyorm)\n[![codecov](https://codecov.io/gh/bigpigeon/toyorm/branch/master/graph/badge.svg)](https://codecov.io/gh/bigpigeon/toyorm)\n[![Go Report Card](https://goreportcard.com/badge/github.com/bigpigeon/toyorm)](https://goreportcard.com/report/github.com/bigpigeon/toyorm)\n[![GoDoc](https://godoc.org/github.com/bigpigeon/toyorm?status.svg)](https://godoc.org/github.com/bigpigeon/toyorm)\n[![Join the chat](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/toyorm/toyorm)\n\n---\n\n\u003c!-- START doctoc generated TOC please keep comment here to allow auto update --\u003e\n\u003c!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --\u003e\n\n\n  - [A Simple Example](#a-simple-example)\n  - [Website Example](#website-example)\n  - [Database connection](#database-connection)\n  - [Model definition](#model-definition)\n      - [example](#example)\n      - [type translate](#type-translate)\n    - [Bind models](#bind-models)\n  - [Sql Operation](#sql-operation)\n    - [create table](#create-table)\n    - [drop table](#drop-table)\n    - [insert/save data](#insertsave-data)\n    - [update](#update)\n    - [find](#find)\n    - [delete](#delete)\n  - [ToyBrick](#toybrick)\n    - [Where condition](#where-condition)\n      - [usage](#usage)\n      - [SearchExpr](#searchexpr)\n      - [example](#example-1)\n    - [Transaction](#transaction)\n    - [Debug](#debug)\n    - [IgnoreMode](#ignoremode)\n    - [BindFields](#bindfields)\n    - [Scope](#scope)\n    - [Template](#template)\n      - [Custom insert](#custom-insert)\n      - [Custom find](#custom-find)\n      - [Custom update](#custom-update)\n      - [Placeholder](#placeholder)\n    - [Thread safe](#thread-safe)\n    - [Preload](#preload)\n      - [Preload example](#preload-example)\n      - [One to one](#one-to-one)\n      - [Belong to](#belong-to)\n      - [One to many](#one-to-many)\n      - [Many to many](#many-to-many)\n      - [Load preload](#load-preload)\n    - [Join](#join)\n      - [Join Example](#join-example)\n      - [Model Define](#model-define)\n      - [Join in Find](#join-in-find)\n      - [Preload On Join](#preload-on-join)\n  - [Result](#result)\n    - [Selector](#selector)\n- [Collection](#collection)\n  - [ToyCollection](#toycollection)\n  - [CollectionBrick](#collectionbrick)\n    - [Selector](#selector-1)\n    - [id generator](#id-generator)\n    - [sql action](#sql-action)\n  - [collection example](#collection-example)\n- [toy-doctor](#toy-doctor)\n\n\u003c!-- END doctoc generated TOC please keep comment here to allow auto update --\u003e\n\n---\n\n\n\n### Support database\n\n[sqlite3](https://www.sqlite.org/)  [mysql](https://www.mysql.com/) [postgresql](https://www.postgresql.org/)\n\n\n### Go version\n\nversion go-1.9\n\n---\n\n### A Simple Example\n\n[here](examples/simple_example)\n\n---\n\n### Website Example\n\n[here](examples/website)\n\n---\n\n### Database connection\n\nimport database driver\n\n```golang\n// if database is mysql\n_ \"github.com/go-sql-driver/mysql\"\n// if database is sqlite3\n_ \"github.com/mattn/go-sqlite3\"\n// when database is postgres\n_ \"github.com/lib/pq\"\n```\n\ncreate a toy\n\n```golang\n// if database is mysql, make sure your mysql have toyorm_example schema\ntoy, err = toyorm.Open(\"mysql\", \"root:@tcp(localhost:3306)/toyorm_example?charset=utf8\u0026parseTime=True\")\n// if database is sqlite3\ntoy,err = toyorm.Open(\"sqlite3\", \"toyorm_test.db\")\n// when database is postgres\ntoy, err = toyorm.Open(\"postgres\", \"user=postgres dbname=toyorm sslmode=disable\")\n```\n\n\n### Model definition\n\n##### example\n\n```golang\ntype Extra map[string]interface{}\n\nfunc (e Extra) Scan(value interface{}) error {\n     switch v := value.(type) {\n     case string:\n          return json.Unmarshal([]byte(v), e)\n     case []byte:\n          return json.Unmarshal(v, e)\n     default:\n          return errors.New(\"not support type\")\n     }\n}\n\nfunc (e Extra) Value() (driver.Value, error) {\n     return json.Marshal(e)\n}\n\ntype UserDetail struct {\n     ID       int  `toyorm:\"primary key;auto_increment\"`\n     UserID   uint `toyorm:\"index\"`\n     MainPage string\n     Extra    Extra `toyorm:\"type:VARCHAR(1024)\"`\n}\n\ntype Blog struct {\n     toyorm.ModelDefault\n     UserID  uint   `toyorm:\"index\"`\n     Title   string `toyorm:\"index\"`\n     Content string\n}\n\ntype User struct {\n     toyorm.ModelDefault\n     Name    string `toyorm:\"unique index\"`\n     Age     int\n     Sex     string\n     Detail  *UserDetail\n     Friends []*User\n     Blog    []Blog\n}\n```\n\n##### type translate\n\nif sql type is not match, toyorm will ignore it in Sql Operation\n\n\nyou can use **\\\u003ctype:sql_type\\\u003e** field tag to specified their sql type\n\n\nthe following type will auto translate to sql type\n\n\nGo Type | sql type\n--------|-----------\nbool    | BOOLEAN\nint8,int16,int32,uint8,uint16,uint32| INTEGER\nint64,uint64,int,uint| BIGINT\nfloat32,float64| FLOAT\nstring  | VARCHAR(255)\ntime.Time | TIMESTAMP\n[]byte    | VARCHAR(255)\nsql.NullBool | BOOLEAN\nsql.NullInt64 | BIGINT\nsql.NullFloat64 | FLOAT\nsql.NullString | VARCHAR(255)\nsql.RawBytes | VARCHAR(255)\n\n**special fields**\n\n1. special fields have some process in handlers, do not try to change it type or set it value\n\n\nField Name| Type      | Description\n----------|-----------|------------\nCreatedAt |time.Time  | generate when element be create\nUpdatedAt |time.Time  | generate when element be update/create\nDeletedAt |*time.Time | delete mode is soft\nCas       |int        | (not support for sqlite3)save operation will failure when it's value modified by third party e.g in postgres:cas=1 insert xxx conflict(id) update cas = 2 where cas = 1\n\n**field tags**\n\n1. tag format can be \\\u003ckey:value\\\u003e or \\\u003ckey\\\u003e\n\n2. the following is special tag\n\nKey           |Value                   |Description\n--------------|------------------------|-----------\nindex         | void or string         | use for optimization when search condition have this field,if you want make a combined,just set same index name with fields\nunique index  | void or string         | have unique limit index, other same as index\nprimary key   | void                   | allow multiple primary key,but some operation not support\n\\-            | void                    | ignore this field in sql\ntype          | string                  | sql type\ncolumn        | string                  | sql column name\nauto_increment| void                    | recommend, if your table primary key have auto_increment attribute must add it\nautoincrement | void                    | same as auto_increment\nforeign key   | void                   | to add foreign key feature when create table\nalias         | string                 | change field name with toyorm\njoin          | string                 | to select related field when call brick.Join\nbelong to     | string                 | to select related field when call brick.Preload with BelongTo container\none to one    | string                 | to select related field when call brick.Preload with OneToOne container\none to many   | string                 | to select related field when call brick.Preload with OneToMany container\ndefault       | srring                 | default value in database\n\nother custom TAG will append to end of CREATE TABLE field\n\n\n#### Bind models\n\n1. model kind must be a struct or a point with struct\n\n2. the model is what information that toyorm know about the table\n\n```golang\nbrick := toy.Model(\u0026User{})\n// or\nbrick := toy.Model(User{})\n```\n\n### Sql Operation\n\n---\n\n#### create table\n\n```golang\nvar err error\n_, err = toy.Model(\u0026User{}).Debug().CreateTable()\n// CREATE TABLE user (id BIGINT AUTO_INCREMENT,created_at TIMESTAMP NULL,updated_at TIMESTAMP NULL,deleted_at TIMESTAMP NULL,name VARCHAR(255),age BIGINT ,sex VARCHAR(255) , PRIMARY KEY(id))\n// CREATE INDEX idx_user_deletedat ON user(deleted_at)\n// CREATE UNIQUE INDEX udx_user_name ON user(name)\n_, err =toy.Model(\u0026UserDetail{}).Debug().CreateTable()\n// CREATE TABLE user_detail (id BIGINT AUTO_INCREMENT,user_id BIGINT,main_page Text,extra VARCHAR(1024), PRIMARY KEY(id))\n// CREATE INDEX idx_user_detail_userid ON user_detail(user_id)\n_, err =toy.Model(\u0026Blog{}).Debug().CreateTable()\n// CREATE TABLE blog (id BIGINT AUTO_INCREMENT,created_at TIMESTAMP NULL,updated_at TIMESTAMP NULL,deleted_at TIMESTAMP NULL,user_id BIGINT,title VARCHAR(255),content VARCHAR(255) , PRIMARY KEY(id))\n// CREATE INDEX idx_blog_deletedat ON blog(deleted_at)\n// CREATE INDEX idx_blog_userid ON blog(user_id)\n// CREATE INDEX idx_blog_title ON blog(title)\n```\n\n#### drop table\n\n```golang\nvar err error\n_, err =toy.Model(\u0026User{}).Debug().DropTable()\n// DROP TABLE user\n_, err =toy.Model(\u0026UserDetail{}).Debug().DropTable()\n// DROP TABLE user_detail\n_, err =toy.Model(\u0026Blog{}).Debug().DropTable()\n// DROP TABLE blog\n```\n\n#### insert/save data\n\n// insert with autoincrement will set id to source data\n\n```golang\nuser := \u0026User{\n    Name: \"bigpigeon\",\n    Age:  18,\n    Sex:  \"male\",\n}\n_, err = toy.Model(\u0026User{}).Debug().Insert(\u0026user)\n// INSERT INTO user(created_at,updated_at,name,age,sex) VALUES(?,?,?,?,?) , args:[]interface {}{time.Time{wall:0xbe8df5112e7f07c8, ext:210013499, loc:(*time.Location)(0x141af80)}, time.Time{wall:0xbe8df5112e7f1768, ext:210017044, loc:(*time.Location)(0x141af80)}, \"bigpigeon\", 18, \"male\"}\n// print user format with json\n/* {\n  \"ID\": 1,\n  \"CreatedAt\": \"2018-01-11T20:47:00.780077+08:00\",\n  \"UpdatedAt\": \"2018-01-11T20:47:00.780081+08:00\",\n  \"DeletedAt\": null,\n  \"Name\": \"bigpigeon\",\n  \"Age\": 18,\n  \"Sex\": \"male\",\n  \"Detail\": null,\n  \"Friends\": null,\n  \"Blog\": null\n}*/\n```\n\n// save data use \"REPLACE INTO\" when primary key exist\n\n```golang\nusers := []User{\n    {\n        ModelDefault: toyorm.ModelDefault{ID: 1},\n        Name:         \"bigpigeon\",\n        Age:          18,\n        Sex:          \"male\",\n    },\n    {\n        Name: \"fatpigeon\",\n        Age:  27,\n        Sex:  \"male\",\n    },\n}\n_, err = toy.Model(\u0026User{}).Debug().Save(\u0026user)\n// SELECT id,created_at FROM user WHERE id IN (?), args:[]interface {}{0x1}\n// REPLACE INTO user(id,created_at,updated_at,name,age,sex) VALUES(?,?,?,?,?,?) , args:[]interface {}{0x1, time.Time{wall:0x0, ext:63651278036, loc:(*time.Location)(nil)}, time.Time{wall:0xbe8dfb5511465918, ext:302600558, loc:(*time.Location)(0x141af80)}, \"bigpigeon\", 18, \"male\"}\n// INSERT INTO user(created_at,updated_at,name,age,sex) VALUES(?,?,?,?,?) , args:[]interface {}{time.Time{wall:0xbe8dfb551131b7d8, ext:301251230, loc:(*time.Location)(0x141af80)}, time.Time{wall:0xbe8dfb5511465918, ext:302600558, loc:(*time.Location)(0x141af80)}, \"fatpigeon\", 27, \"male\"}\n\n```\n\n#### update\n\n```golang\ntoy.Model(\u0026User{}).Debug().Update(\u0026User{\n    Age: 4,\n})\n// UPDATE user SET updated_at=?,age=? WHERE deleted_at IS NULL, args:[]interface {}{time.Time{wall:0xbe8df4eb81b6c050, ext:233425327, loc:(*time.Location)(0x141af80)}, 4}\n```\n\n\n#### find\n\nfind one\n\n```golang\nvar user User\n_, err = toy.Model(\u0026User{}).Debug().Find(\u0026user}\n// SELECT id,created_at,updated_at,deleted_at,name,age,sex FROM user WHERE deleted_at IS NULL LIMIT 1, args:[]interface {}(nil)\n// print user format with json\n/* {\n  \"ID\": 1,\n  \"CreatedAt\": \"2018-01-11T12:47:01Z\",\n  \"UpdatedAt\": \"2018-01-11T12:47:01Z\",\n  \"DeletedAt\": null,\n  \"Name\": \"bigpigeon\",\n  \"Age\": 4,\n  \"Sex\": \"male\",\n  \"Detail\": null,\n  \"Friends\": null,\n  \"Blog\": null\n}*/\n```\n\nfind multiple\n\n```golang\nvar users []User\n_, err = brick.Debug().Find(\u0026users)\nfmt.Printf(\"find users %s\\n\", JsonEncode(\u0026users))\n\n// SELECT id,created_at,updated_at,deleted_at,name,age,sex FROM user WHERE deleted_at IS NULL, args:[]interface {}(nil)\n```\n\n#### delete\n\ndelete with primary key\n\n```golang\n_, err = brick.Debug().Delete(\u0026user)\n// UPDATE user SET deleted_at=? WHERE id IN (?), args:[]interface {}{(*time.Time)(0xc4200f0520), 0x1}\n```\n\ndelete with condition\n\n```golang\n_, err = brick.Debug().Where(toyorm.ExprEqual, Offsetof(User{}.Name), \"bigpigeon\").DeleteWithConditions()\n// UPDATE user SET deleted_at=? WHERE name = ?, args:[]interface {}{(*time.Time)(0xc4200dbfa0), \"bigpigeon\"}\n```\n\n### ToyBrick\n\n-----\n\nuse **toy.Model** will create a ToyBrick, you need use it to build grammar and operate the database\n\n#### Where condition\n\naffective update/find/delete operation\n\n##### usage\n\nwhere will clean old conditions and make new one\n\n    brick.Where(\u003cexpr\u003e, \u003cKey\u003e, [value])\n\nwhereGroup add multiple condition with same expr\n\n    brick.WhereGroup(\u003cexpr\u003e, \u003cgroup\u003e)\n\nconditions will copy conditions and clean old conditions\n\n    brick.Conditions(\u003ctoyorm.Search\u003e)\n\nor \u0026 and condition will use or/and to link new condition when current condition is not nil\n\n    brick.Or().Condition(\u003cexpr\u003e, \u003cKey\u003e, [value])\n    brick.Or().ConditionGroup(\u003cexpr\u003e, \u003cgroup\u003e)\n    brick.And().Condition(\u003cexpr\u003e, \u003cKey\u003e, [value])\n\nor \u0026 and conditions will use or/and to link new conditions\n\n    brick.Or().Conditions(\u003ctoyorm.Search\u003e)\n    brick.And().Conditions(\u003ctoyorm.Search\u003e)\n\n##### SearchExpr\n\nSearchExpr        |  to sql      | example\n------------------|--------------|:----------------\nExprAnd           | AND          | brick.WhereGroup(ExprAnd, Product{Name:\"food one\", Count: 4}) // WHERE name = \"food one\" AND Count = 4\nExprOr            | OR           | brick.WhereGroup(ExprOr, Product{Name:\"food one\", Count: 4}) // WHERE name = \"food one\" OR Count = \"4\"\nExprEqual         | =            | brick.Where(ExprEqual, OffsetOf(Product{}.Name), \"food one\") // WHERE name = \"find one\"\nExprNotEqual      | \u003c\u003e           | brick.Where(ExprNotEqual, OffsetOf(Product{}.Name), \"food one\") // WHERE name \u003c\u003e \"find one\"\nExprGreater       | \u003e            | brick.Where(ExprGreater, OffsetOf(Product{}.Count), 3) // WHERE count \u003e 3\nExprGreaterEqual  | \u003e=           | brick.Where(ExprGreaterEqual, OffsetOf(Product{}.Count), 3) // WHERE count \u003e= 3\nExprLess          | \u003c            | brick.Where(ExprLess, OffsetOf(Product{}.Count), 3) // WHERE count \u003c 3\nExprLessEqual     | \u003c=           | brick.Where(ExprLessEqual, OffsetOf(Product{}.Count), 3) // WHERE count \u003c= 3\nExprBetween       | Between      | brick.Where(ExprBetween, OffsetOf(Product{}.Count), [2]int{2,3}) // WHERE count BETWEEN 2 AND 3\nExprNotBetween    | NOT Between  | brick.Where(ExprNotBetween, OffsetOf(Product{}.Count), [2]int{2,3}) // WHERE count NOT BETWEEN 2 AND 3\nExprIn            | IN           | brick.Where(ExprIn, OffsetOf(Product{}.Count), []int{1, 2, 3}) // WHERE count IN (1,2,3)\nExprNotIn         | NOT IN       | brick.Where(ExprNotIn, OffsetOf(Product{}.Count), []int{1, 2, 3}) // WHERE count NOT IN (1,2,3)\nExprLike          | LIKE         | brick.Where(ExprLike, OffsetOf(Product{}.Name), \"one\") // WHERE name LIKE \"one\"\nExprNotLike       | NOT LIKE     | brick.Where(ExprNotLike, OffsetOf(Product{}.Name), \"one\") // WHERE name NOT LIKE \"one\"\nExprNull          | IS NULL      | brick.Where(ExprNull, OffsetOf(Product{}.DeletedAt)) // WHERE DeletedAt IS NULL\nExprNotNull       | IS NOT NULL  | brick.Where(ExprNotNull, OffsetOf(Product{}.DeletedAt)) // WHERE DeletedAt IS NOT NULL\n\n##### example\n\nsingle condition\n\n```golang\nbrick = brick.Where(toyorm.ExprEqual, Offsetof(Product{}.Tag), \"food\")\nor use string\nbrick = brick.Where(\"=\", Offsetof(Product{}.Tag), \"food\")\n// WHERE tag = \"food\"\n```\n\ncombination condition\n\n```golang\nbrick = brick.Where(toyorm.ExprEqual, Offsetof(Product{}.Count), 2).And().\n    Condition(toyorm.ExprGreater, Offsetof(Product{}.Price), 3).Or().\n    Condition(toyorm.ExprEqual, Offsetof(Product{}.Count), 4)\nor use string\nbrick = brick.Where(\"=\", Offsetof(Product{}.Count), 2).And().\n    Condition(\"\u003e\", Offsetof(Product{}.Price), 3).Or().\n    Condition(\"=\", Offsetof(Product{}.Count), 4)\n// WHERE count = 2 and price \u003e 3 or count = 4\n```\n\npriority condition\n\n```golang\nbrick.Where(toyorm.ExprGreater, Offsetof(Product{}.Price), 3).And().Conditions(\n    brick.Where(toyorm.ExprEqual, Offsetof(Product{}.Count), 2).Or().\n    Condition(toyorm.ExprEqual, Offsetof(Product{}.Count), 1).Search\n)\nor use string\nbrick.Where(\"\u003e\", Offsetof(Product{}.Price), 3).And().Conditions(\n    brick.Where(\"=\", Offsetof(Product{}.Count), 2).Or().\n    Condition(\"=\", Offsetof(Product{}.Count), 1).Search\n)\n// WHERE price \u003e 3 and (count = 2 or count = 1)\n```\n\n\n```golang\nbrick.Conditions(\n    brick.Where(toyorm.ExprEqual, Offsetof(Product{}.Count), 2).Or().\n        Condition(toyorm.ExprEqual, Offsetof(Product{}.Count), 1).Search,\n).And().Conditions(\n    brick.Where(toyorm.ExprEqual, Offsetof(Product{}.Price), 3).Or().\n        Condition(toyorm.ExprEqual, Offsetof(Product{}.Price), 4).Search,\n)\nor use string\nbrick.Conditions(\n    brick.Where(\"=\", Offsetof(Product{}.Count), 2).Or().\n        Condition(\"=\", Offsetof(Product{}.Count), 1).Search,\n).And().Conditions(\n    brick.Where(\"=\", Offsetof(Product{}.Price), 3).Or().\n        Condition(\"=\", Offsetof(Product{}.Price), 4).Search,\n)\n// WHERE (count = ? OR count = ?) AND (price = ? OR price = ?)\n```\n\nlimit \u0026 offset\n\n```golang\nbrick := brick.Offset(2).Limit(2)\n// LIMIT 2 OFFSET 2\n```\n\norder by\n\n```golang\nbrick = brick.OrderBy(Offsetof(Product{}.Name))\n// ORDER BY name\n```\n\norder by desc\n\n```golang\nbrick = brick.OrderBy(brick.ToDesc(Offsetof(Product{}.Name)))\n// ORDER BY name DESC\n```\n\n#### Template Field\n\nsometimes the condition of sql is not a normal field, use TempField to wrapper normal field\n\n```golang\nbrick = brick.Where(\"=\", brick.TempField(Offsetof(Product{}.Name), \"LOWER(%s)\"), \"name\")\n// WHERE LOWER(name) = ?\n```\n\n#### Transaction\n\n---\n\nstart a transaction\n\n```golang\nbrick = brick.Begin()\n```\n\nrollback all sql action\n\n```golang\nerr = brick.Rollback()\n```\n\ncommit all sql action\n\n```golang\nerr = brick.Commit()\n```\n\n#### Debug\n\nif Set debug all sql action will have log\n\n```golang\nbrick = brick.Debug()\n```\n\n\n#### IgnoreMode\n\nwhen I Update or Search with struct that have some zero value, did I update it ?\n\n\nuse IgnoreMode to differentiate what zero value should update\n\n```golang\nbrick = brick.IgnoreMode(toyorm.Mode(\"Update\"), toyorm.IgnoreZero ^ toyorm.IgnoreZeroLen)\n// ignore all zeor value but excloud zero len slice\n// now field = []int(nil) will ignore when update\n// but field = []int{} will update when update\n// now field = map[int]int(nil) will ignore when update\n// but field = map[int]int{} will update when update\n```\n\nIn default\n\nOperation | Mode       | affect\n----------|------------|--------\nInsert    | IgnoreNo   | brick.Insert(\u003cstruct\u003e)\nReplace   | IgnoreNo   | brick.Replace(\u003cstruct\u003e)\nCondition | IgnoreZero | brick.WhereGroup(ExprAnd/ExprOr, \u003cstruct\u003e)\nUpdate    | IgnoreZero | brick.Update(\u003cstruct\u003e)\n\n**All of IgnoreMode**\n\nmode              |  effective\n------------------|---------------\nIgnoreFalse       | ignore field type is bool and value is false\nIgnoreZeroInt     | ignore field type is int/uint/uintptr(incloud their 16,32,64 bit type) and value is 0\nIgnoreZeroFloat   | ignore field type is float32/float64 and value is 0.0\nIgnoreZeroComplex | ignore field type is complex64/complex128 and value is 0 + 0i\nIgnoreNilString   | ignore field type is string and value is \"\"\nIgnoreNilPoint    | ignore field type is point/map/slice and value is nil\nIgnoreZeroLen     | ignore field type is map/array/slice and len = 0\nIgnoreNullStruct  | ignore field type is struct and value is zero value struct e.g type A struct{A string,B int}, A{\"\", 0} will be ignore\nIgnoreNil         | ignore with IgnoreNilPoint and IgnoreZeroLen\nIgnoreZero        | ignore all of the above\n\n\n#### BindFields\n\nif bind a not null fields, the IgnoreMode will failure\n\n```golang\n{\n    var p Product\n    result, err := brick.BindDefaultFields(Offsetof(p.Price), Offsetof(p.UpdatedAt)).Update(\u0026Product{\n        Price: 0,\n    })\n   // process error\n   ...\n}\nvar products []Product\nresult, err = brick.Find(\u0026products)\n// process error\n...\n\nfor _, p := range products {\n    fmt.Printf(\"product name %s, price %v\\n\", p.Name, p.Price)\n}\n```\n\n#### Scope\n\nuse scope to do some custom operation\n\n\n```golang\n// desc all order by fields\nbrick.Scope(func(t *ToyBrick) *ToyBrick{\n\n    newOrderBy := make([]*ModelFields, len(t.orderBy))\n    for i, f := range t.orderBy {\n        newOrderBy = append(newOrderBy, t.ToDesc(f))\n    }\n    newt := *t\n    newt.orderBy = newOrderBy\n    return \u0026newt\n})\n```\n\n\n#### Template\n\nuse template exec to replace default exec\n\n\nnow template only support Insert/Save/Update/Find\n\n##### Custom insert\n\n```golang\ndata := Product{\n    Name:  \"bag\",\n    Price: 9999,\n    Count: 2,\n    Tag:   \"container\",\n}\nresult, err := brick.Template(\"INSERT INTO $ModelName($Columns) Values($Values)\").Insert(\u0026data)\n// INSERT INTO product(created_at,updated_at,deleted_at,name,price,count,tag) Values(?,?,?,?,?,?,?) args:[\"2018-04-01T17:05:48.927499+08:00\",\"2018-04-01T17:05:48.927499+08:00\",null,\"bag\",9999,2,\"container\"]\n```\n\n##### Custom find\n\n```golang\nvar data Product\n// if driver is mysql use \"USE INDEX\" replace \"INDEXED BY\"\nresult, err := brick.Template(\"SELECT $Columns FROM $ModelName INDEXED BY idx_product_name $Conditions\").\n    Where(\"=\", Offsetof(Product{}.Name), \"bag\").Find(\u0026data)\n// SELECT id,created_at,updated_at,deleted_at,name,price,count,tag FROM product INDEXED BY idx_product_name  WHERE deleted_at IS NULL AND name = ? LIMIT 1  args:[\"bag\"]\n```\n\n\n##### Custom update\n\nset count = count + 2\n\n```golang\nresult, err := brick.Template(fmt.Sprintf(\"UPDATE $ModelName SET $Values,$FN-Count = $0x%x + ? $Conditions\", Offsetof(Product{}.Count)), 2).\n\tWhere(\"=\", Offsetof(Product{}.Name), \"bag\").Update(\u0026Product{Price: 200})\n// UPDATE product SET updated_at = ?,price = ?,count = count + ?  WHERE deleted_at IS NULL AND name = ?  args:[\"2018-04-01T17:50:35.205377+08:00\",200,2,\"bag\"]\n```\n\n##### Placeholder\n\nfollower placeholder use in template example\n\n\ntwo special placeholder\n\n1. $FN- will convert struct field name to table field name e.g $FN-Name =\u003e name\n2. $0x will convert struct field offset to table field name e.g $0x58 =\u003e Count\n\naction \\\\  placeholder | $ModelName | $Columns    | $Values                | $Conditions\n-----------------------|------------|-------------|------------------| -----------|\nFind                   | product    | id,data,... | -               |  WHERE ... ORDER BY ... GROUP BY ... LIMIT ... OFFSET ...\nInsert                 | product    | id,data,... | ?,?,...         | WHERE ... ORDER BY ... GROUP BY ... LIMIT ... OFFSET ...\nSave                   | product    | id,data,... | ?,?,...           | WHERE ... ORDER BY ... GROUP BY ... LIMIT ... OFFSET ...\nUpdate                 | product    | id,data,... | id = ?,data = ?,...    | WHERE ... ORDER BY ... GROUP BY ... LIMIT ... OFFSET ...\n\n#### Thread safe\n\nThread safe if you comply with the following agreement\n\n1. make sure **ToyBrick** object is read only, if you want to change it, create a new one\n\n2. do not use **append** to change ToyBrick's slice data,use **make** and **copy** to clone new slice\n\n\n#### Preload\n\n---\n\npreload need have relation field and container field\n\n\nrelations field is used to link the main record and sub record\n\n\ncontainer field is used to hold sub record\n\n##### Preload example\n\n[here](examples/preload_example)\n\n##### One to one\n\nrelation field at sub model\n\n\nrelation field name must be main model type name + main model primary key name\n\n```golang\ntype User struct {\n    toyorm.ModelDefault\n    // container field\n    Detail  *UserDetail\n}\n\ntype UserDetail struct {\n    ID       int    `toyorm:\"primary key;auto_increment\"`\n    // relation field\n    UserID   uint   `toyorm:\"index\"`\n    MainPage string `toyorm:\"type:Text\"`\n}\n\n// load preload\nbrick = toy.Model(\u0026User{}).Debug().Preload(OffsetOf(User.Detail)).Enter()\n\n```\n\n##### Belong to\n\nrelation field at main model\n\n\nrelation field name must be container field name + sub model primary key name\n\n```golang\ntype User struct {\n    toyorm.ModelDefault\n    // container field\n    Detail   *UserDetail\n    // relation field\n    DetailID int `toyorm:\"index\"`\n}\n\ntype UserDetail struct {\n    ID       int    `toyorm:\"primary key;auto_increment\"`\n    MainPage string `toyorm:\"type:Text\"`\n}\n\n```\n\n##### One to many\n\nrelation field at sub model\n\n\n\nrelation field name must be main model type name + main model primary key name\n\n\n```golang\ntype User struct {\n    toyorm.ModelDefault\n    // container field\n    Blog    []Blog\n}\n\ntype Blog struct {\n    toyorm.ModelDefault\n    // relation field\n    UserID  uint   `toyorm:\"index\"`\n    Title   string `toyorm:\"index\"`\n    Content string\n}\n\n```\n\n##### Many to many\n\nmany to many not need to specified the relation ship,it relation field at middle model\n\n```\ntype User struct {\n    toyorm.ModelDefault\n    // container field\n    Friends    []*User\n}\n```\n\n##### Load preload\n\nwhen you finish model definition, it time to load preload\n\n```golang\n// create a main brick\nbrick = toy.Model(\u0026User{})\n// create a sub brick\nsubBrick := brick.Preload(OffsetOf(User.Blog))\n// you can editing any attribute what you want, just like editing it on main model\nsubBrick = subBrick.Where(ExprEqual, OffsetOf(Blog.Title), \"my blog\")\n// finished change ,use Enter() go back the main brick\nbrick = subBrick.Enter()\n```\n\n\nif you not like relation field name rule,use custom module to create it\n\n```golang\n// one to one custom\nbrick.CustomOneToOnePreload(\u003cmain container\u003e, \u003csub relation\u003e, [sub model struct])\n// belong to custom\nbrick.CustomBelongToPreload(\u003cmain container\u003e, \u003cmain relation\u003e, [sub model struct])\n// one to many\nbrick.CustomOneToManyPreload(\u003cmain container\u003e, \u003csub relation\u003e, [sub model struct])\n// many to many\nbrick.CustomManyToManyPreload(\u003cmiddle model struct\u003e, \u003cmain container\u003e, \u003cmain relation\u003e, \u003csub relation\u003e, [sub model struct])\n```\n\nor use tag declaration\n\n```golang\ntype UserDetail struct {\n    ID       int    `toyorm:\"primary key;auto_increment\"`\n    MainID   uint32 `toyorm:\"index;one to one:Detail\"` // declaration the container field\n    MainPage string `toyorm:\"type:Text\"`\n    Extra    Extra  `toyorm:\"type:VARCHAR(1024)\"`\n}\n\ntype Blog struct {\n    toyorm.ModelDefault\n    MainID  uint32 `toyorm:\"index;one to many:Blog\"` // declaration the container field\n    Title   string `toyorm:\"index\"`\n    Content string\n}\n\ntype User struct {\n    toyorm.ModelDefault\n    Name    string `toyorm:\"unique index\"`\n    Age     int\n    Sex     string\n    Detail  *UserDetail\n    Friends []*User\n    Blog    []Blog\n}\n\n// now custom relation field is MainID\nbrick = brick.Preload(Offsetof(User{}.Blog)).Enter()\nbrick = brick.Preload(Offsetof(User{}.Detail)).Enter()\n```\n\n#### Join\n\n---\n\ndifferent association query is join query\n\n##### Join Example\n\n[here](examples/join_example)\n\n##### Model Define\n\nuse join tag to association related field/ join tag value must same as container field name\n\n```golang\ntype Extra map[string]interface{}\n\nfunc (e *Extra) Scan(value interface{}) error {\n\tswitch v := value.(type) {\n\tcase string:\n\t\treturn json.Unmarshal([]byte(v), e)\n\tcase []byte:\n\t\treturn json.Unmarshal(v, e)\n\tdefault:\n\t\treturn errors.New(\"not support type\")\n\t}\n}\n\nfunc (e Extra) Value() (driver.Value, error) {\n\treturn json.Marshal(e)\n}\n\ntype Color struct {\n\tName string `toyorm:\"primary key;join:ColorDetail\"`\n\tCode int32\n}\n\ntype Comment struct {\n\ttoyorm.ModelDefault\n\tProductDetailProductID uint32 `toyorm:\"index\"`\n\tData                   string `toyorm:\"type:VARCHAR(1024)\"`\n}\n\ntype ProductDetail struct {\n\tProductID  uint32 `toyorm:\"primary key;join:Detail\"`\n\tTitle      string\n\tCustomPage string `toyorm:\"type:text\"`\n\tExtra      Extra  `toyorm:\"type:VARCHAR(2048)\"`\n\tColor      string `toyorm:\"join:ColorDetail\"`\n\tColorJoin  Color  `toyorm:\"alias:ColorDetail\"`\n\tComment    []Comment\n}\n\ntype Product struct {\n\tID        uint32     `toyorm:\"primary key;auto_increment;join:Detail\"`\n\tCreatedAt time.Time  `toyorm:\"NULL\"`\n\tDeletedAt *time.Time `toyorm:\"NULL\"`\n\tName      string\n\tCount     int\n\tPrice     float64\n\tDetail    *ProductDetail\n}\n```\n\n##### Join in Find\n\nSwap on Join just like Enter on Preload, can back to previous data\n\n```golang\nbrick := toy.Model(\u0026tab).Debug().\n    Join(Offsetof(tab.Detail)).\n    Join(Offsetof(detailTab.ColorJoin)).Swap().Swap()\nvar scanData []Product\nresult, err = brick.Find(\u0026scanData)\n// SELECT m.id,m.created_at,m.deleted_at,m.name,m.count,m.price,m_0.product_id,m_0.title,m_0.custom_page,m_0.extra,m_0.color,m_0_0.name,m_0_0.code FROM `product` as `m` JOIN `product_detail` AS `m_0` ON m.id = m_0.product_id JOIN `color` AS `m_0_0` ON m_0.color = m_0_0.name   WHERE m.deleted_at IS NULL\n```\n\nuse Join to switch current model and add conditions\n\n```golang\n// where Product.Name = \"clean stick\" or Color.Name = \"black\"\nbrick := toy.Model(\u0026tab).Debug().Where(\"=\", Offsetof(tab.Name), \"clean stick\").\n    Join(Offsetof(tab.Detail)).\n    Join(Offsetof(detailTab.ColorJoin)).Or().Condition(\"=\", Offsetof(colorTab.Name), \"black\").\n    Swap().Swap()\nvar scanData []Product\nresult, err = brick.Find(\u0026scanData)\n// SELECT m.id,m.created_at,m.deleted_at,m.name,m.count,m.price,m_0.product_id,m_0.title,m_0.custom_page,m_0.extra,m_0.color,m_0_0.name,m_0_0.code FROM `product` as `m` JOIN `product_detail` AS `m_0` ON m.id = m_0.product_id JOIN `color` AS `m_0_0` ON m_0.color = m_0_0.name   WHERE m.deleted_at IS NULL AND (m.name = ? OR m_0_0.name = ?)  args:[\"clean stick\",\"black\"]\n```\n\nset order just like add conditions\n\n```golang\n// where Product.Name = \"clean stick\" or Color.Name = \"black\"\nbrick := toy.Model(\u0026tab).Debug().\n    Join(Offsetof(tab.Detail)).\n    Join(Offsetof(detailTab.ColorJoin)).OrderBy(Offsetof(colorTab.Name)).\n    Swap().Swap()\nvar scanData []Product\nresult, err = brick.Find(\u0026scanData)\n// SELECT m.id,m.created_at,m.deleted_at,m.name,m.count,m.price,m_0.product_id,m_0.title,m_0.custom_page,m_0.extra,m_0.color,m_0_0.name,m_0_0.code FROM `product` as `m` JOIN `product_detail` AS `m_0` ON m.id = m_0.product_id JOIN `color` AS `m_0_0` ON m_0.color = m_0_0.name   WHERE m.deleted_at IS NULL ORDER BY m_0_0.name\n```\n\nalso can set GroupBy but here not example\n\n##### Preload On Join\n\nPreload method also work on Join mode\n\n```golang\nbrick := toy.Model(\u0026tab).Debug().\n    Join(Offsetof(tab.Detail)).Preload(Offsetof(detailTab.Comment)).Enter().\n    Join(Offsetof(detailTab.ColorJoin)).Swap().Swap()\nvar scanData []Product\nresult, err = brick.Find(\u0026scanData)\n// SELECT m.id,m.created_at,m.deleted_at,m.name,m.count,m.price,m_0.product_id,m_0.title,m_0.custom_page,m_0.extra,m_0.color,m_0_0.name,m_0_0.code FROM `product` as `m` JOIN `product_detail` AS `m_0` ON m.id = m_0.product_id JOIN `color` AS `m_0_0` ON m_0.color = m_0_0.name   WHERE m.deleted_at IS NULL\n// SELECT id,created_at,updated_at,deleted_at,product_detail_product_id,data FROM `comment`   WHERE deleted_at IS NULL AND product_detail_product_id IN (?,?,?)  args:[1,2,3]\n```\n\n### Custom Table Name\n\ncustom your table name with different platform\n\n```golang\ntype User struct {\n    ID uint32 `toyorm:\"primary key\"`\n    Name string `toyorm:\"index\"`\n    Platform string `toyorm:\"-\"`\n}\nfunc (u *User) TableName() string {\n    return \"user_\" + u.Platform\n}\n\nbrick := toy.Model(\u0026User{Platform:\"p1\"}).Debug()\nbrick.CreateTable()\n// CREATE TABLE user_p1 (id BIGINT AUTO_INCREMENT,name VARCHAR(255), PRIMARY KEY(id))\nbrick := toy.Model(\u0026User{Platform:\"p2\"}).Debug()\nbrick.CreateTable()\n// CREATE TABLE user_p2 (id BIGINT AUTO_INCREMENT,name VARCHAR(255), PRIMARY KEY(id))\n```\n\ntable name method also work on Preload and Join\n\n```golang\ntype UserDetail struct {\n    ID uint32 `toyorm:\"primary key\"`\n    UserID uint32\n    Data string\n}\nfunc (u *UserDetail) TableName() string {\n    return \"user_detail_\" + u.Platform\n}\n\ntype User struct {\n    ID uint32 `toyorm:\"primary key\"`\n    Name string `toyorm:\"index\"`\n    Detail *UserDetail\n    Platform string `toyorm:\"-\"`\n}\nfunc (u *User) TableName() string {\n    return \"user_\" + u.Platform\n}\n\nbrick := toy.Model(\u0026User{Platform:\"p1\", Detail:\u0026UserDetail{Platform:\"p1\"}}).Debug().\n    Preload(Offsetof(User{}.UserDetail)).Enter()\nbrick.CreateTable()\n// CREATE TABLE user_p1 (id BIGINT AUTO_INCREMENT,name VARCHAR(255), PRIMARY KEY(id))\n// CREATE TABLE user_detail_p1 (id BIGINT AUTO_INCREMENT, user_id BIGINT, data VARCHAR(255), PRIMARY KEY(id))\n```\n\n\nin one to many or many to many mode , need to set it's first element val\n\n```golang\ntype Address struct {\n    ID uint32 `toyorm:\"primary key\"`\n    UserID uint32\n    Addr string\n    Platform string `toyorm:\"-\"`\n}\nfunc (a *Address) TableName() string {\n    return \"address_\" + a.Platform\n}\ntype User struct {\n    ID uint32 `toyorm:\"primary key\"`\n    Name string `toyorm:\"index\"`\n    Addresses []Address\n    Platform string `toyorm:\"-\"`\n}\nfunc (u *User) TableName() string {\n    return \"user_\" + u.Platform\n}\n\nbrick := toy.Model(\u0026User{Platform:\"p1\", Addresses:[]Address{{Platform:\"p1\"}}}).Debug().\n    Preload(Offsetof(User{}.Addresses)).Enter()\nbrick.CreateTable()\n// CREATE TABLE user_p1 (id BIGINT AUTO_INCREMENT,name VARCHAR(255), PRIMARY KEY(id))\n// CREATE TABLE address_p1 (id BIGINT AUTO_INCREMENT, user_id BIGINT, addr VARCHAR(255), PRIMARY KEY(id))\n```\n\n### Result\n\n**use Report to view sql action**\n\nreport format\n\ninsert\n\n```golang\nuser := User{\n    Detail: \u0026UserDetail{\n        MainPage: \"some html code with you page\",\n        Extra:    Extra{\"title\": \"my blog\"},\n    },\n    Blog: []Blog{\n        {Title: \"how to write a blog\", Content: \"first ...\"},\n        {Title: \"blog introduction\", Content: \"...\"},\n    },\n    Friends: []*User{\n        {\n            Detail: \u0026UserDetail{\n                MainPage: \"some html code with you page\",\n                Extra:    Extra{},\n            },\n            Blog: []Blog{\n                {Title: \"some python tech\", Content: \"first ...\"},\n                {Title: \"my eleme_union_meal usage\", Content: \"...\"},\n            },\n            Name: \"fatpigeon\",\n            Age:  18,\n            Sex:  \"male\",\n        },\n    },\n    Name: \"bigpigeon\",\n    Age:  18,\n    Sex:  \"male\",\n}\nresult, err = brick.Save(\u0026user)\n// error process ...\nfmt.Printf(\"report:\\n%s\\n\", result.Report())\n\n/*\n// [0, ] means affected the 0 element\n// [0-0, ] means affected the 0 element the 0 sub element\nreport:\n[0, ] INSERT INTO user(created_at,updated_at,deleted_at,name,age,sex) VALUES(?,?,?,?,?,?)  args:[\"2018-02-28T17:31:20.012285+08:00\",\"2018-02-28T17:31:20.012285+08:00\",null,\"bigpigeon\",18,\"male\"]\n\tpreload Detail\n\t[0-, ] INSERT INTO user_detail(user_id,main_page,extra) VALUES(?,?,?)  args:[1,\"some html code with you page\",{\"title\":\"my blog\"}]\n\tpreload Blog\n\t[0-0, ] INSERT INTO blog(created_at,updated_at,deleted_at,user_id,title,content) VALUES(?,?,?,?,?,?)  args:[\"2018-02-28T17:31:20.013968+08:00\",\"2018-02-28T17:31:20.013968+08:00\",null,1,\"how to write a blog\",\"first ...\"]\n\t[0-1, ] INSERT INTO blog(created_at,updated_at,deleted_at,user_id,title,content) VALUES(?,?,?,?,?,?)  args:[\"2018-02-28T17:31:20.013968+08:00\",\"2018-02-28T17:31:20.013968+08:00\",null,1,\"blog introduction\",\"...\"]\n\tpreload Friends\n\t[0-0, ] INSERT INTO user(created_at,updated_at,deleted_at,name,age,sex) VALUES(?,?,?,?,?,?)  args:[\"2018-02-28T17:31:20.015207+08:00\",\"2018-02-28T17:31:20.015207+08:00\",null,\"fatpigeon\",18,\"male\"]\n\t\tpreload Detail\n\t\t[0-0-, ] INSERT INTO user_detail(user_id,main_page,extra) VALUES(?,?,?)  args:[2,\"some html code with you page\",{}]\n\t\tpreload Blog\n\t\t[0-0-0, ] INSERT INTO blog(created_at,updated_at,deleted_at,user_id,title,content) VALUES(?,?,?,?,?,?)  args:[\"2018-02-28T17:31:20.016389+08:00\",\"2018-02-28T17:31:20.016389+08:00\",null,2,\"some python tech\",\"first ...\"]\n\t\t[0-0-1, ] INSERT INTO blog(created_at,updated_at,deleted_at,user_id,title,content) VALUES(?,?,?,?,?,?)  args:[\"2018-02-28T17:31:20.016389+08:00\",\"2018-02-28T17:31:20.016389+08:00\",null,2,\"my eleme_union_meal usage\",\"...\"]\n*/\n```\n\nfind\n\n```golang\nbrick := brick.Preload(Offsetof(User{}.Friends)).\n    Preload(Offsetof(User{}.Detail)).Enter().\n    Preload(Offsetof(User{}.Blog)).Enter().\n    Enter()\nvar users []User\nresult, err = brick.Find(\u0026users)\n// some error process\n...\n// print the report\nfmt.Printf(\"report:\\n%s\\n\", result.Report())\n\n// report log\n/*\nreport:\n[0, 1, ] SELECT id,created_at,updated_at,deleted_at,name,age,sex FROM user WHERE deleted_at IS NULL  args:null\n\tpreload Detail\n\t[0-, 1-, ] SELECT id,user_id,main_page,extra FROM user_detail WHERE user_id IN (?,?)  args:[2,1]\n\tpreload Blog\n\t[0-0, 0-1, 1-0, 1-1, ] SELECT id,created_at,updated_at,deleted_at,user_id,title,content FROM blog WHERE deleted_at IS NULL AND user_id IN (?,?)  args:[1,2]\n\tpreload Friends\n\t[0-0, ] SELECT id,created_at,updated_at,deleted_at,name,age,sex FROM user WHERE deleted_at IS NULL AND id IN (?)  args:[2]\n\t\tpreload Detail\n\t\t[0-0-, ] SELECT id,user_id,main_page,extra FROM user_detail WHERE user_id IN (?)  args:[2]\n\t\tpreload Blog\n\t\t[0-0-0, 0-0-1, ] SELECT id,created_at,updated_at,deleted_at,user_id,title,content FROM blog WHERE deleted_at IS NULL AND user_id IN (?)  args:[2]\n*/\n\n```\n\n**use Err to view sql action error**\n\n```golang\nvar users []struct {\n    ID     uint32\n    Age    bool\n    Detail *UserDetail\n    Blog   []Blog\n}\nresult, err = brick.Find(\u0026users)\nif err != nil {\n    panic(err)\n}\nif err := result.Err(); err != nil {\n    fmt.Printf(\"error:\\n%s\\n\", err)\n}\n\n/*\nerror:\nSELECT id,age FROM user WHERE deleted_at IS NULL  args:null errors(\n\t[0]sql: Scan error on column index 1: sql/driver: couldn't convert \"18\" into type bool\n\t[1]sql: Scan error on column index 1: sql/driver: couldn't convert \"18\" into type bool\n)\n*/\n```\n\n#### Selector\n\ntoyorm support following selector\n\noperation  \\\\  selector | OffsetOf | Name string | map\\[OffsetOf\\]interface{} | map\\[string\\]interface{} | struct |\n--------------------|----------|-----------------|--------------------------------|--------------------------|--------|\nUpdate              | no       | no              | yes                            | yes                      | yes\nInsert              | no       | no              | yes                            | yes                      | yes\nSave                | no       | no              | yes                            | yes                      | yes\nWhere \u0026 Conditions  | yes      | yes             | no                             | no                       | no\nWhereGroup \u0026 ConditionGroup | no |  no           | yes                            | yes                      | yes\nBindFields          | yes      | yes             | no                             | no                       | no\nPreload \u0026 Custom Preload | yes | yes             | no                             | no                       | no\nOrderBy             | yes      | yes             | no                             | no                       | no\nFind                | no       | no              | no                             | no                       | yes\n\n\n\n\n\n## Collection\n\n\ncollection provide multiple database operation\n\n### ToyCollection\n\nToyCollection is basic of the collection , it like Toy\n\n```toyorm\ntoyCollection, err = toyorm.OpenCollection(\"sqlite3\", []string{\"\", \"\"}...)\n```\n\n### CollectionBrick\n\nCollectionBrick use to build grammar and operate the database, like ToyBrick\n\n```toyorm\nbrick := toyCollection.Model(\u0026User{})\n```\n\n#### Selector\n\nSelector use to select database when Insert/Save\n\n\nCollectionBrick has default selector **dbPrimaryKeySelector**\n\n\nyou can custom db selector\n\n\n```toyorm\nfunc idSelector(n int, keys ...interface{}) int {\n\tsum := 0\n\tfor _, k := range keys {\n\t\tswitch val := k.(type) {\n\t\tcase int:\n\t\t\tsum += val\n\t\tcase int32:\n\t\t\tsum += int(val)\n\t\tcase uint:\n\t\t\tsum += int(val)\n\t\tcase uint32:\n\t\t\tsum += int(val)\n\t\tdefault:\n\t\t\tpanic(\"primary key type not match\")\n\t\t}\n\t}\n\treturn sum % n\n}\n\nbrick = brick.Selector(idSelector)\n```\n\n#### id generator\n\nin this mode,Field tag **auto_increment** was invalid\n\n\nyou need create id generator\n\n\n```golang\n// a context id generator\ntype IDGenerator map[*toyorm.Model]chan int\n\nfunc (g IDGenerator) CollectionIDGenerate(ctx *toyorm.CollectionContext) error {\n\tif g[ctx.Brick.Model] == nil {\n\t\tidGenerate := make(chan int)\n\t\tgo func() {\n\t\t\tcurrent := 1\n\t\t\tfor {\n\t\t\t\t// if have redis, use redis-cli\n\t\t\t\tidGenerate \u003c- current\n\t\t\t\tcurrent++\n\t\t\t}\n\n\t\t}()\n\t\tg[ctx.Brick.Model] = idGenerate\n\t}\n\tprimaryKey := ctx.Brick.Model.GetOnePrimary()\n\tfor _, record := range ctx.Result.Records.GetRecords() {\n\t\tif field := record.Field(primaryKey.Name()); field.IsValid() == false || toyorm.IsZero(field) {\n\t\t\tv := \u003c-g[ctx.Brick.Model]\n\t\t\trecord.SetField(primaryKey.Name(), reflect.ValueOf(v))\n\t\t}\n\t}\n\treturn nil\n\n}\n\n// set id genetor to model context\ntoy.SetModelHandlers(\"Save\", brick.Model, toyorm.CollectionHandlersChain{idGenerate.CollectionIDGenerate})\n// set id genetor to all preload models context\nfor _, pBrick := range brick.MapPreloadBrick {\n\t\ttoy.SetModelHandlers(\"Save\", pBrick.Model, toyorm.CollectionHandlersChain{idGenerate.CollectionIDGenerate})\n}\n```\n\n\n#### sql action\n\ntoy collection sql action is same as Toy\n\n\ninsert\n\n```golang\nusers := []User{\n    {Name: \"Turing\"},\n    {Name: \"Shannon\"},\n    {Name: \"Ritchie\"},\n    {Name: \"Jobs\"},\n}\nresult, err = userBrick.Insert(\u0026users)\n// error process\n\n// view report log\nfmt.Printf(\"report:\\n%s\", result.Report())\n```\n\nfind\n\n```golang\nvar jobs User\nresult, err = userBrick.Where(toyorm.ExprEqual, Offsetof(User{}.Name), \"Jobs\").Find(\u0026jobs)\n// error process\n\n// view report log\nfmt.Printf(\"report:\\n%s\", result.Report())\n```\n\ndelete\n\n```golang\nresult, err = userBrick.Delete(\u0026jobs)\n// error process\n\n// view report log\nfmt.Printf(\"delete report:\\n%s\\n\", result.Report())\n```\n\n### collection example\n\n[here](examples/collection_example)\n\n## toy-doctor\n\nparameter check within ToyBrick method call\n\n[toy-doctor](https://github.com/bigpigeon/toy-doctor)","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbigpigeon%2Ftoyorm","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbigpigeon%2Ftoyorm","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbigpigeon%2Ftoyorm/lists"}