Git Product home page Git Product logo

go-queryset's Introduction

go-queryset GoDoc Go Report Card Codacy Badge Maintainability Build Status Coverage Status

100% type-safe ORM for Go (Golang) with code generation and MySQL, PostgreSQL, Sqlite3, SQL Server support. GORM under the hood.

Contents

Installation

go get -u github.com/jirfag/go-queryset/cmd/goqueryset

Usage

Define models

Imagine you have model User in your models.go file:

type User struct {
	gorm.Model
	Rating      int
	RatingMarks int
}

Now transform it by adding comments for query set generation:

//go:generate goqueryset -in models.go

// User struct represent user model. Next line (gen:qs) is needed to autogenerate UserQuerySet.
// gen:qs
type User struct {
	gorm.Model
	Rating      int
	RatingMarks int
}

Take a look at line // gen:qs. It's a necessary line to enable querysets for this struct. You can put it at any line in struct's doc-comment.

Then execute next shell command:

go generate ./...

And you will get file autogenerated_models.go in the same directory (and package) as models.go.

In this autogenerated file you will find a lot of autogenerated typesafe methods like these:

func (qs UserQuerySet) CreatedAtGte(createdAt time.Time) UserQuerySet {
	return qs.w(qs.db.Where("created_at >= ?", createdAt))
}

func (qs UserQuerySet) RatingGt(rating int) UserQuerySet {
	return qs.w(qs.db.Where("rating > ?", rating))
}

func (qs UserQuerySet) IDEq(ID uint) UserQuerySet {
	return qs.w(qs.db.Where("id = ?", ID))
}

func (qs UserQuerySet) DeletedAtIsNull() UserQuerySet {
	return qs.w(qs.db.Where("deleted_at IS NULL"))
}

func (o *User) Delete(db *gorm.DB) error {
	return db.Delete(o).Error
}

func (qs UserQuerySet) OrderAscByCreatedAt() UserQuerySet {
	return qs.w(qs.db.Order("created_at ASC"))
}

See full autogenerated file here.

Now you can use this queryset for creating/reading/updating/deleting. Let's take a look at these operations.

Relation with GORM

You can embed and not embed gorm.Model into your model (e.g. if you don't need DeletedAt field), but you must use *gorm.DB to properly work. Don't worry if you don't use GORM yet, it's easy to create *gorm.DB:

import (
    "github.com/jinzhu/gorm"
    _ "github.com/jinzhu/gorm/dialects/mysql"
)

func getGormDB() *gorm.DB {
	db, err := gorm.Open("mysql", "user:password@/dbname?charset=utf8&parseTime=True&loc=Local")
	// ...
}

If you already use another ORM or raw sql.DB, you can reuse your sql.DB object (to reuse connections pool):

	var sqlDB *sql.DB = getSQLDBFromAnotherORM()
	var gormDB *gorm.DB
	gormDB, err = gorm.Open("mysql", sqlDB)

Create

u := User{
	Rating: 5,
	RatingMarks: 0,
}
err := u.Create(getGormDB())

Under the hood Create method just calls db.Create(&u).

Select

It's the most powerful feature of query set. Let's execute some queries:

Select all users

var users []User
err := NewUserQuerySet(getGormDB()).All(&users)
if err == gorm.ErrRecordNotFound {
	// no records were found
}

It generates this SQL request for MySQL:

SELECT * FROM `users` WHERE `users`.deleted_at IS NULL

deleted_at filtering is added by GORM (soft-delete), to disable it use Unscoped.

Select one user

var user User
err := NewUserQuerySet(getGormDB()).One(&user)

Select N users with highest rating

var users []User
err := NewUserQuerySet(getGormDB()).
	RatingMarksGte(minMarks).
	OrderDescByRating().
	Limit(N).
	All(&users)

Select users registered today

In this example we will define custom method on generated UserQuerySet for later reuse in multiple functions:

func (qs UserQuerySet) RegisteredToday() UserQuerySet {
	// autogenerated typesafe method CreatedAtGte(time.Time)
	return qs.CreatedAtGte(getTodayBegin())
}

...
var users []User
err := NewUserQuerySet(getGormDB()).
	RegisteredToday().
	OrderDescByCreatedAt().
	Limit(N).
	All(&users)

Select specific fields

By default all fields are fetched using the * field selector. using the select methd it is possible to limit the SQL statement to fetch specific fields:

var users []User
err := NewUserQuerySet(getGormDB()).Select(UserDBSchema.ID, UserDBSchema.Rating).All(&users)
if err == gorm.ErrRecordNotFound {
	// no records were found
}

It generates this SQL request for MySQL:

SELECT id, rating FROM `users` WHERE `users`.deleted_at IS NULL

Update

Update one record by primary key

u := User{
	Model: gorm.Model{
		ID: uint(7),
	},
	Rating: 1,
}
err := u.Update(getGormDB(), UserDBSchema.Rating)

Goqueryset generates DB names for struct fields into UserDBSchema variable. In this example we used UserDBSchema.Rating.

And this code generates next SQL:

UPDATE `users` SET `rating` = ? WHERE `users`.deleted_at IS NULL AND `users`.`id` = ?

Update multiple record or without model object

Sometimes we don't have model object or we are updating multiple rows in DB. For these cases there is another typesafe interface:

err := NewUserQuerySet(getGormDB()).
	RatingLt(1).
	GetUpdater().
	SetRatingMarks(0).
	Update()
UPDATE `users` SET `rating_marks` = ? WHERE `users`.deleted_at IS NULL AND ((rating < ?))

UpdateNum

This method makes the same sql queries as Update() method, except return values: it returns number of affected rows and error

num, err := NewUserQuerySet(getGormDB()).
	RatingLt(1).
	GetUpdater().
	SetRatingMarks(0).
	UpdateNum()
UPDATE `users` SET `rating_marks` = ? WHERE `users`.deleted_at IS NULL AND ((rating < ?))

Delete

Delete one record by primary key

u := User{
	Model: gorm.Model{
		ID: uint(7),
	},
}
err := u.Delete(getGormDB())

Delete multiple records

err := NewUserQuerySet(getGormDB()).
	RatingMarksEq(0).
	Delete()

Full list of generated methods

QuerySet methods - func (qs {StructName}QuerySet)

  • create new queryset: New{StructName}QuerySet(db *gorm.DB)
func NewUserQuerySet(db *gorm.DB) UserQuerySet
  • filter by field (where)

    • all field types
      • Equals: {FieldName}(Eq|Ne)(arg {FieldType})
       func (qs UserQuerySet) RatingEq(rating int) UserQuerySet
      • In: {FieldName}(Not)In(arg {FieldType}, argsRest ...{FieldType})
       func (qs UserQuerySet) NameIn(name string, nameRest ...string) UserQuerySet {}
       func (qs UserQuerySet) NameNotIn(name string, nameRest ...string) UserQuerySet {}
      • Order(Asc|Desc)By{FieldName}()
       func (qs UserQuerySet) OrderDescByRating() UserQuerySet
    • numeric types (int, int64, uint etc + time.Time):
      • {FieldName}(Lt|Lte|Gt|Gte)(arg {FieldType)
       func (qs UserQuerySet) RatingGt(rating int) UserQuerySet
    • string types (string):
      • {FieldName}(Like/Notlike)(arg {FieldType)
       func (qs UserQuerySet) NameLike(name string) UserQuerySet
    • pointer fields: {FieldName}IsNull(), {FieldName}IsNotNull()
     func (qs UserQuerySet) ProfileIsNull() UserQuerySet {}
     func (qs UserQuerySet) ProfileIsNotNull() UserQuerySet {}
  • preload related object (for structs fields or pointers to structs fields): Preload{FieldName}() For struct

     	type User struct {
     		profile *Profile
     	}

    will be generated:

     func (qs UserQuerySet) PreloadProfile() UserQuerySet

    Preload functions call gorm.Preload to preload related object.

  • selectors

    • Select all objects, return gorm.ErrRecordNotFound if no records
     func (qs UserQuerySet) All(users *[]User) error
    • Select one object, return gorm.ErrRecordNotFound if no records
     func (qs UserQuerySet) One(user *User) error
  • Limit

func (qs UserQuerySet) Limit(limit int) UserQuerySet
  • get updater (for update + where, based on current queryset):
func (qs UserQuerySet) GetUpdater() UserUpdater
  • delete with conditions from current queryset: Delete()
func (qs UserQuerySet) Delete() error
  • Aggregations
    • Count
     func (qs UserQuerySet) Count() (int, error)

Object methods - func (u *User)

  • create object
func (o *User) Create(db *gorm.DB) error
  • delete object by PK
func (o *User) Delete(db *gorm.DB) error
  • update object by PK
func (o *User) Update(db *gorm.DB, fields ...userDBSchemaField) error

Pay attention that field names are automatically generated into variable

type userDBSchemaField string

// UserDBSchema stores db field names of User
var UserDBSchema = struct {
	ID          userDBSchemaField
	CreatedAt   userDBSchemaField
	UpdatedAt   userDBSchemaField
	DeletedAt   userDBSchemaField
	Rating      userDBSchemaField
	RatingMarks userDBSchemaField
}{

	ID:          userDBSchemaField("id"),
	CreatedAt:   userDBSchemaField("created_at"),
	UpdatedAt:   userDBSchemaField("updated_at"),
	DeletedAt:   userDBSchemaField("deleted_at"),
	Rating:      userDBSchemaField("rating"),
	RatingMarks: userDBSchemaField("rating_marks"),
}

And they are typed, so you won't have string-misprint error.

Updater methods - func (u UserUpdater)

  • set field: Set{FieldName}
func (u UserUpdater) SetCreatedAt(createdAt time.Time) UserUpdater
  • execute update: Update()
func (u UserUpdater) Update() error

Golang version

Golang >= 1.8 is required. Tested on go 1.8, 1.9 versions by Travis CI

Why?

Why not just use GORM?

I like GORM: it's the best ORM for golang, it has fantastic documentation, but as a Golang developers team lead I can point out some troubles with it:

  1. GORM isn't typesafe: it's so easy to spend 1 hour trying to execute simple Update. GORM gets all arguments as interface{} and in the case of invalid GORM usage you won't get error: you will get invalid SQL, no SQL (!) and error == nil etc. It's easy to get SELECT * FROM t WHERE string_field == 1 SQL in production without type safety.
  2. GORM is difficult for beginners because of unclear interface{} interfaces: one can't easily find which arguments to pass to GORM methods.

Why not another ORM?

Type-safety, like with GORM.

Why not any ORM?

I didn't see any ORM that properly handles code duplication. GORM is the best with Scopes support, but even it's far from ideal. E.g. we have GORM and next typical code:

type User struct {
	gorm.Model
	Rating      int
	RatingMarks int
}

func GetUsersWithMaxRating(limit int) ([]User, error) {
	var users []User
	if err := getGormDB().Order("rating DESC").Limit(limit).Find(&users).Error; err != nil {
		return nil, err
	}
	return users, nil
}

func GetUsersRegisteredToday(limit int) ([]User, error) {
	var users []User
	today := getTodayBegin()
	err := getGormDB().Where("created_at >= ?", today).Limit(limit).Find(&users).Error
	if err != nil {
		return nil, err
	}
	return users, nil
}

At one moment PM asks us to implement new function, returning list of users registered today AND sorted by rating. Copy-paste way is to add Order("rating DESC") to GetUsersRegisteredToday. But it leads to typical copy-paste troubles: when we change rating calculation logics (e.g. to .Where("rating_marks >= ?", 10).Order("rating DESC")) we must change it in two places.

How to solve it? First idea is to make reusable functions:

func queryUsersWithMaxRating(db *gorm.DB, limit int) *gorm.DB {
	return db.Order("rating DESC").Limit(limit)
}

func queryUsersRegisteredToday(db *gorm.DB, limit int) *gorm.DB {
	today := getTodayBegin()
	return db.Where("created_at >= ?", today).Limit(limit)
}

func GetUsersWithMaxRating(limit int) ([]User, error) {
	var users []User
	if err := queryUsersWithMaxRating(getGormDB(), limit).Find(&users).Error; err != nil {
		return nil, err
	}
	return users, nil
}

func GetUsersRegisteredToday(limit int) ([]User, error) {
	var users []User
	if err := queryUsersRegisteredToday(getGormDB(), limit).Find(&users).Error; err != nil {
		return nil, err
	}
	return users, nil
}

func GetUsersRegisteredTodayWithMaxRating(limit int) ([]User, error) {
	var users []User
	err := queryUsersWithMaxRating(queryUsersRegisteredToday(getGormDB(), limit), limit).
		Find(&users).Error
	if err != nil {
		return nil, err
	}
	return users, nil
}

We can use GORM Scopes to improve how it looks:

func queryUsersWithMaxRating(db *gorm.DB) *gorm.DB {
	return db.Order("rating DESC")
}

func queryUsersRegisteredToday(db *gorm.DB) *gorm.DB {
	return db.Where("created_at >= ?", getTodayBegin())
}

func GetUsersRegisteredTodayWithMaxRating(limit int) ([]User, error) {
	var users []User
	err := getGormDB().
		Scopes(queryUsersWithMaxRating, queryUsersRegisteredToday).
		Limit(limit).
		Find(&users).Error
	if err != nil {
		return nil, err
	}
	return users, nil
}

Looks nice, but we loosed ability to parametrize our reusable GORM queries (scopes): they must have only one argument of type *gorm.DB. It means that we must move out Limit from them (let's say we get it from user). If we need to implement query QueryUsersRegisteredAfter(db *gorm.DB, t time.Time) - we can't do it.

Now compare it with go-queryset solution:

// UserQuerySet is an autogenerated struct with a lot of typesafe methods.
// We can define any methods on it because it's in the same package
func (qs UserQuerySet) WithMaxRating(minMarks int) UserQuerySet {
	return qs.RatingMarksGte(minMarks).OrderDescByRating()
}

func (qs UserQuerySet) RegisteredToday() UserQuerySet {
	// autogenerated typesafe method CreatedAtGte(time.Time)
	return qs.CreatedAtGte(getTodayBegin())
}

// now we can parametrize it
const minRatingMarks = 10

func GetUsersWithMaxRating(limit int) ([]User, error) {
	var users []User
	err := NewUserQuerySet(getGormDB()).
		WithMaxRating(minRatingMarks). // reuse our method
		Limit(limit).                  // autogenerated typesafe method Limit(int)
		All(&users)                    // autogenerated typesafe method All(*[]User)
	if err != nil {
		return nil, err
	}
	return users, nil
}

func GetUsersRegisteredToday(limit int) ([]User, error) {
	var users []User
	err := NewUserQuerySet(getGormDB()).
		RegisteredToday(). // reuse our method
		Limit(limit).      // autogenerated typesafe method Limit(int)
		All(&users)        // autogenerated typesafe method All(*[]User)
	if err != nil {
		return nil, err
	}
	return users, nil
}

func GetUsersRegisteredTodayWithMaxRating(limit int) ([]User, error) {
	var users []User
	err := NewUserQuerySet(getGormDB()).
		RegisteredToday().             // reuse our method
		WithMaxRating(minRatingMarks). // reuse our method
		Limit(limit).
		All(&users) // autogenerated typesafe method All(*[]User)
	if err != nil {
		return nil, err
	}
	return users, nil
}

Why not raw SQL queries?

No type-safety, a lot of boilerplate code.

Why not go-kallax?

  1. It works only with PostgreSQL. Go-queryset supports mysql, postgresql, sqlite, mssql etc (all that gorm supports).
  2. Lacks simplier model updating interface

How it relates to another languages ORMs

QuerySet pattern is similar to:

Features

  • 100% typesafe: there is no one method with interface{} arguments.
  • QuerySet pattern allows to reuse queries by defining custom methods on it.
  • Supports all DBMS that GORM supports: MySQL, PostgreSQL, Sqlite3, SQL Server.
  • Supports creating, selecting, updating, deleting of objects.

Limitations

  • Joins aren't supported
  • Struct tags aren't supported

Performance

Runtime

Performance is similar to GORM performance. GORM uses reflection and it may be slow, so why don't we generate raw SQL code?

  1. Despite the fact GORM uses reflection, it's the most popular ORM for golang. There are really few tasks where you are CPU-bound while working with DB, usually you are CPU-bound in machine with DB and network/disk bound on machine with golang server.
  2. Premature optimization is the root of all evil.
  3. Go-queryset is fully compatible with GORM.
  4. Code generation is used here not to speedup things, but to create nice interfaces.
  5. The main purpose of go-queryset isn't speed, but usage convenience.

Code generation

Code generation is fast:

  1. We parse AST of needed file and find needed structs.
  2. We load package and parse it by go/types
  3. We don't use reflect module for parsing, because it's slow

go-queryset's People

Contributors

amalfra avatar aviau avatar derelektrobesen avatar dmitrydorofeev avatar jirfag avatar jqs7 avatar lightglitch avatar lucas0707 avatar stffabi avatar to6ka avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

go-queryset's Issues

Database relations

Is it supports database relations? For example code below.

type User struct {
	Model
	Name     string         `form:"name" json:"name" valid:"required~Name is required"`
	Email    string         `form:"email" gorm:"unique_index" json:"email" valid:"email~Email isn't valid"`
   --->Stores   []Store        `form:"stores" json:"stores" valid:"email~Stores isn't valid"`
	Password string         `form:"password" json:"-" valid:"required~Password is required"`
	BirthDay time.Time      `form:"birthday" json:"birthday" valid:"required~Birth day is required" time_format:"02-01-2006"`
	Role     roles.RoleType `gorm:"default:0" json:"role"`
}

Fields in select statements are not wrapped with apostrophes

If we trying to call go-queryset with statement like NewUserQuerySet(db).NameEq("some_name").One(&user) we should see sql statement like

SELECT * FROM `users` WHERE `users`.`deleted_at` AND ((`name` = ?)) IS NULL ORDER BY `users`.`id` ASC LIMIT 1

but the library generates following statement:

SELECT * FROM `users` WHERE `users`.deleted_at AND ((name = ?)) IS NULL ORDER BY `users`.`id` ASC LIMIT 1

Possibly it could be critical in some cases

Interesting Generation Error

First off, awesome job on this package, I was really impressed with the api it generated so much so we started migrating our GORM queries across to use this package instead.

So the issue is I have an Enum that is imported from another package. This seems to work fine on other code generations we have. However, when I generated code using go-queryset I got an unexpected result.

The struct // gen:qs is above looks like this:

type Example struct {

	PriceID int64

	Currency forex.Currency

        ....
}

The Generated code for currency looks like this:

// CurrencyEq is an autogenerated method
// nolint: dupl
func (qs ExampleQuerySet) CurrencyEq(currency v1.Currency) ExampleQuerySet {
	return qs.w(qs.db.Where("currency = ?", currency))
}

// CurrencyGt is an autogenerated method
// nolint: dupl
func (qs ExampleQuerySet) CurrencyGt(currency v1.Currency) ExampleQuerySet {
	return qs.w(qs.db.Where("currency > ?", currency))
}

....

The error is that the generated code has placed v1 as the package selector and not the actual package name forex.

This is non-blocking for me as It's very easy to fix up manually. However, this would suggest that the code generation is picking up v1 from the file path? The import is like so: import "go.company.com/internal-files/companyapi/forex/v1".

Happy to help look into this problem but I thought I'd raise it first just in case something like this has occurred else where.

QuerySet

is QuerySet instance thread-safe?

Queryset Preload Methods Repeat Keys

When writing a Query that involves preloading a foreign-key object (for example: DBModel.NewObjectQuerySet(DBConn).PrimaryKeyEq(pk).PreloadNestedObject()), the Nested Objects are fetched in a query that has a WHERE clause with all the foreign-key IDs.

The problem arises when said Nested Objects are the same for multiple instances of Object (a many-to-one relationship); the underlying query repeats the key. For example:

Object PK Nested Object FK
1 1
2 2
3 1
4 1
5 1

Results in the following query when using PreloadNestedObject():

SELECT * FROM NestedObject WHERE pk IN (1, 1, 1, 1, 2);

This massively slows down the underlying queries when fetching fairly average resultsets (tens of seconds every 30000 simple records).

Is offset supported?

Is querying with offset supported? I want to use limit combined with offset for typical pagination.

setup error

An error occurred during installation

go get -u github.com/jirfag/go-queryset/cmd/goqueryset

go: finding github.com/jirfag/go-queryset latest
go: finding golang.org/x/crypto latest
go: finding github.com/denisenkom/go-mssqldb latest
go: finding github.com/erikstmartin/go-testdb latest
go: finding golang.org/x/sys latest
go: finding golang.org/x/tools latest
go: finding golang.org/x/net latest
go: finding golang.org/x/sync latest
go: finding golang.org/x/oauth2 latest
go: finding github.com/google/pprof latest
go: finding golang.org/x/lint latest
go: finding github.com/jstemmer/go-junit-report latest
go: finding golang.org/x/build latest
go: finding github.com/shurcooL/issuesapp latest
go: finding github.com/neelance/astrewrite latest
go: finding golang.org/x/perf latest
go: finding github.com/shurcooL/go-goon latest
go: finding github.com/shurcooL/github_flavored_markdown latest
go: finding go4.org latest
go: finding github.com/golang/glog latest
go: finding github.com/shurcooL/htmlg latest
go: finding dmitri.shuralyov.com/service/change latest
go: finding github.com/bradfitz/go-smtpd latest
go: finding github.com/shurcooL/go latest
go: finding google.golang.org/genproto latest
go: finding github.com/anmitsu/go-shlex latest
go: finding github.com/jellevandenhooff/dkim latest
go: finding github.com/shurcooL/httpfs latest
go: gopkg.in/DATA-DOG/[email protected]: parsing go.mod: missing module line
go: finding github.com/shurcooL/webdavfs latest
go: sourcegraph.com/sourcegraph/[email protected]: parsing go.mod: unexpected module path "github.com/sourcegraph/go-diff"
go: finding golang.org/x/time latest
go get: error loading module requirements

Generate correct names for query sets for private structures

// gen:qs
struct localStruct {
  // some fields
}

// Generated code:
type localStructQuerySet struct { ... }

// not capitalized `L`
// global function `NewlocalStructQuerySet` returns local structure `localStructQuerySet`
func NewlocalStructQuerySet(...) {...} 

Support aggregations and selecting only some fields

Now we write:

	db := ctx.GetGorm().
		Select("max("+PointDBSchema.TripPointID.String()+") as max_id, "+
			"count("+PointDBSchema.PlaceID.String()+") as rating").
		Where(PointDBSchema.LocationID.String()+" = ?", locationID).
		Group(PointDBSchema.PlaceID.String()).
		Order("rating DESC").
		Limit(limit)

NotIn QuerySet function

this function
StatusNotIn(status types.TXStatus, statusRest ...types.TXStatus)

has to be used like this:
StatusNotIn(types.CompletedTXStatuses[0], types.CompletedTXStatuses[1:]...)

as for me, better way is
StatusNotIn(types.CompletedTXStatuses...)

Missing packages on generate

When I run go generate ./... using the latest master code. I get a file called autogenerated_***.go just like the readme states it should. The problem is that it is importing a package called base "github.com/jirfag/go-queryset/queryset/base" which doesn't seem to exist.

It also misses the packages "time" and "fmt"

Any ideas?

check if table exists ?

Just trying this out.. Its pretty nice..
i dont think the generated code checks if the Table exists ?

What do you think about the idea of being able to generate the check and for all functions to call it ?
It would be a flag on the generator, so you dont have to use it.

Its very nice at Development time to more fast.

--

It brings uo the whole question of migrations of course too....

vgo/go mod support

Thanks for this great package. I noticed that since I moved my project to go modules, the generation stopped working.

The issue appears to be with packages created outside the $GOPATH. Is there planned support for this?

The method `Update` can only be used inside a package.

From your examples:

// Update updates User fields by primary key
func (o *User) Update(db *gorm.DB, fields ...userDBSchemaField) error {
	...
}

Method Update receives arguments of type userDBSchemaField (unexported), but method is exported. I propose rename type userDBSchemaField from unexported type to exported.

can't generate query sets on Windows

My ENV:
go version go1.9.2 windows/amd64

My Code Path:

E:\GOPATH\src\test\models.go

My question :
To excute go generate ./... got a failure info : can't generate query sets: can't parse file models.go to get structs: can't load types for file models.go in package "./." But in Desktop dictionary path “test/models.go” to excute go generate ./... was successful !

The code :

import "github.com/jinzhu/gorm"

//go:generate goqueryset -in models.go
// User struct represent user model. Next line (gen:qs) is needed to autogenerate UserQuerySet.
// gen:qs
type User struct {
	gorm.Model
	Rating      int
	RatingMarks int
}

field names are not escaped

where key = ?

will result in

Error 1064: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'key = ?)

not needed check for gorm.ErrRecordNotFound in Update()

218     if err := db.Model(o).Updates(u).Error; err != nil {
219         if err == gorm.ErrRecordNotFound {
220             return err
221         }
222 
223         return fmt.Errorf("can't update Entry %v fields %v: %s",
224             o, fields, err)
225     }

gorm will never return this error from Update()

go-queryset ignores custom column tag

Sample:

Value int `gorm:"column:someprefix_value"`

will result in:

func (qs QuerySet) ValueEq(value int) QuerySet {
       return qs.w(qs.db.Where("value = ?", value))
}

A question regarding the logic of the Select methods (All in particular)

@jirfag great project and I am glad I found it even though this late.

I have one question though. With respect to code readability, does it make sense to copy gorm's way of accepting a result pointer as an argument?

What I mean is the following. Every QuerySet instance has one of these methods:

func (qs UserQuerySet) All(ret *[]User) error {
	return qs.db.Find(ret).Error
}

I allowed myself to insert a second method that looks one idea cleaner if you are coming from other languages:

func (qs UserQuerySet) AllRet() ([]User, error) {
	var ret []User
	err := qs.db.Find(&ret).Error
	return ret, err
}

Since I don't want to make false claims, I also wrote a quick benchmark testing each and every method with the same conditions (10000 mock users benching for 5s). Here are the results:

BenchmarkUserSelectAll-8   	      361764	     14977 ns/op	    6154 B/op	     100 allocs/op
BenchmarkUserSelectAllRet-8   368132	     14634 ns/op	    6153 B/op	     100 allocs/op

Without going as far as to claim that my method is faster, I'd just say they are on par with one another. What was then the motivation to follow the result pointer approach? Could we make an improvement proposal in the future?

Support UUID as field type

I have tried to use UUID as a field type and it just gets ignored (which is not very useful if it is your primary key). I have checked the fields.go file and it seems to be ignored because it is a type that doesn't have a //gen:qs comment on top of it.

However, with some more testing it seems that the root problem is more about the handling of fixed sized arrays as field types. I can understand that this is normally a problem since it doesn't map to SQL style databases very well. However, fixed byte arrays are generally supported.

Missing Private Fields

This system is excellent. However, all private fields are ignored. This is probably a good decision in general but I think that if the gorm struct tag Column is present then even private fields should be generated for.

GORM 2.0 compliance

Any plans to make the generator GORM 2.0 compliant soon?

Perhaps I might be all wrong here (or just plain optimistic), but at glance, it does not seem to be quite a big deal - here's the simplest patch I could think of (neither unit tests nor examples fixed yet):

diff --git a/go.mod b/go.mod
index 7fd83ac..9071c01 100644
--- a/go.mod
+++ b/go.mod
@@ -7,8 +7,6 @@ require (
 	github.com/go-sql-driver/mysql v0.0.0-20170822214809-26471af196a1 // indirect
 	github.com/gofrs/uuid v3.2.0+incompatible // indirect
 	github.com/jinzhu/gorm v1.9.2
-	github.com/jinzhu/inflection v0.0.0-20170102125226-1c35d901db3d // indirect
-	github.com/jinzhu/now v1.0.0 // indirect
 	github.com/lib/pq v1.0.0 // indirect
 	github.com/mattn/go-sqlite3 v1.10.0 // indirect
 	github.com/pkg/errors v0.8.1
@@ -16,6 +14,7 @@ require (
 	golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 // indirect
 	golang.org/x/tools v0.0.0-20190226205152-f727befe758c
 	gopkg.in/DATA-DOG/go-sqlmock.v1 v1.2.0
+	gorm.io/gorm v1.20.8
 )
 
 go 1.13
diff --git a/go.sum b/go.sum
index 7f4f78c..84deb88 100644
--- a/go.sum
+++ b/go.sum
@@ -53,8 +53,12 @@ github.com/jinzhu/gorm v1.9.2 h1:lCvgEaqe/HVE+tjAR2mt4HbbHAZsQOv3XAZiEZV37iw=
 github.com/jinzhu/gorm v1.9.2/go.mod h1:Vla75njaFJ8clLU1W44h34PjIkijhjHIYnZxMqCdxqo=
 github.com/jinzhu/inflection v0.0.0-20170102125226-1c35d901db3d h1:jRQLvyVGL+iVtDElaEIDdKwpPqUIZJfzkNLV34htpEc=
 github.com/jinzhu/inflection v0.0.0-20170102125226-1c35d901db3d/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
+github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
+github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
 github.com/jinzhu/now v1.0.0 h1:6WV8LvwPpDhKjo5U9O6b4+xdG/jTXNPwlDme/MTo8Ns=
 github.com/jinzhu/now v1.0.0/go.mod h1:oHTiXerJ20+SfYcrdlBO7rzZRJWGwSTQ0iUY2jI6Gfc=
+github.com/jinzhu/now v1.1.1 h1:g39TucaRWyV3dwDO++eEc6qf8TVIQ/Da48WmqjZ3i7E=
+github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
 github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
@@ -166,6 +170,8 @@ gopkg.in/DATA-DOG/go-sqlmock.v1 v1.2.0/go.mod h1:OdE7CF6DbADk7lN8LIKRzRJTTZXIjtW
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
 gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gorm.io/gorm v1.20.8 h1:iToaOdZgjNvlc44NFkxfLa3U9q63qwaxt0FdNCiwOMs=
+gorm.io/gorm v1.20.8/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
 grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
 honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/internal/queryset/field/field.go b/internal/queryset/field/field.go
index 0b7e344..75ad6ed 100644
--- a/internal/queryset/field/field.go
+++ b/internal/queryset/field/field.go
@@ -1,12 +1,11 @@
 package field
 
 import (
+	"bytes"
 	"fmt"
 	"go/types"
 	"reflect"
 	"strings"
-
-	"github.com/jinzhu/gorm"
 )
 
 type BaseInfo struct {
@@ -99,7 +98,7 @@ func (g InfoGenerator) GenFieldInfo(f Field) *Info {
 		return nil
 	}
 
-	dbName := gorm.ToDBName(f.Name())
+	dbName := toDBName(f.Name()) // Equvalent to old: gorm.ToDBName(f.Name())
 	if dbColName := tagSetting["COLUMN"]; dbColName != "" {
 		dbName = dbColName
 	}
@@ -162,3 +161,52 @@ func (g InfoGenerator) GenFieldInfo(f Field) *Info {
 		return nil
 	}
 }
+
+func toDBName(name string) string {
+	const (
+		lower = false
+		upper = true
+	)
+
+	if name == "" {
+		return ""
+	}
+
+	var (
+		value                                    = name // commonInitialismsReplacer.Replace(name)
+		buf                                      = bytes.NewBufferString("")
+		lastCase, currCase, nextCase, nextNumber bool
+	)
+
+	for i, v := range value[:len(value)-1] {
+		nextCase = bool(value[i+1] >= 'A' && value[i+1] <= 'Z')
+		nextNumber = bool(value[i+1] >= '0' && value[i+1] <= '9')
+
+		if i > 0 {
+			if currCase == upper {
+				if lastCase == upper && (nextCase == upper || nextNumber == upper) {
+					buf.WriteRune(v)
+				} else {
+					if value[i-1] != '_' && value[i+1] != '_' {
+						buf.WriteRune('_')
+					}
+					buf.WriteRune(v)
+				}
+			} else {
+				buf.WriteRune(v)
+				if i == len(value)-2 && (nextCase == upper && nextNumber == lower) {
+					buf.WriteRune('_')
+				}
+			}
+		} else {
+			currCase = upper
+			buf.WriteRune(v)
+		}
+		lastCase = currCase
+		currCase = nextCase
+	}
+
+	buf.WriteByte(value[len(value)-1])
+
+	return strings.ToLower(buf.String())
+}
diff --git a/internal/queryset/generator/generator.go b/internal/queryset/generator/generator.go
index a0bf12f..f817eb0 100644
--- a/internal/queryset/generator/generator.go
+++ b/internal/queryset/generator/generator.go
@@ -59,7 +59,7 @@ import (
 	"strings"
 	"time"
 
-	"github.com/jinzhu/gorm"
+	"gorm.io/jgorm"
 )
 `
 
diff --git a/internal/queryset/methods/queryset.go b/internal/queryset/methods/queryset.go
index b42395b..bb8f2af 100644
--- a/internal/queryset/methods/queryset.go
+++ b/internal/queryset/methods/queryset.go
@@ -353,8 +353,8 @@ func NewCountMethod(qsTypeName string) CountMethod {
 	return CountMethod{
 		baseQuerySetMethod: newBaseQuerySetMethod(qsTypeName),
 		namedMethod:        newNamedMethod("Count"),
-		constRetMethod:     newConstRetMethod("(int, error)"),
-		constBodyMethod: newConstBodyMethod(`var count int
+		constRetMethod:     newConstRetMethod("(int64, error)"),
+		constBodyMethod: newConstBodyMethod(`var count int64
 			err := %s.Count(&count).Error
 			return count, err`, qsDbName),
 	}

Any thoughts in this regard so far?
Thanks.
Regards

Object Mapper design/functionality?

Hi,
Thank you for the effort you have put into this project and making it available as open source.

I'm new to Go and am looking for an Object Mapper library in Golang - I'm struggling to see past the sytax right now - hence this question.

Essentially Object Mapper approach builds on the lessons learned from the AvtiveRecord and DataMapper patterns/conventions, as well as the implementation lessons from like named project.

Something with the design and functionality of rom-rb.

Does your library a Object Mapper type functionality?
If not would you kind enough to share the name of any OM libraries that your know of in Goland?

Fields list in selects

Now it generates:

// One is used to retrieve one result. It returns gorm.ErrRecordNotFound
// if nothing was fetched
func (qs PaymentQuerySet) One(ret *Payment) error {
 return qs.db.First(ret).Error
}

If would be awesome if it was this like:

// One is used to retrieve one result. It returns gorm.ErrRecordNotFound
// if nothing was fetched
func (qs PaymentQuerySet) One(ret *Payment, fields ...paymentDBSchemaField) error {
 if (len(fields) != 0) {
  qs = qs.w(qs.db.Select(fields...))
 }
 return qs.db.First(ret).Error
}

Cannot generate code

I have installed goqueryset which looks something like the following:

// go:generate goqueryset -in types.go

// gen:qs
type Object struct {
	ID                 id.TargetID `gorm:"PRIMARY_KEY"`
	LastSeen           *LastSeen   `gorm:"foreignkey:TargetID"`
	RegistrationStatus database.RegistrationStatus
	RegistrationToken  string `gorm:"UNIQUE"`
}

// gen:qs
type LastSeen struct {
	TargetID id.TargetID
	Time     time.Time
}

However, if I run the following go generate in the same folder that file is, I get an exit code of 0 and no file

column name mis-converting

i've defined a model with column name vipprc but goqueryset generate it to 'v_ip_prc' column name

like

type Foo struct {
  VIPPRC `gorm:"column:vipprc"`
}

to

VIPPRC:       POSSRLDTDBSchemaField("v_ip_prc"),

EDIT:
sorry i make a typo of gorm struct tag

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.