分類
發燒車訊

golang連接達夢數據庫的一個坑

golang連接達夢數據庫的一個坑

有一次項目中用到了達夢數據庫,後端語言使用的golang,達夢官方並未適配專門的golang連接方式,正一籌莫展的時候發現達夢提供了odbc的連接,這樣可以使用類似mssqlodbc連接方式連接達夢數據庫。

使用的達夢數據庫版本為DM8

達夢數據庫開啟odbc連接

參考博客1、參考博客2

參照上面兩個博客內容配置odbc連接

golang代碼

一些參考文檔:

package main
import (
	"fmt"
	_ "github.com/alexbrainman/odbc"  // google's odbc driver
	"github.com/go-xorm/xorm"
	"xorm.io/core"
	"github.com/axgle/mahonia"
)

type Address struct {
    Addressid int64 `xorm:"addressid"`
    Address1 string `xorm:"address1"`
    Address2 string `xorm:"address2"`
    City string `xorm:"city"`
    Postalcode string `xorm:"postalcode"`
}

// 字符串解碼函數,處理中文亂碼
func ConvertToString(src string, srcCode string, tagCode string) string {
    srcCoder := mahonia.NewDecoder(srcCode)
    srcResult := srcCoder.ConvertString(src)
    tagCoder := mahonia.NewDecoder(tagCode)
    _, cdata, _ := tagCoder.Translate([]byte(srcResult), true)
    result := string(cdata)
    return result
}

func main() {
	engine, err := xorm.NewEngine("odbc", "driver={DM8 ODBC DRIVER};server=127.0.0.1:5236;database=DM;uid=SYSDBA;pwd=password;charset=utf8")
	if err != nil {
		fmt.Println("new engine got error:", err)
		return
	}
	engine.ShowSQL(true)//控制台打印出生成的SQL語句;
	engine.Logger().SetLevel(core.LOG_DEBUG)
	if err := engine.Ping(); err != nil {
		fmt.Println("ping got error:", err)
		return
	}

	// 1) sql查詢
	results, err := engine.Query("select addressid, address1, address2, city, postalcode from person.address limit 5 offset 2")
	if err != nil {
		fmt.Println("查詢出錯:", err)
		return
	}
	for i, e := range results {
		fmt.Printf("%v\t", i)
		for k, v := range e {
			// 達夢數據庫中文默認為gbk
			fmt.Printf("%v=%v\t", k, ConvertToString(string(v), "gbk", "utf-8"))
		}
		fmt.Printf("\n")
	}
	fmt.Println("*******************************")
	// 2) 使用struct 映射結果
	engine.SetMapper(core.SameMapper{})
	var sliceOfAddress []Address
	err = engine.Table("person.address").Limit(5, 0).Find(&sliceOfAddress)
	if err != nil {
		fmt.Println("查詢出錯:", err)
		return
	}
	for i,e := range sliceOfAddress {
		e.Address1 = ConvertToString(e.Address1, "gbk", "utf-8")
		e.Address2 = ConvertToString(e.Address2, "gbk", "utf-8")
		e.City = ConvertToString(e.City, "gbk", "utf-8")
		fmt.Printf("%v=%v\n", i, e)
	}
}

1)解決 golang.org/x/ 下包下載不下來的問題

https://studygolang.com/articles/19051?fr=sidebar

https://studygolang.com/articles/24075?fr=sidebar

2)無效的表或視圖名[person.address](這個也是最坑的一點)

原因:我們使用的是odbc的方式連接達夢數據庫,實際上使用的是mssql的驅動,第一個1) SQL查詢結果是OK的,但是2) struct 查詢就會報錯:

[xorm] [info]  2020/06/08 16:52:40.183731 [SQL] SELECT TOP 5 "addressid", "address1", "address2", "city", "postalcode" FROM "person.address"
查詢出錯: SQLPrepare: {42S02} 第1 行附近出現錯誤:
無效的表或視圖名[person.address]

通過日誌發現,xorm吧沒一個字段和表名都添加上了雙引號:SELECT TOP 5 "addressid", "address1", "address2", "city", "postalcode" FROM "person.address"這個sql在mssql中執行是沒問題的,但是放到達夢數據庫中就會報錯,因為達夢不支持雙引號包裹字段、命名空間、表名:

這樣就很坑爹了,我嘗試着修改結構體的xorm:"addressid"去掉其中的雙引號,但是問題還存在,最後想到打印出來的sql中待了雙引號,說明xorm後台還是拼接的sql語句,如果找到拼接sql語句的代碼然後去掉其中的雙引號不是就好了么。於是跟蹤代碼查找拼接sql的代碼:

解決方式一)

1、engine.Table("person.address").Limit(5, 0).Find(&sliceOfAddress)

先找到engin模塊的Find()方法:

代碼路徑:src.github.com/go-xorm/xorm/engine.go

// 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...)
}

發現其實調用的是session.Find() 方法

2、src.github.com/go-xorm/session_find.go

// Find retrieve records from table, condiBeans's non-empty fields
// are conditions. beans could be []Struct, []*Struct, map[int64]Struct
// map[int64]*Struct
func (session *Session) Find(rowsSlicePtr interface{}, condiBean ...interface{}) error {
	if session.isAutoClose {
		defer session.Close()
	}
	return session.find(rowsSlicePtr, condiBean...)
}

發現實際上調用的是session.find(rowsSlicePtr, condiBean...)

func (session *Session) find(rowsSlicePtr interface{}, condiBean ...interface{}) error {
	
	defer session.resetStatement()

	// 代碼省略 。。。

	var sqlStr string
	var args []interface{}
	var err error
    // 此處就是拼接sql的代碼
	if session.statement.RawSQL == "" {
		// 代碼省略 。。。
	} else {
		sqlStr = session.statement.RawSQL
		args = session.statement.RawParams
	}
    // 獲得配置信息判斷當前數據庫類型
	uri := session.engine.Dialect().URI()
	// 判斷當前是否是達夢數據庫
	if uri.DbType == "mssql" && uri.DbName == "DM" {
		newSqlStr := strings.Replace(sqlStr, "\"", "", -1) // 去掉雙引號
		sqlStr = newSqlStr
	}

	// 代碼省略 。。。
	return session.noCacheFind(table, sliceValue, sqlStr, args...)
}

通過session.engine.Dialect().URI()獲得配置信息,這段代碼怎麼來的,實際上是在xorm.NewEngine()的時候會解析配置信息,並賦值給enginedialect屬性,代碼位置:src.github.com/go-xorm/xorm/xorm.go

engine := &Engine{
		db:             db,
		dialect:        dialect,
		Tables:         make(map[reflect.Type]*core.Table),
		mutex:          &sync.RWMutex{},
		TagIdentifier:  "xorm",
		TZLocation:     time.Local,
		tagHandlers:    defaultTagHandlers,
		cachers:        make(map[string]core.Cacher),
		defaultContext: context.Background(),
	}

找到sql之後去掉雙引號即可,因為做了判斷只有是達夢的類型數據庫的時候才修改,所以不會影響其他類型的數據庫。至此問題得到了解決。

解決方式二)

上面的方式一是一種解決方案,其實有更簡便的,因為我們在創建engine的時候已經確定了dialect類型為dialect_mssql,找到src.github.com/go-xorm/xorm/dialect_mssql.go找到方法Quote稍作修改即可:

func (db *mssql) Quote(name string) string {
	fmt.Println("Quote -> ", db.URI().DbName) // DM
	if  db.URI().DbName == "DM" { // 如果是達夢數據庫不添加雙引號
		return name
	}
	return "\"" + name + "\""
}

這樣,是在拼接SQL之前修改了邏輯,二方式一是在拼接之後再去掉引號,方式二更方便一點。
注意:
查閱最新的文檔,發現最新的xorm.io/core v1.0.1版本已經支持使用engine.Dialect().SetQuotePolicy(core.QuotePolicyNone)來設置引號策略。如果你使用的是該版本,直接設置即可。
Dialect結構體、QuotePolicy常量值
輸出結果:

[xorm] [info]  2020/06/08 17:14:18.061667 PING DATABASE odbc
[xorm] [info]  2020/06/08 17:14:19.315349 [SQL] select addressid, address1, address2, city, postalcode from person.address limit 5 offset 2
0       ADDRESSID=3     ADDRESS1=青山區青翠苑1號        ADDRESS2=       CITY=武漢市青山區       POSTALCODE=430080
1       ADDRESSID=4     ADDRESS1=武昌區武船新村115號    ADDRESS2=       CITY=武漢市武昌區       POSTALCODE=430063
2       ADDRESSID=5     ADDRESS1=漢陽大道熊家灣15號     ADDRESS2=       CITY=武漢市漢陽區       POSTALCODE=430050
3       ADDRESSID=6     ADDRESS1=洪山區保利花園50-1-304 ADDRESS2=       CITY=武漢市洪山區       POSTALCODE=430073
4       ADDRESSID=7     ADDRESS1=洪山區保利花園51-1-702 ADDRESS2=       CITY=武漢市洪山區       POSTALCODE=430073
*******************************
[xorm] [info]  2020/06/08 17:14:19.324291 [SQL] SELECT TOP 5 addressid, address1, address2, city, postalcode FROM person.address
0={1 洪山區369號金地太陽城56-1-202  武漢市洪山區 430073}
1={2 洪山區369號金地太陽城57-2-302  武漢市洪山區 430073}
2={3 青山區青翠苑1號  武漢市青山區 430080}
3={4 武昌區武船新村115號  武漢市武昌區 430063}
4={5 漢陽大道熊家灣15號  武漢市漢陽區 430050}

參考文檔

xorm的操作指南

xorm的pkg文檔

go語言中文文檔

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※回頭車貨運收費標準

網頁設計最專業,超強功能平台可客製化

※別再煩惱如何寫文案,掌握八大原則!