d28f005552
Fix limit for databases other than sqlite go mod tidy && go mod vendor Remove unneeded break statements Make everything work with the new xorm version Fix xorm logging Fix lint Fix redis init Fix using id field Fix database init for testing Change default database log level Add xorm logger Use const for postgres go mod tidy Merge branch 'master' into update/xorm # Conflicts: # go.mod # go.sum # vendor/modules.txt go mod vendor Fix loading fixtures for postgres Go mod vendor1 Update xorm to version 1 Co-authored-by: kolaente <k@knt.li> Reviewed-on: https://kolaente.dev/vikunja/api/pulls/323
1277 lines
36 KiB
Go
1277 lines
36 KiB
Go
// Copyright 2015 The Xorm Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package xorm
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"reflect"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"xorm.io/xorm/caches"
|
|
"xorm.io/xorm/core"
|
|
"xorm.io/xorm/dialects"
|
|
"xorm.io/xorm/internal/utils"
|
|
"xorm.io/xorm/log"
|
|
"xorm.io/xorm/names"
|
|
"xorm.io/xorm/schemas"
|
|
"xorm.io/xorm/tags"
|
|
)
|
|
|
|
// Engine is the major struct of xorm, it means a database manager.
|
|
// Commonly, an application only need one engine
|
|
type Engine struct {
|
|
cacherMgr *caches.Manager
|
|
defaultContext context.Context
|
|
dialect dialects.Dialect
|
|
engineGroup *EngineGroup
|
|
logger log.ContextLogger
|
|
tagParser *tags.Parser
|
|
db *core.DB
|
|
|
|
driverName string
|
|
dataSourceName string
|
|
|
|
TZLocation *time.Location // The timezone of the application
|
|
DatabaseTZ *time.Location // The timezone of the database
|
|
}
|
|
|
|
func (engine *Engine) SetCacher(tableName string, cacher caches.Cacher) {
|
|
engine.cacherMgr.SetCacher(tableName, cacher)
|
|
}
|
|
|
|
func (engine *Engine) GetCacher(tableName string) caches.Cacher {
|
|
return engine.cacherMgr.GetCacher(tableName)
|
|
}
|
|
|
|
func (engine *Engine) SetQuotePolicy(quotePolicy dialects.QuotePolicy) {
|
|
engine.dialect.SetQuotePolicy(quotePolicy)
|
|
}
|
|
|
|
// BufferSize sets buffer size for iterate
|
|
func (engine *Engine) BufferSize(size int) *Session {
|
|
session := engine.NewSession()
|
|
session.isAutoClose = true
|
|
return session.BufferSize(size)
|
|
}
|
|
|
|
// ShowSQL show SQL statement or not on logger if log level is great than INFO
|
|
func (engine *Engine) ShowSQL(show ...bool) {
|
|
engine.logger.ShowSQL(show...)
|
|
engine.DB().Logger = engine.logger
|
|
}
|
|
|
|
// Logger return the logger interface
|
|
func (engine *Engine) Logger() log.ContextLogger {
|
|
return engine.logger
|
|
}
|
|
|
|
// SetLogger set the new logger
|
|
func (engine *Engine) SetLogger(logger interface{}) {
|
|
var realLogger log.ContextLogger
|
|
switch t := logger.(type) {
|
|
case log.Logger:
|
|
realLogger = log.NewLoggerAdapter(t)
|
|
case log.ContextLogger:
|
|
realLogger = t
|
|
}
|
|
engine.logger = realLogger
|
|
engine.DB().Logger = realLogger
|
|
}
|
|
|
|
// SetLogLevel sets the logger level
|
|
func (engine *Engine) SetLogLevel(level log.LogLevel) {
|
|
engine.logger.SetLevel(level)
|
|
}
|
|
|
|
// SetDisableGlobalCache disable global cache or not
|
|
func (engine *Engine) SetDisableGlobalCache(disable bool) {
|
|
engine.cacherMgr.SetDisableGlobalCache(disable)
|
|
}
|
|
|
|
// DriverName return the current sql driver's name
|
|
func (engine *Engine) DriverName() string {
|
|
return engine.driverName
|
|
}
|
|
|
|
// DataSourceName return the current connection string
|
|
func (engine *Engine) DataSourceName() string {
|
|
return engine.dataSourceName
|
|
}
|
|
|
|
// SetMapper set the name mapping rules
|
|
func (engine *Engine) SetMapper(mapper names.Mapper) {
|
|
engine.SetTableMapper(mapper)
|
|
engine.SetColumnMapper(mapper)
|
|
}
|
|
|
|
// SetTableMapper set the table name mapping rule
|
|
func (engine *Engine) SetTableMapper(mapper names.Mapper) {
|
|
engine.tagParser.SetTableMapper(mapper)
|
|
}
|
|
|
|
// SetColumnMapper set the column name mapping rule
|
|
func (engine *Engine) SetColumnMapper(mapper names.Mapper) {
|
|
engine.tagParser.SetColumnMapper(mapper)
|
|
}
|
|
|
|
// Quote Use QuoteStr quote the string sql
|
|
func (engine *Engine) Quote(value string) string {
|
|
value = strings.TrimSpace(value)
|
|
if len(value) == 0 {
|
|
return value
|
|
}
|
|
|
|
buf := strings.Builder{}
|
|
engine.QuoteTo(&buf, value)
|
|
|
|
return buf.String()
|
|
}
|
|
|
|
// QuoteTo quotes string and writes into the buffer
|
|
func (engine *Engine) QuoteTo(buf *strings.Builder, value string) {
|
|
if buf == nil {
|
|
return
|
|
}
|
|
|
|
value = strings.TrimSpace(value)
|
|
if value == "" {
|
|
return
|
|
}
|
|
engine.dialect.Quoter().QuoteTo(buf, value)
|
|
}
|
|
|
|
// SQLType A simple wrapper to dialect's core.SqlType method
|
|
func (engine *Engine) SQLType(c *schemas.Column) string {
|
|
return engine.dialect.SQLType(c)
|
|
}
|
|
|
|
// AutoIncrStr Database's autoincrement statement
|
|
func (engine *Engine) AutoIncrStr() string {
|
|
return engine.dialect.AutoIncrStr()
|
|
}
|
|
|
|
// SetConnMaxLifetime sets the maximum amount of time a connection may be reused.
|
|
func (engine *Engine) SetConnMaxLifetime(d time.Duration) {
|
|
engine.DB().SetConnMaxLifetime(d)
|
|
}
|
|
|
|
// SetMaxOpenConns is only available for go 1.2+
|
|
func (engine *Engine) SetMaxOpenConns(conns int) {
|
|
engine.DB().SetMaxOpenConns(conns)
|
|
}
|
|
|
|
// SetMaxIdleConns set the max idle connections on pool, default is 2
|
|
func (engine *Engine) SetMaxIdleConns(conns int) {
|
|
engine.DB().SetMaxIdleConns(conns)
|
|
}
|
|
|
|
// SetDefaultCacher set the default cacher. Xorm's default not enable cacher.
|
|
func (engine *Engine) SetDefaultCacher(cacher caches.Cacher) {
|
|
engine.cacherMgr.SetDefaultCacher(cacher)
|
|
}
|
|
|
|
// GetDefaultCacher returns the default cacher
|
|
func (engine *Engine) GetDefaultCacher() caches.Cacher {
|
|
return engine.cacherMgr.GetDefaultCacher()
|
|
}
|
|
|
|
// NoCache If you has set default cacher, and you want temporilly stop use cache,
|
|
// you can use NoCache()
|
|
func (engine *Engine) NoCache() *Session {
|
|
session := engine.NewSession()
|
|
session.isAutoClose = true
|
|
return session.NoCache()
|
|
}
|
|
|
|
// NoCascade If you do not want to auto cascade load object
|
|
func (engine *Engine) NoCascade() *Session {
|
|
session := engine.NewSession()
|
|
session.isAutoClose = true
|
|
return session.NoCascade()
|
|
}
|
|
|
|
// MapCacher Set a table use a special cacher
|
|
func (engine *Engine) MapCacher(bean interface{}, cacher caches.Cacher) error {
|
|
engine.SetCacher(dialects.FullTableName(engine.dialect, engine.GetTableMapper(), bean, true), cacher)
|
|
return nil
|
|
}
|
|
|
|
// NewDB provides an interface to operate database directly
|
|
func (engine *Engine) NewDB() (*core.DB, error) {
|
|
return core.Open(engine.driverName, engine.dataSourceName)
|
|
}
|
|
|
|
// DB return the wrapper of sql.DB
|
|
func (engine *Engine) DB() *core.DB {
|
|
return engine.db
|
|
}
|
|
|
|
// Dialect return database dialect
|
|
func (engine *Engine) Dialect() dialects.Dialect {
|
|
return engine.dialect
|
|
}
|
|
|
|
// NewSession New a session
|
|
func (engine *Engine) NewSession() *Session {
|
|
session := &Session{engine: engine}
|
|
session.Init()
|
|
return session
|
|
}
|
|
|
|
// Close the engine
|
|
func (engine *Engine) Close() error {
|
|
return engine.DB().Close()
|
|
}
|
|
|
|
// Ping tests if database is alive
|
|
func (engine *Engine) Ping() error {
|
|
session := engine.NewSession()
|
|
defer session.Close()
|
|
return session.Ping()
|
|
}
|
|
|
|
// SQL method let's you manually write raw SQL and operate
|
|
// For example:
|
|
//
|
|
// engine.SQL("select * from user").Find(&users)
|
|
//
|
|
// This code will execute "select * from user" and set the records to users
|
|
func (engine *Engine) SQL(query interface{}, args ...interface{}) *Session {
|
|
session := engine.NewSession()
|
|
session.isAutoClose = true
|
|
return session.SQL(query, args...)
|
|
}
|
|
|
|
// NoAutoTime Default if your struct has "created" or "updated" filed tag, the fields
|
|
// will automatically be filled with current time when Insert or Update
|
|
// invoked. Call NoAutoTime if you dont' want to fill automatically.
|
|
func (engine *Engine) NoAutoTime() *Session {
|
|
session := engine.NewSession()
|
|
session.isAutoClose = true
|
|
return session.NoAutoTime()
|
|
}
|
|
|
|
// NoAutoCondition disable auto generate Where condition from bean or not
|
|
func (engine *Engine) NoAutoCondition(no ...bool) *Session {
|
|
session := engine.NewSession()
|
|
session.isAutoClose = true
|
|
return session.NoAutoCondition(no...)
|
|
}
|
|
|
|
func (engine *Engine) loadTableInfo(table *schemas.Table) error {
|
|
colSeq, cols, err := engine.dialect.GetColumns(engine.db, engine.defaultContext, table.Name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, name := range colSeq {
|
|
table.AddColumn(cols[name])
|
|
}
|
|
indexes, err := engine.dialect.GetIndexes(engine.db, engine.defaultContext, table.Name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
table.Indexes = indexes
|
|
|
|
var seq int
|
|
for _, index := range indexes {
|
|
for _, name := range index.Cols {
|
|
parts := strings.Split(name, " ")
|
|
if len(parts) > 1 {
|
|
if parts[1] == "DESC" {
|
|
seq = 1
|
|
}
|
|
}
|
|
if col := table.GetColumn(parts[0]); col != nil {
|
|
col.Indexes[index.Name] = index.Type
|
|
} else {
|
|
return fmt.Errorf("Unknown col %s seq %d, in index %v of table %v, columns %v", name, seq, index.Name, table.Name, table.ColumnsSeq())
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// DBMetas Retrieve all tables, columns, indexes' informations from database.
|
|
func (engine *Engine) DBMetas() ([]*schemas.Table, error) {
|
|
tables, err := engine.dialect.GetTables(engine.db, engine.defaultContext)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for _, table := range tables {
|
|
if err = engine.loadTableInfo(table); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return tables, nil
|
|
}
|
|
|
|
// DumpAllToFile dump database all table structs and data to a file
|
|
func (engine *Engine) DumpAllToFile(fp string, tp ...schemas.DBType) error {
|
|
f, err := os.Create(fp)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer f.Close()
|
|
return engine.DumpAll(f, tp...)
|
|
}
|
|
|
|
// DumpAll dump database all table structs and data to w
|
|
func (engine *Engine) DumpAll(w io.Writer, tp ...schemas.DBType) error {
|
|
tables, err := engine.DBMetas()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return engine.DumpTables(tables, w, tp...)
|
|
}
|
|
|
|
// DumpTablesToFile dump specified tables to SQL file.
|
|
func (engine *Engine) DumpTablesToFile(tables []*schemas.Table, fp string, tp ...schemas.DBType) error {
|
|
f, err := os.Create(fp)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer f.Close()
|
|
return engine.DumpTables(tables, f, tp...)
|
|
}
|
|
|
|
// DumpTables dump specify tables to io.Writer
|
|
func (engine *Engine) DumpTables(tables []*schemas.Table, w io.Writer, tp ...schemas.DBType) error {
|
|
return engine.dumpTables(tables, w, tp...)
|
|
}
|
|
|
|
// dumpTables dump database all table structs and data to w with specify db type
|
|
func (engine *Engine) dumpTables(tables []*schemas.Table, w io.Writer, tp ...schemas.DBType) error {
|
|
var dstDialect dialects.Dialect
|
|
if len(tp) == 0 {
|
|
dstDialect = engine.dialect
|
|
} else {
|
|
dstDialect = dialects.QueryDialect(tp[0])
|
|
if dstDialect == nil {
|
|
return errors.New("Unsupported database type")
|
|
}
|
|
|
|
uri := engine.dialect.URI()
|
|
destURI := *uri
|
|
dstDialect.Init(&destURI)
|
|
}
|
|
|
|
_, err := io.WriteString(w, fmt.Sprintf("/*Generated by xorm %s, from %s to %s*/\n\n",
|
|
time.Now().In(engine.TZLocation).Format("2006-01-02 15:04:05"), engine.dialect.URI().DBType, dstDialect.URI().DBType))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for i, table := range tables {
|
|
tableName := table.Name
|
|
if dstDialect.URI().Schema != "" {
|
|
tableName = fmt.Sprintf("%s.%s", dstDialect.URI().Schema, table.Name)
|
|
}
|
|
originalTableName := table.Name
|
|
if engine.dialect.URI().Schema != "" {
|
|
originalTableName = fmt.Sprintf("%s.%s", engine.dialect.URI().Schema, table.Name)
|
|
}
|
|
if i > 0 {
|
|
_, err = io.WriteString(w, "\n")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
sqls, _ := dstDialect.CreateTableSQL(table, tableName)
|
|
for _, s := range sqls {
|
|
_, err = io.WriteString(w, s+";\n")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if len(table.PKColumns()) > 0 && dstDialect.URI().DBType == schemas.MSSQL {
|
|
fmt.Fprintf(w, "SET IDENTITY_INSERT [%s] ON;\n", table.Name)
|
|
}
|
|
|
|
for _, index := range table.Indexes {
|
|
_, err = io.WriteString(w, dstDialect.CreateIndexSQL(table.Name, index)+";\n")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
cols := table.ColumnsSeq()
|
|
colNames := engine.dialect.Quoter().Join(cols, ", ")
|
|
destColNames := dstDialect.Quoter().Join(cols, ", ")
|
|
|
|
rows, err := engine.DB().QueryContext(engine.defaultContext, "SELECT "+colNames+" FROM "+engine.Quote(originalTableName))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer rows.Close()
|
|
|
|
for rows.Next() {
|
|
dest := make([]interface{}, len(cols))
|
|
err = rows.ScanSlice(&dest)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = io.WriteString(w, "INSERT INTO "+dstDialect.Quoter().Quote(tableName)+" ("+destColNames+") VALUES (")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var temp string
|
|
for i, d := range dest {
|
|
col := table.GetColumn(cols[i])
|
|
if col == nil {
|
|
return errors.New("unknow column error")
|
|
}
|
|
|
|
if d == nil {
|
|
temp += ", NULL"
|
|
} else if col.SQLType.IsText() || col.SQLType.IsTime() {
|
|
var v = fmt.Sprintf("%s", d)
|
|
if strings.HasSuffix(v, " +0000 UTC") {
|
|
temp += fmt.Sprintf(", '%s'", v[0:len(v)-len(" +0000 UTC")])
|
|
} else {
|
|
temp += ", '" + strings.Replace(v, "'", "''", -1) + "'"
|
|
}
|
|
} else if col.SQLType.IsBlob() {
|
|
if reflect.TypeOf(d).Kind() == reflect.Slice {
|
|
temp += fmt.Sprintf(", %s", dstDialect.FormatBytes(d.([]byte)))
|
|
} else if reflect.TypeOf(d).Kind() == reflect.String {
|
|
temp += fmt.Sprintf(", '%s'", d.(string))
|
|
}
|
|
} else if col.SQLType.IsNumeric() {
|
|
switch reflect.TypeOf(d).Kind() {
|
|
case reflect.Slice:
|
|
if col.SQLType.Name == schemas.Bool {
|
|
temp += fmt.Sprintf(", %v", strconv.FormatBool(d.([]byte)[0] != byte('0')))
|
|
} else {
|
|
temp += fmt.Sprintf(", %s", string(d.([]byte)))
|
|
}
|
|
case reflect.Int16, reflect.Int8, reflect.Int32, reflect.Int64, reflect.Int:
|
|
if col.SQLType.Name == schemas.Bool {
|
|
temp += fmt.Sprintf(", %v", strconv.FormatBool(reflect.ValueOf(d).Int() > 0))
|
|
} else {
|
|
temp += fmt.Sprintf(", %v", d)
|
|
}
|
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
|
if col.SQLType.Name == schemas.Bool {
|
|
temp += fmt.Sprintf(", %v", strconv.FormatBool(reflect.ValueOf(d).Uint() > 0))
|
|
} else {
|
|
temp += fmt.Sprintf(", %v", d)
|
|
}
|
|
default:
|
|
temp += fmt.Sprintf(", %v", d)
|
|
}
|
|
} else {
|
|
s := fmt.Sprintf("%v", d)
|
|
if strings.Contains(s, ":") || strings.Contains(s, "-") {
|
|
if strings.HasSuffix(s, " +0000 UTC") {
|
|
temp += fmt.Sprintf(", '%s'", s[0:len(s)-len(" +0000 UTC")])
|
|
} else {
|
|
temp += fmt.Sprintf(", '%s'", s)
|
|
}
|
|
} else {
|
|
temp += fmt.Sprintf(", %s", s)
|
|
}
|
|
}
|
|
}
|
|
_, err = io.WriteString(w, temp[2:]+");\n")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// FIXME: Hack for postgres
|
|
if dstDialect.URI().DBType == schemas.POSTGRES && table.AutoIncrColumn() != nil {
|
|
_, err = io.WriteString(w, "SELECT setval('"+tableName+"_id_seq', COALESCE((SELECT MAX("+table.AutoIncrColumn().Name+") + 1 FROM "+dstDialect.Quoter().Quote(tableName)+"), 1), false);\n")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Cascade use cascade or not
|
|
func (engine *Engine) Cascade(trueOrFalse ...bool) *Session {
|
|
session := engine.NewSession()
|
|
session.isAutoClose = true
|
|
return session.Cascade(trueOrFalse...)
|
|
}
|
|
|
|
// Where method provide a condition query
|
|
func (engine *Engine) Where(query interface{}, args ...interface{}) *Session {
|
|
session := engine.NewSession()
|
|
session.isAutoClose = true
|
|
return session.Where(query, args...)
|
|
}
|
|
|
|
// ID method provoide a condition as (id) = ?
|
|
func (engine *Engine) ID(id interface{}) *Session {
|
|
session := engine.NewSession()
|
|
session.isAutoClose = true
|
|
return session.ID(id)
|
|
}
|
|
|
|
// Before apply before Processor, affected bean is passed to closure arg
|
|
func (engine *Engine) Before(closures func(interface{})) *Session {
|
|
session := engine.NewSession()
|
|
session.isAutoClose = true
|
|
return session.Before(closures)
|
|
}
|
|
|
|
// After apply after insert Processor, affected bean is passed to closure arg
|
|
func (engine *Engine) After(closures func(interface{})) *Session {
|
|
session := engine.NewSession()
|
|
session.isAutoClose = true
|
|
return session.After(closures)
|
|
}
|
|
|
|
// Charset set charset when create table, only support mysql now
|
|
func (engine *Engine) Charset(charset string) *Session {
|
|
session := engine.NewSession()
|
|
session.isAutoClose = true
|
|
return session.Charset(charset)
|
|
}
|
|
|
|
// StoreEngine set store engine when create table, only support mysql now
|
|
func (engine *Engine) StoreEngine(storeEngine string) *Session {
|
|
session := engine.NewSession()
|
|
session.isAutoClose = true
|
|
return session.StoreEngine(storeEngine)
|
|
}
|
|
|
|
// Distinct use for distinct columns. Caution: when you are using cache,
|
|
// distinct will not be cached because cache system need id,
|
|
// but distinct will not provide id
|
|
func (engine *Engine) Distinct(columns ...string) *Session {
|
|
session := engine.NewSession()
|
|
session.isAutoClose = true
|
|
return session.Distinct(columns...)
|
|
}
|
|
|
|
// Select customerize your select columns or contents
|
|
func (engine *Engine) Select(str string) *Session {
|
|
session := engine.NewSession()
|
|
session.isAutoClose = true
|
|
return session.Select(str)
|
|
}
|
|
|
|
// Cols only use the parameters as select or update columns
|
|
func (engine *Engine) Cols(columns ...string) *Session {
|
|
session := engine.NewSession()
|
|
session.isAutoClose = true
|
|
return session.Cols(columns...)
|
|
}
|
|
|
|
// AllCols indicates that all columns should be use
|
|
func (engine *Engine) AllCols() *Session {
|
|
session := engine.NewSession()
|
|
session.isAutoClose = true
|
|
return session.AllCols()
|
|
}
|
|
|
|
// MustCols specify some columns must use even if they are empty
|
|
func (engine *Engine) MustCols(columns ...string) *Session {
|
|
session := engine.NewSession()
|
|
session.isAutoClose = true
|
|
return session.MustCols(columns...)
|
|
}
|
|
|
|
// UseBool xorm automatically retrieve condition according struct, but
|
|
// if struct has bool field, it will ignore them. So use UseBool
|
|
// to tell system to do not ignore them.
|
|
// If no parameters, it will use all the bool field of struct, or
|
|
// it will use parameters's columns
|
|
func (engine *Engine) UseBool(columns ...string) *Session {
|
|
session := engine.NewSession()
|
|
session.isAutoClose = true
|
|
return session.UseBool(columns...)
|
|
}
|
|
|
|
// Omit only not use the parameters as select or update columns
|
|
func (engine *Engine) Omit(columns ...string) *Session {
|
|
session := engine.NewSession()
|
|
session.isAutoClose = true
|
|
return session.Omit(columns...)
|
|
}
|
|
|
|
// Nullable set null when column is zero-value and nullable for update
|
|
func (engine *Engine) Nullable(columns ...string) *Session {
|
|
session := engine.NewSession()
|
|
session.isAutoClose = true
|
|
return session.Nullable(columns...)
|
|
}
|
|
|
|
// In will generate "column IN (?, ?)"
|
|
func (engine *Engine) In(column string, args ...interface{}) *Session {
|
|
session := engine.NewSession()
|
|
session.isAutoClose = true
|
|
return session.In(column, args...)
|
|
}
|
|
|
|
// NotIn will generate "column NOT IN (?, ?)"
|
|
func (engine *Engine) NotIn(column string, args ...interface{}) *Session {
|
|
session := engine.NewSession()
|
|
session.isAutoClose = true
|
|
return session.NotIn(column, args...)
|
|
}
|
|
|
|
// Incr provides a update string like "column = column + ?"
|
|
func (engine *Engine) Incr(column string, arg ...interface{}) *Session {
|
|
session := engine.NewSession()
|
|
session.isAutoClose = true
|
|
return session.Incr(column, arg...)
|
|
}
|
|
|
|
// Decr provides a update string like "column = column - ?"
|
|
func (engine *Engine) Decr(column string, arg ...interface{}) *Session {
|
|
session := engine.NewSession()
|
|
session.isAutoClose = true
|
|
return session.Decr(column, arg...)
|
|
}
|
|
|
|
// SetExpr provides a update string like "column = {expression}"
|
|
func (engine *Engine) SetExpr(column string, expression interface{}) *Session {
|
|
session := engine.NewSession()
|
|
session.isAutoClose = true
|
|
return session.SetExpr(column, expression)
|
|
}
|
|
|
|
// Table temporarily change the Get, Find, Update's table
|
|
func (engine *Engine) Table(tableNameOrBean interface{}) *Session {
|
|
session := engine.NewSession()
|
|
session.isAutoClose = true
|
|
return session.Table(tableNameOrBean)
|
|
}
|
|
|
|
// Alias set the table alias
|
|
func (engine *Engine) Alias(alias string) *Session {
|
|
session := engine.NewSession()
|
|
session.isAutoClose = true
|
|
return session.Alias(alias)
|
|
}
|
|
|
|
// Limit will generate "LIMIT start, limit"
|
|
func (engine *Engine) Limit(limit int, start ...int) *Session {
|
|
session := engine.NewSession()
|
|
session.isAutoClose = true
|
|
return session.Limit(limit, start...)
|
|
}
|
|
|
|
// Desc will generate "ORDER BY column1 DESC, column2 DESC"
|
|
func (engine *Engine) Desc(colNames ...string) *Session {
|
|
session := engine.NewSession()
|
|
session.isAutoClose = true
|
|
return session.Desc(colNames...)
|
|
}
|
|
|
|
// Asc will generate "ORDER BY column1,column2 Asc"
|
|
// This method can chainable use.
|
|
//
|
|
// engine.Desc("name").Asc("age").Find(&users)
|
|
// // SELECT * FROM user ORDER BY name DESC, age ASC
|
|
//
|
|
func (engine *Engine) Asc(colNames ...string) *Session {
|
|
session := engine.NewSession()
|
|
session.isAutoClose = true
|
|
return session.Asc(colNames...)
|
|
}
|
|
|
|
// OrderBy will generate "ORDER BY order"
|
|
func (engine *Engine) OrderBy(order string) *Session {
|
|
session := engine.NewSession()
|
|
session.isAutoClose = true
|
|
return session.OrderBy(order)
|
|
}
|
|
|
|
// Prepare enables prepare statement
|
|
func (engine *Engine) Prepare() *Session {
|
|
session := engine.NewSession()
|
|
session.isAutoClose = true
|
|
return session.Prepare()
|
|
}
|
|
|
|
// Join the join_operator should be one of INNER, LEFT OUTER, CROSS etc - this will be prepended to JOIN
|
|
func (engine *Engine) Join(joinOperator string, tablename interface{}, condition string, args ...interface{}) *Session {
|
|
session := engine.NewSession()
|
|
session.isAutoClose = true
|
|
return session.Join(joinOperator, tablename, condition, args...)
|
|
}
|
|
|
|
// GroupBy generate group by statement
|
|
func (engine *Engine) GroupBy(keys string) *Session {
|
|
session := engine.NewSession()
|
|
session.isAutoClose = true
|
|
return session.GroupBy(keys)
|
|
}
|
|
|
|
// Having generate having statement
|
|
func (engine *Engine) Having(conditions string) *Session {
|
|
session := engine.NewSession()
|
|
session.isAutoClose = true
|
|
return session.Having(conditions)
|
|
}
|
|
|
|
// Table table struct
|
|
type Table struct {
|
|
*schemas.Table
|
|
Name string
|
|
}
|
|
|
|
// IsValid if table is valid
|
|
func (t *Table) IsValid() bool {
|
|
return t.Table != nil && len(t.Name) > 0
|
|
}
|
|
|
|
// TableInfo get table info according to bean's content
|
|
func (engine *Engine) TableInfo(bean interface{}) (*schemas.Table, error) {
|
|
v := utils.ReflectValue(bean)
|
|
return engine.tagParser.ParseWithCache(v)
|
|
}
|
|
|
|
// IsTableEmpty if a table has any reocrd
|
|
func (engine *Engine) IsTableEmpty(bean interface{}) (bool, error) {
|
|
session := engine.NewSession()
|
|
defer session.Close()
|
|
return session.IsTableEmpty(bean)
|
|
}
|
|
|
|
// IsTableExist if a table is exist
|
|
func (engine *Engine) IsTableExist(beanOrTableName interface{}) (bool, error) {
|
|
session := engine.NewSession()
|
|
defer session.Close()
|
|
return session.IsTableExist(beanOrTableName)
|
|
}
|
|
|
|
// IDOf get id from one struct
|
|
func (engine *Engine) IDOf(bean interface{}) (schemas.PK, error) {
|
|
return engine.IDOfV(reflect.ValueOf(bean))
|
|
}
|
|
|
|
// TableName returns table name with schema prefix if has
|
|
func (engine *Engine) TableName(bean interface{}, includeSchema ...bool) string {
|
|
return dialects.FullTableName(engine.dialect, engine.GetTableMapper(), bean, includeSchema...)
|
|
}
|
|
|
|
// IDOfV get id from one value of struct
|
|
func (engine *Engine) IDOfV(rv reflect.Value) (schemas.PK, error) {
|
|
return engine.idOfV(rv)
|
|
}
|
|
|
|
func (engine *Engine) idOfV(rv reflect.Value) (schemas.PK, error) {
|
|
v := reflect.Indirect(rv)
|
|
table, err := engine.tagParser.ParseWithCache(v)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
pk := make([]interface{}, len(table.PrimaryKeys))
|
|
for i, col := range table.PKColumns() {
|
|
var err error
|
|
|
|
fieldName := col.FieldName
|
|
for {
|
|
parts := strings.SplitN(fieldName, ".", 2)
|
|
if len(parts) == 1 {
|
|
break
|
|
}
|
|
|
|
v = v.FieldByName(parts[0])
|
|
if v.Kind() == reflect.Ptr {
|
|
v = v.Elem()
|
|
}
|
|
if v.Kind() != reflect.Struct {
|
|
return nil, ErrUnSupportedType
|
|
}
|
|
fieldName = parts[1]
|
|
}
|
|
|
|
pkField := v.FieldByName(fieldName)
|
|
switch pkField.Kind() {
|
|
case reflect.String:
|
|
pk[i], err = engine.idTypeAssertion(col, pkField.String())
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
pk[i], err = engine.idTypeAssertion(col, strconv.FormatInt(pkField.Int(), 10))
|
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
|
// id of uint will be converted to int64
|
|
pk[i], err = engine.idTypeAssertion(col, strconv.FormatUint(pkField.Uint(), 10))
|
|
}
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return schemas.PK(pk), nil
|
|
}
|
|
|
|
func (engine *Engine) idTypeAssertion(col *schemas.Column, sid string) (interface{}, error) {
|
|
if col.SQLType.IsNumeric() {
|
|
n, err := strconv.ParseInt(sid, 10, 64)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return n, nil
|
|
} else if col.SQLType.IsText() {
|
|
return sid, nil
|
|
} else {
|
|
return nil, errors.New("not supported")
|
|
}
|
|
}
|
|
|
|
// CreateIndexes create indexes
|
|
func (engine *Engine) CreateIndexes(bean interface{}) error {
|
|
session := engine.NewSession()
|
|
defer session.Close()
|
|
return session.CreateIndexes(bean)
|
|
}
|
|
|
|
// CreateUniques create uniques
|
|
func (engine *Engine) CreateUniques(bean interface{}) error {
|
|
session := engine.NewSession()
|
|
defer session.Close()
|
|
return session.CreateUniques(bean)
|
|
}
|
|
|
|
// ClearCacheBean if enabled cache, clear the cache bean
|
|
func (engine *Engine) ClearCacheBean(bean interface{}, id string) error {
|
|
tableName := dialects.FullTableName(engine.dialect, engine.GetTableMapper(), bean)
|
|
cacher := engine.GetCacher(tableName)
|
|
if cacher != nil {
|
|
cacher.ClearIds(tableName)
|
|
cacher.DelBean(tableName, id)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ClearCache if enabled cache, clear some tables' cache
|
|
func (engine *Engine) ClearCache(beans ...interface{}) error {
|
|
for _, bean := range beans {
|
|
tableName := dialects.FullTableName(engine.dialect, engine.GetTableMapper(), bean)
|
|
cacher := engine.GetCacher(tableName)
|
|
if cacher != nil {
|
|
cacher.ClearIds(tableName)
|
|
cacher.ClearBeans(tableName)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// UnMapType remove table from tables cache
|
|
func (engine *Engine) UnMapType(t reflect.Type) {
|
|
engine.tagParser.ClearCacheTable(t)
|
|
}
|
|
|
|
// Sync the new struct changes to database, this method will automatically add
|
|
// table, column, index, unique. but will not delete or change anything.
|
|
// If you change some field, you should change the database manually.
|
|
func (engine *Engine) Sync(beans ...interface{}) error {
|
|
session := engine.NewSession()
|
|
defer session.Close()
|
|
|
|
for _, bean := range beans {
|
|
v := utils.ReflectValue(bean)
|
|
tableNameNoSchema := dialects.FullTableName(engine.dialect, engine.GetTableMapper(), bean)
|
|
table, err := engine.tagParser.ParseWithCache(v)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
isExist, err := session.Table(bean).isTableExist(tableNameNoSchema)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !isExist {
|
|
err = session.createTable(bean)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
/*isEmpty, err := engine.IsEmptyTable(bean)
|
|
if err != nil {
|
|
return err
|
|
}*/
|
|
var isEmpty bool
|
|
if isEmpty {
|
|
err = session.dropTable(bean)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = session.createTable(bean)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
for _, col := range table.Columns() {
|
|
isExist, err := engine.dialect.IsColumnExist(engine.db, session.ctx, tableNameNoSchema, col.Name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !isExist {
|
|
if err := session.statement.SetRefBean(bean); err != nil {
|
|
return err
|
|
}
|
|
err = session.addColumn(col.Name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
for name, index := range table.Indexes {
|
|
if err := session.statement.SetRefBean(bean); err != nil {
|
|
return err
|
|
}
|
|
if index.Type == schemas.UniqueType {
|
|
isExist, err := session.isIndexExist2(tableNameNoSchema, index.Cols, true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !isExist {
|
|
if err := session.statement.SetRefBean(bean); err != nil {
|
|
return err
|
|
}
|
|
|
|
err = session.addUnique(tableNameNoSchema, name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
} else if index.Type == schemas.IndexType {
|
|
isExist, err := session.isIndexExist2(tableNameNoSchema, index.Cols, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !isExist {
|
|
if err := session.statement.SetRefBean(bean); err != nil {
|
|
return err
|
|
}
|
|
|
|
err = session.addIndex(tableNameNoSchema, name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
} else {
|
|
return errors.New("unknow index type")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Sync2 synchronize structs to database tables
|
|
func (engine *Engine) Sync2(beans ...interface{}) error {
|
|
s := engine.NewSession()
|
|
defer s.Close()
|
|
return s.Sync2(beans...)
|
|
}
|
|
|
|
// CreateTables create tabls according bean
|
|
func (engine *Engine) CreateTables(beans ...interface{}) error {
|
|
session := engine.NewSession()
|
|
defer session.Close()
|
|
|
|
err := session.Begin()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, bean := range beans {
|
|
err = session.createTable(bean)
|
|
if err != nil {
|
|
session.Rollback()
|
|
return err
|
|
}
|
|
}
|
|
return session.Commit()
|
|
}
|
|
|
|
// DropTables drop specify tables
|
|
func (engine *Engine) DropTables(beans ...interface{}) error {
|
|
session := engine.NewSession()
|
|
defer session.Close()
|
|
|
|
err := session.Begin()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, bean := range beans {
|
|
err = session.dropTable(bean)
|
|
if err != nil {
|
|
session.Rollback()
|
|
return err
|
|
}
|
|
}
|
|
return session.Commit()
|
|
}
|
|
|
|
// DropIndexes drop indexes of a table
|
|
func (engine *Engine) DropIndexes(bean interface{}) error {
|
|
session := engine.NewSession()
|
|
defer session.Close()
|
|
return session.DropIndexes(bean)
|
|
}
|
|
|
|
// Exec raw sql
|
|
func (engine *Engine) Exec(sqlOrArgs ...interface{}) (sql.Result, error) {
|
|
session := engine.NewSession()
|
|
defer session.Close()
|
|
return session.Exec(sqlOrArgs...)
|
|
}
|
|
|
|
// Query a raw sql and return records as []map[string][]byte
|
|
func (engine *Engine) Query(sqlOrArgs ...interface{}) (resultsSlice []map[string][]byte, err error) {
|
|
session := engine.NewSession()
|
|
defer session.Close()
|
|
return session.Query(sqlOrArgs...)
|
|
}
|
|
|
|
// QueryString runs a raw sql and return records as []map[string]string
|
|
func (engine *Engine) QueryString(sqlOrArgs ...interface{}) ([]map[string]string, error) {
|
|
session := engine.NewSession()
|
|
defer session.Close()
|
|
return session.QueryString(sqlOrArgs...)
|
|
}
|
|
|
|
// QueryInterface runs a raw sql and return records as []map[string]interface{}
|
|
func (engine *Engine) QueryInterface(sqlOrArgs ...interface{}) ([]map[string]interface{}, error) {
|
|
session := engine.NewSession()
|
|
defer session.Close()
|
|
return session.QueryInterface(sqlOrArgs...)
|
|
}
|
|
|
|
// Insert one or more records
|
|
func (engine *Engine) Insert(beans ...interface{}) (int64, error) {
|
|
session := engine.NewSession()
|
|
defer session.Close()
|
|
return session.Insert(beans...)
|
|
}
|
|
|
|
// InsertOne insert only one record
|
|
func (engine *Engine) InsertOne(bean interface{}) (int64, error) {
|
|
session := engine.NewSession()
|
|
defer session.Close()
|
|
return session.InsertOne(bean)
|
|
}
|
|
|
|
// Update records, bean's non-empty fields are updated contents,
|
|
// condiBean' non-empty filds are conditions
|
|
// CAUTION:
|
|
// 1.bool will defaultly be updated content nor conditions
|
|
// You should call UseBool if you have bool to use.
|
|
// 2.float32 & float64 may be not inexact as conditions
|
|
func (engine *Engine) Update(bean interface{}, condiBeans ...interface{}) (int64, error) {
|
|
session := engine.NewSession()
|
|
defer session.Close()
|
|
return session.Update(bean, condiBeans...)
|
|
}
|
|
|
|
// Delete records, bean's non-empty fields are conditions
|
|
func (engine *Engine) Delete(bean interface{}) (int64, error) {
|
|
session := engine.NewSession()
|
|
defer session.Close()
|
|
return session.Delete(bean)
|
|
}
|
|
|
|
// Get retrieve one record from table, bean's non-empty fields
|
|
// are conditions
|
|
func (engine *Engine) Get(bean interface{}) (bool, error) {
|
|
session := engine.NewSession()
|
|
defer session.Close()
|
|
return session.Get(bean)
|
|
}
|
|
|
|
// Exist returns true if the record exist otherwise return false
|
|
func (engine *Engine) Exist(bean ...interface{}) (bool, error) {
|
|
session := engine.NewSession()
|
|
defer session.Close()
|
|
return session.Exist(bean...)
|
|
}
|
|
|
|
// Find retrieve records from table, condiBeans's non-empty fields
|
|
// are conditions. beans could be []Struct, []*Struct, map[int64]Struct
|
|
// map[int64]*Struct
|
|
func (engine *Engine) Find(beans interface{}, condiBeans ...interface{}) error {
|
|
session := engine.NewSession()
|
|
defer session.Close()
|
|
return session.Find(beans, condiBeans...)
|
|
}
|
|
|
|
// FindAndCount find the results and also return the counts
|
|
func (engine *Engine) FindAndCount(rowsSlicePtr interface{}, condiBean ...interface{}) (int64, error) {
|
|
session := engine.NewSession()
|
|
defer session.Close()
|
|
return session.FindAndCount(rowsSlicePtr, condiBean...)
|
|
}
|
|
|
|
// Iterate record by record handle records from table, bean's non-empty fields
|
|
// are conditions.
|
|
func (engine *Engine) Iterate(bean interface{}, fun IterFunc) error {
|
|
session := engine.NewSession()
|
|
defer session.Close()
|
|
return session.Iterate(bean, fun)
|
|
}
|
|
|
|
// Rows return sql.Rows compatible Rows obj, as a forward Iterator object for iterating record by record, bean's non-empty fields
|
|
// are conditions.
|
|
func (engine *Engine) Rows(bean interface{}) (*Rows, error) {
|
|
session := engine.NewSession()
|
|
return session.Rows(bean)
|
|
}
|
|
|
|
// Count counts the records. bean's non-empty fields are conditions.
|
|
func (engine *Engine) Count(bean ...interface{}) (int64, error) {
|
|
session := engine.NewSession()
|
|
defer session.Close()
|
|
return session.Count(bean...)
|
|
}
|
|
|
|
// Sum sum the records by some column. bean's non-empty fields are conditions.
|
|
func (engine *Engine) Sum(bean interface{}, colName string) (float64, error) {
|
|
session := engine.NewSession()
|
|
defer session.Close()
|
|
return session.Sum(bean, colName)
|
|
}
|
|
|
|
// SumInt sum the records by some column. bean's non-empty fields are conditions.
|
|
func (engine *Engine) SumInt(bean interface{}, colName string) (int64, error) {
|
|
session := engine.NewSession()
|
|
defer session.Close()
|
|
return session.SumInt(bean, colName)
|
|
}
|
|
|
|
// Sums sum the records by some columns. bean's non-empty fields are conditions.
|
|
func (engine *Engine) Sums(bean interface{}, colNames ...string) ([]float64, error) {
|
|
session := engine.NewSession()
|
|
defer session.Close()
|
|
return session.Sums(bean, colNames...)
|
|
}
|
|
|
|
// SumsInt like Sums but return slice of int64 instead of float64.
|
|
func (engine *Engine) SumsInt(bean interface{}, colNames ...string) ([]int64, error) {
|
|
session := engine.NewSession()
|
|
defer session.Close()
|
|
return session.SumsInt(bean, colNames...)
|
|
}
|
|
|
|
// ImportFile SQL DDL file
|
|
func (engine *Engine) ImportFile(ddlPath string) ([]sql.Result, error) {
|
|
session := engine.NewSession()
|
|
defer session.Close()
|
|
return session.ImportFile(ddlPath)
|
|
}
|
|
|
|
// Import SQL DDL from io.Reader
|
|
func (engine *Engine) Import(r io.Reader) ([]sql.Result, error) {
|
|
session := engine.NewSession()
|
|
defer session.Close()
|
|
return session.Import(r)
|
|
}
|
|
|
|
// nowTime return current time
|
|
func (engine *Engine) nowTime(col *schemas.Column) (interface{}, time.Time) {
|
|
t := time.Now()
|
|
var tz = engine.DatabaseTZ
|
|
if !col.DisableTimeZone && col.TimeZone != nil {
|
|
tz = col.TimeZone
|
|
}
|
|
return dialects.FormatTime(engine.dialect, col.SQLType.Name, t.In(tz)), t.In(engine.TZLocation)
|
|
}
|
|
|
|
// GetColumnMapper returns the column name mapper
|
|
func (engine *Engine) GetColumnMapper() names.Mapper {
|
|
return engine.tagParser.GetColumnMapper()
|
|
}
|
|
|
|
// GetTableMapper returns the table name mapper
|
|
func (engine *Engine) GetTableMapper() names.Mapper {
|
|
return engine.tagParser.GetTableMapper()
|
|
}
|
|
|
|
// GetTZLocation returns time zone of the application
|
|
func (engine *Engine) GetTZLocation() *time.Location {
|
|
return engine.TZLocation
|
|
}
|
|
|
|
// SetTZLocation sets time zone of the application
|
|
func (engine *Engine) SetTZLocation(tz *time.Location) {
|
|
engine.TZLocation = tz
|
|
}
|
|
|
|
// GetTZDatabase returns time zone of the database
|
|
func (engine *Engine) GetTZDatabase() *time.Location {
|
|
return engine.DatabaseTZ
|
|
}
|
|
|
|
// SetTZDatabase sets time zone of the database
|
|
func (engine *Engine) SetTZDatabase(tz *time.Location) {
|
|
engine.DatabaseTZ = tz
|
|
}
|
|
|
|
// SetSchema sets the schema of database
|
|
func (engine *Engine) SetSchema(schema string) {
|
|
engine.dialect.URI().SetSchema(schema)
|
|
}
|
|
|
|
// Unscoped always disable struct tag "deleted"
|
|
func (engine *Engine) Unscoped() *Session {
|
|
session := engine.NewSession()
|
|
session.isAutoClose = true
|
|
return session.Unscoped()
|
|
}
|
|
|
|
func (engine *Engine) tbNameWithSchema(v string) string {
|
|
return dialects.TableNameWithSchema(engine.dialect, v)
|
|
}
|
|
|
|
// Context creates a session with the context
|
|
func (engine *Engine) Context(ctx context.Context) *Session {
|
|
session := engine.NewSession()
|
|
session.isAutoClose = true
|
|
return session.Context(ctx)
|
|
}
|
|
|
|
// SetDefaultContext set the default context
|
|
func (engine *Engine) SetDefaultContext(ctx context.Context) {
|
|
engine.defaultContext = ctx
|
|
}
|
|
|
|
// PingContext tests if database is alive
|
|
func (engine *Engine) PingContext(ctx context.Context) error {
|
|
session := engine.NewSession()
|
|
defer session.Close()
|
|
return session.PingContext(ctx)
|
|
}
|
|
|
|
// Transaction Execute sql wrapped in a transaction(abbr as tx), tx will automatic commit if no errors occurred
|
|
func (engine *Engine) Transaction(f func(*Session) (interface{}, error)) (interface{}, error) {
|
|
session := engine.NewSession()
|
|
defer session.Close()
|
|
|
|
if err := session.Begin(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
result, err := f(session)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := session.Commit(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return result, nil
|
|
}
|