go-django-queries

module
v1.0.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 26, 2025 License: GPL-2.0

README

go-django-queries

while Go-Django tries to do as little as possible with the database, some things might be made easier if we provide some helper functions to work with the database.

Table of Contents

Latest version: v1.0.2

Installation

The package is easily installed with go get.

go get github.com/Nigel2392/go-django-queries@latest

Usage

We provide a short example of how to use the package.

It is assumed that the database tables are already set-up and that the go-django app is already instantiated.

Most of this package is confirmed to be compatible with MySQL, SQLite3 and PostgreSQL - using SQLX.Rebind(...) to translate the queries in between databases.

Basic setup for different databases

Semantics for PostgreSQL, MySQL or SQLite3 might differ slightly, such as which quotes to use for the table names and column names.

This can be overridden (on a package level) by setting queries.Quote to the desired quote character, by default it is set to "`".

package main

import (
    "github.com/Nigel2392/go-django-queries/src"
)

func init() {
    // Set the quote character to be used for table and column names
    queries.Quote = `"`
}
Defining your models

The models are defined using the attrs package, which is part of the go-django package.

type Profile struct {
    ID    int
    Name  string
    Email string
}

func (m *Profile) FieldDefs() attrs.Definitions {
    return attrs.Define(m,
        attrs.NewField(m, "ID", &attrs.FieldConfig{
            Primary:  true,
            ReadOnly: true,
        }),
        attrs.NewField(m, "Name", &attrs.FieldConfig{}),
        attrs.NewField(m, "Email", &attrs.FieldConfig{}),
    ).WithTableName("profiles")
}

type User struct {
    ID      int
    Name    string
    Profile *Profile
}

func (m *User) FieldDefs() attrs.Definitions {
    return attrs.Define(m,
        attrs.NewField(m, "ID", &attrs.FieldConfig{
            Primary:  true,
            ReadOnly: true,
        }),
        attrs.NewField(m, "Name", &attrs.FieldConfig{}),
        attrs.NewField(m, "Profile", &attrs.FieldConfig{
            RelForeignKey: &Profile{},
            Column:        "profile_id",
        }),
    ).WithTableName("users")
}

type Todo struct {
 ID          int
 Title       string
 Description string
 Done        bool
 User        *User
}

func (m *Todo) FieldDefs() attrs.Definitions {
 return attrs.Define(m,
        attrs.NewField(m, "ID", &attrs.FieldConfig{
            Column:   "id", // can be inferred, but explicitly set for clarity
            Primary:  true,
            ReadOnly: true,
        }),
        attrs.NewField(m, "Title", &attrs.FieldConfig{
            Column: "title", // can be inferred, but explicitly set for clarity
        }),
        attrs.NewField(m, "Description", &attrs.FieldConfig{
            Column: "description", // can be inferred, but explicitly set for clarity
            FormWidget: func(cfg attrs.FieldConfig) widgets.Widget {
             return widgets.NewTextarea(nil)
            },
        }),
        attrs.NewField(m, "Done", &attrs.FieldConfig{}),
        attrs.NewField(m, "User", &attrs.FieldConfig{
            Column:        "user_id",
            RelForeignKey: &User{},
        }),
    ).WithTableName("todos")
}
Inserting new records
  // Create a new profile instance
  var profile = &Profile{
    Name:  "test profile",
    Email: "test@example.com",
  }

  if err := queries.CreateObject(profile); err != nil || profile.ID == 0 {
     t.Fatalf("Failed to insert profile: %v", err)
  }

  // Create a new user instance
  var user = &User{
     Name:    "test user",
     Profile: profile,
  }

  if err := queries.CreateObject(user); err != nil || user.ID == 0 {
     t.Fatalf("Failed to insert user: %v", err)
  }

  // Create a new todo instance
  var todo = &Todo{
    Title:       "New Test Todo",
    Description: "This is a new test todo",
    Done:        false,
    User:        user,
  }

  if err := queries.CreateObject(todo); err != nil {
    t.Fatalf("Failed to insert todo: %v", err)
  }
Updating records
  // Update the todo instance
  todo.Title = "Updated Test Todo"
  todo.Done = true

  if err := queries.UpdateObject(todo); err != nil {
    t.Fatalf("Failed to update todo: %v", err)
  }
Deleting records
  if err := queries.DeleteObject(todo); err != nil {
    t.Fatalf("Failed to delete todo: %v", err)
  }
Querying records
  query := queries.Objects(&Todo{}).
    // Select the user and profile fields, append a star to
    // automatically select all related fields, User.Profile would always result in a join
    Select("ID", "Title", "Description", "Done", "User.*", "User.Profile.*").

    // Select the user and profile fields, leaving out star operator
    // This would not result in a join, and only fetch the user's ID
    Select("ID", "Title", "Description", "Done", "User").
    
    // Select the user and profile fields, leaving out star operator
    // for the profile, but not for the user
    // This would result in a join, but only for the user - not the profile.
    Select("ID", "Title", "Description", "Done", "User.*", "User.Profile").

    // Generate a WHERE clause with the given conditions
    Filter(
      queries.Q("Title__icontains", "new test"),
      queries.Q("Done", true),
      queries.Q("User.Name__icontains", "test"),
      queries.Q("User.ID", user.ID),
      queries.Q("User.Profile.Email__icontains", profile.Email),
      queries.Q("User.Profile.ID", profile.ID),
    ).
    // Generate an ORDER BY clause with the given conditions
    OrderBy("-ID", "-User.Name", "-User.Profile.Email").
    Limit(5).
    All()


    // todos is of type Query[[]*Todo, *Todo]
    todos, err := query.Exec() // / []*Todo, error
    if err != nil {
      t.Fatalf("Failed to query todos: %v", err)
    }

    fmt.Printf("Queried todos: %v\n", todos)
    fmt.Printf("Executed SQL: %s\n", query.SQL())
The Query interface

The Query represents a query that can be executed against the database.

It is used to give the developers a way to introspect the query and its arguments.

No database lookup will be performed until the Exec() method is called.

The Exec() method will return the result of the query and an error if one occurred.

// - T1 is the type of the result
// - T2 is the type of the model
type Query[T1 any, T2 any] interface {
  // The SQL query string to be executed
  SQL() string

  // Arguments to be passed to the query
  Args() []any

  // The model which the query is for
  Model() T2

  // Execute the query and return the result 
  Exec() (T1, error)
}
Database Drivers & Query translations

This only works automatically if your driver is one of the following:

  • github.com/go-sql-driver/mysql.MySQLDriver
  • github.com/mattn/go-sqlite3.SQLiteDriver
  • github.com/jackc/pgx/v5/stdlib.Driver

Otherwise the database driver needs to be explicitly registered with RegisterDriver.

package main

import (
    "github.com/Nigel2392/go-django-queries/src"
    myDriver "path/to/your/database/driver"
)

func init() {
    // Register the database driver with the package
    queries.RegisterDriver(&myDriver.SQLiteDriver{}, "sqlite3")
}

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL