diff --git a/executor/dummy_executor.go b/executor/dummy_executor.go new file mode 100644 index 0000000..ffc25e9 --- /dev/null +++ b/executor/dummy_executor.go @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2022, Xiongfa Li. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package executor + +import ( + "context" + "github.com/acmestack/gobatis/common" + "github.com/acmestack/gobatis/reflection" +) + +type dummyExecutor struct{} + +func NewDummyExecutor() dummyExecutor { + return dummyExecutor{} +} + +func (e dummyExecutor) Close(rollback bool) { + +} + +func (e dummyExecutor) Query(ctx context.Context, result reflection.Object, sql string, params ...interface{}) error { + return nil +} + +func (e dummyExecutor) Exec(ctx context.Context, sql string, params ...interface{}) (common.Result, error) { + return nil, nil +} + +func (e dummyExecutor) Begin() error { + return nil +} + +func (e dummyExecutor) Commit(require bool) error { + return nil +} + +func (e dummyExecutor) Rollback(require bool) error { + return nil +} diff --git a/extension/executor.go b/extension/executor.go new file mode 100644 index 0000000..d2c4245 --- /dev/null +++ b/extension/executor.go @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2022, Xiongfa Li. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package extension + +import ( + "context" + "github.com/acmestack/gobatis/common" + "github.com/acmestack/gobatis/executor" + "github.com/acmestack/gobatis/reflection" + "github.com/xfali/aop" + "runtime" + "strings" +) + +type executorEx struct { + proxy aop.Proxy +} + +func NewExecutorExtension(e executor.Executor) *executorEx { + ret := &executorEx{ + proxy: aop.New(e), + } + return ret +} + +func (e *executorEx) Extend(pointCut aop.PointCut, advice aop.Advice) Extension { + e.proxy.AddAdvisor(pointCut, advice) + return e +} + +func (e *executorEx) Close(rollback bool) { + e.proxy.Call(caller(), rollback) +} + +func (e *executorEx) Query(ctx context.Context, result reflection.Object, sql string, params ...interface{}) error { + r, err := e.proxy.Call(caller(), ctx, result, sql, params) + if err != nil { + return err + } + if r[0] == nil { + return nil + } + return r[0].(error) +} + +func (e *executorEx) Exec(ctx context.Context, sql string, params ...interface{}) (res common.Result, rerr error) { + r, err := e.proxy.Call(caller(), ctx, sql, params) + if err != nil { + return nil, err + } + if r[0] != nil { + res = r[0].(common.Result) + } + if r[1] != nil { + rerr = r[1].(error) + } + return +} + +func (e *executorEx) Begin() error { + r, err := e.proxy.Call(caller()) + if err != nil { + return err + } + if r[0] == nil { + return nil + } + return r[0].(error) +} + +func (e *executorEx) Commit(require bool) error { + r, err := e.proxy.Call(caller(), require) + if err != nil { + return err + } + if r[0] == nil { + return nil + } + return r[0].(error) +} + +func (e *executorEx) Rollback(require bool) error { + r, err := e.proxy.Call(caller(), require) + if err != nil { + return err + } + if r[0] == nil { + return nil + } + return r[0].(error) +} + +func caller() string { + pc := make([]uintptr, 1) + runtime.Callers(2, pc) + f := runtime.FuncForPC(pc[0]) + name := f.Name() + if i := strings.LastIndex(name, "."); i != -1 { + return name[i+1:] + } + return name +} diff --git a/extension/extension.go b/extension/extension.go new file mode 100644 index 0000000..12ecd74 --- /dev/null +++ b/extension/extension.go @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2022, Xiongfa Li. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package extension + +import "github.com/xfali/aop" + +type Extension interface { + // Extend add extension point + // pointCut: PointCut for extension + // advice: implementation of extension points + Extend(pointCut aop.PointCut, advice aop.Advice) Extension +} diff --git a/extension/extension_test.go b/extension/extension_test.go new file mode 100644 index 0000000..a4e06cd --- /dev/null +++ b/extension/extension_test.go @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2022, Xiongfa Li. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package extension + +import ( + "context" + "fmt" + "github.com/acmestack/gobatis/executor" + "github.com/acmestack/gobatis/reflection" + "github.com/xfali/aop" + "testing" +) + +func TestExecutorExtension(t *testing.T) { + ex := NewExecutorExtension(executor.NewDummyExecutor()) + ex.Extend(aop.PointCutRegExp("", "(.*?)", nil, nil), func(invocation aop.Invocation, params []interface{}) (ret []interface{}) { + t.Log("params: ", fmt.Sprintln(params...)) + ret = invocation.Invoke(params) + if len(ret) > 0 { + t.Log("result: ", fmt.Sprintln(ret...)) + } else { + t.Log("result: nil") + } + return ret + }) + var i int = 0 + result, err := reflection.GetObjectInfo(&i) + if err != nil { + t.Fatal(err) + } + ex.Begin() + ex.Exec(context.Background(), "update tbl where id = ? and name = ?", "hello", "world") + ex.Query(context.Background(), result, "select * from tbl where id = ? and name = ?", "hello", "world") + ex.Commit(false) + ex.Rollback(false) + ex.Close(false) +} diff --git a/faccreator.go b/faccreator.go index bf4d8e5..d2ea44f 100644 --- a/faccreator.go +++ b/faccreator.go @@ -82,3 +82,9 @@ func SetDataSource(ds datasource.DataSource) FacOpt { }) } } + +func AddExtensions(advisors ...factory.Advisor) FacOpt { + return func(f *factory.DefaultFactory) { + f.Extensions = advisors + } +} diff --git a/factory/default_factory.go b/factory/default_factory.go index a180662..6bc13d3 100644 --- a/factory/default_factory.go +++ b/factory/default_factory.go @@ -19,6 +19,8 @@ package factory import ( "database/sql" + "github.com/acmestack/gobatis/extension" + "github.com/xfali/aop" "sync" "time" @@ -30,6 +32,14 @@ import ( "github.com/acmestack/gobatis/transaction" ) +type Advisor struct { + // PointCut for extension + PointCut aop.PointCut + + // Implementation of extension points + Advice aop.Advice +} + type DefaultFactory struct { MaxConn int MaxIdleConn int @@ -38,6 +48,8 @@ type DefaultFactory struct { DataSource datasource.DataSource + Extensions []Advisor + db *sql.DB mutex sync.Mutex } @@ -83,6 +95,13 @@ func (factory *DefaultFactory) CreateTransaction() transaction.Transaction { } func (factory *DefaultFactory) CreateExecutor(transaction transaction.Transaction) executor.Executor { + if factory.Extensions != nil { + executorEx := extension.NewExecutorExtension(executor.NewSimpleExecutor(transaction)) + for _, ex := range factory.Extensions { + executorEx.Extend(ex.PointCut, ex.Advice) + } + return executorEx + } return executor.NewSimpleExecutor(transaction) } diff --git a/go.mod b/go.mod index 6175fa9..da922fc 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/acmestack/gobatis -go 1.18 +go 1.14 require ( github.com/Masterminds/sprig/v3 v3.2.2 @@ -20,5 +20,6 @@ require ( github.com/mitchellh/reflectwalk v1.0.0 // indirect github.com/shopspring/decimal v1.2.0 // indirect github.com/spf13/cast v1.3.1 // indirect + github.com/xfali/aop v0.0.0-20220808144352-257bcdfe05eb // indirect golang.org/x/crypto v0.0.0-20200414173820-0848c9571904 // indirect ) diff --git a/go.sum b/go.sum index 6e7c9ab..3648845 100644 --- a/go.sum +++ b/go.sum @@ -33,6 +33,8 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/xfali/aop v0.0.0-20220808144352-257bcdfe05eb h1:cVJCRe6sPb2Fi+6mB8Yr5yutSNqPlAYqtX6Mb+VxPiE= +github.com/xfali/aop v0.0.0-20220808144352-257bcdfe05eb/go.mod h1:XcPlImFWgwSUaR34iDPMOeClVFV+9nvuhStxKrcvl84= github.com/xfali/loadbalance v0.0.1 h1:UVOuuDipJ740KyTaBUxTeW6UEC+fukiQCOn/5YQgexA= github.com/xfali/loadbalance v0.0.1/go.mod h1:yrzHHRZMdt2wBpLnBDeU2zbjnRYeNvUWV6sq6+3KVG0= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=