From b2618dee85b778059f57aed2704667f78d993414 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Thu, 23 Sep 2021 11:09:40 +0200 Subject: [PATCH 01/25] Port MySQL schema to Postgres refs #136 --- schema/pgsql/schema.sql | 1914 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 1914 insertions(+) create mode 100644 schema/pgsql/schema.sql diff --git a/schema/pgsql/schema.sql b/schema/pgsql/schema.sql new file mode 100644 index 00000000..6c5fa777 --- /dev/null +++ b/schema/pgsql/schema.sql @@ -0,0 +1,1914 @@ +-- Icinga DB | (c) 2021 Icinga GmbH | GPLv2+ + +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_t AS ENUM ( 'n', 'y' ); +CREATE TYPE acked_t AS ENUM ( 'n', 'y', 'sticky' ); +CREATE TYPE state_type_t AS ENUM ( 'hard', 'soft' ); +CREATE TYPE checkable_type_t AS ENUM ( 'host', 'service' ); +CREATE TYPE comment_type_t AS ENUM ( 'comment', 'ack' ); +CREATE TYPE notification_type_t AS ENUM ( 'downtime_start', 'downtime_end', 'downtime_removed', 'custom', 'acknowledgement', 'problem', 'recovery', 'flapping_start', 'flapping_end' ); +CREATE TYPE history_type_t AS ENUM ( 'notification', 'state_change', 'downtime_start', 'downtime_end', 'comment_add', 'comment_remove', 'flapping_start', 'flapping_end', 'ack_set', 'ack_clear' ); + +CREATE DOMAIN boolenum AS boolenum_t DEFAULT 'n'; +CREATE DOMAIN acked AS acked_t DEFAULT 'n'; +CREATE DOMAIN state_type AS state_type_t DEFAULT 'hard'; +CREATE DOMAIN checkable_type AS checkable_type_t DEFAULT 'host'; +CREATE DOMAIN comment_type AS comment_type_t DEFAULT 'comment'; +CREATE DOMAIN notification_type AS notification_type_t DEFAULT 'downtime_start'; +CREATE DOMAIN history_type AS history_type_t DEFAULT 'notification'; + +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 varchar(255) NOT NULL, + display_name varchar(255) NOT NULL, + + address varchar(255) NOT NULL, + address6 varchar(255) NOT NULL, + address_bin bytea4 DEFAULT NULL, + address6_bin bytea16 DEFAULT NULL, + + checkcommand varchar(255) NOT NULL, + checkcommand_id bytea20 NOT NULL, + + max_check_attempts uint NOT NULL, + + check_timeperiod varchar(255) 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, + passive_checks_enabled boolenum NOT NULL, + event_handler_enabled boolenum NOT NULL, + notifications_enabled boolenum NOT NULL, + + flapping_enabled boolenum NOT NULL, + flapping_threshold_low float NOT NULL, + flapping_threshold_high float NOT NULL, + + perfdata_enabled boolenum NOT NULL, + + eventcommand varchar(255) NOT NULL, + eventcommand_id bytea20 DEFAULT NULL, + + is_volatile boolenum NOT NULL, + + 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 varchar(255) NOT NULL, + zone_id bytea20 DEFAULT NULL, + + command_endpoint varchar(255) 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(LOWER(display_name)); +CREATE INDEX idx_host_name_ci ON host(LOWER(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 varchar(255) NOT NULL, + display_name varchar(255) 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, + 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, + is_handled boolenum NOT NULL, + is_reachable boolenum NOT NULL, + is_flapping boolenum NOT NULL, + is_overdue boolenum NOT NULL, + + is_acknowledged acked NOT NULL, + acknowledgement_comment_id bytea20 DEFAULT NULL, + last_comment_id bytea20 DEFAULT NULL, + + in_downtime boolenum NOT NULL, + + 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 varchar(255) NOT NULL, + display_name varchar(255) NOT NULL, + + checkcommand varchar(255) NOT NULL, + checkcommand_id bytea20 NOT NULL, + + max_check_attempts uint NOT NULL, + + check_timeperiod varchar(255) 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, + passive_checks_enabled boolenum NOT NULL, + event_handler_enabled boolenum NOT NULL, + notifications_enabled boolenum NOT NULL, + + flapping_enabled boolenum NOT NULL, + flapping_threshold_low float NOT NULL, + flapping_threshold_high float NOT NULL, + + perfdata_enabled boolenum NOT NULL, + + eventcommand varchar(255) NOT NULL, + eventcommand_id bytea20 DEFAULT NULL, + + is_volatile boolenum NOT NULL, + + 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 varchar(255) NOT NULL, + zone_id bytea20 DEFAULT NULL, + + command_endpoint varchar(255) 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(LOWER(display_name)); +CREATE INDEX idx_service_host_id ON service(host_id, display_name); +CREATE INDEX idx_service_name_ci ON service(LOWER(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 varchar(255) NOT NULL, + display_name varchar(255) 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, + 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, + is_handled boolenum NOT NULL, + is_reachable boolenum NOT NULL, + is_flapping boolenum NOT NULL, + is_overdue boolenum NOT NULL, + + is_acknowledged acked NOT NULL, + acknowledgement_comment_id bytea20 DEFAULT NULL, + last_comment_id bytea20 DEFAULT NULL, + + in_downtime boolenum NOT NULL, + + 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 varchar(255) 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, + + icinga2_version varchar(255) NOT NULL, + icinga2_start_time biguint NOT NULL, + icinga2_notifications_enabled boolenum NOT NULL, + icinga2_active_service_checks_enabled boolenum NOT NULL, + icinga2_active_host_checks_enabled boolenum NOT NULL, + icinga2_event_handlers_enabled boolenum NOT NULL, + icinga2_flap_detection_enabled boolenum NOT NULL, + icinga2_performance_data_enabled boolenum NOT NULL, + + 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 varchar(255) 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 varchar(64) DEFAULT NULL, + repeat_key boolenum NOT NULL, + required boolenum NOT NULL, + set_if varchar(255) DEFAULT NULL, + skip_key boolenum NOT NULL, + + 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 varchar(255) 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 varchar(64) DEFAULT NULL, + repeat_key boolenum NOT NULL, + required boolenum NOT NULL, + set_if varchar(255) DEFAULT NULL, + skip_key boolenum NOT NULL, + + 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 varchar(255) 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 varchar(64) DEFAULT NULL, + repeat_key boolenum NOT NULL, + required boolenum NOT NULL, + set_if varchar(255) DEFAULT NULL, + skip_key boolenum NOT NULL, + + 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, + 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 varchar(255) NOT NULL, + text text NOT NULL, + entry_type comment_type NOT NULL, + entry_time biguint NOT NULL, + is_persistent boolenum NOT NULL, + is_sticky boolenum NOT NULL, + 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, + 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 varchar(255) 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, + flexible_duration biguint NOT NULL, + + is_in_effect boolenum NOT NULL, + 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 varchar(255) 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 text 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(LOWER(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 text 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(LOWER(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 text 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(LOWER(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 varchar(255) NOT NULL, + display_name varchar(255) NOT NULL, + prefer_includes boolenum NOT NULL, + + 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 varchar(255) 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 varchar(255) NOT NULL, + display_name varchar(255) NOT NULL, + + email varchar(255) NOT NULL, + pager varchar(255) NOT NULL, + + notifications_enabled boolenum NOT NULL, + + 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"(LOWER(display_name)); +CREATE INDEX idx_user_name_ci ON "user"(LOWER(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 varchar(255) NOT NULL, + display_name varchar(255) 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(LOWER(display_name)); +CREATE INDEX idx_usergroup_name_ci ON usergroup(LOWER(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 varchar(255) NOT NULL, + + is_global boolenum NOT NULL, + 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, + host_id bytea20 NOT NULL, + service_id bytea20 DEFAULT NULL, + notification_id bytea20 NOT NULL, + + type notification_type NOT NULL, + 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, + host_id bytea20 NOT NULL, + service_id bytea20 DEFAULT NULL, + + event_time biguint NOT NULL, + state_type state_type NOT NULL, + 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, + host_id bytea20 NOT NULL, + service_id bytea20 DEFAULT NULL, + + entry_time biguint NOT NULL, + author varchar(255) NOT NULL, + cancelled_by varchar(255) DEFAULT NULL, + comment text NOT NULL, + is_flexible boolenum NOT NULL, + 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, + 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, + host_id bytea20 NOT NULL, + service_id bytea20 DEFAULT NULL, + + entry_time biguint NOT NULL, + author varchar(255) NOT NULL, + removed_by varchar(255) DEFAULT NULL, + comment text NOT NULL, + entry_type comment_type NOT NULL, + is_persistent boolenum NOT NULL, + is_sticky boolenum NOT NULL, + expire_time biguint DEFAULT NULL, + remove_time biguint DEFAULT NULL, + has_been_removed boolenum NOT NULL, + + 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, + 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, + host_id bytea20 NOT NULL, + service_id bytea20 DEFAULT NULL, + + set_time biguint NOT NULL, + clear_time biguint DEFAULT NULL, + author varchar(255) DEFAULT NULL, + cleared_by varchar(255) 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, + 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, + 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 (2, extract(epoch from now()) * 1000); From aed0e9124f4baf64d3a648501822b1030a93dfa5 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Thu, 23 Sep 2021 18:09:27 +0200 Subject: [PATCH 02/25] Avoid SQL syntax Postgres doesn't understand refs #136 --- pkg/driver/driver.go | 2 ++ pkg/icingadb/db.go | 66 ++++++++++++++++++++++++++++++++++++++++---- pkg/icingadb/ha.go | 10 +++---- 3 files changed, 67 insertions(+), 11 deletions(-) diff --git a/pkg/driver/driver.go b/pkg/driver/driver.go index f246861e..1e087f1e 100644 --- a/pkg/driver/driver.go +++ b/pkg/driver/driver.go @@ -8,6 +8,7 @@ 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" @@ -79,6 +80,7 @@ func (d Driver) OpenConnector(name string) (driver.Connector, error) { func Register(logger *logging.Logger) { sql.Register("icingadb-mysql", &Driver{ctxDriver: &mysql.MySQLDriver{}, Logger: logger}) _ = mysql.SetLogger(mysqlLogger(func(v ...interface{}) { logger.Debug(v...) })) + sqlx.BindDriver("icingadb-pgsql", sqlx.DOLLAR) } // ctxDriver helps ensure that we only support drivers that implement driver.Driver and driver.DriverContext. diff --git a/pkg/icingadb/db.go b/pkg/icingadb/db.go index bfbf9246..95de40f6 100644 --- a/pkg/icingadb/db.go +++ b/pkg/icingadb/db.go @@ -18,6 +18,8 @@ import ( "golang.org/x/sync/errgroup" "golang.org/x/sync/semaphore" "reflect" + "regexp" + "strconv" "strings" "sync" "time" @@ -101,10 +103,10 @@ 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( + return db.PostProcessPlaceholders(fmt.Sprintf( `DELETE FROM %s WHERE id IN (?)`, utils.TableName(from), - ) + )) } // BuildInsertStmt returns an INSERT INTO statement for the given struct. @@ -123,13 +125,22 @@ func (db *DB) BuildInsertStmt(into interface{}) (string, int) { // which the database ignores rows that have already been inserted. func (db *DB) BuildInsertIgnoreStmt(into interface{}) (string, int) { columns := db.BuildColumns(into) + var clause string + + switch db.DriverName() { + case "icingadb-mysql": + // MySQL treats UPDATE id = id as a no-op. + clause = "ON DUPLICATE KEY UPDATE id = id" + case "icingadb-pgsql": + clause = "ON CONFLICT DO NOTHING" + } return fmt.Sprintf( - // MySQL treats UPDATE id = id as a no-op. - `INSERT INTO %s (%s) VALUES (%s) ON DUPLICATE KEY UPDATE id = id`, + `INSERT INTO %s (%s) VALUES (%s) %s`, utils.TableName(into), strings.Join(columns, ", "), fmt.Sprintf(":%s", strings.Join(columns, ", :")), + clause, ), len(columns) } @@ -177,17 +188,45 @@ func (db *DB) BuildUpsertStmt(subject interface{}) (stmt string, placeholders in updateColumns = insertColumns } + var clause, setFormat string + switch db.DriverName() { + case "icingadb-mysql": + clause = "ON DUPLICATE KEY UPDATE" + setFormat = "%[1]s = VALUES(%[1]s)" + case "icingadb-pgsql": + var ids []string + for _, column := range insertColumns { + if column == "id" { + ids = []string{column} + break + } + } + + if ids == nil { + for _, column := range insertColumns { + if strings.HasSuffix(column, "_id") { + ids = append(ids, column) + break + } + } + } + + clause = fmt.Sprintf("ON CONFLICT (%s) DO UPDATE SET", strings.Join(ids, ",")) + 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`, + `INSERT INTO %s (%s) VALUES (%s) %s %s`, utils.TableName(subject), strings.Join(insertColumns, ","), fmt.Sprintf(":%s", strings.Join(insertColumns, ",:")), + clause, strings.Join(set, ","), ), len(insertColumns) } @@ -547,6 +586,21 @@ func (db *DB) GetSemaphoreForTable(table string) *semaphore.Weighted { } } +var placeholder = regexp.MustCompile(`\?`) + +// PostProcessPlaceholders returns query with placeholders (?) suitable for db. +func (db *DB) PostProcessPlaceholders(query string) string { + if db.DriverName() == "icingadb-pgsql" { + var i uint64 + return placeholder.ReplaceAllStringFunc(query, func(string) string { + i++ + return "$" + strconv.FormatUint(i, 10) + }) + } + + return query +} + func (db *DB) log(ctx context.Context, query string, counter *com.Counter) periodic.Stopper { return periodic.Start(ctx, db.logger.Interval(), func(tick periodic.Tick) { if count := counter.Reset(); count > 0 { diff --git a/pkg/icingadb/ha.go b/pkg/icingadb/ha.go index 1195462d..267a93c4 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.PostProcessPlaceholders("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.PostProcessPlaceholders("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.PostProcessPlaceholders("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) From 23d8938251005b975dbb21dd19674529803a9086 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Wed, 17 Nov 2021 17:37:20 +0100 Subject: [PATCH 03/25] .github/workflows/compliance/anonymize-license.pl: handle github.com/lib/pq license refs #136 --- .github/workflows/compliance/anonymize-license.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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(?: \(.+?\))?$/ || /^$/) { $_ = "" } From ad895b560d5e9cda402522e30904cf39c5731e78 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Thu, 23 Sep 2021 18:12:10 +0200 Subject: [PATCH 04/25] Handle Postgres-specific errors refs #136 --- go.mod | 1 + go.sum | 3 ++- pkg/icingadb/db.go | 30 ++++++++++++++++++++++++++++++ pkg/utils/utils.go | 11 +++++++++++ 4 files changed, 44 insertions(+), 1 deletion(-) 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/icingadb/db.go b/pkg/icingadb/db.go index 95de40f6..0f3e9f00 100644 --- a/pkg/icingadb/db.go +++ b/pkg/icingadb/db.go @@ -14,6 +14,7 @@ import ( "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" @@ -630,6 +631,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/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 } } From 1c3bfcf99de332b562267a45c02d70f06d49e6a5 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Thu, 23 Sep 2021 18:13:52 +0200 Subject: [PATCH 05/25] Support Postgres refs #136 --- config.yml.example | 1 + pkg/config/database.go | 94 +++++++++++++++++++++++++++++++++--------- pkg/driver/driver.go | 3 +- pkg/driver/pgsql.go | 22 ++++++++++ 4 files changed, 100 insertions(+), 20 deletions(-) create mode 100644 pkg/driver/pgsql.go 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/pkg/config/database.go b/pkg/config/database.go index 8857425e..454f4089 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,73 @@ 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 - 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 +121,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 1e087f1e..eb96cf3b 100644 --- a/pkg/driver/driver.go +++ b/pkg/driver/driver.go @@ -76,9 +76,10 @@ 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("icingadb-pgsql", &Driver{ctxDriver: PgSQLDriver{}, Logger: logger}) _ = mysql.SetLogger(mysqlLogger(func(v ...interface{}) { logger.Debug(v...) })) sqlx.BindDriver("icingadb-pgsql", sqlx.DOLLAR) } 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) +} From db996b1839df9fbd5a905fec3fc11bd7c883a7b1 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Thu, 23 Sep 2021 18:14:07 +0200 Subject: [PATCH 06/25] Document Postgres support refs #136 --- README.md | 5 +++-- doc/01-About.md | 5 +++-- doc/02-Installation.md | 40 ++++++++++++++++++++++++++++++++++++++-- doc/03-Configuration.md | 1 + 4 files changed, 45 insertions(+), 6 deletions(-) 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/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..30c67730 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,38 @@ 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 -O icingadb icingadb +``` + +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. From eca23a95ed87b9deb17789d2836b0e74e046ab90 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Fri, 24 Sep 2021 10:21:35 +0200 Subject: [PATCH 07/25] DB#Build*Stmt(): quote table names Rationale: see https://dba.stackexchange.com/q/73136 refs #136 --- pkg/config/database.go | 1 + pkg/icingadb/db.go | 12 ++++++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/pkg/config/database.go b/pkg/config/database.go index 454f4089..71c557ff 100644 --- a/pkg/config/database.go +++ b/pkg/config/database.go @@ -49,6 +49,7 @@ func (d *Database) Open(logger *logging.Logger) (*icingadb.DB, error) { 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 { diff --git a/pkg/icingadb/db.go b/pkg/icingadb/db.go index 0f3e9f00..9f676867 100644 --- a/pkg/icingadb/db.go +++ b/pkg/icingadb/db.go @@ -105,7 +105,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 db.PostProcessPlaceholders(fmt.Sprintf( - `DELETE FROM %s WHERE id IN (?)`, + `DELETE FROM "%s" WHERE id IN (?)`, utils.TableName(from), )) } @@ -115,7 +115,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, ", :")), @@ -137,7 +137,7 @@ func (db *DB) BuildInsertIgnoreStmt(into interface{}) (string, int) { } return fmt.Sprintf( - `INSERT INTO %s (%s) VALUES (%s) %s`, + `INSERT INTO "%s" (%s) VALUES (%s) %s`, utils.TableName(into), strings.Join(columns, ", "), fmt.Sprintf(":%s", strings.Join(columns, ", :")), @@ -149,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), ) @@ -172,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 @@ -223,7 +223,7 @@ func (db *DB) BuildUpsertStmt(subject interface{}) (stmt string, placeholders in } return fmt.Sprintf( - `INSERT INTO %s (%s) VALUES (%s) %s %s`, + `INSERT INTO "%s" (%s) VALUES (%s) %s %s`, utils.TableName(subject), strings.Join(insertColumns, ","), fmt.Sprintf(":%s", strings.Join(insertColumns, ",:")), From 908bb420047465e08ee67c546ad9487dd4573678 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Fri, 24 Sep 2021 11:03:39 +0200 Subject: [PATCH 08/25] Postgres: upsert: only handle primary key conflicts refs #136 --- pkg/icingadb/db.go | 27 ++++++--------------------- 1 file changed, 6 insertions(+), 21 deletions(-) diff --git a/pkg/icingadb/db.go b/pkg/icingadb/db.go index 9f676867..5bf776d9 100644 --- a/pkg/icingadb/db.go +++ b/pkg/icingadb/db.go @@ -125,6 +125,7 @@ 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 @@ -133,12 +134,12 @@ func (db *DB) BuildInsertIgnoreStmt(into interface{}) (string, int) { // MySQL treats UPDATE id = id as a no-op. clause = "ON DUPLICATE KEY UPDATE id = id" case "icingadb-pgsql": - clause = "ON CONFLICT DO NOTHING" + clause = fmt.Sprintf("ON CONFLICT ON CONSTRAINT pk_%s DO NOTHING", table) } return fmt.Sprintf( `INSERT INTO "%s" (%s) VALUES (%s) %s`, - utils.TableName(into), + table, strings.Join(columns, ", "), fmt.Sprintf(":%s", strings.Join(columns, ", :")), clause, @@ -181,6 +182,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 { @@ -195,24 +197,7 @@ func (db *DB) BuildUpsertStmt(subject interface{}) (stmt string, placeholders in clause = "ON DUPLICATE KEY UPDATE" setFormat = "%[1]s = VALUES(%[1]s)" case "icingadb-pgsql": - var ids []string - for _, column := range insertColumns { - if column == "id" { - ids = []string{column} - break - } - } - - if ids == nil { - for _, column := range insertColumns { - if strings.HasSuffix(column, "_id") { - ids = append(ids, column) - break - } - } - } - - clause = fmt.Sprintf("ON CONFLICT (%s) DO UPDATE SET", strings.Join(ids, ",")) + clause = fmt.Sprintf("ON CONFLICT ON CONSTRAINT pk_%s DO UPDATE SET", table) setFormat = "%[1]s = EXCLUDED.%[1]s" } @@ -224,7 +209,7 @@ func (db *DB) BuildUpsertStmt(subject interface{}) (stmt string, placeholders in return fmt.Sprintf( `INSERT INTO "%s" (%s) VALUES (%s) %s %s`, - utils.TableName(subject), + table, strings.Join(insertColumns, ","), fmt.Sprintf(":%s", strings.Join(insertColumns, ",:")), clause, From 5b87fd94ee5df52a2d9d017c61f576fe181feaeb Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Mon, 27 Sep 2021 16:20:15 +0200 Subject: [PATCH 09/25] Don't re-invent sqlx.DB#Rebind() refs #136 --- pkg/icingadb/db.go | 19 +------------------ pkg/icingadb/ha.go | 6 +++--- 2 files changed, 4 insertions(+), 21 deletions(-) diff --git a/pkg/icingadb/db.go b/pkg/icingadb/db.go index 5bf776d9..8b78361c 100644 --- a/pkg/icingadb/db.go +++ b/pkg/icingadb/db.go @@ -19,8 +19,6 @@ import ( "golang.org/x/sync/errgroup" "golang.org/x/sync/semaphore" "reflect" - "regexp" - "strconv" "strings" "sync" "time" @@ -104,7 +102,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 db.PostProcessPlaceholders(fmt.Sprintf( + return db.Rebind(fmt.Sprintf( `DELETE FROM "%s" WHERE id IN (?)`, utils.TableName(from), )) @@ -572,21 +570,6 @@ func (db *DB) GetSemaphoreForTable(table string) *semaphore.Weighted { } } -var placeholder = regexp.MustCompile(`\?`) - -// PostProcessPlaceholders returns query with placeholders (?) suitable for db. -func (db *DB) PostProcessPlaceholders(query string) string { - if db.DriverName() == "icingadb-pgsql" { - var i uint64 - return placeholder.ReplaceAllStringFunc(query, func(string) string { - i++ - return "$" + strconv.FormatUint(i, 10) - }) - } - - return query -} - func (db *DB) log(ctx context.Context, query string, counter *com.Counter) periodic.Stopper { return periodic.Start(ctx, db.logger.Interval(), func(tick periodic.Tick) { if count := counter.Reset(); count > 0 { diff --git a/pkg/icingadb/ha.go b/pkg/icingadb/ha.go index 267a93c4..743c040a 100644 --- a/pkg/icingadb/ha.go +++ b/pkg/icingadb/ha.go @@ -236,7 +236,7 @@ func (h *HA) realize(ctx context.Context, s *icingaredisv1.IcingaStatus, t *type return errors.Wrap(errBegin, "can't start transaction") } - query := h.db.PostProcessPlaceholders("SELECT id, heartbeat FROM icingadb_instance " + + 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 := h.db.PostProcessPlaceholders("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,7 +351,7 @@ func (h *HA) removeOldInstances(s *icingaredisv1.IcingaStatus, envId types.Binar case <-h.ctx.Done(): return case <-time.After(timeout): - query := h.db.PostProcessPlaceholders("DELETE FROM icingadb_instance " + + 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, From dccf02e11d440d96f6a850cefd44d39ab8f9898f Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Fri, 19 Nov 2021 16:28:28 +0100 Subject: [PATCH 10/25] Introduce BulkChunkSplitPolicy --- pkg/com/entity_bulker.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/pkg/com/entity_bulker.go b/pkg/com/entity_bulker.go index 1ccb873b..8e151ea3 100644 --- a/pkg/com/entity_bulker.go +++ b/pkg/com/entity_bulker.go @@ -8,6 +8,17 @@ import ( "time" ) +// BulkChunkSplitPolicy is a state machine which tracks the items of a chunk a bulker assembles. +type BulkChunkSplitPolicy interface { + // Track 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. + Track(contracts.Entity) bool + + // Reset resets the state machine. + Reset() +} + // EntityBulker reads all entities from a channel and streams them in chunks into a Bulk channel. type EntityBulker struct { ch chan []contracts.Entity From 6b59f2e47c5865af2535bcd5087fc62274d314ed Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Fri, 19 Nov 2021 16:29:06 +0100 Subject: [PATCH 11/25] Introduce NeverSplit --- pkg/com/entity_bulker.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pkg/com/entity_bulker.go b/pkg/com/entity_bulker.go index 8e151ea3..23fe567e 100644 --- a/pkg/com/entity_bulker.go +++ b/pkg/com/entity_bulker.go @@ -19,6 +19,16 @@ type BulkChunkSplitPolicy interface { Reset() } +// NeverSplit is a pseudo state machine which never demands splitting. +type NeverSplit struct{} + +func (NeverSplit) Track(contracts.Entity) bool { + return false +} + +func (NeverSplit) Reset() { +} + // EntityBulker reads all entities from a channel and streams them in chunks into a Bulk channel. type EntityBulker struct { ch chan []contracts.Entity @@ -135,3 +145,5 @@ func oneEntityBulk(ctx context.Context, ch <-chan contracts.Entity) <-chan []con return out } + +var _ BulkChunkSplitPolicy = NeverSplit{} From 3854424a91aad78fed83de6818ee61c74756b61b Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Fri, 19 Nov 2021 16:29:20 +0100 Subject: [PATCH 12/25] Introduce SplitOnDupId --- pkg/com/entity_bulker.go | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/pkg/com/entity_bulker.go b/pkg/com/entity_bulker.go index 23fe567e..092818b5 100644 --- a/pkg/com/entity_bulker.go +++ b/pkg/com/entity_bulker.go @@ -29,6 +29,33 @@ func (NeverSplit) Track(contracts.Entity) bool { func (NeverSplit) Reset() { } +// SplitOnDupId is a state machine which tracks the inputs' IDs. +// Once an already seen input arrives, it demands splitting. +type SplitOnDupId struct { + seenIds map[string]struct{} +} + +func (sodi *SplitOnDupId) Track(entity contracts.Entity) bool { + id := entity.ID().String() + + _, ok := sodi.seenIds[id] + if ok { + sodi.seenIds = map[string]struct{}{id: {}} + } else { + sodi.seenIds[id] = struct{}{} + } + + return ok +} + +func (sodi *SplitOnDupId) Reset() { + sodi.seenIds = map[string]struct{}{} +} + +func NewSplitOnDupId() *SplitOnDupId { + return &SplitOnDupId{map[string]struct{}{}} +} + // EntityBulker reads all entities from a channel and streams them in chunks into a Bulk channel. type EntityBulker struct { ch chan []contracts.Entity @@ -146,4 +173,7 @@ func oneEntityBulk(ctx context.Context, ch <-chan contracts.Entity) <-chan []con return out } -var _ BulkChunkSplitPolicy = NeverSplit{} +var ( + _ BulkChunkSplitPolicy = NeverSplit{} + _ BulkChunkSplitPolicy = (*SplitOnDupId)(nil) +) From d4f2c13d9c19514fe1d487d303b713d8888c02d1 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Fri, 19 Nov 2021 16:37:34 +0100 Subject: [PATCH 13/25] NewEntityBulker(): allow custom BulkChunkSplitPolicy refs #136 --- pkg/com/entity_bulker.go | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/pkg/com/entity_bulker.go b/pkg/com/entity_bulker.go index 092818b5..e07a40e4 100644 --- a/pkg/com/entity_bulker.go +++ b/pkg/com/entity_bulker.go @@ -64,14 +64,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, splitPolicy BulkChunkSplitPolicy, +) *EntityBulker { b := &EntityBulker{ ch: make(chan []contracts.Entity), ctx: ctx, mu: sync.Mutex{}, } - go b.run(ch, count) + go b.run(ch, count, splitPolicy) return b } @@ -81,7 +83,7 @@ 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, splitPolicy BulkChunkSplitPolicy) { defer close(b.ch) bufCh := make(chan contracts.Entity, count) @@ -119,6 +121,15 @@ func (b *EntityBulker) run(ch <-chan contracts.Entity, count int) { break } + if splitPolicy.Track(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 @@ -130,6 +141,8 @@ func (b *EntityBulker) run(ch <-chan contracts.Entity, count int) { if len(buf) > 0 { b.ch <- buf } + + splitPolicy.Reset() } return nil @@ -146,11 +159,11 @@ func BulkEntities(ctx context.Context, ch <-chan contracts.Entity, count int) <- return oneEntityBulk(ctx, ch) } - return NewEntityBulker(ctx, ch, count).Bulk() + return NewEntityBulker(ctx, ch, count, NeverSplit{}).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() { From db2c3af769b10a108904f7ede2312ac48def92c4 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Fri, 19 Nov 2021 16:40:28 +0100 Subject: [PATCH 14/25] BulkEntities(): allow custom BulkChunkSplitPolicy refs #136 --- pkg/com/entity_bulker.go | 6 ++++-- pkg/icingadb/db.go | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/pkg/com/entity_bulker.go b/pkg/com/entity_bulker.go index e07a40e4..07323df5 100644 --- a/pkg/com/entity_bulker.go +++ b/pkg/com/entity_bulker.go @@ -154,12 +154,14 @@ func (b *EntityBulker) run(ch <-chan contracts.Entity, count int, splitPolicy Bu } // 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, splitPolicy BulkChunkSplitPolicy, +) <-chan []contracts.Entity { if count <= 1 { return oneEntityBulk(ctx, ch) } - return NewEntityBulker(ctx, ch, count, NeverSplit{}).Bulk() + return NewEntityBulker(ctx, ch, count, splitPolicy).Bulk() } // oneEntityBulk operates just as NewEntityBulker(ctx, ch, 1, splitPolicy).Bulk(), diff --git a/pkg/icingadb/db.go b/pkg/icingadb/db.go index 8b78361c..8dfb2ab4 100644 --- a/pkg/icingadb/db.go +++ b/pkg/icingadb/db.go @@ -310,7 +310,7 @@ func (db *DB) NamedBulkExec( 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 { @@ -378,7 +378,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 { From 7f4b895ea951b66a2b5e0a005e0e05289cb6ef63 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Fri, 19 Nov 2021 16:55:29 +0100 Subject: [PATCH 15/25] NamedBulkExec(): allow custom BulkChunkSplitPolicy By the way avoid duplicate rows in the same upsert chunk to avoid Postgres error 21000 (ON CONFLICT DO UPDATE command cannot affect row a second time). refs #136 --- pkg/icingadb/db.go | 10 ++++++---- pkg/icingadb/runtime_updates.go | 6 +++--- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/pkg/icingadb/db.go b/pkg/icingadb/db.go index 8dfb2ab4..1d71ff91 100644 --- a/pkg/icingadb/db.go +++ b/pkg/icingadb/db.go @@ -304,13 +304,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, splitPolicy com.BulkChunkSplitPolicy, ) error { var counter com.Counter defer db.log(ctx, query, &counter).Stop() g, ctx := errgroup.WithContext(ctx) - bulk := com.BulkEntities(ctx, arg, count, com.NeverSplit{}) + bulk := com.BulkEntities(ctx, arg, count, splitPolicy) g.Go(func() error { for { @@ -501,7 +501,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. @@ -517,7 +517,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.NewSplitOnDupId(), + ) } // UpdateStreamed bulk updates the specified entities via NamedBulkExecTx. diff --git a/pkg/icingadb/runtime_updates.go b/pkg/icingadb/runtime_updates.go index 6616d9f2..d063976b 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.NewSplitOnDupId(), ) }) 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.NewSplitOnDupId(), ) }) 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.NewSplitOnDupId(), ) }) g.Go(func() error { From 3eb14274dd6330cd58727a3b83166fe188f72ce7 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Wed, 24 Nov 2021 16:03:57 +0100 Subject: [PATCH 16/25] DB#BuildDeleteStmt(): don't pre-rebind `?`s refs #136 --- pkg/icingadb/db.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/icingadb/db.go b/pkg/icingadb/db.go index 1d71ff91..7fbcb910 100644 --- a/pkg/icingadb/db.go +++ b/pkg/icingadb/db.go @@ -102,10 +102,10 @@ func (db *DB) BuildColumns(subject interface{}) []string { // BuildDeleteStmt returns a DELETE statement for the given struct. func (db *DB) BuildDeleteStmt(from interface{}) string { - return db.Rebind(fmt.Sprintf( + return fmt.Sprintf( `DELETE FROM "%s" WHERE id IN (?)`, utils.TableName(from), - )) + ) } // BuildInsertStmt returns an INSERT INTO statement for the given struct. From 10a70e8b71ad45812e74ccccf53d69ed21e3d62d Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Tue, 30 Nov 2021 16:49:29 +0100 Subject: [PATCH 17/25] Pass BulkChunkSplitPolicyFactory-s, not BulkChunkSplitPolicy-s refs #136 --- pkg/com/entity_bulker.go | 47 ++++++++++++++++++++------------- pkg/icingadb/db.go | 10 +++---- pkg/icingadb/runtime_updates.go | 6 ++--- 3 files changed, 36 insertions(+), 27 deletions(-) diff --git a/pkg/com/entity_bulker.go b/pkg/com/entity_bulker.go index 07323df5..168fa29b 100644 --- a/pkg/com/entity_bulker.go +++ b/pkg/com/entity_bulker.go @@ -19,23 +19,33 @@ type BulkChunkSplitPolicy interface { Reset() } -// NeverSplit is a pseudo state machine which never demands splitting. -type NeverSplit struct{} +type BulkChunkSplitPolicyFactory func() BulkChunkSplitPolicy -func (NeverSplit) Track(contracts.Entity) bool { +func NeverSplit() BulkChunkSplitPolicy { + return neverSplit{} +} + +func SplitOnDupId() BulkChunkSplitPolicy { + return &splitOnDupId{map[string]struct{}{}} +} + +// neverSplit is a pseudo state machine which never demands splitting. +type neverSplit struct{} + +func (neverSplit) Track(contracts.Entity) bool { return false } -func (NeverSplit) Reset() { +func (neverSplit) Reset() { } -// SplitOnDupId is a state machine which tracks the inputs' IDs. +// splitOnDupId is a state machine which tracks the inputs' IDs. // Once an already seen input arrives, it demands splitting. -type SplitOnDupId struct { +type splitOnDupId struct { seenIds map[string]struct{} } -func (sodi *SplitOnDupId) Track(entity contracts.Entity) bool { +func (sodi *splitOnDupId) Track(entity contracts.Entity) bool { id := entity.ID().String() _, ok := sodi.seenIds[id] @@ -48,14 +58,10 @@ func (sodi *SplitOnDupId) Track(entity contracts.Entity) bool { return ok } -func (sodi *SplitOnDupId) Reset() { +func (sodi *splitOnDupId) Reset() { sodi.seenIds = map[string]struct{}{} } -func NewSplitOnDupId() *SplitOnDupId { - return &SplitOnDupId{map[string]struct{}{}} -} - // EntityBulker reads all entities from a channel and streams them in chunks into a Bulk channel. type EntityBulker struct { ch chan []contracts.Entity @@ -65,7 +71,7 @@ type EntityBulker struct { // NewEntityBulker returns a new EntityBulker and starts streaming. func NewEntityBulker( - ctx context.Context, ch <-chan contracts.Entity, count int, splitPolicy BulkChunkSplitPolicy, + ctx context.Context, ch <-chan contracts.Entity, count int, splitPolicyFactory BulkChunkSplitPolicyFactory, ) *EntityBulker { b := &EntityBulker{ ch: make(chan []contracts.Entity), @@ -73,7 +79,7 @@ func NewEntityBulker( mu: sync.Mutex{}, } - go b.run(ch, count, splitPolicy) + go b.run(ch, count, splitPolicyFactory) return b } @@ -83,10 +89,11 @@ func (b *EntityBulker) Bulk() <-chan []contracts.Entity { return b.ch } -func (b *EntityBulker) run(ch <-chan contracts.Entity, count int, splitPolicy BulkChunkSplitPolicy) { +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 { @@ -155,13 +162,13 @@ func (b *EntityBulker) run(ch <-chan contracts.Entity, count int, splitPolicy Bu // 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, splitPolicy BulkChunkSplitPolicy, + 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, splitPolicy).Bulk() + return NewEntityBulker(ctx, ch, count, splitPolicyFactory).Bulk() } // oneEntityBulk operates just as NewEntityBulker(ctx, ch, 1, splitPolicy).Bulk(), @@ -189,6 +196,8 @@ func oneEntityBulk(ctx context.Context, ch <-chan contracts.Entity) <-chan []con } var ( - _ BulkChunkSplitPolicy = NeverSplit{} - _ BulkChunkSplitPolicy = (*SplitOnDupId)(nil) + _ BulkChunkSplitPolicy = neverSplit{} + _ BulkChunkSplitPolicy = (*splitOnDupId)(nil) + _ BulkChunkSplitPolicyFactory = NeverSplit + _ BulkChunkSplitPolicyFactory = SplitOnDupId ) diff --git a/pkg/icingadb/db.go b/pkg/icingadb/db.go index 7fbcb910..3a440046 100644 --- a/pkg/icingadb/db.go +++ b/pkg/icingadb/db.go @@ -304,13 +304,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, splitPolicy com.BulkChunkSplitPolicy, + 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, splitPolicy) + bulk := com.BulkEntities(ctx, arg, count, splitPolicyFactory) g.Go(func() error { for { @@ -378,7 +378,7 @@ func (db *DB) NamedBulkExecTx( defer db.log(ctx, query, &counter).Stop() g, ctx := errgroup.WithContext(ctx) - bulk := com.BulkEntities(ctx, arg, count, com.NeverSplit{}) + bulk := com.BulkEntities(ctx, arg, count, com.NeverSplit) g.Go(func() error { for { @@ -501,7 +501,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, com.NeverSplit{}) + return db.NamedBulkExec(ctx, stmt, db.BatchSizeByPlaceholders(placeholders), sem, forward, nil, com.NeverSplit) } // UpsertStreamed bulk upserts the specified entities via NamedBulkExec. @@ -518,7 +518,7 @@ func (db *DB) UpsertStreamed(ctx context.Context, entities <-chan contracts.Enti stmt, placeholders := db.BuildUpsertStmt(first) return db.NamedBulkExec( - ctx, stmt, db.BatchSizeByPlaceholders(placeholders), sem, forward, succeeded, com.NewSplitOnDupId(), + ctx, stmt, db.BatchSizeByPlaceholders(placeholders), sem, forward, succeeded, com.SplitOnDupId, ) } diff --git a/pkg/icingadb/runtime_updates.go b/pkg/icingadb/runtime_updates.go index d063976b..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, com.NewSplitOnDupId(), + 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, com.NewSplitOnDupId(), + 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, com.NewSplitOnDupId(), + ctx, cvFlatStmt, cvFlatCount, sem, flatCustomvars, upsertedFlatCustomvars, com.SplitOnDupId, ) }) g.Go(func() error { From 8da164c50f1d3e33fb3619ca86355c4f19bb05a1 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Tue, 30 Nov 2021 16:56:44 +0100 Subject: [PATCH 18/25] Throw BulkChunkSplitPolicy#Reset() away refs #136 --- pkg/com/entity_bulker.go | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/pkg/com/entity_bulker.go b/pkg/com/entity_bulker.go index 168fa29b..bff50857 100644 --- a/pkg/com/entity_bulker.go +++ b/pkg/com/entity_bulker.go @@ -14,9 +14,6 @@ type BulkChunkSplitPolicy interface { // 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. Track(contracts.Entity) bool - - // Reset resets the state machine. - Reset() } type BulkChunkSplitPolicyFactory func() BulkChunkSplitPolicy @@ -36,9 +33,6 @@ func (neverSplit) Track(contracts.Entity) bool { return false } -func (neverSplit) Reset() { -} - // splitOnDupId is a state machine which tracks the inputs' IDs. // Once an already seen input arrives, it demands splitting. type splitOnDupId struct { @@ -58,10 +52,6 @@ func (sodi *splitOnDupId) Track(entity contracts.Entity) bool { return ok } -func (sodi *splitOnDupId) Reset() { - sodi.seenIds = map[string]struct{}{} -} - // EntityBulker reads all entities from a channel and streams them in chunks into a Bulk channel. type EntityBulker struct { ch chan []contracts.Entity @@ -149,7 +139,7 @@ func (b *EntityBulker) run(ch <-chan contracts.Entity, count int, splitPolicyFac b.ch <- buf } - splitPolicy.Reset() + splitPolicy = splitPolicyFactory() } return nil From b84fc6211266f485996deca096259e453a8a43b0 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Tue, 30 Nov 2021 17:07:42 +0100 Subject: [PATCH 19/25] Simplify BulkChunkSplitPolicy refs #136 --- pkg/com/entity_bulker.go | 60 +++++++++++++++++----------------------- 1 file changed, 25 insertions(+), 35 deletions(-) diff --git a/pkg/com/entity_bulker.go b/pkg/com/entity_bulker.go index bff50857..0396ec09 100644 --- a/pkg/com/entity_bulker.go +++ b/pkg/com/entity_bulker.go @@ -9,47 +9,39 @@ import ( ) // BulkChunkSplitPolicy is a state machine which tracks the items of a chunk a bulker assembles. -type BulkChunkSplitPolicy interface { - // Track 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. - Track(contracts.Entity) bool -} +// 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{} + return neverSplit } -func SplitOnDupId() BulkChunkSplitPolicy { - return &splitOnDupId{map[string]struct{}{}} -} - -// neverSplit is a pseudo state machine which never demands splitting. -type neverSplit struct{} - -func (neverSplit) Track(contracts.Entity) bool { - return false -} - -// splitOnDupId is a state machine which tracks the inputs' IDs. +// SplitOnDupId returns a state machine which tracks the inputs' IDs. // Once an already seen input arrives, it demands splitting. -type splitOnDupId struct { - seenIds map[string]struct{} +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 (sodi *splitOnDupId) Track(entity contracts.Entity) bool { - id := entity.ID().String() - - _, ok := sodi.seenIds[id] - if ok { - sodi.seenIds = map[string]struct{}{id: {}} - } else { - sodi.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. @@ -118,7 +110,7 @@ func (b *EntityBulker) run(ch <-chan contracts.Entity, count int, splitPolicyFac break } - if splitPolicy.Track(v) { + if splitPolicy(v) { if len(buf) > 0 { b.ch <- buf buf = make([]contracts.Entity, 0, count) @@ -186,8 +178,6 @@ func oneEntityBulk(ctx context.Context, ch <-chan contracts.Entity) <-chan []con } var ( - _ BulkChunkSplitPolicy = neverSplit{} - _ BulkChunkSplitPolicy = (*splitOnDupId)(nil) _ BulkChunkSplitPolicyFactory = NeverSplit _ BulkChunkSplitPolicyFactory = SplitOnDupId ) From c3b8f80abf41bfb9c1b42fc369c0138e912b15d9 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Tue, 30 Nov 2021 17:55:41 +0100 Subject: [PATCH 20/25] Decouple *SQL schema versions refs #136 --- cmd/icingadb/main.go | 17 +++++++++++++---- schema/pgsql/schema.sql | 2 +- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/cmd/icingadb/main.go b/cmd/icingadb/main.go index b23c952c..634de299 100644 --- a/cmd/icingadb/main.go +++ b/cmd/icingadb/main.go @@ -24,10 +24,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 +318,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 "icingadb-mysql": + expectedDbSchemaVersion = expectedMysqlSchemaVersion + case "icingadb-pgsql": + 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/schema/pgsql/schema.sql b/schema/pgsql/schema.sql index 6c5fa777..cc1cdda1 100644 --- a/schema/pgsql/schema.sql +++ b/schema/pgsql/schema.sql @@ -1911,4 +1911,4 @@ CREATE TABLE icingadb_schema ( ALTER SEQUENCE icingadb_schema_id_seq OWNED BY icingadb_schema.id; INSERT INTO icingadb_schema (version, timestamp) - VALUES (2, extract(epoch from now()) * 1000); + VALUES (1, extract(epoch from now()) * 1000); From 2cf4529cc0f11245d421d279f55f3cc10840ca98 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Mon, 13 Dec 2021 13:09:04 +0100 Subject: [PATCH 21/25] Postgres: use case-insensitive CITEXT columns not to require web to LOWER(). refs #136 --- doc/02-Installation.md | 4 ++ schema/pgsql/schema.sql | 116 +++++++++++++++++++++------------------- 2 files changed, 64 insertions(+), 56 deletions(-) diff --git a/doc/02-Installation.md b/doc/02-Installation.md index 30c67730..7abba5c5 100644 --- a/doc/02-Installation.md +++ b/doc/02-Installation.md @@ -172,8 +172,12 @@ Set up a PostgreSQL database for Icinga DB: createuser -P icingadb createdb -E UTF8 -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: ``` diff --git a/schema/pgsql/schema.sql b/schema/pgsql/schema.sql index cc1cdda1..b30ef910 100644 --- a/schema/pgsql/schema.sql +++ b/schema/pgsql/schema.sql @@ -1,5 +1,9 @@ -- 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 ); @@ -32,20 +36,20 @@ CREATE TABLE host ( properties_checksum bytea20 NOT NULL, name varchar(255) NOT NULL, - name_ci varchar(255) NOT NULL, - display_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 varchar(255) NOT NULL, + checkcommand citext NOT NULL, checkcommand_id bytea20 NOT NULL, max_check_attempts uint NOT NULL, - check_timeperiod varchar(255) NOT NULL, + check_timeperiod citext NOT NULL, check_timeperiod_id bytea20 DEFAULT NULL, check_timeout uint DEFAULT NULL, @@ -63,7 +67,7 @@ CREATE TABLE host ( perfdata_enabled boolenum NOT NULL, - eventcommand varchar(255) NOT NULL, + eventcommand citext NOT NULL, eventcommand_id bytea20 DEFAULT NULL, is_volatile boolenum NOT NULL, @@ -74,10 +78,10 @@ CREATE TABLE host ( icon_image_id bytea20 DEFAULT NULL, icon_image_alt varchar(32) NOT NULL, - zone varchar(255) NOT NULL, + zone citext NOT NULL, zone_id bytea20 DEFAULT NULL, - command_endpoint varchar(255) NOT NULL, + command_endpoint citext NOT NULL, command_endpoint_id bytea20 DEFAULT NULL, CONSTRAINT pk_host PRIMARY KEY (id) @@ -101,8 +105,8 @@ 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(LOWER(display_name)); -CREATE INDEX idx_host_name_ci ON host(LOWER(name_ci)); +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)'; @@ -137,8 +141,8 @@ CREATE TABLE hostgroup ( properties_checksum bytea20 NOT NULL, name varchar(255) NOT NULL, - name_ci varchar(255) NOT NULL, - display_name varchar(255) NOT NULL, + name_ci citext NOT NULL, + display_name citext NOT NULL, zone_id bytea20 DEFAULT NULL, @@ -307,15 +311,15 @@ CREATE TABLE service ( host_id bytea20 NOT NULL, name varchar(255) NOT NULL, - name_ci varchar(255) NOT NULL, - display_name varchar(255) NOT NULL, + name_ci citext NOT NULL, + display_name citext NOT NULL, - checkcommand varchar(255) NOT NULL, + checkcommand citext NOT NULL, checkcommand_id bytea20 NOT NULL, max_check_attempts uint NOT NULL, - check_timeperiod varchar(255) NOT NULL, + check_timeperiod citext NOT NULL, check_timeperiod_id bytea20 DEFAULT NULL, check_timeout uint DEFAULT NULL, @@ -333,7 +337,7 @@ CREATE TABLE service ( perfdata_enabled boolenum NOT NULL, - eventcommand varchar(255) NOT NULL, + eventcommand citext NOT NULL, eventcommand_id bytea20 DEFAULT NULL, is_volatile boolenum NOT NULL, @@ -344,10 +348,10 @@ CREATE TABLE service ( icon_image_id bytea20 DEFAULT NULL, icon_image_alt varchar(32) NOT NULL, - zone varchar(255) NOT NULL, + zone citext NOT NULL, zone_id bytea20 DEFAULT NULL, - command_endpoint varchar(255) NOT NULL, + command_endpoint citext NOT NULL, command_endpoint_id bytea20 DEFAULT NULL, CONSTRAINT pk_service PRIMARY KEY (id) @@ -367,9 +371,9 @@ 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(LOWER(display_name)); +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(LOWER(name_ci)); +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)'; @@ -403,8 +407,8 @@ CREATE TABLE servicegroup ( properties_checksum bytea20 NOT NULL, name varchar(255) NOT NULL, - name_ci varchar(255) NOT NULL, - display_name varchar(255) NOT NULL, + name_ci citext NOT NULL, + display_name citext NOT NULL, zone_id bytea20 DEFAULT NULL, @@ -574,7 +578,7 @@ CREATE TABLE endpoint ( properties_checksum bytea20 NOT NULL, name varchar(255) NOT NULL, - name_ci varchar(255) NOT NULL, + name_ci citext NOT NULL, zone_id bytea20 NOT NULL, @@ -640,7 +644,7 @@ CREATE TABLE checkcommand ( properties_checksum bytea20 NOT NULL, name varchar(255) NOT NULL, - name_ci varchar(255) NOT NULL, + name_ci citext NOT NULL, command text NOT NULL, timeout uint NOT NULL, @@ -670,7 +674,7 @@ CREATE TABLE checkcommand_argument ( argument_value text DEFAULT NULL, argument_order smallint DEFAULT NULL, description text DEFAULT NULL, - argument_key_override varchar(64) DEFAULT NULL, + argument_key_override citext DEFAULT NULL, repeat_key boolenum NOT NULL, required boolenum NOT NULL, set_if varchar(255) DEFAULT NULL, @@ -745,7 +749,7 @@ CREATE TABLE eventcommand ( properties_checksum bytea20 NOT NULL, name varchar(255) NOT NULL, - name_ci varchar(255) NOT NULL, + name_ci citext NOT NULL, command text NOT NULL, timeout smalluint NOT NULL, @@ -775,7 +779,7 @@ CREATE TABLE eventcommand_argument ( argument_value text DEFAULT NULL, argument_order smallint DEFAULT NULL, description text DEFAULT NULL, - argument_key_override varchar(64) DEFAULT NULL, + argument_key_override citext DEFAULT NULL, repeat_key boolenum NOT NULL, required boolenum NOT NULL, set_if varchar(255) DEFAULT NULL, @@ -848,7 +852,7 @@ CREATE TABLE notificationcommand ( properties_checksum bytea20 NOT NULL, name varchar(255) NOT NULL, - name_ci varchar(255) NOT NULL, + name_ci citext NOT NULL, command text NOT NULL, timeout smalluint NOT NULL, @@ -878,7 +882,7 @@ CREATE TABLE notificationcommand_argument ( argument_value text DEFAULT NULL, argument_order smallint DEFAULT NULL, description text DEFAULT NULL, - argument_key_override varchar(64) DEFAULT NULL, + argument_key_override citext DEFAULT NULL, repeat_key boolenum NOT NULL, required boolenum NOT NULL, set_if varchar(255) DEFAULT NULL, @@ -954,7 +958,7 @@ CREATE TABLE comment ( properties_checksum bytea20 NOT NULL, name varchar(548) NOT NULL, - author varchar(255) NOT NULL, + author citext NOT NULL, text text NOT NULL, entry_type comment_type NOT NULL, entry_time biguint NOT NULL, @@ -1007,7 +1011,7 @@ CREATE TABLE downtime ( properties_checksum bytea20 NOT NULL, name varchar(548) NOT NULL, - author varchar(255) NOT NULL, + author citext NOT NULL, comment text NOT NULL, entry_time biguint NOT NULL, scheduled_start_time biguint NOT NULL, @@ -1079,7 +1083,7 @@ CREATE TABLE notification ( properties_checksum bytea20 NOT NULL, name varchar(255) NOT NULL, - name_ci varchar(255) NOT NULL, + name_ci citext NOT NULL, host_id bytea20 NOT NULL, service_id bytea20 DEFAULT NULL, @@ -1216,7 +1220,7 @@ 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 text NOT NULL, + icon_image citext NOT NULL, CONSTRAINT pk_icon_image PRIMARY KEY (environment_id, id) ); @@ -1224,7 +1228,7 @@ CREATE TABLE icon_image ( 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(LOWER(icon_image)); +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'; @@ -1232,7 +1236,7 @@ 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 text NOT NULL, + action_url citext NOT NULL, CONSTRAINT pk_action_url PRIMARY KEY (environment_id, id) ); @@ -1240,7 +1244,7 @@ CREATE TABLE action_url ( 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(LOWER(action_url)); +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'; @@ -1248,7 +1252,7 @@ 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 text NOT NULL, + notes_url citext NOT NULL, CONSTRAINT pk_notes_url PRIMARY KEY (environment_id, id) ); @@ -1256,7 +1260,7 @@ CREATE TABLE notes_url ( 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(LOWER(notes_url)); +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'; @@ -1269,8 +1273,8 @@ CREATE TABLE timeperiod ( properties_checksum bytea20 NOT NULL, name varchar(255) NOT NULL, - name_ci varchar(255) NOT NULL, - display_name varchar(255) NOT NULL, + name_ci citext NOT NULL, + display_name citext NOT NULL, prefer_includes boolenum NOT NULL, zone_id bytea20 DEFAULT NULL, @@ -1294,7 +1298,7 @@ CREATE TABLE timeperiod_range ( id bytea20 NOT NULL, environment_id bytea20 NOT NULL, timeperiod_id bytea20 NOT NULL, - range_key varchar(255) NOT NULL, + range_key citext NOT NULL, range_value varchar(255) NOT NULL, @@ -1420,8 +1424,8 @@ CREATE TABLE "user" ( properties_checksum bytea20 NOT NULL, name varchar(255) NOT NULL, - name_ci varchar(255) NOT NULL, - display_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, @@ -1445,8 +1449,8 @@ 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"(LOWER(display_name)); -CREATE INDEX idx_user_name_ci ON "user"(LOWER(name_ci)); +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)'; @@ -1467,8 +1471,8 @@ CREATE TABLE usergroup ( properties_checksum bytea20 NOT NULL, name varchar(255) NOT NULL, - name_ci varchar(255) NOT NULL, - display_name varchar(255) NOT NULL, + name_ci citext NOT NULL, + display_name citext NOT NULL, zone_id bytea20 DEFAULT NULL, @@ -1481,8 +1485,8 @@ 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(LOWER(display_name)); -CREATE INDEX idx_usergroup_name_ci ON usergroup(LOWER(name_ci)); +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)'; @@ -1568,7 +1572,7 @@ CREATE TABLE zone ( properties_checksum bytea20 NOT NULL, name varchar(255) NOT NULL, - name_ci varchar(255) NOT NULL, + name_ci citext NOT NULL, is_global boolenum NOT NULL, parent_id bytea20 DEFAULT NULL, @@ -1699,8 +1703,8 @@ CREATE TABLE downtime_history ( service_id bytea20 DEFAULT NULL, entry_time biguint NOT NULL, - author varchar(255) NOT NULL, - cancelled_by varchar(255) DEFAULT NULL, + author citext NOT NULL, + cancelled_by citext DEFAULT NULL, comment text NOT NULL, is_flexible boolenum NOT NULL, flexible_duration biguint NOT NULL, @@ -1744,8 +1748,8 @@ CREATE TABLE comment_history ( service_id bytea20 DEFAULT NULL, entry_time biguint NOT NULL, - author varchar(255) NOT NULL, - removed_by varchar(255) DEFAULT NULL, + author citext NOT NULL, + removed_by citext DEFAULT NULL, comment text NOT NULL, entry_type comment_type NOT NULL, is_persistent boolenum NOT NULL, @@ -1809,8 +1813,8 @@ CREATE TABLE acknowledgement_history ( set_time biguint NOT NULL, clear_time biguint DEFAULT NULL, - author varchar(255) DEFAULT NULL, - cleared_by varchar(255) 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, From 21ad40cf07e9237b772e19d19238aaf83e02a87a Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Mon, 13 Dec 2021 13:45:11 +0100 Subject: [PATCH 22/25] Docs: Postgres: explicitly set en_US.UTF-8 refs #136 --- doc/02-Installation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/02-Installation.md b/doc/02-Installation.md index 7abba5c5..5bfcddfa 100644 --- a/doc/02-Installation.md +++ b/doc/02-Installation.md @@ -171,7 +171,7 @@ Set up a PostgreSQL database for Icinga DB: # su -l postgres createuser -P icingadb -createdb -E UTF8 -O icingadb icingadb +createdb -E UTF8 --locale en_US.UTF-8 -T template0 -O icingadb icingadb psql icingadb <<<'CREATE EXTENSION IF NOT EXISTS citext;' ``` From 53c0aa431f9c5c8e78017ab49e182df7960ebc02 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Mon, 13 Dec 2021 17:04:21 +0100 Subject: [PATCH 23/25] Postgres: replace ENUM DOMAINs with TYPEs Before: idb=# select * from icingadb_instance where responsible = 'y'; ERROR: operator does not exist: boolenum = unknown LINE 1: select * from icingadb_instance where responsible = 'y'; ^ HINT: No operator matches the given name and argument type(s). You might need to add explicit type casts. idb=# refs #136 --- schema/pgsql/schema.sql | 166 +++++++++++++++++++--------------------- 1 file changed, 79 insertions(+), 87 deletions(-) diff --git a/schema/pgsql/schema.sql b/schema/pgsql/schema.sql index b30ef910..20b22e0c 100644 --- a/schema/pgsql/schema.sql +++ b/schema/pgsql/schema.sql @@ -13,21 +13,13 @@ CREATE DOMAIN uint AS bigint CONSTRAINT between_0_and_4294967295 CHECK ( VALUE I 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_t AS ENUM ( 'n', 'y' ); -CREATE TYPE acked_t AS ENUM ( 'n', 'y', 'sticky' ); -CREATE TYPE state_type_t AS ENUM ( 'hard', 'soft' ); -CREATE TYPE checkable_type_t AS ENUM ( 'host', 'service' ); -CREATE TYPE comment_type_t AS ENUM ( 'comment', 'ack' ); -CREATE TYPE notification_type_t AS ENUM ( 'downtime_start', 'downtime_end', 'downtime_removed', 'custom', 'acknowledgement', 'problem', 'recovery', 'flapping_start', 'flapping_end' ); -CREATE TYPE history_type_t AS ENUM ( 'notification', 'state_change', 'downtime_start', 'downtime_end', 'comment_add', 'comment_remove', 'flapping_start', 'flapping_end', 'ack_set', 'ack_clear' ); - -CREATE DOMAIN boolenum AS boolenum_t DEFAULT 'n'; -CREATE DOMAIN acked AS acked_t DEFAULT 'n'; -CREATE DOMAIN state_type AS state_type_t DEFAULT 'hard'; -CREATE DOMAIN checkable_type AS checkable_type_t DEFAULT 'host'; -CREATE DOMAIN comment_type AS comment_type_t DEFAULT 'comment'; -CREATE DOMAIN notification_type AS notification_type_t DEFAULT 'downtime_start'; -CREATE DOMAIN history_type AS history_type_t DEFAULT 'notification'; +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, @@ -56,21 +48,21 @@ CREATE TABLE host ( check_interval uint NOT NULL, check_retry_interval uint NOT NULL, - active_checks_enabled boolenum NOT NULL, - passive_checks_enabled boolenum NOT NULL, - event_handler_enabled boolenum NOT NULL, - notifications_enabled boolenum 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, + flapping_enabled boolenum NOT NULL DEFAULT 'n', flapping_threshold_low float NOT NULL, flapping_threshold_high float NOT NULL, - perfdata_enabled boolenum NOT NULL, + perfdata_enabled boolenum NOT NULL DEFAULT 'n', eventcommand citext NOT NULL, eventcommand_id bytea20 DEFAULT NULL, - is_volatile boolenum NOT NULL, + is_volatile boolenum NOT NULL DEFAULT 'n', action_url_id bytea20 DEFAULT NULL, notes_url_id bytea20 DEFAULT NULL, @@ -237,7 +229,7 @@ CREATE TABLE host_state ( environment_id bytea20 NOT NULL, properties_checksum bytea20 NOT NULL, - state_type state_type 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, @@ -252,17 +244,17 @@ CREATE TABLE host_state ( check_commandline text DEFAULT NULL, - is_problem boolenum NOT NULL, - is_handled boolenum NOT NULL, - is_reachable boolenum NOT NULL, - is_flapping boolenum NOT NULL, - is_overdue boolenum NOT 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, + is_acknowledged acked NOT NULL DEFAULT 'n', acknowledgement_comment_id bytea20 DEFAULT NULL, last_comment_id bytea20 DEFAULT NULL, - in_downtime boolenum NOT NULL, + in_downtime boolenum NOT NULL DEFAULT 'n', execution_time uint DEFAULT NULL, latency uint DEFAULT NULL, @@ -326,21 +318,21 @@ CREATE TABLE service ( check_interval uint NOT NULL, check_retry_interval uint NOT NULL, - active_checks_enabled boolenum NOT NULL, - passive_checks_enabled boolenum NOT NULL, - event_handler_enabled boolenum NOT NULL, - notifications_enabled boolenum 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, + flapping_enabled boolenum NOT NULL DEFAULT 'n', flapping_threshold_low float NOT NULL, flapping_threshold_high float NOT NULL, - perfdata_enabled boolenum NOT NULL, + perfdata_enabled boolenum NOT NULL DEFAULT 'n', eventcommand citext NOT NULL, eventcommand_id bytea20 DEFAULT NULL, - is_volatile boolenum NOT NULL, + is_volatile boolenum NOT NULL DEFAULT 'n', action_url_id bytea20 DEFAULT NULL, notes_url_id bytea20 DEFAULT NULL, @@ -503,7 +495,7 @@ CREATE TABLE service_state ( environment_id bytea20 NOT NULL, properties_checksum bytea20 NOT NULL, - state_type state_type 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, @@ -518,17 +510,17 @@ CREATE TABLE service_state ( check_commandline text DEFAULT NULL, - is_problem boolenum NOT NULL, - is_handled boolenum NOT NULL, - is_reachable boolenum NOT NULL, - is_flapping boolenum NOT NULL, - is_overdue boolenum NOT 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, + is_acknowledged acked NOT NULL DEFAULT 'n', acknowledgement_comment_id bytea20 DEFAULT NULL, last_comment_id bytea20 DEFAULT NULL, - in_downtime boolenum NOT NULL, + in_downtime boolenum NOT NULL DEFAULT 'n', execution_time uint DEFAULT NULL, latency uint DEFAULT NULL, @@ -612,16 +604,16 @@ CREATE TABLE icingadb_instance ( environment_id bytea20 NOT NULL, endpoint_id bytea20 DEFAULT NULL, heartbeat biguint NOT NULL, - responsible boolenum 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, - icinga2_active_service_checks_enabled boolenum NOT NULL, - icinga2_active_host_checks_enabled boolenum NOT NULL, - icinga2_event_handlers_enabled boolenum NOT NULL, - icinga2_flap_detection_enabled boolenum NOT NULL, - icinga2_performance_data_enabled boolenum 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) ); @@ -675,10 +667,10 @@ CREATE TABLE checkcommand_argument ( argument_order smallint DEFAULT NULL, description text DEFAULT NULL, argument_key_override citext DEFAULT NULL, - repeat_key boolenum NOT NULL, - required boolenum NOT 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, + skip_key boolenum NOT NULL DEFAULT 'n', CONSTRAINT pk_checkcommand_argument PRIMARY KEY (id) ); @@ -780,10 +772,10 @@ CREATE TABLE eventcommand_argument ( argument_order smallint DEFAULT NULL, description text DEFAULT NULL, argument_key_override citext DEFAULT NULL, - repeat_key boolenum NOT NULL, - required boolenum NOT 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, + skip_key boolenum NOT NULL DEFAULT 'n', CONSTRAINT pk_eventcommand_argument PRIMARY KEY (id) ); @@ -883,10 +875,10 @@ CREATE TABLE notificationcommand_argument ( argument_order smallint DEFAULT NULL, description text DEFAULT NULL, argument_key_override citext DEFAULT NULL, - repeat_key boolenum NOT NULL, - required boolenum NOT 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, + skip_key boolenum NOT NULL DEFAULT 'n', CONSTRAINT pk_notificationcommand_argument PRIMARY KEY (id) ); @@ -950,7 +942,7 @@ CREATE TABLE comment ( id bytea20 NOT NULL, environment_id bytea20 NOT NULL, - object_type checkable_type NOT NULL, + object_type checkable_type NOT NULL DEFAULT 'host', host_id bytea20 NOT NULL, service_id bytea20 DEFAULT NULL, @@ -960,10 +952,10 @@ CREATE TABLE comment ( author citext NOT NULL, text text NOT NULL, - entry_type comment_type NOT NULL, + entry_type comment_type NOT NULL DEFAULT 'comment', entry_time biguint NOT NULL, - is_persistent boolenum NOT NULL, - is_sticky boolenum 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, @@ -1003,7 +995,7 @@ CREATE TABLE downtime ( triggered_by_id bytea20 DEFAULT NULL, parent_id bytea20 DEFAULT NULL, - object_type checkable_type NOT NULL, + object_type checkable_type NOT NULL DEFAULT 'host', host_id bytea20 NOT NULL, service_id bytea20 DEFAULT NULL, @@ -1017,10 +1009,10 @@ CREATE TABLE downtime ( scheduled_start_time biguint NOT NULL, scheduled_end_time biguint NOT NULL, scheduled_duration biguint NOT NULL, - is_flexible boolenum NOT NULL, + is_flexible boolenum NOT NULL DEFAULT 'n', flexible_duration biguint NOT NULL, - is_in_effect boolenum NOT NULL, + is_in_effect boolenum NOT NULL DEFAULT 'n', start_time biguint DEFAULT NULL, end_time biguint DEFAULT NULL, duration biguint NOT NULL, @@ -1275,7 +1267,7 @@ CREATE TABLE timeperiod ( name varchar(255) NOT NULL, name_ci citext NOT NULL, display_name citext NOT NULL, - prefer_includes boolenum NOT NULL, + prefer_includes boolenum NOT NULL DEFAULT 'n', zone_id bytea20 DEFAULT NULL, @@ -1430,7 +1422,7 @@ CREATE TABLE "user" ( email varchar(255) NOT NULL, pager varchar(255) NOT NULL, - notifications_enabled boolenum NOT NULL, + notifications_enabled boolenum NOT NULL DEFAULT 'n', timeperiod_id bytea20 DEFAULT NULL, @@ -1574,7 +1566,7 @@ CREATE TABLE zone ( name varchar(255) NOT NULL, name_ci citext NOT NULL, - is_global boolenum NOT NULL, + is_global boolenum NOT NULL DEFAULT 'n', parent_id bytea20 DEFAULT NULL, depth tinyuint NOT NULL, @@ -1601,12 +1593,12 @@ CREATE TABLE notification_history ( id bytea20 NOT NULL, environment_id bytea20 NOT NULL, endpoint_id bytea20 DEFAULT NULL, - object_type checkable_type NOT 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, + type notification_type NOT NULL DEFAULT 'downtime_start', send_time biguint NOT NULL, state tinyuint NOT NULL, previous_hard_state tinyuint NOT NULL, @@ -1660,12 +1652,12 @@ CREATE TABLE state_history ( id bytea20 NOT NULL, environment_id bytea20 NOT NULL, endpoint_id bytea20 DEFAULT NULL, - object_type checkable_type NOT 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, + state_type state_type NOT NULL DEFAULT 'hard', soft_state tinyuint NOT NULL, hard_state tinyuint NOT NULL, previous_soft_state tinyuint NOT NULL, @@ -1698,7 +1690,7 @@ CREATE TABLE downtime_history ( endpoint_id bytea20 DEFAULT NULL, triggered_by_id bytea20 DEFAULT NULL, parent_id bytea20 DEFAULT NULL, - object_type checkable_type NOT NULL, + object_type checkable_type NOT NULL DEFAULT 'host', host_id bytea20 NOT NULL, service_id bytea20 DEFAULT NULL, @@ -1706,14 +1698,14 @@ CREATE TABLE downtime_history ( author citext NOT NULL, cancelled_by citext DEFAULT NULL, comment text NOT NULL, - is_flexible boolenum 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, + has_been_cancelled boolenum NOT NULL DEFAULT 'n', trigger_time biguint NOT NULL, cancel_time biguint DEFAULT NULL, @@ -1743,7 +1735,7 @@ 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, + object_type checkable_type NOT NULL DEFAULT 'host', host_id bytea20 NOT NULL, service_id bytea20 DEFAULT NULL, @@ -1751,12 +1743,12 @@ CREATE TABLE comment_history ( author citext NOT NULL, removed_by citext DEFAULT NULL, comment text NOT NULL, - entry_type comment_type NOT NULL, - is_persistent boolenum NOT NULL, - is_sticky boolenum 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, + has_been_removed boolenum NOT NULL DEFAULT 'n', CONSTRAINT pk_comment_history PRIMARY KEY (comment_id) ); @@ -1777,7 +1769,7 @@ CREATE TABLE flapping_history ( id bytea20 NOT NULL, environment_id bytea20 NOT NULL, endpoint_id bytea20 DEFAULT NULL, - object_type checkable_type NOT NULL, + object_type checkable_type NOT NULL DEFAULT 'host', host_id bytea20 NOT NULL, service_id bytea20 DEFAULT NULL, @@ -1807,7 +1799,7 @@ CREATE TABLE acknowledgement_history ( id bytea20 NOT NULL, environment_id bytea20 NOT NULL, endpoint_id bytea20 DEFAULT NULL, - object_type checkable_type NOT NULL, + object_type checkable_type NOT NULL DEFAULT 'host', host_id bytea20 NOT NULL, service_id bytea20 DEFAULT NULL, @@ -1843,7 +1835,7 @@ CREATE TABLE history ( id bytea20 NOT NULL, environment_id bytea20 NOT NULL, endpoint_id bytea20 DEFAULT NULL, - object_type checkable_type NOT 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, @@ -1853,7 +1845,7 @@ CREATE TABLE history ( flapping_history_id bytea20 DEFAULT NULL, acknowledgement_history_id bytea20 DEFAULT NULL, - event_type history_type NOT NULL, + event_type history_type NOT NULL DEFAULT 'notification', event_time biguint NOT NULL, CONSTRAINT pk_history PRIMARY KEY (id), From d898681d3c99bd9a964ec8b9dbb2c09effbfbd4e Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Fri, 4 Feb 2022 12:11:46 +0100 Subject: [PATCH 24/25] Wrap "icingadb-*sql" in constants refs #136 --- cmd/icingadb/main.go | 5 +++-- pkg/driver/driver.go | 9 ++++++--- pkg/icingadb/db.go | 13 +++++++------ 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/cmd/icingadb/main.go b/cmd/icingadb/main.go index 634de299..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" @@ -320,9 +321,9 @@ func run() int { func checkDbSchema(ctx context.Context, db *icingadb.DB) error { var expectedDbSchemaVersion uint16 switch db.DriverName() { - case "icingadb-mysql": + case driver.MySQL: expectedDbSchemaVersion = expectedMysqlSchemaVersion - case "icingadb-pgsql": + case driver.PostgreSQL: expectedDbSchemaVersion = expectedPostgresSchemaVersion } diff --git a/pkg/driver/driver.go b/pkg/driver/driver.go index eb96cf3b..f86e37f0 100644 --- a/pkg/driver/driver.go +++ b/pkg/driver/driver.go @@ -15,6 +15,9 @@ import ( "time" ) +const MySQL = "icingadb-mysql" +const PostgreSQL = "icingadb-pgsql" + var timeout = time.Minute * 5 // RetryConnector wraps driver.Connector with retry logic. @@ -78,10 +81,10 @@ func (d Driver) OpenConnector(name string) (driver.Connector, error) { // 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("icingadb-pgsql", &Driver{ctxDriver: PgSQLDriver{}, 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("icingadb-pgsql", sqlx.DOLLAR) + sqlx.BindDriver(PostgreSQL, sqlx.DOLLAR) } // ctxDriver helps ensure that we only support drivers that implement driver.Driver and driver.DriverContext. diff --git a/pkg/icingadb/db.go b/pkg/icingadb/db.go index 3a440046..500d0e39 100644 --- a/pkg/icingadb/db.go +++ b/pkg/icingadb/db.go @@ -2,13 +2,14 @@ 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" @@ -128,10 +129,10 @@ func (db *DB) BuildInsertIgnoreStmt(into interface{}) (string, int) { var clause string switch db.DriverName() { - case "icingadb-mysql": + case driver.MySQL: // MySQL treats UPDATE id = id as a no-op. clause = "ON DUPLICATE KEY UPDATE id = id" - case "icingadb-pgsql": + case driver.PostgreSQL: clause = fmt.Sprintf("ON CONFLICT ON CONSTRAINT pk_%s DO NOTHING", table) } @@ -191,10 +192,10 @@ func (db *DB) BuildUpsertStmt(subject interface{}) (stmt string, placeholders in var clause, setFormat string switch db.DriverName() { - case "icingadb-mysql": + case driver.MySQL: clause = "ON DUPLICATE KEY UPDATE" setFormat = "%[1]s = VALUES(%[1]s)" - case "icingadb-pgsql": + case driver.PostgreSQL: clause = fmt.Sprintf("ON CONFLICT ON CONSTRAINT pk_%s DO UPDATE SET", table) setFormat = "%[1]s = EXCLUDED.%[1]s" } @@ -584,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 } From 3f6b303f4d77ba07f970ba3fa5a30db70fb300db Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Wed, 23 Feb 2022 17:44:54 +0100 Subject: [PATCH 25/25] Remove null bytes from strings PostgreSQL does not allow null bytes in varchar, char and text fields. --- pkg/types/string.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) 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{}