Repository: cloudspannerecosystem/harbourbridge
Pull request #141 (2021-05-27 01:49:13)

Handle negative expressions e.g., negative int64, float64 column values in mysqldump parser
Pingcap parser that we use to parse mysqldump returns an AST node with type ValueExpr for positive int64 and float64 values. Whereas, for negative values it returns an UnaryExpression which contains the unary operator and the corresponding operand value. We are adding handling for for UnaryExpressions with Minus operator e.g., "-123" or "-123.456" such that the same can be converted to valid int64 and float64 column types in the intermediate representation. 

Before this change, mysqldump parser would simply drop these rows altogether.


Patch:
diff --git a/mysql/mysqldump.go b/mysql/mysqldump.go
index 60cb17f..bde1445 100644
--- a/mysql/mysqldump.go
+++ b/mysql/mysqldump.go
@@ -25,6 +25,8 @@ import (
 	"github.com/cloudspannerecosystem/harbourbridge/schema"
 	"github.com/pingcap/parser"
 	"github.com/pingcap/parser/ast"
+	"github.com/pingcap/parser/opcode"
+	"github.com/pingcap/tidb/types"
 	driver "github.com/pingcap/tidb/types/parser_driver"
 )
 
@@ -726,6 +728,19 @@ func getVals(row []ast.ExprNode) ([]string, error) {
 		switch valueNode := item.(type) {
 		case *driver.ValueExpr:
 			values = append(values, fmt.Sprintf("%v", valueNode.GetValue()))
+		case *ast.UnaryOperationExpr:
+			if valueNode.Op != opcode.Minus {
+				return nil, fmt.Errorf("unexpected UnaryOperationExpr node with opcode %v", valueNode.Op)
+			}
+			valExpr, ok := valueNode.V.(*driver.ValueExpr)
+			if !ok {
+				return nil, fmt.Errorf("unexpected UnaryOperationExpr node with value type %T", valueNode.V)
+			}
+			value, err := getNegativeUnaryVals(valExpr)
+			if err != nil {
+				return nil, fmt.Errorf("unexpected UnaryOperationExpr node with value %v", valExpr.GetValue())
+			}
+			values = append(values, value)
 		default:
 			return nil, fmt.Errorf("unexpected value node %T", valueNode)
 		}
@@ -733,6 +748,21 @@ func getVals(row []ast.ExprNode) ([]string, error) {
 	return values, nil
 }
 
+func getNegativeUnaryVals(valExpr *driver.ValueExpr) (string, error) {
+	switch val := valExpr.GetValue().(type) {
+	case int64:
+		return fmt.Sprintf("%v", -1*val), nil
+	case *types.MyDecimal:
+		floatVal, err := val.ToFloat64()
+		if err != nil {
+			return "", fmt.Errorf("unexpected UnaryOperationExpr with value %v", val)
+		}
+		return fmt.Sprintf("%v", -1*floatVal), nil
+	default:
+		return "", fmt.Errorf("unexpected UnaryOperationExpr value with type %T", val)
+	}
+}
+
 func getTableNameInsert(stmt *ast.TableRefsClause) (string, error) {
 	if stmt.TableRefs == nil {
 		return "", fmt.Errorf("can't build table name as tablerefs is empty")


Test patch:
diff --git a/mysql/mysqldump_test.go b/mysql/mysqldump_test.go
index 1215b89..f8a365f 100644
--- a/mysql/mysqldump_test.go
+++ b/mysql/mysqldump_test.go
@@ -738,6 +738,15 @@ CREATE TABLE test (a text PRIMARY KEY, b text);`,
 			expectedData: []spannerData{
 				spannerData{table: "test", cols: []string{"id", "a", "b", "c", "d"}, vals: []interface{}{int64(1), int64(88), int64(44), int64(22), float64(444.9876)}}},
 		},
+		{
+			name: "Data conversion: negative values for smallint, mediumint, bigint, double",
+			input: `
+	CREATE TABLE test (id integer PRIMARY KEY, a smallint, b mediumint, c bigint, d double);
+	INSERT INTO test (id, a, b, c, d) VALUES (-1, -88, -44, -22, -444.9876);
+	`,
+			expectedData: []spannerData{
+				spannerData{table: "test", cols: []string{"id", "a", "b", "c", "d"}, vals: []interface{}{int64(-1), int64(-88), int64(-44), int64(-22), float64(-444.9876)}}},
+		},
 		// test with different timezone
 		{
 			name: "Data conversion:  text, timestamp, datetime, varchar",