mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-05-28 16:59:34 +08:00
- 后端新增 IRIS 连接、查询、DDL、索引元数据和 DataGrid 编辑能力 - 接入 optional driver-agent、构建标签、revision 生成和变更检测流程 - 前端新增 IRIS 连接入口、方言映射、能力配置和图标展示 - 修复 IRIS 主键识别、事务开启错误处理和驱动连接关闭问题 - 补充后端、前端和构建脚本相关回归测试 Refs #408
217 lines
4.0 KiB
Go
217 lines
4.0 KiB
Go
package intersystems
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"database/sql/driver"
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"unicode"
|
|
|
|
_ "io"
|
|
_ "math"
|
|
_ "reflect"
|
|
_ "strconv"
|
|
_ "strings"
|
|
_ "time"
|
|
_ "unsafe"
|
|
|
|
"github.com/caretdev/go-irisnative/src/connection"
|
|
)
|
|
|
|
var (
|
|
ErrCouldNotDetectUsername = errors.New("intersystems: Could not detect default username. Please provide one explicitly")
|
|
)
|
|
|
|
var (
|
|
_ driver.Driver = Driver{}
|
|
)
|
|
|
|
type values map[string]string
|
|
|
|
// Driver implements database/sql/driver.Driver.
|
|
type Driver struct{}
|
|
|
|
func (d Driver) Open(name string) (driver.Conn, error) {
|
|
return Open(name)
|
|
}
|
|
|
|
func init() {
|
|
sql.Register("intersystems", &Driver{})
|
|
sql.Register("iris", &Driver{})
|
|
}
|
|
|
|
func Open(dsn string) (_ driver.Conn, err error) {
|
|
c, err := NewConnector(dsn)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return c.open(context.Background())
|
|
}
|
|
|
|
type conn struct {
|
|
c connection.Connection
|
|
tx bool
|
|
}
|
|
|
|
func (c *Connector) open(ctx context.Context) (cn *conn, err error) {
|
|
o := make(values)
|
|
for k, v := range c.opts {
|
|
o[k] = v
|
|
}
|
|
host := o["host"]
|
|
addr := net.JoinHostPort(host, o["port"])
|
|
namespace := o["namespace"]
|
|
login := o["user"]
|
|
password := o["password"]
|
|
|
|
cn = &conn{}
|
|
|
|
cn.c, err = connection.Connect(addr, namespace, login, password)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return cn, nil
|
|
}
|
|
|
|
func (cn *conn) Begin() (driver.Tx, error) {
|
|
return cn.c.BeginTx(driver.TxOptions{})
|
|
}
|
|
|
|
func (cn *conn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) {
|
|
return cn.c.BeginTx(opts)
|
|
}
|
|
|
|
func (cn *conn) Close() (err error) {
|
|
cn.c.Disconnect()
|
|
return nil
|
|
}
|
|
|
|
func (cn *conn) Prepare(q string) (st driver.Stmt, err error) {
|
|
return cn.c.Prepare(q)
|
|
}
|
|
|
|
func (cn *conn) Commit() error {
|
|
if !cn.tx {
|
|
panic("transaction already closed")
|
|
}
|
|
cn.tx = false
|
|
cn.c.Commit()
|
|
return nil
|
|
}
|
|
|
|
func (cn *conn) Rollback() error {
|
|
if !cn.tx {
|
|
panic("transaction already closed")
|
|
}
|
|
cn.tx = false
|
|
cn.c.Rollback()
|
|
return nil
|
|
}
|
|
|
|
func (cn *conn) Exec(query string, args []driver.NamedValue) (res driver.Result, err error) {
|
|
parameters := make([]interface{}, len(args))
|
|
for i, a := range args {
|
|
parameters[i] = a
|
|
}
|
|
_, err = cn.c.DirectUpdate(query, parameters...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return res, nil
|
|
}
|
|
|
|
func (cn *conn) Query(query string, args []driver.NamedValue) (rows driver.Rows, err error) {
|
|
parameters := make([]interface{}, len(args))
|
|
for i, a := range args {
|
|
parameters[i] = a
|
|
}
|
|
// var rs *connection.ResultSet
|
|
_, err = cn.c.Query(query, parameters...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// rows = &connection.Rows{
|
|
// cn: cn.c,
|
|
// rs: rs,
|
|
// }
|
|
return
|
|
}
|
|
|
|
func parseOpts(name string, o values) error {
|
|
s := newScanner(name)
|
|
|
|
for {
|
|
var (
|
|
keyRunes, valRunes []rune
|
|
r rune
|
|
ok bool
|
|
)
|
|
|
|
if r, ok = s.SkipSpaces(); !ok {
|
|
break
|
|
}
|
|
|
|
// Scan the key
|
|
for !unicode.IsSpace(r) && r != '=' {
|
|
keyRunes = append(keyRunes, r)
|
|
if r, ok = s.Next(); !ok {
|
|
break
|
|
}
|
|
}
|
|
|
|
// Skip any whitespace if we're not at the = yet
|
|
if r != '=' {
|
|
r, ok = s.SkipSpaces()
|
|
}
|
|
|
|
// The current character should be =
|
|
if r != '=' || !ok {
|
|
return fmt.Errorf(`missing "=" after %q in connection info string"`, string(keyRunes))
|
|
}
|
|
|
|
// Skip any whitespace after the =
|
|
if r, ok = s.SkipSpaces(); !ok {
|
|
// If we reach the end here, the last value is just an empty string as per libpq.
|
|
o[string(keyRunes)] = ""
|
|
break
|
|
}
|
|
|
|
if r != '\'' {
|
|
for !unicode.IsSpace(r) {
|
|
if r == '\\' {
|
|
if r, ok = s.Next(); !ok {
|
|
return fmt.Errorf(`missing character after backslash`)
|
|
}
|
|
}
|
|
valRunes = append(valRunes, r)
|
|
|
|
if r, ok = s.Next(); !ok {
|
|
break
|
|
}
|
|
}
|
|
} else {
|
|
quote:
|
|
for {
|
|
if r, ok = s.Next(); !ok {
|
|
return fmt.Errorf(`unterminated quoted string literal in connection string`)
|
|
}
|
|
switch r {
|
|
case '\'':
|
|
break quote
|
|
case '\\':
|
|
r, _ = s.Next()
|
|
fallthrough
|
|
default:
|
|
valRunes = append(valRunes, r)
|
|
}
|
|
}
|
|
}
|
|
|
|
o[string(keyRunes)] = string(valRunes)
|
|
}
|
|
|
|
return nil
|
|
}
|