From 15676d634a6222e430d550fea46a1979329219d6 Mon Sep 17 00:00:00 2001 From: xiongjiwei Date: Mon, 20 Dec 2021 12:25:46 +0800 Subject: [PATCH 01/10] add log --- executor/load_data.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/executor/load_data.go b/executor/load_data.go index 1202675ebbce0..2db290b6297e7 100644 --- a/executor/load_data.go +++ b/executor/load_data.go @@ -565,6 +565,26 @@ func (e *LoadDataInfo) getLine(prevData, curData []byte, ignore bool) ([]byte, [ // terminated symbol in the middle of prevData and curData lineLen := startingLen + endIdx + terminatedLen + defer func() { + r := recover() + if r != nil { + logutil.BgLogger().Info("load data", + zap.String("Field Terminated", e.FieldsInfo.Terminated), + zap.String("Field Enclosed", string(e.FieldsInfo.Enclosed)), + zap.Bool("Field OptEnclosed", e.FieldsInfo.OptEnclosed), + zap.String("Line Starting", e.LinesInfo.Starting), + zap.String("Line Terminated", e.LinesInfo.Terminated), + zap.Int("endIdx", endIdx), + zap.Int("prevLen", prevLen), + zap.Int("lineLen", lineLen), + zap.Int("nextDataIdx", nextDataIdx), + zap.Bool("inquotor", inquotor), + zap.Bool("ignore", ignore), + zap.ByteString("prevData", prevData), + zap.ByteString("curData", curData), + ) + } + }() return prevData[startingLen : startingLen+endIdx], curData[lineLen-prevLen:], true } From 38bd4d10138f9cee4a2bd020507398eacc443f38 Mon Sep 17 00:00:00 2001 From: xiongjiwei Date: Tue, 21 Dec 2021 18:15:53 +0800 Subject: [PATCH 02/10] log --- executor/load_data.go | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/executor/load_data.go b/executor/load_data.go index 2db290b6297e7..0fa02e7ed7bff 100644 --- a/executor/load_data.go +++ b/executor/load_data.go @@ -407,6 +407,9 @@ func (e *LoadDataInfo) getValidData(prevData, curData []byte) ([]byte, []byte) { func (e *LoadDataInfo) isInQuoter(bs []byte) bool { inQuoter := false + if e.FieldsInfo.Enclosed == byte(0) { + return false + } for i := 0; i < len(bs); i++ { switch bs[i] { case e.FieldsInfo.Enclosed: @@ -461,7 +464,7 @@ func (e *LoadDataInfo) IndexOfTerminator(bs []byte, inQuoter bool) int { atFieldStart := true loop: for i := 0; i < len(bs); i++ { - if atFieldStart && bs[i] == e.FieldsInfo.Enclosed { + if atFieldStart && e.FieldsInfo.Enclosed != byte(0) &&bs[i] == e.FieldsInfo.Enclosed { inQuoter = !inQuoter atFieldStart = false continue @@ -536,7 +539,7 @@ func (e *LoadDataInfo) getLine(prevData, curData []byte, ignore bool) ([]byte, [ if ignore { endIdx = strings.Index(string(hack.String(curData[startingLen:])), e.LinesInfo.Terminated) } else { - endIdx = e.IndexOfTerminator(curData[startingLen:], inquotor) + endIdx = e.IndexOfTerminator(curData[startingLen:], false) } if endIdx != -1 { nextDataIdx := startingLen + endIdx + terminatedLen @@ -557,7 +560,7 @@ func (e *LoadDataInfo) getLine(prevData, curData []byte, ignore bool) ([]byte, [ if ignore { endIdx = strings.Index(string(hack.String(prevData[startingLen:])), e.LinesInfo.Terminated) } else { - endIdx = e.IndexOfTerminator(prevData[startingLen:], inquotor) + endIdx = e.IndexOfTerminator(prevData[startingLen:], false) } if endIdx >= prevLen { return prevData[startingLen : startingLen+endIdx], curData[nextDataIdx:], true @@ -580,9 +583,10 @@ func (e *LoadDataInfo) getLine(prevData, curData []byte, ignore bool) ([]byte, [ zap.Int("nextDataIdx", nextDataIdx), zap.Bool("inquotor", inquotor), zap.Bool("ignore", ignore), - zap.ByteString("prevData", prevData), - zap.ByteString("curData", curData), + zap.Bool("prevData-contains-00bytes", bytes.Contains(prevData, []byte{e.FieldsInfo.Enclosed})), + zap.Bool("curData-contains-00bytes", bytes.Contains(curData, []byte{e.FieldsInfo.Enclosed})), ) + panic(r) } }() return prevData[startingLen : startingLen+endIdx], curData[lineLen-prevLen:], true From c57a8a209e0950a35931fc6bea585d2d97adec40 Mon Sep 17 00:00:00 2001 From: xiongjiwei Date: Wed, 22 Dec 2021 10:50:35 +0800 Subject: [PATCH 03/10] refactor --- executor/load_data.go | 113 ++++++++---------------------------------- 1 file changed, 22 insertions(+), 91 deletions(-) diff --git a/executor/load_data.go b/executor/load_data.go index 0fa02e7ed7bff..36e064469d1c1 100644 --- a/executor/load_data.go +++ b/executor/load_data.go @@ -366,43 +366,13 @@ func (e *LoadDataInfo) SetMaxRowsInBatch(limit uint64) { // getValidData returns prevData and curData that starts from starting symbol. // If the data doesn't have starting symbol, prevData is nil and curData is curData[len(curData)-startingLen+1:]. // If curData size less than startingLen, curData is returned directly. -func (e *LoadDataInfo) getValidData(prevData, curData []byte) ([]byte, []byte) { - startingLen := len(e.LinesInfo.Starting) - if startingLen == 0 { - return prevData, curData - } - - prevLen := len(prevData) - if prevLen > 0 { - // starting symbol in the prevData - idx := strings.Index(string(hack.String(prevData)), e.LinesInfo.Starting) - if idx != -1 { - return prevData[idx:], curData - } - - // starting symbol in the middle of prevData and curData - restStart := curData - if len(curData) >= startingLen { - restStart = curData[:startingLen-1] - } - prevData = append(prevData, restStart...) - idx = strings.Index(string(hack.String(prevData)), e.LinesInfo.Starting) - if idx != -1 { - return prevData[idx:prevLen], curData - } - } - - // starting symbol in the curData +func (e *LoadDataInfo) getValidData(curData []byte) ([]byte, bool) { idx := strings.Index(string(hack.String(curData)), e.LinesInfo.Starting) - if idx != -1 { - return nil, curData[idx:] + if idx == -1 { + return curData[len(curData)-len(e.LinesInfo.Starting)+1:], false } - // no starting symbol - if len(curData) >= startingLen { - curData = curData[len(curData)-startingLen+1:] - } - return nil, curData + return curData[idx:], true } func (e *LoadDataInfo) isInQuoter(bs []byte) bool { @@ -464,7 +434,7 @@ func (e *LoadDataInfo) IndexOfTerminator(bs []byte, inQuoter bool) int { atFieldStart := true loop: for i := 0; i < len(bs); i++ { - if atFieldStart && e.FieldsInfo.Enclosed != byte(0) &&bs[i] == e.FieldsInfo.Enclosed { + if atFieldStart && e.FieldsInfo.Enclosed != byte(0) && bs[i] == e.FieldsInfo.Enclosed { inQuoter = !inQuoter atFieldStart = false continue @@ -508,66 +478,31 @@ loop: // getLine returns a line, curData, the next data start index and a bool value. // If it has starting symbol the bool is true, otherwise is false. func (e *LoadDataInfo) getLine(prevData, curData []byte, ignore bool) ([]byte, []byte, bool) { - startingLen := len(e.LinesInfo.Starting) - prevData, curData = e.getValidData(prevData, curData) - if prevData == nil && len(curData) < startingLen { - return nil, curData, false - } - inquotor := e.isInQuoter(prevData) - prevLen := len(prevData) - terminatedLen := len(e.LinesInfo.Terminated) - curStartIdx := 0 - if prevLen < startingLen { - curStartIdx = startingLen - prevLen - } - endIdx := -1 - if len(curData) >= curStartIdx { - if ignore { - endIdx = strings.Index(string(hack.String(curData[curStartIdx:])), e.LinesInfo.Terminated) - } else { - endIdx = e.IndexOfTerminator(curData[curStartIdx:], inquotor) - } - } - if endIdx == -1 { - // no terminated symbol - if len(prevData) == 0 { - return nil, curData, true - } - - // terminated symbol in the middle of prevData and curData + if prevData != nil { curData = append(prevData, curData...) - if ignore { - endIdx = strings.Index(string(hack.String(curData[startingLen:])), e.LinesInfo.Terminated) - } else { - endIdx = e.IndexOfTerminator(curData[startingLen:], false) + } + startLen := len(e.LinesInfo.Starting) + if startLen != 0 { + if len(curData) < startLen { + return nil, curData, false } - if endIdx != -1 { - nextDataIdx := startingLen + endIdx + terminatedLen - return curData[startingLen : startingLen+endIdx], curData[nextDataIdx:], true + var ok bool + curData, ok = e.getValidData(curData) + if !ok { + return nil, curData, false } - // no terminated symbol - return nil, curData, true } - - // terminated symbol in the curData - nextDataIdx := curStartIdx + endIdx + terminatedLen - if len(prevData) == 0 { - return curData[curStartIdx : curStartIdx+endIdx], curData[nextDataIdx:], true - } - - // terminated symbol in the curData - prevData = append(prevData, curData[:nextDataIdx]...) + endIdx := -1 if ignore { - endIdx = strings.Index(string(hack.String(prevData[startingLen:])), e.LinesInfo.Terminated) + endIdx = strings.Index(string(hack.String(curData[startLen:])), e.LinesInfo.Terminated) } else { - endIdx = e.IndexOfTerminator(prevData[startingLen:], false) + endIdx = e.IndexOfTerminator(curData[startLen:], false) } - if endIdx >= prevLen { - return prevData[startingLen : startingLen+endIdx], curData[nextDataIdx:], true + + if endIdx == -1 { + return nil, curData, true } - // terminated symbol in the middle of prevData and curData - lineLen := startingLen + endIdx + terminatedLen defer func() { r := recover() if r != nil { @@ -578,10 +513,6 @@ func (e *LoadDataInfo) getLine(prevData, curData []byte, ignore bool) ([]byte, [ zap.String("Line Starting", e.LinesInfo.Starting), zap.String("Line Terminated", e.LinesInfo.Terminated), zap.Int("endIdx", endIdx), - zap.Int("prevLen", prevLen), - zap.Int("lineLen", lineLen), - zap.Int("nextDataIdx", nextDataIdx), - zap.Bool("inquotor", inquotor), zap.Bool("ignore", ignore), zap.Bool("prevData-contains-00bytes", bytes.Contains(prevData, []byte{e.FieldsInfo.Enclosed})), zap.Bool("curData-contains-00bytes", bytes.Contains(curData, []byte{e.FieldsInfo.Enclosed})), @@ -589,7 +520,7 @@ func (e *LoadDataInfo) getLine(prevData, curData []byte, ignore bool) ([]byte, [ panic(r) } }() - return prevData[startingLen : startingLen+endIdx], curData[lineLen-prevLen:], true + return curData[startLen : startLen+endIdx], curData[startLen+endIdx+len(e.LinesInfo.Terminated):], true } // InsertData inserts data into specified table according to the specified format. From 9824ee8be0124887e1851d51d9fb0ce5a31f28a1 Mon Sep 17 00:00:00 2001 From: xiongjiwei Date: Wed, 22 Dec 2021 14:07:57 +0800 Subject: [PATCH 04/10] refactor --- executor/load_data.go | 34 ---------------------------------- 1 file changed, 34 deletions(-) diff --git a/executor/load_data.go b/executor/load_data.go index 36e064469d1c1..0307e09082189 100644 --- a/executor/load_data.go +++ b/executor/load_data.go @@ -375,23 +375,6 @@ func (e *LoadDataInfo) getValidData(curData []byte) ([]byte, bool) { return curData[idx:], true } -func (e *LoadDataInfo) isInQuoter(bs []byte) bool { - inQuoter := false - if e.FieldsInfo.Enclosed == byte(0) { - return false - } - for i := 0; i < len(bs); i++ { - switch bs[i] { - case e.FieldsInfo.Enclosed: - inQuoter = !inQuoter - case e.FieldsInfo.Escaped: - i++ - default: - } - } - return inQuoter -} - // IndexOfTerminator return index of terminator, if not, return -1. // normally, the field terminator and line terminator is short, so we just use brute force algorithm. func (e *LoadDataInfo) IndexOfTerminator(bs []byte, inQuoter bool) int { @@ -503,23 +486,6 @@ func (e *LoadDataInfo) getLine(prevData, curData []byte, ignore bool) ([]byte, [ return nil, curData, true } - defer func() { - r := recover() - if r != nil { - logutil.BgLogger().Info("load data", - zap.String("Field Terminated", e.FieldsInfo.Terminated), - zap.String("Field Enclosed", string(e.FieldsInfo.Enclosed)), - zap.Bool("Field OptEnclosed", e.FieldsInfo.OptEnclosed), - zap.String("Line Starting", e.LinesInfo.Starting), - zap.String("Line Terminated", e.LinesInfo.Terminated), - zap.Int("endIdx", endIdx), - zap.Bool("ignore", ignore), - zap.Bool("prevData-contains-00bytes", bytes.Contains(prevData, []byte{e.FieldsInfo.Enclosed})), - zap.Bool("curData-contains-00bytes", bytes.Contains(curData, []byte{e.FieldsInfo.Enclosed})), - ) - panic(r) - } - }() return curData[startLen : startLen+endIdx], curData[startLen+endIdx+len(e.LinesInfo.Terminated):], true } From 300d8f12b672df2b691254ab54c8e6f7be50f12c Mon Sep 17 00:00:00 2001 From: xiongjiwei Date: Wed, 22 Dec 2021 16:06:48 +0800 Subject: [PATCH 05/10] log --- executor/load_data.go | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/executor/load_data.go b/executor/load_data.go index 0307e09082189..9f81691a7edff 100644 --- a/executor/load_data.go +++ b/executor/load_data.go @@ -464,6 +464,31 @@ func (e *LoadDataInfo) getLine(prevData, curData []byte, ignore bool) ([]byte, [ if prevData != nil { curData = append(prevData, curData...) } + defer func() { + r := recover() + if r != nil { + log := make([]byte, 0) + copy(log, curData) + for i := 0; i < len(curData); i++ { + if bytes.HasPrefix(curData, []byte(e.LinesInfo.Terminated)) || + bytes.HasPrefix(curData, []byte{e.FieldsInfo.Enclosed}) || + bytes.HasPrefix(curData, []byte(e.FieldsInfo.Terminated)) || + bytes.HasPrefix(curData, []byte{e.FieldsInfo.Escaped}) { + continue + } + log[i] = '?' + } + logutil.BgLogger().Info("load data", + zap.String("Field Terminated", e.FieldsInfo.Terminated), + zap.String("Field Enclosed", string(e.FieldsInfo.Enclosed)), + zap.Bool("Field OptEnclosed", e.FieldsInfo.OptEnclosed), + zap.String("Line Starting", e.LinesInfo.Starting), + zap.String("Line Terminated", e.LinesInfo.Terminated), + zap.ByteString("data", log), + ) + panic(r) + } + }() startLen := len(e.LinesInfo.Starting) if startLen != 0 { if len(curData) < startLen { From 39b4a1b05d0bf91401c7823787e8fd891d0238ee Mon Sep 17 00:00:00 2001 From: xiongjiwei Date: Wed, 12 Jan 2022 11:16:53 +0800 Subject: [PATCH 06/10] remove log --- executor/load_data.go | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/executor/load_data.go b/executor/load_data.go index 9f81691a7edff..0307e09082189 100644 --- a/executor/load_data.go +++ b/executor/load_data.go @@ -464,31 +464,6 @@ func (e *LoadDataInfo) getLine(prevData, curData []byte, ignore bool) ([]byte, [ if prevData != nil { curData = append(prevData, curData...) } - defer func() { - r := recover() - if r != nil { - log := make([]byte, 0) - copy(log, curData) - for i := 0; i < len(curData); i++ { - if bytes.HasPrefix(curData, []byte(e.LinesInfo.Terminated)) || - bytes.HasPrefix(curData, []byte{e.FieldsInfo.Enclosed}) || - bytes.HasPrefix(curData, []byte(e.FieldsInfo.Terminated)) || - bytes.HasPrefix(curData, []byte{e.FieldsInfo.Escaped}) { - continue - } - log[i] = '?' - } - logutil.BgLogger().Info("load data", - zap.String("Field Terminated", e.FieldsInfo.Terminated), - zap.String("Field Enclosed", string(e.FieldsInfo.Enclosed)), - zap.Bool("Field OptEnclosed", e.FieldsInfo.OptEnclosed), - zap.String("Line Starting", e.LinesInfo.Starting), - zap.String("Line Terminated", e.LinesInfo.Terminated), - zap.ByteString("data", log), - ) - panic(r) - } - }() startLen := len(e.LinesInfo.Starting) if startLen != 0 { if len(curData) < startLen { From be515718394d256cd69e50e74467e777836e04d8 Mon Sep 17 00:00:00 2001 From: xiongjiwei Date: Wed, 12 Jan 2022 11:32:04 +0800 Subject: [PATCH 07/10] add test --- executor/write_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/executor/write_test.go b/executor/write_test.go index 11e402f446631..abf1f9c3c448c 100644 --- a/executor/write_test.go +++ b/executor/write_test.go @@ -2104,6 +2104,7 @@ func TestLoadDataEscape(t *testing.T) { {nil, []byte("7\trtn0ZbN\n"), []string{"7|" + string([]byte{'r', 't', 'n', '0', 'Z', 'b', 'N'})}, nil, trivialMsg}, {nil, []byte("8\trtn0Zb\\N\n"), []string{"8|" + string([]byte{'r', 't', 'n', '0', 'Z', 'b', 'N'})}, nil, trivialMsg}, {nil, []byte("9\ttab\\ tab\n"), []string{"9|tab tab"}, nil, trivialMsg}, + {[]byte("1\ta string\\"), []byte("\n1\n"), []string{"1|a string\n1"}, nil, trivialMsg}, } deleteSQL := "delete from load_data_test" selectSQL := "select * from load_data_test;" From 30379c3d3ea0bdae1e53decbc346da86bb9f46a9 Mon Sep 17 00:00:00 2001 From: xiongjiwei Date: Wed, 12 Jan 2022 11:54:33 +0800 Subject: [PATCH 08/10] linter --- executor/load_data.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/executor/load_data.go b/executor/load_data.go index 0307e09082189..c35bf2aa303ec 100644 --- a/executor/load_data.go +++ b/executor/load_data.go @@ -475,7 +475,7 @@ func (e *LoadDataInfo) getLine(prevData, curData []byte, ignore bool) ([]byte, [ return nil, curData, false } } - endIdx := -1 + var endIdx int if ignore { endIdx = strings.Index(string(hack.String(curData[startLen:])), e.LinesInfo.Terminated) } else { From 29eac69df58e782de580b78c460e9315e6a74b77 Mon Sep 17 00:00:00 2001 From: xiongjiwei Date: Fri, 14 Jan 2022 16:26:24 +0800 Subject: [PATCH 09/10] address comments --- executor/load_data.go | 14 +++++++------- executor/write_test.go | 22 ---------------------- 2 files changed, 7 insertions(+), 29 deletions(-) diff --git a/executor/load_data.go b/executor/load_data.go index c35bf2aa303ec..7d124c0cacf3d 100644 --- a/executor/load_data.go +++ b/executor/load_data.go @@ -363,9 +363,8 @@ func (e *LoadDataInfo) SetMaxRowsInBatch(limit uint64) { e.curBatchCnt = 0 } -// getValidData returns prevData and curData that starts from starting symbol. -// If the data doesn't have starting symbol, prevData is nil and curData is curData[len(curData)-startingLen+1:]. -// If curData size less than startingLen, curData is returned directly. +// getValidData returns curData that starts from starting symbol. +// If the data doesn't have starting symbol, return curData[len(curData)-startingLen+1:] and false. func (e *LoadDataInfo) getValidData(curData []byte) ([]byte, bool) { idx := strings.Index(string(hack.String(curData)), e.LinesInfo.Starting) if idx == -1 { @@ -375,9 +374,9 @@ func (e *LoadDataInfo) getValidData(curData []byte) ([]byte, bool) { return curData[idx:], true } -// IndexOfTerminator return index of terminator, if not, return -1. +// indexOfTerminator return index of terminator, if not, return -1. // normally, the field terminator and line terminator is short, so we just use brute force algorithm. -func (e *LoadDataInfo) IndexOfTerminator(bs []byte, inQuoter bool) int { +func (e *LoadDataInfo) indexOfTerminator(bs []byte) int { fieldTerm := []byte(e.FieldsInfo.Terminated) fieldTermLen := len(fieldTerm) lineTerm := []byte(e.LinesInfo.Terminated) @@ -415,6 +414,7 @@ func (e *LoadDataInfo) IndexOfTerminator(bs []byte, inQuoter bool) int { } } atFieldStart := true + inQuoter := false loop: for i := 0; i < len(bs); i++ { if atFieldStart && e.FieldsInfo.Enclosed != byte(0) && bs[i] == e.FieldsInfo.Enclosed { @@ -423,7 +423,7 @@ loop: continue } restLen := len(bs) - i - 1 - if inQuoter && bs[i] == e.FieldsInfo.Enclosed { + if inQuoter && e.FieldsInfo.Enclosed != byte(0) && bs[i] == e.FieldsInfo.Enclosed { // look ahead to see if it is end of line or field. switch cmpTerm(restLen, bs[i+1:]) { case lineTermType: @@ -479,7 +479,7 @@ func (e *LoadDataInfo) getLine(prevData, curData []byte, ignore bool) ([]byte, [ if ignore { endIdx = strings.Index(string(hack.String(curData[startLen:])), e.LinesInfo.Terminated) } else { - endIdx = e.IndexOfTerminator(curData[startLen:], false) + endIdx = e.indexOfTerminator(curData[startLen:]) } if endIdx == -1 { diff --git a/executor/write_test.go b/executor/write_test.go index abf1f9c3c448c..8ac7f2fad104d 100644 --- a/executor/write_test.go +++ b/executor/write_test.go @@ -24,7 +24,6 @@ import ( "github.com/pingcap/tidb/config" "github.com/pingcap/tidb/executor" "github.com/pingcap/tidb/kv" - "github.com/pingcap/tidb/parser/ast" "github.com/pingcap/tidb/parser/model" "github.com/pingcap/tidb/parser/mysql" "github.com/pingcap/tidb/planner/core" @@ -2111,27 +2110,6 @@ func TestLoadDataEscape(t *testing.T) { checkCases(tests, ld, t, tk, ctx, selectSQL, deleteSQL) } -func TestLoadDataWithLongContent(t *testing.T) { - e := &executor.LoadDataInfo{ - FieldsInfo: &ast.FieldsClause{Terminated: ",", Escaped: '\\', Enclosed: '"'}, - LinesInfo: &ast.LinesClause{Terminated: "\n"}, - } - tests := []struct { - content string - inQuoter bool - expectedIndex int - }{ - {"123,123\n123,123", false, 7}, - {"123123\\n123123", false, -1}, - {"123123\n123123", true, -1}, - {"123123\n123123\"\n", true, 14}, - } - - for _, tt := range tests { - require.Equal(t, tt.expectedIndex, e.IndexOfTerminator([]byte(tt.content), tt.inQuoter)) - } -} - // TestLoadDataSpecifiedColumns reuse TestLoadDataEscape's test case :-) func TestLoadDataSpecifiedColumns(t *testing.T) { trivialMsg := "Records: 1 Deleted: 0 Skipped: 0 Warnings: 0" From aa23b1b98be9834e36a0570e300e4b8012467914 Mon Sep 17 00:00:00 2001 From: xiongjiwei Date: Tue, 18 Jan 2022 17:37:25 +0800 Subject: [PATCH 10/10] add comments --- executor/write_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/executor/write_test.go b/executor/write_test.go index 8ac7f2fad104d..cfdcf7f1f4d95 100644 --- a/executor/write_test.go +++ b/executor/write_test.go @@ -2103,6 +2103,7 @@ func TestLoadDataEscape(t *testing.T) { {nil, []byte("7\trtn0ZbN\n"), []string{"7|" + string([]byte{'r', 't', 'n', '0', 'Z', 'b', 'N'})}, nil, trivialMsg}, {nil, []byte("8\trtn0Zb\\N\n"), []string{"8|" + string([]byte{'r', 't', 'n', '0', 'Z', 'b', 'N'})}, nil, trivialMsg}, {nil, []byte("9\ttab\\ tab\n"), []string{"9|tab tab"}, nil, trivialMsg}, + // data broken at escape character. {[]byte("1\ta string\\"), []byte("\n1\n"), []string{"1|a string\n1"}, nil, trivialMsg}, } deleteSQL := "delete from load_data_test"