yjp 1 жил өмнө
parent
commit
66bf36a1fe

+ 543 - 12
README.md

@@ -1,10 +1,68 @@
 # 数据服务SDK使用文档
 
-## 入门
+数据服务设计意图是要统一管理各类数据源,数据服务SDK主要用于对接数据服务,目前仅实现了对数据库中PostgresSQL的支持。
 
-数据服务SDK主要用于对接数据服务,实现数据的增删改查操作,使用sdk前,需要通过datactl在数据服务中预先创建好对应的Namespace,DataSource和
+## 数据库操作
 
-DataContainer资源
+### 入门
+
+可以通过sql包实现数据库的增删改查操作,使用sdk前,需要通过datactl在数据服务中预先创建好对应的Namespace,DataSource和
+
+DataContainer资源,demo使用的资源yaml如下所示:
+
+```yaml
+kind: Namespace
+spec:
+  name: ns-sdk-demo
+
+---
+
+kind: DataSource
+spec:
+  type: database
+  namespace: ns-sdk-demo
+  name: ds-sdk-demo
+  spec:
+    type: postgres
+    user_name: test
+    password: "123456"
+    address: "10.0.0.84"
+    port: "30432"
+    database: test
+    max_connections: 40
+    max_idle_connections: 10
+
+---
+
+kind: DataContainer
+spec:
+  namespace: ns-sdk-demo
+  data_source: ds-sdk-demo
+  name: dc-sdk-demo
+  spec:
+    table_name: test.classes
+    columns:
+      - name: id
+        type: varchar(32)
+        comment: id
+        primary_key: true
+      - name: name
+        type: varchar(128)
+        comment: 班名
+        not_null: true
+      - name: student_num
+        type: integer
+        comment: 学生数量
+        default: 60
+      - name: created_time
+        type: "timestamp with time zone"
+        comment: 创建时间
+        not_null: true
+      - name: last_updated_time
+        type: "timestamp with time zone"
+        comment: 最近更新时间
+        not_null: true
+```
 
 ```go
 package main
@@ -13,15 +71,16 @@ import (
 	"fmt"
 	"git.sxidc.com/service-supports/ds-sdk/sdk"
 	"git.sxidc.com/service-supports/ds-sdk/sdk/raw_sql_tpl"
+	"git.sxidc.com/service-supports/ds-sdk/sql"
 	"strconv"
 	"time"
 )
 
 const (
-	token      = "IpTTwAQweh/BP51fz5CzWKQFaXHvZe6ewvk6yOcAOkU="
-	address    = "localhost"
-	httpPort   = "10000"
-	grpcPort   = "10001"
+	token      = "数据服务管理员分配的token"
+	address    = "数据服务地址"
+	httpPort   = "数据服务http端口"
+	grpcPort   = "数据服务grpc端口"
 	namespace  = "ns-sdk-demo"
 	dataSource = "ds-sdk-demo"
 	tableName  = "test.classes"
@@ -61,12 +120,12 @@ func main() {
 		StudentNum: 10,
 	}
 
-	err = sdk.InsertEntity(sdk.GetInstance(), tableName, class)
+	err = sql.InsertEntity(sdk.GetInstance(), tableName, class)
 	if err != nil {
 		panic(err)
 	}
 
-	tableRow, err := sdk.QueryOne(sdk.GetInstance(), &raw_sql_tpl.QueryOneExecuteParams{
+	tableRow, err := sql.QueryOne(sdk.GetInstance(), &raw_sql_tpl.QueryOneExecuteParams{
 		TableName:     tableName,
 		SelectColumns: []string{"id", "name", "created_time", "last_updated_time"},
 		Conditions:    raw_sql_tpl.NewConditions().Equal("id", class.ID),
@@ -78,16 +137,488 @@ func main() {
 	fmt.Println(tableRow)
 
 	classInfo := new(ClassInfo)
-	err = sdk.ParseSqlResults(tableRow, classInfo)
+	err = sql.ParseSqlResults(tableRow, classInfo)
 	if err != nil {
 		panic(err)
 	}
 
 	fmt.Println(classInfo)
 
-	err = sdk.DeleteEntity(sdk.GetInstance(), tableName, class)
+	err = sql.DeleteEntity(sdk.GetInstance(), tableName, class)
+	if err != nil {
+		panic(err)
+	}
+}
+```
+
+代码中定义了需要用到的各类常量以及结构,Class结构作为数据库持久化的实体使用,ClassInfo则是作为查询结果的接受实体使用。
+
+sdk会利用Class字段,将Class字段名称变为蛇形后作为表的列名使用,如果字段名称转换后是id(字段名称是ID,Id等),就会被作为主键使用,还可以通过sdk的
+sqlmapping Tag对字段含义进行扩充,稍后会对sqlmapping Tag进行说明。对命名为CreatedTime和LastUpdatedTime的字段,在没有赋值的情况下,sdk会自动添加创建时间和更新时间。
+
+Demo中的ClassInfo并不是必须的,sdk所有数据查询命令返回的是map[string]any或者[]map[string]any类型的数据,其中map的key是列名,value是列值,
+sdk对mapstructure包进行了封装,如果程序需要,可以将查询结果转到一个添加了mapstructure Tag的结构上。mapstructure Tag的使用可以查看文档:
+
+https://pkg.go.dev/github.com/mitchellh/mapstructure
+
+Demo的main函数中,首先初始化了sdk实例,然后执行了插入操作,单查询操作和删除操作。
+
+### 事务的使用
+
+sdk提供了事务操作,可以参考下面的代码:
+
+```go
+package main
+
+import (
+	"git.sxidc.com/service-supports/ds-sdk/sdk"
+	"git.sxidc.com/service-supports/ds-sdk/sql"
+	"strconv"
+	"time"
+)
+
+const (
+	token      = "IpTTwAQweh/BP51fz5CzWKQFaXHvZe6ewvk6yOcAOkU="
+	address    = "localhost"
+	httpPort   = "10000"
+	grpcPort   = "10001"
+	namespace  = "ns-sdk-demo"
+	dataSource = "ds-sdk-demo"
+	tableName  = "test.classes"
+)
+
+type Class struct {
+	ID              string
+	Name            string
+	StudentNum      int
+	CreatedTime     time.Time
+	LastUpdatedTime time.Time
+}
+
+func main() {
+	err := sdk.InitInstance(token, address, httpPort, grpcPort, namespace, dataSource)
+	if err != nil {
+		panic(err)
+	}
+
+	defer func() {
+		err := sdk.DestroyInstance()
+		if err != nil {
+			panic(err)
+		}
+	}()
+
+	class := &Class{
+		ID:         "id" + strconv.Itoa(time.Now().Nanosecond()),
+		Name:       "test",
+		StudentNum: 10,
+	}
+
+	err = sdk.GetInstance().Transaction(func(tx *sdk.Transaction) error {
+		err = sql.InsertEntity(tx, tableName, class)
+		if err != nil {
+			panic(err)
+		}
+
+		err = sql.DeleteEntity(tx, tableName, class)
+		if err != nil {
+			panic(err)
+		}
+
+		return nil
+	})
 	if err != nil {
 		panic(err)
 	}
 }
-```
+```
+
+使用事务仅需要调用sdk实例的Transaction函数即可,在事务函数中,将sql包函数的第一个参数(Executor接口)替换为tx即可,这样也方便于实现非事务操作向事务操作的转换,
+如果在事务函数中需要查询,则可以继续使用sdk实例进行查询当前数据库中的数据(不过查询不到事务前面步骤的结果,这里为了防止不必要错误的发生,tx没有提供查询的方法)。
+
+### sqlmapping Tag
+
+可以使用sqlmapping Tag对实体字段进行更多扩展,下面的例子使用了sqlmapping Tag:
+
+```go
+type Class struct {
+	ID              string
+	Name            string `sqlmapping:"updateClear;"`
+	StudentNum      int    `sqlmapping:"column:student_num;notUpdate;"`
+	CreatedTime     *time.Time
+	LastUpdatedTime time.Time
+	Ignored         string `sqlmapping:"-"`
+}
+```
+
+对Name字段添加了updateClear,这里允许将数据库的name列更新为零值(默认如果该字段是零值,则不执行更新),对StudentNum字段添加了
+column和notUpdate两个sqlmapping Tag,column指定了该字段对应的数据库列,notUpdate则禁止了对student_num列的更新。Ignored字段则通过sqlmapping的
+'-' Tag标定为不进行持久化的字段。
+
+下面是sqlmapping Tag支持的所有Tag:
+
+| Tag         | 说明                                                                            |
+|-------------|-------------------------------------------------------------------------------|
+| -           | 忽略该字段,不进行持久化(不对应任何数据库表列)                                                      |
+| column      | 显式指定该字段对应的数据库表列,如column:foo                                                   |
+| key         | 该列是否作为逻辑键(实际到底哪个字段为键,是由DataContainer定义确定的)使用,如果一个结构的多个字段使用了key,这几个字段将被作为联合键使用 |
+| notUpdate   | 不对该列进行更新操作                                                                    |
+| updateClear | 允许将该列清空为零值                                                                    |
+
+### SQL语句执行
+
+sdk还提供了直接执行SQL语句的函数和方法,主要有两个:ExecuteRawSql和ExecuteSql。
+
+ExecuteRawSql执行一条SQL语句,该语句可以用Golang的模板编写,执行时可以通过传递执行参数渲染模板;
+ExecuteSql则执行一个SQL资源(提前在数据服务中创建),也可以传递模板参数,还可以使用每一步SQL执行语句后的结果。
+
+### sql包说明
+
+```go
+type SqlExecutor interface {
+	ExecuteRawSql(sql string, executeParams map[string]any) ([]map[string]any, error)
+	ExecuteSql(name string, executeParams map[string]any) ([]map[string]any, error)
+}
+```
+
+SQL执行器接口定义,在sdk中,SDK和事务都实现了该接口
+
+```go
+func InsertEntity[T any](executor SqlExecutor, tableName string, e T) error
+```
+
+通过实体插入数据
+
+参数:
+
+1. executor: 执行器
+2. tableName: 要操作的表名
+3. e: 实体,需要持久化的字段应该都赋值
+
+返回值: 
+
+是否出现错误
+
+```go
+func DeleteEntity[T any](executor SqlExecutor, tableName string, e T) error
+```
+
+通过实体删除数据
+
+参数:
+
+1. executor: 执行器
+2. tableName: 要操作的表名
+3. e: 实体,仅赋值key字段即可
+
+返回值:
+
+是否出现错误
+
+```go
+func UpdateEntity[T any](executor SqlExecutor, tableName string, e T) error
+```
+
+通过实体更新数据
+
+参数:
+
+1. executor: 执行器
+2. tableName: 要操作的表名
+3. e: 实体,仅赋值key字段和需要更新的字段即可
+
+返回值:
+
+是否出现错误
+
+```go
+func Insert(executor SqlExecutor, executeParams *raw_sql_tpl.InsertExecuteParams) error
+```
+
+通过简化的INSERT语句插入数据,简化的INSERT语句:
+
+```go
+const InsertTpl = `
+INSERT INTO
+    {{ .table_name }} ({{ .columns | join "," }})
+VALUES
+	({{ .values | join "," }})
+`
+```
+
+参数:
+
+1. executor: 执行器
+2. executeParams: 插入操作需要的参数,如下所示
+
+```go
+type InsertExecuteParams struct {
+	TableName string
+	*TableRows
+}
+```
+
+TableRows可以通过rat_sql_tpl中的NewTableRows()函数创建,其中Add方法可以用来添加列
+
+返回值:
+
+是否出现错误
+
+```go
+func Delete(executor SqlExecutor, executeParams *raw_sql_tpl.DeleteExecuteParams) error
+```
+
+通过简化的DELETE语句删除数据,简化的DELETE语句:
+
+```go
+const DeleteTpl = `
+DELETE FROM
+	{{ .table_name }}
+WHERE
+	{{ range .conditions }} {{ . }} AND {{ end }} 1 = 1
+`
+```
+
+参数:
+
+1. executor: 执行器
+2. executeParams: 删除操作需要的参数,如下所示
+
+```go
+type DeleteExecuteParams struct {
+    TableName string
+    *Conditions
+}
+```
+
+Conditions可以通过rat_sql_tpl中的NewConditions()函数创建,其中有各种条件可供使用,如Equal,Like等
+
+返回值:
+
+是否出现错误
+
+```go
+func Update(executor SqlExecutor, executeParams *raw_sql_tpl.UpdateExecuteParams) error
+```
+
+通过简化的UPDATE语句更新数据,简化的DELETE语句:
+
+```go
+const UpdateTpl = `
+UPDATE
+	{{ .table_name }}
+SET
+	{{ .set_list | join "," }}
+WHERE
+	{{ range .conditions }} {{ . }} AND {{ end }} 1 = 1
+`
+```
+
+参数:
+
+1. executor: 执行器
+2. executeParams: 更新操作需要的参数,如下所示
+
+```go
+type UpdateExecuteParams struct {
+    TableName string
+    *TableRows
+    *Conditions
+}
+```
+
+TableRows可以通过rat_sql_tpl中的NewTableRows()函数创建,其中Add方法可以用来添加列
+
+Conditions可以通过rat_sql_tpl中的NewConditions()函数创建,其中有各种条件可供使用,如Equal,Like等
+
+返回值:
+
+是否出现错误
+
+```go
+func Query(executor SqlExecutor, executeParams *raw_sql_tpl.QueryExecuteParams) ([]map[string]any, int64, error)
+```
+
+通过简化的SELETE语句查询数据,简化的SELETE语句:
+
+```go
+const QueryTpl = `
+SELECT
+	{{ if .select_columns }}{{ .select_columns | join "," }}{{ else }}*{{ end }} 
+FROM
+	{{ .table_name }}
+WHERE
+	{{ range .conditions }} {{ . }} AND {{ end }} 1 = 1 
+{{ if .limit }}LIMIT {{ .limit }}{{ end }}
+{{ if .offset }}OFFSET {{ .offset }}{{ end }}
+`
+```
+
+参数:
+
+1. executor: 执行器
+2. executeParams: 查询操作需要的参数,如下所示
+
+```go
+type QueryExecuteParams struct {
+    TableName     string
+    SelectColumns []string
+    *Conditions
+    PageNo   int
+    PageSize int
+}
+```
+
+Conditions可以通过rat_sql_tpl中的NewConditions()函数创建,其中有各种条件可供使用,如Equal,Like等
+
+返回值:
+
+1. 查询结果
+2. 总数
+3. 是否出现错误
+
+```go
+func QueryOne(executor SqlExecutor, executeParams *raw_sql_tpl.QueryOneExecuteParams) (map[string]any, error)
+```
+
+通过简化的SELETE语句执行单查询
+
+参数:
+
+1. executor: 执行器
+2. executeParams: 查询操作需要的参数,如下所示
+
+```go
+type QueryOneExecuteParams struct {
+    TableName     string
+    SelectColumns []string
+    *Conditions
+}
+```
+
+Conditions可以通过rat_sql_tpl中的NewConditions()函数创建,其中有各种条件可供使用,如Equal,Like等
+
+返回值:
+
+1. 查询结果
+2. 是否出现错误
+
+```go
+func Count(executor SqlExecutor, executeParams *raw_sql_tpl.CountExecuteParams) (int64, error)
+```
+
+通过简化的COUNT语句执行计数,简化的COUNT语句
+
+```go
+const CountTpl = `
+SELECT
+	COUNT(*)
+FROM
+	{{ .table_name }}
+WHERE
+	{{ range .conditions }} {{ . }} AND {{ end }} 1 = 1
+`
+```
+
+参数:
+
+1. executor: 执行器
+2. executeParams: 计数操作需要的参数,如下所示
+
+```go
+type CountExecuteParams struct {
+    TableName string
+    *Conditions
+}
+```
+
+Conditions可以通过rat_sql_tpl中的NewConditions()函数创建,其中有各种条件可供使用,如Equal,Like等
+
+返回值:
+
+1. 计数结果
+2. 是否出现错误
+
+```go
+func CheckExist(executor SqlExecutor, executeParams *raw_sql_tpl.CheckExistExecuteParams) (bool, error)
+```
+
+通过简化的COUNT语句执行存在性检查
+
+参数:
+
+1. executor: 执行器
+2. executeParams: 检查操作需要的参数,如下所示
+
+```go
+type CheckExistExecuteParams struct {
+    TableName string
+    *Conditions
+}
+```
+
+Conditions可以通过rat_sql_tpl中的NewConditions()函数创建,其中有各种条件可供使用,如Equal,Like等
+
+返回值:
+
+1. 是否存在的结果
+2. 是否出现错误
+
+```go
+func CheckHasOnlyOne(executor SqlExecutor, executeParams *raw_sql_tpl.CheckHasOnlyOneExecuteParams) (bool, error)
+```
+
+通过简化的COUNT语句执行唯一性检查
+
+参数:
+
+1. executor: 执行器
+2. executeParams: 检查操作需要的参数,如下所示
+
+```go
+type CheckHasOnlyOneExecuteParams struct {
+    TableName string
+    *Conditions
+}
+```
+
+Conditions可以通过rat_sql_tpl中的NewConditions()函数创建,其中有各种条件可供使用,如Equal,Like等
+
+返回值:
+
+1. 是否唯一的结果
+2. 是否出现错误
+
+```go
+func ExecuteRawSql(executor SqlExecutor, sql string, executeParams map[string]any) ([]map[string]any, error)
+```
+
+执行SQL语句
+
+参数:
+
+1. executor: 执行器
+2. sql: SQL语句,可以使用Golang模板语法编写
+2. executeParams: 检查操作需要的参数,如下所示
+
+返回值:
+
+1. 执行结果,对于写操作,结果为空,对于读操作,为读出的数据
+2. 是否出现错误
+
+```go
+func ExecuteSql(executor SqlExecutor, name string, executeParams map[string]any) ([]map[string]any, error)
+```
+
+执行SQL语句
+
+参数:
+
+1. executor: 执行器
+2. name: 数据服务的SQL资源名称
+2. executeParams: 检查操作需要的参数,如下所示
+
+返回值:
+
+1. 执行结果,对于写操作,结果为空,对于读操作,为读出的数据
+2. 是否出现错误

+ 2 - 1
sdk/raw_sql_tpl/raw_sql_tpl.go

@@ -152,7 +152,8 @@ SELECT
 FROM
 	{{ .table_name }}
 WHERE
-	{{ range .conditions }} {{ . }} AND {{ end }} 1 = 1`
+	{{ range .conditions }} {{ . }} AND {{ end }} 1 = 1
+`
 
 type CountExecuteParams struct {
 	TableName string

+ 2 - 0
test/resources.yaml

@@ -47,6 +47,8 @@ spec:
       - name: created_time
         type: "timestamp with time zone"
         comment: 创建时间
+        not_null: true
       - name: last_updated_time
         type: "timestamp with time zone"
         comment: 最近更新时间
+        not_null: true