snapshot of https://github.com/mattermost/mattermost.git (master, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Server CI / Postgres (shard 0) (push) Has been skipped
Server CI / Postgres (shard 3) (push) Has been skipped
BuildEnv Docker Image / build-image (push) Failing after 4s
BuildEnv Docker Image / build-image-fips (push) Failing after 4s
Migration Review & Release Notes / Generate migration review & release notes (push) Failing after 3s
Server CI / Build Environment (push) Failing after 3s
Server CI / Postgres (shard 1) (push) Has been skipped
Tools CI / check-style (mattermost-govet) (push) Failing after 4s
Server CI / Postgres (shard 2) (push) Has been skipped
Tools CI / check-style (mmgotool) (push) Failing after 3s
Server CI / Elasticsearch v8 Compatibility (push) Has been skipped
Server CI / OpenSearch v2 Compatibility (push) Has been skipped
Server CI / Postgres FIPS (shard 0) (push) Has been skipped
Tools CI / Test (mattermost-govet) (push) Failing after 3s
Server CI / Postgres FIPS (shard 1) (push) Has been skipped
Server CI / Postgres FIPS (shard 2) (push) Has been skipped
Tools CI / Test (mmgotool) (push) Failing after 3s
Server CI / Postgres FIPS (shard 3) (push) Has been skipped
Server CI / Run mmctl tests (push) Has been skipped
Server CI / Run mmctl tests (FIPS) (push) Has been skipped
Server CI / Merge Postgres Test Results (push) Has been skipped
Server CI / Merge Postgres FIPS Test Results (push) Has been skipped
Server CI / Check backport migrations (push) Has been skipped
Server CI / Check generated files (push) Has been skipped
Server CI / check-style (push) Has been skipped
Server CI / Vet API (push) Has been skipped
Server CI / Build mattermost server app (push) Has been skipped
Server CI / Server CI Complete (push) Failing after 2s
Server CI / Server CI Artifacts (push) Has been skipped
Server CI / Sentry Upload (push) Has been skipped
Server CI / Server CI Report (push) Failing after 5m1s
Server CI Nightly Race / Race Detector (push) Has been skipped
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Webapp CI Cache Warm / Warm Webapp node_modules Cache (push) Has been cancelled
E2E CI Cache Warm / Warm E2E npm Registry Cache (push) Has been cancelled
API / build (push) Has been cancelled
Web App CI / check-types (push) Has been cancelled
Web App CI / check-lint (push) Has been cancelled
Web App CI / check-i18n (push) Has been cancelled
Web App CI / check-external-links (push) Has been cancelled
Web App CI / test (platform) (push) Has been cancelled
Web App CI / test (mattermost-redux) (push) Has been cancelled
Web App CI / test (channels shard 1/4) (push) Has been cancelled
Web App CI / test (channels shard 2/4) (push) Has been cancelled
Web App CI / test (channels shard 3/4) (push) Has been cancelled
Web App CI / test (channels shard 4/4) (push) Has been cancelled
Web App CI / upload-coverage (push) Has been cancelled
Web App CI / build (push) Has been cancelled
YAML Lint / yamllint (push) Has been cancelled
Push mirrored docker images / cd/Push mirrored docker images (push) Has been cancelled
docs-cd / Build and deploy docs (push) Has been cancelled
Docs CI / build (push) Has been cancelled
E2E Cache Warm: Cypress deps / Warm Cypress deps cache (push) Has been cancelled
Scorecards supply-chain security / Scorecard analysis (push) Has been cancelled
Server CI Weekly / Postgres FIPS (push) Has been skipped
Server CI Weekly / Run mmctl tests (FIPS) (push) Has been skipped
Server CI Weekly / Postgres with binary parameters (push) Has been skipped
Server CI Weekly / Build Environment (push) Failing after 1s
Server CI Cache Warm / Warm Go Module and Build Cache (push) Failing after 2s
Server CI Nightly Race / Build Environment (push) Failing after 1s
Some checks failed
Server CI / Postgres (shard 0) (push) Has been skipped
Server CI / Postgres (shard 3) (push) Has been skipped
BuildEnv Docker Image / build-image (push) Failing after 4s
BuildEnv Docker Image / build-image-fips (push) Failing after 4s
Migration Review & Release Notes / Generate migration review & release notes (push) Failing after 3s
Server CI / Build Environment (push) Failing after 3s
Server CI / Postgres (shard 1) (push) Has been skipped
Tools CI / check-style (mattermost-govet) (push) Failing after 4s
Server CI / Postgres (shard 2) (push) Has been skipped
Tools CI / check-style (mmgotool) (push) Failing after 3s
Server CI / Elasticsearch v8 Compatibility (push) Has been skipped
Server CI / OpenSearch v2 Compatibility (push) Has been skipped
Server CI / Postgres FIPS (shard 0) (push) Has been skipped
Tools CI / Test (mattermost-govet) (push) Failing after 3s
Server CI / Postgres FIPS (shard 1) (push) Has been skipped
Server CI / Postgres FIPS (shard 2) (push) Has been skipped
Tools CI / Test (mmgotool) (push) Failing after 3s
Server CI / Postgres FIPS (shard 3) (push) Has been skipped
Server CI / Run mmctl tests (push) Has been skipped
Server CI / Run mmctl tests (FIPS) (push) Has been skipped
Server CI / Merge Postgres Test Results (push) Has been skipped
Server CI / Merge Postgres FIPS Test Results (push) Has been skipped
Server CI / Check backport migrations (push) Has been skipped
Server CI / Check generated files (push) Has been skipped
Server CI / check-style (push) Has been skipped
Server CI / Vet API (push) Has been skipped
Server CI / Build mattermost server app (push) Has been skipped
Server CI / Server CI Complete (push) Failing after 2s
Server CI / Server CI Artifacts (push) Has been skipped
Server CI / Sentry Upload (push) Has been skipped
Server CI / Server CI Report (push) Failing after 5m1s
Server CI Nightly Race / Race Detector (push) Has been skipped
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Webapp CI Cache Warm / Warm Webapp node_modules Cache (push) Has been cancelled
E2E CI Cache Warm / Warm E2E npm Registry Cache (push) Has been cancelled
API / build (push) Has been cancelled
Web App CI / check-types (push) Has been cancelled
Web App CI / check-lint (push) Has been cancelled
Web App CI / check-i18n (push) Has been cancelled
Web App CI / check-external-links (push) Has been cancelled
Web App CI / test (platform) (push) Has been cancelled
Web App CI / test (mattermost-redux) (push) Has been cancelled
Web App CI / test (channels shard 1/4) (push) Has been cancelled
Web App CI / test (channels shard 2/4) (push) Has been cancelled
Web App CI / test (channels shard 3/4) (push) Has been cancelled
Web App CI / test (channels shard 4/4) (push) Has been cancelled
Web App CI / upload-coverage (push) Has been cancelled
Web App CI / build (push) Has been cancelled
YAML Lint / yamllint (push) Has been cancelled
Push mirrored docker images / cd/Push mirrored docker images (push) Has been cancelled
docs-cd / Build and deploy docs (push) Has been cancelled
Docs CI / build (push) Has been cancelled
E2E Cache Warm: Cypress deps / Warm Cypress deps cache (push) Has been cancelled
Scorecards supply-chain security / Scorecard analysis (push) Has been cancelled
Server CI Weekly / Postgres FIPS (push) Has been skipped
Server CI Weekly / Run mmctl tests (FIPS) (push) Has been skipped
Server CI Weekly / Postgres with binary parameters (push) Has been skipped
Server CI Weekly / Build Environment (push) Failing after 1s
Server CI Cache Warm / Warm Go Module and Build Cache (push) Failing after 2s
Server CI Nightly Race / Build Environment (push) Failing after 1s
This commit is contained in:
275
server/channels/store/sqlstore/sqlx_wrapper_test.go
Normal file
275
server/channels/store/sqlstore/sqlx_wrapper_test.go
Normal file
@@ -0,0 +1,275 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql/driver"
|
||||
"errors"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/lib/pq"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
"github.com/mattermost/mattermost/server/public/shared/mlog"
|
||||
)
|
||||
|
||||
// requireQueryTimeout asserts that err represents a query-timeout cancellation.
|
||||
// Three error shapes are possible depending on timing in the pq driver:
|
||||
// - context.DeadlineExceeded — context fires before the query reaches the server.
|
||||
// - pq.Error{Code:"57014"} — PostgreSQL cancels the in-flight query and returns
|
||||
// "canceling statement due to user request" on the original connection.
|
||||
// - driver.ErrBadConn — pq's watchCancel goroutine sets cn.err on the client side
|
||||
// before the query loop reads the server's 57014 response; pq short-circuits and
|
||||
// returns this sentinel rather than the server error.
|
||||
func requireQueryTimeout(t *testing.T, err error) {
|
||||
t.Helper()
|
||||
require.Error(t, err)
|
||||
var pqErr *pq.Error
|
||||
switch {
|
||||
case errors.As(err, &pqErr):
|
||||
require.Equal(t, "57014", string(pqErr.Code))
|
||||
case errors.Is(err, driver.ErrBadConn):
|
||||
// client-side short-circuit; see comment above
|
||||
default:
|
||||
require.ErrorIs(t, err, context.DeadlineExceeded)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSqlX(t *testing.T) {
|
||||
t.Run("NamedParse", func(t *testing.T) {
|
||||
queries := []struct {
|
||||
in string
|
||||
out string
|
||||
}{
|
||||
{
|
||||
in: `SELECT pg_sleep(:Timeout)`,
|
||||
out: `SELECT pg_sleep(:timeout)`,
|
||||
},
|
||||
{
|
||||
in: `SELECT u.Username FROM Bots
|
||||
LIMIT
|
||||
:Limit
|
||||
OFFSET
|
||||
:Offset`,
|
||||
out: `SELECT u.Username FROM Bots
|
||||
LIMIT
|
||||
:limit
|
||||
OFFSET
|
||||
:offset`,
|
||||
},
|
||||
{
|
||||
in: `UPDATE OAuthAccessData SET Token =:Token`,
|
||||
out: `UPDATE OAuthAccessData SET Token =:token`,
|
||||
},
|
||||
}
|
||||
for _, q := range queries {
|
||||
out := namedParamRegex.ReplaceAllStringFunc(q.in, strings.ToLower)
|
||||
assert.Equal(t, q.out, out)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func makeStoreWithTimeout(t *testing.T, driver string, queryTimeout time.Duration) *SqlStore {
|
||||
t.Helper()
|
||||
settings, err := makeSqlSettings(driver)
|
||||
if err != nil {
|
||||
t.Skip(err)
|
||||
}
|
||||
*settings.QueryTimeout = int(queryTimeout.Seconds())
|
||||
store := &SqlStore{
|
||||
settings: settings,
|
||||
logger: mlog.CreateConsoleTestLogger(t),
|
||||
quitMonitor: make(chan struct{}),
|
||||
wgMonitor: &sync.WaitGroup{},
|
||||
}
|
||||
require.NoError(t, store.initConnection())
|
||||
t.Cleanup(store.Close)
|
||||
return store
|
||||
}
|
||||
|
||||
func TestSqlxQuery(t *testing.T) {
|
||||
store := makeStoreWithTimeout(t, model.DatabaseDriverPostgres, 1*time.Second)
|
||||
|
||||
t.Run("db/happy", func(t *testing.T) {
|
||||
rows, err := store.GetMaster().Query("SELECT 1")
|
||||
require.NoError(t, err)
|
||||
defer rows.Close()
|
||||
require.True(t, rows.Next())
|
||||
var v int
|
||||
require.NoError(t, rows.Scan(&v))
|
||||
require.Equal(t, 1, v)
|
||||
})
|
||||
|
||||
t.Run("db/timeout", func(t *testing.T) {
|
||||
_, err := store.GetMaster().Query("SELECT pg_sleep(2)")
|
||||
requireQueryTimeout(t, err)
|
||||
})
|
||||
|
||||
t.Run("tx/happy", func(t *testing.T) {
|
||||
tx, err := store.GetMaster().Begin()
|
||||
require.NoError(t, err)
|
||||
defer func() { assert.NoError(t, tx.Rollback()) }()
|
||||
rows, err := tx.Query("SELECT 1")
|
||||
require.NoError(t, err)
|
||||
defer rows.Close()
|
||||
require.True(t, rows.Next())
|
||||
var v int
|
||||
require.NoError(t, rows.Scan(&v))
|
||||
require.Equal(t, 1, v)
|
||||
})
|
||||
|
||||
t.Run("tx/timeout", func(t *testing.T) {
|
||||
tx, err := store.GetMaster().Begin()
|
||||
require.NoError(t, err)
|
||||
_, err = tx.Query("SELECT pg_sleep(2)")
|
||||
require.Error(t, tx.Rollback()) // connection is dead after timeout
|
||||
requireQueryTimeout(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSqlxQueryRow(t *testing.T) {
|
||||
store := makeStoreWithTimeout(t, model.DatabaseDriverPostgres, 1*time.Second)
|
||||
|
||||
t.Run("db/happy", func(t *testing.T) {
|
||||
var v int
|
||||
require.NoError(t, store.GetMaster().QueryRow("SELECT 1").Scan(&v))
|
||||
require.Equal(t, 1, v)
|
||||
})
|
||||
|
||||
t.Run("db/timeout", func(t *testing.T) {
|
||||
var v int
|
||||
err := store.GetMaster().QueryRow("SELECT 1 FROM (SELECT pg_sleep(2)) s").Scan(&v)
|
||||
requireQueryTimeout(t, err)
|
||||
})
|
||||
|
||||
t.Run("tx/happy", func(t *testing.T) {
|
||||
tx, err := store.GetMaster().Begin()
|
||||
require.NoError(t, err)
|
||||
defer func() { assert.NoError(t, tx.Rollback()) }()
|
||||
var v int
|
||||
require.NoError(t, tx.QueryRow("SELECT 1").Scan(&v))
|
||||
require.Equal(t, 1, v)
|
||||
})
|
||||
|
||||
t.Run("tx/timeout", func(t *testing.T) {
|
||||
tx, err := store.GetMaster().Begin()
|
||||
require.NoError(t, err)
|
||||
var v int
|
||||
err = tx.QueryRow("SELECT 1 FROM (SELECT pg_sleep(2)) s").Scan(&v)
|
||||
require.Error(t, tx.Rollback()) // connection is dead after timeout
|
||||
requireQueryTimeout(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestEnsureQueryTimeout(t *testing.T) {
|
||||
t.Run("no deadline adds timeout", func(t *testing.T) {
|
||||
timeout := 30 * time.Second
|
||||
ctx, cancel := ensureQueryTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
deadline, ok := ctx.Deadline()
|
||||
require.True(t, ok)
|
||||
require.WithinDuration(t, time.Now().Add(timeout), deadline, time.Second)
|
||||
})
|
||||
|
||||
t.Run("existing deadline is preserved", func(t *testing.T) {
|
||||
originalDeadline := time.Now().Add(5 * time.Minute)
|
||||
parent, parentCancel := context.WithDeadline(context.Background(), originalDeadline)
|
||||
defer parentCancel()
|
||||
|
||||
ctx, cancel := ensureQueryTimeout(parent, 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
deadline, ok := ctx.Deadline()
|
||||
require.True(t, ok)
|
||||
require.Equal(t, originalDeadline, deadline)
|
||||
})
|
||||
|
||||
t.Run("no-op cancel is safe to call", func(t *testing.T) {
|
||||
parent, parentCancel := context.WithTimeout(context.Background(), time.Minute)
|
||||
defer parentCancel()
|
||||
|
||||
_, cancel := ensureQueryTimeout(parent, 30*time.Second)
|
||||
require.NotPanics(t, func() { cancel() }) // calling the no-op cancel must not panic
|
||||
})
|
||||
|
||||
t.Run("noTimeoutKey suppresses timeout injection", func(t *testing.T) {
|
||||
ctx := context.WithValue(context.Background(), noTimeoutKey{}, true)
|
||||
newCtx, cancel := ensureQueryTimeout(ctx, 30*time.Second)
|
||||
defer cancel()
|
||||
_, ok := newCtx.Deadline()
|
||||
require.False(t, ok)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSqlxSelectContext(t *testing.T) {
|
||||
store := makeStoreWithTimeout(t, model.DatabaseDriverPostgres, 1*time.Second)
|
||||
|
||||
t.Run("adds timeout when context has none", func(t *testing.T) {
|
||||
// context.Background() has no deadline — wrapper adds 1s; pg_sleep(2) times out.
|
||||
var result []string
|
||||
err := store.GetMaster().SelectContext(context.Background(), &result, "SELECT pg_sleep(2)")
|
||||
requireQueryTimeout(t, err)
|
||||
})
|
||||
|
||||
t.Run("respects caller deadline shorter than wrapper timeout", func(t *testing.T) {
|
||||
// 1ns context fires before the query reaches the server.
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 1)
|
||||
defer cancel()
|
||||
var result []string
|
||||
err := store.GetMaster().SelectContext(ctx, &result, "SELECT pg_sleep(2)")
|
||||
requireQueryTimeout(t, err)
|
||||
})
|
||||
|
||||
t.Run("respects caller deadline longer than wrapper timeout", func(t *testing.T) {
|
||||
// Caller supplies a 5s deadline; wrapper queryTimeout is 1s.
|
||||
// With "add only if missing" the 5s deadline is used — the 2s sleep completes.
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
var result []int
|
||||
err := store.GetMaster().SelectContext(ctx, &result, "SELECT 1 FROM (SELECT pg_sleep(2)) s")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []int{1}, result)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSqlxSelect(t *testing.T) {
|
||||
store := makeStoreWithTimeout(t, model.DatabaseDriverPostgres, 1*time.Second)
|
||||
|
||||
t.Run("SelectCtx", func(t *testing.T) {
|
||||
var result []string
|
||||
err := store.GetMaster().SelectContext(context.Background(), &result, "SELECT 'test' AS col")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []string{"test"}, result)
|
||||
|
||||
// Test timeout
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 1)
|
||||
defer cancel()
|
||||
query := "SELECT pg_sleep(2)"
|
||||
err = store.GetMaster().SelectContext(ctx, &result, query)
|
||||
requireQueryTimeout(t, err)
|
||||
})
|
||||
|
||||
t.Run("SelectBuilderCtx", func(t *testing.T) {
|
||||
var result []string
|
||||
builder := store.getQueryBuilder().
|
||||
Select("'test' AS col")
|
||||
err := store.GetMaster().SelectBuilderCtx(context.Background(), &result, builder)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []string{"test"}, result)
|
||||
|
||||
// Test timeout
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 1)
|
||||
defer cancel()
|
||||
builder = store.getQueryBuilder().
|
||||
Select("pg_sleep(2)")
|
||||
err = store.GetMaster().SelectBuilderCtx(ctx, &result, builder)
|
||||
requireQueryTimeout(t, err)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user