{"id":28546414,"url":"https://github.com/ulule/makroud","last_synced_at":"2025-10-11T20:33:32.855Z","repository":{"id":39618283,"uuid":"73715330","full_name":"ulule/makroud","owner":"ulule","description":"A high level SQL Connector","archived":false,"fork":false,"pushed_at":"2022-11-01T16:22:57.000Z","size":1589,"stargazers_count":11,"open_issues_count":12,"forks_count":1,"subscribers_count":8,"default_branch":"master","last_synced_at":"2025-10-04T04:48:45.788Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","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/ulule.png","metadata":{"files":{"readme":"README.md","changelog":null,"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":"2016-11-14T14:56:43.000Z","updated_at":"2021-11-09T16:51:53.000Z","dependencies_parsed_at":"2022-09-16T11:32:18.335Z","dependency_job_id":null,"html_url":"https://github.com/ulule/makroud","commit_stats":null,"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"purl":"pkg:github/ulule/makroud","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ulule%2Fmakroud","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ulule%2Fmakroud/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ulule%2Fmakroud/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ulule%2Fmakroud/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ulule","download_url":"https://codeload.github.com/ulule/makroud/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ulule%2Fmakroud/sbom","scorecard":{"id":908237,"data":{"date":"2025-08-11","repo":{"name":"github.com/ulule/makroud","commit":"d3179a4707b8cd82dd8e1d72b8df98fb2edce7c2"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":4,"checks":[{"name":"Code-Review","score":4,"reason":"Found 4/9 approved changesets -- score normalized to 4","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Pinned-Dependencies","score":3,"reason":"dependency not pinned by hash detected -- score normalized to 3","details":["Warn: containerImage not pinned by hash: scripts/conf/go/Dockerfile:1: pin your Docker image by updating golang:1-buster to golang:1-buster@sha256:eb3f9ac805435c1b2c965d63ce460988e1000058e1f67881324746362baf9572","Warn: goCommand not pinned by hash: scripts/lint:13","Info:   0 out of   1 containerImage dependencies pinned","Info:   1 out of   2 goCommand dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":-1,"reason":"internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration","details":null,"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 27 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-24T18:10:52.875Z","repository_id":39618283,"created_at":"2025-08-24T18:10:52.875Z","updated_at":"2025-08-24T18:10:52.875Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279008617,"owners_count":26084480,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-11T02:00:06.511Z","response_time":55,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":[],"created_at":"2025-06-09T23:39:17.953Z","updated_at":"2025-10-11T20:33:32.849Z","avatar_url":"https://github.com/ulule.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Makroud\n\n[![CircleCI][circle-img]][circle-url]\n[![Documentation][godoc-img]][godoc-url]\n![License][license-img]\n\n_A high level SQL Connector._\n\n## Introduction\n\nMakroud is a high level SQL Connector that only support **PostgreSQL** at the moment.\n\nIt's an advanced mapper and/or a lightweight ORM that relies on reflection to generate queries.\n**Using reflection has its flaws**, type safety is not guaranteed and a panic is still possible,\neven if you are very careful and vigilant. **However,** development is super easy and straightforward\nsince it doesn't rely on code generation.\n\nMakroud isn't a migration tool and it doesn't inspect the database to define the application data model\n_(since there is no code generation)_. It's **really** important to have Active Record that are synchronized with your\ndata model in your database.\n\nIt also support simple associations _(one, many)_ with preloading.\n\nUnder the hood, it relies on [Loukoum](https://github.com/ulule/loukoum) for query generation.\nIn addition, it's heavily inspired by [Sqlx](https://github.com/jmoiron/sqlx) for its extended mapper and\n[Sqalx](https://github.com/heetch/sqalx) to support nested transaction.\n\n## Installation\n\nUsing [Go Modules](https://github.com/golang/go/wiki/Modules)\n\n```console\ngo get github.com/ulule/makroud@v0.8.0\n```\n\n## Usage\n\n### Create a Driver\n\nA **Driver** is a high level abstraction of a database connection or a transaction.\nIt's almost required everytime alongside a `context.Context` to manipulate rows.\n\n```go\ndriver, err := makroud.New(\n\tmakroud.Host(cfg.Host),\n\tmakroud.Port(cfg.Port),\n\tmakroud.User(cfg.User),\n\tmakroud.Password(cfg.Password),\n\tmakroud.Database(cfg.Name),\n\tmakroud.SSLMode(cfg.SSLMode),\n\tmakroud.MaxOpenConnections(cfg.MaxOpenConnections),\n\tmakroud.MaxIdleConnections(cfg.MaxIdleConnections),\n)\n```\n\nAlso, you can use a struct directly if you don't need to use\n[functional options](https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis):\n\n```go\ndriver, err := makroud.NewWithOptions(\u0026makroud.ClientOptions{\n\tHost:               cfg.Host,\n\tPort:               cfg.Port,\n\tUser:               cfg.User,\n\tPassword:           cfg.Password,\n\tDatabase:           cfg.Name,\n\tSSLMode:            cfg.SSLMode,\n\tMaxOpenConnections: cfg.MaxOpenConnections,\n\tMaxIdleConnections: cfg.MaxIdleConnections,\n})\n```\n\n### Advanced mapper\n\nIf a lightweight ORM doesn't fit your requirements and an advanced mapper is enough for your usecase:\nmakroud does that very well.\n\nYou could either use primitive and compound types for your queries:\n\n```go\nimport \"github.com/ulule/makroud\"\n\nstmt := `SELECT id FROM users WHERE email = 'john.doe@example.com'`\nid := int64(0)\n\nerr := makroud.RawExec(ctx, driver, stmt, \u0026id)\nif err != nil {\n\treturn err\n}\n\nstmt = `SELECT created_at FROM users WHERE id = 42`\ncreated := time.Time{}\n\nerr := makroud.RawExec(ctx, driver, stmt, \u0026created)\nif err != nil {\n\treturn err\n}\n\nstmt = `SELECT email FROM users WHERE id IN (1, 2, 3, 4)`\nlist := []string{}\n\nerr := makroud.RawExec(ctx, driver, stmt, \u0026list)\nif err != nil {\n\treturn err\n}\n```\n\nOr, define a struct that contains your database table _(or view)_ columns:\n\n```go\nimport \"github.com/ulule/makroud\"\nimport \"github.com/ulule/loukoum/v3\"\n\ntype User struct {\n\tID       int64  `mk:\"id\"`\n\tEmail    string `mk:\"email\"`\n\tPassword string `mk:\"password\"`\n\tCountry  string `mk:\"country\"`\n\tLocale   string `mk:\"locale\"`\n}\n\nusers := []User{}\nstmt := loukoum.Select(\"*\").\n\tFrom(\"users\").\n\tWhere(loukoum.Condition(\"id\").In(1, 2, 3, 4))\n\nerr := makroud.Exec(ctx, driver, stmt, \u0026users)\nif err != nil {\n\treturn err\n}\n```\n\n### Lightweight ORM\n\n#### Define a Model\n\nWith the [**Active Record**](https://en.wikipedia.org/wiki/Active_record_pattern) approach, you have to define a\nmodel that wraps your database table _(or view)_ columns into properties.\n\nModels are structs that contain basic go types, pointers, `sql.Scanner`, `driver.Valuer` or `Model` interface.\nAll the fields of this struct will be columns in the database table.\n\n#### An example\n\n```go\ntype User struct {\n\t// Columns\n\tID        string `makroud:\"column:id,pk:ulid\"`\n\tEmail     string `makroud:\"column:email\"`\n\tPassword  string `makroud:\"column:password\"`\n\tCountry   string `makroud:\"column:country\"`\n\tLocale    string `makroud:\"column:locale\"`\n\tProfileID string `makroud:\"column:profile_id,fk:profiles\"`\n\t// Relationships\n\tRoles    []Role    `makroud:\"relation:roles.user_id\"`\n\tProfile  *Profile  `makroud:\"relation:profile_id\"`\n}\n\nfunc (User) TableName() string {\n\treturn \"users\"\n}\n```\n\n#### What does it means ?\n\nFirst of all, you have to define a `TableName` method that returns the database table name _(or view)_.\nWithout that information, `makroud` cannot uses that struct as a `Model`.\n\nThen, you have to define your model columns using struct tags:\n\n- **column**(`string`): Define column name.\n- **pk**(`bool|string`): Define column as a primary key, it accepts the following argument:\n  - **true**: Uses internal db mechanism to define primary key value\n  - **ulid**: Generate a [ULID](https://github.com/ulid/spec) to define primary key value\n  - **uuid-v1**: Generate a [UUID V1](\u003chttps://en.wikipedia.org/wiki/Universally_unique_identifier#Version_1_(date-time_and_MAC_address)\u003e)\n    to define primary key value\n  - **uuid-v4**: Generate a [UUID V4](\u003chttps://en.wikipedia.org/wiki/Universally_unique_identifier#Version_4_(random)\u003e)\n    to define primary key value\n- **default**(`bool`): On insert, if model has a zero value, it will use the db default value.\n- **fk**(`string`): Define column as a foreign key, reference table must be provided.\n- **relation**(`string`): Define which column to use for preload. The column must be prefixed by the table name\n  if it's not the model table name _(However, the prefix is optional if the table name is the same as the model)_.\n  See [Preload](https://github.com/ulule/makroud#preload) section for further information.\n- **-**(`bool`): Ignore this field.\n\n\u003e **NOTE:** Tags of type `bool` can be set as `key:true` or just `key` for implicit `true`.\n\n\u003e **NOTE:** Tags must be separated by a comma (`tagA, tagB, tagC`).\n\nKeep in mind that a model **requires one primary key (and just one)**. It's a known limitation that only one primary\nkey can be specified and it can't be a composite key.\n\nAfter that, you can define optional relationships _(or associations)_ that can be preloaded later.\nThe preload mechanism, which enables you to fetch relationships from database, support these types:\n\n- `Model`\n- `*Model`\n- `[]Model`\n- `[]*Model`\n- `*[]Model`\n- `*[]*Model`\n\n\u003e **NOTE:** You could either use `makroud` or `mk` as tag identifier.\n\n#### Conventions\n\n##### ID as Primary Key\n\nBy default, if the `pk` tag is undefined, `makroud` will use the field named `ID` as primary key with\nthis configuration: `pk:db`\n\n```go\ntype User struct {\n\tID   string `makroud:\"column:id\"`   // Field named ID will be used as a primary key by default.\n\tName string `makroud:\"column:name\"`\n}\n```\n\n##### Snake Case Column Name\n\nBy default, if the `column` tag is undefined, `makroud` will transform field name to lower snake case as column name.\n\n```go\ntype User struct {\n\tID   string `makroud:\"pk\"` // Column name is `id`\n\tName string `makroud:\"\"`   // Column name is `name`\n}\n```\n\n##### Preload relationships\n\nBy default, if the `relation` tag is undefined, `makroud` will infer the column name to use for the preload mechanism.\n\n**Local foreign key:**\n\nLet's define a user with a profile:\n\n```go\ntype User struct {\n\tID       string  `makroud:\"column:id,pk\"`\n\tEmail    string  `makroud:\"column:email\"`\n\tPID      string  `makroud:\"column:profile_id,fk:profiles\"`\n\tProfile  *Profile\n}\n\nfunc (User) TableName() string {\n\treturn \"users\"\n}\n\ntype Profile struct {\n\tID         string  `makroud:\"column:id,pk:ulid\"`\n\tFirstName  string  `makroud:\"column:first_name\"`\n\tLastName   string  `makroud:\"column:last_name\"`\n\tEnabled    bool    `makroud:\"column:enabled\"`\n}\n\nfunc (Profile) TableName() string {\n\treturn \"profiles\"\n}\n```\n\nSince the field `Profile` in the `User` has no `relation` tag, `makroud` will try to find, in the first pass,\nthe field with the name `ProfileID` _(FieldName + ID)_ in `User` model.\nIt's mandatory that this field is also a foreign key to the `profiles` table.\n\nUnfortunately for us, `User` model has no such field. So, `makroud` will try to find, in the second and final pass,\nthe first field that is a foreign key to the `profiles` table. In our example, it will use the field `PID`.\n\n**Remote foreign key:**\n\nLet's define a user with a profile:\n\n```go\ntype User struct {\n\tID       string  `makroud:\"column:id,pk\"`\n\tEmail    string  `makroud:\"column:email\"`\n\tProfile  *Profile\n}\n\nfunc (User) TableName() string {\n\treturn \"users\"\n}\n\ntype Profile struct {\n\tID         string  `makroud:\"column:id,pk:ulid\"`\n\tFirstName  string  `makroud:\"column:first_name\"`\n\tLastName   string  `makroud:\"column:last_name\"`\n\tEnabled    bool    `makroud:\"column:enabled\"`\n\tUID        string  `makroud:\"column:user_id,fk:users\"`\n}\n\nfunc (Profile) TableName() string {\n\treturn \"profiles\"\n}\n```\n\nSince the field `Profile` in the `User` has no `relation` tag, `makroud` will try to find, in the first pass,\nthe field with the name `UserID` _(ModelName + ID)_ in `Profile` model.\nIt's mandatory that this field is also a foreign key to the `users` table.\n\nUnfortunately for us, `Profile` model has no such field. So, `makroud` will try to find, in the second and final pass,\nthe first field that is a foreign key to the `users` table. In our example, it will use the field `UID`.\n\n##### CreatedAt tracking\n\nFor models having a `CreatedAt` field, it will be set to current time when the record is first created.\n\n```go\ntype User struct {\n\tID        string    `makroud:\"column:id,pk\"`\n\tName      string    `makroud:\"column:name\"`\n\tCreatedAt time.Time `makroud:\"column:created_at\"`\n}\n```\n\nYou can override the default field name and/or column name by adding this method:\n\n```go\nfunc (User) CreatedKey() string {\n\treturn \"created\"\n}\n```\n\n##### UpdatedAt tracking\n\nFor models having a `UpdatedAt` field, it will be set to current time when records are updated.\n\n```go\ntype User struct {\n\tID        string    `makroud:\"column:id,pk\"`\n\tName      string    `makroud:\"column:name\"`\n\tUpdatedAt time.Time `makroud:\"column:updated_at\"`\n}\n```\n\nYou can override the default field name and/or column name by adding this method:\n\n```go\nfunc (User) UpdatedKey() string {\n\treturn \"updated\"\n}\n```\n\n##### DeletedAt tracking\n\nFor models having a `DeletedAt` field, it will be set to current time when records are archived.\n\n```go\ntype User struct {\n\tID        string      `makroud:\"column:id,pk\"`\n\tName      string      `makroud:\"column:name\"`\n\tDeletedAt pq.NullTime `makroud:\"column:deleted_at\"`\n}\n```\n\nYou can override the default field name and/or column name by adding this method:\n\n```go\nfunc (User) DeletedKey() string {\n\treturn \"deleted\"\n}\n```\n\n### Operations\n\nFor the following sections, we assume that you have a `context.Context` and a `makroud.Driver` instance.\n\n#### Insert\n\nFor a simple insert, you can save a model like this:\n\n```go\nfunc CreateUser(ctx context.Context, driver makroud.Driver, name string) (*User, error) {\n\tuser := \u0026User{\n\t\tName: name,\n\t}\n\n\terr := makroud.Save(ctx, driver, user)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn user, nil\n}\n```\n\nOr for more complex statements, use a [Loukoum](https://github.com/ulule/loukoum) `InsertBuilder` alongside the model.\n\n```go\nimport \"github.com/ulule/loukoum/v3\"\n\nfunc CreateUser(ctx context.Context, driver makroud.Driver, name string) (*User, error) {\n\tuser := \u0026User{\n\t\tName: name,\n\t}\n\n\tstmt := loukoum.Insert(\"users\").\n\t\tSet(loukoum.Pair(\"name\", user.Name)).\n\t\tReturning(\"id, created_at, updated_at\")\n\n\terr := makroud.Exec(ctx, driver, stmt, user)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn user, nil\n}\n```\n\n#### Update\n\nFor a simple update, asumming your model have a primary key defined, you can save it by executing:\n\n```go\nfunc UpdateUser(ctx context.Context, driver makroud.Driver, user *User, name string) error {\n\tuser.Name = name\n\treturn makroud.Save(ctx, driver, user)\n}\n```\n\nOr for more complex statements, use a [Loukoum](https://github.com/ulule/loukoum) `UpdateBuilder` alongside the model.\n\n```go\nimport \"github.com/ulule/loukoum/v3\"\n\nfunc UpdateUser(ctx context.Context, driver makroud.Driver, user *User, name string) error {\n\tuser.Name = name\n\n\tstmt := loukoum.Update(\"users\").\n\t\tSet(\n\t\t\tloukoum.Pair(\"updated_at\", loukoum.Raw(\"NOW()\")),\n\t\t\tloukoum.Pair(\"name\", user.Name),\n\t\t).\n\t\tWhere(loukoum.Condition(\"id\").Equal(user.ID)).\n\t\tReturning(\"updated_at\")\n\n\terr := makroud.Exec(ctx, driver, stmt, user)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn user, nil\n}\n```\n\n#### Delete\n\nFor a simple delete _(using a `DELETE` statement)_, asumming your model have a primary key defined,\nyou can delete it using:\n\n```go\nfunc DeleteUser(ctx context.Context, driver makroud.Driver, user *User) error {\n\treturn makroud.Delete(ctx, driver, user)\n}\n```\n\nOr for more complex statements, use a [Loukoum](https://github.com/ulule/loukoum) `DeleteBuilder` alongside the model.\n\n```go\nimport \"github.com/ulule/loukoum/v3\"\n\nfunc DeleteUser(ctx context.Context, driver makroud.Driver, user *User) error {\n\tstmt := loukoum.Delete(\"users\").Where(loukoum.Condition(\"id\").Equal(user.ID))\n\n\terr := makroud.Exec(ctx, driver, stmt, user)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn user, nil\n}\n```\n\n#### Archive\n\nArchive executes an `UPDATE` on `DeletedAt` field on given value.\n\n```go\nfunc ArchiveUser(ctx context.Context, driver makroud.Driver, user *User) error {\n\treturn makroud.Archive(ctx, driver, user)\n}\n```\n\n\u003e **NOTE**: If the model has no `DeletedAt` field, an error is returned.\n\nOr for more complex statements, use a [Loukoum](https://github.com/ulule/loukoum) `UpdateBuilder` alongside the model.\n\n```go\nimport \"github.com/ulule/loukoum/v3\"\n\nfunc ArchiveUser(ctx context.Context, driver makroud.Driver, user *User) error {\n\tuser.Name = name\n\n\tstmt := loukoum.Update(\"users\").\n\t\tSet(\n\t\t\tloukoum.Pair(\"deleted_at\", loukoum.Raw(\"NOW()\")),\n\t\t\tloukoum.Pair(\"name\", \"\"),\n\t\t).\n\t\tWhere(loukoum.Condition(\"id\").Equal(user.ID)).\n\t\tReturning(\"deleted_at\")\n\n\terr := makroud.Exec(ctx, driver, stmt, user)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn user, nil\n}\n```\n\n#### Query\n\nBecause querying data is a bit more complex than just writing and/or deleting stuff. By using [Loukoum](https://github.com/ulule/loukoum) components, you can either execute simple query:\n\n```go\nimport \"github.com/ulule/loukoum/v3\"\n\nfunc GetUserByID(ctx context.Context, driver makroud.Driver, id string) (*User, error) {\n\tuser := \u0026User{}\n\terr := makroud.Select(ctx, driver, user, loukoum.Condition(\"id\").Equal(id))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn user, nil\n}\n\nfunc ListMessagesByUserID(ctx context.Context, driver makroud.Driver,\n\tuserID string, page int) ([]*Message, error) {\n\n\tmessages := []*Message{}\n\terr := makroud.Select(ctx, driver, \u0026messages,\n\t\tloukoum.Condition(\"user_id\").Equal(id),\n\t\tloukoum.Order(\"created_at\", loukoum.Desc),\n\t\tloukoum.Limit(50),\n\t\tloukoum.Offset(50 * (page - 1)),\n\t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn messages, nil\n}\n```\n\nOr execute more complex statements:\n\n```go\nimport \"github.com/ulule/loukoum/v3\"\n\nfunc FindStaffComments(ctx context.Context, driver makroud.Driver) ([]*Comment, error) {\n\tcomments := []*Comment{}\n\n\tstmt := loukoum.Select(\"id\", \"email\", \"status\", \"user_id\", \"message\", \"created_at\").\n\t\tFrom(\"comments\").\n\t\tWhere(loukoum.Condition(\"deleted_at\").IsNull(true)).\n\t\tWhere(\n\t\t\tloukoum.Condition(\"user_id\").In(\n\t\t\t\tloukoum.Select(\"id\").\n\t\t\t\t\tFrom(\"users\").\n\t\t\t\t\tWhere(loukoum.Condition(\"role\").Equal(\"staff\")),\n\t\t\t),\n\t\t)\n\n\terr := makroud.Exec(ctx, driver, stmt, \u0026comments)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn comments, nil\n}\n```\n\nAlso, it supports query without `Model`.\n\n```go\nfunc FindUserIDWithStaffRole(ctx context.Context, driver makroud.Driver) ([]string, error) {\n\tlist := []string{}\n\n\tstmt := loukoum.Select(\"id\").\n\t\tFrom(\"users\").\n\t\tWhere(loukoum.Condition(\"role\").Equal(\"staff\"))\n\n\terr := makroud.Exec(ctx, driver, stmt, \u0026list)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn list, nil\n}\n```\n\n### Transaction\n\nSometimes, you need to execute queries and/or commands inside a transaction block, that bundles\nmultiple steps into a single, all-or-nothing operation.\n\nThis is achieved by declaring a lambda function.\nIf this function returns an error, the transaction rollbacks automatically.\nOtherwise, the transaction will be committed.\n\n```go\nfunc SetupUsers(ctx context.Context, driver makroud.Driver) error {\n\treturn makroud.Transaction(ctx, driver, nil, func(tx makroud.Driver) error {\n\n\t\terr := makroud.Save(ctx, tx, \u0026User{\n\t\t\tName: \"Benjamin\",\n\t\t})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\terr = makroud.Save(ctx, tx, \u0026User{\n\t\t\tName: \"Taha\",\n\t\t})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\treturn nil\n\t})\n}\n```\n\nAnd other times, transactions with an isolation level.\n\n```go\nfunc Withdraw(ctx context.Context, driver makroud.Driver) error {\n\treturn makroud.Transaction(ctx, driver, makroud.LevelSerializable,\n\t\tfunc(tx makroud.Driver) error {\n\t\t\t// Withdraw operation...\n\t\t\treturn nil\n\t\t},\n\t)\n}\n```\n\nOr even, nested transaction with the option `SavepointEnabled`.\n\n```go\nfunc AcceptOffer(ctx context.Context, driver makroud.Driver) error {\n\treturn makroud.Transaction(ctx, driver, nil, func(tx1 makroud.Driver) error {\n\t\t//\n\t\t// Execute several operations.\n\t\t//\n\t\terr := makroud.Transaction(ctx, tx1, nil, func(tx2 makroud.Driver) error {\n\t\t\t//\n\t\t\t// Execute complex operations that may succeed...\n\t\t\t//\n\t\t\treturn err\n\t\t})\n\t\tif err != nil {\n\t\t\t//\n\t\t\t// Execute fallback operations if an error has occurred...\n\t\t\t//\n\t\t\treturn nil\n\t\t}\n\t\t//\n\t\t// Execute normal operations otherwise...\n\t\t//\n\t\treturn nil\n\t})\n}\n```\n\n### Preload\n\nOn models having associations, you can execute a preload to fetch these relationships from the database.\n\nLet's define a user with a profile:\n\n```go\ntype User struct {\n\tID       string   `makroud:\"column:id,pk\"`\n\tEmail    string   `makroud:\"column:email\"`\n\tProfile  *Profile `makroud:\"relation:profiles.user_id\"`\n}\n\nfunc (User) TableName() string {\n\treturn \"users\"\n}\n\ntype Profile struct {\n\tID         string  `makroud:\"column:id,pk:ulid\"`\n\tFirstName  string  `makroud:\"column:first_name\"`\n\tLastName   string  `makroud:\"column:last_name\"`\n\tUserID     string  `makroud:\"column:user_id,fk:users\"`\n\tEnabled    bool    `makroud:\"column:enabled\"`\n}\n\nfunc (Profile) TableName() string {\n\treturn \"profiles\"\n}\n```\n\nOnce you obtain a user record, you can preload its profile by executing:\n\n```go\nerr := makroud.Preload(ctx, driver, \u0026user, makroud.WithPreloadField(\"Profile\"))\n```\n\n**Or,** if preloading requires specific conditions, you can use a callback like this:\n\n```go\nimport \"github.com/ulule/loukoum/v3/builder\"\n\nerr := makroud.Preload(ctx, driver, \u0026user,\n\tmakroud.WithPreloadCallback(\"Profile\", func(query builder.Select) builder.Select {\n\t\treturn query.Where(loukoum.Condition(\"enabled\").Equal(true))\n\t}),\n)\n```\n\nIf there is no error and if the user record has a profile, then you should have the `Profile` value loaded.\n\n\u003c!---\n\n## Benchmarks\n\nA [benchmark repository](https://github.com/ulule/makroud-benchmarks) containing result with\nother ORM or Mapper using reflection is available.\n\n\u003e **NOTE:** A benchmark is always an observation, not a measurement of performance.\n\n![SelectAll NsOp](https://raw.githubusercontent.com/ulule/makroud-benchmarks/master/graph/images/select_all_nsop.png)\n![SelectAll Bop](https://raw.githubusercontent.com/ulule/makroud-benchmarks/master/graph/images/select_all_bop.png)\n![SelectAll Aop](https://raw.githubusercontent.com/ulule/makroud-benchmarks/master/graph/images/select_all_aop.png)\n![SelectSubset NsOp](https://raw.githubusercontent.com/ulule/makroud-benchmarks/master/graph/images/select_subset_nsop.png)\n![SelectSubset Bop](https://raw.githubusercontent.com/ulule/makroud-benchmarks/master/graph/images/select_subset_bop.png)\n![SelectSubset Aop](https://raw.githubusercontent.com/ulule/makroud-benchmarks/master/graph/images/select_subset_aop.png)\n![SelectComplex NsOp](https://raw.githubusercontent.com/ulule/makroud-benchmarks/master/graph/images/select_complex_nsop.png)\n![SelectComplex Bop](https://raw.githubusercontent.com/ulule/makroud-benchmarks/master/graph/images/select_complex_bop.png)\n![SelectComplex Aop](https://raw.githubusercontent.com/ulule/makroud-benchmarks/master/graph/images/select_complex_aop.png)\n![Insert NsOp](https://raw.githubusercontent.com/ulule/makroud-benchmarks/master/graph/images/insert_nsop.png)\n![Insert Bop](https://raw.githubusercontent.com/ulule/makroud-benchmarks/master/graph/images/insert_bop.png)\n![Insert Aop](https://raw.githubusercontent.com/ulule/makroud-benchmarks/master/graph/images/insert_aop.png)\n![Update NsOp](https://raw.githubusercontent.com/ulule/makroud-benchmarks/master/graph/images/update_nsop.png)\n![Update Bop](https://raw.githubusercontent.com/ulule/makroud-benchmarks/master/graph/images/update_bop.png)\n![Update Aop](https://raw.githubusercontent.com/ulule/makroud-benchmarks/master/graph/images/update_aop.png)\n![Delete NsOp](https://raw.githubusercontent.com/ulule/makroud-benchmarks/master/graph/images/delete_nsop.png)\n![Delete Bop](https://raw.githubusercontent.com/ulule/makroud-benchmarks/master/graph/images/delete_bop.png)\n![Delete Aop](https://raw.githubusercontent.com/ulule/makroud-benchmarks/master/graph/images/delete_aop.png)\n\n--\u003e\n\n## Development\n\n### Docker\n\nThe test suite is running on PostgreSQL. We use [Docker](https://docs.docker.com/install/) to create a running\ncontainer using [scripts/database](scripts/database).\n\n### Testing\n\nTo run the test suite, simply execute:\n\n```\nscripts/test\n```\n\nAlso, you can execute the linter with:\n\n```\nscripts/lint\n```\n\n#### Notes\n\nIf you have to examine rows generated from unit test, you can prevent the test suite to cleanup by using:\n\n```\nDB_KEEP=true scripts/test\n```\n\nThen, you can access the database with:\n\n```\nscripts/database --client\n```\n\n### Random\n\nBecause sometimes it's hard to think of a good test fixture, using generators can save your productivity.\n\nThis website was a great help to write unit test: http://www.fantasynamegenerators.com\n\n## License\n\nThis is Free Software, released under the [`MIT License`][license-url].\n\n## Contributing\n\n- Ping us on twitter:\n  - [@novln\\_](https://twitter.com/novln_)\n  - [@oibafsellig](https://twitter.com/oibafsellig)\n  - [@thoas](https://twitter.com/thoas)\n- Fork the [project](https://github.com/ulule/loukoum)\n- Fix [bugs](https://github.com/ulule/loukoum/issues)\n\n**Don't hesitate ;)**\n\n[godoc-url]: https://godoc.org/github.com/ulule/makroud\n[godoc-img]: https://godoc.org/github.com/ulule/makroud?status.svg\n[license-img]: https://img.shields.io/badge/license-MIT-blue.svg\n[license-url]: LICENSE\n[circle-url]: https://circleci.com/gh/ulule/makroud/tree/master\n[circle-img]: https://circleci.com/gh/ulule/makroud.svg?style=shield\u0026circle-token=e53497efffde023bac7f2710bd12c5d0e71f5af4\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fulule%2Fmakroud","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fulule%2Fmakroud","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fulule%2Fmakroud/lists"}