create extension if not exists pgcrypto; create extension if not exists postgis; create extension if not exists timescaledb; drop schema if exists eventhub cascade; create schema if not exists eventhub; create table if not exists eventhub.event_source ( id integer generated always as identity primary key, tenant_key text not null, provider_key text not null, source_kind text not null, source_key text not null, source_instance_key text not null default 'default', tenant_provider_setting_key text, external_fleet_key text, created_at timestamptz not null default now(), constraint ux_event_source unique (tenant_key, provider_key, source_kind, source_key, source_instance_key) ); create table if not exists eventhub.import_run ( id uuid primary key, tenant_key text not null, event_source_id integer not null references eventhub.event_source(id), mode text not null, status text not null, refresh_master_data_first boolean not null default true, source_group_type text, source_group_entity_id text, source_group_code text, source_group_name text, import_scope_type text not null, root_source_org_entity_id text, root_source_org_code text, root_source_org_name text, include_children boolean not null default false, occurred_from timestamptz, occurred_to timestamptz, requested_event_families text[] not null default '{}', acquisition_strategy text, metadata jsonb not null default '{}'::jsonb, planned_package_count integer not null default 0, started_at timestamptz not null default now(), finished_at timestamptz, error_message text, constraint chk_import_run_occ_time_order check (occurred_from is null or occurred_to is null or occurred_from < occurred_to) ); create table if not exists eventhub.import_cursor ( id uuid primary key, tenant_key text not null, event_source_id integer not null references eventhub.event_source(id), scope_hash text not null, event_family text not null, source_kind text not null, cursor_type text not null, last_source_package_imported_at timestamptz, last_source_package_id text, last_source_row_updated_at timestamptz, last_occurred_to timestamptz, updated_at timestamptz not null default now(), constraint ux_import_cursor unique (tenant_key, event_source_id, scope_hash, event_family, source_kind, cursor_type) ); create table if not exists eventhub.data_package ( id uuid primary key, event_source_id integer not null references eventhub.event_source(id), import_run_id uuid references eventhub.import_run(id), tenant_key text not null, package_key text not null, package_type text not null, status text not null, source_group_type text, source_group_entity_id text, source_group_code text, source_group_name text, import_scope_type text, root_source_org_entity_id text, root_source_org_code text, root_source_org_name text, include_children boolean not null default false, occurred_from timestamptz, occurred_to timestamptz, event_family text, business_date date, external_package_id text, extraction_code text, extraction_source_kind text, entity_axis text, batch_no integer, chunk_from timestamptz, chunk_to timestamptz, source_package_kind text, source_package_id text, source_package_entity_id text, source_package_period_from timestamptz, source_package_period_to timestamptz, source_package_imported_at timestamptz, received_at timestamptz not null default now(), completed_at timestamptz, event_count integer not null default 0, metadata jsonb not null default '{}'::jsonb, error_message text, constraint ux_data_package_package_key unique (tenant_key, event_source_id, package_key), constraint chk_data_package_occ_time_order check (occurred_from is null or occurred_to is null or occurred_from < occurred_to), constraint chk_data_package_chunk_time_order check (chunk_from is null or chunk_to is null or chunk_from < chunk_to) ); create table if not exists eventhub.source_master_entity ( id uuid primary key, tenant_key text not null, event_source_id integer not null references eventhub.event_source(id), entity_type text not null, source_entity_id text not null, source_external_key text, display_name text, active boolean, valid_from timestamptz, valid_to timestamptz, source_updated_at timestamptz, payload jsonb not null default '{}'::jsonb, created_at timestamptz not null default now(), updated_at timestamptz not null default now(), constraint ux_source_master_entity unique (tenant_key, event_source_id, entity_type, source_entity_id), constraint chk_source_master_entity_valid_time_order check (valid_from is null or valid_to is null or valid_from <= valid_to) ); create table if not exists eventhub.source_master_relation ( id uuid primary key, tenant_key text not null, event_source_id integer not null references eventhub.event_source(id), relation_key text not null, relation_type text not null, from_entity_type text not null, from_source_entity_id text not null, to_entity_type text not null, to_source_entity_id text not null, valid_from timestamptz, valid_to timestamptz, source_updated_at timestamptz, payload jsonb not null default '{}'::jsonb, created_at timestamptz not null default now(), updated_at timestamptz not null default now(), constraint ux_source_master_relation unique (tenant_key, event_source_id, relation_key), constraint chk_source_master_relation_valid_time_order check (valid_from is null or valid_to is null or valid_from <= valid_to) ); create table if not exists eventhub.driver ( id uuid primary key, first_names text, last_name text, birth_date date, source_updated_at timestamptz, payload jsonb not null default '{}'::jsonb, created_at timestamptz not null default now(), updated_at timestamptz not null default now() ); create table if not exists eventhub.driver_card ( id uuid primary key, driver_id uuid references eventhub.driver(id), nation text not null, card_number text not null, source_updated_at timestamptz, payload jsonb not null default '{}'::jsonb, created_at timestamptz not null default now(), updated_at timestamptz not null default now() ); create table if not exists eventhub.source_driver_identity ( id uuid primary key, tenant_key text not null, event_source_id integer not null references eventhub.event_source(id), source_driver_entity_id text not null, driver_id uuid not null references eventhub.driver(id) on delete cascade, source_updated_at timestamptz, payload jsonb not null default '{}'::jsonb, created_at timestamptz not null default now(), updated_at timestamptz not null default now(), constraint ux_source_driver_identity unique (tenant_key, event_source_id, source_driver_entity_id) ); create table if not exists eventhub.source_driver_card_identity ( id uuid primary key, tenant_key text not null, event_source_id integer not null references eventhub.event_source(id), source_driver_card_entity_id text not null, driver_card_id uuid not null references eventhub.driver_card(id) on delete cascade, source_updated_at timestamptz, payload jsonb not null default '{}'::jsonb, created_at timestamptz not null default now(), updated_at timestamptz not null default now(), constraint ux_source_driver_card_identity unique (tenant_key, event_source_id, source_driver_card_entity_id) ); create table if not exists eventhub.vehicle ( id uuid primary key, vin text, created_at timestamptz not null default now(), updated_at timestamptz not null default now() ); create table if not exists eventhub.vehicle_registration ( id uuid primary key, nation text not null, registration_number text not null, source_updated_at timestamptz, payload jsonb not null default '{}'::jsonb, created_at timestamptz not null default now(), updated_at timestamptz not null default now() ); create table if not exists eventhub.source_vehicle_identity ( id uuid primary key, tenant_key text not null, event_source_id integer not null references eventhub.event_source(id), source_vehicle_entity_id text not null, vehicle_id uuid not null references eventhub.vehicle(id) on delete cascade, source_updated_at timestamptz, payload jsonb not null default '{}'::jsonb, created_at timestamptz not null default now(), updated_at timestamptz not null default now(), constraint ux_source_vehicle_identity unique (tenant_key, event_source_id, source_vehicle_entity_id) ); create table if not exists eventhub.source_vehicle_registration_identity ( id uuid primary key, tenant_key text not null, event_source_id integer not null references eventhub.event_source(id), source_registration_entity_id text not null, vehicle_registration_id uuid not null references eventhub.vehicle_registration(id) on delete cascade, source_updated_at timestamptz, payload jsonb not null default '{}'::jsonb, created_at timestamptz not null default now(), updated_at timestamptz not null default now(), constraint ux_source_vehicle_registration_identity unique (tenant_key, event_source_id, source_registration_entity_id) ); create table if not exists eventhub.vehicle_registration_assignment ( id uuid primary key, tenant_key text not null, event_source_id integer not null references eventhub.event_source(id), vehicle_registration_id uuid not null references eventhub.vehicle_registration(id) on delete cascade, vehicle_id uuid not null references eventhub.vehicle(id) on delete cascade, valid_from timestamptz, valid_to timestamptz, source_updated_at timestamptz, payload jsonb not null default '{}'::jsonb, created_at timestamptz not null default now(), updated_at timestamptz not null default now(), constraint chk_vehicle_registration_assignment_valid_time_order check (valid_from is null or valid_to is null or valid_from <= valid_to) ); create table if not exists eventhub.event ( id uuid not null, event_source_id integer not null references eventhub.event_source(id), data_package_id uuid not null references eventhub.data_package(id), external_source_event_id text not null, driver_id uuid references eventhub.driver(id), driver_card_id uuid references eventhub.driver_card(id), vehicle_id uuid references eventhub.vehicle(id), vehicle_registration_id uuid references eventhub.vehicle_registration(id), source_package_id text, source_package_entity_id uuid references eventhub.source_master_entity(id), occurred_at timestamptz not null, received_partner_at timestamptz, received_hub_at timestamptz not null default now(), event_domain text not null, event_type text not null, lifecycle text not null, odometer_m bigint, position geography(Point, 4326), payload jsonb not null default '{}'::jsonb, manual_entry boolean not null default false, source_record_key_hash text not null, event_signature_hash text, created_at timestamptz not null default now(), constraint pk_event primary key (occurred_at, id), constraint chk_event_driver_or_vehicle_ref check ( driver_id is not null or driver_card_id is not null or vehicle_id is not null or vehicle_registration_id is not null ) ); create table if not exists eventhub.event_source_record ( source_record_key_hash text primary key, event_occurred_at timestamptz not null, event_id uuid not null, created_at timestamptz not null default now() ); create table if not exists eventhub.event_detail ( event_occurred_at timestamptz not null, event_id uuid not null, detail_type text not null, attributes jsonb not null default '{}'::jsonb, created_at timestamptz not null default now(), constraint pk_event_detail primary key (event_occurred_at, event_id, detail_type) ); select create_hypertable( 'eventhub.event', 'occurred_at', chunk_time_interval => interval '7 days', if_not_exists => true ); alter table eventhub.event_source_record add constraint fk_event_source_record_event foreign key (event_occurred_at, event_id) references eventhub.event(occurred_at, id) on delete cascade deferrable initially deferred; alter table eventhub.event_detail add constraint fk_event_detail_event foreign key (event_occurred_at, event_id) references eventhub.event(occurred_at, id) on delete cascade; create index if not exists idx_data_package_source_time on eventhub.data_package(tenant_key, event_source_id, received_at desc); create index if not exists idx_data_package_scope on eventhub.data_package(tenant_key, import_scope_type, root_source_org_entity_id, occurred_from, occurred_to); create index if not exists idx_data_package_extraction on eventhub.data_package(tenant_key, event_source_id, import_run_id, event_family, extraction_source_kind, extraction_code, batch_no); create index if not exists idx_import_run_source_status on eventhub.import_run(tenant_key, event_source_id, status, started_at desc); create index if not exists idx_source_master_entity_type_key on eventhub.source_master_entity(tenant_key, event_source_id, entity_type, source_external_key) where source_external_key is not null; create index if not exists idx_source_master_entity_payload_gin on eventhub.source_master_entity using gin(payload); create index if not exists idx_source_master_relation_from on eventhub.source_master_relation(tenant_key, event_source_id, from_entity_type, from_source_entity_id, relation_type); create index if not exists idx_source_master_relation_to on eventhub.source_master_relation(tenant_key, event_source_id, to_entity_type, to_source_entity_id, relation_type); create index if not exists idx_source_master_relation_payload_gin on eventhub.source_master_relation using gin(payload); create index if not exists idx_vehicle_vin on eventhub.vehicle(vin) where vin is not null; create index if not exists idx_vehicle_registration_plate on eventhub.vehicle_registration(nation, registration_number); create index if not exists idx_vehicle_registration_assignment_registration_time on eventhub.vehicle_registration_assignment(vehicle_registration_id, valid_from desc, valid_to); create index if not exists idx_vehicle_registration_assignment_vehicle_time on eventhub.vehicle_registration_assignment(vehicle_id, valid_from desc, valid_to); create index if not exists idx_event_source_record_event on eventhub.event_source_record(event_occurred_at, event_id); create index if not exists idx_event_signature on eventhub.event(event_signature_hash) where event_signature_hash is not null; create index if not exists idx_event_source_time on eventhub.event(event_source_id, occurred_at desc); create index if not exists idx_event_package_time on eventhub.event(data_package_id, occurred_at desc); create index if not exists idx_event_source_package_id on eventhub.event(source_package_id) where source_package_id is not null; create index if not exists idx_event_domain_type_time on eventhub.event(event_domain, event_type, occurred_at desc); create index if not exists idx_driver_card_key on eventhub.driver_card(nation, card_number); create index if not exists idx_driver_card_driver on eventhub.driver_card(driver_id) where driver_id is not null; create unique index if not exists ux_driver_card_key on eventhub.driver_card(nation, card_number); create unique index if not exists ux_vehicle_vin on eventhub.vehicle(vin) where vin is not null; create unique index if not exists ux_vehicle_registration_plate on eventhub.vehicle_registration(nation, registration_number); create index if not exists idx_source_driver_identity_driver on eventhub.source_driver_identity(driver_id); create index if not exists idx_source_driver_card_identity_card on eventhub.source_driver_card_identity(driver_card_id); create index if not exists idx_source_vehicle_identity_vehicle on eventhub.source_vehicle_identity(vehicle_id); create index if not exists idx_source_vehicle_registration_identity_registration on eventhub.source_vehicle_registration_identity(vehicle_registration_id); create index if not exists idx_event_driver_time on eventhub.event(driver_id, occurred_at desc) where driver_id is not null; create index if not exists idx_event_driver_card_time on eventhub.event(driver_card_id, occurred_at desc) where driver_card_id is not null; create index if not exists idx_event_vehicle_time on eventhub.event(vehicle_id, occurred_at desc) where vehicle_id is not null; create index if not exists idx_event_vehicle_registration_time on eventhub.event(vehicle_registration_id, occurred_at desc) where vehicle_registration_id is not null; create index if not exists idx_event_position_gist on eventhub.event using gist(position) where position is not null; create index if not exists idx_event_payload_gin on eventhub.event using gin(payload); create index if not exists idx_event_detail_type on eventhub.event_detail(detail_type); create index if not exists idx_event_detail_attributes_gin on eventhub.event_detail using gin(attributes); create index if not exists idx_event_detail_yellowfox_slot on eventhub.event_detail(detail_type, (attributes ->> 'slot'), event_occurred_at) where detail_type in ('DRIVER_ACTIVITY', 'DRIVER_CARD'); create index if not exists idx_event_detail_yellowfox_eventtype_state on eventhub.event_detail( (attributes ->> 'yellowFoxEventType'), (attributes ->> 'yellowFoxState'), event_occurred_at ) where attributes ? 'yellowFoxEventType'; create index if not exists idx_event_detail_yellowfox_ignition on eventhub.event_detail(detail_type, (attributes ->> 'ignitionState'), event_occurred_at) where attributes ? 'ignitionState';