mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-07-08 03:21:32 +08:00
🐛 fix(sql-editor): 修复脚本执行拆分与元数据只读提示
- Oracle 匿名块:识别 BEGIN/DECLARE...END 块,避免按内部分号错误拆分 - 执行路径:PL/SQL 块跳过批量写入路径,保持单条语句语义 - SQL 文件:同步修复流式 SQL 文件拆分逻辑 - 查询结果:系统元数据表保持只读但不再弹业务表主键提示 - 测试覆盖:补充前后端拆分、执行和 information_schema 回归用例
This commit is contained in:
@@ -770,13 +770,16 @@ func (a *App) DBQueryMulti(config connection.ConnectionConfig, dbName string, qu
|
||||
// 适用于 MySQL/MariaDB/Doris/PostgreSQL/SQLite/DuckDB 等支持多语句 Exec 的驱动
|
||||
if !allReadOnly {
|
||||
allWrite := true
|
||||
containsPLSQLBlock := false
|
||||
for _, stmt := range statements {
|
||||
if strings.TrimSpace(stmt) != "" && isReadOnlySQLQuery(runConfig.Type, stmt) {
|
||||
allWrite = false
|
||||
break
|
||||
}
|
||||
if isPLSQLBlockStatement(stmt) {
|
||||
containsPLSQLBlock = true
|
||||
}
|
||||
}
|
||||
if allWrite {
|
||||
if allWrite && !containsPLSQLBlock {
|
||||
if batcher, ok := dbInst.(db.BatchWriteExecer); ok {
|
||||
affected, batchErr := batcher.ExecBatchContext(ctx, query)
|
||||
if batchErr != nil && shouldRefreshCachedConnection(batchErr) {
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
type fakeBatchWriteDB struct {
|
||||
batchCalls int
|
||||
execCalls int
|
||||
execQueries []string
|
||||
lastQuery string
|
||||
}
|
||||
|
||||
@@ -33,6 +34,7 @@ func (f *fakeBatchWriteDB) Query(query string) ([]map[string]interface{}, []stri
|
||||
|
||||
func (f *fakeBatchWriteDB) Exec(query string) (int64, error) {
|
||||
f.execCalls++
|
||||
f.execQueries = append(f.execQueries, query)
|
||||
return 1, nil
|
||||
}
|
||||
|
||||
@@ -70,6 +72,7 @@ func (f *fakeBatchWriteDB) GetTriggers(dbName, tableName string) ([]connection.T
|
||||
|
||||
func (f *fakeBatchWriteDB) ExecContext(ctx context.Context, query string) (int64, error) {
|
||||
f.execCalls++
|
||||
f.execQueries = append(f.execQueries, query)
|
||||
return 1, nil
|
||||
}
|
||||
|
||||
@@ -79,6 +82,45 @@ func (f *fakeBatchWriteDB) ExecBatchContext(ctx context.Context, query string) (
|
||||
return 500, nil
|
||||
}
|
||||
|
||||
func TestDBQueryMultiKeepsOracleAnonymousBlockAsSingleStatement(t *testing.T) {
|
||||
originalNewDatabaseFunc := newDatabaseFunc
|
||||
t.Cleanup(func() {
|
||||
newDatabaseFunc = originalNewDatabaseFunc
|
||||
})
|
||||
|
||||
fakeDB := &fakeBatchWriteDB{}
|
||||
newDatabaseFunc = func(dbType string) (db.Database, error) {
|
||||
return fakeDB, nil
|
||||
}
|
||||
|
||||
app := NewAppWithSecretStore(secretstore.NewUnavailableStore("test"))
|
||||
config := connection.ConnectionConfig{
|
||||
Type: "oracle",
|
||||
Host: "127.0.0.1",
|
||||
Port: 1521,
|
||||
User: "app",
|
||||
}
|
||||
query := `BEGIN
|
||||
INSERT INTO tmp_disable_trigger (table_name) VALUES ('t_memcard_reg');
|
||||
UPDATE t_memcard_reg SET CARDLEVEL = 1 WHERE MEMCARDNO = '8032277312';
|
||||
DELETE FROM tmp_disable_trigger WHERE table_name = 't_memcard_reg';
|
||||
END;`
|
||||
|
||||
result := app.DBQueryMulti(config, "ORCLPDB1", query, "oracle-plsql-test")
|
||||
if !result.Success {
|
||||
t.Fatalf("expected DBQueryMulti success, got failure: %s", result.Message)
|
||||
}
|
||||
if fakeDB.batchCalls != 0 {
|
||||
t.Fatalf("expected PL/SQL block to skip batch path, got batchCalls=%d", fakeDB.batchCalls)
|
||||
}
|
||||
if fakeDB.execCalls != 1 || len(fakeDB.execQueries) != 1 {
|
||||
t.Fatalf("expected one sequential exec call, got execCalls=%d queries=%#v", fakeDB.execCalls, fakeDB.execQueries)
|
||||
}
|
||||
if fakeDB.execQueries[0] != query {
|
||||
t.Fatalf("expected PL/SQL block to stay intact, got %q", fakeDB.execQueries[0])
|
||||
}
|
||||
}
|
||||
|
||||
var _ db.BatchWriteExecer = (*fakeBatchWriteDB)(nil)
|
||||
|
||||
func TestDBQueryMultiUsesBatchWriteExecerForAllWriteStatements(t *testing.T) {
|
||||
|
||||
@@ -662,6 +662,9 @@ func isSQLFileBatchableWriteStatement(dbType string, stmt string) bool {
|
||||
if isReadOnlySQLQuery(dbType, stmt) {
|
||||
return false
|
||||
}
|
||||
if isPLSQLBlockStatement(stmt) {
|
||||
return false
|
||||
}
|
||||
switch leadingSQLKeyword(stmt) {
|
||||
case "insert", "update", "delete", "replace", "merge", "upsert":
|
||||
return true
|
||||
|
||||
@@ -322,3 +322,38 @@ func TestStreamSQLFileHandlesSplitTokenBoundaries(t *testing.T) {
|
||||
t.Fatalf("unexpected full-width semicolon statement: %q", statements[2])
|
||||
}
|
||||
}
|
||||
|
||||
func TestStreamSQLFileKeepsOracleAnonymousBlockTogether(t *testing.T) {
|
||||
input := strings.Join([]string{
|
||||
"BEGIN",
|
||||
" INSERT INTO tmp_disable_trigger (table_name) VALUES ('t_memcard_reg');",
|
||||
" UPDATE t_memcard_reg SET CARDLEVEL = 1 WHERE MEMCARDNO = '8032277312';",
|
||||
" DELETE FROM tmp_disable_trigger WHERE table_name = 't_memcard_reg';",
|
||||
"END;",
|
||||
"SELECT 1 FROM dual;",
|
||||
}, "\n")
|
||||
var statements []string
|
||||
|
||||
count, err := streamSQLFile(&chunkedReader{data: []byte(input), step: 3}, func(index int, stmt string) error {
|
||||
statements = append(statements, stmt)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("streamSQLFile returned error: %v", err)
|
||||
}
|
||||
if count != 2 || len(statements) != 2 {
|
||||
t.Fatalf("expected 2 statements, got count=%d statements=%#v", count, statements)
|
||||
}
|
||||
if statements[0] != strings.Join([]string{
|
||||
"BEGIN",
|
||||
" INSERT INTO tmp_disable_trigger (table_name) VALUES ('t_memcard_reg');",
|
||||
" UPDATE t_memcard_reg SET CARDLEVEL = 1 WHERE MEMCARDNO = '8032277312';",
|
||||
" DELETE FROM tmp_disable_trigger WHERE table_name = 't_memcard_reg';",
|
||||
"END;",
|
||||
}, "\n") {
|
||||
t.Fatalf("unexpected anonymous block statement: %q", statements[0])
|
||||
}
|
||||
if statements[1] != "SELECT 1 FROM dual" {
|
||||
t.Fatalf("unexpected second statement: %q", statements[1])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,9 @@ package app
|
||||
import "strings"
|
||||
|
||||
// splitSQLStatements 按分号拆分 SQL 文本为独立语句。
|
||||
// 正确处理单引号/双引号/反引号字符串、行注释(-- / #)、块注释(/* */)和
|
||||
// PostgreSQL/Kingbase 的 $$...$$ dollar-quoting,避免在这些上下文中错误拆分。
|
||||
// 正确处理单引号/双引号/反引号字符串、行注释(-- / #)、块注释(/* */)、
|
||||
// PostgreSQL/Kingbase 的 $$...$$ dollar-quoting,以及 Oracle PL/SQL 匿名块,
|
||||
// 避免在这些上下文中错误拆分。
|
||||
// 同时支持 SQL 标准的转义单引号(两个连续单引号 '' 表示字面量引号)。
|
||||
func splitSQLStatements(sql string) []string {
|
||||
text := strings.ReplaceAll(sql, "\r\n", "\n")
|
||||
@@ -18,6 +19,9 @@ func splitSQLStatements(sql string) []string {
|
||||
inLineComment := false
|
||||
inBlockComment := false
|
||||
var dollarTag string // postgres/kingbase: $$...$$ or $tag$...$tag$
|
||||
plsqlDepth := 0
|
||||
plsqlDeclareBeginSkips := 0
|
||||
justClosedPLSQLBlock := false
|
||||
|
||||
push := func() {
|
||||
s := strings.TrimSpace(cur.String())
|
||||
@@ -108,6 +112,35 @@ func splitSQLStatements(sql string) []string {
|
||||
continue
|
||||
}
|
||||
|
||||
if isSQLIdentifierStart(ch) {
|
||||
tokenStart := i
|
||||
tokenEnd := i + 1
|
||||
for tokenEnd < len(text) && isSQLIdentifierPart(text[tokenEnd]) {
|
||||
tokenEnd++
|
||||
}
|
||||
token := strings.ToLower(text[tokenStart:tokenEnd])
|
||||
if token == "begin" && plsqlDeclareBeginSkips > 0 {
|
||||
plsqlDeclareBeginSkips--
|
||||
justClosedPLSQLBlock = false
|
||||
} else if token == "begin" && shouldEnterPLSQLBlock(text, tokenEnd) {
|
||||
plsqlDepth++
|
||||
justClosedPLSQLBlock = false
|
||||
} else if token == "declare" && shouldEnterPLSQLDeclareBlock(text, tokenEnd) {
|
||||
plsqlDepth++
|
||||
plsqlDeclareBeginSkips++
|
||||
justClosedPLSQLBlock = false
|
||||
} else if token == "end" && plsqlDepth > 0 && !isPLSQLControlEnd(text, tokenEnd) {
|
||||
plsqlDepth--
|
||||
if plsqlDeclareBeginSkips > plsqlDepth {
|
||||
plsqlDeclareBeginSkips = plsqlDepth
|
||||
}
|
||||
justClosedPLSQLBlock = plsqlDepth == 0
|
||||
}
|
||||
cur.WriteString(text[tokenStart:tokenEnd])
|
||||
i = tokenEnd - 1
|
||||
continue
|
||||
}
|
||||
|
||||
// 行注释开始
|
||||
if ch == '-' && next == '-' {
|
||||
inLineComment = true
|
||||
@@ -140,11 +173,33 @@ func splitSQLStatements(sql string) []string {
|
||||
|
||||
// 分号分隔(支持全角分号";")
|
||||
if ch == ';' {
|
||||
if plsqlDepth > 0 {
|
||||
cur.WriteByte(ch)
|
||||
continue
|
||||
}
|
||||
if justClosedPLSQLBlock {
|
||||
cur.WriteByte(ch)
|
||||
push()
|
||||
justClosedPLSQLBlock = false
|
||||
continue
|
||||
}
|
||||
push()
|
||||
continue
|
||||
}
|
||||
// 全角分号 UTF-8 序列: 0xEF 0xBC 0x9B
|
||||
if ch == 0xEF && i+2 < len(text) && text[i+1] == 0xBC && text[i+2] == 0x9B {
|
||||
if plsqlDepth > 0 {
|
||||
cur.WriteString(";")
|
||||
i += 2
|
||||
continue
|
||||
}
|
||||
if justClosedPLSQLBlock {
|
||||
cur.WriteString(";")
|
||||
push()
|
||||
justClosedPLSQLBlock = false
|
||||
i += 2
|
||||
continue
|
||||
}
|
||||
push()
|
||||
i += 2
|
||||
continue
|
||||
@@ -157,6 +212,110 @@ func splitSQLStatements(sql string) []string {
|
||||
return statements
|
||||
}
|
||||
|
||||
func isSQLIdentifierStart(ch byte) bool {
|
||||
return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '_'
|
||||
}
|
||||
|
||||
func isSQLIdentifierPart(ch byte) bool {
|
||||
return isSQLIdentifierStart(ch) || (ch >= '0' && ch <= '9') || ch == '$' || ch == '#'
|
||||
}
|
||||
|
||||
func skipSQLWhitespaceAndComments(text string, pos int) int {
|
||||
i := pos
|
||||
for i < len(text) {
|
||||
switch text[i] {
|
||||
case ' ', '\t', '\n', '\r', '\f':
|
||||
i++
|
||||
continue
|
||||
case '-':
|
||||
if i+1 < len(text) && text[i+1] == '-' {
|
||||
i += 2
|
||||
for i < len(text) && text[i] != '\n' {
|
||||
i++
|
||||
}
|
||||
continue
|
||||
}
|
||||
case '/':
|
||||
if i+1 < len(text) && text[i+1] == '*' {
|
||||
i += 2
|
||||
for i+1 < len(text) && !(text[i] == '*' && text[i+1] == '/') {
|
||||
i++
|
||||
}
|
||||
if i+1 < len(text) {
|
||||
i += 2
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
func nextSQLSignificantToken(text string, pos int) string {
|
||||
i := skipSQLWhitespaceAndComments(text, pos)
|
||||
if i >= len(text) || !isSQLIdentifierStart(text[i]) {
|
||||
return ""
|
||||
}
|
||||
end := i + 1
|
||||
for end < len(text) && isSQLIdentifierPart(text[end]) {
|
||||
end++
|
||||
}
|
||||
return strings.ToLower(text[i:end])
|
||||
}
|
||||
|
||||
func nextSQLSignificantByte(text string, pos int) byte {
|
||||
i := skipSQLWhitespaceAndComments(text, pos)
|
||||
if i >= len(text) {
|
||||
return 0
|
||||
}
|
||||
return text[i]
|
||||
}
|
||||
|
||||
func shouldEnterPLSQLBlock(text string, tokenEnd int) bool {
|
||||
switch nextSQLSignificantByte(text, tokenEnd) {
|
||||
case 0, ';':
|
||||
return false
|
||||
}
|
||||
switch nextSQLSignificantToken(text, tokenEnd) {
|
||||
case "transaction", "work", "isolation", "read", "write":
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func isPLSQLBlockStatement(stmt string) bool {
|
||||
text := strings.TrimSpace(stmt)
|
||||
if text == "" {
|
||||
return false
|
||||
}
|
||||
if strings.HasSuffix(text, "/") {
|
||||
text = strings.TrimSpace(strings.TrimSuffix(text, "/"))
|
||||
}
|
||||
token := nextSQLSignificantToken(text, 0)
|
||||
if token == "declare" {
|
||||
return shouldEnterPLSQLDeclareBlock(text, len("declare"))
|
||||
}
|
||||
if token != "begin" {
|
||||
return false
|
||||
}
|
||||
return shouldEnterPLSQLBlock(text, len("begin"))
|
||||
}
|
||||
|
||||
func shouldEnterPLSQLDeclareBlock(text string, tokenEnd int) bool {
|
||||
return nextSQLSignificantToken(text, tokenEnd) != ""
|
||||
}
|
||||
|
||||
func isPLSQLControlEnd(text string, tokenEnd int) bool {
|
||||
switch nextSQLSignificantToken(text, tokenEnd) {
|
||||
case "if", "loop", "case":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// parseSQLDollarTag 解析 PostgreSQL/Kingbase 的 dollar-quoting 标签。
|
||||
func parseSQLDollarTag(s string) string {
|
||||
if len(s) < 2 || s[0] != '$' {
|
||||
|
||||
@@ -18,6 +18,9 @@ type sqlStreamSplitter struct {
|
||||
inLineComment bool
|
||||
inBlockComment bool
|
||||
dollarTag string
|
||||
plsqlDepth int
|
||||
declareSkips int
|
||||
closedPLSQL bool
|
||||
}
|
||||
|
||||
// Feed 将一个 chunk 喂入拆分器,返回在此 chunk 中完成的 SQL 语句列表。
|
||||
@@ -118,6 +121,43 @@ func (s *sqlStreamSplitter) Feed(chunk []byte) []string {
|
||||
continue
|
||||
}
|
||||
|
||||
if isSQLIdentifierStart(ch) {
|
||||
tokenStart := i
|
||||
tokenEnd := i + 1
|
||||
for tokenEnd < len(text) && isSQLIdentifierPart(text[tokenEnd]) {
|
||||
tokenEnd++
|
||||
}
|
||||
token := strings.ToLower(text[tokenStart:tokenEnd])
|
||||
if shouldDeferPLSQLKeywordPrefixInStream(text, tokenStart, tokenEnd, token) {
|
||||
s.pending = text[tokenStart:]
|
||||
break
|
||||
}
|
||||
if shouldDeferPLSQLKeywordInStream(text, tokenStart, tokenEnd, token) {
|
||||
s.pending = text[tokenStart:]
|
||||
break
|
||||
}
|
||||
if token == "begin" && s.declareSkips > 0 {
|
||||
s.declareSkips--
|
||||
s.closedPLSQL = false
|
||||
} else if token == "begin" && shouldEnterPLSQLBlock(text, tokenEnd) {
|
||||
s.plsqlDepth++
|
||||
s.closedPLSQL = false
|
||||
} else if token == "declare" && shouldEnterPLSQLDeclareBlock(text, tokenEnd) {
|
||||
s.plsqlDepth++
|
||||
s.declareSkips++
|
||||
s.closedPLSQL = false
|
||||
} else if token == "end" && s.plsqlDepth > 0 && !isPLSQLControlEnd(text, tokenEnd) {
|
||||
s.plsqlDepth--
|
||||
if s.declareSkips > s.plsqlDepth {
|
||||
s.declareSkips = s.plsqlDepth
|
||||
}
|
||||
s.closedPLSQL = s.plsqlDepth == 0
|
||||
}
|
||||
s.cur.WriteString(text[tokenStart:tokenEnd])
|
||||
i = tokenEnd - 1
|
||||
continue
|
||||
}
|
||||
|
||||
// 行注释开始
|
||||
if ch == '-' && i+1 >= len(text) {
|
||||
s.pending = text[i:]
|
||||
@@ -162,6 +202,20 @@ func (s *sqlStreamSplitter) Feed(chunk []byte) []string {
|
||||
|
||||
// 分号分隔
|
||||
if ch == ';' {
|
||||
if s.plsqlDepth > 0 {
|
||||
s.cur.WriteByte(ch)
|
||||
continue
|
||||
}
|
||||
if s.closedPLSQL {
|
||||
s.cur.WriteByte(ch)
|
||||
stmt := strings.TrimSpace(s.cur.String())
|
||||
if stmt != "" {
|
||||
statements = append(statements, stmt)
|
||||
}
|
||||
s.cur.Reset()
|
||||
s.closedPLSQL = false
|
||||
continue
|
||||
}
|
||||
stmt := strings.TrimSpace(s.cur.String())
|
||||
if stmt != "" {
|
||||
statements = append(statements, stmt)
|
||||
@@ -175,6 +229,22 @@ func (s *sqlStreamSplitter) Feed(chunk []byte) []string {
|
||||
break
|
||||
}
|
||||
if ch == 0xEF && i+2 < len(text) && text[i+1] == 0xBC && text[i+2] == 0x9B {
|
||||
if s.plsqlDepth > 0 {
|
||||
s.cur.WriteString(";")
|
||||
i += 2
|
||||
continue
|
||||
}
|
||||
if s.closedPLSQL {
|
||||
s.cur.WriteString(";")
|
||||
stmt := strings.TrimSpace(s.cur.String())
|
||||
if stmt != "" {
|
||||
statements = append(statements, stmt)
|
||||
}
|
||||
s.cur.Reset()
|
||||
s.closedPLSQL = false
|
||||
i += 2
|
||||
continue
|
||||
}
|
||||
stmt := strings.TrimSpace(s.cur.String())
|
||||
if stmt != "" {
|
||||
statements = append(statements, stmt)
|
||||
@@ -217,6 +287,44 @@ func isIncompleteSQLDollarTag(s string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func shouldDeferPLSQLKeywordInStream(text string, tokenStart int, tokenEnd int, token string) bool {
|
||||
switch token {
|
||||
case "begin", "declare", "end":
|
||||
default:
|
||||
return false
|
||||
}
|
||||
if tokenEnd >= len(text) {
|
||||
return true
|
||||
}
|
||||
next := skipSQLWhitespaceAndComments(text, tokenEnd)
|
||||
if next >= len(text) {
|
||||
return true
|
||||
}
|
||||
if isSQLIdentifierStart(text[next]) {
|
||||
nextEnd := next + 1
|
||||
for nextEnd < len(text) && isSQLIdentifierPart(text[nextEnd]) {
|
||||
nextEnd++
|
||||
}
|
||||
return nextEnd >= len(text)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func shouldDeferPLSQLKeywordPrefixInStream(text string, tokenStart int, tokenEnd int, token string) bool {
|
||||
if tokenEnd < len(text) {
|
||||
return false
|
||||
}
|
||||
for _, keyword := range []string{"begin", "declare", "end"} {
|
||||
if strings.HasPrefix(keyword, token) && token != keyword {
|
||||
if tokenStart > 0 && isSQLIdentifierPart(text[tokenStart-1]) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// streamSQLFile 从 reader 中流式读取 SQL 并逐条回调。
|
||||
// onStatement 返回 error 时停止读取并返回该 error。
|
||||
// 返回总处理语句数和可能的错误。
|
||||
|
||||
@@ -111,3 +111,59 @@ func TestSplitSQLStatements_SQLEscapedQuoteMultiple(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSplitSQLStatements_OracleAnonymousBlock(t *testing.T) {
|
||||
input := `BEGIN
|
||||
INSERT INTO tmp_disable_trigger (table_name) VALUES ('t_memcard_reg');
|
||||
UPDATE t_memcard_reg SET CARDLEVEL = 1 WHERE MEMCARDNO = '8032277312';
|
||||
DELETE FROM tmp_disable_trigger WHERE table_name = 't_memcard_reg';
|
||||
END;
|
||||
SELECT 1 FROM dual;`
|
||||
got := splitSQLStatements(input)
|
||||
want := []string{
|
||||
`BEGIN
|
||||
INSERT INTO tmp_disable_trigger (table_name) VALUES ('t_memcard_reg');
|
||||
UPDATE t_memcard_reg SET CARDLEVEL = 1 WHERE MEMCARDNO = '8032277312';
|
||||
DELETE FROM tmp_disable_trigger WHERE table_name = 't_memcard_reg';
|
||||
END;`,
|
||||
"SELECT 1 FROM dual",
|
||||
}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("splitSQLStatements(%q) = %#v, want %#v", input, got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSplitSQLStatements_OracleDeclareBlock(t *testing.T) {
|
||||
input := `DECLARE
|
||||
v_count NUMBER;
|
||||
BEGIN
|
||||
SELECT COUNT(*) INTO v_count FROM t_memcard_reg;
|
||||
UPDATE t_memcard_reg SET CARDLEVEL = v_count WHERE MEMCARDNO = '8032277312';
|
||||
END;
|
||||
SELECT 1 FROM dual;`
|
||||
got := splitSQLStatements(input)
|
||||
want := []string{
|
||||
`DECLARE
|
||||
v_count NUMBER;
|
||||
BEGIN
|
||||
SELECT COUNT(*) INTO v_count FROM t_memcard_reg;
|
||||
UPDATE t_memcard_reg SET CARDLEVEL = v_count WHERE MEMCARDNO = '8032277312';
|
||||
END;`,
|
||||
"SELECT 1 FROM dual",
|
||||
}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("splitSQLStatements(%q) = %#v, want %#v", input, got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSplitSQLStatements_TransactionBeginStillSplits(t *testing.T) {
|
||||
input := "BEGIN; UPDATE accounts SET balance = balance - 1 WHERE id = 1; COMMIT;"
|
||||
got := splitSQLStatements(input)
|
||||
want := []string{
|
||||
"BEGIN",
|
||||
"UPDATE accounts SET balance = balance - 1 WHERE id = 1",
|
||||
"COMMIT",
|
||||
}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("splitSQLStatements(%q) = %#v, want %#v", input, got, want)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user