* rm "No TEST_DATABASE... override" log message
Let's only log if this value is actually overridden.
* rm "(Created|Dropped) temporary database" message
* only log "Pinging SQL" on subsequent attempts
* disable morph logging from TestMain
* Fix style issues in store test files
- Add missing parameter to migrate() function calls in tests
- Remove unused log function in settings.go
- Fix formatting with go fmt
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
* within sqlstore, use "enable" vs "disable" for clarity
* remove trailing newline from morph logs
---------
Co-authored-by: Claude <noreply@anthropic.com>
* [MM-28779] Fix errcheck issues in server/channels/app/brand.go
Remove brand.go from the errcheck exclusion list in .golangci.yml and fixed the error by properly handling the return value from a.MoveFile().
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
* [MM-28779] Add test to verify brand image backup functionality
Add a new test that verifies backup of the original brand image happens when a new one is uploaded. This helps to ensure the fix for errcheck issues is working as expected.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
* use seperate temporary filestore for each test
* Use FileSettings.Directory instead of finding the dir programatically
* Fix another test
* Fix defer
* Update server/channels/api4/job_test.go
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Fix bad bot commit
* Cleanup logs message
* cleanup file path
* Fix error variable names
* WIP:cleanup panic ussage
* Revert "WIP:cleanup panic ussage"
This reverts commit c3284e4427a41c818acc161926cd2535dee9a6b9.
* cleanup error checks
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
(*Busy).Set would set its own timer, and additionally
send a message across the cluster. In this case, the cluster
is mocked locally. But the timer calculation happens again.
We marshal the expiry time with b.expires.Unix() and send that
as part of model.ServerBusyState. This is parsed again in
ClusterEventChanged and converted to duration with time.Until.
Therefore, if it takes longer for the code to reach those lines,
then the new time calculated would have already expired, failing
the test.
To fix this, we increase the timeout. This slows down the test
at the cost of extra reliability. This is a common failure point
with any timer related tests.
Additionally, we also change the condition in compareBusyState
to check for less-than rather than strict equality.
https://mattermost.atlassian.net/browse/MM-63300
```release-note
NONE
```
Co-authored-by: Mattermost Build <build@mattermost.com>
* MM-63925 Remove most nested transactions from channel_store_categories.go
There's one place which still has a nested transaction in
CreateInitialSidebarCategories, but that's because it's calling out to a
different part of the store. The only way to avoid that would be to
break the extraction like UpdateSidebarCategories does to update
preferences, but I chose not to follow that pattern here and leave it
as-is.
* MM-63923 Prevent deadlocks caused by updating multiple categories in a different order
* MM-63923 Prevent deadlocks while deleting from SidebarChannels
This could also have been resolved by sorting the categories, but
combining the queries seems a bit more elegant.
* MM-63924 Ensure adding SidebarChannels rows is idempotent
* Add additional test to cause deadlocks
* Prevent channels from appearing in a single category multiple times
* Other review feedback
* Bump prepackage Zoom plugin version to 1.9.0
* Bump prepackage GitLab plugin version to 1.10.0
* fixed zoom version
---------
Co-authored-by: Mattermost Build <build@mattermost.com>
* Adds the capability to retrieve a property field by name
Allows to retrieve a property field by name and groupID. As the name
is only unique within the context of a group, and we can have multiple
fields with the same name in the store, for this method the groupID is
directly included in the query instead of being an optional field.
* Adds the targetID parameter to correctly filter fields
* Ensure the method only retrieves non-deleted fields
---------
Co-authored-by: Miguel de la Cruz <miguel@ctrlz.es>
Co-authored-by: Mattermost Build <build@mattermost.com>
A recent change to the enterprise cluster code introduced a change to the enterprise API interface. GetClusterInfos() can now return an error. This commit introduces code to handle that error.
On page load, we load ALL channels and channel members from all teams.
But then, on team_switch, we would again load channels and channel
members from that team. This was redundant and mainly kept
because previously the websocket events were considered unreliable.
Now with reliable websockets, and client-side pings, we can detect
broken connections faster and recover without loss.
Additionally, the getAllChannelMembers call would page through
all responses on the client side. This was inefficient and incur
extra latency. To optimize for this, we introduce server-side
streaming of the full response if page is set to -1.
This optimizes the intial response as well.
https://mattermost.atlassian.net/browse/MM-56906
```release-note
Optimize team switch operation by removing calls to get channels
and channel members.
```
Co-authored-by: Mattermost Build <build@mattermost.com>
We switch to using iterators introduced in Go 1.23
to make iteration alloc-free and fast. And since
element removal is allowed while iterating a map,
this also means we don't need to even copy the slice
any more.
While here, we also address the comment https://github.com/mattermost/mattermost/pull/30178#discussion_r1954862151.
I have simply gone back to using []string as the map
entry rather than a type alias or a redirection with
a struct.
https://mattermost.atlassian.net/browse/MM-63130
```release-note
NONE
```
* Changed back nil to len
```release-note
NONE
```
* fixing unused assignment
```release-note
NONE
```
* add benchmark
```
goos: linux
goarch: amd64
pkg: github.com/mattermost/mattermost/server/v8/channels/app/platform
cpu: Intel(R) Core(TM) i5-8265U CPU @ 1.60GHz
│ old.txt │ new.txt │
│ sec/op │ sec/op vs base │
HubConnIndexIterator/2_users-8 93.53n ± 1% 38.09n ± 1% -59.27% (p=0.000 n=10)
HubConnIndexIterator/3_users-8 106.30n ± 0% 38.41n ± 1% -63.86% (p=0.000 n=10)
HubConnIndexIterator/4_users-8 111.30n ± 1% 38.66n ± 1% -65.27% (p=0.000 n=10)
geomean 103.4n 38.39n -62.89%
│ old.txt │ new.txt │
│ B/op │ B/op vs base │
HubConnIndexIterator/2_users-8 16.00 ± 0% 24.00 ± 0% +50.00% (p=0.000 n=10)
HubConnIndexIterator/3_users-8 24.00 ± 0% 24.00 ± 0% ~ (p=1.000 n=10) ¹
HubConnIndexIterator/4_users-8 32.00 ± 0% 24.00 ± 0% -25.00% (p=0.000 n=10)
geomean 23.08 24.00 +4.00%
¹ all samples are equal
│ old.txt │ new.txt │
│ allocs/op │ allocs/op vs base │
HubConnIndexIterator/2_users-8 1.000 ± 0% 1.000 ± 0% ~ (p=1.000 n=10) ¹
HubConnIndexIterator/3_users-8 1.000 ± 0% 1.000 ± 0% ~ (p=1.000 n=10) ¹
HubConnIndexIterator/4_users-8 1.000 ± 0% 1.000 ± 0% ~ (p=1.000 n=10) ¹
geomean 1.000 1.000 +0.00%
¹ all samples are equal
```
```release-note
NONE
```
* ForChannel test as well
```release-note
NONE
```
* review comments
```release-note
NONE
```
* fix lint errors
```release-note
NONE
```
---------
Co-authored-by: Mattermost Build <build@mattermost.com>
For our MySQL customers, we have discovered that the query is not
able to choose the right plan by itself without adequate hints.
This is only for MySQL as we have confirmed from multiple customers
that Postgres takes the right index idx_sidebarcategories_userid_teamid
for the sidebarCategories table. And if it doesn't, then a VACUUM ANALYZE
fixes it.
But for MySQL, we have to do two things:
- Pass an index hint to let it use idx_sidebarcategories_userid_teamid.
- Pass an optimizer hint to materialize the sub-query. This is used
to materialize the doesNotHaveSidebarChannel sub-query into a temporary
table, letting MySQL reuse the contents of the table for further processing
in the parent sections of the query.
I have confirmed both locally and in the customer environment
that it gives a clear benefit.
*LOCAL*
OLD:
```
| -> Nested loop antijoin (cost=2889.85 rows=19767) (actual time=3.355..38.033 rows=15 loops=1)
-> Nested loop inner join (cost=66.65 rows=110) (actual time=0.410..1.689 rows=220 loops=1)
-> Filter: ((Channels.DeleteAt = 0) and (Channels.`Type` in ('O','P'))) (cost=25.25 rows=110) (actual time=0.394..0.886 rows=220 loops=1)
-> Index lookup on Channels using idx_channels_team_id_display_name (TeamId='team01'), with index condition: (Channels.Id is not null) (cost=25.25 rows=220) (actual time=0.389..0.793 rows=220 loops=1)
-> Single-row covering index lookup on ChannelMembers using PRIMARY (ChannelId=Channels.Id, UserId='user000') (cost=0.28 rows=1) (actual time=0.003..0.003 rows=1 loops=220)
-> Nested loop inner join (cost=4967.50 rows=180) (actual time=0.165..0.165 rows=1 loops=220)
-> Covering index lookup on SidebarChannels using PRIMARY (ChannelId=Channels.Id) (cost=7.86 rows=180) (actual time=0.055..0.062 rows=13 loops=220)
-> Filter: ((SidebarCategories.TeamId = 'team01') and (SidebarCategories.UserId = 'user000')) (cost=44.93 rows=1) (actual time=0.008..0.008 rows=0 loops=2881)
-> Single-row index lookup on SidebarCategories using PRIMARY (Id=SidebarChannels.CategoryId) (cost=44.93 rows=1) (actual time=0.006..0.006 rows=1 loops=2881)
|
```
NEW:
```
| -> Nested loop antijoin (cost=5879.73 rows=58021) (actual time=1.544..3.135 rows=15 loops=1)
-> Nested loop inner join (cost=66.65 rows=110) (actual time=0.421..1.778 rows=220 loops=1)
-> Filter: ((Channels.DeleteAt = 0) and (Channels.`Type` in ('O','P'))) (cost=25.25 rows=110) (actual time=0.405..0.945 rows=220 loops=1)
-> Index lookup on Channels using idx_channels_team_id_display_name (TeamId='team01'), with index condition: (Channels.Id is not null) (cost=25.25 rows=220) (actual time=0.400..0.859 rows=220 loops=1)
-> Single-row covering index lookup on ChannelMembers using PRIMARY (ChannelId=Channels.Id, UserId='user000') (cost=0.28 rows=1) (actual time=0.003..0.004 rows=1 loops=220)
-> Single-row index lookup on <subquery2> using <auto_distinct_key> (ChannelId=Channels.Id) (cost=130.37..130.37 rows=1) (actual time=0.006..0.006 rows=1 loops=220)
-> Materialize with deduplication (cost=130.35..130.35 rows=527) (actual time=1.118..1.118 rows=205 loops=1)
-> Filter: (SidebarChannels.ChannelId is not null) (cost=77.61 rows=527) (actual time=0.059..0.851 rows=523 loops=1)
-> Nested loop inner join (cost=77.61 rows=527) (actual time=0.058..0.786 rows=523 loops=1)
-> Covering index lookup on SidebarCategories using idx_sidebarcategories_userid_teamid (UserId='user000', TeamId='team01') (cost=2.81 rows=15) (actual time=0.025..0.031 rows=15 loops=1)
-> Covering index lookup on SidebarChannels using idx_sidebarchannels_categoryid (CategoryId=SidebarCategories.Id) (cost=1.70 rows=35) (actual time=0.032..0.046 rows=35 loops=15)
```
Performance improvement from 38ms to 3ms.
*CUSTOMER ENV* (with sensitive data wiped off)
OLD:
```
| -> Sort: channels.DisplayName (actual time=512..512 rows=5 loops=1)
-> Stream results (cost=3.28 rows=1.44) (actual time=223..512 rows=5 loops=1)
-> Nested loop antijoin (cost=3.28 rows=1.44) (actual time=223..512 rows=5 loops=1)
-> Nested loop inner join (cost=3.02 rows=0.3) (actual time=0.025..0.0878 rows=5 loops=1)
-> Covering index lookup on ChannelMembers using idx_channelmembers_user_id_channel_id_last_viewed_at (UserId='') (cost=0.916 rows=6) (actual time=0.0146..0.023 rows=6 loops=1)
-> Filter: ((channels.DeleteAt = 0) and (channels.TeamId = '') and (channels.`Type` in ('O','P'))) (cost=0.251 rows=0.05) (actual time=0.00999..0.0102 rows=0.833 loops=6)
-> Single-row index lookup on Channels using PRIMARY (Id=channelmembers.ChannelId) (cost=0.251 rows=1) (actual time=0.00778..0.00785 rows=1 loops=6)
-> Nested loop inner join (cost=2.85 rows=4.81) (actual time=102..102 rows=0 loops=5)
-> Covering index lookup on SidebarChannels using PRIMARY (ChannelId=channelmembers.ChannelId) (cost=2.01 rows=4.81) (actual time=0.0125..13.8 rows=24134 loops=5)
-> Filter: ((sidebarcategories.TeamId = '') and (sidebarcategories.UserId = '')) (cost=1.54 rows=1) (actual time=0.00359..0.00359 rows=0 loops=120671)
-> Single-row index lookup on SidebarCategories using PRIMARY (Id=sidebarchannels.CategoryId) (cost=1.54 rows=1) (actual time=0.00316..0.00319 rows=1 loops=120671)
```
NEW:
```
Here is the output
| -> Sort: channels.DisplayName (actual time=0.12..0.12 rows=5 loops=1)
-> Stream results (cost=3.45 rows=4.01) (actual time=0.0797..0.11 rows=5 loops=1)
-> Nested loop antijoin (cost=3.45 rows=4.01) (actual time=0.0769..0.106 rows=5 loops=1)
-> Nested loop inner join (cost=3.02 rows=0.3) (actual time=0.0291..0.0555 rows=5 loops=1)
-> Covering index lookup on ChannelMembers using idx_channelmembers_user_id_channel_id_last_viewed_at (UserId='') (cost=0.916 rows=6) (actual time=0.0145..0.0162 rows=6 loops=1)
-> Filter: ((channels.DeleteAt = 0) and (channels.TeamId = '') and (channels.`Type` in ('O','P'))) (cost=0.251 rows=0.05) (actual time=0.00611..0.00619 rows=0.833 loops=6)
-> Single-row index lookup on Channels using PRIMARY (Id=channelmembers.ChannelId) (cost=0.251 rows=1) (actual time=0.0053..0.00534 rows=1 loops=6)
-> Single-row index lookup on <subquery2> using <auto_distinct_key> (ChannelId=channelmembers.ChannelId) (cost=7.01..7.01 rows=1) (actual time=0.00956..0.00956 rows=0 loops=5)
-> Materialize with deduplication (cost=7..7 rows=13.4) (actual time=0.0451..0.0451 rows=0 loops=1)
-> Filter: (sidebarchannels.ChannelId is not null) (cost=5.66 rows=13.4) (actual time=0.0441..0.0441 rows=0 loops=1)
-> Nested loop inner join (cost=5.66 rows=13.4) (actual time=0.0439..0.0439 rows=0 loops=1)
-> Covering index lookup on SidebarCategories using idx_sidebarcategories_userid_teamid (UserId='', TeamId='') (cost=0.592 rows=3) (actual time=0.0105..0.0134 rows=3 loops=1)
-> Covering index lookup on SidebarChannels using idx_sidebarchannels_categoryid (CategoryId=sidebarcategories.Id) (cost=1.39 rows=4.46) (actual time=0.00999..0.00999 rows=0 loops=3)
```
Performance improvement from 512ms to 0.12ms.
https://mattermost.atlassian.net/browse/MM-64209
```release-note
NONE
```