From 5317814184a88069ccdb358cad227cf500d8cab0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=B9ng=20T=E1=BA=A1=20Quang?= Date: Tue, 27 Apr 2021 14:16:53 +0700 Subject: [PATCH 1/5] Fix to allow single column, one level function call and using VALUES after ON DUPLICATE KEY --- named.go | 6 +++--- named_test.go | 22 +++++++++++++++++++++- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/named.go b/named.go index 1f41612..5ffe44c 100644 --- a/named.go +++ b/named.go @@ -224,13 +224,13 @@ func bindStruct(bindType int, query string, arg interface{}, m *reflectx.Mapper) return bound, arglist, nil } -var valueBracketReg = regexp.MustCompile(`(?i)VALUES\s*(\([^(]*.[^(]\))`) +var valueBracketReg = regexp.MustCompile(`(?i)VALUES\s*(\((?:[^(]|\([^(]*\))*\))`) func fixBound(bound string, loop int) string { loc := valueBracketReg.FindAllStringSubmatchIndex(bound, -1) - // Either no VALUES () found or more than one found?? - if len(loc) != 1 { + // defensive guard when "VALUES (...)" not found + if len(loc) < 1 { return bound } // defensive guard. loc should be len 4 representing the starting and diff --git a/named_test.go b/named_test.go index 70bc448..0c1f0f8 100644 --- a/named_test.go +++ b/named_test.go @@ -105,6 +105,7 @@ type Test struct { } func (t Test) Error(err error, msg ...interface{}) { + t.t.Helper() if err != nil { if len(msg) == 0 { t.t.Error(err) @@ -115,6 +116,7 @@ func (t Test) Error(err error, msg ...interface{}) { } func (t Test) Errorf(err error, format string, args ...interface{}) { + t.t.Helper() if err != nil { t.t.Errorf(format, args...) } @@ -339,7 +341,7 @@ func TestFixBounds(t *testing.T) { { name: `found twice test`, query: `INSERT INTO foo (a,b,c,d) VALUES (:name, :age, :first, :last) VALUES (:name, :age, :first, :last)`, - expect: `INSERT INTO foo (a,b,c,d) VALUES (:name, :age, :first, :last) VALUES (:name, :age, :first, :last)`, + expect: `INSERT INTO foo (a,b,c,d) VALUES (:name, :age, :first, :last),(:name, :age, :first, :last) VALUES (:name, :age, :first, :last)`, loop: 2, }, { @@ -354,6 +356,24 @@ func TestFixBounds(t *testing.T) { expect: `INSERT INTO foo (a,b) values(:a, :b),(:a, :b)`, loop: 2, }, + { + name: `on duplicate key using VALUES`, + query: `INSERT INTO foo (a,b) VALUES(:a, :b) ON DUPLICATE KEY UPDATE a=VALUES(a)`, + expect: `INSERT INTO foo (a,b) VALUES(:a, :b),(:a, :b) ON DUPLICATE KEY UPDATE a=VALUES(a)`, + loop: 2, + }, + { + name: `single column`, + query: `INSERT INTO foo (a) VALUES(:a)`, + expect: `INSERT INTO foo (a) VALUES(:a),(:a)`, + loop: 2, + }, + { + name: `call now`, + query: `INSERT INTO foo (a, b) VALUES(:a, NOW())`, + expect: `INSERT INTO foo (a, b) VALUES(:a, NOW()),(:a, NOW())`, + loop: 2, + }, } for _, tc := range table { From b20d6792f4845804a18eac1a21f928eaaba4d63a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=B9ng=20T=E1=BA=A1=20Quang?= Date: Tue, 27 Apr 2021 15:34:08 +0700 Subject: [PATCH 2/5] Allow multi-level depth function calls --- named.go | 38 ++++++++++++++++++++++++++++---------- named_test.go | 39 ++++++++++++++++++--------------------- 2 files changed, 46 insertions(+), 31 deletions(-) diff --git a/named.go b/named.go index 5ffe44c..f9d14ba 100644 --- a/named.go +++ b/named.go @@ -224,29 +224,47 @@ func bindStruct(bindType int, query string, arg interface{}, m *reflectx.Mapper) return bound, arglist, nil } -var valueBracketReg = regexp.MustCompile(`(?i)VALUES\s*(\((?:[^(]|\([^(]*\))*\))`) +var valuesReg = regexp.MustCompile(`(?i)VALUES\s*\(`) -func fixBound(bound string, loop int) string { +func findMatchingClosingBracketIndex(s string) int { + count := 0 + for i, ch := range s { + if ch == '(' { + count++ + } + if ch == ')' { + count-- + if count == 0 { + return i + } + } + } + return 0 +} - loc := valueBracketReg.FindAllStringSubmatchIndex(bound, -1) +func fixBound(bound string, loop int) string { + loc := valuesReg.FindStringIndex(bound) // defensive guard when "VALUES (...)" not found if len(loc) < 1 { return bound } - // defensive guard. loc should be len 4 representing the starting and - // ending index for the whole regex match and the starting + ending - // index for the single inside group - if len(loc[0]) != 4 { + + openingBracketIndex := loc[1] - 1 + index := findMatchingClosingBracketIndex(bound[openingBracketIndex:]) + // defensive guard. must have closing bracket + if index == 0 { return bound } + closingBracketIndex := openingBracketIndex + index + 1 + var buffer bytes.Buffer - buffer.WriteString(bound[0:loc[0][1]]) + buffer.WriteString(bound[0:closingBracketIndex]) for i := 0; i < loop-1; i++ { buffer.WriteString(",") - buffer.WriteString(bound[loc[0][2]:loc[0][3]]) + buffer.WriteString(bound[openingBracketIndex:closingBracketIndex]) } - buffer.WriteString(bound[loc[0][1]:]) + buffer.WriteString(bound[closingBracketIndex:]) return buffer.String() } diff --git a/named_test.go b/named_test.go index 0c1f0f8..1037de4 100644 --- a/named_test.go +++ b/named_test.go @@ -3,7 +3,6 @@ package sqlx import ( "database/sql" "fmt" - "regexp" "testing" ) @@ -358,20 +357,32 @@ func TestFixBounds(t *testing.T) { }, { name: `on duplicate key using VALUES`, - query: `INSERT INTO foo (a,b) VALUES(:a, :b) ON DUPLICATE KEY UPDATE a=VALUES(a)`, - expect: `INSERT INTO foo (a,b) VALUES(:a, :b),(:a, :b) ON DUPLICATE KEY UPDATE a=VALUES(a)`, + query: `INSERT INTO foo (a,b) VALUES (:a, :b) ON DUPLICATE KEY UPDATE a=VALUES(a)`, + expect: `INSERT INTO foo (a,b) VALUES (:a, :b),(:a, :b) ON DUPLICATE KEY UPDATE a=VALUES(a)`, loop: 2, }, { name: `single column`, - query: `INSERT INTO foo (a) VALUES(:a)`, - expect: `INSERT INTO foo (a) VALUES(:a),(:a)`, + query: `INSERT INTO foo (a) VALUES (:a)`, + expect: `INSERT INTO foo (a) VALUES (:a),(:a)`, loop: 2, }, { name: `call now`, - query: `INSERT INTO foo (a, b) VALUES(:a, NOW())`, - expect: `INSERT INTO foo (a, b) VALUES(:a, NOW()),(:a, NOW())`, + query: `INSERT INTO foo (a, b) VALUES (:a, NOW())`, + expect: `INSERT INTO foo (a, b) VALUES (:a, NOW()),(:a, NOW())`, + loop: 2, + }, + { + name: `two level depth function call`, + query: `INSERT INTO foo (a, b) VALUES (:a, YEAR(NOW()))`, + expect: `INSERT INTO foo (a, b) VALUES (:a, YEAR(NOW())),(:a, YEAR(NOW()))`, + loop: 2, + }, + { + name: `missing closing bracket`, + query: `INSERT INTO foo (a, b) VALUES (:a, YEAR(NOW())`, + expect: `INSERT INTO foo (a, b) VALUES (:a, YEAR(NOW())`, loop: 2, }, } @@ -384,18 +395,4 @@ func TestFixBounds(t *testing.T) { } }) } - - t.Run("regex changed", func(t *testing.T) { - var valueBracketRegChanged = regexp.MustCompile(`(VALUES)\s+(\([^(]*.[^(]\))`) - saveRegexp := valueBracketReg - defer func() { - valueBracketReg = saveRegexp - }() - valueBracketReg = valueBracketRegChanged - - res := fixBound("VALUES (:a, :b)", 2) - if res != "VALUES (:a, :b)" { - t.Errorf("changed regex should return string") - } - }) } From 3ab43dbec9270483ab1935a7650dbe9cb68f0d5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=B9ng=20T=E1=BA=A1=20Quang?= Date: Wed, 28 Apr 2021 09:17:11 +0700 Subject: [PATCH 3/5] Fix: Table contains VALUES --- named.go | 2 +- named_test.go | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/named.go b/named.go index f9d14ba..58bedd5 100644 --- a/named.go +++ b/named.go @@ -224,7 +224,7 @@ func bindStruct(bindType int, query string, arg interface{}, m *reflectx.Mapper) return bound, arglist, nil } -var valuesReg = regexp.MustCompile(`(?i)VALUES\s*\(`) +var valuesReg = regexp.MustCompile(`\)\s*(?i)VALUES\s*\(`) func findMatchingClosingBracketIndex(s string) int { count := 0 diff --git a/named_test.go b/named_test.go index 1037de4..9ff03b3 100644 --- a/named_test.go +++ b/named_test.go @@ -385,6 +385,12 @@ func TestFixBounds(t *testing.T) { expect: `INSERT INTO foo (a, b) VALUES (:a, YEAR(NOW())`, loop: 2, }, + { + name: `table with "values" at the end`, + query: `INSERT INTO table_values (a, b) VALUES (:a, :b)`, + expect: `INSERT INTO table_values (a, b) VALUES (:a, :b),(:a, :b)`, + loop: 2, + }, } for _, tc := range table { From f107302e9b2571ebc44d84b934fa9100a7719540 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=B9ng=20T=E1=BA=A1=20Quang?= Date: Fri, 7 May 2021 09:13:03 +0700 Subject: [PATCH 4/5] Fix: Make sure loc[1] exists --- named.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/named.go b/named.go index 58bedd5..728aa04 100644 --- a/named.go +++ b/named.go @@ -245,7 +245,7 @@ func findMatchingClosingBracketIndex(s string) int { func fixBound(bound string, loop int) string { loc := valuesReg.FindStringIndex(bound) // defensive guard when "VALUES (...)" not found - if len(loc) < 1 { + if len(loc) < 2 { return bound } From 4b6b69ec278c1fbfafba4336f8c84065b0911029 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=B9ng=20T=E1=BA=A1=20Quang?= Date: Sun, 9 May 2021 17:57:34 +0700 Subject: [PATCH 5/5] Multiline indented query test case --- named_test.go | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/named_test.go b/named_test.go index 9ff03b3..8481b35 100644 --- a/named_test.go +++ b/named_test.go @@ -391,6 +391,37 @@ func TestFixBounds(t *testing.T) { expect: `INSERT INTO table_values (a, b) VALUES (:a, :b),(:a, :b)`, loop: 2, }, + { + name: `multiline indented query`, + query: `INSERT INTO foo ( + a, + b, + c, + d + ) VALUES ( + :name, + :age, + :first, + :last + )`, + expect: `INSERT INTO foo ( + a, + b, + c, + d + ) VALUES ( + :name, + :age, + :first, + :last + ),( + :name, + :age, + :first, + :last + )`, + loop: 2, + }, } for _, tc := range table {