{"id":13707135,"url":"https://github.com/spacemonkeygo/dbx","last_synced_at":"2026-01-13T18:22:15.421Z","repository":{"id":55108752,"uuid":"69763805","full_name":"spacemonkeygo/dbx","owner":"spacemonkeygo","description":"A neat codegen-based database wrapper written in Go","archived":false,"fork":false,"pushed_at":"2021-01-09T22:35:34.000Z","size":979,"stargazers_count":73,"open_issues_count":15,"forks_count":11,"subscribers_count":8,"default_branch":"master","last_synced_at":"2025-05-06T00:37:30.261Z","etag":null,"topics":["code-generation","database","dbx","go","golang","orm","sql"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/spacemonkeygo.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-10-01T21:06:11.000Z","updated_at":"2025-02-07T12:48:44.000Z","dependencies_parsed_at":"2022-08-14T12:10:57.216Z","dependency_job_id":null,"html_url":"https://github.com/spacemonkeygo/dbx","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/spacemonkeygo/dbx","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spacemonkeygo%2Fdbx","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spacemonkeygo%2Fdbx/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spacemonkeygo%2Fdbx/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spacemonkeygo%2Fdbx/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/spacemonkeygo","download_url":"https://codeload.github.com/spacemonkeygo/dbx/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spacemonkeygo%2Fdbx/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28395922,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-13T14:36:09.778Z","status":"ssl_error","status_checked_at":"2026-01-13T14:35:19.697Z","response_time":56,"last_error":"SSL_read: 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":["code-generation","database","dbx","go","golang","orm","sql"],"created_at":"2024-08-02T22:01:21.257Z","updated_at":"2026-01-13T18:22:15.370Z","avatar_url":"https://github.com/spacemonkeygo.png","language":"Go","readme":"# DBX\n\nDBX is a tool to generate database schemas and code to operate with it. It\ncurrently generates Go bindings to Postgres and/or SQLite, but it should be\nfairly straightforward to add other database *and* language targets.\n\n## How it works\n\nDBX takes a description of models and operations to perform on those models\nand can generate code to interact with sql databases.\n\n## Installing\n\n```\ngo get gopkg.in/spacemonkeygo/dbx.v1\n```\n\n## Basic Example\n\n### Declaring a Model\n\nConsider a basic `user` model with a primary key, a unique identifier, some\ntimestamps for keeping track of modifications and a name. We will require that\nthe id and the name fields are unique.\n\n```\nmodel user (\n\tkey    pk\n\tunique id\n\tunique name\n\n\tfield pk         serial64\n\tfield created_at timestamp ( autoinsert )\n\tfield updated_at timestamp ( autoinsert, autoupdate )\n\tfield id         text\n\tfield name       text\n)\n```\n\nIf we place this model in a file called `example.dbx`, we can build some go\nsource with the command\n\n```\n$ dbx.v1 golang example.dbx .\n```\n\nThis will create an `example.go` file in the current directory. Check the\noutput of `dbx.v1 golang` for more options like controling the package name or\nother features of the generated code.\n\nGenerating a schema is also straightforward:\n\n```\n$ dbx.v1 schema examples.dbx .\n```\n\nThis creates an `example.dbx.postgres.sql` file in the current directory with\nsql statements to create the tables for the models.\n\nBy default DBX will generate code for all of the models and fields and use the\npostgres SQL dialect. See the dialects section below for more discussion on\nother supported dialects and how to generate them.\n\nThis example package doesn't do very much because we didn't ask for very much,\nbut it does include a struct definition like\n\n```\ntype User struct {\n\tPk        int64\n\tCreatedAt time.Time\n\tUpdatedAt time.Time\n\tId        string\n\tName      string\n}\n```\n\nas well as concrete types `DB` and `Tx`, and interfaces that they implement\nthat look like\n\n```\ntype Methods interface {\n}\n\ntype TxMethods interface {\n\tMethods\n\n\tCommit() error\n\tRollback() error\n}\n\ntype DBMethods interface {\n\tSchema() string\n\tMethods\n}\n```\n\nThe `Methods` interface is shared between the `Tx` and `DB` interfaces and will\ncontain methods to interact with the database when they are generated. If you\nwere to pass the userdata option on the generate command, then the `User`\nstruct would come with an `interface{}` and a `sync.Mutex` to store some\narbitrary data on a value.\n\nThe package comes with some customizable hooks.\n\n```\nvar WrapErr = func(err *Error) error { return err }\nvar Logger func(format string, args ...interface{})\n```\n\n- All of the errors returned by the database are passed through the `WrapErr`\nfunction so that you may process them however you wish: by adding contextual\ninformation or stack traces for example.\n- If the `Logger` is not nil, all of the SQL statements that would be executed\nare passed to it in the args, as well as other informational statements.\n- There is a `Hooks` type on the `*DB` that contains hooks like `Now` for\nmocking out time in your tests so that any `autoinsert`/`autoupdate` time\nfields can be given a deterministic value.\n\nThe package has an `Open` function that returns a `*DB` instance. It's\nsignature looks like\n\n```\nfunc Open(driver, source string) (db *DB, err error)\n```\n\nThe driver must be one of the dialects passed in at generation time, which by\ndefault is just `postgres`. The `*DB` type lets you `Open` a new transaction\nrepresented by `*Tx`, `Close` the database, or run queries as normal. It has a\n`DB` field that exposes the raw `\"database/sql\".(*DB)` value.\n\nWe can instruct DBX to generate code for interacting with the database now.\n\n### Declaring Operations\n\nThere are four kinds of operations, `create`, `read`, `update` and `delete`. We\ncan add one of each operation for the `user` model based on the primary key:\n\n```\ncreate user ( )\nupdate user ( where user.pk = ? )\ndelete user ( where user.pk = ? )\nread one (\n\tselect user\n\twhere  user.pk = ?\n)\n```\n\nRegenerating the Go code will expand our database interface:\n\n```\ntype Methods interface {\n\tCreate_User(ctx context.Context,\n\t\tuser_id User_Id_Field,\n\t\tuser_name User_Name_Field) (\n\t\tuser *User, err error)\n\n\tDelete_User_By_Pk(ctx context.Context,\n\t\tuser_pk User_Pk_Field) (\n\t\tdeleted bool, err error)\n\n\tGet_User_By_Pk(ctx context.Context,\n\t\tuser_pk User_Pk_Field) (\n\t\tuser *User, err error)\n\n\tUpdate_User_By_Pk(ctx context.Context,\n\t\tuser_pk User_Pk_Field,\n\t\tupdate User_Update_Fields) (\n\t\tuser *User, err error)\n}\n```\n\nThe fields are all wrapped in their own type so that arguments cannot be passed\nin the wrong order: both the id and name fields are strings, and so we prevent\nany of those errors at compile time.\n\nFor example, to create a user, we could write\n\n```\ndb.Create_User(ctx,\n\t\tUser_Id(\"some unique id i just generated\"),\n\t\tUser_Name(\"Donny B. Xavier\"))\n```\n\n### Transactions\n\nDBX attempts to expose transaction handling, just like the `database/sql`\npackage, but that can sometimes be verbose with handling Commits and Rollbacks.\nConsider a function to create a user within a transaction:\n\n```\nfunc createUser(ctx context.Context, db *DB) (user *User, err error) {\n\ttx, err := db.Open()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer func() {\n\t\tif err == nil {\n\t\t\terr = tx.Commit()\n\t\t} else {\n\t\t\t// tx.Rollback() returns an error, perhaps we should log it, or\n\t\t\t// do something else? the choice is yours.\n\t\t\ttx.Rollback()\n\t\t}\n\t}()\n\n\treturn tx.Create_User(ctx, \n\t\tUser_Id(\"some unique id i just generated\"),\n\t\tUser_Name(\"Donny B. Xavier\"))\n}\n```\n\nGo allows you to define a package as a collection of multiple files, and so it\nmight be worthwhile for you to add a helper method to the `*DB` type in another\nfile like this:\n\n```\nfunc (db *DB) WithTx(ctx context.Context,\n\tfn func(context.Context, *Tx) error) (err error) {\n\n\ttx, err := db.Open()\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer func() {\n\t\tif err == nil {\n\t\t\terr = tx.Commit()\n\t\t} else {\n\t\t\ttx.Rollback() // log this perhaps?\n\t\t}\n\t}()\n\treturn fn(ctx, tx)\n}\n```\n\nThen `createUser` can be succinctly written\n\n```\nfunc createUser(ctx context.Context, db *DB) (user *User, err error) {\n\terr = db.WithTx(func(ctx context.Context, tx *Tx) error) {\n\t\tuser, err = tx.Create_User(ctx,\n\t\t\tUser_Id(\"some unique id i just generated\"),\n\t\t\tUser_Name(\"Donny B. Xavier\"))\n\t\treturn err\n\t})\n\treturn user, err\n}\n```\n\nDBX does not generate this helper for you so that you can have full control\nover how you want to handle the error in the Rollback case.\n\n### Dialects\n\nDBX doesn't work with just Postgres, and is designed to be agnostic to many\ndifferent database engines. Currently, it supports Postgres and SQLite3. Any\nof the above commands can be passed the `--dialect` (or, shorthand `-d`) flag\nto specify additional dialects. For example, running\n\n```\ndbx.v1 schema -d postgres -d sqlite3 example.dbx .\ndbx.v1 golang -d postgres -d sqlite3 example.dbx .\n```\n\nwill create both `example.dbx.postgres.sql` and `example.dbx.sqlite3.sql` with\nthe statements required to create the tables, and generate the Go code to\noperate with both sqlite3 and postgres.\n\n### Generate\n\nAll of these commands are intended to normally be used with `//go:generate`\ndirectives, such as:\n\n```\n//go:generate dbx.v1 golang -d postgres -d sqlite3 example.dbx .\n//go:generate dbx.v1 schema -d postgres -d sqlite3 example.dbx .\n```\n\nA great spot to put them would be in the file that modifies the hooks and adds\nother customizations.\n\n## Details\n\nDetailed documentation below. If you notice any difference between the\ndocumentation and the actual behavior, please open an issue and we'll fix it!\n\n### Grammar\n\nA DBX file has two constructs: tuples and lists. A list contains comma\nseparated tuples, and tuples contain white space separated strings or more\nlists. Somewhat like Go automatic semicolon insertion, commas are inserted at a\nnewline if the previous token was not a comma.\n\nFor example, this is a list of three tuples:\n\n```\n(\n\ttuple one\n\tanother tuple here\n\tthe third ( tuple )\n)\n```\n\nThe first tuple contains two strings, `\"tuple\"` and `\"one\"`. The second tuple\ncontains three strings, `\"another\"`, `\"tuple\"`, and `\"here\"`. The last tuple\ncontains two strings and a list containing one tuple, `\"the\"`, `\"third\"` and\n`( tuple )`. This list could be written with explicit commas either with or\nwithout newlines:\n\n```\n( tuple one, another tuple here, the third (\n\ttuple\n) )\n```\n\n```\n( tuple one,\n  another tuple here,\n  the third ( tuple ),\n)\n```\n\nare all the same grammatically. A dbx file implicitly has a list at the top\nlevel that does not require opening and closing parenthesis.\n\n### Models\n\n```\nmodel \u003cname\u003e (\n\t// table is optional and gives the name of the table the model will use.\n\ttable \u003cname\u003e\n\n\t// key is required and declares the primary key for the model. it can\n\t// be either a single field or a multiple fields for a composite primary\n\t// key.\n\tkey \u003cfield names\u003e\n\t\n\t// unique constraints are optional and on any number of fields. you can\n\t// have as many unique constraints as you want.\n\tunique \u003cfield names\u003e\n\t\n\t// indexes are optional and you can have as many as you want.\n\tindex (\n\t\t// the name of the index.\n\t\t// BUG: we only allow one empty name index :)\n\t\tname \u003cname\u003e\n\t\t\n\t\t// fields describes which fields are in the index\n\t\tfields \u003cfields\u003e\n\t\t\n\t\t// when set, the index will have a unique constraint\n\t\tunique\n\t)\n\t\n\t// field declares a normal field to have the name and type. attributes is\n\t// an optional list that can be used to tune specific details about the\n\t// field like nullable. see the section on attributes to see the full list.\n\tfield \u003cname\u003e \u003ctype\u003e ( attributes )\n\t\n\t// a model can have foreign key relations to another model's field. the\n\t// relation describes what happens on delete: if the related field's row is\n\t// removed, what do we do to the row that describes this model? as normal\n\t// fields, there are a number of optional attributes.\n\tfield \u003cname\u003e \u003cmodel\u003e.\u003cfield\u003e \u003crelation kind\u003e ( attributes )\n)\n```\n\n#### Attributes\n\nFields can have these attributes\n\n- `column \u003cname\u003e`: use this name for the column name\n- `nullable`: this field is nullable (can have NULL as a value)\n- `updatable`: this field can be updated\n- `autoinsert`: this field will be inserted with the zero value of the type, or\nthe current time if the field is a time field, and you won't have to specify it\non any Create calls.\n- `autoupdate`: this field will be updated with the zero value of the type, or\nthe current time if the field is a time field, and you won't have to specify it\non any Update calls. BUG: this is only really useful on timestamp fields :)\n- `length \u003clength\u003e`: on text fields, this specifies the maximum length of the\ntext.\n\n#### Field Types\n\nFields may be any of these types\n\n- `serial`\n- `serial64`\n- `int`\n- `int64`\n- `uint`\n- `uint64`\n- `bool`\n- `text`\n- `timestamp`\n- `utimestamp` (timestamp with no timezone. expected to be in UTC)\n- `float`\n- `float64`\n- `blob`\n\n#### Foreign Key Relation Kinds\n\nA foreign key relation can be any of these\n\n- `setnull`: when the related row goes away, set this field to null. the field\nmust be `nullable`.\n- `cascade`: when the related row goes away, delete this row.\n- `restrict`: do not allow the related row to go away.\n\n#### Foreign Key Attributes\n\nA foreign key can have these attributes\n\n- `column \u003cname\u003e`: use this name for the column name\n- `nullable`: this field is nullable (can have NULL as a value)\n- `updatable`: this field can be updated\n\n### Create\n\n```\ncreate \u003cmodel\u003e (\n\t// raw will cause the generation of a \"raw\" create that exposes every field\n\traw\n\t\n\t// suffix will cause the generated create method to have the desired value\n\tsuffix \u003cparts\u003e\n)\n```\n\n### Read\n\n\n`\u003cviews\u003e` is a list of views that describe what kind of reads to generate and\nis constrained by whether or not a read is distinct. a read is said to be\ndistinct if the where clauses and join conditions identify a unique result.\n\nthe following views are defined for all reads:\n* `count`  - returns the number of results\n* `has`    - returns if there are results or not\n* `first`  - returns the first result or nothing\n* `scalar` - returns a single result, nothing, or fails if there is more than\n\t\t     one result\n* `one`    - returns a single result or fails if there are no results or more\n             than one result\n\nthe following views are only defined for non-distinct reads:\n* `all` - returns all results\n* `limitoffset` - returns a limited number of results starting at an offset\n* `paged` - returns limited number of results paged by a forward iterator\n\n```\nread \u003cviews\u003e (\n\t// select describes what values will be returned from the read. you can\n\t// specify either models or just a field of a model, like \"user\" or\n\t// \"project.id\"\n\tselect \u003cfield refs\u003e\n\t\n\t// a read can have any number of where clauses. the clause refers to an\n\t// expression, an operation like \"!=\" or \"\u003c=\", and another expression. if\n\t// the right side field has a placeholder (?), the read will fill it in\n\t// with an argument and be generated with a parameter for that argument. \n\t// multiple where clauses will be joined by \"and\"s.\n\t//\n\t// \u003cexpr\u003e can be one of the following:\n\t// 1) placeholder\n\t//    where animal.name = ?\n\t// 2) null\n\t//    where animal.name = null\n\t// 3) string literal\n\t//    where animal.name = \"Tiger\"\n\t// 4) number literal\n\t//    where animal.age \u003c 30\n\t// 5) boolean literal\n\t//    where animal.dead = false\n\t// 6) model field reference: \u003cmodel\u003e.\u003cfield\u003e\n\t//    where animal.height = animal.width\n\t// 7) SQL function call: \u003cname\u003e(\u003cexpr\u003e)\n\t//    where lower(animal.name) = \"tiger\"\n\t//\n\t// SQL function calls take an expression for each argument. Currently only\n\t// \"lower\" is implemented.\n\t//\n\t// \u003climited-expr\u003e is the same as \u003cexpr\u003e except that it can only contain\n\t// a model field reference, optionally wrapped in one or more function\n\t// calls.\n\twhere \u003climited-expr\u003e \u003cop\u003e \u003cexpr\u003e\n\t\n\t// a join describes a join for the read. it brings the right hand side\n\t// model into scope for the selects, and the joins must be in a consistent\n\t// order.\n\tjoin \u003cmodel.field\u003e = \u003cmodel.field\u003e\n\t\n\t// orderby controls the order the rows are returned. direction has to be\n\t// either \"asc\" or \"desc\".\n\torderby \u003cdirection\u003e \u003cmodel.field\u003e\n\t\n\t// suffix will cause the generated read methods to have the desired value\n\tsuffix \u003cparts\u003e\n)\n```\n\n### Update\n\nSee the documentation on Read for information about where join, and suffix.\nUpdate is required to have enough information in the where and join clauses\nfor dbx to determine that it will be updating a single row.\n\n```\nupdate \u003cmodel\u003e (\n\twhere \u003cmodel.field\u003e \u003cop\u003e \u003cmodel.field or \"?\"\u003e\n\tjoin \u003cmodel.field\u003e = \u003cmodel.field\u003e\n\tsuffix \u003cparts\u003e\n)\n```\n\n### Delete\n\nSee the documentation on Read for information about where, join and suffix.\n\n```\ndelete \u003cmodel\u003e (\n\twhere \u003cmodel.field\u003e \u003cop\u003e \u003cmodel.field or \"?\"\u003e\n\tjoin \u003cmodel.field\u003e = \u003cmodel.field\u003e\n\tsuffix \u003cparts\u003e\n)\n```\n\n## Other\n\n### Formatting\n\nDBX comes with a formatter for your dbx source code. It currently has some bugs\nand limitations, but defines a canonical way to store your dbx files.\n\n- It ignores comments, so formatting will remove any comments :)\n- The command can only read from stdin, and output to stdout.\n\n### Errors\n\nDBX takes errors very seriously and attempts to have a great user experience\naround them. If you think an error is misleading, the caret is in the wrong or\nless than optimal position, or not obvious what the solution is, please open an\nissue and we will try to explain and make the error better. For example,\npassing the model with a typo for the foreign key:\n\n```\nmodel user (\n\tkey   pf\n\tfield pk serial64\n)\n```\n\nwe receive the error:\n\n```\nexample.dbx:2:8: no field \"pf\" defined on model \"user\"\n\ncontext:\n   1: model user (\n   2:     key   pf\n                ^\n```\n\n\n## License\n\nCopyright (C) 2017 Space Monkey, Inc.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n   http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n","funding_links":[],"categories":["Go"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fspacemonkeygo%2Fdbx","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fspacemonkeygo%2Fdbx","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fspacemonkeygo%2Fdbx/lists"}