2024-03-11 20:04:20 +08:00
package mssql
import (
"fmt"
"mayfly-go/internal/db/dbm/dbi"
"mayfly-go/pkg/errorx"
2024-03-21 03:35:18 +00:00
"mayfly-go/pkg/logx"
2024-03-11 20:04:20 +08:00
"mayfly-go/pkg/utils/anyx"
"mayfly-go/pkg/utils/collx"
2024-03-18 12:25:40 +08:00
"mayfly-go/pkg/utils/stringx"
2024-03-11 20:04:20 +08:00
"strings"
2024-03-21 17:15:52 +08:00
"github.com/may-fly/cast"
2024-03-11 20:04:20 +08:00
)
const (
2024-03-21 03:35:18 +00:00
MSSQL_META_FILE = "metasql/mssql_meta.sql"
MSSQL_DBS_KEY = "MSSQL_DBS"
MSSQL_DB_SCHEMAS_KEY = "MSSQL_DB_SCHEMAS"
MSSQL_TABLE_INFO_KEY = "MSSQL_TABLE_INFO"
MSSQL_INDEX_INFO_KEY = "MSSQL_INDEX_INFO"
MSSQL_COLUMN_MA_KEY = "MSSQL_COLUMN_MA"
2024-03-11 20:04:20 +08:00
)
type MssqlMetaData struct {
2024-03-15 13:31:53 +08:00
dbi . DefaultMetaData
2024-03-11 20:04:20 +08:00
dc * dbi . DbConn
}
func ( md * MssqlMetaData ) GetDbServer ( ) ( * dbi . DbServer , error ) {
_ , res , err := md . dc . Query ( "SELECT @@VERSION as version" )
if err != nil {
return nil , err
}
ds := & dbi . DbServer {
2024-03-21 17:15:52 +08:00
Version : cast . ToString ( res [ 0 ] [ "version" ] ) ,
2024-03-11 20:04:20 +08:00
}
return ds , nil
}
func ( md * MssqlMetaData ) GetDbNames ( ) ( [ ] string , error ) {
_ , res , err := md . dc . Query ( dbi . GetLocalSql ( MSSQL_META_FILE , MSSQL_DBS_KEY ) )
if err != nil {
return nil , err
}
databases := make ( [ ] string , 0 )
for _ , re := range res {
2024-03-21 17:15:52 +08:00
databases = append ( databases , cast . ToString ( re [ "dbname" ] ) )
2024-03-11 20:04:20 +08:00
}
return databases , nil
}
// 获取表基础元信息, 如表名等
2024-03-15 09:01:51 +00:00
func ( md * MssqlMetaData ) GetTables ( tableNames ... string ) ( [ ] dbi . Table , error ) {
meta := md . dc . GetMetaData ( )
schema := md . dc . Info . CurrentSchema ( )
names := strings . Join ( collx . ArrayMap [ string , string ] ( tableNames , func ( val string ) string {
return fmt . Sprintf ( "'%s'" , meta . RemoveQuote ( val ) )
} ) , "," )
var res [ ] map [ string ] any
var err error
2024-03-18 12:25:40 +08:00
sql , err := stringx . TemplateParse ( dbi . GetLocalSql ( MSSQL_META_FILE , MSSQL_TABLE_INFO_KEY ) , collx . M { "tableNames" : names } )
if err != nil {
return nil , err
2024-03-15 09:01:51 +00:00
}
2024-03-11 20:04:20 +08:00
2024-03-18 12:25:40 +08:00
_ , res , err = md . dc . Query ( sql , schema )
2024-03-11 20:04:20 +08:00
if err != nil {
return nil , err
}
tables := make ( [ ] dbi . Table , 0 )
for _ , re := range res {
tables = append ( tables , dbi . Table {
2024-03-21 17:15:52 +08:00
TableName : cast . ToString ( re [ "tableName" ] ) ,
TableComment : cast . ToString ( re [ "tableComment" ] ) ,
CreateTime : cast . ToString ( re [ "createTime" ] ) ,
TableRows : cast . ToInt ( re [ "tableRows" ] ) ,
DataLength : cast . ToInt64 ( re [ "dataLength" ] ) ,
IndexLength : cast . ToInt64 ( re [ "indexLength" ] ) ,
2024-03-11 20:04:20 +08:00
} )
}
return tables , nil
}
// 获取列元信息, 如列名等
func ( md * MssqlMetaData ) GetColumns ( tableNames ... string ) ( [ ] dbi . Column , error ) {
2024-03-15 09:01:51 +00:00
meta := md . dc . GetMetaData ( )
2024-03-26 21:46:03 +08:00
columnHelper := meta . GetColumnHelper ( )
2024-03-11 20:04:20 +08:00
tableName := strings . Join ( collx . ArrayMap [ string , string ] ( tableNames , func ( val string ) string {
2024-03-15 09:01:51 +00:00
return fmt . Sprintf ( "'%s'" , meta . RemoveQuote ( val ) )
2024-03-11 20:04:20 +08:00
} ) , "," )
_ , res , err := md . dc . Query ( fmt . Sprintf ( dbi . GetLocalSql ( MSSQL_META_FILE , MSSQL_COLUMN_MA_KEY ) , tableName ) , md . dc . Info . CurrentSchema ( ) )
if err != nil {
return nil , err
}
columns := make ( [ ] dbi . Column , 0 )
for _ , re := range res {
2024-03-21 03:35:18 +00:00
column := dbi . Column {
2024-03-11 20:04:20 +08:00
TableName : anyx . ToString ( re [ "TABLE_NAME" ] ) ,
ColumnName : anyx . ToString ( re [ "COLUMN_NAME" ] ) ,
2024-03-21 03:35:18 +00:00
DataType : dbi . ColumnDataType ( anyx . ToString ( re [ "DATA_TYPE" ] ) ) ,
2024-03-21 17:15:52 +08:00
CharMaxLength : cast . ToInt ( re [ "CHAR_MAX_LENGTH" ] ) ,
2024-03-11 20:04:20 +08:00
ColumnComment : anyx . ToString ( re [ "COLUMN_COMMENT" ] ) ,
2024-03-21 20:28:24 +08:00
Nullable : anyx . ToString ( re [ "NULLABLE" ] ) == "YES" ,
2024-03-21 17:15:52 +08:00
IsPrimaryKey : cast . ToInt ( re [ "IS_PRIMARY_KEY" ] ) == 1 ,
IsIdentity : cast . ToInt ( re [ "IS_IDENTITY" ] ) == 1 ,
ColumnDefault : cast . ToString ( re [ "COLUMN_DEFAULT" ] ) ,
NumPrecision : cast . ToInt ( re [ "NUM_PRECISION" ] ) ,
NumScale : cast . ToInt ( re [ "NUM_SCALE" ] ) ,
2024-03-21 03:35:18 +00:00
}
2024-03-26 21:46:03 +08:00
columnHelper . FixColumn ( & column )
2024-03-21 03:35:18 +00:00
columns = append ( columns , column )
2024-03-11 20:04:20 +08:00
}
return columns , nil
}
// 获取表主键字段名,不存在主键标识则默认第一个字段
func ( md * MssqlMetaData ) GetPrimaryKey ( tablename string ) ( string , error ) {
columns , err := md . GetColumns ( tablename )
if err != nil {
return "" , err
}
if len ( columns ) == 0 {
return "" , errorx . NewBiz ( "[%s] 表不存在" , tablename )
}
for _ , v := range columns {
if v . IsPrimaryKey {
return v . ColumnName , nil
}
}
return columns [ 0 ] . ColumnName , nil
}
2024-03-27 08:22:26 +08:00
// 需要收集唯一键涉及的字段,所以需要查询出带主键的索引
2024-03-11 20:04:20 +08:00
func ( md * MssqlMetaData ) getTableIndexWithPK ( tableName string ) ( [ ] dbi . Index , error ) {
_ , res , err := md . dc . Query ( dbi . GetLocalSql ( MSSQL_META_FILE , MSSQL_INDEX_INFO_KEY ) , md . dc . Info . CurrentSchema ( ) , tableName )
if err != nil {
return nil , err
}
indexs := make ( [ ] dbi . Index , 0 )
for _ , re := range res {
indexs = append ( indexs , dbi . Index {
2024-03-21 17:15:52 +08:00
IndexName : cast . ToString ( re [ "indexName" ] ) ,
ColumnName : cast . ToString ( re [ "columnName" ] ) ,
IndexType : cast . ToString ( re [ "indexType" ] ) ,
IndexComment : cast . ToString ( re [ "indexComment" ] ) ,
IsUnique : cast . ToInt ( re [ "isUnique" ] ) == 1 ,
SeqInIndex : cast . ToInt ( re [ "seqInIndex" ] ) ,
2024-03-26 09:05:28 +00:00
IsPrimaryKey : cast . ToInt ( re [ "isPrimaryKey" ] ) == 1 ,
2024-03-11 20:04:20 +08:00
} )
}
// 把查询结果以索引名分组,多个索引字段以逗号连接
result := make ( [ ] dbi . Index , 0 )
key := ""
for _ , v := range indexs {
// 当前的索引名
in := v . IndexName
if key == in {
// 索引字段已根据名称和字段顺序排序,故取最后一个即可
i := len ( result ) - 1
// 同索引字段以逗号连接
result [ i ] . ColumnName = result [ i ] . ColumnName + "," + v . ColumnName
} else {
key = in
result = append ( result , v )
}
}
return indexs , nil
}
// 获取表索引信息
func ( md * MssqlMetaData ) GetTableIndex ( tableName string ) ( [ ] dbi . Index , error ) {
indexs , _ := md . getTableIndexWithPK ( tableName )
result := make ( [ ] dbi . Index , 0 )
2024-03-26 09:05:28 +00:00
// 过滤掉主键索引
2024-03-11 20:04:20 +08:00
for _ , v := range indexs {
2024-03-26 09:05:28 +00:00
if v . IsPrimaryKey {
2024-03-11 20:04:20 +08:00
continue
}
2024-03-21 17:15:52 +08:00
result = append ( result , v )
2024-03-11 20:04:20 +08:00
}
return result , nil
}
2024-03-15 13:31:53 +08:00
func ( md * MssqlMetaData ) CopyTableDDL ( tableName string , newTableName string ) ( string , error ) {
2024-03-11 20:04:20 +08:00
if newTableName == "" {
newTableName = tableName
}
2024-03-15 09:01:51 +00:00
meta := md . dc . GetMetaData ( )
2024-03-11 20:04:20 +08:00
// 查询表名和表注释, 设置表注释
2024-03-21 03:35:18 +00:00
tbs , err := md . GetTables ( tableName )
if err != nil || len ( tbs ) < 1 {
logx . Errorf ( "获取表信息失败, %s" , tableName )
2024-03-11 20:04:20 +08:00
return "" , err
}
2024-03-21 03:35:18 +00:00
tabInfo := & dbi . Table {
2024-03-21 17:15:52 +08:00
TableName : newTableName ,
2024-03-21 03:35:18 +00:00
TableComment : tbs [ 0 ] . TableComment ,
2024-03-11 20:04:20 +08:00
}
// 查询列信息
columns , err := md . GetColumns ( tableName )
if err != nil {
2024-03-21 03:35:18 +00:00
logx . Errorf ( "获取列信息失败, %s" , tableName )
2024-03-11 20:04:20 +08:00
return "" , err
}
2024-03-21 03:35:18 +00:00
sqlArr := meta . GenerateTableDDL ( columns , * tabInfo , true )
2024-03-11 20:04:20 +08:00
2024-03-21 03:35:18 +00:00
// 设置索引
indexs , err := md . GetTableIndex ( tableName )
if err != nil {
logx . Errorf ( "获取索引信息失败, %s" , tableName )
return strings . Join ( sqlArr , ";" ) , err
}
sqlArr = append ( sqlArr , meta . GenerateIndexDDL ( indexs , * tabInfo ) ... )
return strings . Join ( sqlArr , ";" ) , nil
}
// 获取建索引ddl
func ( md * MssqlMetaData ) GenerateIndexDDL ( indexs [ ] dbi . Index , tableInfo dbi . Table ) [ ] string {
tbName := tableInfo . TableName
2024-03-26 09:05:28 +00:00
meta := md . dc . GetMetaData ( )
2024-03-21 03:35:18 +00:00
sqls := make ( [ ] string , 0 )
comments := make ( [ ] string , 0 )
for _ , index := range indexs {
unique := ""
if index . IsUnique {
unique = "unique"
2024-03-11 20:04:20 +08:00
}
2024-03-26 09:05:28 +00:00
// 取出列名,添加引号
cols := strings . Split ( index . ColumnName , "," )
colNames := make ( [ ] string , len ( cols ) )
for i , name := range cols {
colNames [ i ] = meta . QuoteIdentifier ( name )
}
2024-03-21 03:35:18 +00:00
2024-04-19 11:27:29 +00:00
sqls = append ( sqls , fmt . Sprintf ( "create %s NONCLUSTERED index %s on %s.%s(%s)" , unique , meta . QuoteIdentifier ( index . IndexName ) , meta . QuoteIdentifier ( md . dc . Info . CurrentSchema ( ) ) , meta . QuoteIdentifier ( tbName ) , strings . Join ( colNames , "," ) ) )
2024-03-21 03:35:18 +00:00
if index . IndexComment != "" {
2024-03-26 09:05:28 +00:00
comment := meta . QuoteEscape ( index . IndexComment )
comments = append ( comments , fmt . Sprintf ( "EXECUTE sp_addextendedproperty N'MS_Description', N'%s', N'SCHEMA', N'%s', N'TABLE', N'%s', N'INDEX', N'%s'" , comment , md . dc . Info . CurrentSchema ( ) , tbName , index . IndexName ) )
2024-03-11 20:04:20 +08:00
}
2024-03-21 03:35:18 +00:00
}
2024-03-21 17:15:52 +08:00
if len ( comments ) > 0 {
sqls = append ( sqls , comments ... )
}
2024-03-21 03:35:18 +00:00
return sqls
}
func ( md * MssqlMetaData ) genColumnBasicSql ( column dbi . Column ) string {
meta := md . dc . GetMetaData ( )
colName := meta . QuoteIdentifier ( column . ColumnName )
dataType := string ( column . DataType )
incr := ""
if column . IsIdentity {
incr = " IDENTITY(1,1)"
}
nullAble := ""
2024-03-21 20:28:24 +08:00
if ! column . Nullable {
2024-03-21 03:35:18 +00:00
nullAble = " NOT NULL"
}
defVal := "" // 默认值需要判断引号,如函数是不需要引号的 // 为了防止跨源函数不支持 当默认值是函数时,不需要设置默认值
if column . ColumnDefault != "" && ! strings . Contains ( column . ColumnDefault , "(" ) {
// 哪些字段类型默认值需要加引号
mark := false
if collx . ArrayAnyMatches ( [ ] string { "char" , "text" , "date" , "time" , "lob" } , dataType ) {
// 当数据类型是日期时间,默认值是日期时间函数时,默认值不需要引号
if collx . ArrayAnyMatches ( [ ] string { "date" , "time" } , strings . ToLower ( dataType ) ) &&
collx . ArrayAnyMatches ( [ ] string { "DATE" , "TIME" } , strings . ToUpper ( column . ColumnDefault ) ) {
mark = false
} else {
mark = true
}
2024-03-11 20:04:20 +08:00
}
2024-03-21 03:35:18 +00:00
if mark {
defVal = fmt . Sprintf ( " DEFAULT '%s'" , column . ColumnDefault )
} else {
defVal = fmt . Sprintf ( " DEFAULT %s" , column . ColumnDefault )
2024-03-11 20:04:20 +08:00
}
2024-03-21 03:35:18 +00:00
}
2024-03-26 09:05:28 +00:00
columnSql := fmt . Sprintf ( " %s %s%s%s%s" , colName , column . GetColumnType ( ) , incr , nullAble , defVal )
2024-03-21 03:35:18 +00:00
return columnSql
}
// 获取建表ddl
func ( md * MssqlMetaData ) GenerateTableDDL ( columns [ ] dbi . Column , tableInfo dbi . Table , dropBeforeCreate bool ) [ ] string {
tbName := tableInfo . TableName
2024-04-12 07:53:42 +00:00
schemaName := md . dc . Info . CurrentSchema ( )
2024-03-21 03:35:18 +00:00
meta := md . dc . GetMetaData ( )
sqlArr := make ( [ ] string , 0 )
// 删除表
if dropBeforeCreate {
2024-04-12 07:53:42 +00:00
sqlArr = append ( sqlArr , fmt . Sprintf ( "DROP TABLE IF EXISTS %s.%s" , meta . QuoteIdentifier ( schemaName ) , meta . QuoteIdentifier ( tbName ) ) )
2024-03-21 03:35:18 +00:00
}
// 组装建表语句
2024-04-12 07:53:42 +00:00
createSql := fmt . Sprintf ( "CREATE TABLE %s.%s (\n" , meta . QuoteIdentifier ( schemaName ) , meta . QuoteIdentifier ( tbName ) )
2024-03-21 03:35:18 +00:00
fields := make ( [ ] string , 0 )
pks := make ( [ ] string , 0 )
columnComments := make ( [ ] string , 0 )
for _ , column := range columns {
if column . IsPrimaryKey {
pks = append ( pks , meta . QuoteIdentifier ( column . ColumnName ) )
}
fields = append ( fields , md . genColumnBasicSql ( column ) )
commentTmp := "EXECUTE sp_addextendedproperty N'MS_Description', N'%s', N'SCHEMA', N'%s', N'TABLE', N'%s', N'COLUMN', N'%s'"
// 防止注释内含有特殊字符串导致sql出错
if column . ColumnComment != "" {
2024-03-26 09:05:28 +00:00
comment := meta . QuoteEscape ( column . ColumnComment )
2024-03-21 03:35:18 +00:00
columnComments = append ( columnComments , fmt . Sprintf ( commentTmp , comment , md . dc . Info . CurrentSchema ( ) , tbName , column . ColumnName ) )
2024-03-11 20:04:20 +08:00
}
}
2024-03-21 03:35:18 +00:00
// create
createSql += strings . Join ( fields , ",\n" )
2024-03-11 20:04:20 +08:00
if len ( pks ) > 0 {
2024-03-21 03:35:18 +00:00
createSql += fmt . Sprintf ( ", \n PRIMARY KEY CLUSTERED (%s)" , strings . Join ( pks , "," ) )
2024-03-11 20:04:20 +08:00
}
2024-03-21 03:35:18 +00:00
createSql += "\n)"
2024-03-11 20:04:20 +08:00
2024-03-21 03:35:18 +00:00
// comment
tableCommentSql := ""
if tableInfo . TableComment != "" {
commentTmp := "EXECUTE sp_addextendedproperty N'MS_Description', N'%s', N'SCHEMA', N'%s', N'TABLE', N'%s'"
2024-03-26 09:05:28 +00:00
tableCommentSql = fmt . Sprintf ( commentTmp , meta . QuoteEscape ( tableInfo . TableComment ) , md . dc . Info . CurrentSchema ( ) , tbName )
2024-03-11 20:04:20 +08:00
}
2024-03-21 03:35:18 +00:00
sqlArr = append ( sqlArr , createSql )
if tableCommentSql != "" {
sqlArr = append ( sqlArr , tableCommentSql )
2024-03-11 20:04:20 +08:00
}
2024-03-21 03:35:18 +00:00
if len ( columnComments ) > 0 {
sqlArr = append ( sqlArr , columnComments ... )
2024-03-11 20:04:20 +08:00
}
2024-03-21 03:35:18 +00:00
return sqlArr
2024-03-11 20:04:20 +08:00
}
// 获取建表ddl
2024-03-26 09:05:28 +00:00
func ( md * MssqlMetaData ) GetTableDDL ( tableName string , dropBeforeCreate bool ) ( string , error ) {
2024-03-21 03:35:18 +00:00
// 1.获取表信息
tbs , err := md . GetTables ( tableName )
tableInfo := & dbi . Table { }
2024-03-26 09:05:28 +00:00
if err != nil || tbs == nil || len ( tbs ) <= 0 {
2024-03-21 03:35:18 +00:00
logx . Errorf ( "获取表信息失败, %s" , tableName )
return "" , err
}
tableInfo . TableName = tbs [ 0 ] . TableName
tableInfo . TableComment = tbs [ 0 ] . TableComment
// 2.获取列信息
columns , err := md . GetColumns ( tableName )
if err != nil {
logx . Errorf ( "获取列信息失败, %s" , tableName )
return "" , err
}
2024-03-26 09:05:28 +00:00
tableDDLArr := md . GenerateTableDDL ( columns , * tableInfo , dropBeforeCreate )
2024-03-21 03:35:18 +00:00
// 3.获取索引信息
indexs , err := md . GetTableIndex ( tableName )
if err != nil {
logx . Errorf ( "获取索引信息失败, %s" , tableName )
return "" , err
}
// 组装返回
tableDDLArr = append ( tableDDLArr , md . GenerateIndexDDL ( indexs , * tableInfo ) ... )
return strings . Join ( tableDDLArr , ";\n" ) , nil
2024-03-11 20:04:20 +08:00
}
func ( md * MssqlMetaData ) GetSchemas ( ) ( [ ] string , error ) {
_ , res , err := md . dc . Query ( dbi . GetLocalSql ( MSSQL_META_FILE , MSSQL_DB_SCHEMAS_KEY ) )
if err != nil {
return nil , err
}
schemas := make ( [ ] string , 0 )
for _ , re := range res {
2024-03-21 17:15:52 +08:00
schemas = append ( schemas , cast . ToString ( re [ "SCHEMA_NAME" ] ) )
2024-03-11 20:04:20 +08:00
}
return schemas , nil
}
2024-03-15 13:31:53 +08:00
func ( md * MssqlMetaData ) GetIdentifierQuoteString ( ) string {
return "["
}
2024-03-26 21:46:03 +08:00
func ( md * MssqlMetaData ) GetDataHelper ( ) dbi . DataHelper {
return new ( DataHelper )
2024-03-26 09:05:28 +00:00
}
2024-03-26 21:46:03 +08:00
func ( md * MssqlMetaData ) GetColumnHelper ( ) dbi . ColumnHelper {
return new ( ColumnHelper )
2024-03-15 13:31:53 +08:00
}
2024-03-26 09:05:28 +00:00
2024-03-26 21:46:03 +08:00
func ( md * MssqlMetaData ) GetDumpHelper ( ) dbi . DumpHelper {
return new ( DumpHelper )
2024-03-26 09:05:28 +00:00
}