minor cleanup in db adapters

This commit is contained in:
or-else
2025-06-18 16:02:45 +03:00
parent 8785d63d91
commit 3f320da884
8 changed files with 41 additions and 47 deletions
+2 -2
View File
@@ -6,7 +6,7 @@ The config file [`tinode.conf`](./server/tinode.conf) contains extensive instruc
1. Visit the [Releases page](https://github.com/tinode/chat/releases/), choose the latest or otherwise the most suitable release. From the list of binaries download the one for your database and platform. Once the binary is downloaded, unpack it to a directory of your choosing, `cd` to that directory.
2. Make sure your database is running. Make sure it's configured to accept connections from `localhost`. In case of MySQL, Tinode will try to connect as `root` without the password. In case of PostgreSQL, Tinode will try connect as `postgres` with the password `postgres`. See notes below (_Building from Source_, section 4) on how to configure Tinode to use a different user or a password. MySQL 5.7 or above is required. MySQL 5.6 or below **will not work**. PostgreSQL 13 or above is required. PostgreSQL 12 or below **will not work**.
2. Make sure your database is running. Make sure it's configured to accept connections from `localhost`. In case of MySQL, Tinode will try to connect as `root` without the password. In case of PostgreSQL, Tinode will try connect as `postgres` with the password `postgres`. See notes below (_Building from Source_, section 4) on how to configure Tinode to use a different user or a password. MySQL 5.7 or above is required (use InnoDB, not MyISAM storage engine). MySQL 5.6 or below **will not work**. PostgreSQL 13 or above is required. PostgreSQL 12 or below **will not work**.
3. Run the database initializer `init-db` (or `init-db.exe` on Windows):
```
@@ -33,7 +33,7 @@ See [instructions](./docker/README.md)
2. OPTIONAL only if you intend to modify the code: Install [protobuf](https://developers.google.com/protocol-buffers/) and [gRPC](https://grpc.io/docs/languages/go/quickstart/) including [code generator](https://developers.google.com/protocol-buffers/docs/reference/go-generated) for Go.
3. Make sure one of the following databases is installed and running:
* MySQL 5.7 or above. MySQL 5.6 or below **will not work**.
* MySQL 5.7 or above configured with `InnoDB` engine. MySQL 5.6 or below **will not work**.
* PostgreSQL 13 or above. PostgreSQL 12 or below **will not work**.
* MongoDB 4.4 or above. MongoDB 4.2 and below **will not work**.
* RethinkDB (deprecated, support will be dropped in 2027).
+16
View File
@@ -4,9 +4,11 @@ package common
import (
"encoding/json"
"sort"
"strconv"
"strings"
"time"
"github.com/tinode/chat/server/store"
t "github.com/tinode/chat/server/store/types"
)
@@ -157,3 +159,17 @@ func ExtractTags(update map[string]any) []string {
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)
}
+11 -22
View File
@@ -315,7 +315,7 @@ func (a *adapter) CreateDb(reset bool) error {
}
}
if _, err = tx.Exec("CREATE DATABASE " + a.dbName + " CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci"); err != nil {
if _, err = tx.Exec("CREATE DATABASE " + a.dbName + " CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci"); err != nil {
return err
}
@@ -1091,7 +1091,7 @@ func (a *adapter) UserGetAll(ids ...t.Uid) ([]t.User, error) {
continue
}
user.SetUid(encodeUidString(user.Id))
user.SetUid(common.EncodeUidString(user.Id))
user.Public = common.FromJSON(user.Public)
user.Trusted = common.FromJSON(user.Trusted)
@@ -1595,7 +1595,7 @@ func (a *adapter) TopicGet(topic string) (*t.Topic, error) {
return nil, err
}
tt.Owner = encodeUidString(tt.Owner).String()
tt.Owner = common.EncodeUidString(tt.Owner).String()
tt.Public = common.FromJSON(tt.Public)
tt.Trusted = common.FromJSON(tt.Trusted)
@@ -1798,7 +1798,7 @@ func (a *adapter) TopicsForUser(uid t.Uid, keepDeleted bool, opts *t.QueryOpt) (
break
}
joinOn := uid.P2PName(encodeUidString(usr2.Id))
joinOn := uid.P2PName(common.EncodeUidString(usr2.Id))
if sub, ok := join[joinOn]; ok {
sub.UpdatedAt = common.SelectLatestTime(sub.UpdatedAt, usr2.UpdatedAt)
sub.SetState(usr2.State)
@@ -1897,7 +1897,7 @@ func (a *adapter) UsersForTopic(topic string, keepDeleted bool, opts *t.QueryOpt
break
}
sub.User = encodeUidString(sub.User).String()
sub.User = common.EncodeUidString(sub.User).String()
sub.Private = common.FromJSON(sub.Private)
sub.SetPublic(common.FromJSON(public))
sub.SetTrusted(common.FromJSON(trusted))
@@ -2238,7 +2238,7 @@ func (a *adapter) SubsForTopic(topic string, keepDeleted bool, opts *t.QueryOpt)
break
}
ss.User = encodeUidString(ss.User).String()
ss.User = common.EncodeUidString(ss.User).String()
ss.Private = common.FromJSON(ss.Private)
subs = append(subs, ss)
}
@@ -2599,7 +2599,7 @@ func (a *adapter) MessageGetAll(topic string, forUser t.Uid, opts *t.QueryOpt) (
if err = rows.StructScan(&msg); err != nil {
break
}
msg.From = encodeUidString(msg.From).String()
msg.From = common.EncodeUidString(msg.From).String()
msg.Content = common.FromJSON(msg.Content)
msgs = append(msgs, msg)
}
@@ -2771,7 +2771,7 @@ func messageDeleteList(tx *sqlx.Tx, topic string, toDel *t.DelMessage) error {
return err
}
forUser := decodeUidString(toDel.DeletedFor)
forUser := common.DecodeUidString(toDel.DeletedFor)
for _, rng := range delRanges {
if rng.Hi == 0 {
// Dellog must contain valid Low and *Hi*.
@@ -2971,7 +2971,7 @@ func (a *adapter) CredUpsert(cred *t.Credential) (bool, error) {
}()
now := t.TimeNow()
userId := decodeUidString(cred.User)
userId := common.DecodeUidString(cred.User)
// Enforce uniqueness: if credential is confirmed, "method:value" must be unique.
// if credential is not yet confirmed, "userid:method:value" is unique.
@@ -3277,8 +3277,8 @@ func (a *adapter) FileGet(fid string) (*t.FileDef, error) {
return nil, err
}
fd.Id = encodeUidString(fd.Id).String()
fd.User = encodeUidString(fd.User).String()
fd.Id = common.EncodeUidString(fd.Id).String()
fd.User = common.EncodeUidString(fd.User).String()
return &fd, nil
@@ -3522,17 +3522,6 @@ func isMissingDb(err error) bool {
return ok && myerr.Number == 1049
}
// UIDs are stored as decoded int64 values. Take decoded string representation of int64, produce UID.
func encodeUidString(str string) t.Uid {
unum, _ := strconv.ParseInt(str, 10, 64)
return store.EncodeUid(unum)
}
func decodeUidString(str string) int64 {
uid := t.ParseUid(str)
return store.DecodeUid(uid)
}
func init() {
store.RegisterAdapter(&adapter{})
}
+4 -15
View File
@@ -2278,7 +2278,7 @@ func (a *adapter) SubsDelForUser(user t.Uid, hard bool) error {
}
// Find returns a list of users and group topics which match teh given tags, such as "email:jdoe@example.com" or "tel:+18003287448".
// Find returns a list of users and group topics which match the given tags, such as "email:jdoe@example.com" or "tel:+18003287448".
func (a *adapter) Find(caller, promoPrefix string, req [][]string, opt []string, activeOnly bool) ([]t.Subscription, error) {
index := make(map[string]struct{})
var args []any
@@ -2693,7 +2693,7 @@ func messageDeleteList(ctx context.Context, tx pgx.Tx, topic string, toDel *t.De
// Now make log entries. Needed for both hard- and soft-deleting.
// Prepare statement is not needed because the driver prepares the statement on first use then caches it.
forUser := decodeUidString(toDel.DeletedFor)
forUser := common.DecodeUidString(toDel.DeletedFor)
for _, rng := range toDel.SeqIdRanges {
if rng.Hi == 0 {
// Dellog must contain valid Low and *Hi*.
@@ -2895,7 +2895,7 @@ func (a *adapter) CredUpsert(cred *t.Credential) (bool, error) {
}()
now := t.TimeNow()
userId := decodeUidString(cred.User)
userId := common.DecodeUidString(cred.User)
// Enforce uniqueness: if credential is confirmed, "method:value" must be unique.
// if credential is not yet confirmed, "userid:method:value" is unique.
@@ -3466,18 +3466,7 @@ func isMissingDb(err error) bool {
return strings.Contains(msg, "SQLSTATE 3D000")
}
// UIDs are stored as decoded int64 values. Take decoded string representation of int64, produce UID.
func encodeUidString(str string) t.Uid {
unum, _ := strconv.ParseInt(str, 10, 64)
return store.EncodeUid(unum)
}
func decodeUidString(str string) int64 {
uid := t.ParseUid(str)
return store.DecodeUid(uid)
}
// Converting a structure with data to enter a connection string
// setConnStr converts a config structure to a DSN connection string.
func setConnStr(c configType) (string, error) {
// Default to disable SSL mode.
sslMode := "disable"
+1 -1
View File
@@ -8,10 +8,10 @@ import (
"encoding/binary"
"encoding/json"
"errors"
"slices"
"sort"
"strings"
"time"
"slices"
)
// StoreError satisfies Error interface but allows constant values for
+4 -4
View File
@@ -289,16 +289,16 @@
"Net": "tcp",
"Addr": "localhost",
"DBName": "tinode",
// The 'collation=utf8mb4_unicode_ci' is optional but highly recommended for
// emoji and certain CJK characters.
"Collation": "utf8mb4_unicode_ci",
// The 'collation=utf8mb4_0900_ai_ci' is default in MySQL 8.0 and above. It is optional but highly
// recommended for emoji and certain CJK characters in earlier versions of MySQL.
"Collation": "utf8mb4_0900_ai_ci",
// Parse time values to time.Time. Required.
"ParseTime": true,
// DSN: alternative way of specifying database configuration, passed unchanged
// to MySQL driver. See https://github.com/go-sql-driver/mysql#dsn-data-source-name for syntax.
// DSN may optionally start with mysql://
// "dsn": "root@tcp(localhost)/tinode?parseTime=true&collation=utf8mb4_unicode_ci",
// "dsn": "root@tcp(localhost)/tinode?parseTime=true&collation=utf8mb4_0900_ai_ci",
// MySQL connection pool settings.
// Maximum number of open connections to the database. Default: 0 (unlimited).
+1 -1
View File
@@ -502,7 +502,7 @@ func validateTag(tag string) (string, string) {
// hasDuplicateNamespaceTags checks for duplication of unique NS tags.
// Each namespace can have only one tag. This does not prevent tags from
// being duplicate accross requests, just saves an extra DB call.
// being duplicate across requests, just saves an extra DB call.
func hasDuplicateNamespaceTags(src []string, uniqueNS string) bool {
found := map[string]bool{}
for _, tag := range src {
+2 -2
View File
@@ -275,9 +275,9 @@ func (v *validator) Request(user t.Uid, email, lang, resp string, tmpToken []byt
var template *textt.Template
if v.langMatcher != nil {
// Find the template for the requested language.
// Make sure the language tag is standartized. Matcher is a bit dumber than Parse().
// Make sure the language tag is standardized. Matcher is a bit dumber than Parse().
normalized, _ := i18n.Parse(lang)
// The matched tag is usualy not in the list of available languages (e.g. es_ES -> es-u-rg-eszzzz).
// The matched tag is usually not in the list of available languages (e.g. es_ES -> es-u-rg-eszzzz).
// Use index to find the template instead of tag.
_, idx := i18n.MatchStrings(v.langMatcher, normalized.String())
template = v.validationTempl[idx]