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 Context](images/about/icinga-db-in-icinga-context.png) -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);