{"id":35172013,"url":"https://github.com/andrewpillar/database","last_synced_at":"2026-04-28T14:04:33.231Z","repository":{"id":328027792,"uuid":"1114009604","full_name":"andrewpillar/database","owner":"andrewpillar","description":"A simple library for working with database models in Go","archived":false,"fork":false,"pushed_at":"2026-01-26T13:33:28.000Z","size":124,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-01-27T02:53:17.970Z","etag":null,"topics":["database","go","sql","sql-builder"],"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/andrewpillar.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-12-10T19:13:03.000Z","updated_at":"2026-01-26T13:26:01.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/andrewpillar/database","commit_stats":null,"previous_names":["andrewpillar/database"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/andrewpillar/database","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andrewpillar%2Fdatabase","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andrewpillar%2Fdatabase/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andrewpillar%2Fdatabase/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andrewpillar%2Fdatabase/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/andrewpillar","download_url":"https://codeload.github.com/andrewpillar/database/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andrewpillar%2Fdatabase/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32383791,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-28T11:25:28.583Z","status":"ssl_error","status_checked_at":"2026-04-28T11:25:05.435Z","response_time":56,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5: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","go","sql","sql-builder"],"created_at":"2025-12-28T21:01:52.712Z","updated_at":"2026-04-28T14:04:33.193Z","avatar_url":"https://github.com/andrewpillar.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# database\n\ndatabase is a simple library that builds on top of [database/sql][] from the Go\nstandard library to provide modelling and query building. It aims to stay out of\nyour way as much as possible, and makes as few assumptions about the data you\nare working with.\n\n* [Quickstart](#quickstart)\n* [Conventions](#conventions)\n* [Models](#models)\n  * [Parameters](#parameters)\n  * [Field aliases](#field-aliases)\n* [Stores](#stores)\n  * [Creating models](#creating-models)\n  * [Getting models](#getting-models)\n  * [Updating models](#updating-models)\n  * [Deleting models](#deleting-models)\n* [Query building](#query-building)\n  * [Options](#options)\n  * [Expressions](#expressions)\n* [Examples](#examples)\n  * [Custom model scanning](#custom-model-scanning)\n  * [Model relations](#model-relations)\n  * [Blogging application](#blogging-application)\n\n[database/sql]: https://pkg.go.dev/database/sql\n\n## Quickstart\n\nTo start using the library just import it alongside your pre-existing code.\nBelow is an example that defines a simple model, creates it, and retrieves it,\n\n```go\npackage main\n\nimport (\n    \"context\"\n    \"database/sql\"\n    \"log\"\n    \"time\"\n\n    \"github.com/andrewpillar/database\"\n\n    _ \"modernc.org/sqlite\"\n)\n\nconst schema = `CREATE TABLE IF NOT EXISTS posts (\n    id         INTEGER NOT NULL,\n    title      VARCHAR NOT NULL,\n    content    TEXT NOT NULL,\n    created_at TIMESTAMP NOT NULL,\n    PRIMARY KEY (id)\n);`\n\ntype Post struct {\n    ID        int64\n    Title     string\n    Content   string\n    CreatedAt time.Time `db:\"created_at\"`\n}\n\nfunc (p *Post) Table() string { return \"posts\" }\n\nfunc (p *Post) PrimaryKey() *database.PrimaryKey {\n    return \u0026database.PrimaryKey{\n        Columns: []string{\"id\"},\n        Values:  []any{p.ID},\n    }\n}\n\nfunc (p *Post) Params() database.Params {\n    return database.Params{\n        \"id\":         database.CreateOnlyParam(p.ID),\n        \"title\":      database.CreateOnlyParam(p.Title),\n        \"content\":    database.MutableParam(p.Content),\n        \"created_at\": database.CreateOnlyParam(p.CreatedAt),\n    }\n}\n\nfunc main() {\n    db, err := sql.Open(\"sqlite\", \"db.sqlite\")\n\n    if err != nil {\n        log.Fatalln(err)\n    }\n\n    defer db.Close()\n\n    if _, err := db.Exec(schema); err != nil {\n\t\tlog.Fatalln(err)\n\t}\n\n    p := \u0026Post{\n        ID:        10,\n        Title:     \"My first post\",\n        Content:   \"This is a demonstration.\",\n        CreatedAt: time.Now().UTC(),\n    }\n\n    store := database.NewStore(db, func() *Post {\n        return \u0026Post{}\n    })\n\n    ctx := context.Background()\n\n    if err := store.Create(ctx, p); err != nil {\n        log.Fatalln(err)\n    }\n\n    p, ok, err := store.Get(ctx)\n\n    if err != nil {\n        log.Fatalln(err)\n    }\n\n    if !ok {\n        log.Fatalln(\"could not find post\", p.ID)\n    }\n    log.Println(p)\n}\n```\n\n## Conventions\n\nThis library aims to impose minimal conventions upon the user and tries to make\nas few assumptions as possible about the data being worked with. It seeks to\nactively eschew anything that resembles an ORM, and instead opting for a query\nbuilder to allow the user to define their own queries. Because of this, it is\nentirely possible to only use a subset of the library that is necessary. For\nexample, if you require only query building, then use the query builder. If you\nonly need to use [Models](#models) but no [Stores](#stores), then use only\nmodels.\n\nThis library strives to stay out of you way as much as possible, whilst still\nenabling you the ability to seamlessly work with your data.\n\n## Models\n\nModels allow for the mapping of Go structs to database tables. This is done by\nimplementing the [database.Model][] interface. This interface wraps three\nmethods,\n\n* `Table` - The table that contains the Model's data.\n* `PrimaryKey` - The primary key for the Model.\n* `Params` - The parameters of the Model.\n\n[database.Model]: https://pkg.go.dev/github.com/andrewpillar/database#Model\n\nA model for a blogging application might look like this,\n\n```go\ntype Post struct {\n    ID        int64\n    Title     string\n    Content   string\n    CreatedAt time.Time `db:\"created_at\"`\n}\n\nfunc (p *Post) Table() string { return \"posts\" }\n\nfunc (p *Post) PrimaryKey() *database.PrimaryKey {\n    return \u0026database.PrimaryKey{\n        Columns: []string{\"id\"},\n        Values:  []any{p.ID},\n    }\n}\n\nfunc (p *Post) Params() database.Params {\n    return database.Params{\n        \"id\":         database.CreateOnlyParam(p.ID),\n        \"title\":      database.CreateOnlyParam(p.Title),\n        \"content\":    database.MutableParam(p.Content),\n        \"created_at\": database.CreateOnlyParam(p.CreatedAt),\n    }\n}\n```\n\nWith the above implementation, the `Post` model defines its table as being\n`posts`, its primary key as being the `id` column with a value of `p.ID`, and\nits parameters.\n\n### Parameters\n\nModel parameters define which fields on a model can be created, updated, or\nmutated. This is done via the `Params` method which returns a set of\n[database.Params][].\n\n[database.Params]: https://pkg.go.dev/github.com/andrewpillar/database#Params\n\nEach parameter is defined by one of three functions,\n\n* [database.MutableParam][]\n* [database.CreateOnlyParam][]\n* [database.UpdateOnlyParam][]\n\n[database.MutableParam]: https://pkg.go.dev/github.com/andrewpillar/database#MutableParam\n[database.CreateOnlyParam]: https://pkg.go.dev/github.com/andrewpillar/database#CreateOnlyParam\n[database.UpdateOnlyParam]: https://pkg.go.dev/github.com/andrewpillar/database#UpdateOnlyParam\n\nMutable parameters can be set during creation, and modified during updates.\nWhereas a create only param can only be set during creation, and update only can\nonly be set during model updates.\n\nThe Post model defines the following parameters,\n\n```go\nfunc (p *Post) Params() database.Params {\n    return database.Params{\n        \"id\":         database.CreateOnlyParam(p.ID),\n        \"title\":      database.CreateOnlyParam(p.Title),\n        \"content\":    database.MutableParam(p.Content),\n        \"created_at\": database.CreateOnlyParam(p.CreatedAt),\n    }\n}\n```\n\n`p.ID`, `p.Title`, and `p.CreatedAt`, are all create only, so these can only be\nset during model creation. Whereas `p.Content` is defined as mutable, so this\ncan be set during creation, and modified afterwards.\n\n### Field aliases\n\nBy default, the columns being scanned from a table will be compared against the\nstruct field. If the two match, then the column value will be scanned into it.\nFor example, the column `id` would map to the field `ID`, and the column\n`fullname` would map to the field `FullName`.\n\nField aliases can be defined via the `db` struct tag. For example, to map a\nsnake case field to a Pascal Case struct field, then a struct tag should be\ndefined,\n\n```go\ntype Post struct {\n    CreatedAt time.Time `db:\"created_at\"`\n}\n```\n\nThe struct tag can also be used to map column names to nested fields within a\nmodel too. Assume there is a Post model that embeds a User model, and you want\nto map the `user_id` column to the `User.ID` field, then this can be achieved\nlike so,\n\n```go\ntype User struct {\n    ID int64\n}\n\ntype Post struct {\n    ID   int64\n    User *User `db:\"user_id:id\"`\n}\n```\n\nThe format of `\u003ccolumn\u003e:\u003cfield\u003e` tells the scanner to map the column to the\nfield on the underlying struct. This will only work if the field is a struct,\nhas the necessary exported field, and is a pointer.\n\nThis can be taken a step further to scan data into embedded structs, via pattern\nmatching,\n\n```go\ntype User struct {\n    ID int64\n}\n\ntype Moderator struct {\n    *User `db:\"*:*\"`\n}\n```\n\nThe syntax of `*:*` tells the underlying scanner to match _all_ columns it has\nto all of the fields it can in the underlying struct.\n\n\u003e **Note:** The pattern matching only supports the `*` wildcard, this was added\n\u003e to make working with embedded structs in models easier. There is no support\n\u003e for finegrained pattern matching of columns and their mapping.\n\nFinally, multiple columns can be mapped to a single struct field. This is useful\nwhen working with queries that can return different column names depending on\nthe query being run. Consider the following,\n\n```go\ntype User struct {\n    ID int64\n}\n\ntype Post struct {\n    ID   int64\n    User *User `db:\"user_id:id,users.*:*\"`\n}\n```\n\nin the above example a comma separated list of alias values has been configured\nfor the `Post.User` field. This tells the scanner to map the `user_id` column to\nthe `id` field of the `User` model. But, it also tells the scanner to map any\ncolumns with the `users.*` prefix, to the entire `User` model, should the query\nthat is performed haved any columns with said prefix. Configuring such aliases\ncomes in handy when working with joins and you want to load in related model\ndata. In this case, this would allow for the loading in of the User who made a\nPost.\n\n## Stores\n\nStores are the mechanism that operate on models. They handle creating, updating,\nmutating, and querying of models. Each store makes use of [generic][] type\nparameters to determine which model is being worked with.\n\nTo create a store invoke the [database.NewStore][] function passing the database\nconnection and a callback for model instantiation,\n\n[generic]: https://go.dev/doc/tutorial/generics\n[database.NewStore]: https://pkg.go.dev/github.com/andrewpillar/database#NewStore\n\n```go\nposts := database.NewStore(db, func() *Post {\n    return \u0026Post{}\n})\n```\n\n\u003e **Note:** The type parameter is optional when creating a new store. They can\n\u003e be given to provide more explicitness in code, such as `NewStore[*Post]`.\n\nIn the above example a new [database.Store][] is created for working with Post\nmodels. With this in place, Post models can now be created, retrieved, updated,\nand deleted.\n\n[database.Store]: https://pkg.go.dev/github.com/andrewpillar/database#Store\n\n### Creating models\n\nModels can be created via the `Create` and `CreateTx` methods.\n\n```go\np := \u0026Post{\n    ID:        10,\n    Title:     \"Example post\",\n    Content:   \"This is an example post\",\n    CreatedAt: time.Now().UTC(),\n}\n\nif err := posts.Create(ctx, p); err != nil {\n    // Handle error.\n}\n```\n\nThis will populate the table's columns with the model parameters that have been\ndefined as being create only or mutable.\n\nThe `CreateTx` method operates the same, the only difference being that it\noperates on a transaction. This means the transaction needs committing in order\nfor the data to persist in the database.\n\n```go\ntx, err := db.BeginTx(ctx, nil)\n\nif err != nil {\n    // Handle error.\n}\n\ndefer tx.Rollback()\n\np := \u0026Post{\n    ID:        10,\n    Title:     \"Example post\",\n    Content:   \"This is an example post\",\n    CreatedAt: time.Now().UTC(),\n}\n\nif err := posts.CreateTx(ctx, tx, p); err != nil {\n    // Handle error.\n}\n\nif err := tx.Commit(); err != nil {\n    // Handle error.\n}\n```\n\n### Getting models\n\nModels can be retrieved via either the `Get` or `Select` methods.\n\nThe `Get` method returns the first model that matches the given\n[query.Options][], along with whether or not any model was found.\n\n[query.Options]: https://pkg.go.dev/github.com/andrewpillar/database/query#Option\n\n```go\np, ok, err := posts.Get(ctx, query.WhereEq(\"id\", query.Arg(10)))\n\nif err != nil {\n    // Handle error.\n}\n\nif !ok {\n    // Handle not found.\n}\n```\n\nThe `Select` method returns multiple models that match the given query options.\nThis takes a [query.Expr][] that defines the columns to get for the model,\n\n[query.Expr]: https://pkg.go.dev/github.com/andrewpillar/database/query#Expr\n\n```go\npp, err := posts.Select(ctx, query.Columns(\"*\"), query.OrderDesc(\"created_at\"))\n\nif err != nil {\n    // Handle error.\n}\n```\n\n### Updating models\n\nModels can be updated via the `Update`, `UpdateTx`, `UpdateMany`, and\n`UpdateManyTx` methods.\n\n```go\np, ok, err := posts.Get(ctx, query.WhereEq(\"id\", query.Arg(10)))\n\nif err != nil {\n    // Handle error.\n}\n\nif !ok {\n    // Handle not found.\n}\n\np.Content = \"New post content\"\n\nif _, err := posts.Update(ctx, p); err != nil {\n    // Handle error.\n}\n```\n\nThe `UpdateTx` method operates the same, the only difference being that it\noperates on a transaction.\n\nThe `UpdateMany` method takes a map for the fields of the model that should be\nupdated and a list of query options that is used to restrict which models are\nupdated.\n\n```go\nfields := map[string]any{\n    \"id\":           10,\n    \"content\":      \"New post content\",\n    \"non_existent\": \"value\",\n}\n\nif _, err := posts.UpdateMany(ctx, fields, query.WhereGt(1), query.WhereLt(10)); err != nil {\n    // Handle error.\n}\n```\n\nThe above code example will only update the `content` column in the table. This\nis because the `content` column on the Post model is defined as mutable, whereas\nthe `id` column is defined as create only, therefore, it will not be updated.\nThe `non_existent` field will be ignored as it does not exist on the Post model.\n\nThe `UpdateManyTx` method operates the same, the only difference being that it\noperates on a transaction.\n\n### Deleting models\n\nModels can be deleted via the `Delete` and `DeleteTx` methods. These take the\nlsit of models to delete. If an empty list is given then the methods do nothing,\nand no data is deleted.\n\n```go\npp, err := posts.Select(ctx, query.Columns(\"*\"))\n\nif err != nil {\n    // Handle error.\n}\n\nif err := posts.Delete(ctx, pp...); err != nil {\n    // Handle error.\n}\n```\n\nThe `DeleteTx`method operates the same, the only difference being that it\noperates on a transaction.\n\n## Query building\n\nQueries can be built via the `github.com/andrewpillar/database/query` package.\nThis makes use of first class functions for queires to be built up. This aims to\nsupport the most common features of SQL that would be needed for CRUD\noperations, but, the package can be extended upon via the implementation of\ncustom query expressions.\n\nThere are 6 main functions that are used for defining a query,\n\n* [query.Select][]\n* [query.SelectDistinct][]\n* [query.SelectDistinctOn][]\n* [query.Insert][]\n* [query.Update][]\n* [query.Delete][]\n\n[query.Select]: https://pkg.go.dev/github.com/andrewpillar/database/query#Select\n[query.SelectDistinct]: https://pkg.go.dev/github.com/andrewpillar/database/query#SelectDistinct\n[query.SelectDistinctOn]: https://pkg.go.dev/github.com/andrewpillar/database/query#SelectDistinctOn\n[query.Insert]: https://pkg.go.dev/github.com/andrewpillar/database/query#Insert\n[query.Update]: https://pkg.go.dev/github.com/andrewpillar/database/query#Update\n[query.Delete]: https://pkg.go.dev/github.com/andrewpillar/database/query#Delete\n\nEach of these functions operate in a similar way, in that they each take a\nvariadic list of [query.Options][] to build the query, and each of them return a\n[query.Query][] that can be built and passed off to the database connection to\nbe run.\n\n[query.Query]: https://pkg.go.dev/github.com/andrewpillar/database/query#Query\n\n### Options\n\nOptions are the primary building blocks of the query builder. These are a first\nclass function which take a query, modify it, and return it,\n\n```go\ntype Option func(*Query) *Query\n```\n\nthese are passed to the query functions to define how the query ought be built.\nCustom options can be defined by implementing a function that matches the Option\ndefinition. For example, let's consider a blogging application where you might\nwant to implement a search functionality on posts by a tag. A custom option for\nthis could be written like so,\n\n```go\nfunc Search(tag string) query.Option {\n    return func(q *query.Query) *query.Query {\n        return query.WhereIn(\"id\", query.Select(\n            query.Columns(\"post_id\"),\n            query.From(\"post_tags\"),\n            query.WhereLike(\"name\", query.Arg(\"%\" + tag + \"%\")),\n        ))(q)\n    }\n}\n```\n\nthis custom option could then be used like so,\n\n```go\npp, err := posts.Select(ctx, Search(\"programming\"))\n```\n\n### Expressions\n\nSQL expressions are represented via the [query.Expr][] interface that wraps the\n`Args` and `Build` methods.\n\nThe `Args` method returns the list of arguments for the given expression, if\nany, and the `Build` method returns the SQL code for the expression.\n\nFor example, [query.Arg][] returns an argument expression. This would be used\nfor passing arguments through to the underlying query being built. Calling\n`Build` on this expression directly would result in the `?` placeholder value\nbeing generated, what with the `Args` method return the actual argument that is\ngiven. For example,\n\n[query.Arg]: https://pkg.go.dev/github.com/andrewpillar/database/query#Arg\n\n```go\nq := query.Select(\n    query.Columns(\"*\"),\n    query.From(\"users\"),\n    query.WhereEq(\"email\", query.Arg(\"user@example.com\")),\n)\n```\n\nthe `\"user@example.com\"` string is passed to the query being built as an\nargument, via the [query.WhereEq][] function.\n\n[query.WhereEq]: https://pkg.go.dev/github.com/andrewpillar/database/query#WhereEq\n\nQueries returned from the query functions can also be used as expressions, since\nthese also implement the `Args` and `Build` methods. This allows for powerful\nqueries to be built,\n\n```go\nq := query.Select(\n    query.Columns(\"*\"),\n    query.From(\"posts\"),\n    query.WhereEq(\"user_id\", query.Arg(1)),\n    query.WhereIn(\"id\", query.Select(\n        query.Columns(\"id\"),\n        query.From(\"post_tags\"),\n        query.WhereLike(\"name\", Arg(\"%programming%\")),\n    )),\n)\n```\n\nthe above example would result in the following query being built,\n\n```sql\nSELECT *\nFROM posts\nWHERE (\n    user_id = $1\n    AND id IN (\n        SELECT post_id\n        FROM post_tags\n        WHERE (name LIKE $2)\n    )\n)\n```\n\n## Examples\n\nBelow are some examples which will demonstrate how this library can be used in\nvarious scenarios. These exist to show that different parts of the library can\nbe used independent of one another, and can be used alongside the standard\nlibrary itself.\n\nWhilst this library does offer some nice abstractions of the scanning of data\nfrom the database into Go structs, it does not tell you how your data should be\nstructured. For example, with primary keys, it does not say that your primary\nkey should be a single field, or that it should be auto-incrementing.\n\nIn essence, this library was designed with rows of arbitrary data in mind, since\nthat is what data from the database is returned as. Either a row, or rows, that\nhave some column names, and respective values. This library just provides some\nsimple helpers to aid in the scanning of said values into the data you may have\nin your Go code.\n\n### Custom model scanning\n\nBy default, the library makes use of [reflect][] to attempt to deduce how the\ncolumns should be mapped to the struct it is scanning data into. However, custom\nscanning can be implemented on a per-model basis via the [database.RowScanner][]\ninterface.\n\n[reflect]: https://pkg.go.dev/reflect\n[database.RowScanner]: https://pkg.go.dev/github.com/andrewpillar/database#RowScanner\n\nFor example,\n\n```go\ntype Notification struct {\n    ID   int64\n    Data map[string]any\n}\n\nfunc (n *Notification) Scan(r *database.Row) error {\n    var data string\n\n    dest := map[string]any{\n        \"id\":   \u0026n.ID,\n        \"data\": \u0026data,\n    }\n\n    if err := r.Scan(dest); err != nil {\n        return err\n    }\n\n    if err := json.Unmarshal([]byte(data), \u0026n.Data); err != nil {\n        return err\n    }\n    return nil\n}\n```\n\nwith the above implementation, the user defines exactly how the row is scanned\ninto the model. This is achieved by passing a map of pointer values to the\n[Row.Scan][] method. This will scan in only the columns that exist in the row\nand are defined in the given map.\n\n[Row.Scan]: https://pkg.go.dev/github.com/andrewpillar/database#Row.Scan\n\nUnder the hood, a new [Scanner][] is created which is given the database rows\nthat have been selected. This means that it is entirely possible to not used\n[Stores](#stores) when working with models. For example, the following code\ncould be written to retrieve a model,\n\n[Scanner]: https://pkg.go.dev/github.com/andrewpillar/database#Scanner\n\n```go\nrows, err := db.Query(\"SELECT * FROM notifications\")\n\nif err != nil {\n    // Handle error.\n}\n\ndefer rows.Close()\n\nsc, err := database.NewScanner(rows)\n\nif err != nil {\n    // Handle error.\n}\n\nnn := make([]*Notification, 0)\n\nfor rows.Next() {\n    n := \u0026Notification{}\n\n    if err := sc.Scan(n); err != nil {\n        // Handle error.\n    }\n}\n```\n\nOf course, even without the custom `Scan` method, and just through reflection,\nthe same above code would still work.\n\n### Model relations\n\nUnlike in an ORM, there is no way of formally defining relations between models\nwith this library. Instead, it is recommended that the necessary queries are\nbuilt that are used to query the related data, and return them in rows.\n\nFor example, assume a blogging application is being built that has User and Post\nmodels as defined below,\n\n```go\ntype User struct {\n    ID        int64\n    Email     string\n    Username  string\n    CreatedAt time.Time `db:\"created_at\"`\n}\n\ntype Post struct {\n    ID        int64\n    User      *User `db:\"user_id:id,users.*:*\"`\n    Title     string\n    Content   string\n    CreatedAt time.Time `db:\"created_at\"`\n}\n```\n\nIf you wanted to load in all of the posts with their respective user then you\nwould write the following,\n\n```go\nposts := database.NewStore(db, func() *Post {\n    // Make sure the User model is instantiated for scanning, otherwise the\n    // program will panic trying to deference a nil pointer.\n    return \u0026Post{\n        User: \u0026User{},\n    }\n})\n\n// Again, make sure this is fully instantiated because the database.Columns\n// function calls Table on each model it is given to determine the columns of\n// the model.\np := \u0026Post{\n    User: \u0026User{},\n}\n\npp, err := posts.Select(ctx, database.Columns(p, p.User), database.Join(p.User, \"user_id\"))\n\nif err != nil {\n    // Handle error.\n}\n\nfor _, p := range pp {\n    fmt.Printf(\"Post %s by %s\\n\", p.Title, p.User.Username)\n}\n```\n\nThe above code makes use of the `database.Columns` and `database.Join` functions\nwhich simply makes building these queries easier. The same code using the query\nbuilder itself would look something like,\n\n```\nposts.Select(\n    ctx,\n    query.Exprs(\n        query.Ident(\"posts.id\"),\n        query.Ident(\"posts.user_id\"),\n        query.Ident(\"posts.title\"),\n        query.Ident(\"posts.content\"),\n        query.Ident(\"posts.created_at\"),\n        query.ColumnAs(\"users.id\", \"users.id\"),\n        query.ColumnAs(\"users.email\", \"users.email\"),\n        query.ColumnAs(\"users.username\", \"users.username\"),\n        query.ColumnAs(\"users.created_at\", \"users.created_at\"),\n    ),\n    query.From(\"posts\"),\n    query.Join(\"users\", Eq(Ident(\"posts.user_id\"), Ident(\"users.id\"))),\n)\n```\n\nIt is entirely possible to write these queries by hand, and make use of the\n[database.Scanner][] to achieve the same result,\n\n[database.Scanner]: https://pkg.go.dev/github.com/andrewpillar/database#Scanner\n\n```go\nq := `\nSELECT posts.id,\n    posts.user_id,\n    posts.title,\n    posts.content,\n    posts.created_at,\n    users.id AS 'users.id',\n    users.email AS 'users.email',\n    users.username AS 'users.username',\n    users.created_at AS 'users.created_at'\nFROM posts\nJOIN users ON posts.user_id = users.id\n`\n\nrows, err := db.Query(q)\n\nif err != nil {\n    // Handle error.\n}\n\ndefer rows.Close()\n\nsc, err := database.NewScanner(rows)\n\nif err != nil {\n    // Handle error.\n}\n\npp := make([]*Post, 0)\n\nfor rows.Next() {\n    p := \u0026Post{\n        User: \u0026User{},\n    }\n\n    if err := sc.Scan(p); err != nil {\n        // Handle error.\n    }\n}\n```\n\n### Blogging application\n\nThroughout this document, various references were made to an example blogging\napplication being developed to help convey how this library would be used in\nvarious scenarios. The source code for this blogging application exists within\nthe repository at [blog-example][].\n\n[blog-example]: /blog-example\n\nThis is an extremely simple blogging application that will allow you to submit\nposts, and tag them as a user. This also has a rudimentary search system that\nwill allow you to search the posts that have been made via the tags they were\nassigned.\n\nThe [handlers.go][] and [post.go][] files contain code that demonstrates both\nthe ability to dynamically build queries based on input parameters sent in an\nHTTP request, and code that implements custom options to extend the\nfunctionality of the query builder for the application's use case.\n\n[handlers.go]: /blog-example/handlers.go\n[post.go]: /blog-example/post.go\n\nTo build this application simply run,\n\n```shell\n$ go build -o blog ./blog-example\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fandrewpillar%2Fdatabase","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fandrewpillar%2Fdatabase","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fandrewpillar%2Fdatabase/lists"}