go-kallax, Kallax是面向go语言的PostgreSQL类型安全 ORM

分享于 

44分钟阅读

GitHub

  繁體 雙語
Kallax is a PostgreSQL typesafe ORM for the Go language.
  • 源代码名称:go-kallax
  • 源代码网址:http://www.github.com/src-d/go-kallax
  • go-kallax源代码文档
  • go-kallax源代码下载
  • Git URL:
    git://www.github.com/src-d/go-kallax.git
    Git Clone代码到本地:
    git clone http://www.github.com/src-d/go-kallax
    Subversion代码到本地:
    $ svn co --depth empty http://www.github.com/src-d/go-kallax
    Checked out revision 1.
    $ cd repo
    $ svn up trunk
    

    GoDocBuild StatuscodecovGo Report CardLicense: MIT

    Kallax是面向go语言的PostgreSQL类型安全 ORM。

    类的目的是提供一种方式,以编程方式编写查询和与PostgreSQL数据库交互,而不用 Having 编写一行 SQL,使用字符串引用列中的任何类型的值。

    因此,kallax的第一个优先级是为数据访问层提供类型安全。 kallax的另一个目标是确保所有模型都是无需使用数据库特定类型( 例如) 来使用结构。 提供了所有基本go类型和所有JSON和数组操作符的数组支持。

    电子邮件内容

    安装

    安装 kallax的推荐方式是:

    
    go get -u gopkg.in/src-d/go-kallax.v1/...
    
    
    
    

    kallax 包含一个由使用的二进制工具,请确保 $GOPATH/bin 在你的$PATH

    用法

    假设你的模型中有以下文件。

    package modelstypeUserstruct {
     kallax.Model`table:"users" pk:"id"`ID kallax.ULIDUsernamestringEmailstringPasswordstring}

    然后在该软件包的任何文件上放置以下内容:

    //go:generate kallax gen

    现在你所需要做的就是运行 go generate./...,并且将生成一个 kallax.go 文件,并将所有生成的代码。

    如果你不想使用 go generate,即使是首选用户,你可以转到你的包并自己运行 kallax gen

    从生成中排除文件

    有时你可能希望在定义的相同包中使用生成的代码,并在生成模型时产生问题。 你可以通过将 go:generate 注释更改为以下内容来排除包中的文件:

    //go:generate kallax gen -e file1.go -e file2.go

    定义模型

    模型只是嵌入 kallax.Model 类型的go结构。 这里结构的所有字段都将是数据库表中的列。

    模型还需要有一个( 只有一个) 主密钥。 主键是使用 kallax.Model 嵌入中的pk 结构标记定义的。 还可以使用结构标记( pk ) 设置结构中的主键,对于非自动的incrementable主键或者 pk:"autoincr",该字段可以为 pk:"",而不是 auto-incrementable。 有关主键的更多信息,请参见主键部分。

    首先,让我们查看模型字段的规则和约定:

    • 在implement类型 or implement和 driver.Valuer的基本类型或者类型的所有字段都将被视为匹配类型表中的列。
    • 上述类型的数组或者Fragment将被视为它的匹配类型的PostgreSQL数组。
    • are ( 或者指向结构的指针) 或者接口的字段不实现 sql.Scannerdriver.Valuer 将被视为 JSON。 相同规则的数组或者类型的切片也一样。
    • 结构标记 kallax:",inline" 或者嵌入结构( 或者指向结构的指针)的字段将被视为内联,并且它们的字段将被视为模型的root。
    • 默认情况下所有指针字段都可为空值。 这意味着你不需要使用 sql.NullInt64sql.NullBool 和,因为kallax会自动为你处理。 警告:如果在扫描之前,所有JSON和 sql.Scanner 实现器都将用 new(T) 进行初始化,那么它们将被初始化。
    • 默认情况下,列的NAME 将是转换为较低蛇案例的结构字段的NAME ( 比如。 UserName => user_nameUserID => user_id )。 你可以使用结构标记 kallax:"my_custom_name" 重写它。
    • 作为模型本身的结构( 或者指向结构的指针) 切片将被视为 1: n 关系。 模型数组按设计不支持
    • 将结构域的结构或者指针视为模型本身的结构将被视为 1: 1 关系。
    • 对于关系,假定外键是转换为较低的蛇案例加 _id ( 比如 )的模型的NAME。 User => user_id )。 你可以使用结构标记 fk:"my_custom_fk" 重写它。
    • 对于反向关系,你需要使用结构标记 fk:",inverse"。 你可以将 inverse 与使用 fk:"my_custom_fk,inverse" 重写外键组合起来。 在逆中,外键 NAME 不指定关系表中列的NAME,而是列中列的NAME。 另一个表中的列的NAME 始终是另一个模型的主键,不能随时更改。
    • 外键不必在模型,他们被自动管理在下面,由 kallax。

    Kallax还提供一个 kallax.Timestamps 结构,其中包含将自动管理的CreatedAtUpdatedAt

    让我们看一个包含所有这些案例的模型示例:

    typeUserstruct {
     kallax.Model`table:"users" pk:"id,autoincr"` kallax.TimestampsIDint64UsernamestringPasswordstringEmails []string// This is for demo purposes, please don't do this// 1:N relationships load all N rows by default, so// only do it when N is small.// If N is big, you should probably be querying the posts// table instead.Posts []*Post `fk:"poster_id"`}typePoststruct {
     kallax.Model`table:"posts"` kallax.TimestampsIDint64`pk:"autoincr"`Contentstring`kallax:"post_content"`Poster *User `fk:"poster_id,inverse"`MetadataMetadata`kallax:",inline"`}typeMetadatastruct {
     MetadataTypeMetadataTypeMetadatamap[string]interface{} // this will be json}

    结构标记

    标签描述可以用于
    table:"table_name"指定模型的表的NAME。 如果未提供,表的NAME 将是下一个蛇案例中结构的NAME ( 比如 )。 UserPreference => user_preference )嵌入式 kallax.Model
    pk:"primary_key_column_name"指定主键的列 NAME。嵌入式 kallax.Model
    pk:"primary_key_column_name,autoincr"指定autoincrementable主键的列 NAME。嵌入式 kallax.Model
    pk:""指定字段是主键具有有效标识符类型的任何字段
    pk:"autoincr"指定字段是自动incrementable主键具有有效标识符类型的任何字段
    kallax:"column_name"指定列的NAME任何非关系的模型域
    kallax:"-"忽略字段并不存储它任何模型字段
    kallax:",inline"将结构字段的字段添加到模型中。 也可以在逗号之前给出列 NAME,但是它被忽略,因为该字段不再是列。任何结构字段
    fk:"foreign_key_name"外键列的名称任何关系字段
    fk:",inverse"指定关系是反向关系。 还可以在逗号之前给出外键 NAME任何关系字段
    unique:"true"指定列具有唯一约束。任何非主键字段

    主键

    主键类型需要满足标识符接口。 即使他们必须这样做,生成器足够聪明了,可以了解什么时候换一些类型使用户更容易。

    以下类型可以用作主键:

    由于sql映射的工作原理,如果指向 uuid.UUIDkallax.ULID的指针在数据库中显示为 NULL,则不会将指针设置为 nil,而是将它的设置为 uuid.Nil 不鼓励使用指向uuid的指针。

    如果你需要另一个类型作为主键,可以随意打开一个实现请求的请求。

    已知的限制

    • 只能指定一个主键,它不能是复合键。

    模型构造函数

    Kallax为你的类型为 New{TypeName}的类型生成一个构造函数。 但是你可以通过实现名为 New{TypeName}的private 构造函数来定制它。 由kallax生成的构造函数将使用与 private 构造函数相同的签名。 你可以使用它提供默认值,或者使用一些值构建模型。

    如果你实现了这里构造函数:

    funcnewUser(username, passwordstring, emails.. .string) (*User, error) {
     if username!= "" || len(emails) == 0 || password!= "" {
     return errors.New("all fields are required")
     }
     return &User{Username: username, Password: password, Emails: emails}
    }

    Kallax将生成一个具有以下签名的:

    funcNewUser(usernamestring, passwordstring, emails.. .string) (*User, error)

    注意:如果主键不是 auto-incrementable,那么应该为在构造函数中创建的每个模型设置一个 ID。 或者,至少在保存它之前设置它。 插入,更新,删除或者重新加载没有主键集的对象将返回错误。

    如果你没有实现自己的构造函数,kallax将会为你生成一个,只是为了实例化你的对象:

    funcNewT() *T {
     returnnew(T)
    }

    模型事件

    事件可以定义为模型,它们将在模型生命周期的某些时间被调用。

    • BeforeInsert: 将在插入模型之前调用。
    • BeforeUpdate: 将在更新模型之前调用。
    • BeforeSave: 将在更新或者插入模型之前调用。 它总是在 BeforeInsertBeforeUpdate 之前调用。
    • BeforeDelete: 将在删除模型之前调用。
    • AfterInsert: 将在插入模型后调用。 这里事件的存在将导致在事务中运行模型。 如果事件返回错误,它将回滚。
    • AfterUpdate: 将在更新模型后调用。 这里事件的出现将导致模型的更新在事务中运行。 如果事件返回错误,它将回滚。
    • AfterSave: 将在更新或者插入模型后调用。 它总是在 AfterInsertAfterUpdate 之后调用。 这里事件的存在将导致具有模型的操作在事务中运行。 如果事件返回错误,它将回滚。
    • AfterDelete: 将在删除模型后调用。 此事件的存在将导致删除在事务中运行。 如果事件返回错误,它将回滚。

    要实现这些事件,只需实现以下接口。 你可以根据需要实现任意多的功能:

    例如:

    func(u *User) BeforeSave() error {
     if u.Password == "" {
     return errors.New("cannot save user without password")
     }
     if!isCrypted(u.Password) {
     u.Password = crypt(u.Password)
     }
     returnnil}

    Kallax生成的代码

    Kallax为每个模型生成一组代码,并将它的保存到同一包中名为 kallax.go的文件中。

    对于你拥有的每个模型,kallax将为你生成以下内容:

    • 用于使模型与kallax一起工作并满足记录请求拨号接口的内部方法。
    • 名为 {TypeName}Store的存储区:存储是访问数据的方式。 给定类型的存储方式是访问和操作该类型的数据的方法。 可以使用 New{TypeName}Store(*sql.DB) 获取类型存储的实例。
    • 一个名为 {TypeName}Query的查询:查询是你能够编程地构建在存储区中执行的查询的方式。 存储只接受自己类型的查询。 你可以使用 New{TypeName}Query() 创建一个新查询。 查询将包含向查询的每个字段( 称为 FindBy )的查询添加条件的方法。 查询对象不是不可变的,即添加到它的每个条件都会更改查询。 如果要重用一部分查询,可以调用查询的Copy() 方法,这将返回与调用方法相同的查询。
    • 命名为 {TypeName}ResultSet: resultset是遍历并获取存储库返回的resultset中的所有元素的方法。 给定类型的存储将始终返回匹配类型的结果集,该结果集将只返回该类型的记录。
    • 包含所有字段的所有模型的模式。 这样,你就可以访问没有 Having的特定字段的NAME,即使用字符串,也就是一个类型安全的。

    模型架构

    使用架构

    你的kallax.go 中将创建一个全局变量 Schema,它包含一个带有你的每个模型的NAME的字段。 这些是模型的模式。 每个模型模式都包含该模型的所有字段。

    因此,访问用户模型的用户名字段,可以访问它:

    Schema.User.Username

    操纵模型

    对于以下所有部分,我们假设我们有一个用于我们的模型类型的存储 store

    插入模型

    我们只需要使用存储的Insert 方法并将它的传递给模型即可。 如果主键不是 auto-incrementable并且对象没有一组,则插入将失败。

    user:=NewUser("fancy_username", "super_secret_password", "foo@email.me")err:= store.Insert(user)if err!= nil {
     // handle error}

    如果我们的模型有关系,那么它们将被保存,因这里关系之间的关系如这里之等。 插入是递归的。注意: 这些关系将使用 Save 保存,而不是 Insert

    user:=NewUser("foo")
    user.Posts = append(user.Posts, NewPost(user, "new post"))err:= store.Insert(user)if err!= nil {
     // handle error}

    如果模型中存在任何关系,模型和关系将保存在事务中,只有在所有这些关系都正确保存的情况下才成功。

    更新模型

    我们只需要使用存储的Update 方法并将它的传递给模型即可。 如果模型还没有保存或者没有标识,它将返回一个错误。

    user:=FindLast()rowsUpdated, err:= store.Update(user)if err!= nil {
     // handle error}

    默认情况下,更新模型时,更新它的所有字段。 你还可以指定要更新哪些字段以更新它们。

    rowsUpdated, err:= store.Update(user, Schema.User.Username, Schema.User.Password)if err!= nil {
     // handle error}

    如果我们的模型有关系,那么它们将被保存,因这里关系之间的关系如这里之等。 更新是递归的注意: 这些关系将使用 Save 保存,而不是 Update

    user:=FindLastPoster()rowsUpdated, err:= store.Update(user)if err!= nil {
     // handle error}

    如果模型中存在任何关系,模型和关系将保存在事务中,只有在所有这些关系都正确保存的情况下才成功。

    保存模型

    我们只需要使用存储的Save 方法并将它的传递给模型即可保存模型。 如果模型还没有被持久化,则 Save 只是一个 shorthand,如果该模型还没有被持久化,那么它将调用 Insert

    updated, err:= store.Save(user)if err!= nil {
     // handle error}if updated {
     // it was updated, not inserted}

    如果我们的模型有关系,那么它们将被保存,因这里关系之间的关系如这里之等。 DR: 保存是递归的。

    user:=NewUser("foo")
    user.Posts = append(user.Posts, NewPost(user, "new post"))updated, err:= store.Save(user)if err!= nil {
     // handle error}

    如果模型中存在任何关系,模型和关系将保存在事务中,只有在所有这些关系都正确保存的情况下才成功。

    删除模型

    要删除模型,我们只需要使用存储的Delete 方法。 如果模型还没有保存,它将返回一个错误。

    err:= store.Delete(user)if err!= nil {
     // handle error}

    模型的关系是使用 Delete 自动删除的,而不是自动删除的。

    为此,在模型存储中生成了特定的方法。

    对于一对一的关系:

    // remove specific postserr:= store.RemovePosts(user, post1, post2, post3)if err!= nil {
     // handle error}// remove all postserr:= store.RemovePosts(user)

    一对一的关系:

    // remove the thingerr:= store.RemoveThing(user)

    注意,要工作,要删除的内容必须为 也就是说,你需要迅速加载( 或者以后设置)。

    user, err:= store.FindOne(NewUserQuery())checkErr(err)// THIS WON'T WORK! We've not loaded"Things"err:= store.RemoveThings(user)user, err:= store.FindOne(NewUserQuery().WithThings())checkErr(err)// THIS WILL WORK!err:= store.RemoveThings(user)

    查询模型

    简单查询

    要执行查询,你必须执行以下操作:

    • 创建查询
    • 将查询传递到存储的FindFindOneMustFind 或者 MustFindOne
    • 如果使用的方法是 Find 或者 MustFind,则从结果集中收集结果
    // Create the queryq:=NewUserQuery().
     Where(kallax.Like(Schema.User.Username, "joe%")).
     Order(kallax.Asc(Schema.User.Username)).
     Limit(20).
     Offset(2)rs, err:= store.Find(q)if err!= nil {
     // handle error}for rs.Next() {
     user, err:= rs.Get()
     if err!= nil {
     // handle error }
    }

    下一步将在结果到达末尾时自动关闭结果集。 如果你必须提前退出迭代,你可以使用 rs.Close() 手动关闭它。

    你可以使用 FindOne 查询单个行。

    q:=NewUserQuery().
     Where(kallax.Eq(Schema.User.Username, "Joe"))user, err:= store.FindOne(q)

    还可以在没有 Having的情况下,将结果中的所有行手动循环访问结果集 FindAll

    q:=NewUserQuery().
     Where(kallax.Like(Schema.User.Username, "joe%")).
     Order(kallax.Asc(Schema.User.Username)).
     Limit(20).
     Offset(2)users, err:= store.FindAll(q)if err!= nil {
     // handle error}

    默认情况下,检索一行中的所有列。 如果不检索全部,则可以指定要包含/排除的列。 考虑到从数据库检索的部分记录将不可写。 要使它们可以写,你需要 Reload 对象。

    // Select only Username and passwordNewUserQuery().Select(Schema.User.Username, Schema.User.Password)// Select all but passwordNewUserQuery().SelectNot(Schema.User.Password)

    生成的findBys

    对于模型中的每个字段,Kallax生成一个 FindBy,这样做是有意义的。 什么是 FindBy为特定字段向查询中添加条件的shorthand。

    考虑以下模型:

    typePersonstruct {
     kallax.ModelIDint64`pk:"autoincr"`NamestringBirthDate time.TimeAgeint}

    将为该模型生成四个 FindBy:

    func(*PersonQuery) FindByID(...int64) *PersonQueryfunc (*PersonQuery) FindByName(string) *PersonQuery
    func (*PersonQuery) FindByBirthDate(kallax.ScalarCond, time.Time) *PersonQuery
    func (*PersonQuery) FindByAge(kallax.ScalarCond, int) *PersonQuery

    这样,你就可以执行以下操作:

    NewPersonQuery().
     FindByAge(kallax.GtOrEq, 18).
     FindByName("Bobby")

    替代:

    NewPersonQuery().
     Where(kallax.GtOrEq(Schema.Person.Age, 18)).
     Where(kallax.Eq(Schema.Person.Name, "Bobby"))

    为什么生成了三种不同类型的方法?

    • 主键字段以特殊的方式处理,允许通过多个 id,因为由多个id搜索是一个常见操作。
    • 等位( 整数,浮点数,时间。) 搜索的类型允许将运算符传递给它们以确定使用运算符。
    • 只能通过值( 字符串,bools。) 搜索的类型允许传递值。

    计数结果

    你可以将查询传递给 Count,而不是将查询传递给 Find 或者 FindOne,以获取resultset中的行数。

    n, err:= store.Count(q)

    带关系的查询

    默认情况下,除非查询指定了这样的查询,否则不会检索到任何关系。

    对于每个关系,在查询中创建一个方法,以便能够在查询中包含这些关系。

    一对一的关系:

    // Select all posts including the user that posted themq:=NewPostQuery().WithPoster()rs, err:= store.Find(q)

    一对一的关系总是包含在同一个查询中。 所以,如果你有 4个关系,你想要他们全部,只有 1个查询,但一切都将被检索。

    一对一的关系:

    // Select all users including their posts// NOTE: this is a really bad idea, because all posts will be loaded// if the N side of your 1:N relationship is big, consider querying the N store// instead of doing this// A condition can be passed to the `With{Name}` method to filter the results.q:=NewUserQuery().WithPosts(nil)rs, err:= store.Find(q)

    为了避免 1: n 关系的N+1问题,kallax在这个例子中执行批处理。 所以,在一个查询中,从数据库中检索一批用户,然后对这些用户的所有文章进行合并。 重复这里过程,直到结果中没有更多行。 正因为如此,检索 1: n 关系非常快。

    默认批大小为 50,你可以使用 BatchSize 方法更改这里值,所有查询都有。

    注意:如果筛选器传递给 With{Name} 方法,我们不能再保证所有相关对象都存在,因此,检索到的记录将

    重新加载模型

    例如,如果你有一个不可以写的模型,因为只选择了一个字段,可以重新加载它。 加载对象时,对还没有保存的对象所做的所有更改都将丢弃,并用数据库中的值覆盖。

    err:= store.Reload(user)

    重载不会重新加载任何关系,仅仅是模型本身。 在 Reload 之后,模型总是

    查询 JSON

    你可以使用 kallax 包中定义的JSON操作符查询任意 JSON。 还生成了 JSON ( 如果是结构,显然对于贴图来说,它不是)的模式。

    q:=NewPostQuery().Where(kallax.JSONContainsAnyKey(
     Schema.Post.Metadata,
     "foo", "bar",
    ))

    事务

    若要在事务中执行事务,可以使用模型存储的Transaction 方法。 使用提供给回调的存储的所有操作都将在事务中运行。 如果回调返回错误,事务将回滚。

    store.Transaction(func(s *UserStore) error {
     iferr:= s.Insert(user1); err!= nil {
     return err
     }
     return s.Insert(user2)
    })

    如果要存储几个不同类型的模型,事务接收具有模型类型的存储的事实可以能是一个问题。 Kallax有一个名为 StoreFrom的方法,它初始化要具有相同底层存储的类型的存储区。

    store.Transaction(func(s *UserStore) error {
     varpostStorePostStore kallax.StoreFrom(&postStore, s)
     for_, p:=range posts {
     iferr:= postStore.Insert(p); err!= nil {
     return err
     }
     }
     return s.Insert(user)
    })

    在事务中可以使用 Transaction,但它不能打开一个新的,重用现有的。

    警告

    • 不可能使用不是以下类型之一的分片或者类型数组:
      • 基本类型( 比如。[]string[]int64 ) ( runecomplex64complex128 除外)
      • implement实现 sql.Scannerdriver.Valuer的类型是因为kallax实现对所有基本类型的数组支持,同时也为实现 sql.Scannerdriver.Valuer ( 在这种情况下使用反射)的数组提供支持,而不支持任何类型的通用接口,而任何类型都不能支持。 例如考虑下面的类型 type Foo string,使用 []Foo 不受支持。 知道这在扫描行的过程中失败,而不是在代码生成的时候。 以后可能会在代码生成过程中移动到警告或者错误。 支持切片类型的别名,但。 如果我们有 type Strings []string,则支持使用 Strings,因为支持这样的([]string)(&slice),支持 []string
    • time.Timeurl.URL 需要被用作。 也就是说,你不能使用类型 Foo 作为 type Foo time.Timetime.Timeurl.URL 是以特殊方式处理的类型,如果这样做的话,它将与说明一样 type Foo struct {.. . } 而kallax将无法识别正确的类型。
    • 为了在 SaveInsert 或者 Update 上删除它的纳秒,time.Time 字段将被截断,因为PostgreSQL将无法存储它们。 PostgreSQL在内部以UTC形式存储时间。 因此,时间将作为 UTC ( 你可以使用 Local 方法将它们转换回本地时区) 返回。 可以更改将用于从数据库中返回时间的时区,这是PostgreSQL配置文件中的一个。
    • 多维数组或者切片除了在JSON字段内之外,不支持

    迁移

    如果你想自动生成,Kallax可以自动为你的模式生成迁移。 它是与模型生成完全分离的过程,因此它不会强迫你使用kallax生成迁移。

    有时,kallax将无法推断类型或者你将要求某个字段的特定列类型。 你可以使用字段上的sqltype 结构标记来指定。

    typeModelstruct {
     kallax.Model`table:"foo"`StuffSuperCustomType`sqltype:"bytea"`}

    你可以在go和SQL之间看到完整类型映射的完整列表。

    生成迁移

    要生成迁移,你必须运行命令 kallax migrate

    
    kallax migrate --input./users/--input./posts/--out./migrations --name initial_schema
    
    
    
    

    migrate 命令接受以下标志:

    名称重复描述默认值
    --name 或者 -n不是迁移文件的名称( 将转换为 a_snakecase_name )migration
    --input 或者 -i是的这里标志的每一次出现都将指定一个目录,kallax模型可以在其中找到。 如果你的模型分散在多个软件包中,则可以多次指定这里标志必选
    --out 或者 -o不是将在其中生成迁移的目标文件夹./migrations

    每个迁移都由 2个文件组成:

    • TIMESTAMP_NAME.up.sql: 将数据库升级到这里版本的脚本。
    • TIMESTAMP_NAME.down.sql: 将数据库降级到这里版本的脚本。

    此外,还有一个 lock.json 文件,其中最后一次迁移的模式与当前模型不同。

    运行迁移

    若要运行迁移,可以使用 kallax migrate up 或者 kallax migrate downup 将升级你的数据库,down 将降级它。

    以下是 updown 可用的标志:

    名称说明默认值
    --dir 或者 -d存储迁移的目录./migrations
    --dsn数据库连接字符串必选
    --steps 或者 -s要运行的最大迁移数0
    --all向上迁移( 仅适用于 up
    --version 或者 -v运行迁移后所需的数据库的最终版本。 版本是迁移文件开始时的时间戳值0
    • 如果没有向 down 提供 --steps 或者 --version,它们将不执行任何操作。 如果 --all 提供给 up,它将对数据库进行全部升级。
    • 如果 --steps--version 都提供给 up 或者 down,它将只使用 --version,因为它比。

    示例:

    
    kallax migrate up --dir./my-migrations --dsn 'user:pass@localhost:5432/dbname?sslmode=disable' --version 1493991142
    
    
    
    

    用户定义的类型映射

    转到类型SQL类型
    kallax.ULIDuuid
    kallax.UUIDuuid
    kallax.NumericID主键上的serial,外键上的bigint
    主密钥上的int64serial
    外部键和其他字段上的int64bigint
    stringtext
    runechar(1)
    uint8smallint
    int8smallint
    bytesmallint
    uint16integer
    int16smallint
    uint32bigint
    int32integer
    uintnumeric(20)
    intbigint
    int64bigint
    uint64numeric(20)
    float32real
    float64double
    boolboolean
    url.URLtext
    time.Timetimestamptz
    time.Durationbigint
    []bytebytea
    []TT'[] * where T'T 类型的SQL类型,除了 T = byte
    map[K]Vjsonb
    structjsonb
    *structjsonb

    必须显式指定任何其他类型。

    所有不是指针的类型都将是 NOT NULL

    自定义运算符

    你可以使用 NewOperatorNewMultiOperator 函数创建带有kallax的定制运算符。

    NewOperator 创建具有指定格式的运算符。 返回给定模式字段且值返回条件的函数。

    格式是一个字符串,它的中 :col: 将用模式字段替换,:arg: 将被替换为值。

    varGt = kallax.NewOperator(":col:> :arg:")// can be used like this:query.Where(Gt(SomeSchemaField, 9000))

    NewMultiOperator 与前一个完全相同,但是它接受一个变量数。

    varIn = kallax.NewMultiOperator(":col: IN :arg:")// can be used like this:query.Where(In(SomeSchemaField, 4, 5, 6))

    这个函数已经用括号来处理 :arg:

    进一步定制

    如果需要进一步自定义,可以创建自己的自定义运算符。

    你需要这些东西:

    • 使用字段和值创建正确的SQL表达式的条件构造函数( 操作员本身)。
    • 生成SQL表达式的ToSqler

    假设我们想要一个 GREATER 而不是只处理整数的运算符。

    funcGtInt(colkallax.SchemaField, nint) kallax.Condition {
     returnfunc(schema kallax.Schema) kallax.ToSqler {
     // it is VERY important that all SchemaFields// are qualified using the schemareturn &gtInt{col.QualifiedName(schema), n}
     }
    }type gtInt struct {
     col string val int}func(g *gtInt) ToSql() (sqlstring, params []interface{}, err error) {
     return fmt.Sprintf("%s>?", g.col), []interface{}{g.val}, nil}// can be used like this:query.Where(GtInt(SomeSchemaField, 9000))

    对于大多数操作符,NewOperatorNewMultiOperator 已经足够了,所以这些函数的使用比完全定制的方法更可取。 仅在没有其他方法来生成自定义运算符时使用这里方法。

    调试SQL查询

    可以调试使用kallax执行的SQL查询。 为此,只需调用存储的Debug 方法即可。 这将返回一个已经启用调试的新存储。

    store.Debug().Find(myQuery)

    这将使用 log.Printf 记录到 stdout kallax: Query: THE QUERY SQL STATEMENT, args: [arg1 arg2]

    你可以使用自定义记录器( 使用 DebugWith 方法的任何具有类型为 func(string,.. .interface{})的函数)。

    funcmyLogger(messagestring, args.. .interface{}) {
     myloglib.Debugf("%s, args: %v", message, args)
    }
    store.DebugWith(myLogger).Find(myQuery)

    基准测试

    下面是针对 GORM插件。SQLBoiler插件和 database/sql的一些。 将来我们可能为一些更复杂的案例和其他可用的orm添加基准。

    
    BenchmarkKallaxUpdate-4 300 4179176 ns/op 656 B/op 25 allocs/op
    
    
    BenchmarkKallaxUpdateWithRelationships-4 200 5662703 ns/op 6642 B/op 175 allocs/op
    
    
    
    BenchmarkKallaxInsertWithRelationships-4 200 5648433 ns/op 10221 B/op 218 allocs/op
    
    
    BenchmarkSQLBoilerInsertWithRelationships-4 XXX XXXXXXX ns/op XXXX B/op XXX allocs/op
    
    
    BenchmarkRawSQLInsertWithRelationships-4 200 5427503 ns/op 4516 B/op 127 allocs/op
    
    
    BenchmarkGORMInsertWithRelationships-4 200 6196277 ns/op 35080 B/op 610 allocs/op
    
    
    
    BenchmarkKallaxInsert-4 300 3916239 ns/op 1218 B/op 29 allocs/op
    
    
    BenchmarkSQLBoilerInsert-4 300 4356432 ns/op 1151 B/op 35 allocs/op
    
    
    BenchmarkRawSQLInsert-4 300 4065924 ns/op 1052 B/op 27 allocs/op
    
    
    BenchmarkGORMInsert-4 300 4398799 ns/op 4678 B/op 107 allocs/op
    
    
    
    BenchmarkKallaxQueryRelationships/query-4 500 2900095 ns/op 269157 B/op 6200 allocs/op
    
    
    BenchmarkSQLBoilerQueryRelationships/query-4 1000 2082963 ns/op 125587 B/op 5098 allocs/op
    
    
    BenchmarkRawSQLQueryRelationships/query-4 20 59400759 ns/op 294176 B/op 11424 allocs/op
    
    
    BenchmarkGORMQueryRelationships/query-4 300 4758555 ns/op 1069118 B/op 20833 allocs/op
    
    
    
    BenchmarkKallaxQuery/query-4 3000 546742 ns/op 50673 B/op 1590 allocs/op
    
    
    BenchmarkSQLBoilerQuery/query-4 2000 677839 ns/op 54082 B/op 2436 allocs/op
    
    
    BenchmarkRawSQLQuery/query-4 3000 464498 ns/op 37480 B/op 1525 allocs/op
    
    
    BenchmarkGORMQuery/query-4 1000 1388406 ns/op 427401 B/op 7068 allocs/op
    
    
    
    PASS
    
    
    ok gopkg.in/src-d/go-kallax.v1/benchmarks 44.899s
    
    
    
    

    正如我们在基准上看到的,性能损失与原始 database/sql 不相比,GORMs性能损失很大,内存消耗更高。 另一方面,SQLBoiler的内存占用比 kallax ( 在某些情况下) 更低,但性能丢失( 虽然不是很重要) 较大,除了关系( 那是一个回归,以后应该有所改进。)的查询。

    基准代码可以在基准插件文件夹中找到。

    备注:

    • 从2018-05-28到Benchmark的基准测试已经过时( PR #269),的结果将在a 运行,并将很快更新)。
    • 运行的基准基准是在 2015位Pro上运行的,它的中包括of和 8GB的RAM和 128GB 硬盘硬盘运行。
    • 使用 relationships +n解决方案实现的database/sql的基准测试实现了非常幼稚的1解决方案。 这就是为什么结果是错误的。

    确认

    • 极大感谢 masterminds/squirrel 库,这是在这个ORM中使用的出色查询生成器。
    • /Golang,PostgreSQL的驱动程序,它支持内置go类型的支持。
    • mattes/迁移,一个用于管理数据库迁移的Golang库。

    报告 Bug

    Kallax是一个代码生成工具,所以它显然没有用所有可能的类型和案例来测试。 如果发现代码生成的情况,请报告提供最小代码片段的问题,以便重现问题。

    建议功能

    Kallax是一个非常适合我们的for,因这里变更使事情不适合我们或者通过配置添加复杂性。 我们不打算去实现这个特性,只是记住这不是因为它不是好的想法,而是因为它不是好的想法,而是因为它不适合我们的方向,或者我们想让它向前移动。

    运行测试

    出于明显原因,需要PostgreSQL实例来运行这个包的测试。

    默认情况下,它假定一个实例存在于 0.0.0.0:5432 上,用户。密码和数据库 NAME 等于 testing

    如果不是这种情况,你可以设置以下 环境变量:

    • DBNAME: 数据库的NAME
    • DBUSER: 数据库用户
    • DBPASS: 数据库用户密码

    许可证

    麻省理工学院,请参见许可协议。


    LAN  LANG  pos  type  POST  类型  
    相关文章