[performance] filter model and database table improvements (#4277)

- removes unnecessary fields / columns (created_at, updated_at)
- replaces filter.context_* columns with singular filter.contexts bit field which should save both struct memory and database space
- replaces filter.action string with integer enum type which should save both struct memory and database space
- adds links from filter to filter_* tables with Filter{}.KeywordIDs and Filter{}.StatusIDs fields (this also means we now have those ID slices cached, which reduces some lookups)
- removes account_id fields from filter_* tables, since there's a more direct connection between filter and filter_* tables, and filter.account_id already exists
- refactors a bunch of the filter processor logic to save on code repetition, factor in the above changes, fix a few bugs with missed error returns and bring it more in-line with some of our newer code

Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4277
Co-authored-by: kim <grufwub@gmail.com>
Co-committed-by: kim <grufwub@gmail.com>
This commit is contained in:
kim 2025-06-24 17:24:34 +02:00 committed by tobi
commit 996da6e029
82 changed files with 2440 additions and 1722 deletions

View file

@ -104,7 +104,7 @@ func (suite *FiltersTestSuite) TestDeleteFilter() {
func (suite *FiltersTestSuite) TestDeleteAnotherAccountsFilter() {
id := suite.testFilterKeywords["local_account_2_filter_1_keyword_1"].ID
err := suite.deleteFilter(id, http.StatusNotFound, `{"error":"Not Found"}`)
err := suite.deleteFilter(id, http.StatusNotFound, `{"error":"Not Found: filter not found"}`)
if err != nil {
suite.FailNow(err.Error())
}
@ -113,7 +113,7 @@ func (suite *FiltersTestSuite) TestDeleteAnotherAccountsFilter() {
func (suite *FiltersTestSuite) TestDeleteNonexistentFilter() {
id := "not_even_a_real_ULID"
err := suite.deleteFilter(id, http.StatusNotFound, `{"error":"Not Found"}`)
err := suite.deleteFilter(id, http.StatusNotFound, `{"error":"Not Found: filter keyword not found"}`)
if err != nil {
suite.FailNow(err.Error())
}

View file

@ -108,7 +108,7 @@ func (suite *FiltersTestSuite) TestGetFilter() {
func (suite *FiltersTestSuite) TestGetAnotherAccountsFilter() {
id := suite.testFilterKeywords["local_account_2_filter_1_keyword_1"].ID
_, err := suite.getFilter(id, http.StatusNotFound, `{"error":"Not Found"}`)
_, err := suite.getFilter(id, http.StatusNotFound, `{"error":"Not Found: filter not found"}`)
if err != nil {
suite.FailNow(err.Error())
}
@ -117,7 +117,7 @@ func (suite *FiltersTestSuite) TestGetAnotherAccountsFilter() {
func (suite *FiltersTestSuite) TestGetNonexistentFilter() {
id := "not_even_a_real_ULID"
_, err := suite.getFilter(id, http.StatusNotFound, `{"error":"Not Found"}`)
_, err := suite.getFilter(id, http.StatusNotFound, `{"error":"Not Found: filter keyword not found"}`)
if err != nil {
suite.FailNow(err.Error())
}

View file

@ -261,7 +261,7 @@ func (suite *FiltersTestSuite) TestPutAnotherAccountsFilter() {
id := suite.testFilterKeywords["local_account_2_filter_1_keyword_1"].ID
phrase := "GNU/Linux"
context := []string{"home"}
_, err := suite.putFilter(id, &phrase, &context, nil, nil, nil, nil, nil, http.StatusNotFound, `{"error":"Not Found"}`)
_, err := suite.putFilter(id, &phrase, &context, nil, nil, nil, nil, nil, http.StatusNotFound, `{"error":"Not Found: filter not found"}`)
if err != nil {
suite.FailNow(err.Error())
}
@ -271,7 +271,7 @@ func (suite *FiltersTestSuite) TestPutNonexistentFilter() {
id := "not_even_a_real_ULID"
phrase := "GNU/Linux"
context := []string{"home"}
_, err := suite.putFilter(id, &phrase, &context, nil, nil, nil, nil, nil, http.StatusNotFound, `{"error":"Not Found"}`)
_, err := suite.putFilter(id, &phrase, &context, nil, nil, nil, nil, nil, http.StatusNotFound, `{"error":"Not Found: filter keyword not found"}`)
if err != nil {
suite.FailNow(err.Error())
}

View file

@ -87,10 +87,17 @@ func (suite *FiltersTestSuite) getFilters(
func (suite *FiltersTestSuite) TestGetFilters() {
// v1 filters map to individual filter keywords.
wrappingFilterIDs := make(map[string]struct{}, len(suite.testFilters))
expectedFilterIDs := make([]string, 0, len(suite.testFilterKeywords))
expectedFilterKeywords := make([]string, 0, len(suite.testFilterKeywords))
testAccountID := suite.testAccounts["local_account_1"].ID
for _, filter := range suite.testFilters {
if filter.AccountID == testAccountID {
wrappingFilterIDs[filter.ID] = struct{}{}
}
}
for _, filterKeyword := range suite.testFilterKeywords {
if filterKeyword.AccountID == suite.testAccounts["local_account_1"].ID {
if _, ok := wrappingFilterIDs[filterKeyword.FilterID]; ok {
expectedFilterIDs = append(expectedFilterIDs, filterKeyword.ID)
expectedFilterKeywords = append(expectedFilterKeywords, filterKeyword.Keyword)
}

View file

@ -104,7 +104,7 @@ func (suite *FiltersTestSuite) TestDeleteFilter() {
func (suite *FiltersTestSuite) TestDeleteAnotherAccountsFilter() {
id := suite.testFilters["local_account_2_filter_1"].ID
err := suite.deleteFilter(id, http.StatusNotFound, `{"error":"Not Found"}`)
err := suite.deleteFilter(id, http.StatusNotFound, `{"error":"Not Found: filter not found"}`)
if err != nil {
suite.FailNow(err.Error())
}
@ -113,7 +113,7 @@ func (suite *FiltersTestSuite) TestDeleteAnotherAccountsFilter() {
func (suite *FiltersTestSuite) TestDeleteNonexistentFilter() {
id := "not_even_a_real_ULID"
err := suite.deleteFilter(id, http.StatusNotFound, `{"error":"Not Found"}`)
err := suite.deleteFilter(id, http.StatusNotFound, `{"error":"Not Found: filter not found"}`)
if err != nil {
suite.FailNow(err.Error())
}

View file

@ -106,7 +106,7 @@ func (suite *FiltersTestSuite) TestGetFilter() {
func (suite *FiltersTestSuite) TestGetAnotherAccountsFilter() {
id := suite.testFilters["local_account_2_filter_1"].ID
_, err := suite.getFilter(id, http.StatusNotFound, `{"error":"Not Found"}`)
_, err := suite.getFilter(id, http.StatusNotFound, `{"error":"Not Found: filter not found"}`)
if err != nil {
suite.FailNow(err.Error())
}
@ -115,7 +115,7 @@ func (suite *FiltersTestSuite) TestGetAnotherAccountsFilter() {
func (suite *FiltersTestSuite) TestGetNonexistentFilter() {
id := "not_even_a_real_ULID"
_, err := suite.getFilter(id, http.StatusNotFound, `{"error":"Not Found"}`)
_, err := suite.getFilter(id, http.StatusNotFound, `{"error":"Not Found: filter not found"}`)
if err != nil {
suite.FailNow(err.Error())
}

View file

@ -104,7 +104,7 @@ func (suite *FiltersTestSuite) TestDeleteFilterKeyword() {
func (suite *FiltersTestSuite) TestDeleteAnotherAccountsFilterKeyword() {
id := suite.testFilterKeywords["local_account_2_filter_1_keyword_1"].ID
err := suite.deleteFilterKeyword(id, http.StatusNotFound, `{"error":"Not Found"}`)
err := suite.deleteFilterKeyword(id, http.StatusNotFound, `{"error":"Not Found: filter not found"}`)
if err != nil {
suite.FailNow(err.Error())
}
@ -113,7 +113,7 @@ func (suite *FiltersTestSuite) TestDeleteAnotherAccountsFilterKeyword() {
func (suite *FiltersTestSuite) TestDeleteNonexistentFilterKeyword() {
id := "not_even_a_real_ULID"
err := suite.deleteFilterKeyword(id, http.StatusNotFound, `{"error":"Not Found"}`)
err := suite.deleteFilterKeyword(id, http.StatusNotFound, `{"error":"Not Found: filter keyword not found"}`)
if err != nil {
suite.FailNow(err.Error())
}

View file

@ -106,7 +106,7 @@ func (suite *FiltersTestSuite) TestGetFilterKeyword() {
func (suite *FiltersTestSuite) TestGetAnotherAccountsFilterKeyword() {
id := suite.testFilterKeywords["local_account_2_filter_1_keyword_1"].ID
_, err := suite.getFilterKeyword(id, http.StatusNotFound, `{"error":"Not Found"}`)
_, err := suite.getFilterKeyword(id, http.StatusNotFound, `{"error":"Not Found: filter not found"}`)
if err != nil {
suite.FailNow(err.Error())
}
@ -115,7 +115,7 @@ func (suite *FiltersTestSuite) TestGetAnotherAccountsFilterKeyword() {
func (suite *FiltersTestSuite) TestGetNonexistentFilterKeyword() {
id := "not_even_a_real_ULID"
_, err := suite.getFilterKeyword(id, http.StatusNotFound, `{"error":"Not Found"}`)
_, err := suite.getFilterKeyword(id, http.StatusNotFound, `{"error":"Not Found: filter keyword not found"}`)
if err != nil {
suite.FailNow(err.Error())
}

View file

@ -189,7 +189,7 @@ func (suite *FiltersTestSuite) TestPostFilterKeywordKeywordConflict() {
func (suite *FiltersTestSuite) TestPostFilterKeywordAnotherAccountsFilter() {
filterID := suite.testFilters["local_account_2_filter_1"].ID
keyword := "fnords"
_, err := suite.postFilterKeyword(filterID, &keyword, nil, nil, http.StatusNotFound, `{"error":"Not Found"}`)
_, err := suite.postFilterKeyword(filterID, &keyword, nil, nil, http.StatusNotFound, `{"error":"Not Found: filter not found"}`)
if err != nil {
suite.FailNow(err.Error())
}
@ -198,7 +198,7 @@ func (suite *FiltersTestSuite) TestPostFilterKeywordAnotherAccountsFilter() {
func (suite *FiltersTestSuite) TestPostFilterKeywordNonexistentFilter() {
filterID := "not_even_a_real_ULID"
keyword := "fnords"
_, err := suite.postFilterKeyword(filterID, &keyword, nil, nil, http.StatusNotFound, `{"error":"Not Found"}`)
_, err := suite.postFilterKeyword(filterID, &keyword, nil, nil, http.StatusNotFound, `{"error":"Not Found: filter not found"}`)
if err != nil {
suite.FailNow(err.Error())
}

View file

@ -189,7 +189,7 @@ func (suite *FiltersTestSuite) TestPutFilterKeywordKeywordConflict() {
func (suite *FiltersTestSuite) TestPutFilterKeywordAnotherAccountsFilterKeyword() {
filterKeywordID := suite.testFilterKeywords["local_account_2_filter_1_keyword_1"].ID
keyword := "fnord"
_, err := suite.putFilterKeyword(filterKeywordID, &keyword, nil, nil, http.StatusNotFound, `{"error":"Not Found"}`)
_, err := suite.putFilterKeyword(filterKeywordID, &keyword, nil, nil, http.StatusNotFound, `{"error":"Not Found: filter not found"}`)
if err != nil {
suite.FailNow(err.Error())
}
@ -198,7 +198,7 @@ func (suite *FiltersTestSuite) TestPutFilterKeywordAnotherAccountsFilterKeyword(
func (suite *FiltersTestSuite) TestPutFilterKeywordNonexistentFilterKeyword() {
filterKeywordID := "not_even_a_real_ULID"
keyword := "fnord"
_, err := suite.putFilterKeyword(filterKeywordID, &keyword, nil, nil, http.StatusNotFound, `{"error":"Not Found"}`)
_, err := suite.putFilterKeyword(filterKeywordID, &keyword, nil, nil, http.StatusNotFound, `{"error":"Not Found: filter keyword not found"}`)
if err != nil {
suite.FailNow(err.Error())
}

View file

@ -116,7 +116,7 @@ func (suite *FiltersTestSuite) putFilter(filterID string, title *string, context
return nil, err
}
errs := gtserror.NewMultiError(2)
var errs gtserror.MultiError
// check code + body
if resultCode := recorder.Code; expectedHTTPStatus != resultCode {
@ -324,7 +324,7 @@ func (suite *FiltersTestSuite) TestPutFilterEmptyContext() {
func (suite *FiltersTestSuite) TestPutFilterTitleConflict() {
id := suite.testFilters["local_account_1_filter_1"].ID
title := suite.testFilters["local_account_1_filter_2"].Title
_, err := suite.putFilter(id, &title, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, http.StatusConflict, `{"error":"Conflict: you already have a filter with this title"}`, nil)
_, err := suite.putFilter(id, &title, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, http.StatusConflict, `{"error":"Conflict: duplicate title"}`, nil)
if err != nil {
suite.FailNow(err.Error())
}
@ -334,7 +334,7 @@ func (suite *FiltersTestSuite) TestPutAnotherAccountsFilter() {
id := suite.testFilters["local_account_2_filter_1"].ID
title := "GNU/Linux"
context := []string{"home"}
_, err := suite.putFilter(id, &title, &context, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, http.StatusNotFound, `{"error":"Not Found"}`, nil)
_, err := suite.putFilter(id, &title, &context, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, http.StatusNotFound, `{"error":"Not Found: filter not found"}`, nil)
if err != nil {
suite.FailNow(err.Error())
}
@ -344,7 +344,7 @@ func (suite *FiltersTestSuite) TestPutNonexistentFilter() {
id := "not_even_a_real_ULID"
phrase := "GNU/Linux"
context := []string{"home"}
_, err := suite.putFilter(id, &phrase, &context, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, http.StatusNotFound, `{"error":"Not Found"}`, nil)
_, err := suite.putFilter(id, &phrase, &context, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, http.StatusNotFound, `{"error":"Not Found: filter not found"}`, nil)
if err != nil {
suite.FailNow(err.Error())
}

View file

@ -86,10 +86,8 @@ func (suite *FiltersTestSuite) getFilters(
}
func (suite *FiltersTestSuite) TestGetFilters() {
// Set of filter IDs for the test user.
expectedFilterIDs := []string{}
// Map of filter IDs to filter keyword and status IDs.
expectedFilters := map[string]struct {
expectedFilters := map[string]*struct {
keywordIDs []string
statusIDs []string
}{}
@ -98,28 +96,26 @@ func (suite *FiltersTestSuite) TestGetFilters() {
accountID := suite.testAccounts["local_account_1"].ID
for _, filter := range suite.testFilters {
if filter.AccountID == accountID {
expectedFilterIDs = append(expectedFilterIDs, filter.ID)
expectedFilters[filter.ID] = struct {
expectedFilters[filter.ID] = &struct {
keywordIDs []string
statusIDs []string
}{}
}
}
for _, filterKeyword := range suite.testFilterKeywords {
if filterKeyword.AccountID == accountID {
expectedIDsForFilter := expectedFilters[filterKeyword.FilterID]
expectedIDsForFilter.keywordIDs = append(expectedIDsForFilter.keywordIDs, filterKeyword.ID)
expectedFilters[filterKeyword.FilterID] = expectedIDsForFilter
expected, ok := expectedFilters[filterKeyword.FilterID]
if !ok {
continue
}
expected.keywordIDs = append(expected.keywordIDs, filterKeyword.ID)
}
for _, filterStatus := range suite.testFilterStatuses {
if filterStatus.AccountID == accountID {
expectedIDsForFilter := expectedFilters[filterStatus.FilterID]
expectedIDsForFilter.statusIDs = append(expectedIDsForFilter.statusIDs, filterStatus.ID)
expectedFilters[filterStatus.FilterID] = expectedIDsForFilter
expected, ok := expectedFilters[filterStatus.FilterID]
if !ok {
continue
}
expected.statusIDs = append(expected.statusIDs, filterStatus.ID)
}
suite.NotEmpty(expectedFilterIDs)
suite.NotEmpty(expectedFilters)
// Fetch all filters for the logged-in account.
@ -132,23 +128,19 @@ func (suite *FiltersTestSuite) TestGetFilters() {
// Check that we got the right ones.
suite.Len(filters, len(expectedFilters))
actualFilterIDs := []string{}
for _, filter := range filters {
actualFilterIDs = append(actualFilterIDs, filter.ID)
expectedIDsForFilter := expectedFilters[filter.ID]
actualFilterKeywordIDs := []string{}
var actualFilterKeywordIDs []string
for _, filterKeyword := range filter.Keywords {
actualFilterKeywordIDs = append(actualFilterKeywordIDs, filterKeyword.ID)
}
suite.ElementsMatch(actualFilterKeywordIDs, expectedIDsForFilter.keywordIDs)
actualFilterStatusIDs := []string{}
var actualFilterStatusIDs []string
for _, filterStatus := range filter.Statuses {
actualFilterStatusIDs = append(actualFilterStatusIDs, filterStatus.ID)
}
suite.ElementsMatch(actualFilterStatusIDs, expectedIDsForFilter.statusIDs)
}
suite.ElementsMatch(expectedFilterIDs, actualFilterIDs)
}

View file

@ -101,7 +101,7 @@ func (suite *FiltersTestSuite) TestDeleteFilterStatus() {
func (suite *FiltersTestSuite) TestDeleteAnotherAccountsFilterStatus() {
id := suite.testFilterStatuses["local_account_2_filter_1_status_1"].ID
err := suite.deleteFilterStatus(id, http.StatusNotFound, `{"error":"Not Found"}`)
err := suite.deleteFilterStatus(id, http.StatusNotFound, `{"error":"Not Found: filter not found"}`)
if err != nil {
suite.FailNow(err.Error())
}
@ -110,7 +110,7 @@ func (suite *FiltersTestSuite) TestDeleteAnotherAccountsFilterStatus() {
func (suite *FiltersTestSuite) TestDeleteNonexistentFilterStatus() {
id := "not_even_a_real_ULID"
err := suite.deleteFilterStatus(id, http.StatusNotFound, `{"error":"Not Found"}`)
err := suite.deleteFilterStatus(id, http.StatusNotFound, `{"error":"Not Found: filter status not found"}`)
if err != nil {
suite.FailNow(err.Error())
}

View file

@ -104,7 +104,7 @@ func (suite *FiltersTestSuite) TestGetFilterStatus() {
func (suite *FiltersTestSuite) TestGetAnotherAccountsFilterStatus() {
id := suite.testFilterStatuses["local_account_2_filter_1_status_1"].ID
_, err := suite.getFilterStatus(id, http.StatusNotFound, `{"error":"Not Found"}`)
_, err := suite.getFilterStatus(id, http.StatusNotFound, `{"error":"Not Found: filter not found"}`)
if err != nil {
suite.FailNow(err.Error())
}
@ -113,7 +113,7 @@ func (suite *FiltersTestSuite) TestGetAnotherAccountsFilterStatus() {
func (suite *FiltersTestSuite) TestGetNonexistentFilterStatus() {
id := "not_even_a_real_ULID"
_, err := suite.getFilterStatus(id, http.StatusNotFound, `{"error":"Not Found"}`)
_, err := suite.getFilterStatus(id, http.StatusNotFound, `{"error":"Not Found: filter status not found"}`)
if err != nil {
suite.FailNow(err.Error())
}

View file

@ -173,7 +173,7 @@ func (suite *FiltersTestSuite) TestPostFilterStatusStatusIDConflict() {
func (suite *FiltersTestSuite) TestPostFilterStatusAnotherAccountsFilter() {
filterID := suite.testFilters["local_account_2_filter_1"].ID
statusID := suite.testStatuses["admin_account_status_1"].ID
_, err := suite.postFilterStatus(filterID, &statusID, nil, http.StatusNotFound, `{"error":"Not Found"}`)
_, err := suite.postFilterStatus(filterID, &statusID, nil, http.StatusNotFound, `{"error":"Not Found: filter not found"}`)
if err != nil {
suite.FailNow(err.Error())
}
@ -182,7 +182,7 @@ func (suite *FiltersTestSuite) TestPostFilterStatusAnotherAccountsFilter() {
func (suite *FiltersTestSuite) TestPostFilterStatusNonexistentFilter() {
filterID := "not_even_a_real_ULID"
statusID := suite.testStatuses["admin_account_status_1"].ID
_, err := suite.postFilterStatus(filterID, &statusID, nil, http.StatusNotFound, `{"error":"Not Found"}`)
_, err := suite.postFilterStatus(filterID, &statusID, nil, http.StatusNotFound, `{"error":"Not Found: filter not found"}`)
if err != nil {
suite.FailNow(err.Error())
}