commit 22bed4953877030f351f36d9dcb92dd849a302d3 Author: qbhy <96qbhy@gmail.com> Date: Tue Jan 25 23:15:00 2022 +0800 feat: 初始化数据库仓库 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c7cf333 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +tests/.env +tests/db.sqlite \ No newline at end of file diff --git a/config.go b/config.go new file mode 100644 index 0000000..c336e21 --- /dev/null +++ b/config.go @@ -0,0 +1,10 @@ +package database + +import ( + "github.com/goal-web/contracts" +) + +type Config struct { + Default string + Connections map[string]contracts.Fields +} diff --git a/drivers/base.go b/drivers/base.go new file mode 100644 index 0000000..dc3c83e --- /dev/null +++ b/drivers/base.go @@ -0,0 +1,103 @@ +package drivers + +import ( + "github.com/goal-web/collection" + "github.com/goal-web/contracts" + "github.com/goal-web/database/events" + exceptions2 "github.com/goal-web/database/exceptions" + "github.com/goal-web/database/table" + "github.com/goal-web/database/tx" + "github.com/goal-web/supports/exceptions" + "github.com/jmoiron/sqlx" +) + +type Base struct { + *sqlx.DB + events contracts.EventDispatcher +} + +func (this *Base) Query(query string, args ...interface{}) (results contracts.Collection, err error) { + defer func() { + if err == nil { + this.events.Dispatch(&events.QueryExecuted{Sql: query, Bindings: args}) + } + }() + rows, err := this.DB.Query(query, args...) + + if err != nil { + return nil, err + } + + data, err := table.ParseRows(rows) + + if err != nil { + return nil, err + } + + return collection.FromFieldsSlice(data), nil +} + +func (this *Base) Get(dest interface{}, query string, args ...interface{}) (err error) { + defer func() { + if err == nil { + this.events.Dispatch(&events.QueryExecuted{Sql: query, Bindings: args}) + } + }() + return this.DB.Get(dest, query, args...) +} +func (this *Base) Select(dest interface{}, query string, args ...interface{}) (err error) { + defer func() { + if err == nil { + this.events.Dispatch(&events.QueryExecuted{Sql: query, Bindings: args}) + } + }() + return this.DB.Get(dest, query, args...) +} + +func (this *Base) Begin() (contracts.DBTx, error) { + sqlxTx, err := this.DB.Beginx() + if err != nil { + return nil, err + } + return tx.New(sqlxTx, this.events), err +} + +func (this *Base) Transaction(fn func(tx contracts.SqlExecutor) error) (err error) { + sqlxTx, err := this.Begin() + if err != nil { + return exceptions2.BeginException{Exception: exceptions.WithError(err, nil)} + } + + defer func() { // 处理 panic 情况 + if recoverErr := recover(); recoverErr != nil { + rollbackErr := sqlxTx.Rollback() + err = recoverErr.(error) + if rollbackErr != nil { + err = exceptions2.RollbackException{Exception: exceptions.WithPrevious(rollbackErr, nil, exceptions.WithError(err, nil))} + } else { + err = exceptions2.TransactionException{Exception: exceptions.WithError(err, nil)} + } + } + }() + + err = fn(sqlxTx) + + if err != nil { + rollbackErr := sqlxTx.Rollback() + if rollbackErr != nil { + return exceptions2.RollbackException{Exception: exceptions.WithPrevious(rollbackErr, nil, exceptions.WithError(err, nil))} + } + return exceptions2.TransactionException{Exception: exceptions.WithError(err, nil)} + } + + return sqlxTx.Commit() +} + +func (this *Base) Exec(query string, args ...interface{}) (result contracts.Result, err error) { + defer func() { + if err == nil { + this.events.Dispatch(&events.QueryExecuted{Sql: query, Bindings: args}) + } + }() + return this.DB.Exec(query, args...) +} diff --git a/drivers/mysql.go b/drivers/mysql.go new file mode 100644 index 0000000..2e4a060 --- /dev/null +++ b/drivers/mysql.go @@ -0,0 +1,36 @@ +package drivers + +import ( + "fmt" + _ "github.com/go-sql-driver/mysql" + "github.com/goal-web/contracts" + "github.com/goal-web/supports/logs" + "github.com/goal-web/supports/utils" + "github.com/jmoiron/sqlx" +) + +type Mysql struct { + *Base +} + +func MysqlConnector(config contracts.Fields, events contracts.EventDispatcher) contracts.DBConnection { + dsn := utils.GetStringField(config, "unix_socket") + if dsn == "" { + dsn = fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=%s", + utils.GetStringField(config, "username"), + utils.GetStringField(config, "password"), + utils.GetStringField(config, "host"), + utils.GetStringField(config, "port"), + utils.GetStringField(config, "database"), + utils.GetStringField(config, "charset"), + ) + } + db, err := sqlx.Connect("mysql", dsn) + if err != nil { + logs.WithError(err).WithField("config", config).Fatal("mysql 数据库初始化失败") + } + db.SetMaxOpenConns(utils.GetIntField(config, "max_connections")) + db.SetMaxIdleConns(utils.GetIntField(config, "max_idles")) + + return &Mysql{&Base{db, events}} +} diff --git a/drivers/postgresql.go b/drivers/postgresql.go new file mode 100644 index 0000000..32f96f5 --- /dev/null +++ b/drivers/postgresql.go @@ -0,0 +1,32 @@ +package drivers + +import ( + "fmt" + "github.com/goal-web/contracts" + "github.com/goal-web/supports/logs" + "github.com/goal-web/supports/utils" + "github.com/jmoiron/sqlx" + _ "github.com/lib/pq" +) + +type PostgresSQL struct { + *Base +} + +func PostgresSqlConnector(config contracts.Fields, events contracts.EventDispatcher) contracts.DBConnection { + db, err := sqlx.Connect("postgres", fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=%s", + utils.GetStringField(config, "host"), + utils.GetStringField(config, "port"), + utils.GetStringField(config, "username"), + utils.GetStringField(config, "password"), + utils.GetStringField(config, "database"), + utils.GetStringField(config, "sslmode"), + )) + if err != nil { + logs.WithError(err).WithField("config", config).Fatal("postgreSql 数据库初始化失败") + } + db.SetMaxOpenConns(utils.GetIntField(config, "max_connections")) + db.SetMaxIdleConns(utils.GetIntField(config, "max_idles")) + + return &PostgresSQL{&Base{db, events}} +} diff --git a/drivers/sqlite.go b/drivers/sqlite.go new file mode 100644 index 0000000..fb03686 --- /dev/null +++ b/drivers/sqlite.go @@ -0,0 +1,23 @@ +package drivers + +import ( + "github.com/goal-web/contracts" + "github.com/goal-web/supports/logs" + "github.com/goal-web/supports/utils" + "github.com/jmoiron/sqlx" + _ "github.com/mattn/go-sqlite3" +) + +type Sqlite struct { + *Base +} + +func SqliteConnector(config contracts.Fields, events contracts.EventDispatcher) contracts.DBConnection { + db, err := sqlx.Connect("sqlite3", utils.GetStringField(config, "database")) + + if err != nil { + logs.WithError(err).WithField("config", config).Fatal("sqlite 数据库初始化失败") + } + + return &Sqlite{&Base{db, events}} +} diff --git a/events/executed.go b/events/executed.go new file mode 100644 index 0000000..07a844a --- /dev/null +++ b/events/executed.go @@ -0,0 +1,15 @@ +package events + +type QueryExecuted struct { + Sql string + Bindings []interface{} + Connection string +} + +func (this *QueryExecuted) Event() string { + return "QUERY_EXECUTED" +} + +func (this *QueryExecuted) Sync() bool { + return true +} diff --git a/exception.go b/exception.go new file mode 100644 index 0000000..897b749 --- /dev/null +++ b/exception.go @@ -0,0 +1,24 @@ +package database + +import ( + "github.com/goal-web/contracts" +) + +type ConnectionErrorCode int + +const ( + DbDriverDontExist ConnectionErrorCode = iota + DbConnectionDontExist +) + +type DBConnectionException struct { + error + Connection string + Code ConnectionErrorCode + fields contracts.Fields +} + +func (this DBConnectionException) Fields() contracts.Fields { + this.fields["Code"] = this.Code + return this.fields +} diff --git a/exceptions/exceptions.go b/exceptions/exceptions.go new file mode 100644 index 0000000..0e23dee --- /dev/null +++ b/exceptions/exceptions.go @@ -0,0 +1,15 @@ +package exceptions + +import "github.com/goal-web/contracts" + +type TransactionException struct { + contracts.Exception +} + +type RollbackException struct { + contracts.Exception +} + +type BeginException struct { + contracts.Exception +} diff --git a/factory.go b/factory.go new file mode 100644 index 0000000..d02e79e --- /dev/null +++ b/factory.go @@ -0,0 +1,56 @@ +package database + +import ( + "errors" + "github.com/goal-web/contracts" + "github.com/goal-web/supports/utils" +) + +type Factory struct { + events contracts.EventDispatcher + config contracts.Config + connections map[string]contracts.DBConnection + drivers map[string]contracts.DBConnector + dbConfig Config +} + +func (this *Factory) Connection(name ...string) contracts.DBConnection { + connection := this.dbConfig.Default + if len(name) > 0 { + connection = name[0] + } + if connection, existsConnection := this.connections[connection]; existsConnection { + return connection + } + + this.connections[connection] = this.make(connection) + + return this.connections[connection] +} + +func (this *Factory) Extend(name string, driver contracts.DBConnector) { + this.drivers[name] = driver +} + +func (this *Factory) make(name string) contracts.DBConnection { + config := this.config.Get("database").(Config) + + if connectionConfig, existsConnection := config.Connections[name]; existsConnection { + driverName := utils.GetStringField(connectionConfig, "driver") + if driver, existsDriver := this.drivers[driverName]; existsDriver { + return driver(connectionConfig, this.events) + } + + panic(DBConnectionException{ + error: errors.New("该数据库驱动不存在:" + driverName), + Code: DbDriverDontExist, + fields: connectionConfig, + }) + } + + panic(DBConnectionException{ + error: errors.New("数据库连接不存在:" + name), + Code: DbConnectionDontExist, + Connection: name, + }) +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..a2d074d --- /dev/null +++ b/go.mod @@ -0,0 +1,29 @@ +module github.com/goal-web/database + +go 1.17 + +require ( + github.com/go-sql-driver/mysql v1.5.0 + github.com/goal-web/application v0.1.0 + github.com/goal-web/collection v0.1.4 + github.com/goal-web/config v0.1.0 + github.com/goal-web/contracts v0.1.16 + github.com/goal-web/events v0.1.5 + github.com/goal-web/querybuilder v0.1.10 + github.com/goal-web/supports v0.1.11 + github.com/jmoiron/sqlx v1.3.4 + github.com/lib/pq v1.10.4 + github.com/mattn/go-sqlite3 v1.14.10 + github.com/stretchr/testify v1.7.0 +) + +require ( + github.com/apex/log v1.9.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/goal-web/container v0.1.3 // indirect + github.com/pkg/errors v0.8.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/qbhy/parallel v1.3.0 // indirect + github.com/shopspring/decimal v1.3.1 // indirect + gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..e559285 --- /dev/null +++ b/go.sum @@ -0,0 +1,114 @@ +github.com/apex/log v1.9.0 h1:FHtw/xuaM8AgmvDDTI9fiwoAL25Sq2cxojnZICUU8l0= +github.com/apex/log v1.9.0/go.mod h1:m82fZlWIuiWzWP04XCTXmnX0xRkYYbCdYn8jbJeLBEA= +github.com/apex/logs v1.0.0/go.mod h1:XzxuLZ5myVHDy9SAmYpamKKRNApGj54PfYLcFrXqDwo= +github.com/aphistic/golf v0.0.0-20180712155816-02c07f170c5a/go.mod h1:3NqKYiepwy8kCu4PNA+aP7WUV72eXWJeP9/r3/K9aLE= +github.com/aphistic/sweet v0.2.0/go.mod h1:fWDlIh/isSE9n6EPsRmC0det+whmX6dJid3stzu0Xys= +github.com/aws/aws-sdk-go v1.20.6/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/goal-web/application v0.1.0 h1:4zOgmL1DdW7RSF5u1dsEhItecENCl0Kwt5VuP//0OfI= +github.com/goal-web/application v0.1.0/go.mod h1:pHFKbdFw0UsckCsgHKF6agBqdfxT/qNoY1OEWhI5HKM= +github.com/goal-web/collection v0.1.4 h1:O4uSYVeoO/CS/ztLp4FsOIBEFXG6oHwbdPU08hnOtVw= +github.com/goal-web/collection v0.1.4/go.mod h1:Syaj+9lKGoSAenQTJErMvAG/iBAZlr+Rn4rENR1S5J4= +github.com/goal-web/config v0.1.0 h1:mZjlTYEUDOIngauKq6mbJBnJFOVYudH+mtt8VJq1GGY= +github.com/goal-web/config v0.1.0/go.mod h1:d2/W9nOfLMLBq1ICpNXZe/4cKMWIQdYzz4wS9qmpxbk= +github.com/goal-web/container v0.1.3 h1:Xt1mbfDptgGnXEbQegjC078j5cPNXZMgKXmc9O0t/fE= +github.com/goal-web/container v0.1.3/go.mod h1:IpfV+0J1xaKUTl7Pymc02h0wNQtCKWcQqI8ASFd/p/Y= +github.com/goal-web/contracts v0.1.1/go.mod h1:lKHynU2Kgk6xyxL4afOJM4TO1kSa3RrCJ2bm5RtFMBw= +github.com/goal-web/contracts v0.1.7/go.mod h1:lKHynU2Kgk6xyxL4afOJM4TO1kSa3RrCJ2bm5RtFMBw= +github.com/goal-web/contracts v0.1.8/go.mod h1:lKHynU2Kgk6xyxL4afOJM4TO1kSa3RrCJ2bm5RtFMBw= +github.com/goal-web/contracts v0.1.13/go.mod h1:lKHynU2Kgk6xyxL4afOJM4TO1kSa3RrCJ2bm5RtFMBw= +github.com/goal-web/contracts v0.1.16 h1:sDvlEpzLyTOn6YmFwo64iYR8Giwv4afWLXeeyAkH/fM= +github.com/goal-web/contracts v0.1.16/go.mod h1:lKHynU2Kgk6xyxL4afOJM4TO1kSa3RrCJ2bm5RtFMBw= +github.com/goal-web/events v0.1.5 h1:vtoXRd0oYBZMdC/Z58TTFKtJXe8QWcgBgUgZOdm9N7A= +github.com/goal-web/events v0.1.5/go.mod h1:iS8y/sKknId/0+PoI6OBOL5M9pSLi65lsUgW4OTAqy0= +github.com/goal-web/querybuilder v0.1.10 h1:+jJScOMVczLLEYYXJexKjelOE3ukaZc9ulEg/ij4UtY= +github.com/goal-web/querybuilder v0.1.10/go.mod h1:2sOjB1+J49xpem9Y8CwZZCNdkRzWh7Mc287W5z80TMQ= +github.com/goal-web/supports v0.1.1/go.mod h1:TlQ+7igTZhAN2//GcF9lM6hmi3xh/We/PxDjtFTB4Ow= +github.com/goal-web/supports v0.1.2/go.mod h1:TlQ+7igTZhAN2//GcF9lM6hmi3xh/We/PxDjtFTB4Ow= +github.com/goal-web/supports v0.1.10/go.mod h1:md5XsCyIjSwYO0qU+ECXd6kCtLm7OeiqgUjoC6vW284= +github.com/goal-web/supports v0.1.11 h1:IY9f0X1T/vi2tYWlhrJZiFhev1bH0M4ndhqQlkddA9Y= +github.com/goal-web/supports v0.1.11/go.mod h1:md5XsCyIjSwYO0qU+ECXd6kCtLm7OeiqgUjoC6vW284= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmoiron/sqlx v1.3.4 h1:wv+0IJZfL5z0uZoUjlpKgHkgaFSYD+r9CfrXjEXsO7w= +github.com/jmoiron/sqlx v1.3.4/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ= +github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7/go.mod h1:2iMrUgbbvHEiQClaW2NsSzMyGHqN+rDFqY705q49KG0= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk= +github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/mattn/go-sqlite3 v1.14.10 h1:MLn+5bFRlWMGoSRmJour3CL1w/qL96mvipqpwQW/Sfk= +github.com/mattn/go-sqlite3 v1.14.10/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/qbhy/parallel v1.3.0 h1:QxiO29iAGKo5Qt6IntwkP6UJongA7rjMI05IshPLymk= +github.com/qbhy/parallel v1.3.0/go.mod h1:gjKS0IACnz3SWXkeEOUvPPZZxpeQAOW47jT4cpWPvqU= +github.com/rogpeppe/fastuuid v1.1.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= +github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM= +github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM= +github.com/smartystreets/gunit v1.0.0/go.mod h1:qwPWnhz6pn0NnRBP++URONOVyNkPyr4SauJk4cUOwJs= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/tj/assert v0.0.0-20171129193455-018094318fb0/go.mod h1:mZ9/Rh9oLWpLLDRpvE+3b7gP/C2YyLFYxNmcLnPTMe0= +github.com/tj/assert v0.0.3 h1:Df/BlaZ20mq6kuai7f5z2TvPFiwC3xaWJSDQNiIS3Rk= +github.com/tj/assert v0.0.3/go.mod h1:Ne6X72Q+TB1AteidzQncjw9PabbMp4PBMZ1k+vd1Pvk= +github.com/tj/go-buffer v1.1.0/go.mod h1:iyiJpfFcR2B9sXu7KvjbT9fpM4mOelRSDTbntVj52Uc= +github.com/tj/go-elastic v0.0.0-20171221160941-36157cbbebc2/go.mod h1:WjeM0Oo1eNAjXGDx2yma7uG2XoyRZTq1uv3M/o7imD0= +github.com/tj/go-kinesis v0.0.0-20171128231115-08b17f58cb1b/go.mod h1:/yhzCV0xPfx6jb1bBgRFjl5lytqVqZXEaeqWP8lTEao= +github.com/tj/go-spin v1.1.0/go.mod h1:Mg1mzmePZm4dva8Qz60H2lHwmJ2loum4VIrLgVnKwh4= +github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2/go.mod h1:hzfGeIUDq/j97IG+FhNqkowIyEcD88LrW6fyU3K3WqY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c h1:grhR+C34yXImVGp7EzNk+DTIk+323eIUWOmEevy6bDo= +gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/service_provider.go b/service_provider.go new file mode 100644 index 0000000..663a80f --- /dev/null +++ b/service_provider.go @@ -0,0 +1,35 @@ +package database + +import ( + "github.com/goal-web/contracts" + "github.com/goal-web/database/drivers" +) + +type ServiceProvider struct { +} + +func (this *ServiceProvider) Register(application contracts.Application) { + application.Singleton("db.factory", func(config contracts.Config, events contracts.EventDispatcher) contracts.DBFactory { + return &Factory{ + events: events, + config: config, + dbConfig: config.Get("database").(Config), + connections: make(map[string]contracts.DBConnection), + drivers: map[string]contracts.DBConnector{ + "mysql": drivers.MysqlConnector, + "postgres": drivers.PostgresSqlConnector, + "sqlite": drivers.SqliteConnector, + }, + } + }) + application.Singleton("db", func(config contracts.Config, factory contracts.DBFactory) contracts.DBConnection { + return factory.Connection() + }) +} + +func (this *ServiceProvider) Start() error { + return nil +} + +func (this *ServiceProvider) Stop() { +} diff --git a/table/aggregate.go b/table/aggregate.go new file mode 100644 index 0000000..9c15b31 --- /dev/null +++ b/table/aggregate.go @@ -0,0 +1,76 @@ +package table + +import ( + "github.com/goal-web/contracts" + "github.com/goal-web/supports/exceptions" +) + +func (this *Table) Count(columns ...string) int64 { + sql, bindings := this.WithCount(columns...).SelectSql() + var num int64 + err := this.getExecutor().Get(&num, sql, bindings...) + if err != nil { + exceptions.Throw(SelectException{exceptions.WithError(err, contracts.Fields{ + "columns": columns, + "sql": sql, + "bindings": bindings, + })}) + } + return num +} + +func (this *Table) Avg(column string, as ...string) int64 { + sql, bindings := this.WithAvg(column, as...).SelectSql() + var num int64 + err := this.getExecutor().Get(&num, sql, bindings...) + if err != nil { + exceptions.Throw(SelectException{exceptions.WithError(err, contracts.Fields{ + "column": column, + "sql": sql, + "bindings": bindings, + })}) + } + return num +} + +func (this *Table) Sum(column string, as ...string) int64 { + sql, bindings := this.WithSum(column, as...).SelectSql() + var num int64 + err := this.getExecutor().Get(&num, sql, bindings...) + if err != nil { + exceptions.Throw(SelectException{exceptions.WithError(err, contracts.Fields{ + "column": column, + "sql": sql, + "bindings": bindings, + })}) + } + return num +} + +func (this *Table) Max(column string, as ...string) int64 { + sql, bindings := this.WithMax(column, as...).SelectSql() + var num int64 + err := this.getExecutor().Get(&num, sql, bindings...) + if err != nil { + exceptions.Throw(SelectException{exceptions.WithError(err, contracts.Fields{ + "column": column, + "sql": sql, + "bindings": bindings, + })}) + } + return num +} + +func (this *Table) Min(column string, as ...string) int64 { + sql, bindings := this.WithMin(column, as...).SelectSql() + var num int64 + err := this.getExecutor().Get(&num, sql, bindings...) + if err != nil { + exceptions.Throw(SelectException{exceptions.WithError(err, contracts.Fields{ + "column": column, + "sql": sql, + "bindings": bindings, + })}) + } + return num +} diff --git a/table/chunk.go b/table/chunk.go new file mode 100644 index 0000000..0ea0d2c --- /dev/null +++ b/table/chunk.go @@ -0,0 +1,20 @@ +package table + +import "github.com/goal-web/contracts" + +func (this *Table) Chunk(size int, handler func(collection contracts.Collection, page int) error) (err error) { + page := 1 + for err == nil { + newCollection := this.SimplePaginate(int64(size), int64(page)) + err = handler(newCollection, page) + page++ + if newCollection.Len() < size { + break + } + } + return +} + +func (this *Table) ChunkById(size int, handler func(collection contracts.Collection, page int) error) error { + return this.OrderBy("id").Chunk(size, handler) +} diff --git a/table/exceptions.go b/table/exceptions.go new file mode 100644 index 0000000..e5f94a0 --- /dev/null +++ b/table/exceptions.go @@ -0,0 +1,27 @@ +package table + +import "github.com/goal-web/contracts" + +type CreateException struct { + contracts.Exception +} + +type InsertException struct { + contracts.Exception +} + +type UpdateException struct { + contracts.Exception +} + +type DeleteException struct { + contracts.Exception +} + +type SelectException struct { + contracts.Exception +} + +type NotFoundException struct { + contracts.Exception +} diff --git a/table/insert.go b/table/insert.go new file mode 100644 index 0000000..0be603c --- /dev/null +++ b/table/insert.go @@ -0,0 +1,140 @@ +package table + +import ( + "github.com/goal-web/contracts" + "github.com/goal-web/supports/exceptions" + "github.com/goal-web/supports/utils" +) + +func (this *Table) Create(fields contracts.Fields) interface{} { + sql, bindings := this.CreateSql(fields) + result, err := this.getExecutor().Exec(sql, bindings...) + if err != nil { + panic(CreateException{exceptions.WithError(err, fields)}) + } + id, err := result.LastInsertId() + if err != nil { + panic(CreateException{exceptions.WithError(err, fields)}) + } + + if _, existsPrimaryKey := fields[this.primaryKey]; !existsPrimaryKey { + fields[this.primaryKey] = id + } + + if this.class != nil { + return this.class.New(fields) + } + + return fields +} + +func (this *Table) Insert(values ...contracts.Fields) bool { + sql, bindings := this.InsertSql(values) + result, err := this.getExecutor().Exec(sql, bindings...) + + if err != nil { + panic(InsertException{exceptions.WithError(err, contracts.Fields{ + "values": values, + "sql": sql, + })}) + } + + rowsAffected, err := result.RowsAffected() + + if err != nil { + panic(InsertException{exceptions.WithError(err, contracts.Fields{ + "values": values, + "sql": sql, + })}) + } + + return rowsAffected > 0 +} + +func (this *Table) InsertGetId(values ...contracts.Fields) int64 { + sql, bindings := this.InsertSql(values) + result, err := this.getExecutor().Exec(sql, bindings...) + + if err != nil { + panic(InsertException{exceptions.WithError(err, contracts.Fields{ + "values": values, + "sql": sql, + })}) + } + + id, err := result.LastInsertId() + + if err != nil { + panic(InsertException{exceptions.WithError(err, contracts.Fields{ + "values": values, + "sql": sql, + })}) + } + + return id +} + +func (this *Table) InsertOrIgnore(values ...contracts.Fields) int64 { + sql, bindings := this.InsertIgnoreSql(values) + result, err := this.getExecutor().Exec(sql, bindings...) + + if err != nil { + panic(InsertException{exceptions.WithError(err, contracts.Fields{ + "values": values, + "sql": sql, + })}) + } + + rowsAffected, err := result.RowsAffected() + + if err != nil { + panic(InsertException{exceptions.WithError(err, contracts.Fields{ + "values": values, + "sql": sql, + })}) + } + + return rowsAffected +} + +func (this *Table) InsertOrReplace(values ...contracts.Fields) int64 { + sql, bindings := this.InsertReplaceSql(values) + result, err := this.getExecutor().Exec(sql, bindings...) + + if err != nil { + panic(InsertException{exceptions.WithError(err, contracts.Fields{ + "values": values, + "sql": sql, + })}) + } + + rowsAffected, err := result.RowsAffected() + + if err != nil { + panic(InsertException{exceptions.WithError(err, contracts.Fields{ + "values": values, + "sql": sql, + })}) + } + + return rowsAffected +} + +func (this *Table) FirstOrCreate(values ...contracts.Fields) interface{} { + var attributes contracts.Fields + argsLen := len(values) + if argsLen > 0 { + for field, value := range values[0] { + attributes[field] = value + this.Where(field, value) + } + if result := this.First(); result != nil { + return result + } else if argsLen > 1 { + utils.MergeFields(attributes, values[1]) + } + return this.Create(attributes) + } + + return nil +} diff --git a/table/model.go b/table/model.go new file mode 100644 index 0000000..6eb75b3 --- /dev/null +++ b/table/model.go @@ -0,0 +1,31 @@ +package table + +import ( + "github.com/goal-web/contracts" +) + +type model struct { + class contracts.Class + table string + connection string +} + +func Model(class contracts.Class, table string, connection ...string) *Table { + conn := "" + if len(connection) > 0 { + conn = connection[0] + } + return FromModel(model{class: class, table: table, connection: conn}) +} + +func (model model) GetClass() contracts.Class { + return model.class +} + +func (model model) GetTable() string { + return model.table +} + +func (model model) GetConnection() string { + return model.connection +} diff --git a/table/select.go b/table/select.go new file mode 100644 index 0000000..4d52c9b --- /dev/null +++ b/table/select.go @@ -0,0 +1,52 @@ +package table + +import ( + "errors" + "github.com/goal-web/contracts" + "github.com/goal-web/supports/exceptions" +) + +func (this *Table) Get() contracts.Collection { + sql, bindings := this.SelectSql() + + rows, err := this.getExecutor().Query(sql, bindings...) + if err != nil { + panic(SelectException{exceptions.WithError(err, contracts.Fields{"sql": sql, "bindings": bindings})}) + } + + // 返回 Collection + if this.class == nil { + return rows + } + // 返回指定的 Collection + //results := reflect.MakeSlice(reflect.SliceOf(this.class.Type), 0, 0) + //fmt.Println(results, utils.GetTypeKey(reflect.TypeOf(results))) + //err := this.getExecutor().Select(&results, sql, bindings...) + + //if err != nil { + // panic(SelectException{exceptions.WithError(err, contracts.Fields{ + // "sql": sql, + // "bindings": bindings, + // "model": this.class.ClassName(), + // })}) + //} + + return rows.Map(func(fields contracts.Fields) interface{} { + return this.class.New(fields) + }) +} + +func (this *Table) Find(key interface{}) interface{} { + return this.Where(this.primaryKey, key).First() +} + +func (this *Table) First() interface{} { + return this.Take(1).Get().First() +} + +func (this *Table) FirstOrFail() interface{} { + if result := this.First(); result != nil { + return result + } + panic(NotFoundException{exceptions.WithError(errors.New("未找到"), contracts.Fields{})}) +} diff --git a/table/table.go b/table/table.go new file mode 100644 index 0000000..4d1db53 --- /dev/null +++ b/table/table.go @@ -0,0 +1,96 @@ +package table + +import ( + "github.com/goal-web/application" + "github.com/goal-web/contracts" + "github.com/goal-web/querybuilder" + "github.com/goal-web/supports/exceptions" +) + +type Table struct { + contracts.QueryBuilder + executor contracts.SqlExecutor + + table string + primaryKey string + class contracts.Class +} + +func getTable(name string) *Table { + builder := querybuilder.NewQuery(name) + instance := &Table{ + QueryBuilder: builder, + primaryKey: "id", + table: name, + } + builder.Bind(instance) + return instance +} + +// Query 将使用默认 connection +func Query(name string) *Table { + return getTable(name).SetConnection(application.Get("db").(contracts.DBConnection)) +} + +func FromModel(model contracts.Model) *Table { + return WithConnection(model.GetTable(), model.GetConnection()).SetClass(model.GetClass()) +} + +// WithConnection 使用指定链接 +func WithConnection(name string, connection interface{}) *Table { + if connection == "" || connection == nil { + return getTable(name) + } + return getTable(name).SetConnection(connection) +} + +// WithTX 使用TX +func WithTX(name string, tx contracts.DBTx) contracts.QueryBuilder { + return getTable(name).SetExecutor(tx) +} + +// SetConnection 参数要么是 contracts.DBConnection 要么是 string +func (this *Table) SetConnection(connection interface{}) *Table { + if conn, ok := connection.(contracts.DBConnection); ok { + this.executor = conn + } else { + this.executor = application.Get("db.factory").(contracts.DBFactory).Connection(connection.(string)) + } + return this +} + +// SetClass 设置类 +func (this *Table) SetClass(class contracts.Class) *Table { + this.class = class + return this +} + +// SetPrimaryKey 设置主键 +func (this *Table) SetPrimaryKey(name string) *Table { + this.primaryKey = name + return this +} + +// getExecutor 获取 sql 语句的执行者 +func (this *Table) getExecutor() contracts.SqlExecutor { + return this.executor +} + +// SetExecutor 参数必须是 contracts.DBTx 实例 +func (this *Table) SetExecutor(executor contracts.SqlExecutor) contracts.QueryBuilder { + this.executor = executor + return this +} + +func (this *Table) Delete() int64 { + sql, bindings := this.DeleteSql() + result, err := this.getExecutor().Exec(sql, bindings...) + if err != nil { + panic(DeleteException{exceptions.WithError(err, contracts.Fields{"sql": sql, "bindings": bindings})}) + } + num, err := result.RowsAffected() + if err != nil { + panic(DeleteException{exceptions.WithError(err, contracts.Fields{"sql": sql, "bindings": bindings})}) + } + return num +} diff --git a/table/update.go b/table/update.go new file mode 100644 index 0000000..ef6d489 --- /dev/null +++ b/table/update.go @@ -0,0 +1,60 @@ +package table + +import ( + "github.com/goal-web/contracts" + "github.com/goal-web/supports/exceptions" + "github.com/goal-web/supports/utils" +) + +func (this *Table) UpdateOrInsert(attributes contracts.Fields, values ...contracts.Fields) bool { + this.WhereFields(attributes) + sql, bindings := this.UpdateSql(attributes) + result, err := this.getExecutor().Exec(sql, bindings...) + if err != nil { + panic(UpdateException{exceptions.WithError(err, contracts.Fields{ + "attributes": attributes, + "values": values, + })}) + } + num, _ := result.RowsAffected() + if num > 0 { + return true + } + if len(values) > 0 { + utils.MergeFields(attributes, values[0]) + } + return this.Insert(attributes) +} + +func (this *Table) UpdateOrCreate(attributes contracts.Fields, values ...contracts.Fields) interface{} { + this.WhereFields(attributes) + sql, bindings := this.UpdateSql(attributes) + result, err := this.getExecutor().Exec(sql, bindings...) + if err != nil { + panic(UpdateException{exceptions.WithError(err, contracts.Fields{ + "attributes": attributes, + "values": values, + })}) + } + num, _ := result.RowsAffected() + if num > 0 { + return true + } + if len(values) > 0 { + utils.MergeFields(attributes, values[0]) + } + return this.Insert(attributes) +} + +func (this *Table) Update(fields contracts.Fields) int64 { + sql, bindings := this.UpdateSql(fields) + result, err := this.getExecutor().Exec(sql, bindings...) + if err != nil { + panic(UpdateException{exceptions.WithError(err, fields)}) + } + num, err := result.RowsAffected() + if err != nil { + panic(UpdateException{exceptions.WithError(err, fields)}) + } + return num +} diff --git a/table/utils.go b/table/utils.go new file mode 100644 index 0000000..1757603 --- /dev/null +++ b/table/utils.go @@ -0,0 +1,31 @@ +package table + +import ( + "database/sql" + "github.com/goal-web/contracts" +) + +func ParseRows(rows *sql.Rows) ([]contracts.Fields, error) { + columns, err := rows.Columns() + if err != nil { + return nil, err + } + columnLength := len(columns) + cache := make([]interface{}, columnLength) + for index, _ := range cache { + var a interface{} + cache[index] = &a + } + var results []contracts.Fields + for rows.Next() { + _ = rows.Scan(cache...) + + item := make(map[string]interface{}) + for i, data := range cache { + item[columns[i]] = *data.(*interface{}) + } + results = append(results, item) + } + _ = rows.Close() + return results, nil +} diff --git a/tests/bootstrap.go b/tests/bootstrap.go new file mode 100644 index 0000000..b40a2ab --- /dev/null +++ b/tests/bootstrap.go @@ -0,0 +1,85 @@ +package tests + +import ( + "fmt" + "github.com/goal-web/application" + "github.com/goal-web/config" + "github.com/goal-web/contracts" + "github.com/goal-web/database" + "github.com/goal-web/events" + "github.com/goal-web/supports/utils" + "io/ioutil" + "os" +) + +func getApp(path string) contracts.Application { + env := "testing" + app := application.Singleton() + app.Instance("path", path) + + // 设置异常处理器 + app.Singleton("exceptions.handler", func() contracts.ExceptionHandler { + return DefaultExceptionHandler{} + }) + + app.RegisterServices( + &config.ServiceProvider{ + Env: env, + Paths: []string{path}, + Sep: "=", + ConfigProviders: map[string]config.Provider{ + "database": func(env contracts.Env) interface{} { + return database.Config{ + Default: utils.StringOr(env.GetString("db.connection"), "mysql"), + Connections: map[string]contracts.Fields{ + "sqlite": { + "driver": "sqlite", + "database": env.GetString("sqlite.database"), + }, + "mysql": { + "driver": "mysql", + "host": env.GetString("db.host"), + "port": env.GetString("db.port"), + "database": env.GetString("db.database"), + "username": env.GetString("db.username"), + "password": env.GetString("db.password"), + "unix_socket": env.GetString("db.unix_socket"), + "charset": utils.StringOr(env.GetString("db.charset"), "utf8mb4"), + "collation": utils.StringOr(env.GetString("db.collation"), "utf8mb4_unicode_ci"), + "prefix": env.GetString("db.prefix"), + "strict": env.GetBool("db.struct"), + "max_connections": env.GetInt("db.max_connections"), + "max_idles": env.GetInt("db.max_idles"), + }, + "pgsql": { + "driver": "postgres", + "host": env.GetString("db.pgsql.host"), + "port": env.GetString("db.pgsql.port"), + "database": env.GetString("db.pgsql.database"), + "username": env.GetString("db.pgsql.username"), + "password": env.GetString("db.pgsql.password"), + "charset": utils.StringOr(env.GetString("db.pgsql.charset"), "utf8mb4"), + "prefix": env.GetString("db.pgsql.prefix"), + "schema": utils.StringOr(env.GetString("db.pgsql.schema"), "public"), + "sslmode": utils.StringOr(env.GetString("db.pgsql.sslmode"), "disable"), + "max_connections": env.GetInt("db.pgsql.max_connections"), + "max_idles": env.GetInt("db.pgsql.max_idles"), + }, + }, + } + }, + }, + }, + events.ServiceProvider{}, + &database.ServiceProvider{}, + ) + + pidPath := path + "/goal.pid" + // 写入 pid 文件 + _ = ioutil.WriteFile(pidPath, []byte(fmt.Sprintf("%d", os.Getpid())), os.ModePerm) + + go func() { + _ = app.Start() + }() + return app +} diff --git a/tests/database_test.go b/tests/database_test.go new file mode 100644 index 0000000..fdcab2b --- /dev/null +++ b/tests/database_test.go @@ -0,0 +1,113 @@ +package tests + +import ( + "fmt" + "github.com/goal-web/contracts" + "github.com/goal-web/database/table" + "github.com/goal-web/supports/class" + "github.com/stretchr/testify/assert" + "testing" +) + +func getQuery(name string) contracts.QueryBuilder { + getApp("/Users/qbhy/project/go/goal-web/goal/database/tests") + return table.WithConnection(name, "sqlite") +} +func userModel() contracts.QueryBuilder { + getApp("/Users/qbhy/project/go/goal-web/goal/database/tests") + return UserModel().SetConnection("sqlite") +} + +func TestTableCreate(t *testing.T) { + user := getQuery("users").Create(contracts.Fields{ + "name": "qbhy", + }).(contracts.Fields) + + fmt.Println(user) + assert.True(t, user["id"].(int64) > 0) +} + +func TestTableSelect(t *testing.T) { + + users := getQuery("users").Get() + + fmt.Println(users) + + users.Map(func(user contracts.Fields) { + fmt.Println(user, user["id"]) + }) + + assert.True(t, users != nil) +} + +func TestTableQuery(t *testing.T) { + + getQuery("users").Delete() + + user := getQuery("users").Create(contracts.Fields{ + "name": "qbhy", + }).(contracts.Fields) + + fmt.Println(user) + userId := user["id"].(int64) + // 判断插入是否成功 + assert.True(t, userId > 0) + + // 获取数据总量 + assert.True(t, getQuery("users").Count() == 1) + + // 修改数据 + num := getQuery("users").Where("name", "qbhy").Update(contracts.Fields{ + "name": "goal", + }) + assert.True(t, num == 1) + // 判断修改后的数据 + user = getQuery("users").Where("name", "goal").First().(contracts.Fields) + + err := getQuery("users").Chunk(10, func(collection contracts.Collection, page int) error { + assert.True(t, collection.Len() == 1) + fmt.Println(collection.ToJson()) + return nil + }) + + assert.Nil(t, err) + + assert.True(t, user["id"] == userId) + assert.True(t, user["name"] == "goal") + assert.True(t, getQuery("users").Find(userId).(contracts.Fields)["id"] == userId) + assert.True(t, getQuery("users").Where("id", userId).Delete() == 1) + assert.Nil(t, getQuery("users").Find(userId)) +} + +// 定义 class +var UserClass = class.Make(new(User1)) + +// 定义结构体 +type User1 struct { + Id int64 `json:"id"` + NickName string `json:"name"` +} + +// 定义模型 +func UserModel() *table.Table { + return table.Model(UserClass, "users") +} + +func TestModel(t *testing.T) { + user := userModel().Create(contracts.Fields{ + "name": "qbhy", + }).(User1) + + fmt.Println("创建后返回模型", user) + + fmt.Println("用table查询:", + getQuery("users").Get().ToJson()) // query 返回 Collection + + fmt.Println(userModel(). // model 返回 Collection + Get(). + Map(func(user User1) { + fmt.Println("id:", user.Id) + }).ToJson()) + + fmt.Println(userModel().Where("id", ">", 0).Delete()) +} diff --git a/tests/exception_hanlder.go b/tests/exception_hanlder.go new file mode 100644 index 0000000..696df81 --- /dev/null +++ b/tests/exception_hanlder.go @@ -0,0 +1,29 @@ +package tests + +import ( + "github.com/goal-web/contracts" + "github.com/goal-web/supports/logs" + "github.com/goal-web/supports/utils" + "reflect" +) + +type DefaultExceptionHandler struct { + dontReportExceptions []reflect.Type +} + +func NewDefaultHandler(dontReportExceptions []contracts.Exception) DefaultExceptionHandler { + return DefaultExceptionHandler{utils.ConvertToTypes(dontReportExceptions)} +} + +func (handler DefaultExceptionHandler) Handle(exception contracts.Exception) { + logs.WithException(exception). + WithField("exception", reflect.TypeOf(exception).String()). + Error("DefaultExceptionHandler") +} + +func (handler DefaultExceptionHandler) Report(exception contracts.Exception) { +} + +func (handler DefaultExceptionHandler) ShouldReport(exception contracts.Exception) bool { + return !utils.IsInstanceIn(exception, handler.dontReportExceptions...) +} diff --git a/tests/goal.pid b/tests/goal.pid new file mode 100755 index 0000000..409ac73 --- /dev/null +++ b/tests/goal.pid @@ -0,0 +1 @@ +42939 \ No newline at end of file diff --git a/tx/tx.go b/tx/tx.go new file mode 100644 index 0000000..900243e --- /dev/null +++ b/tx/tx.go @@ -0,0 +1,43 @@ +package tx + +import ( + "github.com/goal-web/collection" + "github.com/goal-web/contracts" + "github.com/goal-web/database/events" + "github.com/goal-web/database/table" + "github.com/jmoiron/sqlx" +) + +type Tx struct { + *sqlx.Tx + events contracts.EventDispatcher +} + +func New(tx *sqlx.Tx, events contracts.EventDispatcher) *Tx { + return &Tx{ + Tx: tx, + events: events, + } +} + +func (this *Tx) Query(query string, args ...interface{}) (results contracts.Collection, err error) { + defer func() { + if err == nil { + this.events.Dispatch(&events.QueryExecuted{Sql: query, Bindings: args}) + } + }() + rows, err := this.Tx.Query(query, args...) + + if err != nil { + return nil, err + } + + data, err := table.ParseRows(rows) + results = collection.FromFieldsSlice(data) + + return +} + +func (this *Tx) Exec(query string, args ...interface{}) (contracts.Result, error) { + return this.Tx.Exec(query, args...) +}