diff --git a/.github/workflows/compliance/anonymize-license.pl b/.github/workflows/compliance/anonymize-license.pl
index 642d217f..573eba67 100755
--- a/.github/workflows/compliance/anonymize-license.pl
+++ b/.github/workflows/compliance/anonymize-license.pl
@@ -4,7 +4,7 @@ use warnings;
use strict;
use autodie qw(:all);
-if (/^ ?Copyright / || /^All rights reserved\.$/ || /^(?:The )?\S+ License(?: \(.+?\))?$/ || /^$/) {
+if (/^ ?(?:\w+ )?Copyright / || /^All rights reserved\.$/ || /^(?:The )?\S+ License(?: \(.+?\))?$/ || /^$/) {
$_ = ""
}
diff --git a/README.md b/README.md
index e7e5342e..b57623ff 100644
--- a/README.md
+++ b/README.md
@@ -13,9 +13,10 @@
## About
-Icinga DB serves as a synchronisation daemon between Icinga 2 (Redis) and Icinga Web 2 (MySQL). It synchronises configuration, state and history of an Icinga 2 environment using checksums.
+Icinga DB serves as a synchronisation daemon between Icinga 2 (Redis) and Icinga Web 2 (MySQL/MariaDB/PostgreSQL database).
+It synchronises configuration, state and history of an Icinga 2 environment using checksums.
-Icinga DB also supports reading from multiple environments and writing into a single MySQL instance.
+Icinga DB also supports reading from multiple environments and writing into a single database.
## License
diff --git a/cmd/icingadb/main.go b/cmd/icingadb/main.go
index b23c952c..06449802 100644
--- a/cmd/icingadb/main.go
+++ b/cmd/icingadb/main.go
@@ -5,6 +5,7 @@ import (
"github.com/go-redis/redis/v8"
"github.com/icinga/icingadb/internal/command"
"github.com/icinga/icingadb/pkg/common"
+ "github.com/icinga/icingadb/pkg/driver"
"github.com/icinga/icingadb/pkg/icingadb"
"github.com/icinga/icingadb/pkg/icingadb/history"
"github.com/icinga/icingadb/pkg/icingadb/overdue"
@@ -24,10 +25,11 @@ import (
)
const (
- ExitSuccess = 0
- ExitFailure = 1
- expectedRedisSchemaVersion = "4"
- expectedDbSchemaVersion = 3
+ ExitSuccess = 0
+ ExitFailure = 1
+ expectedRedisSchemaVersion = "4"
+ expectedMysqlSchemaVersion = 3
+ expectedPostgresSchemaVersion = 1
)
func main() {
@@ -317,6 +319,14 @@ func run() int {
// checkDbSchema asserts the database schema of the expected version being present.
func checkDbSchema(ctx context.Context, db *icingadb.DB) error {
+ var expectedDbSchemaVersion uint16
+ switch db.DriverName() {
+ case driver.MySQL:
+ expectedDbSchemaVersion = expectedMysqlSchemaVersion
+ case driver.PostgreSQL:
+ expectedDbSchemaVersion = expectedPostgresSchemaVersion
+ }
+
var version uint16
err := db.QueryRowxContext(ctx, "SELECT version FROM icingadb_schema ORDER BY id DESC LIMIT 1").Scan(&version)
diff --git a/config.yml.example b/config.yml.example
index 68058280..9f913ba4 100644
--- a/config.yml.example
+++ b/config.yml.example
@@ -1,6 +1,7 @@
# This is the configuration file for Icinga DB.
database:
+ type: mysql
host: localhost
port: 3306
database: icingadb
diff --git a/doc/01-About.md b/doc/01-About.md
index 29229dec..3c3e8903 100644
--- a/doc/01-About.md
+++ b/doc/01-About.md
@@ -2,6 +2,7 @@

-Icinga DB serves as a synchronisation daemon between Icinga 2 (Redis) and Icinga Web 2 (MySQL). It synchronises configuration, volatile states and history of an Icinga 2 environment using checksums.
+Icinga DB serves as a synchronisation daemon between Icinga 2 (Redis) and Icinga Web 2 (MySQL/MariaDB/PostgreSQL database).
+It synchronises configuration, volatile states and history of an Icinga 2 environment using checksums.
-Icinga DB also supports reading from multiple environments and writing into a single MySQL instance.
\ No newline at end of file
+Icinga DB also supports reading from multiple environments and writing into a single database.
diff --git a/doc/02-Installation.md b/doc/02-Installation.md
index 58712867..5bfcddfa 100644
--- a/doc/02-Installation.md
+++ b/doc/02-Installation.md
@@ -3,7 +3,7 @@
## Requirements
* Local Redis instance (Will be installed during this documentation)
-* MySQL/MariaDB database `icingadb`, user and schema imports (Will be set up during this documentation)
+* MySQL/MariaDB/PostgreSQL database `icingadb`, user and schema imports (Will be set up during this documentation)
## Setting up Icinga DB
@@ -133,7 +133,11 @@ Debian/Ubuntu:
apt-get install icingadb-redis
```
-### Setting up the MySQL database
+### Setting up the Database
+
+A MySQL/MariaDB or PostgreSQL database is required.
+
+#### MySQL/MariaDB
Note that if you're using a version of MySQL < 5.7 or MariaDB < 10.2, the following server options must be set:
@@ -159,6 +163,42 @@ After creating the database, you can import the Icinga DB schema using the follo
mysql -u root -p icingadb
+
+Set up a PostgreSQL database for Icinga DB:
+
+```
+# su -l postgres
+
+createuser -P icingadb
+createdb -E UTF8 --locale en_US.UTF-8 -T template0 -O icingadb icingadb
+psql icingadb <<<'CREATE EXTENSION IF NOT EXISTS citext;'
+```
+
+The CREATE EXTENSION command requires the postgresql-contrib package.
+(On RHEL/CentOS 7: rh-postgresql95-postgresql-contrib)
+
+Edit `pg_hba.conf`, insert the following before everything else:
+
+```
+local all icingadb md5
+host all icingadb 0.0.0.0/0 md5
+host all icingadb ::/0 md5
+```
+
+To apply those changes, run `systemctl reload postgresql`.
+(On RHEL/CentOS 7 the service is called "rh-postgresql95-postgresql".)
+
+After creating the database you can import the Icinga DB schema using the
+following command. Enter the password when asked.
+
+```
+psql -U icingadb icingadb < /usr/share/icingadb/schema/pgsql/schema.sql
+```
+
+On RHEL/CentOS 7 prefix "createuser", "createdb" and "psql" with
+"/opt/rh/rh-postgresql95/root/usr/bin/".
+
### Running Icinga DB
Foreground:
diff --git a/doc/03-Configuration.md b/doc/03-Configuration.md
index da018de9..7d9ed2e1 100644
--- a/doc/03-Configuration.md
+++ b/doc/03-Configuration.md
@@ -25,6 +25,7 @@ Configuration of the database used by Icinga DB.
Option | Description
-------------------------|-----------------------------------------------
+type | **Optional.** Either `mysql` (default) or `pgsql`.
host | **Required.** Database host or absolute Unix socket path.
port | **Required.** Database port.
database | **Required.** Database database.
diff --git a/go.mod b/go.mod
index 0b8c9efd..c7d0034a 100644
--- a/go.mod
+++ b/go.mod
@@ -10,6 +10,7 @@ require (
github.com/google/uuid v1.3.0
github.com/jessevdk/go-flags v1.5.0
github.com/jmoiron/sqlx v1.3.4
+ github.com/lib/pq v1.10.3
github.com/okzk/sdnotify v0.0.0-20180710141335-d9becc38acbd
github.com/pkg/errors v0.9.1
github.com/ssgreg/journald v1.0.0
diff --git a/go.sum b/go.sum
index d7112093..6b7637e1 100644
--- a/go.sum
+++ b/go.sum
@@ -64,8 +64,9 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
-github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/lib/pq v1.10.3 h1:v9QZf2Sn6AmjXtQeFpdoq/eaNtYP6IN+7lcrygsIAtg=
+github.com/lib/pq v1.10.3/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
diff --git a/pkg/com/entity_bulker.go b/pkg/com/entity_bulker.go
index 1ccb873b..0396ec09 100644
--- a/pkg/com/entity_bulker.go
+++ b/pkg/com/entity_bulker.go
@@ -8,6 +8,42 @@ import (
"time"
)
+// BulkChunkSplitPolicy is a state machine which tracks the items of a chunk a bulker assembles.
+// A call takes an item for the current chunk into account.
+// Output true indicates that the state machine was reset first and the bulker
+// shall finish the current chunk now (not e.g. once $size is reached) without the given item.
+type BulkChunkSplitPolicy func(contracts.Entity) bool
+
+type BulkChunkSplitPolicyFactory func() BulkChunkSplitPolicy
+
+// NeverSplit returns a pseudo state machine which never demands splitting.
+func NeverSplit() BulkChunkSplitPolicy {
+ return neverSplit
+}
+
+// SplitOnDupId returns a state machine which tracks the inputs' IDs.
+// Once an already seen input arrives, it demands splitting.
+func SplitOnDupId() BulkChunkSplitPolicy {
+ seenIds := map[string]struct{}{}
+
+ return func(entity contracts.Entity) bool {
+ id := entity.ID().String()
+
+ _, ok := seenIds[id]
+ if ok {
+ seenIds = map[string]struct{}{id: {}}
+ } else {
+ seenIds[id] = struct{}{}
+ }
+
+ return ok
+ }
+}
+
+func neverSplit(contracts.Entity) bool {
+ return false
+}
+
// EntityBulker reads all entities from a channel and streams them in chunks into a Bulk channel.
type EntityBulker struct {
ch chan []contracts.Entity
@@ -16,14 +52,16 @@ type EntityBulker struct {
}
// NewEntityBulker returns a new EntityBulker and starts streaming.
-func NewEntityBulker(ctx context.Context, ch <-chan contracts.Entity, count int) *EntityBulker {
+func NewEntityBulker(
+ ctx context.Context, ch <-chan contracts.Entity, count int, splitPolicyFactory BulkChunkSplitPolicyFactory,
+) *EntityBulker {
b := &EntityBulker{
ch: make(chan []contracts.Entity),
ctx: ctx,
mu: sync.Mutex{},
}
- go b.run(ch, count)
+ go b.run(ch, count, splitPolicyFactory)
return b
}
@@ -33,10 +71,11 @@ func (b *EntityBulker) Bulk() <-chan []contracts.Entity {
return b.ch
}
-func (b *EntityBulker) run(ch <-chan contracts.Entity, count int) {
+func (b *EntityBulker) run(ch <-chan contracts.Entity, count int, splitPolicyFactory BulkChunkSplitPolicyFactory) {
defer close(b.ch)
bufCh := make(chan contracts.Entity, count)
+ splitPolicy := splitPolicyFactory()
g, ctx := errgroup.WithContext(b.ctx)
g.Go(func() error {
@@ -71,6 +110,15 @@ func (b *EntityBulker) run(ch <-chan contracts.Entity, count int) {
break
}
+ if splitPolicy(v) {
+ if len(buf) > 0 {
+ b.ch <- buf
+ buf = make([]contracts.Entity, 0, count)
+ }
+
+ timeout = time.After(256 * time.Millisecond)
+ }
+
buf = append(buf, v)
case <-timeout:
drain = false
@@ -82,6 +130,8 @@ func (b *EntityBulker) run(ch <-chan contracts.Entity, count int) {
if len(buf) > 0 {
b.ch <- buf
}
+
+ splitPolicy = splitPolicyFactory()
}
return nil
@@ -93,16 +143,18 @@ func (b *EntityBulker) run(ch <-chan contracts.Entity, count int) {
}
// BulkEntities reads all entities from a channel and streams them in chunks into a returned channel.
-func BulkEntities(ctx context.Context, ch <-chan contracts.Entity, count int) <-chan []contracts.Entity {
+func BulkEntities(
+ ctx context.Context, ch <-chan contracts.Entity, count int, splitPolicyFactory BulkChunkSplitPolicyFactory,
+) <-chan []contracts.Entity {
if count <= 1 {
return oneEntityBulk(ctx, ch)
}
- return NewEntityBulker(ctx, ch, count).Bulk()
+ return NewEntityBulker(ctx, ch, count, splitPolicyFactory).Bulk()
}
-// oneEntityBulk operates just as NewEntityBulker(ctx, ch, 1).Bulk(),
-// but without the overhead of the actual bulk creation with a buffer channel and timeout.
+// oneEntityBulk operates just as NewEntityBulker(ctx, ch, 1, splitPolicy).Bulk(),
+// but without the overhead of the actual bulk creation with a buffer channel, timeout and BulkChunkSplitPolicy.
func oneEntityBulk(ctx context.Context, ch <-chan contracts.Entity) <-chan []contracts.Entity {
out := make(chan []contracts.Entity)
go func() {
@@ -124,3 +176,8 @@ func oneEntityBulk(ctx context.Context, ch <-chan contracts.Entity) <-chan []con
return out
}
+
+var (
+ _ BulkChunkSplitPolicyFactory = NeverSplit
+ _ BulkChunkSplitPolicyFactory = SplitOnDupId
+)
diff --git a/pkg/config/database.go b/pkg/config/database.go
index 8857425e..71c557ff 100644
--- a/pkg/config/database.go
+++ b/pkg/config/database.go
@@ -11,6 +11,8 @@ import (
"github.com/jmoiron/sqlx/reflectx"
"github.com/pkg/errors"
"net"
+ "net/url"
+ "strconv"
"sync"
"time"
)
@@ -19,6 +21,7 @@ var registerDriverOnce sync.Once
// Database defines database client configuration.
type Database struct {
+ Type string `yaml:"type" default:"mysql"`
Host string `yaml:"host"`
Port int `yaml:"port"`
Database string `yaml:"database"`
@@ -35,30 +38,74 @@ func (d *Database) Open(logger *logging.Logger) (*icingadb.DB, error) {
driver.Register(logger)
})
- config := mysql.NewConfig()
+ var dsn string
+ switch d.Type {
+ case "mysql":
+ config := mysql.NewConfig()
- config.User = d.User
- config.Passwd = d.Password
- config.Net = "tcp"
- config.Addr = net.JoinHostPort(d.Host, fmt.Sprint(d.Port))
- config.DBName = d.Database
- config.Timeout = time.Minute
+ config.User = d.User
+ config.Passwd = d.Password
+ config.Net = "tcp"
+ config.Addr = net.JoinHostPort(d.Host, fmt.Sprint(d.Port))
+ config.DBName = d.Database
+ config.Timeout = time.Minute
+ config.Params = map[string]string{"sql_mode": "ANSI_QUOTES"}
- tlsConfig, err := d.TlsOptions.MakeConfig(config.Addr)
- if err != nil {
- return nil, err
- }
-
- if tlsConfig != nil {
- config.TLSConfig = "icingadb"
- if err := mysql.RegisterTLSConfig(config.TLSConfig, tlsConfig); err != nil {
- return nil, errors.Wrap(err, "can't register TLS config")
+ tlsConfig, err := d.TlsOptions.MakeConfig(config.Addr)
+ if err != nil {
+ return nil, err
}
+
+ if tlsConfig != nil {
+ config.TLSConfig = "icingadb"
+ if err := mysql.RegisterTLSConfig(config.TLSConfig, tlsConfig); err != nil {
+ return nil, errors.Wrap(err, "can't register TLS config")
+ }
+ }
+
+ dsn = config.FormatDSN()
+ case "pgsql":
+ uri := &url.URL{
+ Scheme: "postgres",
+ User: url.UserPassword(d.User, d.Password),
+ Host: net.JoinHostPort(d.Host, strconv.FormatInt(int64(d.Port), 10)),
+ Path: "/" + url.PathEscape(d.Database),
+ }
+
+ if _, err := d.TlsOptions.MakeConfig(uri.Host); err != nil {
+ return nil, err
+ }
+
+ query := url.Values{"connect_timeout": {"60"}, "binary_parameters": {"yes"}}
+ if d.TlsOptions.Enable {
+ if d.TlsOptions.Insecure {
+ query["sslmode"] = []string{"require"}
+ } else {
+ query["sslmode"] = []string{"verify-full"}
+ }
+
+ if d.TlsOptions.Cert != "" {
+ query["sslcert"] = []string{d.TlsOptions.Cert}
+ }
+
+ if d.TlsOptions.Key != "" {
+ query["sslkey"] = []string{d.TlsOptions.Key}
+ }
+
+ if d.TlsOptions.Ca != "" {
+ query["sslrootcert"] = []string{d.TlsOptions.Ca}
+ }
+ } else {
+ query["sslmode"] = []string{"disable"}
+ }
+
+ uri.RawQuery = query.Encode()
+ dsn = uri.String()
+ default:
+ return nil, unknownDbType(d.Type)
}
- dsn := config.FormatDSN()
-
- db, err := sqlx.Open("icingadb-mysql", dsn)
+ db, err := sqlx.Open("icingadb-"+d.Type, dsn)
if err != nil {
return nil, errors.Wrap(err, "can't open database")
}
@@ -75,5 +122,15 @@ func (d *Database) Open(logger *logging.Logger) (*icingadb.DB, error) {
// Validate checks constraints in the supplied database configuration and returns an error if they are violated.
func (d *Database) Validate() error {
+ switch d.Type {
+ case "mysql", "pgsql":
+ default:
+ return unknownDbType(d.Type)
+ }
+
return d.Options.Validate()
}
+
+func unknownDbType(t string) error {
+ return errors.Errorf(`unknown database type %q, must be one of: "mysql", "pgsql"`, t)
+}
diff --git a/pkg/driver/driver.go b/pkg/driver/driver.go
index f246861e..f86e37f0 100644
--- a/pkg/driver/driver.go
+++ b/pkg/driver/driver.go
@@ -8,12 +8,16 @@ import (
"github.com/icinga/icingadb/pkg/backoff"
"github.com/icinga/icingadb/pkg/logging"
"github.com/icinga/icingadb/pkg/retry"
+ "github.com/jmoiron/sqlx"
"github.com/pkg/errors"
"go.uber.org/zap"
"syscall"
"time"
)
+const MySQL = "icingadb-mysql"
+const PostgreSQL = "icingadb-pgsql"
+
var timeout = time.Minute * 5
// RetryConnector wraps driver.Connector with retry logic.
@@ -75,10 +79,12 @@ func (d Driver) OpenConnector(name string) (driver.Connector, error) {
}, nil
}
-// Register makes our database Driver available under the name "icingadb-mysql".
+// Register makes our database Driver available under the name "icingadb-*sql".
func Register(logger *logging.Logger) {
- sql.Register("icingadb-mysql", &Driver{ctxDriver: &mysql.MySQLDriver{}, Logger: logger})
+ sql.Register(MySQL, &Driver{ctxDriver: &mysql.MySQLDriver{}, Logger: logger})
+ sql.Register(PostgreSQL, &Driver{ctxDriver: PgSQLDriver{}, Logger: logger})
_ = mysql.SetLogger(mysqlLogger(func(v ...interface{}) { logger.Debug(v...) }))
+ sqlx.BindDriver(PostgreSQL, sqlx.DOLLAR)
}
// ctxDriver helps ensure that we only support drivers that implement driver.Driver and driver.DriverContext.
diff --git a/pkg/driver/pgsql.go b/pkg/driver/pgsql.go
new file mode 100644
index 00000000..3c88fe05
--- /dev/null
+++ b/pkg/driver/pgsql.go
@@ -0,0 +1,22 @@
+package driver
+
+import (
+ "database/sql/driver"
+ "github.com/lib/pq"
+)
+
+// PgSQLDriver extends pq.Driver with driver.DriverContext compliance.
+type PgSQLDriver struct {
+ pq.Driver
+}
+
+// Assert interface compliance.
+var (
+ _ driver.Driver = PgSQLDriver{}
+ _ driver.DriverContext = PgSQLDriver{}
+)
+
+// OpenConnector implements the driver.DriverContext interface.
+func (PgSQLDriver) OpenConnector(name string) (driver.Connector, error) {
+ return pq.NewConnector(name)
+}
diff --git a/pkg/icingadb/db.go b/pkg/icingadb/db.go
index bfbf9246..500d0e39 100644
--- a/pkg/icingadb/db.go
+++ b/pkg/icingadb/db.go
@@ -2,18 +2,20 @@ package icingadb
import (
"context"
- "database/sql/driver"
+ sqlDriver "database/sql/driver"
"fmt"
"github.com/go-sql-driver/mysql"
"github.com/icinga/icingadb/internal"
"github.com/icinga/icingadb/pkg/backoff"
"github.com/icinga/icingadb/pkg/com"
"github.com/icinga/icingadb/pkg/contracts"
+ "github.com/icinga/icingadb/pkg/driver"
"github.com/icinga/icingadb/pkg/logging"
"github.com/icinga/icingadb/pkg/periodic"
"github.com/icinga/icingadb/pkg/retry"
"github.com/icinga/icingadb/pkg/utils"
"github.com/jmoiron/sqlx"
+ "github.com/lib/pq"
"github.com/pkg/errors"
"golang.org/x/sync/errgroup"
"golang.org/x/sync/semaphore"
@@ -102,7 +104,7 @@ func (db *DB) BuildColumns(subject interface{}) []string {
// BuildDeleteStmt returns a DELETE statement for the given struct.
func (db *DB) BuildDeleteStmt(from interface{}) string {
return fmt.Sprintf(
- `DELETE FROM %s WHERE id IN (?)`,
+ `DELETE FROM "%s" WHERE id IN (?)`,
utils.TableName(from),
)
}
@@ -112,7 +114,7 @@ func (db *DB) BuildInsertStmt(into interface{}) (string, int) {
columns := db.BuildColumns(into)
return fmt.Sprintf(
- `INSERT INTO %s (%s) VALUES (%s)`,
+ `INSERT INTO "%s" (%s) VALUES (%s)`,
utils.TableName(into),
strings.Join(columns, ", "),
fmt.Sprintf(":%s", strings.Join(columns, ", :")),
@@ -122,14 +124,24 @@ func (db *DB) BuildInsertStmt(into interface{}) (string, int) {
// BuildInsertIgnoreStmt returns an INSERT statement for the specified struct for
// which the database ignores rows that have already been inserted.
func (db *DB) BuildInsertIgnoreStmt(into interface{}) (string, int) {
+ table := utils.TableName(into)
columns := db.BuildColumns(into)
+ var clause string
+
+ switch db.DriverName() {
+ case driver.MySQL:
+ // MySQL treats UPDATE id = id as a no-op.
+ clause = "ON DUPLICATE KEY UPDATE id = id"
+ case driver.PostgreSQL:
+ clause = fmt.Sprintf("ON CONFLICT ON CONSTRAINT pk_%s DO NOTHING", table)
+ }
return fmt.Sprintf(
- // MySQL treats UPDATE id = id as a no-op.
- `INSERT INTO %s (%s) VALUES (%s) ON DUPLICATE KEY UPDATE id = id`,
- utils.TableName(into),
+ `INSERT INTO "%s" (%s) VALUES (%s) %s`,
+ table,
strings.Join(columns, ", "),
fmt.Sprintf(":%s", strings.Join(columns, ", :")),
+ clause,
), len(columns)
}
@@ -137,7 +149,7 @@ func (db *DB) BuildInsertIgnoreStmt(into interface{}) (string, int) {
// and the column list from the specified columns struct.
func (db *DB) BuildSelectStmt(table interface{}, columns interface{}) string {
q := fmt.Sprintf(
- `SELECT %s FROM %s`,
+ `SELECT %s FROM "%s"`,
strings.Join(db.BuildColumns(columns), ", "),
utils.TableName(table),
)
@@ -160,7 +172,7 @@ func (db *DB) BuildUpdateStmt(update interface{}) (string, int) {
}
return fmt.Sprintf(
- `UPDATE %s SET %s WHERE id = :id`,
+ `UPDATE "%s" SET %s WHERE id = :id`,
utils.TableName(update),
strings.Join(set, ", "),
), len(columns) + 1 // +1 because of WHERE id = :id
@@ -169,6 +181,7 @@ func (db *DB) BuildUpdateStmt(update interface{}) (string, int) {
// BuildUpsertStmt returns an upsert statement for the given struct.
func (db *DB) BuildUpsertStmt(subject interface{}) (stmt string, placeholders int) {
insertColumns := db.BuildColumns(subject)
+ table := utils.TableName(subject)
var updateColumns []string
if upserter, ok := subject.(contracts.Upserter); ok {
@@ -177,17 +190,28 @@ func (db *DB) BuildUpsertStmt(subject interface{}) (stmt string, placeholders in
updateColumns = insertColumns
}
+ var clause, setFormat string
+ switch db.DriverName() {
+ case driver.MySQL:
+ clause = "ON DUPLICATE KEY UPDATE"
+ setFormat = "%[1]s = VALUES(%[1]s)"
+ case driver.PostgreSQL:
+ clause = fmt.Sprintf("ON CONFLICT ON CONSTRAINT pk_%s DO UPDATE SET", table)
+ setFormat = "%[1]s = EXCLUDED.%[1]s"
+ }
+
set := make([]string, 0, len(updateColumns))
for _, col := range updateColumns {
- set = append(set, fmt.Sprintf("%s = VALUES(%s)", col, col))
+ set = append(set, fmt.Sprintf(setFormat, col))
}
return fmt.Sprintf(
- `INSERT INTO %s (%s) VALUES (%s) ON DUPLICATE KEY UPDATE %s`,
- utils.TableName(subject),
+ `INSERT INTO "%s" (%s) VALUES (%s) %s %s`,
+ table,
strings.Join(insertColumns, ","),
fmt.Sprintf(":%s", strings.Join(insertColumns, ",:")),
+ clause,
strings.Join(set, ","),
), len(insertColumns)
}
@@ -281,13 +305,13 @@ func (db *DB) BulkExec(ctx context.Context, query string, count int, sem *semaph
// Entities for which the query ran successfully will be streamed on the succeeded channel.
func (db *DB) NamedBulkExec(
ctx context.Context, query string, count int, sem *semaphore.Weighted,
- arg <-chan contracts.Entity, succeeded chan<- contracts.Entity,
+ arg <-chan contracts.Entity, succeeded chan<- contracts.Entity, splitPolicyFactory com.BulkChunkSplitPolicyFactory,
) error {
var counter com.Counter
defer db.log(ctx, query, &counter).Stop()
g, ctx := errgroup.WithContext(ctx)
- bulk := com.BulkEntities(ctx, arg, count)
+ bulk := com.BulkEntities(ctx, arg, count, splitPolicyFactory)
g.Go(func() error {
for {
@@ -355,7 +379,7 @@ func (db *DB) NamedBulkExecTx(
defer db.log(ctx, query, &counter).Stop()
g, ctx := errgroup.WithContext(ctx)
- bulk := com.BulkEntities(ctx, arg, count)
+ bulk := com.BulkEntities(ctx, arg, count, com.NeverSplit)
g.Go(func() error {
for {
@@ -478,7 +502,7 @@ func (db *DB) CreateStreamed(ctx context.Context, entities <-chan contracts.Enti
sem := db.GetSemaphoreForTable(utils.TableName(first))
stmt, placeholders := db.BuildInsertStmt(first)
- return db.NamedBulkExec(ctx, stmt, db.BatchSizeByPlaceholders(placeholders), sem, forward, nil)
+ return db.NamedBulkExec(ctx, stmt, db.BatchSizeByPlaceholders(placeholders), sem, forward, nil, com.NeverSplit)
}
// UpsertStreamed bulk upserts the specified entities via NamedBulkExec.
@@ -494,7 +518,9 @@ func (db *DB) UpsertStreamed(ctx context.Context, entities <-chan contracts.Enti
sem := db.GetSemaphoreForTable(utils.TableName(first))
stmt, placeholders := db.BuildUpsertStmt(first)
- return db.NamedBulkExec(ctx, stmt, db.BatchSizeByPlaceholders(placeholders), sem, forward, succeeded)
+ return db.NamedBulkExec(
+ ctx, stmt, db.BatchSizeByPlaceholders(placeholders), sem, forward, succeeded, com.SplitOnDupId,
+ )
}
// UpdateStreamed bulk updates the specified entities via NamedBulkExecTx.
@@ -559,7 +585,7 @@ func (db *DB) log(ctx context.Context, query string, counter *com.Counter) perio
// IsRetryable checks whether the given error is retryable.
func IsRetryable(err error) bool {
- if errors.Is(err, driver.ErrBadConn) {
+ if errors.Is(err, sqlDriver.ErrBadConn) {
return true
}
@@ -576,6 +602,35 @@ func IsRetryable(err error) bool {
// 1213: Deadlock found when trying to get lock
// 2006: MySQL server has gone away
return true
+ default:
+ return false
+ }
+ }
+
+ var pe *pq.Error
+ if errors.As(err, &pe) {
+ switch pe.Code {
+ case "08000", // connection_exception
+ "08006", // connection_failure
+ "08001", // sqlclient_unable_to_establish_sqlconnection
+ "08004", // sqlserver_rejected_establishment_of_sqlconnection
+ "40001", // serialization_failure
+ "40P01", // deadlock_detected
+ "54000", // program_limit_exceeded
+ "55006", // object_in_use
+ "55P03", // lock_not_available
+ "57P01", // admin_shutdown
+ "57P02", // crash_shutdown
+ "57P03", // cannot_connect_now
+ "58000", // system_error
+ "58030", // io_error
+ "XX000": // internal_error
+ return true
+ default:
+ if strings.HasPrefix(string(pe.Code), "53") {
+ // Class 53 - Insufficient Resources
+ return true
+ }
}
}
diff --git a/pkg/icingadb/ha.go b/pkg/icingadb/ha.go
index 1195462d..743c040a 100644
--- a/pkg/icingadb/ha.go
+++ b/pkg/icingadb/ha.go
@@ -236,8 +236,8 @@ func (h *HA) realize(ctx context.Context, s *icingaredisv1.IcingaStatus, t *type
return errors.Wrap(errBegin, "can't start transaction")
}
- query := `SELECT id, heartbeat FROM icingadb_instance` +
- ` WHERE environment_id = ? AND responsible = ? AND id != ? AND heartbeat > ?`
+ query := h.db.Rebind("SELECT id, heartbeat FROM icingadb_instance " +
+ "WHERE environment_id = ? AND responsible = ? AND id <> ? AND heartbeat > ?")
instance := &v1.IcingadbInstance{}
@@ -339,7 +339,7 @@ func (h *HA) insertEnvironment() error {
func (h *HA) removeInstance(ctx context.Context) {
h.logger.Debugw("Removing our row from icingadb_instance", zap.String("instance_id", hex.EncodeToString(h.instanceId)))
// Intentionally not using h.ctx here as it's already cancelled.
- query := "DELETE FROM icingadb_instance WHERE id = ?"
+ query := h.db.Rebind("DELETE FROM icingadb_instance WHERE id = ?")
_, err := h.db.ExecContext(ctx, query, h.instanceId)
if err != nil {
h.logger.Warnw("Could not remove instance from database", zap.Error(err), zap.String("query", query))
@@ -351,8 +351,8 @@ func (h *HA) removeOldInstances(s *icingaredisv1.IcingaStatus, envId types.Binar
case <-h.ctx.Done():
return
case <-time.After(timeout):
- query := "DELETE FROM icingadb_instance " +
- "WHERE id != ? AND environment_id = ? AND endpoint_id = ? AND heartbeat < ?"
+ query := h.db.Rebind("DELETE FROM icingadb_instance " +
+ "WHERE id <> ? AND environment_id = ? AND endpoint_id = ? AND heartbeat < ?")
heartbeat := types.UnixMilli(time.Now().Add(-timeout))
result, err := h.db.ExecContext(h.ctx, query, h.instanceId, envId,
s.EndpointId, heartbeat)
diff --git a/pkg/icingadb/runtime_updates.go b/pkg/icingadb/runtime_updates.go
index 6616d9f2..1a3ee8af 100644
--- a/pkg/icingadb/runtime_updates.go
+++ b/pkg/icingadb/runtime_updates.go
@@ -109,7 +109,7 @@ func (r *RuntimeUpdates) Sync(
sem := semaphore.NewWeighted(1)
return r.db.NamedBulkExec(
- ctx, upsertStmt, upsertCount, sem, upsertEntities, upserted,
+ ctx, upsertStmt, upsertCount, sem, upsertEntities, upserted, com.SplitOnDupId,
)
})
g.Go(func() error {
@@ -213,7 +213,7 @@ func (r *RuntimeUpdates) Sync(
sem := semaphore.NewWeighted(1)
return r.db.NamedBulkExec(
- ctx, cvStmt, cvCount, sem, customvars, upsertedCustomvars,
+ ctx, cvStmt, cvCount, sem, customvars, upsertedCustomvars, com.SplitOnDupId,
)
})
g.Go(func() error {
@@ -248,7 +248,7 @@ func (r *RuntimeUpdates) Sync(
sem := semaphore.NewWeighted(1)
return r.db.NamedBulkExec(
- ctx, cvFlatStmt, cvFlatCount, sem, flatCustomvars, upsertedFlatCustomvars,
+ ctx, cvFlatStmt, cvFlatCount, sem, flatCustomvars, upsertedFlatCustomvars, com.SplitOnDupId,
)
})
g.Go(func() error {
diff --git a/pkg/types/string.go b/pkg/types/string.go
index a3e2174d..f8ead450 100644
--- a/pkg/types/string.go
+++ b/pkg/types/string.go
@@ -7,6 +7,7 @@ import (
"encoding"
"encoding/json"
"github.com/icinga/icingadb/internal"
+ "strings"
)
// String adds JSON support to sql.NullString.
@@ -52,6 +53,17 @@ func (s *String) UnmarshalJSON(data []byte) error {
return nil
}
+// Value implements the driver.Valuer interface.
+// Supports SQL NULL.
+func (s String) Value() (driver.Value, error) {
+ if !s.Valid {
+ return nil, nil
+ }
+
+ // PostgreSQL does not allow null bytes in varchar, char and text fields.
+ return strings.ReplaceAll(s.String, "\x00", ""), nil
+}
+
// Assert interface compliance.
var (
_ json.Marshaler = String{}
diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go
index 91ed99a5..3f289080 100644
--- a/pkg/utils/utils.go
+++ b/pkg/utils/utils.go
@@ -6,6 +6,7 @@ import (
"fmt"
"github.com/go-sql-driver/mysql"
"github.com/icinga/icingadb/pkg/contracts"
+ "github.com/lib/pq"
"github.com/pkg/errors"
"golang.org/x/exp/utf8string"
"math"
@@ -125,6 +126,16 @@ func IsDeadlock(err error) bool {
switch e.Number {
case 1205, 1213:
return true
+ default:
+ return false
+ }
+ }
+
+ var pe *pq.Error
+ if errors.As(err, &pe) {
+ switch pe.Code {
+ case "40001", "40P01":
+ return true
}
}
diff --git a/schema/pgsql/schema.sql b/schema/pgsql/schema.sql
new file mode 100644
index 00000000..20b22e0c
--- /dev/null
+++ b/schema/pgsql/schema.sql
@@ -0,0 +1,1910 @@
+-- Icinga DB | (c) 2021 Icinga GmbH | GPLv2+
+
+-- Postgres in Docker: ensure CITEXT columns are available during schema import. DB user is a superuser and can do this unconditionally.
+-- Everything else: assert CITEXT columns are available during schema import. DB user isn't the superuser and can do this only if it's a no-op (`NOTICE: extension "citext" already exists, skipping`), i.e. if CITEXT columns are already available.
+CREATE EXTENSION IF NOT EXISTS citext;
+
+CREATE DOMAIN bytea20 AS bytea CONSTRAINT exactly_20_bytes_long CHECK ( VALUE IS NULL OR octet_length(VALUE) = 20 );
+CREATE DOMAIN bytea16 AS bytea CONSTRAINT exactly_16_bytes_long CHECK ( VALUE IS NULL OR octet_length(VALUE) = 16 );
+CREATE DOMAIN bytea4 AS bytea CONSTRAINT exactly_4_bytes_long CHECK ( VALUE IS NULL OR octet_length(VALUE) = 4 );
+
+CREATE DOMAIN biguint AS bigint CONSTRAINT positive CHECK ( VALUE IS NULL OR 0 <= VALUE );
+CREATE DOMAIN uint AS bigint CONSTRAINT between_0_and_4294967295 CHECK ( VALUE IS NULL OR VALUE BETWEEN 0 AND 4294967295 );
+CREATE DOMAIN smalluint AS int CONSTRAINT between_0_and_65535 CHECK ( VALUE IS NULL OR VALUE BETWEEN 0 AND 65535 );
+CREATE DOMAIN tinyuint AS smallint CONSTRAINT between_0_and_255 CHECK ( VALUE IS NULL OR VALUE BETWEEN 0 AND 255 );
+
+CREATE TYPE boolenum AS ENUM ( 'n', 'y' );
+CREATE TYPE acked AS ENUM ( 'n', 'y', 'sticky' );
+CREATE TYPE state_type AS ENUM ( 'hard', 'soft' );
+CREATE TYPE checkable_type AS ENUM ( 'host', 'service' );
+CREATE TYPE comment_type AS ENUM ( 'comment', 'ack' );
+CREATE TYPE notification_type AS ENUM ( 'downtime_start', 'downtime_end', 'downtime_removed', 'custom', 'acknowledgement', 'problem', 'recovery', 'flapping_start', 'flapping_end' );
+CREATE TYPE history_type AS ENUM ( 'notification', 'state_change', 'downtime_start', 'downtime_end', 'comment_add', 'comment_remove', 'flapping_start', 'flapping_end', 'ack_set', 'ack_clear' );
+
+CREATE TABLE host (
+ id bytea20 NOT NULL,
+ environment_id bytea20 NOT NULL,
+ name_checksum bytea20 NOT NULL,
+ properties_checksum bytea20 NOT NULL,
+
+ name varchar(255) NOT NULL,
+ name_ci citext NOT NULL,
+ display_name citext NOT NULL,
+
+ address varchar(255) NOT NULL,
+ address6 varchar(255) NOT NULL,
+ address_bin bytea4 DEFAULT NULL,
+ address6_bin bytea16 DEFAULT NULL,
+
+ checkcommand citext NOT NULL,
+ checkcommand_id bytea20 NOT NULL,
+
+ max_check_attempts uint NOT NULL,
+
+ check_timeperiod citext NOT NULL,
+ check_timeperiod_id bytea20 DEFAULT NULL,
+
+ check_timeout uint DEFAULT NULL,
+ check_interval uint NOT NULL,
+ check_retry_interval uint NOT NULL,
+
+ active_checks_enabled boolenum NOT NULL DEFAULT 'n',
+ passive_checks_enabled boolenum NOT NULL DEFAULT 'n',
+ event_handler_enabled boolenum NOT NULL DEFAULT 'n',
+ notifications_enabled boolenum NOT NULL DEFAULT 'n',
+
+ flapping_enabled boolenum NOT NULL DEFAULT 'n',
+ flapping_threshold_low float NOT NULL,
+ flapping_threshold_high float NOT NULL,
+
+ perfdata_enabled boolenum NOT NULL DEFAULT 'n',
+
+ eventcommand citext NOT NULL,
+ eventcommand_id bytea20 DEFAULT NULL,
+
+ is_volatile boolenum NOT NULL DEFAULT 'n',
+
+ action_url_id bytea20 DEFAULT NULL,
+ notes_url_id bytea20 DEFAULT NULL,
+ notes text NOT NULL,
+ icon_image_id bytea20 DEFAULT NULL,
+ icon_image_alt varchar(32) NOT NULL,
+
+ zone citext NOT NULL,
+ zone_id bytea20 DEFAULT NULL,
+
+ command_endpoint citext NOT NULL,
+ command_endpoint_id bytea20 DEFAULT NULL,
+
+ CONSTRAINT pk_host PRIMARY KEY (id)
+);
+
+ALTER TABLE host ALTER COLUMN id SET STORAGE PLAIN;
+ALTER TABLE host ALTER COLUMN environment_id SET STORAGE PLAIN;
+ALTER TABLE host ALTER COLUMN name_checksum SET STORAGE PLAIN;
+ALTER TABLE host ALTER COLUMN properties_checksum SET STORAGE PLAIN;
+ALTER TABLE host ALTER COLUMN address_bin SET STORAGE PLAIN;
+ALTER TABLE host ALTER COLUMN address6_bin SET STORAGE PLAIN;
+ALTER TABLE host ALTER COLUMN checkcommand_id SET STORAGE PLAIN;
+ALTER TABLE host ALTER COLUMN check_timeperiod_id SET STORAGE PLAIN;
+ALTER TABLE host ALTER COLUMN eventcommand_id SET STORAGE PLAIN;
+ALTER TABLE host ALTER COLUMN action_url_id SET STORAGE PLAIN;
+ALTER TABLE host ALTER COLUMN notes_url_id SET STORAGE PLAIN;
+ALTER TABLE host ALTER COLUMN icon_image_id SET STORAGE PLAIN;
+ALTER TABLE host ALTER COLUMN zone_id SET STORAGE PLAIN;
+ALTER TABLE host ALTER COLUMN command_endpoint_id SET STORAGE PLAIN;
+
+CREATE INDEX idx_action_url_checksum ON host(action_url_id);
+CREATE INDEX idx_notes_url_checksum ON host(notes_url_id);
+CREATE INDEX idx_icon_image_checksum ON host(icon_image_id);
+CREATE INDEX idx_host_display_name ON host(display_name);
+CREATE INDEX idx_host_name_ci ON host(name_ci);
+CREATE INDEX idx_host_name ON host(name);
+
+COMMENT ON COLUMN host.id IS 'sha1(environment.id + name)';
+COMMENT ON COLUMN host.environment_id IS 'environment.id';
+COMMENT ON COLUMN host.name_checksum IS 'sha1(name)';
+COMMENT ON COLUMN host.properties_checksum IS 'sha1(all properties)';
+COMMENT ON COLUMN host.checkcommand IS 'checkcommand.name';
+COMMENT ON COLUMN host.checkcommand_id IS 'checkcommand.id';
+COMMENT ON COLUMN host.check_timeperiod IS 'timeperiod.name';
+COMMENT ON COLUMN host.check_timeperiod_id IS 'timeperiod.id';
+COMMENT ON COLUMN host.eventcommand IS 'eventcommand.name';
+COMMENT ON COLUMN host.eventcommand_id IS 'eventcommand.id';
+COMMENT ON COLUMN host.action_url_id IS 'action_url.id';
+COMMENT ON COLUMN host.notes_url_id IS 'notes_url.id';
+COMMENT ON COLUMN host.icon_image_id IS 'icon_image.id';
+COMMENT ON COLUMN host.zone IS 'zone.name';
+COMMENT ON COLUMN host.zone_id IS 'zone.id';
+COMMENT ON COLUMN host.command_endpoint IS 'endpoint.name';
+COMMENT ON COLUMN host.command_endpoint_id IS 'endpoint.id';
+
+COMMENT ON INDEX idx_action_url_checksum IS 'cleanup';
+COMMENT ON INDEX idx_notes_url_checksum IS 'cleanup';
+COMMENT ON INDEX idx_icon_image_checksum IS 'cleanup';
+COMMENT ON INDEX idx_host_display_name IS 'Host list filtered/ordered by display_name';
+COMMENT ON INDEX idx_host_name_ci IS 'Host list filtered using quick search';
+COMMENT ON INDEX idx_host_name IS 'Host list filtered/ordered by name; Host detail filter';
+
+CREATE TABLE hostgroup (
+ id bytea20 NOT NULL,
+ environment_id bytea20 NOT NULL,
+ name_checksum bytea20 NOT NULL,
+ properties_checksum bytea20 NOT NULL,
+
+ name varchar(255) NOT NULL,
+ name_ci citext NOT NULL,
+ display_name citext NOT NULL,
+
+ zone_id bytea20 DEFAULT NULL,
+
+ CONSTRAINT pk_hostgroup PRIMARY KEY (id)
+);
+
+ALTER TABLE hostgroup ALTER COLUMN id SET STORAGE PLAIN;
+ALTER TABLE hostgroup ALTER COLUMN environment_id SET STORAGE PLAIN;
+ALTER TABLE hostgroup ALTER COLUMN name_checksum SET STORAGE PLAIN;
+ALTER TABLE hostgroup ALTER COLUMN properties_checksum SET STORAGE PLAIN;
+ALTER TABLE hostgroup ALTER COLUMN zone_id SET STORAGE PLAIN;
+
+CREATE INDEX idx_hostgroup_name ON hostgroup(name);
+
+COMMENT ON COLUMN hostgroup.id IS 'sha1(environment.id + name)';
+COMMENT ON COLUMN hostgroup.environment_id IS 'environment.id';
+COMMENT ON COLUMN hostgroup.name_checksum IS 'sha1(name)';
+COMMENT ON COLUMN hostgroup.properties_checksum IS 'sha1(all properties)';
+COMMENT ON COLUMN hostgroup.zone_id IS 'zone.id';
+
+COMMENT ON INDEX idx_hostgroup_name IS 'Host/service/host group list filtered by host group name';
+
+CREATE TABLE hostgroup_member (
+ id bytea20 NOT NULL,
+ environment_id bytea20 NOT NULL,
+ host_id bytea20 NOT NULL,
+ hostgroup_id bytea20 NOT NULL,
+
+ CONSTRAINT pk_hostgroup_member PRIMARY KEY (id)
+);
+
+ALTER TABLE hostgroup_member ALTER COLUMN id SET STORAGE PLAIN;
+ALTER TABLE hostgroup_member ALTER COLUMN environment_id SET STORAGE PLAIN;
+ALTER TABLE hostgroup_member ALTER COLUMN host_id SET STORAGE PLAIN;
+ALTER TABLE hostgroup_member ALTER COLUMN hostgroup_id SET STORAGE PLAIN;
+
+CREATE INDEX idx_hostgroup_member_host_id ON hostgroup_member(host_id, hostgroup_id);
+CREATE INDEX idx_hostgroup_member_hostgroup_id ON hostgroup_member(hostgroup_id, host_id);
+
+COMMENT ON COLUMN hostgroup_member.id IS 'sha1(environment.id + host_id + hostgroup_id)';
+COMMENT ON COLUMN hostgroup_member.environment_id IS 'environment.id';
+COMMENT ON COLUMN hostgroup_member.host_id IS 'host.id';
+COMMENT ON COLUMN hostgroup_member.hostgroup_id IS 'hostgroup.id';
+
+CREATE TABLE host_customvar (
+ id bytea20 NOT NULL,
+ environment_id bytea20 NOT NULL,
+ host_id bytea20 NOT NULL,
+ customvar_id bytea20 NOT NULL,
+
+ CONSTRAINT pk_host_customvar PRIMARY KEY (id)
+);
+
+ALTER TABLE host_customvar ALTER COLUMN id SET STORAGE PLAIN;
+ALTER TABLE host_customvar ALTER COLUMN environment_id SET STORAGE PLAIN;
+ALTER TABLE host_customvar ALTER COLUMN host_id SET STORAGE PLAIN;
+ALTER TABLE host_customvar ALTER COLUMN customvar_id SET STORAGE PLAIN;
+
+CREATE INDEX idx_host_customvar_host_id ON host_customvar(host_id, customvar_id);
+CREATE INDEX idx_host_customvar_customvar_id ON host_customvar(customvar_id, host_id);
+
+COMMENT ON COLUMN host_customvar.id IS 'sha1(environment.id + host_id + customvar_id)';
+COMMENT ON COLUMN host_customvar.environment_id IS 'environment.id';
+COMMENT ON COLUMN host_customvar.host_id IS 'host.id';
+COMMENT ON COLUMN host_customvar.customvar_id IS 'customvar.id';
+
+CREATE TABLE hostgroup_customvar (
+ id bytea20 NOT NULL,
+ environment_id bytea20 NOT NULL,
+ hostgroup_id bytea20 NOT NULL,
+ customvar_id bytea20 NOT NULL,
+
+ CONSTRAINT pk_hostgroup_customvar PRIMARY KEY (id)
+);
+
+ALTER TABLE hostgroup_customvar ALTER COLUMN id SET STORAGE PLAIN;
+ALTER TABLE hostgroup_customvar ALTER COLUMN environment_id SET STORAGE PLAIN;
+ALTER TABLE hostgroup_customvar ALTER COLUMN hostgroup_id SET STORAGE PLAIN;
+ALTER TABLE hostgroup_customvar ALTER COLUMN customvar_id SET STORAGE PLAIN;
+
+CREATE INDEX idx_hostgroup_customvar_hostgroup_id ON hostgroup_customvar(hostgroup_id, customvar_id);
+CREATE INDEX idx_hostgroup_customvar_customvar_id ON hostgroup_customvar(customvar_id, hostgroup_id);
+
+COMMENT ON COLUMN hostgroup_customvar.id IS 'sha1(environment.id + hostgroup_id + customvar_id)';
+COMMENT ON COLUMN hostgroup_customvar.environment_id IS 'environment.id';
+COMMENT ON COLUMN hostgroup_customvar.hostgroup_id IS 'hostgroup.id';
+COMMENT ON COLUMN hostgroup_customvar.customvar_id IS 'customvar.id';
+
+CREATE TABLE host_state (
+ id bytea20 NOT NULL,
+ host_id bytea20 NOT NULL,
+ environment_id bytea20 NOT NULL,
+ properties_checksum bytea20 NOT NULL,
+
+ state_type state_type NOT NULL DEFAULT 'hard',
+ soft_state tinyuint NOT NULL,
+ hard_state tinyuint NOT NULL,
+ previous_soft_state tinyuint NOT NULL,
+ previous_hard_state tinyuint NOT NULL,
+ attempt tinyuint NOT NULL,
+ severity smalluint NOT NULL,
+
+ output text DEFAULT NULL,
+ long_output text DEFAULT NULL,
+ performance_data text DEFAULT NULL,
+ normalized_performance_data text DEFAULT NULL,
+
+ check_commandline text DEFAULT NULL,
+
+ is_problem boolenum NOT NULL DEFAULT 'n',
+ is_handled boolenum NOT NULL DEFAULT 'n',
+ is_reachable boolenum NOT NULL DEFAULT 'n',
+ is_flapping boolenum NOT NULL DEFAULT 'n',
+ is_overdue boolenum NOT NULL DEFAULT 'n',
+
+ is_acknowledged acked NOT NULL DEFAULT 'n',
+ acknowledgement_comment_id bytea20 DEFAULT NULL,
+ last_comment_id bytea20 DEFAULT NULL,
+
+ in_downtime boolenum NOT NULL DEFAULT 'n',
+
+ execution_time uint DEFAULT NULL,
+ latency uint DEFAULT NULL,
+ timeout uint DEFAULT NULL,
+ check_source text DEFAULT NULL,
+ scheduling_source text DEFAULT NULL,
+
+ last_update biguint DEFAULT NULL,
+ last_state_change biguint NOT NULL,
+ next_check biguint NOT NULL,
+ next_update biguint NOT NULL,
+
+ CONSTRAINT pk_host_state PRIMARY KEY (id)
+);
+
+ALTER TABLE host_state ALTER COLUMN id SET STORAGE PLAIN;
+ALTER TABLE host_state ALTER COLUMN host_id SET STORAGE PLAIN;
+ALTER TABLE host_state ALTER COLUMN environment_id SET STORAGE PLAIN;
+ALTER TABLE host_state ALTER COLUMN properties_checksum SET STORAGE PLAIN;
+ALTER TABLE host_state ALTER COLUMN acknowledgement_comment_id SET STORAGE PLAIN;
+ALTER TABLE host_state ALTER COLUMN last_comment_id SET STORAGE PLAIN;
+
+CREATE UNIQUE INDEX idx_host_state_host_id ON host_state(host_id);
+CREATE INDEX idx_host_state_is_problem ON host_state(is_problem, severity);
+CREATE INDEX idx_host_state_severity ON host_state(severity);
+CREATE INDEX idx_host_state_soft_state ON host_state(soft_state, last_state_change);
+CREATE INDEX idx_host_state_last_state_change ON host_state(last_state_change);
+
+COMMENT ON COLUMN host_state.id IS 'host.id';
+COMMENT ON COLUMN host_state.host_id IS 'host.id';
+COMMENT ON COLUMN host_state.environment_id IS 'environment.id';
+COMMENT ON COLUMN host_state.properties_checksum IS 'sha1(all properties)';
+COMMENT ON COLUMN host_state.acknowledgement_comment_id IS 'comment.id';
+COMMENT ON COLUMN host_state.last_comment_id IS 'comment.id';
+
+COMMENT ON INDEX idx_host_state_is_problem IS 'Host list filtered by is_problem ordered by severity';
+COMMENT ON INDEX idx_host_state_severity IS 'Host list filtered/ordered by severity';
+COMMENT ON INDEX idx_host_state_soft_state IS 'Host list filtered/ordered by soft_state; recently recovered filter';
+COMMENT ON INDEX idx_host_state_last_state_change IS 'Host list filtered/ordered by last_state_change';
+
+CREATE TABLE service (
+ id bytea20 NOT NULL,
+ environment_id bytea20 NOT NULL,
+ name_checksum bytea20 NOT NULL,
+ properties_checksum bytea20 NOT NULL,
+ host_id bytea20 NOT NULL,
+
+ name varchar(255) NOT NULL,
+ name_ci citext NOT NULL,
+ display_name citext NOT NULL,
+
+ checkcommand citext NOT NULL,
+ checkcommand_id bytea20 NOT NULL,
+
+ max_check_attempts uint NOT NULL,
+
+ check_timeperiod citext NOT NULL,
+ check_timeperiod_id bytea20 DEFAULT NULL,
+
+ check_timeout uint DEFAULT NULL,
+ check_interval uint NOT NULL,
+ check_retry_interval uint NOT NULL,
+
+ active_checks_enabled boolenum NOT NULL DEFAULT 'n',
+ passive_checks_enabled boolenum NOT NULL DEFAULT 'n',
+ event_handler_enabled boolenum NOT NULL DEFAULT 'n',
+ notifications_enabled boolenum NOT NULL DEFAULT 'n',
+
+ flapping_enabled boolenum NOT NULL DEFAULT 'n',
+ flapping_threshold_low float NOT NULL,
+ flapping_threshold_high float NOT NULL,
+
+ perfdata_enabled boolenum NOT NULL DEFAULT 'n',
+
+ eventcommand citext NOT NULL,
+ eventcommand_id bytea20 DEFAULT NULL,
+
+ is_volatile boolenum NOT NULL DEFAULT 'n',
+
+ action_url_id bytea20 DEFAULT NULL,
+ notes_url_id bytea20 DEFAULT NULL,
+ notes text NOT NULL,
+ icon_image_id bytea20 DEFAULT NULL,
+ icon_image_alt varchar(32) NOT NULL,
+
+ zone citext NOT NULL,
+ zone_id bytea20 DEFAULT NULL,
+
+ command_endpoint citext NOT NULL,
+ command_endpoint_id bytea20 DEFAULT NULL,
+
+ CONSTRAINT pk_service PRIMARY KEY (id)
+);
+
+ALTER TABLE service ALTER COLUMN id SET STORAGE PLAIN;
+ALTER TABLE service ALTER COLUMN environment_id SET STORAGE PLAIN;
+ALTER TABLE service ALTER COLUMN name_checksum SET STORAGE PLAIN;
+ALTER TABLE service ALTER COLUMN properties_checksum SET STORAGE PLAIN;
+ALTER TABLE service ALTER COLUMN host_id SET STORAGE PLAIN;
+ALTER TABLE service ALTER COLUMN checkcommand_id SET STORAGE PLAIN;
+ALTER TABLE service ALTER COLUMN check_timeperiod_id SET STORAGE PLAIN;
+ALTER TABLE service ALTER COLUMN eventcommand_id SET STORAGE PLAIN;
+ALTER TABLE service ALTER COLUMN action_url_id SET STORAGE PLAIN;
+ALTER TABLE service ALTER COLUMN notes_url_id SET STORAGE PLAIN;
+ALTER TABLE service ALTER COLUMN icon_image_id SET STORAGE PLAIN;
+ALTER TABLE service ALTER COLUMN zone_id SET STORAGE PLAIN;
+ALTER TABLE service ALTER COLUMN command_endpoint_id SET STORAGE PLAIN;
+
+CREATE INDEX idx_service_display_name ON service(display_name);
+CREATE INDEX idx_service_host_id ON service(host_id, display_name);
+CREATE INDEX idx_service_name_ci ON service(name_ci);
+CREATE INDEX idx_service_name ON service(name);
+
+COMMENT ON COLUMN service.id IS 'sha1(environment.id + name)';
+COMMENT ON COLUMN service.environment_id IS 'environment.id';
+COMMENT ON COLUMN service.name_checksum IS 'sha1(name)';
+COMMENT ON COLUMN service.properties_checksum IS 'sha1(all properties)';
+COMMENT ON COLUMN service.host_id IS 'sha1(host.id)';
+COMMENT ON COLUMN service.checkcommand IS 'checkcommand.name';
+COMMENT ON COLUMN service.checkcommand_id IS 'checkcommand.id';
+COMMENT ON COLUMN service.check_timeperiod IS 'timeperiod.name';
+COMMENT ON COLUMN service.check_timeperiod_id IS 'timeperiod.id';
+COMMENT ON COLUMN service.eventcommand IS 'eventcommand.name';
+COMMENT ON COLUMN service.eventcommand_id IS 'eventcommand.id';
+COMMENT ON COLUMN service.action_url_id IS 'action_url.id';
+COMMENT ON COLUMN service.notes_url_id IS 'notes_url.id';
+COMMENT ON COLUMN service.icon_image_id IS 'icon_image.id';
+COMMENT ON COLUMN service.zone IS 'zone.name';
+COMMENT ON COLUMN service.zone_id IS 'zone.id';
+COMMENT ON COLUMN service.command_endpoint IS 'endpoint.name';
+COMMENT ON COLUMN service.command_endpoint_id IS 'endpoint.id';
+
+COMMENT ON INDEX idx_service_display_name IS 'Service list filtered/ordered by display_name';
+COMMENT ON INDEX idx_service_host_id IS 'Service list filtered by host and ordered by display_name';
+COMMENT ON INDEX idx_service_name_ci IS 'Service list filtered using quick search';
+COMMENT ON INDEX idx_service_name IS 'Service list filtered/ordered by name; Service detail filter';
+
+CREATE TABLE servicegroup (
+ id bytea20 NOT NULL,
+ environment_id bytea20 NOT NULL,
+ name_checksum bytea20 NOT NULL,
+ properties_checksum bytea20 NOT NULL,
+
+ name varchar(255) NOT NULL,
+ name_ci citext NOT NULL,
+ display_name citext NOT NULL,
+
+ zone_id bytea20 DEFAULT NULL,
+
+ CONSTRAINT pk_servicegroup PRIMARY KEY (id)
+);
+
+ALTER TABLE servicegroup ALTER COLUMN id SET STORAGE PLAIN;
+ALTER TABLE servicegroup ALTER COLUMN environment_id SET STORAGE PLAIN;
+ALTER TABLE servicegroup ALTER COLUMN name_checksum SET STORAGE PLAIN;
+ALTER TABLE servicegroup ALTER COLUMN properties_checksum SET STORAGE PLAIN;
+ALTER TABLE servicegroup ALTER COLUMN zone_id SET STORAGE PLAIN;
+
+COMMENT ON COLUMN servicegroup.id IS 'sha1(environment.id + name)';
+COMMENT ON COLUMN servicegroup.environment_id IS 'environment.id';
+COMMENT ON COLUMN servicegroup.name_checksum IS 'sha1(name)';
+COMMENT ON COLUMN servicegroup.properties_checksum IS 'sha1(all properties)';
+COMMENT ON COLUMN servicegroup.zone_id IS 'zone.id';
+
+CREATE INDEX idx_servicegroup_name ON servicegroup(name);
+COMMENT ON INDEX idx_servicegroup_name IS 'Host/service/service group list filtered by service group name';
+
+CREATE TABLE servicegroup_member (
+ id bytea20 NOT NULL,
+ environment_id bytea20 NOT NULL,
+ service_id bytea20 NOT NULL,
+ servicegroup_id bytea20 NOT NULL,
+
+ CONSTRAINT pk_servicegroup_member PRIMARY KEY (id)
+);
+
+ALTER TABLE servicegroup_member ALTER COLUMN id SET STORAGE PLAIN;
+ALTER TABLE servicegroup_member ALTER COLUMN environment_id SET STORAGE PLAIN;
+ALTER TABLE servicegroup_member ALTER COLUMN service_id SET STORAGE PLAIN;
+ALTER TABLE servicegroup_member ALTER COLUMN servicegroup_id SET STORAGE PLAIN;
+
+CREATE INDEX idx_servicegroup_member_service_id ON servicegroup_member(service_id, servicegroup_id);
+CREATE INDEX idx_servicegroup_member_servicegroup_id ON servicegroup_member(servicegroup_id, service_id);
+
+COMMENT ON COLUMN servicegroup_member.id IS 'sha1(environment.id + servicegroup_id + service_id)';
+COMMENT ON COLUMN servicegroup_member.environment_id IS 'environment.id';
+COMMENT ON COLUMN servicegroup_member.service_id IS 'service.id';
+COMMENT ON COLUMN servicegroup_member.servicegroup_id IS 'servicegroup.id';
+
+CREATE TABLE service_customvar (
+ id bytea20 NOT NULL,
+ environment_id bytea20 NOT NULL,
+ service_id bytea20 NOT NULL,
+ customvar_id bytea20 NOT NULL,
+
+ CONSTRAINT pk_service_customvar PRIMARY KEY (id)
+);
+
+ALTER TABLE service_customvar ALTER COLUMN id SET STORAGE PLAIN;
+ALTER TABLE service_customvar ALTER COLUMN environment_id SET STORAGE PLAIN;
+ALTER TABLE service_customvar ALTER COLUMN service_id SET STORAGE PLAIN;
+ALTER TABLE service_customvar ALTER COLUMN customvar_id SET STORAGE PLAIN;
+
+CREATE INDEX idx_service_customvar_service_id ON service_customvar(service_id, customvar_id);
+CREATE INDEX idx_service_customvar_customvar_id ON service_customvar(customvar_id, service_id);
+
+COMMENT ON COLUMN service_customvar.id IS 'sha1(environment.id + service_id + customvar_id)';
+COMMENT ON COLUMN service_customvar.environment_id IS 'environment.id';
+COMMENT ON COLUMN service_customvar.service_id IS 'service.id';
+COMMENT ON COLUMN service_customvar.customvar_id IS 'customvar.id';
+
+CREATE TABLE servicegroup_customvar (
+ id bytea20 NOT NULL,
+ environment_id bytea20 NOT NULL,
+ servicegroup_id bytea20 NOT NULL,
+ customvar_id bytea20 NOT NULL,
+
+ CONSTRAINT pk_servicegroup_customvar PRIMARY KEY (id)
+);
+
+ALTER TABLE servicegroup_customvar ALTER COLUMN id SET STORAGE PLAIN;
+ALTER TABLE servicegroup_customvar ALTER COLUMN environment_id SET STORAGE PLAIN;
+ALTER TABLE servicegroup_customvar ALTER COLUMN servicegroup_id SET STORAGE PLAIN;
+ALTER TABLE servicegroup_customvar ALTER COLUMN customvar_id SET STORAGE PLAIN;
+
+CREATE INDEX idx_servicegroup_customvar_servicegroup_id ON servicegroup_customvar(servicegroup_id, customvar_id);
+CREATE INDEX idx_servicegroup_customvar_customvar_id ON servicegroup_customvar(customvar_id, servicegroup_id);
+
+COMMENT ON COLUMN servicegroup_customvar.id IS 'sha1(environment.id + servicegroup_id + customvar_id)';
+COMMENT ON COLUMN servicegroup_customvar.environment_id IS 'environment.id';
+COMMENT ON COLUMN servicegroup_customvar.servicegroup_id IS 'servicegroup.id';
+COMMENT ON COLUMN servicegroup_customvar.customvar_id IS 'customvar.id';
+
+CREATE TABLE service_state (
+ id bytea20 NOT NULL,
+ host_id bytea20 NOT NULL,
+ service_id bytea20 NOT NULL,
+ environment_id bytea20 NOT NULL,
+ properties_checksum bytea20 NOT NULL,
+
+ state_type state_type NOT NULL DEFAULT 'hard',
+ soft_state tinyuint NOT NULL,
+ hard_state tinyuint NOT NULL,
+ previous_soft_state tinyuint NOT NULL,
+ previous_hard_state tinyuint NOT NULL,
+ attempt tinyuint NOT NULL,
+ severity smalluint NOT NULL,
+
+ output text DEFAULT NULL,
+ long_output text DEFAULT NULL,
+ performance_data text DEFAULT NULL,
+ normalized_performance_data text DEFAULT NULL,
+
+ check_commandline text DEFAULT NULL,
+
+ is_problem boolenum NOT NULL DEFAULT 'n',
+ is_handled boolenum NOT NULL DEFAULT 'n',
+ is_reachable boolenum NOT NULL DEFAULT 'n',
+ is_flapping boolenum NOT NULL DEFAULT 'n',
+ is_overdue boolenum NOT NULL DEFAULT 'n',
+
+ is_acknowledged acked NOT NULL DEFAULT 'n',
+ acknowledgement_comment_id bytea20 DEFAULT NULL,
+ last_comment_id bytea20 DEFAULT NULL,
+
+ in_downtime boolenum NOT NULL DEFAULT 'n',
+
+ execution_time uint DEFAULT NULL,
+ latency uint DEFAULT NULL,
+ timeout uint DEFAULT NULL,
+ check_source text DEFAULT NULL,
+ scheduling_source text DEFAULT NULL,
+
+ last_update biguint DEFAULT NULL,
+ last_state_change biguint NOT NULL,
+ next_check biguint NOT NULL,
+ next_update biguint NOT NULL,
+
+ CONSTRAINT pk_service_state PRIMARY KEY (id)
+);
+
+ALTER TABLE service_state ALTER COLUMN id SET STORAGE PLAIN;
+ALTER TABLE service_state ALTER COLUMN host_id SET STORAGE PLAIN;
+ALTER TABLE service_state ALTER COLUMN service_id SET STORAGE PLAIN;
+ALTER TABLE service_state ALTER COLUMN environment_id SET STORAGE PLAIN;
+ALTER TABLE service_state ALTER COLUMN properties_checksum SET STORAGE PLAIN;
+ALTER TABLE service_state ALTER COLUMN acknowledgement_comment_id SET STORAGE PLAIN;
+ALTER TABLE service_state ALTER COLUMN last_comment_id SET STORAGE PLAIN;
+
+CREATE UNIQUE INDEX idx_service_state_service_id ON service_state(service_id);
+CREATE INDEX idx_service_state_is_problem ON service_state(is_problem, severity);
+CREATE INDEX idx_service_state_severity ON service_state(severity);
+CREATE INDEX idx_service_state_soft_state ON service_state(soft_state, last_state_change);
+CREATE INDEX idx_service_state_last_state_change ON service_state(last_state_change);
+
+COMMENT ON COLUMN service_state.id IS 'service.id';
+COMMENT ON COLUMN service_state.host_id IS 'host.id';
+COMMENT ON COLUMN service_state.service_id IS 'service.id';
+COMMENT ON COLUMN service_state.environment_id IS 'environment.id';
+COMMENT ON COLUMN service_state.properties_checksum IS 'sha1(all properties)';
+COMMENT ON COLUMN service_state.acknowledgement_comment_id IS 'comment.id';
+COMMENT ON COLUMN service_state.last_comment_id IS 'comment.id';
+
+COMMENT ON INDEX idx_service_state_is_problem IS 'Service list filtered by is_problem ordered by severity';
+COMMENT ON INDEX idx_service_state_severity IS 'Service list filtered/ordered by severity';
+COMMENT ON INDEX idx_service_state_soft_state IS 'Service list filtered/ordered by soft_state; recently recovered filter';
+COMMENT ON INDEX idx_service_state_last_state_change IS 'Service list filtered/ordered by last_state_change';
+
+CREATE TABLE endpoint (
+ id bytea20 NOT NULL,
+ environment_id bytea20 NOT NULL,
+ name_checksum bytea20 NOT NULL,
+ properties_checksum bytea20 NOT NULL,
+
+ name varchar(255) NOT NULL,
+ name_ci citext NOT NULL,
+
+ zone_id bytea20 NOT NULL,
+
+ CONSTRAINT pk_endpoint PRIMARY KEY (id)
+);
+
+ALTER TABLE endpoint ALTER COLUMN id SET STORAGE PLAIN;
+ALTER TABLE endpoint ALTER COLUMN environment_id SET STORAGE PLAIN;
+ALTER TABLE endpoint ALTER COLUMN name_checksum SET STORAGE PLAIN;
+ALTER TABLE endpoint ALTER COLUMN properties_checksum SET STORAGE PLAIN;
+ALTER TABLE endpoint ALTER COLUMN zone_id SET STORAGE PLAIN;
+
+COMMENT ON COLUMN endpoint.id IS 'sha1(environment.id + name)';
+COMMENT ON COLUMN endpoint.environment_id IS 'environment.id';
+COMMENT ON COLUMN endpoint.name_checksum IS 'sha1(name)';
+COMMENT ON COLUMN endpoint.zone_id IS 'zone.id';
+
+CREATE TABLE environment (
+ id bytea20 NOT NULL,
+ name varchar(255) NOT NULL,
+
+ CONSTRAINT pk_environment PRIMARY KEY (id)
+);
+
+ALTER TABLE environment ALTER COLUMN id SET STORAGE PLAIN;
+
+COMMENT ON COLUMN environment.id IS 'sha1(Icinga CA public key)';
+
+CREATE TABLE icingadb_instance (
+ id bytea16 NOT NULL,
+ environment_id bytea20 NOT NULL,
+ endpoint_id bytea20 DEFAULT NULL,
+ heartbeat biguint NOT NULL,
+ responsible boolenum NOT NULL DEFAULT 'n',
+
+ icinga2_version varchar(255) NOT NULL,
+ icinga2_start_time biguint NOT NULL,
+ icinga2_notifications_enabled boolenum NOT NULL DEFAULT 'n',
+ icinga2_active_service_checks_enabled boolenum NOT NULL DEFAULT 'n',
+ icinga2_active_host_checks_enabled boolenum NOT NULL DEFAULT 'n',
+ icinga2_event_handlers_enabled boolenum NOT NULL DEFAULT 'n',
+ icinga2_flap_detection_enabled boolenum NOT NULL DEFAULT 'n',
+ icinga2_performance_data_enabled boolenum NOT NULL DEFAULT 'n',
+
+ CONSTRAINT pk_icingadb_instance PRIMARY KEY (id)
+);
+
+ALTER TABLE icingadb_instance ALTER COLUMN id SET STORAGE PLAIN;
+ALTER TABLE icingadb_instance ALTER COLUMN environment_id SET STORAGE PLAIN;
+ALTER TABLE icingadb_instance ALTER COLUMN endpoint_id SET STORAGE PLAIN;
+
+COMMENT ON COLUMN icingadb_instance.id IS 'UUIDv4';
+COMMENT ON COLUMN icingadb_instance.environment_id IS 'environment.id';
+COMMENT ON COLUMN icingadb_instance.endpoint_id IS 'endpoint.id';
+COMMENT ON COLUMN icingadb_instance.heartbeat IS '*nix timestamp';
+
+CREATE TABLE checkcommand (
+ id bytea20 NOT NULL,
+ environment_id bytea20 NOT NULL,
+ zone_id bytea20 DEFAULT NULL,
+
+ name_checksum bytea20 NOT NULL,
+ properties_checksum bytea20 NOT NULL,
+
+ name varchar(255) NOT NULL,
+ name_ci citext NOT NULL,
+ command text NOT NULL,
+ timeout uint NOT NULL,
+
+ CONSTRAINT pk_checkcommand PRIMARY KEY (id)
+);
+
+ALTER TABLE checkcommand ALTER COLUMN id SET STORAGE PLAIN;
+ALTER TABLE checkcommand ALTER COLUMN environment_id SET STORAGE PLAIN;
+ALTER TABLE checkcommand ALTER COLUMN zone_id SET STORAGE PLAIN;
+ALTER TABLE checkcommand ALTER COLUMN name_checksum SET STORAGE PLAIN;
+ALTER TABLE checkcommand ALTER COLUMN properties_checksum SET STORAGE PLAIN;
+
+COMMENT ON COLUMN checkcommand.id IS 'sha1(environment.id + type + name)';
+COMMENT ON COLUMN checkcommand.environment_id IS 'env.id';
+COMMENT ON COLUMN checkcommand.zone_id IS 'zone.id';
+COMMENT ON COLUMN checkcommand.name_checksum IS 'sha1(name)';
+COMMENT ON COLUMN checkcommand.properties_checksum IS 'sha1(all properties)';
+
+CREATE TABLE checkcommand_argument (
+ id bytea20 NOT NULL,
+ environment_id bytea20 NOT NULL,
+ checkcommand_id bytea20 NOT NULL,
+ argument_key varchar(64) NOT NULL,
+
+ properties_checksum bytea20 NOT NULL,
+
+ argument_value text DEFAULT NULL,
+ argument_order smallint DEFAULT NULL,
+ description text DEFAULT NULL,
+ argument_key_override citext DEFAULT NULL,
+ repeat_key boolenum NOT NULL DEFAULT 'n',
+ required boolenum NOT NULL DEFAULT 'n',
+ set_if varchar(255) DEFAULT NULL,
+ skip_key boolenum NOT NULL DEFAULT 'n',
+
+ CONSTRAINT pk_checkcommand_argument PRIMARY KEY (id)
+);
+
+ALTER TABLE checkcommand_argument ALTER COLUMN id SET STORAGE PLAIN;
+ALTER TABLE checkcommand_argument ALTER COLUMN environment_id SET STORAGE PLAIN;
+ALTER TABLE checkcommand_argument ALTER COLUMN checkcommand_id SET STORAGE PLAIN;
+ALTER TABLE checkcommand_argument ALTER COLUMN argument_key SET STORAGE PLAIN;
+ALTER TABLE checkcommand_argument ALTER COLUMN properties_checksum SET STORAGE PLAIN;
+
+COMMENT ON COLUMN checkcommand_argument.id IS 'sha1(environment.id + checkcommand_id + argument_key)';
+COMMENT ON COLUMN checkcommand_argument.environment_id IS 'env.id';
+COMMENT ON COLUMN checkcommand_argument.checkcommand_id IS 'checkcommand.id';
+COMMENT ON COLUMN checkcommand_argument.properties_checksum IS 'sha1(all properties)';
+
+CREATE TABLE checkcommand_envvar (
+ id bytea20 NOT NULL,
+ environment_id bytea20 NOT NULL,
+ checkcommand_id bytea20 NOT NULL,
+ envvar_key varchar(64) NOT NULL,
+
+ properties_checksum bytea20 NOT NULL,
+
+ envvar_value text NOT NULL,
+
+ CONSTRAINT pk_checkcommand_envvar PRIMARY KEY (id)
+);
+
+ALTER TABLE checkcommand_envvar ALTER COLUMN id SET STORAGE PLAIN;
+ALTER TABLE checkcommand_envvar ALTER COLUMN environment_id SET STORAGE PLAIN;
+ALTER TABLE checkcommand_envvar ALTER COLUMN checkcommand_id SET STORAGE PLAIN;
+ALTER TABLE checkcommand_envvar ALTER COLUMN properties_checksum SET STORAGE PLAIN;
+
+COMMENT ON COLUMN checkcommand_envvar.id IS 'sha1(environment.id + checkcommand_id + envvar_key)';
+COMMENT ON COLUMN checkcommand_envvar.environment_id IS 'env.id';
+COMMENT ON COLUMN checkcommand_envvar.checkcommand_id IS 'checkcommand.id';
+COMMENT ON COLUMN checkcommand_envvar.properties_checksum IS 'sha1(all properties)';
+
+CREATE TABLE checkcommand_customvar (
+ id bytea20 NOT NULL,
+ environment_id bytea20 NOT NULL,
+
+ checkcommand_id bytea20 NOT NULL,
+ customvar_id bytea20 NOT NULL,
+
+ CONSTRAINT pk_checkcommand_customvar PRIMARY KEY (id)
+);
+
+ALTER TABLE checkcommand_customvar ALTER COLUMN id SET STORAGE PLAIN;
+ALTER TABLE checkcommand_customvar ALTER COLUMN environment_id SET STORAGE PLAIN;
+ALTER TABLE checkcommand_customvar ALTER COLUMN checkcommand_id SET STORAGE PLAIN;
+ALTER TABLE checkcommand_customvar ALTER COLUMN customvar_id SET STORAGE PLAIN;
+
+CREATE INDEX idx_checkcommand_customvar_checkcommand_id ON checkcommand_customvar(checkcommand_id, customvar_id);
+CREATE INDEX idx_checkcommand_customvar_customvar_id ON checkcommand_customvar(customvar_id, checkcommand_id);
+
+COMMENT ON COLUMN checkcommand_customvar.id IS 'sha1(environment.id + checkcommand_id + customvar_id)';
+COMMENT ON COLUMN checkcommand_customvar.environment_id IS 'environment.id';
+COMMENT ON COLUMN checkcommand_customvar.checkcommand_id IS 'checkcommand.id';
+COMMENT ON COLUMN checkcommand_customvar.customvar_id IS 'customvar.id';
+
+CREATE TABLE eventcommand (
+ id bytea20 NOT NULL,
+ environment_id bytea20 NOT NULL,
+ zone_id bytea20 DEFAULT NULL,
+
+ name_checksum bytea20 NOT NULL,
+ properties_checksum bytea20 NOT NULL,
+
+ name varchar(255) NOT NULL,
+ name_ci citext NOT NULL,
+ command text NOT NULL,
+ timeout smalluint NOT NULL,
+
+ CONSTRAINT pk_eventcommand PRIMARY KEY (id)
+);
+
+ALTER TABLE eventcommand ALTER COLUMN id SET STORAGE PLAIN;
+ALTER TABLE eventcommand ALTER COLUMN environment_id SET STORAGE PLAIN;
+ALTER TABLE eventcommand ALTER COLUMN zone_id SET STORAGE PLAIN;
+ALTER TABLE eventcommand ALTER COLUMN name_checksum SET STORAGE PLAIN;
+ALTER TABLE eventcommand ALTER COLUMN properties_checksum SET STORAGE PLAIN;
+
+COMMENT ON COLUMN eventcommand.id IS 'sha1(environment.id + type + name)';
+COMMENT ON COLUMN eventcommand.environment_id IS 'env.id';
+COMMENT ON COLUMN eventcommand.zone_id IS 'zone.id';
+COMMENT ON COLUMN eventcommand.name_checksum IS 'sha1(name)';
+COMMENT ON COLUMN eventcommand.properties_checksum IS 'sha1(all properties)';
+
+CREATE TABLE eventcommand_argument (
+ id bytea20 NOT NULL,
+ environment_id bytea20 NOT NULL,
+ eventcommand_id bytea20 NOT NULL,
+ argument_key varchar(64) NOT NULL,
+
+ properties_checksum bytea20 NOT NULL,
+
+ argument_value text DEFAULT NULL,
+ argument_order smallint DEFAULT NULL,
+ description text DEFAULT NULL,
+ argument_key_override citext DEFAULT NULL,
+ repeat_key boolenum NOT NULL DEFAULT 'n',
+ required boolenum NOT NULL DEFAULT 'n',
+ set_if varchar(255) DEFAULT NULL,
+ skip_key boolenum NOT NULL DEFAULT 'n',
+
+ CONSTRAINT pk_eventcommand_argument PRIMARY KEY (id)
+);
+
+ALTER TABLE eventcommand_argument ALTER COLUMN id SET STORAGE PLAIN;
+ALTER TABLE eventcommand_argument ALTER COLUMN environment_id SET STORAGE PLAIN;
+ALTER TABLE eventcommand_argument ALTER COLUMN eventcommand_id SET STORAGE PLAIN;
+ALTER TABLE eventcommand_argument ALTER COLUMN properties_checksum SET STORAGE PLAIN;
+
+COMMENT ON COLUMN eventcommand_argument.id IS 'sha1(environment.id + eventcommand_id + argument_key)';
+COMMENT ON COLUMN eventcommand_argument.environment_id IS 'env.id';
+COMMENT ON COLUMN eventcommand_argument.eventcommand_id IS 'eventcommand.id';
+COMMENT ON COLUMN eventcommand_argument.properties_checksum IS 'sha1(all properties)';
+
+CREATE TABLE eventcommand_envvar (
+ id bytea20 NOT NULL,
+ environment_id bytea20 NOT NULL,
+ eventcommand_id bytea20 NOT NULL,
+ envvar_key varchar(64) NOT NULL,
+
+ properties_checksum bytea20 NOT NULL,
+
+ envvar_value text NOT NULL,
+
+ CONSTRAINT pk_eventcommand_envvar PRIMARY KEY (id)
+);
+
+ALTER TABLE eventcommand_envvar ALTER COLUMN id SET STORAGE PLAIN;
+ALTER TABLE eventcommand_envvar ALTER COLUMN environment_id SET STORAGE PLAIN;
+ALTER TABLE eventcommand_envvar ALTER COLUMN eventcommand_id SET STORAGE PLAIN;
+ALTER TABLE eventcommand_envvar ALTER COLUMN properties_checksum SET STORAGE PLAIN;
+
+COMMENT ON COLUMN eventcommand_envvar.id IS 'sha1(environment.id + eventcommand_id + envvar_key)';
+COMMENT ON COLUMN eventcommand_envvar.environment_id IS 'env.id';
+COMMENT ON COLUMN eventcommand_envvar.eventcommand_id IS 'eventcommand.id';
+COMMENT ON COLUMN eventcommand_envvar.properties_checksum IS 'sha1(all properties)';
+
+CREATE TABLE eventcommand_customvar (
+ id bytea20 NOT NULL,
+ environment_id bytea20 NOT NULL,
+ eventcommand_id bytea20 NOT NULL,
+ customvar_id bytea20 NOT NULL,
+
+ CONSTRAINT pk_eventcommand_customvar PRIMARY KEY (id)
+);
+
+ALTER TABLE eventcommand_customvar ALTER COLUMN id SET STORAGE PLAIN;
+ALTER TABLE eventcommand_customvar ALTER COLUMN environment_id SET STORAGE PLAIN;
+ALTER TABLE eventcommand_customvar ALTER COLUMN eventcommand_id SET STORAGE PLAIN;
+ALTER TABLE eventcommand_customvar ALTER COLUMN customvar_id SET STORAGE PLAIN;
+
+CREATE INDEX idx_eventcommand_customvar_eventcommand_id ON eventcommand_customvar(eventcommand_id, customvar_id);
+CREATE INDEX idx_eventcommand_customvar_customvar_id ON eventcommand_customvar(customvar_id, eventcommand_id);
+
+COMMENT ON COLUMN eventcommand_customvar.id IS 'sha1(environment.id + eventcommand_id + customvar_id)';
+COMMENT ON COLUMN eventcommand_customvar.environment_id IS 'environment.id';
+COMMENT ON COLUMN eventcommand_customvar.eventcommand_id IS 'eventcommand.id';
+COMMENT ON COLUMN eventcommand_customvar.customvar_id IS 'customvar.id';
+
+CREATE TABLE notificationcommand (
+ id bytea20 NOT NULL,
+ environment_id bytea20 NOT NULL,
+ zone_id bytea20 DEFAULT NULL,
+
+ name_checksum bytea20 NOT NULL,
+ properties_checksum bytea20 NOT NULL,
+
+ name varchar(255) NOT NULL,
+ name_ci citext NOT NULL,
+ command text NOT NULL,
+ timeout smalluint NOT NULL,
+
+ CONSTRAINT pk_notificationcommand PRIMARY KEY (id)
+);
+
+ALTER TABLE notificationcommand ALTER COLUMN id SET STORAGE PLAIN;
+ALTER TABLE notificationcommand ALTER COLUMN environment_id SET STORAGE PLAIN;
+ALTER TABLE notificationcommand ALTER COLUMN zone_id SET STORAGE PLAIN;
+ALTER TABLE notificationcommand ALTER COLUMN name_checksum SET STORAGE PLAIN;
+ALTER TABLE notificationcommand ALTER COLUMN properties_checksum SET STORAGE PLAIN;
+
+COMMENT ON COLUMN notificationcommand.id IS 'sha1(environment.id + type + name)';
+COMMENT ON COLUMN notificationcommand.environment_id IS 'env.id';
+COMMENT ON COLUMN notificationcommand.zone_id IS 'zone.id';
+COMMENT ON COLUMN notificationcommand.name_checksum IS 'sha1(name)';
+COMMENT ON COLUMN notificationcommand.properties_checksum IS 'sha1(all properties)';
+
+CREATE TABLE notificationcommand_argument (
+ id bytea20 NOT NULL,
+ environment_id bytea20 NOT NULL,
+ notificationcommand_id bytea20 NOT NULL,
+ argument_key varchar(64) NOT NULL,
+
+ properties_checksum bytea20 NOT NULL,
+
+ argument_value text DEFAULT NULL,
+ argument_order smallint DEFAULT NULL,
+ description text DEFAULT NULL,
+ argument_key_override citext DEFAULT NULL,
+ repeat_key boolenum NOT NULL DEFAULT 'n',
+ required boolenum NOT NULL DEFAULT 'n',
+ set_if varchar(255) DEFAULT NULL,
+ skip_key boolenum NOT NULL DEFAULT 'n',
+
+ CONSTRAINT pk_notificationcommand_argument PRIMARY KEY (id)
+);
+
+ALTER TABLE notificationcommand_argument ALTER COLUMN id SET STORAGE PLAIN;
+ALTER TABLE notificationcommand_argument ALTER COLUMN environment_id SET STORAGE PLAIN;
+ALTER TABLE notificationcommand_argument ALTER COLUMN notificationcommand_id SET STORAGE PLAIN;
+ALTER TABLE notificationcommand_argument ALTER COLUMN properties_checksum SET STORAGE PLAIN;
+
+COMMENT ON COLUMN notificationcommand_argument.id IS 'sha1(environment.id + notificationcommand_id + argument_key)';
+COMMENT ON COLUMN notificationcommand_argument.environment_id IS 'env.id';
+COMMENT ON COLUMN notificationcommand_argument.notificationcommand_id IS 'notificationcommand.id';
+COMMENT ON COLUMN notificationcommand_argument.properties_checksum IS 'sha1(all properties)';
+
+CREATE TABLE notificationcommand_envvar (
+ id bytea20 NOT NULL,
+ environment_id bytea20 NOT NULL,
+ notificationcommand_id bytea20 NOT NULL,
+ envvar_key varchar(64) NOT NULL,
+
+ properties_checksum bytea20 NOT NULL,
+
+ envvar_value text NOT NULL,
+
+ CONSTRAINT pk_notificationcommand_envvar PRIMARY KEY (id)
+);
+
+ALTER TABLE notificationcommand_envvar ALTER COLUMN id SET STORAGE PLAIN;
+ALTER TABLE notificationcommand_envvar ALTER COLUMN environment_id SET STORAGE PLAIN;
+ALTER TABLE notificationcommand_envvar ALTER COLUMN notificationcommand_id SET STORAGE PLAIN;
+ALTER TABLE notificationcommand_envvar ALTER COLUMN properties_checksum SET STORAGE PLAIN;
+
+COMMENT ON COLUMN notificationcommand_envvar.id IS 'sha1(environment.id + notificationcommand_id + envvar_key)';
+COMMENT ON COLUMN notificationcommand_envvar.environment_id IS 'env.id';
+COMMENT ON COLUMN notificationcommand_envvar.notificationcommand_id IS 'notificationcommand.id';
+COMMENT ON COLUMN notificationcommand_envvar.properties_checksum IS 'sha1(all properties)';
+
+CREATE TABLE notificationcommand_customvar (
+ id bytea20 NOT NULL,
+ environment_id bytea20 NOT NULL,
+ notificationcommand_id bytea20 NOT NULL,
+ customvar_id bytea20 NOT NULL,
+
+ CONSTRAINT pk_notificationcommand_customvar PRIMARY KEY (id)
+);
+
+ALTER TABLE notificationcommand_customvar ALTER COLUMN id SET STORAGE PLAIN;
+ALTER TABLE notificationcommand_customvar ALTER COLUMN environment_id SET STORAGE PLAIN;
+ALTER TABLE notificationcommand_customvar ALTER COLUMN notificationcommand_id SET STORAGE PLAIN;
+ALTER TABLE notificationcommand_customvar ALTER COLUMN customvar_id SET STORAGE PLAIN;
+
+CREATE INDEX idx_notificationcommand_customvar_notificationcommand_id ON notificationcommand_customvar(notificationcommand_id, customvar_id);
+CREATE INDEX idx_notificationcommand_customvar_customvar_id ON notificationcommand_customvar(customvar_id, notificationcommand_id);
+
+COMMENT ON COLUMN notificationcommand_customvar.id IS 'sha1(environment.id + notificationcommand_id + customvar_id)';
+COMMENT ON COLUMN notificationcommand_customvar.environment_id IS 'environment.id';
+COMMENT ON COLUMN notificationcommand_customvar.notificationcommand_id IS 'notificationcommand.id';
+COMMENT ON COLUMN notificationcommand_customvar.customvar_id IS 'customvar.id';
+
+CREATE TABLE comment (
+ id bytea20 NOT NULL,
+ environment_id bytea20 NOT NULL,
+
+ object_type checkable_type NOT NULL DEFAULT 'host',
+ host_id bytea20 NOT NULL,
+ service_id bytea20 DEFAULT NULL,
+
+ name_checksum bytea20 NOT NULL,
+ properties_checksum bytea20 NOT NULL,
+ name varchar(548) NOT NULL,
+
+ author citext NOT NULL,
+ text text NOT NULL,
+ entry_type comment_type NOT NULL DEFAULT 'comment',
+ entry_time biguint NOT NULL,
+ is_persistent boolenum NOT NULL DEFAULT 'n',
+ is_sticky boolenum NOT NULL DEFAULT 'n',
+ expire_time biguint DEFAULT NULL,
+
+ zone_id bytea20 DEFAULT NULL,
+
+ CONSTRAINT pk_comment PRIMARY KEY (id)
+);
+
+ALTER TABLE comment ALTER COLUMN id SET STORAGE PLAIN;
+ALTER TABLE comment ALTER COLUMN environment_id SET STORAGE PLAIN;
+ALTER TABLE comment ALTER COLUMN host_id SET STORAGE PLAIN;
+ALTER TABLE comment ALTER COLUMN service_id SET STORAGE PLAIN;
+ALTER TABLE comment ALTER COLUMN name_checksum SET STORAGE PLAIN;
+ALTER TABLE comment ALTER COLUMN properties_checksum SET STORAGE PLAIN;
+ALTER TABLE comment ALTER COLUMN zone_id SET STORAGE PLAIN;
+
+CREATE INDEX idx_comment_name ON comment(name);
+CREATE INDEX idx_comment_entry_time ON comment(entry_time);
+CREATE INDEX idx_comment_author ON comment(author);
+CREATE INDEX idx_comment_expire_time ON comment(expire_time);
+
+COMMENT ON COLUMN comment.id IS 'sha1(environment.id + name)';
+COMMENT ON COLUMN comment.environment_id IS 'environment.id';
+COMMENT ON COLUMN comment.host_id IS 'host.id';
+COMMENT ON COLUMN comment.service_id IS 'service.id';
+COMMENT ON COLUMN comment.name_checksum IS 'sha1(name)';
+COMMENT ON COLUMN comment.name IS '255+1+255+1+36, i.e. "host.name!service.name!UUID"';
+COMMENT ON COLUMN comment.zone_id IS 'zone.id';
+
+COMMENT ON INDEX idx_comment_name IS 'Comment detail filter';
+COMMENT ON INDEX idx_comment_entry_time IS 'Comment list fileted/ordered by entry_time';
+COMMENT ON INDEX idx_comment_author IS 'Comment list filtered/ordered by author';
+COMMENT ON INDEX idx_comment_expire_time IS 'Comment list filtered/ordered by expire_time';
+
+CREATE TABLE downtime (
+ id bytea20 NOT NULL,
+ environment_id bytea20 NOT NULL,
+
+ triggered_by_id bytea20 DEFAULT NULL,
+ parent_id bytea20 DEFAULT NULL,
+ object_type checkable_type NOT NULL DEFAULT 'host',
+ host_id bytea20 NOT NULL,
+ service_id bytea20 DEFAULT NULL,
+
+ name_checksum bytea20 NOT NULL,
+ properties_checksum bytea20 NOT NULL,
+ name varchar(548) NOT NULL,
+
+ author citext NOT NULL,
+ comment text NOT NULL,
+ entry_time biguint NOT NULL,
+ scheduled_start_time biguint NOT NULL,
+ scheduled_end_time biguint NOT NULL,
+ scheduled_duration biguint NOT NULL,
+ is_flexible boolenum NOT NULL DEFAULT 'n',
+ flexible_duration biguint NOT NULL,
+
+ is_in_effect boolenum NOT NULL DEFAULT 'n',
+ start_time biguint DEFAULT NULL,
+ end_time biguint DEFAULT NULL,
+ duration biguint NOT NULL,
+ scheduled_by varchar(767) DEFAULT NULL,
+
+ zone_id bytea20 DEFAULT NULL,
+
+ CONSTRAINT pk_downtime PRIMARY KEY (id)
+);
+
+ALTER TABLE downtime ALTER COLUMN id SET STORAGE PLAIN;
+ALTER TABLE downtime ALTER COLUMN environment_id SET STORAGE PLAIN;
+ALTER TABLE downtime ALTER COLUMN triggered_by_id SET STORAGE PLAIN;
+ALTER TABLE downtime ALTER COLUMN parent_id SET STORAGE PLAIN;
+ALTER TABLE downtime ALTER COLUMN host_id SET STORAGE PLAIN;
+ALTER TABLE downtime ALTER COLUMN service_id SET STORAGE PLAIN;
+ALTER TABLE downtime ALTER COLUMN name_checksum SET STORAGE PLAIN;
+ALTER TABLE downtime ALTER COLUMN properties_checksum SET STORAGE PLAIN;
+ALTER TABLE downtime ALTER COLUMN zone_id SET STORAGE PLAIN;
+
+CREATE INDEX idx_downtime_is_in_effect ON downtime(is_in_effect, start_time);
+CREATE INDEX idx_downtime_name ON downtime(name);
+CREATE INDEX idx_downtime_entry_time ON downtime(entry_time);
+CREATE INDEX idx_downtime_start_time ON downtime(start_time);
+CREATE INDEX idx_downtime_end_time ON downtime(end_time);
+CREATE INDEX idx_downtime_scheduled_start_time ON downtime(scheduled_start_time);
+CREATE INDEX idx_downtime_scheduled_end_time ON downtime(scheduled_end_time);
+CREATE INDEX idx_downtime_author ON downtime(author);
+CREATE INDEX idx_downtime_duration ON downtime(duration);
+
+COMMENT ON COLUMN downtime.id IS 'sha1(environment.id + name)';
+COMMENT ON COLUMN downtime.environment_id IS 'environment.id';
+COMMENT ON COLUMN downtime.triggered_by_id IS 'The ID of the downtime that triggered this downtime. This is set when creating downtimes on a host or service higher up in the dependency chain using the "child_option" "DowntimeTriggeredChildren" and can also be set manually via the API.';
+COMMENT ON COLUMN downtime.parent_id IS 'For service downtimes, the ID of the host downtime that created this downtime by using the "all_services" flag of the schedule-downtime API.';
+COMMENT ON COLUMN downtime.host_id IS 'host.id';
+COMMENT ON COLUMN downtime.service_id IS 'service.id';
+COMMENT ON COLUMN downtime.name_checksum IS 'sha1(name)';
+COMMENT ON COLUMN downtime.name IS '255+1+255+1+36, i.e. "host.name!service.name!UUID"';
+COMMENT ON COLUMN downtime.properties_checksum IS 'sha1(all properties)';
+COMMENT ON COLUMN downtime.start_time IS 'Time when the host went into a problem state during the downtimes timeframe';
+COMMENT ON COLUMN downtime.end_time IS 'Problem state assumed: scheduled_end_time if fixed, start_time + flexible_duration otherwise';
+COMMENT ON COLUMN downtime.duration IS 'Duration of the downtime: When the downtime is flexible, this is the same as flexible_duration otherwise scheduled_duration';
+COMMENT ON COLUMN downtime.scheduled_by IS 'Name of the ScheduledDowntime which created this Downtime. 255+1+255+1+255, i.e. "host.name!service.name!scheduled-downtime-name"';
+COMMENT ON COLUMN downtime.zone_id IS 'zone.id';
+
+COMMENT ON INDEX idx_downtime_is_in_effect IS 'Downtime list filtered/ordered by severity';
+COMMENT ON INDEX idx_downtime_name IS 'Downtime detail filter';
+COMMENT ON INDEX idx_downtime_entry_time IS 'Downtime list filtered/ordered by entry_time';
+COMMENT ON INDEX idx_downtime_start_time IS 'Downtime list filtered/ordered by start_time';
+COMMENT ON INDEX idx_downtime_end_time IS 'Downtime list filtered/ordered by end_time';
+COMMENT ON INDEX idx_downtime_scheduled_start_time IS 'Downtime list filtered/ordered by scheduled_start_time';
+COMMENT ON INDEX idx_downtime_scheduled_end_time IS 'Downtime list filtered/ordered by scheduled_end_time';
+COMMENT ON INDEX idx_downtime_author IS 'Downtime list filtered/ordered by author';
+COMMENT ON INDEX idx_downtime_duration IS 'Downtime list filtered/ordered by duration';
+
+CREATE TABLE notification (
+ id bytea20 NOT NULL,
+ environment_id bytea20 NOT NULL,
+ name_checksum bytea20 NOT NULL,
+ properties_checksum bytea20 NOT NULL,
+
+ name varchar(255) NOT NULL,
+ name_ci citext NOT NULL,
+
+ host_id bytea20 NOT NULL,
+ service_id bytea20 DEFAULT NULL,
+ notificationcommand_id bytea20 NOT NULL,
+
+ times_begin uint DEFAULT NULL,
+ times_end uint DEFAULT NULL,
+ notification_interval uint NOT NULL,
+ timeperiod_id bytea20 DEFAULT NULL,
+
+ states tinyuint NOT NULL,
+ types smalluint NOT NULL,
+
+ zone_id bytea20 DEFAULT NULL,
+
+ CONSTRAINT pk_notification PRIMARY KEY (id)
+);
+
+ALTER TABLE notification ALTER COLUMN id SET STORAGE PLAIN;
+ALTER TABLE notification ALTER COLUMN environment_id SET STORAGE PLAIN;
+ALTER TABLE notification ALTER COLUMN name_checksum SET STORAGE PLAIN;
+ALTER TABLE notification ALTER COLUMN properties_checksum SET STORAGE PLAIN;
+ALTER TABLE notification ALTER COLUMN host_id SET STORAGE PLAIN;
+ALTER TABLE notification ALTER COLUMN service_id SET STORAGE PLAIN;
+ALTER TABLE notification ALTER COLUMN notificationcommand_id SET STORAGE PLAIN;
+ALTER TABLE notification ALTER COLUMN timeperiod_id SET STORAGE PLAIN;
+ALTER TABLE notification ALTER COLUMN zone_id SET STORAGE PLAIN;
+
+CREATE INDEX idx_notification_host_id ON notification(host_id);
+CREATE INDEX idx_notification_service_id ON notification(service_id);
+
+COMMENT ON COLUMN notification.id IS 'sha1(environment.id + name)';
+COMMENT ON COLUMN notification.environment_id IS 'environment.id';
+COMMENT ON COLUMN notification.name_checksum IS 'sha1(name)';
+COMMENT ON COLUMN notification.host_id IS 'host.id';
+COMMENT ON COLUMN notification.service_id IS 'service.id';
+COMMENT ON COLUMN notification.notificationcommand_id IS 'command.id';
+COMMENT ON COLUMN notification.timeperiod_id IS 'timeperiod.id';
+COMMENT ON COLUMN notification.zone_id IS 'zone.id';
+
+CREATE TABLE notification_user (
+ id bytea20 NOT NULL,
+ environment_id bytea20 NOT NULL,
+ notification_id bytea20 NOT NULL,
+ user_id bytea20 NOT NULL,
+
+ CONSTRAINT pk_notification_user PRIMARY KEY (id)
+);
+
+ALTER TABLE notification_user ALTER COLUMN id SET STORAGE PLAIN;
+ALTER TABLE notification_user ALTER COLUMN environment_id SET STORAGE PLAIN;
+ALTER TABLE notification_user ALTER COLUMN notification_id SET STORAGE PLAIN;
+ALTER TABLE notification_user ALTER COLUMN user_id SET STORAGE PLAIN;
+
+CREATE INDEX idx_notification_user_user_id ON notification_user(user_id, notification_id);
+CREATE INDEX idx_notification_user_notification_id ON notification_user(notification_id, user_id);
+
+COMMENT ON COLUMN notification_user.id IS 'sha1(environment.id + notification_id + user_id)';
+COMMENT ON COLUMN notification_user.environment_id IS 'environment.id';
+COMMENT ON COLUMN notification_user.notification_id IS 'notification.id';
+COMMENT ON COLUMN notification_user.user_id IS 'user.id';
+
+CREATE TABLE notification_usergroup (
+ id bytea20 NOT NULL,
+ environment_id bytea20 NOT NULL,
+ notification_id bytea20 NOT NULL,
+ usergroup_id bytea20 NOT NULL,
+
+ CONSTRAINT pk_notification_usergroup PRIMARY KEY (id)
+);
+
+ALTER TABLE notification_usergroup ALTER COLUMN id SET STORAGE PLAIN;
+ALTER TABLE notification_usergroup ALTER COLUMN environment_id SET STORAGE PLAIN;
+ALTER TABLE notification_usergroup ALTER COLUMN notification_id SET STORAGE PLAIN;
+ALTER TABLE notification_usergroup ALTER COLUMN usergroup_id SET STORAGE PLAIN;
+
+CREATE INDEX idx_notification_usergroup_usergroup_id ON notification_usergroup(usergroup_id, notification_id);
+CREATE INDEX idx_notification_usergroup_notification_id ON notification_usergroup(notification_id, usergroup_id);
+
+COMMENT ON COLUMN notification_usergroup.id IS 'sha1(environment.id + notification_id + usergroup_id)';
+COMMENT ON COLUMN notification_usergroup.environment_id IS 'environment.id';
+COMMENT ON COLUMN notification_usergroup.notification_id IS 'notification.id';
+COMMENT ON COLUMN notification_usergroup.usergroup_id IS 'usergroup.id';
+
+CREATE TABLE notification_recipient (
+ id bytea20 NOT NULL,
+ environment_id bytea20 NOT NULL,
+ notification_id bytea20 NOT NULL,
+ user_id bytea20 NULL,
+ usergroup_id bytea20 NULL,
+
+ CONSTRAINT pk_notification_recipient PRIMARY KEY (id)
+);
+
+ALTER TABLE notification_recipient ALTER COLUMN id SET STORAGE PLAIN;
+ALTER TABLE notification_recipient ALTER COLUMN environment_id SET STORAGE PLAIN;
+ALTER TABLE notification_recipient ALTER COLUMN notification_id SET STORAGE PLAIN;
+ALTER TABLE notification_recipient ALTER COLUMN user_id SET STORAGE PLAIN;
+ALTER TABLE notification_recipient ALTER COLUMN usergroup_id SET STORAGE PLAIN;
+
+CREATE INDEX idx_notification_recipient_user_id ON notification_recipient(user_id, notification_id);
+CREATE INDEX idx_notification_recipient_notification_id_user ON notification_recipient(notification_id, user_id);
+CREATE INDEX idx_notification_recipient_usergroup_id ON notification_recipient(usergroup_id, notification_id);
+CREATE INDEX idx_notification_recipient_notification_id_usergroup ON notification_recipient(notification_id, usergroup_id);
+
+COMMENT ON COLUMN notification_recipient.id IS 'sha1(environment.id + notification_id + (user_id | usergroup_id))';
+COMMENT ON COLUMN notification_recipient.environment_id IS 'environment.id';
+COMMENT ON COLUMN notification_recipient.notification_id IS 'notification.id';
+COMMENT ON COLUMN notification_recipient.user_id IS 'user.id';
+COMMENT ON COLUMN notification_recipient.usergroup_id IS 'usergroup.id';
+
+CREATE TABLE notification_customvar (
+ id bytea20 NOT NULL,
+ environment_id bytea20 NOT NULL,
+ notification_id bytea20 NOT NULL,
+ customvar_id bytea20 NOT NULL,
+
+ CONSTRAINT pk_notification_customvar PRIMARY KEY (id)
+);
+
+ALTER TABLE notification_customvar ALTER COLUMN id SET STORAGE PLAIN;
+ALTER TABLE notification_customvar ALTER COLUMN environment_id SET STORAGE PLAIN;
+ALTER TABLE notification_customvar ALTER COLUMN notification_id SET STORAGE PLAIN;
+ALTER TABLE notification_customvar ALTER COLUMN customvar_id SET STORAGE PLAIN;
+
+CREATE INDEX idx_notification_customvar_notification_id ON notification_customvar(notification_id, customvar_id);
+CREATE INDEX idx_notification_customvar_customvar_id ON notification_customvar(customvar_id, notification_id);
+
+COMMENT ON COLUMN notification_customvar.id IS 'sha1(environment.id + notification_id + customvar_id)';
+COMMENT ON COLUMN notification_customvar.environment_id IS 'environment.id';
+COMMENT ON COLUMN notification_customvar.notification_id IS 'notification.id';
+COMMENT ON COLUMN notification_customvar.customvar_id IS 'customvar.id';
+
+CREATE TABLE icon_image (
+ id bytea20 NOT NULL,
+ environment_id bytea20 NOT NULL,
+ icon_image citext NOT NULL,
+
+ CONSTRAINT pk_icon_image PRIMARY KEY (environment_id, id)
+);
+
+ALTER TABLE icon_image ALTER COLUMN id SET STORAGE PLAIN;
+ALTER TABLE icon_image ALTER COLUMN environment_id SET STORAGE PLAIN;
+
+CREATE INDEX idx_icon_image ON icon_image(icon_image);
+
+COMMENT ON COLUMN icon_image.id IS 'sha1(icon_image)';
+COMMENT ON COLUMN icon_image.environment_id IS 'environment.id';
+
+CREATE TABLE action_url (
+ id bytea20 NOT NULL,
+ environment_id bytea20 NOT NULL,
+ action_url citext NOT NULL,
+
+ CONSTRAINT pk_action_url PRIMARY KEY (environment_id, id)
+);
+
+ALTER TABLE action_url ALTER COLUMN id SET STORAGE PLAIN;
+ALTER TABLE action_url ALTER COLUMN environment_id SET STORAGE PLAIN;
+
+CREATE INDEX idx_action_url ON action_url(action_url);
+
+COMMENT ON COLUMN action_url.id IS 'sha1(action_url)';
+COMMENT ON COLUMN action_url.environment_id IS 'environment.id';
+
+CREATE TABLE notes_url (
+ id bytea20 NOT NULL,
+ environment_id bytea20 NOT NULL,
+ notes_url citext NOT NULL,
+
+ CONSTRAINT pk_notes_url PRIMARY KEY (environment_id, id)
+);
+
+ALTER TABLE notes_url ALTER COLUMN id SET STORAGE PLAIN;
+ALTER TABLE notes_url ALTER COLUMN environment_id SET STORAGE PLAIN;
+
+CREATE INDEX idx_notes_url ON notes_url(notes_url);
+
+COMMENT ON COLUMN notes_url.id IS 'sha1(notes_url)';
+COMMENT ON COLUMN notes_url.environment_id IS 'environment.id';
+
+CREATE TABLE timeperiod (
+ id bytea20 NOT NULL,
+ environment_id bytea20 NOT NULL,
+
+ name_checksum bytea20 NOT NULL,
+ properties_checksum bytea20 NOT NULL,
+
+ name varchar(255) NOT NULL,
+ name_ci citext NOT NULL,
+ display_name citext NOT NULL,
+ prefer_includes boolenum NOT NULL DEFAULT 'n',
+
+ zone_id bytea20 DEFAULT NULL,
+
+ CONSTRAINT pk_timeperiod PRIMARY KEY (id)
+);
+
+ALTER TABLE timeperiod ALTER COLUMN id SET STORAGE PLAIN;
+ALTER TABLE timeperiod ALTER COLUMN environment_id SET STORAGE PLAIN;
+ALTER TABLE timeperiod ALTER COLUMN name_checksum SET STORAGE PLAIN;
+ALTER TABLE timeperiod ALTER COLUMN properties_checksum SET STORAGE PLAIN;
+ALTER TABLE timeperiod ALTER COLUMN zone_id SET STORAGE PLAIN;
+
+COMMENT ON COLUMN timeperiod.id IS 'sha1(environment.id + name)';
+COMMENT ON COLUMN timeperiod.environment_id IS 'env.id';
+COMMENT ON COLUMN timeperiod.name_checksum IS 'sha1(name)';
+COMMENT ON COLUMN timeperiod.properties_checksum IS 'sha1(all properties)';
+COMMENT ON COLUMN timeperiod.zone_id IS 'zone.id';
+
+CREATE TABLE timeperiod_range (
+ id bytea20 NOT NULL,
+ environment_id bytea20 NOT NULL,
+ timeperiod_id bytea20 NOT NULL,
+ range_key citext NOT NULL,
+
+ range_value varchar(255) NOT NULL,
+
+ CONSTRAINT pk_timeperiod_range PRIMARY KEY (id)
+);
+
+ALTER TABLE timeperiod_range ALTER COLUMN id SET STORAGE PLAIN;
+ALTER TABLE timeperiod_range ALTER COLUMN environment_id SET STORAGE PLAIN;
+ALTER TABLE timeperiod_range ALTER COLUMN timeperiod_id SET STORAGE PLAIN;
+
+COMMENT ON COLUMN timeperiod_range.id IS 'sha1(environment.id + range_id + timeperiod_id)';
+COMMENT ON COLUMN timeperiod_range.environment_id IS 'env.id';
+COMMENT ON COLUMN timeperiod_range.timeperiod_id IS 'timeperiod.id';
+
+CREATE TABLE timeperiod_override_include (
+ id bytea20 NOT NULL,
+ environment_id bytea20 NOT NULL,
+ timeperiod_id bytea20 NOT NULL,
+ override_id bytea20 NOT NULL,
+
+ CONSTRAINT pk_timeperiod_override_include PRIMARY KEY (id)
+);
+
+ALTER TABLE timeperiod_override_include ALTER COLUMN id SET STORAGE PLAIN;
+ALTER TABLE timeperiod_override_include ALTER COLUMN environment_id SET STORAGE PLAIN;
+ALTER TABLE timeperiod_override_include ALTER COLUMN timeperiod_id SET STORAGE PLAIN;
+ALTER TABLE timeperiod_override_include ALTER COLUMN override_id SET STORAGE PLAIN;
+
+COMMENT ON COLUMN timeperiod_override_include.id IS 'sha1(environment.id + include_id + timeperiod_id)';
+COMMENT ON COLUMN timeperiod_override_include.environment_id IS 'env.id';
+COMMENT ON COLUMN timeperiod_override_include.timeperiod_id IS 'timeperiod.id';
+COMMENT ON COLUMN timeperiod_override_include.override_id IS 'timeperiod.id';
+
+CREATE TABLE timeperiod_override_exclude (
+ id bytea20 NOT NULL,
+ environment_id bytea20 NOT NULL,
+ timeperiod_id bytea20 NOT NULL,
+ override_id bytea20 NOT NULL,
+
+ CONSTRAINT pk_timeperiod_override_exclude PRIMARY KEY (id)
+);
+
+ALTER TABLE timeperiod_override_exclude ALTER COLUMN id SET STORAGE PLAIN;
+ALTER TABLE timeperiod_override_exclude ALTER COLUMN environment_id SET STORAGE PLAIN;
+ALTER TABLE timeperiod_override_exclude ALTER COLUMN timeperiod_id SET STORAGE PLAIN;
+ALTER TABLE timeperiod_override_exclude ALTER COLUMN override_id SET STORAGE PLAIN;
+
+COMMENT ON COLUMN timeperiod_override_exclude.id IS 'sha1(environment.id + exclude_id + timeperiod_id)';
+COMMENT ON COLUMN timeperiod_override_exclude.environment_id IS 'env.id';
+COMMENT ON COLUMN timeperiod_override_exclude.timeperiod_id IS 'timeperiod.id';
+COMMENT ON COLUMN timeperiod_override_exclude.override_id IS 'timeperiod.id';
+
+CREATE TABLE timeperiod_customvar (
+ id bytea20 NOT NULL,
+ environment_id bytea20 NOT NULL,
+ timeperiod_id bytea20 NOT NULL,
+ customvar_id bytea20 NOT NULL,
+
+ CONSTRAINT pk_timeperiod_customvar PRIMARY KEY (id)
+);
+
+ALTER TABLE timeperiod_customvar ALTER COLUMN id SET STORAGE PLAIN;
+ALTER TABLE timeperiod_customvar ALTER COLUMN environment_id SET STORAGE PLAIN;
+ALTER TABLE timeperiod_customvar ALTER COLUMN timeperiod_id SET STORAGE PLAIN;
+ALTER TABLE timeperiod_customvar ALTER COLUMN customvar_id SET STORAGE PLAIN;
+
+CREATE INDEX idx_timeperiod_customvar_timeperiod_id ON timeperiod_customvar(timeperiod_id, customvar_id);
+CREATE INDEX idx_timeperiod_customvar_customvar_id ON timeperiod_customvar(customvar_id, timeperiod_id);
+
+COMMENT ON COLUMN timeperiod_customvar.id IS 'sha1(environment.id + timeperiod_id + customvar_id)';
+COMMENT ON COLUMN timeperiod_customvar.environment_id IS 'environment.id';
+COMMENT ON COLUMN timeperiod_customvar.timeperiod_id IS 'timeperiod.id';
+COMMENT ON COLUMN timeperiod_customvar.customvar_id IS 'customvar.id';
+
+CREATE TABLE customvar (
+ id bytea20 NOT NULL,
+ environment_id bytea20 NOT NULL,
+ name_checksum bytea20 NOT NULL,
+
+ name varchar(255) NOT NULL,
+ value text NOT NULL,
+
+ CONSTRAINT pk_customvar PRIMARY KEY (id)
+);
+
+ALTER TABLE customvar ALTER COLUMN id SET STORAGE PLAIN;
+ALTER TABLE customvar ALTER COLUMN environment_id SET STORAGE PLAIN;
+ALTER TABLE customvar ALTER COLUMN name_checksum SET STORAGE PLAIN;
+
+COMMENT ON COLUMN customvar.id IS 'sha1(environment.id + name + value)';
+COMMENT ON COLUMN customvar.environment_id IS 'environment.id';
+COMMENT ON COLUMN customvar.name_checksum IS 'sha1(name)';
+
+CREATE TABLE customvar_flat (
+ id bytea20 NOT NULL,
+ environment_id bytea20 NOT NULL,
+ customvar_id bytea20 NOT NULL,
+ flatname_checksum bytea20 NOT NULL,
+
+ flatname varchar(512) NOT NULL,
+ flatvalue text NOT NULL,
+
+ CONSTRAINT pk_customvar_flat PRIMARY KEY (id)
+);
+
+ALTER TABLE customvar_flat ALTER COLUMN id SET STORAGE PLAIN;
+ALTER TABLE customvar_flat ALTER COLUMN environment_id SET STORAGE PLAIN;
+ALTER TABLE customvar_flat ALTER COLUMN customvar_id SET STORAGE PLAIN;
+ALTER TABLE customvar_flat ALTER COLUMN flatname_checksum SET STORAGE PLAIN;
+
+CREATE INDEX idx_customvar_flat_customvar_id ON customvar_flat(customvar_id);
+
+COMMENT ON COLUMN customvar_flat.id IS 'sha1(environment.id + flatname + flatvalue)';
+COMMENT ON COLUMN customvar_flat.environment_id IS 'environment.id';
+COMMENT ON COLUMN customvar_flat.customvar_id IS 'sha1(customvar.id)';
+COMMENT ON COLUMN customvar_flat.flatname_checksum IS 'sha1(flatname after conversion)';
+COMMENT ON COLUMN customvar_flat.flatname IS 'Path converted with `.` and `[ ]`';
+
+CREATE TABLE "user" (
+ id bytea20 NOT NULL,
+ environment_id bytea20 NOT NULL,
+ name_checksum bytea20 NOT NULL,
+ properties_checksum bytea20 NOT NULL,
+
+ name varchar(255) NOT NULL,
+ name_ci citext NOT NULL,
+ display_name citext NOT NULL,
+
+ email varchar(255) NOT NULL,
+ pager varchar(255) NOT NULL,
+
+ notifications_enabled boolenum NOT NULL DEFAULT 'n',
+
+ timeperiod_id bytea20 DEFAULT NULL,
+
+ states tinyuint NOT NULL,
+ types smalluint NOT NULL,
+
+ zone_id bytea20 DEFAULT NULL,
+
+ CONSTRAINT pk_user PRIMARY KEY (id)
+);
+
+ALTER TABLE "user" ALTER COLUMN id SET STORAGE PLAIN;
+ALTER TABLE "user" ALTER COLUMN environment_id SET STORAGE PLAIN;
+ALTER TABLE "user" ALTER COLUMN name_checksum SET STORAGE PLAIN;
+ALTER TABLE "user" ALTER COLUMN properties_checksum SET STORAGE PLAIN;
+ALTER TABLE "user" ALTER COLUMN timeperiod_id SET STORAGE PLAIN;
+ALTER TABLE "user" ALTER COLUMN zone_id SET STORAGE PLAIN;
+
+CREATE INDEX idx_user_display_name ON "user"(display_name);
+CREATE INDEX idx_user_name_ci ON "user"(name_ci);
+CREATE INDEX idx_user_name ON "user"(name);
+
+COMMENT ON COLUMN "user".id IS 'sha1(environment.id + name)';
+COMMENT ON COLUMN "user".environment_id IS 'environment.id';
+COMMENT ON COLUMN "user".name_checksum IS 'sha1(name)';
+COMMENT ON COLUMN "user".properties_checksum IS 'sha1(all properties)';
+COMMENT ON COLUMN "user".timeperiod_id IS 'timeperiod.id';
+COMMENT ON COLUMN "user".zone_id IS 'zone.id';
+
+COMMENT ON INDEX idx_user_display_name IS 'User list filtered/ordered by display_name';
+COMMENT ON INDEX idx_user_name_ci IS 'User list filtered using quick search';
+COMMENT ON INDEX idx_user_name IS 'User list filtered/ordered by name; User detail filter';
+
+CREATE TABLE usergroup (
+ id bytea20 NOT NULL,
+ environment_id bytea20 NOT NULL,
+ name_checksum bytea20 NOT NULL,
+ properties_checksum bytea20 NOT NULL,
+
+ name varchar(255) NOT NULL,
+ name_ci citext NOT NULL,
+ display_name citext NOT NULL,
+
+ zone_id bytea20 DEFAULT NULL,
+
+ CONSTRAINT pk_usergroup PRIMARY KEY (id)
+);
+
+ALTER TABLE usergroup ALTER COLUMN id SET STORAGE PLAIN;
+ALTER TABLE usergroup ALTER COLUMN environment_id SET STORAGE PLAIN;
+ALTER TABLE usergroup ALTER COLUMN name_checksum SET STORAGE PLAIN;
+ALTER TABLE usergroup ALTER COLUMN properties_checksum SET STORAGE PLAIN;
+ALTER TABLE usergroup ALTER COLUMN zone_id SET STORAGE PLAIN;
+
+CREATE INDEX idx_usergroup_display_name ON usergroup(display_name);
+CREATE INDEX idx_usergroup_name_ci ON usergroup(name_ci);
+CREATE INDEX idx_usergroup_name ON usergroup(name);
+
+COMMENT ON COLUMN usergroup.id IS 'sha1(environment.id + name)';
+COMMENT ON COLUMN usergroup.environment_id IS 'environment.id';
+COMMENT ON COLUMN usergroup.name_checksum IS 'sha1(name)';
+COMMENT ON COLUMN usergroup.properties_checksum IS 'sha1(all properties)';
+COMMENT ON COLUMN usergroup.zone_id IS 'zone.id';
+
+COMMENT ON INDEX idx_usergroup_display_name IS 'Usergroup list filtered/ordered by display_name';
+COMMENT ON INDEX idx_usergroup_name_ci IS 'Usergroup list filtered using quick search';
+COMMENT ON INDEX idx_usergroup_name IS 'Usergroup list filtered/ordered by name; User detail filter';
+
+CREATE TABLE usergroup_member (
+ id bytea20 NOT NULL,
+ environment_id bytea20 NOT NULL,
+ user_id bytea20 NOT NULL,
+ usergroup_id bytea20 NOT NULL,
+
+ CONSTRAINT pk_usergroup_member PRIMARY KEY (id)
+);
+
+ALTER TABLE usergroup_member ALTER COLUMN id SET STORAGE PLAIN;
+ALTER TABLE usergroup_member ALTER COLUMN environment_id SET STORAGE PLAIN;
+ALTER TABLE usergroup_member ALTER COLUMN user_id SET STORAGE PLAIN;
+ALTER TABLE usergroup_member ALTER COLUMN usergroup_id SET STORAGE PLAIN;
+
+CREATE INDEX idx_usergroup_member_user_id ON usergroup_member(user_id, usergroup_id);
+CREATE INDEX idx_usergroup_member_usergroup_id ON usergroup_member(usergroup_id, user_id);
+
+COMMENT ON COLUMN usergroup_member.id IS 'sha1(environment.id + usergroup_id + user_id)';
+COMMENT ON COLUMN usergroup_member.environment_id IS 'environment.id';
+COMMENT ON COLUMN usergroup_member.user_id IS 'user.id';
+COMMENT ON COLUMN usergroup_member.usergroup_id IS 'usergroup.id';
+
+CREATE TABLE user_customvar (
+ id bytea20 NOT NULL,
+ environment_id bytea20 NOT NULL,
+ user_id bytea20 NOT NULL,
+ customvar_id bytea20 NOT NULL,
+
+ CONSTRAINT pk_user_customvar PRIMARY KEY (id)
+);
+
+ALTER TABLE user_customvar ALTER COLUMN id SET STORAGE PLAIN;
+ALTER TABLE user_customvar ALTER COLUMN environment_id SET STORAGE PLAIN;
+ALTER TABLE user_customvar ALTER COLUMN user_id SET STORAGE PLAIN;
+ALTER TABLE user_customvar ALTER COLUMN customvar_id SET STORAGE PLAIN;
+
+CREATE INDEX idx_user_customvar_user_id ON user_customvar(user_id, customvar_id);
+CREATE INDEX idx_user_customvar_customvar_id ON user_customvar(customvar_id, user_id);
+
+COMMENT ON COLUMN user_customvar.id IS 'sha1(environment.id + user_id + customvar_id)';
+COMMENT ON COLUMN user_customvar.environment_id IS 'environment.id';
+COMMENT ON COLUMN user_customvar.user_id IS 'user.id';
+COMMENT ON COLUMN user_customvar.customvar_id IS 'customvar.id';
+
+CREATE TABLE usergroup_customvar (
+ id bytea20 NOT NULL,
+ environment_id bytea20 NOT NULL,
+ usergroup_id bytea20 NOT NULL,
+ customvar_id bytea20 NOT NULL,
+
+ CONSTRAINT pk_usergroup_customvar PRIMARY KEY (id)
+);
+
+ALTER TABLE usergroup_customvar ALTER COLUMN id SET STORAGE PLAIN;
+ALTER TABLE usergroup_customvar ALTER COLUMN environment_id SET STORAGE PLAIN;
+ALTER TABLE usergroup_customvar ALTER COLUMN usergroup_id SET STORAGE PLAIN;
+ALTER TABLE usergroup_customvar ALTER COLUMN customvar_id SET STORAGE PLAIN;
+
+CREATE INDEX idx_usergroup_customvar_usergroup_id ON usergroup_customvar(usergroup_id, customvar_id);
+CREATE INDEX idx_usergroup_customvar_customvar_id ON usergroup_customvar(customvar_id, usergroup_id);
+
+COMMENT ON COLUMN usergroup_customvar.id IS 'sha1(environment.id + usergroup_id + customvar_id)';
+COMMENT ON COLUMN usergroup_customvar.environment_id IS 'environment.id';
+COMMENT ON COLUMN usergroup_customvar.usergroup_id IS 'usergroup.id';
+COMMENT ON COLUMN usergroup_customvar.customvar_id IS 'customvar.id';
+
+CREATE TABLE zone (
+ id bytea20 NOT NULL,
+ environment_id bytea20 NOT NULL,
+ name_checksum bytea20 NOT NULL,
+ properties_checksum bytea20 NOT NULL,
+
+ name varchar(255) NOT NULL,
+ name_ci citext NOT NULL,
+
+ is_global boolenum NOT NULL DEFAULT 'n',
+ parent_id bytea20 DEFAULT NULL,
+
+ depth tinyuint NOT NULL,
+
+ CONSTRAINT pk_zone PRIMARY KEY (id)
+);
+
+ALTER TABLE zone ALTER COLUMN id SET STORAGE PLAIN;
+ALTER TABLE zone ALTER COLUMN environment_id SET STORAGE PLAIN;
+ALTER TABLE zone ALTER COLUMN name_checksum SET STORAGE PLAIN;
+ALTER TABLE zone ALTER COLUMN properties_checksum SET STORAGE PLAIN;
+ALTER TABLE zone ALTER COLUMN parent_id SET STORAGE PLAIN;
+
+CREATE UNIQUE INDEX idx_environment_id_id ON zone(environment_id, id);
+CREATE INDEX idx_zone_parent_id ON zone(parent_id);
+
+COMMENT ON COLUMN zone.id IS 'sha1(environment.id + name)';
+COMMENT ON COLUMN zone.environment_id IS 'environment.id';
+COMMENT ON COLUMN zone.name_checksum IS 'sha1(name)';
+COMMENT ON COLUMN zone.properties_checksum IS 'sha1(all properties)';
+COMMENT ON COLUMN zone.parent_id IS 'zone.id';
+
+CREATE TABLE notification_history (
+ id bytea20 NOT NULL,
+ environment_id bytea20 NOT NULL,
+ endpoint_id bytea20 DEFAULT NULL,
+ object_type checkable_type NOT NULL DEFAULT 'host',
+ host_id bytea20 NOT NULL,
+ service_id bytea20 DEFAULT NULL,
+ notification_id bytea20 NOT NULL,
+
+ type notification_type NOT NULL DEFAULT 'downtime_start',
+ send_time biguint NOT NULL,
+ state tinyuint NOT NULL,
+ previous_hard_state tinyuint NOT NULL,
+ author text NOT NULL,
+ "text" text NOT NULL,
+ users_notified smalluint NOT NULL,
+
+ CONSTRAINT pk_notification_history PRIMARY KEY (id)
+);
+
+ALTER TABLE notification_history ALTER COLUMN id SET STORAGE PLAIN;
+ALTER TABLE notification_history ALTER COLUMN environment_id SET STORAGE PLAIN;
+ALTER TABLE notification_history ALTER COLUMN endpoint_id SET STORAGE PLAIN;
+ALTER TABLE notification_history ALTER COLUMN host_id SET STORAGE PLAIN;
+ALTER TABLE notification_history ALTER COLUMN service_id SET STORAGE PLAIN;
+ALTER TABLE notification_history ALTER COLUMN notification_id SET STORAGE PLAIN;
+
+CREATE INDEX idx_notification_history_send_time ON notification_history(send_time DESC);
+
+COMMENT ON COLUMN notification_history.id IS 'sha1(environment.name + notification.name + type + send_time)';
+COMMENT ON COLUMN notification_history.environment_id IS 'environment.id';
+COMMENT ON COLUMN notification_history.endpoint_id IS 'endpoint.id';
+COMMENT ON COLUMN notification_history.host_id IS 'host.id';
+COMMENT ON COLUMN notification_history.service_id IS 'service.id';
+COMMENT ON COLUMN notification_history.notification_id IS 'notification.id';
+
+COMMENT ON INDEX idx_notification_history_send_time IS 'Notification list filtered/ordered by send_time';
+
+CREATE TABLE user_notification_history (
+ id bytea20 NOT NULL,
+ environment_id bytea20 NOT NULL,
+ notification_history_id bytea20 NOT NULL,
+ user_id bytea20 NOT NULL,
+
+ CONSTRAINT pk_user_notification_history PRIMARY KEY (id),
+
+ CONSTRAINT fk_user_notification_history_notification_history FOREIGN KEY (notification_history_id) REFERENCES notification_history (id) ON DELETE CASCADE
+);
+
+ALTER TABLE user_notification_history ALTER COLUMN id SET STORAGE PLAIN;
+ALTER TABLE user_notification_history ALTER COLUMN environment_id SET STORAGE PLAIN;
+ALTER TABLE user_notification_history ALTER COLUMN notification_history_id SET STORAGE PLAIN;
+ALTER TABLE user_notification_history ALTER COLUMN user_id SET STORAGE PLAIN;
+
+COMMENT ON COLUMN user_notification_history.id IS 'sha1(notification_history_id + user_id)';
+COMMENT ON COLUMN user_notification_history.environment_id IS 'environment.id';
+COMMENT ON COLUMN user_notification_history.notification_history_id IS 'UUID notification_history.id';
+COMMENT ON COLUMN user_notification_history.user_id IS 'user.id';
+
+CREATE TABLE state_history (
+ id bytea20 NOT NULL,
+ environment_id bytea20 NOT NULL,
+ endpoint_id bytea20 DEFAULT NULL,
+ object_type checkable_type NOT NULL DEFAULT 'host',
+ host_id bytea20 NOT NULL,
+ service_id bytea20 DEFAULT NULL,
+
+ event_time biguint NOT NULL,
+ state_type state_type NOT NULL DEFAULT 'hard',
+ soft_state tinyuint NOT NULL,
+ hard_state tinyuint NOT NULL,
+ previous_soft_state tinyuint NOT NULL,
+ previous_hard_state tinyuint NOT NULL,
+ attempt tinyuint NOT NULL,
+ output text DEFAULT NULL,
+ long_output text DEFAULT NULL,
+ max_check_attempts uint NOT NULL,
+ check_source text DEFAULT NULL,
+ scheduling_source text DEFAULT NULL,
+
+ CONSTRAINT pk_state_history PRIMARY KEY (id)
+);
+
+ALTER TABLE state_history ALTER COLUMN id SET STORAGE PLAIN;
+ALTER TABLE state_history ALTER COLUMN environment_id SET STORAGE PLAIN;
+ALTER TABLE state_history ALTER COLUMN endpoint_id SET STORAGE PLAIN;
+ALTER TABLE state_history ALTER COLUMN host_id SET STORAGE PLAIN;
+ALTER TABLE state_history ALTER COLUMN service_id SET STORAGE PLAIN;
+
+COMMENT ON COLUMN state_history.id IS 'sha1(environment.name + host|service.name + event_time)';
+COMMENT ON COLUMN state_history.environment_id IS 'environment.id';
+COMMENT ON COLUMN state_history.endpoint_id IS 'endpoint.id';
+COMMENT ON COLUMN state_history.host_id IS 'host.id';
+COMMENT ON COLUMN state_history.service_id IS 'service.id';
+
+CREATE TABLE downtime_history (
+ downtime_id bytea20 NOT NULL,
+ environment_id bytea20 NOT NULL,
+ endpoint_id bytea20 DEFAULT NULL,
+ triggered_by_id bytea20 DEFAULT NULL,
+ parent_id bytea20 DEFAULT NULL,
+ object_type checkable_type NOT NULL DEFAULT 'host',
+ host_id bytea20 NOT NULL,
+ service_id bytea20 DEFAULT NULL,
+
+ entry_time biguint NOT NULL,
+ author citext NOT NULL,
+ cancelled_by citext DEFAULT NULL,
+ comment text NOT NULL,
+ is_flexible boolenum NOT NULL DEFAULT 'n',
+ flexible_duration biguint NOT NULL,
+ scheduled_start_time biguint NOT NULL,
+ scheduled_end_time biguint NOT NULL,
+ start_time biguint NOT NULL,
+ end_time biguint NOT NULL,
+ scheduled_by varchar(767) DEFAULT NULL,
+ has_been_cancelled boolenum NOT NULL DEFAULT 'n',
+ trigger_time biguint NOT NULL,
+ cancel_time biguint DEFAULT NULL,
+
+ CONSTRAINT pk_downtime_history PRIMARY KEY (downtime_id)
+);
+
+ALTER TABLE downtime_history ALTER COLUMN downtime_id SET STORAGE PLAIN;
+ALTER TABLE downtime_history ALTER COLUMN environment_id SET STORAGE PLAIN;
+ALTER TABLE downtime_history ALTER COLUMN endpoint_id SET STORAGE PLAIN;
+ALTER TABLE downtime_history ALTER COLUMN triggered_by_id SET STORAGE PLAIN;
+ALTER TABLE downtime_history ALTER COLUMN parent_id SET STORAGE PLAIN;
+ALTER TABLE downtime_history ALTER COLUMN host_id SET STORAGE PLAIN;
+ALTER TABLE downtime_history ALTER COLUMN service_id SET STORAGE PLAIN;
+
+COMMENT ON COLUMN downtime_history.downtime_id IS 'downtime.id';
+COMMENT ON COLUMN downtime_history.environment_id IS 'environment.id';
+COMMENT ON COLUMN downtime_history.endpoint_id IS 'endpoint.id';
+COMMENT ON COLUMN downtime_history.triggered_by_id IS 'The ID of the downtime that triggered this downtime. This is set when creating downtimes on a host or service higher up in the dependency chain using the "child_option" "DowntimeTriggeredChildren" and can also be set manually via the API.';
+COMMENT ON COLUMN downtime_history.parent_id IS 'For service downtimes, the ID of the host downtime that created this downtime by using the "all_services" flag of the schedule-downtime API.';
+COMMENT ON COLUMN downtime_history.host_id IS 'host.id';
+COMMENT ON COLUMN downtime_history.service_id IS 'service.id';
+COMMENT ON COLUMN downtime_history.start_time IS 'Time when the host went into a problem state during the downtimes timeframe';
+COMMENT ON COLUMN downtime_history.end_time IS 'Problem state assumed: scheduled_end_time if fixed, start_time + duration otherwise';
+COMMENT ON COLUMN downtime_history.scheduled_by IS 'Name of the ScheduledDowntime which created this Downtime. 255+1+255+1+255, i.e. "host.name!service.name!scheduled-downtime-name"';
+
+CREATE TABLE comment_history (
+ comment_id bytea20 NOT NULL,
+ environment_id bytea20 NOT NULL,
+ endpoint_id bytea20 DEFAULT NULL,
+ object_type checkable_type NOT NULL DEFAULT 'host',
+ host_id bytea20 NOT NULL,
+ service_id bytea20 DEFAULT NULL,
+
+ entry_time biguint NOT NULL,
+ author citext NOT NULL,
+ removed_by citext DEFAULT NULL,
+ comment text NOT NULL,
+ entry_type comment_type NOT NULL DEFAULT 'comment',
+ is_persistent boolenum NOT NULL DEFAULT 'n',
+ is_sticky boolenum NOT NULL DEFAULT 'n',
+ expire_time biguint DEFAULT NULL,
+ remove_time biguint DEFAULT NULL,
+ has_been_removed boolenum NOT NULL DEFAULT 'n',
+
+ CONSTRAINT pk_comment_history PRIMARY KEY (comment_id)
+);
+
+ALTER TABLE comment_history ALTER COLUMN comment_id SET STORAGE PLAIN;
+ALTER TABLE comment_history ALTER COLUMN environment_id SET STORAGE PLAIN;
+ALTER TABLE comment_history ALTER COLUMN endpoint_id SET STORAGE PLAIN;
+ALTER TABLE comment_history ALTER COLUMN host_id SET STORAGE PLAIN;
+ALTER TABLE comment_history ALTER COLUMN service_id SET STORAGE PLAIN;
+
+COMMENT ON COLUMN comment_history.comment_id IS 'comment.id';
+COMMENT ON COLUMN comment_history.environment_id IS 'environment.id';
+COMMENT ON COLUMN comment_history.endpoint_id IS 'endpoint.id';
+COMMENT ON COLUMN comment_history.host_id IS 'host.id';
+COMMENT ON COLUMN comment_history.service_id IS 'service.id';
+
+CREATE TABLE flapping_history (
+ id bytea20 NOT NULL,
+ environment_id bytea20 NOT NULL,
+ endpoint_id bytea20 DEFAULT NULL,
+ object_type checkable_type NOT NULL DEFAULT 'host',
+ host_id bytea20 NOT NULL,
+ service_id bytea20 DEFAULT NULL,
+
+ start_time biguint NOT NULL,
+ end_time biguint DEFAULT NULL,
+ percent_state_change_start float DEFAULT NULL,
+ percent_state_change_end float DEFAULT NULL,
+ flapping_threshold_low float NOT NULL,
+ flapping_threshold_high float NOT NULL,
+
+ CONSTRAINT pk_flapping_history PRIMARY KEY (id)
+);
+
+ALTER TABLE flapping_history ALTER COLUMN id SET STORAGE PLAIN;
+ALTER TABLE flapping_history ALTER COLUMN environment_id SET STORAGE PLAIN;
+ALTER TABLE flapping_history ALTER COLUMN endpoint_id SET STORAGE PLAIN;
+ALTER TABLE flapping_history ALTER COLUMN host_id SET STORAGE PLAIN;
+ALTER TABLE flapping_history ALTER COLUMN service_id SET STORAGE PLAIN;
+
+COMMENT ON COLUMN flapping_history.id IS 'sha1(environment.id + "Host"|"Service" + host|service.name + start_time)';
+COMMENT ON COLUMN flapping_history.environment_id IS 'environment.id';
+COMMENT ON COLUMN flapping_history.endpoint_id IS 'endpoint.id';
+COMMENT ON COLUMN flapping_history.host_id IS 'host.id';
+COMMENT ON COLUMN flapping_history.service_id IS 'service.id';
+
+CREATE TABLE acknowledgement_history (
+ id bytea20 NOT NULL,
+ environment_id bytea20 NOT NULL,
+ endpoint_id bytea20 DEFAULT NULL,
+ object_type checkable_type NOT NULL DEFAULT 'host',
+ host_id bytea20 NOT NULL,
+ service_id bytea20 DEFAULT NULL,
+
+ set_time biguint NOT NULL,
+ clear_time biguint DEFAULT NULL,
+ author citext DEFAULT NULL,
+ cleared_by citext DEFAULT NULL,
+ comment text DEFAULT NULL,
+ expire_time biguint DEFAULT NULL,
+ is_sticky boolenum DEFAULT NULL,
+ is_persistent boolenum DEFAULT NULL,
+
+ CONSTRAINT pk_acknowledgement_history PRIMARY KEY (id)
+);
+
+ALTER TABLE acknowledgement_history ALTER COLUMN id SET STORAGE PLAIN;
+ALTER TABLE acknowledgement_history ALTER COLUMN environment_id SET STORAGE PLAIN;
+ALTER TABLE acknowledgement_history ALTER COLUMN endpoint_id SET STORAGE PLAIN;
+ALTER TABLE acknowledgement_history ALTER COLUMN host_id SET STORAGE PLAIN;
+ALTER TABLE acknowledgement_history ALTER COLUMN service_id SET STORAGE PLAIN;
+
+COMMENT ON COLUMN acknowledgement_history.id IS 'sha1(environment.id + "Host"|"Service" + host|service.name + set_time)';
+COMMENT ON COLUMN acknowledgement_history.environment_id IS 'environment.id';
+COMMENT ON COLUMN acknowledgement_history.endpoint_id IS 'endpoint.id';
+COMMENT ON COLUMN acknowledgement_history.host_id IS 'host.id';
+COMMENT ON COLUMN acknowledgement_history.service_id IS 'service.id';
+COMMENT ON COLUMN acknowledgement_history.author IS 'NULL if ack_set event happened before Icinga DB history recording';
+COMMENT ON COLUMN acknowledgement_history.comment IS 'NULL if ack_set event happened before Icinga DB history recording';
+COMMENT ON COLUMN acknowledgement_history.is_sticky IS 'NULL if ack_set event happened before Icinga DB history recording';
+COMMENT ON COLUMN acknowledgement_history.is_persistent IS 'NULL if ack_set event happened before Icinga DB history recording';
+
+CREATE TABLE history (
+ id bytea20 NOT NULL,
+ environment_id bytea20 NOT NULL,
+ endpoint_id bytea20 DEFAULT NULL,
+ object_type checkable_type NOT NULL DEFAULT 'host',
+ host_id bytea20 NOT NULL,
+ service_id bytea20 DEFAULT NULL,
+ notification_history_id bytea20 DEFAULT NULL,
+ state_history_id bytea20 DEFAULT NULL,
+ downtime_history_id bytea20 DEFAULT NULL,
+ comment_history_id bytea20 DEFAULT NULL,
+ flapping_history_id bytea20 DEFAULT NULL,
+ acknowledgement_history_id bytea20 DEFAULT NULL,
+
+ event_type history_type NOT NULL DEFAULT 'notification',
+ event_time biguint NOT NULL,
+
+ CONSTRAINT pk_history PRIMARY KEY (id),
+
+ CONSTRAINT fk_history_acknowledgement_history FOREIGN KEY (acknowledgement_history_id) REFERENCES acknowledgement_history (id) ON DELETE CASCADE,
+ CONSTRAINT fk_history_comment_history FOREIGN KEY (comment_history_id) REFERENCES comment_history (comment_id) ON DELETE CASCADE,
+ CONSTRAINT fk_history_downtime_history FOREIGN KEY (downtime_history_id) REFERENCES downtime_history (downtime_id) ON DELETE CASCADE,
+ CONSTRAINT fk_history_flapping_history FOREIGN KEY (flapping_history_id) REFERENCES flapping_history (id) ON DELETE CASCADE,
+ CONSTRAINT fk_history_notification_history FOREIGN KEY (notification_history_id) REFERENCES notification_history (id) ON DELETE CASCADE,
+ CONSTRAINT fk_history_state_history FOREIGN KEY (state_history_id) REFERENCES state_history (id) ON DELETE CASCADE
+);
+
+ALTER TABLE history ALTER COLUMN id SET STORAGE PLAIN;
+ALTER TABLE history ALTER COLUMN environment_id SET STORAGE PLAIN;
+ALTER TABLE history ALTER COLUMN endpoint_id SET STORAGE PLAIN;
+ALTER TABLE history ALTER COLUMN host_id SET STORAGE PLAIN;
+ALTER TABLE history ALTER COLUMN service_id SET STORAGE PLAIN;
+ALTER TABLE history ALTER COLUMN notification_history_id SET STORAGE PLAIN;
+ALTER TABLE history ALTER COLUMN state_history_id SET STORAGE PLAIN;
+ALTER TABLE history ALTER COLUMN downtime_history_id SET STORAGE PLAIN;
+ALTER TABLE history ALTER COLUMN comment_history_id SET STORAGE PLAIN;
+ALTER TABLE history ALTER COLUMN flapping_history_id SET STORAGE PLAIN;
+ALTER TABLE history ALTER COLUMN acknowledgement_history_id SET STORAGE PLAIN;
+
+CREATE INDEX idx_history_event_time ON history(event_time);
+CREATE INDEX idx_history_acknowledgement ON history(acknowledgement_history_id);
+CREATE INDEX idx_history_comment ON history(comment_history_id);
+CREATE INDEX idx_history_downtime ON history(downtime_history_id);
+CREATE INDEX idx_history_flapping ON history(flapping_history_id);
+CREATE INDEX idx_history_notification ON history(notification_history_id);
+CREATE INDEX idx_history_state ON history(state_history_id);
+CREATE INDEX idx_history_host_service_id ON history(host_id, service_id, event_time);
+
+COMMENT ON COLUMN history.id IS 'sha1(environment.name + event_type + x...) given that sha1(environment.name + x...) = *_history_id';
+COMMENT ON COLUMN history.environment_id IS 'environment.id';
+COMMENT ON COLUMN history.endpoint_id IS 'endpoint.id';
+COMMENT ON COLUMN history.host_id IS 'host.id';
+COMMENT ON COLUMN history.service_id IS 'service.id';
+COMMENT ON COLUMN history.notification_history_id IS 'notification_history.id';
+COMMENT ON COLUMN history.state_history_id IS 'state_history.id';
+COMMENT ON COLUMN history.downtime_history_id IS 'downtime_history.downtime_id';
+COMMENT ON COLUMN history.comment_history_id IS 'comment_history.comment_id';
+COMMENT ON COLUMN history.flapping_history_id IS 'flapping_history.id';
+COMMENT ON COLUMN history.acknowledgement_history_id IS 'acknowledgement_history.id';
+
+COMMENT ON INDEX idx_history_event_time IS 'History filtered/ordered by event_time';
+COMMENT ON INDEX idx_history_host_service_id IS 'Host/service history detail filter';
+
+CREATE SEQUENCE icingadb_schema_id_seq;
+
+CREATE TABLE icingadb_schema (
+ id uint NOT NULL DEFAULT nextval('icingadb_schema_id_seq'),
+ version smalluint NOT NULL,
+ timestamp biguint NOT NULL,
+
+ CONSTRAINT pk_icingadb_schema PRIMARY KEY (id)
+);
+
+ALTER SEQUENCE icingadb_schema_id_seq OWNED BY icingadb_schema.id;
+
+INSERT INTO icingadb_schema (version, timestamp)
+ VALUES (1, extract(epoch from now()) * 1000);