bob Go SQL Access Toolkit
repository·main·Indexed 23 days ago
https://github.com/stephenafamo/bobA multi-layered Go SQL access toolkit providing a progressive path from a fluent query builder to a full type-safe ORM and factory code generation. Bob supports Postgres, MySQL/MariaDB, and SQLite, offering four layers of functionality: a dialect-specific query builder, ORM code generation based on database schemas, factory generation for testing relational data, and Go code generation for hand-crafted SQL queries.
What's inside bob
- Bob is a Go SQL access toolkit designed to help build SQL queries without abstracting away the underlying SQL implementation. Instead of hiding SQL, it provides typed 'handrails' to improve the developer experience while allowing for custom crafting and progressive enhancement of queries.
Overview of Bob's capabilities and database support
mainBob is a Go SQL access toolkit designed for correctness, convenience without magic, and cooperation with the standard library. It can be adopted progressively, starting from raw SQL strings and moving toward fully typed models and factories.
Supported Databases
Bob provides full support (Queries, Models, ORM Gen, Factory Gen, and Query Gen) for:
- Postgres
- MySQL/MariaDB
- SQLite
Overview of Bob: Go SQL Access Toolkit
mainBob is a set of Go packages and tools designed for working with SQL databases. It is built on three core philosophies: Correctness (following specifications closely), Convenience (providing helpful tools without unexplainable magic), and Cooperation (working well with the standard library and other tools).
Bob allows for progressive adoption, starting from raw SQL query strings and moving towards fully typed queries with generated models and factories.
Features not implemented in Bob
mainBob intentionally excludes certain features to maintain simplicity and follow best practices:
- Automatic timestamps (
createdAt/updatedAt): Bob does not implement these at the ORM level, as it is considered better practice to implement them at the database level. - Soft deletes: Due to the complexity of handling edge cases, especially regarding relationships and cascading soft deletes, this is not implemented.
- Automatic timestamps (
New features available in Bob
mainBob includes several features that are either not supported or more difficult to implement in SQLBoiler:
- Cross schema generation
- Preloading with LEFT JOINs
- Multi-Key relationships
- Context chaining in hooks
- Relationships across tables (e.g.,
has-one-through,has-many-through) - Easier-to-use expression builders: Bob's query builder architecture reduces the need for non-typed strings when building expressions.
Compare Bob vs GORM
mainBob and GORM represent two different philosophies for SQL access in Go. Use Bob if you prefer a database-first approach with high type safety and incremental adoption. Use GORM if you prefer a code-first approach and require a large ecosystem of existing plugins.
Key Differences
Feature Bob GORM Source of Truth Database-first: You manage your DB with SQL/migrations and generate models from it. Code-first: You define models in Go and GORM manages the database schema. Type Safety High: Generates type-safe code for everything, including relationships. Errors are caught at compile time. Low: Relies heavily on interface{}and magic strings, which can lead to runtime panics.Query Building Powerful: Supports almost any Select, Update, Insert, or Delete query supported by the dialect. Limited: Restricted to a subset of common features across supported dialects. Ecosystem Standard Library: Works with *sql.DB, making it compatible with any standard library package.Specialized: Requires specific drivers, though it has many community plugins. Adoption Incremental: You can use parts of Bob (like just the query builder) without full adoption. All-or-nothing: Requires full adoption of the framework. Testability High: Includes generated factories (inspired by Ruby's FactoryBot) for easy model testing. Low: Does not provide built-in testing helpers. Checking if a relationship has been loaded
mainEach model exposes an
R.Loadedstruct containing boolean flags for each relationship. This allows you to distinguish between a relationship that hasn't been loaded (Loaded.X == false) and one that was loaded but returned no results (Loaded.X == trueandR.X == nilorlen(R.X) == 0).Important: If you manually assign to
R(e.g.,jet.R.Pilot = pilot), you are responsible for manually updatingR.Loadedto keep it in sync. The generated APIs (LoadX,Preload,ThenLoad,AttachX,InsertX) handle this automatically.jet, err := models.FindJet(ctx, db, 1) if !jet.R.Loaded.Pilot { // the pilot relationship has not been loaded } if err := jet.LoadPilot(ctx, db); err != nil { return err } // jet.R.Loaded.Pilot is now true. if jet.R.Loaded.Pilot && jet.R.Pilot == nil { // definitively no pilot } // For to-many: pilot, err := models.FindPilot(ctx, db, 1) if err := pilot.LoadJets(ctx, db); err != nil { return err } // pilot.R.Loaded.Jets is true even if the pilot has no jets. if pilot.R.Loaded.Jets && len(pilot.R.Jets) == 0 { // definitively zero jets }Retrieve related data using column naming conventions
mainBob supports automatic nesting of related data by using specific naming patterns for returned columns:
related_table__column_name(double underscore): Indicates a to-one relationship.related_table.column_name(dot): Indicates a to-many relationship.
When using the
All()method, Bob transforms and nests the rows according to these patterns. You can use the--prefixannotation in your SQL to simplify column naming for joins.-- Nested query example using --prefix SELECT users.*, --prefix:videos. videos.*, --prefix:videos.sponsor__ sponsors.* FROM users LEFT JOIN videos ON videos.user_id = users.id INNER JOIN sponsors ON videos.sponsor_id = sponsors.id WHERE users.id IN ($1);How Bob's plugin system works
mainBob's code generation engine is entirely plugin-based. Every output produced by Bob—including models, factories, and enums—is implemented as a plugin. Custom plugins are treated as first-class citizens, utilizing the same interfaces and mechanisms as the built-in plugins.
Plugins are categorized into two types:
- Standalone output plugins: These register their own output directory and generate code in a dedicated package (e.g.,
models,enums, orfactory). - Template extension plugins: These do not create new output directories. Instead, they extend an existing standalone output by appending additional templates to it (e.g.,
where,loaders, andjoinsextend themodelsoutput).
- Standalone output plugins: These register their own output directory and generate code in a dedicated package (e.g.,
What are bob factories?
mainBob factories are objects used to create templates for your models, which in turn can be used to generate model instances for testing. You can create a factory using
factory.New()and then generate specific templates (e.g.,NewJet) using mods to define how the models should be populated.To speed up testing, you can set 'base mods' on the factory itself. These mods will be automatically applied to every new template generated by that factory instance.
f := factory.New() // Set base mods that apply to ALL templates from this factory f.AddBaseJetMods( factory.JetMods.RandomID(), factory.JetMods.RandomAirportID(), ) // These templates will automatically include the base mods jetTemplate1 := f.NewJet() jetTemplate2 := f.NewJet() // Clear base mods if needed f.ClearBaseJetMods()Understand Bob's approach to automatic timestamps and soft deletes
mainBob does not provide built-in support for automatic
createdAt/updatedAttimestamps or soft deletes.- Timestamps: Bob recommends implementing
createdAtandupdatedAtlogic at the database level rather than within the ORM to avoid unnecessary complexity. - Soft Deletes: Bob does not support soft deletes due to the complexity of handling edge cases, particularly regarding relationships and cascading deletes.
- Timestamps: Bob recommends implementing
Use the Bob Query Builder
mainBob provides a dialect-specific query builder that is highly spec-compliant. Because builders are custom-crafted for each dialect (e.g., Postgres, MySQL), it is difficult to construct invalid queries. It is intended as an alternative to packages likesquirrelorgoqu.