From a439d11f668b3f3204b8742c9013d3e7e2882b67 Mon Sep 17 00:00:00 2001
From: Miguel de la Cruz <miguel@mcrx.me>
Date: Mon, 25 Apr 2022 17:43:06 +0200
Subject: [PATCH] Handle jsonb fields on postgres with binary_parameters set
 (#2854)

* Handle jsonb fields on postgres with binary_parameters set

* Finx linter adding dot
---
 server/services/store/sqlstore/board.go    | 12 +++++------
 server/services/store/sqlstore/sqlstore.go | 25 +++++++++++++++++++++-
 server/services/store/sqlstore/util.go     | 14 ++++++++++++
 3 files changed, 44 insertions(+), 7 deletions(-)

diff --git a/server/services/store/sqlstore/board.go b/server/services/store/sqlstore/board.go
index 193bc38d4..80f3e4861 100644
--- a/server/services/store/sqlstore/board.go
+++ b/server/services/store/sqlstore/board.go
@@ -268,7 +268,7 @@ func (s *SQLStore) getBoardsForUserAndTeam(db sq.BaseRunner, userID, teamID stri
 }
 
 func (s *SQLStore) insertBoard(db sq.BaseRunner, board *model.Board, userID string) (*model.Board, error) {
-	propertiesBytes, err := json.Marshal(board.Properties)
+	propertiesBytes, err := s.MarshalJSONB(board.Properties)
 	if err != nil {
 		s.logger.Error(
 			"failed to marshal board.Properties",
@@ -279,7 +279,7 @@ func (s *SQLStore) insertBoard(db sq.BaseRunner, board *model.Board, userID stri
 		return nil, err
 	}
 
-	cardPropertiesBytes, err := json.Marshal(board.CardProperties)
+	cardPropertiesBytes, err := s.MarshalJSONB(board.CardProperties)
 	if err != nil {
 		s.logger.Error(
 			"failed to marshal board.CardProperties",
@@ -382,11 +382,11 @@ func (s *SQLStore) deleteBoard(db sq.BaseRunner, boardID, userID string) error {
 		return err
 	}
 
-	propertiesBytes, err := json.Marshal(board.Properties)
+	propertiesBytes, err := s.MarshalJSONB(board.Properties)
 	if err != nil {
 		return err
 	}
-	cardPropertiesBytes, err := json.Marshal(board.CardProperties)
+	cardPropertiesBytes, err := s.MarshalJSONB(board.CardProperties)
 	if err != nil {
 		return err
 	}
@@ -692,12 +692,12 @@ func (s *SQLStore) undeleteBoard(db sq.BaseRunner, boardID string, modifiedBy st
 		return nil // undeleting not deleted board is not considered an error (for now)
 	}
 
-	propertiesJSON, err := json.Marshal(board.Properties)
+	propertiesJSON, err := s.MarshalJSONB(board.Properties)
 	if err != nil {
 		return err
 	}
 
-	cardPropertiesJSON, err := json.Marshal(board.CardProperties)
+	cardPropertiesJSON, err := s.MarshalJSONB(board.CardProperties)
 	if err != nil {
 		return err
 	}
diff --git a/server/services/store/sqlstore/sqlstore.go b/server/services/store/sqlstore/sqlstore.go
index d4af54c0b..7e164268c 100644
--- a/server/services/store/sqlstore/sqlstore.go
+++ b/server/services/store/sqlstore/sqlstore.go
@@ -2,6 +2,7 @@ package sqlstore
 
 import (
 	"database/sql"
+	"net/url"
 
 	"github.com/mattermost/mattermost-server/v6/plugin"
 
@@ -24,6 +25,7 @@ type SQLStore struct {
 	logger           *mlog.Logger
 	NewMutexFn       MutexFactory
 	pluginAPI        *plugin.API
+	isBinaryParam    bool
 }
 
 // MutexFactory is used by the store in plugin mode to generate
@@ -49,7 +51,14 @@ func New(params Params) (*SQLStore, error) {
 		pluginAPI:        params.PluginAPI,
 	}
 
-	err := store.Migrate()
+	var err error
+	store.isBinaryParam, err = store.computeBinaryParam()
+	if err != nil {
+		params.Logger.Error(`Cannot compute binary parameter`, mlog.Err(err))
+		return nil, err
+	}
+
+	err = store.Migrate()
 	if err != nil {
 		params.Logger.Error(`Table creation / migration failed`, mlog.Err(err))
 
@@ -58,6 +67,20 @@ func New(params Params) (*SQLStore, error) {
 	return store, nil
 }
 
+// computeBinaryParam returns whether the data source uses binary_parameters
+// when using Postgres.
+func (s *SQLStore) computeBinaryParam() (bool, error) {
+	if s.dbType != model.PostgresDBType {
+		return false, nil
+	}
+
+	url, err := url.Parse(s.connectionString)
+	if err != nil {
+		return false, err
+	}
+	return url.Query().Get("binary_parameters") == "yes", nil
+}
+
 // Shutdown close the connection with the store.
 func (s *SQLStore) Shutdown() error {
 	return s.db.Close()
diff --git a/server/services/store/sqlstore/util.go b/server/services/store/sqlstore/util.go
index 184dcf09a..8a01e933d 100644
--- a/server/services/store/sqlstore/util.go
+++ b/server/services/store/sqlstore/util.go
@@ -2,6 +2,7 @@ package sqlstore
 
 import (
 	"database/sql"
+	"encoding/json"
 	"fmt"
 	"io/ioutil"
 	"os"
@@ -23,6 +24,19 @@ func (s *SQLStore) IsErrNotFound(err error) bool {
 	return model.IsErrNotFound(err)
 }
 
+func (s *SQLStore) MarshalJSONB(data interface{}) ([]byte, error) {
+	b, err := json.Marshal(data)
+	if err != nil {
+		return nil, err
+	}
+
+	if s.isBinaryParam {
+		b = append([]byte{0x01}, b...)
+	}
+
+	return b, nil
+}
+
 func PrepareNewTestDatabase() (dbType string, connectionString string, err error) {
 	dbType = strings.TrimSpace(os.Getenv("FB_STORE_TEST_DB_TYPE"))
 	if dbType == "" {