diff --git a/store/sqlstore/compliance_store.go b/store/sqlstore/compliance_store.go index 0d101cebb3..314d49a472 100644 --- a/store/sqlstore/compliance_store.go +++ b/store/sqlstore/compliance_store.go @@ -4,9 +4,11 @@ package sqlstore import ( - "strconv" + "database/sql" + "fmt" "strings" + sq "github.com/Masterminds/squirrel" "github.com/pkg/errors" "github.com/mattermost/mattermost-server/v6/model" @@ -43,7 +45,18 @@ func (s SqlComplianceStore) Save(compliance *model.Compliance) (*model.Complianc return nil, err } - if err := s.GetMaster().Insert(compliance); err != nil { + // DESC is a keyword + var desc string + if s.DriverName() == model.DatabaseDriverPostgres { + desc = `"desc"` + } else { + desc = "`Desc`" + } + + query := `INSERT INTO Compliances (Id, CreateAt, UserId, Status, Count, ` + desc + `, Type, StartAt, EndAt, Keywords, Emails) + VALUES + (:Id, :CreateAt, :UserId, :Status, :Count, :Desc, :Type, :StartAt, :EndAt, :Keywords, :Emails)` + if _, err := s.GetMasterX().NamedExec(query, compliance); err != nil { return nil, errors.Wrap(err, "failed to save Compliance") } return compliance, nil @@ -54,58 +67,93 @@ func (s SqlComplianceStore) Update(compliance *model.Compliance) (*model.Complia return nil, err } - if _, err := s.GetMaster().Update(compliance); err != nil { + query := s.getQueryBuilder(). + Update("Compliances"). + Set("CreateAt", compliance.CreateAt). + Set("UserId", compliance.UserId). + Set("Status", compliance.Status). + Set("Count", compliance.Count). + Set("Type", compliance.Type). + Set("StartAt", compliance.StartAt). + Set("EndAt", compliance.EndAt). + Set("Keywords", compliance.Keywords). + Set("Emails", compliance.Emails). + Where(sq.Eq{"Id": compliance.Id}) + + // DESC is a keyword + if s.DriverName() == model.DatabaseDriverPostgres { + query = query.Set(`"desc"`, compliance.Desc) + } else { + query = query.Set("`Desc`", compliance.Desc) + } + + queryString, args, err := query.ToSql() + if err != nil { + return nil, errors.Wrap(err, "compliances_tosql") + } + + res, err := s.GetMasterX().Exec(queryString, args...) + if err != nil { return nil, errors.Wrap(err, "failed to update Compliance") } + count, err := res.RowsAffected() + if err != nil { + return nil, errors.Wrap(err, "error while getting rows_affected") + } + if count > 1 { + return nil, fmt.Errorf("unexpected count while updating compliances: count=%d, Id=%s", count, compliance.Id) + } return compliance, nil } func (s SqlComplianceStore) GetAll(offset, limit int) (model.Compliances, error) { - query := "SELECT * FROM Compliances ORDER BY CreateAt DESC LIMIT :Limit OFFSET :Offset" - - var compliances model.Compliances - if _, err := s.GetReplica().Select(&compliances, query, map[string]interface{}{"Offset": offset, "Limit": limit}); err != nil { + query := "SELECT * FROM Compliances ORDER BY CreateAt DESC LIMIT ? OFFSET ?" + compliances := model.Compliances{} + if err := s.GetReplicaX().Select(&compliances, query, limit, offset); err != nil { return nil, errors.Wrap(err, "failed to find all Compliances") } return compliances, nil } func (s SqlComplianceStore) Get(id string) (*model.Compliance, error) { - obj, err := s.GetReplica().Get(model.Compliance{}, id) - if err != nil { + var compliance model.Compliance + if err := s.GetReplicaX().Get(&compliance, `SELECT * FROM Compliances WHERE Id = ?`, id); err != nil { + if err == sql.ErrNoRows { + return nil, store.NewErrNotFound("Compliances", id) + } return nil, errors.Wrapf(err, "failed to get Compliance with id=%s", id) } - if obj == nil { + if compliance.Id == "" { return nil, store.NewErrNotFound("Compliance", id) } - return obj.(*model.Compliance), nil + return &compliance, nil } func (s SqlComplianceStore) ComplianceExport(job *model.Compliance, cursor model.ComplianceExportCursor, limit int) ([]*model.CompliancePost, model.ComplianceExportCursor, error) { - props := map[string]interface{}{"EndTime": job.EndAt, "Limit": limit} - keywordQuery := "" + var argsKeywords []interface{} keywords := strings.Fields(strings.TrimSpace(strings.ToLower(strings.Replace(job.Keywords, ",", " ", -1)))) if len(keywords) > 0 { clauses := make([]string, len(keywords)) for i, keyword := range keywords { keyword = sanitizeSearchTerm(keyword, "\\") - clauses[i] = "LOWER(Posts.Message) LIKE :Keyword" + strconv.Itoa(i) - props["Keyword"+strconv.Itoa(i)] = "%" + keyword + "%" + clauses[i] = "LOWER(Posts.Message) LIKE ?" + argsKeywords = append(argsKeywords, "%"+keyword+"%") } keywordQuery = "AND (" + strings.Join(clauses, " OR ") + ")" } emailQuery := "" + var argsEmails []interface{} emails := strings.Fields(strings.TrimSpace(strings.ToLower(strings.Replace(job.Emails, ",", " ", -1)))) if len(emails) > 0 { clauses := make([]string, len(emails)) for i, email := range emails { - clauses[i] = "Users.Email = :Email" + strconv.Itoa(i) - props["Email"+strconv.Itoa(i)] = email + clauses[i] = "Users.Email = ?" + argsEmails = append(argsEmails, email) } emailQuery = "AND (" + strings.Join(clauses, " OR ") + ")" @@ -114,14 +162,18 @@ func (s SqlComplianceStore) ComplianceExport(job *model.Compliance, cursor model // The idea is to first iterate over the channel posts, and then when we run out of those, // start iterating over the direct message posts. - var channelPosts []*model.CompliancePost + channelPosts := []*model.CompliancePost{} channelsQuery := "" + var argsChannelsQuery []interface{} if !cursor.ChannelsQueryCompleted { if cursor.LastChannelsQueryPostCreateAt == 0 { cursor.LastChannelsQueryPostCreateAt = job.StartAt } - props["LastPostCreateAt"] = cursor.LastChannelsQueryPostCreateAt - props["LastPostId"] = cursor.LastChannelsQueryPostID + //append the named parameters of SQL query in the correct order to argsChannelsQuery + argsChannelsQuery = append(argsChannelsQuery, cursor.LastChannelsQueryPostCreateAt, cursor.LastChannelsQueryPostCreateAt, cursor.LastChannelsQueryPostID, job.EndAt) + argsChannelsQuery = append(argsChannelsQuery, argsEmails...) + argsChannelsQuery = append(argsChannelsQuery, argsKeywords...) + argsChannelsQuery = append(argsChannelsQuery, limit) channelsQuery = ` SELECT Teams.Name AS TeamName, @@ -156,15 +208,15 @@ func (s SqlComplianceStore) ComplianceExport(job *model.Compliance, cursor model AND Posts.ChannelId = Channels.Id AND Posts.UserId = Users.Id AND ( - Posts.CreateAt > :LastPostCreateAt - OR (Posts.CreateAt = :LastPostCreateAt AND Posts.Id > :LastPostId) + Posts.CreateAt > ? + OR (Posts.CreateAt = ? AND Posts.Id > ?) ) - AND Posts.CreateAt < :EndTime + AND Posts.CreateAt < ? ` + emailQuery + ` ` + keywordQuery + ` ORDER BY Posts.CreateAt, Posts.Id - LIMIT :Limit` - if _, err := s.GetReplica().Select(&channelPosts, channelsQuery, props); err != nil { + LIMIT ?` + if err := s.GetReplicaX().Select(&channelPosts, channelsQuery, argsChannelsQuery...); err != nil { return nil, cursor, errors.Wrap(err, "unable to export compliance") } if len(channelPosts) < limit { @@ -175,15 +227,18 @@ func (s SqlComplianceStore) ComplianceExport(job *model.Compliance, cursor model } } - var directMessagePosts []*model.CompliancePost + directMessagePosts := []*model.CompliancePost{} directMessagesQuery := "" + var argsDirectMessagesQuery []interface{} if !cursor.DirectMessagesQueryCompleted && len(channelPosts) < limit { if cursor.LastDirectMessagesQueryPostCreateAt == 0 { cursor.LastDirectMessagesQueryPostCreateAt = job.StartAt } - props["LastPostCreateAt"] = cursor.LastDirectMessagesQueryPostCreateAt - props["LastPostId"] = cursor.LastDirectMessagesQueryPostID - props["Limit"] = limit - len(channelPosts) + //append the named parameters of SQL query in the correct order to argsDirectMessagesQuery + argsDirectMessagesQuery = append(argsDirectMessagesQuery, cursor.LastDirectMessagesQueryPostCreateAt, cursor.LastDirectMessagesQueryPostCreateAt, cursor.LastDirectMessagesQueryPostID, job.EndAt) + argsDirectMessagesQuery = append(argsDirectMessagesQuery, argsEmails...) + argsDirectMessagesQuery = append(argsDirectMessagesQuery, argsKeywords...) + argsDirectMessagesQuery = append(argsDirectMessagesQuery, limit-len(channelPosts)) directMessagesQuery = ` SELECT 'direct-messages' AS TeamName, @@ -217,16 +272,16 @@ func (s SqlComplianceStore) ComplianceExport(job *model.Compliance, cursor model AND Posts.ChannelId = Channels.Id AND Posts.UserId = Users.Id AND ( - Posts.CreateAt > :LastPostCreateAt - OR (Posts.CreateAt = :LastPostCreateAt AND Posts.Id > :LastPostId) + Posts.CreateAt > ? + OR (Posts.CreateAt = ? AND Posts.Id > ?) ) - AND Posts.CreateAt < :EndTime + AND Posts.CreateAt < ? ` + emailQuery + ` ` + keywordQuery + ` ORDER BY Posts.CreateAt, Posts.Id - LIMIT :Limit` + LIMIT ?` - if _, err := s.GetReplica().Select(&directMessagePosts, directMessagesQuery, props); err != nil { + if err := s.GetReplicaX().Select(&directMessagePosts, directMessagesQuery, argsDirectMessagesQuery...); err != nil { return nil, cursor, errors.Wrap(err, "unable to export compliance") } if len(directMessagePosts) < limit { @@ -241,11 +296,8 @@ func (s SqlComplianceStore) ComplianceExport(job *model.Compliance, cursor model } func (s SqlComplianceStore) MessageExport(cursor model.MessageExportCursor, limit int) ([]*model.MessageExport, model.MessageExportCursor, error) { - props := map[string]interface{}{ - "LastPostUpdateAt": cursor.LastPostUpdateAt, - "LastPostId": cursor.LastPostId, - "Limit": limit, - } + var args []interface{} + args = append(args, cursor.LastPostUpdateAt, cursor.LastPostUpdateAt, cursor.LastPostId, limit) query := `SELECT Posts.Id AS PostId, @@ -280,17 +332,17 @@ func (s SqlComplianceStore) MessageExport(cursor model.MessageExportCursor, limi LEFT OUTER JOIN Users ON Posts.UserId = Users.Id LEFT JOIN Bots ON Bots.UserId = Posts.UserId WHERE ( - Posts.UpdateAt > :LastPostUpdateAt + Posts.UpdateAt > ? OR ( - Posts.UpdateAt = :LastPostUpdateAt - AND Posts.Id > :LastPostId + Posts.UpdateAt = ? + AND Posts.Id > ? ) ) AND Posts.Type NOT LIKE 'system_%' ORDER BY PostUpdateAt, PostId - LIMIT :Limit` + LIMIT ?` - var cposts []*model.MessageExport - if _, err := s.GetReplica().Select(&cposts, query, props); err != nil { + cposts := []*model.MessageExport{} + if err := s.GetReplicaX().Select(&cposts, query, args...); err != nil { return nil, cursor, errors.Wrap(err, "unable to export messages") } if len(cposts) > 0 {