// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package postgres
import (
"fmt"
"github.com/jmoiron/sqlx"
_ "github.com/lib/pq" // required for SQL access
migrate "github.com/rubenv/sql-migrate"
)
// Config defines the options that are used when connecting to a PostgreSQL instance
type Config struct {
Host string
Port string
User string
Pass string
Name string
SSLMode string
SSLCert string
SSLKey string
SSLRootCert string
}
// Connect creates a connection to the PostgreSQL instance and applies any
// unapplied database migrations. A non-nil error is returned to indicate
// failure.
func Connect(cfg Config) (*sqlx.DB, error) {
url := fmt.Sprintf("host=%s port=%s user=%s dbname=%s password=%s sslmode=%s sslcert=%s sslkey=%s sslrootcert=%s", cfg.Host, cfg.Port, cfg.User, cfg.Name, cfg.Pass, cfg.SSLMode, cfg.SSLCert, cfg.SSLKey, cfg.SSLRootCert)
db, err := sqlx.Open("postgres", url)
if err != nil {
return nil, err
if err := migrateDB(db); err != nil {
return db, nil
func migrateDB(db *sqlx.DB) error {
migrations := &migrate.MemoryMigrationSource{
Migrations: []*migrate.Migration{
{
Id: "configs_1",
Up: []string{
`CREATE TABLE IF NOT EXISTS configs (
mainflux_thing TEXT UNIQUE NOT NULL,
owner VARCHAR(254),
name TEXT,
mainflux_key CHAR(36) UNIQUE NOT NULL,
external_id TEXT UNIQUE NOT NULL,
external_key TEXT NOT NULL,
content TEXT,
client_cert TEXT,
client_key TEXT,
ca_cert TEXT,
state BIGINT NOT NULL,
PRIMARY KEY (mainflux_thing, owner)
)`,
`CREATE TABLE IF NOT EXISTS unknown_configs (
PRIMARY KEY (external_id, external_key)
`CREATE TABLE IF NOT EXISTS channels (
mainflux_channel TEXT UNIQUE NOT NULL,
metadata JSON,
PRIMARY KEY (mainflux_channel, owner)
`CREATE TABLE IF NOT EXISTS connections (
channel_id TEXT,
channel_owner VARCHAR(256),
config_id TEXT,
config_owner VARCHAR(256),
FOREIGN KEY (channel_id, channel_owner) REFERENCES channels (mainflux_channel, owner) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY (config_id, config_owner) REFERENCES configs (mainflux_thing, owner) ON DELETE CASCADE ON UPDATE CASCADE,
PRIMARY KEY (channel_id, channel_owner, config_id, config_owner)
},
Down: []string{
"DROP TABLE connections",
"DROP TABLE configs",
"DROP TABLE channels",
"DROP TABLE unknown_configs",
Id: "configs_2",
"DROP TABLE IF EXISTS unknown_configs",
"CREATE TABLE IF NOT EXISTS unknown_configs",
_, err := migrate.Exec(db.DB, "postgres", migrations, migrate.Up)
return err