Files
im_server/server/db/common/common.go
T
2026-08-11 15:59:57 +08:00

186 lines
4.9 KiB
Go

// Package common contains utility methods used by all adapters.
package common
import (
"encoding/json"
"sort"
"strconv"
"strings"
"time"
"github.com/tinode/chat/server/auth"
"github.com/tinode/chat/server/store"
t "github.com/tinode/chat/server/store/types"
)
type AuthRecord struct {
Unique string `json:"unique" bson:"_id"`
UserId string `json:"userid"`
Scheme string `json:"scheme"`
AuthLvl auth.Level `json:"authLvl"`
Secret []byte `json:"secret"`
Expires time.Time `json:"expires"`
}
// SelectEarliestUpdatedSubs selects no more than the given number of subscriptions from the
// given slice satisfying the query. When the number of subscriptions is greater than the limit,
// the subscriptions with the earliest timestamp are selected.
func SelectEarliestUpdatedSubs(subs []t.Subscription, opts *t.QueryOpt, maxResults int) []t.Subscription {
limit := maxResults
ims := time.Time{}
if opts != nil {
if opts.Limit > 0 && opts.Limit < limit {
limit = opts.Limit
}
if opts.IfModifiedSince != nil {
ims = *opts.IfModifiedSince
}
}
// No cache management and the number of results is below the limit: return all.
if ims.IsZero() && len(subs) <= limit {
return subs
}
// Now that we fetched potentially more subscriptions than needed, we got to take those with the oldest modifications.
// Sorting in ascending order by modification time.
sort.Slice(subs, func(i, j int) bool {
return subs[i].LastModified().Before(subs[j].LastModified())
})
if !ims.IsZero() {
// Keep only those subscriptions which are newer than ims.
at := sort.Search(len(subs), func(i int) bool {
return subs[i].LastModified().After(ims)
})
subs = subs[at:]
}
// Trim slice at the limit.
if len(subs) > limit {
subs = subs[:limit]
}
return subs
}
// SelectLatestTime picks the latest update timestamp out of the two.
func SelectLatestTime(t1, t2 time.Time) time.Time {
if t1.Before(t2) {
// Subscription has not changed recently, use user's update timestamp.
return t2
}
return t1
}
// RangesToSql converts a slice of ranges to SQL BETWEEN or IN() constraint and arguments.
func RangesToSql(in []t.Range) (string, []any) {
if len(in) > 1 || in[0].Hi == 0 {
var args []any
for _, r := range in {
if r.Hi == 0 {
args = append(args, r.Low)
} else {
for i := r.Low; i < r.Hi; i++ {
args = append(args, i)
}
}
}
return "IN (?" + strings.Repeat(",?", len(args)-1) + ")", args
}
// Optimizing for a special case of single range low..hi.
// SQL's BETWEEN is inclusive-inclusive thus decrement Hi by 1.
return "BETWEEN ? AND ?", []any{in[0].Low, in[0].Hi - 1}
}
// DisjunctionSql converts a slice of disjunctions to SQL HAVING clause and arguments.
func DisjunctionSql(req [][]string, fieldName string) (string, []any) {
var args []any
counts := make([]string, 0, len(req))
for _, reqDisjunction := range req {
// At least one of the tags must be present.
if len(reqDisjunction) == 0 {
continue
}
counts = append(counts, "COUNT("+fieldName+" IN (?"+strings.Repeat(",?", len(reqDisjunction)-1)+") OR NULL)>=1")
for _, tag := range reqDisjunction {
args = append(args, tag)
}
}
return "HAVING " + strings.Join(counts, " AND ") + " ", args
}
// FilterFoundTags keeps only those tags in setTags that are present in the index.
func FilterFoundTags(setTags t.StringSlice, index map[string]struct{}) []string {
foundTags := make([]string, 0, 1)
for _, tag := range setTags {
if _, ok := index[tag]; ok {
foundTags = append(foundTags, tag)
}
}
return foundTags
}
// Convert to JSON before storing to JSON field.
func ToJSON(src any) []byte {
if src == nil {
return nil
}
jval, _ := json.Marshal(src)
return jval
}
// Deserialize JSON data from DB.
func FromJSON(src any) any {
if src == nil {
return nil
}
if bb, ok := src.([]byte); ok {
var out any
json.Unmarshal(bb, &out)
return out
}
return nil
}
// Convert update to a list of columns and arguments.
func UpdateByMap(update map[string]any) (cols []string, args []any) {
for col, arg := range update {
col = strings.ToLower(col)
if col == "public" || col == "trusted" || col == "private" || col == "aux" {
arg = ToJSON(arg)
}
cols = append(cols, col+"=?")
args = append(args, arg)
}
return
}
// If Tags field is updated, get the tags so tags table cab be updated too.
func ExtractTags(update map[string]any) []string {
var tags []string
if val := update["Tags"]; val != nil {
tags, _ = val.(t.StringSlice)
}
return []string(tags)
}
// EncodeUidString takes decoded string representation of int64, produce UID.
// UIDs are stored as decoded int64 values.
func EncodeUidString(str string) t.Uid {
unum, _ := strconv.ParseInt(str, 10, 64)
return store.EncodeUid(unum)
}
// DecodeUidString takes UID as string, converts it to int64 representation.
// UIDs are stored as decoded int64 values.
func DecodeUidString(str string) int64 {
uid := t.ParseUid(str)
return store.DecodeUid(uid)
}