Fixed docs (added info about PostgreSQL), added cache functions in adapter and little refactoring

This commit is contained in:
Minin Aleksandr
2023-04-05 23:29:27 +03:00
parent a3a2f9e4df
commit d9fc193a74
5 changed files with 147 additions and 80 deletions

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. 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**.
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**.
3. Run the database initializer `init-db` (or `init-db.exe` on Windows):
```
@ -36,6 +36,7 @@ See [instructions](./docker/README.md)
* MySQL 5.7 or above. MySQL 5.6 or below **will not work**.
* MongoDB 4.0 or above.
* RethinkDB.
* PostgreSQL 13 or above. PostgreSQL 12 or below **will not work**.
4. Fetch, build Tinode server and tinode-db database initializer:
- **MySQL**:
@ -53,15 +54,20 @@ See [instructions](./docker/README.md)
go install -tags rethinkdb github.com/tinode/chat/server@latest
go install -tags rethinkdb github.com/tinode/chat/tinode-db@latest
```
- **PostgreSQL**:
```
go install -tags postgres github.com/tinode/chat/server@latest
go install -tags postgres github.com/tinode/chat/tinode-db@latest
```
- **All** (bundle all of the above DB adapters):
```
go install -tags "mysql rethinkdb mongodb" github.com/tinode/chat/server@latest
go install -tags "mysql rethinkdb mongodb" github.com/tinode/chat/tinode-db@latest
go install -tags "mysql rethinkdb mongodb postgres" github.com/tinode/chat/server@latest
go install -tags "mysql rethinkdb mongodb postgres" github.com/tinode/chat/tinode-db@latest
```
The steps above install Tinode binaries at `$GOPATH/bin/`, sorces and supporting files are located at `$GOPATH/pkg/mod/github.com/tinode/chat@vX.XX.X/` where `X.XX.X` is the version you installed, such as `0.19.1`.
Note the required **`-tags rethinkdb`**, **`-tags mysql`** or **`-tags mongodb`** build option.
Note the required **`-tags rethinkdb`**, **`-tags mysql`**, **`-tags mongodb`** or **`-tags postgres`** build option.
You may also optionally define `main.buildstamp` for the server by adding a build option, for instance, with a timestamp:
```
@ -118,6 +124,10 @@ MongoDB should run as single node replicaset. See https://docs.mongodb.com/manua
```
rethinkdb --bind all --daemon
```
- **PostgreSQL**: https://www.postgresql.org/docs/current/app-pg-ctl.html
```
pg_ctl start
```
2. Run DB initializer
```

View File

@ -34,7 +34,13 @@ All images are available at https://hub.docker.com/r/tinode/
```
See [instructions](https://hub.docker.com/_/mongo/) for more options. MongoDB 4.2 or above is required.
The name `rethinkdb`, `mysql` or `mongodb` in the `--name` assignment is important. It's used by other containers as a database's host name.
4. **PostgreSQL**: If you've decided to use PostgreSQL backend, run the official PostgreSQL Docker container:
```
$ docker run --name postgres --network tinode-net --restart always --env POSTGRES_PASSWORD=postgres -d postgres:13
```
See [instructions](https://hub.docker.com/_/postgres/) for more options. PostgresSQL 13 or above is required.
The name `rethinkdb`, `mysql`, `mongodb` or `postgres` in the `--name` assignment is important. It's used by other containers as a database's host name.
4. Run the Tinode container for the appropriate database:
@ -53,6 +59,11 @@ All images are available at https://hub.docker.com/r/tinode/
$ docker run -p 6060:6060 -d --name tinode-srv --network tinode-net tinode/tinode-mongodb:latest
```
4. **PostgreSQL**:
```
$ docker run -p 6060:6060 -d --name tinode-srv --network tinode-net tinode/tinode-postgresql:latest
```
You can also run Tinode with the `tinode/tinode` image (which has all of the above DB adapters compiled in). You will need to specify the database adapter via `STORE_USE_ADAPTER` environment variable. E.g. for `mysql`, the command line will look like
```
$ docker run -p 6060:6060 -d -e STORE_USE_ADAPTER mysql --name tinode-srv --network tinode-net tinode/tinode:latest
@ -66,6 +77,7 @@ All images are available at https://hub.docker.com/r/tinode/
* [MySQL tags](https://hub.docker.com/r/tinode/tinode-mysql/tags/)
* [RethinkDB tags](https://hub.docker.com/r/tinode/tinode-rethink/tags/)
* [MongoDB tags](https://hub.docker.com/r/tinode/tinode-mongodb/tags/)
* [PostgreSQL tags](https://hub.docker.com/r/tinode/tinode-postgresql/tags/) (comming soon)
* [All bundle tags](https://hub.docker.com/r/tinode/tinode/tags/) (comming soon)
5. Test the installation by pointing your browser to [http://localhost:6060/](http://localhost:6060/).

View File

@ -168,11 +168,6 @@ func (a *adapter) Open(jsonconfig json.RawMessage) error {
// Actually opening the network connection.
err = a.db.Ping(ctx)
// if isMissingDb(err) {
// // Ignore missing database here. If we are initializing the database
// // missing DB is OK.
// err = nil
// }
if err == nil {
if config.MaxOpenConns > 0 {
@ -221,7 +216,7 @@ func (a *adapter) GetDbVersion() (int, error) {
defer cancel()
}
var vers string
err := a.db.QueryRow(ctx, `SELECT value FROM kvmeta WHERE key = $1`, "version").Scan(&vers)
err := a.db.QueryRow(ctx, "SELECT value FROM kvmeta WHERE key = $1", "version").Scan(&vers)
if err != nil {
if isMissingDb(err) || isMissingTable(err) || err == pgx.ErrNoRows {
@ -241,7 +236,7 @@ func (a *adapter) updateDbVersion(v int) error {
defer cancel()
}
a.version = -1
if _, err := a.db.Exec(ctx, `UPDATE kvmeta SET value = $1 WHERE key = $2`, strconv.Itoa(v), "version"); err != nil {
if _, err := a.db.Exec(ctx, "UPDATE kvmeta SET value = $1 WHERE key = $2", strconv.Itoa(v), "version"); err != nil {
return err
}
return nil
@ -306,10 +301,8 @@ func (a *adapter) CreateDb(reset bool) error {
if a.db != nil {
a.db.Close()
}
// // This DSN has been parsed before and produced no error, not checking for errors here.
// cfg, _ := pgxpool.ParseConfig(a.dsn)
// // Create default database name
// Create default database name
a.poolConfig.ConnConfig.Database = "postgres"
a.db, err = pgxpool.ConnectConfig(ctx, a.poolConfig)
@ -616,15 +609,15 @@ func (a *adapter) UpgradeDb() error {
if a.version == 106 {
// Perform database upgrade from version 106 to version 107.
if _, err := a.db.Exec(ctx, `CREATE UNIQUE INDEX usertags_userid_tag ON usertags(userid, tag)`); err != nil {
if _, err := a.db.Exec(ctx, "CREATE UNIQUE INDEX usertags_userid_tag ON usertags(userid, tag)"); err != nil {
return err
}
if _, err := a.db.Exec(ctx, `CREATE UNIQUE INDEX topictags_userid_tag ON topictags(topic, tag)`); err != nil {
if _, err := a.db.Exec(ctx, "CREATE UNIQUE INDEX topictags_userid_tag ON topictags(topic, tag)"); err != nil {
return err
}
if _, err := a.db.Exec(ctx, `ALTER TABLE credentials ADD deletedat TIMESTAMP(3) AFTER updatedat`); err != nil {
if _, err := a.db.Exec(ctx, "ALTER TABLE credentials ADD deletedat TIMESTAMP(3) AFTER updatedat"); err != nil {
return err
}
@ -669,7 +662,7 @@ func (a *adapter) UpgradeDb() error {
if a.version == 109 {
// Perform database upgrade from version 109 to version 110.
if _, err := a.db.Exec(ctx, `UPDATE topics SET touchedat=updatedat WHERE touchedat IS NULL`); err != nil {
if _, err := a.db.Exec(ctx, "UPDATE topics SET touchedat=updatedat WHERE touchedat IS NULL"); err != nil {
return err
}
@ -680,47 +673,47 @@ func (a *adapter) UpgradeDb() error {
if a.version == 110 {
// Users
if _, err := a.db.Exec(ctx, `ALTER TABLE users MODIFY state SMALLINT NOT NULL DEFAULT 0 AFTER updatedat`); err != nil {
if _, err := a.db.Exec(ctx, "ALTER TABLE users MODIFY state SMALLINT NOT NULL DEFAULT 0 AFTER updatedat"); err != nil {
return err
}
if _, err := a.db.Exec(ctx, `ALTER TABLE users CHANGE deletedat stateat TIMESTAMP(3)`); err != nil {
if _, err := a.db.Exec(ctx, "ALTER TABLE users CHANGE deletedat stateat TIMESTAMP(3)"); err != nil {
return err
}
if _, err := a.db.Exec(ctx, `ALTER TABLE users DROP INDEX users_deletedat`); err != nil {
if _, err := a.db.Exec(ctx, "ALTER TABLE users DROP INDEX users_deletedat"); err != nil {
return err
}
// Add status to formerly soft-deleted users.
if _, err := a.db.Exec(ctx, `UPDATE users SET state=$1 WHERE stateat IS NOT NULL`, t.StateDeleted); err != nil {
if _, err := a.db.Exec(ctx, "UPDATE users SET state=$1 WHERE stateat IS NOT NULL", t.StateDeleted); err != nil {
return err
}
if _, err := a.db.Exec(ctx, `ALTER TABLE users ADD INDEX users_state(state)`); err != nil {
if _, err := a.db.Exec(ctx, "ALTER TABLE users ADD INDEX users_state(state)"); err != nil {
return err
}
// Topics
if _, err := a.db.Exec(ctx, `ALTER TABLE topics ADD state SMALLINT NOT NULL DEFAULT 0 AFTER updatedat`); err != nil {
if _, err := a.db.Exec(ctx, "ALTER TABLE topics ADD state SMALLINT NOT NULL DEFAULT 0 AFTER updatedat"); err != nil {
return err
}
if _, err := a.db.Exec(ctx, `ALTER TABLE topics CHANGE deletedat stateat TIMESTAMP(3)`); err != nil {
if _, err := a.db.Exec(ctx, "ALTER TABLE topics CHANGE deletedat stateat TIMESTAMP(3)"); err != nil {
return err
}
// Add status to formerly soft-deleted topics.
if _, err := a.db.Exec(ctx, `UPDATE topics SET state=$1 WHERE stateat IS NOT NULL`, t.StateDeleted); err != nil {
if _, err := a.db.Exec(ctx, "UPDATE topics SET state=$1 WHERE stateat IS NOT NULL", t.StateDeleted); err != nil {
return err
}
if _, err := a.db.Exec(ctx, `ALTER TABLE topics ADD INDEX topics_state(state)`); err != nil {
if _, err := a.db.Exec(ctx, "ALTER TABLE topics ADD INDEX topics_state(state)"); err != nil {
return err
}
// Subscriptions
if _, err := a.db.Exec(ctx, `ALTER TABLE subscriptions ADD INDEX topics_deletedat(deletedat)`); err != nil {
if _, err := a.db.Exec(ctx, "ALTER TABLE subscriptions ADD INDEX topics_deletedat(deletedat)"); err != nil {
return err
}
@ -731,41 +724,41 @@ func (a *adapter) UpgradeDb() error {
if a.version == 111 {
// Perform database upgrade from version 111 to version 112.
if _, err := a.db.Exec(ctx, `ALTER TABLE users ADD trusted JSON AFTER public`); err != nil {
if _, err := a.db.Exec(ctx, "ALTER TABLE users ADD trusted JSON AFTER public"); err != nil {
return err
}
if _, err := a.db.Exec(ctx, `ALTER TABLE topics ADD trusted JSON AFTER public`); err != nil {
if _, err := a.db.Exec(ctx, "ALTER TABLE topics ADD trusted JSON AFTER public"); err != nil {
return err
}
// Remove NOT NULL constraint, so an avatar upload can be done at registration.
if _, err := a.db.Exec(ctx, `ALTER TABLE fileuploads MODIFY userid BIGINT`); err != nil {
if _, err := a.db.Exec(ctx, "ALTER TABLE fileuploads MODIFY userid BIGINT"); err != nil {
return err
}
if _, err := a.db.Exec(ctx, `ALTER TABLE fileuploads ADD INDEX fileuploads_status(status)`); err != nil {
if _, err := a.db.Exec(ctx, "ALTER TABLE fileuploads ADD INDEX fileuploads_status(status)"); err != nil {
return err
}
// Remove NOT NULL constraint to enable links to users and topics.
if _, err := a.db.Exec(ctx, `ALTER TABLE filepgglinks MODIFY pggid INT`); err != nil {
if _, err := a.db.Exec(ctx, "ALTER TABLE filepgglinks MODIFY pggid INT"); err != nil {
return err
}
if _, err := a.db.Exec(ctx, `ALTER TABLE filepgglinks ADD topic CHAR(25)`); err != nil {
if _, err := a.db.Exec(ctx, "ALTER TABLE filepgglinks ADD topic CHAR(25)"); err != nil {
return err
}
if _, err := a.db.Exec(ctx, `ALTER TABLE filepgglinks ADD userid BIGINT`); err != nil {
if _, err := a.db.Exec(ctx, "ALTER TABLE filepgglinks ADD userid BIGINT"); err != nil {
return err
}
if _, err := a.db.Exec(ctx, `ALTER TABLE filepgglinks ADD FOREIGN KEY(topic) REFERENCES topics(name) ON DELETE CASCADE`); err != nil {
if _, err := a.db.Exec(ctx, "ALTER TABLE filepgglinks ADD FOREIGN KEY(topic) REFERENCES topics(name) ON DELETE CASCADE"); err != nil {
return err
}
if _, err := a.db.Exec(ctx, `ALTER TABLE filepgglinks ADD FOREIGN KEY(userid) REFERENCES users(id) ON DELETE CASCADE`); err != nil {
if _, err := a.db.Exec(ctx, "ALTER TABLE filepgglinks ADD FOREIGN KEY(userid) REFERENCES users(id) ON DELETE CASCADE"); err != nil {
return err
}
@ -847,7 +840,7 @@ func (a *adapter) UserCreate(user *t.User) error {
decoded_uid := store.DecodeUid(user.Uid())
if _, err = tx.Exec(ctx,
`INSERT INTO users(id,createdat,updatedat,state,access,public,trusted,tags) VALUES($1,$2,$3,$4,$5,$6,$7,$8);`,
"INSERT INTO users(id,createdat,updatedat,state,access,public,trusted,tags) VALUES($1,$2,$3,$4,$5,$6,$7,$8);",
decoded_uid,
user.CreatedAt,
user.UpdatedAt,
@ -1121,7 +1114,7 @@ func (a *adapter) UserDelete(uid t.Uid, hard bool) error {
}
// Delete records of messages soft-deleted for the user.
if _, err = tx.Exec(ctx, `DELETE FROM dellog WHERE deletedfor=$1;`, decoded_uid); err != nil {
if _, err = tx.Exec(ctx, "DELETE FROM dellog WHERE deletedfor=$1", decoded_uid); err != nil {
return err
}
@ -1131,34 +1124,34 @@ func (a *adapter) UserDelete(uid t.Uid, hard bool) error {
// Delete topics where the user is the owner.
// First delete all messages in those topics.
if _, err = tx.Exec(ctx, `DELETE FROM dellog USING topics WHERE topics.name=dellog.topic AND topics.owner=$1;`,
if _, err = tx.Exec(ctx, "DELETE FROM dellog USING topics WHERE topics.name=dellog.topic AND topics.owner=$1",
decoded_uid); err != nil {
return err
}
if _, err = tx.Exec(ctx, `DELETE FROM messages USING topics WHERE topics.name=messages.topic AND topics.owner=$1;`,
if _, err = tx.Exec(ctx, "DELETE FROM messages USING topics WHERE topics.name=messages.topic AND topics.owner=$1",
decoded_uid); err != nil {
return err
}
// Delete all subscriptions.
if _, err = tx.Exec(ctx, `DELETE FROM subscriptions USING topics WHERE topics.name=subscriptions.topic AND topics.owner=$1;`,
if _, err = tx.Exec(ctx, "DELETE FROM subscriptions USING topics WHERE topics.name=subscriptions.topic AND topics.owner=$1",
decoded_uid); err != nil {
return err
}
// Delete topic tags.
if _, err = tx.Exec(ctx, `DELETE FROM topictags USING topics WHERE topics.name=topictags.topic AND topics.owner=$1;`,
if _, err = tx.Exec(ctx, "DELETE FROM topictags USING topics WHERE topics.name=topictags.topic AND topics.owner=$1",
decoded_uid); err != nil {
return err
}
// And finally delete the topics.
if _, err = tx.Exec(ctx, `DELETE FROM topics WHERE owner=$1;`, decoded_uid); err != nil {
if _, err = tx.Exec(ctx, "DELETE FROM topics WHERE owner=$1", decoded_uid); err != nil {
return err
}
// Delete user's authentication records.
if _, err = tx.Exec(ctx, `DELETE FROM auth WHERE userid=$1;`, decoded_uid); err != nil {
if _, err = tx.Exec(ctx, "DELETE FROM auth WHERE userid=$1", decoded_uid); err != nil {
return err
}
@ -1601,8 +1594,6 @@ func (a *adapter) TopicGet(topic string) (*t.Topic, error) {
}
tt.Owner = store.EncodeUid(owner).String()
// tt.Public = fromJSON(tt.Public)
// tt.Trusted = fromJSON(tt.Trusted)
return tt, nil
}
@ -1672,8 +1663,6 @@ func (a *adapter) TopicsForUser(uid t.Uid, keepDeleted bool, opts *t.QueryOpt) (
&sub.RecvSeqId, &sub.ReadSeqId, &modeWant, &modeGiven, &sub.Private); err != nil {
break
}
// modeWant = bytes.Trim(modeWant, " ")
// modeGiven = bytes.Trim(modeGiven, " ")
sub.ModeWant.Scan(modeWant)
sub.ModeGiven.Scan(modeGiven)
tname := sub.Topic
@ -1922,13 +1911,10 @@ func (a *adapter) UsersForTopic(topic string, keepDeleted bool, opts *t.QueryOpt
break
}
// sub.SetUid(store.EncodeUid(userId))
sub.User = store.EncodeUid(userId).String()
sub.SetPublic(public)
sub.SetTrusted(trusted)
sub.SetLastSeenAndUA(lastSeen, userAgent)
// modeWant = bytes.Trim(modeWant, " ")
// modeGiven = bytes.Trim(modeGiven, " ")
sub.ModeWant.Scan(modeWant)
sub.ModeGiven.Scan(modeGiven)
subs = append(subs, sub)
@ -2185,7 +2171,6 @@ func (a *adapter) SubscriptionGet(topic string, user t.Uid, keepDeleted bool) (*
return nil, nil
}
//sub.SetUid(store.EncodeUid(userId))
sub.User = store.EncodeUid(userId).String()
sub.ModeWant.Scan(modeWant)
sub.ModeGiven.Scan(modeGiven)
@ -2220,10 +2205,7 @@ func (a *adapter) SubsForUser(forUser t.Uid) ([]t.Subscription, error) {
break
}
// sub.SetUid(store.EncodeUid(userId))
sub.User = store.EncodeUid(userId).String()
// modeWant = bytes.Trim(modeWant, " ")
// modeGiven = bytes.Trim(modeGiven, " ")
sub.ModeWant.Scan(modeWant)
sub.ModeGiven.Scan(modeGiven)
subs = append(subs, sub)
@ -2286,10 +2268,7 @@ func (a *adapter) SubsForTopic(topic string, keepDeleted bool, opts *t.QueryOpt)
break
}
// sub.SetUid(store.EncodeUid(userId))
sub.User = store.EncodeUid(userId).String()
// modeWant = bytes.Trim(modeWant, " ")
// modeGiven = bytes.Trim(modeGiven, " ")
sub.ModeWant.Scan(modeWant)
sub.ModeGiven.Scan(modeGiven)
subs = append(subs, sub)
@ -2382,10 +2361,10 @@ func (a *adapter) SubsDelete(topic string, user t.Uid) error {
func subsDelForUser(ctx context.Context, tx pgx.Tx, user t.Uid, hard bool) error {
var err error
if hard {
_, err = tx.Exec(ctx, `DELETE FROM subscriptions WHERE userid=$1;`, store.DecodeUid(user))
_, err = tx.Exec(ctx, "DELETE FROM subscriptions WHERE userid=$1;", store.DecodeUid(user))
} else {
now := t.TimeNow()
_, err = tx.Exec(ctx, `UPDATE subscriptions SET updatedat=$1,deletedat=$2 WHERE userid=$3 AND deletedat IS NULL;`,
_, err = tx.Exec(ctx, "UPDATE subscriptions SET updatedat=$1,deletedat=$2 WHERE userid=$3 AND deletedat IS NULL;",
now, now, store.DecodeUid(user))
}
return err
@ -2481,7 +2460,6 @@ func (a *adapter) FindUsers(uid t.Uid, req [][]string, opt []string) ([]t.Subscr
// Skip the callee
continue
}
//sub.SetUid(store.EncodeUid(userId))
sub.User = store.EncodeUid(userId).String()
sub.SetPublic(public)
sub.SetTrusted(trusted)
@ -2935,9 +2913,9 @@ func deviceDelete(ctx context.Context, tx pgx.Tx, uid t.Uid, deviceID string) er
var err error
var res pgconn.CommandTag
if deviceID == "" {
res, err = tx.Exec(ctx, `DELETE FROM devices WHERE userid=$1;`, store.DecodeUid(uid))
res, err = tx.Exec(ctx, "DELETE FROM devices WHERE userid=$1", store.DecodeUid(uid))
} else {
res, err = tx.Exec(ctx, `DELETE FROM devices WHERE userid=$1 AND hash=$2;`, store.DecodeUid(uid), deviceHasher(deviceID))
res, err = tx.Exec(ctx, "DELETE FROM devices WHERE userid=$1 AND hash=$2", store.DecodeUid(uid), deviceHasher(deviceID))
}
if err == nil {
@ -3472,6 +3450,75 @@ func (a *adapter) FileLinkAttachments(topic string, userId, pggId t.Uid, fids []
return tx.Commit(ctx)
}
// PCacheGet reads a persistet cache entry.
func (a *adapter) PCacheGet(key string) (string, error) {
ctx, cancel := a.getContext()
if cancel != nil {
defer cancel()
}
var value string
if err := a.db.QueryRow(ctx, `SELECT "value" FROM kvmeta WHERE "key"=$1 LIMIT 1`, key).Scan(&value); err != nil {
if err == pgx.ErrNoRows {
return "", t.ErrNotFound
}
return "", err
}
return value, nil
}
// PCacheUpsert creates or updates a persistent cache entry.
func (a *adapter) PCacheUpsert(key string, value string, failOnDuplicate bool) error {
if strings.Contains(key, "%") {
// Do not allow % in keys: it interferes with LIKE query.
return t.ErrMalformed
}
ctx, cancel := a.getContext()
if cancel != nil {
defer cancel()
}
var action string
if failOnDuplicate {
action = "INSERT"
} else {
action = "REPLACE"
}
_, err := a.db.Exec(ctx, action+` INTO kvmeta("key",createdat,"value") VALUES($1,$2,$3)`, key, t.TimeNow(), value)
if isDupe(err) {
return t.ErrDuplicate
}
return err
}
// PCacheDelete deletes one persistent cache entry.
func (a *adapter) PCacheDelete(key string) error {
ctx, cancel := a.getContext()
if cancel != nil {
defer cancel()
}
_, err := a.db.Exec(ctx, `DELETE FROM kvmeta WHERE "key"=$1`, key)
return err
}
// PCacheExpire expires old entries with the given key prefix.
func (a *adapter) PCacheExpire(keyPrefix string, olderThan time.Time) error {
if keyPrefix == "" {
return t.ErrMalformed
}
ctx, cancel := a.getContext()
if cancel != nil {
defer cancel()
}
_, err := a.db.Exec(ctx, `DELETE FROM kvmeta WHERE "key" LIKE $1 AND createdat<$2`, keyPrefix+"%", olderThan)
return err
}
// Helper functions
// Check if MySQL error is a Error Code: 1062. Duplicate entry ... for key ...
@ -3577,21 +3624,6 @@ func setConnStr(c configType) (string, error) {
return connStr, nil
}
func repeatStringValues(str string, src *int, max int) string {
if max <= 0 {
return ""
}
var newString string
for max > 0 {
newString += fmt.Sprintf(str, *src)
*src += 1
max--
}
return newString
}
func expandQuery(query string, args ...interface{}) (string, []interface{}) {
var expandedArgs []interface{}
var expandedQuery string

View File

@ -223,15 +223,24 @@
// Configurations of individual adapters.
"adapters": {
// PostgreSQL configuration. See https://godoc.org/github.com/jackc/pgx#Config
// for other possible options.
"postgres": {
// PostgreSQL connection settings.
"User":"postgres",
"Passwd": "postgres",
"Host": "localhost",
"Port": "5432",
"DBName": "tinode",
// PostgreSQL connection pool settings.
// Maximum number of open connections to the database. Zero means unlimited.
"max_open_conns": 64,
// Maximum number of connections in the idle connection pool. Zero means no idle connections are retained.
"max_idle_conns": 64,
// Maximum amount of time a connection may be reused. Zero means unlimited.
"conn_max_lifetime": 60,
// Maximum amount of time waiting for a connection from the pool. Zero means no timeout.
"sql_timeout": 10
},

View File

@ -13,6 +13,9 @@ This utility initializes the `tinode` database (or upgrades an existing DB from
- **MongoDB**
`go build -tags mongodb` or `go build -i -tags mongodb` to automatically install missing dependencies.
- **PostgreSQL**
`go build -tags postgres` or `go build -i -tags postgres` to automatically install missing dependencies.
## Run
@ -48,3 +51,4 @@ Avatar photos curtesy of https://www.pexels.com/ under [CC0 license](https://www
* [RethinkDB schema](https://github.com/tinode/chat/tree/master/server/db/rethinkdb/schema.md)
* [MySQL schema](https://github.com/tinode/chat/tree/master/server/db/mysql/schema.sql)
* [MongoDB schema](https://github.com/tinode/chat/tree/master/server/db/mongodb/schema.md)
* [PostgreSQL schema](https://github.com/tinode/chat/tree/master/server/db/postgres/schema.sql)