github.com/status-im/status-go@v1.1.0/protocol/message_persistence.go (about)

     1  package protocol
     2  
     3  import (
     4  	"context"
     5  	"database/sql"
     6  	"encoding/json"
     7  	"fmt"
     8  	"sort"
     9  	"strings"
    10  
    11  	"github.com/golang/protobuf/proto"
    12  	"github.com/lib/pq"
    13  
    14  	"github.com/status-im/status-go/protocol/common"
    15  	"github.com/status-im/status-go/protocol/protobuf"
    16  )
    17  
    18  var basicMessagesSelectQuery = `
    19  SELECT    %s %s
    20  FROM      user_messages m1
    21  LEFT JOIN user_messages m2
    22  ON        m1.response_to = m2.id
    23  LEFT JOIN contacts c
    24  ON        m1.source = c.id
    25  LEFT JOIN discord_messages dm
    26  ON        m1.discord_message_id = dm.id
    27  LEFT JOIN discord_message_authors dm_author
    28  ON        dm.author_id = dm_author.id
    29  LEFT JOIN discord_message_attachments dm_attachment
    30  ON        dm.id = dm_attachment.discord_message_id
    31  LEFT JOIN discord_messages m2_dm
    32  ON        m2.discord_message_id = m2_dm.id
    33  LEFT JOIN discord_message_authors m2_dm_author
    34  ON        m2_dm.author_id = m2_dm_author.id
    35  LEFT JOIN bridge_messages bm
    36  ON        m1.id = bm.user_messages_id
    37  LEFT JOIN bridge_messages bm_response
    38  ON        m2.id = bm_response.user_messages_id
    39  `
    40  
    41  var basicInsertDiscordMessageAuthorQuery = `INSERT OR REPLACE INTO discord_message_authors(id,name,discriminator,nickname,avatar_url, avatar_image_payload) VALUES (?,?,?,?,?,?)`
    42  
    43  var cursor = "substr('0000000000000000000000000000000000000000000000000000000000000000' || m1.clock_value, -64, 64) || m1.id"
    44  var cursorField = cursor + " as cursor"
    45  
    46  var caseSensitiveSearchCond = "(m1.text LIKE '%' || ? || '%' OR bm.content LIKE '%' || ? || '%' OR dm.content LIKE '%' || ? || '%')"
    47  var caseInsensitiveSearchCond = "(LOWER(m1.text) LIKE LOWER('%' || ? || '%') OR LOWER(bm.content) LIKE LOWER('%' || ? || '%') OR LOWER(dm.content) LIKE LOWER('%' || ? || '%'))"
    48  
    49  func (db sqlitePersistence) buildMessagesQueryWithAdditionalFields(additionalSelectFields, whereAndTheRest string) string {
    50  	allFields := db.tableUserMessagesAllFieldsJoin()
    51  	if additionalSelectFields != "" {
    52  		additionalSelectFields = "," + additionalSelectFields
    53  	}
    54  	base := fmt.Sprintf(basicMessagesSelectQuery, allFields, additionalSelectFields)
    55  	return base + " " + whereAndTheRest
    56  }
    57  
    58  func (db sqlitePersistence) buildMessagesQuery(whereAndTheRest string) string {
    59  	return db.buildMessagesQueryWithAdditionalFields("", whereAndTheRest)
    60  }
    61  
    62  func (db sqlitePersistence) tableUserMessagesAllFields() string {
    63  	return `id,
    64      		whisper_timestamp,
    65      		source,
    66      		text,
    67      		content_type,
    68      		username,
    69      		timestamp,
    70      		chat_id,
    71  		local_chat_id,
    72      		message_type,
    73      		clock_value,
    74      		seen,
    75      		outgoing_status,
    76  		parsed_text,
    77  		sticker_pack,
    78  		sticker_hash,
    79  		image_payload,
    80  		image_type,
    81  		album_id,
    82  		album_images,
    83  		album_images_count,
    84  		image_width,
    85  		image_height,
    86  		image_base64,
    87  		audio_payload,
    88  		audio_type,
    89  		audio_duration_ms,
    90  		audio_base64,
    91  		community_id,
    92  		mentions,
    93  		links,
    94  		unfurled_links,
    95  		unfurled_status_links,
    96  		command_id,
    97  		command_value,
    98  		command_from,
    99  		command_address,
   100  		command_contract,
   101  		command_transaction_hash,
   102  		command_state,
   103  		command_signature,
   104  		replace_message,
   105  		edited_at,
   106  		deleted,
   107  		deleted_by,
   108  		deleted_for_me,
   109  		rtl,
   110  		line_count,
   111  		response_to,
   112  		gap_from,
   113  		gap_to,
   114  		contact_request_state,
   115  		contact_verification_status,
   116  		mentioned,
   117  		replied,
   118      discord_message_id`
   119  }
   120  
   121  // keep the same order as in tableUserMessagesScanAllFields
   122  func (db sqlitePersistence) tableUserMessagesAllFieldsJoin() string {
   123  	return `m1.id,
   124      		m1.whisper_timestamp,
   125      		m1.source,
   126      		m1.text,
   127      		m1.content_type,
   128      		m1.username,
   129      		m1.timestamp,
   130      		m1.chat_id,
   131  		m1.local_chat_id,
   132      		m1.message_type,
   133      		m1.clock_value,
   134      		m1.seen,
   135      		m1.outgoing_status,
   136  		m1.parsed_text,
   137  		m1.sticker_pack,
   138  		m1.sticker_hash,
   139  		m1.image_payload,
   140  		m1.image_type,
   141  		COALESCE(m1.album_id, ""),
   142  		COALESCE(m1.album_images_count, 0),
   143  		COALESCE(m1.image_width, 0),
   144  		COALESCE(m1.image_height, 0),
   145  		COALESCE(m1.audio_duration_ms,0),
   146  		m1.community_id,
   147  		m1.mentions,
   148  		m1.links,
   149  		m1.unfurled_links,
   150  		m1.unfurled_status_links,
   151  		m1.command_id,
   152  		m1.command_value,
   153  		m1.command_from,
   154  		m1.command_address,
   155  		m1.command_contract,
   156  		m1.command_transaction_hash,
   157  		m1.command_state,
   158  		m1.command_signature,
   159  		m1.replace_message,
   160  		m1.edited_at,
   161  		m1.deleted,
   162  		m1.deleted_by,
   163  		m1.deleted_for_me,
   164  		m1.rtl,
   165  		m1.line_count,
   166  		m1.response_to,
   167  		m1.gap_from,
   168  		m1.gap_to,
   169  		m1.contact_request_state,
   170  		m1.contact_verification_status,
   171  		m1.mentioned,
   172  		m1.replied,
   173      COALESCE(m1.discord_message_id, ""),
   174      COALESCE(dm.author_id, ""),
   175      COALESCE(dm.type, ""),
   176      COALESCE(dm.timestamp, ""),
   177      COALESCE(dm.timestamp_edited, ""),
   178      COALESCE(dm.content, ""),
   179      COALESCE(dm.reference_message_id, ""),
   180      COALESCE(dm.reference_channel_id, ""),
   181      COALESCE(dm_author.name, ""),
   182      COALESCE(dm_author.discriminator, ""),
   183      COALESCE(dm_author.nickname, ""),
   184      COALESCE(dm_author.avatar_url, ""),
   185      COALESCE(dm_attachment.id, ""),
   186      COALESCE(dm_attachment.discord_message_id, ""),
   187      COALESCE(dm_attachment.url, ""),
   188      COALESCE(dm_attachment.file_name, ""),
   189      COALESCE(dm_attachment.content_type, ""),
   190  		m2.source,
   191  		m2.text,
   192  		m2.parsed_text,
   193  		m2.album_images,
   194  		m2.album_images_count,
   195  		m2.audio_duration_ms,
   196  		m2.community_id,
   197  		m2.id,
   198          m2.content_type,
   199          m2.deleted,
   200          m2.deleted_for_me,
   201  		c.alias,
   202  		c.identicon,
   203      COALESCE(m2.discord_message_id, ""),
   204  		COALESCE(m2_dm_author.name, ""),
   205  		COALESCE(m2_dm_author.nickname, ""),
   206  		COALESCE(m2_dm_author.avatar_url, ""),
   207  		COALESCE(bm.bridge_name, ""),
   208  		COALESCE(bm.user_name, ""),
   209  		COALESCE(bm.user_avatar, ""),
   210  		COALESCE(bm.user_id, ""),
   211  		COALESCE(bm.content, ""),
   212  		COALESCE(bm.message_id, ""),
   213  		COALESCE(bm.parent_message_id, ""),
   214  		COALESCE(bm_response.bridge_name, ""),
   215  		COALESCE(bm_response.user_name, ""),
   216  		COALESCE(bm_response.user_avatar, ""),
   217  		COALESCE(bm_response.user_id, ""),
   218  		COALESCE(bm_response.content, "")`
   219  }
   220  
   221  func (db sqlitePersistence) tableUserMessagesAllFieldsCount() int {
   222  	return strings.Count(db.tableUserMessagesAllFields(), ",") + 1
   223  }
   224  
   225  type scanner interface {
   226  	Scan(dest ...interface{}) error
   227  }
   228  
   229  // keep the same order as in tableUserMessagesAllFieldsJoin
   230  func (db sqlitePersistence) tableUserMessagesScanAllFields(row scanner, message *common.Message, others ...interface{}) error {
   231  	var quotedID sql.NullString
   232  	var ContentType sql.NullInt64
   233  	var quotedText sql.NullString
   234  	var quotedParsedText []byte
   235  	var quotedAlbumImages []byte
   236  	var quotedAlbumImagesCount sql.NullInt64
   237  	var quotedFrom sql.NullString
   238  	var quotedAudioDuration sql.NullInt64
   239  	var quotedCommunityID sql.NullString
   240  	var quotedDeleted sql.NullBool
   241  	var quotedDeletedForMe sql.NullBool
   242  	var serializedMentions []byte
   243  	var serializedLinks []byte
   244  	var serializedUnfurledLinks []byte
   245  	var serializedUnfurledStatusLinks []byte
   246  	var alias sql.NullString
   247  	var identicon sql.NullString
   248  	var communityID sql.NullString
   249  	var gapFrom sql.NullInt64
   250  	var gapTo sql.NullInt64
   251  	var editedAt sql.NullInt64
   252  	var deleted sql.NullBool
   253  	var deletedBy sql.NullString
   254  	var deletedForMe sql.NullBool
   255  	var contactRequestState sql.NullInt64
   256  	var contactVerificationState sql.NullInt64
   257  
   258  	sticker := &protobuf.StickerMessage{}
   259  	command := &common.CommandParameters{}
   260  	audio := &protobuf.AudioMessage{}
   261  	image := &protobuf.ImageMessage{}
   262  	discordMessage := &protobuf.DiscordMessage{
   263  		Author:      &protobuf.DiscordMessageAuthor{},
   264  		Reference:   &protobuf.DiscordMessageReference{},
   265  		Attachments: []*protobuf.DiscordMessageAttachment{},
   266  	}
   267  	bridgeMessage := &protobuf.BridgeMessage{}
   268  
   269  	quotedBridgeMessage := &protobuf.BridgeMessage{}
   270  
   271  	quotedDiscordMessage := &protobuf.DiscordMessage{
   272  		Author: &protobuf.DiscordMessageAuthor{},
   273  	}
   274  
   275  	attachment := &protobuf.DiscordMessageAttachment{}
   276  
   277  	args := []interface{}{
   278  		&message.ID,
   279  		&message.WhisperTimestamp,
   280  		&message.From, // source in table
   281  		&message.Text,
   282  		&message.ContentType,
   283  		&message.Alias,
   284  		&message.Timestamp,
   285  		&message.ChatId,
   286  		&message.LocalChatID,
   287  		&message.MessageType,
   288  		&message.Clock,
   289  		&message.Seen,
   290  		&message.OutgoingStatus,
   291  		&message.ParsedText,
   292  		&sticker.Pack,
   293  		&sticker.Hash,
   294  		&image.Payload,
   295  		&image.Format,
   296  		&image.AlbumId,
   297  		&image.AlbumImagesCount,
   298  		&image.Width,
   299  		&image.Height,
   300  		&audio.DurationMs,
   301  		&communityID,
   302  		&serializedMentions,
   303  		&serializedLinks,
   304  		&serializedUnfurledLinks,
   305  		&serializedUnfurledStatusLinks,
   306  		&command.ID,
   307  		&command.Value,
   308  		&command.From,
   309  		&command.Address,
   310  		&command.Contract,
   311  		&command.TransactionHash,
   312  		&command.CommandState,
   313  		&command.Signature,
   314  		&message.Replace,
   315  		&editedAt,
   316  		&deleted,
   317  		&deletedBy,
   318  		&deletedForMe,
   319  		&message.RTL,
   320  		&message.LineCount,
   321  		&message.ResponseTo,
   322  		&gapFrom,
   323  		&gapTo,
   324  		&contactRequestState,
   325  		&contactVerificationState,
   326  		&message.Mentioned,
   327  		&message.Replied,
   328  		&discordMessage.Id,
   329  		&discordMessage.Author.Id,
   330  		&discordMessage.Type,
   331  		&discordMessage.Timestamp,
   332  		&discordMessage.TimestampEdited,
   333  		&discordMessage.Content,
   334  		&discordMessage.Reference.MessageId,
   335  		&discordMessage.Reference.ChannelId,
   336  		&discordMessage.Author.Name,
   337  		&discordMessage.Author.Discriminator,
   338  		&discordMessage.Author.Nickname,
   339  		&discordMessage.Author.AvatarUrl,
   340  		&attachment.Id,
   341  		&attachment.MessageId,
   342  		&attachment.Url,
   343  		&attachment.FileName,
   344  		&attachment.ContentType,
   345  		&quotedFrom,
   346  		&quotedText,
   347  		&quotedParsedText,
   348  		&quotedAlbumImages,
   349  		&quotedAlbumImagesCount,
   350  		&quotedAudioDuration,
   351  		&quotedCommunityID,
   352  		&quotedID,
   353  		&ContentType,
   354  		&quotedDeleted,
   355  		&quotedDeletedForMe,
   356  		&alias,
   357  		&identicon,
   358  		&quotedDiscordMessage.Id,
   359  		&quotedDiscordMessage.Author.Name,
   360  		&quotedDiscordMessage.Author.Nickname,
   361  		&quotedDiscordMessage.Author.AvatarUrl,
   362  		&bridgeMessage.BridgeName,
   363  		&bridgeMessage.UserName,
   364  		&bridgeMessage.UserAvatar,
   365  		&bridgeMessage.UserID,
   366  		&bridgeMessage.Content,
   367  		&bridgeMessage.MessageID,
   368  		&bridgeMessage.ParentMessageID,
   369  		&quotedBridgeMessage.BridgeName,
   370  		&quotedBridgeMessage.UserName,
   371  		&quotedBridgeMessage.UserAvatar,
   372  		&quotedBridgeMessage.UserID,
   373  		&quotedBridgeMessage.Content,
   374  	}
   375  	err := row.Scan(append(args, others...)...)
   376  	if err != nil {
   377  		return err
   378  	}
   379  
   380  	if editedAt.Valid {
   381  		message.EditedAt = uint64(editedAt.Int64)
   382  	}
   383  
   384  	if deleted.Valid {
   385  		message.Deleted = deleted.Bool
   386  	}
   387  
   388  	if deletedBy.Valid {
   389  		message.DeletedBy = deletedBy.String
   390  	}
   391  
   392  	if deletedForMe.Valid {
   393  		message.DeletedForMe = deletedForMe.Bool
   394  	}
   395  
   396  	if contactRequestState.Valid {
   397  		message.ContactRequestState = common.ContactRequestState(contactRequestState.Int64)
   398  	}
   399  
   400  	if contactVerificationState.Valid {
   401  		message.ContactVerificationState = common.ContactVerificationState(contactVerificationState.Int64)
   402  	}
   403  
   404  	if quotedText.Valid {
   405  		if quotedDeleted.Bool || quotedDeletedForMe.Bool {
   406  			message.QuotedMessage = &common.QuotedMessage{
   407  				ID:           quotedID.String,
   408  				From:         quotedFrom.String,
   409  				Deleted:      quotedDeleted.Bool,
   410  				DeletedForMe: quotedDeletedForMe.Bool,
   411  			}
   412  		} else {
   413  			message.QuotedMessage = &common.QuotedMessage{
   414  				ID:               quotedID.String,
   415  				ContentType:      ContentType.Int64,
   416  				From:             quotedFrom.String,
   417  				Text:             quotedText.String,
   418  				ParsedText:       quotedParsedText,
   419  				AlbumImages:      quotedAlbumImages,
   420  				AlbumImagesCount: quotedAlbumImagesCount.Int64,
   421  				CommunityID:      quotedCommunityID.String,
   422  				Deleted:          quotedDeleted.Bool,
   423  			}
   424  			if message.QuotedMessage.ContentType == int64(protobuf.ChatMessage_DISCORD_MESSAGE) {
   425  				message.QuotedMessage.DiscordMessage = quotedDiscordMessage
   426  			}
   427  			if message.QuotedMessage.ContentType == int64(protobuf.ChatMessage_BRIDGE_MESSAGE) {
   428  				message.QuotedMessage.BridgeMessage = quotedBridgeMessage
   429  			}
   430  		}
   431  	}
   432  	message.Alias = alias.String
   433  	message.Identicon = identicon.String
   434  
   435  	if gapFrom.Valid && gapTo.Valid {
   436  		message.GapParameters = &common.GapParameters{
   437  			From: uint32(gapFrom.Int64),
   438  			To:   uint32(gapTo.Int64),
   439  		}
   440  	}
   441  
   442  	if communityID.Valid {
   443  		message.CommunityID = communityID.String
   444  	}
   445  
   446  	if serializedMentions != nil {
   447  		err := json.Unmarshal(serializedMentions, &message.Mentions)
   448  		if err != nil {
   449  			return err
   450  		}
   451  	}
   452  
   453  	if serializedLinks != nil {
   454  		err := json.Unmarshal(serializedLinks, &message.Links)
   455  		if err != nil {
   456  			return err
   457  		}
   458  	}
   459  
   460  	if serializedUnfurledLinks != nil {
   461  		err = json.Unmarshal(serializedUnfurledLinks, &message.UnfurledLinks)
   462  		if err != nil {
   463  			return err
   464  		}
   465  	}
   466  
   467  	if serializedUnfurledStatusLinks != nil {
   468  		// use proto.Marshal, because json.Marshal doesn't support `oneof` fields
   469  		var links protobuf.UnfurledStatusLinks
   470  		err = proto.Unmarshal(serializedUnfurledStatusLinks, &links)
   471  		if err != nil {
   472  			return err
   473  		}
   474  		message.UnfurledStatusLinks = &links
   475  	}
   476  
   477  	if attachment.Id != "" {
   478  		discordMessage.Attachments = append(discordMessage.Attachments, attachment)
   479  	}
   480  
   481  	switch message.ContentType {
   482  	case protobuf.ChatMessage_STICKER:
   483  		message.Payload = &protobuf.ChatMessage_Sticker{Sticker: sticker}
   484  
   485  	case protobuf.ChatMessage_AUDIO:
   486  		message.Payload = &protobuf.ChatMessage_Audio{Audio: audio}
   487  
   488  	case protobuf.ChatMessage_TRANSACTION_COMMAND:
   489  		message.CommandParameters = command
   490  
   491  	case protobuf.ChatMessage_IMAGE:
   492  		message.Payload = &protobuf.ChatMessage_Image{Image: image}
   493  
   494  	case protobuf.ChatMessage_DISCORD_MESSAGE:
   495  		message.Payload = &protobuf.ChatMessage_DiscordMessage{
   496  			DiscordMessage: discordMessage,
   497  		}
   498  
   499  	case protobuf.ChatMessage_BRIDGE_MESSAGE:
   500  		message.Payload = &protobuf.ChatMessage_BridgeMessage{
   501  			BridgeMessage: bridgeMessage,
   502  		}
   503  	}
   504  
   505  	return nil
   506  }
   507  
   508  func (db sqlitePersistence) tableUserMessagesAllValues(message *common.Message) ([]interface{}, error) {
   509  	var gapFrom, gapTo uint32
   510  
   511  	var albumImages []byte
   512  	if message.QuotedMessage != nil {
   513  		albumImages = []byte(message.QuotedMessage.AlbumImages)
   514  	}
   515  
   516  	sticker := message.GetSticker()
   517  	if sticker == nil {
   518  		sticker = &protobuf.StickerMessage{}
   519  	}
   520  
   521  	image := message.GetImage()
   522  	if image == nil {
   523  		image = &protobuf.ImageMessage{}
   524  	}
   525  
   526  	audio := message.GetAudio()
   527  	if audio == nil {
   528  		audio = &protobuf.AudioMessage{}
   529  	}
   530  
   531  	command := message.CommandParameters
   532  	if command == nil {
   533  		command = &common.CommandParameters{}
   534  	}
   535  
   536  	discordMessage := message.GetDiscordMessage()
   537  	if discordMessage == nil {
   538  		discordMessage = &protobuf.DiscordMessage{
   539  			Author:      &protobuf.DiscordMessageAuthor{},
   540  			Reference:   &protobuf.DiscordMessageReference{},
   541  			Attachments: make([]*protobuf.DiscordMessageAttachment, 0),
   542  		}
   543  	}
   544  
   545  	if message.GapParameters != nil {
   546  		gapFrom = message.GapParameters.From
   547  		gapTo = message.GapParameters.To
   548  	}
   549  
   550  	var serializedMentions []byte
   551  	var err error
   552  	if len(message.Mentions) != 0 {
   553  		serializedMentions, err = json.Marshal(message.Mentions)
   554  		if err != nil {
   555  			return nil, err
   556  		}
   557  	}
   558  
   559  	var serializedLinks []byte
   560  	if len(message.Links) != 0 {
   561  		serializedLinks, err = json.Marshal(message.Links)
   562  		if err != nil {
   563  			return nil, err
   564  		}
   565  	}
   566  
   567  	var serializedUnfurledLinks []byte
   568  	if links := message.GetUnfurledLinks(); links != nil {
   569  		serializedUnfurledLinks, err = json.Marshal(links)
   570  		if err != nil {
   571  			return nil, err
   572  		}
   573  	}
   574  
   575  	var serializedUnfurledStatusLinks []byte
   576  	if links := message.GetUnfurledStatusLinks(); links != nil {
   577  		// use proto.Marshal, because json.Marshal doesn't support `oneof` fields
   578  		serializedUnfurledStatusLinks, err = proto.Marshal(links)
   579  		if err != nil {
   580  			return nil, err
   581  		}
   582  	}
   583  
   584  	return []interface{}{
   585  		message.ID,
   586  		message.WhisperTimestamp,
   587  		message.From, // source in table
   588  		message.Text,
   589  		message.ContentType,
   590  		message.Alias,
   591  		message.Timestamp,
   592  		message.ChatId,
   593  		message.LocalChatID,
   594  		message.MessageType,
   595  		message.Clock,
   596  		message.Seen,
   597  		message.OutgoingStatus,
   598  		message.ParsedText,
   599  		sticker.Pack,
   600  		sticker.Hash,
   601  		image.Payload,
   602  		image.Format,
   603  		image.AlbumId,
   604  		albumImages,
   605  		image.AlbumImagesCount,
   606  		image.Width,
   607  		image.Height,
   608  		message.Base64Image,
   609  		audio.Payload,
   610  		audio.Type,
   611  		audio.DurationMs,
   612  		message.Base64Audio,
   613  		message.CommunityID,
   614  		serializedMentions,
   615  		serializedLinks,
   616  		serializedUnfurledLinks,
   617  		serializedUnfurledStatusLinks,
   618  		command.ID,
   619  		command.Value,
   620  		command.From,
   621  		command.Address,
   622  		command.Contract,
   623  		command.TransactionHash,
   624  		command.CommandState,
   625  		command.Signature,
   626  		message.Replace,
   627  		int64(message.EditedAt),
   628  		message.Deleted,
   629  		message.DeletedBy,
   630  		message.DeletedForMe,
   631  		message.RTL,
   632  		message.LineCount,
   633  		message.ResponseTo,
   634  		gapFrom,
   635  		gapTo,
   636  		message.ContactRequestState,
   637  		message.ContactVerificationState,
   638  		message.Mentioned,
   639  		message.Replied,
   640  		discordMessage.Id,
   641  	}, nil
   642  }
   643  
   644  func (db sqlitePersistence) messageByID(tx *sql.Tx, id string) (*common.Message, error) {
   645  	var err error
   646  	if tx == nil {
   647  		tx, err = db.db.BeginTx(context.Background(), &sql.TxOptions{})
   648  		if err != nil {
   649  			return nil, err
   650  		}
   651  		defer func() {
   652  			if err == nil {
   653  				err = tx.Commit()
   654  				return
   655  			}
   656  			// don't shadow original error
   657  			_ = tx.Rollback()
   658  		}()
   659  	}
   660  
   661  	query := db.buildMessagesQuery("WHERE m1.id = ?")
   662  	rows, err := tx.Query(query, id)
   663  	if err != nil {
   664  		return nil, err
   665  	}
   666  	defer rows.Close()
   667  	return getMessageFromScanRows(db, rows)
   668  }
   669  
   670  func (db sqlitePersistence) albumMessages(chatID, albumID string) ([]*common.Message, error) {
   671  	if albumID == "" {
   672  		return nil, nil
   673  	}
   674  	query := db.buildMessagesQuery("WHERE m1.album_id = ? and m1.local_chat_id = ?")
   675  	rows, err := db.db.Query(query, albumID, chatID)
   676  	if err != nil {
   677  		return nil, err
   678  	}
   679  	defer rows.Close()
   680  	return getMessagesFromScanRows(db, rows, false)
   681  }
   682  
   683  func (db sqlitePersistence) MessageByCommandID(chatID, id string) (*common.Message, error) {
   684  
   685  	where := `WHERE
   686  			m1.command_id = ?
   687  			AND
   688  			m1.local_chat_id = ?
   689  			ORDER BY m1.clock_value DESC
   690  			LIMIT 1`
   691  	query := db.buildMessagesQuery(where)
   692  	rows, err := db.db.Query(query, id, chatID)
   693  	if err != nil {
   694  		return nil, err
   695  	}
   696  	defer rows.Close()
   697  	return getMessageFromScanRows(db, rows)
   698  }
   699  
   700  func (db sqlitePersistence) MessageByID(id string) (*common.Message, error) {
   701  	return db.messageByID(nil, id)
   702  }
   703  
   704  func (db sqlitePersistence) AlbumMessages(chatID, albumID string) ([]*common.Message, error) {
   705  	return db.albumMessages(chatID, albumID)
   706  }
   707  
   708  func (db sqlitePersistence) MessagesExist(ids []string) (map[string]bool, error) {
   709  	result := make(map[string]bool)
   710  	if len(ids) == 0 {
   711  		return result, nil
   712  	}
   713  
   714  	idsArgs := make([]interface{}, 0, len(ids))
   715  	for _, id := range ids {
   716  		idsArgs = append(idsArgs, id)
   717  	}
   718  
   719  	inVector := strings.Repeat("?, ", len(ids)-1) + "?"
   720  	query := "SELECT id FROM user_messages WHERE id IN (" + inVector + ")" // nolint: gosec
   721  	rows, err := db.db.Query(query, idsArgs...)
   722  	if err != nil {
   723  		return nil, err
   724  	}
   725  	defer rows.Close()
   726  
   727  	for rows.Next() {
   728  		var id string
   729  		err := rows.Scan(&id)
   730  		if err != nil {
   731  			return nil, err
   732  		}
   733  		result[id] = true
   734  	}
   735  
   736  	return result, nil
   737  }
   738  
   739  func (db sqlitePersistence) MessagesByIDs(ids []string) ([]*common.Message, error) {
   740  	if len(ids) == 0 {
   741  		return nil, nil
   742  	}
   743  
   744  	idsArgs := make([]interface{}, 0, len(ids))
   745  	for _, id := range ids {
   746  		idsArgs = append(idsArgs, id)
   747  	}
   748  
   749  	inVector := strings.Repeat("?, ", len(ids)-1) + "?"
   750  
   751  	// nolint: gosec
   752  	where := fmt.Sprintf("WHERE NOT(m1.hide) AND m1.id IN (%s)", inVector)
   753  	query := db.buildMessagesQuery(where)
   754  	rows, err := db.db.Query(query, idsArgs...)
   755  	if err != nil {
   756  		return nil, err
   757  	}
   758  	defer rows.Close()
   759  	return getMessagesFromScanRows(db, rows, false)
   760  }
   761  
   762  func (db sqlitePersistence) MessagesByResponseTo(responseTo string) ([]*common.Message, error) {
   763  	where := "WHERE m1.response_to = ?"
   764  	query := db.buildMessagesQuery(where)
   765  	rows, err := db.db.Query(query, responseTo)
   766  	if err != nil {
   767  		return nil, err
   768  	}
   769  	defer rows.Close()
   770  	return getMessagesFromScanRows(db, rows, false)
   771  }
   772  
   773  // MessageByChatID returns all messages for a given chatID in descending order.
   774  // Ordering is accomplished using two concatenated values: ClockValue and ID.
   775  // These two values are also used to compose a cursor which is returned to the result.
   776  func (db sqlitePersistence) MessageByChatID(chatID string, currCursor string, limit int) ([]*common.Message, string, error) {
   777  	cursorWhere := ""
   778  	if currCursor != "" {
   779  		cursorWhere = "AND cursor <= ?" //nolint: goconst
   780  	}
   781  	args := []interface{}{chatID}
   782  	if currCursor != "" {
   783  		args = append(args, currCursor)
   784  	}
   785  	// Build a new column `cursor` at the query time by having a fixed-sized clock value at the beginning
   786  	// concatenated with message ID. Results are sorted using this new column.
   787  	// This new column values can also be returned as a cursor for subsequent requests.
   788  	where := fmt.Sprintf(`
   789              WHERE
   790                  NOT(m1.hide) AND m1.local_chat_id = ? %s
   791              ORDER BY cursor DESC
   792              LIMIT ?`, cursorWhere)
   793  
   794  	query := db.buildMessagesQueryWithAdditionalFields(cursorField, where)
   795  	rows, err := db.db.Query(
   796  		query,
   797  		append(args, limit+1)..., // take one more to figure our whether a cursor should be returned
   798  	)
   799  	if err != nil {
   800  		return nil, "", err
   801  	}
   802  	defer rows.Close()
   803  
   804  	result, cursors, err := getMessagesAndCursorsFromScanRows(db, rows)
   805  	if err != nil {
   806  		return nil, "", err
   807  	}
   808  
   809  	var newCursor string
   810  	if len(result) > limit {
   811  		newCursor = cursors[limit]
   812  		result = result[:limit]
   813  	}
   814  	return result, newCursor, nil
   815  }
   816  
   817  func (db sqlitePersistence) FirstUnseenMessageID(chatID string) (string, error) {
   818  	var id string
   819  	err := db.db.QueryRow(`
   820  			SELECT
   821  				id
   822  			FROM
   823  				user_messages m1
   824  			WHERE
   825  				m1.local_chat_id = ?
   826  				AND NOT(m1.seen) AND NOT(m1.hide) AND NOT(m1.deleted) AND NOT(m1.deleted_for_me)
   827  			ORDER BY m1.clock_value ASC
   828  			LIMIT 1`,
   829  		chatID).Scan(&id)
   830  
   831  	if err == sql.ErrNoRows {
   832  		return "", nil
   833  	}
   834  	if err != nil {
   835  		return "", err
   836  	}
   837  	return id, nil
   838  }
   839  
   840  // Get last chat message that is not hide or deleted or deleted_for_me
   841  func (db sqlitePersistence) LatestMessageByChatID(chatID string) ([]*common.Message, error) {
   842  	args := []interface{}{chatID}
   843  	where := `WHERE
   844                  NOT(m1.hide) AND NOT(m1.deleted) AND NOT(m1.deleted_for_me) AND m1.local_chat_id = ?
   845              ORDER BY cursor DESC
   846              LIMIT ?`
   847  
   848  	query := db.buildMessagesQueryWithAdditionalFields(cursorField, where)
   849  
   850  	rows, err := db.db.Query(
   851  		query,
   852  		append(args, 2)..., // take one more to figure our whether a cursor should be returned
   853  	)
   854  	if err != nil {
   855  		return nil, err
   856  	}
   857  	defer rows.Close()
   858  
   859  	result, _, err := getMessagesAndCursorsFromScanRows(db, rows)
   860  	if err != nil {
   861  		return nil, err
   862  	}
   863  
   864  	if len(result) > 1 {
   865  		result = result[:1]
   866  	}
   867  	return result, nil
   868  }
   869  
   870  func (db sqlitePersistence) latestIncomingMessageClock(chatID string) (uint64, error) {
   871  	var clock uint64
   872  	err := db.db.QueryRow(
   873  		fmt.Sprintf(
   874  			`
   875  			SELECT
   876                  clock_value
   877  			FROM
   878  				user_messages m1
   879  			WHERE
   880  				m1.local_chat_id = ? AND m1.outgoing_status = ''
   881  			%s DESC
   882  			LIMIT 1
   883  		`, cursor),
   884  		chatID).Scan(&clock)
   885  	if err != nil {
   886  		return 0, err
   887  	}
   888  	return clock, nil
   889  }
   890  
   891  func (db sqlitePersistence) PendingContactRequests(currCursor string, limit int) ([]*common.Message, string, error) {
   892  	cursorWhere := ""
   893  	if currCursor != "" {
   894  		cursorWhere = "AND cursor <= ?" //nolint: goconst
   895  	}
   896  	args := []interface{}{protobuf.ChatMessage_CONTACT_REQUEST}
   897  	if currCursor != "" {
   898  		args = append(args, currCursor)
   899  	}
   900  	// Build a new column `cursor` at the query time by having a fixed-sized clock value at the beginning
   901  	// concatenated with message ID. Results are sorted using this new column.
   902  	// This new column values can also be returned as a cursor for subsequent requests.
   903  	where := fmt.Sprintf(`
   904              WHERE
   905                  NOT(m1.hide) AND NOT(m1.seen) AND m1.content_type = ? %s
   906              ORDER BY cursor DESC
   907              LIMIT ?`, cursorWhere)
   908  
   909  	query := db.buildMessagesQueryWithAdditionalFields(cursorField, where)
   910  	rows, err := db.db.Query(
   911  		query,
   912  		append(args, limit+1)..., // take one more to figure our whether a cursor should be returned
   913  	)
   914  	if err != nil {
   915  		return nil, "", err
   916  	}
   917  	defer rows.Close()
   918  
   919  	result, cursors, err := getMessagesAndCursorsFromScanRows(db, rows)
   920  	if err != nil {
   921  		return nil, "", err
   922  	}
   923  
   924  	var newCursor string
   925  	if len(result) > limit {
   926  		newCursor = cursors[limit]
   927  		result = result[:limit]
   928  	}
   929  	return result, newCursor, nil
   930  }
   931  
   932  func (db sqlitePersistence) LatestPendingContactRequestIDForContact(contactID string) (string, error) {
   933  	var id string
   934  	err := db.db.QueryRow(
   935  		fmt.Sprintf(
   936  			`
   937  			SELECT
   938                                  id
   939  			FROM
   940  				user_messages m1
   941  			WHERE
   942  				m1.local_chat_id = ? AND m1.content_type = ?
   943  			ORDER BY %s DESC
   944  			LIMIT 1
   945  		`, cursor),
   946  		contactID, protobuf.ChatMessage_CONTACT_REQUEST).Scan(&id)
   947  	if err == sql.ErrNoRows {
   948  		return "", nil
   949  	}
   950  
   951  	if err != nil {
   952  		return "", err
   953  	}
   954  	return id, nil
   955  }
   956  
   957  func (db sqlitePersistence) LatestContactRequests() ([]LatestContactRequest, error) {
   958  	res := make([]LatestContactRequest, 0)
   959  	rows, err := db.db.Query(
   960  		fmt.Sprintf(
   961  			`
   962  			SELECT
   963                                  id, contact_request_state, local_chat_id
   964  			FROM
   965  				user_messages m1
   966  			WHERE
   967  				m1.content_type = ?
   968  			ORDER BY %s DESC
   969  			LIMIT 200
   970  		`, cursor), protobuf.ChatMessage_CONTACT_REQUEST)
   971  
   972  	if err != nil {
   973  		return res, err
   974  	}
   975  
   976  	defer rows.Close()
   977  
   978  	for rows.Next() {
   979  		var id string
   980  		var contactRequestState sql.NullInt64
   981  		var localChatID string
   982  		err := rows.Scan(&id, &contactRequestState, &localChatID)
   983  
   984  		if err != nil {
   985  			return nil, err
   986  		}
   987  		res = append(res, LatestContactRequest{
   988  			MessageID:           id,
   989  			ContactRequestState: common.ContactRequestState(contactRequestState.Int64),
   990  			ContactID:           localChatID,
   991  		})
   992  	}
   993  
   994  	return res, nil
   995  }
   996  
   997  // AllMessageByChatIDWhichMatchPattern returns all messages which match the search
   998  // term, for a given chatID in descending order.
   999  // Ordering is accomplished using two concatenated values: ClockValue and ID.
  1000  // These two values are also used to compose a cursor which is returned to the result.
  1001  func (db sqlitePersistence) AllMessageByChatIDWhichMatchTerm(chatID string, searchTerm string, caseSensitive bool) ([]*common.Message, error) {
  1002  	if searchTerm == "" {
  1003  		return nil, fmt.Errorf("empty search term")
  1004  	}
  1005  
  1006  	searchCond := ""
  1007  	if caseSensitive {
  1008  		searchCond = "AND " + caseSensitiveSearchCond
  1009  	} else {
  1010  		searchCond = "AND " + caseInsensitiveSearchCond
  1011  	}
  1012  
  1013  	where := fmt.Sprintf(`
  1014              WHERE
  1015                  NOT(m1.hide) AND m1.local_chat_id = ? %s
  1016              ORDER BY cursor DESC`, searchCond)
  1017  
  1018  	query := db.buildMessagesQueryWithAdditionalFields(cursorField, where)
  1019  	rows, err := db.db.Query(
  1020  		query,
  1021  		chatID,
  1022  		searchTerm,
  1023  		searchTerm,
  1024  		searchTerm,
  1025  	)
  1026  
  1027  	if err != nil {
  1028  		return nil, err
  1029  	}
  1030  	defer rows.Close()
  1031  	return getMessagesFromScanRows(db, rows, true)
  1032  }
  1033  
  1034  // AllMessagesFromChatsAndCommunitiesWhichMatchTerm returns all messages which match the search
  1035  // term, if they belong to either any chat from the chatIds array or any channel of any community
  1036  // from communityIds array.
  1037  // Ordering is accomplished using two concatenated values: ClockValue and ID.
  1038  // These two values are also used to compose a cursor which is returned to the result.
  1039  func (db sqlitePersistence) AllMessagesFromChatsAndCommunitiesWhichMatchTerm(communityIds []string, chatIds []string, searchTerm string, caseSensitive bool) ([]*common.Message, error) {
  1040  	if searchTerm == "" {
  1041  		return nil, fmt.Errorf("empty search term")
  1042  	}
  1043  
  1044  	chatsCond := ""
  1045  	if len(chatIds) > 0 {
  1046  		inVector := strings.Repeat("?, ", len(chatIds)-1) + "?"
  1047  		chatsCond = `m1.local_chat_id IN (%s)`
  1048  		chatsCond = fmt.Sprintf(chatsCond, inVector)
  1049  	}
  1050  
  1051  	communitiesCond := ""
  1052  	if len(communityIds) > 0 {
  1053  		inVector := strings.Repeat("?, ", len(communityIds)-1) + "?"
  1054  		communitiesCond = `m1.local_chat_id IN (SELECT id FROM chats WHERE community_id IN (%s))`
  1055  		communitiesCond = fmt.Sprintf(communitiesCond, inVector)
  1056  	}
  1057  
  1058  	searchCond := ""
  1059  	if caseSensitive {
  1060  		searchCond = caseSensitiveSearchCond
  1061  	} else {
  1062  		searchCond = caseInsensitiveSearchCond
  1063  	}
  1064  
  1065  	finalCond := "AND %s AND %s"
  1066  	if len(communityIds) > 0 && len(chatIds) > 0 {
  1067  		finalCond = "AND (%s OR %s) AND %s"
  1068  		finalCond = fmt.Sprintf(finalCond, chatsCond, communitiesCond, searchCond)
  1069  	} else if len(chatIds) > 0 {
  1070  		finalCond = fmt.Sprintf(finalCond, chatsCond, searchCond)
  1071  	} else if len(communityIds) > 0 {
  1072  		finalCond = fmt.Sprintf(finalCond, communitiesCond, searchCond)
  1073  	} else {
  1074  		return nil, fmt.Errorf("you must specify either community ids or chat ids or both")
  1075  	}
  1076  
  1077  	var parameters []string
  1078  	parameters = append(parameters, chatIds...)
  1079  	parameters = append(parameters, communityIds...)
  1080  	parameters = append(parameters, searchTerm)
  1081  	parameters = append(parameters, searchTerm)
  1082  	parameters = append(parameters, searchTerm)
  1083  
  1084  	idsArgs := make([]interface{}, 0, len(parameters))
  1085  	for _, param := range parameters {
  1086  		idsArgs = append(idsArgs, param)
  1087  	}
  1088  
  1089  	where := fmt.Sprintf(`
  1090              WHERE
  1091                  NOT(m1.hide) %s
  1092              ORDER BY cursor DESC`, finalCond)
  1093  
  1094  	finalQuery := db.buildMessagesQueryWithAdditionalFields(cursorField, where)
  1095  	rows, err := db.db.Query(finalQuery, idsArgs...)
  1096  
  1097  	if err != nil {
  1098  		return nil, err
  1099  	}
  1100  	defer rows.Close()
  1101  	return getMessagesFromScanRows(db, rows, true)
  1102  }
  1103  
  1104  func (db sqlitePersistence) AllChatIDsByCommunity(tx *sql.Tx, communityID string) ([]string, error) {
  1105  	var err error
  1106  	var rows *sql.Rows
  1107  	query := "SELECT id FROM chats WHERE community_id = ?"
  1108  	if tx == nil {
  1109  		rows, err = db.db.Query(query, communityID)
  1110  	} else {
  1111  		rows, err = tx.Query(query, communityID)
  1112  	}
  1113  
  1114  	if err != nil {
  1115  		return nil, err
  1116  	}
  1117  	defer rows.Close()
  1118  
  1119  	var rst []string
  1120  
  1121  	for rows.Next() {
  1122  		var chatID string
  1123  		err = rows.Scan(&chatID)
  1124  		if err != nil {
  1125  			return nil, err
  1126  		}
  1127  		rst = append(rst, chatID)
  1128  	}
  1129  
  1130  	return rst, nil
  1131  }
  1132  
  1133  func (db sqlitePersistence) CountActiveChattersInCommunity(communityID string, activeAfterTimestamp int64) (uint, error) {
  1134  	var activeChattersCount uint
  1135  	err := db.db.QueryRow(
  1136  		`
  1137  			SELECT COUNT(DISTINCT source)
  1138  			FROM user_messages
  1139  			JOIN chats ON user_messages.local_chat_id = chats.id
  1140  			WHERE chats.community_id = ?
  1141  			AND user_messages.timestamp >= ?
  1142  		`, communityID, activeAfterTimestamp).Scan(&activeChattersCount)
  1143  	if err == sql.ErrNoRows {
  1144  		return 0, nil
  1145  	}
  1146  	if err != nil {
  1147  		return 0, err
  1148  	}
  1149  
  1150  	return activeChattersCount, nil
  1151  }
  1152  
  1153  // PinnedMessageByChatID returns all pinned messages for a given chatID in descending order.
  1154  // Ordering is accomplished using two concatenated values: ClockValue and ID.
  1155  // These two values are also used to compose a cursor which is returned to the result.
  1156  func (db sqlitePersistence) PinnedMessageByChatIDs(chatIDs []string, currCursor string, limit int) ([]*common.PinnedMessage, string, error) {
  1157  	cursorWhere := ""
  1158  	if currCursor != "" {
  1159  		cursorWhere = "AND cursor <= ?" //nolint: goconst
  1160  	}
  1161  	args := make([]interface{}, len(chatIDs))
  1162  	for i, v := range chatIDs {
  1163  		args[i] = v
  1164  	}
  1165  	if currCursor != "" {
  1166  		args = append(args, currCursor)
  1167  	}
  1168  
  1169  	limitStr := ""
  1170  	if limit > -1 {
  1171  		args = append(args, limit+1) // take one more to figure our whether a cursor should be returned
  1172  	}
  1173  	// Build a new column `cursor` at the query time by having a fixed-sized clock value at the beginning
  1174  	// concatenated with message ID. Results are sorted using this new column.
  1175  	// This new column values can also be returned as a cursor for subsequent requests.
  1176  	allFields := db.tableUserMessagesAllFieldsJoin()
  1177  	rows, err := db.db.Query(
  1178  		fmt.Sprintf(`
  1179   			SELECT
  1180   				%s,
  1181   				pm.clock_value as pinnedAt,
  1182   				pm.pinned_by as pinnedBy,
  1183                                  %s
  1184   			FROM
  1185   				pin_messages pm
  1186   			JOIN
  1187   				user_messages m1
  1188   			ON
  1189   				pm.message_id = m1.id
  1190   			LEFT JOIN
  1191   				user_messages m2
  1192   			ON
  1193   				m1.response_to = m2.id
  1194   			LEFT JOIN
  1195   				contacts c
  1196   			ON
  1197   				m1.source = c.id
  1198  
  1199         LEFT JOIN
  1200               discord_messages dm
  1201         ON
  1202         m1.discord_message_id = dm.id
  1203  
  1204         LEFT JOIN
  1205               discord_message_authors dm_author
  1206         ON
  1207         dm.author_id = dm_author.id
  1208  
  1209         LEFT JOIN
  1210                discord_message_attachments dm_attachment
  1211  			 ON
  1212         dm.id = dm_attachment.discord_message_id
  1213  
  1214  			 LEFT JOIN
  1215  							discord_messages m2_dm
  1216  			 ON
  1217  			 m2.discord_message_id = m2_dm.id
  1218  
  1219  				LEFT JOIN
  1220  							discord_message_authors m2_dm_author
  1221  			 ON
  1222  			 m2_dm.author_id = m2_dm_author.id
  1223  
  1224  			 LEFT JOIN bridge_messages bm
  1225  			 ON m1.id = bm.user_messages_id
  1226  
  1227  			LEFT JOIN bridge_messages bm_response
  1228  			ON m2.id = bm_response.user_messages_id
  1229  
  1230   			WHERE
  1231   				pm.pinned = 1
  1232   				AND NOT(m1.hide) AND m1.local_chat_id IN %s %s
  1233   			ORDER BY cursor DESC
  1234   			%s
  1235   		`, allFields, cursorField, "(?"+strings.Repeat(",?", len(chatIDs)-1)+")", cursorWhere, limitStr),
  1236  		args..., // take one more to figure our whether a cursor should be returned
  1237  	)
  1238  	if err != nil {
  1239  		return nil, "", err
  1240  	}
  1241  	defer rows.Close()
  1242  
  1243  	result, cursors, err := getPinnedMessagesAndCursorsFromScanRows(db, rows)
  1244  	if err != nil {
  1245  		return nil, "", err
  1246  	}
  1247  
  1248  	var newCursor string
  1249  
  1250  	if limit > -1 && len(result) > limit && cursors != nil {
  1251  		newCursor = cursors[limit]
  1252  		result = result[:limit]
  1253  	}
  1254  	return result, newCursor, nil
  1255  }
  1256  
  1257  func (db sqlitePersistence) PinnedMessageByChatID(chatID string, currCursor string, limit int) ([]*common.PinnedMessage, string, error) {
  1258  	return db.PinnedMessageByChatIDs([]string{chatID}, currCursor, limit)
  1259  }
  1260  
  1261  // MessageByChatIDs returns all messages for a given chatIDs in descending order.
  1262  // Ordering is accomplished using two concatenated values: ClockValue and ID.
  1263  // These two values are also used to compose a cursor which is returned to the result.
  1264  func (db sqlitePersistence) MessageByChatIDs(chatIDs []string, currCursor string, limit int) ([]*common.Message, string, error) {
  1265  	cursorWhere := ""
  1266  	if currCursor != "" {
  1267  		cursorWhere = "AND cursor <= ?" //nolint: goconst
  1268  	}
  1269  	args := make([]interface{}, len(chatIDs))
  1270  	for i, v := range chatIDs {
  1271  		args[i] = v
  1272  	}
  1273  	if currCursor != "" {
  1274  		args = append(args, currCursor)
  1275  	}
  1276  	// Build a new column `cursor` at the query time by having a fixed-sized clock value at the beginning
  1277  	// concatenated with message ID. Results are sorted using this new column.
  1278  	// This new column values can also be returned as a cursor for subsequent requests.
  1279  	where := fmt.Sprintf(`
  1280  			WHERE
  1281  				NOT(m1.hide) AND m1.local_chat_id IN %s %s
  1282  			ORDER BY cursor DESC
  1283  			LIMIT ?
  1284  		`, "(?"+strings.Repeat(",?", len(chatIDs)-1)+")", cursorWhere)
  1285  	query := db.buildMessagesQueryWithAdditionalFields(cursorField, where)
  1286  	rows, err := db.db.Query(
  1287  		query,
  1288  		append(args, limit+1)..., // take one more to figure our whether a cursor should be returned
  1289  	)
  1290  	if err != nil {
  1291  		return nil, "", err
  1292  	}
  1293  	defer rows.Close()
  1294  
  1295  	result, cursors, err := getMessagesAndCursorsFromScanRows(db, rows)
  1296  	if err != nil {
  1297  		return nil, "", err
  1298  	}
  1299  
  1300  	var newCursor string
  1301  	if len(result) > limit {
  1302  		newCursor = cursors[limit]
  1303  		result = result[:limit]
  1304  	}
  1305  	return result, newCursor, nil
  1306  }
  1307  
  1308  func (db sqlitePersistence) OldestMessageWhisperTimestampByChatID(chatID string) (timestamp uint64, hasAnyMessage bool, err error) {
  1309  	var whisperTimestamp uint64
  1310  	err = db.db.QueryRow(
  1311  		`
  1312  			SELECT
  1313  				whisper_timestamp
  1314  			FROM
  1315  				user_messages m1
  1316  			WHERE
  1317  				m1.local_chat_id = ?
  1318  			ORDER BY substr('0000000000000000000000000000000000000000000000000000000000000000' || m1.clock_value, -64, 64) || m1.id ASC
  1319  			LIMIT 1
  1320  		`, chatID).Scan(&whisperTimestamp)
  1321  	if err == sql.ErrNoRows {
  1322  		return 0, false, nil
  1323  	}
  1324  	if err != nil {
  1325  		return 0, false, err
  1326  	}
  1327  	return whisperTimestamp, true, nil
  1328  }
  1329  
  1330  // EmojiReactionsByChatID returns the emoji reactions for the queried messages, up to a maximum of 100, as it's a potentially unbound number.
  1331  // NOTE: This is not completely accurate, as the messages in the database might have change since the last call to `MessageByChatID`.
  1332  func (db sqlitePersistence) EmojiReactionsByChatID(chatID string, currCursor string, limit int) ([]*EmojiReaction, error) {
  1333  	cursorWhere := ""
  1334  	if currCursor != "" {
  1335  		cursorWhere = fmt.Sprintf("AND %s <= ?", cursor) //nolint: goconst
  1336  	}
  1337  	args := []interface{}{chatID, chatID}
  1338  	if currCursor != "" {
  1339  		args = append(args, currCursor)
  1340  	}
  1341  	args = append(args, limit)
  1342  	// NOTE: We match against local_chat_id for security reasons.
  1343  	// As a user could potentially send an emoji reaction for a one to
  1344  	// one/group chat that has no access to.
  1345  	// We also limit the number of emoji to a reasonable number (1000)
  1346  	// for now, as we don't want the client to choke on this.
  1347  	// The issue is that your own emoji might not be returned in such cases,
  1348  	// allowing the user to react to a post multiple times.
  1349  	// Jakubgs: Returning the whole list seems like a real overkill.
  1350  	// This will get very heavy in threads that have loads of reactions on loads of messages.
  1351  	// A more sensible response would just include a count and a bool telling you if you are in the list.
  1352  	// nolint: gosec
  1353  	query := fmt.Sprintf(`
  1354  			SELECT
  1355  			    e.clock_value,
  1356  			    e.source,
  1357  			    e.emoji_id,
  1358  			    e.message_id,
  1359  			    e.chat_id,
  1360  			    e.local_chat_id,
  1361  			    e.retracted
  1362  			FROM
  1363  				emoji_reactions e
  1364  			WHERE NOT(e.retracted)
  1365  			AND
  1366  			e.local_chat_id = ?
  1367  			AND
  1368  			e.message_id IN
  1369  			(SELECT id FROM user_messages m1 WHERE NOT(m1.hide) AND m1.local_chat_id = ? %s
  1370  			ORDER BY %s DESC LIMIT ?)
  1371  			LIMIT 1000
  1372  		`, cursorWhere, cursor)
  1373  
  1374  	rows, err := db.db.Query(
  1375  		query,
  1376  		args...,
  1377  	)
  1378  	if err != nil {
  1379  		return nil, err
  1380  	}
  1381  	defer rows.Close()
  1382  
  1383  	var result []*EmojiReaction
  1384  	for rows.Next() {
  1385  		emojiReaction := NewEmojiReaction()
  1386  		err := rows.Scan(&emojiReaction.Clock,
  1387  			&emojiReaction.From,
  1388  			&emojiReaction.Type,
  1389  			&emojiReaction.MessageId,
  1390  			&emojiReaction.ChatId,
  1391  			&emojiReaction.LocalChatID,
  1392  			&emojiReaction.Retracted)
  1393  		if err != nil {
  1394  			return nil, err
  1395  		}
  1396  
  1397  		result = append(result, emojiReaction)
  1398  	}
  1399  
  1400  	return result, nil
  1401  }
  1402  
  1403  // EmojiReactionsByChatIDMessageID returns the emoji reactions for the queried message.
  1404  func (db sqlitePersistence) EmojiReactionsByChatIDMessageID(chatID string, messageID string) ([]*EmojiReaction, error) {
  1405  
  1406  	args := []interface{}{chatID, messageID}
  1407  	query := `SELECT
  1408  			    e.clock_value,
  1409  			    e.source,
  1410  			    e.emoji_id,
  1411  			    e.message_id,
  1412  			    e.chat_id,
  1413  			    e.local_chat_id,
  1414  			    e.retracted
  1415  			FROM
  1416  				emoji_reactions e
  1417  			WHERE NOT(e.retracted)
  1418  			AND
  1419  			e.local_chat_id = ?
  1420  			AND
  1421  			e.message_id = ?
  1422  			LIMIT 1000`
  1423  
  1424  	rows, err := db.db.Query(
  1425  		query,
  1426  		args...,
  1427  	)
  1428  	if err != nil {
  1429  		return nil, err
  1430  	}
  1431  	defer rows.Close()
  1432  
  1433  	var result []*EmojiReaction
  1434  	for rows.Next() {
  1435  		emojiReaction := NewEmojiReaction()
  1436  		err := rows.Scan(&emojiReaction.Clock,
  1437  			&emojiReaction.From,
  1438  			&emojiReaction.Type,
  1439  			&emojiReaction.MessageId,
  1440  			&emojiReaction.ChatId,
  1441  			&emojiReaction.LocalChatID,
  1442  			&emojiReaction.Retracted)
  1443  		if err != nil {
  1444  			return nil, err
  1445  		}
  1446  
  1447  		result = append(result, emojiReaction)
  1448  	}
  1449  
  1450  	return result, nil
  1451  }
  1452  
  1453  // EmojiReactionsByChatIDs returns the emoji reactions for the queried messages, up to a maximum of 100, as it's a potentially unbound number.
  1454  // NOTE: This is not completely accurate, as the messages in the database might have change since the last call to `MessageByChatID`.
  1455  func (db sqlitePersistence) EmojiReactionsByChatIDs(chatIDs []string, currCursor string, limit int) ([]*EmojiReaction, error) {
  1456  	cursorWhere := ""
  1457  	if currCursor != "" {
  1458  		cursorWhere = fmt.Sprintf("AND %s <= ?", cursor) //nolint: goconst
  1459  	}
  1460  	chatsLen := len(chatIDs)
  1461  	args := make([]interface{}, chatsLen*2)
  1462  	for i, v := range chatIDs {
  1463  		args[i] = v
  1464  	}
  1465  	for i, v := range chatIDs {
  1466  		args[chatsLen+i] = v
  1467  	}
  1468  	if currCursor != "" {
  1469  		args = append(args, currCursor)
  1470  	}
  1471  	args = append(args, limit)
  1472  	// NOTE: We match against local_chat_id for security reasons.
  1473  	// As a user could potentially send an emoji reaction for a one to
  1474  	// one/group chat that has no access to.
  1475  	// We also limit the number of emoji to a reasonable number (1000)
  1476  	// for now, as we don't want the client to choke on this.
  1477  	// The issue is that your own emoji might not be returned in such cases,
  1478  	// allowing the user to react to a post multiple times.
  1479  	// Jakubgs: Returning the whole list seems like a real overkill.
  1480  	// This will get very heavy in threads that have loads of reactions on loads of messages.
  1481  	// A more sensible response would just include a count and a bool telling you if you are in the list.
  1482  	// nolint: gosec
  1483  	query := fmt.Sprintf(`
  1484  			SELECT
  1485  			    e.clock_value,
  1486  			    e.source,
  1487  			    e.emoji_id,
  1488  			    e.message_id,
  1489  			    e.chat_id,
  1490  			    e.local_chat_id,
  1491  			    e.retracted
  1492  			FROM
  1493  				emoji_reactions e
  1494  			WHERE NOT(e.retracted)
  1495  			AND
  1496  			e.local_chat_id IN %s
  1497  			AND
  1498  			e.message_id IN
  1499  			(SELECT id FROM user_messages m WHERE NOT(m.hide) AND m.local_chat_id IN %s %s
  1500  			ORDER BY %s DESC LIMIT ?)
  1501  			LIMIT 1000
  1502  		`, "(?"+strings.Repeat(",?", chatsLen-1)+")", "(?"+strings.Repeat(",?", chatsLen-1)+")", cursorWhere, cursor)
  1503  
  1504  	rows, err := db.db.Query(
  1505  		query,
  1506  		args...,
  1507  	)
  1508  	if err != nil {
  1509  		return nil, err
  1510  	}
  1511  	defer rows.Close()
  1512  
  1513  	var result []*EmojiReaction
  1514  	for rows.Next() {
  1515  		emojiReaction := NewEmojiReaction()
  1516  		err := rows.Scan(&emojiReaction.Clock,
  1517  			&emojiReaction.From,
  1518  			&emojiReaction.Type,
  1519  			&emojiReaction.MessageId,
  1520  			&emojiReaction.ChatId,
  1521  			&emojiReaction.LocalChatID,
  1522  			&emojiReaction.Retracted)
  1523  		if err != nil {
  1524  			return nil, err
  1525  		}
  1526  
  1527  		result = append(result, emojiReaction)
  1528  	}
  1529  
  1530  	return result, nil
  1531  }
  1532  func (db sqlitePersistence) SaveMessages(messages []*common.Message) (err error) {
  1533  	tx, err := db.db.BeginTx(context.Background(), &sql.TxOptions{})
  1534  	if err != nil {
  1535  		return
  1536  	}
  1537  	defer func() {
  1538  		if err == nil {
  1539  			err = tx.Commit()
  1540  			return
  1541  		}
  1542  		// don't shadow original error
  1543  		_ = tx.Rollback()
  1544  	}()
  1545  
  1546  	allFields := db.tableUserMessagesAllFields()
  1547  	valuesVector := strings.Repeat("?, ", db.tableUserMessagesAllFieldsCount()-1) + "?"
  1548  	query := "INSERT INTO user_messages(" + allFields + ") VALUES (" + valuesVector + ")" // nolint: gosec
  1549  	stmt, err := tx.Prepare(query)
  1550  	if err != nil {
  1551  		return
  1552  	}
  1553  
  1554  	for _, msg := range messages {
  1555  		var allValues []interface{}
  1556  		allValues, err = db.tableUserMessagesAllValues(msg)
  1557  		if err != nil {
  1558  			return
  1559  		}
  1560  
  1561  		_, err = stmt.Exec(allValues...)
  1562  		if err != nil {
  1563  			return
  1564  		}
  1565  
  1566  		if msg.ContentType == protobuf.ChatMessage_BRIDGE_MESSAGE {
  1567  			// check updates first
  1568  			var hasMessage bool
  1569  			hasMessage, err = db.bridgeMessageExists(tx, msg.GetBridgeMessage().MessageID)
  1570  			if err != nil {
  1571  				return
  1572  			}
  1573  			if hasMessage {
  1574  				// bridge message exists, this is edit
  1575  				err = db.updateBridgeMessageContent(tx, msg.GetBridgeMessage().MessageID, msg.GetBridgeMessage().Content)
  1576  				return
  1577  			}
  1578  
  1579  			err = db.saveBridgeMessage(tx, msg.GetBridgeMessage(), msg.ID)
  1580  			if err != nil {
  1581  				return
  1582  			}
  1583  			// handle replies
  1584  			err = db.findAndUpdateReplies(tx, msg.GetBridgeMessage().MessageID, msg.ID)
  1585  			if err != nil {
  1586  				return
  1587  			}
  1588  			parentMessageID := msg.GetBridgeMessage().ParentMessageID
  1589  			if parentMessageID != "" {
  1590  				err = db.findAndUpdateRepliedTo(tx, parentMessageID, msg.ID)
  1591  				if err != nil {
  1592  					return
  1593  				}
  1594  			}
  1595  
  1596  		}
  1597  	}
  1598  	return
  1599  }
  1600  
  1601  type insertPinMessagesQueries struct {
  1602  	selectStmt  string
  1603  	insertStmt  *sql.Stmt
  1604  	updateStmt  *sql.Stmt
  1605  	transaction *sql.Tx
  1606  }
  1607  
  1608  func (db sqlitePersistence) buildPinMessageQueries() (*insertPinMessagesQueries, error) {
  1609  	tx, err := db.db.BeginTx(context.Background(), nil)
  1610  	if err != nil {
  1611  		return nil, err
  1612  	}
  1613  
  1614  	queries := &insertPinMessagesQueries{}
  1615  	// select
  1616  	queries.selectStmt = "SELECT clock_value FROM pin_messages WHERE id = ?"
  1617  
  1618  	// insert
  1619  	allInsertFields := `id, message_id, whisper_timestamp, chat_id, local_chat_id, clock_value, pinned, pinned_by`
  1620  	insertValues := strings.Repeat("?, ", strings.Count(allInsertFields, ",")) + "?"
  1621  	insertQuery := "INSERT INTO pin_messages(" + allInsertFields + ") VALUES (" + insertValues + ")" // nolint: gosec
  1622  	insertStmt, err := tx.Prepare(insertQuery)
  1623  	if err != nil {
  1624  		return nil, err
  1625  	}
  1626  	queries.insertStmt = insertStmt
  1627  
  1628  	// update
  1629  	updateQuery := "UPDATE pin_messages SET pinned = ?, clock_value = ?, pinned_by = ? WHERE id = ?"
  1630  	updateStmt, err := tx.Prepare(updateQuery)
  1631  	if err != nil {
  1632  		return nil, err
  1633  	}
  1634  	queries.updateStmt = updateStmt
  1635  	queries.transaction = tx
  1636  	return queries, nil
  1637  }
  1638  
  1639  func (db sqlitePersistence) SavePinMessages(messages []*common.PinMessage) (err error) {
  1640  	queries, err := db.buildPinMessageQueries()
  1641  	if err != nil {
  1642  		return
  1643  	}
  1644  	defer func() {
  1645  		if err == nil {
  1646  			err = queries.transaction.Commit()
  1647  			return
  1648  		}
  1649  		// don't shadow original error
  1650  		_ = queries.transaction.Rollback()
  1651  	}()
  1652  	for _, message := range messages {
  1653  		_, err = db.savePinMessage(message, queries)
  1654  		if err != nil {
  1655  			return
  1656  		}
  1657  	}
  1658  
  1659  	return
  1660  }
  1661  
  1662  func (db sqlitePersistence) savePinMessage(message *common.PinMessage, queries *insertPinMessagesQueries) (inserted bool, err error) {
  1663  	tx := queries.transaction
  1664  	selectQuery := queries.selectStmt
  1665  	updateStmt := queries.updateStmt
  1666  	insertStmt := queries.insertStmt
  1667  
  1668  	row := tx.QueryRow(selectQuery, message.ID)
  1669  	var existingClock uint64
  1670  	switch err = row.Scan(&existingClock); err {
  1671  	case sql.ErrNoRows:
  1672  		// not found, insert new record
  1673  		allValues := []interface{}{
  1674  			message.ID,
  1675  			message.MessageId,
  1676  			message.WhisperTimestamp,
  1677  			message.ChatId,
  1678  			message.LocalChatID,
  1679  			message.Clock,
  1680  			message.Pinned,
  1681  			message.From,
  1682  		}
  1683  		_, err = insertStmt.Exec(allValues...)
  1684  		if err != nil {
  1685  			return
  1686  		}
  1687  		inserted = true
  1688  	case nil:
  1689  		// found, update if current message is more recent, otherwise skip
  1690  		if existingClock < message.Clock {
  1691  			// update
  1692  			_, err = updateStmt.Exec(message.Pinned, message.Clock, message.From, message.ID)
  1693  			if err != nil {
  1694  				return
  1695  			}
  1696  			inserted = true
  1697  		}
  1698  
  1699  	default:
  1700  		return
  1701  	}
  1702  
  1703  	return
  1704  }
  1705  
  1706  func (db sqlitePersistence) SavePinMessage(message *common.PinMessage) (inserted bool, err error) {
  1707  	queries, err := db.buildPinMessageQueries()
  1708  	if err != nil {
  1709  		return
  1710  	}
  1711  	defer func() {
  1712  		if err == nil {
  1713  			err = queries.transaction.Commit()
  1714  			return
  1715  		}
  1716  		// don't shadow original error
  1717  		_ = queries.transaction.Rollback()
  1718  	}()
  1719  	return db.savePinMessage(message, queries)
  1720  }
  1721  
  1722  func (db sqlitePersistence) DeleteMessage(id string) error {
  1723  	_, err := db.db.Exec(`DELETE FROM user_messages WHERE id = ?`, id)
  1724  	return err
  1725  }
  1726  
  1727  func (db sqlitePersistence) DeleteMessages(ids []string) error {
  1728  	idsArgs := make([]interface{}, 0, len(ids))
  1729  	for _, id := range ids {
  1730  		idsArgs = append(idsArgs, id)
  1731  	}
  1732  	inVector := strings.Repeat("?, ", len(ids)-1) + "?"
  1733  
  1734  	_, err := db.db.Exec("DELETE FROM user_messages WHERE id IN ("+inVector+")", idsArgs...) // nolint: gosec
  1735  
  1736  	return err
  1737  }
  1738  
  1739  func (db sqlitePersistence) HideMessage(id string) error {
  1740  	_, err := db.db.Exec(`UPDATE user_messages SET hide = 1, seen = 1 WHERE id = ?`, id)
  1741  	return err
  1742  }
  1743  
  1744  // SetHideOnMessage set the hide flag, but not the seen flag, as it's needed by the client to understand whether the count should be updated
  1745  func (db sqlitePersistence) SetHideOnMessage(id string) error {
  1746  	_, err := db.db.Exec(`UPDATE user_messages SET hide = 1 WHERE id = ?`, id)
  1747  	return err
  1748  }
  1749  
  1750  func (db sqlitePersistence) DeleteMessagesByCommunityID(id string) error {
  1751  	return db.deleteMessagesByCommunityID(id)
  1752  }
  1753  
  1754  func (db sqlitePersistence) deleteMessagesByCommunityID(id string) (err error) {
  1755  	tx, err := db.db.BeginTx(context.Background(), &sql.TxOptions{})
  1756  	if err != nil {
  1757  		return err
  1758  	}
  1759  	defer func() {
  1760  		if err == nil {
  1761  			err = tx.Commit()
  1762  			return
  1763  		}
  1764  		// don't shadow original error
  1765  		_ = tx.Rollback()
  1766  	}()
  1767  
  1768  	_, err = tx.Exec(`DELETE FROM user_messages WHERE community_id = ?`, id)
  1769  	if err != nil {
  1770  		return
  1771  	}
  1772  
  1773  	_, err = tx.Exec(`DELETE FROM pin_messages WHERE community_id = ?`, id)
  1774  
  1775  	return
  1776  }
  1777  
  1778  func (db sqlitePersistence) DeleteMessagesByChatID(id string) error {
  1779  	return db.deleteMessagesByChatID(id, nil)
  1780  }
  1781  
  1782  func (db sqlitePersistence) deleteMessagesByChatID(id string, tx *sql.Tx) (err error) {
  1783  	if tx == nil {
  1784  		tx, err = db.db.BeginTx(context.Background(), &sql.TxOptions{})
  1785  		if err != nil {
  1786  			return err
  1787  		}
  1788  		defer func() {
  1789  			if err == nil {
  1790  				err = tx.Commit()
  1791  				return
  1792  			}
  1793  			// don't shadow original error
  1794  			_ = tx.Rollback()
  1795  		}()
  1796  	}
  1797  
  1798  	_, err = tx.Exec(`DELETE FROM user_messages WHERE local_chat_id = ?`, id)
  1799  	if err != nil {
  1800  		return
  1801  	}
  1802  
  1803  	_, err = tx.Exec(`DELETE FROM pin_messages WHERE local_chat_id = ?`, id)
  1804  
  1805  	return
  1806  }
  1807  
  1808  func (db sqlitePersistence) deleteMessagesByChatIDAndClockValueLessThanOrEqual(id string, clock uint64, tx *sql.Tx) (unViewedMessages, unViewedMentions uint, err error) {
  1809  	if tx == nil {
  1810  		tx, err = db.db.BeginTx(context.Background(), &sql.TxOptions{})
  1811  		if err != nil {
  1812  			return 0, 0, err
  1813  		}
  1814  		defer func() {
  1815  			if err == nil {
  1816  				err = tx.Commit()
  1817  				return
  1818  			}
  1819  			// don't shadow original error
  1820  			_ = tx.Rollback()
  1821  		}()
  1822  	}
  1823  
  1824  	_, err = tx.Exec(`DELETE FROM user_messages WHERE local_chat_id = ? AND clock_value <= ?`, id, clock)
  1825  	if err != nil {
  1826  		return
  1827  	}
  1828  
  1829  	_, err = tx.Exec(`DELETE FROM pin_messages WHERE local_chat_id = ? AND clock_value <= ?`, id, clock)
  1830  	if err != nil {
  1831  		return
  1832  	}
  1833  
  1834  	_, err = tx.Exec(
  1835  		`UPDATE chats
  1836  		   SET unviewed_message_count =
  1837  		   (SELECT COUNT(1)
  1838  		   FROM user_messages
  1839  		   WHERE local_chat_id = ? AND seen = 0),
  1840  		   unviewed_mentions_count =
  1841  		   (SELECT COUNT(1)
  1842  		   FROM user_messages
  1843  		   WHERE local_chat_id = ? AND seen = 0 AND (mentioned OR replied)),
  1844                     highlight = 0
  1845  		WHERE id = ?`, id, id, id)
  1846  
  1847  	if err != nil {
  1848  		return 0, 0, err
  1849  	}
  1850  
  1851  	err = tx.QueryRow(`SELECT unviewed_message_count, unviewed_mentions_count FROM chats
  1852  				WHERE id = ?`, id).Scan(&unViewedMessages, &unViewedMentions)
  1853  
  1854  	return
  1855  }
  1856  
  1857  func (db sqlitePersistence) MarkAllRead(chatID string, clock uint64) (int64, int64, error) {
  1858  	tx, err := db.db.BeginTx(context.Background(), &sql.TxOptions{})
  1859  	if err != nil {
  1860  		return 0, 0, err
  1861  	}
  1862  	defer func() {
  1863  		if err == nil {
  1864  			err = tx.Commit()
  1865  			return
  1866  		}
  1867  		// don't shadow original error
  1868  		_ = tx.Rollback()
  1869  	}()
  1870  
  1871  	seenResult, err := tx.Exec(`UPDATE user_messages SET seen = 1 WHERE local_chat_id = ? AND seen = 0 AND clock_value <= ? AND not(mentioned) AND not(replied)`, chatID, clock)
  1872  	if err != nil {
  1873  		return 0, 0, err
  1874  	}
  1875  
  1876  	seen, err := seenResult.RowsAffected()
  1877  	if err != nil {
  1878  		return 0, 0, err
  1879  	}
  1880  
  1881  	mentionedOrRepliedResult, err := tx.Exec(`UPDATE user_messages SET seen = 1 WHERE local_chat_id = ? AND seen = 0 AND clock_value <= ? AND (mentioned OR replied)`, chatID, clock)
  1882  	if err != nil {
  1883  		return 0, 0, err
  1884  	}
  1885  
  1886  	mentionedOrReplied, err := mentionedOrRepliedResult.RowsAffected()
  1887  	if err != nil {
  1888  		return 0, 0, err
  1889  	}
  1890  
  1891  	_, err = tx.Exec(
  1892  		`UPDATE chats
  1893  		   SET unviewed_message_count = 0,
  1894  		   unviewed_mentions_count = 0,
  1895                     highlight = 0
  1896  		WHERE id = ?`, chatID, chatID, chatID)
  1897  
  1898  	if err != nil {
  1899  		return 0, 0, err
  1900  	}
  1901  
  1902  	return (seen + mentionedOrReplied), mentionedOrReplied, nil
  1903  }
  1904  
  1905  func (db sqlitePersistence) MarkAllReadMultiple(chatIDs []string) error {
  1906  	tx, err := db.db.BeginTx(context.Background(), &sql.TxOptions{})
  1907  	if err != nil {
  1908  		return err
  1909  	}
  1910  	defer func() {
  1911  		if err == nil {
  1912  			err = tx.Commit()
  1913  			return
  1914  		}
  1915  		// don't shadow original error
  1916  		_ = tx.Rollback()
  1917  	}()
  1918  
  1919  	idsArgs := make([]interface{}, 0, len(chatIDs))
  1920  	for _, id := range chatIDs {
  1921  		idsArgs = append(idsArgs, id)
  1922  	}
  1923  
  1924  	inVector := strings.Repeat("?, ", len(chatIDs)-1) + "?"
  1925  
  1926  	q := "UPDATE user_messages SET seen = 1 WHERE local_chat_id IN (%s) AND seen != 1"
  1927  	q = fmt.Sprintf(q, inVector)
  1928  	_, err = tx.Exec(q, idsArgs...)
  1929  	if err != nil {
  1930  		return err
  1931  	}
  1932  
  1933  	q = "UPDATE chats SET unviewed_mentions_count = 0, unviewed_message_count = 0, highlight = 0 WHERE id IN (%s)"
  1934  	q = fmt.Sprintf(q, inVector)
  1935  	_, err = tx.Exec(q, idsArgs...)
  1936  	return err
  1937  }
  1938  
  1939  func (db sqlitePersistence) MarkMessagesSeen(chatID string, ids []string) (uint64, uint64, error) {
  1940  	tx, err := db.db.BeginTx(context.Background(), &sql.TxOptions{})
  1941  	if err != nil {
  1942  		return 0, 0, err
  1943  	}
  1944  	defer func() {
  1945  		if err == nil {
  1946  			err = tx.Commit()
  1947  			return
  1948  		}
  1949  		// don't shadow original error
  1950  		_ = tx.Rollback()
  1951  	}()
  1952  
  1953  	idsArgs := make([]interface{}, 0, len(ids))
  1954  	for _, id := range ids {
  1955  		idsArgs = append(idsArgs, id)
  1956  	}
  1957  
  1958  	inVector := strings.Repeat("?, ", len(ids)-1) + "?"
  1959  	q := "UPDATE user_messages SET seen = 1 WHERE NOT(seen) AND (mentioned OR replied) AND id IN (" + inVector + ")" // nolint: gosec
  1960  	_, err = tx.Exec(q, idsArgs...)
  1961  	if err != nil {
  1962  		return 0, 0, err
  1963  	}
  1964  
  1965  	var countWithMentions uint64
  1966  	row := tx.QueryRow("SELECT changes();")
  1967  	if err := row.Scan(&countWithMentions); err != nil {
  1968  		return 0, 0, err
  1969  	}
  1970  
  1971  	q = "UPDATE user_messages SET seen = 1 WHERE NOT(seen) AND NOT(mentioned) AND NOT(replied) AND id IN (" + inVector + ")" // nolint: gosec
  1972  	_, err = tx.Exec(q, idsArgs...)
  1973  	if err != nil {
  1974  		return 0, 0, err
  1975  	}
  1976  
  1977  	var countNoMentions uint64
  1978  	row = tx.QueryRow("SELECT changes();")
  1979  	if err := row.Scan(&countNoMentions); err != nil {
  1980  		return 0, 0, err
  1981  	}
  1982  
  1983  	// Update denormalized count
  1984  	_, err = tx.Exec(
  1985  		`UPDATE chats
  1986                	SET unviewed_message_count =
  1987  		   (SELECT COUNT(1)
  1988  		   FROM user_messages
  1989  		   WHERE local_chat_id = ? AND seen = 0),
  1990  		   unviewed_mentions_count =
  1991  		   (SELECT COUNT(1)
  1992  		   FROM user_messages
  1993  		   WHERE local_chat_id = ? AND seen = 0 AND (mentioned OR replied)),
  1994                     highlight = 0
  1995  		WHERE id = ?`, chatID, chatID, chatID)
  1996  	return countWithMentions + countNoMentions, countWithMentions, err
  1997  }
  1998  
  1999  func (db sqlitePersistence) GetMessageIdsWithGreaterTimestamp(chatID string, messageID string) ([]string, error) {
  2000  	var err error
  2001  	var rows *sql.Rows
  2002  	query := "SELECT id FROM user_messages WHERE local_chat_id = ? AND timestamp >= (SELECT timestamp FROM user_messages WHERE id = ?)"
  2003  
  2004  	rows, err = db.db.Query(query, chatID, messageID)
  2005  
  2006  	if err != nil {
  2007  		return nil, err
  2008  	}
  2009  	defer rows.Close()
  2010  
  2011  	var ids []string
  2012  
  2013  	for rows.Next() {
  2014  		var messageID string
  2015  		err = rows.Scan(&messageID)
  2016  		if err != nil {
  2017  			return nil, err
  2018  		}
  2019  		ids = append(ids, messageID)
  2020  	}
  2021  
  2022  	return ids, nil
  2023  }
  2024  
  2025  func (db sqlitePersistence) MarkMessageAsUnread(chatID string, messageID string) (uint64, uint64, error) {
  2026  	tx, err := db.db.BeginTx(context.Background(), &sql.TxOptions{})
  2027  	if err != nil {
  2028  		return 0, 0, err
  2029  	}
  2030  	defer func() {
  2031  		if err == nil {
  2032  			err = tx.Commit()
  2033  			return
  2034  		}
  2035  		// don't shadow original error
  2036  		_ = tx.Rollback()
  2037  	}()
  2038  
  2039  	// TODO : Reduce number of queries for getting (total unread messages, total messages with mention)
  2040  	// The function expected result is a pair (total unread messages, total messages with mention)
  2041  	// Currently a 2 step operation is needed to obtain this pair
  2042  	_, err = tx.Exec(`UPDATE user_messages SET seen = 1 WHERE local_chat_id = ? AND NOT(seen)`, chatID)
  2043  	if err != nil {
  2044  		return 0, 0, err
  2045  	}
  2046  
  2047  	_, err = tx.Exec(
  2048  		`UPDATE user_messages
  2049  			SET seen = 0
  2050  			WHERE local_chat_id = ?
  2051  			AND seen = 1
  2052  			AND (mentioned OR replied)
  2053  			AND timestamp >= (SELECT timestamp FROM user_messages WHERE id = ?)`, chatID, messageID)
  2054  	if err != nil {
  2055  		return 0, 0, err
  2056  	}
  2057  
  2058  	var countWithMentions uint64
  2059  	row := tx.QueryRow("SELECT changes();")
  2060  	if err := row.Scan(&countWithMentions); err != nil {
  2061  		return 0, 0, err
  2062  	}
  2063  
  2064  	_, err = tx.Exec(
  2065  		`UPDATE user_messages
  2066  			SET seen = 0
  2067  			WHERE local_chat_id = ?
  2068  			AND seen = 1
  2069  			AND NOT(mentioned OR replied)
  2070  			AND timestamp >= (SELECT timestamp FROM user_messages WHERE id = ?)`, chatID, messageID)
  2071  	if err != nil {
  2072  		return 0, 0, err
  2073  	}
  2074  
  2075  	var countNoMentions uint64
  2076  	row = tx.QueryRow("SELECT changes();")
  2077  	if err := row.Scan(&countNoMentions); err != nil {
  2078  		return 0, 0, err
  2079  	}
  2080  
  2081  	count := countWithMentions + countNoMentions
  2082  
  2083  	_, err = tx.Exec(
  2084  		`UPDATE chats
  2085              SET unviewed_message_count = ?, unviewed_mentions_count = ?,
  2086  			highlight = 0
  2087  			WHERE id = ?`, count, countWithMentions, chatID)
  2088  
  2089  	return count, countWithMentions, err
  2090  }
  2091  
  2092  func (db sqlitePersistence) UpdateMessageOutgoingStatus(id string, newOutgoingStatus string) error {
  2093  	_, err := db.db.Exec(`
  2094  		UPDATE user_messages
  2095  		SET outgoing_status = ?
  2096  		WHERE id = ? AND outgoing_status != ?
  2097  	`, newOutgoingStatus, id, common.OutgoingStatusDelivered)
  2098  	return err
  2099  }
  2100  
  2101  // BlockContact updates a contact, deletes all the messages and 1-to-1 chat, updates the unread messages count and returns a map with the new count
  2102  func (db sqlitePersistence) BlockContact(contact *Contact, isDesktopFunc bool) ([]*Chat, error) {
  2103  	var chats []*Chat
  2104  	tx, err := db.db.BeginTx(context.Background(), &sql.TxOptions{})
  2105  	if err != nil {
  2106  		return nil, err
  2107  	}
  2108  	defer func() {
  2109  		if err == nil {
  2110  			err = tx.Commit()
  2111  			return
  2112  		}
  2113  		// don't shadow original error
  2114  		_ = tx.Rollback()
  2115  	}()
  2116  
  2117  	if !isDesktopFunc {
  2118  		// Delete messages
  2119  		_, err = tx.Exec(
  2120  			`DELETE
  2121  			FROM user_messages
  2122  			WHERE source = ?`,
  2123  			contact.ID,
  2124  		)
  2125  		if err != nil {
  2126  			return nil, err
  2127  		}
  2128  	}
  2129  
  2130  	// Update contact
  2131  	err = db.SaveContact(contact, tx)
  2132  	if err != nil {
  2133  		return nil, err
  2134  	}
  2135  
  2136  	if !isDesktopFunc {
  2137  		// Delete one-to-one chat
  2138  		_, err = tx.Exec("DELETE FROM chats WHERE id = ?", contact.ID)
  2139  		if err != nil {
  2140  			return nil, err
  2141  		}
  2142  	}
  2143  
  2144  	// Recalculate denormalized fields
  2145  	_, err = tx.Exec(`
  2146  		UPDATE chats
  2147  		SET
  2148  			unviewed_message_count = (SELECT COUNT(1) FROM user_messages WHERE seen = 0 AND local_chat_id = chats.id),
  2149  			unviewed_mentions_count = (SELECT COUNT(1) FROM user_messages WHERE seen = 0 AND local_chat_id = chats.id AND (mentioned OR replied))`)
  2150  	if err != nil {
  2151  		return nil, err
  2152  	}
  2153  
  2154  	// return the updated chats
  2155  	chats, err = db.chats(tx)
  2156  	if err != nil {
  2157  		return nil, err
  2158  	}
  2159  	for _, c := range chats {
  2160  		var lastMessageID string
  2161  		row := tx.QueryRow(`SELECT id FROM user_messages WHERE local_chat_id = ? ORDER BY clock_value DESC LIMIT 1`, c.ID)
  2162  		switch err := row.Scan(&lastMessageID); err {
  2163  
  2164  		case nil:
  2165  			message, err := db.messageByID(tx, lastMessageID)
  2166  			if err != nil {
  2167  				return nil, err
  2168  			}
  2169  			if message != nil {
  2170  				encodedMessage, err := json.Marshal(message)
  2171  				if err != nil {
  2172  					return nil, err
  2173  				}
  2174  				_, err = tx.Exec(`UPDATE chats SET last_message = ? WHERE id = ?`, encodedMessage, c.ID)
  2175  				if err != nil {
  2176  					return nil, err
  2177  				}
  2178  				c.LastMessage = message
  2179  
  2180  			}
  2181  
  2182  		case sql.ErrNoRows:
  2183  			// Reset LastMessage
  2184  			_, err = tx.Exec(`UPDATE chats SET last_message = NULL WHERE id = ?`, c.ID)
  2185  			if err != nil {
  2186  				return nil, err
  2187  			}
  2188  			c.LastMessage = nil
  2189  		default:
  2190  			return nil, err
  2191  		}
  2192  	}
  2193  
  2194  	return chats, err
  2195  }
  2196  
  2197  func (db sqlitePersistence) HasDiscordMessageAuthor(id string) (exists bool, err error) {
  2198  	err = db.db.QueryRow(`SELECT EXISTS(SELECT 1 FROM discord_message_authors WHERE id = ?)`, id).Scan(&exists)
  2199  	return exists, err
  2200  }
  2201  
  2202  func (db sqlitePersistence) HasDiscordMessageAuthorImagePayload(id string) (hasPayload bool, err error) {
  2203  	err = db.db.QueryRow(`SELECT EXISTS(SELECT 1 FROM discord_message_authors WHERE id = ? AND avatar_image_payload NOT NULL)`, id).Scan(&hasPayload)
  2204  	return hasPayload, err
  2205  }
  2206  
  2207  func (db sqlitePersistence) SaveDiscordMessageAuthor(author *protobuf.DiscordMessageAuthor) (err error) {
  2208  	stmt, err := db.db.Prepare(basicInsertDiscordMessageAuthorQuery)
  2209  	if err != nil {
  2210  		return
  2211  	}
  2212  	_, err = stmt.Exec(
  2213  		author.GetId(),
  2214  		author.GetName(),
  2215  		author.GetDiscriminator(),
  2216  		author.GetNickname(),
  2217  		author.GetAvatarUrl(),
  2218  		author.GetAvatarImagePayload(),
  2219  	)
  2220  	return
  2221  }
  2222  
  2223  func (db sqlitePersistence) SaveDiscordMessageAuthors(authors []*protobuf.DiscordMessageAuthor) (err error) {
  2224  	tx, err := db.db.BeginTx(context.Background(), &sql.TxOptions{})
  2225  	if err != nil {
  2226  		return
  2227  	}
  2228  	defer func() {
  2229  		if err == nil {
  2230  			err = tx.Commit()
  2231  			return
  2232  		}
  2233  		// don't shadow original error
  2234  		_ = tx.Rollback()
  2235  	}()
  2236  
  2237  	stmt, err := tx.Prepare(basicInsertDiscordMessageAuthorQuery)
  2238  	if err != nil {
  2239  		return
  2240  	}
  2241  	defer stmt.Close()
  2242  
  2243  	for _, author := range authors {
  2244  		_, err = stmt.Exec(
  2245  			author.GetId(),
  2246  			author.GetName(),
  2247  			author.GetDiscriminator(),
  2248  			author.GetNickname(),
  2249  			author.GetAvatarUrl(),
  2250  			author.GetAvatarImagePayload(),
  2251  		)
  2252  		if err != nil {
  2253  			return
  2254  		}
  2255  	}
  2256  	return
  2257  }
  2258  
  2259  func (db sqlitePersistence) UpdateDiscordMessageAuthorImage(authorID string, payload []byte) (err error) {
  2260  	query := "UPDATE discord_message_authors SET avatar_image_payload = ? WHERE id = ?"
  2261  	stmt, err := db.db.Prepare(query)
  2262  	if err != nil {
  2263  		return
  2264  	}
  2265  	defer stmt.Close()
  2266  	_, err = stmt.Exec(payload, authorID)
  2267  	return
  2268  }
  2269  
  2270  func (db sqlitePersistence) GetDiscordMessageAuthorImagePayloadByID(id string) ([]byte, error) {
  2271  	payload := make([]byte, 0)
  2272  	row := db.db.QueryRow("SELECT avatar_image_payload FROM discord_message_authors WHERE id = ?", id)
  2273  	err := row.Scan(&payload)
  2274  	return payload, err
  2275  }
  2276  
  2277  func (db sqlitePersistence) GetDiscordMessageAuthorByID(id string) (*protobuf.DiscordMessageAuthor, error) {
  2278  
  2279  	author := &protobuf.DiscordMessageAuthor{}
  2280  
  2281  	row := db.db.QueryRow("SELECT id, name, discriminator, nickname, avatar_url FROM discord_message_authors WHERE id = ?", id)
  2282  	err := row.Scan(
  2283  		&author.Id,
  2284  		&author.Name,
  2285  		&author.Discriminator,
  2286  		&author.Nickname,
  2287  		&author.AvatarUrl)
  2288  	return author, err
  2289  }
  2290  
  2291  func (db sqlitePersistence) SaveDiscordMessage(message *protobuf.DiscordMessage) (err error) {
  2292  	query := "INSERT OR REPLACE INTO discord_messages(id,type,timestamp,timestamp_edited,content,author_id, reference_message_id, reference_channel_id, reference_guild_id) VALUES (?,?,?,?,?,?,?,?,?)"
  2293  	stmt, err := db.db.Prepare(query)
  2294  	if err != nil {
  2295  		return
  2296  	}
  2297  	defer stmt.Close()
  2298  	_, err = stmt.Exec(
  2299  		message.GetId(),
  2300  		message.GetType(),
  2301  		message.GetTimestamp(),
  2302  		message.GetTimestampEdited(),
  2303  		message.GetContent(),
  2304  		message.Author.GetId(),
  2305  		message.Reference.GetMessageId(),
  2306  		message.Reference.GetChannelId(),
  2307  		message.Reference.GetGuildId(),
  2308  	)
  2309  	return
  2310  }
  2311  
  2312  func (db sqlitePersistence) SaveDiscordMessages(messages []*protobuf.DiscordMessage) (err error) {
  2313  	tx, err := db.db.BeginTx(context.Background(), &sql.TxOptions{})
  2314  	if err != nil {
  2315  		return
  2316  	}
  2317  	defer func() {
  2318  		if err == nil {
  2319  			err = tx.Commit()
  2320  			return
  2321  		}
  2322  		// don't shadow original error
  2323  		_ = tx.Rollback()
  2324  	}()
  2325  
  2326  	query := "INSERT OR REPLACE INTO discord_messages(id, author_id, type, timestamp, timestamp_edited, content, reference_message_id, reference_channel_id, reference_guild_id) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)"
  2327  	stmt, err := tx.Prepare(query)
  2328  	if err != nil {
  2329  		return
  2330  	}
  2331  	defer stmt.Close()
  2332  
  2333  	for _, msg := range messages {
  2334  		_, err = stmt.Exec(
  2335  			msg.GetId(),
  2336  			msg.Author.GetId(),
  2337  			msg.GetType(),
  2338  			msg.GetTimestamp(),
  2339  			msg.GetTimestampEdited(),
  2340  			msg.GetContent(),
  2341  			msg.Reference.GetMessageId(),
  2342  			msg.Reference.GetChannelId(),
  2343  			msg.Reference.GetGuildId(),
  2344  		)
  2345  		if err != nil {
  2346  			return
  2347  		}
  2348  	}
  2349  	return
  2350  }
  2351  
  2352  func (db sqlitePersistence) HasDiscordMessageAttachmentPayload(id string, messageID string) (hasPayload bool, err error) {
  2353  	err = db.db.QueryRow(`SELECT EXISTS(SELECT 1 FROM discord_message_attachments WHERE id = ? AND discord_message_id = ? AND payload NOT NULL)`, id, messageID).Scan(&hasPayload)
  2354  	return hasPayload, err
  2355  }
  2356  
  2357  func (db sqlitePersistence) SaveDiscordMessageAttachments(attachments []*protobuf.DiscordMessageAttachment) (err error) {
  2358  	tx, err := db.db.BeginTx(context.Background(), &sql.TxOptions{})
  2359  	if err != nil {
  2360  		return
  2361  	}
  2362  	defer func() {
  2363  		if err == nil {
  2364  			err = tx.Commit()
  2365  			return
  2366  		}
  2367  		// don't shadow original error
  2368  		_ = tx.Rollback()
  2369  	}()
  2370  
  2371  	stmt, err := tx.Prepare("INSERT OR REPLACE INTO discord_message_attachments(id,discord_message_id,url,file_name,file_size_bytes,payload, content_type) VALUES (?,?,?,?,?,?,?)")
  2372  	if err != nil {
  2373  		return
  2374  	}
  2375  	defer stmt.Close()
  2376  	if err != nil {
  2377  		return
  2378  	}
  2379  
  2380  	for _, attachment := range attachments {
  2381  		_, err = stmt.Exec(
  2382  			attachment.GetId(),
  2383  			attachment.GetMessageId(),
  2384  			attachment.GetUrl(),
  2385  			attachment.GetFileName(),
  2386  			attachment.GetFileSizeBytes(),
  2387  			attachment.GetPayload(),
  2388  			attachment.GetContentType(),
  2389  		)
  2390  		if err != nil {
  2391  			return
  2392  		}
  2393  	}
  2394  	return
  2395  }
  2396  
  2397  func (db sqlitePersistence) SaveEmojiReaction(emojiReaction *EmojiReaction) (err error) {
  2398  	query := "INSERT INTO emoji_reactions(id,clock_value,source,emoji_id,message_id,chat_id,local_chat_id,retracted) VALUES (?,?,?,?,?,?,?,?)"
  2399  	stmt, err := db.db.Prepare(query)
  2400  	if err != nil {
  2401  		return
  2402  	}
  2403  
  2404  	_, err = stmt.Exec(
  2405  		emojiReaction.ID(),
  2406  		emojiReaction.Clock,
  2407  		emojiReaction.From,
  2408  		emojiReaction.Type,
  2409  		emojiReaction.MessageId,
  2410  		emojiReaction.ChatId,
  2411  		emojiReaction.LocalChatID,
  2412  		emojiReaction.Retracted,
  2413  	)
  2414  
  2415  	return
  2416  }
  2417  
  2418  func (db sqlitePersistence) EmojiReactionByID(id string) (*EmojiReaction, error) {
  2419  	row := db.db.QueryRow(
  2420  		`SELECT
  2421  			    clock_value,
  2422  			    source,
  2423  			    emoji_id,
  2424  			    message_id,
  2425  			    chat_id,
  2426  			    local_chat_id,
  2427  			    retracted
  2428  			FROM
  2429  				emoji_reactions
  2430  			WHERE
  2431  				emoji_reactions.id = ?
  2432  		`, id)
  2433  
  2434  	emojiReaction := NewEmojiReaction()
  2435  	err := row.Scan(&emojiReaction.Clock,
  2436  		&emojiReaction.From,
  2437  		&emojiReaction.Type,
  2438  		&emojiReaction.MessageId,
  2439  		&emojiReaction.ChatId,
  2440  		&emojiReaction.LocalChatID,
  2441  		&emojiReaction.Retracted,
  2442  	)
  2443  
  2444  	switch err {
  2445  	case sql.ErrNoRows:
  2446  		return nil, common.ErrRecordNotFound
  2447  	case nil:
  2448  		return emojiReaction, nil
  2449  	default:
  2450  		return nil, err
  2451  	}
  2452  }
  2453  
  2454  func (db sqlitePersistence) SaveInvitation(invitation *GroupChatInvitation) (err error) {
  2455  	query := "INSERT INTO group_chat_invitations(id,source,chat_id,message,state,clock) VALUES (?,?,?,?,?,?)"
  2456  	stmt, err := db.db.Prepare(query)
  2457  	if err != nil {
  2458  		return
  2459  	}
  2460  	_, err = stmt.Exec(
  2461  		invitation.ID(),
  2462  		invitation.From,
  2463  		invitation.ChatId,
  2464  		invitation.IntroductionMessage,
  2465  		invitation.State,
  2466  		invitation.Clock,
  2467  	)
  2468  
  2469  	return
  2470  }
  2471  
  2472  func (db sqlitePersistence) GetGroupChatInvitations() (rst []*GroupChatInvitation, err error) {
  2473  
  2474  	tx, err := db.db.Begin()
  2475  	if err != nil {
  2476  		return
  2477  	}
  2478  	defer func() {
  2479  		if err == nil {
  2480  			err = tx.Commit()
  2481  			return
  2482  		}
  2483  		_ = tx.Rollback()
  2484  	}()
  2485  
  2486  	bRows, err := tx.Query(`SELECT
  2487  			    source,
  2488  			    chat_id,
  2489  			    message,
  2490  			    state,
  2491  			    clock
  2492  			FROM
  2493  				group_chat_invitations`)
  2494  	if err != nil {
  2495  		return
  2496  	}
  2497  	defer bRows.Close()
  2498  	for bRows.Next() {
  2499  		invitation := GroupChatInvitation{}
  2500  		err = bRows.Scan(
  2501  			&invitation.From,
  2502  			&invitation.ChatId,
  2503  			&invitation.IntroductionMessage,
  2504  			&invitation.State,
  2505  			&invitation.Clock)
  2506  		if err != nil {
  2507  			return nil, err
  2508  		}
  2509  		rst = append(rst, &invitation)
  2510  	}
  2511  
  2512  	return rst, nil
  2513  }
  2514  
  2515  func (db sqlitePersistence) InvitationByID(id string) (*GroupChatInvitation, error) {
  2516  	row := db.db.QueryRow(
  2517  		`SELECT
  2518  			    source,
  2519  			    chat_id,
  2520  			    message,
  2521  			    state,
  2522  			    clock
  2523  			FROM
  2524  				group_chat_invitations
  2525  			WHERE
  2526  				group_chat_invitations.id = ?
  2527  		`, id)
  2528  
  2529  	chatInvitations := NewGroupChatInvitation()
  2530  	err := row.Scan(&chatInvitations.From,
  2531  		&chatInvitations.ChatId,
  2532  		&chatInvitations.IntroductionMessage,
  2533  		&chatInvitations.State,
  2534  		&chatInvitations.Clock,
  2535  	)
  2536  
  2537  	switch err {
  2538  	case sql.ErrNoRows:
  2539  		return nil, common.ErrRecordNotFound
  2540  	case nil:
  2541  		return chatInvitations, nil
  2542  	default:
  2543  		return nil, err
  2544  	}
  2545  }
  2546  
  2547  // ClearHistory deletes all the messages for a chat and updates it's values
  2548  func (db sqlitePersistence) ClearHistory(chat *Chat, currentClockValue uint64) (err error) {
  2549  	var tx *sql.Tx
  2550  
  2551  	tx, err = db.db.BeginTx(context.Background(), &sql.TxOptions{})
  2552  	if err != nil {
  2553  		return
  2554  	}
  2555  
  2556  	defer func() {
  2557  		if err == nil {
  2558  			err = tx.Commit()
  2559  			return
  2560  		}
  2561  		// don't shadow original error
  2562  		_ = tx.Rollback()
  2563  	}()
  2564  	err = db.clearHistory(chat, currentClockValue, tx, false)
  2565  
  2566  	return
  2567  }
  2568  
  2569  func (db sqlitePersistence) ClearHistoryFromSyncMessage(chat *Chat, currentClockValue uint64) (err error) {
  2570  	var tx *sql.Tx
  2571  
  2572  	tx, err = db.db.BeginTx(context.Background(), &sql.TxOptions{})
  2573  	if err != nil {
  2574  		return
  2575  	}
  2576  
  2577  	defer func() {
  2578  		if err == nil {
  2579  			err = tx.Commit()
  2580  			return
  2581  		}
  2582  		// don't shadow original error
  2583  		_ = tx.Rollback()
  2584  	}()
  2585  	err = db.clearHistoryFromSyncMessage(chat, currentClockValue, tx)
  2586  
  2587  	return
  2588  }
  2589  
  2590  // Deactivate chat sets a chat as inactive and clear its history
  2591  func (db sqlitePersistence) DeactivateChat(chat *Chat, currentClockValue uint64, doClearHistory bool) (err error) {
  2592  	var tx *sql.Tx
  2593  
  2594  	tx, err = db.db.BeginTx(context.Background(), &sql.TxOptions{})
  2595  	if err != nil {
  2596  		return
  2597  	}
  2598  
  2599  	defer func() {
  2600  		if err == nil {
  2601  			err = tx.Commit()
  2602  			return
  2603  		}
  2604  		// don't shadow original error
  2605  		_ = tx.Rollback()
  2606  	}()
  2607  	err = db.deactivateChat(chat, currentClockValue, tx, doClearHistory)
  2608  
  2609  	return
  2610  }
  2611  
  2612  func (db sqlitePersistence) deactivateChat(chat *Chat, currentClockValue uint64, tx *sql.Tx, doClearHistory bool) error {
  2613  	chat.Active = false
  2614  	err := db.saveChat(tx, *chat)
  2615  	if err != nil {
  2616  		return err
  2617  	}
  2618  
  2619  	if !doClearHistory {
  2620  		return nil
  2621  	}
  2622  	return db.clearHistory(chat, currentClockValue, tx, true)
  2623  }
  2624  
  2625  func (db sqlitePersistence) SaveDelete(deleteMessage *DeleteMessage) error {
  2626  	_, err := db.db.Exec(`INSERT INTO user_messages_deletes (clock, chat_id, message_id, source, id) VALUES(?,?,?,?,?)`, deleteMessage.Clock, deleteMessage.ChatId, deleteMessage.MessageId, deleteMessage.From, deleteMessage.ID)
  2627  	return err
  2628  }
  2629  
  2630  func (db sqlitePersistence) GetDeletes(messageID string, from string) ([]*DeleteMessage, error) {
  2631  	rows, err := db.db.Query(`SELECT clock, chat_id, message_id, source, id FROM user_messages_deletes WHERE message_id = ? AND source = ? ORDER BY CLOCK DESC`, messageID, from)
  2632  	if err != nil {
  2633  		return nil, err
  2634  	}
  2635  	defer rows.Close()
  2636  
  2637  	var messages []*DeleteMessage
  2638  	for rows.Next() {
  2639  		d := NewDeleteMessage()
  2640  		err := rows.Scan(&d.Clock, &d.ChatId, &d.MessageId, &d.From, &d.ID)
  2641  		if err != nil {
  2642  			return nil, err
  2643  		}
  2644  		messages = append(messages, d)
  2645  	}
  2646  	return messages, nil
  2647  }
  2648  
  2649  func (db sqlitePersistence) SaveOrUpdateDeleteForMeMessage(deleteForMeMessage *protobuf.SyncDeleteForMeMessage) error {
  2650  	_, err := db.db.Exec(`INSERT OR REPLACE INTO user_messages_deleted_for_mes (clock, message_id)
  2651      SELECT ?,? WHERE NOT EXISTS (SELECT 1 FROM user_messages_deleted_for_mes WHERE message_id = ? AND clock >= ?)`,
  2652  		deleteForMeMessage.Clock, deleteForMeMessage.MessageId, deleteForMeMessage.MessageId, deleteForMeMessage.Clock)
  2653  	return err
  2654  }
  2655  
  2656  func (db sqlitePersistence) GetDeleteForMeMessagesByMessageID(messageID string) ([]*protobuf.SyncDeleteForMeMessage, error) {
  2657  	rows, err := db.db.Query(`SELECT clock, message_id FROM user_messages_deleted_for_mes WHERE message_id = ?`, messageID)
  2658  	if err != nil {
  2659  		return nil, err
  2660  	}
  2661  	defer rows.Close()
  2662  
  2663  	var messages []*protobuf.SyncDeleteForMeMessage
  2664  	for rows.Next() {
  2665  		d := &protobuf.SyncDeleteForMeMessage{}
  2666  		err := rows.Scan(&d.Clock, &d.MessageId)
  2667  		if err != nil {
  2668  			return nil, err
  2669  		}
  2670  		messages = append(messages, d)
  2671  	}
  2672  	return messages, nil
  2673  }
  2674  
  2675  func (db sqlitePersistence) GetDeleteForMeMessages() ([]*protobuf.SyncDeleteForMeMessage, error) {
  2676  	rows, err := db.db.Query(`SELECT clock, message_id FROM user_messages_deleted_for_mes`)
  2677  	if err != nil {
  2678  		return nil, err
  2679  	}
  2680  	defer rows.Close()
  2681  
  2682  	var messages []*protobuf.SyncDeleteForMeMessage
  2683  	for rows.Next() {
  2684  		d := &protobuf.SyncDeleteForMeMessage{}
  2685  		err := rows.Scan(&d.Clock, &d.MessageId)
  2686  		if err != nil {
  2687  			return nil, err
  2688  		}
  2689  		messages = append(messages, d)
  2690  	}
  2691  	return messages, nil
  2692  }
  2693  
  2694  func (db sqlitePersistence) SaveEdit(editMessage *EditMessage) error {
  2695  	if editMessage == nil {
  2696  		return nil
  2697  	}
  2698  
  2699  	_, err := db.db.Exec(`INSERT INTO user_messages_edits (clock, chat_id, message_id, text, source, id, unfurled_links, unfurled_status_links) VALUES(?,?,?,?,?,?,?,?)`, editMessage.Clock, editMessage.ChatId, editMessage.MessageId, editMessage.Text, editMessage.From, editMessage.ID, pq.Array(editMessage.UnfurledLinks), editMessage.UnfurledStatusLinks)
  2700  	return err
  2701  }
  2702  
  2703  func (db sqlitePersistence) GetEdits(messageID string, from string) ([]*EditMessage, error) {
  2704  	rows, err := db.db.Query(`SELECT clock, chat_id, message_id, source, text, id, unfurled_links, unfurled_status_links FROM user_messages_edits WHERE message_id = ? AND source = ? ORDER BY CLOCK DESC`, messageID, from)
  2705  	if err != nil {
  2706  		return nil, err
  2707  	}
  2708  	defer rows.Close()
  2709  
  2710  	var messages []*EditMessage
  2711  	for rows.Next() {
  2712  		e := NewEditMessage()
  2713  		err := rows.Scan(&e.Clock, &e.ChatId, &e.MessageId, &e.From, &e.Text, &e.ID, pq.Array(&e.UnfurledLinks), &e.UnfurledStatusLinks)
  2714  		if err != nil {
  2715  			return nil, err
  2716  		}
  2717  		messages = append(messages, e)
  2718  
  2719  	}
  2720  	return messages, nil
  2721  }
  2722  
  2723  func (db sqlitePersistence) clearHistory(chat *Chat, currentClockValue uint64, tx *sql.Tx, deactivate bool) error {
  2724  	// Set deleted at clock value if it's not a public chat so that
  2725  	// old messages will be discarded, or if it's a straight clear history
  2726  	if !deactivate || (!chat.Public() && !chat.ProfileUpdates() && !chat.Timeline()) {
  2727  		if chat.LastMessage != nil && chat.LastMessage.Clock != 0 {
  2728  			chat.DeletedAtClockValue = chat.LastMessage.Clock
  2729  		}
  2730  		chat.DeletedAtClockValue = currentClockValue
  2731  	}
  2732  
  2733  	// Reset synced-to/from
  2734  	syncedTo := uint32(currentClockValue / 1000)
  2735  	chat.SyncedTo = syncedTo
  2736  	chat.SyncedFrom = 0
  2737  
  2738  	chat.LastMessage = nil
  2739  	chat.UnviewedMessagesCount = 0
  2740  	chat.UnviewedMentionsCount = 0
  2741  	chat.Highlight = true
  2742  
  2743  	err := db.deleteMessagesByChatID(chat.ID, tx)
  2744  	if err != nil {
  2745  		return err
  2746  	}
  2747  
  2748  	err = db.saveChat(tx, *chat)
  2749  	return err
  2750  }
  2751  
  2752  func (db sqlitePersistence) clearHistoryFromSyncMessage(chat *Chat, clearedAt uint64, tx *sql.Tx) error {
  2753  	chat.DeletedAtClockValue = clearedAt
  2754  
  2755  	// Reset synced-to/from
  2756  	syncedTo := uint32(clearedAt / 1000)
  2757  	chat.SyncedTo = syncedTo
  2758  	chat.SyncedFrom = 0
  2759  
  2760  	unViewedMessagesCount, unViewedMentionsCount, err := db.deleteMessagesByChatIDAndClockValueLessThanOrEqual(chat.ID, clearedAt, tx)
  2761  	if err != nil {
  2762  		return err
  2763  	}
  2764  
  2765  	chat.UnviewedMessagesCount = unViewedMessagesCount
  2766  	chat.UnviewedMentionsCount = unViewedMentionsCount
  2767  
  2768  	if chat.LastMessage != nil && chat.LastMessage.Clock <= clearedAt {
  2769  		chat.LastMessage = nil
  2770  	}
  2771  
  2772  	err = db.saveChat(tx, *chat)
  2773  	return err
  2774  }
  2775  
  2776  func (db sqlitePersistence) SetContactRequestState(id string, state common.ContactRequestState) error {
  2777  	_, err := db.db.Exec(`UPDATE user_messages SET contact_request_state = ? WHERE id = ?`, state, id)
  2778  	return err
  2779  }
  2780  
  2781  func getUpdatedChatMessagePayload(originalMessage *protobuf.DiscordMessage, attachmentMessage *protobuf.DiscordMessage) *protobuf.ChatMessage_DiscordMessage {
  2782  	originalMessage.Attachments = append(originalMessage.Attachments, attachmentMessage.Attachments...)
  2783  	return &protobuf.ChatMessage_DiscordMessage{
  2784  		DiscordMessage: originalMessage,
  2785  	}
  2786  }
  2787  
  2788  func getMessageFromScanRows(db sqlitePersistence, rows *sql.Rows) (*common.Message, error) {
  2789  	var msg *common.Message
  2790  
  2791  	for rows.Next() {
  2792  		// There's a possibility of multiple rows per message if the
  2793  		// message has a discordMessage and the discordMessage has multiple
  2794  		// attachments
  2795  		//
  2796  		// Hence, we make sure we're aggregating all attachments on a single
  2797  		// common.Message
  2798  		message := common.NewMessage()
  2799  		err := db.tableUserMessagesScanAllFields(rows, message)
  2800  		if err != nil {
  2801  			return nil, err
  2802  		}
  2803  
  2804  		if msg == nil {
  2805  			msg = message
  2806  		} else if discordMessage := msg.GetDiscordMessage(); discordMessage != nil {
  2807  			msg.Payload = getUpdatedChatMessagePayload(discordMessage, message.GetDiscordMessage())
  2808  		}
  2809  	}
  2810  	if msg == nil {
  2811  		return nil, common.ErrRecordNotFound
  2812  	}
  2813  	return msg, nil
  2814  }
  2815  
  2816  type HasClocks interface {
  2817  	GetClock(i int) uint64
  2818  }
  2819  
  2820  func SortByClock(msgs HasClocks) {
  2821  	sort.Slice(msgs, func(i, j int) bool {
  2822  		return msgs.GetClock(j) < msgs.GetClock(i)
  2823  	})
  2824  }
  2825  
  2826  func getMessagesFromScanRows(db sqlitePersistence, rows *sql.Rows, withCursor bool) ([]*common.Message, error) {
  2827  	messageIdx := make(map[string]*common.Message, 0)
  2828  	var messages common.Messages
  2829  	for rows.Next() {
  2830  		// There's a possibility of multiple rows per message if the
  2831  		// message has a discordMessage and the discordMessage has multiple
  2832  		// attachments
  2833  		//
  2834  		// Hence, we make sure we're aggregating all attachments on a single
  2835  		// common.Message
  2836  		message := common.NewMessage()
  2837  
  2838  		if withCursor {
  2839  			var cursor string
  2840  			if err := db.tableUserMessagesScanAllFields(rows, message, &cursor); err != nil {
  2841  				return nil, err
  2842  			}
  2843  		} else {
  2844  			if err := db.tableUserMessagesScanAllFields(rows, message); err != nil {
  2845  				return nil, err
  2846  			}
  2847  		}
  2848  
  2849  		if msg, ok := messageIdx[message.ID]; !ok {
  2850  			messageIdx[message.ID] = message
  2851  			messages = append(messages, message)
  2852  		} else if discordMessage := msg.GetDiscordMessage(); discordMessage != nil {
  2853  			msg.Payload = getUpdatedChatMessagePayload(discordMessage, message.GetDiscordMessage())
  2854  		}
  2855  	}
  2856  
  2857  	SortByClock(messages)
  2858  
  2859  	return messages, nil
  2860  }
  2861  
  2862  func getMessagesAndCursorsFromScanRows(db sqlitePersistence, rows *sql.Rows) ([]*common.Message, []string, error) {
  2863  
  2864  	var cursors []string
  2865  	var messages common.Messages
  2866  	messageIdx := make(map[string]*common.Message, 0)
  2867  	for rows.Next() {
  2868  		// There's a possibility of multiple rows per message if the
  2869  		// message has a discordMessage and the discordMessage has multiple
  2870  		// attachments
  2871  		//
  2872  		// Hence, we make sure we're aggregating all attachments on a single
  2873  		// common.Message
  2874  
  2875  		var cursor string
  2876  		message := common.NewMessage()
  2877  		if err := db.tableUserMessagesScanAllFields(rows, message, &cursor); err != nil {
  2878  			return nil, nil, err
  2879  		}
  2880  
  2881  		if msg, ok := messageIdx[message.ID]; !ok {
  2882  			messageIdx[message.ID] = message
  2883  			cursors = append(cursors, cursor)
  2884  			messages = append(messages, message)
  2885  		} else if discordMessage := msg.GetDiscordMessage(); discordMessage != nil {
  2886  			msg.Payload = getUpdatedChatMessagePayload(discordMessage, message.GetDiscordMessage())
  2887  		}
  2888  	}
  2889  
  2890  	SortByClock(messages)
  2891  
  2892  	return messages, cursors, nil
  2893  }
  2894  
  2895  func getPinnedMessagesAndCursorsFromScanRows(db sqlitePersistence, rows *sql.Rows) ([]*common.PinnedMessage, []string, error) {
  2896  
  2897  	var cursors []string
  2898  	var messages common.PinnedMessages
  2899  	messageIdx := make(map[string]*common.PinnedMessage, 0)
  2900  
  2901  	for rows.Next() {
  2902  		var (
  2903  			pinnedAt uint64
  2904  			pinnedBy string
  2905  			cursor   string
  2906  		)
  2907  		message := common.NewMessage()
  2908  		if err := db.tableUserMessagesScanAllFields(rows, message, &pinnedAt, &pinnedBy, &cursor); err != nil {
  2909  			return nil, nil, err
  2910  		}
  2911  		if msg, ok := messageIdx[message.ID]; !ok {
  2912  			pinnedMessage := &common.PinnedMessage{
  2913  				Message:  message,
  2914  				PinnedAt: pinnedAt,
  2915  				PinnedBy: pinnedBy,
  2916  			}
  2917  			messageIdx[message.ID] = pinnedMessage
  2918  			messages = append(messages, pinnedMessage)
  2919  			cursors = append(cursors, cursor)
  2920  		} else if discordMessage := msg.Message.GetDiscordMessage(); discordMessage != nil {
  2921  			msg.Message.Payload = getUpdatedChatMessagePayload(discordMessage, message.GetDiscordMessage())
  2922  		}
  2923  	}
  2924  
  2925  	SortByClock(messages)
  2926  
  2927  	return messages, cursors, nil
  2928  }
  2929  
  2930  func (db sqlitePersistence) saveBridgeMessage(tx *sql.Tx, message *protobuf.BridgeMessage, userMessageID string) (err error) {
  2931  	query := "INSERT INTO bridge_messages(user_messages_id,bridge_name,user_name,user_avatar,user_id,content,message_id,parent_message_id) VALUES (?,?,?,?,?,?,?,?)"
  2932  	stmt, err := tx.Prepare(query)
  2933  	if err != nil {
  2934  		return
  2935  	}
  2936  	defer stmt.Close()
  2937  	_, err = stmt.Exec(
  2938  		userMessageID,
  2939  		message.GetBridgeName(),
  2940  		message.GetUserName(),
  2941  		message.GetUserAvatar(),
  2942  		message.GetUserID(),
  2943  		message.GetContent(),
  2944  		message.GetMessageID(),
  2945  		message.GetParentMessageID(),
  2946  	)
  2947  	return
  2948  }
  2949  
  2950  func (db sqlitePersistence) GetCommunityMemberMessagesToDelete(member string, communityID string) ([]*protobuf.DeleteCommunityMemberMessage, error) {
  2951  	rows, err := db.db.Query(`SELECT m.id, m.chat_id FROM user_messages as m
  2952  		INNER JOIN chats AS ch ON ch.id = m.chat_id AND ch.community_id = ?
  2953  		WHERE m.source = ?`, communityID, member)
  2954  
  2955  	if err != nil {
  2956  		return nil, err
  2957  	}
  2958  
  2959  	defer rows.Close()
  2960  
  2961  	result := []*protobuf.DeleteCommunityMemberMessage{}
  2962  
  2963  	for rows.Next() {
  2964  		removeMsgsInfo := &protobuf.DeleteCommunityMemberMessage{}
  2965  		err = rows.Scan(&removeMsgsInfo.Id, &removeMsgsInfo.ChatId)
  2966  		if err != nil {
  2967  			return nil, err
  2968  		}
  2969  		result = append(result, removeMsgsInfo)
  2970  	}
  2971  
  2972  	return result, nil
  2973  }
  2974  
  2975  // Finds status messages id which are replies for bridgeMessageID
  2976  func (db sqlitePersistence) findStatusMessageIdsReplies(tx *sql.Tx, bridgeMessageID string) ([]string, error) {
  2977  	rows, err := tx.Query(`SELECT user_messages_id FROM bridge_messages WHERE parent_message_id = ?`, bridgeMessageID)
  2978  	if err != nil {
  2979  		return []string{}, err
  2980  	}
  2981  	defer rows.Close()
  2982  
  2983  	var statusMessageIDs []string
  2984  	for rows.Next() {
  2985  		var statusMessageID string
  2986  		err = rows.Scan(&statusMessageID)
  2987  		if err != nil {
  2988  			return []string{}, err
  2989  		}
  2990  		statusMessageIDs = append(statusMessageIDs, statusMessageID)
  2991  	}
  2992  	return statusMessageIDs, nil
  2993  }
  2994  
  2995  func (db sqlitePersistence) FindStatusMessageIDForBridgeMessageID(messageID string) (string, error) {
  2996  	rows, err := db.db.Query(`SELECT user_messages_id FROM bridge_messages WHERE message_id = ?`, messageID)
  2997  	if err != nil {
  2998  		return "", err
  2999  	}
  3000  	defer rows.Close()
  3001  
  3002  	if rows.Next() {
  3003  		var statusMessageID string
  3004  		err = rows.Scan(&statusMessageID)
  3005  		if err != nil {
  3006  			return "", err
  3007  		}
  3008  		return statusMessageID, nil
  3009  	}
  3010  	return "", nil
  3011  }
  3012  
  3013  func (db sqlitePersistence) findStatusMessageIDForBridgeMessageID(tx *sql.Tx, messageID string) (string, error) {
  3014  	rows, err := tx.Query(`SELECT user_messages_id FROM bridge_messages WHERE message_id = ?`, messageID)
  3015  	if err != nil {
  3016  		return "", err
  3017  	}
  3018  	defer rows.Close()
  3019  
  3020  	if rows.Next() {
  3021  		var statusMessageID string
  3022  		err = rows.Scan(&statusMessageID)
  3023  		if err != nil {
  3024  			return "", err
  3025  		}
  3026  		return statusMessageID, nil
  3027  	}
  3028  	return "", nil
  3029  }
  3030  
  3031  func (db sqlitePersistence) updateStatusMessagesWithResponse(tx *sql.Tx, statusMessagesToUpdate []string, responseValue string) error {
  3032  	sql := "UPDATE user_messages SET response_to = ? WHERE id IN (?" + strings.Repeat(",?", len(statusMessagesToUpdate)-1) + ")" //nolint: gosec
  3033  	stmt, err := tx.Prepare(sql)
  3034  	if err != nil {
  3035  		return err
  3036  	}
  3037  	defer stmt.Close()
  3038  
  3039  	args := make([]interface{}, 0, len(statusMessagesToUpdate)+1)
  3040  	args = append(args, responseValue)
  3041  	for _, msgToUpdate := range statusMessagesToUpdate {
  3042  		args = append(args, msgToUpdate)
  3043  	}
  3044  	_, err = stmt.Exec(args...)
  3045  	return err
  3046  }
  3047  
  3048  func (db sqlitePersistence) bridgeMessageExists(tx *sql.Tx, bridgeMessageID string) (exists bool, err error) {
  3049  	err = tx.QueryRow(`SELECT EXISTS(SELECT 1 FROM bridge_messages WHERE message_id = ?)`, bridgeMessageID).Scan(&exists)
  3050  	return exists, err
  3051  }
  3052  
  3053  func (db sqlitePersistence) updateBridgeMessageContent(tx *sql.Tx, bridgeMessageID string, content string) error {
  3054  	sql := "UPDATE bridge_messages SET content = ? WHERE message_id = ?"
  3055  	stmt, err := tx.Prepare(sql)
  3056  	if err != nil {
  3057  		return err
  3058  	}
  3059  	defer stmt.Close()
  3060  
  3061  	_, err = stmt.Exec(content, bridgeMessageID)
  3062  	return err
  3063  }
  3064  
  3065  // Finds if there are any messages that are replies to that message (in case replies were received earlier)
  3066  func (db sqlitePersistence) findAndUpdateReplies(tx *sql.Tx, bridgeMessageID string, statusMessageID string) error {
  3067  	replyMessageIds, err := db.findStatusMessageIdsReplies(tx, bridgeMessageID)
  3068  	if err != nil {
  3069  		return err
  3070  	}
  3071  	if len(replyMessageIds) == 0 {
  3072  		return nil
  3073  	}
  3074  	return db.updateStatusMessagesWithResponse(tx, replyMessageIds, statusMessageID)
  3075  }
  3076  
  3077  func (db sqlitePersistence) findAndUpdateRepliedTo(tx *sql.Tx, discordParentMessageID string, statusMessageID string) error {
  3078  	// Finds status messages id which are replies for bridgeMessageID
  3079  	repliedMessageID, err := db.findStatusMessageIDForBridgeMessageID(tx, discordParentMessageID)
  3080  	if err != nil {
  3081  		return err
  3082  	}
  3083  	if repliedMessageID == "" {
  3084  		return nil
  3085  	}
  3086  	return db.updateStatusMessagesWithResponse(tx, []string{statusMessageID}, repliedMessageID)
  3087  }
  3088  
  3089  func (db sqlitePersistence) GetCommunityMemberAllMessages(member string, communityID string) ([]*common.Message, error) {
  3090  	additionalRequestData := "INNER JOIN chats AS ch ON ch.id = m1.chat_id AND ch.community_id = ? WHERE m1.source = ?"
  3091  	query := db.buildMessagesQueryWithAdditionalFields("", additionalRequestData)
  3092  
  3093  	rows, err := db.db.Query(query, communityID, member)
  3094  
  3095  	if err != nil {
  3096  		if err == sql.ErrNoRows {
  3097  			return []*common.Message{}, nil
  3098  		}
  3099  
  3100  		return nil, err
  3101  	}
  3102  
  3103  	return getMessagesFromScanRows(db, rows, false)
  3104  }